接着上两篇文章讲到的,
Android音频录制核心:RecordThread::threadLoop解析-优快云博客、
Android 音频录制核心:ServerProxy::obtainBuffer揭秘-优快云博客
音频数据被写到了AudioRecord的第二个环形缓冲区中,那Android 应用是如何拿到想要的音频数据呢?接下来将逐一分解。
首先,我们都知道AudioRecord要实现音频的录制,需要完成以下步骤:
1.创建一个 AudioRecord 对象,需要指定采样率、音频格式、声道数和缓冲区大小。
2.通过 startRecording() 方法开始录制。
3.在一个循环中使用 read() 方法从缓冲区读取录制的数据。
4.循环结束后调用 stop() 方法停止录制。
5.调用 release() 方法释放资源。
如下代码示例
// 配置AudioRecord参数
int sampleRateInHz = 44100; // 采样率
int channelConfig = AudioFormat.CHANNEL_IN_MONO; // 声道配置
int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 音频格式
int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
// 创建并初始化AudioRecord对象
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);
// 开始录制
audioRecord.startRecording();
// 在循环中读取数据
byte[] audioData = new byte[bufferSizeInBytes];
int readSize = audioRecord.read(audioData, 0, audioData.length);
// 停止录制并释放资源
audioRecord.stop();
audioRecord.release();
本文的重点就是代码中read接口如何拿到audioData的,下面是代码的追踪和分析。
代码路径:frameworks/base/media/java/android/media/AudioRecord.java
根据read()调用的传参重载了多个read函数的定义
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes)
public int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts)
public int read(@NonNull float[] audioData, int offsetInFloats, int sizeInFloats,@ReadMode int readMode)
public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes)
我们以public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes, @ReadMode int readMode)举例讲解:
public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes, @ReadMode int readMode) {
// 1. 检查AudioRecord对象的状态,只有STATE_INITIALIZED状态下才能正常读取,否则返回错误
if (mState != STATE_INITIALIZED) {
return ERROR_INVALID_OPERATION;
}
// 2. 检查readMode参数是否合法,只能是READ_BLOCKING或READ_NON_BLOCKING
if ((readMode != READ_BLOCKING) && (readMode != READ_NON_BLOCKING)) {
Log.e(TAG, "AudioRecord.read() called with invalid blocking mode");
return ERROR_BAD_VALUE;
}
// 3. 检查audioBuffer是否为null,以及sizeInBytes是否小于0
if ( (audioBuffer == null) || (sizeInBytes < 0) ) {
return ERROR_BAD_VALUE;
}
// 4. 调用native层的native_read_in_direct_buffer实际执行读取
return native_read_in_direct_buffer(audioBuffer, sizeInBytes, readMode == READ_BLOCKING);
}
本质上是直接调用JNI 函数native_read_in_direct_buffer;
代码路径:frameworks/base/core/jni/android_media_AudioRecord.cpp
//JNI 映射,把 Java 方法和 Native 实现函数关联起来
{"native_read_in_direct_buffer", "(Ljava/lang/Object;IZ)I",
(void *)android_media_AudioRecord_readInDirectBuffer},
static jint android_media_AudioRecord_readInDirectBuffer(JNIEnv *env, jobject thiz,
jobject jBuffer, jint sizeInBytes,
jboolean isReadBlocking) {
//获取底层AudioRecord对象
//通过`getAudioRecord`函数,从Java对象`thiz`中获取native的`AudioRecord`智能指针。
//如果没取到(比如还没初始化),返回错误码`AUDIO_JAVA_INVALID_OPERATION`。
sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
if (lpRecorder==NULL)
return (jint)AUDIO_JAVA_INVALID_OPERATION;
//检查Direct Buffer能力
//调用JNI接口`GetDirectBufferCapacity`,判断传下来的`jBuffer`是否为Direct Buffer(直接内存)
//如果返回-1,说明不是Direct Buffer或者不支持直接访问,返回错误码`AUDIO_JAVA_BAD_VALUE`
long capacity = env->GetDirectBufferCapacity(jBuffer);
if (capacity == -1) {
// buffer direct access is not supported
ALOGE("Buffer direct access is not supported, can't record");
return (jint)AUDIO_JAVA_BAD_VALUE;
}
//ALOGV("capacity = %ld", capacity);
//获得Direct Buffer的指针
//用JNI接口`GetDirectBufferAddress`获取buffer在native内存中的指针
//如果失败(返回NULL),同样报错并返回
jbyte* nativeFromJavaBuf = (jbyte*) env->GetDirectBufferAddress(jBuffer);
if (nativeFromJavaBuf==NULL) {
ALOGE("Buffer direct access is not supported, can't record");
return (jint)AUDIO_JAVA_BAD_VALUE;
}
//读取音频数据
//调用native层的`AudioRecord`的`read`方法,从音频设备读取数据到`nativeFromJavaBuf`指向的内存。
//读取的长度是 buffer 的容量和 sizeInBytes 中较小的一个,防止溢出
//第三个参数为是否阻塞(根据 isReadBlocking 判断)
ssize_t readSize = lpRecorder->read(nativeFromJavaBuf,
capacity < sizeInBytes ? capacity : sizeInBytes,
isReadBlocking == JNI_TRUE /* blocking */);
if (readSize < 0) {
return interpretReadSizeError(readSize);
}
return (jint)readSize;
}
这段代码的lpRecorder->read会执行到C++层AudioRecord的read函数
代码路径:frameworks/av/media/libaudioclient/AudioRecord.cpp
ssize_t AudioRecord::read(void* buffer, size_t userSize, bool blocking)
{
//检查传输模式:只允许同步(`TRANSFER_SYNC`)模式下使用该读取接口。如果不是同步模式,返回`INVALID_OPERATION`错误码。
if (mTransfer != TRANSFER_SYNC) {
return INVALID_OPERATION;
}
//参数有效性检查:检查`userSize`是否为负,或者`buffer`为NULL但`userSize`不为0,这都是无效参数,返回`BAD_VALUE`。
if (ssize_t(userSize) < 0 || (buffer == NULL && userSize != 0)) {
// Validation. user is most-likely passing an error code, and it would
// make the return value ambiguous (actualSize vs error).
ALOGE("%s(%d) (buffer=%p, size=%zu (%zu)",
__func__, mPortId, buffer, userSize, userSize);
return BAD_VALUE;
}
//循环读取音频数据
//初始化实际读取字节数`read`。
//循环读取,每次最少要读一个音频帧(`mFrameSize`)
//audioBuffer.frameCount设置本轮准备读取的帧数
//调用`obtainBuffer`从底层音频缓冲区获取数据块,方式为阻塞或非阻塞
ssize_t read = 0;
Buffer audioBuffer;
while (userSize >= mFrameSize) {
audioBuffer.frameCount = userSize / mFrameSize;
status_t err = obtainBuffer(&audioBuffer,
blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
//处理obtainBuffer返回值
//如果`obtainBuffer`返回错误,且已经有数据读取过了(`read > 0`),则跳出循环,返回已读数据。
//如果没有数据读取,直接返回错误码。
//某些错误(如超时、被中断)转为`WOULD_BLOCK`。
if (err < 0) {
if (read > 0) {
break;
}
if (err == TIMED_OUT || err == -EINTR) {
err = WOULD_BLOCK;
}
return ssize_t(err);
}
size_t bytesRead = audioBuffer.frameCount * mFrameSize;
//拷贝数据
//计算实际读取的字节数。
//如果是线性PCM格式,用`memcpy_by_audio_format`做格式转换拷贝。
//否则直接`memcpy`。
//更新buffer指针和剩余userSize,累计读取总字节数。
//释放已获取的底层缓冲区(`releaseBuffer`)
if (audio_is_linear_pcm(mFormat)) {
memcpy_by_audio_format(buffer, mFormat, audioBuffer.raw, mServerConfig.format,
audioBuffer.mSize / mServerSampleSize);
} else {
memcpy(buffer, audioBuffer.raw, audioBuffer.mSize);
}
buffer = ((char *) buffer) + bytesRead;
userSize -= bytesRead;
read += bytesRead;
releaseBuffer(&audioBuffer);
}
//统计和返回
//成功读取后,更新内部累计帧计数。
//返回实际读取到的字节数。
if (read > 0) {
mFramesRead += read / mFrameSize;
// mFramesReadTime = systemTime(SYSTEM_TIME_MONOTONIC); // not provided at this time.
}
return read;
}
总结如上的函数调用链:
应用层(Java)→ Framework(Java)→ JNI → native_read_in_direct_buffer(C++)→ AudioRecord::read(C++)
这是Android最经典的调用链,深入理解后再看其他模块的学习将会更顺利。
调用的obtainBuffer 接口是 Android 音频系统(AudioFlinger/AudioRecord)中的核心函数之一,负责在应用层(比如录音App)请求音频数据时,从音频共享内存缓冲区(环形缓冲区)中获得一个可用缓冲区用于读数据。下面解析这里obtainBuffer的设计与逻辑。
status_t AudioRecord::obtainBuffer(Buffer* audioBuffer, int32_t waitCount, size_t *nonContig)
{
//参数检查
//如果 audioBuffer 为 NULL,返回 BAD_VALUE。
//如果 mTransfer 不是 TRANSFER_OBTAIN,说明不是通过 obtainBuffer 方式传输,置 audioBuffer 全部为 0 并返回 INVALID_OPERATION。
if (audioBuffer == NULL) {
if (nonContig != NULL) {
*nonContig = 0;
}
return BAD_VALUE;
}
if (mTransfer != TRANSFER_OBTAIN) {
audioBuffer->frameCount = 0;
audioBuffer->mSize = 0;
audioBuffer->raw = NULL;
if (nonContig != NULL) {
*nonContig = 0;
}
return INVALID_OPERATION;
}
//等待策略选择
//根据 waitCount 设置等待模式:
//-1:阻塞(永远等待),用 `ClientProxy::kForever`。
//0:非阻塞,立即返回,用 `ClientProxy::kNonBlocking`。
//>0:等待一段时间(waitCount × WAIT_PERIOD_MS 毫秒),构造 timespec 超时时间。
//其他:非法参数,打印日志,requested=NULL。
const struct timespec *requested;
struct timespec timeout;
if (waitCount == -1) {
requested = &ClientProxy::kForever;
} else if (waitCount == 0) {
requested = &ClientProxy::kNonBlocking;
} else if (waitCount > 0) {
time_t ms = WAIT_PERIOD_MS * (time_t) waitCount;
timeout.tv_sec = ms / 1000;
timeout.tv_nsec = (long) (ms % 1000) * 1000000;
requested = &timeout;
} else {
ALOGE("%s(%d): invalid waitCount %d", __func__, mPortId, waitCount);
requested = NULL;
}
//调用底层obtainBuffer
//调用另一个 obtainBuffer(带 timespec),把等待策略传进去。
return obtainBuffer(audioBuffer, requested, NULL /*elapsed*/, nonContig);
}
接着调用下一层obtainBuffer:
status_t AudioRecord::obtainBuffer(Buffer* audioBuffer, const struct timespec *requested,
struct timespec *elapsed, size_t *nonContig)
{
//初始化变量
//oldSequence`:用于检测音轨(track)是否被重建。
//`Proxy::Buffer buffer`:实际操作的底层 buffer 结构
//`tryCounter`:最多重试 5 次(比如遇到 DEAD_OBJECT 时)
uint32_t oldSequence = 0;
Proxy::Buffer buffer;
status_t status = NO_ERROR;
static const int32_t kMaxTries = 5;
int32_t tryCounter = kMaxTries;
//循环尝试获取 buffer
//目的是在 media server 死亡(DEAD_OBJECT)时允许重试,避免立刻失败。
do {
// obtainBuffer() is called with mutex unlocked, so keep extra references to these fields to
// keep them from going away if another thread re-creates the track during obtainBuffer()
sp<AudioRecordClientProxy> proxy;
sp<IMemory> iMem;
sp<IMemory> bufferMem;
{
//加锁,处理恢复与引用保存
//加锁保护 mProxy 等成员。
//如果上一次状态是 DEAD_OBJECT,且 mSequence 没变,则尝试调用 restoreRecord_l() 重建音轨(仅线性PCM允许自动重建,压缩音频直接失败)。
//取出 mProxy、mCblkMemory、mBufferMemory 的引用,防止指针失效。
//如果音轨未激活(未 start),强制切换到非阻塞模式(即使传进来是阻塞)。
AutoMutex lock(mLock);
if (status == DEAD_OBJECT) {
// re-create track, unless someone else has already done so
if (mSequence == oldSequence) {
if (!audio_is_linear_pcm(mFormat)) {
// If compressed capture, don't attempt to restore the track.
// Return a DEAD_OBJECT error and let the caller recreate.
tryCounter = 0;
} else {
status = restoreRecord_l("obtainBuffer");
}
if (status != NO_ERROR) {
buffer.mFrameCount = 0;
buffer.mRaw = NULL;
buffer.mNonContig = 0;
break;
}
}
}
oldSequence = mSequence;
// Keep the extra references
proxy = mProxy;
iMem = mCblkMemory;
bufferMem = mBufferMemory;
// Non-blocking if track is stopped
if (!mActive) {
requested = &ClientProxy::kNonBlocking;
}
} // end of lock scope
//真正获取 buffer
//设置 buffer.mFrameCount = 请求帧数。
//调用 proxy->obtainBuffer() 实际从 audio shared memory 申请缓冲区。如果返回 DEAD_OBJECT,则重试。
buffer.mFrameCount = audioBuffer->frameCount;
// FIXME starts the requested timeout and elapsed over from scratch
status = proxy->obtainBuffer(&buffer, requested, elapsed);
} while ((status == DEAD_OBJECT) && (tryCounter-- > 0));
//写回音频缓冲区信息
//把底层 buffer 信息写回 audioBuffer。
//计算可用字节数 audioBuffer->mSize。
//填充 raw 指针(实际可读数据的地址)。
//填写 sequence。
//填写 nonContig(如果有)。
audioBuffer->frameCount = buffer.mFrameCount;
audioBuffer->mSize = buffer.mFrameCount * mServerFrameSize;
audioBuffer->raw = buffer.mRaw;
audioBuffer->sequence = oldSequence;
if (nonContig != NULL) {
*nonContig = buffer.mNonContig;
}
return status;
}
这个函数中用到的关键点与原理
1.阻塞/非阻塞/超时:通过 timespec 等控制等待策略,满足不同读取需求。
2.DEAD_OBJECT 恢复机制:如果音轨被异常销毁,允许自动重建,增强健壮性。
3.多线程安全:通过锁保护重要成员,防止并发失效。
4.环形缓冲区共享:proxy->obtainBuffer() 的本质,是和 AudioFlinger 进程通过共享内存环形缓冲区交互,获取可用的音频数据块。
5.格式与大小适配:返回的 buffer 结构,包含可用帧数、字节数、数据指针等,供上层 read() 使用。
proxy->obtainBuffer()的执行,首先proxy=mProxy,mProxy是在创建并初始化AudioRecord对象的流程中初始化出来的,如下嗲用关系:
status_t AudioRecord::createRecord_l(const Modulo<uint32_t> &epoch)
-->mProxy = new AudioRecordClientProxy(cblk, buffers, mFrameCount, mServerFrameSize);
我们再看看AudioRecordClientProxy类的定义
代码路径:frameworks/av/include/private/media/AudioTrackShared.h
// Proxy used by AudioRecord client
class AudioRecordClientProxy : public ClientProxy {
public:
AudioRecordClientProxy(audio_track_cblk_t* cblk, void *buffers, size_t frameCount,
size_t frameSize)
: ClientProxy(cblk, buffers, frameCount, frameSize,
false /*isOut*/, false /*clientInServer*/) { }
~AudioRecordClientProxy() { }
// Advances the client read pointer to the server write head pointer
// effectively flushing the client read buffer. The effect is
// instantaneous. Returns the number of frames flushed.
uint32_t flush() {
int32_t rear = android_atomic_acquire_load(&mCblk->u.mStreaming.mRear);
int32_t front = mCblk->u.mStreaming.mFront;
android_atomic_release_store(rear, &mCblk->u.mStreaming.mFront);
return (Modulo<int32_t>(rear) - front).unsignedValue();
}
};
AudioRecordClientProxy类是 Android 音频系统底层用于音频采集(录音)时,客户端与共享内存环形缓冲区交互的代理类,它继承自 ClientProxy,ClientProxy实现了与 AudioFlinger 进程共享内存的通用操作,AudioRecordClientProxy 是专用于录音场景的。
构造函数参数
audio_track_cblk_t* cblk:指向共享内存头部控制块(cblk,控制环形缓冲区各指针和状态),还记得上文中环形缓冲区也是同时这个写入音频数据的,在这里通过这个读取,环形缓冲区的写入和读取就关联起来了。
void *buffers:指向实际存储音频数据的缓冲区。
frameCount 和 frameSize:缓冲区的帧数和每帧大小。
构造函数中,isOut=false 表示本代理是输入方向(录音)。
proxy->obtainBuffer函数调用了基类ClientProxy的成员函数。
代码路径:frameworks/av/media/libaudioclient/AudioTrackShared.cpp
status_t ClientProxy::obtainBuffer(Buffer* buffer, const struct timespec *requested, struct timespec *elapsed)
下一篇将继续讲解ClientProxy::obtainBuffer函数的处理逻辑。
2147

被折叠的 条评论
为什么被折叠?



