【Audio开发三】音频audio中帧frameSize ,周期大小periodsize,缓冲区buffer原理详解以及代码流程分析

一、基础概述

在分析获取最小帧数前,我们先来了解几个相关的概念。

1,帧

帧(frame):表示一个完整的声音单元,所谓的声音单元是指一个采样样本。如果是双声道,那么一个完整的声音单元就是 2 个样本,如果是 5.1 声道,那么一个完整的声音单元就是 6 个样本了。帧的大小(一个完整的声音单元的数据量)等于声道数乘以采样位宽,即 frameSize = channelCount * bytesPerSample。这里bytesPerSample就是量化编码的字节大小。无论是框架层还是内核层,都是以帧为单位去管理音频数据缓冲区的。在音频开发领域通常也会说采样点来对应帧这个概念。因为将帧的个数除以采样率就可以直接获得对应音频数据的时长。(PCM格式),又因为采样频率的倒数,就是一个采样点的时间,乘以采样点的数量就是整体音频的时长了。

2,传输延迟

传输延迟(latency):一个处理单元引入的delay。

3,周期

周期(period):Linux ALSA 把数据缓冲区划分为若干个块,dma 每传输完一个块上的数据即发出一个硬件中断,CPU 收到中断信号后,再配置 dma 去传输下一个块上的数据。一个块即是一个周期。

4,周期大小

周期大小(periodSize):即是一个数据块的帧数。

再回到传输延迟(latency),每次传输产生的延迟等于周期大小除以采样率,即 latency = periodSize / sampleRate。因为periodSize 指的是帧数,而帧大小 channelCount * bytesPerSample,所以帧总数除以采样频率就是整个传输过程消耗的时间。

5,音频重采样

音频重采样是指这样的一个过程------把一个采样率的数据转换为另一个采样率的数据。Android 原生系统上,音频硬件设备一般都工作在一个固定的采样率上(如 48 KHz),因此所有音轨数据都需要重采样到这个固定的采样率上,然后再输出。因为系统中可能存在多个音轨同时播放,而每个音轨的采样率可能是不一致的,比如在播放音乐的过程中,来了一个提示音,这时需要把音乐和提示音混音并输出到硬件设备,而音乐的采样率和提示音的采样率不一致,问题来了,如果硬件设备工作的采样率设置为音乐的采样率的话,那么提示音就会失真,因此最简单见效的解决方法是:硬件设备工作的采样率固定一个值,所有音轨在 AudioFlinger 都重采样到这个采样率上,混音后输出到硬件设备,保证所有音轨听起来都不失真。 sample、frame、period、latency 这些概念与 Linux ALSA 及硬件设备的关系非常密切。

二,Audio buffersize实现流程分析

1.参数介绍

AudioSource:采样通道,一般为MediaRecorder.AudioSource.MIC;

SampleRate:采样率,一般在8000Hz~48000Hz之间,不同的硬件设备这个值不同;

Channel:采样声道数,一般为单通道或立体声,即 AudioFormat.CHANNEL_CONFIGURATION_MONO或 AudioFormat.CHANNEL_CONFIGURATION_STEREO;

AudioFormat:采样精度,一般为8位或16位,即AudioFormat.ENCODING_16BIT或8BIT;

bufferSize:缓冲区大小:可以通过getMinBufferSize来获取;

buffer:DMA缓冲区大小

period: 周期,是每两个硬件中断之间的帧数,即传输多少个采样帧数据,DMA反馈一个中断,一般一个周期包含1024个采样帧,在HAL层中定义。

frame: 每个采样帧的大小。为一个采样点的字节数*声道数,因为对于多通道的话,用1个采样点的字节数表示不全,因为播放的时候肯定是多个声道的数据都要播放出来才行,所以为了方便,就说1秒钟有多少个frame,这样就能抛开声道数,把意思表达全了。

sample: 采样声道,一般为2个字节。

在Audio系统中,buffer的组成如图所示

2,Audio子系统之AudioRecord.getMinBufferSize

为了让音频数据通路能正常运转,共享FIFO必须达到最小缓冲区的大小。如果数据缓冲区分配得过小,那么播放声音会频繁遭遇 underrun,underrun 是消费者(AudioFlinger::PlaybackThread)不能及时从缓冲区拿到数据,造成的后果就是声音断续卡顿。

1、获取方法

cpp 复制代码
AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);

从函数参数来看,返回值取决于采样率、音频格式、声道数这三个属性。

2,接下来进入系统分析具体实现

调用服务端的函数,获取frameCount大小,最后返回了frameCount声道数采样精度,其中frameCount表示最小采样帧数,继续分析frameCount的计算方法

复制代码
    frameworks/av/media/libmedia/AudioRecord.cpp
cpp 复制代码
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;
    }
    //计算出最小的frame
    // 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;
}

此时frameCount= size2/(声道数采样精度),注意这里需要double一下,而size是由hal层得到的,AudioSystem::getInputBufferSize()函数最终会调用到HAL层

复制代码
    frameworks/av/media/libmedia/AudioSystem.cpp
cpp 复制代码
status_t AudioSystem::getInputBufferSize(uint32_t sampleRate, audio_format_t format,
        audio_channel_mask_t channelMask, size_t* buffSize)
{
    const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
    if (af == 0) {
        return PERMISSION_DENIED;
    }
    Mutex::Autolock _l(gLockCache);
    // Do we have a stale gInBufferSize or are we requesting the input buffer size for new values
    size_t inBuffSize = gInBuffSize;
    if ((inBuffSize == 0) || (sampleRate != gPrevInSamplingRate) || (format != gPrevInFormat)
        || (channelMask != gPrevInChannelMask)) {
        gLockCache.unlock();
        inBuffSize = af->getInputBufferSize(sampleRate, format, channelMask);
        gLockCache.lock();
        if (inBuffSize == 0) {
            ALOGE("AudioSystem::getInputBufferSize failed sampleRate %d format %#x channelMask %x",
                    sampleRate, format, channelMask);
            return BAD_VALUE;
        }
        // A benign race is possible here: we could overwrite a fresher cache entry
        // save the request params
        gPrevInSamplingRate = sampleRate;
        gPrevInFormat = format;
        gPrevInChannelMask = channelMask;
 
        gInBuffSize = inBuffSize;
    }
    *buffSize = inBuffSize;
 
    return NO_ERROR;
}

所以实际上会通过binder到达AudioFlinger中

frameworks\av\services\audioflinger\AudioFlinger.cpp

cpp 复制代码
size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, audio_format_t format,
        audio_channel_mask_t channelMask) const
{
    status_t ret = initCheck();
    if (ret != NO_ERROR) {
        return 0;
    }
 
    AutoMutex lock(mHardwareLock);
    mHardwareStatus = AUDIO_HW_GET_INPUT_BUFFER_SIZE;
    audio_config_t config;
    memset(&config, 0, sizeof(config));
    config.sample_rate = sampleRate;
    config.channel_mask = channelMask;
    config.format = format;
 
    audio_hw_device_t *dev = mPrimaryHardwareDev->hwDevice();
    size_t size = dev->get_input_buffer_size(dev, &config);
    mHardwareStatus = AUDIO_HW_IDLE;
    return size;
}

把参数传递给hal层,获取buffer大小

hardware\aw\audio\tulip\audio_hw.c

cpp 复制代码
static size_t get_input_buffer_size(uint32_t sample_rate, int format, int channel_count)
{
    size_t size;
    size_t device_rate;
 
    if (check_input_parameters(sample_rate, format, channel_count) != 0)
        return 0;
 
    /* take resampling into account and return the closest majoring
    multiple of 16 frames, as audioflinger expects audio buffers to
    be a multiple of 16 frames */
    size = (pcm_config_mm_in.period_size * sample_rate) / pcm_config_mm_in.rate;
    size = ((size + 15) / 16) * 16;
 
    return size * channel_count * sizeof(short);
}

这里包含一个结构体struct pcm_config,定义了一个周期包含了多少采样帧,并根据结构体的rate数据进行重采样计算,这里的rate是以MM_SAMPLING_RATE为标准,即44100,一个采样周期有1024个采样帧,然后计算出重采样之后的size

同时audioflinger的音频buffer是16的整数倍,所以再次计算得出一个最接近16倍的整数,最后返回size声道数1帧数据所占字节数

c 复制代码
struct pcm_config pcm_config_mm_in = {
    .channels = 2,
    .rate = MM_SAMPLING_RATE,
    .period_size = 1024,
    .period_count = CAPTURE_PERIOD_COUNT,
    .format = PCM_FORMAT_S16_LE,
};

3,总结:

minBuffSize = ((((((((pcm_config_mm_in.period_size * sample_rate) / pcm_config_mm_in.rate) + 15) / 16) * 16) * channel_count * sizeof(short)) * 2) / (audio_channel_count_from_in_mask(channelMask) * audio_bytes_per_sample(format))) * channelCount * audio_bytes_per_sample(format);

=(((((((pcm_config_mm_in.period_size * sample_rate) / pcm_config_mm_in.rate) + 15) / 16) * 16) * channel_count * sizeof(short)) * 2)

其中:pcm_config_mm_in.period_size=1024;pcm_config_mm_in.rate=44100;这里我们可以看到他除掉(channelCount*format),后面又乘回来了,这个是因为在AudioRecord.cpp对frameCount进行了一次校验,判断是否支持该参数的设置。

以getMinBufferSize(44100, MONO, 16BIT);为例,即sample_rate=44100,channel_count=1, format=2,那么

BufferSize = (((1024sample_rate/44100)+15)/16)16channel_countsizeof(short)*2 = 4096

即最小缓冲区大小为:周期大小 * 重采样 * 采样声道数 * 2 * 采样精度所占字节数;这里的2的解释为We double the size of input buffer for ping pong use of record buffer,采样精度:PCM_8_BIT为unsigned char,PCM_16_BIT为short,PCM_32_BIT为int。

相关推荐
TechVoyager_82469 小时前
多形态输入与12 通道音频:IT6615 这颗 HDMI 发射器的接口宽度
音视频·音频芯片·芯片选型·视频发射
Raymond16889 小时前
AI配音工具长文本合成架构技术解析:全局音色锚定与声学状态跟踪
人工智能·架构·实时互动·视觉检测·音视频·语音识别
hflmwl816 小时前
Wise Data Recovery找回误删的文档照片音乐和视频
音视频·datarecovery·找回误删的文档照片音乐和视频·找回误删的文档照片音乐视频
技灵AI16 小时前
AI 视频转场提示词怎么写?让前后画面衔接更自然
人工智能·prompt·aigc·音视频
youandyouyou18 小时前
跨区培训的屏幕共享怎么做才稳?同屏同步与弱网优化实践
音视频·网络通信
开开心心就好18 小时前
课堂随机点名工具,绿色版免安装双击即用
java·服务器·前端·ffmpeg·音视频·perl·symfony
美狐美颜SDK开放平台18 小时前
直播APP集成美颜SDK,如何梳理第三方组件的数据权限?
开发语言·人工智能·深度学习·音视频·第三方美颜sdk
l143723326720 小时前
批量视频硬字幕擦除多少钱?影响报价的关键因素全解析
音视频
重生六道20 小时前
B站视频时长统计:一段JS脚本搞定分P/合集总时长(附倍速计算)
开发语言·javascript·音视频
音视频牛哥20 小时前
不换摄像机,也能接入 WebRTC?SmartMediaKit RTSP/RTMP 转 WHIP 技术实践
音视频·webrtc·smartmediakit·rtsp转whip·rtmp转whip·whip推流·rtsp转webrtc播放