diff options
Diffstat (limited to 'services/audioflinger')
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, ×tamp.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 |