Android音频系统是一个比较复杂的系统,从应用层到框架层、框架层通过JNI到Native层的运行时库。运行时库通过Binder和系统服务通信(与音频相关的系统服务是AudioFlinger和AudioPolicyService),系统服务通过HAL层调用tinyalsa库,最终让驱动播放出声音。可以说音频系统涵盖了Android系统代码的全部层次,本文我们来从应用层开始分析音频系统中音频数据的流向。
因为Android系统的代码非常之庞大复杂,本人在研究Android的代码时一般都用编程里说的抽象,就是从一开始就确定自己要分析的重点,看代码的时候只关注自己要研究的点,其他的只要不影响思路的代码一律忽略。
我们从Android播放PCM码流的应用层例子来分析Android音频系统中音频数据的流向。
在应用层想要播放一个PCM文件,代码比较简单,主要的就三步
mAudioTrack = new AudioTrack()
mAudioTrack.play() //开始播放
mAudioTrack.write() //在播放线程里不停的write需要播放的音频数据
下面我们就来分析应用层的这三个简单的函数,Android系统都做了什么,才让声音播放出来的?
首先得看AudioTrack的构造函数
// frameworks\base\media\java\android\media\AudioTrack.java
整个构造函数最主要的工作就是native_setup()
native_setup是JNI接口对应的函数是
// frameworks\base\core\jni\android_media_AudioTrack.cpp
static int android_media_AudioTrack_native_setup()
{
//创建Native层的AudioTrack对象
sp
/*
* AudioTrack中有MODE_STATIC和MODE_STREAM两种分类
* STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中
* STATIC的意思是一开始创建的时候就把音频数据一次性的放到一个固定的buffer,然后直接传给audiotrack
* 本文只讲解MODE_STREAM这种模式的情况
*/
//set就是整个函数最主要做的事情
lpTrack->set(
atStreamType,// stream type
sampleRateInHertz,
format,// word length, PCM
nativeChannelMask,
frameCount,
AUDIO_OUTPUT_FLAG_NONE,
audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)
0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
0,// shared mem
true,// thread can call Java
sessionId);// audio session ID
}
// frameworks\av\media\libmedia\AudioTrack.cpp
status_t AudioTrack::set()
{
//该函数获取声音的输出设备,就是我们解码后的数据需要往输出设备上写,这样才能播放得出来声音
//该函数会通过Binder调用到系统服务AudioFlinger和AudioPolicyService
//因为本文的重点不在这里所以不详细讲解这个函数了
AudioSystem::getOutput()
//mCbf指向audioCallback函数
mCbf = cbf;
//因为cbf不为NULL,显然这里另起线程AudioTrackThread
//下面我们来详细分析AudioTrackThread线程
if (cbf != NULL) {
mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
mAudioTrackThread->run("AudioTrack", ANDROID_PRIORITY_AUDIO, 0 /*stack*/);
}
//因为是MODE_STREAM模式,所以sharedBuffer为NULL
status_t status = createTrack_l(streamType,
sampleRate,
format,
frameCount,
flags,
sharedBuffer,
output,
0 /*epoch*/);
}
//下面来分析AudioTrackThread的主线程函数
bool AudioTrack::AudioTrackThread::threadLoop()
{
//单从函数的命名来看,AudioTrackThread线程就是要不停的处理AudioBuffer的
//因为代码跑到这里的时候我们还没有开始播放声音,所以显然这个线程只是跑起来了,但是还需要等应用开始调用start后才开始真正干活
//我们先不急着分析它,先往下看,后面在回来分析这个线程都做了什么。
nsecs_t ns = mReceiver.processAudioBuffer(this);
}
status_t AudioTrack::createTrack_l()
{
//通过Binder调用了系统服务AudioFlinger的createTrack方法
sp
sampleRate,
// AudioFlinger only sees 16-bit PCM
format == AUDIO_FORMAT_PCM_8_BIT ?
AUDIO_FORMAT_PCM_16_BIT : format,
mChannelMask,
frameCount,
&trackFlags,
sharedBuffer,
output,
tid,
&mSessionId,
mName,
mClientUid,
&status);
}
//从这里开始代码已经跑到AudioFlinger进程了,和原来的进程不是同一个进程了
sp
{
//这个thread是AudioFlinger进程里的PlaybackThread线程,专门用来播放声音的线程
//本文重点不在研究PlaybackThread是怎么来的,只需要知道系统服务AudioFlinger进程里有好多线程在做不一样的事情
//除了PlaybackThread播放线程外,还有录音线程等
track = thread->createTrack_l(client, streamType, sampleRate, format,
channelMask, frameCount, sharedBuffer, lSessionId, flags, tid, clientUid, &lStatus);
}
sp
{
//根据是否限时构造track对象
if (!isTimed) {
track = new Track(this, client, streamType, sampleRate, format,
channelMask, frameCount, sharedBuffer, sessionId, uid, *flags);
}
}
//我们接着分析Track的构造函数
AudioFlinger::PlaybackThread::Track::Track(……)
: TrackBase(thread, client, sampleRate, format, channelMask, frameCount, sharedBuffer,
sessionId, uid, true /*isOut*/),
……
{
//我们先来分析TrackBase的构造函数
AudioFlinger::ThreadBase::TrackBase::TrackBase()
{
//如果播放模式是MODE_STATIC,sharedBuffer不为NULL;如果是MODE_STREAM,则sharedBuffer为NULL
mCblkMemory = client->heap()->allocate(size);
mCblk = static_cast
mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
//原来存放音频的共享内存是在这里分配的,audio_track_cblk_t就是存放音频数据的结构体
//在该构造函数中分配完内存后,Track中创建AudioTrackServerProxy对象时传递的参数MBuffer就不会为NULL了。
}
//因为是MODE_STREAM模式,sharedBuffer等于0 同时mCblk也不等于NULL
mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount, mFrameSize);
//到这里已经基本清楚了,以申请到的audio_track_cblk_t结构当参数new出来的AudioTrackServerProxy是专门管理内存的
//就是说AudioFlinger就是通过AudioTrackServerProxy对象来管理audio_track_cblk_t的
}
//到这里为止应用层的mAudioTrack = new AudioTrack()代码就已经基本跑完了
//下面我们继续分析mAudioTrack.play()
// frameworks\base\media\java\android\media\AudioTrack.java
public void play() throws IllegalStateException {
//调用JNI接口android_media_AudioTrack.cpp的android_media_AudioTrack_start
native_start();
}
static void android_media_AudioTrack_start(JNIEnv *env, jobject thiz)
{
sp
//调用AudioTrack的start方法
lpTrack->start();
}
status_t AudioTrack::start()
{
sp
//显然之前暂停的AudioTrackThread线程开始启动了,运行mReceiver.processAudioBuffer(this)代码了
t->resume();
//mAudioTrack是之前在AudioTrack::createTrack_l()得到的IAudioTrack对象
//显然这里通过Binder调用了AudioFlinger::PlaybackThread::Track的start方法
status = mAudioTrack->start();
}
status_t AudioFlinger::PlaybackThread::Track::start(AudioSystem::sync_event_t event,
int triggerSession)
{
PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
status = playbackThread->addTrack_l(this);
}
status_t AudioFlinger::PlaybackThread::addTrack_l(const sp
{
broadcast_l();
}
void AudioFlinger::PlaybackThread::broadcast_l()
{
mSignalPending = true;
mWaitWorkCV.broadcast();
}
//显然mAudioTrack->start就只是给AudioFlinger的PlaybackThread线程发送消息,让PlaybackThread线程开始播放
//我们分析PlaybackThread线程的主函数AudioFlinger::PlaybackThread::threadLoop就明白了
bool AudioFlinger::PlaybackThread::threadLoop()
{
//因为受到mWaitWorkCV.broadcast,加上mSignalPending标志已经被志为true,所以线程进入播放循环
threadLoop_mix();
ssize_t ret = threadLoop_write();
}
//先看threadLoop_mix,从名字上看应该是混音处理
void AudioFlinger::MixerThread::threadLoop_mix()
{
mAudioMixer->process(pts);
}
//mAudioMixer到底是什么鬼?
//我们到AudioFlinger的MixerThread构造函数里一看就明白了
AudioFlinger::MixerThread::MixerThread()
{
mAudioMixer = new AudioMixer(mNormalFrameCount, mSampleRate);
}
void AudioMixer::process()
{
//hook是一个指针,我们暂时可以不去理会这个指针指向的函数是什么
//先继续分析threadLoop_write
mState.hook(&mState, pts);
}
ssize_t AudioFlinger::PlaybackThread::threadLoop_write()
{
bytesWritten = mOutput->stream->write(mOutput->stream,
mMixBuffer + offset, mBytesRemaining);
//在明显不过了,该函数就是从mMixBuffer里将音频数据写给驱动,这样驱动就可以播放出声音来了
//那么mMixBuffer是从哪来的?
}
//在AudioFlinger::PlaybackThread的构造函数里发现有调用了readOutputParameters方法
void AudioFlinger::PlaybackThread::readOutputParameters()
{
delete[] mAllocMixBuffer;
size_t align = (mFrameSize < sizeof(int16_t)) ? sizeof(int16_t) : mFrameSize;
mAllocMixBuffer = new int8_t[mNormalFrameCount * mFrameSize + align - 1];
mMixBuffer = (int16_t *) ((((size_t)mAllocMixBuffer + align - 1) / align) * align);
//原来在PlaybackThread的构造函数里申请了混音使用的Buffer
//显然混音后的数据就放在这个Buffer里,那么混音器又是去哪里拿数据来混音的呢?
//我们在回去看AudioMixer::process里调用的mState.hook
}
//显然hook在正常状态下是指向process__OneTrack16BitsStereoNoResampling函数的
void AudioMixer::process__OneTrack16BitsStereoNoResampling()
{
//t.bufferProvider是AudioBufferProvider,而AudioBufferProvider是基类。
//t.bufferProvider实际的对象是Track,就是说这里真正调用的是AudioFlinger::PlaybackThread::Track::getNextBuffer()
t.bufferProvider->getNextBuffer()
}
AudioFlinger::PlaybackThread::Track::getNextBuffer()
{
//看见mServerProxy了吧?Track构造出来的时候对mServerProxy进行赋值了
//刚好验证了我们刚才的想法,PlaybackThread就是从audio_track_cblk_t里拿的数据来混音的
//所以该函数其实是调用了AudioTrackServerProxy的父类ServerProxy的obtainBuffer方法
mServerProxy->obtainBuffer()
}
// frameworks/av/media/libmedia/AudioTrackShared.cpp
ServerProxy::obtainBuffer()
{
//看见mCblk了吧,mCblk就是上面提到的内存
//也就是说PlaybackThread其实就是从AudioFlinger申请的共享内存了拿音频数据来进行混音处理的
audio_track_cblk_t* cblk = mCblk;
}
//代码分析到这里我们已经基本搞清楚了服务AudioFlinger的PlaybackThread线程就是从AudioFlinger申请的共享内存里拿的数据进行混音处理
//混音处理后的数据放在mAllocMixBuffer里,接着从mAllocMixBuffer里拿数据写到驱动去播放声音
//那么用户端是怎么把音频数据写到共享内存的呢?下面分析应用层的mAudioTrack.write()
//之前说了应用层的工程师想要播放PCM码流的话,需要自己new一个线程不停的执行mAudioTrack.write()
//在AudioTrack.java里看见write函数有两个,这个和音频数据需要字节对齐有关,我们可以不要理会,挑一个分析就可以了
//照之前的分析,代码自然会通过JNI接口调用到Native层,我们直接到Native层看看就好了
static jint android_media_AudioTrack_native_write_byte()
{
//lpTrack参数就是AudioTrack对象
jint written = writeToTrack(lpTrack, javaAudioFormat, cAudioData, offsetInBytes, sizeInBytes);
}
jint writeToTrack(const sp
jint offsetInBytes, jint sizeInBytes) {
//因为是MODE_STREAM模式,sharedBuffer等于0
written = track->write(data + offsetInBytes, sizeInBytes);
}
ssize_t AudioTrack::write(const void* buffer, size_t userSize)
{
status_t err = obtainBuffer(&audioBuffer, &ClientProxy::kForever);
if (mFormat == AUDIO_FORMAT_PCM_8_BIT && !(mFlags & AUDIO_OUTPUT_FLAG_DIRECT)) {
// Divide capacity by 2 to take expansion into account
toWrite = audioBuffer.size >> 1;
memcpy_to_i16_from_u8(audioBuffer.i16, (const uint8_t *) buffer, toWrite);
} else {
toWrite = audioBuffer.size;
memcpy(audioBuffer.i8, buffer, toWrite);
}
//代码比较明显了,就是obtainBuffer获取内存,然后将从应用层传下来的数据buffer拷贝到内存audioBuffer里
//audioBuffer到底是什么鬼?我们接着分析obtainBuffer函数
}
status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, const struct timespec *requested,
struct timespec *elapsed, size_t *nonContig)
{
sp
sp
proxy = mProxy;
iMem = mCblkMemory;
status = proxy->obtainBuffer(&buffer, requested, elapsed);
//真相大白!AudioTrackClientProxy和上面提到的AudioTrackServerProxy对应
//服务端通过AudioTrackServerProxy来管理内存,即从内存里取出音频数据
//客户端通过AudioTrackClientProxy来管理内存,即往内存里放入音频数据
//那么mProxy、iMem和mCblkMemory到底都是什么东西呢?
}
//我们回到AudioTrack::createTrack_l里一看,一切都一目了然了
status_t AudioTrack::createTrack_l(
audio_stream_type_t streamType,
uint32_t sampleRate,
audio_format_t format,
size_t frameCount,
audio_output_flags_t flags,
const sp
audio_io_handle_t output,
size_t epoch)
{
sp
//我们在通过Binder让AudioFlinger给我们返回一个track时,AudioFlinger已经申请了内存mCblkMemory、mCblk
//我们通过返回的track当然可以拿到接口去管理内存咯!
//通过IMemory获取mCblk的接口
sp
mAudioTrack = track;
mCblkMemory = iMem;
audio_track_cblk_t* cblk = static_cast
mCblk = cblk;
//由cblk当参数new出来的AudioRecordClientProxy对象就可以和AudioFlinger端的AudioRecordServerProxy对象一起管理着同一个audio_track_cblk_t
mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSizeAF);
}
//不知道大伙还记不记得上面讲AudioTrack::set()时我们曾new出一个AudioTrackThread线程
//而在AudioTrack::start()时我们让线程运行mReceiver.processAudioBuffer(this)
//下面我来分析一下AudioTrackThread的主函数处理的processAudioBuffer都做了什么
nsecs_t AudioTrack::processAudioBuffer(const sp
{
//这里和AudioTrack::write时一样也获取内存
status_t err = obtainBuffer(&audioBuffer, requested, NULL, &nonContig);
if (mFormat == AUDIO_FORMAT_PCM_8_BIT && !(mFlags & AUDIO_OUTPUT_FLAG_DIRECT)) {
// 8 to 16 bit conversion, note that source and destination are the same address
memcpy_to_i16_from_u8(audioBuffer.i16, (const uint8_t *) audioBuffer.i8, writtenSize);
audioBuffer.size <<= 1;
}
//这个线程所做的事情就是把audioBuffer.i8的数据拷贝到audioBuffer.i16
//也就是说不管AudioTrack::write时将数据拷贝到哪里,经该线程数据最终都会被拷贝到audioBuffer.i16里
//至于为什么要这么做本人也还没有搞明白
}
总结:
经上面的分析,我们可以将Android播放PCM码流的流程总结一下
就是当应用层new出一个AudioTrack时,代码从框架层通过JNI接口调到Native层的运行时库,代码运行在运行时库时,new出AudioTrackThread线程。
接着由运行时库通过Binder和音频服务通信,而音频服务AudioFlinger会申请出共享内存,同时new出PlaybackThread线程。
而应用层在调用AudioTrack::start()时,AudioTrackThread线程主函数进行processAudioBuffer处理,即该线程通过AudioTrackClientProxy对象获取共享内存,对共享内存里的数据进行处理。
同时AudioTrack::start()会通过Binder和音频服务AudioFlinger通信,给AudioFlinger的PlaybackThread线程发送广播,让PlaybackThread线程开始进入播放循环,即从混音器的缓冲区里拿出音频数据给驱动写,让声音播放出来。
最后在应用层会new一个线程不停的AudioTrack.write(),这个相当于该线程会通过AudioTrackClientProxy对象获取共享内存,将应用层的音频数据拷贝到共享内存里。
整个PCM码流播放的流程就是这样,你们会发现本文没有提到音频的另外一个服务AudioPolicyService,其实这个服务很重要,但是因为这个服务负责的事情,对我们对音频数据的流向的理解没有多大的影响,所以为了大家好理解文章里没有提到。