source: trunk/TextSubCodec.c @ 1325

Revision 1325, 15.7 KB checked in by astrange, 4 years ago (diff)

SSA: avoid a string copy in TextSubCodec? and skip extremely large packets

Line 
1/*
2 * TextSubCodec.c
3 * Created by David Conrad on 3/21/06.
4 *
5 * This file is part of Perian.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#if __MACH__
23    #include <Carbon/Carbon.h>
24    #include <QuickTime/QuickTime.h>
25#else
26    #include <ConditionalMacros.h>
27    #include <Endian.h>
28    #include <ImageCodec.h>
29#endif
30
31#include "CommonUtils.h"
32#include "PerianResourceIDs.h"
33#include "SubRenderer.h"
34
35static const int kMaxSubPacketSize = 1*1024*1024; // 1MB
36
37// Data structures
38typedef struct TextSubGlobalsRecord {
39        ComponentInstance               self;
40        ComponentInstance               delegateComponent;
41        ComponentInstance               target;
42        OSType**                                wantedDestinationPixelTypeH;
43        ImageCodecMPDrawBandUPP drawBandUPP;
44       
45        CGColorSpaceRef         colorSpace;
46
47        SubRendererPtr          ssa;
48        Boolean                                 translateSRT;
49} TextSubGlobalsRecord, *TextSubGlobals;
50
51typedef struct {
52        long            width;
53        long            height;
54        long            depth;
55    long        dataSize;
56   
57    Ptr             baseAddr;
58        OSType                  pixelFormat;
59} TextSubDecompressRecord;
60
61// Setup required for ComponentDispatchHelper.c
62#define IMAGECODEC_BASENAME()           TextSubCodec
63#define IMAGECODEC_GLOBALS()            TextSubGlobals storage
64
65#define CALLCOMPONENT_BASENAME()        IMAGECODEC_BASENAME()
66#define CALLCOMPONENT_GLOBALS()         IMAGECODEC_GLOBALS()
67
68#define COMPONENT_UPP_PREFIX()          uppImageCodec
69#define COMPONENT_DISPATCH_FILE         "TextSubCodecDispatch.h"
70#define COMPONENT_SELECT_PREFIX()       kImageCodec
71
72#define GET_DELEGATE_COMPONENT()        (storage->delegateComponent)
73
74#if __MACH__
75        #include <CoreServices/Components.k.h>
76        #include <QuickTime/ImageCodec.k.h>
77        #include <QuickTime/ComponentDispatchHelper.c>
78#else
79        #include <Components.k.h>
80        #include <ImageCodec.k.h>
81        #include <ComponentDispatchHelper.c>
82#endif
83
84#define kNumPixelFormatsSupportedTextSub 2
85
86/* -- This Image Decompressor Uses the Base Image Decompressor Component --
87        The base image decompressor is an Apple-supplied component
88        that makes it easier for developers to create new decompressors.
89        The base image decompressor does most of the housekeeping and
90        interface functions required for a QuickTime decompressor component,
91        including scheduling for asynchronous decompression.
92*/
93
94// Component Open Request - Required
95pascal ComponentResult TextSubCodecOpen(TextSubGlobals glob, ComponentInstance self)
96{
97        ComponentResult err;
98
99        // Allocate memory for our globals, set them up and inform the component manager that we've done so
100        glob = (TextSubGlobals)NewPtrClear(sizeof(TextSubGlobalsRecord));
101        if (err = MemError()) goto bail;
102
103        SetComponentInstanceStorage(self, (Handle)glob);
104
105        glob->self = self;
106        glob->target = self;
107        glob->wantedDestinationPixelTypeH = (OSType **)NewHandleClear((kNumPixelFormatsSupportedTextSub+1) * sizeof(OSType));
108        if (err = MemError()) goto bail;
109        glob->drawBandUPP = NULL;
110        glob->ssa = NULL;
111        glob->colorSpace = NULL;
112        glob->translateSRT = true;
113       
114        // Open and target an instance of the base decompressor as we delegate
115        // most of our calls to the base decompressor instance
116        err = OpenADefaultComponent(decompressorComponentType, kBaseCodecType, &glob->delegateComponent);
117        if (err) goto bail;
118
119        ComponentSetTarget(glob->delegateComponent, self);
120
121bail:
122        return err;
123}
124
125// Component Close Request - Required
126pascal ComponentResult TextSubCodecClose(TextSubGlobals glob, ComponentInstance self)
127{
128        // Make sure to close the base component and dealocate our storage
129        if (glob) {
130                if (glob->delegateComponent) {
131                        CloseComponent(glob->delegateComponent);
132                }
133                if (glob->wantedDestinationPixelTypeH) {
134                        DisposeHandle((Handle)glob->wantedDestinationPixelTypeH);
135                }
136                if (glob->drawBandUPP) {
137                        DisposeImageCodecMPDrawBandUPP(glob->drawBandUPP);
138                }
139                if (glob->ssa) SubRendererDispose(glob->ssa);
140                DisposePtr((Ptr)glob);
141        }
142
143        return noErr;
144}
145
146// Component Version Request - Required
147pascal ComponentResult TextSubCodecVersion(TextSubGlobals glob)
148{
149#pragma unused(glob)
150       
151    return kTextSubCodecVersion;
152}
153
154// Component Target Request
155//              Allows another component to "target" you i.e., you call another component whenever
156// you would call yourself (as a result of your component being used by another component)
157pascal ComponentResult TextSubCodecTarget(TextSubGlobals glob, ComponentInstance target)
158{
159        glob->target = target;
160        return noErr;
161}
162
163// Component GetMPWorkFunction Request
164//              Allows your image decompressor component to perform asynchronous decompression
165// in a single MP task by taking advantage of the Base Decompressor. If you implement
166// this selector, your DrawBand function must be MP-safe. MP safety means not
167// calling routines that may move or purge memory and not calling any routines which
168// might cause 68K code to be executed.
169pascal ComponentResult TextSubCodecGetMPWorkFunction(TextSubGlobals glob, ComponentMPWorkFunctionUPP *workFunction, void **refCon)
170{
171        if (NULL == glob->drawBandUPP)
172                glob->drawBandUPP = NewImageCodecMPDrawBandUPP((ImageCodecMPDrawBandProcPtr)TextSubCodecDrawBand);
173               
174        return ImageCodecGetBaseMPWorkFunction(glob->delegateComponent, workFunction, refCon, glob->drawBandUPP, glob);
175}
176
177#pragma mark-
178
179// ImageCodecInitialize
180//              The first function call that your image decompressor component receives from the base image
181// decompressor is always a call to ImageCodecInitialize . In response to this call, your image decompressor
182// component returns an ImageSubCodecDecompressCapabilities structure that specifies its capabilities.
183pascal ComponentResult TextSubCodecInitialize(TextSubGlobals glob, ImageSubCodecDecompressCapabilities *cap)
184{
185#pragma unused(glob)
186
187        // Secifies the size of the ImageSubCodecDecompressRecord structure
188        // and say we can support asyncronous decompression
189        // With the help of the base image decompressor, any image decompressor
190        // that uses only interrupt-safe calls for decompression operations can
191        // support asynchronous decompression.
192        cap->decompressRecordSize = sizeof(TextSubDecompressRecord);
193        cap->canAsync = true;
194       
195        cap->subCodecIsMultiBufferAware = true;
196        cap->subCodecSupportsDecodeSmoothing = true;
197
198        return noErr;
199}
200
201// ImageCodecPreflight
202//              The base image decompressor gets additional information about the capabilities of your image
203// decompressor component by calling ImageCodecPreflight. The base image decompressor uses this
204// information when responding to a call to the ImageCodecPredecompress function,
205// which the ICM makes before decompressing an image. You are required only to provide values for
206// the wantedDestinationPixelSize and wantedDestinationPixelTypes fields and can also modify other
207// fields if necessary.
208pascal ComponentResult TextSubCodecPreflight(TextSubGlobals glob, CodecDecompressParams *p)
209{
210        CodecCapabilities *capabilities = p->capabilities;
211        OSTypePtr         formats = *glob->wantedDestinationPixelTypeH;
212
213        // Fill in formats for wantedDestinationPixelTypeH
214        // Terminate with an OSType value 0  - see IceFloe #7
215        // http://developer.apple.com/quicktime/icefloe/dispatch007.html
216   
217    // we want ARGB because Quartz can use it easily
218    *formats++  = k32RGBAPixelFormat;
219        *formats++      = k32ARGBPixelFormat;
220       
221        // Specify the minimum image band height supported by the component
222        // bandInc specifies a common factor of supported image band heights -
223        // if your component supports only image bands that are an even
224    // multiple of some number of pixels high report this common factor in bandInc
225        capabilities->bandMin = (**p->imageDescription).height;
226        capabilities->bandInc = capabilities->bandMin;
227
228        // Indicate the wanted destination using the wantedDestinationPixelTypeH previously set up
229        capabilities->wantedPixelSize  = 0;     
230   
231        p->wantedDestinationPixelTypes = glob->wantedDestinationPixelTypeH;
232
233        // Specify the number of pixels the image must be extended in width and height if
234        // the component cannot accommodate the image at its given width and height
235        capabilities->extendWidth = 0;
236        capabilities->extendHeight = 0;
237       
238        capabilities->flags |= codecCanAsync | codecCanAsyncWhen | codecCanScale;
239        capabilities->flags2 |= codecDrawsHigherQualityScaled;   
240       
241        if (!glob->ssa) {
242                if (!glob->colorSpace)
243                        glob->colorSpace = GetSRGBColorSpace();
244               
245                if ((**p->imageDescription).cType == kSubFormatSSA) {
246                        long count;
247                        glob->translateSRT = false;
248                       
249                        CountImageDescriptionExtensionType(p->imageDescription,kSubFormatSSA,&count);
250                        if (count == 1) {
251                                Handle ssaheader;
252                                GetImageDescriptionExtension(p->imageDescription,&ssaheader,kSubFormatSSA,1);
253                               
254                                glob->ssa = SubRendererCreateWithSSA(*ssaheader, GetHandleSize(ssaheader), (**p->imageDescription).width, (**p->imageDescription).height);
255                        } 
256                } 
257               
258                if (!glob->ssa) glob->ssa = SubRendererCreateWithSRT((**p->imageDescription).width,(**p->imageDescription).height);
259        }
260       
261        return noErr;
262}
263
264// ImageCodecBeginBand
265//              The ImageCodecBeginBand function allows your image decompressor component to save information about
266// a band before decompressing it. This function is never called at interrupt time. The base image decompressor
267// preserves any changes your component makes to any of the fields in the ImageSubCodecDecompressRecord
268// or CodecDecompressParams structures. If your component supports asynchronous scheduled decompression, it
269// may receive more than one ImageCodecBeginBand call before receiving an ImageCodecDrawBand call.
270pascal ComponentResult TextSubCodecBeginBand(TextSubGlobals glob, CodecDecompressParams *p, ImageSubCodecDecompressRecord *drp, long flags)
271{
272        TextSubDecompressRecord *myDrp = (TextSubDecompressRecord *)drp->userDecompressRecord;
273       
274        // Let base codec know that all our frames are key frames (a.k.a., sync samples)
275        // This allows the base codec to perform frame dropping on our behalf if needed
276    drp->frameType = kCodecFrameTypeKey;
277
278        myDrp->pixelFormat = p->dstPixMap.pixelFormat;
279        myDrp->width = p->dstRect.right - p->dstRect.left;
280        myDrp->height = p->dstRect.bottom - p->dstRect.top;
281        myDrp->depth = (**p->imageDescription).depth;
282    myDrp->dataSize = p->bufferSize;
283       
284        return noErr;
285}
286
287// ImageCodecDrawBand
288//              The base image decompressor calls your image decompressor component's ImageCodecDrawBand function
289// to decompress a band or frame. Your component must implement this function. If the ImageSubCodecDecompressRecord
290// structure specifies a progress function or data-loading function, the base image decompressor will never call ImageCodecDrawBand
291// at interrupt time. If the ImageSubCodecDecompressRecord structure specifies a progress function, the base image decompressor
292// handles codecProgressOpen and codecProgressClose calls, and your image decompressor component must not implement these functions.
293// If not, the base image decompressor may call the ImageCodecDrawBand function at interrupt time.
294// When the base image decompressor calls your ImageCodecDrawBand function, your component must perform the decompression specified
295// by the fields of the ImageSubCodecDecompressRecord structure. The structure includes any changes your component made to it
296// when performing the ImageCodecBeginBand function. If your component supports asynchronous scheduled decompression,
297// it may receive more than one ImageCodecBeginBand call before receiving an ImageCodecDrawBand call.
298pascal ComponentResult TextSubCodecDrawBand(TextSubGlobals glob, ImageSubCodecDecompressRecord *drp)
299{
300        TextSubDecompressRecord *myDrp = (TextSubDecompressRecord *)drp->userDecompressRecord;
301        CGImageAlphaInfo alphaFormat = (myDrp->pixelFormat == k32ARGBPixelFormat) ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaPremultipliedLast;
302
303    CGContextRef c = CGBitmapContextCreate(drp->baseAddr, myDrp->width, myDrp->height,
304                                                                                   8, drp->rowBytes,  glob->colorSpace,
305                                                                                   alphaFormat);
306       
307        CGContextClearRect(c, CGRectMake(0,0, myDrp->width, myDrp->height));
308       
309        CFMutableStringRef buf;
310       
311        if (drp->codecData[0] == '\n' && myDrp->dataSize == 1) goto leave; // skip empty packets
312        if (myDrp->dataSize > kMaxSubPacketSize) goto leave; // skip very large packets, they probably cause stack overflows
313       
314        buf = (CFMutableStringRef)CFStringCreateWithBytesNoCopy(NULL, (UInt8*)drp->codecData, myDrp->dataSize, kCFStringEncodingUTF8, false, kCFAllocatorNull);
315        if (!buf) goto leave;
316       
317        if (glob->translateSRT) {
318                CFStringRef origBuf = buf;
319                buf = CFStringCreateMutableCopy(NULL, 0, buf);
320                CFRelease(origBuf);
321                if (!buf) goto leave;
322                CFStringFindAndReplace(buf, CFSTR("<i>"),  CFSTR("{\\i1}"), CFRangeMake(0,CFStringGetLength(buf)), 0);
323                CFStringFindAndReplace(buf, CFSTR("</i>"), CFSTR("{\\i0}"), CFRangeMake(0,CFStringGetLength(buf)), 0);
324                CFStringFindAndReplace(buf, CFSTR("<"),    CFSTR("{"),      CFRangeMake(0,CFStringGetLength(buf)), 0);
325                CFStringFindAndReplace(buf, CFSTR(">"),    CFSTR("}"),      CFRangeMake(0,CFStringGetLength(buf)), 0);
326        }
327       
328        SubRendererRenderPacket(glob->ssa, c, buf, myDrp->width, myDrp->height);
329       
330        if (IsTransparentSubtitleHackEnabled())
331                ConvertImageToQDTransparent(drp->baseAddr, myDrp->pixelFormat, drp->rowBytes, myDrp->width, myDrp->height);
332               
333        CFRelease(buf);
334       
335leave:
336        CGContextRelease(c);
337        return noErr;
338}
339
340// ImageCodecEndBand
341//              The ImageCodecEndBand function notifies your image decompressor component that decompression of a band has finished or
342// that it was terminated by the Image Compression Manager. Your image decompressor component is not required to implement
343// the ImageCodecEndBand function. The base image decompressor may call the ImageCodecEndBand function at interrupt time.
344// After your image decompressor component handles an ImageCodecEndBand call, it can perform any tasks that are required
345// when decompression is finished, such as disposing of data structures that are no longer needed. Because this function
346// can be called at interrupt time, your component cannot use this function to dispose of data structures; this
347// must occur after handling the function. The value of the result parameter should be set to noErr if the band or frame was
348// drawn successfully. If it is any other value, the band or frame was not drawn.
349pascal ComponentResult TextSubCodecEndBand(TextSubGlobals glob, ImageSubCodecDecompressRecord *drp, OSErr result, long flags)
350{
351#pragma unused(glob, drp,result, flags)
352       
353        return noErr;
354}
355
356// ImageCodecGetSourceDataGammaLevel
357// Returns 2.2, the gamma for sRGB.
358pascal ComponentResult TextSubCodecGetSourceDataGammaLevel(TextSubGlobals glob, Fixed *sourceDataGammaLevel)
359{
360        *sourceDataGammaLevel = FloatToFixed(2.2);
361        return noErr;
362}
363
364// ImageCodecGetCodecInfo
365//              Your component receives the ImageCodecGetCodecInfo request whenever an application calls the Image Compression Manager's GetCodecInfo function.
366// Your component should return a formatted compressor information structure defining its capabilities.
367// Both compressors and decompressors may receive this request.
368pascal ComponentResult TextSubCodecGetCodecInfo(TextSubGlobals glob, CodecInfo *info)
369{
370        OSErr err = noErr;
371        ComponentDescription desc;
372        short resid;
373       
374        GetComponentInfo((Component)glob->self, &desc, 0, 0, 0);
375       
376        if (desc.componentSubType == kSubFormatSSA)
377                resid = kSSASubCodecResourceID;
378        else
379                resid = kTextSubCodecResourceID;
380
381        if (info == NULL) {
382                err = paramErr;
383        } else {
384                CodecInfo **tempCodecInfo;
385
386                err = GetComponentResource((Component)glob->self, codecInfoResourceType, resid, (Handle *)&tempCodecInfo);
387                if (err == noErr) {
388                        *info = **tempCodecInfo;
389                        DisposeHandle((Handle)tempCodecInfo);
390                }
391        }
392
393        return err;
394}
Note: See TracBrowser for help on using the repository browser.