blob: 885cedcf711b31f8f25e7d58b0245e1763f9f1fb [file] [log] [blame]
// Copyright (c) 2010 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
#include <QuickTime/QuickTime.h>
#include "WebMExportStructs.h"
#include "WebMVideoStream.h"
OSStatus EnableMultiPassWithTemporaryFile(ICMCompressionSessionOptionsRef inCompressionSessionOptions,
ICMMultiPassStorageRef *outMultiPassStorage)
{
FSRef tempDirRef;
ICMMultiPassStorageRef multiPassStorage = NULL;
OSStatus status;
*outMultiPassStorage = NULL;
// users temp directory
status = FSFindFolder(kUserDomain, kTemporaryFolderType,
kCreateFolder, &tempDirRef);
if (noErr != status) goto bail;
// create storage using a temporary file with a unique file name
status = ICMMultiPassStorageCreateWithTemporaryFile(kCFAllocatorDefault,
&tempDirRef,
NULL, 0,
&multiPassStorage);
if (noErr != status) goto bail;
// enable multi-pass by setting the compression session options
// note - the compression session options object retains the multi-pass
// storage object
status = ICMCompressionSessionOptionsSetProperty(inCompressionSessionOptions,
kQTPropertyClass_ICMCompressionSessionOptions,
kICMCompressionSessionOptionsPropertyID_MultiPassStorage,
sizeof(ICMMultiPassStorageRef),
&multiPassStorage);
bail:
if (noErr != status) {
// this api is NULL safe so we can just call it
ICMMultiPassStorageRelease(multiPassStorage);
} else {
*outMultiPassStorage = multiPassStorage;
}
return status;
}
//callback function of the decompression session
static void _frameDecompressedCallback(void *refCon, OSStatus inerr,
ICMDecompressionTrackingFlags decompressionFlags,
CVPixelBufferRef pixelBuffer, TimeValue64 displayTime,
TimeValue64 displayDuration,
ICMValidTimeFlags validTimeFlags, void *reserved,
void *sourceFrameRefCon)
{
OSStatus err;
dbg_printf("[webM] >> _frameDecompressedCallback(err=%d)\n", inerr);
if (!inerr)
{
GenericStreamPtr vs = (GenericStreamPtr) refCon;
if ((decompressionFlags & kICMDecompressionTracking_EmittingFrame) && pixelBuffer)
{
ICMCompressionFrameOptionsRef frameOptions = NULL;
OSType pf = CVPixelBufferGetPixelFormatType(pixelBuffer);
dbg_printf("[webM] _frame_decompressed() = %ld; dFlags %ld,"
" dispTime %lld, dispDur %lld, timeFlags %ld [%ld '%4.4s' (%ld x %ld)]\n",
inerr, decompressionFlags, displayTime, displayDuration, validTimeFlags,
CVPixelBufferGetDataSize(pixelBuffer), (char *) &pf,
CVPixelBufferGetWidth(pixelBuffer),
CVPixelBufferGetHeight(pixelBuffer));
// Feed the frame to the compression session.
dbg_printf("feeding the frame to the compression session\n");
err = ICMCompressionSessionEncodeFrame(vs->vid.compressionSession, pixelBuffer,
displayTime, displayDuration,
validTimeFlags, frameOptions,
NULL, NULL);
if (err !=0)
{
const char* errString = GetMacOSStatusErrorString(err);
dbg_printf("[WebM] ICMCompressionSessionEncodeFrame err = %s\n", errString);
}
}
if (decompressionFlags & kICMDecompressionTracking_ReleaseSourceData)
{
// if we were responsible for managing source data buffers,
// we should release the source buffer here,
// using sourceFramemovieGet.refCon to identify it.
}
}
dbg_printf("[webM] exit _frameDecompressedCallback err= %ld\n", err);
}
static void addint32toDictionary(CFMutableDictionaryRef dictionary, CFStringRef key, SInt32 i)
{
CFNumberRef number = CFNumberCreate(NULL, kCFNumberSInt32Type, &i);
if (!number) return;
CFDictionaryAddValue(dictionary, key, number);
CFRelease(number);
}
static void addShortToDictionary(CFMutableDictionaryRef dictionary, CFStringRef key, short i)
{
CFNumberRef number = CFNumberCreate(NULL, kCFNumberShortType, &i);
if (!number) return;
CFDictionaryAddValue(dictionary, key, number);
CFRelease(number);
}
ComponentResult openDecompressionSession(GenericStreamPtr vs)
{
ImageDescriptionHandle idh = (ImageDescriptionHandle)(vs->source.params.desc);
ComponentResult err = noErr;
CFMutableDictionaryRef pixelBufferAttributes = NULL;
ICMDecompressionTrackingCallbackRecord trackingCallBackRecord;
OSType pixelFormat = k422YpCbCr8PixelFormat;
ImageDescription *id = *idh;
//create a dictionary describingg the pixel buffer we want to get back
pixelBufferAttributes = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
addShortToDictionary(pixelBufferAttributes, kCVPixelBufferWidthKey, id->width);
addShortToDictionary(pixelBufferAttributes, kCVPixelBufferHeightKey, id->height);
addint32toDictionary(pixelBufferAttributes, kCVPixelBufferHeightKey, pixelFormat);
//call back function for the decompression session
trackingCallBackRecord.decompressionTrackingCallback = _frameDecompressedCallback;
trackingCallBackRecord.decompressionTrackingRefCon = (void *) vs; //reference to pass to the callback function
err = ICMDecompressionSessionCreate(NULL, idh, /* sessionOptions */ NULL, pixelBufferAttributes,
&trackingCallBackRecord, &vs->vid.decompressionSession);
return err;
}
static OSStatus
_frame_compressed_callback(void *efRefCon, ICMCompressionSessionRef session,
OSStatus err, ICMEncodedFrameRef ef, void *reserved)
{
dbg_printf("[webM] enter _frame_compressed_callback(err = %d)\n", err);
GenericStreamPtr vs = (GenericStreamPtr) efRefCon;
if (err)
return err;
UInt32 enc_size = ICMEncodedFrameGetDataSize(ef);
ImageDescriptionHandle imgDesc;
err = ICMEncodedFrameGetImageDescription(ef, &imgDesc);
if (!err)
{
ImageDescription *id = *imgDesc;
dbg_printf("[webM --%08lx] :: _frame_compressed() = %ld, '%4.4s'"
" %08lx %08lx [%d x %d] [%f x %f] %ld %d %d %d\n",
(UInt32) - 1, err, (char *) &id->cType,
id->temporalQuality, id->spatialQuality, id->width,
id->height, id->hRes / 65536.0, id->vRes / 65536.0,
id->dataSize, id->frameCount, id->depth, id->clutID);
dbg_printf("[webM -- %08lx] :: _frame_compressed() = %lld %ld %ld\n",
(UInt32) - 1, ICMEncodedFrameGetDecodeDuration(ef),
enc_size, ICMEncodedFrameGetBufferSize(ef));
}
dbg_printf("[webM] allocate data %d bytes\n", enc_size);
//pass appropriate flags for frame type
ICMFrameType frame_type = ICMEncodedFrameGetFrameType(ef);
UInt16 frameFlags = VIDEO_FRAME;
//NOTE: With Altref frames, the decodeTimeStamp Represents the altref time
// this is a workaround that I don't like, ideally some other sort of
// parameter could handle this.
UInt64 displayTime = ICMEncodedFrameGetDisplayTimeStamp(ef);
UInt64 timeScale = ICMEncodedFrameGetTimeScale(ef);
dbg_printf("[WebM] ICMEncodedFrameGetDisplayTimeStamp %llu ICMEncodedFrameGetTimeScale %llu\n",
displayTime, timeScale);
UInt32 decodeNum = ICMEncodedFrameGetDecodeNumber(ef);
UInt32 timeMs = displayTime * 1000/timeScale;
dbg_printf("[WebM] adding frame %d to queue %lu flags %lx\n", decodeNum, timeMs, frameFlags);
if (frame_type != kICMFrameType_Unknown)
{
if (frame_type == kICMFrameType_I)
frameFlags += KEY_FRAME;
//create a buffer with frame data
void * buf = malloc(enc_size);
memcpy(buf, ICMEncodedFrameGetDataPtr(ef), enc_size);
addFrameToQueue(&vs->frameQueue, buf, enc_size, timeMs, frameFlags, decodeNum -1);
}
else
{
//There are two frames embedded in altref frames...
UInt32 decodeTimeMs = vs->vid.lastTimeMs + (timeMs - vs->vid.lastTimeMs)/2;
const unsigned char * cBuf = ICMEncodedFrameGetDataPtr(ef);
UInt32 altrefPortion= *((UInt32*)cBuf);
dbg_printf("[WebM]Size Of altref data in frame %lu at time %lu\n", altrefPortion, decodeTimeMs);
void * altrefBuf = malloc(altrefPortion);
memcpy(altrefBuf, &cBuf[4], altrefPortion);
frameFlags += ALT_REF_FRAME; // currently using the unknown to indicat alt-ref
addFrameToQueue(&vs->frameQueue, altrefBuf, altrefPortion, decodeTimeMs, frameFlags, decodeNum -1);
//also write the following interframe
UInt32 framePortion = enc_size - altrefPortion -4;
dbg_printf("[WebM]Size Of inter frame %lu at time %lu\n", framePortion, timeMs);
frameFlags = VIDEO_FRAME;
void * frameBuf = malloc(altrefPortion);
memcpy(frameBuf, &cBuf[altrefPortion+4], altrefPortion);
addFrameToQueue(&vs->frameQueue, frameBuf, framePortion, timeMs, frameFlags, decodeNum -1);
}
vs->vid.lastTimeMs = timeMs;
dbg_printf("[webM] exit _frame_compressed_callback(err = %d)\n", err);
return err;
}
//Using the componentInstance, load settings and then pass them to the compression session
//this in turn sends all these parameters to the VP8 Component
static ComponentResult setCompressionSettings(WebMExportGlobalsPtr glob, ICMCompressionSessionOptionsRef options)
{
ComponentInstance videoCI = NULL;
ComponentResult err = getVideoComponentInstace(glob, &videoCI);
if(err) goto bail;
// --- Hardcoded settings ---
//Allow P Frames
err = ICMCompressionSessionOptionsSetAllowTemporalCompression(options, true);
if (err) goto bail;
// Disable B frames.
err = ICMCompressionSessionOptionsSetAllowFrameReordering(options, false);
if (err) goto bail;
// --- Transfer spatial settings ----
SCSpatialSettings ss;
err = SCGetInfo(videoCI, scSpatialSettingsType, &ss);
if (err) goto bail;
dbg_printf("[WebM]Spatial settings - depth %d Quality %lx\n", ss.depth, ss.spatialQuality);
// ------ Transfer Temporal Settings --------
SCTemporalSettings ts;
err = SCGetInfo(videoCI, scTemporalSettingsType, &ts);
if (err) goto bail;
dbg_printf("[WebM] Temporal Settings max keyframerate %ld quality %ld frameRate %f\n",
ts.keyFrameRate, ts.temporalQuality,FixedToFloat(ts.frameRate));
SInt32 keyFrameRate = ts.keyFrameRate;
if (keyFrameRate != 0)
{
err = ICMCompressionSessionOptionsSetMaxKeyFrameInterval(options,
keyFrameRate);
if (err) goto bail;
}
err = ICMCompressionSessionOptionsSetProperty(options,
kQTPropertyClass_ICMCompressionSessionOptions,
kICMCompressionSessionOptionsPropertyID_Quality,
sizeof(CodecQ), &ts.temporalQuality);
if (err) goto bail;
err = ICMCompressionSessionOptionsSetProperty(options,
kQTPropertyClass_ICMCompressionSessionOptions,
kICMCompressionSessionOptionsPropertyID_ExpectedFrameRate,
sizeof(Fixed), &ts.frameRate);
if (err) goto bail;
// ------ Transfer Datarate Settings ----
SCDataRateSettings ds;
err = SCGetInfo(videoCI, scDataRateSettingsType, &ds);
dbg_printf("[webm] DataRateSettings %ld frameDuration %ld, spatial Quality %d, temporal Quality %d\n",
ds.dataRate, ds.frameDuration, ds.minSpatialQuality, ds.minTemporalQuality);
if (err) goto bail;
if (ds.dataRate != 0)
{
err = ICMCompressionSessionOptionsSetProperty(options,
kQTPropertyClass_ICMCompressionSessionOptions,
kICMCompressionSessionOptionsPropertyID_AverageDataRate,
sizeof(SInt32), &ds.dataRate);
if (err) goto bail;
}
// ------ Transfer Custom Settings ----
if (glob->videoSettingsCustom == NULL)
{
glob->videoSettingsCustom = NewHandleClear(0);
SetHandleSize(glob->videoSettingsCustom, 0);
}
err = SCGetInfo(videoCI, scCodecSettingsType, &glob->videoSettingsCustom);
if (glob->videoSettingsCustom != NULL)
{
err = ICMCompressionSessionOptionsSetProperty(options,
kQTPropertyClass_ICMCompressionSessionOptions,
kICMCompressionSessionOptionsPropertyID_CompressorSettings,
sizeof(glob->videoSettingsCustom),
&glob->videoSettingsCustom);
}
QTUnlockContainer(glob->videoSettingsAtom);
bail:
if (videoCI != NULL)
CloseComponent(videoCI);
if (err)
dbg_printf("[webm] Error in setCompressionSettingFromAC %d\n", err);
return err;
}
ComponentResult openCompressionSession(WebMExportGlobalsPtr globals, GenericStreamPtr vs)
{
ComponentResult err = noErr;
ICMCompressionSessionOptionsRef options;
ICMEncodedFrameOutputRecord efor;
ImageDescription *id = *(ImageDescriptionHandle) vs->source.params.desc;
int width = id->width;
int height = id->height;
StreamSource *source = &vs->source;
err = ICMCompressionSessionOptionsCreate(NULL, &options);
if (err) goto bail;
ICMCompressionSessionOptionsSetDurationsNeeded(options, true);
setCompressionSettings(globals, options);
efor.encodedFrameOutputCallback = _frame_compressed_callback;
efor.encodedFrameOutputRefCon = (void *) vs;
efor.frameDataAllocator = NULL;
//Even though VP8 doesn't use this, I am enabling a temporary storage file
// This is so quick time allows the two pass
if (vs->vid.bTwoPass)
{
ICMMultiPassStorageRef multiPassStorage = NULL;
OSStatus status = EnableMultiPassWithTemporaryFile(options, &multiPassStorage);
if (noErr != status) goto bail;
ICMMultiPassStorageRelease(multiPassStorage);
}
dbg_printf("[webM] openCompressionSession timeScale = %ld (%d x %d)\n",
vs->source.timeScale, width, height);
err = ICMCompressionSessionCreate(NULL, width,
height,
'VP80', /* fixed for now... */
vs->source.timeScale, options,
NULL, &efor, &vs->vid.compressionSession);
dbg_printf("[webM] created compression Session %d\n", err);
if(err) goto bail;
//After initializing the compression session, set passes if there are two passes
if (vs->vid.bTwoPass)
err = startPass(vs,1);
bail:
if (err)
dbg_printf("[WebM] Open Comprlession Session error = \n", err);
return err;
}
static void initFrameTimeRecord(GenericStreamPtr vs, ICMFrameTimeRecord *frameTimeRecord)
{
memset(frameTimeRecord, 0, sizeof(ICMFrameTimeRecord));
frameTimeRecord->recordSize = sizeof(ICMFrameTimeRecord);
//si->source.time is in milliseconds whereas this value needs to be in timeScale Units
//*(TimeValue64 *) &frameTimeRecord->value = vs->currentFrame * vs->source.params.durationPerSample / si->source.timeScale;
frameTimeRecord->value = SInt64ToWide(vs->source.time);
frameTimeRecord->scale = vs->source.timeScale;
frameTimeRecord->rate = fixed1;
frameTimeRecord->duration = vs->source.params.durationPerSample;
frameTimeRecord->flags = icmFrameTimeDecodeImmediately;
frameTimeRecord->frameNumber = vs->framesIn;
dbg_printf("[webM] init Frame Time %lld scale = %ld\n", frameTimeRecord->value, frameTimeRecord->scale);
}
//buffer is null when there are no more frames
ComponentResult compressNextFrame(WebMExportGlobalsPtr globals, GenericStreamPtr vs)
{
ComponentResult err = noErr;
ICMFrameTimeRecord frameTimeRecord;
initMovieGetParams(&vs->source);
err = InvokeMovieExportGetDataUPP(vs->source.refCon, &vs->source.params,
vs->source.dataProc);
if (err == eofErr)
{
vs->source.eos = true;
err= noErr;
}
else
{
vs->framesIn += 1;
}
if (err != noErr)
return err;
dbg_printDataParams(&vs->source);
initFrameTimeRecord(vs, &frameTimeRecord);
if (vs->vid.decompressionSession == NULL)
{
err = openDecompressionSession(vs);
dbg_printf("[webM] open new decompression session %d\n", err);
if (err != noErr) return err;
}
if (vs->vid.compressionSession == NULL)
{
err = openCompressionSession(globals, vs);
dbg_printf("[webM] open new compression session %d\n", err);
if (err != noErr) return err;
}
//the callback function also compresses, the compressed data is then kept in vs->outBuf.data
dbg_printf("[webm] Call ICMDecompressionSessionDecodeFrame\n");
if (!vs->source.eos)
{
dbg_printf("[WebM] Setting frame time to %lld\n",frameTimeRecord.value, frameTimeRecord.scale);
err = ICMDecompressionSessionDecodeFrame(vs->vid.decompressionSession,
(UInt8 *) vs->source.params.dataPtr,
vs->source.params.dataSize,
NULL, //session options
&frameTimeRecord, vs);
}
else
{
dbg_printf("Completing Frames\n");
ICMCompressionSessionCompleteFrames(vs->vid.compressionSession,
true, //complete all frames
0, //ignored when complete all frames true
0); //also ignored
vs->complete = true;
}
//increment next source time
double framerate = globals->framerate;
if (framerate == 0)
{
globals->framerate = (1.0 * vs->source.params.sourceTimeScale) / (1.0 * vs->source.params.durationPerSample );
dbg_printf("[webm] Recomputing framerate %ld / %ld\n",
vs->source.params.durationPerSample, vs->source.params.sourceTimeScale);
framerate = globals->framerate;
}
vs->source.time = (SInt32)(vs->framesIn / globals->framerate * vs->source.timeScale) ;
dbg_printf("exit compressFrame err = %d\n", err);
return err;
}
ComponentResult initVideoStream(GenericStreamPtr vs)
{
memset(vs, 0, sizeof(GenericStreamPtr));
}
ComponentResult startPass(GenericStreamPtr vs,int pass)
{
ComponentResult err = noErr;
ICMCompressionPassModeFlags cpFlag =0;
dbg_printf("[WEBM] Start pass %d session %ld\n", pass, (UInt32) vs->vid.compressionSession);
if (pass == 1)
{
ICMCompressionPassModeFlags readFlags;
Boolean bSupports = ICMCompressionSessionSupportsMultiPassEncoding(vs->vid.compressionSession,
0, &readFlags);
dbg_printf("[WebM] Supports multipass %d, flags %u\n", bSupports, readFlags);
cpFlag = kICMCompressionPassMode_WriteToMultiPassStorage;
}
else if (pass == 2)
cpFlag = kICMCompressionPassMode_OutputEncodedFrames | kICMCompressionPassMode_ReadFromMultiPassStorage;
else
return paramErr;
OSStatus os= ICMCompressionSessionBeginPass(vs->vid.compressionSession, cpFlag, 0);
if (os)
{
dbg_printf("[WebM] Error on ICMCompressionSessionBeginPass %d\n", os);
err = os; //TODO choose an apropriate error message
}
return err;
}
OSStatus endPass(GenericStreamPtr vs)
{
dbg_printf("[WEBM] End pass session %ld\n", (UInt32) vs->vid.compressionSession);
OSStatus os = ICMCompressionSessionEndPass(vs->vid.compressionSession);
if (os != 0)
dbg_printf("[WEBM] Error: endPass got error %d\n", os);
return os;
}