本篇文章主要介绍Android Audio模块的MixerThread混音业务如何实现的?建议在阅读这篇文章之间先阅读混音理论基础篇,并且在分析源码之前:头脑里构想一个大致的混音过程,多路音频AudioTrack叠加在一起进行混音,多路音频混音的是数据对象是pcm数据,它如何叠加,混音后的音量又是如何处理?
首先回忆一下单路AudioTrack的音频播放流程,如下:
最后,我们分析的就是MixerThread。
创建MixerThread线程位于解析audio的configuration配置文件后,创建输出通道output就会创建各个PlaybackThread,其中就包括MixerThread,源码如下:
AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output,
audio_io_handle_t id, audio_devices_t device, bool systemReady, type_t type)
: PlaybackThread(audioFlinger, output, id, device, type, systemReady),
mFastMixerFutex(0),
mMasterMono(false)
{
setMasterBalance(audioFlinger->getMasterBalance_l());
//混音器AudioMixer
mAudioMixer = new AudioMixer(mNormalFrameCount, mSampleRate);
if (type == DUPLICATING) {
return;
}
// 通向HAL层的变量,混音后的数据会通过mOutputSink发送到HAL
mOutputSink = new AudioStreamOutSink(output->stream);
.....
}
在MixerThread中,混音的核心就在AudioMixer混音器,混音的相关逻辑就在里面。
为什么要先讲解PlaybackThread线程,因为MixerThread继承PlaybackThread,而PlaybackThread的threadLoop函数尤为重要,主要负责加载音频数据,调用AudioMixer处理混音,然后把混音后的数据写入到HAL层,threadLoop函数相当复杂,这里大致列出其中的几个关键点:
bool AudioFlinger::PlaybackThread::threadLoop()
{
......
//将需要混音的Track赋值到AudioMixer中,并配置相关混音参数Paramter
mMixerStatus = prepareTracks_l(&tracksToRemove);
//开始混音
threadLoop_mix();
//将混音后的数据写入到HAL中去
ret = threadLoop_write();
.....
}
threadLoop是回播线程的循环函数,会一直循环运行,threadLoop_mix由MixerThread类重写了,所以和之前文章中讲解的稍有不同。
因为混音逻辑主要在AudioMixer类中,而音频pcm数据和配置参数又分属在多个Track类中,所以要把大部分信息如音频format、采样率音频读取缓存地址、写入缓冲地址都要弄到AudioMixer中去,并且不同Track中采样率可能不一致还要为AudioMixer配置重采样管理器等等工作,这些工作都是在prepareTracks_l函数中来完成,在开始分析这个函数之前,先看看Track(位于PlaybackTracks.h)中的一些重要成员变量,后续会使用到;
class Track : public TrackBase, public VolumeProvider {
....
//mSharedBuffer也就是应用进程客户端传递过来,为0表示AudioFlinger创建共享内存,不为0表示应用进程创建共享内存
sp<IMemory> mSharedBuffer;
//track的音频类型,如AUDIO_STREAM_VOICE_CALL、AUDIO_STREAM_MUSIC等
const audio_stream_type_t mStreamType;
//混音后音频数据存放的地址
effect_buffer_t *mMainBuffer;
int32_t *mAuxBuffer;
....
}
//构造函数
AudioFlinger::PlaybackThread::Track::Track(.....):
mSharedBuffer(sharedBuffer),
mStreamType(streamType),
//sinkBuffer是混音后音频数据存放的地址
mMainBuffer(thread->sinkBuffer()),
mAuxBuffer(NULL),
...{}
mMainBuffer来源于thread->sinkBuffer,sink翻译为输出,实质上也就是混音后数据存放的地址,最后在threadLoop_write函数中时会从sinkBuffer将音频数据输出到HAL层;而mAuxBuffer则是音效Effect那边传入的buffer地址,混音时会将部分音频写到这个mAuxBuffer用于Effect特性使用;
同理,在AudioMixer混音时内部也有一个Track来对应上面的Audio的Track
混音器内部的Track和外面AudioFlinger的Track是一一对应的,保存了Audio Track的重要信息,看看内部Track的成员变量就知道:
struct Track {
//needs是一个状态变量,其中每个bit位表示不同的状态
uint32_t needs;
union {
/* volume代表应用层设置的音量,这里MAX_NUM_VOLUMES是2,表示左右两个通道的音量值,
* 应用层设置音量0~1的float音量值,转换到这里是int16,转换规则是设置音量float*16bit
* 的最大值,完成类型且值大小比例转换
**/
int16_t volume[MAX_NUM_VOLUMES];
int32_t volumeRL;
};
/**
* MAX_NUM_VOLUMES一般是2,即是左右声道的音量值
* **/
int32_t prevVolume[MAX_NUM_VOLUMES]; //上一次设置的音量值,是32bit;是由volume[MAX_NUM_VOLUMES]左移16bit转换的
int32_t volumeInc[MAX_NUM_VOLUMES]; //如果是渐变音量ramp,volumeInc代表每次变化的音量值
........
//源track的channelMask
audio_channel_mask_t channelMask;
/**bufferProvider实质是PlaybackThread创建的Track,而Track继承VolumeProvider,
* 其基类就是这个类型,通过这个provider可以调用音频数据获取接口
* */
AudioBufferProvider* bufferProvider;
//buffer会从bufferProvider中读取到真实的音频数据
mutable AudioBufferProvider::Buffer buffer; // 8 bytes
//hook相当重要,它会保存一个函数指针,指向当前Track混音的函数
hook_t hook;
//无论是否重采样,buffer的音频裸数据raw会写入到mIn
const void *mIn; // current location in buffer
//混音器
std::unique_ptr<AudioResampler> mResampler;
uint32_t sampleRate;
//混音后音频数据输出地址,存放的是真实音频数据,还要乘以mVloume音量大小
//这个地址一般是外部Track的MainBuffer,而MainBuffer又是PlaybackThread的mSinkBuffer指定
int32_t* mainBuffer;
/** 混音后辅助音频输出地址,这个辅助音频值为真实音频数据的极小部分,
* 几分之一那种, 最后还要乘以mAuxLevel;
* 这个地址一般由外部Track的auxBuffer指定,而auxBuffer又是
**/
int32_t* auxBuffer;
int32_t sessionId;
//最终混音之后的输出的格式,分析代码为AUDIO_FORMAT_PCM_16_BIT
audio_format_t mMixerFormat; // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
//track源格式
audio_format_t mFormat; // input track format
//混音内部格式 分析代码统一为AUDIO_FORMAT_PCM_FLOAT
audio_format_t mMixerInFormat; // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
audio_channel_mask_t mMixerChannelMask; //源通道存储MASK
uint32_t mMixerChannelCount; //源通道数
}
看到这里你可能还不知道每个变量如何使用,在后续的流程中,你会逐步了解混音中如何来使用。
务必记住hook、mainBuffer、volume音量相关的变量
从应用层到混音内部的Track对应框架:

下面,我们分析混音执行的逻辑:
回到prepareTracks_l函数中,看看在混音之前,为AudioMixer混音器具体做了哪些工作?如下代码:
AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l(
Vector< sp<Track> > *tracksToRemove)
{
//processDeletedTrackIds参数传入的是一个函数指针,而processDeletedTrackIds会遍历mDeletedTrackIds
(void)mTracks.processDeletedTrackIds([this](int trackId) {
// 集合,把需要删除的track删除掉,如果混音器AudioMixer也存在此track,把混音器里面对应的track也删除
if (mAudioMixer->exists(trackId)) {
mAudioMixer->destroy(trackId);
}
});
//mDeletedTrackIds集合请空,保存了需要删除的track
mTracks.clearDeletedTrackIds();
//初始化混音状态为IDLE
mixer_state mixerStatus = MIXER_IDLE;
//mActiveTracks保存了活跃的Track,也就是要被混音的
size_t count = mActiveTracks.size();
//materVolume是系统的主音量,还有其他的type类型音量等
float masterVolume = mMasterVolume;
//mMasterMute为系统主音量是否静音状态
bool masterMute = mMasterMute;
//如果系统为静音则设置主音量为0不发声
if (masterMute) {
masterVolume = 0;
}
......
bool noFastHapticTrack = true;
//遍历当前MixerThread线程下所有的存活Track
for (size_t i=0 ; i<count ; i++) {
const sp<Track> t = mActiveTracks[i];
// this const just means the local variable doesn't change
Track* const track = t.get();
.........
{
audio_track_cblk_t* cblk = track->cblk();
const int trackId = track->id();
//先检查以下混音器audioMixer是否已经创建内部Track了
if (!mAudioMixer->exists(trackId)) {
//在混音前内部AudioMixer创建一个Track,对应外面AudioFlinger的Track
status_t status = mAudioMixer->create(
trackId,
track->mChannelMask,
track->mFormat,
track->mSessionId);
......
}
//计算一次混音最少需要多少帧
size_t desiredFrames;
const uint32_t sampleRate = track->mAudioTrackServerProxy->getSampleRate();
AudioPlaybackRate playbackRate = track->mAudioTrackServerProxy->getPlaybackRate();
//desired想要的,mNormalFrameCount是HAL层共享内存缓冲区可接收最少的帧数,配置采样率播
//放速度计算上层应该供应最少的帧数,防止出现underrun情况
desiredFrames = sourceFramesNeededWithTimestretch(
sampleRate, mNormalFrameCount, mSampleRate, playbackRate.mSpeed);
desiredFrames += mAudioMixer->getUnreleasedFrames(trackId);
uint32_t minFrames = 1;
if ((track->sharedBuffer() == 0) && !track->isStopped() && !track->isPausing() &&
(mMixerStatusIgnoringFastTracks == MIXER_TRACKS_READY)) {
minFrames = desiredFrames;
}
//共享内存提供的音频数据量
size_t framesReady = track->framesReady();
//如果已准备的音频帧大于最小帧,这说明有足够的数据进行混音;否则数据不够需要等待
if ((framesReady >= minFrames) && track->isReady() &&
!track->isPaused() && !track->isTerminated())
{
mixedTracks++;
//开始计算音量
int param = AudioMixer::VOLUME;
if (track->mFillingUpStatus == Track::FS_FILLED) {
// no ramp for the first volume setting
track->mFillingUpStatus = Track::FS_ACTIVE;
if (track->mState == TrackBase::RESUMING) {
track->mState = TrackBase::ACTIVE;
//由暂停到恢复,且mServer代表提供数据端部位空
if (cblk->mServer != 0) {
//加入渐变音效,也就是音量恢复时音量大小从小变大
param = AudioMixer::RAMP_VOLUME;
}
}
//重置重采样管理器,将混音器总的重采样管理器重置为空
mAudioMixer->setParameter(trackId, AudioMixer::RESAMPLE, AudioMixer::RESET, NULL);
mLeftVolFloat = -1.0;
// FIXME should not make a decision based on mServer
} else if (cblk->mServer != 0) {
// If the track is stopped before the first frame was mixed,
// do not apply ramp
param = AudioMixer::RAMP_VOLUME;
}
// compute volume for this track
uint32_t vl, vr; // in U8.24 integer format
float vlf, vrf, vaf; // in [0.0, 1.0] float format
// read original volumes with volume control
float typeVolume = mStreamTypes[track->streamType()].volume;
//为什么是乘法,不是加法呢,type音频音量*master主音量
float v = masterVolume * typeVolume;
const sp<AudioTrackServerProxy> proxy = track->mAudioTrackServerProxy;
//这个是渐变音量,会暴露接口给应用层,由应用层决定音量变化VolumeShaper,根据released已经
//播放的音频位置,确定渐变VolumeShaper的对应点的音量
const float vh = track->getVolumeHandler()->getVolume(
track->mAudioTrackServerProxy->framesReleased()).first;
//当前track已经pause 或者 此类型的音频已经设置静音 将把音量设置为0
if (track->isPausing() || mStreamTypes[track->streamType()].mute
|| track->isPlaybackRestricted()) {
vl = vr = 0;
vlf = vrf = vaf = 0.;
if (track->isPausing()) {
track->setPaused();
}
} else {
//获取当前Track的音量,mCblk内传递过来,用户端上层传入,由用户控制
gain_minifloat_packed_t vlr = proxy->getVolumeLR();
//左右声道值合成在vlr中,将其分解为两个单独的声道值
vlf = float_from_gain(gain_minifloat_unpack_left(vlr));
vrf = float_from_gain(gain_minifloat_unpack_right(vlr));
//GAIN_FLOAT_UNITY为1.0,也就是最大不能超过1
if (vlf > GAIN_FLOAT_UNITY) {
ALOGV("Track left volume out of range: %.3g", vlf);
vlf = GAIN_FLOAT_UNITY;
}
if (vrf > GAIN_FLOAT_UNITY) {
ALOGV("Track right volume out of range: %.3g", vrf);
vrf = GAIN_FLOAT_UNITY;
}
//为啥做乘法,看混音理论派音量分贝叠加
vlf *= v * vh;
vrf *= v * vh;
......
}
track->setFinalVolume((vrf + vlf) / 2.f);
......
// XXX: these things DON'T need to be done each time
mAudioMixer->setBufferProvider(trackId, track);
mAudioMixer->enable(trackId);
//param可能取值VOLUME和RAMP_VOLUME 设置音量或auxlevel到AudioMixer的Track的mAuxLevel或mVloume
mAudioMixer->setParameter(trackId, param, AudioMixer::VOLUME0, &vlf);
mAudioMixer->setParameter(trackId, param, AudioMixer::VOLUME1, &vrf);
mAudioMixer->setParameter(trackId, param, AudioMixer::AUXLEVEL, &vaf);
mAudioMixer->setParameter(
trackId,
AudioMixer::TRACK,
AudioMixer::FORMAT, (void *)track->format());
mAudioMixer->setParameter(
trackId,
AudioMixer::TRACK,
AudioMixer::CHANNEL_MASK, (void *)(uintptr_t)track->channelMask());
mAudioMixer->setParameter(
trackId,
AudioMixer::TRACK,
AudioMixer::MIXER_CHANNEL_MASK,
(void *)(uintptr_t)(mChannelMask | mHapticChannelMask));
//创建重采样管理器
mAudioMixer->setParameter(
trackId,
AudioMixer::RESAMPLE,
AudioMixer::SAMPLE_RATE,
(void *)(uintptr_t)reqSampleRate);
AudioPlaybackRate playbackRate = proxy->getPlaybackRate();
//设置回播率
mAudioMixer->setParameter(
trackId,
AudioMixer::TIMESTRETCH,
AudioMixer::PLAYBACK_RATE,
&playbackRate);
//mainBuffer是音频数据来源的地址
mAudioMixer->setParameter(
trackId,
AudioMixer::TRACK,
AudioMixer::MAIN_BUFFER, (void *)track->mainBuffer());
//auxBuffer是音频效果effect那边的buffer
mAudioMixer->setParameter(
trackId,
AudioMixer::TRACK,
AudioMixer::AUX_BUFFER, (void *)track->auxBuffer());
.....
if (mMixerStatusIgnoringFastTracks != MIXER_TRACKS_READY ||
mixerStatus != MIXER_TRACKS_ENABLED) {
mixerStatus = MIXER_TRACKS_READY;
}
} else {
//音频数据没准备够的处理
.......
mixerStatus = MIXER_TRACKS_ENABLED;
}
}
}
//返回状态
return mixerStatus;
}
从上面代码可以得知主要分为以下几个工作:
最后,返回mixerStatus状态到threadLoop函数中,会根据返回状态决定是否混音音频数据还是睡眠等待音频数据填满。
首先,从应用层到AudioFlinger中间通过匿名共享内存(大小固定)传递音频数据,同理AudioFlinger到HAL层也是匿名共享内存来传递音频数据。
其次,HAL层的缓存区在建立时将其大小buffersize传递给了PlaybackThread,计算出来该缓冲区处理音频数据最小帧数为mNormalFrameCount.
最后,由于Kernal层处理音频的采样率和应用层的音频采样率可能不相等,所以要mNormalFrameCount转换一下才行,判断应用层提供的数据大于它才行。
//计算一次混音最少需要多少帧
size_t desiredFrames;
const uint32_t sampleRate = track->mAudioTrackServerProxy->getSampleRate();
AudioPlaybackRate playbackRate = track->mAudioTrackServerProxy->getPlaybackRate();
//desired想要的,mNormalFrameCount是HAL层共享内存缓冲区可接收最少的帧数,配置采样率播
//放速度计算上层应该供应最少的帧数,防止出现underrun情况
desiredFrames = sourceFramesNeededWithTimestretch(
sampleRate, mNormalFrameCount, mSampleRate, playbackRate.mSpeed);
desiredFrames += mAudioMixer->getUnreleasedFrames(trackId);
//minFrames默认等于1,是假如是STATIC模式,一次写入即可
uint32_t minFrames = 1;
if ((track->sharedBuffer() == 0) && !track->isStopped() && !track->isPausing() &&
(mMixerStatusIgnoringFastTracks == MIXER_TRACKS_READY)) {
minFrames = desiredFrames;
}
看看这个sourceFramesNeededWithTimestretch是如何计算的?
static inline size_t sourceFramesNeededWithTimestretch(
uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate,
float speed) {
// required is the number of input frames the resampler needs
size_t required = sourceFramesNeeded(srcSampleRate, dstFramesRequired, dstSampleRate);
// to deliver this, the time stretcher requires:
return required * (double)speed + 1 + 1; // accounting for rounding dependencies
}
static inline size_t sourceFramesNeeded(
uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate) {
/* 为什么要这么做?可以这么理解:
* 1. 采样器的采样率sampleRate=count/单位时间h;
* 2. 同理,播放时将音频数据单位时间内处理的数量也是有一个转换率,也可以理解为采样率,应该叫转换率,也就是下面的公式dstSampleRate
* 3. dstFrameRequireed就是播放时,转换率能处理完的数据;然后按照源采样率公式和目标采样率公式就可以计算出源要提供多少的数据
* 才能满足目的帧数
**/
return srcSampleRate == dstSampleRate ? dstFramesRequired :
size_t((uint64_t)dstFramesRequired * srcSampleRate / dstSampleRate + 1 + 1);
}
应用端提供的大小大致是这个过程,看上面sourceFramesNeeded函数内注释,不同采样率的转换规则,最后要乘上播放速度即可;细节方面还是有许多疑问:
转换后的帧数为什么存在着加1?不担心变大后HAL层缓冲区放不下处理不过来吗?乘speed播放速度也是,不担心HAL缓冲区放不下吗? 对这块理解的可在评论区留言
最后计算出来desiredFrames就是HAL能处理的最小帧数大小,只要应用层提供的数据大于它就说明数据充足,可以进行下一步了。
在Android的音频系统中,音量可以分为这些音量:
设置的内容很多,包含format、sampleRate、音量volume、音频数据输入地址mainBuffer、重采样器等等,都是通过统一的方法setParameter来设置进去的,我们进去看看函数:
void AudioMixer::setParameter(int name, int target, int param, void *value)
{
LOG_ALWAYS_FATAL_IF(!exists(name), "invalid name: %d", name);
const std::shared_ptr<Track> &track = mTracks[name];
int valueInt = static_cast<int>(reinterpret_cast<uintptr_t>(value));
int32_t *valueBuf = reinterpret_cast<int32_t*>(value);
switch (target) {
case TRACK:
switch (param) {
case CHANNEL_MASK:
case MAIN_BUFFER:
case AUX_BUFFER:
case FORMAT:
case MIXER_FORMAT:
case MIXER_CHANNEL_MASK:
case HAPTIC_ENABLED:
case HAPTIC_INTENSITY:
default:ALWAYS_FATAL("setParameter track: bad param %d", param);
break;
}
case RESAMPLE:
switch (param) {
case SAMPLE_RATE:
if (track->setResampler(uint32_t(valueInt), mSampleRate)) {
ALOGV("setParameter(RESAMPLE, SAMPLE_RATE, %u)",
uint32_t(valueInt));
invalidate();
}
case RESET:
case REMOVE:
default:
LOG_ALWAYS_FATAL("setParameter resample: bad param %d", param);
}
break;
case RAMP_VOLUME:
case VOLUME:
switch (param) {
case AUXLEVEL:
if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
target == RAMP_VOLUME ? mFrameCount : 0,
&track->auxLevel, &track->prevAuxLevel, &track->auxInc,
&track->mAuxLevel, &track->mPrevAuxLevel, &track->mAuxInc)) {
ALOGV("setParameter(%s, AUXLEVEL: %04x)",
target == VOLUME ? "VOLUME" : "RAMP_VOLUME", track->auxLevel);
invalidate();
}
break;
default:
if ((unsigned)param >= VOLUME0 && (unsigned)param < VOLUME0 + MAX_NUM_VOLUMES) {
if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
target == RAMP_VOLUME ? mFrameCount : 0,
&track->volume[param - VOLUME0],
&track->prevVolume[param - VOLUME0],
&track->volumeInc[param - VOLUME0],
&track->mVolume[param - VOLUME0],
&track->mPrevVolume[param - VOLUME0],
&track->mVolumeInc[param - VOLUME0])) {
ALOGV("setParameter(%s, VOLUME%d: %04x)",
target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0,
track->volume[param - VOLUME0]);
invalidate();
}
} else {
LOG_ALWAYS_FATAL("setParameter volume: bad param %d", param);
}
}
break;
case TIMESTRETCH:
switch (param) {
case PLAYBACK_RATE:
default:
break;
default:
LOG_ALWAYS_FATAL("setParameter: bad target %d", target);
}
}
setParameter内部就是一大堆的switch-case语法,主要是往AudioMixer内部的Track设置一些参数等,捡重要的几个来讲
在上面参数设置中name为RESAMPLE,param为SAMPLE_RATE就是设置重采样管理器,分析setResampler函数:
/**
* trackSampleRate为混音前采样率 devSampleRate为混音后
* **/
bool AudioMixer::Track::setResampler(uint32_t trackSampleRate, uint32_t devSampleRate)
{
/** 是否需要重采样的依据:
* 1. 当前音频采样率和目标采样率是否相等
* 2. mResampler采样管理器是否为空,mResampler属于Track内部的成员
* **/
if (trackSampleRate != devSampleRate || mResampler.get() != nullptr) {
//sampleRate为Track成员,创建Track时赋值为mSampleRate
if (sampleRate != trackSampleRate) {
sampleRate = trackSampleRate;
if (mResampler.get() == nullptr) {
......
//创建重采样AudioResampler,参数为重采样之后的参数
mResampler.reset(AudioResampler::create(
mMixerInFormat,
resamplerChannelCount,
devSampleRate, quality));
}
return true;
}
}
return false;
}
主要根据重采样之后的声道数、采样率以及质量来确定选择什么样的Resampler,最后设置到Track的mResampler成员
在具体分析往track设置音量时,先了解以下track内部关于音量volume的成员变量有哪些:
static constexpr uint32_t MAX_NUM_VOLUMES = FCC_2; // stereo volume only 值为2
union {
//volume代表上层设置的音量,float转换为16bit时的音量值,这里数组是2表示左右两个通道
int16_t volume[MAX_NUM_VOLUMES]; // U4.12 fixed point (top bit should be zero)
int32_t volumeRL;
};
/**
* MAX_NUM_VOLUMES一般是2,即是左右声道的音量值
* **/
int32_t prevVolume[MAX_NUM_VOLUMES]; //上一次的音量值,是32bit,渐变音量ramp_vloume时会用到
int32_t volumeInc[MAX_NUM_VOLUMES]; //渐变音量ramp_vloume时会用到,每次音量的增量
//以下三个和上面的三个变量意义是一样的,只是他们保存的是来自应用层设置的原始音量值float类型,
float mVolume[MAX_NUM_VOLUMES];
float mPrevVolume[MAX_NUM_VOLUMES];
float mVolumeInc[MAX_NUM_VOLUMES];
上面的音量参数带m开头表示应用层AudioTrack设置的原始音量值float类型,非m开头是转换为int类型后的音量值,最后在混音计算时都是用非m开头的变量;上面提到了ramp_volume渐变音量,它是一个什么东西呢?简单来说就是有变化的音量,声音从高到低或从低到高,其实现原理就是每次设置音量时都用上一次的音量(preVolume)加上音量增量(volumeInc),就实现了音量的变化
有了上面的认识后,再来看AudioMixer关于音量设置setParameter(trackId, target=VOLUME/RAMP_VOLUME, param = AudioMixer::VOLUME0, value=&vlf)就简单多,这个音量实质就是给上面的volume、preVolume等变量计算赋值:
//确保param是在0~MAX_NUM_VOLUMES也就是左右声道数范围内
if ((unsigned)param >= VOLUME0 && (unsigned)param < VOLUME0 + MAX_NUM_VOLUMES) {
if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
//是否渐变音量
target == RAMP_VOLUME ? mFrameCount : 0,
//指针读取track音量成员地址,param - VOLUME0就是获取数组的index位置
&track->volume[param - VOLUME0],
&track->prevVolume[param - VOLUME0],
&track->volumeInc[param - VOLUME0],
&track->mVolume[param - VOLUME0],
&track->mPrevVolume[param - VOLUME0],
&track->mVolumeInc[param - VOLUME0])) {
ALOGV("setParameter(%s, VOLUME%d: %04x)",
target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0,
track->volume[param - VOLUME0]);
invalidate();
}
}
setVolumeRampVariables看看关键的setVolumeRampVariables函数:
newVolume 应用层设置的音量
ramp渐变音量的话就是音频帧数 否则就是0
pIntSetVolum = volume
pIntPreVolume = preVolume
pIntVolumeInc = volumeInc
pSetVolume = mVolume
pPreVolume = mPreVolume
pVolumeInc = mVolumeInc
static inline bool setVolumeRampVariables(float newVolume, int32_t ramp,
int16_t *pIntSetVolume, int32_t *pIntPrevVolume, int32_t *pIntVolumeInc,
float *pSetVolume, float *pPrevVolume, float *pVolumeInc) {
//如果要新设置newValue等于之前设置的音量,就说明音量没变化,无需走下面的逻辑
if (newVolume == *pSetVolume) {
return false;
}
if (newVolume < 0) {
newVolume = 0; // 音量不允许有负值
} else {
//判断newVolume属于无效值、无穷大、0或者正常数
switch (fpclassify(newVolume)) {
//亚正常和无效值
case FP_SUBNORMAL:
case FP_NAN:
newVolume = 0;
break;
//0
case FP_ZERO:
break; // zero volume is fine
//无穷大
case FP_INFINITE:
//无穷大去最大值UNITY_GAIN_FLOAT即可,也就是1.0f最大值
newVolume = AudioMixer::UNITY_GAIN_FLOAT;
break;
//正常值
case FP_NORMAL:
default:
if (newVolume > AudioMixer::UNITY_GAIN_FLOAT) {
newVolume = AudioMixer::UNITY_GAIN_FLOAT;
}
break;
}
}
// ramp渐变音量不为0 则为播放的音频帧数count
if (ramp != 0) {
//计算渐变音量每次音量增量,除以ramp帧数,也就是每帧的音量变化值
const float inc = (newVolume - *pPrevVolume) / ramp;
// could be inf, cannot be nan, subnormal
const float maxv = std::max(newVolume, *pPrevVolume);
//inc是0不正常 非0正常
if (isnormal(inc) // inc must be a normal number (no subnormals, infinite, nan)
&& maxv + inc != maxv) { // inc must make forward progress
*pVolumeInc = inc; //将每次变化的音量赋值给pVolumInc,这里是float类型
} else {
ramp = 0; // 不渐变音量
}
}
//设置的音量newVolume是float类型且在0~1之间,把它乘UNITY_GAIN_INT(16bit最大值)转换为16bit整型
const float scaledVolume = newVolume * AudioMixer::UNITY_GAIN_INT;
const int32_t intVolume = (scaledVolume >= (float)AudioMixer::UNITY_GAIN_INT) ?
AudioMixer::UNITY_GAIN_INT : (int32_t)scaledVolume; //强转为32bit
if (ramp != 0) {
//intVolume是以16bit来强转32bit的,要扩展到真正的32位就移动至高位即可,
//在减去上一次pIntPrevVolume音量值,除音频帧数ramp就得到音量增量
const int32_t inc = ((intVolume << 16) - *pIntPrevVolume) / ramp;
if (inc != 0) {
*pIntVolumeInc = inc; //将音频增量赋值给pVolumInc,这里是int类型
} else {
ramp = 0; // ramp not allowed
}
}
// 如果不使用渐变音量ramp,则track相关渐变音量的变量都为0
if (ramp == 0) {
*pVolumeInc = 0; //渐变音量的步长音量设置为0,也就是无渐变
*pPrevVolume = newVolume; //上一次的初始音量等于设置音量 float类型
*pIntVolumeInc = 0; //同pVolumeInc,只是是整型
//上一次音量整型值,因为intVolume是用newVolume按照16bit转换的,现在要转成32bit,把低16移动到高位即可
*pIntPrevVolume = intVolume << 16;
}
*pSetVolume = newVolume; //设置的音量float
*pIntSetVolume = intVolume; //设置音量整型int
return true;
}
总结一下:
此函数就是为Track结构体内相关音量的成员变量赋值,mVolume成员就为设置的新音量值,volume为设置音量float转换的Int类型;如果是ramp_volume渐变音量,则volumeInc=(volume - preVolume)/frameCount,就是每次的音频音量增量,mVolume赋值同理。
关于Track内的其他成员赋值,在AudioMixer的setParameter函数内都会涉及到,这里就不具体展开讲解,只把最终Track内部的各个成员变量的意思介绍即可!
//bufferProvider实质是PlaybackThread创建的Track,AudioBufferProvider是它的基类
AudioBufferProvider* bufferProvider;
//buffer会从bufferProvider中读取到真实的音频数据
mutable AudioBufferProvider::Buffer buffer; // 8 bytes
//hook函数相当重要,它可以作为函数指针,通常保存混音函数,
//AudioMixer也有一个叫mHook的成员,要注意和它的区别
hook_t hook;
//无论是否重采样,buffer的音频裸数据raw会写入到mIn,
const void *mIn; // current location in buffer
//重采样器
std::unique_ptr<AudioResampler> mResampler;
//此Tack混音前的采样率
uint32_t sampleRate;
//混音后音频数据输出地址,存放的是真实音频数据
//这个地址一般是外部Track的MainBuffer,而MainBuffer又是PlaybackThread的mSinkBuffer指定
int32_t* mainBuffer;
/** 混音后辅助音频输出地址,这个辅助音频值为真实音频数据的极小部分,
* 几分之一那种, 最后还要乘以mAuxLevel;
* 这个地址一般由外部Track的auxBuffer指定,而auxBuffer又是
**/
int32_t* auxBuffer;
//最终混音之后的输出的格式,分析代码为AUDIO_FORMAT_PCM_16_BIT
audio_format_t mMixerFormat; // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
//track源格式
audio_format_t mFormat; // input track format
//混音内部格式 分析代码统一为AUDIO_FORMAT_PCM_FLOAT
audio_format_t mMixerInFormat; // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
到这里prepareTracks函数差不多就完成了,当然这个函数还有其他部分如处理underrun数据不够的部分,不在此篇混音的重点,暂忽略!
总结一下:
prepareTracks函数就是混音之前做好准备工作,为AudioMixer混音前创建一一对应的内部Track,并为Track赋值音量、buffer、format、重采样管理器等参数,为混音前做好准备工作
回看上面的《PlaybackThread回播线程》章节,prepareTracks完成后就会执行到threadLoop_mix混音函数,不难猜想到混音函数肯定是对上个步骤各个Track进行混音,进去看看:
void AudioFlinger::MixerThread::threadLoop_mix()
{
// mix buffers...
mAudioMixer->process();
mCurrentWriteLength = mSinkBufferSize;
if ((mSleepTimeUs == 0) && (sleepTimeShift > 0)) {
sleepTimeShift--;
}
mSleepTimeUs = 0;
mStandbyTimeNs = systemTime() + mStandbyDelayNs;
//TODO: delay standby when effects have a tail
}
很简单,就是执行混音前AudioMixer的process函数:
void process() {
for (const auto &pair : mTracks) {
// 清除缓冲区的buffer
const std::shared_ptr<Track> &t = pair.second;
if (t->mKeepContractedChannels) {
t->clearContractedBuffer();
}
}
//执行混音函数mHook
(this->*mHook)();
processHapticData();
}
重点就是执行mHook函数指针保存的函数,mHook是AudioMixer的成员变量,它指向了谁呢?
还记得setParameter时,经常会调用invalidate()函数,这个函数就是给mHook赋值的,如下:
void invalidate() {
mHook = &AudioMixer::process__validate;
}
直接去看process__validate函数把!
//AudioMixer中name为mainBuffer, value为数组集合存储的name
std::unordered_map<void * /* mainBuffer */, std::vector<int /* name */>> mGroups;
// AudioMixer中保存所有的已经enable的track的name
std::vector<int /* name */> mEnabled;
// AudioMixer中保存所有的Track
std::map<int /* name */, std::shared_ptr<Track>> mTracks;
void AudioMixer::process__validate()
{
bool all16BitsStereoNoResample = true;
bool resampling = false;
bool volumeRamp = false;
mEnabled.clear();
//存储此次混音的Tracks
mGroups.clear();
//遍历AudioMixer内部所有的Track
for (const auto &pair : mTracks) {
const int name = pair.first;
const std::shared_ptr<Track> &t = pair.second;
//每个track在prepareTracks时会enable
if (!t->enabled) continue;
//emplace_back是往map里面添加name,但是效率push_back高,push_back会依次调用构造函数和复制函数
//emplace_back根据参数调用构造函数,
mEnabled.emplace_back(name); // we add to mEnabled in order of name.
//注意mGroup key-mainBuffer混音后的输出地址 value是Track的唯一表示name的集合vector;
//因为可能有很多Track都使用同一个输出地址
mGroups[t->mainBuffer].emplace_back(name); // mGroups also in order of name.
//n是一个状态字段,每个bit代表不同意思
uint32_t n = 0;
// FIXME can overflow (mask is only 3 bits)
n |= NEEDS_CHANNEL_1 + t->channelCount - 1;
//是否需要重采样,查看t的重采样成员是否为空
if (t->doesResample()) {
n |= NEEDS_RESAMPLE;
}
//AUX是啥? aux in辅助音频接入接口,外部音源可以通过此接口接入到内部,不知道这里是不是这个意思
if (t->auxLevel != 0 && t->auxBuffer != NULL) {
n |= NEEDS_AUX;
}
//volumeInc保存渐变音量每次的增量,如果有值说明需要混音的时候要有渐变音量
if (t->volumeInc[0]|t->volumeInc[1]) {
volumeRamp = true;
} else if (!t->doesResample() && t->volumeRL == 0) {
//没有重采样管理器 音量也为0 就静默此track
n |= NEEDS_MUTE;
}
t->needs = n; //将状态赋给track的needs
if (n & NEEDS_MUTE) {
//沉默静音就不做任何处理,也不读取音频数据
t->hook = &Track::track__nop; //track__nop函数为空
} else {
if (n & NEEDS_AUX) {
//双声道不采样为false,就是不重采样
all16BitsStereoNoResample = false;
}
//需要重采样
if (n & NEEDS_RESAMPLE) {
all16BitsStereoNoResample = false;
resampling = true;
//决定track的混音函数hook是什么? mMixerChannelCount为源track的通道数
//mMixerInFormat为混音内部格式 mMixerFormat为混音之后的格式
t->hook = Track::getTrackHook(TRACKTYPE_RESAMPLE, t->mMixerChannelCount,
t->mMixerInFormat, t->mMixerFormat);
//不需要重采样
} else {
//单通道
if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){
t->hook = Track::getTrackHook(
//目的是多通道 源是单通道 type就是TRACKTYPE_NORESAMPLEMONO; 否则就是TRACKTYPE_NORESAMPLE
(t->mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO // TODO: MONO_HACK
&& t->channelMask == AUDIO_CHANNEL_OUT_MONO)
? TRACKTYPE_NORESAMPLEMONO : TRACKTYPE_NORESAMPLE,
t->mMixerChannelCount,
t->mMixerInFormat, t->mMixerFormat);
all16BitsStereoNoResample = false;
}
//多通道
if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2){
//这里只是指定Track的hook混音函数,到底是谁在哪个地方用的呢?在本页内,重采样操作之后就会调用Track的hook函数进行混音
t->hook = Track::getTrackHook(TRACKTYPE_NORESAMPLE, t->mMixerChannelCount,
t->mMixerInFormat, t->mMixerFormat);
}
}
}
}
//mHook重新制定函数指针process__nop,也就是去读取音频数据的函数
mHook = &AudioMixer::process__nop;
//mEnabled保存了需要混音track的名字集合
if (mEnabled.size() > 0) {
//需要重采样
if (resampling) {
//创建混音、重采样的缓冲区,缓冲区大小等于声道数*帧数
if (mOutputTemp.get() == nullptr) {
mOutputTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]);
}
if (mResampleTemp.get() == nullptr) {
mResampleTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]);
}
//mHook是AudioMixerc重采样函数
mHook = &AudioMixer::process__genericResampling;
//不需要重采样
} else {
// 指定mHook为不走重采样函数
mHook = &AudioMixer::process__genericNoResampling;
if (all16BitsStereoNoResample && !volumeRamp) {
//如果使用的track数量只有一个
if (mEnabled.size() == 1) {
const std::shared_ptr<Track> &t = mTracks[mEnabled[0]];
if ((t->needs & NEEDS_MUTE) == 0) { //并且还是mute静音
mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat);
}
}
}
}
}
//因为上面步骤已经重新制定mHook和Track内的hook函数指针,这里process会执行mHook,也就是混音函数
process();
.......
}
上面函数关键点有以下几个:
如下,因为是制定hook函数指针,肯定有一堆if-else或者switch-case来选定,还真的是:
AudioMixer::hook_t AudioMixer::Track::getTrackHook(int trackType, uint32_t channelCount,
audio_format_t mixerInFormat, audio_format_t mixerOutFormat __unused)
{
.....
switch (trackType) {
case TRACKTYPE_NOP:
return &Track::track__nop;
//需要重采样
case TRACKTYPE_RESAMPLE:
switch (mixerInFormat) {
//默认的内部格式mixerInFormat都是FLOAT
case AUDIO_FORMAT_PCM_FLOAT:
return (AudioMixer::hook_t) &Track::track__Resample<
MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
return (AudioMixer::hook_t) &Track::track__Resample<
MIXTYPE_MULTI, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
break;
}
break;
//单声道且不需要重采样
case TRACKTYPE_NORESAMPLEMONO:
switch (mixerInFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
//MIXTYPE_MONOEXPAND单通道要扩展为多通道吗?
return (AudioMixer::hook_t) &Track::track__NoResample<
MIXTYPE_MONOEXPAND, float /*TO*/, float /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
return (AudioMixer::hook_t) &Track::track__NoResample<
MIXTYPE_MONOEXPAND, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
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::track__NoResample<
MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
return (AudioMixer::hook_t) &Track::track__NoResample<
MIXTYPE_MULTI, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
break;
}
break;
default:
LOG_ALWAYS_FATAL("bad trackType: %d", trackType);
break;
}
return NULL;
}
以上就是制定hook函数指针的地方,最终真的混音逻辑,外层3个case抽一个进去了解即可,它们的实现都是大同小异的!
选取这个hook函数指针:
case AUDIO_FORMAT_PCM_FLOAT:
return (AudioMixer::hook_t) &Track::track__Resample<
MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>;
track__Resample的泛型参数记住,很关键,决定最终选取哪个函数:
template <int MIXTYPE, typename TO, typename TI, typename TA>
//TO* out混音后输出地址 outFrameCount音频输出帧数
//temp为缓存地址,保存当前track的数据,但是最终都会累加到out地址上
void AudioMixer::Track::track__Resample(TO* out, size_t outFrameCount, TO* temp, TA* aux)
{
ALOGVV("track__Resample\n");
mResampler->setSampleRate(sampleRate);
const bool ramp = needsRamp();
if (ramp || aux != NULL) {
//重采样音量设置为最大
mResampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
memset(temp, 0, outFrameCount * mMixerChannelCount * sizeof(TO));
//开始重采样,重采样后的数据输出到temp缓冲区
mResampler->resample((int32_t*)temp, outFrameCount, bufferProvider);
//开始混音
volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, true /* ADJUSTVOL */>(
out, outFrameCount, temp, aux, ramp);
} else { // constant volume gain
mResampler->setVolume(mVolume[0], mVolume[1]);
mResampler->resample((int32_t*)out, outFrameCount, bufferProvider);
}
}
这里重采样就不展开了,重采样会对track的采样率进行转换,转换为统一采样率,这样才可以混音;mResampler内部也比较复杂,深入进去又会牵扯很多,这里就不继续展开!重点看看volumeMix如何混音! 注意volumeMix的泛型,其中iS_same是对TI和float类型判断,相同为true,不同为false,继续进入volumeMix看看:
template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL,
typename TO, typename TI, typename TA>
void AudioMixer::Track::volumeMix(TO *out, size_t outFrames,
const TI *in, TA *aux, bool ramp)
{
//USEFLOATVOL根据传值为true
if (USEFLOATVOL) {
if (ramp) {
volumeRampMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
mPrevVolume, mVolumeInc,
#ifdef FLOAT_AUX
&mPrevAuxLevel, mAuxInc
#else
&prevAuxLevel, auxInc
#endif
);
......
} else {
//先看看简单的
volumeMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
mVolume,
#ifdef FLOAT_AUX
mAuxLevel
#else
auxLevel
#endif
);
}
} else {
if (ramp) {
volumeRampMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
prevVolume, volumeInc, &prevAuxLevel, auxInc);
if (ADJUSTVOL) {
adjustVolumeRamp(aux != NULL);
}
} else {
volumeMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
volume, auxLevel);
}
}
}
不难看出,最终都会走到volumeMulti和volumeRampMulti两个函数,注意传递的参数:
mMixerChannelCount:当前track的声道数
out:混音后数据输出地址
outFrame:混音的音频帧数
in: 混音前的数据地址
aux: 不清楚是啥意思
volume: 混音音量(非ramp渐变音量)
preVolume: 上一次的音量(ramp)
volumeInc: 此次音量的增量(ramp)
然后看混音函数了,先看看普通音量的混音:
template <int MIXTYPE,
typename TO, typename TI, typename TV, typename TA, typename TAV>
static void volumeMulti(uint32_t channels, TO* out, size_t frameCount,
const TI* in, TA* aux, const TV *vol, TAV vola)
{
//通道数 进入2
switch (channels) {
case 1:
volumeMulti<MIXTYPE, 1>(out, frameCount, in, aux, vol, vola);
break;
case 2:
volumeMulti<MIXTYPE, 2>(out, frameCount, in, aux, vol, vola);
break;
case 3:
volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 3>(out, frameCount, in, aux, vol, vola);
break;
case 4:
volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 4>(out, frameCount, in, aux, vol, vola);
break;
case 5:
volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 5>(out, frameCount, in, aux, vol, vola);
break;
case 6:
volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 6>(out, frameCount, in, aux, vol, vola);
break;
case 7:
volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 7>(out, frameCount, in, aux, vol, vola);
break;
case 8:
volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 8>(out, frameCount, in, aux, vol, vola);
break;
}
}
以2声道的为例进入看看,还是挺复杂的,又是一大堆switch-case,不过离我们最终混音已经很接近了:
template <int MIXTYPE, int NCHAN,
typename TO, typename TI, typename TV, typename TA, typename TAV>
//in地址保存了混音之前的数据 混音之后数据保存在out
//vol是在AudioMixer的Track中的mVolume,它是一个数组
//vola为AudioMixer的Track的mAuxLevel
//aux为AudioMixer的Track的auxBuffer
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:
//NCHAN代表有几个声道
for (int i = 0; i < NCHAN; ++i) {
//out就是已经累加的混音数据,in代表的是当前track的音频数据, vol[i]是当前的音量数据
//MixMulAux内部就是音频数据与音量相乘,不信就进去看看,还有一个auxaccum是传入地址进去
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
}
break;
case MIXTYPE_MONOEXPAND:
//声道扩张的情况,out多声道存储的都是单声道的值,此处in并为加加
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
}
in++;
break;
case MIXTYPE_MULTI_SAVEONLY:
for (int i = 0; i < NCHAN; ++i) {
//auxaccum传入MixMulAux函数中,会累加自身和in的一部分;auxaccum += (1.0/*in类型<<字节长度-1)
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
}
break;
case MIXTYPE_MULTI_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
}
break;
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
}
break;
default:
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
break;
}
//多个通道求平均
auxaccum /= NCHAN;
//aux作为辅助通道保存输出
*aux++ += MixMul<TA, TA, TAV>(auxaccum, vola);
} while (--frameCount);
} else {
do {
switch (MIXTYPE) {
//正常有多个混音也可能走这里
case MIXTYPE_MULTI:
for (int i = 0; i < NCHAN; ++i) {
//+= 有一个累加的过程,这里用到vol,它是Track的mVloume成员,2个长度的数组
*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;
case MIXTYPE_MULTI_SAVEONLY:
for (int i = 0; i < NCHAN; ++i) {
/** 做乘法,in*vol,并让结果不超过out类型的最大值
* 还要注意一点,如果NCHAN是2个通道,那out存储会依次
* 存储左右左右通道的数据
* **/
*out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
}
break;
case MIXTYPE_MULTI_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
}
break;
case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
}
break;
default:
LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
break;
}
} while (--frameCount);
}
}
inline TO MixMulAux(TI value, TV volume, TA *auxaccum) {
MixAccum<TA, TI>(auxaccum, value);
return MixMul<TO, TI, TV>(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);
}
......
看到这里可能有点晕了,用一幅图总结下这个混音流程:

与普通音量混音不同点在AudioMixerOps::volumeRampMulti函数上,如下提取的关键部分:
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
//每次音量都会增加一个音量增量volinc,这个就是在setParamter时计算的volumeInc音量增量
vol[i] += volinc[i];
}
所以在播放音频时,ramp方式的混音音量会有渐变效果!
到这里当前Track的混音流程就完成了!但是我们还不知道如何吊起混音hook函数的,这里就要到AudioMixer的mHook函数指针去找找了;
因为在process__validate函数中,mHook可能是:
//需要重采样
mHook = &AudioMixer::process__genericResampling;
//不需要重采样
mHook = &AudioMixer::process__genericNoResampling;
这两种可能,我们逐一分析
函数如下:
void AudioMixer::process__genericNoResampling()
{
ALOGVV("process__genericNoResampling\n");
int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32)));
//按mainBuffer遍历mGroup
for (const auto &pair : mGroups) {
const auto &group = pair.second;
//group是一个vector集合,包含了许多Track,他们的输出mainBuffer地址是一致的
for (const int name : group) {
const std::shared_ptr<Track> &t = mTracks[name];
t->buffer.frameCount = mFrameCount;
//提取AudioTrack客户端共享内存的音频数据
t->bufferProvider->getNextBuffer(&t->buffer);
t->frameCount = t->buffer.frameCount;
t->mIn = t->buffer.raw;
}
//out应该是Track的MainBuffer,也就是混音之后的输出地址
int32_t *out = (int *)pair.first;
size_t numFrames = 0;
do {
const size_t frameCount = std::min((size_t)BLOCKSIZE, mFrameCount - numFrames);
//outTemp作为临时的混音输出地址,最后会将数据转到out,也就是mainBuffer
memset(outTemp, 0, sizeof(outTemp));
for (const int name : group) {
const std::shared_ptr<Track> &t = mTracks[name];
int32_t *aux = NULL;
//auxBuffer地址 在混音时会读取原始音频数据的一部分值
if (CC_UNLIKELY(t->needs & NEEDS_AUX)) {
aux = t->auxBuffer + numFrames;
}
for (int outFrames = frameCount; outFrames > 0; ) {
if (t->mIn == nullptr) {
break;
}
//inframe作为下面hook此次要混音的音频数据个数;
size_t inFrames = (t->frameCount > outFrames)?outFrames:t->frameCount;
if (inFrames > 0) {
//这里没有把mIn音频数据传入到hook函数中,是因为hook指针指向的函数是Track类自身的函数
//,这个函数可以调用Track内部的成员,也就是mIn,拿到mIn的原始音频数据后混音输出到outTemp
//完成混音到数据转移;这个hook函数可以理解为混音函数,涉及多音轨音频数据叠加的
(t.get()->*t->hook)(
outTemp + (frameCount - outFrames) * t->mMixerChannelCount,
inFrames, mResampleTemp.get() /* naked ptr */, aux);
t->frameCount -= inFrames;
outFrames -= inFrames;
if (CC_UNLIKELY(aux != NULL)) {
aux += inFrames;
}
}
//混音完成后,释放buffer
if (t->frameCount == 0 && outFrames) {
t->bufferProvider->releaseBuffer(&t->buffer);
t->buffer.frameCount = (mFrameCount - numFrames) -
(frameCount - outFrames);
t->bufferProvider->getNextBuffer(&t->buffer);
t->mIn = t->buffer.raw;
if (t->mIn == nullptr) {
break;
}
t->frameCount = t->buffer.frameCount;
}
}
}
const std::shared_ptr<Track> &t1 = mTracks[group[0]];
//拷贝混音后的数据out,out也就是音频最终的输出地址
convertMixerFormat(out, t1->mMixerFormat, outTemp, t1->mMixerInFormat,
frameCount * t1->mMixerChannelCount);
//输出地址改变偏移,因为已经存储了部分数据了
out = reinterpret_cast<int32_t*>((uint8_t*)out
+ frameCount * t1->mMixerChannelCount
* audio_bytes_per_sample(t1->mMixerFormat));
numFrames += frameCount;
} while (numFrames < mFrameCount);
// 释放每个track的buffer
for (const int name : group) {
const std::shared_ptr<Track> &t = mTracks[name];
t->bufferProvider->releaseBuffer(&t->buffer);
}
}
}
代码功能很简单。
首先,从应用端取出音频数据getNextBuffer
其次,调用track的hook混音函数,进行混音并把混音的结果保存在缓存outTemp中
最后,在把混音后数据从outTemp转移到out缓存buffer,也就是混音后输出的mainbuffer地址
void AudioMixer::process__genericResampling()
{
//mOutTemp是process__validate函数中创建的缓冲区
int32_t * const outTemp = mOutputTemp.get(); // naked ptr
size_t numFrames = mFrameCount;
for (const auto &pair : mGroups) {
const auto &group = pair.second;
//t1表示group中的第一个Track
const std::shared_ptr<Track> &t1 = mTracks[group[0]];
// 初始化outtemp缓冲区
memset(outTemp, 0, sizeof(*outTemp) * t1->mMixerChannelCount * mFrameCount);
for (const int name : group) {
const std::shared_ptr<Track> &t = mTracks[name];
int32_t *aux = NULL;
if (CC_UNLIKELY(t->needs & NEEDS_AUX)) {
aux = t->auxBuffer;
}
//如果确实要重采样就直接调用hook,因为hook里面自己包含了取数据相关操作
if (t->needs & NEEDS_RESAMPLE) {
(t.get()->*t->hook)(outTemp, numFrames, mResampleTemp.get() /* naked ptr */, aux);
} else { //不重采样情况
size_t outFrames = 0;
//自己取数据
while (outFrames < numFrames) {
t->buffer.frameCount = numFrames - outFrames;
t->bufferProvider->getNextBuffer(&t->buffer);
t->mIn = t->buffer.raw;
// t->mIn == nullptr can happen if the track was flushed just after having
// been enabled for mixing.
if (t->mIn == nullptr) break;
//混音
(t.get()->*t->hook)(
outTemp + outFrames * t->mMixerChannelCount, t->buffer.frameCount,
mResampleTemp.get() /* naked ptr */,
aux != nullptr ? aux + outFrames : nullptr);
outFrames += t->buffer.frameCount;
//释放缓冲区
t->bufferProvider->releaseBuffer(&t->buffer);
}
}
}
//将混音数据保存在t1的mainBuffer中,因为group中所有的track的mainBuffer是一样
convertMixerFormat(t1->mainBuffer, t1->mMixerFormat,
outTemp, t1->mMixerInFormat, numFrames * t1->mMixerChannelCount);
}
}
功能很简单,看上面代码注释即可;
混音业务调用如下:

mHook、hook不仅仅只会指定图中的几个函数,这里只是针对混音业务时,可能的取值
混音的两个重要点,先确定是否需要重采样Resample,然后确定是否普通音量混音还是渐变音量混音;而且在混音时,如果track音轨数少于目标音轨,还需要进行音轨扩张;
第一次看到这个Hook设计模式感觉还不错,不停的修改Hook函数指针,达到转换业务的逻辑,降低了模块不同业务之间的耦合,这种结构组织松散了代码结构,但是相关业务却紧密连接在一起!如下图:

如上图,如果不用hook模式,这种业务组合有4种调用方式,可能需要加if-else来判断业务怎么走,但是hook方式的话,就不用了,如下图:

提前将业务指定到hook,一条业务调用即可!
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来
我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion
参见下面的示例,我想最好使用第二种方法,但第一种也可以。哪种方法最好,使用另一种的后果是什么?classTestdefstartp"started"endtest=Test.newtest.startendclassTest2defstartp"started"endendtest2=Test2.newtest2.start 最佳答案 我肯定会说第二种变体更有意义。第一个不会导致错误,但对象实例化完全过时且毫无意义。外部变量在类的范围内不可见:var="string"classAvar=A.newendputsvar#=>strin
如果我构建了一个应用程序来访问来自Gmail、Twitter和Facebook的一些数据,并且我希望用户只需输入一次他们的身份验证信息,并且在几天或几周后重置,那会怎样是在Ruby中动态执行此操作的最佳方法吗?我看到很多人只是拥有他们客户/用户凭证的配置文件,如下所示:gmail_account:username:myClientpassword:myClientsPassword这看起来a)非常不安全,b)如果我想为成千上万的用户存储此类信息,它就无法工作。推荐的方法是什么?我希望能够在这些服务之上构建一个界面,因此每次用户进行交易时都必须输入凭据是不可行的。
我正在使用Devise在Rails应用程序中,并希望通过API公开一些模型数据,但应该像应用程序一样限制对API的访问。$curlhttp://myapp.com/api/v1/sales/7.json{"error":"Youneedtosigninorsignupbeforecontinuing."}很明显。在这种情况下是否有访问API的最佳实践?我更喜欢一步验证+获取数据,但这只是为了让客户的工作更轻松。他们将使用JQuery在客户端提取数据。感谢您提供任何信息!凡妮莎 最佳答案 我建议您按照以下帖子中的选项2:使用APIke
我正在开发一个Rails2.3.1网站。在整个网站中,我需要一个用于在各种页面(主页、创建帖子页面、帖子列表页面、评论列表页面等)上创建帖子的表单——只要说这个表单需要在由各种Controller)。这些页面中的每一个都显示在相应的Controller/操作中检索到的各种其他信息。例如,主页列出了最新的10篇文章、从数据库中提取的内容等。因此,我已将帖子创建表单移动到它自己的部分中,并将该部分包含在所有必要的页面中。请注意,部分POST中的表单到/questions(路由到PostsController::create——这是默认的Rails行为)。我遇到的问题是当Posts表单没有正
我正在按照我一直在研究的研讨会实现“服务对象”,我正在构建一个redditAPI应用程序。我需要对象返回一些东西,所以我不能只执行初始化程序中的所有内容。我有这两个选择:选项1:类需要实例化classSubListFromUserdefuser_subscribed_subs(client)@client=client@subreddits=sort_subs_by_name(user_subs_from_reddit)endprivatedefsort_subs_by_name(subreddits)subreddits.sort_by{|sr|sr[:name].downcase}
美团外卖搜索工程团队在Elasticsearch的优化实践中,基于Location-BasedService(LBS)业务场景对Elasticsearch的查询性能进行优化。该优化基于Run-LengthEncoding(RLE)设计了一款高效的倒排索引结构,使检索耗时(TP99)降低了84%。本文从问题分析、技术选型、优化方案等方面进行阐述,并给出最终灰度验证的结论。1.前言最近十年,Elasticsearch已经成为了最受欢迎的开源检索引擎,其作为离线数仓、近线检索、B端检索的经典基建,已沉淀了大量的实践案例及优化总结。然而在高并发、高可用、大数据量的C端场景,目前可参考的资料并不多。因此