【Android】AudioRecord::getMinBufferSize源码分析

Android录制音频,一般会使用AudioRecord。今天我们来分析一下AudioRecord的源代码,看看它是怎么完成音频录制的。本文的代码基于Android 9.0。

AudioRecord的简单使用

使用AudioRecord录制音频,主要包含以下几个步骤:

  1. 首先要创建AudioRecord的对象,通过构造函数public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)。AudioRecord还有其它构造函数,我们这里以这个构造函数为例。这里,audioSource指定音频源,音频源在MediaRecorder.AudioSorce中定义,可定时MIC,VOICE_COMMUNICATION等。具体可以查阅AudioSource的源码。sampleRateInHz指定采样频率,一般采用44100Hz保证在所有设备上都可以正常工作。channelConfig指定声道,这在AudioFormat中定义,可以是CHANNEL_IN_MONO(单声道),CHANNEL_IN_STEREO(立体声)等。audioFormat质地感音频格式,即采样深度,可以是ENCODING_PCM_8BIT, ENCODING_PCM_16BIT等,定义在AudioFormat中。最后一个参数是bufferSizeInBytes,即录制过程中缓冲区的大小。缓冲区的大小一般通过getMinBufferSize获取。getMinBufferSize决定录制过程中所需要的最小缓冲区,如果我们传给bufferSizeInBytes的缓冲区大小比getMinBufferSize返回的值更小,AudioRecord的初始化可能会失败。关于AudioRecord构造函数内部逻辑比较简单,这里不展开讨论。我们重点关注一下getMinBufferSize的实现,它是如何确定这个最小缓冲区大小的呢?

getMinBufferSize()代码分析

getMinBufferSize函数的源代码如下:

static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
        int channelCount = 0;
        switch (channelConfig) {
        case AudioFormat.CHANNEL_IN_DEFAULT: // AudioFormat.CHANNEL_CONFIGURATION_DEFAULT
        case AudioFormat.CHANNEL_IN_MONO:
        case AudioFormat.CHANNEL_CONFIGURATION_MONO:
            channelCount = 1;
            break;
        case AudioFormat.CHANNEL_IN_STEREO:
        case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
        case (AudioFormat.CHANNEL_IN_FRONT | AudioFormat.CHANNEL_IN_BACK):
            channelCount = 2;
            break;
        case AudioFormat.CHANNEL_INVALID:
        default:
            loge("getMinBufferSize(): Invalid channel configuration.");
            return ERROR_BAD_VALUE;
        }

        int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
        if (size == 0) {
            return ERROR_BAD_VALUE;
        }
        else if (size == -1) {
            return ERROR;
        }
        else {
            return size;
        }
    }

首先需要根据channelConfig,确定通道数。这其实很好理解,双通道因为有两个通道,需要的数据缓冲区自然要是单通道的两倍。如果channelConfig无效,将会返回错误ERROR_BAD_VALUE。在成功获取了通道数之后,通过,native_get_min_buff_size方法来获取缓冲区的大小。核心逻辑在native_get_min_buff_size中。接下来,我们分析native_get_min_buff_size的代码。

native_get_min_buff_size源码分析

native_get_min_buff_size定义在android_media_AudioRecord.cpp中:{"native_get_min_buff_size", 874 "(III)I", (void *)android_media_AudioRecord_get_min_buff_size}。这里可以看到,native_get_min_buff_size对应的native方法是android_media_AudioRecord_get_min_buff_size。
android_media_AudioRecord_get_min_buff_size的源代码如下:

static jint android_media_AudioRecord_get_min_buff_size(JNIEnv *env,  jobject thiz,
    jint sampleRateInHertz, jint channelCount, jint audioFormat) {

    ALOGV(">> android_media_AudioRecord_get_min_buff_size(%d, %d, %d)",
          sampleRateInHertz, channelCount, audioFormat);

    size_t frameCount = 0;
    audio_format_t format = audioFormatToNative(audioFormat);
    status_t result = AudioRecord::getMinFrameCount(&frameCount,
            sampleRateInHertz,
            format,
            audio_channel_in_mask_from_count(channelCount));

    if (result == BAD_VALUE) {
        return 0;
    }
    if (result != NO_ERROR) {
        return -1;
    }
    return frameCount * channelCount * audio_bytes_per_sample(format);

android_media_AudioRecord_get_min_buff_size首先把java层的audioFormat转换成c++层的audio_format_t,然后调用AudioRecord(c++)类的getMinFrameCount方法,获取最小帧数frameCount。然后将最小帧数frameCount,通道数channelCount,和根据format计算出的每一个采样的字节数相乘,得出最小缓冲区大小。这个公式`frameCount * channelCount * audio_bytes_per_sample(format)'很好理解,重点在于AudioRecord::getMinFrameCount,它的代码如下:

status_t AudioRecord::getMinFrameCount(
        size_t* frameCount,
        uint32_t sampleRate,
        audio_format_t format,
        audio_channel_mask_t channelMask)
{
    if (frameCount == NULL) {
        return BAD_VALUE;
    }

    size_t size;
    status_t status = AudioSystem::getInputBufferSize(sampleRate, format, channelMask, &size);
    if (status != NO_ERROR) {
        ALOGE("AudioSystem could not query the input buffer size for sampleRate %u, format %#x, "
              "channelMask %#x; status %d", sampleRate, format, channelMask, status);
        return status;
    }

    // We double the size of input buffer for ping pong use of record buffer.
    // Assumes audio_is_linear_pcm(format)
    if ((*frameCount = (size * 2) / (audio_channel_count_from_in_mask(channelMask) *
            audio_bytes_per_sample(format))) == 0) {
        ALOGE("Unsupported configuration: sampleRate %u, format %#x, channelMask %#x",
            sampleRate, format, channelMask);
        return BAD_VALUE;
    }

    return NO_ERROR;
}

这里有两处关键代码:AudioSystem::getInputBufferSize,和(*frameCount = (size * 2) / (audio_channel_count_from_in_mask(channelMask) * audio_bytes_per_sample(format)))。首先我们看AudioSystem::getInputBufferSize的实现:

status_t AudioSystem::getInputBufferSize(uint32_t sampleRate, audio_format_t format,
        audio_channel_mask_t channelMask, size_t* buffSize)
{
    const sp<AudioFlingerClient> afc = getAudioFlingerClient();
    if (afc == 0) {
        return NO_INIT;
    }
    return afc->getInputBufferSize(sampleRate, format, channelMask, buffSize);
}

AudioSystem::getInputBufferSize通过AudioFlingerClient的getInputBufferSize获取输入缓冲区缓冲区大小。我们继续跟进,看一下AudioFlingerClient的getInputBufferSize方法做了什么:

size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, audio_format_t format,
        audio_channel_mask_t channelMask) const
1344{
1345    status_t ret = initCheck();
1346    if (ret != NO_ERROR) {
1347        return 0;
1348    }
1349    if ((sampleRate == 0) ||
1350            !audio_is_valid_format(format) || !audio_has_proportional_frames(format) ||
1351            !audio_is_input_channel(channelMask)) {
1352        return 0;
1353    }
1354
1355    AutoMutex lock(mHardwareLock);
1356    mHardwareStatus = AUDIO_HW_GET_INPUT_BUFFER_SIZE;
1357    audio_config_t config, proposed;
1358    memset(&proposed, 0, sizeof(proposed));
1359    proposed.sample_rate = sampleRate;
1360    proposed.channel_mask = channelMask;
1361    proposed.format = format;
1362
1363    sp<DeviceHalInterface> dev = mPrimaryHardwareDev->hwDevice();
1364    size_t frames;
1365    for (;;) {
1366        // Note: config is currently a const parameter for get_input_buffer_size()
1367        // but we use a copy from proposed in case config changes from the call.
1368        config = proposed;
1369        status_t result = dev->getInputBufferSize(&config, &frames);
1370        if (result == OK && frames != 0) {
1371            break; // hal success, config is the result
1372        }
1373        // change one parameter of the configuration each iteration to a more "common" value
1374        // to see if the device will support it.
1375        if (proposed.format != AUDIO_FORMAT_PCM_16_BIT) {
1376            proposed.format = AUDIO_FORMAT_PCM_16_BIT;
1377        } else if (proposed.sample_rate != 44100) { // 44.1 is claimed as must in CDD as well as
1378            proposed.sample_rate = 44100;           // legacy AudioRecord.java. TODO: Query hw?
1379        } else {
1380            ALOGW("getInputBufferSize failed with minimum buffer size sampleRate %u, "
1381                    "format %#x, channelMask 0x%X",
1382                    sampleRate, format, channelMask);
1383            break; // retries failed, break out of loop with frames == 0.
1384        }
1385    }
1386    mHardwareStatus = AUDIO_HW_IDLE;
1387    if (frames > 0 && config.sample_rate != sampleRate) {
1388        frames = destinationFramesPossible(frames, sampleRate, config.sample_rate);
1389    }
1390    return frames; // may be converted to bytes at the Java level.
1391}

这里是将参数传递个HAL层,然后获取inputBuffer的size。硬件不同,这个值也会不同。关键的代码在for循环中。首先尝试调用dev->getInputBufferSize,如果能够正常获取InputBufferSize,则返回该值。否则,调整参数为一般参数,通过destinationFramesPossible再次尝试获取InputBufferSize。
总结:以上就是getMinBufferSize的主要流程。可以看出,这个值是根据不同的硬件会得出不同的值,以保证在不同硬件上能够正常工作。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值