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)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES:= \
|
||||
test-mixer.cpp \
|
||||
AudioMixer.cpp.arm \
|
||||
LOCAL_SRC_FILES:= test-mixer.cpp AudioMixer.cpp.arm BufferProviders.cpp
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
bionic \
|
||||
bionic/libstdc++/include \
|
||||
external/stlport/stlport \
|
||||
$(call include-path-for, audio-effects) \
|
||||
$(call include-path-for, audio-utils) \
|
||||
frameworks/av/services/audioflinger
|
||||
frameworks/av/services/audioflinger \
|
||||
external/sonic
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libsndfile
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libstlport \
|
||||
libeffects \
|
||||
libnbaio \
|
||||
libcommon_time_client \
|
||||
@ -27,10 +21,13 @@ LOCAL_SHARED_LIBRARIES := \
|
||||
libdl \
|
||||
libcutils \
|
||||
libutils \
|
||||
liblog
|
||||
liblog \
|
||||
libsonic
|
||||
|
||||
LOCAL_MODULE:= test-mixer
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
LOCAL_CXX_STL := libc++
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
@ -39,9 +39,6 @@
|
||||
#include <common_time/local_clock.h>
|
||||
#include <common_time/cc_helper.h>
|
||||
|
||||
#include <media/EffectsFactoryApi.h>
|
||||
#include <audio_effects/effect_downmix.h>
|
||||
|
||||
#include "AudioMixerOps.h"
|
||||
#include "AudioMixer.h"
|
||||
|
||||
@ -69,9 +66,16 @@
|
||||
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
|
||||
#endif
|
||||
|
||||
// Set kUseNewMixer to true to use the new mixer engine. Otherwise the
|
||||
// original code will be used. This is false for now.
|
||||
static const bool kUseNewMixer = false;
|
||||
// TODO: Move these macro/inlines to a header file.
|
||||
template <typename T>
|
||||
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.
|
||||
// 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;
|
||||
}
|
||||
|
||||
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.
|
||||
@ -407,6 +129,7 @@ AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate, uint32_t maxNumTr
|
||||
t->resampler = NULL;
|
||||
t->downmixerBufferProvider = NULL;
|
||||
t->mReformatBufferProvider = NULL;
|
||||
t->mTimestretchBufferProvider = NULL;
|
||||
t++;
|
||||
}
|
||||
|
||||
@ -419,6 +142,7 @@ AudioMixer::~AudioMixer()
|
||||
delete t->resampler;
|
||||
delete t->downmixerBufferProvider;
|
||||
delete t->mReformatBufferProvider;
|
||||
delete t->mTimestretchBufferProvider;
|
||||
t++;
|
||||
}
|
||||
delete [] mState.outputTemp;
|
||||
@ -430,6 +154,10 @@ void AudioMixer::setLog(NBLog::Writer *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,
|
||||
audio_format_t format, int sessionId)
|
||||
{
|
||||
@ -492,24 +220,25 @@ int AudioMixer::getTrackName(audio_channel_mask_t channelMask,
|
||||
t->mInputBufferProvider = NULL;
|
||||
t->mReformatBufferProvider = NULL;
|
||||
t->downmixerBufferProvider = NULL;
|
||||
t->mPostDownmixReformatBufferProvider = NULL;
|
||||
t->mTimestretchBufferProvider = NULL;
|
||||
t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT;
|
||||
t->mFormat = format;
|
||||
t->mMixerInFormat = kUseFloat && kUseNewMixer
|
||||
? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
|
||||
t->mMixerInFormat = selectMixerInFormat(format);
|
||||
t->mDownmixRequiresFormat = AUDIO_FORMAT_INVALID; // no format required
|
||||
t->mMixerChannelMask = audio_channel_mask_from_representation_and_bits(
|
||||
AUDIO_CHANNEL_REPRESENTATION_POSITION, AUDIO_CHANNEL_OUT_STEREO);
|
||||
t->mMixerChannelCount = audio_channel_count_from_out_mask(t->mMixerChannelMask);
|
||||
t->mPlaybackRate = AUDIO_PLAYBACK_RATE_DEFAULT;
|
||||
// Check the downmixing (or upmixing) requirements.
|
||||
status_t status = initTrackDownmix(t, n);
|
||||
status_t status = t->prepareForDownmix();
|
||||
if (status != OK) {
|
||||
ALOGE("AudioMixer::getTrackName invalid channelMask (%#x)", channelMask);
|
||||
return -1;
|
||||
}
|
||||
// initTrackDownmix() may change the input format requirement.
|
||||
// If you desire floating point input to the mixer, it may change
|
||||
// to integer because the downmixer requires integer to process.
|
||||
// prepareForDownmix() may change mDownmixRequiresFormat
|
||||
ALOGVV("mMixerFormat:%#x mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat);
|
||||
prepareTrackForReformat(t, n);
|
||||
t->prepareForReformat();
|
||||
mTrackNames |= 1 << n;
|
||||
return TRACK0 + n;
|
||||
}
|
||||
@ -526,7 +255,7 @@ void AudioMixer::invalidateState(uint32_t mask)
|
||||
}
|
||||
|
||||
// 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.
|
||||
bool AudioMixer::setChannelMasks(int name,
|
||||
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?
|
||||
// update to try using our desired format (if we aren't already using it)
|
||||
const audio_format_t prevMixerInFormat = track.mMixerInFormat;
|
||||
track.mMixerInFormat = kUseFloat && kUseNewMixer
|
||||
? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
|
||||
const status_t status = initTrackDownmix(&mState.tracks[name], name);
|
||||
const audio_format_t prevDownmixerFormat = track.mDownmixRequiresFormat;
|
||||
const status_t status = mState.tracks[name].prepareForDownmix();
|
||||
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);
|
||||
|
||||
const bool mixerInFormatChanged = prevMixerInFormat != track.mMixerInFormat;
|
||||
if (mixerInFormatChanged) {
|
||||
prepareTrackForReformat(&track, name); // because of downmixer, track format may change!
|
||||
if (prevDownmixerFormat != track.mDownmixRequiresFormat) {
|
||||
track.prepareForReformat(); // because of downmixer, track format may change!
|
||||
}
|
||||
|
||||
if (track.resampler && (mixerInFormatChanged || mixerChannelCountChanged)) {
|
||||
// resampler input format or channels may have changed.
|
||||
if (track.resampler && mixerChannelCountChanged) {
|
||||
// resampler channels may have changed.
|
||||
const uint32_t resetToSampleRate = track.sampleRate;
|
||||
delete track.resampler;
|
||||
track.resampler = NULL;
|
||||
@ -576,99 +302,129 @@ bool AudioMixer::setChannelMasks(int name,
|
||||
return true;
|
||||
}
|
||||
|
||||
status_t AudioMixer::initTrackDownmix(track_t* pTrack, int trackName)
|
||||
{
|
||||
// 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::track_t::unprepareForDownmix() {
|
||||
ALOGV("AudioMixer::unprepareForDownmix(%p)", this);
|
||||
|
||||
void AudioMixer::unprepareTrackForDownmix(track_t* pTrack, int trackName __unused) {
|
||||
ALOGV("AudioMixer::unprepareTrackForDownmix(%d)", trackName);
|
||||
|
||||
if (pTrack->downmixerBufferProvider != NULL) {
|
||||
mDownmixRequiresFormat = AUDIO_FORMAT_INVALID;
|
||||
if (downmixerBufferProvider != NULL) {
|
||||
// this track had previously been configured with a downmixer, delete it
|
||||
ALOGV(" deleting old downmixer");
|
||||
delete pTrack->downmixerBufferProvider;
|
||||
pTrack->downmixerBufferProvider = NULL;
|
||||
reconfigureBufferProviders(pTrack);
|
||||
delete downmixerBufferProvider;
|
||||
downmixerBufferProvider = NULL;
|
||||
reconfigureBufferProviders();
|
||||
} else {
|
||||
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
|
||||
unprepareTrackForDownmix(pTrack, trackName);
|
||||
if (DownmixerBufferProvider::isMultichannelCapable()) {
|
||||
DownmixerBufferProvider* pDbp = new DownmixerBufferProvider(pTrack->channelMask,
|
||||
pTrack->mMixerChannelMask,
|
||||
AUDIO_FORMAT_PCM_16_BIT /* TODO: use pTrack->mMixerInFormat, now only PCM 16 */,
|
||||
pTrack->sampleRate, pTrack->sessionId, kCopyBufferFrameCount);
|
||||
unprepareForDownmix();
|
||||
// MONO_HACK 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 (channelMask == mMixerChannelMask
|
||||
|| (channelMask == AUDIO_CHANNEL_OUT_MONO
|
||||
&& 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
|
||||
pTrack->mMixerInFormat = AUDIO_FORMAT_PCM_16_BIT; // PCM 16 bit required for downmix
|
||||
pTrack->downmixerBufferProvider = pDbp;
|
||||
reconfigureBufferProviders(pTrack);
|
||||
mDownmixRequiresFormat = AUDIO_FORMAT_PCM_16_BIT; // PCM 16 bit required for downmix
|
||||
downmixerBufferProvider = pDbp;
|
||||
reconfigureBufferProviders();
|
||||
return NO_ERROR;
|
||||
}
|
||||
delete pDbp;
|
||||
}
|
||||
|
||||
// Effect downmixer does not accept the channel conversion. Let's use our remixer.
|
||||
RemixBufferProvider* pRbp = new RemixBufferProvider(pTrack->channelMask,
|
||||
pTrack->mMixerChannelMask, pTrack->mMixerInFormat, kCopyBufferFrameCount);
|
||||
RemixBufferProvider* pRbp = new RemixBufferProvider(channelMask,
|
||||
mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount);
|
||||
// Remix always finds a conversion whereas Downmixer effect above may fail.
|
||||
pTrack->downmixerBufferProvider = pRbp;
|
||||
reconfigureBufferProviders(pTrack);
|
||||
downmixerBufferProvider = pRbp;
|
||||
reconfigureBufferProviders();
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
void AudioMixer::unprepareTrackForReformat(track_t* pTrack, int trackName __unused) {
|
||||
ALOGV("AudioMixer::unprepareTrackForReformat(%d)", trackName);
|
||||
if (pTrack->mReformatBufferProvider != NULL) {
|
||||
delete pTrack->mReformatBufferProvider;
|
||||
pTrack->mReformatBufferProvider = NULL;
|
||||
reconfigureBufferProviders(pTrack);
|
||||
void AudioMixer::track_t::unprepareForReformat() {
|
||||
ALOGV("AudioMixer::unprepareForReformat(%p)", this);
|
||||
bool requiresReconfigure = false;
|
||||
if (mReformatBufferProvider != NULL) {
|
||||
delete mReformatBufferProvider;
|
||||
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);
|
||||
// discard the previous reformatter if there was one
|
||||
unprepareTrackForReformat(pTrack, trackName);
|
||||
// only configure reformatter if needed
|
||||
if (pTrack->mFormat != pTrack->mMixerInFormat) {
|
||||
pTrack->mReformatBufferProvider = new ReformatBufferProvider(
|
||||
audio_channel_count_from_out_mask(pTrack->channelMask),
|
||||
pTrack->mFormat, pTrack->mMixerInFormat,
|
||||
ALOGV("AudioMixer::prepareForReformat(%p) with format %#x", this, mFormat);
|
||||
// discard previous reformatters
|
||||
unprepareForReformat();
|
||||
// only configure reformatters as needed
|
||||
const audio_format_t targetFormat = mDownmixRequiresFormat != AUDIO_FORMAT_INVALID
|
||||
? mDownmixRequiresFormat : mMixerInFormat;
|
||||
bool requiresReconfigure = false;
|
||||
if (mFormat != targetFormat) {
|
||||
mReformatBufferProvider = new ReformatBufferProvider(
|
||||
audio_channel_count_from_out_mask(channelMask),
|
||||
mFormat,
|
||||
targetFormat,
|
||||
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;
|
||||
}
|
||||
|
||||
void AudioMixer::reconfigureBufferProviders(track_t* pTrack)
|
||||
void AudioMixer::track_t::reconfigureBufferProviders()
|
||||
{
|
||||
pTrack->bufferProvider = pTrack->mInputBufferProvider;
|
||||
if (pTrack->mReformatBufferProvider) {
|
||||
pTrack->mReformatBufferProvider->setBufferProvider(pTrack->bufferProvider);
|
||||
pTrack->bufferProvider = pTrack->mReformatBufferProvider;
|
||||
bufferProvider = mInputBufferProvider;
|
||||
if (mReformatBufferProvider) {
|
||||
mReformatBufferProvider->setBufferProvider(bufferProvider);
|
||||
bufferProvider = mReformatBufferProvider;
|
||||
}
|
||||
if (pTrack->downmixerBufferProvider) {
|
||||
pTrack->downmixerBufferProvider->setBufferProvider(pTrack->bufferProvider);
|
||||
pTrack->bufferProvider = pTrack->downmixerBufferProvider;
|
||||
if (downmixerBufferProvider) {
|
||||
downmixerBufferProvider->setBufferProvider(bufferProvider);
|
||||
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;
|
||||
track.resampler = NULL;
|
||||
// delete the downmixer
|
||||
unprepareTrackForDownmix(&mState.tracks[name], name);
|
||||
mState.tracks[name].unprepareForDownmix();
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -748,41 +506,99 @@ void AudioMixer::disable(int name)
|
||||
static inline bool setVolumeRampVariables(float newVolume, int32_t ramp,
|
||||
int16_t *pIntSetVolume, int32_t *pIntPrevVolume, int32_t *pIntVolumeInc,
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
/* set the floating point volume variables */
|
||||
if (ramp != 0) {
|
||||
*pVolumeInc = (newVolume - *pSetVolume) / ramp;
|
||||
*pPrevVolume = *pSetVolume;
|
||||
if (newVolume < 0) {
|
||||
newVolume = 0; // should not have negative volumes
|
||||
} else {
|
||||
*pVolumeInc = 0;
|
||||
*pPrevVolume = newVolume;
|
||||
switch (fpclassify(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 */
|
||||
int32_t intVolume = newVolume * AudioMixer::UNITY_GAIN_INT;
|
||||
if (intVolume > AudioMixer::UNITY_GAIN_INT) {
|
||||
intVolume = AudioMixer::UNITY_GAIN_INT;
|
||||
} else if (intVolume < 0) {
|
||||
ALOGE("negative volume %.7g", newVolume);
|
||||
intVolume = 0; // should never happen, but for safety check.
|
||||
// set floating point volume ramp
|
||||
if (ramp != 0) {
|
||||
// when the ramp completes, *pPrevVolume is set to *pSetVolume, so there
|
||||
// is no computational mismatch; hence equality is checked here.
|
||||
ALOGD_IF(*pPrevVolume != *pSetVolume, "previous float ramp hasn't finished,"
|
||||
" prev:%f set_to:%f", *pPrevVolume, *pSetVolume);
|
||||
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;
|
||||
*pPrevVolume = newVolume;
|
||||
return true;
|
||||
}
|
||||
if (ramp != 0) {
|
||||
*pIntVolumeInc = ((intVolume - *pIntSetVolume) << 16) / ramp;
|
||||
*pIntPrevVolume = (*pIntVolumeInc == 0 ? intVolume : *pIntSetVolume) << 16;
|
||||
} else {
|
||||
*pIntVolumeInc = 0;
|
||||
*pIntPrevVolume = intVolume << 16;
|
||||
}
|
||||
*pSetVolume = newVolume;
|
||||
*pIntSetVolume = intVolume;
|
||||
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);
|
||||
track.mFormat = format;
|
||||
ALOGV("setParameter(TRACK, FORMAT, %#x)", format);
|
||||
prepareTrackForReformat(&track, name);
|
||||
track.prepareForReformat();
|
||||
invalidateState(1 << name);
|
||||
}
|
||||
} break;
|
||||
@ -912,6 +728,28 @@ void AudioMixer::setParameter(int name, int target, int param, void *value)
|
||||
}
|
||||
}
|
||||
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:
|
||||
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
|
||||
// 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.
|
||||
if (!((trackSampleRate == 44100 && devSampleRate == 48000) ||
|
||||
(trackSampleRate == 48000 && devSampleRate == 44100))) {
|
||||
quality = AudioResampler::DYN_LOW_QUALITY;
|
||||
} else {
|
||||
if (isMusicRate(trackSampleRate)) {
|
||||
quality = AudioResampler::DEFAULT_QUALITY;
|
||||
} else {
|
||||
quality = AudioResampler::DYN_LOW_QUALITY;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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
|
||||
* variables appropriately.
|
||||
*
|
||||
@ -974,7 +835,8 @@ inline void AudioMixer::track_t::adjustVolumeRamp(bool aux, bool useFloat)
|
||||
{
|
||||
if (useFloat) {
|
||||
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;
|
||||
prevVolume[i] = volume[i] << 16;
|
||||
mVolumeInc[i] = 0.;
|
||||
@ -1032,10 +894,15 @@ void AudioMixer::setBufferProvider(int name, AudioBufferProvider* bufferProvider
|
||||
if (mState.tracks[name].mReformatBufferProvider != NULL) {
|
||||
mState.tracks[name].mReformatBufferProvider->reset();
|
||||
} 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;
|
||||
reconfigureBufferProviders(&mState.tracks[name]);
|
||||
mState.tracks[name].reconfigureBufferProviders();
|
||||
}
|
||||
|
||||
|
||||
@ -1114,7 +981,8 @@ void AudioMixer::process__validate(state_t* state, int64_t pts)
|
||||
} else {
|
||||
if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){
|
||||
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,
|
||||
t.mMixerChannelCount,
|
||||
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 <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 <media/AudioBufferProvider.h>
|
||||
#include "AudioResampler.h"
|
||||
|
||||
#include <hardware/audio_effect.h>
|
||||
#include <system/audio.h>
|
||||
#include <media/nbaio/NBLog.h>
|
||||
#include "BufferProviders.h"
|
||||
|
||||
// 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
|
||||
@ -58,7 +60,7 @@ public:
|
||||
static const uint32_t MAX_NUM_CHANNELS_TO_DOWNMIX = AUDIO_CHANNEL_COUNT_MAX;
|
||||
|
||||
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
|
||||
|
||||
@ -72,6 +74,7 @@ public:
|
||||
RESAMPLE = 0x3001,
|
||||
RAMP_VOLUME = 0x3002, // ramp to new volume
|
||||
VOLUME = 0x3003, // don't ramp
|
||||
TIMESTRETCH = 0x3004,
|
||||
|
||||
// set Parameter names
|
||||
// for target TRACK
|
||||
@ -99,6 +102,9 @@ public:
|
||||
VOLUME0 = 0x4200,
|
||||
VOLUME1 = 0x4201,
|
||||
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;
|
||||
|
||||
static inline bool isValidPcmTrackFormat(audio_format_t format) {
|
||||
return format == AUDIO_FORMAT_PCM_16_BIT ||
|
||||
format == AUDIO_FORMAT_PCM_24_BIT_PACKED ||
|
||||
format == AUDIO_FORMAT_PCM_32_BIT ||
|
||||
format == AUDIO_FORMAT_PCM_FLOAT;
|
||||
switch (format) {
|
||||
case AUDIO_FORMAT_PCM_8_BIT:
|
||||
case AUDIO_FORMAT_PCM_16_BIT:
|
||||
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
|
||||
case AUDIO_FORMAT_PCM_32_BIT:
|
||||
case AUDIO_FORMAT_PCM_FLOAT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
@ -153,7 +165,6 @@ private:
|
||||
|
||||
struct state_t;
|
||||
struct track_t;
|
||||
class CopyBufferProvider;
|
||||
|
||||
typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp,
|
||||
int32_t* aux);
|
||||
@ -205,17 +216,38 @@ private:
|
||||
int32_t* auxBuffer;
|
||||
|
||||
// 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.
|
||||
CopyBufferProvider* mReformatBufferProvider; // provider wrapper for reformatting.
|
||||
CopyBufferProvider* downmixerBufferProvider; // wrapper for channel conversion.
|
||||
PassthruBufferProvider* mReformatBufferProvider; // provider wrapper for reformatting.
|
||||
PassthruBufferProvider* downmixerBufferProvider; // wrapper for channel conversion.
|
||||
PassthruBufferProvider* mPostDownmixReformatBufferProvider;
|
||||
PassthruBufferProvider* mTimestretchBufferProvider;
|
||||
|
||||
int32_t sessionId;
|
||||
|
||||
// 16-byte boundary
|
||||
audio_format_t mMixerFormat; // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
|
||||
audio_format_t mFormat; // input track format
|
||||
audio_format_t mMixerInFormat; // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
|
||||
// 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 mPrevVolume[MAX_NUM_VOLUMES]; // floating point previous volume
|
||||
@ -225,10 +257,11 @@ private:
|
||||
float mPrevAuxLevel; // floating point prev aux level
|
||||
float mAuxInc; // floating point aux increment
|
||||
|
||||
// 16-byte boundary
|
||||
audio_channel_mask_t mMixerChannelMask;
|
||||
uint32_t mMixerChannelCount;
|
||||
|
||||
AudioPlaybackRate mPlaybackRate;
|
||||
|
||||
bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
|
||||
bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate);
|
||||
bool doesResample() const { return resampler != NULL; }
|
||||
@ -236,6 +269,13 @@ private:
|
||||
void adjustVolumeRamp(bool aux, bool useFloat = false);
|
||||
size_t getUnreleasedFrames() const { return resampler != NULL ?
|
||||
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);
|
||||
@ -254,112 +294,6 @@ private:
|
||||
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.
|
||||
uint32_t mTrackNames;
|
||||
|
||||
@ -382,14 +316,6 @@ private:
|
||||
bool setChannelMasks(int name,
|
||||
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,
|
||||
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
|
||||
|
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 <inttypes.h>
|
||||
#include <math.h>
|
||||
@ -39,7 +23,7 @@ static void usage(const char* name) {
|
||||
fprintf(stderr, "Usage: %s [-f] [-m] [-c channels]"
|
||||
" [-s sample-rate] [-o <output-file>] [-a <aux-buffer-file>] [-P csv]"
|
||||
" (<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, " -c number of mixer output channels\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, " -P # frames provided per call to resample() in CSV format\n");
|
||||
fprintf(stderr, " <input-file> is a WAV file\n");
|
||||
fprintf(stderr, " <command> can be 'sine:<channels>,<frequency>,<samplerate>'\n");
|
||||
fprintf(stderr, " 'chirp:<channels>,<samplerate>'\n");
|
||||
fprintf(stderr, " <command> can be 'sine:[(i|f),]<channels>,<frequency>,<samplerate>'\n");
|
||||
fprintf(stderr, " 'chirp:[(i|f),]<channels>,<samplerate>'\n");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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[]) {
|
||||
const char* const progname = argv[0];
|
||||
bool useInputFloat = false;
|
||||
@ -88,8 +84,9 @@ int main(int argc, char* argv[]) {
|
||||
std::vector<int> Pvalues;
|
||||
const char* outputFilename = NULL;
|
||||
const char* auxFilename = NULL;
|
||||
std::vector<int32_t> Names;
|
||||
std::vector<SignalProvider> Providers;
|
||||
std::vector<int32_t> names;
|
||||
std::vector<SignalProvider> providers;
|
||||
std::vector<audio_format_t> formats;
|
||||
|
||||
for (int ch; (ch = getopt(argc, argv, "fmc:s:o:a:P:")) != -1;) {
|
||||
switch (ch) {
|
||||
@ -138,54 +135,65 @@ int main(int argc, char* argv[]) {
|
||||
size_t outputFrames = 0;
|
||||
|
||||
// create providers for each track
|
||||
Providers.resize(argc);
|
||||
names.resize(argc);
|
||||
providers.resize(argc);
|
||||
formats.resize(argc);
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
static const char chirp[] = "chirp:";
|
||||
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))) {
|
||||
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) {
|
||||
printf("creating chirp(%d %d)\n", v[0], v[1]);
|
||||
if (useInputFloat) {
|
||||
Providers[i].setChirp<float>(v[0], 0, v[1]/2, v[1], kSeconds);
|
||||
if (useFloat) {
|
||||
providers[i].setChirp<float>(v[0], 0, v[1]/2, v[1], kSeconds);
|
||||
formats[i] = AUDIO_FORMAT_PCM_FLOAT;
|
||||
} 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 {
|
||||
fprintf(stderr, "malformed input '%s'\n", argv[i]);
|
||||
}
|
||||
} else if (!strncmp(argv[i], sine, strlen(sine))) {
|
||||
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) {
|
||||
printf("creating sine(%d %d %d)\n", v[0], v[1], v[2]);
|
||||
if (useInputFloat) {
|
||||
Providers[i].setSine<float>(v[0], v[1], v[2], kSeconds);
|
||||
if (useFloat) {
|
||||
providers[i].setSine<float>(v[0], v[1], v[2], kSeconds);
|
||||
formats[i] = AUDIO_FORMAT_PCM_FLOAT;
|
||||
} 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 {
|
||||
fprintf(stderr, "malformed input '%s'\n", argv[i]);
|
||||
}
|
||||
} else {
|
||||
printf("creating filename(%s)\n", argv[i]);
|
||||
if (useInputFloat) {
|
||||
Providers[i].setFile<float>(argv[i]);
|
||||
providers[i].setFile<float>(argv[i]);
|
||||
formats[i] = AUDIO_FORMAT_PCM_FLOAT;
|
||||
} 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
|
||||
size_t nframes = (int64_t) Providers[i].getNumFrames() * outputSampleRate
|
||||
/ Providers[i].getSampleRate();
|
||||
size_t nframes = (int64_t) providers[i].getNumFrames() * outputSampleRate
|
||||
/ providers[i].getSampleRate();
|
||||
if (i == 0 || outputFrames > nframes) { // choose minimum for outputFrames
|
||||
outputFrames = nframes;
|
||||
}
|
||||
@ -213,22 +221,20 @@ int main(int argc, char* argv[]) {
|
||||
// create the mixer.
|
||||
const size_t mixerFrameCount = 320; // typical numbers may range from 240 or 960
|
||||
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_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
|
||||
|
||||
// set up the tracks.
|
||||
for (size_t i = 0; i < Providers.size(); ++i) {
|
||||
//printf("track %d out of %d\n", i, Providers.size());
|
||||
uint32_t channelMask = audio_channel_out_mask_from_count(Providers[i].getNumChannels());
|
||||
for (size_t i = 0; i < providers.size(); ++i) {
|
||||
//printf("track %d out of %d\n", i, providers.size());
|
||||
uint32_t channelMask = audio_channel_out_mask_from_count(providers[i].getNumChannels());
|
||||
int32_t name = mixer->getTrackName(channelMask,
|
||||
inputFormat, AUDIO_SESSION_OUTPUT_MIX);
|
||||
formats[i], AUDIO_SESSION_OUTPUT_MIX);
|
||||
ALOG_ASSERT(name >= 0);
|
||||
Names.push_back(name);
|
||||
mixer->setBufferProvider(name, &Providers[i]);
|
||||
names[i] = name;
|
||||
mixer->setBufferProvider(name, &providers[i]);
|
||||
mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
|
||||
(void *)outputAddr);
|
||||
mixer->setParameter(
|
||||
@ -240,7 +246,7 @@ int main(int argc, char* argv[]) {
|
||||
name,
|
||||
AudioMixer::TRACK,
|
||||
AudioMixer::FORMAT,
|
||||
(void *)(uintptr_t)inputFormat);
|
||||
(void *)(uintptr_t)formats[i]);
|
||||
mixer->setParameter(
|
||||
name,
|
||||
AudioMixer::TRACK,
|
||||
@ -255,7 +261,7 @@ int main(int argc, char* argv[]) {
|
||||
name,
|
||||
AudioMixer::RESAMPLE,
|
||||
AudioMixer::SAMPLE_RATE,
|
||||
(void *)(uintptr_t)Providers[i].getSampleRate());
|
||||
(void *)(uintptr_t)providers[i].getSampleRate());
|
||||
if (useRamp) {
|
||||
mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &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.
|
||||
size_t i;
|
||||
for (i = 0; i < outputFrames - mixerFrameCount; i += mixerFrameCount) {
|
||||
for (size_t j = 0; j < Names.size(); ++j) {
|
||||
mixer->setParameter(Names[j], AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
|
||||
for (size_t j = 0; j < names.size(); ++j) {
|
||||
mixer->setParameter(names[j], AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
|
||||
(char *) outputAddr + i * outputFrameSize);
|
||||
if (auxFilename) {
|
||||
mixer->setParameter(Names[j], AudioMixer::TRACK, AudioMixer::AUX_BUFFER,
|
||||
mixer->setParameter(names[j], AudioMixer::TRACK, AudioMixer::AUX_BUFFER,
|
||||
(char *) auxAddr + i * auxFrameSize);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user