/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "SoftAACEncoder2" #include #include "SoftAACEncoder2.h" #include #include #include namespace android { template static void InitOMXParams(T *params) { params->nSize = sizeof(T); params->nVersion.s.nVersionMajor = 1; params->nVersion.s.nVersionMinor = 0; params->nVersion.s.nRevision = 0; params->nVersion.s.nStep = 0; } SoftAACEncoder2::SoftAACEncoder2( const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) : SimpleSoftOMXComponent(name, callbacks, appData, component), mAACEncoder(NULL), mNumChannels(1), mSampleRate(44100), mBitRate(0), mSBRMode(-1), mSBRRatio(0), mAACProfile(OMX_AUDIO_AACObjectLC), mSentCodecSpecificData(false), mInputSize(0), mInputFrame(NULL), mInputTimeUs(-1ll), mSawInputEOS(false), mSignalledError(false) { initPorts(); CHECK_EQ(initEncoder(), (status_t)OK); setAudioParams(); } SoftAACEncoder2::~SoftAACEncoder2() { aacEncClose(&mAACEncoder); delete[] mInputFrame; mInputFrame = NULL; } void SoftAACEncoder2::initPorts() { OMX_PARAM_PORTDEFINITIONTYPE def; InitOMXParams(&def); def.nPortIndex = 0; def.eDir = OMX_DirInput; def.nBufferCountMin = kNumBuffers; def.nBufferCountActual = def.nBufferCountMin; def.nBufferSize = kNumSamplesPerFrame * sizeof(int16_t) * 2; def.bEnabled = OMX_TRUE; def.bPopulated = OMX_FALSE; def.eDomain = OMX_PortDomainAudio; def.bBuffersContiguous = OMX_FALSE; def.nBufferAlignment = 1; def.format.audio.cMIMEType = const_cast("audio/raw"); def.format.audio.pNativeRender = NULL; def.format.audio.bFlagErrorConcealment = OMX_FALSE; def.format.audio.eEncoding = OMX_AUDIO_CodingPCM; addPort(def); def.nPortIndex = 1; def.eDir = OMX_DirOutput; def.nBufferCountMin = kNumBuffers; def.nBufferCountActual = def.nBufferCountMin; def.nBufferSize = 8192; def.bEnabled = OMX_TRUE; def.bPopulated = OMX_FALSE; def.eDomain = OMX_PortDomainAudio; def.bBuffersContiguous = OMX_FALSE; def.nBufferAlignment = 2; def.format.audio.cMIMEType = const_cast("audio/aac"); def.format.audio.pNativeRender = NULL; def.format.audio.bFlagErrorConcealment = OMX_FALSE; def.format.audio.eEncoding = OMX_AUDIO_CodingAAC; addPort(def); } status_t SoftAACEncoder2::initEncoder() { if (AACENC_OK != aacEncOpen(&mAACEncoder, 0, 0)) { ALOGE("Failed to init AAC encoder"); return UNKNOWN_ERROR; } return OK; } OMX_ERRORTYPE SoftAACEncoder2::internalGetParameter( OMX_INDEXTYPE index, OMX_PTR params) { switch (index) { case OMX_IndexParamAudioPortFormat: { OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams = (OMX_AUDIO_PARAM_PORTFORMATTYPE *)params; if (!isValidOMXParam(formatParams)) { return OMX_ErrorBadParameter; } if (formatParams->nPortIndex > 1) { return OMX_ErrorUndefined; } if (formatParams->nIndex > 0) { return OMX_ErrorNoMore; } formatParams->eEncoding = (formatParams->nPortIndex == 0) ? OMX_AUDIO_CodingPCM : OMX_AUDIO_CodingAAC; return OMX_ErrorNone; } case OMX_IndexParamAudioAac: { OMX_AUDIO_PARAM_AACPROFILETYPE *aacParams = (OMX_AUDIO_PARAM_AACPROFILETYPE *)params; if (!isValidOMXParam(aacParams)) { return OMX_ErrorBadParameter; } if (aacParams->nPortIndex != 1) { return OMX_ErrorUndefined; } aacParams->nBitRate = mBitRate; aacParams->nAudioBandWidth = 0; aacParams->nAACtools = 0; aacParams->nAACERtools = 0; aacParams->eAACProfile = (OMX_AUDIO_AACPROFILETYPE) mAACProfile; aacParams->eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4FF; aacParams->eChannelMode = OMX_AUDIO_ChannelModeStereo; aacParams->nChannels = mNumChannels; aacParams->nSampleRate = mSampleRate; aacParams->nFrameLength = 0; switch (mSBRMode) { case 1: // sbr on switch (mSBRRatio) { case 0: // set both OMX AAC tool flags aacParams->nAACtools |= OMX_AUDIO_AACToolAndroidSSBR; aacParams->nAACtools |= OMX_AUDIO_AACToolAndroidDSBR; break; case 1: // set single-rate SBR active aacParams->nAACtools |= OMX_AUDIO_AACToolAndroidSSBR; aacParams->nAACtools &= ~OMX_AUDIO_AACToolAndroidDSBR; break; case 2: // set dual-rate SBR active aacParams->nAACtools &= ~OMX_AUDIO_AACToolAndroidSSBR; aacParams->nAACtools |= OMX_AUDIO_AACToolAndroidDSBR; break; default: ALOGE("invalid SBR ratio %d", mSBRRatio); TRESPASS(); } break; case 0: // sbr off case -1: // sbr undefined aacParams->nAACtools &= ~OMX_AUDIO_AACToolAndroidSSBR; aacParams->nAACtools &= ~OMX_AUDIO_AACToolAndroidDSBR; break; default: ALOGE("invalid SBR mode %d", mSBRMode); TRESPASS(); } return OMX_ErrorNone; } case OMX_IndexParamAudioPcm: { OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams = (OMX_AUDIO_PARAM_PCMMODETYPE *)params; if (!isValidOMXParam(pcmParams)) { return OMX_ErrorBadParameter; } if (pcmParams->nPortIndex != 0) { return OMX_ErrorUndefined; } pcmParams->eNumData = OMX_NumericalDataSigned; pcmParams->eEndian = OMX_EndianBig; pcmParams->bInterleaved = OMX_TRUE; pcmParams->nBitPerSample = 16; pcmParams->ePCMMode = OMX_AUDIO_PCMModeLinear; pcmParams->eChannelMapping[0] = OMX_AUDIO_ChannelLF; pcmParams->eChannelMapping[1] = OMX_AUDIO_ChannelRF; pcmParams->nChannels = mNumChannels; pcmParams->nSamplingRate = mSampleRate; return OMX_ErrorNone; } default: return SimpleSoftOMXComponent::internalGetParameter(index, params); } } OMX_ERRORTYPE SoftAACEncoder2::internalSetParameter( OMX_INDEXTYPE index, const OMX_PTR params) { switch (index) { case OMX_IndexParamStandardComponentRole: { const OMX_PARAM_COMPONENTROLETYPE *roleParams = (const OMX_PARAM_COMPONENTROLETYPE *)params; if (!isValidOMXParam(roleParams)) { return OMX_ErrorBadParameter; } if (strncmp((const char *)roleParams->cRole, "audio_encoder.aac", OMX_MAX_STRINGNAME_SIZE - 1)) { return OMX_ErrorUndefined; } return OMX_ErrorNone; } case OMX_IndexParamAudioPortFormat: { const OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams = (const OMX_AUDIO_PARAM_PORTFORMATTYPE *)params; if (!isValidOMXParam(formatParams)) { return OMX_ErrorBadParameter; } if (formatParams->nPortIndex > 1) { return OMX_ErrorUndefined; } if (formatParams->nIndex > 0) { return OMX_ErrorNoMore; } if ((formatParams->nPortIndex == 0 && formatParams->eEncoding != OMX_AUDIO_CodingPCM) || (formatParams->nPortIndex == 1 && formatParams->eEncoding != OMX_AUDIO_CodingAAC)) { return OMX_ErrorUndefined; } return OMX_ErrorNone; } case OMX_IndexParamAudioAac: { OMX_AUDIO_PARAM_AACPROFILETYPE *aacParams = (OMX_AUDIO_PARAM_AACPROFILETYPE *)params; if (!isValidOMXParam(aacParams)) { return OMX_ErrorBadParameter; } if (aacParams->nPortIndex != 1) { return OMX_ErrorUndefined; } mBitRate = aacParams->nBitRate; mNumChannels = aacParams->nChannels; mSampleRate = aacParams->nSampleRate; if (aacParams->eAACProfile != OMX_AUDIO_AACObjectNull) { mAACProfile = aacParams->eAACProfile; } if (!(aacParams->nAACtools & OMX_AUDIO_AACToolAndroidSSBR) && !(aacParams->nAACtools & OMX_AUDIO_AACToolAndroidDSBR)) { mSBRMode = 0; mSBRRatio = 0; } else if ((aacParams->nAACtools & OMX_AUDIO_AACToolAndroidSSBR) && !(aacParams->nAACtools & OMX_AUDIO_AACToolAndroidDSBR)) { mSBRMode = 1; mSBRRatio = 1; } else if (!(aacParams->nAACtools & OMX_AUDIO_AACToolAndroidSSBR) && (aacParams->nAACtools & OMX_AUDIO_AACToolAndroidDSBR)) { mSBRMode = 1; mSBRRatio = 2; } else { mSBRMode = -1; // codec default sbr mode mSBRRatio = 0; } if (setAudioParams() != OK) { return OMX_ErrorUndefined; } return OMX_ErrorNone; } case OMX_IndexParamAudioPcm: { OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams = (OMX_AUDIO_PARAM_PCMMODETYPE *)params; if (!isValidOMXParam(pcmParams)) { return OMX_ErrorBadParameter; } if (pcmParams->nPortIndex != 0) { return OMX_ErrorUndefined; } mNumChannels = pcmParams->nChannels; mSampleRate = pcmParams->nSamplingRate; if (setAudioParams() != OK) { return OMX_ErrorUndefined; } return OMX_ErrorNone; } default: return SimpleSoftOMXComponent::internalSetParameter(index, params); } } static CHANNEL_MODE getChannelMode(OMX_U32 nChannels) { CHANNEL_MODE chMode = MODE_INVALID; switch (nChannels) { case 1: chMode = MODE_1; break; case 2: chMode = MODE_2; break; case 3: chMode = MODE_1_2; break; case 4: chMode = MODE_1_2_1; break; case 5: chMode = MODE_1_2_2; break; case 6: chMode = MODE_1_2_2_1; break; default: chMode = MODE_INVALID; } return chMode; } static AUDIO_OBJECT_TYPE getAOTFromProfile(OMX_U32 profile) { if (profile == OMX_AUDIO_AACObjectLC) { return AOT_AAC_LC; } else if (profile == OMX_AUDIO_AACObjectHE) { return AOT_SBR; } else if (profile == OMX_AUDIO_AACObjectHE_PS) { return AOT_PS; } else if (profile == OMX_AUDIO_AACObjectLD) { return AOT_ER_AAC_LD; } else if (profile == OMX_AUDIO_AACObjectELD) { return AOT_ER_AAC_ELD; } else { ALOGW("Unsupported AAC profile - defaulting to AAC-LC"); return AOT_AAC_LC; } } status_t SoftAACEncoder2::setAudioParams() { // We call this whenever sample rate, number of channels, bitrate or SBR mode change // in reponse to setParameter calls. ALOGV("setAudioParams: %u Hz, %u channels, %u bps, %i sbr mode, %i sbr ratio", mSampleRate, mNumChannels, mBitRate, mSBRMode, mSBRRatio); if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_AOT, getAOTFromProfile(mAACProfile))) { ALOGE("Failed to set AAC encoder parameters"); return UNKNOWN_ERROR; } if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_SAMPLERATE, mSampleRate)) { ALOGE("Failed to set AAC encoder parameters"); return UNKNOWN_ERROR; } if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_BITRATE, mBitRate)) { ALOGE("Failed to set AAC encoder parameters"); return UNKNOWN_ERROR; } if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_CHANNELMODE, getChannelMode(mNumChannels))) { ALOGE("Failed to set AAC encoder parameters"); return UNKNOWN_ERROR; } if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_TRANSMUX, TT_MP4_RAW)) { ALOGE("Failed to set AAC encoder parameters"); return UNKNOWN_ERROR; } if (mSBRMode != -1 && mAACProfile == OMX_AUDIO_AACObjectELD) { if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_SBR_MODE, mSBRMode)) { ALOGE("Failed to set AAC encoder parameters"); return UNKNOWN_ERROR; } } /* SBR ratio parameter configurations: 0: Default configuration wherein SBR ratio is configured depending on audio object type by the FDK. 1: Downsampled SBR (default for ELD) 2: Dualrate SBR (default for HE-AAC) */ if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_SBR_RATIO, mSBRRatio)) { ALOGE("Failed to set AAC encoder parameters"); return UNKNOWN_ERROR; } return OK; } void SoftAACEncoder2::onQueueFilled(OMX_U32 /* portIndex */) { if (mSignalledError) { return; } List &inQueue = getPortQueue(0); List &outQueue = getPortQueue(1); if (!mSentCodecSpecificData) { // The very first thing we want to output is the codec specific // data. It does not require any input data but we will need an // output buffer to store it in. if (outQueue.empty()) { return; } if (AACENC_OK != aacEncEncode(mAACEncoder, NULL, NULL, NULL, NULL)) { ALOGE("Unable to initialize encoder for profile / sample-rate / bit-rate / channels"); notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); mSignalledError = true; return; } OMX_U32 actualBitRate = aacEncoder_GetParam(mAACEncoder, AACENC_BITRATE); if (mBitRate != actualBitRate) { ALOGW("Requested bitrate %u unsupported, using %u", mBitRate, actualBitRate); } AACENC_InfoStruct encInfo; if (AACENC_OK != aacEncInfo(mAACEncoder, &encInfo)) { ALOGE("Failed to get AAC encoder info"); notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); mSignalledError = true; return; } BufferInfo *outInfo = *outQueue.begin(); OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader; outHeader->nFilledLen = encInfo.confSize; outHeader->nFlags = OMX_BUFFERFLAG_CODECCONFIG; uint8_t *out = outHeader->pBuffer + outHeader->nOffset; memcpy(out, encInfo.confBuf, encInfo.confSize); outQueue.erase(outQueue.begin()); outInfo->mOwnedByUs = false; notifyFillBufferDone(outHeader); mSentCodecSpecificData = true; } size_t numBytesPerInputFrame = mNumChannels * kNumSamplesPerFrame * sizeof(int16_t); // Limit input size so we only get one ELD frame if (mAACProfile == OMX_AUDIO_AACObjectELD && numBytesPerInputFrame > 512) { numBytesPerInputFrame = 512; } for (;;) { // We do the following until we run out of buffers. while (mInputSize < numBytesPerInputFrame) { // As long as there's still input data to be read we // will drain "kNumSamplesPerFrame * mNumChannels" samples // into the "mInputFrame" buffer and then encode those // as a unit into an output buffer. if (mSawInputEOS || inQueue.empty()) { return; } BufferInfo *inInfo = *inQueue.begin(); OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader; const void *inData = inHeader->pBuffer + inHeader->nOffset; size_t copy = numBytesPerInputFrame - mInputSize; if (copy > inHeader->nFilledLen) { copy = inHeader->nFilledLen; } if (mInputFrame == NULL) { mInputFrame = new int16_t[numBytesPerInputFrame / sizeof(int16_t)]; } if (mInputSize == 0) { mInputTimeUs = inHeader->nTimeStamp; } memcpy((uint8_t *)mInputFrame + mInputSize, inData, copy); mInputSize += copy; inHeader->nOffset += copy; inHeader->nFilledLen -= copy; // "Time" on the input buffer has in effect advanced by the // number of audio frames we just advanced nOffset by. inHeader->nTimeStamp += (copy * 1000000ll / mSampleRate) / (mNumChannels * sizeof(int16_t)); if (inHeader->nFilledLen == 0) { if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) { mSawInputEOS = true; // Pad any remaining data with zeroes. memset((uint8_t *)mInputFrame + mInputSize, 0, numBytesPerInputFrame - mInputSize); mInputSize = numBytesPerInputFrame; } inQueue.erase(inQueue.begin()); inInfo->mOwnedByUs = false; notifyEmptyBufferDone(inHeader); inData = NULL; inHeader = NULL; inInfo = NULL; } } // At this point we have all the input data necessary to encode // a single frame, all we need is an output buffer to store the result // in. if (outQueue.empty()) { return; } BufferInfo *outInfo = *outQueue.begin(); OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader; uint8_t *outPtr = (uint8_t *)outHeader->pBuffer + outHeader->nOffset; size_t outAvailable = outHeader->nAllocLen - outHeader->nOffset; AACENC_InArgs inargs; AACENC_OutArgs outargs; memset(&inargs, 0, sizeof(inargs)); memset(&outargs, 0, sizeof(outargs)); inargs.numInSamples = numBytesPerInputFrame / sizeof(int16_t); void* inBuffer[] = { (unsigned char *)mInputFrame }; INT inBufferIds[] = { IN_AUDIO_DATA }; INT inBufferSize[] = { (INT)numBytesPerInputFrame }; INT inBufferElSize[] = { sizeof(int16_t) }; AACENC_BufDesc inBufDesc; inBufDesc.numBufs = sizeof(inBuffer) / sizeof(void*); inBufDesc.bufs = (void**)&inBuffer; inBufDesc.bufferIdentifiers = inBufferIds; inBufDesc.bufSizes = inBufferSize; inBufDesc.bufElSizes = inBufferElSize; void* outBuffer[] = { outPtr }; INT outBufferIds[] = { OUT_BITSTREAM_DATA }; INT outBufferSize[] = { 0 }; INT outBufferElSize[] = { sizeof(UCHAR) }; AACENC_BufDesc outBufDesc; outBufDesc.numBufs = sizeof(outBuffer) / sizeof(void*); outBufDesc.bufs = (void**)&outBuffer; outBufDesc.bufferIdentifiers = outBufferIds; outBufDesc.bufSizes = outBufferSize; outBufDesc.bufElSizes = outBufferElSize; // Encode the mInputFrame, which is treated as a modulo buffer AACENC_ERROR encoderErr = AACENC_OK; size_t nOutputBytes = 0; do { memset(&outargs, 0, sizeof(outargs)); outBuffer[0] = outPtr; outBufferSize[0] = outAvailable - nOutputBytes; encoderErr = aacEncEncode(mAACEncoder, &inBufDesc, &outBufDesc, &inargs, &outargs); if (encoderErr == AACENC_OK) { outPtr += outargs.numOutBytes; nOutputBytes += outargs.numOutBytes; if (outargs.numInSamples > 0) { int numRemainingSamples = inargs.numInSamples - outargs.numInSamples; if (numRemainingSamples > 0) { memmove(mInputFrame, &mInputFrame[outargs.numInSamples], sizeof(int16_t) * numRemainingSamples); } inargs.numInSamples -= outargs.numInSamples; } } } while (encoderErr == AACENC_OK && inargs.numInSamples > 0); outHeader->nFilledLen = nOutputBytes; outHeader->nFlags = OMX_BUFFERFLAG_ENDOFFRAME; if (mSawInputEOS) { // We also tag this output buffer with EOS if it corresponds // to the final input buffer. outHeader->nFlags = OMX_BUFFERFLAG_EOS; } outHeader->nTimeStamp = mInputTimeUs; #if 0 ALOGI("sending %d bytes of data (time = %lld us, flags = 0x%08lx)", nOutputBytes, mInputTimeUs, outHeader->nFlags); hexdump(outHeader->pBuffer + outHeader->nOffset, outHeader->nFilledLen); #endif outQueue.erase(outQueue.begin()); outInfo->mOwnedByUs = false; notifyFillBufferDone(outHeader); outHeader = NULL; outInfo = NULL; mInputSize = 0; } } } // namespace android android::SoftOMXComponent *createSoftOMXComponent( const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) { return new android::SoftAACEncoder2(name, callbacks, appData, component); }