/*
* Copyright (C) 2017 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 "RecordBufferConverter"
//#define LOG_NDEBUG 0
#include <audio_utils/primitives.h>
#include <audio_utils/format.h>
#include <media/AudioMixer.h> // for UNITY_GAIN_FLOAT
#include <media/AudioResampler.h>
#include <media/BufferProviders.h>
#include <media/RecordBufferConverter.h>
#include <utils/Log.h>
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
#endif
template <typename T>
static inline T max(const T& a, const T& b)
{
return a > b ? a : b;
}
namespace android {
RecordBufferConverter::RecordBufferConverter(
audio_channel_mask_t srcChannelMask, audio_format_t srcFormat,
uint32_t srcSampleRate,
audio_channel_mask_t dstChannelMask, audio_format_t dstFormat,
uint32_t dstSampleRate) :
mSrcChannelMask(AUDIO_CHANNEL_INVALID), // updateParameters will set following vars
// mSrcFormat
// mSrcSampleRate
// mDstChannelMask
// mDstFormat
// mDstSampleRate
// mSrcChannelCount
// mDstChannelCount
// mDstFrameSize
mBuf(NULL), mBufFrames(0), mBufFrameSize(0),
mResampler(NULL),
mIsLegacyDownmix(false),
mIsLegacyUpmix(false),
mRequiresFloat(false),
mInputConverterProvider(NULL)
{
(void)updateParameters(srcChannelMask, srcFormat, srcSampleRate,
dstChannelMask, dstFormat, dstSampleRate);
}
RecordBufferConverter::~RecordBufferConverter() {
free(mBuf);
delete mResampler;
delete mInputConverterProvider;
}
void RecordBufferConverter::reset() {
if (mResampler != NULL) {
mResampler->reset();
}
}
size_t RecordBufferConverter::convert(void *dst,
AudioBufferProvider *provider, size_t frames)
{
if (mInputConverterProvider != NULL) {
mInputConverterProvider->setBufferProvider(provider);
provider = mInputConverterProvider;
}
if (mResampler == NULL) {
ALOGV("NO RESAMPLING sampleRate:%u mSrcFormat:%#x mDstFormat:%#x",
mSrcSampleRate, mSrcFormat, mDstFormat);
AudioBufferProvider::Buffer buffer;
for (size_t i = frames; i > 0; ) {
buffer.frameCount = i;
status_t status = provider->getNextBuffer(&buffer);
if (status != OK || buffer.frameCount == 0) {
frames -= i; // cannot fill request.
break;
}
// format convert to destination buffer
convertNoResampler(dst, buffer.raw, buffer.frameCount);
dst = (int8_t*)dst + buffer.frameCount * mDstFrameSize;
i -= buffer.frameCount;
provider->releaseBuffer(&buffer);
}
} else {
ALOGV("RESAMPLING mSrcSampleRate:%u mDstSampleRate:%u mSrcFormat:%#x mDstFormat:%#x",
mSrcSampleRate, mDstSampleRate, mSrcFormat, mDstFormat);
// reallocate buffer if needed
if (mBufFrameSize != 0 && mBufFrames < frames) {
free(mBuf);
mBufFrames = frames;
(void)posix_memalign(&mBuf, 32, mBufFrames * mBufFrameSize);
}
// resampler accumulates, but we only have one source track
memset(mBuf, 0, frames * mBufFrameSize);
frames = mResampler->resample((int32_t*)mBuf, frames, provider);
// format convert to destination buffer
convertResampler(dst, mBuf, frames);
}
return frames;
}
status_t RecordBufferConverter::updateParameters(
audio_channel_mask_t srcChannelMask, audio_format_t srcFormat,
uint32_t srcSampleRate,
audio_channel_mask_t dstChannelMask, audio_format_t dstFormat,
uint32_t dstSampleRate)
{
// quick evaluation if there is any change.
if (mSrcFormat == srcFormat
&& mSrcChannelMask == srcChannelMask
&& mSrcSampleRate == srcSampleRate
&& mDstFormat == dstFormat
&& mDstChannelMask == dstChannelMask
&& mDstSampleRate == dstSampleRate) {
return NO_ERROR;
}
ALOGV("RecordBufferConverter updateParameters srcMask:%#x dstMask:%#x"
" srcFormat:%#x dstFormat:%#x srcRate:%u dstRate:%u",
srcChannelMask, dstChannelMask, srcFormat, dstFormat, srcSampleRate, dstSampleRate);
const bool valid =
audio_is_input_channel(srcChannelMask)
&& audio_is_input_channel(dstChannelMask)
&& audio_is_valid_format(srcFormat) && audio_is_linear_pcm(srcFormat)
&& audio_is_valid_format(dstFormat) && audio_is_linear_pcm(dstFormat)
&& (srcSampleRate <= dstSampleRate * AUDIO_RESAMPLER_DOWN_RATIO_MAX)
; // no upsampling checks for now
if (!valid) {
return BAD_VALUE;
}
mSrcFormat = srcFormat;
mSrcChannelMask = srcChannelMask;
mSrcSampleRate = srcSampleRate;
mDstFormat = dstFormat;
mDstChannelMask = dstChannelMask;
mDstSampleRate = dstSampleRate;
// compute derived parameters
mSrcChannelCount = audio_channel_count_from_in_mask(srcChannelMask);
mDstChannelCount = audio_channel_count_from_in_mask(dstChannelMask);
mDstFrameSize = mDstChannelCount * audio_bytes_per_sample(mDstFormat);
// do we need to resample?
delete mResampler;
mResampler = NULL;
if (mSrcSampleRate != mDstSampleRate) {
mResampler = AudioResampler::create(AUDIO_FORMAT_PCM_FLOAT,
mSrcChannelCount, mDstSampleRate);
mResampler->setSampleRate(mSrcSampleRate);
mResampler->setVolume(AudioMixer::UNITY_GAIN_FLOAT, AudioMixer::UNITY_GAIN_FLOAT);
}
// are we running legacy channel conversion modes?
mIsLegacyDownmix = (mSrcChannelMask == AUDIO_CHANNEL_IN_STEREO
|| mSrcChannelMask == AUDIO_CHANNEL_IN_FRONT_BACK)
&& mDstChannelMask == AUDIO_CHANNEL_IN_MONO;
mIsLegacyUpmix = mSrcChannelMask == AUDIO_CHANNEL_IN_MONO
&& (mDstChannelMask == AUDIO_CHANNEL_IN_STEREO
|| mDstChannelMask == AUDIO_CHANNEL_IN_FRONT_BACK);
// do we need to process in float?
mRequiresFloat = mResampler != NULL || mIsLegacyDownmix || mIsLegacyUpmix;
// do we need a staging buffer to convert for destination (we can still optimize this)?
// we use mBufFrameSize > 0 to indicate both frame size as well as buffer necessity
if (mResampler != NULL) {
mBufFrameSize = max(mSrcChannelCount, (uint32_t)FCC_2)
* audio_bytes_per_sample(AUDIO_FORMAT_PCM_FLOAT);
} else if (mIsLegacyUpmix || mIsLegacyDownmix) { // legacy modes always float
mBufFrameSize = mDstChannelCount * audio_bytes_per_sample(AUDIO_FORMAT_PCM_FLOAT);
} else if (mSrcChannelMask != mDstChannelMask && mDstFormat != mSrcFormat) {
mBufFrameSize = mDstChannelCount * audio_bytes_per_sample(mSrcFormat);
} else {
mBufFrameSize = 0;
}
mBufFrames = 0; // force the buffer to be resized.
// do we need an input converter buffer provider to give us float?
delete mInputConverterProvider;
mInputConverterProvider = NULL;
if (mRequiresFloat && mSrcFormat != AUDIO_FORMAT_PCM_FLOAT) {
mInputConverterProvider = new ReformatBufferProvider(
audio_channel_count_from_in_mask(mSrcChannelMask),
mSrcFormat,
AUDIO_FORMAT_PCM_FLOAT,
256 /* provider buffer frame count */);
}
// do we need a remixer to do channel mask conversion
if (!mIsLegacyDownmix && !mIsLegacyUpmix && mSrcChannelMask != mDstChannelMask) {
(void) memcpy_by_index_array_initialization_from_channel_mask(
mIdxAry, ARRAY_SIZE(mIdxAry), mDstChannelMask, mSrcChannelMask);
}
return NO_ERROR;
}
void RecordBufferConverter::convertNoResampler(
void *dst, const void *src, size_t frames)
{
// src is native type unless there is legacy upmix or downmix, whereupon it is float.
if (mBufFrameSize != 0 && mBufFrames < frames) {
free(mBuf);
mBufFrames = frames;
(void)posix_memalign(&mBuf, 32, mBufFrames * mBufFrameSize);
}
// do we need to do legacy upmix and downmix?
if (mIsLegacyUpmix || mIsLegacyDownmix) {
void *dstBuf = mBuf != NULL ? mBuf : dst;
if (mIsLegacyUpmix) {
upmix_to_stereo_float_from_mono_float((float *)dstBuf,
(const float *)src, frames);
} else /*mIsLegacyDownmix */ {
downmix_to_mono_float_from_stereo_float((float *)dstBuf,
(const float *)src, frames);
}
if (mBuf != NULL) {
memcpy_by_audio_format(dst, mDstFormat, mBuf, AUDIO_FORMAT_PCM_FLOAT,
frames * mDstChannelCount);
}
return;
}
// do we need to do channel mask conversion?
if (mSrcChannelMask != mDstChannelMask) {
void *dstBuf = mBuf != NULL ? mBuf : dst;
memcpy_by_index_array(dstBuf, mDstChannelCount,
src, mSrcChannelCount, mIdxAry, audio_bytes_per_sample(mSrcFormat), frames);
if (dstBuf == dst) {
return; // format is the same
}
}
// convert to destination buffer
const void *convertBuf = mBuf != NULL ? mBuf : src;
memcpy_by_audio_format(dst, mDstFormat, convertBuf, mSrcFormat,
frames * mDstChannelCount);
}
void RecordBufferConverter::convertResampler(
void *dst, /*not-a-const*/ void *src, size_t frames)
{
// src buffer format is ALWAYS float when entering this routine
if (mIsLegacyUpmix) {
; // mono to stereo already handled by resampler
} else if (mIsLegacyDownmix
|| (mSrcChannelMask == mDstChannelMask && mSrcChannelCount == 1)) {
// the resampler outputs stereo for mono input channel (a feature?)
// must convert to mono
downmix_to_mono_float_from_stereo_float((float *)src,
(const float *)src, frames);
} else if (mSrcChannelMask != mDstChannelMask) {
// convert to mono channel again for channel mask conversion (could be skipped
// with further optimization).
if (mSrcChannelCount == 1) {
downmix_to_mono_float_from_stereo_float((float *)src,
(const float *)src, frames);
}
// convert to destination format (in place, OK as float is larger than other types)
if (mDstFormat != AUDIO_FORMAT_PCM_FLOAT) {
memcpy_by_audio_format(src, mDstFormat, src, AUDIO_FORMAT_PCM_FLOAT,
frames * mSrcChannelCount);
}
// channel convert and save to dst
memcpy_by_index_array(dst, mDstChannelCount,
src, mSrcChannelCount, mIdxAry, audio_bytes_per_sample(mDstFormat), frames);
return;
}
// convert to destination format and save to dst
memcpy_by_audio_format(dst, mDstFormat, src, AUDIO_FORMAT_PCM_FLOAT,
frames * mDstChannelCount);
}
// ----------------------------------------------------------------------------
} // namespace android