https://blog.csdn.net/langwang2/article/details/50186275

AudioTrack 分析

2015年12月05日 15:17:16 阅读数:1498更多

个人分类: android技术

AudioTrack主要是用来播放声音的,AudioTrack贯穿了JAVA层,JNI层和Native层。

AudioTrack JAVA层:

framework\base\media\java\android\media\AudioTrack.java

以AudioTrack的使用方法举例:

[cpp] view plaincopy

  1. // 得到一个满足最小要求的缓冲区的大小
  2. int bufsize = AudioTrack.getMinBufferSize(8000,//采样率 = 每秒8K个点
  3.   AudioFormat.CHANNEL_CONFIGURATION_STEREO,//双声道
  4. AudioFormat.ENCODING_PCM_16BIT);//采样精度 = 一个采样点16比特 = 2个字节
  5. //创建AudioTrack
  6. AudioTrack trackplayer = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,
  7.   AudioFormat.CHANNEL_CONFIGURATION_ STEREO,
  8.   AudioFormat.ENCODING_PCM_16BIT,
  9.   bufsize,
  10. AudioTrack.MODE_STREAM);//
  11. trackplayer.play() ;//开始
  12. trackplayer.write(bytes_pkg, 0, bytes_pkg.length) ;//往track中写数据
  13. //...
  14. trackplayer.stop();//停止播放
  15. trackplayer.release();//释放底层资源

AudioTrack Native层:

[cpp] view plaincopy

  1. // 得到一个满足最小要求的缓冲区的大小
  2. int bufsize = AudioTrack.getMinBufferSize(8000,//采样率 = 每秒8K个点
  3.   AudioFormat.CHANNEL_CONFIGURATION_STEREO,//双声道
  4. AudioFormat.ENCODING_PCM_16BIT);//采样精度 = 一个采样点16比特 = 2个字节

getMinBufferSize实际调用了JNI通过Native代码来实现。

framework/base/core/jni/android_media_track.cpp

[cpp] view plaincopy

  1. static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env,  jobject thiz,
  2. jint sampleRateInHertz, jint nbChannels, jint audioFormat)
  3. {//注意我们传入的参数是:
  4. //sampleRateInHertz = 8000
  5. //nbChannels = 2;
  6. //audioFormat = AudioFormat.ENCODING_PCM_16BIT
  7. int afSamplingRate;
  8. int afFrameCount;
  9. uint32_t afLatency;
  10. if (AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) {
  11. return -1;
  12. }
  13. if (AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) {
  14. return -1;
  15. }
  16. if (AudioSystem::getOutputLatency(&afLatency) != NO_ERROR) {
  17. return -1;
  18. }
  19. //音频中最常见的是frame,解释:一个frame就是1个采样点的字节数*声道。
  20. // Ensure that buffer depth covers at least audio hardware latency
  21. uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSamplingRate);
  22. if (minBufCount < 2) minBufCount = 2;
  23. uint32_t minFrameCount =
  24. (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;
  25. //下面根据最小的framecount计算最小的buffersize
  26. int minBuffSize = minFrameCount
  27. * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1)
  28. * nbChannels;
  29. return minBuffSize;
  30. }

MODE_STREAM和MODE_STATIC

STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。这个和我们在socket中发送数据一样,应用层从某个地方获取数据,例如通过编解码得到PCM数据,然后write到audiotrack。这种方式的坏处就是总是在JAVA层和Native层交互,效率损失较大。

In Streaming mode, the application writes a continuous stream of data to the AudioTrack, using one of the write() methods. These are blocking and return when the data has been transferred from the Java layer to the native layer and queued for playback.

MODE_STREAM模式终使用Write方法,该方法是阻塞的,当数据从Java层到Native层执行播放完毕后才返回。

而STATIC的意思是一开始创建的时候,就把音频数据放到一个固定的buffer,然后直接传给audiotrack,后续就不用一次次得write了。AudioTrack会自己播放这个buffer中的数据。这种方法对于铃声等内存占用较小,延时要求较高的声音来说很适用。

AudioTrack的构造函数
public  AudioTrack  (int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)
AudioTrack的构造函数中有一个变量用来指定buffer的大小bufferSizeInBytes。

AudioTrack在Native层会对这个变量的值进行有效性判断。首先,它至少要等于或者大于getMinBufferSize返回的值,然后它必须是frame大小的整数倍。

举例说明,MODE_STREAM模式下,在JAVA层构造AudioTrack时,bufferSizeInBytes的大小设定为9600,在Native层调用Write方法拷贝数据至Hardware进行回放,每次拷贝的大小为320.则需要拷贝到30次,声卡才发出声音。即需要将数据填满缓冲区才进行播放。(320*30=9600)

=================================================================================================

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层搞,太狭隘了。)最终对应到函数

static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env, jobject thiz,

jint sampleRateInHertz, jint nbChannels, jint audioFormat)

{//注意我们传入的参数是:

//sampleRateInHertz = 8000

//nbChannels = 2;

//audioFormat = AudioFormat.ENCODING_PCM_16BIT

int afSamplingRate;

int afFrameCount;

uint32_t afLatency;

//下面涉及到AudioSystem,这里先不解释了,

//反正知道从AudioSystem那查询了一些信息

if (AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) {

return -1;

}

if (AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) {

return -1;

}

if (AudioSystem::getOutputLatency(&afLatency) != NO_ERROR) {

return -1;

}

//音频中最常见的是frame这个单位,什么意思?经过多方查找,最后还是在ALSA的wiki中

//找到解释了。一个frame就是1个采样点的字节数*声道。为啥搞个frame出来?因为对于多//声道的话,用1个采样点的字节数表示不全,因为播放的时候肯定是多个声道的数据都要播出来//才行。所以为了方便,就说1秒钟有多少个frame,这样就能抛开声道数,把意思表示全了。

// Ensure that buffer depth covers at least audio hardware latency

uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSamplingRate);

if (minBufCount < 2) minBufCount = 2;

uint32_t minFrameCount =

(afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;

//下面根据最小的framecount计算最小的buffersize

int minBuffSize = minFrameCount

* (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1)

* nbChannels;

return minBuffSize;

}

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下面的函数

static int

android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,

jint streamType, jint sampleRateInHertz, jint channels,

jint audioFormat, jint buffSizeInBytes, jint memoryMode)

{

int afSampleRate;

int afFrameCount;

下面又要调用一堆东西,烦不烦呐?具体干什么用的,以后分析到AudioSystem再说。

AudioSystem::getOutputFrameCount(&afFrameCount, streamType);

AudioSystem::getOutputSamplingRate(&afSampleRate, streamType);

AudioSystem::isOutputChannel(channels);

popCount是统计一个整数中有多少位为1的算法

int nbChannels = AudioSystem::popCount(channels);

if (streamType == javaAudioTrackFields.STREAM_MUSIC) {

atStreamType = AudioSystem::MUSIC;

}

int bytesPerSample = audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1;

int format = audioFormat == javaAudioTrackFields.PCM16 ?

AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT;

int frameCount = buffSizeInBytes / (nbChannels * bytesPerSample);

//上面是根据Buffer大小和一个Frame大小来计算帧数的。

// AudioTrackJniStorage,就是一个保存一些数据的地方,这

//里边有一些有用的知识,下面再详细解释

AudioTrackJniStorage* lpJniStorage = new AudioTrackJniStorage();

jclass clazz = env->GetObjectClass(thiz);

lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz);

lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this);

lpJniStorage->mStreamType = atStreamType;

//创建真正的AudioTrack对象

AudioTrack* lpTrack = new AudioTrack();

if (memoryMode == javaAudioTrackFields.MODE_STREAM) {

//如果是STREAM流方式的话,把刚才那些参数设进去

lpTrack->set(

atStreamType,// stream type

sampleRateInHertz,

format,// word length, PCM

channels,

frameCount,

0,// flags

audioCallback,

&(lpJniStorage->mCallbackData),//callback, callback data (user)

0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack

0,// 共享内存,STREAM模式需要用户一次次写,所以就不用共享内存了

true);// thread can call Java

} else if (memoryMode == javaAudioTrackFields.MODE_STATIC) {

//如果是static模式,需要用户一次性把数据写进去,然后

//再由audioTrack自己去把数据读出来,所以需要一个共享内存

//这里的共享内存是指C++AudioTrack和AudioFlinger之间共享的内容

//因为真正播放的工作是由AudioFlinger来完成的。

lpJniStorage->allocSharedMem(buffSizeInBytes);

lpTrack->set(

atStreamType,// stream type

sampleRateInHertz,

format,// word length, PCM

channels,

frameCount,

0,// flags

audioCallback,

&(lpJniStorage->mCallbackData),//callback, callback data (user));

0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack

lpJniStorage->mMemBase,// shared mem

true);// thread can call Java

}

if (lpTrack->initCheck() != NO_ERROR) {

LOGE("Error initializing AudioTrack");

goto native_init_failure;

}

//又来这一招,把C++AudioTrack对象指针保存到JAVA对象的一个变量中

//这样,Native层的AudioTrack对象就和JAVA层的AudioTrack对象关联起来了。

env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, (int)lpTrack);

env->SetIntField(thiz, javaAudioTrackFields.jniData, (int)lpJniStorage);

}

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传入的回调函数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<IMemory>& sharedBuffer,

audio_io_handle_t output)

{

status_t status;

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

const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();

//下面这个调用最终会在AudioFlinger中出现。暂时不管它。

sp<IAudioTrack> track = audioFlinger->createTrack(getpid(),

streamType,

sampleRate,

format,

channelCount,

frameCount,

((uint16_t)flags) << 16,

sharedBuffer,

output,

&status);

//看见没,从track也就是AudioFlinger那边得到一个IMemory接口

//这个看来就是最终write写入的地方

sp<IMemory> cblk = track->getCblk();

mAudioTrack.clear();

mAudioTrack = track;

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

mCblkMemory = cblk;

mCblk = static_cast<audio_track_cblk_t*>(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;

}

还记得我们说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函数触发。

void AudioTrack::start()

{

//start函数调用AudioTrackThread函数触发产生一个新的线程,执行mAudioTrackThread的

threadLoop

sp<AudioTrackThread> t = mAudioTrackThread;

t->run("AudioTrackThread", THREAD_PRIORITY_AUDIO_CLIENT);

//让AudioFlinger中的track也start

status_t status = mAudioTrack->start();

}

bool AudioTrack::AudioTrackThread::threadLoop()

{

//太恶心了,又调用AudioTrack的processAudioBuffer函数

return mReceiver.processAudioBuffer(this);

}

bool AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread)

{

Buffer audioBuffer;

uint32_t frames;

size_t writtenSize;

...回调1

mCbf(EVENT_UNDERRUN, mUserData, 0);

...回调2 都是传递一些信息到JNI里边

mCbf(EVENT_BUFFER_END, mUserData, 0);

// Manage loop end callback

while (mLoopCount > mCblk->loopCount) {

mCbf(EVENT_LOOP_END, mUserData, (void *)&loopCount);

}

//下面好像有写数据的东西

do {

audioBuffer.frameCount = frames;

//获得buffer,

status_t err = obtainBuffer(&audioBuffer, 1);

size_t reqSize = audioBuffer.size;

//把buffer回调到JNI那去,这是单独一个线程,而我们还有上层用户在那不停

//地write呢,怎么会这样?

mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer);

audioBuffer.size = writtenSize;

frames -= audioBuffer.frameCount;

releaseBuffer(&audioBuffer); //释放buffer,和obtain相对应,看来是LOCK和UNLOCK

操作了

}

while (frames);

return true;

}

难道真的有两处在write数据?看来必须得到mCbf去看看了,传的是EVENT_MORE_DATA标志。

mCbf由set的时候传入C++的AudioTrack,实际函数是:

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数据到音频设备中去。我们拭目以待。

(1)创建AudioRecord和AudioTrack类对象:创建这两个类的对象比较复杂,通过对文档的反复和仔细理解,并通过多次失败的尝试,并在北理工的某个Android大牛的网上的文章启发下,我们也最终成功地创建了这两个类的对象。创建AudioRecord和AudioTrack类对象的代码如下:

AudioRecord类:

view plain

  1. m_in_buf_size =AudioRecord.getMinBufferSize(8000,
  2. AudioFormat.CHANNEL_CONFIGURATION_MONO,
  3. AudioFormat.ENCODING_PCM_16BIT);
  4. m_in_rec = new AudioRecord(MediaRecorder.AudioSource.MIC,
  5. 8000,
  6. AudioFormat.CHANNEL_CONFIGURATION_MONO,
  7. AudioFormat.ENCODING_PCM_16BIT,
  8. m_in_buf_size) ;
  9. AudioTrack类:
  10. m_out_buf_size = android.media.AudioTrack.getMinBufferSize(8000,
  11. AudioFormat.CHANNEL_CONFIGURATION_MONO,
  12. AudioFormat.ENCODING_PCM_16BIT);
  13. m_out_trk = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,
  14. AudioFormat.CHANNEL_CONFIGURATION_MONO,
  15. AudioFormat.ENCODING_PCM_16BIT,
  16. m_out_buf_size,
  17. AudioTrack.MODE_STREAM);

(2)关于AudioRecord和AudioTrack类的监听函数,不用也行。

(3)调试方面,包括初始化后看logcat信息,以确定类的工作状态,初始化是否成功等。

编写好代码,没有语法错误,调用模拟器运行、调试代码时,logcat发挥了很好的功用。刚调试时,经常会出现模拟器显示出现异常,这时我们可以在代码的一些关键语句后添加如Log.d("test1","OK");这样的语句进行标识,出现异常时我们就可以在logcat窗口观察代码执行到哪里出现异常,然后进行相应的修改、调试。模拟器不会出现异常时,又遇到了录放音的问题。录音方面,刚开始选择将语音编码数据存放在多个固定大小的文件中进行传送,但是这种情况下会出现声音断续的现象,而且要反复的建立文件,比较麻烦,后来想到要进行网上传输,直接将语音编码数据以数据流的形式传送,经过验证,这种方法可行并且使代码更加简洁。放音方面,将接收到的数据流存放在一个数组中,然后将数组中数据写到AudioTrack中。刚开始只是“嘟”几声,经过检查发现只是把数据写一次,加入循环,让数据反复写到AudioTrack中,就可以听到正常的语音了。接下来的工作主要是改善话音质量与话音延迟,在进行通话的过程中,观察logcat窗口,发现向数组中写数据时会出现Bufferflow的情况,于是把重心转移到数组大小的影响上,经过试验,发现 AudioRecord一次会读640个数据,然后就对录音和放音中有数组的地方进行实验修改。AudioRecord和AudioTrack进行实例化时,参数中各有一个数组大小,经过试验这个数组大小和AudioRecord和AudioTrack能正常实例化所需的最小Buffer大小(即上面实例化时的m_in_buf_size和m_out_buf_size参数)相等且服务器方进行缓存数据的数组尺寸是上述数值的2倍时,语音质量最好。由于录音和放音的速度不一致,受到北理工大牛的启发,在录音方面,将存放录音数据的数组放到LinkedList中,当LinkedList中数组个数达到2(这个也是经过试验验证话音质量最好时的数据)时,将先录好的数组中数据传送出去。经过上述反复试验和修改,最终使双方通话质量较好,且延时较短(大概有2秒钟)。

(4)通过套接字传输和接收数据

数据传送部分,使用的是套接字。通信双方,通过不同的端口向服务器发送请求,与服务器连接上后,开始通话向服务器发送数据,服务器通过一个套接字接收到一方的数据后,先存在一个数组中,然后将该数组中数据以数据流的形式再通过另一个套接字传送到另一方。这样就实现了双方数据的传送。

(5)代码架构

为避免反复录入和读取数据占用较多资源,使程序在进行录放音时不能执行其他命令,故将录音和放音各写成一个线程类,然后在主程序中,通过MENU控制通话的开始、停止、结束。

最后说明,AudioRecord和AudioTrack类可以用,只是稍微复杂些。以下贴出双方通信的源码,希望对大家有所帮助:

主程序Daudioclient:

view plain

  1. package cn.Daudioclient;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.Menu;
  5. import android.view.MenuItem;
  6. public class Daudioclient extends Activity {
  7. public static final int MENU_START_ID = Menu.FIRST ;
  8. public static final int MENU_STOP_ID = Menu.FIRST + 1 ;
  9. public static final int MENU_EXIT_ID = Menu.FIRST + 2 ;
  10. protected Saudioserver     m_player ;
  11. protected Saudioclient     m_recorder ;
  12. @Override
  13. public void onCreate(Bundle savedInstanceState) {
  14. super.onCreate(savedInstanceState);
  15. setContentView(R.layout.main);
  16. }
  17. public boolean onCreateOptionsMenu(Menu aMenu)
  18. {
  19. boolean res = super.onCreateOptionsMenu(aMenu) ;
  20. aMenu.add(0, MENU_START_ID, 0, "START") ;
  21. aMenu.add(0, MENU_STOP_ID, 0, "STOP") ;
  22. aMenu.add(0, MENU_EXIT_ID, 0, "EXIT") ;
  23. return res ;
  24. }
  25. public boolean onOptionsItemSelected(MenuItem aMenuItem)
  26. {
  27. switch (aMenuItem.getItemId()) {
  28. case MENU_START_ID:
  29. {
  30. m_player = new Saudioserver() ;
  31. m_recorder = new Saudioclient() ;
  32. m_player.init() ;
  33. m_recorder.init() ;
  34. m_recorder.start() ;
  35. m_player.start() ;
  36. }
  37. break ;
  38. case MENU_STOP_ID:
  39. {
  40. m_recorder.free() ;
  41. m_player.free() ;
  42. m_player = null ;
  43. m_recorder = null ;
  44. }
  45. break ;
  46. case MENU_EXIT_ID:
  47. {
  48. int pid = android.os.Process.myPid() ;
  49. android.os.Process.killProcess(pid) ;
  50. }
  51. break ;
  52. default:
  53. break ;
  54. }
  55. return super.onOptionsItemSelected(aMenuItem);
  56. }
  57. }

录音程序Saudioclient:

view plain

  1. package cn.Daudioclient;
  2. import java.io.DataOutputStream;
  3. import java.io.IOException;
  4. import java.net.Socket;
  5. import java.net.UnknownHostException;
  6. import java.util.LinkedList;
  7. import android.media.AudioFormat;
  8. import android.media.AudioRecord;
  9. import android.media.MediaRecorder;
  10. import android.util.Log;
  11. public class Saudioclient extends Thread
  12. {
  13. protected AudioRecord m_in_rec ;
  14. protected int         m_in_buf_size ;
  15. protected byte []     m_in_bytes ;
  16. protected boolean     m_keep_running ;
  17. protected Socket      s;
  18. protected DataOutputStream dout;
  19. protected LinkedList<byte[]>  m_in_q ;
  20. public void run()
  21. {
  22. try
  23. {
  24. byte [] bytes_pkg ;
  25. m_in_rec.startRecording() ;
  26. while(m_keep_running)
  27. {
  28. m_in_rec.read(m_in_bytes, 0, m_in_buf_size) ;
  29. bytes_pkg = m_in_bytes.clone() ;
  30. if(m_in_q.size() >= 2)
  31. {
  32. dout.write(m_in_q.removeFirst() , 0, m_in_q.removeFirst() .length);
  33. }
  34. m_in_q.add(bytes_pkg) ;
  35. }
  36. m_in_rec.stop() ;
  37. m_in_rec = null ;
  38. m_in_bytes = null ;
  39. dout.close();
  40. }
  41. catch(Exception e)
  42. {
  43. e.printStackTrace();
  44. }
  45. }
  46. public void init()
  47. {
  48. m_in_buf_size =  AudioRecord.getMinBufferSize(8000,
  49. AudioFormat.CHANNEL_CONFIGURATION_MONO,
  50. AudioFormat.ENCODING_PCM_16BIT);
  51. m_in_rec = new AudioRecord(MediaRecorder.AudioSource.MIC,
  52. 8000,
  53. AudioFormat.CHANNEL_CONFIGURATION_MONO,
  54. AudioFormat.ENCODING_PCM_16BIT,
  55. m_in_buf_size) ;
  56. m_in_bytes = new byte [m_in_buf_size] ;
  57. m_keep_running = true ;
  58. m_in_q=new LinkedList<byte[]>();
  59. try
  60. {
  61. s=new Socket("192.168.1.100",4332);
  62. dout=new DataOutputStream(s.getOutputStream());
  63. //new Thread(R1).start();
  64. }
  65. catch (UnknownHostException e)
  66. {
  67. // TODO Auto-generated catch block
  68. e.printStackTrace();
  69. }
  70. catch (IOException e)
  71. {
  72. // TODO Auto-generated catch block
  73. e.printStackTrace();
  74. }
  75. }
  76. public void free()
  77. {
  78. m_keep_running = false ;
  79. try {
  80. Thread.sleep(1000) ;
  81. } catch(Exception e) {
  82. Log.d("sleep exceptions.../n","") ;
  83. }
  84. }
  85. }

放音程序Saudioserver:

view plain

  1. package cn.Daudioclient;
  2. import java.io.DataInputStream;
  3. import java.io.IOException;
  4. import java.net.Socket;
  5. import android.media.AudioFormat;
  6. import android.media.AudioManager;
  7. import android.media.AudioTrack;
  8. import android.util.Log;
  9. public class Saudioserver extends Thread
  10. {
  11. protected AudioTrack m_out_trk ;
  12. protected int        m_out_buf_size ;
  13. protected byte []    m_out_bytes ;
  14. protected boolean    m_keep_running ;
  15. private Socket s;
  16. private DataInputStream din;
  17. public void init()
  18. {
  19. try
  20. {
  21. s=new Socket("192.168.1.100",4331);
  22. din=new DataInputStream(s.getInputStream());
  23. m_keep_running = true ;
  24. m_out_buf_size = AudioTrack.getMinBufferSize(8000,
  25. AudioFormat.CHANNEL_CONFIGURATION_MONO,
  26. AudioFormat.ENCODING_PCM_16BIT);
  27. m_out_trk = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,
  28. AudioFormat.CHANNEL_CONFIGURATION_MONO,
  29. AudioFormat.ENCODING_PCM_16BIT,
  30. m_out_buf_size,
  31. AudioTrack.MODE_STREAM);
  32. m_out_bytes=new byte[m_out_buf_size];
  33. // new Thread(R1).start();
  34. }
  35. catch(Exception e)
  36. {
  37. e.printStackTrace();
  38. }
  39. }
  40. public void free()
  41. {
  42. m_keep_running = false ;
  43. try {
  44. Thread.sleep(1000) ;
  45. } catch(Exception e) {
  46. Log.d("sleep exceptions.../n","") ;
  47. }
  48. }
  49. public void run()
  50. {
  51. byte [] bytes_pkg = null ;
  52. m_out_trk.play() ;
  53. while(m_keep_running) {
  54. try
  55. {
  56. din.read(m_out_bytes);
  57. bytes_pkg = m_out_bytes.clone() ;
  58. m_out_trk.write(bytes_pkg, 0, bytes_pkg.length) ;
  59. }
  60. catch(Exception e)
  61. {
  62. e.printStackTrace();
  63. }
  64. }
  65. m_out_trk.stop() ;
  66. m_out_trk = null ;
  67. try {
  68. din.close();
  69. } catch (IOException e) {
  70. // TODO Auto-generated catch block
  71. e.printStackTrace();
  72. }
  73. }
  74. }

AudioTrack 分析相关推荐

  1. AudioTrack分析

    第一部分 AudioTrack分析 一 目的 本文的目的是通过从Audio系统来分析Android的代码,包括Android自定义的那套机制和一些常见类的使用,比如Thread,MemoryBase等 ...

  2. [2021.05.26]AudioTrack流程分析

    转载自: Android深入浅出之Audio 第一部分 AudioTrack分析_阿拉神农的博客-CSDN博客 UML顺序图: AudioTrack.svg https://download.csdn ...

  3. AudioTrack

    第一部分 AudioTrack分析 一 目的 本文的目的是通过从Audio系统来分析Android的代码,包括Android自定义的那套机制和一些常见类的使用,比如Thread,MemoryBase等 ...

  4. android文章链接

    1. http://hi.baidu.com/idrod/home : 楼主写的android源码分析值得一看. 2. http://wenku.baidu.com/view/de258fabd1f3 ...

  5. 音视频的流程:录制、播放、编码解码、上传下载等

    仿网易云音乐 安卓版-- https://github.com/aa112901/remusic Android本地视频播放器开发- http://blog.csdn.NET/jwzhangjie/a ...

  6. Android 8.0 开机动画,RK3326 android10.0(Q) 开机logo+开关机动画替换

    RK3326 android10.0(Q) 开机logo+开关机动画替换 2020年08月14日 | 萬仟网移动技术 | 我要评论 开机logouboot和kernel阶段的logo分别为开机显示的第 ...

  7. 1.7 深入理解Audio系统

    第7章 深入理解Audio系统 7.1 概述 Audio系统是Android平台的重要组成部分,它主要包括三方面的内容: AudioRcorder和AudioTrack:这两个类属于Audio系统对外 ...

  8. [深入理解Android卷一全文-第七章]深入理解Audio系统

    由于<深入理解Android 卷一>和<深入理解Android卷二>不再出版,而知识的传播不应该因为纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容. ...

  9. 音频应用(如sip与Voip),编解码API(Ringtone,SoundPool,MediaPlayer),回声消除等(Lame Speex等),OpenSL ES

    3套音频播放API:MediaPlayer,SoundPool,AudioTrack. android录音项目,用单例模式集成了record,并实时转码mp3- https://github.com/ ...

最新文章

  1. Word插入Latex公式的几种方式~(TeXsWord、EqualX、Aurora、向Office插入LaTeX公式的工具)
  2. C#中教你一步步实现一个电话本窗体程序
  3. 管理者的困境:放权或者崩溃
  4. python dry原则_关于Python 的这几个技巧,你应该知道
  5. Matplotlib——绘制图表
  6. 在Windows下不使用密码远程登陆Linux
  7. 趣味图解 | 什么是缺页错误 Page Fault?
  8. angular 2+ innerHTML属性中内联样式丢失
  9. c java string转int_jni java和C之间的值传递(int String int[])
  10. 好用的 windows10 软件推荐
  11. 苹果马桶iPoo,果粉还hold住吗
  12. 候鸟算法matlab实现
  13. antd 覆盖css样式不生效(antd避坑)
  14. 新变种Emotet恶意样本分析
  15. Android jetpack 篮球计分板的实现
  16. 游戏应用出海本地化策略,巴西篇
  17. 冯巩的155句经典语
  18. 关于纸质书管理APP的测评
  19. # Python3 面试试题--Python语言特性
  20. c++ web 框架 ---ricky.chu

热门文章

  1. 创业思维的误区 (余世维)
  2. 零基础入门C 语言如何实现面向对象编程
  3. katex常用公式符号一览表
  4. 大数据HBase(四):HBase的相关操作-客户端命令式
  5. Unhandled promise rejection --解决办法
  6. 关于北大青鸟????
  7. python后端开发的缺点_前后端分离优缺点
  8. 迪思杰支持mysql_迪思杰Oracle数据库复制产品
  9. 目录大全_安卓面经_Android面经_150道安卓基础面试题全解析
  10. 蓝桥杯历年省赛JAVA-B组真题汇总及题目详解