// Closes the instance of the component. // Release all storage associated with this instance. // Note that if a component's Open function fails with an error, its Close function will still be called. ComponentResult VP8_Encoder_Close( VP8EncoderGlobals glob, ComponentInstance self) { dbg_printf("[vp8e - %08lx] Close Called\n", (UInt32)glob); if (glob) { if (glob->stats.buf != NULL) { free(glob->stats.buf); glob->stats.buf =NULL; glob->stats.sz=0; } if (glob->codec) //see if i've initialized the vpx_codec { if (vpx_codec_destroy(glob->codec)) dbg_printf("[vp8e - %08lx] Failed to destroy codec\n", (UInt32)glob); free(glob->codec); } ICMCompressionSessionOptionsRelease(glob->sessionOptions); glob->sessionOptions = NULL; if (glob->raw) { vpx_img_free(glob->raw); free(glob->raw); } if (glob->sourceQueue.queue != NULL) free(glob->sourceQueue.queue); free(glob); } return noErr; }
int qCreateEncoderAPI(QEncoder **eptr, char* encoderArgs, int semaIndex, int width, int height) { QEncoder* encoder; OSStatus err; QVideoArgs* args = (QVideoArgs*)encoderArgs; //XXXX: we should be able to configure this SInt32 averageDataRate = 100000; ICMEncodedFrameOutputRecord encodedFrameOutputRecord = {0}; ICMCompressionSessionOptionsRef sessionOptions = NULL; CFMutableDictionaryRef pixelBufferAttributes = NULL; CFNumberRef number = NULL; OSType pixelFormat = Q_PIXEL_FORMAT; fprintf(QSTDERR, "\nqCreateEncoderQT(): ABOUT TO TRY TO CREATE ENCODER"); fprintf(QSTDERR, "\n\t time-scale: %d", args->timeScale); fprintf(QSTDERR, "\n\t big-endian: %d", (args->isBigEndian)[0]); fprintf(QSTDERR, "\n\t codec-type: %c%c%c%c", ((unsigned char*)&(args->codecType))[3], ((unsigned char*)&(args->codecType))[2], ((unsigned char*)&(args->codecType))[1], ((unsigned char*)&(args->codecType))[0]); encoder = (QEncoder*)malloc(sizeof(QEncoder)); if (encoder == NULL) { fprintf(QSTDERR, "\nqCreateDecoderQT: failed to malloc encoder struct"); return -2; } encoder->semaIndex = semaIndex; encoder->timeScale = args->timeScale; encoder->codecType = *((CodecType*)(args->codecType)); encoder->width = width; encoder->height = height; err = ICMCompressionSessionOptionsCreate( NULL, &sessionOptions ); if(err != noErr) { fprintf(QSTDERR, "\nqCreateEncoderQT(): could not create session options"); fprintf(QSTDERR, "\n\tQUICKTIME ERROR CODE: %d", err); free(encoder); return -5; } // Create and configure the compressor component. OSType codecManufacturer = 'appl'; ComponentDescription componentDescription; componentDescription.componentType = FOUR_CHAR_CODE('imco'); componentDescription.componentSubType = encoder->codecType; componentDescription.componentManufacturer = codecManufacturer; componentDescription.componentFlags = 0; componentDescription.componentFlagsMask = 0; Component compressorComponent = FindNextComponent(0, &componentDescription); if(compressorComponent == NULL) { fprintf(QSTDERR, "\nqCreateEncoderQT(): could not find a matching compressor"); ICMCompressionSessionOptionsRelease(sessionOptions); free(encoder); return -5; } err = OpenAComponent(compressorComponent, &(encoder->compressor)); if(err != noErr) { fprintf(QSTDERR, "\nqCreateEncoderQT(): failed to open compressor component"); fprintf(QSTDERR, "\n\tQUICKTIME ERROR CODE: %d", err); ICMCompressionSessionOptionsRelease(sessionOptions); free(encoder); return -5; } // If we want to use H.264, we need to muck around a bit. // XXXX: "clean up" this code. if(encoder->codecType == FOUR_CHAR_CODE('avc1')) { // Profile is currently fixed to Baseline // The level is adjusted by the use of the // bitrate, but the SPS returned reveals // level 1.1 in case of QCIF and level 1.3 // in case of CIF Handle h264Settings = NewHandleClear(0); err = ImageCodecGetSettings(encoder->compressor, h264Settings); if(err != noErr) { fprintf(QSTDERR, "\nqCreateEncoderQT(): failed to get codec settings"); fprintf(QSTDERR, "\n\tQUICKTIME ERROR CODE: %d", err); ICMCompressionSessionOptionsRelease(sessionOptions); CloseComponent(encoder->compressor); free(encoder); return -5; } // For some reason, the QTAtomContainer functions will crash if used on the atom // container returned by ImageCodecGetSettings. // Therefore, we have to parse the atoms self to set the correct settings. unsigned i; unsigned settingsSize = GetHandleSize(h264Settings) / 4; UInt32 *data = (UInt32 *)*h264Settings; for(i = 0; i < settingsSize; i++) { // Forcing Baseline profile #if defined(__BIG_ENDIAN__) if(data[i] == FOUR_CHAR_CODE('sprf')) { i+=4; data[i] = 1; } #else if(data[i] == FOUR_CHAR_CODE('frps')) { i+=4; // data[i] = CFSwapInt32(1); data[i] = 16777216; // avoid CFSwapInt32; } #endif // if video sent is CIF size, we set this flag to one to have the picture // encoded in 5 slices instead of two. // If QCIF is sent, this flag remains zero to send two slices instead of // one. #if defined(__BIG_ENDIAN__) else if(/*videoSize == XMVideoSize_CIF &&*/ data[i] == FOUR_CHAR_CODE('susg')) { i+=4; data[i] = 1; } #else else if(/*videoSize == XMVideoSize_CIF &&*/ data[i] == FOUR_CHAR_CODE('gsus')) { i+=4; // data[i] = CFSwapInt32(1); data[i] = 16777216; // avoid CFSwapInt32; } #endif } err = ImageCodecSetSettings(encoder->compressor, h264Settings); if(err != noErr) { fprintf(QSTDERR, "\nqCreateEncoderQT(): failed to set codec settings"); fprintf(QSTDERR, "\n\tQUICKTIME ERROR CODE: %d", err); ICMCompressionSessionOptionsRelease(sessionOptions); CloseComponent(encoder->compressor); free(encoder); return -5; } } err = ICMCompressionSessionOptionsSetProperty(sessionOptions, kQTPropertyClass_ICMCompressionSessionOptions, kICMCompressionSessionOptionsPropertyID_CompressorComponent, sizeof(encoder->compressor), &(encoder->compressor)); // XXXX: allow (some of) the following options to be set from the 'encoderArgs' // We must set this flag to enable P or B frames. err = ICMCompressionSessionOptionsSetAllowTemporalCompression( sessionOptions, true ); if(err != noErr) { fprintf(QSTDERR, "\nqCreateEncoderQT(): could not enable temporal compression"); fprintf(QSTDERR, "\n\tQUICKTIME ERROR CODE: %d", err); ICMCompressionSessionOptionsRelease(sessionOptions); CloseComponent(encoder->compressor); free(encoder); return -5; } // We must set this flag to enable B frames. // XXXX: err = ICMCompressionSessionOptionsSetAllowFrameReordering( sessionOptions, true ); err = ICMCompressionSessionOptionsSetAllowFrameReordering( sessionOptions, false ); if(err != noErr) { fprintf(QSTDERR, "\nqCreateEncoderQT(): could not enable frame reordering"); fprintf(QSTDERR, "\n\tQUICKTIME ERROR CODE: %d", err); ICMCompressionSessionOptionsRelease(sessionOptions); CloseComponent(encoder->compressor); free(encoder); return -5; } // Set the maximum key frame interval, also known as the key frame rate. // XXXX: even 5 frames might be a bit long for videoconferencing err = ICMCompressionSessionOptionsSetMaxKeyFrameInterval( sessionOptions, 3 ); if(err != noErr) { fprintf(QSTDERR, "\nqCreateEncoderQT(): could not set maximum keyframe interval"); fprintf(QSTDERR, "\n\tQUICKTIME ERROR CODE: %d", err); ICMCompressionSessionOptionsRelease(sessionOptions); CloseComponent(encoder->compressor); free(encoder); return -5; } // This allows the compressor more flexibility (ie, dropping and coalescing frames). // XXXX: does this mean that playback has to be more careful about when to actually display decoded frames? // XXXX: err = ICMCompressionSessionOptionsSetAllowFrameTimeChanges( sessionOptions, true ); err = ICMCompressionSessionOptionsSetAllowFrameTimeChanges( sessionOptions, false ); if(err != noErr) { fprintf(QSTDERR, "\nqCreateEncoderQT(): could not set enable frame time changes"); fprintf(QSTDERR, "\n\tQUICKTIME ERROR CODE: %d", err); ICMCompressionSessionOptionsRelease(sessionOptions); CloseComponent(encoder->compressor); free(encoder); return -5; } // XXXX: CaptureAndCompressIPBMovie set this to true err = ICMCompressionSessionOptionsSetDurationsNeeded( sessionOptions, false ); if(err != noErr) { fprintf(QSTDERR, "\nqCreateEncoderQT(): could not set whether frame durations are needed"); fprintf(QSTDERR, "\n\tQUICKTIME ERROR CODE: %d", err); ICMCompressionSessionOptionsRelease(sessionOptions); CloseComponent(encoder->compressor); free(encoder); return -5; } // Set the average data rate. // XXXX: another good one to parameterize // XXXX: can we change this one at runtime? err = ICMCompressionSessionOptionsSetProperty( sessionOptions, kQTPropertyClass_ICMCompressionSessionOptions, kICMCompressionSessionOptionsPropertyID_AverageDataRate, sizeof( averageDataRate ), &averageDataRate ); if(err != noErr) { fprintf(QSTDERR, "\nqCreateEncoderQT(): could not set average data rate"); fprintf(QSTDERR, "\n\tQUICKTIME ERROR CODE: %d", err); ICMCompressionSessionOptionsRelease(sessionOptions); CloseComponent(encoder->compressor); free(encoder); return -5; } encodedFrameOutputRecord.encodedFrameOutputCallback = (void*)qQuickTimeEncoderCallback; encodedFrameOutputRecord.encodedFrameOutputRefCon = encoder; encodedFrameOutputRecord.frameDataAllocator = NULL; // Specify attributes for the compression-session's pixel-buffer pool. pixelBufferAttributes = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); number = CFNumberCreate( NULL, kCFNumberIntType, &width ); CFDictionaryAddValue( pixelBufferAttributes, kCVPixelBufferWidthKey, number ); CFRelease( number ); number = CFNumberCreate( NULL, kCFNumberIntType, &height ); CFDictionaryAddValue( pixelBufferAttributes, kCVPixelBufferHeightKey, number ); CFRelease( number ); number = CFNumberCreate( NULL, kCFNumberSInt32Type, &pixelFormat ); CFDictionaryAddValue( pixelBufferAttributes, kCVPixelBufferPixelFormatTypeKey, number ); CFRelease( number ); err = ICMCompressionSessionCreate( NULL, width, height, args->codecType, args->timeScale, sessionOptions, pixelBufferAttributes, &encodedFrameOutputRecord, &(encoder->session)); CFRelease(pixelBufferAttributes); if(err != noErr) { fprintf(QSTDERR, "\nqCreateEncoderQT(): could not create compression session"); fprintf(QSTDERR, "\n\tQUICKTIME ERROR CODE: %d", err); ICMCompressionSessionOptionsRelease(sessionOptions); CloseComponent(encoder->compressor); free(encoder); return -5; } ICMCompressionSessionOptionsRelease(sessionOptions); *eptr = encoder; return 0; }
// Prepare to compress frames. // Compressor should record session and sessionOptions for use in later calls. // Compressor may modify imageDescription at this point. // Compressor may create and return pixel buffer options. ComponentResult VP8_Encoder_PrepareToCompressFrames( VP8EncoderGlobals glob, ICMCompressorSessionRef session, ICMCompressionSessionOptionsRef sessionOptions, ImageDescriptionHandle imageDescription, void *reserved, CFDictionaryRef *compressorPixelBufferAttributesOut) { dbg_printf("[vp8e] Prepare to Compress Frames\n", (UInt32)glob); ComponentResult err = noErr; CFMutableDictionaryRef compressorPixelBufferAttributes = NULL; //This format later needs to be converted OSType pixelFormatList[] = { k422YpCbCr8PixelFormat }; // also known as '2vuy' Fixed gammaLevel; int frameIndex; SInt32 widthRoundedUp, heightRoundedUp; // Record the compressor session for later calls to the ICM. // Note: this is NOT a CF type and should NOT be CFRetained or CFReleased. glob->session = session; // Retain the session options for later use. ICMCompressionSessionOptionsRelease(glob->sessionOptions); glob->sessionOptions = sessionOptions; ICMCompressionSessionOptionsRetain(glob->sessionOptions); // Modify imageDescription here if needed. // We'll set the image description gamma level to say "2.2". gammaLevel = kQTCCIR601VideoGammaLevel; err = ICMImageDescriptionSetProperty(imageDescription, kQTPropertyClass_ImageDescription, kICMImageDescriptionPropertyID_GammaLevel, sizeof(gammaLevel), &gammaLevel); if (err) goto bail; // Record the dimensions from the image description. glob->width = (*imageDescription)->width; glob->height = (*imageDescription)->height; dbg_printf("[vp8e - %08lx] Prepare to compress frame width %d height %d\n", (UInt32)glob, glob->width, glob->height); if (glob->width < 16 || glob->width % 2 || glob->height < 16 || glob->height % 2) dbg_printf("[vp8e - %08lx] Warning :: Invalid resolution: %ldx%ld", (UInt32)glob, glob->width, glob->height); if (glob->raw == NULL) glob->raw = calloc(1, sizeof(vpx_image_t)); //Right now I'm only using YV12, this is great for webm, as I control the spit component if (!vpx_img_alloc(glob->raw, IMG_FMT_YV12, glob->width, glob->height, 1)) { dbg_printf("[vp8e - %08lx] Error: Failed to allocate image %dx%d", (UInt32)glob, glob->width, glob->height); err = paramErr; goto bail; } glob->maxEncodedDataSize = glob->width * glob->height * 2; dbg_printf("[vp8e - %08lx] currently allocating %d bytes as my max encoded size\n", (UInt32)glob, glob->maxEncodedDataSize); // Create a pixel buffer attributes dictionary. err = createPixelBufferAttributesDictionary(glob->width, glob->height, pixelFormatList, sizeof(pixelFormatList) / sizeof(OSType), &compressorPixelBufferAttributes); if (err) goto bail; *compressorPixelBufferAttributesOut = compressorPixelBufferAttributes; compressorPixelBufferAttributes = NULL; /* Populate encoder configuration */ glob->res = vpx_codec_enc_config_default((&vpx_codec_vp8_cx_algo), &glob->cfg, 0); if (glob->res) { dbg_printf("[vp8e - %08lx] Failed to get config: %s\n", (UInt32)glob, vpx_codec_err_to_string(glob->res)); err = paramErr; //this may be something different .... goto bail; } glob->cfg.g_w = glob->width; glob->cfg.g_h = glob->height; dbg_printf("[vp8e - %08lx] resolution %dx%d\n", (UInt32)glob, glob->cfg.g_w, glob->cfg.g_h); bail: if (err) dbg_printf("[vp8e - %08lx] Error %d\n", (UInt32)glob, err); if (compressorPixelBufferAttributes) CFRelease(compressorPixelBufferAttributes); return err; }