Android Audio基础(13)——audiomixer

在 Android 平台上,音频混合器 AudioMixer 主要用在 AudioFlinger 里,将多路音频源数据混音(包括混音、音量处理、重采样及处理声道等)。位于 framework 的音频处理模库 libaudioprocessing(frameworks/av/media/libaudioprocessing)中。

一、音频混合器

混音器(AudioMixer)是在混音回放线程类(MixerThread)中的构造函数内创建。

1、混合器创建

源码位置:/frameworks/av/services/audioflinger/Threads.cpp

AudioFlinger::MixerThread::MixerThread(……) 
{……// 往hal层一次写数据的大小。hal层设备的采样率。mAudioMixer = new AudioMixer(mNormalFrameCount, mSampleRate);……
}

这说明了一个 MixerThread 对应一个 AudioMixer,而且 MixerThread 传了两个参数给AudioMixer:
mNormalFrameCount:一次输送数据的长度,把源 buffer 的音频数据写入目的 buffer。
mSampleRate:音频数据输出的采样率。

2、功能接口

AudioMixer源码位置:/frameworks/av/media/libaudioprocessing/AudioMixer.cpp

void AudioMixer::setBufferProvider(int name, AudioBufferProvider* bufferProvider)

此方法允许用户根据给定的索引设置一个音频缓冲区提供者,name 作为索引,bufferProvider由 mActiveTracks 取出来的 Track 对象提供。这样audiomixer混音时就从track对应的IFFO取数据。

void AudioMixer::setParameter(int name, int target, int param, void *value)

该函数主要用于设置特定参数值,涉及到了音频处理中多种参数的设置,包括通道掩码、缓冲区指针、格式、重采样、音量衰减、时间拉伸等。

这里我们主要看一下 target 和 param 的不同参数所对应实现的相关功能:

在这里插入图片描述
标注基类是直接透传调用 AudioMixerBase.cpp 中的对应函数。AudioMixer 继承自 AudioMixerBase,同时基类中也有几个比较重要的函数。这里我们需要关注的是参数MAIN_BUFFER。audiomixer会将从track获取的FIFO数据和MAIN_BUFFER混音后存入MAIN_BUFFER。

AudioMixerBase

源码位置:/frameworks/av/media/libaudioprocessing/AudioMixerBase.cpp

// 启用指定的音频轨道
void AudioMixerBase::enable(int name)
// 禁用指定的音频轨道
void AudioMixerBase::disable(int name)
// 销毁指定的音频轨道
void AudioMixerBase::destroy(int name)

这几个函数主要是用于音频轨道的控制功能,确保了轨道能够在不同状态下正确工作。

刷新函数

上面的接口中大量调用 invalidate() 函数,该函数的主要作用就是刷新 Track。源码位置:/frameworks/av/media/libaudioprocessing/include/media/AudioMixerBase.h

// 当轨道信息改变,需要确定一个新的进程钩子时调用下面的函数
void invalidate() {mHook = &AudioMixerBase::process__validate;
}

可以看到该函数用于当轨道信息发生变化时,重新确定一个新的进程钩子。这个钩子通常用于验证和处理轨道信息的变化,调用 AudioMixerBase 中的 process__validate() 函数。

void AudioMixerBase::process__validate()
{……// 遍历轨道for (const auto &pair : mTracks) {const int name = pair.first;const std::shared_ptr<TrackBase> &t = pair.second;// 判断该track是否需要混音if (!t->enabled) continue;// 将启用的轨道添加到 mEnabled 和 mGroups 中mEnabled.emplace_back(name);mGroups[t->mainBuffer].emplace_back(name);// 计算每个轨道的需求 (needs) 并设置相应的处理钩子 (hook)uint32_t n = 0;// 可以溢出(掩码只有3位)n |= NEEDS_CHANNEL_1 + t->channelCount - 1;// 设置重采样 NEEDS_RESAMPLE 标志位if (t->doesResample()) {n |= NEEDS_RESAMPLE;}// 设置辅助通道 NEEDS_AUX 标志位if (t->auxLevel != 0 && t->auxBuffer != NULL) {n |= NEEDS_AUX;}if (t->volumeInc[0]|t->volumeInc[1]) {// 增益正在变化volumeRamp = true;} else if (!t->doesResample() && t->volumeRL == 0) {// 不需要重采样且增益为零n |= NEEDS_MUTE;}// 将所有设置好的标志位保存到 t->needs 中t->needs = n;if (n & NEEDS_MUTE) { // 需要静音t->hook = &TrackBase::track__nop;} else {if (n & NEEDS_AUX) { // 处理辅助通道all16BitsStereoNoResample = false;}if (n & NEEDS_RESAMPLE) { // 处理重采样all16BitsStereoNoResample = false;resampling = true;if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1&& t->channelMask == AUDIO_CHANNEL_OUT_MONO // MONO_HACK&& isAudioChannelPositionMask(t->mMixerChannelMask)) { // 重采样单声道t->hook = TrackBase::getTrackHook(TRACKTYPE_RESAMPLEMONO, t->mMixerChannelCount,t->mMixerInFormat, t->mMixerFormat);} else if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2&& t->useStereoVolume()) { // 重采样立体声t->hook = TrackBase::getTrackHook(TRACKTYPE_RESAMPLESTEREO, t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat);} else { // 其他情况t->hook = TrackBase::getTrackHook(TRACKTYPE_RESAMPLE, t->mMixerChannelCount,t->mMixerInFormat, t->mMixerFormat);}ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,"Track %d needs downmix + resample", name);} else { // 处理无需重采样的情况……}}}// 根据条件选择合适的处理钩子mHook = &AudioMixerBase::process__nop;if (mEnabled.size() > 0) {if (resampling) {if (mOutputTemp.get() == nullptr) {mOutputTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]);}if (mResampleTemp.get() == nullptr) {mResampleTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]);}mHook = &AudioMixerBase::process__genericResampling;} else {mHook = &AudioMixerBase::process__genericNoResampling;if (all16BitsStereoNoResample && !volumeRamp) {if (mEnabled.size() == 1) {const std::shared_ptr<TrackBase> &t = mTracks[mEnabled[0]];if ((t->needs & NEEDS_MUTE) == 0) {mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat, t->useStereoVolume());}}}}}……process();// 处理音量渐变,设置最优状态和轨道处理钩子if (mEnabled.size() > 0) {bool allMuted = true;for (const int name : mEnabled) {const std::shared_ptr<TrackBase> &t = mTracks[name];if (!t->doesResample() && t->volumeRL == 0) {t->needs |= NEEDS_MUTE;t->hook = &TrackBase::track__nop;} else {allMuted = false;}}if (allMuted) {mHook = &AudioMixerBase::process__nop;} else if (all16BitsStereoNoResample) {if (mEnabled.size() == 1) {const std::shared_ptr<TrackBase> &t = mTracks[mEnabled[0]];mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat, t->useStereoVolume());}}}
}

这些步骤确保了音频轨道信息变化时能够正确选择合理的钩子函数处理各种情况,包括重采样、音量渐变等。

二、AudioMixer混音

  关于混音,我们已经知道:混音以 track 为源,mainBuffer 为目标,frameCount 为一次混音长度。AudioMixer 最多能维护 32 个 track。track 可以对应不同 mainBuffer,尽管一般情况下他们的 mainBuffer 都是同一个。

在这里插入图片描述

调用 AudioMixer 的 process 方法进行混音的,实际上混音的方法是调用 AudioMixerBase 内的 process_xxx 方法,各个 process 方法大同小异。下面来分析 process__genericResampling 这个方法。
1、AudioMixerBase.cppp
rocess__genericResampling

void AudioMixerBase::process__genericResampling()
{ALOGVV("process__genericResampling\n");// 初始化 outTemp 指针int32_t * const outTemp = mOutputTemp.get(); // 获取当前帧数 numFramessize_t numFrames = mFrameCount;// 遍历每个音频组 mGroupsfor (const auto &pair : mGroups) {const auto &group = pair.second;// 获取第一个轨道 t1const std::shared_ptr<TrackBase> &t1 = mTracks[group[0]];// 清除了临时缓冲区 outTempmemset(outTemp, 0, sizeof(*outTemp) * t1->mMixerChannelCount * mFrameCount);// 处理每个轨道for (const int name : group) {const std::shared_ptr<TrackBase> &t = mTracks[name];int32_t *aux = NULL;if (CC_UNLIKELY(t->needs & NEEDS_AUX)) {aux = t->auxBuffer;}// 如果轨道需要重采样,则直接调用重采样钩子if (t->needs & NEEDS_RESAMPLE) {(t.get()->*t->hook)(outTemp, numFrames, mResampleTemp.get() /* naked ptr */, aux);} else { // 逐帧获取数据并调用处理钩子// 清除临时缓冲区size_t outFrames = 0;// 获取每个轨道的数据并调用相应的处理钩子while (outFrames < numFrames) {t->buffer.frameCount = numFrames - outFrames;t->bufferProvider->getNextBuffer(&t->buffer);t->mIn = t->buffer.raw;// t->mIn == nullptr:启用混音后刚刚刷新音轨if (t->mIn == nullptr) break;(t.get()->*t->hook)(outTemp + outFrames * t->mMixerChannelCount, t->buffer.frameCount,mResampleTemp.get() /* naked ptr */, aux != nullptr ? aux + outFrames : nullptr);outFrames += t->buffer.frameCount;t->bufferProvider->releaseBuffer(&t->buffer);}}}// 将处理后的数据转换为混音器所需的格式convertMixerFormat(t1->mainBuffer, t1->mMixerFormat,outTemp, t1->mMixerInFormat, numFrames * t1->mMixerChannelCount);}
}

这些步骤确保了在需要重采样的情况下,能够正确处理每个轨道的数据,并将其转换为所需的格式。

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

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

相关文章

【React】使用Swiper报错`Swiper` needs at least one child

问题 聊天页面的表情面板&#xff0c;滑动效果使用了ant design mobile的Swiper。 原代码中&#xff0c;Swiper 组件在 isShow 为 false 时渲染的是 <></>&#xff08;空元素&#xff09;&#xff0c;控制台警告Swiper needs at least one child&#xff0c;Swip…

Matlab教程001:软件介绍和界面使用

1.1 软件介绍 1.1.1 Matlab的介绍 MATLAB&#xff08;MATrix LABoratory&#xff09;是一款由 MathWorks 公司开发的高级编程语言和交互式环境&#xff0c;广泛用于 科学计算、数据分析、机器学习、工程建模、仿真和信号处理 等领域。 1.1.2 主要应用领域 数据分析与可视化…

蓝桥杯算法实战分享:算法进阶之路与实战技巧

引言 蓝桥杯作为国内极具影响力的程序设计竞赛&#xff0c;为众多编程爱好者和专业人才提供了展示自我的舞台。参与蓝桥杯不仅能检验自身编程水平&#xff0c;还能拓宽技术视野&#xff0c;为未来职业发展积累宝贵经验。本文将结合历年真题与参赛经验&#xff0c;全面分享蓝桥…

Android Compose 层叠布局(ZStack、Surface)源码深度剖析(十三)

Android Compose 层叠布局&#xff08;ZStack、Surface&#xff09;源码深度剖析 一、引言 在 Android 应用开发领域&#xff0c;用户界面&#xff08;UI&#xff09;的设计与实现一直是至关重要的环节。随着技术的不断演进&#xff0c;Android Compose 作为一种全新的声明式…

MongoDB 面试备战指南

MongoDB 面试备战指南 一、基础概念 1. MongoDB是什么类型的数据库&#xff1f;和关系型数据库有什么区别&#xff1f; 答案&#xff1a; MongoDB是文档型NoSQL数据库&#xff0c;核心区别&#xff1a; 数据模型&#xff1a;存储JSON-like文档&#xff08;动态schema&#xf…

毫米波雷达标定(2)

1. 前言 前面文章中介绍了产线上毫米波雷达的标定原理和流程,这篇文章则主要介绍其在线标定方法。相对于产线标定,在线标定具备使用自然场景而不是依赖特定标靶的优点,但因此其标定精度会相对差一点。在线标定一般应用于售出产品的维护场景,如果其标定结果精度可以满足使用…

Linux fority source和__builtin_xxx

这段代码是用于启用和配置 GCC/Clang 的 Fortify Source 安全机制的预处理指令。Fortify Source 主要用于在编译时增强对缓冲区溢出等内存安全问题的检查。以下是对每一部分的详细解释&#xff1a; 1. 最外层条件编译 # if CONFIG_FORTIFY_SOURCE > 0目的&#xff1a;检查…

SQL GROUP BY 自定义排序规则

在 SQL 中&#xff0c;GROUP BY 子句用于将结果集按一个或多个列进行分组。默认情况下&#xff0c;GROUP BY 会按照列的自然顺序&#xff08;如字母顺序或数字顺序&#xff09;进行排序。如果你需要按照自定义的排序规则对结果进行分组&#xff0c;可以使用 ORDER BY 子句结合 …

语言模型理论基础-持续更新-思路清晰

1.预训练 相似的任务A、B&#xff0c;任务A已经用大数据完成了训练&#xff0c;得到模型A。 我们利用-特征提取模型的-“浅层参数通用”的特性&#xff0c;使用模型A的浅层参数&#xff0c;其他参数再通过任务B去训练&#xff08;微调&#xff09;。 2.统计语言模型 通过条件…

ResNet与注意力机制:深度学习中的强强联合

引言 在深度学习领域&#xff0c;卷积神经网络&#xff08;CNN&#xff09;一直是图像处理任务的主流架构。然而&#xff0c;随着网络深度的增加&#xff0c;梯度消失和梯度爆炸问题逐渐显现&#xff0c;限制了网络的性能。为了解决这一问题&#xff0c;ResNet&#xff08;残差…

【C++】——C++11新特性

目录 前言 1.初始化列表 2.std::initializer_list 3.auto 4.decltype 5.nullptr 6.左值引用和右值引用 6.1右值引用的真面目 6.2左值引用和右值引用比较 6.3右值引用的意义 6.3.1移动构造 6.4万能引用 6.5完美转发——forward 结语 前言 C&#xff0c;这门在系统…

【C++网络编程】第5篇:UDP与广播通信

一、UDP协议核心特性 1. UDP vs TCP ​特性 ​UDP​TCP连接方式无连接面向连接&#xff08;三次握手&#xff09;可靠性不保证数据到达或顺序可靠传输&#xff08;超时重传、顺序控制&#xff09;传输效率低延迟&#xff0c;高吞吐相对较低&#xff08;因握手和确认机制&…

MOSN(Modular Open Smart Network)是一款主要使用 Go 语言开发的云原生网络代理平台

前言 大家好&#xff0c;我是老马。 sofastack 其实出来很久了&#xff0c;第一次应该是在 2022 年左右开始关注&#xff0c;但是一直没有深入研究。 最近想学习一下 SOFA 对于生态的设计和思考。 sofaboot 系列 SOFABoot-00-sofaboot 概览 SOFABoot-01-蚂蚁金服开源的 s…

微信小程序日常开发问题整理

微信小程序日常开发问题整理 1 切换渲染模式1.1 WebView 的链接在模拟器可以打开&#xff0c;手机上无法打开。 1 切换渲染模式 1.1 WebView 的链接在模拟器可以打开&#xff0c;手机上无法打开。 在 app.json 中看到如下配置项&#xff0c;那么当前项目就是 keyline 渲染模式…

【Altium Designer】铜皮编辑

一、动态铜皮与静态铜皮的区分与切换 动态铜皮&#xff08;活铜&#xff09;&#xff1a; 通过快捷键 PG 创建&#xff0c;支持自动避让其他网络对象&#xff0c;常用于大面积铺铜&#xff08;如GND或电源网络&#xff09;。修改动态铜皮后&#xff0c;需通过 Tools → Polygo…

Java「Deque」 方法详解:从入门到实战

Java Deque 各种方法解析&#xff1a;从入门到实战 在 Java 编程中&#xff0c;Deque&#xff08;双端队列&#xff09;是一个功能强大的数据结构&#xff0c;允许开发者从队列的两端高效地添加、删除和检查元素。作为 java.util 包中的一部分&#xff0c;Deque 接口继承自 Qu…

ffmpeg+QOpenGLWidget显示视频

​一个基于 ‌FFmpeg 4.x‌ 和 QOpenGLWidget的简单视频播放器代码示例&#xff0c;实现视频解码和渲染到 Qt 窗口的功能。 1&#xff09;ffmpeg库界面&#xff0c;视频解码支持软解和硬解方式。 硬解后&#xff0c;硬件解码完成需要将数据从GPU复制到CPU。优先采用av_hwf…

深入解析嵌入式内核:从架构到实践

一、嵌入式内核概述 嵌入式内核是嵌入式操作系统的核心组件&#xff0c;负责管理硬件资源、调度任务、处理中断等关键功能。其核心目标是在资源受限的环境中提供高效、实时的控制能力。与通用操作系统不同&#xff0c;嵌入式内核通常具有高度可裁剪性、实时性和可靠性&#xff…

20250324-使用 `nltk` 的 `sent_tokenize`, `word_tokenize、WordNetLemmatizer` 方法时报错

解决使用 nltk 的 sent_tokenize, word_tokenize、WordNetLemmatizer 方法时报错问题 第 2 节的手动方法的法1可解决大部分问题&#xff0c;可首先尝试章节 2 的方法 1. nltk.download(‘punkt_tab’) LookupError: *******************************************************…

『 C++ 』多线程同步:条件变量及其接口的应用实践

文章目录 条件变量概述条件变量简介条件变量的基本用法 案例&#xff1a;两个线程交替打印奇偶数代码解释 std::unique_lock::try_lock_until 介绍代码示例代码解释注意事项 std::condition_variable::wait 详细解析与示例std::condition_variable::wait 接口介绍代码示例代码解…