/* * Copyright (C) 2014 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 "SoftOpus" #include #include "SoftOpus.h" #include #include #include #include extern "C" { #include #include } namespace android { static const int kRate = 48000; // Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies // mappings for up to 8 channels. This information is part of the Vorbis I // Specification: // http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html static const int kMaxChannels = 8; 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; } SoftOpus::SoftOpus( const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) : SimpleSoftOMXComponent(name, callbacks, appData, component), mInputBufferCount(0), mDecoder(NULL), mHeader(NULL), mCodecDelay(0), mSeekPreRoll(0), mAnchorTimeUs(0), mNumFramesOutput(0), mOutputPortSettingsChange(NONE) { initPorts(); CHECK_EQ(initDecoder(), (status_t)OK); } SoftOpus::~SoftOpus() { if (mDecoder != NULL) { opus_multistream_decoder_destroy(mDecoder); mDecoder = NULL; } if (mHeader != NULL) { delete mHeader; mHeader = NULL; } } void SoftOpus::initPorts() { OMX_PARAM_PORTDEFINITIONTYPE def; InitOMXParams(&def); def.nPortIndex = 0; def.eDir = OMX_DirInput; def.nBufferCountMin = kNumBuffers; def.nBufferCountActual = def.nBufferCountMin; def.nBufferSize = 960 * 6; 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(MEDIA_MIMETYPE_AUDIO_OPUS); def.format.audio.pNativeRender = NULL; def.format.audio.bFlagErrorConcealment = OMX_FALSE; def.format.audio.eEncoding = (OMX_AUDIO_CODINGTYPE)OMX_AUDIO_CodingAndroidOPUS; addPort(def); def.nPortIndex = 1; def.eDir = OMX_DirOutput; def.nBufferCountMin = kNumBuffers; def.nBufferCountActual = def.nBufferCountMin; def.nBufferSize = kMaxNumSamplesPerBuffer * sizeof(int16_t) * kMaxChannels; 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/raw"); def.format.audio.pNativeRender = NULL; def.format.audio.bFlagErrorConcealment = OMX_FALSE; def.format.audio.eEncoding = OMX_AUDIO_CodingPCM; addPort(def); } status_t SoftOpus::initDecoder() { return OK; } OMX_ERRORTYPE SoftOpus::internalGetParameter( OMX_INDEXTYPE index, OMX_PTR params) { switch ((int)index) { case OMX_IndexParamAudioAndroidOpus: { OMX_AUDIO_PARAM_ANDROID_OPUSTYPE *opusParams = (OMX_AUDIO_PARAM_ANDROID_OPUSTYPE *)params; if (!isValidOMXParam(opusParams)) { return OMX_ErrorBadParameter; } if (opusParams->nPortIndex != 0) { return OMX_ErrorUndefined; } opusParams->nAudioBandWidth = 0; opusParams->nSampleRate = kRate; opusParams->nBitRate = 0; if (!isConfigured()) { opusParams->nChannels = 1; } else { opusParams->nChannels = mHeader->channels; } 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 != 1) { 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->nSamplingRate = kRate; if (!isConfigured()) { pcmParams->nChannels = 1; } else { pcmParams->nChannels = mHeader->channels; } return OMX_ErrorNone; } default: return SimpleSoftOMXComponent::internalGetParameter(index, params); } } OMX_ERRORTYPE SoftOpus::internalSetParameter( OMX_INDEXTYPE index, const OMX_PTR params) { switch ((int)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_decoder.opus", OMX_MAX_STRINGNAME_SIZE - 1)) { return OMX_ErrorUndefined; } return OMX_ErrorNone; } case OMX_IndexParamAudioAndroidOpus: { const OMX_AUDIO_PARAM_ANDROID_OPUSTYPE *opusParams = (const OMX_AUDIO_PARAM_ANDROID_OPUSTYPE *)params; if (!isValidOMXParam(opusParams)) { return OMX_ErrorBadParameter; } if (opusParams->nPortIndex != 0) { return OMX_ErrorUndefined; } return OMX_ErrorNone; } default: return SimpleSoftOMXComponent::internalSetParameter(index, params); } } bool SoftOpus::isConfigured() const { return mInputBufferCount >= 1; } static uint16_t ReadLE16(const uint8_t *data, size_t data_size, uint32_t read_offset) { if (read_offset + 1 > data_size) return 0; uint16_t val; val = data[read_offset]; val |= data[read_offset + 1] << 8; return val; } // Maximum packet size used in Xiph's opusdec. static const int kMaxOpusOutputPacketSizeSamples = 960 * 6; // Default audio output channel layout. Used to initialize |stream_map| in // OpusHeader, and passed to opus_multistream_decoder_create() when the header // does not contain mapping information. The values are valid only for mono and // stereo output: Opus streams with more than 2 channels require a stream map. static const int kMaxChannelsWithDefaultLayout = 2; static const uint8_t kDefaultOpusChannelLayout[kMaxChannelsWithDefaultLayout] = { 0, 1 }; // Parses Opus Header. Header spec: http://wiki.xiph.org/OggOpus#ID_Header static bool ParseOpusHeader(const uint8_t *data, size_t data_size, OpusHeader* header) { // Size of the Opus header excluding optional mapping information. const size_t kOpusHeaderSize = 19; // Offset to the channel count byte in the Opus header. const size_t kOpusHeaderChannelsOffset = 9; // Offset to the pre-skip value in the Opus header. const size_t kOpusHeaderSkipSamplesOffset = 10; // Offset to the gain value in the Opus header. const size_t kOpusHeaderGainOffset = 16; // Offset to the channel mapping byte in the Opus header. const size_t kOpusHeaderChannelMappingOffset = 18; // Opus Header contains a stream map. The mapping values are in the header // beyond the always present |kOpusHeaderSize| bytes of data. The mapping // data contains stream count, coupling information, and per channel mapping // values: // - Byte 0: Number of streams. // - Byte 1: Number coupled. // - Byte 2: Starting at byte 2 are |header->channels| uint8 mapping // values. const size_t kOpusHeaderNumStreamsOffset = kOpusHeaderSize; const size_t kOpusHeaderNumCoupledOffset = kOpusHeaderNumStreamsOffset + 1; const size_t kOpusHeaderStreamMapOffset = kOpusHeaderNumStreamsOffset + 2; if (data_size < kOpusHeaderSize) { ALOGV("Header size is too small."); return false; } header->channels = *(data + kOpusHeaderChannelsOffset); if (header->channels <= 0 || header->channels > kMaxChannels) { ALOGV("Invalid Header, wrong channel count: %d", header->channels); return false; } header->skip_samples = ReadLE16(data, data_size, kOpusHeaderSkipSamplesOffset); header->gain_db = static_cast( ReadLE16(data, data_size, kOpusHeaderGainOffset)); header->channel_mapping = *(data + kOpusHeaderChannelMappingOffset); if (!header->channel_mapping) { if (header->channels > kMaxChannelsWithDefaultLayout) { ALOGV("Invalid Header, missing stream map."); return false; } header->num_streams = 1; header->num_coupled = header->channels > 1; header->stream_map[0] = 0; header->stream_map[1] = 1; return true; } if (data_size < kOpusHeaderStreamMapOffset + header->channels) { ALOGV("Invalid stream map; insufficient data for current channel " "count: %d", header->channels); return false; } header->num_streams = *(data + kOpusHeaderNumStreamsOffset); header->num_coupled = *(data + kOpusHeaderNumCoupledOffset); if (header->num_streams + header->num_coupled != header->channels) { ALOGV("Inconsistent channel mapping."); return false; } for (int i = 0; i < header->channels; ++i) header->stream_map[i] = *(data + kOpusHeaderStreamMapOffset + i); return true; } // Convert nanoseconds to number of samples. static uint64_t ns_to_samples(uint64_t ns, int kRate) { return static_cast(ns) * kRate / 1000000000; } void SoftOpus::onQueueFilled(OMX_U32 portIndex) { List &inQueue = getPortQueue(0); List &outQueue = getPortQueue(1); if (mOutputPortSettingsChange != NONE) { return; } if (portIndex == 0 && mInputBufferCount < 3) { BufferInfo *info = *inQueue.begin(); OMX_BUFFERHEADERTYPE *header = info->mHeader; const uint8_t *data = header->pBuffer + header->nOffset; size_t size = header->nFilledLen; if (mInputBufferCount == 0) { CHECK(mHeader == NULL); mHeader = new OpusHeader(); memset(mHeader, 0, sizeof(*mHeader)); if (!ParseOpusHeader(data, size, mHeader)) { ALOGV("Parsing Opus Header failed."); notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); return; } uint8_t channel_mapping[kMaxChannels] = {0}; if (mHeader->channels <= kMaxChannelsWithDefaultLayout) { memcpy(&channel_mapping, kDefaultOpusChannelLayout, kMaxChannelsWithDefaultLayout); } else { memcpy(&channel_mapping, mHeader->stream_map, mHeader->channels); } int status = OPUS_INVALID_STATE; mDecoder = opus_multistream_decoder_create(kRate, mHeader->channels, mHeader->num_streams, mHeader->num_coupled, channel_mapping, &status); if (!mDecoder || status != OPUS_OK) { ALOGV("opus_multistream_decoder_create failed status=%s", opus_strerror(status)); notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); return; } status = opus_multistream_decoder_ctl(mDecoder, OPUS_SET_GAIN(mHeader->gain_db)); if (status != OPUS_OK) { ALOGV("Failed to set OPUS header gain; status=%s", opus_strerror(status)); notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); return; } } else if (mInputBufferCount == 1) { mCodecDelay = ns_to_samples( *(reinterpret_cast(header->pBuffer + header->nOffset)), kRate); mSamplesToDiscard = mCodecDelay; } else { mSeekPreRoll = ns_to_samples( *(reinterpret_cast(header->pBuffer + header->nOffset)), kRate); notify(OMX_EventPortSettingsChanged, 1, 0, NULL); mOutputPortSettingsChange = AWAITING_DISABLED; } inQueue.erase(inQueue.begin()); info->mOwnedByUs = false; notifyEmptyBufferDone(header); ++mInputBufferCount; return; } while (!inQueue.empty() && !outQueue.empty()) { BufferInfo *inInfo = *inQueue.begin(); OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader; // Ignore CSD re-submissions. if (inHeader->nFlags & OMX_BUFFERFLAG_CODECCONFIG) { inQueue.erase(inQueue.begin()); inInfo->mOwnedByUs = false; notifyEmptyBufferDone(inHeader); return; } BufferInfo *outInfo = *outQueue.begin(); OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader; if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) { inQueue.erase(inQueue.begin()); inInfo->mOwnedByUs = false; notifyEmptyBufferDone(inHeader); outHeader->nFilledLen = 0; outHeader->nFlags = OMX_BUFFERFLAG_EOS; outQueue.erase(outQueue.begin()); outInfo->mOwnedByUs = false; notifyFillBufferDone(outHeader); return; } if (inHeader->nOffset == 0) { mAnchorTimeUs = inHeader->nTimeStamp; mNumFramesOutput = 0; } // When seeking to zero, |mCodecDelay| samples has to be discarded // instead of |mSeekPreRoll| samples (as we would when seeking to any // other timestamp). if (inHeader->nTimeStamp == 0) { mSamplesToDiscard = mCodecDelay; } const uint8_t *data = inHeader->pBuffer + inHeader->nOffset; const uint32_t size = inHeader->nFilledLen; size_t frameSize = kMaxOpusOutputPacketSizeSamples; if (frameSize > outHeader->nAllocLen / sizeof(int16_t) / mHeader->channels) { frameSize = outHeader->nAllocLen / sizeof(int16_t) / mHeader->channels; android_errorWriteLog(0x534e4554, "27833616"); } int numFrames = opus_multistream_decode(mDecoder, data, size, (int16_t *)outHeader->pBuffer, frameSize, 0); if (numFrames < 0) { ALOGE("opus_multistream_decode returned %d", numFrames); notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); return; } outHeader->nOffset = 0; if (mSamplesToDiscard > 0) { if (mSamplesToDiscard > numFrames) { mSamplesToDiscard -= numFrames; numFrames = 0; } else { numFrames -= mSamplesToDiscard; outHeader->nOffset = mSamplesToDiscard * sizeof(int16_t) * mHeader->channels; mSamplesToDiscard = 0; } } outHeader->nFilledLen = numFrames * sizeof(int16_t) * mHeader->channels; outHeader->nFlags = 0; outHeader->nTimeStamp = mAnchorTimeUs + (mNumFramesOutput * 1000000ll) / kRate; mNumFramesOutput += numFrames; inInfo->mOwnedByUs = false; inQueue.erase(inQueue.begin()); inInfo = NULL; notifyEmptyBufferDone(inHeader); inHeader = NULL; outInfo->mOwnedByUs = false; outQueue.erase(outQueue.begin()); outInfo = NULL; notifyFillBufferDone(outHeader); outHeader = NULL; ++mInputBufferCount; } } void SoftOpus::onPortFlushCompleted(OMX_U32 portIndex) { if (portIndex == 0 && mDecoder != NULL) { // Make sure that the next buffer output does not still // depend on fragments from the last one decoded. mNumFramesOutput = 0; opus_multistream_decoder_ctl(mDecoder, OPUS_RESET_STATE); mAnchorTimeUs = 0; mSamplesToDiscard = mSeekPreRoll; } } void SoftOpus::onReset() { mInputBufferCount = 0; mNumFramesOutput = 0; if (mDecoder != NULL) { opus_multistream_decoder_destroy(mDecoder); mDecoder = NULL; } if (mHeader != NULL) { delete mHeader; mHeader = NULL; } mOutputPortSettingsChange = NONE; } void SoftOpus::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) { if (portIndex != 1) { return; } switch (mOutputPortSettingsChange) { case NONE: break; case AWAITING_DISABLED: { CHECK(!enabled); mOutputPortSettingsChange = AWAITING_ENABLED; break; } default: { CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED); CHECK(enabled); mOutputPortSettingsChange = NONE; break; } } } } // namespace android android::SoftOMXComponent *createSoftOMXComponent( const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData, OMX_COMPONENTTYPE **component) { return new android::SoftOpus(name, callbacks, appData, component); }