Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
perian/VobSubCodec.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
594 lines (480 sloc)
17.9 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* VobSubCodec.h | |
* Created by David Conrad on 3/4/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 | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
* 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 | |
*/ | |
#include <Carbon/Carbon.h> | |
#include <QuickTime/QuickTime.h> | |
#include <zlib.h> | |
#include <libavutil/avutil.h> | |
#include <libavutil/intmath.h> | |
#include <libavutil/internal.h> | |
#include <libavutil/intreadwrite.h> | |
#include "PerianResourceIDs.h" | |
#include "Codecprintf.h" | |
#include "CommonUtils.h" | |
#include "FFmpegUtils.h" | |
// Data structures | |
typedef struct { | |
ComponentInstance self; | |
ComponentInstance delegateComponent; | |
ComponentInstance target; | |
OSType** wantedDestinationPixelTypeH; | |
ImageCodecMPDrawBandUPP drawBandUPP; | |
UInt32 palette[16]; | |
int compressed; | |
uint8_t *codecData; | |
unsigned int bufferSize; | |
AVCodec *avCodec; | |
AVCodecContext *avContext; | |
AVSubtitle subtitle; | |
} VobSubCodecGlobalsRecord, *VobSubCodecGlobals; | |
typedef struct { | |
long width; | |
long height; | |
long bufferSize; | |
char decoded; | |
} VobSubDecompressRecord; | |
typedef struct { | |
// color format is 32-bit ARGB | |
UInt32 pixelColor[16]; | |
UInt32 duration; | |
} PacketControlData; | |
// Setup required for ComponentDispatchHelper.c | |
#define IMAGECODEC_BASENAME() VobSubCodec | |
#define IMAGECODEC_GLOBALS() VobSubCodecGlobals storage | |
#define CALLCOMPONENT_BASENAME() IMAGECODEC_BASENAME() | |
#define CALLCOMPONENT_GLOBALS() IMAGECODEC_GLOBALS() | |
#define COMPONENT_UPP_PREFIX() uppImageCodec | |
#define COMPONENT_DISPATCH_FILE "VobSubCodecDispatch.h" | |
#define COMPONENT_SELECT_PREFIX() kImageCodec | |
#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 kNumPixelFormatsSupportedVobSub 1 | |
// dest must be at least as large as src | |
int ExtractVobSubPacket(UInt8 *dest, UInt8 *framedSrc, int srcSize, int *usedSrcBytes, int index); | |
static ComponentResult ReadPacketControls(UInt8 *packet, UInt32 palette[16], PacketControlData *controlDataOut); | |
ComponentResult VobSubCodecOpen(VobSubCodecGlobals glob, ComponentInstance self) | |
{ | |
ComponentResult err; | |
// Allocate memory for our globals, set them up and inform the component manager that we've done so | |
glob = (VobSubCodecGlobals)NewPtrClear(sizeof(VobSubCodecGlobalsRecord)); | |
if ((err = MemError())) goto bail; | |
SetComponentInstanceStorage(self, (Handle)glob); | |
glob->self = self; | |
glob->target = self; | |
glob->wantedDestinationPixelTypeH = (OSType **)NewHandleClear((kNumPixelFormatsSupportedVobSub+1) * sizeof(OSType)); | |
if ((err = MemError())) goto bail; | |
glob->drawBandUPP = NULL; | |
// 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); | |
bail: | |
return err; | |
} | |
ComponentResult VobSubCodecClose(VobSubCodecGlobals glob, ComponentInstance self) | |
{ | |
int i; | |
// Make sure to close the base component and dealocate our storage | |
if (glob) { | |
if (glob->delegateComponent) { | |
CloseComponent(glob->delegateComponent); | |
} | |
if (glob->wantedDestinationPixelTypeH) { | |
DisposeHandle((Handle)glob->wantedDestinationPixelTypeH); | |
} | |
if (glob->drawBandUPP) { | |
DisposeImageCodecMPDrawBandUPP(glob->drawBandUPP); | |
} | |
if (glob->codecData) { | |
av_freep(&glob->codecData); | |
} | |
if (glob->avCodec) { | |
avcodec_close(glob->avContext); | |
} | |
if (glob->avContext) { | |
av_freep(&glob->avContext); | |
} | |
if (glob->subtitle.rects) { | |
for (i = 0; i < glob->subtitle.num_rects; i++) { | |
av_freep(&glob->subtitle.rects[i]->pict.data[0]); | |
av_freep(&glob->subtitle.rects[i]->pict.data[1]); | |
av_freep(&glob->subtitle.rects[i]); | |
} | |
av_freep(&glob->subtitle.rects); | |
} | |
DisposePtr((Ptr)glob); | |
} | |
return noErr; | |
} | |
ComponentResult VobSubCodecVersion(VobSubCodecGlobals glob) | |
{ | |
return kVobSubCodecVersion; | |
} | |
ComponentResult VobSubCodecTarget(VobSubCodecGlobals glob, ComponentInstance target) | |
{ | |
glob->target = target; | |
return noErr; | |
} | |
ComponentResult VobSubCodecGetMPWorkFunction(VobSubCodecGlobals glob, ComponentMPWorkFunctionUPP *workFunction, void **refCon) | |
{ | |
if (glob->drawBandUPP == NULL) | |
glob->drawBandUPP = NewImageCodecMPDrawBandUPP((ImageCodecMPDrawBandProcPtr)VobSubCodecDrawBand); | |
return ImageCodecGetBaseMPWorkFunction(glob->delegateComponent, workFunction, refCon, glob->drawBandUPP, glob); | |
} | |
ComponentResult VobSubCodecInitialize(VobSubCodecGlobals glob, ImageSubCodecDecompressCapabilities *cap) | |
{ | |
cap->decompressRecordSize = sizeof(VobSubDecompressRecord); | |
cap->canAsync = true; | |
cap->baseCodecShouldCallDecodeBandForAllFrames = true; | |
if(cap->recordSize > offsetof(ImageSubCodecDecompressCapabilities, baseCodecShouldCallDecodeBandForAllFrames)) | |
cap->subCodecIsMultiBufferAware = true; | |
return noErr; | |
} | |
static ComponentResult SetupColorPalette(VobSubCodecGlobals glob, ImageDescriptionHandle imageDescription) { | |
OSErr err = noErr; | |
Handle descExtension = NewHandle(0); | |
err = GetImageDescriptionExtension(imageDescription, &descExtension, kVobSubIdxExtension, 1); | |
if (err) goto bail; | |
char *string = (char *) *descExtension; | |
char *palette = strnstr(string, "palette:", GetHandleSize(descExtension)); | |
if (palette != NULL) { | |
sscanf(palette, "palette: %lx, %lx, %lx, %lx, %lx, %lx, %lx, %lx, %lx, %lx, %lx, %lx, %lx, %lx, %lx, %lx", | |
&glob->palette[ 0], &glob->palette[ 1], &glob->palette[ 2], &glob->palette[ 3], | |
&glob->palette[ 4], &glob->palette[ 5], &glob->palette[ 6], &glob->palette[ 7], | |
&glob->palette[ 8], &glob->palette[ 9], &glob->palette[10], &glob->palette[11], | |
&glob->palette[12], &glob->palette[13], &glob->palette[14], &glob->palette[15]); | |
} | |
bail: | |
DisposeHandle(descExtension); | |
return err; | |
} | |
ComponentResult VobSubCodecPreflight(VobSubCodecGlobals glob, CodecDecompressParams *p) | |
{ | |
CodecCapabilities *capabilities = p->capabilities; | |
OSTypePtr formats = *glob->wantedDestinationPixelTypeH; | |
// Specify the minimum image band height supported by the component | |
// bandInc specifies a common factor of supported image band heights | |
capabilities->bandMin = (**p->imageDescription).height; | |
capabilities->bandInc = capabilities->bandMin; | |
// Indicate the wanted destination using the wantedDestinationPixelTypeH previously set up | |
capabilities->wantedPixelSize = 0; | |
// we want 4:4:4:4 ARGB | |
*formats++ = k32ARGBPixelFormat; | |
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; | |
// get the color palette info from the image description | |
SetupColorPalette(glob, p->imageDescription); | |
if (isImageDescriptionExtensionPresent(p->imageDescription, kMKVCompressionExtension)) | |
glob->compressed = 1; | |
if (!glob->avCodec) { | |
FFInitFFmpeg(); | |
glob->avCodec = avcodec_find_decoder(CODEC_ID_DVD_SUBTITLE); | |
glob->avContext = avcodec_alloc_context3(glob->avCodec); | |
if (avcodec_open2(glob->avContext, glob->avCodec, NULL)) { | |
Codecprintf(NULL, "Error opening DVD subtitle decoder\n"); | |
return codecErr; | |
} | |
} | |
return noErr; | |
} | |
ComponentResult VobSubCodecBeginBand(VobSubCodecGlobals glob, CodecDecompressParams *p, ImageSubCodecDecompressRecord *drp, long flags) | |
{ | |
VobSubDecompressRecord *myDrp = (VobSubDecompressRecord *)drp->userDecompressRecord; | |
// Let base codec know that all our frames are keyframes | |
drp->frameType = kCodecFrameTypeKey; | |
myDrp->width = (**p->imageDescription).width; | |
myDrp->height = (**p->imageDescription).height; | |
myDrp->bufferSize = p->bufferSize; | |
myDrp->decoded = p->frameTime ? (0 != (p->frameTime->flags & icmFrameAlreadyDecoded)) : false; | |
return noErr; | |
} | |
void DecompressZlib(VobSubCodecGlobals glob, uint8_t *data, long *bufferSize) | |
{ | |
ComponentResult err = noErr; | |
z_stream strm; | |
strm.zalloc = Z_NULL; | |
strm.zfree = Z_NULL; | |
strm.opaque = Z_NULL; | |
strm.avail_in = 0; | |
strm.next_in = Z_NULL; | |
err = inflateInit(&strm); | |
if (err != Z_OK) return; | |
strm.avail_in = *bufferSize; | |
strm.next_in = data; | |
// first, get the size of the decompressed data | |
strm.avail_out = 2; | |
strm.next_out = glob->codecData; | |
err = inflate(&strm, Z_SYNC_FLUSH); | |
if (err < Z_OK) goto bail; | |
if (strm.avail_out != 0) goto bail; | |
// reallocate our buffer to be big enough to store the decompressed packet | |
*bufferSize = AV_RB16(glob->codecData); | |
glob->codecData = fast_realloc_with_padding(glob->codecData, &glob->bufferSize, *bufferSize); | |
// then decompress the rest of it | |
strm.avail_out = glob->bufferSize - 2; | |
strm.next_out = glob->codecData + 2; | |
inflate(&strm, Z_SYNC_FLUSH); | |
bail: | |
inflateEnd(&strm); | |
} | |
ComponentResult VobSubCodecDecodeBand(VobSubCodecGlobals glob, ImageSubCodecDecompressRecord *drp, unsigned long flags) | |
{ | |
VobSubDecompressRecord *myDrp = (VobSubDecompressRecord *)drp->userDecompressRecord; | |
UInt8 *data = (UInt8 *) drp->codecData; | |
int ret, got_sub; | |
if(myDrp->bufferSize < 4) | |
{ | |
myDrp->decoded = true; | |
return noErr; | |
} | |
if (glob->codecData == NULL) { | |
glob->codecData = av_malloc(myDrp->bufferSize + 2); | |
glob->bufferSize = myDrp->bufferSize + 2; | |
} | |
// make sure we have enough space to store the packet | |
glob->codecData = fast_realloc_with_padding(glob->codecData, &glob->bufferSize, myDrp->bufferSize + 2); | |
if (glob->compressed) { | |
DecompressZlib(glob, data, &myDrp->bufferSize); | |
// the header of a spu PS packet starts 0x000001bd | |
// if it's raw spu data, the 1st 2 bytes are the length of the data | |
} else if (data[0] + data[1] == 0) { | |
// remove the MPEG framing | |
myDrp->bufferSize = ExtractVobSubPacket(glob->codecData, data, myDrp->bufferSize, NULL, -1); | |
} else { | |
memcpy(glob->codecData, drp->codecData, myDrp->bufferSize); | |
} | |
AVPacket pkt; | |
av_init_packet(&pkt); | |
pkt.data = glob->codecData; | |
pkt.size = myDrp->bufferSize; | |
ret = avcodec_decode_subtitle2(glob->avContext, &glob->subtitle, &got_sub, &pkt); | |
if (ret < 0 || !got_sub) { | |
Codecprintf(NULL, "Error decoding DVD subtitle %d / %ld\n", ret, myDrp->bufferSize); | |
return codecErr; | |
} | |
myDrp->decoded = true; | |
return noErr; | |
} | |
ComponentResult VobSubCodecDrawBand(VobSubCodecGlobals glob, ImageSubCodecDecompressRecord *drp) | |
{ | |
OSErr err = noErr; | |
VobSubDecompressRecord *myDrp = (VobSubDecompressRecord *)drp->userDecompressRecord; | |
PacketControlData controlData; | |
uint8_t *data = glob->codecData; | |
unsigned int i, j, x, y; | |
int usePalette = 0; | |
if(!myDrp->decoded) | |
err = VobSubCodecDecodeBand(glob, drp, 0); | |
if (err) return err; | |
// clear the buffer to pure transparent | |
memset(drp->baseAddr, 0, myDrp->height * drp->rowBytes); | |
if(myDrp->bufferSize < 4) | |
return noErr; | |
err = ReadPacketControls(data, glob->palette, &controlData); | |
if (err == noErr) | |
usePalette = true; | |
for (i = 0; i < glob->subtitle.num_rects; i++) { | |
AVSubtitleRect *rect = glob->subtitle.rects[i]; | |
uint8_t *line = (uint8_t *)drp->baseAddr + drp->rowBytes * rect->y + rect->x*4; | |
uint8_t *sub = rect->pict.data[0]; | |
unsigned int w = FFMIN(rect->w, myDrp->width - rect->x); | |
unsigned int h = FFMIN(rect->h, myDrp->height - rect->y); | |
uint32_t *palette = (uint32_t *)rect->pict.data[1]; | |
if (usePalette) { | |
for (j = 0; j < 4; j++) | |
palette[j] = EndianU32_BtoN(controlData.pixelColor[j]); | |
} | |
for (y = 0; y < h; y++) { | |
uint32_t *pixel = (uint32_t *) line; | |
for (x = 0; x < w; x++) | |
pixel[x] = palette[sub[x]]; | |
line += drp->rowBytes; | |
sub += rect->pict.linesize[0]; | |
} | |
} | |
if (IsTransparentSubtitleHackEnabled()) | |
ConvertImageToQDTransparent(drp->baseAddr, k32ARGBPixelFormat, drp->rowBytes, myDrp->width, myDrp->height); | |
return err; | |
} | |
ComponentResult VobSubCodecEndBand(VobSubCodecGlobals glob, ImageSubCodecDecompressRecord *drp, OSErr result, long flags) | |
{ | |
return noErr; | |
} | |
// Gamma curve value for VobSub. | |
// Not specified, so assume it's the same as the video on the DVD. | |
// modern PAL uses NTSC-ish gamma, so don't even bother guessing it. | |
ComponentResult VobSubCodecGetSourceDataGammaLevel(VobSubCodecGlobals glob, Fixed *sourceDataGammaLevel) | |
{ | |
*sourceDataGammaLevel = FloatToFixed(1/.45); // == ~2.2 | |
return noErr; | |
} | |
ComponentResult VobSubCodecGetCodecInfo(VobSubCodecGlobals glob, CodecInfo *info) | |
{ | |
OSErr err = noErr; | |
if (info == NULL) { | |
err = paramErr; | |
} else { | |
CodecInfo **tempCodecInfo; | |
err = GetComponentResource((Component)glob->self, codecInfoResourceType, kVobSubCodecResourceID, (Handle *)&tempCodecInfo); | |
if (err == noErr) { | |
*info = **tempCodecInfo; | |
DisposeHandle((Handle)tempCodecInfo); | |
} | |
} | |
return err; | |
} | |
int ExtractVobSubPacket(UInt8 *dest, UInt8 *framedSrc, int srcSize, int *usedSrcBytes, int index) { | |
int copiedBytes = 0; | |
UInt8 *currentPacket = framedSrc; | |
int packetSize = INT_MAX; | |
while (currentPacket - framedSrc < srcSize && copiedBytes < packetSize) { | |
// 3-byte start code: 0x00 00 01 | |
if (currentPacket[0] + currentPacket[1] != 0 || currentPacket[2] != 1) { | |
Codecprintf(NULL, "VobSub Codec: !! Unknown header: %02x %02x %02x\n", currentPacket[0], currentPacket[1], currentPacket[2]); | |
return copiedBytes; | |
} | |
int packet_length; | |
switch (currentPacket[3]) { | |
case 0xba: | |
// discard PS packets; nothing in them we're interested in | |
// here, packet_length is the additional stuffing | |
packet_length = currentPacket[13] & 0x7; | |
currentPacket += 14 + packet_length; | |
break; | |
case 0xbe: | |
case 0xbf: | |
// skip padding and navigation data | |
// (navigation shouldn't be present anyway) | |
packet_length = currentPacket[4]; | |
packet_length <<= 8; | |
packet_length += currentPacket[5]; | |
currentPacket += 6 + packet_length; | |
break; | |
case 0xbd: | |
// a private stream packet, contains subtitle data | |
packet_length = currentPacket[4]; | |
packet_length <<= 8; | |
packet_length += currentPacket[5]; | |
int header_data_length = currentPacket[8]; | |
int packetIndex = currentPacket[header_data_length + 9] & 0x1f; | |
if(index == -1) | |
index = packetIndex; | |
if(index == packetIndex) | |
{ | |
int blockSize = packet_length - 1 - (header_data_length + 3); | |
memcpy(&dest[copiedBytes], | |
// header's 9 bytes + extension, we don't want 1st byte of packet | |
¤tPacket[9 + header_data_length + 1], | |
// we don't want the 1-byte stream ID, or the header | |
blockSize); | |
copiedBytes += blockSize; | |
if(packetSize == INT_MAX) | |
{ | |
packetSize = dest[0] << 8 | dest[1]; | |
} | |
} | |
currentPacket += packet_length + 6; | |
break; | |
default: | |
// unknown packet, probably video, return for now | |
Codecprintf(NULL, "VobSubCodec - Unknown packet type %x, aborting\n", (int)currentPacket[3]); | |
return copiedBytes; | |
} // switch (currentPacket[3]) | |
} // while (currentPacket - framedSrc < srcSize) | |
if(usedSrcBytes != NULL) | |
*usedSrcBytes = currentPacket - framedSrc; | |
return copiedBytes; | |
} | |
static ComponentResult ReadPacketControls(UInt8 *packet, UInt32 palette[16], PacketControlData *controlDataOut) { | |
// to set whether the key sequences 0x03 - 0x06 have been seen | |
UInt16 controlSeqSeen = 0; | |
int i = 0; | |
Boolean loop = TRUE; | |
int controlOffset = (packet[2] << 8) + packet[3] + 4; | |
uint8_t *controlSeq = packet + controlOffset; | |
memset(controlDataOut, 0, sizeof(PacketControlData)); | |
while (loop) { | |
switch (controlSeq[i]) { | |
case 0x00: | |
// subpicture identifier, we don't care | |
i++; | |
break; | |
case 0x01: | |
// start displaying, we don't care | |
i++; | |
break; | |
case 0x03: | |
// palette info | |
controlDataOut->pixelColor[3] += palette[controlSeq[i+1] >> 4 ]; | |
controlDataOut->pixelColor[2] += palette[controlSeq[i+1] & 0xf]; | |
controlDataOut->pixelColor[1] += palette[controlSeq[i+2] >> 4 ]; | |
controlDataOut->pixelColor[0] += palette[controlSeq[i+2] & 0xf]; | |
i += 3; | |
controlSeqSeen |= 0x0f; | |
break; | |
case 0x04: | |
// alpha info | |
controlDataOut->pixelColor[3] += (controlSeq[i + 1] & 0xf0) << 20; | |
controlDataOut->pixelColor[2] += (controlSeq[i + 1] & 0x0f) << 24; | |
controlDataOut->pixelColor[1] += (controlSeq[i + 2] & 0xf0) << 20; | |
controlDataOut->pixelColor[0] += (controlSeq[i + 2] & 0x0f) << 24; | |
// double the nibble | |
controlDataOut->pixelColor[3] += (controlSeq[i + 1] & 0xf0) << 24; | |
controlDataOut->pixelColor[2] += (controlSeq[i + 1] & 0x0f) << 28; | |
controlDataOut->pixelColor[1] += (controlSeq[i + 2] & 0xf0) << 24; | |
controlDataOut->pixelColor[0] += (controlSeq[i + 2] & 0x0f) << 28; | |
i += 3; | |
controlSeqSeen |= 0xf0; | |
break; | |
case 0x05: | |
// coordinates of image, ffmpeg takes care of this | |
i += 7; | |
break; | |
case 0x06: | |
// offset of the first graphic line, and second, ffmpeg takes care of this | |
i += 5; | |
break; | |
case 0xff: | |
// end of control sequence | |
loop = FALSE; | |
break; | |
default: | |
Codecprintf(NULL, " !! Unknown control sequence 0x%02x aborting (offset %x)\n", controlSeq[i], i); | |
loop = FALSE; | |
break; | |
} | |
} | |
// force fully transparent to transparent black; needed? for graphicsModePreBlackAlpha | |
for (i = 0; i < 4; i++) { | |
if ((controlDataOut->pixelColor[i] & 0xff000000) == 0) | |
controlDataOut->pixelColor[i] = 0; | |
} | |
if (controlSeqSeen != 0xff) | |
return -1; | |
return noErr; | |
} |