前言
Linus Benedict Torvalds : RTFSC – Read The Funning Source Code
概括
The AudioTrack class manages and plays a single audio resource for Java applications.
AudioTrack
|
|
getMinBufferSize
Returns the estimated minimum buffer size required for an AudioTrack object to be created in the MODE_STREAM mode.
返回一个从 AudioTrack 对象中获取最小可被创建在 MODE_STREAM 模式下的最小缓冲区大小估计值。
estimated : 估计
The size is an estimate because it does not consider either the route or the sink, since neither is known yet. Note that this size doesn’t guarantee a smooth playback under load, and higher values should be chosen according to the expected frequency at which the buffer will be refilled with additional data to play. For example, if you intend to dynamically set the source sample rate of an AudioTrack to a higher value than the initial source sample rate, be sure to configure the buffer size based on the highest planned sample rate.
在 Android SDK 中getMinBufferSize函数在经过一系列检查后会调用native层的 native_get_min_buff_size 函数,因为在音频录制的时候我们也要检查硬件是否支持我们需要的采样率和采样精度。
java层流程:
- 声道检测,是否单声道、双声道、多声道。
- 采样率检测,是否在 4000~192000 之间。
- 硬件层检测,判断是否支持我们设置的采样率和采样精度,并且返回最小估值。
native层流程:
- 查询采样率,一般返回的是所支持的最高采样率,例如44100。(最大采样率)
- 查询硬件内部缓冲的大小,以Frame为单位。(帧数)
- 查询硬件的延时时间。(延迟量)
- 计算最少缓冲区个数:延迟量 / ((1000 * 帧数) / 最大采样率)。
- 通过缓冲区数量计算得出最少Frame数。
- 计算Buffer大小:PCM格式数据:bufrer是由每帧的字节数,通道数量和采样精度的乘积(frameCount channelCount bytesPerSample);其他格式数据:buffer大小就是Frame大小。
Frame 用来描述数据量的多少的单位,1单位的Frame等于1个采样点的字节数×声道数(例如 16k 采样精度占2字节,一般用MONO两声道,Frame=2*2)。
|
|
总结:getMinBufSize会综合考虑硬件的情况(例如是否支持采样率,硬件本身的延迟情况等)后,得出一个最小缓冲区的大小。一般我们分配的缓冲大小会是它的整数倍。
创建AudioTrack
Class constructor with AudioAttributes and AudioFormat.
java层流程:
- 检查传入的参数是否符合。(具体的判断标准可以看下文代码)
- 检查传入的Buffer缓冲区大小是否符合。(因为在上一步获取了最小Buffer,这里一般都没问题)
- 通过调用 Native 层构造此类。
native层流程:
- 获取framecount、samplerate和channel的信息。
- 通过
AudioSystem::popCount
函数从channel int类型里获取通道数。(popCount用于统计一个整数中有多少位为1,有很多经典的算法) - 将传入进来的Java层参数值转换为native层值。
- 通过传入的参数来计算一次framecount值。
- 创建一个native层audiotrack对象。
- 创建一个audioTrackJniStorage对象。(这个数据将在每个AudioTrack回调中传递)
- 将native参数传入创建出来的audiotrack对象。
- 将audiotrack对象指针保存到java层的变量中,联通java和native层。
Java层:
Native层:
先记下几个关键点:
- AudioTrackJniStorage 对象。
- AudioTrack 创建。
- AudioTrack 流模式的设置。
AudioTrackJniStorage
AudioTrackJniStorage 是对Android 共享内存机制的一个封装类。里面包含了两个关键变量:MemoryHeapBase 和 MemoryBase。
MemoryHeapBase 是Android 的一套基于Binder机制的对内存操作的类。从BnMemoryHeap派生。服务端(Bnxxx),代理端(Bpxxx)。
|
|
在这里我们能明白 MemoryHeapBase 是共享内存的核心类,MemoryBase 是在 MemoryHeapBase 基础上包了一层offset的类。在我们new 了一段共享内存后,通过把 MemoryBase 传递给我们要共享内存的代理端来实现共享。
- 分配了一块共享内存,使两个进程可以共享这块内存。
- 基于Binder通信,使这两个类的进程就可以交互。
|
|
new AudioTrack
将AudioTrack 状态初始为未加载。
|
|
AudioTrack.set
别看 set 函数很长,其实核心做的事就三件:
- 检测跟转换各种参数。
- 创建 AudioTrackThread 工作线程。
- 调用核心创建函数 createTrack_l。
|
|
AudioTrackThread
a small internal class to handle the callback.
这个线程是用于AudioTrack(native)与AudioTrack(java)间的数据事件通知的,为上层应用处理事件提供了一个入口。
AudioTrack.createTrack_l
看到核心 createTrack 的时候我们应该明白这几个步骤:
- 获取内核 AudioFlinger 对象。
- 通过 AudioFlinger 创建 track。
- 从拿到的 track 里开始为以后的 write函数做准备。
总结:AudioTrack 其实也是一个壳,核心的工作都是由 AudioFlinger 实现,到此,我们基本明白 AudioTrack 的调用也是一步步函数封装加上共享内存的调用。
|
|
AudioTrack.play
Starts playing an AudioTrack.
java层流程:
- 检查当前是否被禁止。是的话就将音量降为0。
- 调用native层的播放函数。
native层流程:
- 取出之前native层创建的audiotrack对象。
- 调用audiotrack对象的start开始播放。
native层:
AudioTrack.write
Writes the audio data to the audio sink for playback (streaming mode), or copies audio data for later playback (static buffer mode).
write有两种方式,一种是读流播放:先调用play,再调用write;另一种是读文件:先调用writ再调用play。
java层流程:
- 检查当前传入的格式,播放的起始、终点是否合法。
- 调用native层的写入函数。
native层流程:
- 取出之前native层创建的audiotrack对象。
- 调用audiotrack对象的write开始写入播放信息。
这里在调用函数时有一个注意的点:
The format specified in the AudioTrack constructor should be ENCODING_PCM_16BIT to correspond to the data in the array.write(short[] audioData, int offsetInShorts, int sizeInShorts)
The format specified in the AudioTrack constructor should be ENCODING_PCM_8BIT to correspond to the data in the array. The format can be ENCODING_PCM_16BIT, but this is deprecated.write(byte[] audioData, int offsetInBytes, int sizeInBytes, int writeMode)
也就是在调用write写入的时候如果pcm是16bit的格式就尽量选择传入short数组格式。
在write Java层有两种方式,一种是读流的方式进行播放,一种是读文件形式。两种形式调用的方法不同,但最终调用native层的方法都是一样的。
native层:
AudioTrack.stop、AudioTrack.release
Stops playing the audio data. Releases the native AudioTrack resources.
因为release里面就包含了stop。我们直接分析release就好。
java层流程:
- 调用stop函数停止播放,stop函数也很粗暴直接调用native层stop。
- 调用native层release函数。
native层流程:
- stop 首先取出之前native层创建的audiotrack对象,调用stop方法。
- release 也首先调用stop,然后获取AudioTrackJniStorage对象,释放AudioTrackJniStorage保存的资源。
|
|
总结
音频这块要了解的东西确实太多了,针对 接下来的核心层我们要分层次的去了解,大致对于应用层来说了解应该是足够了。