/* This file is part of the HandBrake source code. Homepage: . It may be used under the terms of the GNU General Public License. */ #include "hb.h" #include "downmix.h" #include #include enum AAC_MODE { AAC_MODE_LC, AAC_MODE_HE }; int encCoreAudioInitLC( hb_work_object_t *, hb_job_t * ); int encCoreAudioInitHE( hb_work_object_t *, hb_job_t * ); int encCoreAudioInit( hb_work_object_t *, hb_job_t *, enum AAC_MODE mode ); int encCoreAudioWork( hb_work_object_t *, hb_buffer_t **, hb_buffer_t ** ); void encCoreAudioClose( hb_work_object_t * ); hb_work_object_t hb_encca_aac = { WORK_ENC_CA_AAC, "AAC encoder (Apple)", encCoreAudioInitLC, encCoreAudioWork, encCoreAudioClose }; hb_work_object_t hb_encca_haac = { WORK_ENC_CA_HAAC, "HE-AAC encoder (Apple)", encCoreAudioInitHE, encCoreAudioWork, encCoreAudioClose }; struct hb_work_private_s { hb_job_t *job; AudioConverterRef converter; uint8_t *obuf; uint8_t *buf; hb_list_t *list; unsigned long isamples, isamplesiz, omaxpacket, nchannels; uint64_t pts, ibytes; Float64 osamplerate; int layout; hb_chan_map_t *ichanmap; }; #define MP4ESDescrTag 0x03 #define MP4DecConfigDescrTag 0x04 #define MP4DecSpecificDescrTag 0x05 // based off of mov_mp4_read_descr_len from mov.c in ffmpeg's libavformat static int readDescrLen(UInt8 **buffer) { int len = 0; int count = 4; while (count--) { int c = *(*buffer)++; len = (len << 7) | (c & 0x7f); if (!(c & 0x80)) break; } return len; } // based off of mov_mp4_read_descr from mov.c in ffmpeg's libavformat static int readDescr(UInt8 **buffer, int *tag) { *tag = *(*buffer)++; return readDescrLen(buffer); } // based off of mov_read_esds from mov.c in ffmpeg's libavformat static long ReadESDSDescExt(void* descExt, UInt8 **buffer, UInt32 *size, int versionFlags) { UInt8 *esds = (UInt8 *) descExt; int tag, len; *size = 0; if (versionFlags) esds += 4; // version + flags readDescr(&esds, &tag); esds += 2; // ID if (tag == MP4ESDescrTag) esds++; // priority readDescr(&esds, &tag); if (tag == MP4DecConfigDescrTag) { esds++; // object type id esds++; // stream type esds += 3; // buffer size db esds += 4; // max bitrate esds += 4; // average bitrate len = readDescr(&esds, &tag); if (tag == MP4DecSpecificDescrTag) { *buffer = calloc(1, len + 8); if (*buffer) { memcpy(*buffer, esds, len); *size = len; } } } return noErr; } /*********************************************************************** * hb_work_encCoreAudio_init switches *********************************************************************** * **********************************************************************/ int encCoreAudioInitLC( hb_work_object_t * w, hb_job_t * job ) { return encCoreAudioInit( w, job, AAC_MODE_LC ); } int encCoreAudioInitHE( hb_work_object_t * w, hb_job_t * job ) { return encCoreAudioInit( w, job, AAC_MODE_HE ); } /*********************************************************************** * hb_work_encCoreAudio_init *********************************************************************** * **********************************************************************/ int encCoreAudioInit( hb_work_object_t * w, hb_job_t * job, enum AAC_MODE mode ) { hb_work_private_t * pv = calloc( 1, sizeof( hb_work_private_t ) ); hb_audio_t * audio = w->audio; AudioStreamBasicDescription input, output; UInt32 tmp, tmpsiz = sizeof( tmp ); OSStatus err; w->private_data = pv; pv->job = job; // pass the number of channels used into the private work data pv->nchannels = HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT( audio->config.out.mixdown ); bzero( &input, sizeof( AudioStreamBasicDescription ) ); input.mSampleRate = ( Float64 ) audio->config.out.samplerate; input.mFormatID = kAudioFormatLinearPCM; input.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian; input.mBytesPerPacket = 4 * pv->nchannels; input.mFramesPerPacket = 1; input.mBytesPerFrame = input.mBytesPerPacket * input.mFramesPerPacket; input.mChannelsPerFrame = pv->nchannels; input.mBitsPerChannel = 32; bzero( &output, sizeof( AudioStreamBasicDescription ) ); switch ( mode ) { case AAC_MODE_HE: output.mFormatID = kAudioFormatMPEG4AAC_HE; break; case AAC_MODE_LC: default: output.mFormatID = kAudioFormatMPEG4AAC; break; } output.mSampleRate = ( Float64 ) audio->config.out.samplerate; output.mChannelsPerFrame = pv->nchannels; // let CoreAudio decide the rest... // initialise encoder err = AudioConverterNew( &input, &output, &pv->converter ); if( err != noErr) { // Retry without the samplerate bzero( &output, sizeof( AudioStreamBasicDescription ) ); switch ( mode ) { case AAC_MODE_HE: output.mFormatID = kAudioFormatMPEG4AAC_HE; break; case AAC_MODE_LC: default: output.mFormatID = kAudioFormatMPEG4AAC; break; } output.mChannelsPerFrame = pv->nchannels; err = AudioConverterNew( &input, &output, &pv->converter ); if( err != noErr) { hb_log( "Error creating an AudioConverter err=%"PRId64" %"PRIu64, (int64_t)err, (uint64_t)output.mBytesPerFrame ); *job->die = 1; return 0; } } // set encoder quality to maximum tmp = kAudioConverterQuality_Max; AudioConverterSetProperty( pv->converter, kAudioConverterCodecQuality, sizeof( tmp ), &tmp ); if( audio->config.out.bitrate > 0 ) { // set encoder bitrate control mode to constrained variable tmp = kAudioCodecBitRateControlMode_VariableConstrained; AudioConverterSetProperty( pv->converter, kAudioCodecPropertyBitRateControlMode, sizeof( tmp ), &tmp ); // get available bitrates AudioValueRange *bitrates; ssize_t bitrateCounts; err = AudioConverterGetPropertyInfo( pv->converter, kAudioConverterApplicableEncodeBitRates, &tmpsiz, NULL); bitrates = malloc( tmpsiz ); err = AudioConverterGetProperty( pv->converter, kAudioConverterApplicableEncodeBitRates, &tmpsiz, bitrates); bitrateCounts = tmpsiz / sizeof( AudioValueRange ); // set bitrate tmp = audio->config.out.bitrate * 1000; if( tmp < bitrates[0].mMinimum ) tmp = bitrates[0].mMinimum; if( tmp > bitrates[bitrateCounts-1].mMinimum ) tmp = bitrates[bitrateCounts-1].mMinimum; free( bitrates ); if( tmp != audio->config.out.bitrate * 1000 ) hb_log( "encca_aac: sanitizing track %d audio bitrate %d to %"PRIu32"", audio->config.out.track, audio->config.out.bitrate, tmp/1000 ); AudioConverterSetProperty( pv->converter, kAudioConverterEncodeBitRate, sizeof( tmp ), &tmp ); } else if( audio->config.out.quality >= 0 ) { if( mode != AAC_MODE_LC ) { hb_log( "encCoreAudioInit: internal error, VBR set but not applicable" ); return 1; } // set encoder bitrate control mode to variable tmp = kAudioCodecBitRateControlMode_Variable; AudioConverterSetProperty( pv->converter, kAudioCodecPropertyBitRateControlMode, sizeof( tmp ), &tmp ); // set quality tmp = audio->config.out.quality; AudioConverterSetProperty( pv->converter, kAudioCodecPropertySoundQualityForVBR, sizeof( tmp ), &tmp ); } else { hb_log( "encCoreAudioInit: internal error, bitrate/quality not set" ); return 1; } // get real input tmpsiz = sizeof( input ); AudioConverterGetProperty( pv->converter, kAudioConverterCurrentInputStreamDescription, &tmpsiz, &input ); // get real output tmpsiz = sizeof( output ); AudioConverterGetProperty( pv->converter, kAudioConverterCurrentOutputStreamDescription, &tmpsiz, &output ); // set sizes pv->isamplesiz = input.mBytesPerPacket; pv->isamples = audio->config.out.samples_per_frame = output.mFramesPerPacket; pv->osamplerate = output.mSampleRate; // set channel map and layout (for remapping) pv->ichanmap = audio->config.in.channel_map; switch( audio->config.out.mixdown ) { case HB_AMIXDOWN_MONO: pv->layout = HB_INPUT_CH_LAYOUT_MONO; break; case HB_AMIXDOWN_STEREO: case HB_AMIXDOWN_DOLBY: case HB_AMIXDOWN_DOLBYPLII: pv->layout = HB_INPUT_CH_LAYOUT_STEREO; break; case HB_AMIXDOWN_6CH: default: pv->layout = HB_INPUT_CH_LAYOUT_3F2R | HB_INPUT_CH_LAYOUT_HAS_LFE; break; } // get maximum output size AudioConverterGetProperty( pv->converter, kAudioConverterPropertyMaximumOutputPacketSize, &tmpsiz, &tmp ); pv->omaxpacket = tmp; // get magic cookie (elementary stream descriptor) tmp = HB_CONFIG_MAX_SIZE; AudioConverterGetProperty( pv->converter, kAudioConverterCompressionMagicCookie, &tmp, w->config->extradata.bytes ); // CoreAudio returns a complete ESDS, but we only need // the DecoderSpecific info. UInt8* buffer = NULL; ReadESDSDescExt(w->config->extradata.bytes, &buffer, &tmpsiz, 0); w->config->extradata.length = tmpsiz; memmove( w->config->extradata.bytes, buffer, w->config->extradata.length ); pv->list = hb_list_init(); pv->buf = NULL; return 0; } /*********************************************************************** * Close *********************************************************************** * **********************************************************************/ void encCoreAudioClose( hb_work_object_t * w ) { hb_work_private_t * pv = w->private_data; if( pv->converter ) { AudioConverterDispose( pv->converter ); hb_list_empty( &pv->list ); free( pv->obuf ); free( pv->buf ); free( pv ); w->private_data = NULL; } } /* Called whenever necessary by AudioConverterFillComplexBuffer */ static OSStatus inInputDataProc( AudioConverterRef converter, UInt32 *npackets, AudioBufferList *buffers, AudioStreamPacketDescription** ignored, void *userdata ) { hb_work_private_t *pv = userdata; if( pv->ibytes == 0 ) { *npackets = 0; hb_log( "CoreAudio: no data to use in inInputDataProc" ); return 1; } if( pv->buf != NULL ) free( pv->buf ); uint64_t pts, pos; buffers->mBuffers[0].mDataByteSize = MIN( *npackets * pv->isamplesiz, pv->ibytes ); buffers->mBuffers[0].mData = pv->buf = calloc( 1, buffers->mBuffers[0].mDataByteSize ); if( hb_list_bytes( pv->list ) >= buffers->mBuffers[0].mDataByteSize ) { hb_list_getbytes( pv->list, buffers->mBuffers[0].mData, buffers->mBuffers[0].mDataByteSize, &pts, &pos ); } else { hb_log( "CoreAudio: Not enough data, exiting inInputDataProc" ); *npackets = 0; return 1; } if( pv->ichanmap != &hb_qt_chan_map ) { hb_layout_remap( pv->ichanmap, &hb_qt_chan_map, pv->layout, (float*)buffers->mBuffers[0].mData, buffers->mBuffers[0].mDataByteSize / pv->isamplesiz ); } *npackets = buffers->mBuffers[0].mDataByteSize / pv->isamplesiz; pv->ibytes -= buffers->mBuffers[0].mDataByteSize; return noErr; } /*********************************************************************** * Encode *********************************************************************** * **********************************************************************/ static hb_buffer_t * Encode( hb_work_object_t * w ) { hb_work_private_t * pv = w->private_data; UInt32 npackets = 1; /* check if we need more data */ if( ( pv->ibytes = hb_list_bytes( pv->list ) ) < pv->isamples * pv->isamplesiz ) return NULL; hb_buffer_t * obuf; AudioStreamPacketDescription odesc = { 0 }; AudioBufferList obuflist = { .mNumberBuffers = 1, .mBuffers = { { .mNumberChannels = pv->nchannels } }, }; obuf = hb_buffer_init( pv->omaxpacket ); obuflist.mBuffers[0].mDataByteSize = obuf->size; obuflist.mBuffers[0].mData = obuf->data; OSStatus err = AudioConverterFillComplexBuffer( pv->converter, inInputDataProc, pv, &npackets, &obuflist, &odesc ); if( err ) { hb_log( "CoreAudio: Not enough data" ); return NULL; } if( odesc.mDataByteSize == 0 || npackets == 0 ) { hb_log( "CoreAudio: 0 packets returned " ); return NULL; } obuf->size = odesc.mDataByteSize; obuf->s.start = pv->pts; pv->pts += 90000LL * pv->isamples / pv->osamplerate; obuf->s.stop = pv->pts; obuf->s.type = AUDIO_BUF; obuf->s.frametype = HB_FRAME_AUDIO; return obuf; } static hb_buffer_t *Flush( hb_work_object_t *w, hb_buffer_t *bufin ) { hb_work_private_t *pv = w->private_data; // pad whatever data we have out to four input frames. int nbytes = hb_list_bytes( pv->list ); int pad = pv->isamples * pv->isamplesiz - nbytes; if ( pad > 0 ) { hb_buffer_t *tmp = hb_buffer_init( pad ); memset( tmp->data, 0, pad ); hb_list_add( pv->list, tmp ); } hb_buffer_t *bufout = NULL, *buf = NULL; while ( hb_list_bytes( pv->list ) >= pv->isamples * pv->isamplesiz ) { hb_buffer_t *b = Encode( w ); if ( b ) { if ( bufout == NULL ) { bufout = b; } else { buf->next = b; } buf = b; } } // add the eof marker to the end of our buf chain if ( buf ) buf->next = bufin; else bufout = bufin; return bufout; } /*********************************************************************** * Work *********************************************************************** * **********************************************************************/ int encCoreAudioWork( hb_work_object_t * w, hb_buffer_t ** buf_in, hb_buffer_t ** buf_out ) { hb_work_private_t * pv = w->private_data; hb_buffer_t * buf; if( (*buf_in)->size <= 0 ) { // EOF on input. Finish encoding what we have buffered then send // it & the eof downstream. *buf_out = Flush( w, *buf_in ); *buf_in = NULL; return HB_WORK_DONE; } hb_list_add( pv->list, *buf_in ); *buf_in = NULL; *buf_out = buf = Encode( w ); while( buf ) { buf->next = Encode( w ); buf = buf->next; } return HB_WORK_OK; }