Skip to content
Switch branches/tags
Go to file
Cannot retrieve contributors at this time
* TextSubCodec.c
* Created by David Conrad on 3/21/06.
* This file is part of Perian.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#if __MACH__
#include <Carbon/Carbon.h>
#include <QuickTime/QuickTime.h>
#include <ConditionalMacros.h>
#include <Endian.h>
#include <ImageCodec.h>
#include "CommonUtils.h"
#include "PerianResourceIDs.h"
#include "SubRenderer.h"
static const int kMaxSubPacketSize = 1*1024*1024; // 1MB
// Data structures
typedef struct TextSubGlobalsRecord {
ComponentInstance self;
ComponentInstance delegateComponent;
ComponentInstance target;
OSType** wantedDestinationPixelTypeH;
ImageCodecMPDrawBandUPP drawBandUPP;
CGColorSpaceRef colorSpace;
SubRendererPtr ssa;
Boolean translateSRT;
} TextSubGlobalsRecord, *TextSubGlobals;
typedef struct {
long width;
long height;
long depth;
long dataSize;
Ptr baseAddr;
OSType pixelFormat;
} TextSubDecompressRecord;
// Setup required for ComponentDispatchHelper.c
#define IMAGECODEC_BASENAME() TextSubCodec
#define IMAGECODEC_GLOBALS() TextSubGlobals storage
#define COMPONENT_UPP_PREFIX() uppImageCodec
#define COMPONENT_DISPATCH_FILE "TextSubCodecDispatch.h"
#define GET_DELEGATE_COMPONENT() (storage->delegateComponent)
#include <CoreServices/Components.k.h>
#include <QuickTime/ImageCodec.k.h>
#pragma GCC visibility push(default)
#include <QuickTime/ComponentDispatchHelper.c>
#pragma GCC visibility pop
#define kNumPixelFormatsSupportedTextSub 2
/* -- This Image Decompressor Uses the Base Image Decompressor Component --
The base image decompressor is an Apple-supplied component
that makes it easier for developers to create new decompressors.
The base image decompressor does most of the housekeeping and
interface functions required for a QuickTime decompressor component,
including scheduling for asynchronous decompression.
// Component Open Request - Required
pascal ComponentResult TextSubCodecOpen(TextSubGlobals glob, ComponentInstance self)
ComponentResult err;
// Allocate memory for our globals, set them up and inform the component manager that we've done so
glob = (TextSubGlobals)NewPtrClear(sizeof(TextSubGlobalsRecord));
if ((err = MemError())) goto bail;
SetComponentInstanceStorage(self, (Handle)glob);
glob->self = self;
glob->target = self;
glob->wantedDestinationPixelTypeH = (OSType **)NewHandleClear((kNumPixelFormatsSupportedTextSub+1) * sizeof(OSType));
if ((err = MemError())) goto bail;
glob->drawBandUPP = NULL;
glob->ssa = NULL;
glob->colorSpace = NULL;
glob->translateSRT = true;
// Open and target an instance of the base decompressor as we delegate
// most of our calls to the base decompressor instance
err = OpenADefaultComponent(decompressorComponentType, kBaseCodecType, &glob->delegateComponent);
if (err) goto bail;
ComponentSetTarget(glob->delegateComponent, self);
return err;
// Component Close Request - Required
pascal ComponentResult TextSubCodecClose(TextSubGlobals glob, ComponentInstance self)
// Make sure to close the base component and dealocate our storage
if (glob) {
if (glob->delegateComponent) {
if (glob->wantedDestinationPixelTypeH) {
if (glob->drawBandUPP) {
if (glob->ssa) SubRendererDispose(glob->ssa);
if (glob->colorSpace) CGColorSpaceRelease(glob->colorSpace);
return noErr;
// Component Version Request - Required
pascal ComponentResult TextSubCodecVersion(TextSubGlobals glob)
#pragma unused(glob)
return kTextSubCodecVersion;
// Component Target Request
// Allows another component to "target" you i.e., you call another component whenever
// you would call yourself (as a result of your component being used by another component)
pascal ComponentResult TextSubCodecTarget(TextSubGlobals glob, ComponentInstance target)
glob->target = target;
return noErr;
// Component GetMPWorkFunction Request
// Allows your image decompressor component to perform asynchronous decompression
// in a single MP task by taking advantage of the Base Decompressor. If you implement
// this selector, your DrawBand function must be MP-safe. MP safety means not
// calling routines that may move or purge memory and not calling any routines which
// might cause 68K code to be executed.
pascal ComponentResult TextSubCodecGetMPWorkFunction(TextSubGlobals glob, ComponentMPWorkFunctionUPP *workFunction, void **refCon)
if (NULL == glob->drawBandUPP)
glob->drawBandUPP = NewImageCodecMPDrawBandUPP((ImageCodecMPDrawBandProcPtr)TextSubCodecDrawBand);
return ImageCodecGetBaseMPWorkFunction(glob->delegateComponent, workFunction, refCon, glob->drawBandUPP, glob);
#pragma mark-
// ImageCodecInitialize
// The first function call that your image decompressor component receives from the base image
// decompressor is always a call to ImageCodecInitialize . In response to this call, your image decompressor
// component returns an ImageSubCodecDecompressCapabilities structure that specifies its capabilities.
pascal ComponentResult TextSubCodecInitialize(TextSubGlobals glob, ImageSubCodecDecompressCapabilities *cap)
#pragma unused(glob)
// Secifies the size of the ImageSubCodecDecompressRecord structure
// and say we can support asyncronous decompression
// With the help of the base image decompressor, any image decompressor
// that uses only interrupt-safe calls for decompression operations can
// support asynchronous decompression.
cap->decompressRecordSize = sizeof(TextSubDecompressRecord);
cap->canAsync = true;
cap->subCodecIsMultiBufferAware = true;
cap->subCodecSupportsDecodeSmoothing = true;
return noErr;
// ImageCodecPreflight
// The base image decompressor gets additional information about the capabilities of your image
// decompressor component by calling ImageCodecPreflight. The base image decompressor uses this
// information when responding to a call to the ImageCodecPredecompress function,
// which the ICM makes before decompressing an image. You are required only to provide values for
// the wantedDestinationPixelSize and wantedDestinationPixelTypes fields and can also modify other
// fields if necessary.
pascal ComponentResult TextSubCodecPreflight(TextSubGlobals glob, CodecDecompressParams *p)
CodecCapabilities *capabilities = p->capabilities;
OSTypePtr formats = *glob->wantedDestinationPixelTypeH;
// Fill in formats for wantedDestinationPixelTypeH
// Terminate with an OSType value 0 - see IceFloe #7
// we want ARGB because Quartz can use it easily
*formats++ = k32RGBAPixelFormat;
*formats++ = k32ARGBPixelFormat;
// Specify the minimum image band height supported by the component
// bandInc specifies a common factor of supported image band heights -
// if your component supports only image bands that are an even
// multiple of some number of pixels high report this common factor in bandInc
capabilities->bandMin = (**p->imageDescription).height;
capabilities->bandInc = capabilities->bandMin;
// Indicate the wanted destination using the wantedDestinationPixelTypeH previously set up
capabilities->wantedPixelSize = 0;
p->wantedDestinationPixelTypes = glob->wantedDestinationPixelTypeH;
// Specify the number of pixels the image must be extended in width and height if
// the component cannot accommodate the image at its given width and height
capabilities->extendWidth = 0;
capabilities->extendHeight = 0;
capabilities->flags |= codecCanAsync | codecCanAsyncWhen | codecCanScale;
capabilities->flags2 |= codecDrawsHigherQualityScaled;
if (!glob->ssa) {
Ptr ssaheader = NULL;
size_t ssaheadersize = 0;
if (!glob->colorSpace)
glob->colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
if ((**p->imageDescription).cType == kSubFormatSSA) {
long count;
glob->translateSRT = false;
if (count == 1) {
Handle ssahand;
ssaheader = *ssahand;
ssaheadersize = GetHandleSize(ssahand);
glob->ssa = SubRendererCreate((**p->imageDescription).cType == kSubFormatSSA, ssaheader,
ssaheadersize, (**p->imageDescription).width,(**p->imageDescription).height);
return noErr;
// ImageCodecBeginBand
// The ImageCodecBeginBand function allows your image decompressor component to save information about
// a band before decompressing it. This function is never called at interrupt time. The base image decompressor
// preserves any changes your component makes to any of the fields in the ImageSubCodecDecompressRecord
// or CodecDecompressParams structures. If your component supports asynchronous scheduled decompression, it
// may receive more than one ImageCodecBeginBand call before receiving an ImageCodecDrawBand call.
pascal ComponentResult TextSubCodecBeginBand(TextSubGlobals glob, CodecDecompressParams *p, ImageSubCodecDecompressRecord *drp, long flags)
TextSubDecompressRecord *myDrp = (TextSubDecompressRecord *)drp->userDecompressRecord;
// Let base codec know that all our frames are key frames (a.k.a., sync samples)
// This allows the base codec to perform frame dropping on our behalf if needed
drp->frameType = kCodecFrameTypeKey;
myDrp->pixelFormat = p->dstPixMap.pixelFormat;
myDrp->width = p->dstRect.right - p->dstRect.left;
myDrp->height = p->dstRect.bottom - p->;
myDrp->depth = (**p->imageDescription).depth;
myDrp->dataSize = p->bufferSize;
return noErr;
// ImageCodecDrawBand
// The base image decompressor calls your image decompressor component's ImageCodecDrawBand function
// to decompress a band or frame. Your component must implement this function. If the ImageSubCodecDecompressRecord
// structure specifies a progress function or data-loading function, the base image decompressor will never call ImageCodecDrawBand
// at interrupt time. If the ImageSubCodecDecompressRecord structure specifies a progress function, the base image decompressor
// handles codecProgressOpen and codecProgressClose calls, and your image decompressor component must not implement these functions.
// If not, the base image decompressor may call the ImageCodecDrawBand function at interrupt time.
// When the base image decompressor calls your ImageCodecDrawBand function, your component must perform the decompression specified
// by the fields of the ImageSubCodecDecompressRecord structure. The structure includes any changes your component made to it
// when performing the ImageCodecBeginBand function. If your component supports asynchronous scheduled decompression,
// it may receive more than one ImageCodecBeginBand call before receiving an ImageCodecDrawBand call.
pascal ComponentResult TextSubCodecDrawBand(TextSubGlobals glob, ImageSubCodecDecompressRecord *drp)
TextSubDecompressRecord *myDrp = (TextSubDecompressRecord *)drp->userDecompressRecord;
CGImageAlphaInfo alphaFormat = (myDrp->pixelFormat == k32ARGBPixelFormat) ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaPremultipliedLast;
CGContextRef c = CGBitmapContextCreate(drp->baseAddr, myDrp->width, myDrp->height,
8, drp->rowBytes, glob->colorSpace,
CGContextClearRect(c, CGRectMake(0,0, myDrp->width, myDrp->height));
CFMutableStringRef buf;
if (drp->codecData[0] == '\n' && myDrp->dataSize == 1) goto leave; // skip empty packets
if (myDrp->dataSize > kMaxSubPacketSize) goto leave; // skip very large packets, they probably cause stack overflows
buf = (CFMutableStringRef)CFStringCreateWithBytesNoCopy(NULL, (UInt8*)drp->codecData, myDrp->dataSize, kCFStringEncodingUTF8, false, kCFAllocatorNull);
if (!buf) goto leave;
if (glob->translateSRT) {
CFStringRef origBuf = buf;
buf = CFStringCreateMutableCopy(NULL, 0, buf);
if (!buf) goto leave;
CFStringFindAndReplace(buf, CFSTR("<i>"), CFSTR("{\\i1}"), CFRangeMake(0,CFStringGetLength(buf)), 0);
CFStringFindAndReplace(buf, CFSTR("</i>"), CFSTR("{\\i0}"), CFRangeMake(0,CFStringGetLength(buf)), 0);
CFStringFindAndReplace(buf, CFSTR("<"), CFSTR("{"), CFRangeMake(0,CFStringGetLength(buf)), 0);
CFStringFindAndReplace(buf, CFSTR(">"), CFSTR("}"), CFRangeMake(0,CFStringGetLength(buf)), 0);
SubRendererRenderPacket(glob->ssa, c, buf, myDrp->width, myDrp->height);
if (IsTransparentSubtitleHackEnabled())
ConvertImageToQDTransparent(drp->baseAddr, myDrp->pixelFormat, drp->rowBytes, myDrp->width, myDrp->height);
return noErr;
// ImageCodecEndBand
// The ImageCodecEndBand function notifies your image decompressor component that decompression of a band has finished or
// that it was terminated by the Image Compression Manager. Your image decompressor component is not required to implement
// the ImageCodecEndBand function. The base image decompressor may call the ImageCodecEndBand function at interrupt time.
// After your image decompressor component handles an ImageCodecEndBand call, it can perform any tasks that are required
// when decompression is finished, such as disposing of data structures that are no longer needed. Because this function
// can be called at interrupt time, your component cannot use this function to dispose of data structures; this
// must occur after handling the function. The value of the result parameter should be set to noErr if the band or frame was
// drawn successfully. If it is any other value, the band or frame was not drawn.
pascal ComponentResult TextSubCodecEndBand(TextSubGlobals glob, ImageSubCodecDecompressRecord *drp, OSErr result, long flags)
#pragma unused(glob, drp,result, flags)
return noErr;
// ImageCodecGetSourceDataGammaLevel
// Returns 2.2, the gamma for sRGB.
pascal ComponentResult TextSubCodecGetSourceDataGammaLevel(TextSubGlobals glob, Fixed *sourceDataGammaLevel)
*sourceDataGammaLevel = FloatToFixed(2.2);
return noErr;
// ImageCodecGetCodecInfo
// Your component receives the ImageCodecGetCodecInfo request whenever an application calls the Image Compression Manager's GetCodecInfo function.
// Your component should return a formatted compressor information structure defining its capabilities.
// Both compressors and decompressors may receive this request.
pascal ComponentResult TextSubCodecGetCodecInfo(TextSubGlobals glob, CodecInfo *info)
OSErr err = noErr;
ComponentDescription desc;
short resid;
GetComponentInfo((Component)glob->self, &desc, 0, 0, 0);
if (desc.componentSubType == kSubFormatSSA)
resid = kSSASubCodecResourceID;
resid = kTextSubCodecResourceID;
if (info == NULL) {
err = paramErr;
} else {
CodecInfo **tempCodecInfo;
err = GetComponentResource((Component)glob->self, codecInfoResourceType, resid, (Handle *)&tempCodecInfo);
if (err == noErr) {
*info = **tempCodecInfo;
return err;