mirror of
https://github.com/ioacademy-jikim/multimedia
synced 2025-06-07 16:06:18 +00:00
오디오 믹서수정
This commit is contained in:
parent
2e8959101d
commit
cd8732f3b1
@ -1,24 +1,18 @@
|
|||||||
LOCAL_PATH:= $(call my-dir)
|
LOCAL_PATH:= $(call my-dir)
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
LOCAL_SRC_FILES:= \
|
LOCAL_SRC_FILES:= test-mixer.cpp AudioMixer.cpp.arm BufferProviders.cpp
|
||||||
test-mixer.cpp \
|
|
||||||
AudioMixer.cpp.arm \
|
|
||||||
|
|
||||||
LOCAL_C_INCLUDES := \
|
LOCAL_C_INCLUDES := \
|
||||||
bionic \
|
|
||||||
bionic/libstdc++/include \
|
|
||||||
external/stlport/stlport \
|
|
||||||
$(call include-path-for, audio-effects) \
|
$(call include-path-for, audio-effects) \
|
||||||
$(call include-path-for, audio-utils) \
|
$(call include-path-for, audio-utils) \
|
||||||
frameworks/av/services/audioflinger
|
frameworks/av/services/audioflinger \
|
||||||
|
external/sonic
|
||||||
|
|
||||||
LOCAL_STATIC_LIBRARIES := \
|
LOCAL_STATIC_LIBRARIES := \
|
||||||
libsndfile
|
libsndfile
|
||||||
|
|
||||||
LOCAL_SHARED_LIBRARIES := \
|
LOCAL_SHARED_LIBRARIES := \
|
||||||
libstlport \
|
|
||||||
libeffects \
|
libeffects \
|
||||||
libnbaio \
|
libnbaio \
|
||||||
libcommon_time_client \
|
libcommon_time_client \
|
||||||
@ -27,10 +21,13 @@ LOCAL_SHARED_LIBRARIES := \
|
|||||||
libdl \
|
libdl \
|
||||||
libcutils \
|
libcutils \
|
||||||
libutils \
|
libutils \
|
||||||
liblog
|
liblog \
|
||||||
|
libsonic
|
||||||
|
|
||||||
LOCAL_MODULE:= test-mixer
|
LOCAL_MODULE:= test-mixer
|
||||||
|
|
||||||
LOCAL_MODULE_TAGS := optional
|
LOCAL_MODULE_TAGS := optional
|
||||||
|
|
||||||
|
LOCAL_CXX_STL := libc++
|
||||||
|
|
||||||
include $(BUILD_EXECUTABLE)
|
include $(BUILD_EXECUTABLE)
|
||||||
|
@ -39,9 +39,6 @@
|
|||||||
#include <common_time/local_clock.h>
|
#include <common_time/local_clock.h>
|
||||||
#include <common_time/cc_helper.h>
|
#include <common_time/cc_helper.h>
|
||||||
|
|
||||||
#include <media/EffectsFactoryApi.h>
|
|
||||||
#include <audio_effects/effect_downmix.h>
|
|
||||||
|
|
||||||
#include "AudioMixerOps.h"
|
#include "AudioMixerOps.h"
|
||||||
#include "AudioMixer.h"
|
#include "AudioMixer.h"
|
||||||
|
|
||||||
@ -69,9 +66,16 @@
|
|||||||
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
|
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Set kUseNewMixer to true to use the new mixer engine. Otherwise the
|
// TODO: Move these macro/inlines to a header file.
|
||||||
// original code will be used. This is false for now.
|
template <typename T>
|
||||||
static const bool kUseNewMixer = false;
|
static inline
|
||||||
|
T max(const T& x, const T& y) {
|
||||||
|
return x > y ? x : y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set kUseNewMixer to true to use the new mixer engine always. Otherwise the
|
||||||
|
// original code will be used for stereo sinks, the new mixer for multichannel.
|
||||||
|
static const bool kUseNewMixer = true;
|
||||||
|
|
||||||
// Set kUseFloat to true to allow floating input into the mixer engine.
|
// Set kUseFloat to true to allow floating input into the mixer engine.
|
||||||
// If kUseNewMixer is false, this is ignored or may be overridden internally
|
// If kUseNewMixer is false, this is ignored or may be overridden internally
|
||||||
@ -91,288 +95,6 @@ T min(const T& a, const T& b)
|
|||||||
return a < b ? a : b;
|
return a < b ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioMixer::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;
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioMixer::CopyBufferProvider::~CopyBufferProvider()
|
|
||||||
{
|
|
||||||
ALOGV("~CopyBufferProvider(%p)", this);
|
|
||||||
if (mBuffer.frameCount != 0) {
|
|
||||||
mTrackBufferProvider->releaseBuffer(&mBuffer);
|
|
||||||
}
|
|
||||||
free(mLocalBufferData);
|
|
||||||
}
|
|
||||||
|
|
||||||
status_t AudioMixer::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 AudioMixer::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 AudioMixer::CopyBufferProvider::reset()
|
|
||||||
{
|
|
||||||
if (mBuffer.frameCount != 0) {
|
|
||||||
mTrackBufferProvider->releaseBuffer(&mBuffer);
|
|
||||||
}
|
|
||||||
mConsumed = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioMixer::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);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioMixer::DownmixerBufferProvider::~DownmixerBufferProvider()
|
|
||||||
{
|
|
||||||
ALOGV("~DownmixerBufferProvider (%p)", this);
|
|
||||||
EffectRelease(mDownmixHandle);
|
|
||||||
mDownmixHandle = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioMixer::DownmixerBufferProvider::copyFrames(void *dst, const void *src, size_t frames)
|
|
||||||
{
|
|
||||||
mDownmixConfig.inputCfg.buffer.frameCount = frames;
|
|
||||||
mDownmixConfig.inputCfg.buffer.raw = const_cast<void *>(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 AudioMixer::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 AudioMixer::DownmixerBufferProvider::sIsMultichannelCapable = false;
|
|
||||||
/*static*/ effect_descriptor_t AudioMixer::DownmixerBufferProvider::sDwnmFxDesc;
|
|
||||||
|
|
||||||
AudioMixer::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);
|
|
||||||
// TODO: consider channel representation in index array formulation
|
|
||||||
// We ignore channel representation, and just use the bits.
|
|
||||||
memcpy_by_index_array_initialization(mIdxAry, ARRAY_SIZE(mIdxAry),
|
|
||||||
audio_channel_mask_get_bits(outputChannelMask),
|
|
||||||
audio_channel_mask_get_bits(inputChannelMask));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioMixer::RemixBufferProvider::copyFrames(void *dst, const void *src, size_t frames)
|
|
||||||
{
|
|
||||||
memcpy_by_index_array(dst, mOutputChannels,
|
|
||||||
src, mInputChannels, mIdxAry, mSampleSize, frames);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioMixer::ReformatBufferProvider::ReformatBufferProvider(int32_t channels,
|
|
||||||
audio_format_t inputFormat, audio_format_t outputFormat,
|
|
||||||
size_t bufferFrameCount) :
|
|
||||||
CopyBufferProvider(
|
|
||||||
channels * audio_bytes_per_sample(inputFormat),
|
|
||||||
channels * audio_bytes_per_sample(outputFormat),
|
|
||||||
bufferFrameCount),
|
|
||||||
mChannels(channels),
|
|
||||||
mInputFormat(inputFormat),
|
|
||||||
mOutputFormat(outputFormat)
|
|
||||||
{
|
|
||||||
ALOGV("ReformatBufferProvider(%p)(%d, %#x, %#x)", this, channels, inputFormat, outputFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioMixer::ReformatBufferProvider::copyFrames(void *dst, const void *src, size_t frames)
|
|
||||||
{
|
|
||||||
memcpy_by_audio_format(dst, mOutputFormat, src, mInputFormat, frames * mChannels);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Ensure mConfiguredNames bitmask is initialized properly on all architectures.
|
// Ensure mConfiguredNames bitmask is initialized properly on all architectures.
|
||||||
@ -407,6 +129,7 @@ AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate, uint32_t maxNumTr
|
|||||||
t->resampler = NULL;
|
t->resampler = NULL;
|
||||||
t->downmixerBufferProvider = NULL;
|
t->downmixerBufferProvider = NULL;
|
||||||
t->mReformatBufferProvider = NULL;
|
t->mReformatBufferProvider = NULL;
|
||||||
|
t->mTimestretchBufferProvider = NULL;
|
||||||
t++;
|
t++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,6 +142,7 @@ AudioMixer::~AudioMixer()
|
|||||||
delete t->resampler;
|
delete t->resampler;
|
||||||
delete t->downmixerBufferProvider;
|
delete t->downmixerBufferProvider;
|
||||||
delete t->mReformatBufferProvider;
|
delete t->mReformatBufferProvider;
|
||||||
|
delete t->mTimestretchBufferProvider;
|
||||||
t++;
|
t++;
|
||||||
}
|
}
|
||||||
delete [] mState.outputTemp;
|
delete [] mState.outputTemp;
|
||||||
@ -430,6 +154,10 @@ void AudioMixer::setLog(NBLog::Writer *log)
|
|||||||
mState.mLog = log;
|
mState.mLog = log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline audio_format_t selectMixerInFormat(audio_format_t inputFormat __unused) {
|
||||||
|
return kUseFloat && kUseNewMixer ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
int AudioMixer::getTrackName(audio_channel_mask_t channelMask,
|
int AudioMixer::getTrackName(audio_channel_mask_t channelMask,
|
||||||
audio_format_t format, int sessionId)
|
audio_format_t format, int sessionId)
|
||||||
{
|
{
|
||||||
@ -492,24 +220,25 @@ int AudioMixer::getTrackName(audio_channel_mask_t channelMask,
|
|||||||
t->mInputBufferProvider = NULL;
|
t->mInputBufferProvider = NULL;
|
||||||
t->mReformatBufferProvider = NULL;
|
t->mReformatBufferProvider = NULL;
|
||||||
t->downmixerBufferProvider = NULL;
|
t->downmixerBufferProvider = NULL;
|
||||||
|
t->mPostDownmixReformatBufferProvider = NULL;
|
||||||
|
t->mTimestretchBufferProvider = NULL;
|
||||||
t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT;
|
t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT;
|
||||||
t->mFormat = format;
|
t->mFormat = format;
|
||||||
t->mMixerInFormat = kUseFloat && kUseNewMixer
|
t->mMixerInFormat = selectMixerInFormat(format);
|
||||||
? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
|
t->mDownmixRequiresFormat = AUDIO_FORMAT_INVALID; // no format required
|
||||||
t->mMixerChannelMask = audio_channel_mask_from_representation_and_bits(
|
t->mMixerChannelMask = audio_channel_mask_from_representation_and_bits(
|
||||||
AUDIO_CHANNEL_REPRESENTATION_POSITION, AUDIO_CHANNEL_OUT_STEREO);
|
AUDIO_CHANNEL_REPRESENTATION_POSITION, AUDIO_CHANNEL_OUT_STEREO);
|
||||||
t->mMixerChannelCount = audio_channel_count_from_out_mask(t->mMixerChannelMask);
|
t->mMixerChannelCount = audio_channel_count_from_out_mask(t->mMixerChannelMask);
|
||||||
|
t->mPlaybackRate = AUDIO_PLAYBACK_RATE_DEFAULT;
|
||||||
// Check the downmixing (or upmixing) requirements.
|
// Check the downmixing (or upmixing) requirements.
|
||||||
status_t status = initTrackDownmix(t, n);
|
status_t status = t->prepareForDownmix();
|
||||||
if (status != OK) {
|
if (status != OK) {
|
||||||
ALOGE("AudioMixer::getTrackName invalid channelMask (%#x)", channelMask);
|
ALOGE("AudioMixer::getTrackName invalid channelMask (%#x)", channelMask);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// initTrackDownmix() may change the input format requirement.
|
// prepareForDownmix() may change mDownmixRequiresFormat
|
||||||
// If you desire floating point input to the mixer, it may change
|
|
||||||
// to integer because the downmixer requires integer to process.
|
|
||||||
ALOGVV("mMixerFormat:%#x mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat);
|
ALOGVV("mMixerFormat:%#x mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat);
|
||||||
prepareTrackForReformat(t, n);
|
t->prepareForReformat();
|
||||||
mTrackNames |= 1 << n;
|
mTrackNames |= 1 << n;
|
||||||
return TRACK0 + n;
|
return TRACK0 + n;
|
||||||
}
|
}
|
||||||
@ -526,7 +255,7 @@ void AudioMixer::invalidateState(uint32_t mask)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Called when channel masks have changed for a track name
|
// Called when channel masks have changed for a track name
|
||||||
// TODO: Fix Downmixbufferprofider not to (possibly) change mixer input format,
|
// TODO: Fix DownmixerBufferProvider not to (possibly) change mixer input format,
|
||||||
// which will simplify this logic.
|
// which will simplify this logic.
|
||||||
bool AudioMixer::setChannelMasks(int name,
|
bool AudioMixer::setChannelMasks(int name,
|
||||||
audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask) {
|
audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask) {
|
||||||
@ -551,21 +280,18 @@ bool AudioMixer::setChannelMasks(int name,
|
|||||||
|
|
||||||
// channel masks have changed, does this track need a downmixer?
|
// channel masks have changed, does this track need a downmixer?
|
||||||
// update to try using our desired format (if we aren't already using it)
|
// update to try using our desired format (if we aren't already using it)
|
||||||
const audio_format_t prevMixerInFormat = track.mMixerInFormat;
|
const audio_format_t prevDownmixerFormat = track.mDownmixRequiresFormat;
|
||||||
track.mMixerInFormat = kUseFloat && kUseNewMixer
|
const status_t status = mState.tracks[name].prepareForDownmix();
|
||||||
? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
|
|
||||||
const status_t status = initTrackDownmix(&mState.tracks[name], name);
|
|
||||||
ALOGE_IF(status != OK,
|
ALOGE_IF(status != OK,
|
||||||
"initTrackDownmix error %d, track channel mask %#x, mixer channel mask %#x",
|
"prepareForDownmix error %d, track channel mask %#x, mixer channel mask %#x",
|
||||||
status, track.channelMask, track.mMixerChannelMask);
|
status, track.channelMask, track.mMixerChannelMask);
|
||||||
|
|
||||||
const bool mixerInFormatChanged = prevMixerInFormat != track.mMixerInFormat;
|
if (prevDownmixerFormat != track.mDownmixRequiresFormat) {
|
||||||
if (mixerInFormatChanged) {
|
track.prepareForReformat(); // because of downmixer, track format may change!
|
||||||
prepareTrackForReformat(&track, name); // because of downmixer, track format may change!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.resampler && (mixerInFormatChanged || mixerChannelCountChanged)) {
|
if (track.resampler && mixerChannelCountChanged) {
|
||||||
// resampler input format or channels may have changed.
|
// resampler channels may have changed.
|
||||||
const uint32_t resetToSampleRate = track.sampleRate;
|
const uint32_t resetToSampleRate = track.sampleRate;
|
||||||
delete track.resampler;
|
delete track.resampler;
|
||||||
track.resampler = NULL;
|
track.resampler = NULL;
|
||||||
@ -576,99 +302,129 @@ bool AudioMixer::setChannelMasks(int name,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
status_t AudioMixer::initTrackDownmix(track_t* pTrack, int trackName)
|
void AudioMixer::track_t::unprepareForDownmix() {
|
||||||
{
|
ALOGV("AudioMixer::unprepareForDownmix(%p)", this);
|
||||||
// Only remix (upmix or downmix) if the track and mixer/device channel masks
|
|
||||||
// are not the same and not handled internally, as mono -> stereo currently is.
|
|
||||||
if (pTrack->channelMask != pTrack->mMixerChannelMask
|
|
||||||
&& !(pTrack->channelMask == AUDIO_CHANNEL_OUT_MONO
|
|
||||||
&& pTrack->mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO)) {
|
|
||||||
return prepareTrackForDownmix(pTrack, trackName);
|
|
||||||
}
|
|
||||||
// no remix necessary
|
|
||||||
unprepareTrackForDownmix(pTrack, trackName);
|
|
||||||
return NO_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioMixer::unprepareTrackForDownmix(track_t* pTrack, int trackName __unused) {
|
mDownmixRequiresFormat = AUDIO_FORMAT_INVALID;
|
||||||
ALOGV("AudioMixer::unprepareTrackForDownmix(%d)", trackName);
|
if (downmixerBufferProvider != NULL) {
|
||||||
|
|
||||||
if (pTrack->downmixerBufferProvider != NULL) {
|
|
||||||
// this track had previously been configured with a downmixer, delete it
|
// this track had previously been configured with a downmixer, delete it
|
||||||
ALOGV(" deleting old downmixer");
|
ALOGV(" deleting old downmixer");
|
||||||
delete pTrack->downmixerBufferProvider;
|
delete downmixerBufferProvider;
|
||||||
pTrack->downmixerBufferProvider = NULL;
|
downmixerBufferProvider = NULL;
|
||||||
reconfigureBufferProviders(pTrack);
|
reconfigureBufferProviders();
|
||||||
} else {
|
} else {
|
||||||
ALOGV(" nothing to do, no downmixer to delete");
|
ALOGV(" nothing to do, no downmixer to delete");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
status_t AudioMixer::prepareTrackForDownmix(track_t* pTrack, int trackName)
|
status_t AudioMixer::track_t::prepareForDownmix()
|
||||||
{
|
{
|
||||||
ALOGV("AudioMixer::prepareTrackForDownmix(%d) with mask 0x%x", trackName, pTrack->channelMask);
|
ALOGV("AudioMixer::prepareForDownmix(%p) with mask 0x%x",
|
||||||
|
this, channelMask);
|
||||||
|
|
||||||
// discard the previous downmixer if there was one
|
// discard the previous downmixer if there was one
|
||||||
unprepareTrackForDownmix(pTrack, trackName);
|
unprepareForDownmix();
|
||||||
if (DownmixerBufferProvider::isMultichannelCapable()) {
|
// MONO_HACK Only remix (upmix or downmix) if the track and mixer/device channel masks
|
||||||
DownmixerBufferProvider* pDbp = new DownmixerBufferProvider(pTrack->channelMask,
|
// are not the same and not handled internally, as mono -> stereo currently is.
|
||||||
pTrack->mMixerChannelMask,
|
if (channelMask == mMixerChannelMask
|
||||||
AUDIO_FORMAT_PCM_16_BIT /* TODO: use pTrack->mMixerInFormat, now only PCM 16 */,
|
|| (channelMask == AUDIO_CHANNEL_OUT_MONO
|
||||||
pTrack->sampleRate, pTrack->sessionId, kCopyBufferFrameCount);
|
&& mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO)) {
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
// DownmixerBufferProvider is only used for position masks.
|
||||||
|
if (audio_channel_mask_get_representation(channelMask)
|
||||||
|
== AUDIO_CHANNEL_REPRESENTATION_POSITION
|
||||||
|
&& DownmixerBufferProvider::isMultichannelCapable()) {
|
||||||
|
DownmixerBufferProvider* pDbp = new DownmixerBufferProvider(channelMask,
|
||||||
|
mMixerChannelMask,
|
||||||
|
AUDIO_FORMAT_PCM_16_BIT /* TODO: use mMixerInFormat, now only PCM 16 */,
|
||||||
|
sampleRate, sessionId, kCopyBufferFrameCount);
|
||||||
|
|
||||||
if (pDbp->isValid()) { // if constructor completed properly
|
if (pDbp->isValid()) { // if constructor completed properly
|
||||||
pTrack->mMixerInFormat = AUDIO_FORMAT_PCM_16_BIT; // PCM 16 bit required for downmix
|
mDownmixRequiresFormat = AUDIO_FORMAT_PCM_16_BIT; // PCM 16 bit required for downmix
|
||||||
pTrack->downmixerBufferProvider = pDbp;
|
downmixerBufferProvider = pDbp;
|
||||||
reconfigureBufferProviders(pTrack);
|
reconfigureBufferProviders();
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
delete pDbp;
|
delete pDbp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Effect downmixer does not accept the channel conversion. Let's use our remixer.
|
// Effect downmixer does not accept the channel conversion. Let's use our remixer.
|
||||||
RemixBufferProvider* pRbp = new RemixBufferProvider(pTrack->channelMask,
|
RemixBufferProvider* pRbp = new RemixBufferProvider(channelMask,
|
||||||
pTrack->mMixerChannelMask, pTrack->mMixerInFormat, kCopyBufferFrameCount);
|
mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount);
|
||||||
// Remix always finds a conversion whereas Downmixer effect above may fail.
|
// Remix always finds a conversion whereas Downmixer effect above may fail.
|
||||||
pTrack->downmixerBufferProvider = pRbp;
|
downmixerBufferProvider = pRbp;
|
||||||
reconfigureBufferProviders(pTrack);
|
reconfigureBufferProviders();
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMixer::unprepareTrackForReformat(track_t* pTrack, int trackName __unused) {
|
void AudioMixer::track_t::unprepareForReformat() {
|
||||||
ALOGV("AudioMixer::unprepareTrackForReformat(%d)", trackName);
|
ALOGV("AudioMixer::unprepareForReformat(%p)", this);
|
||||||
if (pTrack->mReformatBufferProvider != NULL) {
|
bool requiresReconfigure = false;
|
||||||
delete pTrack->mReformatBufferProvider;
|
if (mReformatBufferProvider != NULL) {
|
||||||
pTrack->mReformatBufferProvider = NULL;
|
delete mReformatBufferProvider;
|
||||||
reconfigureBufferProviders(pTrack);
|
mReformatBufferProvider = NULL;
|
||||||
|
requiresReconfigure = true;
|
||||||
|
}
|
||||||
|
if (mPostDownmixReformatBufferProvider != NULL) {
|
||||||
|
delete mPostDownmixReformatBufferProvider;
|
||||||
|
mPostDownmixReformatBufferProvider = NULL;
|
||||||
|
requiresReconfigure = true;
|
||||||
|
}
|
||||||
|
if (requiresReconfigure) {
|
||||||
|
reconfigureBufferProviders();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
status_t AudioMixer::prepareTrackForReformat(track_t* pTrack, int trackName)
|
status_t AudioMixer::track_t::prepareForReformat()
|
||||||
{
|
{
|
||||||
ALOGV("AudioMixer::prepareTrackForReformat(%d) with format %#x", trackName, pTrack->mFormat);
|
ALOGV("AudioMixer::prepareForReformat(%p) with format %#x", this, mFormat);
|
||||||
// discard the previous reformatter if there was one
|
// discard previous reformatters
|
||||||
unprepareTrackForReformat(pTrack, trackName);
|
unprepareForReformat();
|
||||||
// only configure reformatter if needed
|
// only configure reformatters as needed
|
||||||
if (pTrack->mFormat != pTrack->mMixerInFormat) {
|
const audio_format_t targetFormat = mDownmixRequiresFormat != AUDIO_FORMAT_INVALID
|
||||||
pTrack->mReformatBufferProvider = new ReformatBufferProvider(
|
? mDownmixRequiresFormat : mMixerInFormat;
|
||||||
audio_channel_count_from_out_mask(pTrack->channelMask),
|
bool requiresReconfigure = false;
|
||||||
pTrack->mFormat, pTrack->mMixerInFormat,
|
if (mFormat != targetFormat) {
|
||||||
|
mReformatBufferProvider = new ReformatBufferProvider(
|
||||||
|
audio_channel_count_from_out_mask(channelMask),
|
||||||
|
mFormat,
|
||||||
|
targetFormat,
|
||||||
kCopyBufferFrameCount);
|
kCopyBufferFrameCount);
|
||||||
reconfigureBufferProviders(pTrack);
|
requiresReconfigure = true;
|
||||||
|
}
|
||||||
|
if (targetFormat != mMixerInFormat) {
|
||||||
|
mPostDownmixReformatBufferProvider = new ReformatBufferProvider(
|
||||||
|
audio_channel_count_from_out_mask(mMixerChannelMask),
|
||||||
|
targetFormat,
|
||||||
|
mMixerInFormat,
|
||||||
|
kCopyBufferFrameCount);
|
||||||
|
requiresReconfigure = true;
|
||||||
|
}
|
||||||
|
if (requiresReconfigure) {
|
||||||
|
reconfigureBufferProviders();
|
||||||
}
|
}
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMixer::reconfigureBufferProviders(track_t* pTrack)
|
void AudioMixer::track_t::reconfigureBufferProviders()
|
||||||
{
|
{
|
||||||
pTrack->bufferProvider = pTrack->mInputBufferProvider;
|
bufferProvider = mInputBufferProvider;
|
||||||
if (pTrack->mReformatBufferProvider) {
|
if (mReformatBufferProvider) {
|
||||||
pTrack->mReformatBufferProvider->setBufferProvider(pTrack->bufferProvider);
|
mReformatBufferProvider->setBufferProvider(bufferProvider);
|
||||||
pTrack->bufferProvider = pTrack->mReformatBufferProvider;
|
bufferProvider = mReformatBufferProvider;
|
||||||
}
|
}
|
||||||
if (pTrack->downmixerBufferProvider) {
|
if (downmixerBufferProvider) {
|
||||||
pTrack->downmixerBufferProvider->setBufferProvider(pTrack->bufferProvider);
|
downmixerBufferProvider->setBufferProvider(bufferProvider);
|
||||||
pTrack->bufferProvider = pTrack->downmixerBufferProvider;
|
bufferProvider = downmixerBufferProvider;
|
||||||
|
}
|
||||||
|
if (mPostDownmixReformatBufferProvider) {
|
||||||
|
mPostDownmixReformatBufferProvider->setBufferProvider(bufferProvider);
|
||||||
|
bufferProvider = mPostDownmixReformatBufferProvider;
|
||||||
|
}
|
||||||
|
if (mTimestretchBufferProvider) {
|
||||||
|
mTimestretchBufferProvider->setBufferProvider(bufferProvider);
|
||||||
|
bufferProvider = mTimestretchBufferProvider;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -687,10 +443,12 @@ void AudioMixer::deleteTrackName(int name)
|
|||||||
delete track.resampler;
|
delete track.resampler;
|
||||||
track.resampler = NULL;
|
track.resampler = NULL;
|
||||||
// delete the downmixer
|
// delete the downmixer
|
||||||
unprepareTrackForDownmix(&mState.tracks[name], name);
|
mState.tracks[name].unprepareForDownmix();
|
||||||
// delete the reformatter
|
// delete the reformatter
|
||||||
unprepareTrackForReformat(&mState.tracks[name], name);
|
mState.tracks[name].unprepareForReformat();
|
||||||
|
// delete the timestretch provider
|
||||||
|
delete track.mTimestretchBufferProvider;
|
||||||
|
track.mTimestretchBufferProvider = NULL;
|
||||||
mTrackNames &= ~(1<<name);
|
mTrackNames &= ~(1<<name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -748,41 +506,99 @@ void AudioMixer::disable(int name)
|
|||||||
static inline bool setVolumeRampVariables(float newVolume, int32_t ramp,
|
static inline bool setVolumeRampVariables(float newVolume, int32_t ramp,
|
||||||
int16_t *pIntSetVolume, int32_t *pIntPrevVolume, int32_t *pIntVolumeInc,
|
int16_t *pIntSetVolume, int32_t *pIntPrevVolume, int32_t *pIntVolumeInc,
|
||||||
float *pSetVolume, float *pPrevVolume, float *pVolumeInc) {
|
float *pSetVolume, float *pPrevVolume, float *pVolumeInc) {
|
||||||
|
// check floating point volume to see if it is identical to the previously
|
||||||
|
// set volume.
|
||||||
|
// We do not use a tolerance here (and reject changes too small)
|
||||||
|
// as it may be confusing to use a different value than the one set.
|
||||||
|
// If the resulting volume is too small to ramp, it is a direct set of the volume.
|
||||||
if (newVolume == *pSetVolume) {
|
if (newVolume == *pSetVolume) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
/* set the floating point volume variables */
|
if (newVolume < 0) {
|
||||||
if (ramp != 0) {
|
newVolume = 0; // should not have negative volumes
|
||||||
*pVolumeInc = (newVolume - *pSetVolume) / ramp;
|
|
||||||
*pPrevVolume = *pSetVolume;
|
|
||||||
} else {
|
} else {
|
||||||
*pVolumeInc = 0;
|
switch (fpclassify(newVolume)) {
|
||||||
*pPrevVolume = newVolume;
|
case FP_SUBNORMAL:
|
||||||
|
case FP_NAN:
|
||||||
|
newVolume = 0;
|
||||||
|
break;
|
||||||
|
case FP_ZERO:
|
||||||
|
break; // zero volume is fine
|
||||||
|
case FP_INFINITE:
|
||||||
|
// Infinite volume could be handled consistently since
|
||||||
|
// floating point math saturates at infinities,
|
||||||
|
// but we limit volume to unity gain float.
|
||||||
|
// ramp = 0; break;
|
||||||
|
//
|
||||||
|
newVolume = AudioMixer::UNITY_GAIN_FLOAT;
|
||||||
|
break;
|
||||||
|
case FP_NORMAL:
|
||||||
|
default:
|
||||||
|
// Floating point does not have problems with overflow wrap
|
||||||
|
// that integer has. However, we limit the volume to
|
||||||
|
// unity gain here.
|
||||||
|
// TODO: Revisit the volume limitation and perhaps parameterize.
|
||||||
|
if (newVolume > AudioMixer::UNITY_GAIN_FLOAT) {
|
||||||
|
newVolume = AudioMixer::UNITY_GAIN_FLOAT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*pSetVolume = newVolume;
|
|
||||||
|
|
||||||
/* set the legacy integer volume variables */
|
// set floating point volume ramp
|
||||||
int32_t intVolume = newVolume * AudioMixer::UNITY_GAIN_INT;
|
if (ramp != 0) {
|
||||||
if (intVolume > AudioMixer::UNITY_GAIN_INT) {
|
// when the ramp completes, *pPrevVolume is set to *pSetVolume, so there
|
||||||
intVolume = AudioMixer::UNITY_GAIN_INT;
|
// is no computational mismatch; hence equality is checked here.
|
||||||
} else if (intVolume < 0) {
|
ALOGD_IF(*pPrevVolume != *pSetVolume, "previous float ramp hasn't finished,"
|
||||||
ALOGE("negative volume %.7g", newVolume);
|
" prev:%f set_to:%f", *pPrevVolume, *pSetVolume);
|
||||||
intVolume = 0; // should never happen, but for safety check.
|
const float inc = (newVolume - *pPrevVolume) / ramp; // could be inf, nan, subnormal
|
||||||
|
const float maxv = max(newVolume, *pPrevVolume); // could be inf, cannot be nan, subnormal
|
||||||
|
|
||||||
|
if (isnormal(inc) // inc must be a normal number (no subnormals, infinite, nan)
|
||||||
|
&& maxv + inc != maxv) { // inc must make forward progress
|
||||||
|
*pVolumeInc = inc;
|
||||||
|
// ramp is set now.
|
||||||
|
// Note: if newVolume is 0, then near the end of the ramp,
|
||||||
|
// it may be possible that the ramped volume may be subnormal or
|
||||||
|
// temporarily negative by a small amount or subnormal due to floating
|
||||||
|
// point inaccuracies.
|
||||||
|
} else {
|
||||||
|
ramp = 0; // ramp not allowed
|
||||||
}
|
}
|
||||||
if (intVolume == *pIntSetVolume) {
|
}
|
||||||
*pIntVolumeInc = 0;
|
|
||||||
/* TODO: integer/float workaround: ignore floating volume ramp */
|
// compute and check integer volume, no need to check negative values
|
||||||
|
// The integer volume is limited to "unity_gain" to avoid wrapping and other
|
||||||
|
// audio artifacts, so it never reaches the range limit of U4.28.
|
||||||
|
// We safely use signed 16 and 32 bit integers here.
|
||||||
|
const float scaledVolume = newVolume * AudioMixer::UNITY_GAIN_INT; // not neg, subnormal, nan
|
||||||
|
const int32_t intVolume = (scaledVolume >= (float)AudioMixer::UNITY_GAIN_INT) ?
|
||||||
|
AudioMixer::UNITY_GAIN_INT : (int32_t)scaledVolume;
|
||||||
|
|
||||||
|
// set integer volume ramp
|
||||||
|
if (ramp != 0) {
|
||||||
|
// integer volume is U4.12 (to use 16 bit multiplies), but ramping uses U4.28.
|
||||||
|
// when the ramp completes, *pIntPrevVolume is set to *pIntSetVolume << 16, so there
|
||||||
|
// is no computational mismatch; hence equality is checked here.
|
||||||
|
ALOGD_IF(*pIntPrevVolume != *pIntSetVolume << 16, "previous int ramp hasn't finished,"
|
||||||
|
" prev:%d set_to:%d", *pIntPrevVolume, *pIntSetVolume << 16);
|
||||||
|
const int32_t inc = ((intVolume << 16) - *pIntPrevVolume) / ramp;
|
||||||
|
|
||||||
|
if (inc != 0) { // inc must make forward progress
|
||||||
|
*pIntVolumeInc = inc;
|
||||||
|
} else {
|
||||||
|
ramp = 0; // ramp not allowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no ramp, or ramp not allowed, then clear float and integer increments
|
||||||
|
if (ramp == 0) {
|
||||||
*pVolumeInc = 0;
|
*pVolumeInc = 0;
|
||||||
*pPrevVolume = newVolume;
|
*pPrevVolume = newVolume;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (ramp != 0) {
|
|
||||||
*pIntVolumeInc = ((intVolume - *pIntSetVolume) << 16) / ramp;
|
|
||||||
*pIntPrevVolume = (*pIntVolumeInc == 0 ? intVolume : *pIntSetVolume) << 16;
|
|
||||||
} else {
|
|
||||||
*pIntVolumeInc = 0;
|
*pIntVolumeInc = 0;
|
||||||
*pIntPrevVolume = intVolume << 16;
|
*pIntPrevVolume = intVolume << 16;
|
||||||
}
|
}
|
||||||
|
*pSetVolume = newVolume;
|
||||||
*pIntSetVolume = intVolume;
|
*pIntSetVolume = intVolume;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -828,7 +644,7 @@ void AudioMixer::setParameter(int name, int target, int param, void *value)
|
|||||||
ALOG_ASSERT(audio_is_linear_pcm(format), "Invalid format %#x", format);
|
ALOG_ASSERT(audio_is_linear_pcm(format), "Invalid format %#x", format);
|
||||||
track.mFormat = format;
|
track.mFormat = format;
|
||||||
ALOGV("setParameter(TRACK, FORMAT, %#x)", format);
|
ALOGV("setParameter(TRACK, FORMAT, %#x)", format);
|
||||||
prepareTrackForReformat(&track, name);
|
track.prepareForReformat();
|
||||||
invalidateState(1 << name);
|
invalidateState(1 << name);
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
@ -912,6 +728,28 @@ void AudioMixer::setParameter(int name, int target, int param, void *value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case TIMESTRETCH:
|
||||||
|
switch (param) {
|
||||||
|
case PLAYBACK_RATE: {
|
||||||
|
const AudioPlaybackRate *playbackRate =
|
||||||
|
reinterpret_cast<AudioPlaybackRate*>(value);
|
||||||
|
ALOGW_IF(!isAudioPlaybackRateValid(*playbackRate),
|
||||||
|
"bad parameters speed %f, pitch %f",playbackRate->mSpeed,
|
||||||
|
playbackRate->mPitch);
|
||||||
|
if (track.setPlaybackRate(*playbackRate)) {
|
||||||
|
ALOGV("setParameter(TIMESTRETCH, PLAYBACK_RATE, STRETCH_MODE, FALLBACK_MODE "
|
||||||
|
"%f %f %d %d",
|
||||||
|
playbackRate->mSpeed,
|
||||||
|
playbackRate->mPitch,
|
||||||
|
playbackRate->mStretchMode,
|
||||||
|
playbackRate->mFallbackMode);
|
||||||
|
// invalidateState(1 << name);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
LOG_ALWAYS_FATAL("setParameter timestretch: bad param %d", param);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
LOG_ALWAYS_FATAL("setParameter: bad target %d", target);
|
LOG_ALWAYS_FATAL("setParameter: bad target %d", target);
|
||||||
@ -931,11 +769,10 @@ bool AudioMixer::track_t::setResampler(uint32_t trackSampleRate, uint32_t devSam
|
|||||||
// FIXME this is flawed for dynamic sample rates, as we choose the resampler
|
// FIXME this is flawed for dynamic sample rates, as we choose the resampler
|
||||||
// quality level based on the initial ratio, but that could change later.
|
// quality level based on the initial ratio, but that could change later.
|
||||||
// Should have a way to distinguish tracks with static ratios vs. dynamic ratios.
|
// Should have a way to distinguish tracks with static ratios vs. dynamic ratios.
|
||||||
if (!((trackSampleRate == 44100 && devSampleRate == 48000) ||
|
if (isMusicRate(trackSampleRate)) {
|
||||||
(trackSampleRate == 48000 && devSampleRate == 44100))) {
|
|
||||||
quality = AudioResampler::DYN_LOW_QUALITY;
|
|
||||||
} else {
|
|
||||||
quality = AudioResampler::DEFAULT_QUALITY;
|
quality = AudioResampler::DEFAULT_QUALITY;
|
||||||
|
} else {
|
||||||
|
quality = AudioResampler::DYN_LOW_QUALITY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove MONO_HACK. Resampler sees #channels after the downmixer
|
// TODO: Remove MONO_HACK. Resampler sees #channels after the downmixer
|
||||||
@ -957,6 +794,30 @@ bool AudioMixer::track_t::setResampler(uint32_t trackSampleRate, uint32_t devSam
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AudioMixer::track_t::setPlaybackRate(const AudioPlaybackRate &playbackRate)
|
||||||
|
{
|
||||||
|
if ((mTimestretchBufferProvider == NULL &&
|
||||||
|
fabs(playbackRate.mSpeed - mPlaybackRate.mSpeed) < AUDIO_TIMESTRETCH_SPEED_MIN_DELTA &&
|
||||||
|
fabs(playbackRate.mPitch - mPlaybackRate.mPitch) < AUDIO_TIMESTRETCH_PITCH_MIN_DELTA) ||
|
||||||
|
isAudioPlaybackRateEqual(playbackRate, mPlaybackRate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mPlaybackRate = playbackRate;
|
||||||
|
if (mTimestretchBufferProvider == NULL) {
|
||||||
|
// TODO: Remove MONO_HACK. Resampler sees #channels after the downmixer
|
||||||
|
// but if none exists, it is the channel count (1 for mono).
|
||||||
|
const int timestretchChannelCount = downmixerBufferProvider != NULL
|
||||||
|
? mMixerChannelCount : channelCount;
|
||||||
|
mTimestretchBufferProvider = new TimestretchBufferProvider(timestretchChannelCount,
|
||||||
|
mMixerInFormat, sampleRate, playbackRate);
|
||||||
|
reconfigureBufferProviders();
|
||||||
|
} else {
|
||||||
|
reinterpret_cast<TimestretchBufferProvider*>(mTimestretchBufferProvider)
|
||||||
|
->setPlaybackRate(playbackRate);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* Checks to see if the volume ramp has completed and clears the increment
|
/* Checks to see if the volume ramp has completed and clears the increment
|
||||||
* variables appropriately.
|
* variables appropriately.
|
||||||
*
|
*
|
||||||
@ -974,7 +835,8 @@ inline void AudioMixer::track_t::adjustVolumeRamp(bool aux, bool useFloat)
|
|||||||
{
|
{
|
||||||
if (useFloat) {
|
if (useFloat) {
|
||||||
for (uint32_t i = 0; i < MAX_NUM_VOLUMES; i++) {
|
for (uint32_t i = 0; i < MAX_NUM_VOLUMES; i++) {
|
||||||
if (mVolumeInc[i] != 0 && fabs(mVolume[i] - mPrevVolume[i]) <= fabs(mVolumeInc[i])) {
|
if ((mVolumeInc[i] > 0 && mPrevVolume[i] + mVolumeInc[i] >= mVolume[i]) ||
|
||||||
|
(mVolumeInc[i] < 0 && mPrevVolume[i] + mVolumeInc[i] <= mVolume[i])) {
|
||||||
volumeInc[i] = 0;
|
volumeInc[i] = 0;
|
||||||
prevVolume[i] = volume[i] << 16;
|
prevVolume[i] = volume[i] << 16;
|
||||||
mVolumeInc[i] = 0.;
|
mVolumeInc[i] = 0.;
|
||||||
@ -1032,10 +894,15 @@ void AudioMixer::setBufferProvider(int name, AudioBufferProvider* bufferProvider
|
|||||||
if (mState.tracks[name].mReformatBufferProvider != NULL) {
|
if (mState.tracks[name].mReformatBufferProvider != NULL) {
|
||||||
mState.tracks[name].mReformatBufferProvider->reset();
|
mState.tracks[name].mReformatBufferProvider->reset();
|
||||||
} else if (mState.tracks[name].downmixerBufferProvider != NULL) {
|
} else if (mState.tracks[name].downmixerBufferProvider != NULL) {
|
||||||
|
mState.tracks[name].downmixerBufferProvider->reset();
|
||||||
|
} else if (mState.tracks[name].mPostDownmixReformatBufferProvider != NULL) {
|
||||||
|
mState.tracks[name].mPostDownmixReformatBufferProvider->reset();
|
||||||
|
} else if (mState.tracks[name].mTimestretchBufferProvider != NULL) {
|
||||||
|
mState.tracks[name].mTimestretchBufferProvider->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
mState.tracks[name].mInputBufferProvider = bufferProvider;
|
mState.tracks[name].mInputBufferProvider = bufferProvider;
|
||||||
reconfigureBufferProviders(&mState.tracks[name]);
|
mState.tracks[name].reconfigureBufferProviders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1114,7 +981,8 @@ void AudioMixer::process__validate(state_t* state, int64_t pts)
|
|||||||
} else {
|
} else {
|
||||||
if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){
|
if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){
|
||||||
t.hook = getTrackHook(
|
t.hook = getTrackHook(
|
||||||
t.mMixerChannelCount == 2 // TODO: MONO_HACK.
|
(t.mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO // TODO: MONO_HACK
|
||||||
|
&& t.channelMask == AUDIO_CHANNEL_OUT_MONO)
|
||||||
? TRACKTYPE_NORESAMPLEMONO : TRACKTYPE_NORESAMPLE,
|
? TRACKTYPE_NORESAMPLEMONO : TRACKTYPE_NORESAMPLE,
|
||||||
t.mMixerChannelCount,
|
t.mMixerChannelCount,
|
||||||
t.mMixerInFormat, t.mMixerFormat);
|
t.mMixerInFormat, t.mMixerFormat);
|
||||||
@ -2236,4 +2104,4 @@ AudioMixer::process_hook_t AudioMixer::getProcessHook(int processType, uint32_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
}; // namespace android
|
} // namespace android
|
||||||
|
@ -21,14 +21,16 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <hardware/audio_effect.h>
|
||||||
|
#include <media/AudioBufferProvider.h>
|
||||||
|
#include <media/AudioResamplerPublic.h>
|
||||||
|
#include <media/nbaio/NBLog.h>
|
||||||
|
#include <system/audio.h>
|
||||||
|
#include <utils/Compat.h>
|
||||||
#include <utils/threads.h>
|
#include <utils/threads.h>
|
||||||
|
|
||||||
#include <media/AudioBufferProvider.h>
|
|
||||||
#include "AudioResampler.h"
|
#include "AudioResampler.h"
|
||||||
|
#include "BufferProviders.h"
|
||||||
#include <hardware/audio_effect.h>
|
|
||||||
#include <system/audio.h>
|
|
||||||
#include <media/nbaio/NBLog.h>
|
|
||||||
|
|
||||||
// FIXME This is actually unity gain, which might not be max in future, expressed in U.12
|
// FIXME This is actually unity gain, which might not be max in future, expressed in U.12
|
||||||
#define MAX_GAIN_INT AudioMixer::UNITY_GAIN_INT
|
#define MAX_GAIN_INT AudioMixer::UNITY_GAIN_INT
|
||||||
@ -58,7 +60,7 @@ public:
|
|||||||
static const uint32_t MAX_NUM_CHANNELS_TO_DOWNMIX = AUDIO_CHANNEL_COUNT_MAX;
|
static const uint32_t MAX_NUM_CHANNELS_TO_DOWNMIX = AUDIO_CHANNEL_COUNT_MAX;
|
||||||
|
|
||||||
static const uint16_t UNITY_GAIN_INT = 0x1000;
|
static const uint16_t UNITY_GAIN_INT = 0x1000;
|
||||||
static const float UNITY_GAIN_FLOAT = 1.0f;
|
static const CONSTEXPR float UNITY_GAIN_FLOAT = 1.0f;
|
||||||
|
|
||||||
enum { // names
|
enum { // names
|
||||||
|
|
||||||
@ -72,6 +74,7 @@ public:
|
|||||||
RESAMPLE = 0x3001,
|
RESAMPLE = 0x3001,
|
||||||
RAMP_VOLUME = 0x3002, // ramp to new volume
|
RAMP_VOLUME = 0x3002, // ramp to new volume
|
||||||
VOLUME = 0x3003, // don't ramp
|
VOLUME = 0x3003, // don't ramp
|
||||||
|
TIMESTRETCH = 0x3004,
|
||||||
|
|
||||||
// set Parameter names
|
// set Parameter names
|
||||||
// for target TRACK
|
// for target TRACK
|
||||||
@ -99,6 +102,9 @@ public:
|
|||||||
VOLUME0 = 0x4200,
|
VOLUME0 = 0x4200,
|
||||||
VOLUME1 = 0x4201,
|
VOLUME1 = 0x4201,
|
||||||
AUXLEVEL = 0x4210,
|
AUXLEVEL = 0x4210,
|
||||||
|
// for target TIMESTRETCH
|
||||||
|
PLAYBACK_RATE = 0x4300, // Configure timestretch on this track name;
|
||||||
|
// parameter 'value' is a pointer to the new playback rate.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -127,10 +133,16 @@ public:
|
|||||||
size_t getUnreleasedFrames(int name) const;
|
size_t getUnreleasedFrames(int name) const;
|
||||||
|
|
||||||
static inline bool isValidPcmTrackFormat(audio_format_t format) {
|
static inline bool isValidPcmTrackFormat(audio_format_t format) {
|
||||||
return format == AUDIO_FORMAT_PCM_16_BIT ||
|
switch (format) {
|
||||||
format == AUDIO_FORMAT_PCM_24_BIT_PACKED ||
|
case AUDIO_FORMAT_PCM_8_BIT:
|
||||||
format == AUDIO_FORMAT_PCM_32_BIT ||
|
case AUDIO_FORMAT_PCM_16_BIT:
|
||||||
format == AUDIO_FORMAT_PCM_FLOAT;
|
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
|
||||||
|
case AUDIO_FORMAT_PCM_32_BIT:
|
||||||
|
case AUDIO_FORMAT_PCM_FLOAT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -153,7 +165,6 @@ private:
|
|||||||
|
|
||||||
struct state_t;
|
struct state_t;
|
||||||
struct track_t;
|
struct track_t;
|
||||||
class CopyBufferProvider;
|
|
||||||
|
|
||||||
typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp,
|
typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp,
|
||||||
int32_t* aux);
|
int32_t* aux);
|
||||||
@ -205,17 +216,38 @@ private:
|
|||||||
int32_t* auxBuffer;
|
int32_t* auxBuffer;
|
||||||
|
|
||||||
// 16-byte boundary
|
// 16-byte boundary
|
||||||
|
|
||||||
|
/* Buffer providers are constructed to translate the track input data as needed.
|
||||||
|
*
|
||||||
|
* TODO: perhaps make a single PlaybackConverterProvider class to move
|
||||||
|
* all pre-mixer track buffer conversions outside the AudioMixer class.
|
||||||
|
*
|
||||||
|
* 1) mInputBufferProvider: The AudioTrack buffer provider.
|
||||||
|
* 2) mReformatBufferProvider: If not NULL, performs the audio reformat to
|
||||||
|
* match either mMixerInFormat or mDownmixRequiresFormat, if the downmixer
|
||||||
|
* requires reformat. For example, it may convert floating point input to
|
||||||
|
* PCM_16_bit if that's required by the downmixer.
|
||||||
|
* 3) downmixerBufferProvider: If not NULL, performs the channel remixing to match
|
||||||
|
* the number of channels required by the mixer sink.
|
||||||
|
* 4) mPostDownmixReformatBufferProvider: If not NULL, performs reformatting from
|
||||||
|
* the downmixer requirements to the mixer engine input requirements.
|
||||||
|
* 5) mTimestretchBufferProvider: Adds timestretching for playback rate
|
||||||
|
*/
|
||||||
AudioBufferProvider* mInputBufferProvider; // externally provided buffer provider.
|
AudioBufferProvider* mInputBufferProvider; // externally provided buffer provider.
|
||||||
CopyBufferProvider* mReformatBufferProvider; // provider wrapper for reformatting.
|
PassthruBufferProvider* mReformatBufferProvider; // provider wrapper for reformatting.
|
||||||
CopyBufferProvider* downmixerBufferProvider; // wrapper for channel conversion.
|
PassthruBufferProvider* downmixerBufferProvider; // wrapper for channel conversion.
|
||||||
|
PassthruBufferProvider* mPostDownmixReformatBufferProvider;
|
||||||
|
PassthruBufferProvider* mTimestretchBufferProvider;
|
||||||
|
|
||||||
int32_t sessionId;
|
int32_t sessionId;
|
||||||
|
|
||||||
// 16-byte boundary
|
|
||||||
audio_format_t mMixerFormat; // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
|
audio_format_t mMixerFormat; // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
|
||||||
audio_format_t mFormat; // input track format
|
audio_format_t mFormat; // input track format
|
||||||
audio_format_t mMixerInFormat; // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
|
audio_format_t mMixerInFormat; // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
|
||||||
// each track must be converted to this format.
|
// each track must be converted to this format.
|
||||||
|
audio_format_t mDownmixRequiresFormat; // required downmixer format
|
||||||
|
// AUDIO_FORMAT_PCM_16_BIT if 16 bit necessary
|
||||||
|
// AUDIO_FORMAT_INVALID if no required format
|
||||||
|
|
||||||
float mVolume[MAX_NUM_VOLUMES]; // floating point set volume
|
float mVolume[MAX_NUM_VOLUMES]; // floating point set volume
|
||||||
float mPrevVolume[MAX_NUM_VOLUMES]; // floating point previous volume
|
float mPrevVolume[MAX_NUM_VOLUMES]; // floating point previous volume
|
||||||
@ -225,10 +257,11 @@ private:
|
|||||||
float mPrevAuxLevel; // floating point prev aux level
|
float mPrevAuxLevel; // floating point prev aux level
|
||||||
float mAuxInc; // floating point aux increment
|
float mAuxInc; // floating point aux increment
|
||||||
|
|
||||||
// 16-byte boundary
|
|
||||||
audio_channel_mask_t mMixerChannelMask;
|
audio_channel_mask_t mMixerChannelMask;
|
||||||
uint32_t mMixerChannelCount;
|
uint32_t mMixerChannelCount;
|
||||||
|
|
||||||
|
AudioPlaybackRate mPlaybackRate;
|
||||||
|
|
||||||
bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
|
bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
|
||||||
bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate);
|
bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate);
|
||||||
bool doesResample() const { return resampler != NULL; }
|
bool doesResample() const { return resampler != NULL; }
|
||||||
@ -236,6 +269,13 @@ private:
|
|||||||
void adjustVolumeRamp(bool aux, bool useFloat = false);
|
void adjustVolumeRamp(bool aux, bool useFloat = false);
|
||||||
size_t getUnreleasedFrames() const { return resampler != NULL ?
|
size_t getUnreleasedFrames() const { return resampler != NULL ?
|
||||||
resampler->getUnreleasedFrames() : 0; };
|
resampler->getUnreleasedFrames() : 0; };
|
||||||
|
|
||||||
|
status_t prepareForDownmix();
|
||||||
|
void unprepareForDownmix();
|
||||||
|
status_t prepareForReformat();
|
||||||
|
void unprepareForReformat();
|
||||||
|
bool setPlaybackRate(const AudioPlaybackRate &playbackRate);
|
||||||
|
void reconfigureBufferProviders();
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef void (*process_hook_t)(state_t* state, int64_t pts);
|
typedef void (*process_hook_t)(state_t* state, int64_t pts);
|
||||||
@ -254,112 +294,6 @@ private:
|
|||||||
track_t tracks[MAX_NUM_TRACKS] __attribute__((aligned(32)));
|
track_t tracks[MAX_NUM_TRACKS] __attribute__((aligned(32)));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Base AudioBufferProvider class used for DownMixerBufferProvider, RemixBufferProvider,
|
|
||||||
// and ReformatBufferProvider.
|
|
||||||
// It handles a private buffer for use in converting format or channel masks from the
|
|
||||||
// input data to a form acceptable by the mixer.
|
|
||||||
// TODO: Make a ResamplerBufferProvider when integers are entirely removed from the
|
|
||||||
// processing pipeline.
|
|
||||||
class CopyBufferProvider : public AudioBufferProvider {
|
|
||||||
public:
|
|
||||||
// Use a private buffer of bufferFrameCount frames (each frame is outputFrameSize bytes).
|
|
||||||
// If bufferFrameCount is 0, no private buffer is created and in-place modification of
|
|
||||||
// the upstream buffer provider's buffers is performed by copyFrames().
|
|
||||||
CopyBufferProvider(size_t inputFrameSize, size_t outputFrameSize,
|
|
||||||
size_t bufferFrameCount);
|
|
||||||
virtual ~CopyBufferProvider();
|
|
||||||
|
|
||||||
// Overrides AudioBufferProvider methods
|
|
||||||
virtual status_t getNextBuffer(Buffer* buffer, int64_t pts);
|
|
||||||
virtual void releaseBuffer(Buffer* buffer);
|
|
||||||
|
|
||||||
// Other public methods
|
|
||||||
|
|
||||||
// call this to release the buffer to the upstream provider.
|
|
||||||
// treat it as an audio discontinuity for future samples.
|
|
||||||
virtual void reset();
|
|
||||||
|
|
||||||
// this function should be supplied by the derived class. It converts
|
|
||||||
// #frames in the *src pointer to the *dst pointer. It is public because
|
|
||||||
// some providers will allow this to work on arbitrary buffers outside
|
|
||||||
// of the internal buffers.
|
|
||||||
virtual void copyFrames(void *dst, const void *src, size_t frames) = 0;
|
|
||||||
|
|
||||||
// set the upstream buffer provider. Consider calling "reset" before this function.
|
|
||||||
void setBufferProvider(AudioBufferProvider *p) {
|
|
||||||
mTrackBufferProvider = p;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
AudioBufferProvider* mTrackBufferProvider;
|
|
||||||
const size_t mInputFrameSize;
|
|
||||||
const size_t mOutputFrameSize;
|
|
||||||
private:
|
|
||||||
AudioBufferProvider::Buffer mBuffer;
|
|
||||||
const size_t mLocalBufferFrameCount;
|
|
||||||
void* mLocalBufferData;
|
|
||||||
size_t mConsumed;
|
|
||||||
};
|
|
||||||
|
|
||||||
// DownmixerBufferProvider wraps a track AudioBufferProvider to provide
|
|
||||||
// position dependent downmixing by an Audio Effect.
|
|
||||||
class DownmixerBufferProvider : public CopyBufferProvider {
|
|
||||||
public:
|
|
||||||
DownmixerBufferProvider(audio_channel_mask_t inputChannelMask,
|
|
||||||
audio_channel_mask_t outputChannelMask, audio_format_t format,
|
|
||||||
uint32_t sampleRate, int32_t sessionId, size_t bufferFrameCount);
|
|
||||||
virtual ~DownmixerBufferProvider();
|
|
||||||
virtual void copyFrames(void *dst, const void *src, size_t frames);
|
|
||||||
bool isValid() const { return mDownmixHandle != NULL; }
|
|
||||||
|
|
||||||
static status_t init();
|
|
||||||
static bool isMultichannelCapable() { return sIsMultichannelCapable; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
effect_handle_t mDownmixHandle;
|
|
||||||
effect_config_t mDownmixConfig;
|
|
||||||
|
|
||||||
// effect descriptor for the downmixer used by the mixer
|
|
||||||
static effect_descriptor_t sDwnmFxDesc;
|
|
||||||
// indicates whether a downmix effect has been found and is usable by this mixer
|
|
||||||
static bool sIsMultichannelCapable;
|
|
||||||
// FIXME: should we allow effects outside of the framework?
|
|
||||||
// We need to here. A special ioId that must be <= -2 so it does not map to a session.
|
|
||||||
static const int32_t SESSION_ID_INVALID_AND_IGNORED = -2;
|
|
||||||
};
|
|
||||||
|
|
||||||
// RemixBufferProvider wraps a track AudioBufferProvider to perform an
|
|
||||||
// upmix or downmix to the proper channel count and mask.
|
|
||||||
class RemixBufferProvider : public CopyBufferProvider {
|
|
||||||
public:
|
|
||||||
RemixBufferProvider(audio_channel_mask_t inputChannelMask,
|
|
||||||
audio_channel_mask_t outputChannelMask, audio_format_t format,
|
|
||||||
size_t bufferFrameCount);
|
|
||||||
virtual void copyFrames(void *dst, const void *src, size_t frames);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
const audio_format_t mFormat;
|
|
||||||
const size_t mSampleSize;
|
|
||||||
const size_t mInputChannels;
|
|
||||||
const size_t mOutputChannels;
|
|
||||||
int8_t mIdxAry[sizeof(uint32_t)*8]; // 32 bits => channel indices
|
|
||||||
};
|
|
||||||
|
|
||||||
// ReformatBufferProvider wraps a track AudioBufferProvider to convert the input data
|
|
||||||
// to an acceptable mixer input format type.
|
|
||||||
class ReformatBufferProvider : public CopyBufferProvider {
|
|
||||||
public:
|
|
||||||
ReformatBufferProvider(int32_t channels,
|
|
||||||
audio_format_t inputFormat, audio_format_t outputFormat,
|
|
||||||
size_t bufferFrameCount);
|
|
||||||
virtual void copyFrames(void *dst, const void *src, size_t frames);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
const int32_t mChannels;
|
|
||||||
const audio_format_t mInputFormat;
|
|
||||||
const audio_format_t mOutputFormat;
|
|
||||||
};
|
|
||||||
|
|
||||||
// bitmask of allocated track names, where bit 0 corresponds to TRACK0 etc.
|
// bitmask of allocated track names, where bit 0 corresponds to TRACK0 etc.
|
||||||
uint32_t mTrackNames;
|
uint32_t mTrackNames;
|
||||||
|
|
||||||
@ -382,14 +316,6 @@ private:
|
|||||||
bool setChannelMasks(int name,
|
bool setChannelMasks(int name,
|
||||||
audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask);
|
audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask);
|
||||||
|
|
||||||
// TODO: remove unused trackName/trackNum from functions below.
|
|
||||||
static status_t initTrackDownmix(track_t* pTrack, int trackName);
|
|
||||||
static status_t prepareTrackForDownmix(track_t* pTrack, int trackNum);
|
|
||||||
static void unprepareTrackForDownmix(track_t* pTrack, int trackName);
|
|
||||||
static status_t prepareTrackForReformat(track_t* pTrack, int trackNum);
|
|
||||||
static void unprepareTrackForReformat(track_t* pTrack, int trackName);
|
|
||||||
static void reconfigureBufferProviders(track_t* pTrack);
|
|
||||||
|
|
||||||
static void track__genericResample(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
|
static void track__genericResample(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
|
||||||
int32_t* aux);
|
int32_t* aux);
|
||||||
static void track__nop(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
|
static void track__nop(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
|
||||||
@ -465,6 +391,6 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
}; // namespace android
|
} // namespace android
|
||||||
|
|
||||||
#endif // ANDROID_AUDIO_MIXER_H
|
#endif // ANDROID_AUDIO_MIXER_H
|
||||||
|
542
02_day/test-mixer/BufferProviders.cpp
Normal file
542
02_day/test-mixer/BufferProviders.cpp
Normal file
@ -0,0 +1,542 @@
|
|||||||
|
/*
|
||||||
|
* 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 <audio_effects/effect_downmix.h>
|
||||||
|
#include <audio_utils/primitives.h>
|
||||||
|
#include <audio_utils/format.h>
|
||||||
|
#include <media/AudioResamplerPublic.h>
|
||||||
|
#include <media/EffectsFactoryApi.h>
|
||||||
|
|
||||||
|
#include <utils/Log.h>
|
||||||
|
|
||||||
|
#include "Configuration.h"
|
||||||
|
#include "BufferProviders.h"
|
||||||
|
|
||||||
|
#ifndef ARRAY_SIZE
|
||||||
|
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace android {
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
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);
|
||||||
|
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<void *>(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
|
@ -1,19 +1,3 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
@ -39,7 +23,7 @@ static void usage(const char* name) {
|
|||||||
fprintf(stderr, "Usage: %s [-f] [-m] [-c channels]"
|
fprintf(stderr, "Usage: %s [-f] [-m] [-c channels]"
|
||||||
" [-s sample-rate] [-o <output-file>] [-a <aux-buffer-file>] [-P csv]"
|
" [-s sample-rate] [-o <output-file>] [-a <aux-buffer-file>] [-P csv]"
|
||||||
" (<input-file> | <command>)+\n", name);
|
" (<input-file> | <command>)+\n", name);
|
||||||
fprintf(stderr, " -f enable floating point input track\n");
|
fprintf(stderr, " -f enable floating point input track by default\n");
|
||||||
fprintf(stderr, " -m enable floating point mixer output\n");
|
fprintf(stderr, " -m enable floating point mixer output\n");
|
||||||
fprintf(stderr, " -c number of mixer output channels\n");
|
fprintf(stderr, " -c number of mixer output channels\n");
|
||||||
fprintf(stderr, " -s mixer sample-rate\n");
|
fprintf(stderr, " -s mixer sample-rate\n");
|
||||||
@ -47,8 +31,8 @@ static void usage(const char* name) {
|
|||||||
fprintf(stderr, " -a <aux-buffer-file>\n");
|
fprintf(stderr, " -a <aux-buffer-file>\n");
|
||||||
fprintf(stderr, " -P # frames provided per call to resample() in CSV format\n");
|
fprintf(stderr, " -P # frames provided per call to resample() in CSV format\n");
|
||||||
fprintf(stderr, " <input-file> is a WAV file\n");
|
fprintf(stderr, " <input-file> is a WAV file\n");
|
||||||
fprintf(stderr, " <command> can be 'sine:<channels>,<frequency>,<samplerate>'\n");
|
fprintf(stderr, " <command> can be 'sine:[(i|f),]<channels>,<frequency>,<samplerate>'\n");
|
||||||
fprintf(stderr, " 'chirp:<channels>,<samplerate>'\n");
|
fprintf(stderr, " 'chirp:[(i|f),]<channels>,<samplerate>'\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static int writeFile(const char *filename, const void *buffer,
|
static int writeFile(const char *filename, const void *buffer,
|
||||||
@ -78,6 +62,18 @@ static int writeFile(const char *filename, const void *buffer,
|
|||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *parseFormat(const char *s, bool *useFloat) {
|
||||||
|
if (!strncmp(s, "f,", 2)) {
|
||||||
|
*useFloat = true;
|
||||||
|
return s + 2;
|
||||||
|
}
|
||||||
|
if (!strncmp(s, "i,", 2)) {
|
||||||
|
*useFloat = false;
|
||||||
|
return s + 2;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
const char* const progname = argv[0];
|
const char* const progname = argv[0];
|
||||||
bool useInputFloat = false;
|
bool useInputFloat = false;
|
||||||
@ -88,8 +84,9 @@ int main(int argc, char* argv[]) {
|
|||||||
std::vector<int> Pvalues;
|
std::vector<int> Pvalues;
|
||||||
const char* outputFilename = NULL;
|
const char* outputFilename = NULL;
|
||||||
const char* auxFilename = NULL;
|
const char* auxFilename = NULL;
|
||||||
std::vector<int32_t> Names;
|
std::vector<int32_t> names;
|
||||||
std::vector<SignalProvider> Providers;
|
std::vector<SignalProvider> providers;
|
||||||
|
std::vector<audio_format_t> formats;
|
||||||
|
|
||||||
for (int ch; (ch = getopt(argc, argv, "fmc:s:o:a:P:")) != -1;) {
|
for (int ch; (ch = getopt(argc, argv, "fmc:s:o:a:P:")) != -1;) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
@ -138,54 +135,65 @@ int main(int argc, char* argv[]) {
|
|||||||
size_t outputFrames = 0;
|
size_t outputFrames = 0;
|
||||||
|
|
||||||
// create providers for each track
|
// create providers for each track
|
||||||
Providers.resize(argc);
|
names.resize(argc);
|
||||||
|
providers.resize(argc);
|
||||||
|
formats.resize(argc);
|
||||||
for (int i = 0; i < argc; ++i) {
|
for (int i = 0; i < argc; ++i) {
|
||||||
static const char chirp[] = "chirp:";
|
static const char chirp[] = "chirp:";
|
||||||
static const char sine[] = "sine:";
|
static const char sine[] = "sine:";
|
||||||
static const double kSeconds = 10;
|
static const double kSeconds = 1;
|
||||||
|
bool useFloat = useInputFloat;
|
||||||
|
|
||||||
if (!strncmp(argv[i], chirp, strlen(chirp))) {
|
if (!strncmp(argv[i], chirp, strlen(chirp))) {
|
||||||
std::vector<int> v;
|
std::vector<int> v;
|
||||||
|
const char *s = parseFormat(argv[i] + strlen(chirp), &useFloat);
|
||||||
|
|
||||||
parseCSV(argv[i] + strlen(chirp), v);
|
parseCSV(s, v);
|
||||||
if (v.size() == 2) {
|
if (v.size() == 2) {
|
||||||
printf("creating chirp(%d %d)\n", v[0], v[1]);
|
printf("creating chirp(%d %d)\n", v[0], v[1]);
|
||||||
if (useInputFloat) {
|
if (useFloat) {
|
||||||
Providers[i].setChirp<float>(v[0], 0, v[1]/2, v[1], kSeconds);
|
providers[i].setChirp<float>(v[0], 0, v[1]/2, v[1], kSeconds);
|
||||||
|
formats[i] = AUDIO_FORMAT_PCM_FLOAT;
|
||||||
} else {
|
} else {
|
||||||
Providers[i].setChirp<int16_t>(v[0], 0, v[1]/2, v[1], kSeconds);
|
providers[i].setChirp<int16_t>(v[0], 0, v[1]/2, v[1], kSeconds);
|
||||||
|
formats[i] = AUDIO_FORMAT_PCM_16_BIT;
|
||||||
}
|
}
|
||||||
Providers[i].setIncr(Pvalues);
|
providers[i].setIncr(Pvalues);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "malformed input '%s'\n", argv[i]);
|
fprintf(stderr, "malformed input '%s'\n", argv[i]);
|
||||||
}
|
}
|
||||||
} else if (!strncmp(argv[i], sine, strlen(sine))) {
|
} else if (!strncmp(argv[i], sine, strlen(sine))) {
|
||||||
std::vector<int> v;
|
std::vector<int> v;
|
||||||
|
const char *s = parseFormat(argv[i] + strlen(sine), &useFloat);
|
||||||
|
|
||||||
parseCSV(argv[i] + strlen(sine), v);
|
parseCSV(s, v);
|
||||||
if (v.size() == 3) {
|
if (v.size() == 3) {
|
||||||
printf("creating sine(%d %d %d)\n", v[0], v[1], v[2]);
|
printf("creating sine(%d %d %d)\n", v[0], v[1], v[2]);
|
||||||
if (useInputFloat) {
|
if (useFloat) {
|
||||||
Providers[i].setSine<float>(v[0], v[1], v[2], kSeconds);
|
providers[i].setSine<float>(v[0], v[1], v[2], kSeconds);
|
||||||
|
formats[i] = AUDIO_FORMAT_PCM_FLOAT;
|
||||||
} else {
|
} else {
|
||||||
Providers[i].setSine<int16_t>(v[0], v[1], v[2], kSeconds);
|
providers[i].setSine<int16_t>(v[0], v[1], v[2], kSeconds);
|
||||||
|
formats[i] = AUDIO_FORMAT_PCM_16_BIT;
|
||||||
}
|
}
|
||||||
Providers[i].setIncr(Pvalues);
|
providers[i].setIncr(Pvalues);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "malformed input '%s'\n", argv[i]);
|
fprintf(stderr, "malformed input '%s'\n", argv[i]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printf("creating filename(%s)\n", argv[i]);
|
printf("creating filename(%s)\n", argv[i]);
|
||||||
if (useInputFloat) {
|
if (useInputFloat) {
|
||||||
Providers[i].setFile<float>(argv[i]);
|
providers[i].setFile<float>(argv[i]);
|
||||||
|
formats[i] = AUDIO_FORMAT_PCM_FLOAT;
|
||||||
} else {
|
} else {
|
||||||
Providers[i].setFile<short>(argv[i]);
|
providers[i].setFile<short>(argv[i]);
|
||||||
|
formats[i] = AUDIO_FORMAT_PCM_16_BIT;
|
||||||
}
|
}
|
||||||
Providers[i].setIncr(Pvalues);
|
providers[i].setIncr(Pvalues);
|
||||||
}
|
}
|
||||||
// calculate the number of output frames
|
// calculate the number of output frames
|
||||||
size_t nframes = (int64_t) Providers[i].getNumFrames() * outputSampleRate
|
size_t nframes = (int64_t) providers[i].getNumFrames() * outputSampleRate
|
||||||
/ Providers[i].getSampleRate();
|
/ providers[i].getSampleRate();
|
||||||
if (i == 0 || outputFrames > nframes) { // choose minimum for outputFrames
|
if (i == 0 || outputFrames > nframes) { // choose minimum for outputFrames
|
||||||
outputFrames = nframes;
|
outputFrames = nframes;
|
||||||
}
|
}
|
||||||
@ -213,22 +221,20 @@ int main(int argc, char* argv[]) {
|
|||||||
// create the mixer.
|
// create the mixer.
|
||||||
const size_t mixerFrameCount = 320; // typical numbers may range from 240 or 960
|
const size_t mixerFrameCount = 320; // typical numbers may range from 240 or 960
|
||||||
AudioMixer *mixer = new AudioMixer(mixerFrameCount, outputSampleRate);
|
AudioMixer *mixer = new AudioMixer(mixerFrameCount, outputSampleRate);
|
||||||
audio_format_t inputFormat = useInputFloat
|
|
||||||
? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
|
|
||||||
audio_format_t mixerFormat = useMixerFloat
|
audio_format_t mixerFormat = useMixerFloat
|
||||||
? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
|
? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
|
||||||
float f = AudioMixer::UNITY_GAIN_FLOAT / Providers.size(); // normalize volume by # tracks
|
float f = AudioMixer::UNITY_GAIN_FLOAT / providers.size(); // normalize volume by # tracks
|
||||||
static float f0; // zero
|
static float f0; // zero
|
||||||
|
|
||||||
// set up the tracks.
|
// set up the tracks.
|
||||||
for (size_t i = 0; i < Providers.size(); ++i) {
|
for (size_t i = 0; i < providers.size(); ++i) {
|
||||||
//printf("track %d out of %d\n", i, Providers.size());
|
//printf("track %d out of %d\n", i, providers.size());
|
||||||
uint32_t channelMask = audio_channel_out_mask_from_count(Providers[i].getNumChannels());
|
uint32_t channelMask = audio_channel_out_mask_from_count(providers[i].getNumChannels());
|
||||||
int32_t name = mixer->getTrackName(channelMask,
|
int32_t name = mixer->getTrackName(channelMask,
|
||||||
inputFormat, AUDIO_SESSION_OUTPUT_MIX);
|
formats[i], AUDIO_SESSION_OUTPUT_MIX);
|
||||||
ALOG_ASSERT(name >= 0);
|
ALOG_ASSERT(name >= 0);
|
||||||
Names.push_back(name);
|
names[i] = name;
|
||||||
mixer->setBufferProvider(name, &Providers[i]);
|
mixer->setBufferProvider(name, &providers[i]);
|
||||||
mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
|
mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
|
||||||
(void *)outputAddr);
|
(void *)outputAddr);
|
||||||
mixer->setParameter(
|
mixer->setParameter(
|
||||||
@ -240,7 +246,7 @@ int main(int argc, char* argv[]) {
|
|||||||
name,
|
name,
|
||||||
AudioMixer::TRACK,
|
AudioMixer::TRACK,
|
||||||
AudioMixer::FORMAT,
|
AudioMixer::FORMAT,
|
||||||
(void *)(uintptr_t)inputFormat);
|
(void *)(uintptr_t)formats[i]);
|
||||||
mixer->setParameter(
|
mixer->setParameter(
|
||||||
name,
|
name,
|
||||||
AudioMixer::TRACK,
|
AudioMixer::TRACK,
|
||||||
@ -255,7 +261,7 @@ int main(int argc, char* argv[]) {
|
|||||||
name,
|
name,
|
||||||
AudioMixer::RESAMPLE,
|
AudioMixer::RESAMPLE,
|
||||||
AudioMixer::SAMPLE_RATE,
|
AudioMixer::SAMPLE_RATE,
|
||||||
(void *)(uintptr_t)Providers[i].getSampleRate());
|
(void *)(uintptr_t)providers[i].getSampleRate());
|
||||||
if (useRamp) {
|
if (useRamp) {
|
||||||
mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &f0);
|
mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &f0);
|
||||||
mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &f0);
|
mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &f0);
|
||||||
@ -277,11 +283,11 @@ int main(int argc, char* argv[]) {
|
|||||||
// pump the mixer to process data.
|
// pump the mixer to process data.
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i < outputFrames - mixerFrameCount; i += mixerFrameCount) {
|
for (i = 0; i < outputFrames - mixerFrameCount; i += mixerFrameCount) {
|
||||||
for (size_t j = 0; j < Names.size(); ++j) {
|
for (size_t j = 0; j < names.size(); ++j) {
|
||||||
mixer->setParameter(Names[j], AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
|
mixer->setParameter(names[j], AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
|
||||||
(char *) outputAddr + i * outputFrameSize);
|
(char *) outputAddr + i * outputFrameSize);
|
||||||
if (auxFilename) {
|
if (auxFilename) {
|
||||||
mixer->setParameter(Names[j], AudioMixer::TRACK, AudioMixer::AUX_BUFFER,
|
mixer->setParameter(names[j], AudioMixer::TRACK, AudioMixer::AUX_BUFFER,
|
||||||
(char *) auxAddr + i * auxFrameSize);
|
(char *) auxAddr + i * auxFrameSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user