Android 9.0 AudioRecord代码分析

简介

打算开始写一些Android代码的流程分析,加深一些记忆,理清一些细节,如果刚好能帮助到你,那就更加好了。手头有的最新的Android代码,就是9.0,所以以此为基准。
为什么从AudioRecord开始?
一是这个接口的修改新旧Android版本的差别不大。
二是它要相对简单一些。
所以作为一个开始来分析代码,其中也有一些细节,比如IBinder/Sharememory等,后面再单独开一篇文章分析代码细节。

Java 接口及用法

构造函数:

   /**
     * Class constructor.
     * Though some invalid parameters will result in an {@link IllegalArgumentException} exception,
     * other errors do not.  Thus you should call {@link #getState()} immediately after construction
     * to confirm that the object is usable.
     * @param audioSource the recording source.
     *   See {@link MediaRecorder.AudioSource} for the recording source definitions.
     * @param sampleRateInHz the sample rate expressed in Hertz. 44100Hz is currently the only
     *   rate that is guaranteed to work on all devices, but other rates such as 22050,
     *   16000, and 11025 may work on some devices.
     *   {@link AudioFormat#SAMPLE_RATE_UNSPECIFIED} means to use a route-dependent value
     *   which is usually the sample rate of the source.
     *   {@link #getSampleRate()} can be used to retrieve the actual sample rate chosen.
     * @param channelConfig describes the configuration of the audio channels.
     *   See {@link AudioFormat#CHANNEL_IN_MONO} and
     *   {@link AudioFormat#CHANNEL_IN_STEREO}.  {@link AudioFormat#CHANNEL_IN_MONO} is guaranteed
     *   to work on all devices.
     * @param audioFormat the format in which the audio data is to be returned.
     *   See {@link AudioFormat#ENCODING_PCM_8BIT}, {@link AudioFormat#ENCODING_PCM_16BIT},
     *   and {@link AudioFormat#ENCODING_PCM_FLOAT}.
     * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is written
     *   to during the recording. New audio data can be read from this buffer in smaller chunks
     *   than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum
     *   required buffer size for the successful creation of an AudioRecord instance. Using values
     *   smaller than getMinBufferSize() will result in an initialization failure.
     * @throws java.lang.IllegalArgumentException
     */
    public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
            int bufferSizeInBytes)

其中

  1. audioSource,定义在MediaRecorder.AudioSource,有很多种不同的source,底层的AudioFlinger会根据不同的source做一些不同的处理,比如VOICE_COMMUNICATION,需要进行回声消除的处理,一般只是想录音的话,选择AudioSource.MIC即可,或者选择AudioSource.DEFAULT,底层一般也是MIC。
  2. sampleRateInHz,音频采样率,设置一个需要的值,看抓到的音频数据要做什么使用,44100一般用作高品质录音,常规设置16000就可以满足使用,当然后续读取PCM数据量的大小与这里设置的采样率大小是正相关的
  3. channelConfig: AudioFormat.CHANNEL_IN_STEREO 和 AudioFormat.CHANNEL_IN_MONO,就是双声道和单声道,看需要设置
  4. audioFormat:表示一个PCM采样点的位数,8位/16位/32位,ENCODING_PCM_8BIT/ENCODING_PCM_16BIT/ENCODING_PCM_FLOAT,一般使用16位
  5. bufferSizeInBytes:设置底层obtainBuffer/releaseBuffer的缓冲区大小,作为从应用的进程和AudioServer的进程之间共享内存的大小,分配的函数在: AudioFlinger::ThreadBase::TrackBase::TrackBase,一般读取AudioRecord.getMinBufferSize,设置为最小buffersize即可,这个minbuffersize已经包含了两个buffer的pingpong机制,可以满足要求。
    在这里插入图片描述
    跟着Android的代码进去看了一下这个bufferSizeInBytes的用法,需要收回之前AudioRecord比较简单是说法了,代码也是极其复杂的。

read函数

这里我们重点分析一下read函数,看下数据是如何从驱动,一路上到APK的,流程会很长。

    public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {
        return read(audioData, offsetInBytes, sizeInBytes, READ_BLOCKING);
    }
READ_BLOCKING/READ_NON_BLOCKING 这里可以选择block或者nonbblock的方式,大部分我想应该是BLOCK的,应用方便处理,而且音频是流式的,理论上不会阻塞。
    private native final int native_read_in_byte_array(byte[] audioData,
            int offsetInBytes, int sizeInBytes, boolean isBlocking);

上面还是在java里面,下面开始进到native代码里面,这里欠一篇java和jni的调用的分析

frameworks/base/core/jni/android_media_AudioRecord.cpp
template <typename T>
static jint android_media_AudioRecord_readInArray(JNIEnv *env,  jobject thiz,
                                                  T javaAudioData,
                                                  jint offsetInSamples, jint sizeInSamples,
                                                  jboolean isReadBlocking) {

frameworks/av/media/libaudioclient/AudioRecord.cpp
ssize_t AudioRecord::read(void* buffer, size_t userSize, bool blocking)

...... 省略部分不重要的代码
        status_t err = obtainBuffer(&audioBuffer,
                blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
......
        size_t bytesRead = audioBuffer.size;
        memcpy(buffer, audioBuffer.i8, bytesRead);
......
这里就是从audioBuffer里面将数据复制到用户的buffer里面,等下返回给APK,那么audioBuffer又是什么呢?

status_t AudioRecord::obtainBuffer(Buffer* audioBuffer, const struct timespec *requested,
        struct timespec *elapsed, size_t *nonContig)
                status = proxy->obtainBuffer(&buffer, requested, elapsed);
                audioBuffer->raw = buffer.mRaw;

在这里插入图片描述
注意这里Buffer的定义比较乱,很多结构体里都有这个定义,Google也发现了这个问题,要统一起来,而且还有一个union在里面,为了支持8bit/16bit录音的使用,所以这个audioBuffer->raw与前面的audioBuffer->i8实际上是一个地址。

所以上面的问题pcm地址audioBuffer->raw = buffer.mRaw;
其中buffer是Proxy::Buffer buffer;
sp<AudioRecordClientProxy> proxy = mProxy;
status = proxy->obtainBuffer(&buffer, requested, elapsed);
变成了这个buffer是怎么来的,这里要返回去看这个mProxy是什么?
mProxy = new AudioRecordClientProxy(cblk, buffers, mFrameCount, mFrameSize);
其中比较重要的参数是cblk和buffers,,再继续往前追一下,这两个变量的出处。

    iMemPointer = output.cblk ->pointer();
    cblk = static_cast<audio_track_cblk_t*>(iMemPointer);
    buffers = output.buffers->pointer();
    mProxy = new AudioRecordClientProxy(cblk, buffers, mFrameCount, mFrameSize);
可以看出这个AudioRecordClientProxy,只是方便调用cblk和buffers的Proxy,提供了obtainBuffer的接口,来从真正的共享buffer里面将获得数据,这里面的细节不再追进去了,实际上是通过audio_track_cblk_t的一个共享内存,采用read/write指针的方法(mFront/mRear)来看有多少avail的数据,然后获取指针出来。

重点是output的出处
status_t AudioRecord::createRecord_l(const Modulo<uint32_t> &epoch, const String16& opPackageName)
record = audioFlinger->createRecord(input,
                                                              output,
                                                              &status);
input里面包括的是我们想设置的参数,output里面是AudioServer实际设置的参数,已经分配的共享内存。

问题的重点是前面的cblk和buffers是什么???

上面还是在APK的进程里面,下面开始进到AudioServer的进程里面了,尴尬不,这里又欠一篇IBinder跨进程调用的分析

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

class IAudioFlinger : public IInterface
virtual sp<media::IAudioRecord> createRecord(const CreateRecordInput& input,
                                        CreateRecordOutput& output,
                                        status_t *status) = 0;

sp<media::IAudioRecord> AudioFlinger::createRecord(const CreateRecordInput& input,
                                                   CreateRecordOutput& output,
                                                   status_t *status)
	//上来先把我们最重要的变量clear一下
    output.cblk.clear();
    output.buffers.clear();
    //从recordTrack当中得到这个共享内存
    output.cblk = recordTrack->getCblk();
    output.buffers = recordTrack->getBuffers();

再看这个recordTrack,是一个RecordThread创建出来的
recordTrack = thread->createRecordTrack_l(client, input.attr, &output.sampleRate,
                                                  input.config.format, input.config.channel_mask,
                                                  &output.frameCount, sessionId,
                                                  &output.notificationFrameCount,
                                                  clientUid, &output.flags,
                                                  input.clientInfo.clientTid,
                                                  &lStatus, portId);
传了一大堆参数进去,细节不深究,那么getCblk得到了什么?
class RecordTrack : public TrackBase {
class TrackBase : public ExtendedAudioBufferProvider, public RefBase {
            sp<IMemory> getCblk() const { return mCblkMemory; }
        mCblkMemory = client->heap()->allocate(size);

sp<MemoryDealer> AudioFlinger::Client::heap() const
{
    return mMemoryDealer;
}

mMemoryDealer = new MemoryDealer(
            audioFlinger->getClientSharedHeapSize(),
            (std::string("AudioFlinger::Client(") + std::to_string(pid) + ")").c_str());
getCblk实际得到了一个heap的内存,这个内存分配在AudioServer里面,write在AudioServer里面,read在AudioRecord里面,也就是在应用APK的进程空间。

下面要看下,数据如何从HAL层到mMemoryDealer。

RecordThread -> RecordTrack
bool AudioFlinger::RecordThread::threadLoop()
	status_t result = mInput->stream->read(
                    (uint8_t*)mRsmpInBuffer + rear * mFrameSize, mBufferSize, &bytesRead);
	(void)posix_memalign(&mRsmpInBuffer, 32, mRsmpInFramesOA * mFrameSize);
	
	从HAL层里面将数据读到这个mRsmpInBuffer,名义上看是resample用的,因为HAL层出来的采样率与上层需要的不一定一样,所以需要一个resample的过程,这个buffer里面有可能是44100hz的pcm数据。
            // loop over each active track
        for (size_t i = 0; i < size; i++) {
            activeTrack = activeTracks[i];
            status_t status = activeTrack->getNextBuffer(&activeTrack->mSink);
一个RecordThread里面可以有几个active的RecordTrack,将mRsmpInBuffer的数据针对不同track的需要resample成不同的PCM数据。

status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer)
{
    ServerProxy::Buffer buf;
    buf.mFrameCount = buffer->frameCount;
    status_t status = mServerProxy->obtainBuffer(&buf);  //还记得前面说的ClientProxy了吗,这里对应到了ServerProxy,是同一个buffer没错了。
    buffer->frameCount = buf.mFrameCount;
    buffer->raw = buf.mRaw;
    if (buf.mFrameCount == 0) {
        // FIXME also wake futex so that overrun is noticed more quickly
        (void) android_atomic_or(CBLK_OVERRUN, &mCblk->mFlags);
    }
    return status;
}

好了,这里彻底对上了,将mResamplerBufferProvider里面的原始pcm数据,通过mRecordBufferConverter::convert转换成需要的samplerate。
framesOut = activeTrack->mRecordBufferConverter->convert(
       activeTrack->mSink.raw, activeTrack->mResamplerBufferProvider, framesOut);

再看一下这个converter
    mRecordBufferConverter = new RecordBufferConverter(
            thread->mChannelMask, thread->mFormat, thread->mSampleRate,
            channelMask, format, sampleRate);
很清楚,就是从RecordThread的格式转换为RecordTrack的格式,细节不再进去追了,只是一些实现细节。

到这里以后,从AudioServer到APK的数据流已经清楚了,就是从HAL层到这个mMemoryDealer的共享内存,然后通过mServerProxy和AudioRecordClientProxy对这个共享内存的跨进程同步,复制到AudioRecord的audioBuffer当中,然后复制到Java层。

其他接口

AudioRecord.startRecording();
AudioRecord.stop();
控制开始和结束AudioRecord的API,必须使用

AudioRecord.setNotificationMarkerPosition
AudioRecord.setPositionNotificationPeriod
AudioRecord.setRecordPositionUpdateListener
看起来是可以周期性的notify一下app,不确定什么场景,难道APK自己计数一下read到的数据长度不行?非得回调?

AudioRecord.getTimestamp
获取当前录制音频的时间戳

AudioRecord用法samplecode

从Android的cts里面复制出来的代码,cts是一个理解API很好的代码库

mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, mHz,
                            AudioFormat.CHANNEL_CONFIGURATION_MONO,
                            AudioFormat.ENCODING_PCM_16BIT,
                            AudioRecord.getMinBufferSize(mHz,
                                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                                    AudioFormat.ENCODING_PCM_16BIT) * 10);
mAudioRecord.startRecording();
while (System.currentTimeMillis() - time < RECORD_TIME) {
    Thread.sleep(SLEEP_TIME);
     mAudioRecord.read(byteData, 0, BUFFER_SIZE);   //只是samplecode, 可以读到数据,一般应用不会这样写
}
mAudioRecord.stop();

后记

大致分析了一下AudioRecord的数据流程,不涉及到HAL层的代码,因为各家的实现会有不同,里面的细节很多,包括IBinder调用/cblk的共享内存/JNI的调用都还没有详细分析,留作后面的功课。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值