您好,欢迎来到榕意旅游网。
搜索
您的当前位置:首页Android深入浅出之AudioTrack分析

Android深入浅出之AudioTrack分析

来源:榕意旅游网
Android深⼊浅出之AudioTrack分析

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(this), mStreamType, 这个值是AudioManager.STREAM_MUSIC mSampleRate, 这个值是8000mChannels, 这个值是2

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 mMemHeap;//这两个Memory很重要 sp 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机制不了解的,可以参考: 有好⼏个构造函数,我们看看我们使⽤的:

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& heap, ssize_t offset, size_t size); virtual sp getMemory(ssize_t* offset, size_t* size) const;protected:

size_t getSize() const { return mSize; } ssize_t getOffset() const { return mOffset; }

const sp& 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变成位平台了,看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& 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传⼊的回调函数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& sharedBuffer, audio_io_handle_t output){

status_t status;

//啊,看来和audioFlinger挂上关系了呀。

const sp& audioFlinger = AudioSystem::get_audio_flinger();

//下⾯这个调⽤最终会在AudioFlinger中出现。暂时不管它。 sp track = audioFlinger->createTrack(getpid(), streamType, sampleRate, format,

channelCount, frameCount,

((uint16_t)flags) << 16, sharedBuffer, output, &status);

//看见没,从track也就是AudioFlinger那边得到⼀个IMemory接⼝//这个看来就是最终write写⼊的地⽅ sp cblk = track->getCblk(); mAudioTrack.clear(); mAudioTrack = track;

mCblkMemory.clear();//sp的clear,就看着做是delete XXX吧 mCblkMemory = cblk;

mCblk = static_cast(cblk->pointer()); mCblk->out = 1;

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 t = mAudioTrackThread;10

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& thread)30 31 {32

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

本站由北京市万商天勤律师事务所王兴未律师提供法律服务