Android Audio基础(18)——最小缓冲区

在创建 AudioTrack 时有一个缓冲区大小的参数,最小缓冲区参数通过 AudioTrack.getMinBufferSize() 获取。

一、最小缓冲区

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

1、获取方法

AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);

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

2、AudioTrack

源码位置:/frameworks/base/media/java/android/media/AudioTrack.java

getMinBufferSize

static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {int channelCount = 0;switch(channelConfig) {case AudioFormat.CHANNEL_OUT_MONO:case AudioFormat.CHANNEL_CONFIGURATION_MONO:channelCount = 1; //单声道break;case AudioFormat.CHANNEL_OUT_STEREO:case AudioFormat.CHANNEL_CONFIGURATION_STEREO:channelCount = 2; //双声道break;default:if (!isMultichannelConfigSupported(channelConfig, audioFormat)) {loge("getMinBufferSize(): Invalid channel configuration.");return ERROR_BAD_VALUE;} else {channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);}}if (!AudioFormat.isPublicEncoding(audioFormat)) {loge("getMinBufferSize(): Invalid audio format.");return ERROR_BAD_VALUE;}// 采样率// 注意: AudioFormat.SAMPLE_RATE_UNSPECIFIED不允许if ( (sampleRateInHz < AudioFormat.SAMPLE_RATE_HZ_MIN) || (sampleRateInHz > AudioFormat.SAMPLE_RATE_HZ_MAX) ) {loge("getMinBufferSize(): " + sampleRateInHz + " Hz is not a supported sample rate.");return ERROR_BAD_VALUE; //采样率支持:4KHz ~ 192KHz}// 进入Native层int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);if (size <= 0) {loge("getMinBufferSize(): error querying hardware");return ERROR;}  else {return size;}
}

下面看一下对应的 native 方法 native_get_min_buff_size()。

3、android_media_AudioTrack.cpp

源码位置:/frameworks/base/core/jni/android_media_AudioTrack.cpp

native_get_min_buff_size

// 返回创建AudioTrack所需的最小buffer大小
// 如果查询硬件出错,返回-1
static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env,  jobject thiz,jint sampleRateInHertz, jint channelCount, jint audioFormat) {size_t frameCount;// 获取最低帧数,也就是确定至少设置多少个frame才能保证声音正常播放const status_t status = AudioTrack::getMinFrameCount(&frameCount, AUDIO_STREAM_DEFAULT, sampleRateInHertz);if (status != NO_ERROR) {ALOGE("AudioTrack::getMinFrameCount() for sample rate %d failed with status %d", sampleRateInHertz, status);return -1;}const audio_format_t format = audioFormatToNative(audioFormat);if (audio_has_proportional_frames(format)) {const size_t bytesPerSample = audio_bytes_per_sample(format);// PCM 数据最小缓冲区大小return frameCount * channelCount * bytesPerSample;} else {return frameCount;}
}

最小缓冲区的大小 = 最低帧数 * 帧大小(声道数 * 位宽),(位宽以字节为单位)。

二、最低帧数

1、基础概述

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

帧(frame):表示一个完整的声音单元,所谓的声音单元是指一个采样样本。如果是双声道,那么一个完整的声音单元就是 2 个样本,如果是 5.1 声道,那么一个完整的声音单元就是 6 个样本了。帧的大小(一个完整的声音单元的数据量)等于声道数乘以采样位宽,即 frameSize = channelCount * bytesPerSample。无论是框架层还是内核层,都是以帧为单位去管理音频数据缓冲区的。在音频开发领域通常也会说采样点来对应帧这个概念。因为将帧的个数除以采样率就可以直接获得对应音频数据的时长。(PCM格式)
传输延迟
传输延迟(latency):一个处理单元引入的delay。
周期
周期(period):Linux ALSA 把数据缓冲区划分为若干个块,dma 每传输完一个块上的数据即发出一个硬件中断,CPU 收到中断信号后,再配置 dma 去传输下一个块上的数据。一个块即是一个周期。
周期大小
周期大小(periodSize):即是一个数据块的帧数。
再回到传输延迟(latency),每次传输产生的延迟等于周期大小除以采样率,即 latency = periodSize / sampleRate。

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

2、AudioTrack.cpp

源码位置:/frameworks/av/media/libaudioclient/AudioTrack.cpp

getMinFrameCount

status_t AudioTrack::getMinFrameCount(size_t* frameCount, audio_stream_type_t streamType, uint32_t sampleRate)
{if (frameCount == NULL) {return BAD_VALUE;}// 通过binder调用到AudioFlinger::sampleRate()获取硬件设备的采样率uint32_t afSampleRate;status_t status;status = AudioSystem::getOutputSamplingRate(&afSampleRate, streamType);if (status != NO_ERROR) {ALOGE("%s(): Unable to query output sample rate for stream type %d; status %d",__func__, streamType, status);return status;}// 通过binder调用到AudioFlinger::frameCount()获取硬件设备的周期大小size_t afFrameCount;status = AudioSystem::getOutputFrameCount(&afFrameCount, streamType);if (status != NO_ERROR) {ALOGE("%s(): Unable to query output frame count for stream type %d; status %d",__func__, streamType, status);return status;}// 通过binder调用到AudioFlinger::latency()获取硬件设备的传输延时uint32_t afLatency;status = AudioSystem::getOutputLatency(&afLatency, streamType);if (status != NO_ERROR) {ALOGE("%s(): Unable to query output latency for stream type %d; status %d",__func__, streamType, status);return status;}// 根据afLatency、afFrameCount、afSampleRate计算出一个最低帧数*frameCount = AudioSystem::calculateMinFrameCount(afLatency, afFrameCount, afSampleRate, sampleRate, 1.0f /*, 0 notificationsPerBufferReq*/);// 正常情况下应始终产生一个非零值if (*frameCount == 0) {ALOGE("%s(): failed for streamType %d, sampleRate %u", __func__, streamType, sampleRate);return BAD_VALUE;}ALOGV("%s(): getMinFrameCount=%zu: afFrameCount=%zu, afSampleRate=%u, afLatency=%u",__func__, *frameCount, afFrameCount, afSampleRate, afLatency);return NO_ERROR;
}

这里比较重要的就是调用 calculateMinFrameCount() 方法计算最低帧数。

3、AudioSystem.cpp

源码位置:/frameworks/av/media/libaudioclient/AudioSystem.cpp

我个人的理解:audiosystem就是native层的audiomanager。

calculateMinFrameCount

/* static */ size_t AudioSystem::calculateMinFrameCount(uint32_t afLatencyMs, uint32_t afFrameCount, uint32_t afSampleRate,uint32_t sampleRate, float speed /*, uint32_t notificationsPerBufferReq*/) {// 确保缓冲区深度至少覆盖音频硬件延迟uint32_t minBufCount = afLatencyMs / ((1000 * afFrameCount) / afSampleRate);if (minBufCount < 2) {minBufCount = 2;}
#if 0// The notificationsPerBufferReq parameter is not yet used for non-fast tracks,// but keeping the code here to make it easier to add later.if (minBufCount < notificationsPerBufferReq) {minBufCount = notificationsPerBufferReq;}
#endifALOGV("calculateMinFrameCount afLatency %u  afFrameCount %u  afSampleRate %u  ""sampleRate %u  speed %f  minBufCount: %u" /*"  notificationsPerBufferReq %u"*/,afLatencyMs, afFrameCount, afSampleRate, sampleRate, speed, minBufCount /*, notificationsPerBufferReq*/);return minBufCount * sourceFramesNeededWithTimestretch(sampleRate, afFrameCount, afSampleRate, speed);
} 

这个函数根据硬件设备的配置信息(采样率、周期大小、传输延迟)和音轨的采样率,计算出一个最低帧数。
背后的设计思想是:
如果硬件延迟大于>2倍periodsize,则用硬件延迟作为最小buffersize。否则用2倍的periodsize。
保证缓冲区的数据量是每个周期数据量的两倍,这样可以保证进行乒乓操作。

4、AudioResamplerPublic.h

源码位置:/frameworks/av/include/media/AudioResamplerPublic.h

sourceFramesNeededWithTimestretch

static inline size_t sourceFramesNeededWithTimestretch( uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate, float speed) {size_t required = sourceFramesNeeded(srcSampleRate, dstFramesRequired, dstSampleRate);return required * (double)speed + 1 + 1; // accounting for rounding dependencies
}

sourceFramesNeeded

static inline size_t sourceFramesNeeded(uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate) {    // +1 for rounding - always do this even if matched ratio (resampler may use phases not ratio)    // +1 for additional sample needed for interpolation    return srcSampleRate == dstSampleRate ? dstFramesRequired : size_t((uint64_t)dstFramesRequired * srcSampleRate / dstSampleRate + 1 + 1);}

calculateMinFrameCount() 这个函数根据硬件设备的配置信息(采样率、周期大小、传输延迟)和音轨的采样率,计算出一个最低帧数。 注意,getMinBufferSize() 方法获取到的是保证音频播放时缓冲区的最小值。很多应用都会大于这个最小值。此外,这个最小值只适用于PCM数据播放,PCM数据播放,PCM数据播放。如果是编码后的音频数据(DD,DDP,DTS)需要应用自己计算最小值!!!!!!!!!!!!!!!!!!!!!!

用一张图概括整篇文章。
记忆要点:缓冲区至少两倍于每个中断搬运的数据。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/36066.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue:Vue2和Vue3创建项目的几种常用方式以及区别

前言 Vue.js 和 Element UI 都是用 JavaScript 编写的。 1、Vue.js 是一个渐进式 JavaScript 框架。2、Element UI 是基于 Vue.js 的组件库。3、JavaScript 是这两个项目的主要编程语言。 而Element Plus是基于TypeScript开发的。 一、Vue2 1、基于vuecli工具创建 vue2 …

游戏成瘾与学习动力激发策略研究——了解“情感解离”“创伤理论”

一、情感解离(Emotional Dissociation) 定义:情感解离是一种心理防御机制,指个体在经历无法承受的情绪压力或创伤时,通过切断情感体验与认知、记忆或现实感知的联系来保护自我。它不是简单的“麻木”,而是大脑为应对极端刺激而启动的“紧急逃生通道”。 核心特征 1、意…

WPF跨平台开发探讨:借助相关技术实现多平台应用

WPF跨平台开发探讨&#xff1a;借助相关技术实现多平台应用 一、前言二、WPF 跨平台开发的现状与挑战2.1 WPF 的平台局限性2.2 跨平台开发面临的挑战 三、实现 WPF 跨平台开发的相关技术3.1.NET MAUI 简介3.2.NET MAUI 的关键特性3.3 其他相关技术和工具 四、借助.NET MAUI 实现…

ImGui 学习笔记(五) —— 字体文件加载问题

ImGui 加载字体文件的函数似乎存在编码问题&#xff0c;这一点可能跟源文件的编码也有关系&#xff0c;我目前源文件编码是 UTF-16。 当参数中包含中文字符时&#xff0c;ImGui 内部将字符转换为宽字符字符集时候&#xff0c;采用的 MultiByteToWideChar API 参数不太对&#…

汽车一键启动PKE无钥匙系统

移动管家汽车一键启动PKE舒适无钥匙遥控远程系统是一种集成了多项先进功能的汽车电子系统&#xff0c;主要目的是提高驾驶便利性和安全性。 以下是该系统的具体功能&#xff1a; 功能类别 功能描述 无钥匙进入 感应无钥匙进入&#xff08;自动感应开关门&#xff09; 一…

【从零开始学习计算机科学与技术】计算机网络(五)网络层

【从零开始学习计算机科学与技术】计算机网络(五)网络层 网络层无连接服务的实现:数据报子网面向连接服务的实现:虚电路子网IP协议子网及子网划分子网掩码子网规划可变长子网掩码 (VLSM)无类别域间路由—CIDRIP路由转发过程ARP与RARPARP的工作过程:RARP的工作过程如下:DH…

HTML5扫雷游戏开发实战

HTML5扫雷游戏开发实战 这里写目录标题 HTML5扫雷游戏开发实战项目介绍技术栈项目架构1. 游戏界面设计2. 核心类设计 核心功能实现1. 游戏初始化2. 地雷布置算法3. 数字计算逻辑4. 扫雷功能实现 性能优化1. DOM操作优化2. 算法优化 项目亮点技术难点突破1. 首次点击保护2. 连锁…

docker安装node部分问题

sudo n latest sudo: n: command not found 如果运行 sudo n latest 时出现&#xff1a; sudo: n: command not found 说明 n 版本管理工具 未安装 或 未添加到 PATH 环境变量。 &#x1f6e0; 解决方案 1️⃣ 先检查 n 是否已安装 运行&#xff1a; which n或者&#xff1…

2025-03-17 NO.1 Quest3 开发环境配置教程

文章目录 准备条件1 Quest3 激活1.1 下载 Oculus 助手1.2 打开 quest 热点1.3 Quest3 连接 wifi1.4 参考教程 2 登录 Oculus&#xff08;*&#xff09;2.1 创建 Meta 账号&#xff08;*&#xff09;2.2 Oculus 软件下载与配置&#xff08;*&#xff09; 3 创建项目3.1 下载 Uni…

简单记一些Kalibr在20.04安装下踩的坑

赠品&#xff1a;官方Kalibr测试数据下载 包括双目&#xff0c;和IMU双目 通过网盘分享的文件&#xff1a;kalibr官方测试数据 链接: https://pan.baidu.com/s/1TgeXuTYLoTrlBbKy5Jf41A?pwdyha6 提取码: yha6 https://github.com/ethz-asl/kalibr/wiki/downloads 先说结论&a…

【C++】:C++11详解 —— 右值引用

目录 左值和右值 左值的概念 右值的概念 左值 vs 右值 左值引用 和 右值引用 左值引用 右值引用 左值引用 vs 右值引用 使用场景 左值引用的使用场景 左值引用的短板 右值引用的使用场景 1. 实现移动语义&#xff08;资源高效转移&#xff09; 2. 优化容器操作&a…

SpringMVC(四)Restful软件架构风格

目录 ​编辑 API接口设计的架构风格 一 Dao层实现&#xff08;处理数据库&#xff09; 二 Sercice层实现&#xff08;处理业务逻辑&#xff09; 三 Controller层&#xff08;处理http请求&#xff09; 四 补充知识点 1 PathVariable - 路径变量 2 CrossOrigin(Origins …

c++图论(二)之图的存储图解

在 C 中实现图的存储时&#xff0c;常用的方法包括 邻接矩阵&#xff08;Adjacency Matrix&#xff09;、邻接表&#xff08;Adjacency List&#xff09; 和 边列表&#xff08;Edge List&#xff09;。以下是具体实现方法、优缺点分析及代码示例&#xff1a; 1. 邻接矩阵&…

ABAP PDF预览

画个屏幕 PDF JPG TXT都可以参考预览&#xff0c;把二进制流传递给标准函数就行 *&---------------------------------------------------------------------* *& Report YDEMO2 *&---------------------------------------------------------------------* *&am…

Compose 的产生和原理

引言 compose 出现的目的&#xff1a; 重新定义android 上ui 的编写方式。为了提高android 原生ui开发效率。让android 的UI开发方式跟上时代。 正文 compose 是什么&#xff1f; 就是一套ui框架 和flutter 一样是一套ui框架 Flutter&#xff1a;跨平台开发趋势与企业应用的…

单口路由器多拨号ADSL实现方法

条件是多拨号场景&#xff0c;公司路由器接口不够用

H3C SecPath SysScan-AK 系列漏洞扫描系统

H3C SecPath SysScan-AK 系列是一款专业的漏洞扫描系统&#xff0c;旨在帮助组织和企业快速、准确地发现网络和系统中存在的安全漏洞。该系统具有以下特点&#xff1a; 1. 多样化的扫描能力&#xff1a;支持对各类网络设备、服务器、应用程序等进行漏洞扫描&#xff0c;能够全面…

[蓝桥杯 2023 省 B] 飞机降落

[蓝桥杯 2023 省 B] 飞机降落 题目描述 N N N 架飞机准备降落到某个只有一条跑道的机场。其中第 i i i 架飞机在 T i T_{i} Ti​ 时刻到达机场上空&#xff0c;到达时它的剩余油料还可以继续盘旋 D i D_{i} Di​ 个单位时间&#xff0c;即它最早可以于 T i T_{i} Ti​ 时刻…

Kafka详解——介绍与部署

1. 什么是 Kafka&#xff1f; Kafka 是一个分布式的消息队列系统&#xff0c;最初由 LinkedIn 开发&#xff0c;后来成为 Apache 开源项目。它的主要用途包括实时数据处理、日志收集、数据流管道构建等。Kafka 具备高吞吐量、可扩展性、持久性和容错性&#xff0c;广泛应用于大…

win10搭建opengl环境搭建并测试--输出立方体球体和碗型并在球体上贴图

参照本文档可以完成环境搭建和测试&#xff0c;如果想要快速完成环境的搭建可以获取本人的工程&#xff0c;包括所用到的工具链和测试工程源码获取&#xff08;非免费介意务下载&#xff09;&#xff1a;链接: https://pan.baidu.com/s/1H2ejbT7kLM9ore5MqyomgA 提取码: 8s1b …