前言:平时开发中经常碰到录音器和音乐播放器,录音器和音乐播放器是音频系统在应用层最直观的体现;
android原生音乐器在播放音乐时用的技术是MeidaPlayer,我一直想知道这个MeidaPlayer是怎么播放音乐的,所以对android的音频系统进行了剖析;
剖析音频系统,肯定是JAVA层和Native层一起分析了;
首先分析的是AudioTrack,它是Audio系统对外提供的API,MeidaPlayer在播放音乐时最终用到的也是它,它是最基本的音频数据输出类;
AudioTrack,它是Audio系统对外提供的API,在java层和native层都有对应的类;
1.先来看下在java层的AudioTrack的构造函数:
AudioTrack.java
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes, int mode)
throws IllegalArgumentException {
this(streamType, sampleRateInHz, channelConfig, audioFormat,
bufferSizeInBytes, mode, AudioManager.AUDIO_SESSION_ID_GENERATE);
}
我们来分析一下这个构造函数,了解构造函数中参数的实际意义,对了解整个音频系统很重要;
①streamType:音频流类型,如AudioManager.STREAM_MUSIC,暂且先将它理解为一个数字就行,它的作用在后面会体现;
②sampleRateInHz:采样率,如8000,就表示了每秒8000点,采样率代表了音频的音质,每秒采的点越多,那么数据就越多,数据越多音质就越高;
③channelConfig:声道数,如AudioFormat. CHANNEL_ CONFIGURATION_ STEREO,表示双声道,它指的是接收音频的管道有几个,比如人,是靠两只耳朵来接收音频信息的,那这个声道数就是2,有的人也是一只耳朵,那就是1;
④audioFormat:音频的编码格式,也可以称为采样精度,精度越高,就代表对这个音频得编码就越细,音频的音质也就越高,如:AudioFormat.ENCODING_PCM_16BIT,表示一个采样点16比特,相当于2个字节;
⑤bufferSizeInBytes:根据音频数据的特性来确定所要分配的缓冲区的最小size,因为流首先是被写在缓冲区里面的;
⑥mode:数据加载模式,如AudioTrack.MODE_STREAM,它只有两种模式,这个后面会解释;
注意:采样率和采样精度越高,音频的音质越好,但是音频所占的内存就越大,这也是为啥网易云音乐中的高音质歌曲在下载时内存有几个G的原因;
2.说完了AudioTrack的构造函数,顺便把AudioTrack使用方式来介绍一下吧;
//①创建bufferSizeInBytes缓冲区的大小
int bufsize= AudioTrack. getMinBufferSize( 8000, AudioFormat.CHANNEL_CONFIGURATION_STEREO,AudioFormat. ENCODING_PCM_16BIT);
//②创建AudioTrack
AudioTrack trackplayer= new AudioTrack( AudioManager. STREAM_ MUSIC, AudioFormat. CHANNEL_CONFIGURATION_STEREO,AudioFormat.ENCODING_PCM_16BIT,bufsize,AudioTrack.MODE_STREAM);
//③开始播放
trackplayer. play();
......
//④调用write往track中写数据
trackplayer.write(bytes_pkg,0,bytes_pkg.length);
......
//⑤停止播放
trackplayer. stop();
//⑥释放底层资源
trackplayer.release();
在创建AudioTrack的时候,我们需要指明音频流类型和数据加载模式,先分析这两个;
①数据加载模式
AudioTrack有两种数据加载模式,MODE_STREAM和MODE_STATIC;
MODE_STREAM:日常开发中比较常见的一种模式,这种模式下,通过write一次次把音频数据写到AudioTrack中,也就是先写进去多少,AudioTrack就播放多少,后面再继续写继续播放;这种工作方式由于每次 都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会使引起延时;
MODE_STATIC:这种数据加载模式,相比于MODE_STREAM就比较简单粗暴了,它在play之前就把所有的数据通过一次write调用传递到AudioTrack的内部缓冲区中,后续就不必再传递数据了,这种模式呢,也有缺点,就是如果一次性写进去的数据太多,会导致系统无法分配足够的内存来存储数据;这种模式适用于像铃声这种内存占用量较小,延时要求比较高的文件;
②音频流类型
说起音频,就肯定离不开音频流,因为音频就是以流的形式存在的;不同的音频类型,其实就是音频流不同;
android将系统的声音分为好几种流类型,常见的有:
(1)STREAM_ALARM:警告声;
(2)STREAM_MUSIC:音乐声,例如music;
(3)STREAM_RING:铃声;
(4)STREAM_SYSTEM:系统声音,如低电量提示音,锁屏声音等;
(5)STREAM_VOICE_CALL:通话声
注意:这些类型的划分与音频数据本身并没有关系,如MUSIC和RING类型都可以是某首MP3歌曲,把音频流进行分类,是和Audio系统对音频的管理策略有关,以便于管理;
3.在创建AudioTrack的时候,我们需要指明bufferSizeInBytes缓冲区的大小,也就是:
AudioTrack. getMinBufferSize( 8000, AudioFormat.CHANNEL_CONFIGURATION_STEREO,AudioFormat. ENCODING_PCM_16BIT);
我们来分析下这个方法:
AudioTrack.java
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:
if (!isMultichannelConfigSupported(channelConfig)) {
return ERROR_BAD_VALUE;
} else {
channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
}
}
//目前只支持的音频格式类型,目前只支持PCM8,PCM16等
if (!AudioFormat.isPublicEncoding(audioFormat)) {
return ERROR_BAD_VALUE;
}
//对编码格式的要求,太高或者太低都不行
if ( (sampleRateInHz < AudioFormat.SAMPLE_RATE_HZ_MIN) ||
(sampleRateInHz > AudioFormat.SAMPLE_RATE_HZ_MAX) ) {
return ERROR_BAD_VALUE;
}
//调用native函数进行计算缓冲区的大小
int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
if (size <= 0) {
return ERROR;
}
else {
return size;
}
}
点评:上述代码先对音频格式和采样率的合理性进行了判断,然后将缓冲区大小的计算交给了native方法去执行;
有个问题,如果只是个简单计算,为啥在java层不能完成呢?
答案是:还需要确认硬件是否支持上述的音频格式和采样率参数,所以得进入native层去查询;
android_media_AudioTrack.cpp在frameworks\base\core\jni路径下;
继续来看这个代码:native_get_min_buff_size();
android_media_AudioTrack.cpp
static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env, jobject thiz,
jint sampleRateInHertz, jint channelCount, jint audioFormat) {
//注意我们传进来的参数:sampleRateInHertz为8000,channelCount为2(双声道),audioFormat为AudioFormat. ENCODING_PCM_16BIT
size_t frameCount;
//查询硬件内部的缓冲大小,查询结果以Frame为单位,将查出来的Frame的个数保存在frameCount中
const status_t status = AudioTrack::getMinFrameCount(&frameCount, AUDIO_STREAM_DEFAULT,
sampleRateInHertz);
if (status != NO_ERROR) {
return -1;
}
//返回相应的音频编码格式:这里返回的是AUDIO_FORMAT_PCM_16_BIT
const audio_format_t format = audioFormatToNative(audioFormat);
//如果硬件支持该音频编码格式
if (audio_has_proportional_frames(format)) {
//根据编码格式来计算每个采样需要多少个字节
const size_t bytesPerSample = audio_bytes_per_sample(format);
//得到最小的缓冲大小
return frameCount * channelCount * bytesPerSample;
} else {
return frameCount;
}
}
点评:帧Frame被用来直观的描述数据量的多少,比如,一帧等于多少字节,在这里,1单位的帧等于1个采样点的字节数x声道数,即2x2=4;
4.我们在回到AudioTrack的构造函数,核心代码如下;
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes, int mode, int sessionId)
throws IllegalArgumentException {
this((new AudioAttributes.Builder())
.setLegacyStreamType(streamType)
.build(),
(new AudioFormat.Builder())
.setChannelMask(channelConfig)
.setEncoding(audioFormat)
.setSampleRate(sampleRateInHz)
.build(),
bufferSizeInBytes,
mode, sessionId);
deprecateStreamTypeForPlayback(streamType, "AudioTrack", "AudioTrack()");
}
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int mode, int sessionId)
throws IllegalArgumentException {
super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
//检查参数是否合法
audioParamCheck(rate, channelMask, channelIndexMask, encoding, mode);
//音频流格式,它在上述的audioParamCheck()中已经完成了转换
mStreamType = AudioSystem.STREAM_DEFAULT;
//检查缓冲区大小
audioBuffSizeCheck(bufferSizeInBytes);
//调用native层的native_setup,进行初始化,我们看下native_setup方法中的几个参数,如mChannelMask,mDataLoadMode等,
//其实就是我们传递过来的channel和mode,只不过是在检查合法性的时候做了一些修改
int initResult = native_setup(new WeakReference(this), mAttributes,
sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/);
if (mDataLoadMode == MODE_STATIC) {
//如果当前的数据加载类型是MODE_STATIC
mState = STATE_NO_STATIC_DATA;
} else {
//如果当前的数据加载类型是非MODE_STATIC,那设置当前状态为初始化状态,这个状态很重要
mState = STATE_INITIALIZED;
}
}
继续来看native_setup()方法,来看下它的核心代码:
代码6:android_media_AudioTrack.cpp
static jint
android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, jobject jaa,
jintArray jSampleRate, jint channelPositionMask, jint channelIndexMask,
jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession,
jlong nativeAudioTrack) {
//声明Native层的AudioTrack对象
sp lpTrack = 0;
//声明Native层的AudioTrackJniStorage对象
AudioTrackJniStorage* lpJniStorage = NULL;
if (nativeAudioTrack == 0) {
//将java层的采样率转化成native层的采样率
int* sampleRates = env->GetIntArrayElements(jSampleRate, NULL);
int sampleRateInHertz = sampleRates[0];
env->ReleaseIntArrayElements(jSampleRate, sampleRates, JNI_ABORT);
//将java层的声道数转化成native层的声道数
audio_channel_mask_t nativeChannelMask = nativeChannelMaskFromJavaChannelMasks(
channelPositionMask, channelIndexMask);
uint32_t channelCount = audio_channel_count_from_out_mask(nativeChannelMask);
//检查编码格式,并转化成native层的编码格式
audio_format_t format = audioFormatToNative(audioFormat);
//计算以帧为单位的缓冲大小
size_t frameCount;
if (audio_is_linear_pcm(format)) {
const size_t bytesPerSample = audio_bytes_per_sample(format);
frameCount = buffSizeInBytes / (channelCount * bytesPerSample);
} else {
frameCount = buffSizeInBytes;
}
//创建native层的AudioTrack对象
lpTrack = new AudioTrack();
//创建AudioTrackJniStorage对象
lpJniStorage = new AudioTrackJniStorage();
lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz);
// we use a weak reference so the AudioTrack object can be garbage collected.
lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this);
lpJniStorage->mCallbackData.busy = false;
//初始化native层的AudioTrack对象
status_t status = NO_ERROR;
switch (memoryMode) {
case MODE_STREAM:
//数据加载模式为STREAM模式
status = lpTrack->set(
AUDIO_STREAM_DEFAULT,//指定流类型
sampleRateInHertz,
format,//采样点精度,即编码格式,一般为PCM16和PCM8
nativeChannelMask,
frameCount,
AUDIO_OUTPUT_FLAG_NONE,
audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)
0,
0,// 共享类型,STREAM模式下为空,实际使用的共享内存有AudioFlinger创建
true,
sessionId,
AudioTrack::TRANSFER_SYNC,
NULL,
-1, -1,
paa);
break;
case MODE_STATIC:
//数据加载模式为STATIC模式,需要先创建共享内存
if (!lpJniStorage->allocSharedMem(buffSizeInBytes)) {
ALOGE("Error creating AudioTrack in static mode: error creating mem heap base");
goto native_init_failure;
}
status = lpTrack->set(
AUDIO_STREAM_DEFAULT,
sampleRateInHertz,
format,
nativeChannelMask,
frameCount,
AUDIO_OUTPUT_FLAG_NONE,
audioCallback, &(lpJniStorage->mCallbackData),
0,
lpJniStorage->mMemBase,//STATIC模式,需要传递共享内存
true,
sessionId,
AudioTrack::TRANSFER_SHARED,
NULL,
-1, -1,
paa);
break;
default:
goto native_init_failure;
}
}
//把JNI层中new出来的AudioTrack对象指针保存到Java对象的一个变量中,这样就把JNI层的AudioTrack对象和Java层的AudioTrack对象关联起来了,
//这就是Android的常用技法。
setAudioTrack(env, thiz, lpTrack);
//lpJniStorage对象指针也保存到Java对象中。
env->SetLongField(thiz, javaAudioTrackFields.jniData, (jlong)lpJniStorage);
return (jint) AUDIO_JAVA_SUCCESS;
}
点评:我们看到,
AudioTrack的构造函数,会将自己的状态更改为已经初始化了的状态,并调用native方法,创建在native层的AudioTrack对象,对它进行初始化,并完成JNI层的AudioTrack对象和Java层的AudioTrack对象的关联;
从上述代码我们还可以看到,在不同的数据加载模式下,AudioTrack对象的创建也会不同,在MODE_STATIC模式下进行数据加载,会用到共享内存;
5.我们刚分析了AudioTrack的构造函数,接下来分下它的play,write,stop,release方法;
①play()方法
AudioTrack.java
public void play() throws IllegalStateException {
//如果AudioTrack之前没有被初始化,被报错
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("play() called on uninitialized AudioTrack.");
}
final int delay = getStartDelayMs();
//是否设置播放延迟
if (delay == 0) {
//没有延迟
startImpl();
} else {
new Thread() {
public void run() {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
baseSetStartDelayMs(0);
try {
startImpl();
} catch (IllegalStateException e) {
}
}
}.start();
}
}
private void startImpl() {
synchronized(mPlayStateLock) {
baseStart();
native_start();
//更改播放状态为:正在播放状态
mPlayState = PLAYSTATE_PLAYING;
}
}
继续进入native方法分析,native_start():
android_media_AudioTrack.cpp
static void android_media_AudioTrack_start(JNIEnv *env, jobject thiz)
{
//从java的AudioTrack对象中获取对应的native层的AudioTrack对象指针
sp lpTrack = getAudioTrack(env, thiz);
//调用AudioTrack的start方法
lpTrack->start();
}
native层AudioTrack的start方法后面再来分析;
②write()方法
AudioTrack.java
public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {
return write(audioData, offsetInBytes, sizeInBytes, WRITE_BLOCKING);
}
public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes,
@WriteMode int writeMode) {
//调用native的native_write_byte方法
int ret = native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat,
writeMode == WRITE_BLOCKING);
//如果当前的数据加载类型是MODE_STATIC,在write后才会将状态设置为初始化,这跟MODE_STREAM类型是不一样的
if ((mDataLoadMode == MODE_STATIC)
&& (mState == STATE_NO_STATIC_DATA)
&& (ret > 0)) {
// benign race with respect to other APIs that read mState
mState = STATE_INITIALIZED;
}
return ret;
}
继续看native层的native_write_byte方法:
android_media_AudioTrack.cpp
static jint android_media_AudioTrack_writeArray(JNIEnv *env, jobject thiz,
T javaAudioData,
jint offsetInSamples, jint sizeInSamples,
jint javaAudioFormat,
jboolean isWriteBlocking) {
sp lpTrack = getAudioTrack(env, thiz);
//调用writeToTrack()方法
jint samplesWritten = writeToTrack(lpTrack, javaAudioFormat, cAudioData,
offsetInSamples, sizeInSamples, isWriteBlocking == JNI_TRUE /* blocking */);
envReleaseArrayElements(env, javaAudioData, cAudioData, 0);
return samplesWritten;
}
static jint writeToTrack(const sp& track, jint audioFormat, const T *data,
jint offsetInSamples, jint sizeInSamples, bool blocking) {
ssize_t written = 0;
size_t sizeInBytes = sizeInSamples * sizeof(T);
//track->sharedBuffer() == 0,表示此时的数据加载是STREAM模式,如果不等于0,那就是STATIC模式
if (track->sharedBuffer() == 0) {
//STREAM模式,调用write写数据
written = track->write(data + offsetInSamples, sizeInBytes, blocking);
if (written == (ssize_t) WOULD_BLOCK) {
written = 0;
}
} else {
if ((size_t)sizeInBytes > track->sharedBuffer()->size()) {
sizeInBytes = track->sharedBuffer()->size();
}
//在STATIC模式下,直接把数据memcpy到共享内存,所以在这种模式下,要先调用write,后调用play
memcpy(track->sharedBuffer()->pointer(), data + offsetInSamples, sizeInBytes);
written = sizeInBytes;
}
if (written >= 0) {
return written / sizeof(T);
}
return interpretWriteSizeError(written);
}
③stop方法
AudioTrack.java
public void stop()
throws IllegalStateException {
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("stop() called on uninitialized AudioTrack.");
}
// stop playing
synchronized(mPlayStateLock) {
//调用native方法
native_stop();
baseStop();
//将播放状态置为设置为停止状态
mPlayState = PLAYSTATE_STOPPED;
mAvSyncHeader = null;
mAvSyncBytesRemaining = 0;
}
}
android_media_AudioTrack.cpp
android_media_AudioTrack_stop(JNIEnv *env, jobject thiz)
{
sp lpTrack = getAudioTrack(env, thiz);
//调用Native层的stop方法
lpTrack->stop();
}
Native层的stop方法后面再分析;
④release方法
AudioTrack.java
public void release() {
try {
//它也会调用stop()
stop();
} catch(IllegalStateException ise) {
}
baseRelease();
//调用native方法
native_release();
//将状态置为未初始化,下次用到AudioTrack时,需要重新创建AudioTrack,才会将状态置为初始化状态,所以,AudioTrack是一次性使用
mState = STATE_UNINITIALIZED;
}
android_media_AudioTrack.cpp
static void android_media_AudioTrack_release(JNIEnv *env, jobject thiz) {
//释放资源
sp lpTrack = setAudioTrack(env, thiz, 0);
// delete the JNI data
AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField(
thiz, javaAudioTrackFields.jniData);
//之前保存在java对象中的指针变量此时要设置为0
env->SetLongField(thiz, javaAudioTrackFields.jniData, 0);
}
点评:java层的AudioTrack分析结束了,java层的AudioTrack只是将工作交给了Native层的AudioTrack;
注意:需要留意下java层AudioTrack中的几个状态,其中mState状态只在AudioTrack被new出来的时候才会被置为STATE_INITIALIZED,而且还要保证此时的数据编码格式为MODE_STREAM,mState状态只有被置为STATE_INITIALIZED后,write,play,stop方法才可以调用,并且在release方法后,mState状态会被置为STATE_UNINITIALIZED,下次要想继续用AudioTrack的话,必须要重新创建它;如果数据编码格式为MODE_STATIC,AudioTrack在调用了write()方法后才会将mState状态置为STATE_INITIALIZED,所以,MODE_STATIC模式下,只能先write,后play,顺序不能乱;
6.Native层的AudioTrack分析
通过上面的分析我们知道,在java层new AudioTrack的时候,会创建出在native层的AudioTrack对象,并且调用该对象的set方法进行初始化,native层的AudioTrack在frameworks\av\media\libaudioclient目录下,我们来分析下Native层的AudioTrack;
AudioTrack的无参构造器:
AudioTrack.cpp
AudioTrack::AudioTrack()
: mStatus(NO_INIT),//把初始状态设置为NO_INIT
mState(STATE_STOPPED),
mPreviousPriority(ANDROID_PRIORITY_NORMAL),
mPreviousSchedulingGroup(SP_DEFAULT),
mPausedPosition(0),
mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE),
mRoutedDeviceId(AUDIO_PORT_HANDLE_NONE),
mPortId(AUDIO_PORT_HANDLE_NONE)
{
mAttributes.content_type = AUDIO_CONTENT_TYPE_UNKNOWN;
mAttributes.usage = AUDIO_USAGE_UNKNOWN;
mAttributes.flags = 0x0;
strcpy(mAttributes.tags, "");
}
AudioTrack的初始化,即set方法:
status_t AudioTrack::set(
audio_stream_type_t streamType,
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
size_t frameCount,
audio_output_flags_t flags,
callback_t cbf,
void* user,
int32_t notificationFrames,
const sp& sharedBuffer,
bool threadCanCallJava,
audio_session_t sessionId,
transfer_type transferType,
const audio_offload_info_t *offloadInfo,
uid_t uid,
pid_t pid,
const audio_attributes_t* pAttributes,
bool doNotReconnect,
float maxRequiredSpeed)
{
..................................................
//cbf是JNI层传入的回调函数audioCallback,如果用户设置了回调函数,则启动一个线程
if (cbf != NULL) {
mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
mAudioTrackThread->run("AudioTrack", ANDROID_PRIORITY_AUDIO, 0 /*stack*/);
}
....................................
//调用createTrack_l
status_t status = createTrack_l();
....................................
return NO_ERROR;
}
set()方法很长,大部分语句是用来赋值的,核心代码是 status_t status = createTrack_l();
我们直接来看这个方法:
status_t AudioTrack::createTrack_l()
{
//得到AudioFlinger的Binder代理端BpAudioFlinger;
const sp& audioFlinger = AudioSystem::get_audio_flinger();
//向audioFlinger发送createTrack请求,返回IAudioTrack对象,后续AudioFlinger和AudioTrack的交互就是围绕着IAudioTrack进行
sp track = audioFlinger->createTrack(streamType,
mSampleRate,
mFormat,
mChannelMask,
&temp,
&flags,
mSharedBuffer,
output,
mClientPid,
tid,
&mSessionId,
mClientUid,
&status,
mPortId);
//在STREAM模式下,没有在AudioTrack端创建共享内存,但AudioTrack和AudioFlinger的数据交互是通过共享内存完成的,这块共享内存最终由AudioFlinger创建;
//下面这个方法会取出AudioFlinger创建的共享内存
sp iMem = track->getCblk();
//IMemory的Pointer在此处将返回共享内存的首地址,类型为void *
void *iMemPointer = iMem->pointer();
//static_cast直接把这个void *类型转换成audio_track_cblk_t,表明这块内存的首部中存在audio_track_cblk_t这个对象
audio_track_cblk_t* cblk = static_cast(iMemPointer);
mCblk = cblk;
void* buffers;
if (mSharedBuffer == 0) {
//buffers指向数据空间,它的起始位置是共享内存的首部加上audio_track_cblk_t的大小
buffers = cblk + 1;
} else {
//STATIC模式下的处理
buffers = mSharedBuffer->pointer();
}
return NO_ERROR;
}
点评:createTrack_l()方法也很长,核心代码是:audioFlinger->createTrack(),返回一个IAudioTrack对象;从上面的代码我们知道,IAudioTrack中有一块共享内存,其头部是一个audio_track_cblk_t对象,在该对象之后才是数据缓冲;
关于这个audio_track_cblk_t对象,它的声明在在AudioTrackShared.h中,定义在AudioTrack.cpp中,它的逻辑很复杂,这里就不详细分析,就说下它的作用:它是用来协调和管理AudioTrack和AudioFlinger二者数据生产和消费的步伐,举个例子,音频输入设备采用环形缓冲的方式管理,当生产者没有及时提供数据时,输出设备就会循环使用缓冲中的数据,这样就会听到一段重复的声音,所以,这里需要用到同步,这就是audio_track_cblk_t对象的作用;
7.在上面的代码中,我们看到有这么一句:
mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
AudioTrackThread有什么用呢?
这个线程与外界数据的输入方式有关系,AudioTrack支持两种数据输入方式:
Push方式:用户主动调用write写数据,这相当于数据被push到AudioTrack;
Pull方式:在JNI层的代码中可以发现,在构造AudioTrack时,传入了一个回调函数audioCallback,pull方式就是AudioTrackThread调用这个回调函数主动从用户那里pull数据,它的具体代码有机会在分析;
8.write方法在AudioTrack.cpp中的实现
write函数涉及Audio系统中最重要的问题,即数据是如何传输的;
我们直接来看代码:
ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking)
{
//Buffer是一个辅助性的结构
Buffer audioBuffer;
while (userSize >= mFrameSize) {
//以帧为单位
audioBuffer.frameCount = userSize / mFrameSize;
//从共享内存中得到一块空闲的数据块
status_t err = obtainBuffer(&audioBuffer,
blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
//空闲数据缓冲区的大小是audioBuffer.size,地址在
size_t toWrite = audioBuffer.size;
//地址在audioBuffer.i8中,数据传递通过memcpy来完成
memcpy(audioBuffer.i8, buffer, toWrite);
buffer = ((const char *) buffer) + toWrite;
userSize -= toWrite;
written += toWrite;
//更新写位置,同时会触发消费者
releaseBuffer(&audioBuffer);
}
if (written > 0) {
mFramesWritten += written / mFrameSize;
}
return written;
}
点评:通过write函数,发现数据的传递其实是很简单的memcpy,但生产者和消费者协调,则是通过obtainBuffer和releaseBuffer来完成的;
obtainBuffer和releaseBuffer的代码我就不粘贴了,就说下他们的功能:
obtainBuffer的功能,就是从CB管理的数据缓冲中得到一块可写空间,如果没有的话,那就等,而releaseBuffer,则是在使用完这块空间后更新写指针的位置;
9.stop方法在AudioTrack.cpp中的实现
void AudioTrack::stop()
{
//mAudioTrack是IAudioTrack类型,那其stop的最终处理也在AudioFlinger端
mAudioTrack->stop();
sp t = mAudioTrackThread;
if (t != 0) {
if (!isOffloaded_l()) {
//请求停止AudioTrackThread
t->pause();
}
} else {
setpriority(PRIO_PROCESS, 0, mPreviousPriority);
set_sched_policy(0, mPreviousSchedulingGroup);
}
}
点评:stop的工作比较简单,就是调用IAudioTrack的stop,并且还要求退出回调线程。
10.播放音频在AudioTrack.cpp中的实现
status_t AudioTrack::start()
{
status = mAudioTrack->start();
}
我们看到,播放操作还是交给了IAudioTrack,也就是AudioFlinger端;
10.我们分析完了AudioTrack,来总结一下吧:
①java层的AudioTrack只是上层的api,我们对java层AudioTrack的api调用,实际都会传递到native层的AudioTrack去执行;
②native层的AudioTrack在初始化的时候,会跨进程的调用AudioFlinger的createTrack方法,并返回一个IAudioTrack的对象,native层的AudioTrack以及AudioFlinger借此对象进行交互;
③得到IAudioTrack对象以后,native层AudioTrack的start(),stop等方法的最终操作会传递给AudioFlinger去执行;
④在native层的AudioTrack中将数据write进内存,AudioFlinger通过内存共享与AudioTrack共享同一块资源,从而完成数据的传递;