bool UBQuickTimeFile::createCompressionSession() { CodecType codecType = kH264CodecType; CFStringRef keys[] = {kCVPixelBufferPixelFormatTypeKey, kCVPixelBufferWidthKey, kCVPixelBufferHeightKey}; int width = mFrameSize.width(); int height = mFrameSize.height(); int pixelFormat = k32BGRAPixelFormat; CFTypeRef values[] = { (CFTypeRef)CFNumberCreate(0, kCFNumberIntType, (void*)&pixelFormat), (CFTypeRef)CFNumberCreate(0, kCFNumberIntType, (void*)&width), (CFTypeRef)CFNumberCreate(0, kCFNumberIntType, (void*)&height) }; CFDictionaryRef pixelBufferAttributes = CFDictionaryCreate(kCFAllocatorDefault , (const void **)keys, (const void **)values, 3, 0, 0); if(!pixelBufferAttributes) { setLastErrorMessage("Could not create CV buffer pool pixel buffer attributes"); return false; } OSStatus err = noErr; ICMEncodedFrameOutputRecord encodedFrameOutputRecord = {0}; ICMCompressionSessionOptionsRef sessionOptions = 0; err = ICMCompressionSessionOptionsCreate(0, &sessionOptions); if(err) { setLastErrorMessage(QString("ICMCompressionSessionOptionsCreate() failed %1").arg(err)); goto bail; } // We must set this flag to enable P or B frames. err = ICMCompressionSessionOptionsSetAllowTemporalCompression(sessionOptions, true); if(err) { setLastErrorMessage(QString("ICMCompressionSessionOptionsSetAllowTemporalCompression() failed %1").arg(err)); goto bail; } // We must set this flag to enable B frames. err = ICMCompressionSessionOptionsSetAllowFrameReordering(sessionOptions, true); if(err) { setLastErrorMessage(QString("ICMCompressionSessionOptionsSetAllowFrameReordering() failed %1").arg(err)); goto bail; } // Set the maximum key frame interval, also known as the key frame rate. err = ICMCompressionSessionOptionsSetMaxKeyFrameInterval(sessionOptions, mFramesPerSecond); if(err) { setLastErrorMessage(QString("ICMCompressionSessionOptionsSetMaxKeyFrameInterval() failed %1").arg(err)); goto bail; } // This allows the compressor more flexibility (ie, dropping and coalescing frames). err = ICMCompressionSessionOptionsSetAllowFrameTimeChanges(sessionOptions, true); if(err) { setLastErrorMessage(QString("ICMCompressionSessionOptionsSetAllowFrameTimeChanges() failed %1").arg(err)); goto bail; } // Set the average quality. err = ICMCompressionSessionOptionsSetProperty(sessionOptions, kQTPropertyClass_ICMCompressionSessionOptions, kICMCompressionSessionOptionsPropertyID_Quality, sizeof(mSpatialQuality), &mSpatialQuality); if(err) { setLastErrorMessage(QString("ICMCompressionSessionOptionsSetProperty(Quality) failed %1").arg(err)); goto bail; } //qDebug() << "available quality" << mSpatialQuality; encodedFrameOutputRecord.encodedFrameOutputCallback = addEncodedFrameToMovie; encodedFrameOutputRecord.encodedFrameOutputRefCon = this; encodedFrameOutputRecord.frameDataAllocator = 0; err = ICMCompressionSessionCreate(0, mFrameSize.width(), mFrameSize.height(), codecType, mTimeScale, sessionOptions, pixelBufferAttributes, &encodedFrameOutputRecord, &mVideoCompressionSession); if(err) { setLastErrorMessage(QString("ICMCompressionSessionCreate() failed %1").arg(err)); goto bail; } mCVPixelBufferPool = ICMCompressionSessionGetPixelBufferPool(mVideoCompressionSession); if(!mCVPixelBufferPool) { setLastErrorMessage("ICMCompressionSessionGetPixelBufferPool() failed."); err = !noErr; goto bail; } if(mRecordAudio) { mWaveRecorder = new UBAudioQueueRecorder(); if(mWaveRecorder->init(mAudioRecordingDeviceName)) { connect(mWaveRecorder, SIGNAL(newWaveBuffer(void*, long, int , const AudioStreamPacketDescription*)) , this, SLOT(appendAudioBuffer(void*, long, int, const AudioStreamPacketDescription*))); connect(mWaveRecorder, SIGNAL(audioLevelChanged(quint8)), this, SIGNAL(audioLevelChanged(quint8))); } else {
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; }
static ComponentResult _setup_cs(StreamInfoPtr si) { ComponentResult err = noErr; ICMEncodedFrameOutputRecord efor; SInt32 tmpval = 0; dbg_printf("[ vOE] >> [%08lx] :: _setup_cs()\n", (UInt32) -1); if (si->si_v.cs) { ICMCompressionSessionCompleteFrames(si->si_v.cs, true, 0, 0); ICMCompressionSessionRelease(si->si_v.cs); si->si_v.cs = NULL; } err = ICMCompressionSessionOptionsCreate(NULL, &si->si_v.cs_opts); if (err) goto bail; // We must set this flag to enable P or B frames. err = ICMCompressionSessionOptionsSetAllowTemporalCompression(si->si_v.cs_opts, true); dbg_printf("[ vOE] ?1 [%08lx] :: _setup_cs() = %ld\n", (UInt32) -1, err); if (err) goto bail; // We must set this flag to enable B frames. err = ICMCompressionSessionOptionsSetAllowFrameReordering(si->si_v.cs_opts, true); dbg_printf("[ vOE] ?2 [%08lx] :: _setup_cs() = %ld\n", (UInt32) -1, err); if (err) goto bail; // Set the maximum key frame rate. err = ICMCompressionSessionOptionsSetMaxKeyFrameInterval(si->si_v.cs_opts, si->si_v.keyrate); dbg_printf("[ vOE] ?3 [%08lx] :: _setup_cs() = %ld\n", (UInt32) -1, err); if (err) goto bail; // This allows the compressor more flexibility // (ie, dropping and coalescing frames). err = ICMCompressionSessionOptionsSetAllowFrameTimeChanges(si->si_v.cs_opts, true); dbg_printf("[ vOE] ?4 [%08lx] :: _setup_cs() = %ld\n", (UInt32) -1, err); if (err) goto bail; // We need durations when we store frames. err = ICMCompressionSessionOptionsSetDurationsNeeded(si->si_v.cs_opts, true); dbg_printf("[ vOE] ?5 [%08lx] :: _setup_cs() = %ld\n", (UInt32) -1, err); if (err) goto bail; tmpval = si->si_v.quality; err = ICMCompressionSessionOptionsSetProperty(si->si_v.cs_opts, kQTPropertyClass_ICMCompressionSessionOptions, kICMCompressionSessionOptionsPropertyID_Quality, sizeof(tmpval), &tmpval); dbg_printf("[ vOE] ?6 [%08lx] :: _setup_cs() = %ld\n", (UInt32) -1, err); if (err) goto bail; err = ICMCompressionSessionOptionsSetProperty(si->si_v.cs_opts, kQTPropertyClass_ICMCompressionSessionOptions, kICMCompressionSessionOptionsPropertyID_ExpectedFrameRate, sizeof(si->si_v.fps), &si->si_v.fps); dbg_printf("[ vOE] ?7 [%08lx] :: _setup_cs() = %ld\n", (UInt32) -1, err); if (err) goto bail; tmpval = si->si_v.bitrate; err = ICMCompressionSessionOptionsSetProperty(si->si_v.cs_opts, kQTPropertyClass_ICMCompressionSessionOptions, kICMCompressionSessionOptionsPropertyID_AverageDataRate, sizeof(tmpval), &tmpval); dbg_printf("[ vOE] ?8 [%08lx] :: _setup_cs() = %ld\n", (UInt32) -1, err); if (err) goto bail; if (si->si_v.custom != NULL) { err = ICMCompressionSessionOptionsSetProperty(si->si_v.cs_opts, kQTPropertyClass_ICMCompressionSessionOptions, kICMCompressionSessionOptionsPropertyID_CompressorSettings, sizeof(si->si_v.custom), &si->si_v.custom); dbg_printf("[ vOE] ?9 [%08lx] :: _setup_cs() = %ld\n", (UInt32) -1, err); if (err) goto bail; } if (!err) { efor.encodedFrameOutputCallback = _frame_compressed; efor.encodedFrameOutputRefCon = (void *) si; efor.frameDataAllocator = NULL; err = ICMCompressionSessionCreate(NULL, si->si_v.width >> 16, si->si_v.height >> 16, kVideoFormatXiphTheora, /* fixed for now... */ si->sourceTimeScale, si->si_v.cs_opts, NULL, &efor, &si->si_v.cs); dbg_printf("[ vOE] ?A [%08lx] :: _setup_cs() = %ld [%lx x %lx]\n", (UInt32) -1, err, si->si_v.width, si->si_v.height); }