Android深入浅出之Audio
第一部分 AudioTrack分析
一 目的
本文的目的是通过从Audio系统来分析Android的代码,包括Android自定义的那套机制和一些常见类的使用,比如Thread,MemoryBase等。
分析的流程是:
l 先从API层对应的某个类开始,用户层先要有一个简单的使用流程。
l 根据这个流程,一步步进入到JNI,服务层。在此过程中,碰到不熟悉或者第一次见到的类或者方法,都会解释。也就是深度优先的方法。
1.1 分析工具
分析工具很简单,就是sourceinsight和android的API doc文档。当然还得有android的源代码。我这里是基于froyo的源码。
注意,froyo源码太多了,不要一股脑的加入到sourceinsight中,只要把framwork目录下的源码加进去就可以了,后续如要用的话,再加别的目录。
二 Audio系统
先看看Audio里边有哪些东西?通过Android的SDK文档,发现主要有三个:
l AudioManager:这个主要是用来管理Audio系统的
l AudioTrack:这个主要是用来播放声音的
l AudioRecord:这个主要是用来录音的
其中AudioManager的理解需要考虑整个系统上声音的策略问题,例如来电话铃声,短信铃声等,主要是策略上的问题。一般看来,最简单的就是播放声音了。所以我们打算从AudioTrack开始分析。
三 AudioTrack(JAVA层)
JAVA的AudioTrack类的代码在:
framework\base\media\java\android\media\AudioTrack.java中。
3.1 AudioTrack API的使用例子
先看看使用例子,然后跟进去分析。至于AudioTrack的其他使用方法和说明,需要大家自己去看API文档了。
//根据采样率,采样精度,单双声道来得到frame的大小。 int bufsize = AudioTrack.getMinBufferSize(8000,//每秒8K个点 AudioFormat.CHANNEL_CONFIGURATION_STEREO,//双声道 AudioFormat.ENCODING_PCM_16BIT);//一个采样点16比特-2个字节 //注意,按照数字音频的知识,这个算出来的是一秒钟buffer的大小。 //创建AudioTrack AudioTrack trackplayer = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_ STEREO, AudioFormat.ENCODING_PCM_16BIT, bufsize, AudioTrack.MODE_STREAM);// trackplayer.play() ;//开始 trackplayer.write(bytes_pkg, 0, bytes_pkg.length) ;//往track中写数据 …. trackplayer.stop();//停止播放 trackplayer.release();//释放底层资源。
这里需要解释下两个东西:
1 AudioTrack.MODE_STREAM的意思:
AudioTrack中有MODE_STATIC和MODE_STREAM两种分类。STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。这个和我们在socket中发送数据一样,应用层从某个地方获取数据,例如通过编解码得到PCM数据,然后write到audiotrack。
这种方式的坏处就是总是在JAVA层和Native层交互,效率损失较大。
而STATIC的意思是一开始创建的时候,就把音频数据放到一个固定的buffer,然后直接传给audiotrack,后续就不用一次次得write了。AudioTrack会自己播放这个buffer中的数据。
这种方法对于铃声等内存占用较小,延时要求较高的声音来说很适用。
2 StreamType
这个在构造AudioTrack的第一个参数中使用。这个参数和Android中的AudioManager有关系,涉及到手机上的音频管理策略。
Android将系统的声音分为以下几类常见的(未写全):
l STREAM_ALARM:警告声
l STREAM_MUSCI:音乐声,例如music等
l STREAM_RING:铃声
l STREAM_SYSTEM:系统声音
l STREAM_VOCIE_CALL:电话声音
为什么要分这么多呢?以前在台式机上开发的时候很少知道有这么多的声音类型,不过仔细思考下,发现这样做是有道理的。例如你在听music的时候接到电话,这个时候music播放肯定会停止,此时你只能听到电话,如果你调节音量的话,这个调节肯定只对电话起作用。当电话打完了,再回到music,你肯定不用再调节音量了。
其实系统将这几种声音的数据分开管理,所以,这个参数对AudioTrack来说,它的含义就是告诉系统,我现在想使用的是哪种类型的声音,这样系统就可以对应管理他们了。
3.2 分析之getMinBufferSize
AudioTrack的例子就几个函数。先看看第一个函数:
AudioTrack.getMinBufferSize(8000,//每秒8K个点 AudioFormat.CHANNEL_CONFIGURATION_STEREO,//双声道 AudioFormat.ENCODING_PCM_16BIT); ----->AudioTrack.JAVA
//注意,这是个static函数
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) { int channelCount = 0; switch(channelConfig) { case AudioFormat.CHANNEL_OUT_MONO: case AudioFormat.CHANNEL_CONFIGURATION_MONO: channelCount = 1; break; case AudioFormat.CHANNEL_OUT_STEREO: case AudioFormat.CHANNEL_CONFIGURATION_STEREO: channelCount = 2;--->看到了吧,外面名字搞得这么酷,其实就是指声道数 break; default: loge("getMinBufferSize(): Invalid channel configuration."); return AudioTrack.ERROR_BAD_VALUE; } //目前只支持PCM8和PCM16精度的音频 if ((audioFormat != AudioFormat.ENCODING_PCM_16BIT) && (audioFormat != AudioFormat.ENCODING_PCM_8BIT)) { loge("getMinBufferSize(): Invalid audio format."); return AudioTrack.ERROR_BAD_VALUE; } //ft,对采样频率也有要求,太低或太高都不行,人耳分辨率在20HZ到40KHZ之间 if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) { loge("getMinBufferSize(): " + sampleRateInHz +"Hz is not a supported sample rate."); return AudioTrack.ERROR_BAD_VALUE; } //调用native函数,够烦的,什么事情都搞到JNI层去。 int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat); if ((size == -1) || (size == 0)) { loge("getMinBufferSize(): error querying hardware"); return AudioTrack.ERROR; } else { return size; }
native_get_min_buff_size--->在framework/base/core/jni/android_media_track.cpp中实现。(不了解JNI的一定要学习下,否则只能在JAVA层搞,太狭隘了。)最终对应到函数
1 static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env, jobject thiz, 2 3 jint sampleRateInHertz, jint nbChannels, jint audioFormat) 4 5 {//注意我们传入的参数是: 6 7 //sampleRateInHertz = 8000 8 9 //nbChannels = 2; 10 11 //audioFormat = AudioFormat.ENCODING_PCM_16BIT 12 13 int afSamplingRate; 14 15 int afFrameCount; 16 17 uint32_t afLatency; 18 19 //下面涉及到AudioSystem,这里先不解释了, 20 21 //反正知道从AudioSystem那查询了一些信息 22 23 if (AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) { 24 25 return -1; 26 27 } 28 29 if (AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) { 30 31 return -1; 32 33 } 34 35 36 37 if (AudioSystem::getOutputLatency(&afLatency) != NO_ERROR) { 38 39 return -1; 40 41 } 42 43 //音频中最常见的是frame这个单位,什么意思?经过多方查找,最后还是在ALSA的wiki中 44 45 //找到解释了。一个frame就是1个采样点的字节数*声道。为啥搞个frame出来?因为对于多//声道的话,用1个采样点的字节数表示不全,因为播放的时候肯定是多个声道的数据都要播出来//才行。所以为了方便,就说1秒钟有多少个frame,这样就能抛开声道数,把意思表示全了。 46 47 // Ensure that buffer depth covers at least audio hardware latency 48 49 uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSamplingRate); 50 51 if (minBufCount < 2) minBufCount = 2; 52 53 uint32_t minFrameCount = 54 55 (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate; 56 57 //下面根据最小的framecount计算最小的buffersize 58 59 int minBuffSize = minFrameCount 60 61 * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1) 62 63 * nbChannels; 64 65 return minBuffSize; 66 67 }
getMinBufSize函数完了后,我们得到一个满足最小要求的缓冲区大小。这样用户分配缓冲区就有了依据。下面就需要创建AudioTrack对象了
3.3 分析之new AudioTrack
先看看调用函数:
AudioTrack trackplayer = new AudioTrack( AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_ STEREO, AudioFormat.ENCODING_PCM_16BIT, bufsize, AudioTrack.MODE_STREAM);
其实现代码在AudioTrack.java中。
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) throws IllegalArgumentException { mState = STATE_UNINITIALIZED; // 获得主线程的Looper,这个在MediaScanner分析中已经讲过了 if ((mInitializationLooper = Looper.myLooper()) == null) { mInitializationLooper = Looper.getMainLooper(); } //检查参数是否合法之类的,可以不管它 audioParamCheck(streamType, sampleRateInHz, channelConfig, audioFormat, mode); //我是用getMinBufsize得到的大小,总不会出错吧? audioBuffSizeCheck(bufferSizeInBytes); // 调用native层的native_setup,把自己的WeakReference传进去了 //不了解JAVA WeakReference的可以上网自己查一下,很简单的 int initResult = native_setup(new WeakReference<AudioTrack>(this), mStreamType, 这个值是AudioManager.STREAM_MUSIC mSampleRate, 这个值是8000 mChannels, 这个值是2 mAudioFormat,这个值是AudioFormat.ENCODING_PCM_16BIT mNativeBufferSizeInBytes, //这个是刚才getMinBufSize得到的 mDataLoadMode);DataLoadMode是MODE_STREAM .... }
上面函数调用最终进入了JNI层android_media_AudioTrack.cpp下面的函数
1 static int 2 3 android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, 4 5 jint streamType, jint sampleRateInHertz, jint channels, 6 7 jint audioFormat, jint buffSizeInBytes, jint memoryMode) 8 9 { 10 11 int afSampleRate; 12 13 int afFrameCount; 14 15 下面又要调用一堆东西,烦不烦呐?具体干什么用的,以后分析到AudioSystem再说。 16 17 AudioSystem::getOutputFrameCount(&afFrameCount, streamType); 18 19 AudioSystem::getOutputSamplingRate(&afSampleRate, streamType); 20 21 22 23 AudioSystem::isOutputChannel(channels); 24 25 popCount是统计一个整数中有多少位为1的算法 26 27 int nbChannels = AudioSystem::popCount(channels); 28 29 30 31 if (streamType == javaAudioTrackFields.STREAM_MUSIC) { 32 33 atStreamType = AudioSystem::MUSIC; 34 35 } 36 37 int bytesPerSample = audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1; 38 39 int format = audioFormat == javaAudioTrackFields.PCM16 ? 40 41 AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT; 42 43 int frameCount = buffSizeInBytes / (nbChannels * bytesPerSample); 44 45 //上面是根据Buffer大小和一个Frame大小来计算帧数的。 46 47 // AudioTrackJniStorage,就是一个保存一些数据的地方,这 48 49 //里边有一些有用的知识,下面再详细解释 50 51 AudioTrackJniStorage* lpJniStorage = new AudioTrackJniStorage(); 52 53 54 55 jclass clazz = env->GetObjectClass(thiz); 56 57 lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz); 58 59 lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this); 60 61 lpJniStorage->mStreamType = atStreamType; 62 63 64 65 //创建真正的AudioTrack对象 66 67 AudioTrack* lpTrack = new AudioTrack(); 68 69 if (memoryMode == javaAudioTrackFields.MODE_STREAM) { 70 71 //如果是STREAM流方式的话,把刚才那些参数设进去 72 73 lpTrack->set( 74 75 atStreamType,// stream type 76 77 sampleRateInHertz, 78 79 format,// word length, PCM 80 81 channels, 82 83 frameCount, 84 85 0,// flags 86 87 audioCallback, 88 89 &(lpJniStorage->mCallbackData),//callback, callback data (user) 90 91 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack 92 93 0,// 共享内存,STREAM模式需要用户一次次写,所以就不用共享内存了 94 95 true);// thread can call Java 96 97 98 99 } else if (memoryMode == javaAudioTrackFields.MODE_STATIC) { 100 101 //如果是static模式,需要用户一次性把数据写进去,然后 102 103 //再由audioTrack自己去把数据读出来,所以需要一个共享内存 104 105 //这里的共享内存是指C++AudioTrack和AudioFlinger之间共享的内容 106 107 //因为真正播放的工作是由AudioFlinger来完成的。 108 109 lpJniStorage->allocSharedMem(buffSizeInBytes); 110 111 lpTrack->set( 112 113 atStreamType,// stream type 114 115 sampleRateInHertz, 116 117 format,// word length, PCM 118 119 channels, 120 121 frameCount, 122 123 0,// flags 124 125 audioCallback, 126 127 &(lpJniStorage->mCallbackData),//callback, callback data (user)); 128 129 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack 130 131 lpJniStorage->mMemBase,// shared mem 132 133 true);// thread can call Java 134 135 } 136 137 138 139 if (lpTrack->initCheck() != NO_ERROR) { 140 141 LOGE("Error initializing AudioTrack"); 142 143 goto native_init_failure; 144 145 } 146 147 //又来这一招,把C++AudioTrack对象指针保存到JAVA对象的一个变量中 148 149 //这样,Native层的AudioTrack对象就和JAVA层的AudioTrack对象关联起来了。 150 151 env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, (int)lpTrack); 152 153 env->SetIntField(thiz, javaAudioTrackFields.jniData, (int)lpJniStorage); 154 155 }
1 AudioTrackJniStorage详解
这个类其实就是一个辅助类,但是里边有一些知识很重要,尤其是Android封装的一套共享内存的机制。这里一并讲解,把这块搞清楚了,我们就能轻松得在两个进程间进行内存的拷贝。
AudioTrackJniStorage的代码很简单。
struct audiotrack_callback_cookie { jclass audioTrack_class; jobject audioTrack_ref; };
cookie其实就是把JAVA中的一些东西保存了下,没什么特别的意义
class AudioTrackJniStorage { public: sp<MemoryHeapBase> mMemHeap;//这两个Memory很重要 sp<MemoryBase> mMemBase; audiotrack_callback_cookie mCallbackData; int mStreamType; bool allocSharedMem(int sizeInBytes) { mMemHeap = new MemoryHeapBase(sizeInBytes, 0, "AudioTrack Heap Base"); mMemBase = new MemoryBase(mMemHeap, 0, sizeInBytes); //注意用法,先弄一个HeapBase,再把HeapBase传入到MemoryBase中去。 return true; } };
2 MemoryHeapBase
MemroyHeapBase也是Android搞的一套基于Binder机制的对内存操作的类。既然是Binder机制,那么肯定有一个服务端(Bnxxx),一个代理端Bpxxx。看看MemoryHeapBase定义:
class MemoryHeapBase : public virtual BnMemoryHeap
{
果然,从BnMemoryHeap派生,那就是Bn端。这样就和Binder挂上钩了
//Bp端调用的函数最终都会调到Bn这来
对Binder机制不了解的,可以参考:
http://blog.csdn.net/Innost/archive/2011/01/08/6124685.aspx
有好几个构造函数,我们看看我们使用的:
MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name) : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), mDevice(0), mNeedUnmap(false) { const size_t pagesize = getpagesize(); size = ((size + pagesize-1) & ~(pagesize-1)); //创建共享内存,ashmem_create_region这个是系统提供的,可以不管它 //设备上打开的是/dev/ashmem设备,而Host上打开的是一个tmp文件 int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size); mapfd(fd, size);//把刚才那个fd通过mmap方式得到一块内存 //不明白得去man mmap看看
mapfd完了后,mBase变量指向内存的起始位置, mSize是分配的内存大小,mFd是
ashmem_create_region返回的文件描述符
}
MemoryHeapBase提供了一下几个函数,可以获取共享内存的大小和位置。
getBaseID()--->返回mFd,如果为负数,表明刚才创建共享内存失败了
getBase()->返回mBase,内存位置
getSize()->返回mSize,内存大小
有了MemoryHeapBase,又搞了一个MemoryBase,这又是一个和Binder机制挂钩的类。
唉,这个估计是一个在MemoryHeapBase上的方便类吧?因为我看见了offset
那么估计这个类就是一个能返回当前Buffer中写位置(就是offset)的方便类
这样就不用用户到处去计算读写位置了。
class MemoryBase : public BnMemory { public: MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size); virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size) const; protected: size_t getSize() const { return mSize; } ssize_t getOffset() const { return mOffset; } const sp<IMemoryHeap>& getHeap() const { return mHeap; } };
好了,明白上面两个MemoryXXX,我们可以猜测下大概的使用方法了。
l BnXXX端先分配BnMemoryHeapBase和BnMemoryBase,
l 然后把BnMemoryBase传递到BpXXX
l BpXXX就可以使用BpMemoryBase得到BnXXX端分配的共享内存了。
注意,既然是进程间共享内存,那么Bp端肯定使用memcpy之类的函数来操作内存,这些函数是没有同步保护的,而且Android也不可能在系统内部为这种共享内存去做增加同步保护。所以看来后续在操作这些共享内存的时候,肯定存在一个跨进程的同步保护机制。我们在后面讲实际播放的时候会碰到。
另外,这里的SharedBuffer最终会在Bp端也就是AudioFlinger那用到。
3.4 分析之play和write
JAVA层到这一步后就是调用play和write了。JAVA层这两个函数没什么内容,都是直接转到native层干活了。
先看看play函数对应的JNI函数
static void android_media_AudioTrack_start(JNIEnv *env, jobject thiz) { //看见没,从JAVA那个AudioTrack对象获取保存的C++层的AudioTrack对象指针 //从int类型直接转换成指针。要是以后ARM变成64位平台了,看google怎么改! AudioTrack *lpTrack = (AudioTrack *)env->GetIntField( thiz, javaAudioTrackFields.nativeTrackInJavaObj); lpTrack->start(); //这个以后再说 }
下面是write。我们写的是short数组,
static jint android_media_AudioTrack_native_write_short(JNIEnv *env, jobject thiz, jshortArray javaAudioData, jint offsetInShorts, jint sizeInShorts, jint javaAudioFormat) { return (android_media_AudioTrack_native_write(env, thiz, (jbyteArray) javaAudioData, offsetInShorts*2, sizeInShorts*2, javaAudioFormat) / 2); }
烦人,又根据Byte还是Short封装了下,最终会调到重要函数writeToTrack去
jint writeToTrack(AudioTrack* pTrack, jint audioFormat, jbyte* data, jint offsetInBytes, jint sizeInBytes) { ssize_t written = 0; // regular write() or copy the data to the AudioTrack's shared memory? if (pTrack->sharedBuffer() == 0) { //创建的是流的方式,所以没有共享内存在track中 //还记得我们在native_setup中调用的set吗?流模式下AudioTrackJniStorage可没创建 //共享内存 written = pTrack->write(data + offsetInBytes, sizeInBytes); } else { if (audioFormat == javaAudioTrackFields.PCM16) { // writing to shared memory, check for capacity if ((size_t)sizeInBytes > pTrack->sharedBuffer()->size()) { sizeInBytes = pTrack->sharedBuffer()->size(); } //看见没?STATIC模式的,就直接把数据拷贝到共享内存里 //当然,这个共享内存是pTrack的,是我们在set时候把AudioTrackJniStorage的 //共享设进去的 memcpy(pTrack->sharedBuffer()->pointer(), data + offsetInBytes, sizeInBytes); written = sizeInBytes; } else if (audioFormat == javaAudioTrackFields.PCM8) { PCM8格式的要先转换成PCM16 } return written; }
到这里,似乎很简单啊,JAVA层的AudioTrack,无非就是调用write函数,而实际由JNI层的C++ AudioTrack write数据。反正JNI这层是再看不出什么有意思的东西了。
四 AudioTrack(C++层)
接上面的内容,我们知道在JNI层,有以下几个步骤:
l new了一个AudioTrack
l 调用set函数,把AudioTrackJniStorage等信息传进去
l 调用了AudioTrack的start函数
l 调用AudioTrack的write函数
那么,我们就看看真正干活的的C++AudioTrack吧。
AudioTrack.cpp位于framework\base\libmedia\AudioTrack.cpp
4.1 new AudioTrack()和set调用
JNI层调用的是最简单的构造函数:
AudioTrack::AudioTrack() : mStatus(NO_INIT) //把状态初始化成NO_INIT。Android大量使用了设计模式中的state。 { }
接下来调用set。我们看看JNI那set了什么
lpTrack->set( atStreamType, //应该是Music吧 sampleRateInHertz,//8000 format,// 应该是PCM_16吧 channels,//立体声=2 frameCount,// 0,// flags audioCallback, //JNI中的一个回调函数 &(lpJniStorage->mCallbackData),//回调函数的参数 0,// 通知回调函数,表示AudioTrack需要数据,不过暂时没用上 0,//共享buffer地址,stream模式没有 true);//回调线程可以调JAVA的东西
那我们看看set函数把。
status_t AudioTrack::set( int streamType, uint32_t sampleRate, int format, int channels, int frameCount, uint32_t flags, callback_t cbf, void* user, int notificationFrames, const sp<IMemory>& sharedBuffer, bool threadCanCallJava) {
...前面一堆的判断,等以后讲AudioSystem再说
audio_io_handle_t output = AudioSystem::getOutput((AudioSystem::stream_type)streamType, sampleRate, format, channels, (AudioSystem::output_flags)flags); //createTrack?看来这是真正干活的 status_t status = createTrack(streamType, sampleRate, format, channelCount, frameCount, flags, sharedBuffer, output); //cbf是JNI传入的回调函