summaryrefslogtreecommitdiffstats
path: root/services/audioflinger
diff options
context:
space:
mode:
Diffstat (limited to 'services/audioflinger')
-rw-r--r--services/audioflinger/AudioFlinger.cpp24
-rw-r--r--services/audioflinger/AudioFlinger.h23
-rw-r--r--services/audioflinger/AudioMixer.cpp511
-rw-r--r--services/audioflinger/AudioMixer.h42
-rw-r--r--services/audioflinger/AudioMixerOps.h361
-rw-r--r--services/audioflinger/AudioResampler.cpp11
-rw-r--r--services/audioflinger/AudioResamplerDyn.cpp162
-rw-r--r--services/audioflinger/AudioResamplerDyn.h4
-rw-r--r--services/audioflinger/AudioResamplerFirProcess.h208
-rw-r--r--services/audioflinger/FastMixer.cpp18
-rw-r--r--services/audioflinger/PatchPanel.cpp6
-rw-r--r--services/audioflinger/PlaybackTracks.h7
-rw-r--r--services/audioflinger/ServiceUtilities.cpp7
-rw-r--r--services/audioflinger/ServiceUtilities.h1
-rwxr-xr-x[-rw-r--r--]services/audioflinger/Threads.cpp410
-rw-r--r--services/audioflinger/Threads.h44
-rw-r--r--services/audioflinger/Tracks.cpp52
-rw-r--r--services/audioflinger/tests/Android.mk73
-rwxr-xr-xservices/audioflinger/tests/build_and_run_all_unit_tests.sh22
-rwxr-xr-xservices/audioflinger/tests/mixer_to_wav_tests.sh134
-rw-r--r--services/audioflinger/tests/resampler_tests.cpp317
-rwxr-xr-xservices/audioflinger/tests/run_all_unit_tests.sh11
-rw-r--r--services/audioflinger/tests/test-mixer.cpp286
-rw-r--r--services/audioflinger/tests/test_utils.h307
24 files changed, 2717 insertions, 324 deletions
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 60810d5..a269886 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -169,7 +169,8 @@ AudioFlinger::AudioFlinger()
mBtNrecIsOff(false),
mIsLowRamDevice(true),
mIsDeviceTypeKnown(false),
- mGlobalEffectEnableTime(0)
+ mGlobalEffectEnableTime(0),
+ mPrimaryOutputSampleRate(0)
{
getpid_cached = getpid();
char value[PROPERTY_VALUE_MAX];
@@ -1609,6 +1610,19 @@ audio_io_handle_t AudioFlinger::openOutput(audio_module_handle_t module,
mHardwareStatus = AUDIO_HW_OUTPUT_OPEN;
audio_stream_out_t *outStream = NULL;
+
+ // FOR TESTING ONLY:
+ // Enable increased sink precision for mixing mode if kEnableExtendedPrecision is true.
+ if (kEnableExtendedPrecision && // Check only for Normal Mixing mode
+ !(flags & (AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD | AUDIO_OUTPUT_FLAG_DIRECT))) {
+ // Update format
+ //config.format = AUDIO_FORMAT_PCM_FLOAT;
+ //config.format = AUDIO_FORMAT_PCM_24_BIT_PACKED;
+ //config.format = AUDIO_FORMAT_PCM_32_BIT;
+ //config.format = AUDIO_FORMAT_PCM_8_24_BIT;
+ // ALOGV("openOutput() upgrading format to %#08x", config.format);
+ }
+
status_t status = hwDevHal->open_output_stream(hwDevHal,
id,
*pDevices,
@@ -1632,9 +1646,9 @@ audio_io_handle_t AudioFlinger::openOutput(audio_module_handle_t module,
if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
thread = new OffloadThread(this, output, id, *pDevices);
ALOGV("openOutput() created offload output: ID %d thread %p", id, thread);
- } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT) ||
- (config.format != AUDIO_FORMAT_PCM_16_BIT) ||
- (config.channel_mask != AUDIO_CHANNEL_OUT_STEREO)) {
+ } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)
+ || !isValidPcmSinkFormat(config.format)
+ || (config.channel_mask != AUDIO_CHANNEL_OUT_STEREO)) {
thread = new DirectOutputThread(this, output, id, *pDevices);
ALOGV("openOutput() created direct output: ID %d thread %p", id, thread);
} else {
@@ -1668,6 +1682,8 @@ audio_io_handle_t AudioFlinger::openOutput(audio_module_handle_t module,
mHardwareStatus = AUDIO_HW_SET_MODE;
hwDevHal->set_mode(hwDevHal, mMode);
mHardwareStatus = AUDIO_HW_IDLE;
+
+ mPrimaryOutputSampleRate = config.sample_rate;
}
return id;
}
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 19b1732..1ccef24 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -50,6 +50,8 @@
#include <media/AudioBufferProvider.h>
#include <media/ExtendedAudioBufferProvider.h>
+
+#include "FastCapture.h"
#include "FastMixer.h"
#include <media/nbaio/NBAIO.h>
#include "AudioWatchdog.h"
@@ -323,6 +325,24 @@ private:
audio_devices_t devices);
void purgeStaleEffects_l();
+ // Set kEnableExtendedPrecision to true to use extended precision in MixerThread
+ static const bool kEnableExtendedPrecision = false;
+
+ // Returns true if format is permitted for the PCM sink in the MixerThread
+ static inline bool isValidPcmSinkFormat(audio_format_t format) {
+ switch (format) {
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return true;
+ case AUDIO_FORMAT_PCM_FLOAT:
+ case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+ case AUDIO_FORMAT_PCM_32_BIT:
+ case AUDIO_FORMAT_PCM_8_24_BIT:
+ return kEnableExtendedPrecision;
+ default:
+ return false;
+ }
+ }
+
// standby delay for MIXER and DUPLICATING playback threads is read from property
// ro.audio.flinger_standbytime_ms or defaults to kDefaultStandbyTimeInNsecs
static nsecs_t mStandbyTimeInNsecs;
@@ -690,6 +710,9 @@ private:
nsecs_t mGlobalEffectEnableTime; // when a global effect was last enabled
sp<PatchPanel> mPatchPanel;
+
+ uint32_t mPrimaryOutputSampleRate; // sample rate of the primary output, or zero if none
+ // protected by mHardwareLock
};
#undef INCLUDING_FROM_AUDIOFLINGER_H
diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp
index ace3bf1..af312c4 100644
--- a/services/audioflinger/AudioMixer.cpp
+++ b/services/audioflinger/AudioMixer.cpp
@@ -40,8 +40,36 @@
#include <media/EffectsFactoryApi.h>
+#include "AudioMixerOps.h"
#include "AudioMixer.h"
+// Use the FCC_2 macro for code assuming Fixed Channel Count of 2 and
+// whose stereo assumption may need to be revisited later.
+#ifndef FCC_2
+#define FCC_2 2
+#endif
+
+/* VERY_VERY_VERBOSE_LOGGING will show exactly which process hook and track hook is
+ * being used. This is a considerable amount of log spam, so don't enable unless you
+ * are verifying the hook based code.
+ */
+//#define VERY_VERY_VERBOSE_LOGGING
+#ifdef VERY_VERY_VERBOSE_LOGGING
+#define ALOGVV ALOGV
+//define ALOGVV printf // for test-mixer.cpp
+#else
+#define ALOGVV(a...) do { } while (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;
+
+// Set kUseFloat to true to allow floating input into the mixer engine.
+// If kUseNewMixer is false, this is ignored or may be overridden internally
+// because of downmix/upmix support.
+static const bool kUseFloat = true;
+
namespace android {
// ----------------------------------------------------------------------------
@@ -265,8 +293,8 @@ int AudioMixer::getTrackName(audio_channel_mask_t channelMask,
// assume default parameters for the track, except where noted below
track_t* t = &mState.tracks[n];
t->needs = 0;
- t->volume[0] = UNITY_GAIN;
- t->volume[1] = UNITY_GAIN;
+ t->volume[0] = UNITY_GAIN_INT;
+ t->volume[1] = UNITY_GAIN_INT;
// no initialization needed
// t->prevVolume[0]
// t->prevVolume[1]
@@ -300,15 +328,19 @@ int AudioMixer::getTrackName(audio_channel_mask_t channelMask,
t->downmixerBufferProvider = NULL;
t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT;
t->mFormat = format;
- t->mMixerInFormat = AUDIO_FORMAT_PCM_16_BIT;
- if (t->mFormat != t->mMixerInFormat) {
- prepareTrackForReformat(t, n);
- }
- status_t status = initTrackDownmix(&mState.tracks[n], n, channelMask);
+ t->mMixerInFormat = kUseFloat && kUseNewMixer
+ ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
+ // Check the downmixing (or upmixing) requirements.
+ status_t status = initTrackDownmix(t, n, channelMask);
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.
+ ALOGVV("mMixerFormat:%#x mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat);
+ prepareTrackForReformat(t, n);
mTrackNames |= 1 << n;
return TRACK0 + n;
}
@@ -443,6 +475,7 @@ status_t AudioMixer::prepareTrackForDownmix(track_t* pTrack, int trackName)
}// end of scope for local variables that are not used in goto label "noDownmixForActiveTrack"
// initialization successful:
+ pTrack->mMixerInFormat = AUDIO_FORMAT_PCM_16_BIT; // 16 bit input is required for downmix
pTrack->downmixerBufferProvider = pDbp;
reconfigureBufferProviders(pTrack);
return NO_ERROR;
@@ -467,12 +500,15 @@ status_t AudioMixer::prepareTrackForReformat(track_t* pTrack, int trackName)
{
ALOGV("AudioMixer::prepareTrackForReformat(%d) with format %#x", trackName, pTrack->mFormat);
// discard the previous reformatter if there was one
- unprepareTrackForReformat(pTrack, trackName);
- pTrack->mReformatBufferProvider = new ReformatBufferProvider(
- audio_channel_count_from_out_mask(pTrack->channelMask),
- pTrack->mFormat, pTrack->mMixerInFormat);
- reconfigureBufferProviders(pTrack);
- return NO_ERROR;
+ 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);
+ reconfigureBufferProviders(pTrack);
+ }
+ return NO_ERROR;
}
void AudioMixer::reconfigureBufferProviders(track_t* pTrack)
@@ -536,6 +572,44 @@ void AudioMixer::disable(int name)
}
}
+/* Sets the volume ramp variables for the AudioMixer.
+ *
+ * The volume ramp variables are used to transition between the previous
+ * volume to the target volume. The duration of the transition is
+ * set by ramp, which is either 0 for immediate, or typically one state
+ * framecount period.
+ *
+ * @param newFloatValue new volume target in float [0.0, 1.0].
+ * @param ramp number of frames to increment over. ramp is 0 if the volume
+ * should be set immediately.
+ * @param volume reference to the U4.12 target volume, set on return.
+ * @param prevVolume reference to the U4.27 previous volume, set on return.
+ * @param volumeInc reference to the increment per output audio frame, set on return.
+ * @return true if the volume has changed, false if volume is same.
+ */
+static inline bool setVolumeRampVariables(float newFloatValue, int32_t ramp,
+ int16_t &volume, int32_t &prevVolume, int32_t &volumeInc) {
+ int32_t newValue = newFloatValue * AudioMixer::UNITY_GAIN_INT;
+ if (newValue > AudioMixer::UNITY_GAIN_INT) {
+ newValue = AudioMixer::UNITY_GAIN_INT;
+ } else if (newValue < 0) {
+ ALOGE("negative volume %.7g", newFloatValue);
+ newValue = 0; // should never happen, but for safety check.
+ }
+ if (newValue == volume) {
+ return false;
+ }
+ if (ramp != 0) {
+ volumeInc = ((newValue - volume) << 16) / ramp;
+ prevVolume = (volumeInc == 0 ? newValue : volume) << 16;
+ } else {
+ volumeInc = 0;
+ prevVolume = newValue << 16;
+ }
+ volume = newValue;
+ return true;
+}
+
void AudioMixer::setParameter(int name, int target, int param, void *value)
{
name -= TRACK0;
@@ -558,8 +632,15 @@ void AudioMixer::setParameter(int name, int target, int param, void *value)
track.channelMask = mask;
track.channelCount = channelCount;
// the mask has changed, does this track need a downmixer?
- initTrackDownmix(&mState.tracks[name], name, mask);
+ // update to try using our desired format (if we aren't already using it)
+ track.mMixerInFormat = kUseFloat && kUseNewMixer
+ ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
+ status_t status = initTrackDownmix(&mState.tracks[name], name, mask);
+ ALOGE_IF(status != OK,
+ "Invalid channel mask %#x, initTrackDownmix returned %d",
+ mask, status);
ALOGV("setParameter(TRACK, CHANNEL_MASK, %x)", mask);
+ prepareTrackForReformat(&track, name); // format may have changed
invalidateState(1 << name);
}
} break;
@@ -583,11 +664,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);
- //if (track.mFormat != track.mMixerInFormat)
- {
- ALOGD("Reformatting!");
- prepareTrackForReformat(&track, name);
- }
+ prepareTrackForReformat(&track, name);
invalidateState(1 << name);
}
} break;
@@ -637,41 +714,23 @@ void AudioMixer::setParameter(int name, int target, int param, void *value)
switch (param) {
case VOLUME0:
case VOLUME1:
- if (track.volume[param-VOLUME0] != valueInt) {
- ALOGV("setParameter(VOLUME, VOLUME0/1: %04x)", valueInt);
- track.prevVolume[param-VOLUME0] = track.volume[param-VOLUME0] << 16;
- track.volume[param-VOLUME0] = valueInt;
- if (target == VOLUME) {
- track.prevVolume[param-VOLUME0] = valueInt << 16;
- track.volumeInc[param-VOLUME0] = 0;
- } else {
- int32_t d = (valueInt<<16) - track.prevVolume[param-VOLUME0];
- int32_t volInc = d / int32_t(mState.frameCount);
- track.volumeInc[param-VOLUME0] = volInc;
- if (volInc == 0) {
- track.prevVolume[param-VOLUME0] = valueInt << 16;
- }
- }
+ if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
+ target == RAMP_VOLUME ? mState.frameCount : 0,
+ track.volume[param - VOLUME0], track.prevVolume[param - VOLUME0],
+ track.volumeInc[param - VOLUME0])) {
+ ALOGV("setParameter(%s, VOLUME%d: %04x)",
+ target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0,
+ track.volume[param - VOLUME0]);
invalidateState(1 << name);
}
break;
case AUXLEVEL:
//ALOG_ASSERT(0 <= valueInt && valueInt <= MAX_GAIN_INT, "bad aux level %d", valueInt);
- if (track.auxLevel != valueInt) {
- ALOGV("setParameter(VOLUME, AUXLEVEL: %04x)", valueInt);
- track.prevAuxLevel = track.auxLevel << 16;
- track.auxLevel = valueInt;
- if (target == VOLUME) {
- track.prevAuxLevel = valueInt << 16;
- track.auxInc = 0;
- } else {
- int32_t d = (valueInt<<16) - track.prevAuxLevel;
- int32_t volInc = d / int32_t(mState.frameCount);
- track.auxInc = volInc;
- if (volInc == 0) {
- track.prevAuxLevel = valueInt << 16;
- }
- }
+ if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
+ target == RAMP_VOLUME ? mState.frameCount : 0,
+ track.auxLevel, track.prevAuxLevel, track.auxInc)) {
+ ALOGV("setParameter(%s, AUXLEVEL: %04x)",
+ target == VOLUME ? "VOLUME" : "RAMP_VOLUME", track.auxLevel);
invalidateState(1 << name);
}
break;
@@ -703,7 +762,20 @@ bool AudioMixer::track_t::setResampler(uint32_t value, uint32_t devSampleRate)
} else {
quality = AudioResampler::DEFAULT_QUALITY;
}
- const int bits = mMixerInFormat == AUDIO_FORMAT_PCM_16_BIT ? 16 : /* FLOAT */ 32;
+
+ int bits;
+ switch (mMixerInFormat) {
+ case AUDIO_FORMAT_PCM_16_BIT:
+ bits = 16;
+ break;
+ case AUDIO_FORMAT_PCM_FLOAT:
+ bits = 32; // 32 bits to the AudioResampler::create() indicates float.
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Invalid mMixerInFormat: %#x", mMixerInFormat);
+ break;
+ }
+ ALOGVV("Creating resampler with %d bits\n", bits);
resampler = AudioResampler::create(
bits,
// the resampler sees the number of channels after the downmixer, if any
@@ -828,16 +900,19 @@ void AudioMixer::process__validate(state_t* state, int64_t pts)
if (n & NEEDS_RESAMPLE) {
all16BitsStereoNoResample = false;
resampling = true;
- t.hook = track__genericResample;
+ t.hook = getTrackHook(TRACKTYPE_RESAMPLE, FCC_2,
+ t.mMixerInFormat, t.mMixerFormat);
ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,
"Track %d needs downmix + resample", i);
} else {
if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){
- t.hook = track__16BitsMono;
+ t.hook = getTrackHook(TRACKTYPE_NORESAMPLEMONO, FCC_2,
+ t.mMixerInFormat, t.mMixerFormat);
all16BitsStereoNoResample = false;
}
if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2){
- t.hook = track__16BitsStereo;
+ t.hook = getTrackHook(TRACKTYPE_NORESAMPLE, FCC_2,
+ t.mMixerInFormat, t.mMixerFormat);
ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,
"Track %d needs downmix", i);
}
@@ -868,7 +943,10 @@ void AudioMixer::process__validate(state_t* state, int64_t pts)
state->hook = process__genericNoResampling;
if (all16BitsStereoNoResample && !volumeRamp) {
if (countActiveTracks == 1) {
- state->hook = process__OneTrack16BitsStereoNoResampling;
+ const int i = 31 - __builtin_clz(state->enabledTracks);
+ track_t& t = state->tracks[i];
+ state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, FCC_2,
+ t.mMixerInFormat, t.mMixerFormat);
}
}
}
@@ -911,6 +989,7 @@ void AudioMixer::process__validate(state_t* state, int64_t pts)
void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFrameCount,
int32_t* temp, int32_t* aux)
{
+ ALOGVV("track__genericResample\n");
t->resampler->setSampleRate(t->sampleRate);
// ramp gain - resample to temp buffer and scale/mix in 2nd step
@@ -918,7 +997,7 @@ void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFram
// always resample with unity gain when sending to auxiliary buffer to be able
// to apply send level after resampling
// TODO: modify each resampler to support aux channel?
- t->resampler->setVolume(UNITY_GAIN, UNITY_GAIN);
+ t->resampler->setVolume(UNITY_GAIN_INT, UNITY_GAIN_INT);
memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t));
t->resampler->resample(temp, outFrameCount, t->bufferProvider);
if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc)) {
@@ -928,7 +1007,7 @@ void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFram
}
} else {
if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1])) {
- t->resampler->setVolume(UNITY_GAIN, UNITY_GAIN);
+ t->resampler->setVolume(UNITY_GAIN_INT, UNITY_GAIN_INT);
memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t));
t->resampler->resample(temp, outFrameCount, t->bufferProvider);
volumeRampStereo(t, out, outFrameCount, temp, aux);
@@ -1022,6 +1101,7 @@ void AudioMixer::volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32
void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount,
int32_t* temp __unused, int32_t* aux)
{
+ ALOGVV("track__16BitsStereo\n");
const int16_t *in = static_cast<const int16_t *>(t->in);
if (CC_UNLIKELY(aux != NULL)) {
@@ -1113,6 +1193,7 @@ void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount
void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount,
int32_t* temp __unused, int32_t* aux)
{
+ ALOGVV("track__16BitsMono\n");
const int16_t *in = static_cast<int16_t const *>(t->in);
if (CC_UNLIKELY(aux != NULL)) {
@@ -1200,6 +1281,7 @@ void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount,
// no-op case
void AudioMixer::process__nop(state_t* state, int64_t pts)
{
+ ALOGVV("process__nop\n");
uint32_t e0 = state->enabledTracks;
size_t sampleCount = state->frameCount * MAX_NUM_CHANNELS;
while (e0) {
@@ -1247,6 +1329,7 @@ void AudioMixer::process__nop(state_t* state, int64_t pts)
// generic code without resampling
void AudioMixer::process__genericNoResampling(state_t* state, int64_t pts)
{
+ ALOGVV("process__genericNoResampling\n");
int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32)));
// acquire each track's buffer
@@ -1329,18 +1412,12 @@ void AudioMixer::process__genericNoResampling(state_t* state, int64_t pts)
}
}
}
- switch (t1.mMixerFormat) {
- case AUDIO_FORMAT_PCM_FLOAT:
- memcpy_to_float_from_q4_27(reinterpret_cast<float *>(out), outTemp, BLOCKSIZE * 2);
- out += BLOCKSIZE * 2; // output is 2 floats/frame.
- break;
- case AUDIO_FORMAT_PCM_16_BIT:
- ditherAndClamp(out, outTemp, BLOCKSIZE);
- out += BLOCKSIZE; // output is 1 int32_t (2 int16_t samples)/frame
- break;
- default:
- LOG_ALWAYS_FATAL("bad mixer format: %d", t1.mMixerFormat);
- }
+
+ convertMixerFormat(out, t1.mMixerFormat, outTemp, t1.mMixerInFormat,
+ BLOCKSIZE * FCC_2);
+ // TODO: fix ugly casting due to choice of out pointer type
+ out = reinterpret_cast<int32_t*>((uint8_t*)out
+ + BLOCKSIZE * FCC_2 * audio_bytes_per_sample(t1.mMixerFormat));
numFrames += BLOCKSIZE;
} while (numFrames < state->frameCount);
}
@@ -1359,6 +1436,7 @@ void AudioMixer::process__genericNoResampling(state_t* state, int64_t pts)
// generic code with resampling
void AudioMixer::process__genericResampling(state_t* state, int64_t pts)
{
+ ALOGVV("process__genericResampling\n");
// this const just means that local variable outTemp doesn't change
int32_t* const outTemp = state->outputTemp;
const size_t size = sizeof(int32_t) * MAX_NUM_CHANNELS * state->frameCount;
@@ -1422,16 +1500,7 @@ void AudioMixer::process__genericResampling(state_t* state, int64_t pts)
}
}
}
- switch (t1.mMixerFormat) {
- case AUDIO_FORMAT_PCM_FLOAT:
- memcpy_to_float_from_q4_27(reinterpret_cast<float*>(out), outTemp, numFrames*2);
- break;
- case AUDIO_FORMAT_PCM_16_BIT:
- ditherAndClamp(out, outTemp, numFrames);
- break;
- default:
- LOG_ALWAYS_FATAL("bad mixer format: %d", t1.mMixerFormat);
- }
+ convertMixerFormat(out, t1.mMixerFormat, outTemp, t1.mMixerInFormat, numFrames * FCC_2);
}
}
@@ -1439,6 +1508,7 @@ void AudioMixer::process__genericResampling(state_t* state, int64_t pts)
void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state,
int64_t pts)
{
+ ALOGVV("process__OneTrack16BitsStereoNoResampling\n");
// This method is only called when state->enabledTracks has exactly
// one bit set. The asserts below would verify this, but are commented out
// since the whole point of this method is to optimize performance.
@@ -1450,6 +1520,7 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state,
AudioBufferProvider::Buffer& b(t.buffer);
int32_t* out = t.mainBuffer;
+ float *fout = reinterpret_cast<float*>(out);
size_t numFrames = state->frameCount;
const int16_t vl = t.volume[0];
@@ -1463,9 +1534,10 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state,
// in == NULL can happen if the track was flushed just after having
// been enabled for mixing.
- if (in == NULL || ((unsigned long)in & 3)) {
- memset(out, 0, numFrames*MAX_NUM_CHANNELS*sizeof(int16_t));
- ALOGE_IF(((unsigned long)in & 3), "process stereo track: input buffer alignment pb: "
+ if (in == NULL || (((uintptr_t)in) & 3)) {
+ memset(out, 0, numFrames
+ * MAX_NUM_CHANNELS * audio_bytes_per_sample(t.mMixerFormat));
+ ALOGE_IF((((uintptr_t)in) & 3), "process stereo track: input buffer alignment pb: "
"buffer %p track %d, channels %d, needs %08x",
in, i, t.channelCount, t.needs);
return;
@@ -1473,8 +1545,7 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state,
size_t outFrames = b.frameCount;
switch (t.mMixerFormat) {
- case AUDIO_FORMAT_PCM_FLOAT: {
- float *fout = reinterpret_cast<float*>(out);
+ case AUDIO_FORMAT_PCM_FLOAT:
do {
uint32_t rl = *reinterpret_cast<const uint32_t *>(in);
in += 2;
@@ -1485,9 +1556,9 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state,
// Note: In case of later int16_t sink output,
// conversion and clamping is done by memcpy_to_i16_from_float().
} while (--outFrames);
- } break;
+ break;
case AUDIO_FORMAT_PCM_16_BIT:
- if (CC_UNLIKELY(uint32_t(vl) > UNITY_GAIN || uint32_t(vr) > UNITY_GAIN)) {
+ if (CC_UNLIKELY(uint32_t(vl) > UNITY_GAIN_INT || uint32_t(vr) > UNITY_GAIN_INT)) {
// volume is boosted, so we might need to clamp even though
// we process only one track.
do {
@@ -1662,5 +1733,275 @@ int64_t AudioMixer::calculateOutputPTS(const track_t& t, int64_t basePTS,
ALOGW_IF(!sIsMultichannelCapable, "unable to find downmix effect");
}
+/* This process hook is called when there is a single track without
+ * aux buffer, volume ramp, or resampling.
+ * TODO: Update the hook selection: this can properly handle aux and ramp.
+ */
+template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+void AudioMixer::process_NoResampleOneTrack(state_t* state, int64_t pts)
+{
+ ALOGVV("process_NoResampleOneTrack\n");
+ // CLZ is faster than CTZ on ARM, though really not sure if true after 31 - clz.
+ const int i = 31 - __builtin_clz(state->enabledTracks);
+ ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled");
+ track_t *t = &state->tracks[i];
+ TO* out = reinterpret_cast<TO*>(t->mainBuffer);
+ TA* aux = reinterpret_cast<TA*>(t->auxBuffer);
+ const bool ramp = t->needsRamp();
+
+ for (size_t numFrames = state->frameCount; numFrames; ) {
+ AudioBufferProvider::Buffer& b(t->buffer);
+ // get input buffer
+ b.frameCount = numFrames;
+ const int64_t outputPTS = calculateOutputPTS(*t, pts, state->frameCount - numFrames);
+ t->bufferProvider->getNextBuffer(&b, outputPTS);
+ const TI *in = reinterpret_cast<TI*>(b.raw);
+
+ // in == NULL can happen if the track was flushed just after having
+ // been enabled for mixing.
+ if (in == NULL || (((uintptr_t)in) & 3)) {
+ memset(out, 0, numFrames
+ * NCHAN * audio_bytes_per_sample(t->mMixerFormat));
+ ALOGE_IF((((uintptr_t)in) & 3), "process_NoResampleOneTrack: bus error: "
+ "buffer %p track %p, channels %d, needs %#x",
+ in, t, t->channelCount, t->needs);
+ return;
+ }
+
+ const size_t outFrames = b.frameCount;
+ if (ramp) {
+ volumeRampMulti<MIXTYPE_MULTI_SAVEONLY, NCHAN>(out, outFrames, in, aux,
+ t->prevVolume, t->volumeInc, &t->prevAuxLevel, t->auxInc);
+ } else {
+ volumeMulti<MIXTYPE_MULTI_SAVEONLY, NCHAN>(out, outFrames, in, aux,
+ t->volume, t->auxLevel);
+ }
+ out += outFrames * NCHAN;
+ if (aux != NULL) {
+ aux += NCHAN;
+ }
+ numFrames -= b.frameCount;
+
+ // release buffer
+ t->bufferProvider->releaseBuffer(&b);
+ }
+ if (ramp) {
+ t->adjustVolumeRamp(aux != NULL);
+ }
+}
+
+/* This track hook is called to do resampling then mixing,
+ * pulling from the track's upstream AudioBufferProvider.
+ */
+template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+void AudioMixer::track__Resample(track_t* t, TO* out, size_t outFrameCount, TO* temp, TA* aux)
+{
+ ALOGVV("track__Resample\n");
+ t->resampler->setSampleRate(t->sampleRate);
+
+ const bool ramp = t->needsRamp();
+ if (ramp || aux != NULL) {
+ // if ramp: resample with unity gain to temp buffer and scale/mix in 2nd step.
+ // if aux != NULL: resample with unity gain to temp buffer then apply send level.
+
+ t->resampler->setVolume(UNITY_GAIN_INT, UNITY_GAIN_INT);
+ memset(temp, 0, outFrameCount * NCHAN * sizeof(TO));
+ t->resampler->resample((int32_t*)temp, outFrameCount, t->bufferProvider);
+ if (ramp) {
+ volumeRampMulti<MIXTYPE_MULTI, NCHAN>(out, outFrameCount, temp, aux,
+ t->prevVolume, t->volumeInc, &t->prevAuxLevel, t->auxInc);
+ t->adjustVolumeRamp(aux != NULL);
+ } else {
+ volumeMulti<MIXTYPE_MULTI, NCHAN>(out, outFrameCount, temp, aux,
+ t->volume, t->auxLevel);
+ }
+ } else { // constant volume gain
+ t->resampler->setVolume(t->volume[0], t->volume[1]);
+ t->resampler->resample((int32_t*)out, outFrameCount, t->bufferProvider);
+ }
+}
+
+/* This track hook is called to mix a track, when no resampling is required.
+ * The input buffer should be present in t->in.
+ */
+template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+void AudioMixer::track__NoResample(track_t* t, TO* out, size_t frameCount,
+ TO* temp __unused, TA* aux)
+{
+ ALOGVV("track__NoResample\n");
+ const TI *in = static_cast<const TI *>(t->in);
+
+ if (t->needsRamp()) {
+ volumeRampMulti<MIXTYPE, NCHAN>(out, frameCount, in, aux,
+ t->prevVolume, t->volumeInc, &t->prevAuxLevel, t->auxInc);
+ t->adjustVolumeRamp(aux != NULL);
+ } else {
+ volumeMulti<MIXTYPE, NCHAN>(out, frameCount, in, aux, t->volume, t->auxLevel);
+ }
+ // MIXTYPE_MONOEXPAND reads a single input channel and expands to NCHAN output channels.
+ // MIXTYPE_MULTI reads NCHAN input channels and places to NCHAN output channels.
+ in += (MIXTYPE == MIXTYPE_MONOEXPAND) ? frameCount : frameCount * NCHAN;
+ t->in = in;
+}
+
+/* The Mixer engine generates either int32_t (Q4_27) or float data.
+ * We use this function to convert the engine buffers
+ * to the desired mixer output format, either int16_t (Q.15) or float.
+ */
+void AudioMixer::convertMixerFormat(void *out, audio_format_t mixerOutFormat,
+ void *in, audio_format_t mixerInFormat, size_t sampleCount)
+{
+ switch (mixerInFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ switch (mixerOutFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ memcpy(out, in, sampleCount * sizeof(float)); // MEMCPY. TODO optimize out
+ break;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ memcpy_to_i16_from_float((int16_t*)out, (float*)in, sampleCount);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+ break;
+ }
+ break;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ switch (mixerOutFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ memcpy_to_float_from_q4_27((float*)out, (int32_t*)in, sampleCount);
+ break;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ // two int16_t are produced per iteration
+ ditherAndClamp((int32_t*)out, (int32_t*)in, sampleCount >> 1);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+ break;
+ }
+ break;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+ break;
+ }
+}
+
+/* Returns the proper track hook to use for mixing the track into the output buffer.
+ */
+AudioMixer::hook_t AudioMixer::getTrackHook(int trackType, int channels,
+ audio_format_t mixerInFormat, audio_format_t mixerOutFormat __unused)
+{
+ if (!kUseNewMixer && channels == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) {
+ switch (trackType) {
+ case TRACKTYPE_NOP:
+ return track__nop;
+ case TRACKTYPE_RESAMPLE:
+ return track__genericResample;
+ case TRACKTYPE_NORESAMPLEMONO:
+ return track__16BitsMono;
+ case TRACKTYPE_NORESAMPLE:
+ return track__16BitsStereo;
+ default:
+ LOG_ALWAYS_FATAL("bad trackType: %d", trackType);
+ break;
+ }
+ }
+ LOG_ALWAYS_FATAL_IF(channels != FCC_2); // TODO: must be stereo right now
+ switch (trackType) {
+ case TRACKTYPE_NOP:
+ return track__nop;
+ case TRACKTYPE_RESAMPLE:
+ switch (mixerInFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return (AudioMixer::hook_t)
+ track__Resample<MIXTYPE_MULTI, 2, float, float, int32_t>;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return (AudioMixer::hook_t)\
+ track__Resample<MIXTYPE_MULTI, 2, int32_t, int16_t, int32_t>;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+ break;
+ }
+ break;
+ case TRACKTYPE_NORESAMPLEMONO:
+ switch (mixerInFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return (AudioMixer::hook_t)
+ track__NoResample<MIXTYPE_MONOEXPAND, 2, float, float, int32_t>;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return (AudioMixer::hook_t)
+ track__NoResample<MIXTYPE_MONOEXPAND, 2, int32_t, int16_t, int32_t>;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+ break;
+ }
+ break;
+ case TRACKTYPE_NORESAMPLE:
+ switch (mixerInFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return (AudioMixer::hook_t)
+ track__NoResample<MIXTYPE_MULTI, 2, float, float, int32_t>;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return (AudioMixer::hook_t)
+ track__NoResample<MIXTYPE_MULTI, 2, int32_t, int16_t, int32_t>;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+ break;
+ }
+ break;
+ default:
+ LOG_ALWAYS_FATAL("bad trackType: %d", trackType);
+ break;
+ }
+ return NULL;
+}
+
+/* Returns the proper process hook for mixing tracks. Currently works only for
+ * PROCESSTYPE_NORESAMPLEONETRACK, a mix involving one track, no resampling.
+ */
+AudioMixer::process_hook_t AudioMixer::getProcessHook(int processType, int channels,
+ audio_format_t mixerInFormat, audio_format_t mixerOutFormat)
+{
+ if (processType != PROCESSTYPE_NORESAMPLEONETRACK) { // Only NORESAMPLEONETRACK
+ LOG_ALWAYS_FATAL("bad processType: %d", processType);
+ return NULL;
+ }
+ if (!kUseNewMixer && channels == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) {
+ return process__OneTrack16BitsStereoNoResampling;
+ }
+ LOG_ALWAYS_FATAL_IF(channels != FCC_2); // TODO: must be stereo right now
+ switch (mixerInFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ switch (mixerOutFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, 2,
+ float, float, int32_t>;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, 2,
+ int16_t, float, int32_t>;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+ break;
+ }
+ break;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ switch (mixerOutFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, 2,
+ float, int16_t, int32_t>;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, 2,
+ int16_t, int16_t, int32_t>;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+ break;
+ }
+ break;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+ break;
+ }
+ return NULL;
+}
+
// ----------------------------------------------------------------------------
}; // namespace android
diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h
index 573ba96..e6de00c 100644
--- a/services/audioflinger/AudioMixer.h
+++ b/services/audioflinger/AudioMixer.h
@@ -31,7 +31,7 @@
#include <media/nbaio/NBLog.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
+#define MAX_GAIN_INT AudioMixer::UNITY_GAIN_INT
namespace android {
@@ -58,7 +58,8 @@ public:
// maximum number of channels supported for the content
static const uint32_t MAX_NUM_CHANNELS_TO_DOWNMIX = 8;
- static const uint16_t UNITY_GAIN = 0x1000;
+ static const uint16_t UNITY_GAIN_INT = 0x1000;
+ static const float UNITY_GAIN_FLOAT = 1.0f;
enum { // names
@@ -220,6 +221,7 @@ private:
// 16-byte boundary
+ bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
bool setResampler(uint32_t sampleRate, uint32_t devSampleRate);
bool doesResample() const { return resampler != NULL; }
void resetResampler() { if (resampler != NULL) resampler->reset(); }
@@ -228,12 +230,14 @@ private:
resampler->getUnreleasedFrames() : 0; };
};
+ typedef void (*process_hook_t)(state_t* state, int64_t pts);
+
// pad to 32-bytes to fill cache line
struct state_t {
uint32_t enabledTracks;
uint32_t needsChanged;
size_t frameCount;
- void (*hook)(state_t* state, int64_t pts); // one of process__*, never NULL
+ process_hook_t hook; // one of process__*, never NULL
int32_t *outputTemp;
int32_t *resampleTemp;
NBLog::Writer* mLog;
@@ -344,6 +348,38 @@ private:
static uint64_t sLocalTimeFreq;
static pthread_once_t sOnceControl;
static void sInitRoutine();
+
+ // multi-format process hooks
+ template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+ static void process_NoResampleOneTrack(state_t* state, int64_t pts);
+
+ // multi-format track hooks
+ template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+ static void track__Resample(track_t* t, TO* out, size_t frameCount,
+ TO* temp __unused, TA* aux);
+ template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+ static void track__NoResample(track_t* t, TO* out, size_t frameCount,
+ TO* temp __unused, TA* aux);
+
+ static void convertMixerFormat(void *out, audio_format_t mixerOutFormat,
+ void *in, audio_format_t mixerInFormat, size_t sampleCount);
+
+ // hook types
+ enum {
+ PROCESSTYPE_NORESAMPLEONETRACK,
+ };
+ enum {
+ TRACKTYPE_NOP,
+ TRACKTYPE_RESAMPLE,
+ TRACKTYPE_NORESAMPLE,
+ TRACKTYPE_NORESAMPLEMONO,
+ };
+
+ // functions for determining the proper process and track hooks.
+ static process_hook_t getProcessHook(int processType, int channels,
+ audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
+ static hook_t getTrackHook(int trackType, int channels,
+ audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
};
// ----------------------------------------------------------------------------
diff --git a/services/audioflinger/AudioMixerOps.h b/services/audioflinger/AudioMixerOps.h
new file mode 100644
index 0000000..de92946
--- /dev/null
+++ b/services/audioflinger/AudioMixerOps.h
@@ -0,0 +1,361 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_AUDIO_MIXER_OPS_H
+#define ANDROID_AUDIO_MIXER_OPS_H
+
+namespace android {
+
+/* Behavior of is_same<>::value is true if the types are identical,
+ * false otherwise. Identical to the STL std::is_same.
+ */
+template<typename T, typename U>
+struct is_same
+{
+ static const bool value = false;
+};
+
+template<typename T>
+struct is_same<T, T> // partial specialization
+{
+ static const bool value = true;
+};
+
+
+/* MixMul is a multiplication operator to scale an audio input signal
+ * by a volume gain, with the formula:
+ *
+ * O(utput) = I(nput) * V(olume)
+ *
+ * The output, input, and volume may have different types.
+ * There are 27 variants, of which 14 are actually defined in an
+ * explicitly templated class.
+ *
+ * The following type variables and the underlying meaning:
+ *
+ * Output type TO: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
+ * Input signal type TI: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
+ * Volume type TV: int32_t (U4.28) or int16_t (U4.12) or float [-1,1]
+ *
+ * For high precision audio, only the <TO, TI, TV> = <float, float, float>
+ * needs to be accelerated. This is perhaps the easiest form to do quickly as well.
+ */
+
+template <typename TO, typename TI, typename TV>
+inline TO MixMul(TI value, TV volume) {
+ COMPILE_TIME_ASSERT_FUNCTION_SCOPE(false);
+ // should not be here :-).
+ // To avoid mistakes, this template is always specialized.
+ return value * volume;
+}
+
+template <>
+inline int32_t MixMul<int32_t, int16_t, int16_t>(int16_t value, int16_t volume) {
+ return value * volume;
+}
+
+template <>
+inline int32_t MixMul<int32_t, int32_t, int16_t>(int32_t value, int16_t volume) {
+ return (value >> 12) * volume;
+}
+
+template <>
+inline int32_t MixMul<int32_t, int16_t, int32_t>(int16_t value, int32_t volume) {
+ return value * (volume >> 16);
+}
+
+template <>
+inline int32_t MixMul<int32_t, int32_t, int32_t>(int32_t value, int32_t volume) {
+ return (value >> 12) * (volume >> 16);
+}
+
+template <>
+inline float MixMul<float, float, int16_t>(float value, int16_t volume) {
+ static const float norm = 1. / (1 << 12);
+ return value * volume * norm;
+}
+
+template <>
+inline float MixMul<float, float, int32_t>(float value, int32_t volume) {
+ static const float norm = 1. / (1 << 28);
+ return value * volume * norm;
+}
+
+template <>
+inline int16_t MixMul<int16_t, float, int16_t>(float value, int16_t volume) {
+ return clamp16_from_float(MixMul<float, float, int16_t>(value, volume));
+}
+
+template <>
+inline int16_t MixMul<int16_t, float, int32_t>(float value, int32_t volume) {
+ return clamp16_from_float(MixMul<float, float, int32_t>(value, volume));
+}
+
+template <>
+inline float MixMul<float, int16_t, int16_t>(int16_t value, int16_t volume) {
+ static const float norm = 1. / (1 << (15 + 12));
+ return static_cast<float>(value) * static_cast<float>(volume) * norm;
+}
+
+template <>
+inline float MixMul<float, int16_t, int32_t>(int16_t value, int32_t volume) {
+ static const float norm = 1. / (1ULL << (15 + 28));
+ return static_cast<float>(value) * static_cast<float>(volume) * norm;
+}
+
+template <>
+inline int16_t MixMul<int16_t, int16_t, int16_t>(int16_t value, int16_t volume) {
+ return clamp16(MixMul<int32_t, int16_t, int16_t>(value, volume) >> 12);
+}
+
+template <>
+inline int16_t MixMul<int16_t, int32_t, int16_t>(int32_t value, int16_t volume) {
+ return clamp16(MixMul<int32_t, int32_t, int16_t>(value, volume) >> 12);
+}
+
+template <>
+inline int16_t MixMul<int16_t, int16_t, int32_t>(int16_t value, int32_t volume) {
+ return clamp16(MixMul<int32_t, int16_t, int32_t>(value, volume) >> 12);
+}
+
+template <>
+inline int16_t MixMul<int16_t, int32_t, int32_t>(int32_t value, int32_t volume) {
+ return clamp16(MixMul<int32_t, int32_t, int32_t>(value, volume) >> 12);
+}
+
+/*
+ * MixAccum is used to add into an accumulator register of a possibly different
+ * type. The TO and TI types are the same as MixMul.
+ */
+
+template <typename TO, typename TI>
+inline void MixAccum(TO *auxaccum, TI value) {
+ if (!is_same<TO, TI>::value) {
+ LOG_ALWAYS_FATAL("MixAccum type not properly specialized: %d %d\n",
+ sizeof(TO), sizeof(TI));
+ }
+ *auxaccum += value;
+}
+
+template<>
+inline void MixAccum<float, int16_t>(float *auxaccum, int16_t value) {
+ static const float norm = 1. / (1 << 15);
+ *auxaccum += norm * value;
+}
+
+template<>
+inline void MixAccum<float, int32_t>(float *auxaccum, int32_t value) {
+ static const float norm = 1. / (1 << 27);
+ *auxaccum += norm * value;
+}
+
+template<>
+inline void MixAccum<int32_t, int16_t>(int32_t *auxaccum, int16_t value) {
+ *auxaccum += value << 12;
+}
+
+template<>
+inline void MixAccum<int32_t, float>(int32_t *auxaccum, float value) {
+ *auxaccum += clampq4_27_from_float(value);
+}
+
+/* MixMulAux is just like MixMul except it combines with
+ * an accumulator operation MixAccum.
+ */
+
+template <typename TO, typename TI, typename TV, typename TA>
+inline TO MixMulAux(TI value, TV volume, TA *auxaccum) {
+ MixAccum<TA, TI>(auxaccum, value);
+ return MixMul<TO, TI, TV>(value, volume);
+}
+
+/* MIXTYPE is used to determine how the samples in the input frame
+ * are mixed with volume gain into the output frame.
+ * See the volumeRampMulti functions below for more details.
+ */
+enum {
+ MIXTYPE_MULTI,
+ MIXTYPE_MONOEXPAND,
+ MIXTYPE_MULTI_SAVEONLY,
+};
+
+/*
+ * The volumeRampMulti and volumeRamp functions take a MIXTYPE
+ * which indicates the per-frame mixing and accumulation strategy.
+ *
+ * MIXTYPE_MULTI:
+ * NCHAN represents number of input and output channels.
+ * TO: int32_t (Q4.27) or float
+ * TI: int32_t (Q4.27) or int16_t (Q0.15) or float
+ * TV: int32_t (U4.28) or int16_t (U4.12) or float
+ * vol: represents a volume array.
+ *
+ * This accumulates into the out pointer.
+ *
+ * MIXTYPE_MONOEXPAND:
+ * Single input channel. NCHAN represents number of output channels.
+ * TO: int32_t (Q4.27) or float
+ * TI: int32_t (Q4.27) or int16_t (Q0.15) or float
+ * TV: int32_t (U4.28) or int16_t (U4.12) or float
+ * Input channel count is 1.
+ * vol: represents volume array.
+ *
+ * This accumulates into the out pointer.
+ *
+ * MIXTYPE_MULTI_SAVEONLY:
+ * NCHAN represents number of input and output channels.
+ * TO: int16_t (Q.15) or float
+ * TI: int32_t (Q4.27) or int16_t (Q0.15) or float
+ * TV: int32_t (U4.28) or int16_t (U4.12) or float
+ * vol: represents a volume array.
+ *
+ * MIXTYPE_MULTI_SAVEONLY does not accumulate into the out pointer.
+ */
+
+template <int MIXTYPE, int NCHAN,
+ typename TO, typename TI, typename TV, typename TA, typename TAV>
+inline void volumeRampMulti(TO* out, size_t frameCount,
+ const TI* in, TA* aux, TV *vol, const TV *volinc, TAV *vola, TAV volainc)
+{
+#ifdef ALOGVV
+ ALOGVV("volumeRampMulti, MIXTYPE:%d\n", MIXTYPE);
+#endif
+ if (aux != NULL) {
+ do {
+ TA auxaccum = 0;
+ switch (MIXTYPE) {
+ case MIXTYPE_MULTI:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
+ vol[i] += volinc[i];
+ }
+ break;
+ case MIXTYPE_MULTI_SAVEONLY:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
+ vol[i] += volinc[i];
+ }
+ break;
+ case MIXTYPE_MONOEXPAND:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
+ vol[i] += volinc[i];
+ }
+ in++;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
+ break;
+ }
+ auxaccum /= NCHAN;
+ *aux++ += MixMul<TA, TA, TAV>(auxaccum, *vola);
+ vola[0] += volainc;
+ } while (--frameCount);
+ } else {
+ do {
+ switch (MIXTYPE) {
+ case MIXTYPE_MULTI:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
+ vol[i] += volinc[i];
+ }
+ break;
+ case MIXTYPE_MULTI_SAVEONLY:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
+ vol[i] += volinc[i];
+ }
+ break;
+ case MIXTYPE_MONOEXPAND:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ += MixMul<TO, TI, TV>(*in, vol[i]);
+ vol[i] += volinc[i];
+ }
+ in++;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
+ break;
+ }
+ } while (--frameCount);
+ }
+}
+
+template <int MIXTYPE, int NCHAN,
+ typename TO, typename TI, typename TV, typename TA, typename TAV>
+inline void volumeMulti(TO* out, size_t frameCount,
+ const TI* in, TA* aux, const TV *vol, TAV vola)
+{
+#ifdef ALOGVV
+ ALOGVV("volumeMulti MIXTYPE:%d\n", MIXTYPE);
+#endif
+ if (aux != NULL) {
+ do {
+ TA auxaccum = 0;
+ switch (MIXTYPE) {
+ case MIXTYPE_MULTI:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
+ }
+ break;
+ case MIXTYPE_MULTI_SAVEONLY:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
+ }
+ break;
+ case MIXTYPE_MONOEXPAND:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
+ }
+ in++;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
+ break;
+ }
+ auxaccum /= NCHAN;
+ *aux++ += MixMul<TA, TA, TAV>(auxaccum, vola);
+ } while (--frameCount);
+ } else {
+ do {
+ switch (MIXTYPE) {
+ case MIXTYPE_MULTI:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
+ }
+ break;
+ case MIXTYPE_MULTI_SAVEONLY:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
+ }
+ break;
+ case MIXTYPE_MONOEXPAND:
+ for (int i = 0; i < NCHAN; ++i) {
+ *out++ += MixMul<TO, TI, TV>(*in, vol[i]);
+ }
+ in++;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
+ break;
+ }
+ } while (--frameCount);
+ }
+}
+
+};
+
+#endif /* ANDROID_AUDIO_MIXER_OPS_H */
diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp
index 562c4ea..b8a0357 100644
--- a/services/audioflinger/AudioResampler.cpp
+++ b/services/audioflinger/AudioResampler.cpp
@@ -259,13 +259,14 @@ AudioResampler::AudioResampler(int bitDepth, int inChannelCount,
mPhaseFraction(0), mLocalTimeFreq(0),
mPTS(AudioBufferProvider::kInvalidPTS), mQuality(quality) {
// sanity check on format
- if ((bitDepth != 16) ||(inChannelCount < 1) || (inChannelCount > 2)) {
- ALOGE("Unsupported sample format, %d bits, %d channels", bitDepth,
- inChannelCount);
- // ALOG_ASSERT(0);
+ if ((bitDepth != 16 && (quality < DYN_LOW_QUALITY || bitDepth != 32))
+ || inChannelCount < 1
+ || inChannelCount > (quality < DYN_LOW_QUALITY ? 2 : 8)) {
+ LOG_ALWAYS_FATAL("Unsupported sample format %d quality %d bits, %d channels",
+ quality, bitDepth, inChannelCount);
}
if (sampleRate <= 0) {
- ALOGE("Unsupported sample rate %d Hz", sampleRate);
+ LOG_ALWAYS_FATAL("Unsupported sample rate %d Hz", sampleRate);
}
// initialize common members
diff --git a/services/audioflinger/AudioResamplerDyn.cpp b/services/audioflinger/AudioResamplerDyn.cpp
index a4446a4..7ca10c1 100644
--- a/services/audioflinger/AudioResamplerDyn.cpp
+++ b/services/audioflinger/AudioResamplerDyn.cpp
@@ -38,11 +38,6 @@
namespace android {
-// generate a unique resample type compile-time constant (constexpr)
-#define RESAMPLETYPE(CHANNELS, LOCKED, STRIDE) \
- ((((CHANNELS)-1)&1) | !!(LOCKED)<<1 \
- | ((STRIDE)==8 ? 1 : (STRIDE)==16 ? 2 : 0)<<2)
-
/*
* InBuffer is a type agnostic input buffer.
*
@@ -403,12 +398,76 @@ void AudioResamplerDyn<TC, TI, TO>::setSampleRate(int32_t inSampleRate)
// determine which resampler to use
// check if locked phase (works only if mPhaseIncrement has no "fractional phase bits")
int locked = (mPhaseIncrement << (sizeof(mPhaseIncrement)*8 - c.mShift)) == 0;
- int stride = (c.mHalfNumCoefs&7)==0 ? 16 : (c.mHalfNumCoefs&3)==0 ? 8 : 2;
if (locked) {
mPhaseFraction = mPhaseFraction >> c.mShift << c.mShift; // remove fractional phase
}
- setResampler(RESAMPLETYPE(mChannelCount, locked, stride));
+ // stride is the minimum number of filter coefficients processed per loop iteration.
+ // We currently only allow a stride of 16 to match with SIMD processing.
+ // This means that the filter length must be a multiple of 16,
+ // or half the filter length (mHalfNumCoefs) must be a multiple of 8.
+ //
+ // Note: A stride of 2 is achieved with non-SIMD processing.
+ int stride = ((c.mHalfNumCoefs & 7) == 0) ? 16 : 2;
+ LOG_ALWAYS_FATAL_IF(stride < 16, "Resampler stride must be 16 or more");
+ LOG_ALWAYS_FATAL_IF(mChannelCount > 8 || mChannelCount < 1,
+ "Resampler channels(%d) must be between 1 to 8", mChannelCount);
+ // stride 16 (falls back to stride 2 for machines that do not support NEON)
+ if (locked) {
+ switch (mChannelCount) {
+ case 1:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<1, true, 16>;
+ break;
+ case 2:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<2, true, 16>;
+ break;
+ case 3:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<3, true, 16>;
+ break;
+ case 4:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<4, true, 16>;
+ break;
+ case 5:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<5, true, 16>;
+ break;
+ case 6:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<6, true, 16>;
+ break;
+ case 7:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<7, true, 16>;
+ break;
+ case 8:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<8, true, 16>;
+ break;
+ }
+ } else {
+ switch (mChannelCount) {
+ case 1:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<1, false, 16>;
+ break;
+ case 2:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<2, false, 16>;
+ break;
+ case 3:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<3, false, 16>;
+ break;
+ case 4:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<4, false, 16>;
+ break;
+ case 5:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<5, false, 16>;
+ break;
+ case 6:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<6, false, 16>;
+ break;
+ case 7:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<7, false, 16>;
+ break;
+ case 8:
+ mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<8, false, 16>;
+ break;
+ }
+ }
#ifdef DEBUG_RESAMPLER
printf("channels:%d %s stride:%d %s coef:%d shift:%d\n",
mChannelCount, locked ? "locked" : "interpolated",
@@ -424,34 +483,12 @@ void AudioResamplerDyn<TC, TI, TO>::resample(int32_t* out, size_t outFrameCount,
}
template<typename TC, typename TI, typename TO>
-void AudioResamplerDyn<TC, TI, TO>::setResampler(unsigned resampleType)
-{
- // stride 16 (falls back to stride 2 for machines that do not support NEON)
- switch (resampleType) {
- case RESAMPLETYPE(1, true, 16):
- mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<1, true, 16>;
- return;
- case RESAMPLETYPE(2, true, 16):
- mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<2, true, 16>;
- return;
- case RESAMPLETYPE(1, false, 16):
- mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<1, false, 16>;
- return;
- case RESAMPLETYPE(2, false, 16):
- mResampleFunc = &AudioResamplerDyn<TC, TI, TO>::resample<2, false, 16>;
- return;
- default:
- LOG_ALWAYS_FATAL("Invalid resampler type: %u", resampleType);
- mResampleFunc = NULL;
- return;
- }
-}
-
-template<typename TC, typename TI, typename TO>
template<int CHANNELS, bool LOCKED, int STRIDE>
void AudioResamplerDyn<TC, TI, TO>::resample(TO* out, size_t outFrameCount,
AudioBufferProvider* provider)
{
+ // TODO Mono -> Mono is not supported. OUTPUT_CHANNELS reflects minimum of stereo out.
+ const int OUTPUT_CHANNELS = (CHANNELS < 2) ? 2 : CHANNELS;
const Constants& c(mConstants);
const TC* const coefs = mConstants.mFirCoefs;
TI* impulse = mInBuffer.getImpulse();
@@ -459,10 +496,16 @@ void AudioResamplerDyn<TC, TI, TO>::resample(TO* out, size_t outFrameCount,
uint32_t phaseFraction = mPhaseFraction;
const uint32_t phaseIncrement = mPhaseIncrement;
size_t outputIndex = 0;
- size_t outputSampleCount = outFrameCount * 2; // stereo output
- size_t inFrameCount = getInFrameCountRequired(outFrameCount) + (phaseFraction != 0);
- ALOG_ASSERT(0 < inFrameCount && inFrameCount < (1U << 31));
+ size_t outputSampleCount = outFrameCount * OUTPUT_CHANNELS;
const uint32_t phaseWrapLimit = c.mL << c.mShift;
+ size_t inFrameCount = (phaseIncrement * (uint64_t)outFrameCount + phaseFraction)
+ / phaseWrapLimit;
+ // sanity check that inFrameCount is in signed 32 bit integer range.
+ ALOG_ASSERT(0 <= inFrameCount && inFrameCount < (1U << 31));
+
+ //ALOGV("inFrameCount:%d outFrameCount:%d"
+ // " phaseIncrement:%u phaseFraction:%u phaseWrapLimit:%u",
+ // inFrameCount, outFrameCount, phaseIncrement, phaseFraction, phaseWrapLimit);
// NOTE: be very careful when modifying the code here. register
// pressure is very high and a small change might cause the compiler
@@ -472,12 +515,19 @@ void AudioResamplerDyn<TC, TI, TO>::resample(TO* out, size_t outFrameCount,
// the following logic is a bit convoluted to keep the main processing loop
// as tight as possible with register allocation.
while (outputIndex < outputSampleCount) {
- // buffer is empty, fetch a new one
- while (mBuffer.frameCount == 0) {
+ //ALOGV("LOOP: inFrameCount:%d outputIndex:%d outFrameCount:%d"
+ // " phaseFraction:%u phaseWrapLimit:%u",
+ // inFrameCount, outputIndex, outFrameCount, phaseFraction, phaseWrapLimit);
+
+ // check inputIndex overflow
+ ALOG_ASSERT(inputIndex <= mBuffer.frameCount, "inputIndex%d > frameCount%d",
+ inputIndex, mBuffer.frameCount);
+ // Buffer is empty, fetch a new one if necessary (inFrameCount > 0).
+ // We may not fetch a new buffer if the existing data is sufficient.
+ while (mBuffer.frameCount == 0 && inFrameCount > 0) {
mBuffer.frameCount = inFrameCount;
- ALOG_ASSERT(inFrameCount > 0);
provider->getNextBuffer(&mBuffer,
- calculateOutputPTS(outputIndex / 2));
+ calculateOutputPTS(outputIndex / OUTPUT_CHANNELS));
if (mBuffer.raw == NULL) {
goto resample_exit;
}
@@ -486,9 +536,9 @@ void AudioResamplerDyn<TC, TI, TO>::resample(TO* out, size_t outFrameCount,
mInBuffer.template readAdvance<CHANNELS>(
impulse, c.mHalfNumCoefs,
reinterpret_cast<TI*>(mBuffer.raw), inputIndex);
+ inputIndex++;
phaseFraction -= phaseWrapLimit;
while (phaseFraction >= phaseWrapLimit) {
- inputIndex++;
if (inputIndex >= mBuffer.frameCount) {
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
@@ -497,6 +547,7 @@ void AudioResamplerDyn<TC, TI, TO>::resample(TO* out, size_t outFrameCount,
mInBuffer.template readAdvance<CHANNELS>(
impulse, c.mHalfNumCoefs,
reinterpret_cast<TI*>(mBuffer.raw), inputIndex);
+ inputIndex++;
phaseFraction -= phaseWrapLimit;
}
}
@@ -507,9 +558,6 @@ void AudioResamplerDyn<TC, TI, TO>::resample(TO* out, size_t outFrameCount,
const int halfNumCoefs = c.mHalfNumCoefs;
const TO* const volumeSimd = mVolumeSimd;
- // reread the last input in.
- mInBuffer.template readAgain<CHANNELS>(impulse, halfNumCoefs, in, inputIndex);
-
// main processing loop
while (CC_LIKELY(outputIndex < outputSampleCount)) {
// caution: fir() is inlined and may be large.
@@ -518,26 +566,34 @@ void AudioResamplerDyn<TC, TI, TO>::resample(TO* out, size_t outFrameCount,
// from the input samples in impulse[-halfNumCoefs+1]... impulse[halfNumCoefs]
// from the polyphase filter of (phaseFraction / phaseWrapLimit) in coefs.
//
+ //ALOGV("LOOP2: inFrameCount:%d outputIndex:%d outFrameCount:%d"
+ // " phaseFraction:%u phaseWrapLimit:%u",
+ // inFrameCount, outputIndex, outFrameCount, phaseFraction, phaseWrapLimit);
+ ALOG_ASSERT(phaseFraction < phaseWrapLimit);
fir<CHANNELS, LOCKED, STRIDE>(
&out[outputIndex],
phaseFraction, phaseWrapLimit,
coefShift, halfNumCoefs, coefs,
impulse, volumeSimd);
- outputIndex += 2;
+
+ outputIndex += OUTPUT_CHANNELS;
phaseFraction += phaseIncrement;
while (phaseFraction >= phaseWrapLimit) {
- inputIndex++;
if (inputIndex >= frameCount) {
goto done; // need a new buffer
}
mInBuffer.template readAdvance<CHANNELS>(impulse, halfNumCoefs, in, inputIndex);
+ inputIndex++;
phaseFraction -= phaseWrapLimit;
}
}
done:
- // often arrives here when input buffer runs out
- if (inputIndex >= frameCount) {
+ // We arrive here when we're finished or when the input buffer runs out.
+ // Regardless we need to release the input buffer if we've acquired it.
+ if (inputIndex > 0) { // we've acquired a buffer (alternatively could check frameCount)
+ ALOG_ASSERT(inputIndex == frameCount, "inputIndex(%d) != frameCount(%d)",
+ inputIndex, frameCount); // must have been fully read.
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
ALOG_ASSERT(mBuffer.frameCount == 0);
@@ -545,14 +601,12 @@ done:
}
resample_exit:
- // Release frames to avoid the count being inaccurate for pts timing.
- // TODO: Avoid this extra check by making fetch count exact. This is tricky
- // due to the overfetching mechanism which loads unnecessarily when
- // mBuffer.frameCount == 0.
- if (inputIndex) {
- mBuffer.frameCount = inputIndex;
- provider->releaseBuffer(&mBuffer);
- }
+ // inputIndex must be zero in all three cases:
+ // (1) the buffer never was been acquired; (2) the buffer was
+ // released at "done:"; or (3) getNextBuffer() failed.
+ ALOG_ASSERT(inputIndex == 0, "Releasing: inputindex:%d frameCount:%d phaseFraction:%u",
+ inputIndex, mBuffer.frameCount, phaseFraction);
+ ALOG_ASSERT(mBuffer.frameCount == 0); // there must be no frames in the buffer
mInBuffer.setImpulse(impulse);
mPhaseFraction = phaseFraction;
}
diff --git a/services/audioflinger/AudioResamplerDyn.h b/services/audioflinger/AudioResamplerDyn.h
index 8c56319..3dced8a 100644
--- a/services/audioflinger/AudioResamplerDyn.h
+++ b/services/audioflinger/AudioResamplerDyn.h
@@ -110,12 +110,10 @@ private:
void createKaiserFir(Constants &c, double stopBandAtten,
int inSampleRate, int outSampleRate, double tbwCheat);
- void setResampler(unsigned resampleType);
-
template<int CHANNELS, bool LOCKED, int STRIDE>
void resample(TO* out, size_t outFrameCount, AudioBufferProvider* provider);
- // declare a pointer to member function for resample
+ // define a pointer to member function type for resample
typedef void (AudioResamplerDyn<TC, TI, TO>::*resample_ABP_t)(TO* out,
size_t outFrameCount, AudioBufferProvider* provider);
diff --git a/services/audioflinger/AudioResamplerFirProcess.h b/services/audioflinger/AudioResamplerFirProcess.h
index 76d2d66..bb0f1c9 100644
--- a/services/audioflinger/AudioResamplerFirProcess.h
+++ b/services/audioflinger/AudioResamplerFirProcess.h
@@ -44,14 +44,14 @@ static inline
void mac(float& l, float& r, TC coef, const float* samples)
{
l += *samples++ * coef;
- r += *samples++ * coef;
+ r += *samples * coef;
}
template<typename TC>
static inline
void mac(float& l, TC coef, const float* samples)
{
- l += *samples++ * coef;
+ l += *samples * coef;
}
/* variant for output type TO = int32_t output samples */
@@ -69,62 +69,48 @@ float volumeAdjust(float value, float volume)
}
/*
- * Calculates a single output frame (two samples).
- *
- * This function computes both the positive half FIR dot product and
- * the negative half FIR dot product, accumulates, and then applies the volume.
+ * Helper template functions for loop unrolling accumulator operations.
*
- * This is a locked phase filter (it does not compute the interpolation).
- *
- * Use fir() to compute the proper coefficient pointers for a polyphase
- * filter bank.
+ * Unrolling the loops achieves about 2x gain.
+ * Using a recursive template rather than an array of TO[] for the accumulator
+ * values is an additional 10-20% gain.
*/
-template <int CHANNELS, int STRIDE, typename TC, typename TI, typename TO>
-static inline
-void ProcessL(TO* const out,
- int count,
- const TC* coefsP,
- const TC* coefsN,
- const TI* sP,
- const TI* sN,
- const TO* const volumeLR)
+template<int CHANNELS, typename TO>
+class Accumulator : public Accumulator<CHANNELS-1, TO> // recursive
{
- COMPILE_TIME_ASSERT_FUNCTION_SCOPE(CHANNELS >= 1 && CHANNELS <= 2)
- if (CHANNELS == 2) {
- TO l = 0;
- TO r = 0;
- do {
- mac(l, r, *coefsP++, sP);
- sP -= CHANNELS;
- mac(l, r, *coefsN++, sN);
- sN += CHANNELS;
- } while (--count > 0);
- out[0] += volumeAdjust(l, volumeLR[0]);
- out[1] += volumeAdjust(r, volumeLR[1]);
- } else { /* CHANNELS == 1 */
- TO l = 0;
- do {
- mac(l, *coefsP++, sP);
- sP -= CHANNELS;
- mac(l, *coefsN++, sN);
- sN += CHANNELS;
- } while (--count > 0);
- out[0] += volumeAdjust(l, volumeLR[0]);
- out[1] += volumeAdjust(l, volumeLR[1]);
+public:
+ inline void clear() {
+ value = 0;
+ Accumulator<CHANNELS-1, TO>::clear();
}
-}
+ template<typename TC, typename TI>
+ inline void acc(TC coef, const TI*& data) {
+ mac(value, coef, data++);
+ Accumulator<CHANNELS-1, TO>::acc(coef, data);
+ }
+ inline void volume(TO*& out, TO gain) {
+ *out++ = volumeAdjust(value, gain);
+ Accumulator<CHANNELS-1, TO>::volume(out, gain);
+ }
+
+ TO value; // one per recursive inherited base class
+};
+
+template<typename TO>
+class Accumulator<0, TO> {
+public:
+ inline void clear() {
+ }
+ template<typename TC, typename TI>
+ inline void acc(TC coef __unused, const TI*& data __unused) {
+ }
+ inline void volume(TO*& out __unused, TO gain __unused) {
+ }
+};
/*
- * Calculates a single output frame (two samples) interpolating phase.
- *
- * This function computes both the positive half FIR dot product and
- * the negative half FIR dot product, accumulates, and then applies the volume.
- *
- * This is an interpolated phase filter.
- *
- * Use fir() to compute the proper coefficient pointers for a polyphase
- * filter bank.
+ * Helper template functions for interpolating filter coefficients.
*/
template<typename TC, typename T>
@@ -159,30 +145,98 @@ int32_t interpolate(int32_t coef_0, int32_t coef_1, uint32_t lerp)
return mulAdd(static_cast<int16_t>(lerp), (coef_1-coef_0)<<1, coef_0);
}
-template <int CHANNELS, int STRIDE, typename TC, typename TI, typename TO, typename TINTERP>
+/* class scope for passing in functions into templates */
+struct InterpCompute {
+ template<typename TC, typename TINTERP>
+ static inline
+ TC interpolatep(TC coef_0, TC coef_1, TINTERP lerp) {
+ return interpolate(coef_0, coef_1, lerp);
+ }
+
+ template<typename TC, typename TINTERP>
+ static inline
+ TC interpolaten(TC coef_0, TC coef_1, TINTERP lerp) {
+ return interpolate(coef_0, coef_1, lerp);
+ }
+};
+
+struct InterpNull {
+ template<typename TC, typename TINTERP>
+ static inline
+ TC interpolatep(TC coef_0, TC coef_1 __unused, TINTERP lerp __unused) {
+ return coef_0;
+ }
+
+ template<typename TC, typename TINTERP>
+ static inline
+ TC interpolaten(TC coef_0 __unused, TC coef_1, TINTERP lerp __unused) {
+ return coef_1;
+ }
+};
+
+/*
+ * Calculates a single output frame (two samples).
+ *
+ * The Process*() functions compute both the positive half FIR dot product and
+ * the negative half FIR dot product, accumulates, and then applies the volume.
+ *
+ * Use fir() to compute the proper coefficient pointers for a polyphase
+ * filter bank.
+ *
+ * ProcessBase() is the fundamental processing template function.
+ *
+ * ProcessL() calls ProcessBase() with TFUNC = InterpNull, for fixed/locked phase.
+ * Process() calls ProcessBase() with TFUNC = InterpCompute, for interpolated phase.
+ */
+
+template <int CHANNELS, int STRIDE, typename TFUNC, typename TC, typename TI, typename TO, typename TINTERP>
static inline
-void Process(TO* const out,
+void ProcessBase(TO* const out,
int count,
const TC* coefsP,
const TC* coefsN,
- const TC* coefsP1 __unused,
- const TC* coefsN1 __unused,
const TI* sP,
const TI* sN,
TINTERP lerpP,
const TO* const volumeLR)
{
- COMPILE_TIME_ASSERT_FUNCTION_SCOPE(CHANNELS >= 1 && CHANNELS <= 2)
- adjustLerp<TC, TINTERP>(lerpP); // coefficient type adjustment for interpolation
+ COMPILE_TIME_ASSERT_FUNCTION_SCOPE(CHANNELS > 0)
- if (CHANNELS == 2) {
+ if (CHANNELS > 2) {
+ // TO accum[CHANNELS];
+ Accumulator<CHANNELS, TO> accum;
+
+ // for (int j = 0; j < CHANNELS; ++j) accum[j] = 0;
+ accum.clear();
+ for (size_t i = 0; i < count; ++i) {
+ TC c = TFUNC::interpolatep(coefsP[0], coefsP[count], lerpP);
+
+ // for (int j = 0; j < CHANNELS; ++j) mac(accum[j], c, sP + j);
+ const TI *tmp_data = sP; // tmp_ptr seems to work better
+ accum.acc(c, tmp_data);
+
+ coefsP++;
+ sP -= CHANNELS;
+ c = TFUNC::interpolaten(coefsN[count], coefsN[0], lerpP);
+
+ // for (int j = 0; j < CHANNELS; ++j) mac(accum[j], c, sN + j);
+ tmp_data = sN; // tmp_ptr seems faster than directly using sN
+ accum.acc(c, tmp_data);
+
+ coefsN++;
+ sN += CHANNELS;
+ }
+ // for (int j = 0; j < CHANNELS; ++j) out[j] += volumeAdjust(accum[j], volumeLR[0]);
+ TO *tmp_out = out; // may remove if const out definition changes.
+ accum.volume(tmp_out, volumeLR[0]);
+ } else if (CHANNELS == 2) {
TO l = 0;
TO r = 0;
for (size_t i = 0; i < count; ++i) {
- mac(l, r, interpolate(coefsP[0], coefsP[count], lerpP), sP);
+ mac(l, r, TFUNC::interpolatep(coefsP[0], coefsP[count], lerpP), sP);
coefsP++;
sP -= CHANNELS;
- mac(l, r, interpolate(coefsN[count], coefsN[0], lerpP), sN);
+ mac(l, r, TFUNC::interpolaten(coefsN[count], coefsN[0], lerpP), sN);
coefsN++;
sN += CHANNELS;
}
@@ -191,10 +245,10 @@ void Process(TO* const out,
} else { /* CHANNELS == 1 */
TO l = 0;
for (size_t i = 0; i < count; ++i) {
- mac(l, interpolate(coefsP[0], coefsP[count], lerpP), sP);
+ mac(l, TFUNC::interpolatep(coefsP[0], coefsP[count], lerpP), sP);
coefsP++;
sP -= CHANNELS;
- mac(l, interpolate(coefsN[count], coefsN[0], lerpP), sN);
+ mac(l, TFUNC::interpolaten(coefsN[count], coefsN[0], lerpP), sN);
coefsN++;
sN += CHANNELS;
}
@@ -203,6 +257,36 @@ void Process(TO* const out,
}
}
+template <int CHANNELS, int STRIDE, typename TC, typename TI, typename TO>
+static inline
+void ProcessL(TO* const out,
+ int count,
+ const TC* coefsP,
+ const TC* coefsN,
+ const TI* sP,
+ const TI* sN,
+ const TO* const volumeLR)
+{
+ ProcessBase<CHANNELS, STRIDE, InterpNull>(out, count, coefsP, coefsN, sP, sN, 0, volumeLR);
+}
+
+template <int CHANNELS, int STRIDE, typename TC, typename TI, typename TO, typename TINTERP>
+static inline
+void Process(TO* const out,
+ int count,
+ const TC* coefsP,
+ const TC* coefsN,
+ const TC* coefsP1 __unused,
+ const TC* coefsN1 __unused,
+ const TI* sP,
+ const TI* sN,
+ TINTERP lerpP,
+ const TO* const volumeLR)
+{
+ adjustLerp<TC, TINTERP>(lerpP); // coefficient type adjustment for interpolations
+ ProcessBase<CHANNELS, STRIDE, InterpCompute>(out, count, coefsP, coefsN, sP, sN, lerpP, volumeLR);
+}
+
/*
* Calculates a single output frame (two samples) from input sample pointer.
*
diff --git a/services/audioflinger/FastMixer.cpp b/services/audioflinger/FastMixer.cpp
index 13b21ec..c486630 100644
--- a/services/audioflinger/FastMixer.cpp
+++ b/services/audioflinger/FastMixer.cpp
@@ -273,10 +273,9 @@ void FastMixer::onStateChange()
ALOG_ASSERT(name >= 0);
mixer->setBufferProvider(name, bufferProvider);
if (fastTrack->mVolumeProvider == NULL) {
- mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0,
- (void *) MAX_GAIN_INT);
- mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1,
- (void *) MAX_GAIN_INT);
+ float f = AudioMixer::UNITY_GAIN_FLOAT;
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &f);
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &f);
}
mixer->setParameter(name, AudioMixer::RESAMPLE,
AudioMixer::REMOVE, NULL);
@@ -336,12 +335,11 @@ void FastMixer::onWork()
ALOG_ASSERT(name >= 0);
if (fastTrack->mVolumeProvider != NULL) {
gain_minifloat_packed_t vlr = fastTrack->mVolumeProvider->getVolumeLR();
- mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0,
- (void *) (uintptr_t)
- (float_from_gain(gain_minifloat_unpack_left(vlr)) * MAX_GAIN_INT));
- mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1,
- (void *) (uintptr_t)
- (float_from_gain(gain_minifloat_unpack_right(vlr)) * MAX_GAIN_INT));
+ float vlf = float_from_gain(gain_minifloat_unpack_left(vlr));
+ float vrf = float_from_gain(gain_minifloat_unpack_right(vlr));
+
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &vlf);
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &vrf);
}
// FIXME The current implementation of framesReady() for fast tracks
// takes a tryLock, which can block
diff --git a/services/audioflinger/PatchPanel.cpp b/services/audioflinger/PatchPanel.cpp
index 96a8127..6d84296 100644
--- a/services/audioflinger/PatchPanel.cpp
+++ b/services/audioflinger/PatchPanel.cpp
@@ -188,7 +188,7 @@ status_t AudioFlinger::PatchPanel::createAudioPatch(const struct audio_patch *pa
}
// limit to connections between sinks and sources on same HW module
if (patch->sinks[i].ext.mix.hw_module != src_module) {
- ALOGW("createAudioPatch() cannot connect source on module %d to"
+ ALOGW("createAudioPatch() cannot connect source on module %d to "
"sink on module %d", src_module, patch->sinks[i].ext.mix.hw_module);
return BAD_VALUE;
}
@@ -235,7 +235,7 @@ status_t AudioFlinger::PatchPanel::createAudioPatch(const struct audio_patch *pa
param.addInt(String8(AudioParameter::keyInputSource),
(int)patch->sinks[0].ext.mix.usecase.source);
- ALOGW("createAudioPatch() AUDIO_PORT_TYPE_DEVICE setParameters %s",
+ ALOGV("createAudioPatch() AUDIO_PORT_TYPE_DEVICE setParameters %s",
param.toString().string());
status = thread->setParameters(param.toString());
}
@@ -354,7 +354,7 @@ status_t AudioFlinger::PatchPanel::releaseAudioPatch(audio_patch_handle_t handle
}
AudioParameter param;
param.addInt(String8(AudioParameter::keyRouting), 0);
- ALOGW("releaseAudioPatch() AUDIO_PORT_TYPE_DEVICE setParameters %s",
+ ALOGV("releaseAudioPatch() AUDIO_PORT_TYPE_DEVICE setParameters %s",
param.toString().string());
status = thread->setParameters(param.toString());
}
diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h
index 6f1f293..79bdfe8 100644
--- a/services/audioflinger/PlaybackTracks.h
+++ b/services/audioflinger/PlaybackTracks.h
@@ -54,6 +54,7 @@ public:
return mStreamType;
}
bool isOffloaded() const { return (mFlags & IAudioFlinger::TRACK_OFFLOAD) != 0; }
+ bool isDirect() const { return (mFlags & IAudioFlinger::TRACK_DIRECT) != 0; }
status_t setParameters(const String8& keyValuePairs);
status_t attachAuxEffect(int EffectId);
void setAuxBuffer(int EffectId, int32_t *buffer);
@@ -157,6 +158,12 @@ private:
AudioTrackServerProxy* mAudioTrackServerProxy;
bool mResumeToStopping; // track was paused in stopping state.
bool mFlushHwPending; // track requests for thread flush
+
+ // for last call to getTimestamp
+ bool mPreviousValid;
+ uint32_t mPreviousFramesWritten;
+ AudioTimestamp mPreviousTimestamp;
+
}; // end of Track
class TimedTrack : public Track {
diff --git a/services/audioflinger/ServiceUtilities.cpp b/services/audioflinger/ServiceUtilities.cpp
index 152455d..8246fef 100644
--- a/services/audioflinger/ServiceUtilities.cpp
+++ b/services/audioflinger/ServiceUtilities.cpp
@@ -59,6 +59,13 @@ bool settingsAllowed() {
return ok;
}
+bool modifyAudioRoutingAllowed() {
+ static const String16 sModifyAudioRoutingAllowed("android.permission.MODIFY_AUDIO_ROUTING");
+ bool ok = checkCallingPermission(sModifyAudioRoutingAllowed);
+ if (!ok) ALOGE("android.permission.MODIFY_AUDIO_ROUTING");
+ return ok;
+}
+
bool dumpAllowed() {
// don't optimize for same pid, since mediaserver never dumps itself
static const String16 sDump("android.permission.DUMP");
diff --git a/services/audioflinger/ServiceUtilities.h b/services/audioflinger/ServiceUtilities.h
index 531bc56..df6f6f4 100644
--- a/services/audioflinger/ServiceUtilities.h
+++ b/services/audioflinger/ServiceUtilities.h
@@ -24,6 +24,7 @@ bool recordingAllowed();
bool captureAudioOutputAllowed();
bool captureHotwordAllowed();
bool settingsAllowed();
+bool modifyAudioRoutingAllowed();
bool dumpAllowed();
}
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 742163b..67a0119 100644..100755
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -38,6 +38,7 @@
#include <audio_utils/minifloat.h>
// NBAIO implementations
+#include <media/nbaio/AudioStreamInSource.h>
#include <media/nbaio/AudioStreamOutSink.h>
#include <media/nbaio/MonoPipe.h>
#include <media/nbaio/MonoPipeReader.h>
@@ -53,6 +54,7 @@
#include "AudioFlinger.h"
#include "AudioMixer.h"
#include "FastMixer.h"
+#include "FastCapture.h"
#include "ServiceUtilities.h"
#include "SchedulingPolicyService.h"
@@ -131,9 +133,17 @@ static const enum {
// up large writes into smaller ones, and the wrapper would need to deal with scheduler.
} kUseFastMixer = FastMixer_Static;
+// Whether to use fast capture
+static const enum {
+ FastCapture_Never, // never initialize or use: for debugging only
+ FastCapture_Always, // always initialize and use, even if not needed: for debugging only
+ FastCapture_Static, // initialize if needed, then use all the time if initialized
+} kUseFastCapture = FastCapture_Static;
+
// Priorities for requestPriority
static const int kPriorityAudioApp = 2;
static const int kPriorityFastMixer = 3;
+static const int kPriorityFastCapture = 3;
// IAudioFlinger::createTrack() reports back to client the total size of shared memory area
// for the track. The client then sub-divides this into smaller buffers for its use.
@@ -1147,12 +1157,12 @@ AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinge
type_t type)
: ThreadBase(audioFlinger, id, device, AUDIO_DEVICE_NONE, type),
mNormalFrameCount(0), mSinkBuffer(NULL),
- mMixerBufferEnabled(false),
+ mMixerBufferEnabled(AudioFlinger::kEnableExtendedPrecision),
mMixerBuffer(NULL),
mMixerBufferSize(0),
mMixerBufferFormat(AUDIO_FORMAT_INVALID),
mMixerBufferValid(false),
- mEffectBufferEnabled(false),
+ mEffectBufferEnabled(AudioFlinger::kEnableExtendedPrecision),
mEffectBuffer(NULL),
mEffectBufferSize(0),
mEffectBufferFormat(AUDIO_FORMAT_INVALID),
@@ -1391,9 +1401,10 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTrac
frameCount, mFrameCount);
} else {
ALOGV("AUDIO_OUTPUT_FLAG_FAST denied: isTimed=%d sharedBuffer=%p frameCount=%d "
- "mFrameCount=%d format=%d isLinear=%d channelMask=%#x sampleRate=%u mSampleRate=%u "
+ "mFrameCount=%d format=%#x mFormat=%#x isLinear=%d channelMask=%#x "
+ "sampleRate=%u mSampleRate=%u "
"hasFastMixer=%d tid=%d fastTrackAvailMask=%#x",
- isTimed, sharedBuffer.get(), frameCount, mFrameCount, format,
+ isTimed, sharedBuffer.get(), frameCount, mFrameCount, format, mFormat,
audio_is_linear_pcm(format),
channelMask, sampleRate, mSampleRate, hasFastMixer(), tid, mFastTrackAvailMask);
*flags &= ~IAudioFlinger::TRACK_FAST;
@@ -1650,7 +1661,7 @@ bool AudioFlinger::PlaybackThread::destroyTrack_l(const sp<Track>& track)
track->mState = TrackBase::STOPPED;
if (!trackActive) {
removeTrack_l(track);
- } else if (track->isFastTrack() || track->isOffloaded()) {
+ } else if (track->isFastTrack() || track->isOffloaded() || track->isDirect()) {
track->mState = TrackBase::STOPPING_1;
}
@@ -1799,9 +1810,10 @@ void AudioFlinger::PlaybackThread::readOutputParameters_l()
if (!audio_is_valid_format(mFormat)) {
LOG_ALWAYS_FATAL("HAL format %#x not valid for output", mFormat);
}
- if ((mType == MIXER || mType == DUPLICATING) && mFormat != AUDIO_FORMAT_PCM_16_BIT) {
- LOG_ALWAYS_FATAL("HAL format %#x not supported for mixed output; "
- "must be AUDIO_FORMAT_PCM_16_BIT", mFormat);
+ if ((mType == MIXER || mType == DUPLICATING)
+ && !isValidPcmSinkFormat(mFormat)) {
+ LOG_FATAL("HAL format %#x not supported for mixed output",
+ mFormat);
}
mFrameSize = audio_stream_frame_size(&mOutput->stream->common);
mBufferSize = mOutput->stream->common.get_buffer_size(&mOutput->stream->common);
@@ -1858,7 +1870,9 @@ void AudioFlinger::PlaybackThread::readOutputParameters_l()
}
mNormalFrameCount = multiplier * mFrameCount;
// round up to nearest 16 frames to satisfy AudioMixer
- mNormalFrameCount = (mNormalFrameCount + 15) & ~15;
+ if (mType == MIXER || mType == DUPLICATING) {
+ mNormalFrameCount = (mNormalFrameCount + 15) & ~15;
+ }
ALOGI("HAL output buffer size %u frames, normal sink buffer size %u frames", mFrameCount,
mNormalFrameCount);
@@ -2646,7 +2660,7 @@ status_t AudioFlinger::PlaybackThread::getTimestamp_l(AudioTimestamp& timestamp)
if (mNormalSink != 0) {
return mNormalSink->getTimestamp(timestamp);
}
- if (mType == OFFLOAD && mOutput->stream->get_presentation_position) {
+ if ((mType == OFFLOAD || mType == DIRECT) && mOutput->stream->get_presentation_position) {
uint64_t position64;
int ret = mOutput->stream->get_presentation_position(
mOutput->stream, &position64, &timestamp.mTime);
@@ -2850,8 +2864,6 @@ AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, Aud
}
#endif
- } else {
- mFastMixer = NULL;
}
switch (kUseFastMixer) {
@@ -2870,7 +2882,7 @@ AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, Aud
AudioFlinger::MixerThread::~MixerThread()
{
- if (mFastMixer != NULL) {
+ if (mFastMixer != 0) {
FastMixerStateQueue *sq = mFastMixer->sq();
FastMixerState *state = sq->begin();
if (state->mCommand == FastMixerState::COLD_IDLE) {
@@ -2892,7 +2904,7 @@ AudioFlinger::MixerThread::~MixerThread()
ALOG_ASSERT(fastTrack->mBufferProvider != NULL);
delete fastTrack->mBufferProvider;
sq->end(false /*didModify*/);
- delete mFastMixer;
+ mFastMixer.clear();
#ifdef AUDIO_WATCHDOG
if (mAudioWatchdog != 0) {
mAudioWatchdog->requestExit();
@@ -2908,7 +2920,7 @@ AudioFlinger::MixerThread::~MixerThread()
uint32_t AudioFlinger::MixerThread::correctLatency_l(uint32_t latency) const
{
- if (mFastMixer != NULL) {
+ if (mFastMixer != 0) {
MonoPipe *pipe = (MonoPipe *)mPipeSink.get();
latency += (pipe->getAvgFrames() * 1000) / mSampleRate;
}
@@ -2925,7 +2937,7 @@ ssize_t AudioFlinger::MixerThread::threadLoop_write()
{
// FIXME we should only do one push per cycle; confirm this is true
// Start the fast mixer if it's not already running
- if (mFastMixer != NULL) {
+ if (mFastMixer != 0) {
FastMixerStateQueue *sq = mFastMixer->sq();
FastMixerState *state = sq->begin();
if (state->mCommand != FastMixerState::MIX_WRITE &&
@@ -2959,7 +2971,7 @@ ssize_t AudioFlinger::MixerThread::threadLoop_write()
void AudioFlinger::MixerThread::threadLoop_standby()
{
// Idle the fast mixer if it's currently running
- if (mFastMixer != NULL) {
+ if (mFastMixer != 0) {
FastMixerStateQueue *sq = mFastMixer->sq();
FastMixerState *state = sq->begin();
if (!(state->mCommand & FastMixerState::IDLE)) {
@@ -3122,7 +3134,7 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
FastMixerState *state = NULL;
bool didModify = false;
FastMixerStateQueue::block_t block = FastMixerStateQueue::BLOCK_UNTIL_PUSHED;
- if (mFastMixer != NULL) {
+ if (mFastMixer != 0) {
sq = mFastMixer->sq();
state = sq->begin();
}
@@ -3369,9 +3381,11 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
}
// compute volume for this track
- uint32_t vl, vr, va;
+ uint32_t vl, vr; // in U8.24 integer format
+ float vlf, vrf, vaf; // in [0.0, 1.0] float format
if (track->isPausing() || mStreamTypes[track->streamType()].mute) {
- vl = vr = va = 0;
+ vl = vr = 0;
+ vlf = vrf = vaf = 0.;
if (track->isPausing()) {
track->setPaused();
}
@@ -3382,8 +3396,8 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
float v = masterVolume * typeVolume;
AudioTrackServerProxy *proxy = track->mAudioTrackServerProxy;
gain_minifloat_packed_t vlr = proxy->getVolumeLR();
- float vlf = float_from_gain(gain_minifloat_unpack_left(vlr));
- float vrf = float_from_gain(gain_minifloat_unpack_right(vlr));
+ vlf = float_from_gain(gain_minifloat_unpack_left(vlr));
+ vrf = float_from_gain(gain_minifloat_unpack_right(vlr));
// track volumes come from shared memory, so can't be trusted and must be clamped
if (vlf > GAIN_FLOAT_UNITY) {
ALOGV("Track left volume out of range: %.3g", vlf);
@@ -3394,26 +3408,31 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
vrf = GAIN_FLOAT_UNITY;
}
// now apply the master volume and stream type volume
- // FIXME we're losing the wonderful dynamic range in the minifloat representation
- float v8_24 = v * (MAX_GAIN_INT * MAX_GAIN_INT);
- vl = (uint32_t) (v8_24 * vlf);
- vr = (uint32_t) (v8_24 * vrf);
+ vlf *= v;
+ vrf *= v;
// assuming master volume and stream type volume each go up to 1.0,
- // vl and vr are now in 8.24 format
-
+ // then derive vl and vr as U8.24 versions for the effect chain
+ const float scaleto8_24 = MAX_GAIN_INT * MAX_GAIN_INT;
+ vl = (uint32_t) (scaleto8_24 * vlf);
+ vr = (uint32_t) (scaleto8_24 * vrf);
+ // vl and vr are now in U8.24 format
uint16_t sendLevel = proxy->getSendLevel_U4_12();
// send level comes from shared memory and so may be corrupt
if (sendLevel > MAX_GAIN_INT) {
ALOGV("Track send level out of range: %04X", sendLevel);
sendLevel = MAX_GAIN_INT;
}
- va = (uint32_t)(v * sendLevel);
+ // vaf is represented as [0.0, 1.0] float by rescaling sendLevel
+ vaf = v * sendLevel * (1. / MAX_GAIN_INT);
}
// Delegate volume control to effect in track effect chain if needed
if (chain != 0 && chain->setVolume_l(&vl, &vr)) {
// Do not ramp volume if volume is controlled by effect
param = AudioMixer::VOLUME;
+ // Update remaining floating point volume levels
+ vlf = (float)vl / (1 << 24);
+ vrf = (float)vr / (1 << 24);
track->mHasVolumeController = true;
} else {
// force no volume ramp when volume controller was just disabled or removed
@@ -3424,29 +3443,13 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
track->mHasVolumeController = false;
}
- // FIXME Use float
- // Convert volumes from 8.24 to 4.12 format
- // This additional clamping is needed in case chain->setVolume_l() overshot
- vl = (vl + (1 << 11)) >> 12;
- if (vl > MAX_GAIN_INT) {
- vl = MAX_GAIN_INT;
- }
- vr = (vr + (1 << 11)) >> 12;
- if (vr > MAX_GAIN_INT) {
- vr = MAX_GAIN_INT;
- }
-
- if (va > MAX_GAIN_INT) {
- va = MAX_GAIN_INT; // va is uint32_t, so no need to check for -
- }
-
// XXX: these things DON'T need to be done each time
mAudioMixer->setBufferProvider(name, track);
mAudioMixer->enable(name);
- mAudioMixer->setParameter(name, param, AudioMixer::VOLUME0, (void *)(uintptr_t)vl);
- mAudioMixer->setParameter(name, param, AudioMixer::VOLUME1, (void *)(uintptr_t)vr);
- mAudioMixer->setParameter(name, param, AudioMixer::AUXLEVEL, (void *)(uintptr_t)va);
+ mAudioMixer->setParameter(name, param, AudioMixer::VOLUME0, &vlf);
+ mAudioMixer->setParameter(name, param, AudioMixer::VOLUME1, &vrf);
+ mAudioMixer->setParameter(name, param, AudioMixer::AUXLEVEL, &vaf);
mAudioMixer->setParameter(
name,
AudioMixer::TRACK,
@@ -3674,7 +3677,7 @@ bool AudioFlinger::MixerThread::checkForNewParameter_l(const String8& keyValuePa
// if !&IDLE, holds the FastMixer state to restore after new parameters processed
FastMixerState::Command previousCommand = FastMixerState::HOT_IDLE;
- if (mFastMixer != NULL) {
+ if (mFastMixer != 0) {
FastMixerStateQueue *sq = mFastMixer->sq();
FastMixerState *state = sq->begin();
if (!(state->mCommand & FastMixerState::IDLE)) {
@@ -3779,7 +3782,7 @@ bool AudioFlinger::MixerThread::checkForNewParameter_l(const String8& keyValuePa
}
if (!(previousCommand & FastMixerState::IDLE)) {
- ALOG_ASSERT(mFastMixer != NULL);
+ ALOG_ASSERT(mFastMixer != 0);
FastMixerStateQueue *sq = mFastMixer->sq();
FastMixerState *state = sq->begin();
ALOG_ASSERT(state->mCommand == FastMixerState::HOT_IDLE);
@@ -3946,14 +3949,16 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::DirectOutputThread::prep
// The first time a track is added we wait
// for all its buffers to be filled before processing it
uint32_t minFrames;
- if ((track->sharedBuffer() == 0) && !track->isStopped() && !track->isPausing()) {
+ if ((track->sharedBuffer() == 0) && !track->isStopping_1() && !track->isPausing()) {
minFrames = mNormalFrameCount;
} else {
minFrames = 1;
}
- if ((track->framesReady() >= minFrames) && track->isReady() &&
- !track->isPaused() && !track->isTerminated())
+ ALOGI("prepareTracks_l minFrames %d state %d frames ready %d, ",
+ minFrames, track->mState, track->framesReady());
+ if ((track->framesReady() >= minFrames) && track->isReady() && !track->isPaused() &&
+ !track->isStopping_2() && !track->isStopped())
{
ALOGVV("track %d s=%08x [OK]", track->name(), cblk->mServer);
@@ -3980,17 +3985,26 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::DirectOutputThread::prep
if (!mEffectChains.isEmpty() && last) {
mEffectChains[0]->clearInputBuffer();
}
-
- ALOGVV("track %d s=%08x [NOT READY]", track->name(), cblk->mServer);
- if ((track->sharedBuffer() != 0) || track->isTerminated() ||
- track->isStopped() || track->isPaused()) {
+ if (track->isStopping_1()) {
+ track->mState = TrackBase::STOPPING_2;
+ }
+ if ((track->sharedBuffer() != 0) || track->isStopped() ||
+ track->isStopping_2() || track->isPaused()) {
// We have consumed all the buffers of this track.
// Remove it from the list of active tracks.
- // TODO: implement behavior for compressed audio
- size_t audioHALFrames = (latency_l() * mSampleRate) / 1000;
+ size_t audioHALFrames;
+ if (audio_is_linear_pcm(mFormat)) {
+ audioHALFrames = (latency_l() * mSampleRate) / 1000;
+ } else {
+ audioHALFrames = 0;
+ }
+
size_t framesWritten = mBytesWritten / mFrameSize;
if (mStandby || !last ||
track->presentationComplete(framesWritten, audioHALFrames)) {
+ if (track->isStopping_2()) {
+ track->mState = TrackBase::STOPPED;
+ }
if (track->isStopped()) {
track->reset();
}
@@ -4760,16 +4774,151 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger,
#endif
, mReadOnlyHeap(new MemoryDealer(kRecordThreadReadOnlyHeapSize,
"RecordThreadRO", MemoryHeapBase::READ_ONLY))
+ // mFastCapture below
+ , mFastCaptureFutex(0)
+ // mInputSource
+ // mPipeSink
+ // mPipeSource
+ , mPipeFramesP2(0)
+ // mPipeMemory
+ // mFastCaptureNBLogWriter
+ , mFastTrackAvail(true)
{
snprintf(mName, kNameLength, "AudioIn_%X", id);
mNBLogWriter = audioFlinger->newWriter_l(kLogSize, mName);
readInputParameters_l();
+
+ // create an NBAIO source for the HAL input stream, and negotiate
+ mInputSource = new AudioStreamInSource(input->stream);
+ size_t numCounterOffers = 0;
+ const NBAIO_Format offers[1] = {Format_from_SR_C(mSampleRate, mChannelCount, mFormat)};
+ ssize_t index = mInputSource->negotiate(offers, 1, NULL, numCounterOffers);
+ ALOG_ASSERT(index == 0);
+
+ // initialize fast capture depending on configuration
+ bool initFastCapture;
+ switch (kUseFastCapture) {
+ case FastCapture_Never:
+ initFastCapture = false;
+ break;
+ case FastCapture_Always:
+ initFastCapture = true;
+ break;
+ case FastCapture_Static:
+ uint32_t primaryOutputSampleRate;
+ {
+ AutoMutex _l(audioFlinger->mHardwareLock);
+ primaryOutputSampleRate = audioFlinger->mPrimaryOutputSampleRate;
+ }
+ initFastCapture =
+ // either capture sample rate is same as (a reasonable) primary output sample rate
+ (((primaryOutputSampleRate == 44100 || primaryOutputSampleRate == 48000) &&
+ (mSampleRate == primaryOutputSampleRate)) ||
+ // or primary output sample rate is unknown, and capture sample rate is reasonable
+ ((primaryOutputSampleRate == 0) &&
+ ((mSampleRate == 44100 || mSampleRate == 48000)))) &&
+ // and the buffer size is < 10 ms
+ (mFrameCount * 1000) / mSampleRate < 10;
+ break;
+ // case FastCapture_Dynamic:
+ }
+
+ if (initFastCapture) {
+ // create a Pipe for FastMixer to write to, and for us and fast tracks to read from
+ NBAIO_Format format = mInputSource->format();
+ size_t pipeFramesP2 = roundup(mFrameCount * 8);
+ size_t pipeSize = pipeFramesP2 * Format_frameSize(format);
+ void *pipeBuffer;
+ const sp<MemoryDealer> roHeap(readOnlyHeap());
+ sp<IMemory> pipeMemory;
+ if ((roHeap == 0) ||
+ (pipeMemory = roHeap->allocate(pipeSize)) == 0 ||
+ (pipeBuffer = pipeMemory->pointer()) == NULL) {
+ ALOGE("not enough memory for pipe buffer size=%zu", pipeSize);
+ goto failed;
+ }
+ // pipe will be shared directly with fast clients, so clear to avoid leaking old information
+ memset(pipeBuffer, 0, pipeSize);
+ Pipe *pipe = new Pipe(pipeFramesP2, format, pipeBuffer);
+ const NBAIO_Format offers[1] = {format};
+ size_t numCounterOffers = 0;
+ ssize_t index = pipe->negotiate(offers, 1, NULL, numCounterOffers);
+ ALOG_ASSERT(index == 0);
+ mPipeSink = pipe;
+ PipeReader *pipeReader = new PipeReader(*pipe);
+ numCounterOffers = 0;
+ index = pipeReader->negotiate(offers, 1, NULL, numCounterOffers);
+ ALOG_ASSERT(index == 0);
+ mPipeSource = pipeReader;
+ mPipeFramesP2 = pipeFramesP2;
+ mPipeMemory = pipeMemory;
+
+ // create fast capture
+ mFastCapture = new FastCapture();
+ FastCaptureStateQueue *sq = mFastCapture->sq();
+#ifdef STATE_QUEUE_DUMP
+ // FIXME
+#endif
+ FastCaptureState *state = sq->begin();
+ state->mCblk = NULL;
+ state->mInputSource = mInputSource.get();
+ state->mInputSourceGen++;
+ state->mPipeSink = pipe;
+ state->mPipeSinkGen++;
+ state->mFrameCount = mFrameCount;
+ state->mCommand = FastCaptureState::COLD_IDLE;
+ // already done in constructor initialization list
+ //mFastCaptureFutex = 0;
+ state->mColdFutexAddr = &mFastCaptureFutex;
+ state->mColdGen++;
+ state->mDumpState = &mFastCaptureDumpState;
+#ifdef TEE_SINK
+ // FIXME
+#endif
+ mFastCaptureNBLogWriter = audioFlinger->newWriter_l(kFastCaptureLogSize, "FastCapture");
+ state->mNBLogWriter = mFastCaptureNBLogWriter.get();
+ sq->end();
+ sq->push(FastCaptureStateQueue::BLOCK_UNTIL_PUSHED);
+
+ // start the fast capture
+ mFastCapture->run("FastCapture", ANDROID_PRIORITY_URGENT_AUDIO);
+ pid_t tid = mFastCapture->getTid();
+ int err = requestPriority(getpid_cached, tid, kPriorityFastMixer);
+ if (err != 0) {
+ ALOGW("Policy SCHED_FIFO priority %d is unavailable for pid %d tid %d; error %d",
+ kPriorityFastCapture, getpid_cached, tid, err);
+ }
+
+#ifdef AUDIO_WATCHDOG
+ // FIXME
+#endif
+
+ }
+failed: ;
+
+ // FIXME mNormalSource
}
AudioFlinger::RecordThread::~RecordThread()
{
+ if (mFastCapture != 0) {
+ FastCaptureStateQueue *sq = mFastCapture->sq();
+ FastCaptureState *state = sq->begin();
+ if (state->mCommand == FastCaptureState::COLD_IDLE) {
+ int32_t old = android_atomic_inc(&mFastCaptureFutex);
+ if (old == -1) {
+ (void) syscall(__NR_futex, &mFastCaptureFutex, FUTEX_WAKE_PRIVATE, 1);
+ }
+ }
+ state->mCommand = FastCaptureState::EXIT;
+ sq->end();
+ sq->push(FastCaptureStateQueue::BLOCK_UNTIL_PUSHED);
+ mFastCapture->join();
+ mFastCapture.clear();
+ }
+ mAudioFlinger->unregisterWriter(mFastCaptureNBLogWriter);
mAudioFlinger->unregisterWriter(mNBLogWriter);
delete[] mRsmpInBuffer;
}
@@ -4824,6 +4973,8 @@ reacquire_wakelock:
// activeTracks accumulates a copy of a subset of mActiveTracks
Vector< sp<RecordTrack> > activeTracks;
+ // reference to the (first and only) fast track
+ sp<RecordTrack> fastTrack;
{ // scope for mLock
Mutex::Autolock _l(mLock);
@@ -4905,6 +5056,11 @@ reacquire_wakelock:
activeTracks.add(activeTrack);
i++;
+ if (activeTrack->isFastTrack()) {
+ ALOG_ASSERT(!mFastTrackAvail);
+ ALOG_ASSERT(fastTrack == 0);
+ fastTrack = activeTrack;
+ }
}
if (doBroadcast) {
mStartStopCond.broadcast();
@@ -4930,6 +5086,36 @@ reacquire_wakelock:
effectChains[i]->process_l();
}
+ // Start the fast capture if it's not already running
+ if (mFastCapture != 0) {
+ FastCaptureStateQueue *sq = mFastCapture->sq();
+ FastCaptureState *state = sq->begin();
+ if (state->mCommand != FastCaptureState::READ_WRITE /* FIXME &&
+ (kUseFastMixer != FastMixer_Dynamic || state->mTrackMask > 1)*/) {
+ if (state->mCommand == FastCaptureState::COLD_IDLE) {
+ int32_t old = android_atomic_inc(&mFastCaptureFutex);
+ if (old == -1) {
+ (void) syscall(__NR_futex, &mFastCaptureFutex, FUTEX_WAKE_PRIVATE, 1);
+ }
+ }
+ state->mCommand = FastCaptureState::READ_WRITE;
+#if 0 // FIXME
+ mFastCaptureDumpState.increaseSamplingN(mAudioFlinger->isLowRamDevice() ?
+ FastCaptureDumpState::kSamplingNforLowRamDevice : FastMixerDumpState::kSamplingN);
+#endif
+ state->mCblk = fastTrack != 0 ? fastTrack->cblk() : NULL;
+ sq->end();
+ sq->push(FastCaptureStateQueue::BLOCK_UNTIL_PUSHED);
+#if 0
+ if (kUseFastCapture == FastCapture_Dynamic) {
+ mNormalSource = mPipeSource;
+ }
+#endif
+ } else {
+ sq->end(false /*didModify*/);
+ }
+ }
+
// Read from HAL to keep up with fastest client if multiple active tracks, not slowest one.
// Only the client(s) that are too slow will overrun. But if even the fastest client is too
// slow, then this RecordThread will overrun by not calling HAL read often enough.
@@ -4937,26 +5123,49 @@ reacquire_wakelock:
// copy to the right place. Permitted because mRsmpInBuffer was over-allocated.
int32_t rear = mRsmpInRear & (mRsmpInFramesP2 - 1);
- ssize_t bytesRead = mInput->stream->read(mInput->stream,
- &mRsmpInBuffer[rear * mChannelCount], mBufferSize);
- if (bytesRead <= 0) {
- ALOGE("read failed: bytesRead=%d < %u", bytesRead, mBufferSize);
+ ssize_t framesRead;
+
+ // If an NBAIO source is present, use it to read the normal capture's data
+ if (mPipeSource != 0) {
+ size_t framesToRead = mBufferSize / mFrameSize;
+ framesRead = mPipeSource->read(&mRsmpInBuffer[rear * mChannelCount],
+ framesToRead, AudioBufferProvider::kInvalidPTS);
+ if (framesRead == 0) {
+ // since pipe is non-blocking, simulate blocking input
+ sleepUs = (framesToRead * 1000000LL) / mSampleRate;
+ }
+ // otherwise use the HAL / AudioStreamIn directly
+ } else {
+ ssize_t bytesRead = mInput->stream->read(mInput->stream,
+ &mRsmpInBuffer[rear * mChannelCount], mBufferSize);
+ if (bytesRead < 0) {
+ framesRead = bytesRead;
+ } else {
+ framesRead = bytesRead / mFrameSize;
+ }
+ }
+
+ if (framesRead < 0 || (framesRead == 0 && mPipeSource == 0)) {
+ ALOGE("read failed: framesRead=%d", framesRead);
// Force input into standby so that it tries to recover at next read attempt
inputStandBy();
sleepUs = kRecordThreadSleepUs;
- continue;
}
- ALOG_ASSERT((size_t) bytesRead <= mBufferSize);
- size_t framesRead = bytesRead / mFrameSize;
+ if (framesRead <= 0) {
+ goto unlock;
+ }
ALOG_ASSERT(framesRead > 0);
+
if (mTeeSink != 0) {
(void) mTeeSink->write(&mRsmpInBuffer[rear * mChannelCount], framesRead);
}
// If destination is non-contiguous, we now correct for reading past end of buffer.
- size_t part1 = mRsmpInFramesP2 - rear;
- if (framesRead > part1) {
- memcpy(mRsmpInBuffer, &mRsmpInBuffer[mRsmpInFramesP2 * mChannelCount],
- (framesRead - part1) * mFrameSize);
+ {
+ size_t part1 = mRsmpInFramesP2 - rear;
+ if ((size_t) framesRead > part1) {
+ memcpy(mRsmpInBuffer, &mRsmpInBuffer[mRsmpInFramesP2 * mChannelCount],
+ (framesRead - part1) * mFrameSize);
+ }
}
rear = mRsmpInRear += framesRead;
@@ -4965,6 +5174,11 @@ reacquire_wakelock:
for (size_t i = 0; i < size; i++) {
activeTrack = activeTracks[i];
+ // skip fast tracks, as those are handled directly by FastCapture
+ if (activeTrack->isFastTrack()) {
+ continue;
+ }
+
enum {
OVERRUN_UNKNOWN,
OVERRUN_TRUE,
@@ -5159,6 +5373,7 @@ reacquire_wakelock:
}
+unlock:
// enable changes in effect chain
unlockEffectChains(effectChains);
// effectChains doesn't need to be cleared, since it is cleared by destructor at scope end
@@ -5193,6 +5408,30 @@ void AudioFlinger::RecordThread::standbyIfNotAlreadyInStandby()
void AudioFlinger::RecordThread::inputStandBy()
{
+ // Idle the fast capture if it's currently running
+ if (mFastCapture != 0) {
+ FastCaptureStateQueue *sq = mFastCapture->sq();
+ FastCaptureState *state = sq->begin();
+ if (!(state->mCommand & FastCaptureState::IDLE)) {
+ state->mCommand = FastCaptureState::COLD_IDLE;
+ state->mColdFutexAddr = &mFastCaptureFutex;
+ state->mColdGen++;
+ mFastCaptureFutex = 0;
+ sq->end();
+ // BLOCK_UNTIL_PUSHED would be insufficient, as we need it to stop doing I/O now
+ sq->push(FastCaptureStateQueue::BLOCK_UNTIL_ACKED);
+#if 0
+ if (kUseFastCapture == FastCapture_Dynamic) {
+ // FIXME
+ }
+#endif
+#ifdef AUDIO_WATCHDOG
+ // FIXME
+#endif
+ } else {
+ sq->end(false /*didModify*/);
+ }
+ }
mInput->stream->common.standby(&mInput->stream->common);
}
@@ -5219,36 +5458,40 @@ sp<AudioFlinger::RecordThread::RecordTrack> AudioFlinger::RecordThread::createRe
// use case: callback handler and frame count is default or at least as large as HAL
(
(tid != -1) &&
- ((frameCount == 0) ||
+ ((frameCount == 0) /*||
+ // FIXME must be equal to pipe depth, so don't allow it to be specified by client
// FIXME not necessarily true, should be native frame count for native SR!
- (frameCount >= mFrameCount))
+ (frameCount >= mFrameCount)*/)
) &&
// PCM data
audio_is_linear_pcm(format) &&
+ // native format
+ (format == mFormat) &&
// mono or stereo
( (channelMask == AUDIO_CHANNEL_IN_MONO) ||
(channelMask == AUDIO_CHANNEL_IN_STEREO) ) &&
- // hardware sample rate
- // FIXME actually the native hardware sample rate
+ // native channel mask
+ (channelMask == mChannelMask) &&
+ // native hardware sample rate
(sampleRate == mSampleRate) &&
// record thread has an associated fast capture
- hasFastCapture()
- // fast capture does not require slots
+ hasFastCapture() &&
+ // there are sufficient fast track slots available
+ mFastTrackAvail
) {
- // if frameCount not specified, then it defaults to fast capture (HAL) frame count
+ // if frameCount not specified, then it defaults to pipe frame count
if (frameCount == 0) {
- // FIXME wrong mFrameCount
- frameCount = mFrameCount * kFastTrackMultiplier;
+ frameCount = mPipeFramesP2;
}
ALOGV("AUDIO_INPUT_FLAG_FAST accepted: frameCount=%d mFrameCount=%d",
frameCount, mFrameCount);
} else {
ALOGV("AUDIO_INPUT_FLAG_FAST denied: frameCount=%d "
"mFrameCount=%d format=%d isLinear=%d channelMask=%#x sampleRate=%u mSampleRate=%u "
- "hasFastCapture=%d tid=%d",
+ "hasFastCapture=%d tid=%d mFastTrackAvail=%d",
frameCount, mFrameCount, format,
audio_is_linear_pcm(format),
- channelMask, sampleRate, mSampleRate, hasFastCapture(), tid);
+ channelMask, sampleRate, mSampleRate, hasFastCapture(), tid, mFastTrackAvail);
*flags &= ~IAudioFlinger::TRACK_FAST;
// FIXME It's not clear that we need to enforce this any more, since we have a pipe.
// For compatibility with AudioRecord calculation, buffer depth is forced
@@ -5477,6 +5720,10 @@ void AudioFlinger::RecordThread::removeTrack_l(const sp<RecordTrack>& track)
{
mTracks.remove(track);
// need anything related to effects here?
+ if (track->isFastTrack()) {
+ ALOG_ASSERT(!mFastTrackAvail);
+ mFastTrackAvail = true;
+ }
}
void AudioFlinger::RecordThread::dump(int fd, const Vector<String16>& args)
@@ -5495,6 +5742,7 @@ void AudioFlinger::RecordThread::dumpInternals(int fd, const Vector<String16>& a
} else {
dprintf(fd, " No active record clients\n");
}
+ dprintf(fd, " Fast track available: %s\n", mFastTrackAvail ? "yes" : "no");
dumpBase(fd, args);
}
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index eeb33d9..3eb1eb9 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -851,7 +851,7 @@ protected:
AudioMixer* mAudioMixer; // normal mixer
private:
// one-time initialization, no locks required
- FastMixer* mFastMixer; // non-NULL if there is also a fast mixer
+ sp<FastMixer> mFastMixer; // non-0 if there is also a fast mixer
sp<AudioWatchdog> mAudioWatchdog; // non-0 if there is an audio watchdog thread
// contents are not guaranteed to be consistent, no locks required
@@ -867,7 +867,7 @@ private:
int32_t mFastMixerFutex; // for cold idle
public:
- virtual bool hasFastMixer() const { return mFastMixer != NULL; }
+ virtual bool hasFastMixer() const { return mFastMixer != 0; }
virtual FastTrackUnderruns getFastTrackUnderruns(size_t fastIndex) const {
ALOG_ASSERT(fastIndex < FastMixerState::kMaxFastTracks);
return mFastMixerDumpState.mTracks[fastIndex].mUnderruns;
@@ -1063,6 +1063,8 @@ public:
virtual sp<MemoryDealer> readOnlyHeap() const { return mReadOnlyHeap; }
+ virtual sp<IMemory> pipeMemory() const { return mPipeMemory; }
+
sp<AudioFlinger::RecordThread::RecordTrack> createRecordTrack_l(
const sp<AudioFlinger::Client>& client,
uint32_t sampleRate,
@@ -1114,7 +1116,7 @@ public:
static void syncStartEventCallback(const wp<SyncEvent>& event);
virtual size_t frameCount() const { return mFrameCount; }
- bool hasFastCapture() const { return false; }
+ bool hasFastCapture() const { return mFastCapture != 0; }
private:
// Enter standby if not already in standby, and set mStandby flag
@@ -1144,4 +1146,40 @@ private:
const sp<NBAIO_Sink> mTeeSink;
const sp<MemoryDealer> mReadOnlyHeap;
+
+ // one-time initialization, no locks required
+ sp<FastCapture> mFastCapture; // non-0 if there is also a fast capture
+ // FIXME audio watchdog thread
+
+ // contents are not guaranteed to be consistent, no locks required
+ FastCaptureDumpState mFastCaptureDumpState;
+#ifdef STATE_QUEUE_DUMP
+ // FIXME StateQueue observer and mutator dump fields
+#endif
+ // FIXME audio watchdog dump
+
+ // accessible only within the threadLoop(), no locks required
+ // mFastCapture->sq() // for mutating and pushing state
+ int32_t mFastCaptureFutex; // for cold idle
+
+ // The HAL input source is treated as non-blocking,
+ // but current implementation is blocking
+ sp<NBAIO_Source> mInputSource;
+ // The source for the normal capture thread to read from: mInputSource or mPipeSource
+ sp<NBAIO_Source> mNormalSource;
+ // If a fast capture is present, the non-blocking pipe sink written to by fast capture,
+ // otherwise clear
+ sp<NBAIO_Sink> mPipeSink;
+ // If a fast capture is present, the non-blocking pipe source read by normal thread,
+ // otherwise clear
+ sp<NBAIO_Source> mPipeSource;
+ // Depth of pipe from fast capture to normal thread and fast clients, always power of 2
+ size_t mPipeFramesP2;
+ // If a fast capture is present, the Pipe as IMemory, otherwise clear
+ sp<IMemory> mPipeMemory;
+
+ static const size_t kFastCaptureLogSize = 4 * 1024;
+ sp<NBLog::Writer> mFastCaptureNBLogWriter;
+
+ bool mFastTrackAvail; // true if fast track available
};
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 7ddc71c..4fbb973 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -223,6 +223,8 @@ AudioFlinger::ThreadBase::TrackBase::~TrackBase()
// relying on the automatic clear() at end of scope.
mClient.clear();
}
+ // flush the binder command buffer
+ IPCThreadState::self()->flushCommands();
}
// AudioBufferProvider interface
@@ -382,7 +384,10 @@ AudioFlinger::PlaybackThread::Track::Track(
mIsInvalid(false),
mAudioTrackServerProxy(NULL),
mResumeToStopping(false),
- mFlushHwPending(false)
+ mFlushHwPending(false),
+ mPreviousValid(false),
+ mPreviousFramesWritten(0)
+ // mPreviousTimestamp
{
if (mCblk == NULL) {
return;
@@ -429,8 +434,6 @@ AudioFlinger::PlaybackThread::Track::~Track()
// This prevents that leak.
if (mSharedBuffer != 0) {
mSharedBuffer.clear();
- // flush the binder command buffer
- IPCThreadState::self()->flushCommands();
}
}
@@ -703,7 +706,7 @@ void AudioFlinger::PlaybackThread::Track::stop()
if (playbackThread->mActiveTracks.indexOf(this) < 0) {
reset();
mState = STOPPED;
- } else if (!isFastTrack() && !isOffloaded()) {
+ } else if (!isFastTrack() && !isOffloaded() && !isDirect()) {
mState = STOPPED;
} else {
// For fast tracks prepareTracks_l() will set state to STOPPING_2
@@ -847,27 +850,51 @@ status_t AudioFlinger::PlaybackThread::Track::getTimestamp(AudioTimestamp& times
{
// Client should implement this using SSQ; the unpresented frame count in latch is irrelevant
if (isFastTrack()) {
+ // FIXME no lock held to set mPreviousValid = false
return INVALID_OPERATION;
}
sp<ThreadBase> thread = mThread.promote();
if (thread == 0) {
+ // FIXME no lock held to set mPreviousValid = false
return INVALID_OPERATION;
}
Mutex::Autolock _l(thread->mLock);
PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
- if (!isOffloaded()) {
+ if (!isOffloaded() && !isDirect()) {
if (!playbackThread->mLatchQValid) {
+ mPreviousValid = false;
return INVALID_OPERATION;
}
uint32_t unpresentedFrames =
((int64_t) playbackThread->mLatchQ.mUnpresentedFrames * mSampleRate) /
playbackThread->mSampleRate;
uint32_t framesWritten = mAudioTrackServerProxy->framesReleased();
+ bool checkPreviousTimestamp = mPreviousValid && framesWritten >= mPreviousFramesWritten;
if (framesWritten < unpresentedFrames) {
+ mPreviousValid = false;
return INVALID_OPERATION;
}
- timestamp.mPosition = framesWritten - unpresentedFrames;
- timestamp.mTime = playbackThread->mLatchQ.mTimestamp.mTime;
+ mPreviousFramesWritten = framesWritten;
+ uint32_t position = framesWritten - unpresentedFrames;
+ struct timespec time = playbackThread->mLatchQ.mTimestamp.mTime;
+ if (checkPreviousTimestamp) {
+ if (time.tv_sec < mPreviousTimestamp.mTime.tv_sec ||
+ (time.tv_sec == mPreviousTimestamp.mTime.tv_sec &&
+ time.tv_nsec < mPreviousTimestamp.mTime.tv_nsec)) {
+ ALOGW("Time is going backwards");
+ }
+ // position can bobble slightly as an artifact; this hides the bobble
+ static const uint32_t MINIMUM_POSITION_DELTA = 8u;
+ if ((position <= mPreviousTimestamp.mPosition) ||
+ (position - mPreviousTimestamp.mPosition) < MINIMUM_POSITION_DELTA) {
+ position = mPreviousTimestamp.mPosition;
+ time = mPreviousTimestamp.mTime;
+ }
+ }
+ timestamp.mPosition = position;
+ timestamp.mTime = time;
+ mPreviousTimestamp = timestamp;
+ mPreviousValid = true;
return NO_ERROR;
}
@@ -953,8 +980,6 @@ bool AudioFlinger::PlaybackThread::Track::presentationComplete(size_t framesWrit
}
if (framesWritten >= mPresentationCompleteFrames || isOffloaded()) {
- ALOGV("presentationComplete() session %d complete: framesWritten %d",
- mSessionId, framesWritten);
triggerEvents(AudioSystem::SYNC_EVENT_PRESENTATION_COMPLETE);
mAudioTrackServerProxy->setStreamEndDone();
return true;
@@ -1854,7 +1879,7 @@ AudioFlinger::RecordThread::RecordTrack::RecordTrack(
: TrackBase(thread, client, sampleRate, format,
channelMask, frameCount, 0 /*sharedBuffer*/, sessionId, uid,
flags, false /*isOut*/,
- (flags & IAudioFlinger::TRACK_FAST) != 0 ? ALLOC_READONLY : ALLOC_CBLK),
+ flags & IAudioFlinger::TRACK_FAST ? ALLOC_PIPE : ALLOC_CBLK),
mOverflow(false), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpOutFrameCount(0),
// See real initialization of mRsmpInFront at RecordThread::start()
mRsmpInUnrel(0), mRsmpInFront(0), mFramesToDrop(0), mResamplerBufferProvider(NULL)
@@ -1873,9 +1898,14 @@ AudioFlinger::RecordThread::RecordTrack::RecordTrack(
mResampler = AudioResampler::create(16, thread->mChannelCount, sampleRate);
// source SR
mResampler->setSampleRate(thread->mSampleRate);
- mResampler->setVolume(AudioMixer::UNITY_GAIN, AudioMixer::UNITY_GAIN);
+ mResampler->setVolume(AudioMixer::UNITY_GAIN_INT, AudioMixer::UNITY_GAIN_INT);
mResamplerBufferProvider = new ResamplerBufferProvider(this);
}
+
+ if (flags & IAudioFlinger::TRACK_FAST) {
+ ALOG_ASSERT(thread->mFastTrackAvail);
+ thread->mFastTrackAvail = false;
+ }
}
AudioFlinger::RecordThread::RecordTrack::~RecordTrack()
diff --git a/services/audioflinger/tests/Android.mk b/services/audioflinger/tests/Android.mk
new file mode 100644
index 0000000..7bba05b
--- /dev/null
+++ b/services/audioflinger/tests/Android.mk
@@ -0,0 +1,73 @@
+# Build the unit tests for audioflinger
+
+#
+# resampler unit test
+#
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SHARED_LIBRARIES := \
+ liblog \
+ libutils \
+ libcutils \
+ libstlport \
+ libaudioutils \
+ libaudioresampler
+
+LOCAL_STATIC_LIBRARIES := \
+ libgtest \
+ libgtest_main
+
+LOCAL_C_INCLUDES := \
+ bionic \
+ bionic/libstdc++/include \
+ external/gtest/include \
+ external/stlport/stlport \
+ $(call include-path-for, audio-utils) \
+ frameworks/av/services/audioflinger
+
+LOCAL_SRC_FILES := \
+ resampler_tests.cpp
+
+LOCAL_MODULE := resampler_tests
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_EXECUTABLE)
+
+#
+# audio mixer test tool
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ test-mixer.cpp \
+ ../AudioMixer.cpp.arm \
+
+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
+
+LOCAL_STATIC_LIBRARIES := \
+ libsndfile
+
+LOCAL_SHARED_LIBRARIES := \
+ libstlport \
+ libeffects \
+ libnbaio \
+ libcommon_time_client \
+ libaudioresampler \
+ libaudioutils \
+ libdl \
+ libcutils \
+ libutils \
+ liblog
+
+LOCAL_MODULE:= test-mixer
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/audioflinger/tests/build_and_run_all_unit_tests.sh b/services/audioflinger/tests/build_and_run_all_unit_tests.sh
new file mode 100755
index 0000000..2c453b0
--- /dev/null
+++ b/services/audioflinger/tests/build_and_run_all_unit_tests.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+fi
+
+# ensure we have mm
+. $ANDROID_BUILD_TOP/build/envsetup.sh
+
+pushd $ANDROID_BUILD_TOP/frameworks/av/services/audioflinger/
+pwd
+mm
+
+echo "waiting for device"
+adb root && adb wait-for-device remount
+adb push $OUT/system/lib/libaudioresampler.so /system/lib
+adb push $OUT/system/bin/resampler_tests /system/bin
+
+sh $ANDROID_BUILD_TOP/frameworks/av/services/audioflinger/tests/run_all_unit_tests.sh
+
+popd
diff --git a/services/audioflinger/tests/mixer_to_wav_tests.sh b/services/audioflinger/tests/mixer_to_wav_tests.sh
new file mode 100755
index 0000000..93bff47
--- /dev/null
+++ b/services/audioflinger/tests/mixer_to_wav_tests.sh
@@ -0,0 +1,134 @@
+#!/bin/bash
+#
+# This script uses test-mixer to generate WAV files
+# for evaluation of the AudioMixer component.
+#
+# Sine and chirp signals are used for input because they
+# show up as clear lines, either horizontal or diagonal,
+# on a spectrogram. This means easy verification of multiple
+# track mixing.
+#
+# After execution, look for created subdirectories like
+# mixer_i_i
+# mixer_i_f
+# mixer_f_f
+#
+# Recommend using a program such as audacity to evaluate
+# the output WAV files, e.g.
+#
+# cd testdir
+# audacity *.wav
+#
+# Using Audacity:
+#
+# Under "Waveform" view mode you can zoom into the
+# start of the WAV file to verify proper ramping.
+#
+# Select "Spectrogram" to see verify the lines
+# (sine = horizontal, chirp = diagonal) which should
+# be clear (except for around the start as the volume
+# ramping causes spectral distortion).
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+fi
+
+# ensure we have mm
+. $ANDROID_BUILD_TOP/build/envsetup.sh
+
+pushd $ANDROID_BUILD_TOP/frameworks/av/services/audioflinger/
+
+# build
+pwd
+mm
+
+# send to device
+echo "waiting for device"
+adb root && adb wait-for-device remount
+adb push $OUT/system/lib/libaudioresampler.so /system/lib
+adb push $OUT/system/bin/test-mixer /system/bin
+
+# createwav creates a series of WAV files testing various
+# mixer settings
+# $1 = flags
+# $2 = directory
+function createwav() {
+# create directory if it doesn't exist
+ if [ ! -d $2 ]; then
+ mkdir $2
+ fi
+
+# Test:
+# process__genericResampling
+# track__Resample / track__genericResample
+ adb shell test-mixer $1 -s 48000 \
+ -o /sdcard/tm48000gr.wav \
+ sine:2,4000,7520 chirp:2,9200 sine:1,3000,18000
+ adb pull /sdcard/tm48000gr.wav $2
+
+# Test:
+# process__genericResample
+# track__Resample / track__genericResample
+# track__NoResample / track__16BitsStereo / track__16BitsMono
+# Aux buffer
+ adb shell test-mixer $1 -s 9307 \
+ -a /sdcard/aux9307gra.wav -o /sdcard/tm9307gra.wav \
+ sine:2,1000,3000 sine:1,2000,9307 chirp:2,9307
+ adb pull /sdcard/tm9307gra.wav $2
+ adb pull /sdcard/aux9307gra.wav $2
+
+# Test:
+# process__genericNoResampling
+# track__NoResample / track__16BitsStereo / track__16BitsMono
+ adb shell test-mixer $1 -s 32000 \
+ -o /sdcard/tm32000gnr.wav \
+ sine:2,1000,32000 chirp:2,32000 sine:1,3000,32000
+ adb pull /sdcard/tm32000gnr.wav $2
+
+# Test:
+# process__genericNoResampling
+# track__NoResample / track__16BitsStereo / track__16BitsMono
+# Aux buffer
+ adb shell test-mixer $1 -s 32000 \
+ -a /sdcard/aux32000gnra.wav -o /sdcard/tm32000gnra.wav \
+ sine:2,1000,32000 chirp:2,32000 sine:1,3000,32000
+ adb pull /sdcard/tm32000gnra.wav $2
+ adb pull /sdcard/aux32000gnra.wav $2
+
+# Test:
+# process__NoResampleOneTrack / process__OneTrack16BitsStereoNoResampling
+# Downmixer
+ adb shell test-mixer $1 -s 32000 \
+ -o /sdcard/tm32000nrot.wav \
+ sine:6,1000,32000
+ adb pull /sdcard/tm32000nrot.wav $2
+
+# Test:
+# process__NoResampleOneTrack / OneTrack16BitsStereoNoResampling
+# Aux buffer
+ adb shell test-mixer $1 -s 44100 \
+ -a /sdcard/aux44100nrota.wav -o /sdcard/tm44100nrota.wav \
+ sine:2,2000,44100
+ adb pull /sdcard/tm44100nrota.wav $2
+ adb pull /sdcard/aux44100nrota.wav $2
+}
+
+#
+# Call createwav to generate WAV files in various combinations
+#
+# i_i = integer input track, integer mixer output
+# f_f = float input track, float mixer output
+# i_f = integer input track, float_mixer output
+#
+# If the mixer output is float, then the output WAV file is pcm float.
+#
+# TODO: create a "snr" like "diff" to automatically
+# compare files in these directories together.
+#
+
+createwav "" "tests/mixer_i_i"
+createwav "-f -m" "tests/mixer_f_f"
+createwav "-m" "tests/mixer_i_f"
+
+popd
diff --git a/services/audioflinger/tests/resampler_tests.cpp b/services/audioflinger/tests/resampler_tests.cpp
new file mode 100644
index 0000000..d76c376
--- /dev/null
+++ b/services/audioflinger/tests/resampler_tests.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "audioflinger_resampler_tests"
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <time.h>
+#include <math.h>
+#include <vector>
+#include <utility>
+#include <cutils/log.h>
+#include <gtest/gtest.h>
+#include <media/AudioBufferProvider.h>
+#include "AudioResampler.h"
+#include "test_utils.h"
+
+void resample(int channels, void *output,
+ size_t outputFrames, const std::vector<size_t> &outputIncr,
+ android::AudioBufferProvider *provider, android::AudioResampler *resampler)
+{
+ for (size_t i = 0, j = 0; i < outputFrames; ) {
+ size_t thisFrames = outputIncr[j++];
+ if (j >= outputIncr.size()) {
+ j = 0;
+ }
+ if (thisFrames == 0 || thisFrames > outputFrames - i) {
+ thisFrames = outputFrames - i;
+ }
+ resampler->resample((int32_t*) output + channels*i, thisFrames, provider);
+ i += thisFrames;
+ }
+}
+
+void buffercmp(const void *reference, const void *test,
+ size_t outputFrameSize, size_t outputFrames)
+{
+ for (size_t i = 0; i < outputFrames; ++i) {
+ int check = memcmp((const char*)reference + i * outputFrameSize,
+ (const char*)test + i * outputFrameSize, outputFrameSize);
+ if (check) {
+ ALOGE("Failure at frame %d", i);
+ ASSERT_EQ(check, 0); /* fails */
+ }
+ }
+}
+
+void testBufferIncrement(size_t channels, bool useFloat,
+ unsigned inputFreq, unsigned outputFreq,
+ enum android::AudioResampler::src_quality quality)
+{
+ const int bits = useFloat ? 32 : 16;
+ // create the provider
+ std::vector<int> inputIncr;
+ SignalProvider provider;
+ if (useFloat) {
+ provider.setChirp<float>(channels,
+ 0., outputFreq/2., outputFreq, outputFreq/2000.);
+ } else {
+ provider.setChirp<int16_t>(channels,
+ 0., outputFreq/2., outputFreq, outputFreq/2000.);
+ }
+ provider.setIncr(inputIncr);
+
+ // calculate the output size
+ size_t outputFrames = ((int64_t) provider.getNumFrames() * outputFreq) / inputFreq;
+ size_t outputFrameSize = channels * (useFloat ? sizeof(float) : sizeof(int32_t));
+ size_t outputSize = outputFrameSize * outputFrames;
+ outputSize &= ~7;
+
+ // create the resampler
+ const int volumePrecision = 12; /* typical unity gain */
+ android::AudioResampler* resampler;
+
+ resampler = android::AudioResampler::create(bits, channels, outputFreq, quality);
+ resampler->setSampleRate(inputFreq);
+ resampler->setVolume(1 << volumePrecision, 1 << volumePrecision);
+
+ // set up the reference run
+ std::vector<size_t> refIncr;
+ refIncr.push_back(outputFrames);
+ void* reference = malloc(outputSize);
+ resample(channels, reference, outputFrames, refIncr, &provider, resampler);
+
+ provider.reset();
+
+#if 0
+ /* this test will fail - API interface issue: reset() does not clear internal buffers */
+ resampler->reset();
+#else
+ delete resampler;
+ resampler = android::AudioResampler::create(bits, channels, outputFreq, quality);
+ resampler->setSampleRate(inputFreq);
+ resampler->setVolume(1 << volumePrecision, 1 << volumePrecision);
+#endif
+
+ // set up the test run
+ std::vector<size_t> outIncr;
+ outIncr.push_back(1);
+ outIncr.push_back(2);
+ outIncr.push_back(3);
+ void* test = malloc(outputSize);
+ inputIncr.push_back(1);
+ inputIncr.push_back(3);
+ provider.setIncr(inputIncr);
+ resample(channels, test, outputFrames, outIncr, &provider, resampler);
+
+ // check
+ buffercmp(reference, test, outputFrameSize, outputFrames);
+
+ free(reference);
+ free(test);
+ delete resampler;
+}
+
+template <typename T>
+inline double sqr(T v)
+{
+ double dv = static_cast<double>(v);
+ return dv * dv;
+}
+
+template <typename T>
+double signalEnergy(T *start, T *end, unsigned stride)
+{
+ double accum = 0;
+
+ for (T *p = start; p < end; p += stride) {
+ accum += sqr(*p);
+ }
+ unsigned count = (end - start + stride - 1) / stride;
+ return accum / count;
+}
+
+void testStopbandDownconversion(size_t channels,
+ unsigned inputFreq, unsigned outputFreq,
+ unsigned passband, unsigned stopband,
+ enum android::AudioResampler::src_quality quality)
+{
+ // create the provider
+ std::vector<int> inputIncr;
+ SignalProvider provider;
+ provider.setChirp<int16_t>(channels,
+ 0., inputFreq/2., inputFreq, inputFreq/2000.);
+ provider.setIncr(inputIncr);
+
+ // calculate the output size
+ size_t outputFrames = ((int64_t) provider.getNumFrames() * outputFreq) / inputFreq;
+ size_t outputFrameSize = channels * sizeof(int32_t);
+ size_t outputSize = outputFrameSize * outputFrames;
+ outputSize &= ~7;
+
+ // create the resampler
+ const int volumePrecision = 12; /* typical unity gain */
+ android::AudioResampler* resampler;
+
+ resampler = android::AudioResampler::create(16, channels, outputFreq, quality);
+ resampler->setSampleRate(inputFreq);
+ resampler->setVolume(1 << volumePrecision, 1 << volumePrecision);
+
+ // set up the reference run
+ std::vector<size_t> refIncr;
+ refIncr.push_back(outputFrames);
+ void* reference = malloc(outputSize);
+ resample(channels, reference, outputFrames, refIncr, &provider, resampler);
+
+ int32_t *out = reinterpret_cast<int32_t *>(reference);
+
+ // check signal energy in passband
+ const unsigned passbandFrame = passband * outputFreq / 1000.;
+ const unsigned stopbandFrame = stopband * outputFreq / 1000.;
+
+ // check each channel separately
+ for (size_t i = 0; i < channels; ++i) {
+ double passbandEnergy = signalEnergy(out, out + passbandFrame * channels, channels);
+ double stopbandEnergy = signalEnergy(out + stopbandFrame * channels,
+ out + outputFrames * channels, channels);
+ double dbAtten = -10. * log10(stopbandEnergy / passbandEnergy);
+ ASSERT_GT(dbAtten, 60.);
+
+#if 0
+ // internal verification
+ printf("if:%d of:%d pbf:%d sbf:%d sbe: %f pbe: %f db: %.2f\n",
+ provider.getNumFrames(), outputFrames,
+ passbandFrame, stopbandFrame, stopbandEnergy, passbandEnergy, dbAtten);
+ for (size_t i = 0; i < 10; ++i) {
+ printf("%d\n", out[i+passbandFrame*channels]);
+ }
+ for (size_t i = 0; i < 10; ++i) {
+ printf("%d\n", out[i+stopbandFrame*channels]);
+ }
+#endif
+ }
+
+ free(reference);
+ delete resampler;
+}
+
+/* Buffer increment test
+ *
+ * We compare a reference output, where we consume and process the entire
+ * buffer at a time, and a test output, where we provide small chunks of input
+ * data and process small chunks of output (which may not be equivalent in size).
+ *
+ * Two subtests - fixed phase (3:2 down) and interpolated phase (147:320 up)
+ */
+TEST(audioflinger_resampler, bufferincrement_fixedphase) {
+ // all of these work
+ static const enum android::AudioResampler::src_quality kQualityArray[] = {
+ android::AudioResampler::LOW_QUALITY,
+ android::AudioResampler::MED_QUALITY,
+ android::AudioResampler::HIGH_QUALITY,
+ android::AudioResampler::VERY_HIGH_QUALITY,
+ android::AudioResampler::DYN_LOW_QUALITY,
+ android::AudioResampler::DYN_MED_QUALITY,
+ android::AudioResampler::DYN_HIGH_QUALITY,
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+ testBufferIncrement(2, false, 48000, 32000, kQualityArray[i]);
+ }
+}
+
+TEST(audioflinger_resampler, bufferincrement_interpolatedphase) {
+ // all of these work except low quality
+ static const enum android::AudioResampler::src_quality kQualityArray[] = {
+// android::AudioResampler::LOW_QUALITY,
+ android::AudioResampler::MED_QUALITY,
+ android::AudioResampler::HIGH_QUALITY,
+ android::AudioResampler::VERY_HIGH_QUALITY,
+ android::AudioResampler::DYN_LOW_QUALITY,
+ android::AudioResampler::DYN_MED_QUALITY,
+ android::AudioResampler::DYN_HIGH_QUALITY,
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+ testBufferIncrement(2, false, 22050, 48000, kQualityArray[i]);
+ }
+}
+
+TEST(audioflinger_resampler, bufferincrement_fixedphase_multi) {
+ // only dynamic quality
+ static const enum android::AudioResampler::src_quality kQualityArray[] = {
+ android::AudioResampler::DYN_LOW_QUALITY,
+ android::AudioResampler::DYN_MED_QUALITY,
+ android::AudioResampler::DYN_HIGH_QUALITY,
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+ testBufferIncrement(4, false, 48000, 32000, kQualityArray[i]);
+ }
+}
+
+TEST(audioflinger_resampler, bufferincrement_interpolatedphase_multi_float) {
+ // only dynamic quality
+ static const enum android::AudioResampler::src_quality kQualityArray[] = {
+ android::AudioResampler::DYN_LOW_QUALITY,
+ android::AudioResampler::DYN_MED_QUALITY,
+ android::AudioResampler::DYN_HIGH_QUALITY,
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+ testBufferIncrement(8, true, 22050, 48000, kQualityArray[i]);
+ }
+}
+
+/* Simple aliasing test
+ *
+ * This checks stopband response of the chirp signal to make sure frequencies
+ * are properly suppressed. It uses downsampling because the stopband can be
+ * clearly isolated by input frequencies exceeding the output sample rate (nyquist).
+ */
+TEST(audioflinger_resampler, stopbandresponse) {
+ // not all of these may work (old resamplers fail on downsampling)
+ static const enum android::AudioResampler::src_quality kQualityArray[] = {
+ //android::AudioResampler::LOW_QUALITY,
+ //android::AudioResampler::MED_QUALITY,
+ //android::AudioResampler::HIGH_QUALITY,
+ //android::AudioResampler::VERY_HIGH_QUALITY,
+ android::AudioResampler::DYN_LOW_QUALITY,
+ android::AudioResampler::DYN_MED_QUALITY,
+ android::AudioResampler::DYN_HIGH_QUALITY,
+ };
+
+ // in this test we assume a maximum transition band between 12kHz and 20kHz.
+ // there must be at least 60dB relative attenuation between stopband and passband.
+ for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+ testStopbandDownconversion(2, 48000, 32000, 12000, 20000, kQualityArray[i]);
+ }
+
+ // in this test we assume a maximum transition band between 7kHz and 15kHz.
+ // there must be at least 60dB relative attenuation between stopband and passband.
+ // (the weird ratio triggers interpolative resampling)
+ for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
+ testStopbandDownconversion(2, 48000, 22101, 7000, 15000, kQualityArray[i]);
+ }
+}
diff --git a/services/audioflinger/tests/run_all_unit_tests.sh b/services/audioflinger/tests/run_all_unit_tests.sh
new file mode 100755
index 0000000..ffae6ae
--- /dev/null
+++ b/services/audioflinger/tests/run_all_unit_tests.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+fi
+
+echo "waiting for device"
+adb root && adb wait-for-device remount
+
+adb shell /system/bin/resampler_tests
diff --git a/services/audioflinger/tests/test-mixer.cpp b/services/audioflinger/tests/test-mixer.cpp
new file mode 100644
index 0000000..3940702
--- /dev/null
+++ b/services/audioflinger/tests/test-mixer.cpp
@@ -0,0 +1,286 @@
+/*
+ * 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>
+#include <vector>
+#include <audio_utils/primitives.h>
+#include <audio_utils/sndfile.h>
+#include <media/AudioBufferProvider.h>
+#include "AudioMixer.h"
+#include "test_utils.h"
+
+/* Testing is typically through creation of an output WAV file from several
+ * source inputs, to be later analyzed by an audio program such as Audacity.
+ *
+ * Sine or chirp functions are typically more useful as input to the mixer
+ * as they show up as straight lines on a spectrogram if successfully mixed.
+ *
+ * A sample shell script is provided: mixer_to_wave_tests.sh
+ */
+
+using namespace android;
+
+static void usage(const char* name) {
+ fprintf(stderr, "Usage: %s [-f] [-m]"
+ " [-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, " -m enable floating point mixer output\n");
+ fprintf(stderr, " -s mixer sample-rate\n");
+ fprintf(stderr, " -o <output-file> WAV file, pcm16 (or float if -m specified)\n");
+ 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");
+}
+
+static int writeFile(const char *filename, const void *buffer,
+ uint32_t sampleRate, uint32_t channels, size_t frames, bool isBufferFloat) {
+ if (filename == NULL) {
+ return 0; // ok to pass in NULL filename
+ }
+ // write output to file.
+ SF_INFO info;
+ info.frames = 0;
+ info.samplerate = sampleRate;
+ info.channels = channels;
+ info.format = SF_FORMAT_WAV | (isBufferFloat ? SF_FORMAT_FLOAT : SF_FORMAT_PCM_16);
+ printf("saving file:%s channels:%d samplerate:%d frames:%d\n",
+ filename, info.channels, info.samplerate, frames);
+ SNDFILE *sf = sf_open(filename, SFM_WRITE, &info);
+ if (sf == NULL) {
+ perror(filename);
+ return EXIT_FAILURE;
+ }
+ if (isBufferFloat) {
+ (void) sf_writef_float(sf, (float*)buffer, frames);
+ } else {
+ (void) sf_writef_short(sf, (short*)buffer, frames);
+ }
+ sf_close(sf);
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char* argv[]) {
+ const char* const progname = argv[0];
+ bool useInputFloat = false;
+ bool useMixerFloat = false;
+ bool useRamp = true;
+ uint32_t outputSampleRate = 48000;
+ uint32_t outputChannels = 2; // stereo for now
+ std::vector<int> Pvalues;
+ const char* outputFilename = NULL;
+ const char* auxFilename = NULL;
+ std::vector<int32_t> Names;
+ std::vector<SignalProvider> Providers;
+
+ for (int ch; (ch = getopt(argc, argv, "fms:o:a:P:")) != -1;) {
+ switch (ch) {
+ case 'f':
+ useInputFloat = true;
+ break;
+ case 'm':
+ useMixerFloat = true;
+ break;
+ case 's':
+ outputSampleRate = atoi(optarg);
+ break;
+ case 'o':
+ outputFilename = optarg;
+ break;
+ case 'a':
+ auxFilename = optarg;
+ break;
+ case 'P':
+ if (parseCSV(optarg, Pvalues) < 0) {
+ fprintf(stderr, "incorrect syntax for -P option\n");
+ return EXIT_FAILURE;
+ }
+ break;
+ case '?':
+ default:
+ usage(progname);
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ usage(progname);
+ return EXIT_FAILURE;
+ }
+ if ((unsigned)argc > AudioMixer::MAX_NUM_TRACKS) {
+ fprintf(stderr, "too many tracks: %d > %u", argc, AudioMixer::MAX_NUM_TRACKS);
+ return EXIT_FAILURE;
+ }
+
+ size_t outputFrames = 0;
+
+ // create providers for each track
+ Providers.resize(argc);
+ for (int i = 0; i < argc; ++i) {
+ static const char chirp[] = "chirp:";
+ static const char sine[] = "sine:";
+ static const double kSeconds = 1;
+
+ if (!strncmp(argv[i], chirp, strlen(chirp))) {
+ std::vector<int> v;
+
+ parseCSV(argv[i] + strlen(chirp), 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);
+ } else {
+ Providers[i].setChirp<int16_t>(v[0], 0, v[1]/2, v[1], kSeconds);
+ }
+ 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;
+
+ parseCSV(argv[i] + strlen(sine), v);
+ if (v.size() == 3) {
+ printf("creating sine(%d %d)\n", v[0], v[1]);
+ if (useInputFloat) {
+ Providers[i].setSine<float>(v[0], v[1], v[2], kSeconds);
+ } else {
+ Providers[i].setSine<int16_t>(v[0], v[1], v[2], kSeconds);
+ }
+ 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]);
+ } else {
+ Providers[i].setFile<short>(argv[i]);
+ }
+ Providers[i].setIncr(Pvalues);
+ }
+ // calculate the number of output frames
+ size_t nframes = (int64_t) Providers[i].getNumFrames() * outputSampleRate
+ / Providers[i].getSampleRate();
+ if (i == 0 || outputFrames > nframes) { // choose minimum for outputFrames
+ outputFrames = nframes;
+ }
+ }
+
+ // create the output buffer.
+ const size_t outputFrameSize = outputChannels
+ * (useMixerFloat ? sizeof(float) : sizeof(int16_t));
+ const size_t outputSize = outputFrames * outputFrameSize;
+ void *outputAddr = NULL;
+ (void) posix_memalign(&outputAddr, 32, outputSize);
+ memset(outputAddr, 0, outputSize);
+
+ // create the aux buffer, if needed.
+ const size_t auxFrameSize = sizeof(int32_t); // Q4.27 always
+ const size_t auxSize = outputFrames * auxFrameSize;
+ void *auxAddr = NULL;
+ if (auxFilename) {
+ (void) posix_memalign(&auxAddr, 32, auxSize);
+ memset(auxAddr, 0, auxSize);
+ }
+
+ // 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
+ 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());
+ int32_t name = mixer->getTrackName(channelMask,
+ inputFormat, AUDIO_SESSION_OUTPUT_MIX);
+ ALOG_ASSERT(name >= 0);
+ Names.push_back(name);
+ mixer->setBufferProvider(name, &Providers[i]);
+ mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
+ (void *) outputAddr);
+ mixer->setParameter(
+ name,
+ AudioMixer::TRACK,
+ AudioMixer::MIXER_FORMAT, (void *)mixerFormat);
+ mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::FORMAT,
+ (void *)(uintptr_t)inputFormat);
+ mixer->setParameter(
+ name,
+ AudioMixer::RESAMPLE,
+ AudioMixer::SAMPLE_RATE,
+ (void *)(uintptr_t)Providers[i].getSampleRate());
+ if (useRamp) {
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &f0);
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &f0);
+ mixer->setParameter(name, AudioMixer::RAMP_VOLUME, AudioMixer::VOLUME0, &f);
+ mixer->setParameter(name, AudioMixer::RAMP_VOLUME, AudioMixer::VOLUME1, &f);
+ } else {
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &f);
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &f);
+ }
+ if (auxFilename) {
+ mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::AUX_BUFFER,
+ (void *) auxAddr);
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::AUXLEVEL, &f0);
+ mixer->setParameter(name, AudioMixer::RAMP_VOLUME, AudioMixer::AUXLEVEL, &f);
+ }
+ mixer->enable(name);
+ }
+
+ // 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,
+ (char *) outputAddr + i * outputFrameSize);
+ if (auxFilename) {
+ mixer->setParameter(Names[j], AudioMixer::TRACK, AudioMixer::AUX_BUFFER,
+ (char *) auxAddr + i * auxFrameSize);
+ }
+ }
+ mixer->process(AudioBufferProvider::kInvalidPTS);
+ }
+ outputFrames = i; // reset output frames to the data actually produced.
+
+ // write to files
+ writeFile(outputFilename, outputAddr,
+ outputSampleRate, outputChannels, outputFrames, useMixerFloat);
+ if (auxFilename) {
+ // Aux buffer is always in q4_27 format for now.
+ // memcpy_to_i16_from_q4_27(), but with stereo frame count (not sample count)
+ ditherAndClamp((int32_t*)auxAddr, (int32_t*)auxAddr, outputFrames >> 1);
+ writeFile(auxFilename, auxAddr, outputSampleRate, 1, outputFrames, false);
+ }
+
+ delete mixer;
+ free(outputAddr);
+ free(auxAddr);
+ return EXIT_SUCCESS;
+}
diff --git a/services/audioflinger/tests/test_utils.h b/services/audioflinger/tests/test_utils.h
new file mode 100644
index 0000000..f954292
--- /dev/null
+++ b/services/audioflinger/tests/test_utils.h
@@ -0,0 +1,307 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_AUDIO_TEST_UTILS_H
+#define ANDROID_AUDIO_TEST_UTILS_H
+
+#include <audio_utils/sndfile.h>
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+template<typename T, typename U>
+struct is_same
+{
+ static const bool value = false;
+};
+
+template<typename T>
+struct is_same<T, T> // partial specialization
+{
+ static const bool value = true;
+};
+
+template<typename T>
+static inline T convertValue(double val)
+{
+ if (is_same<T, int16_t>::value) {
+ return floor(val * 32767.0 + 0.5);
+ } else if (is_same<T, int32_t>::value) {
+ return floor(val * (1UL<<31) + 0.5);
+ }
+ return val; // assume float or double
+}
+
+// Convert a list of integers in CSV format to a Vector of those values.
+// Returns the number of elements in the list, or -1 on error.
+static inline int parseCSV(const char *string, std::vector<int>& values)
+{
+ // pass 1: count the number of values and do syntax check
+ size_t numValues = 0;
+ bool hadDigit = false;
+ for (const char *p = string; ; ) {
+ switch (*p++) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ hadDigit = true;
+ break;
+ case '\0':
+ if (hadDigit) {
+ // pass 2: allocate and initialize vector of values
+ values.resize(++numValues);
+ values[0] = atoi(p = string);
+ for (size_t i = 1; i < numValues; ) {
+ if (*p++ == ',') {
+ values[i++] = atoi(p);
+ }
+ }
+ return numValues;
+ }
+ // fall through
+ case ',':
+ if (hadDigit) {
+ hadDigit = false;
+ numValues++;
+ break;
+ }
+ // fall through
+ default:
+ return -1;
+ }
+ }
+}
+
+/* Creates a type-independent audio buffer provider from
+ * a buffer base address, size, framesize, and input increment array.
+ *
+ * No allocation or deallocation of the provided buffer is done.
+ */
+class TestProvider : public android::AudioBufferProvider {
+public:
+ TestProvider(void* addr, size_t frames, size_t frameSize,
+ const std::vector<int>& inputIncr)
+ : mAddr(addr),
+ mNumFrames(frames),
+ mFrameSize(frameSize),
+ mNextFrame(0), mUnrel(0), mInputIncr(inputIncr), mNextIdx(0)
+ {
+ }
+
+ TestProvider()
+ : mAddr(NULL), mNumFrames(0), mFrameSize(0),
+ mNextFrame(0), mUnrel(0), mNextIdx(0)
+ {
+ }
+
+ void setIncr(const std::vector<int>& inputIncr) {
+ mInputIncr = inputIncr;
+ mNextIdx = 0;
+ }
+
+ virtual android::status_t getNextBuffer(Buffer* buffer, int64_t pts __unused = kInvalidPTS)
+ {
+ size_t requestedFrames = buffer->frameCount;
+ if (requestedFrames > mNumFrames - mNextFrame) {
+ buffer->frameCount = mNumFrames - mNextFrame;
+ }
+ if (!mInputIncr.empty()) {
+ size_t provided = mInputIncr[mNextIdx++];
+ ALOGV("getNextBuffer() mValue[%d]=%u not %u",
+ mNextIdx-1, provided, buffer->frameCount);
+ if (provided < buffer->frameCount) {
+ buffer->frameCount = provided;
+ }
+ if (mNextIdx >= mInputIncr.size()) {
+ mNextIdx = 0;
+ }
+ }
+ ALOGV("getNextBuffer() requested %u frames out of %u frames available"
+ " and returned %u frames\n",
+ requestedFrames, mNumFrames - mNextFrame, buffer->frameCount);
+ mUnrel = buffer->frameCount;
+ if (buffer->frameCount > 0) {
+ buffer->raw = (char *)mAddr + mFrameSize * mNextFrame;
+ return android::NO_ERROR;
+ } else {
+ buffer->raw = NULL;
+ return android::NOT_ENOUGH_DATA;
+ }
+ }
+
+ virtual void releaseBuffer(Buffer* buffer)
+ {
+ if (buffer->frameCount > mUnrel) {
+ ALOGE("releaseBuffer() released %u frames but only %u available "
+ "to release\n", buffer->frameCount, mUnrel);
+ mNextFrame += mUnrel;
+ mUnrel = 0;
+ } else {
+
+ ALOGV("releaseBuffer() released %u frames out of %u frames available "
+ "to release\n", buffer->frameCount, mUnrel);
+ mNextFrame += buffer->frameCount;
+ mUnrel -= buffer->frameCount;
+ }
+ buffer->frameCount = 0;
+ buffer->raw = NULL;
+ }
+
+ void reset()
+ {
+ mNextFrame = 0;
+ }
+
+ size_t getNumFrames()
+ {
+ return mNumFrames;
+ }
+
+
+protected:
+ void* mAddr; // base address
+ size_t mNumFrames; // total frames
+ int mFrameSize; // frame size (# channels * bytes per sample)
+ size_t mNextFrame; // index of next frame to provide
+ size_t mUnrel; // number of frames not yet released
+ std::vector<int> mInputIncr; // number of frames provided per call
+ size_t mNextIdx; // index of next entry in mInputIncr to use
+};
+
+/* Creates a buffer filled with a sine wave.
+ */
+template<typename T>
+static void createSine(void *vbuffer, size_t frames,
+ size_t channels, double sampleRate, double freq)
+{
+ double tscale = 1. / sampleRate;
+ T* buffer = reinterpret_cast<T*>(vbuffer);
+ for (size_t i = 0; i < frames; ++i) {
+ double t = i * tscale;
+ double y = sin(2. * M_PI * freq * t);
+ T yt = convertValue<T>(y);
+
+ for (size_t j = 0; j < channels; ++j) {
+ buffer[i*channels + j] = yt / (j + 1);
+ }
+ }
+}
+
+/* Creates a buffer filled with a chirp signal (a sine wave sweep).
+ *
+ * When creating the Chirp, note that the frequency is the true sinusoidal
+ * frequency not the sampling rate.
+ *
+ * http://en.wikipedia.org/wiki/Chirp
+ */
+template<typename T>
+static void createChirp(void *vbuffer, size_t frames,
+ size_t channels, double sampleRate, double minfreq, double maxfreq)
+{
+ double tscale = 1. / sampleRate;
+ T *buffer = reinterpret_cast<T*>(vbuffer);
+ // note the chirp constant k has a divide-by-two.
+ double k = (maxfreq - minfreq) / (2. * tscale * frames);
+ for (size_t i = 0; i < frames; ++i) {
+ double t = i * tscale;
+ double y = sin(2. * M_PI * (k * t + minfreq) * t);
+ T yt = convertValue<T>(y);
+
+ for (size_t j = 0; j < channels; ++j) {
+ buffer[i*channels + j] = yt / (j + 1);
+ }
+ }
+}
+
+/* This derived class creates a buffer provider of datatype T,
+ * consisting of an input signal, e.g. from createChirp().
+ * The number of frames can be obtained from the base class
+ * TestProvider::getNumFrames().
+ */
+
+class SignalProvider : public TestProvider {
+public:
+ SignalProvider()
+ : mSampleRate(0),
+ mChannels(0)
+ {
+ }
+
+ virtual ~SignalProvider()
+ {
+ free(mAddr);
+ mAddr = NULL;
+ }
+
+ template <typename T>
+ void setChirp(size_t channels, double minfreq, double maxfreq, double sampleRate, double time)
+ {
+ createBufferByFrames<T>(channels, sampleRate, sampleRate*time);
+ createChirp<T>(mAddr, mNumFrames, mChannels, mSampleRate, minfreq, maxfreq);
+ }
+
+ template <typename T>
+ void setSine(size_t channels,
+ double freq, double sampleRate, double time)
+ {
+ createBufferByFrames<T>(channels, sampleRate, sampleRate*time);
+ createSine<T>(mAddr, mNumFrames, mChannels, mSampleRate, freq);
+ }
+
+ template <typename T>
+ void setFile(const char *file_in)
+ {
+ SF_INFO info;
+ info.format = 0;
+ SNDFILE *sf = sf_open(file_in, SFM_READ, &info);
+ if (sf == NULL) {
+ perror(file_in);
+ return;
+ }
+ createBufferByFrames<T>(info.channels, info.samplerate, info.frames);
+ if (is_same<T, float>::value) {
+ (void) sf_readf_float(sf, (float *) mAddr, mNumFrames);
+ } else if (is_same<T, short>::value) {
+ (void) sf_readf_short(sf, (short *) mAddr, mNumFrames);
+ }
+ sf_close(sf);
+ }
+
+ template <typename T>
+ void createBufferByFrames(size_t channels, uint32_t sampleRate, size_t frames)
+ {
+ mNumFrames = frames;
+ mChannels = channels;
+ mFrameSize = mChannels * sizeof(T);
+ free(mAddr);
+ mAddr = malloc(mFrameSize * mNumFrames);
+ mSampleRate = sampleRate;
+ }
+
+ uint32_t getSampleRate() const {
+ return mSampleRate;
+ }
+
+ uint32_t getNumChannels() const {
+ return mChannels;
+ }
+
+protected:
+ uint32_t mSampleRate;
+ uint32_t mChannels;
+};
+
+#endif // ANDROID_AUDIO_TEST_UTILS_H