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; }
View Code
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_16BIT12
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 latency48
49 uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSamplingRate);50
51 if (minBufCount < 2) minBufCount = 2;52
53 uint32_t minFrameCount =
55 (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;56
57 //下⾯根据最⼩的framecount计算最⼩的buffersize 58
59 int minBuffSize = minFrameCount60
61 * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1)62
63 * nbChannels;
65 return minBuffSize;66 67 }
View Code
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 mAudioFormat,这个值是AudioFormat.ENCODING_PCM_16BIT mNativeBufferSizeInBytes, //这个是刚才getMinBufSize得到的mDataLoadMode);DataLoadMode是MODE_STREAM ....} View Code 上⾯函数调⽤最终进⼊了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 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 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 &(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 type114 115 sampleRateInHertz,116 117 format,// word length, PCM118 119 channels,120 121 frameCount,122 123 0,// flags124 125 audioCallback,126 127 &(lpJniStorage->mCallbackData),//callback, callback data (user));128 129 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack130 131 lpJniStorage->mMemBase,// shared mem132 133 true);// thread can call Java134 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);1 155 } View Code 1 AudioTrackJniStorage详解 这个类其实就是⼀个辅助类,但是⾥边有⼀些知识很重要,尤其是Android封装的⼀套共享内存的机制。这⾥⼀并讲解,把这块搞清楚了,我们就能轻松得在两个进程间进⾏内存的拷贝。 AudioTrackJniStorage的代码很简单。 struct audiotrack_callback_cookie { jclass audioTrack_class; jobject audioTrack_ref; }; cookie其实就是把JAVA中的⼀些东西保存了下,没什么特别的意义 class AudioTrackJniStorage { public: sp 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机制不了解的,可以参考: 有好⼏个构造函数,我们看看我们使⽤的: 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 size_t getSize() const { return mSize; } ssize_t getOffset() const { return mOffset; } const sp 好了,明⽩上⾯两个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变成位平台了,看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 ...前⾯⼀堆的判断,等以后讲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传⼊的回调函数audioCallback if (cbf != 0) { //看来,怎么着也要创建这个线程了! mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava); } return NO_ERROR;} 看看真正⼲活的createTrack status_t AudioTrack::createTrack( int streamType, uint32_t sampleRate, int format, int channelCount, int frameCount, uint32_t flags, const sp status_t status; //啊,看来和audioFlinger挂上关系了呀。 const sp //下⾯这个调⽤最终会在AudioFlinger中出现。暂时不管它。 sp channelCount, frameCount, ((uint16_t)flags) << 16, sharedBuffer, output, &status); //看见没,从track也就是AudioFlinger那边得到⼀个IMemory接⼝//这个看来就是最终write写⼊的地⽅ sp mCblkMemory.clear();//sp mCblk = static_cast mFrameCount = mCblk->frameCount;if (sharedBuffer == 0) { //终于看到buffer相关的了。注意我们这⾥的情况 //STREAM模式没有传⼊共享buffer,但是数据确实⼜需要buffer承载。//反正AudioTrack是没有创建buffer,那只能是刚才从AudioFlinger中得到//的buffer了。 mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t); } return NO_ERROR;} View Code 还记得我们说MemoryXXX没有同步机制,所以这⾥应该有⼀个东西能体现同步的,那么我告诉⼤家,就在audio_track_cblk_t结构中。它的头⽂件在framework/base/include/private/media/AudioTrackShared.h实现⽂件就在AudioTrack.cpp中 audio_track_cblk_t::audio_track_cblk_t() //看见下⾯的SHARED没?都是表⽰跨进程共享的意思。这个我就不跟进去说了//等以后介绍同步⽅⾯的知识时,再细说 : lock(Mutex::SHARED), cv(Condition::SHARED), user(0), server(0), userBase(0), serverBase(0), buffers(0), frameCount(0), loopStart(UINT_MAX), loopEnd(UINT_MAX), loopCount(0), volumeLR(0), flowControlFlag(1), forceReady(0){} 到这⾥,⼤家应该都有个⼤概的全景了。 l AudioTrack得到AudioFlinger中的⼀个IAudioTrack对象,这⾥边有⼀个很重要的数据结构audio_track_cblk_t,它包括⼀块缓冲区地址,包括⼀些进程间同步的内容,可能还有数据位置等内容 l AudioTrack启动了⼀个线程,叫AudioTrackThread,这个线程⼲嘛的呢?还不知道 l AudioTrack调⽤write函数,肯定是把数据写到那块共享缓冲了,然后IAudioTrack在另外⼀个进程AudioFlinger中(其实AudioFlinger是⼀个服务,在mediaservice中运⾏)接收数据,并最终写到⾳频设备中。那我们先看看AudioTrackThread⼲什么了。调⽤的语句是: mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava); AudioTrackThread从Thread中派⽣,这个内容在深⼊浅出Binder机制讲过了。反正最终会调⽤AudioTrackAThread的threadLoop函数。先看看构造函数 AudioTrack::AudioTrackThread::AudioTrackThread(AudioTrack& receiver, bool bCanCallJava) : Thread(bCanCallJava), mReceiver(receiver){ //mReceiver就是AudioTrack对象 // bCanCallJava为TRUE } 这个线程的启动由AudioTrack的start函数触发。 1 void AudioTrack::start() 2 3 { 4 5 //start函数调⽤AudioTrackThread函数触发产⽣⼀个新的线程,执⾏mAudioTrackThread的 6 7 threadLoop 8 9 sp 11 t->run(\"AudioTrackThread\12 13 //让AudioFlinger中的track也start14 15 status_t status = mAudioTrack->start();16 17 }18 19 bool AudioTrack::AudioTrackThread::threadLoop()20 21 {22 23 //太恶⼼了,⼜调⽤AudioTrack的processAudioBuffer函数24 25 return mReceiver.processAudioBuffer(this);26 27 }28 29 bool AudioTrack::processAudioBuffer(const sp 33 Buffer audioBuffer;34 35 uint32_t frames;36 37 size_t writtenSize;38 39 ...回调140 41 mCbf(EVENT_UNDERRUN, mUserData, 0);42 43 ...回调2 都是传递⼀些信息到JNI⾥边44 45 mCbf(EVENT_BUFFER_END, mUserData, 0);46 47 // Manage loop end callback48 49 while (mLoopCount > mCblk->loopCount) {50 51 mCbf(EVENT_LOOP_END, mUserData, (void *)&loopCount);52 53 } 55 //下⾯好像有写数据的东西56 57 do {58 59 audioBuffer.frameCount = frames;60 61 //获得buffer,62 63 status_t err = obtainBuffer(&audioBuffer, 1); 65 size_t reqSize = audioBuffer.size;66 67 //把buffer回调到JNI那去,这是单独⼀个线程,⽽我们还有上层⽤户在那不停68 69 //地write呢,怎么会这样?70 71 mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer);72 73 audioBuffer.size = writtenSize;74 75 frames -= audioBuffer.frameCount;76 77 releaseBuffer(&audioBuffer); //释放buffer,和obtain相对应,看来是LOCK和UNLOCK操作了78 79 }80 81 while (frames);82 83 return true;84 85 } 难道真的有两处在write数据?看来必须得到mCbf去看看了,传的是EVENT_MORE_DATA标志。mCbf由set的时候传⼊C++的Audi View Code oTrack,实际函数是: static void audioCallback(int event, void* user, void *info) { if (event == AudioTrack::EVENT_MORE_DATA) { //哈哈,太好了,这个函数没往⾥边写数据 AudioTrack::Buffer* pBuff = (AudioTrack::Buffer*)info; pBuff->size = 0; } 从代码上看,本来google考虑是异步的回调⽅式来写数据,可惜发现这种⽅式会⽐较复杂,尤其是对⽤户开放的JAVA AudioTrack会很不好处理,所以嘛,偷偷摸摸得给绕过去了。 太好了,看来就只有⽤户的write会真正的写数据了,这个AudioTrackThread除了通知⼀下,也没什么实际有意义的操作了。让我们看看write吧。 4.2 write ssize_t AudioTrack::write(const void* buffer, size_t userSize){ 够简单,就是obtainBuffer,memcpy数据,然后releasBuffer 眯着眼睛都能想到,obtainBuffer⼀定是Lock住内存了,releaseBuffer⼀定是unlock内存了 do { audioBuffer.frameCount = userSize/frameSize(); status_t err = obtainBuffer(&audioBuffer, -1); size_t toWrite; toWrite = audioBuffer.size; memcpy(audioBuffer.i8, src, toWrite); src += toWrite; } userSize -= toWrite; written += toWrite; releaseBuffer(&audioBuffer); } while (userSize); return written;} obtainBuffer太复杂了,不过⼤家知道其⼤概⼯作⽅式就可以了 status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, int32_t waitCount){ //恕我中间省略太多,⼤部分都是和当前数据位置相关, uint32_t framesAvail = cblk->framesAvailable(); cblk->lock.lock();//看见没,lock了 result = cblk->cv.waitRelative(cblk->lock, milliseconds(waitTimeMs)); //我发现很多地⽅都要判断远端的AudioFlinger的状态,⽐如是否退出了之类的,难道//没有⼀个好的⽅法来集中处理这种事情吗? if (result == DEAD_OBJECT) { result = createTrack(mStreamType, cblk->sampleRate, mFormat, mChannelCount, mFrameCount, mFlags, mSharedBuffer,getOutput()); }//得到buffer audioBuffer->raw = (int8_t *)cblk->buffer(u); return active ? status_t(NO_ERROR) : status_t(STOPPED);} 在看看releaseBuffer void AudioTrack::releaseBuffer(Buffer* audioBuffer){ audio_track_cblk_t* cblk = mCblk;cblk->stepUser(audioBuffer->frameCount);} uint32_t audio_track_cblk_t::stepUser(uint32_t frameCount){ uint32_t u = this->user; u += frameCount; if (out) { if (bufferTimeoutMs == MAX_STARTUP_TIMEOUT_MS-1) { bufferTimeoutMs = MAX_RUN_TIMEOUT_MS; } } else if (u > this->server) { u = this->server; } if (u >= userBase + this->frameCount) { userBase += this->frameCount; } this->user = u; flowControlFlag = 0; return u;} 奇怪了,releaseBuffer没有unlock操作啊?难道我失误了?再去看看obtainBuffer?为何写得这么晦涩难懂? 原来在obtainBuffer中会某⼀次进去lock,再某⼀次进去可能就是unlock了。没看到obtainBuffer中到处有lock,unlock,wait等同步操作吗。⼀定是这个道理。难怪写这么复杂。还使⽤了少⽤的goto语句。唉,有必要这样吗!五 AudioTrack总结 通过这⼀次的分析,我⾃⼰觉得有以下⼏个点: l AudioTrack的⼯作原理,尤其是数据的传递这⼀块,做了⽐较细致的分析,包括共享内存,跨进程的同步等,也能解释不少疑惑了。l 看起来,最重要的⼯作是在AudioFlinger中做的。通过AudioTrack的介绍,我们给后续深⼊分析AudioFlinger提供了⼀个切⼊点 ⼯作原理和流程嘛,再说⼀次好了,JAVA层就看最前⾯那个例⼦吧,实在没什么说的。 l AudioTrack被new出来,然后set了⼀堆信息,同时会通过Binder机制调⽤另外⼀端的AudioFlinger,得到IAudioTrack对象,通过它和AudioFlinger交互。 l 调⽤start函数后,会启动⼀个线程专门做回调处理,代码⾥边也会有那种数据拷贝的回调,但是JNI层的回调函数实际并没有往⾥边写数据,⼤家只要看write就可以了l ⽤户⼀次次得write,那AudioTrack⽆⾮就是把数据memcpy到共享buffer中咯 l 可想⽽知,AudioFlinger那⼀定有⼀个线程在memcpy数据到⾳频设备中去。我们拭⽬以待。 因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- nryq.cn 版权所有 赣ICP备2024042798号-6
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务