/* * Copyright (C) 2015 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_TAG "BufferProvider" //#define LOG_NDEBUG 0 #include #include #include #include #include #include #include #include "Configuration.h" #include "BufferProviders.h" #ifndef ARRAY_SIZE #define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) #endif namespace android { // ---------------------------------------------------------------------------- template static inline T min(const T& a, const T& b) { return a < b ? a : b; } CopyBufferProvider::CopyBufferProvider(size_t inputFrameSize, size_t outputFrameSize, size_t bufferFrameCount) : mInputFrameSize(inputFrameSize), mOutputFrameSize(outputFrameSize), mLocalBufferFrameCount(bufferFrameCount), mLocalBufferData(NULL), mConsumed(0) { ALOGV("CopyBufferProvider(%p)(%zu, %zu, %zu)", this, inputFrameSize, outputFrameSize, bufferFrameCount); LOG_ALWAYS_FATAL_IF(inputFrameSize < outputFrameSize && bufferFrameCount == 0, "Requires local buffer if inputFrameSize(%zu) < outputFrameSize(%zu)", inputFrameSize, outputFrameSize); if (mLocalBufferFrameCount) { (void)posix_memalign(&mLocalBufferData, 32, mLocalBufferFrameCount * mOutputFrameSize); } mBuffer.frameCount = 0; } CopyBufferProvider::~CopyBufferProvider() { ALOGV("~CopyBufferProvider(%p)", this); if (mBuffer.frameCount != 0) { mTrackBufferProvider->releaseBuffer(&mBuffer); } free(mLocalBufferData); } status_t CopyBufferProvider::getNextBuffer(AudioBufferProvider::Buffer *pBuffer, int64_t pts) { //ALOGV("CopyBufferProvider(%p)::getNextBuffer(%p (%zu), %lld)", // this, pBuffer, pBuffer->frameCount, pts); if (mLocalBufferFrameCount == 0) { status_t res = mTrackBufferProvider->getNextBuffer(pBuffer, pts); if (res == OK) { copyFrames(pBuffer->raw, pBuffer->raw, pBuffer->frameCount); } return res; } if (mBuffer.frameCount == 0) { mBuffer.frameCount = pBuffer->frameCount; status_t res = mTrackBufferProvider->getNextBuffer(&mBuffer, pts); // At one time an upstream buffer provider had // res == OK and mBuffer.frameCount == 0, doesn't seem to happen now 7/18/2014. // // By API spec, if res != OK, then mBuffer.frameCount == 0. // but there may be improper implementations. ALOG_ASSERT(res == OK || mBuffer.frameCount == 0); if (res != OK || mBuffer.frameCount == 0) { // not needed by API spec, but to be safe. pBuffer->raw = NULL; pBuffer->frameCount = 0; return res; } mConsumed = 0; } ALOG_ASSERT(mConsumed < mBuffer.frameCount); size_t count = min(mLocalBufferFrameCount, mBuffer.frameCount - mConsumed); count = min(count, pBuffer->frameCount); pBuffer->raw = mLocalBufferData; pBuffer->frameCount = count; copyFrames(pBuffer->raw, (uint8_t*)mBuffer.raw + mConsumed * mInputFrameSize, pBuffer->frameCount); return OK; } void CopyBufferProvider::releaseBuffer(AudioBufferProvider::Buffer *pBuffer) { //ALOGV("CopyBufferProvider(%p)::releaseBuffer(%p(%zu))", // this, pBuffer, pBuffer->frameCount); if (mLocalBufferFrameCount == 0) { mTrackBufferProvider->releaseBuffer(pBuffer); return; } // LOG_ALWAYS_FATAL_IF(pBuffer->frameCount == 0, "Invalid framecount"); mConsumed += pBuffer->frameCount; // TODO: update for efficiency to reuse existing content if (mConsumed != 0 && mConsumed >= mBuffer.frameCount) { mTrackBufferProvider->releaseBuffer(&mBuffer); ALOG_ASSERT(mBuffer.frameCount == 0); } pBuffer->raw = NULL; pBuffer->frameCount = 0; } void CopyBufferProvider::reset() { if (mBuffer.frameCount != 0) { mTrackBufferProvider->releaseBuffer(&mBuffer); } mConsumed = 0; } DownmixerBufferProvider::DownmixerBufferProvider( audio_channel_mask_t inputChannelMask, audio_channel_mask_t outputChannelMask, audio_format_t format, uint32_t sampleRate, int32_t sessionId, size_t bufferFrameCount) : CopyBufferProvider( audio_bytes_per_sample(format) * audio_channel_count_from_out_mask(inputChannelMask), audio_bytes_per_sample(format) * audio_channel_count_from_out_mask(outputChannelMask), bufferFrameCount) // set bufferFrameCount to 0 to do in-place { ALOGV("DownmixerBufferProvider(%p)(%#x, %#x, %#x %u %d)", this, inputChannelMask, outputChannelMask, format, sampleRate, sessionId); if (!sIsMultichannelCapable || EffectCreate(&sDwnmFxDesc.uuid, sessionId, SESSION_ID_INVALID_AND_IGNORED, &mDownmixHandle) != 0) { ALOGE("DownmixerBufferProvider() error creating downmixer effect"); mDownmixHandle = NULL; return; } // channel input configuration will be overridden per-track mDownmixConfig.inputCfg.channels = inputChannelMask; // FIXME: Should be bits mDownmixConfig.outputCfg.channels = outputChannelMask; // FIXME: should be bits mDownmixConfig.inputCfg.format = format; mDownmixConfig.outputCfg.format = format; mDownmixConfig.inputCfg.samplingRate = sampleRate; mDownmixConfig.outputCfg.samplingRate = sampleRate; mDownmixConfig.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ; mDownmixConfig.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_WRITE; // input and output buffer provider, and frame count will not be used as the downmix effect // process() function is called directly (see DownmixerBufferProvider::getNextBuffer()) mDownmixConfig.inputCfg.mask = EFFECT_CONFIG_SMP_RATE | EFFECT_CONFIG_CHANNELS | EFFECT_CONFIG_FORMAT | EFFECT_CONFIG_ACC_MODE; mDownmixConfig.outputCfg.mask = mDownmixConfig.inputCfg.mask; int cmdStatus; uint32_t replySize = sizeof(int); // Configure downmixer status_t status = (*mDownmixHandle)->command(mDownmixHandle, EFFECT_CMD_SET_CONFIG /*cmdCode*/, sizeof(effect_config_t) /*cmdSize*/, &mDownmixConfig /*pCmdData*/, &replySize, &cmdStatus /*pReplyData*/); if (status != 0 || cmdStatus != 0) { ALOGE("DownmixerBufferProvider() error %d cmdStatus %d while configuring downmixer", status, cmdStatus); EffectRelease(mDownmixHandle); mDownmixHandle = NULL; return; } // Enable downmixer replySize = sizeof(int); status = (*mDownmixHandle)->command(mDownmixHandle, EFFECT_CMD_ENABLE /*cmdCode*/, 0 /*cmdSize*/, NULL /*pCmdData*/, &replySize, &cmdStatus /*pReplyData*/); if (status != 0 || cmdStatus != 0) { ALOGE("DownmixerBufferProvider() error %d cmdStatus %d while enabling downmixer", status, cmdStatus); EffectRelease(mDownmixHandle); mDownmixHandle = NULL; return; } // Set downmix type // parameter size rounded for padding on 32bit boundary const int psizePadded = ((sizeof(downmix_params_t) - 1)/sizeof(int) + 1) * sizeof(int); const int downmixParamSize = sizeof(effect_param_t) + psizePadded + sizeof(downmix_type_t); effect_param_t * const param = (effect_param_t *) malloc(downmixParamSize); CHECK(param != NULL); param->psize = sizeof(downmix_params_t); const downmix_params_t downmixParam = DOWNMIX_PARAM_TYPE; memcpy(param->data, &downmixParam, param->psize); const downmix_type_t downmixType = DOWNMIX_TYPE_FOLD; param->vsize = sizeof(downmix_type_t); memcpy(param->data + psizePadded, &downmixType, param->vsize); replySize = sizeof(int); status = (*mDownmixHandle)->command(mDownmixHandle, EFFECT_CMD_SET_PARAM /* cmdCode */, downmixParamSize /* cmdSize */, param /*pCmdData*/, &replySize, &cmdStatus /*pReplyData*/); free(param); if (status != 0 || cmdStatus != 0) { ALOGE("DownmixerBufferProvider() error %d cmdStatus %d while setting downmix type", status, cmdStatus); EffectRelease(mDownmixHandle); mDownmixHandle = NULL; return; } ALOGV("DownmixerBufferProvider() downmix type set to %d", (int) downmixType); } DownmixerBufferProvider::~DownmixerBufferProvider() { ALOGV("~DownmixerBufferProvider (%p)", this); EffectRelease(mDownmixHandle); mDownmixHandle = NULL; } void DownmixerBufferProvider::copyFrames(void *dst, const void *src, size_t frames) { mDownmixConfig.inputCfg.buffer.frameCount = frames; mDownmixConfig.inputCfg.buffer.raw = const_cast(src); mDownmixConfig.outputCfg.buffer.frameCount = frames; mDownmixConfig.outputCfg.buffer.raw = dst; // may be in-place if src == dst. status_t res = (*mDownmixHandle)->process(mDownmixHandle, &mDownmixConfig.inputCfg.buffer, &mDownmixConfig.outputCfg.buffer); ALOGE_IF(res != OK, "DownmixBufferProvider error %d", res); } /* call once in a pthread_once handler. */ /*static*/ status_t DownmixerBufferProvider::init() { // find multichannel downmix effect if we have to play multichannel content uint32_t numEffects = 0; int ret = EffectQueryNumberEffects(&numEffects); if (ret != 0) { ALOGE("AudioMixer() error %d querying number of effects", ret); return NO_INIT; } ALOGV("EffectQueryNumberEffects() numEffects=%d", numEffects); for (uint32_t i = 0 ; i < numEffects ; i++) { if (EffectQueryEffect(i, &sDwnmFxDesc) == 0) { ALOGV("effect %d is called %s", i, sDwnmFxDesc.name); if (memcmp(&sDwnmFxDesc.type, EFFECT_UIID_DOWNMIX, sizeof(effect_uuid_t)) == 0) { ALOGI("found effect \"%s\" from %s", sDwnmFxDesc.name, sDwnmFxDesc.implementor); sIsMultichannelCapable = true; break; } } } ALOGW_IF(!sIsMultichannelCapable, "unable to find downmix effect"); return NO_INIT; } /*static*/ bool DownmixerBufferProvider::sIsMultichannelCapable = false; /*static*/ effect_descriptor_t DownmixerBufferProvider::sDwnmFxDesc; RemixBufferProvider::RemixBufferProvider(audio_channel_mask_t inputChannelMask, audio_channel_mask_t outputChannelMask, audio_format_t format, size_t bufferFrameCount) : CopyBufferProvider( audio_bytes_per_sample(format) * audio_channel_count_from_out_mask(inputChannelMask), audio_bytes_per_sample(format) * audio_channel_count_from_out_mask(outputChannelMask), bufferFrameCount), mFormat(format), mSampleSize(audio_bytes_per_sample(format)), mInputChannels(audio_channel_count_from_out_mask(inputChannelMask)), mOutputChannels(audio_channel_count_from_out_mask(outputChannelMask)) { ALOGV("RemixBufferProvider(%p)(%#x, %#x, %#x) %zu %zu", this, format, inputChannelMask, outputChannelMask, mInputChannels, mOutputChannels); (void) memcpy_by_index_array_initialization_from_channel_mask( mIdxAry, ARRAY_SIZE(mIdxAry), outputChannelMask, inputChannelMask); } void RemixBufferProvider::copyFrames(void *dst, const void *src, size_t frames) { memcpy_by_index_array(dst, mOutputChannels, src, mInputChannels, mIdxAry, mSampleSize, frames); } ReformatBufferProvider::ReformatBufferProvider(int32_t channelCount, audio_format_t inputFormat, audio_format_t outputFormat, size_t bufferFrameCount) : CopyBufferProvider( channelCount * audio_bytes_per_sample(inputFormat), channelCount * audio_bytes_per_sample(outputFormat), bufferFrameCount), mChannelCount(channelCount), mInputFormat(inputFormat), mOutputFormat(outputFormat) { ALOGV("ReformatBufferProvider(%p)(%u, %#x, %#x)", this, channelCount, inputFormat, outputFormat); } void ReformatBufferProvider::copyFrames(void *dst, const void *src, size_t frames) { memcpy_by_audio_format(dst, mOutputFormat, src, mInputFormat, frames * mChannelCount); } TimestretchBufferProvider::TimestretchBufferProvider(int32_t channelCount, audio_format_t format, uint32_t sampleRate, const AudioPlaybackRate &playbackRate) : mChannelCount(channelCount), mFormat(format), mSampleRate(sampleRate), mFrameSize(channelCount * audio_bytes_per_sample(format)), mLocalBufferFrameCount(0), mLocalBufferData(NULL), mRemaining(0), mSonicStream(sonicCreateStream(sampleRate, mChannelCount)), mFallbackFailErrorShown(false), mAudioPlaybackRateValid(false) { LOG_ALWAYS_FATAL_IF(mSonicStream == NULL, "TimestretchBufferProvider can't allocate Sonic stream"); setPlaybackRate(playbackRate); ALOGV("TimestretchBufferProvider(%p)(%u, %#x, %u %f %f %d %d)", this, channelCount, format, sampleRate, playbackRate.mSpeed, playbackRate.mPitch, playbackRate.mStretchMode, playbackRate.mFallbackMode); mBuffer.frameCount = 0; } TimestretchBufferProvider::~TimestretchBufferProvider() { ALOGV("~TimestretchBufferProvider(%p)", this); sonicDestroyStream(mSonicStream); if (mBuffer.frameCount != 0) { mTrackBufferProvider->releaseBuffer(&mBuffer); } free(mLocalBufferData); } status_t TimestretchBufferProvider::getNextBuffer( AudioBufferProvider::Buffer *pBuffer, int64_t pts) { ALOGV("TimestretchBufferProvider(%p)::getNextBuffer(%p (%zu), %lld)", this, pBuffer, pBuffer->frameCount, pts); // BYPASS //return mTrackBufferProvider->getNextBuffer(pBuffer, pts); // check if previously processed data is sufficient. if (pBuffer->frameCount <= mRemaining) { ALOGV("previous sufficient"); pBuffer->raw = mLocalBufferData; return OK; } // do we need to resize our buffer? if (pBuffer->frameCount > mLocalBufferFrameCount) { void *newmem; if (posix_memalign(&newmem, 32, pBuffer->frameCount * mFrameSize) == OK) { if (mRemaining != 0) { memcpy(newmem, mLocalBufferData, mRemaining * mFrameSize); } free(mLocalBufferData); mLocalBufferData = newmem; mLocalBufferFrameCount = pBuffer->frameCount; } } // need to fetch more data const size_t outputDesired = pBuffer->frameCount - mRemaining; size_t dstAvailable; do { mBuffer.frameCount = mPlaybackRate.mSpeed == AUDIO_TIMESTRETCH_SPEED_NORMAL ? outputDesired : outputDesired * mPlaybackRate.mSpeed + 1; status_t res = mTrackBufferProvider->getNextBuffer(&mBuffer, pts); ALOG_ASSERT(res == OK || mBuffer.frameCount == 0); if (res != OK || mBuffer.frameCount == 0) { // not needed by API spec, but to be safe. ALOGV("upstream provider cannot provide data"); if (mRemaining == 0) { pBuffer->raw = NULL; pBuffer->frameCount = 0; return res; } else { // return partial count pBuffer->raw = mLocalBufferData; pBuffer->frameCount = mRemaining; return OK; } } // time-stretch the data dstAvailable = min(mLocalBufferFrameCount - mRemaining, outputDesired); size_t srcAvailable = mBuffer.frameCount; processFrames((uint8_t*)mLocalBufferData + mRemaining * mFrameSize, &dstAvailable, mBuffer.raw, &srcAvailable); // release all data consumed mBuffer.frameCount = srcAvailable; mTrackBufferProvider->releaseBuffer(&mBuffer); } while (dstAvailable == 0); // try until we get output data or upstream provider fails. // update buffer vars with the actual data processed and return with buffer mRemaining += dstAvailable; pBuffer->raw = mLocalBufferData; pBuffer->frameCount = mRemaining; return OK; } void TimestretchBufferProvider::releaseBuffer(AudioBufferProvider::Buffer *pBuffer) { ALOGV("TimestretchBufferProvider(%p)::releaseBuffer(%p (%zu))", this, pBuffer, pBuffer->frameCount); // BYPASS //return mTrackBufferProvider->releaseBuffer(pBuffer); // LOG_ALWAYS_FATAL_IF(pBuffer->frameCount == 0, "Invalid framecount"); if (pBuffer->frameCount < mRemaining) { memcpy(mLocalBufferData, (uint8_t*)mLocalBufferData + pBuffer->frameCount * mFrameSize, (mRemaining - pBuffer->frameCount) * mFrameSize); mRemaining -= pBuffer->frameCount; } else if (pBuffer->frameCount == mRemaining) { mRemaining = 0; } else { LOG_ALWAYS_FATAL("Releasing more frames(%zu) than available(%zu)", pBuffer->frameCount, mRemaining); } pBuffer->raw = NULL; pBuffer->frameCount = 0; } void TimestretchBufferProvider::reset() { mRemaining = 0; } status_t TimestretchBufferProvider::setPlaybackRate(const AudioPlaybackRate &playbackRate) { mPlaybackRate = playbackRate; mFallbackFailErrorShown = false; sonicSetSpeed(mSonicStream, mPlaybackRate.mSpeed); //TODO: pitch is ignored for now //TODO: optimize: if parameters are the same, don't do any extra computation. mAudioPlaybackRateValid = isAudioPlaybackRateValid(mPlaybackRate); return OK; } void TimestretchBufferProvider::processFrames(void *dstBuffer, size_t *dstFrames, const void *srcBuffer, size_t *srcFrames) { ALOGV("processFrames(%zu %zu) remaining(%zu)", *dstFrames, *srcFrames, mRemaining); // Note dstFrames is the required number of frames. // Ensure consumption from src is as expected. //TODO: add logic to track "very accurate" consumption related to speed, original sampling //rate, actual frames processed. const size_t targetSrc = *dstFrames * mPlaybackRate.mSpeed; if (*srcFrames < targetSrc) { // limit dst frames to that possible *dstFrames = *srcFrames / mPlaybackRate.mSpeed; } else if (*srcFrames > targetSrc + 1) { *srcFrames = targetSrc + 1; } if (!mAudioPlaybackRateValid) { //fallback mode if (*dstFrames > 0) { switch(mPlaybackRate.mFallbackMode) { case AUDIO_TIMESTRETCH_FALLBACK_CUT_REPEAT: if (*dstFrames <= *srcFrames) { size_t copySize = mFrameSize * *dstFrames; memcpy(dstBuffer, srcBuffer, copySize); } else { // cyclically repeat the source. for (size_t count = 0; count < *dstFrames; count += *srcFrames) { size_t remaining = min(*srcFrames, *dstFrames - count); memcpy((uint8_t*)dstBuffer + mFrameSize * count, srcBuffer, mFrameSize * remaining); } } break; case AUDIO_TIMESTRETCH_FALLBACK_DEFAULT: case AUDIO_TIMESTRETCH_FALLBACK_MUTE: memset(dstBuffer,0, mFrameSize * *dstFrames); break; case AUDIO_TIMESTRETCH_FALLBACK_FAIL: default: if(!mFallbackFailErrorShown) { ALOGE("invalid parameters in TimestretchBufferProvider fallbackMode:%d", mPlaybackRate.mFallbackMode); mFallbackFailErrorShown = true; } break; } } } else { switch (mFormat) { case AUDIO_FORMAT_PCM_FLOAT: if (sonicWriteFloatToStream(mSonicStream, (float*)srcBuffer, *srcFrames) != 1) { ALOGE("sonicWriteFloatToStream cannot realloc"); *srcFrames = 0; // cannot consume all of srcBuffer } *dstFrames = sonicReadFloatFromStream(mSonicStream, (float*)dstBuffer, *dstFrames); break; case AUDIO_FORMAT_PCM_16_BIT: if (sonicWriteShortToStream(mSonicStream, (short*)srcBuffer, *srcFrames) != 1) { ALOGE("sonicWriteShortToStream cannot realloc"); *srcFrames = 0; // cannot consume all of srcBuffer } *dstFrames = sonicReadShortFromStream(mSonicStream, (short*)dstBuffer, *dstFrames); break; default: // could also be caught on construction LOG_ALWAYS_FATAL("invalid format %#x for TimestretchBufferProvider", mFormat); } } } // ---------------------------------------------------------------------------- } // namespace android