Android音频录制:AudioRecord.read()接口深度解析

接着上两篇文章讲到的,

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函数的处理逻辑。
        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值