当前位置: 首页 > news >正文

Android AudioFlinger(五)—— 揭开AudioMixer面纱

前言:

在 Android 音频系统中,AudioMixer 是音频框架中一个关键的组件,用于处理多路音频流的混音操作。它主要存在于音频回放路径中,是 AudioFlinger 服务的一部分。

上一节我们讲threadloop的时候,提到了一个函数prepareTracks_l,在这个函数的最后就调用了 mAudioMixer->create、mAudioMixer->setParameter去设置参数,channel、format、volume等等。

AudioMixer继承自 AudioMixerBase,当我们去看AudioMixer的构造函数的时候发现并没有做任何操作
在这里插入图片描述

那他的初始化代码在哪里呢?

走进AudioMixer:

我们看prepareTracks_l内关于mAudioMixer的调用流程就可以发现,他首先调用了create函数,然而Audiomixer内部却没有实现create接口,我们追溯到它的父类,发现在AudioMixerBase对象种定义了create接口并且实现了。

我们粗略的看下create里主要做了什么,代码多我做了删减。

status_t AudioMixerBase::create(int name, audio_channel_mask_t channelMask, audio_format_t format, int sessionId)
{LOG_ALWAYS_FATAL_IF(exists(name), "name %d already exists", name);if (!isValidChannelMask(channelMask)) {ALOGE("%s invalid channelMask: %#x", __func__, channelMask);return BAD_VALUE;}if (!isValidFormat(format)) {ALOGE("%s invalid format: %#x", __func__, format);return BAD_VALUE;}auto t = preCreateTrack();{t->needs = 0;t->volume[0] = 0;...t->channelCount = audio_channel_count_from_out_mask(channelMask);t->enabled = false;t->channelMask = channelMask;t->sessionId = sessionId;t->hook = NULL;...// setBufferProvider(name, AudioBufferProvider *) is required before enable(name)t->sampleRate = mSampleRate;t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT;t->mFormat = format;t->mMixerChannelCount = audio_channel_count_from_out_mask(t->mMixerChannelMask);t->mInputFrameSize = audio_bytes_per_frame(t->channelCount, t->mFormat);status_t status = postCreateTrack(t.get());if (status != OK) return status;mTracks[name] = t;return OK;}
}

可以看到除了一开始做了channel和format的判断,后面基本上就是对track的初始化,像volume、channel、format、sampleRate还有Hook的初始化。

初始化完成后就开始调用AudioMixer内部的接口了,我们依次往下看发现还有getUnreleasedFrames、setParameter、setBufferProvider、process等。
我们先看下setParameter,当属性变化的时候就会调用到这里。


void AudioMixer::setParameter(int name, int target, int param, void *value)
{LOG_ALWAYS_FATAL_IF(!exists(name), "invalid name: %d", name);const std::shared_ptr<Track> &track = getTrack(name);int valueInt = static_cast<int>(reinterpret_cast<uintptr_t>(value));int32_t *valueBuf = reinterpret_cast<int32_t*>(value);switch (target) {case TRACK:switch (param) {case CHANNEL_MASK: {const audio_channel_mask_t trackChannelMask =static_cast<audio_channel_mask_t>(valueInt);if (setChannelMasks(name, trackChannelMask,static_cast<audio_channel_mask_t>(track->mMixerChannelMask | track->mMixerHapticChannelMask))) {ALOGV("setParameter(TRACK, CHANNEL_MASK, %x)", trackChannelMask);invalidate();}} break;case MAIN_BUFFER:if (track->mainBuffer != valueBuf) {track->mainBuffer = valueBuf;ALOGV("setParameter(TRACK, MAIN_BUFFER, %p)", valueBuf);if (track->mKeepContractedChannels) {track->prepareForAdjustChannels(mFrameCount);}invalidate();}break;case AUX_BUFFER:AudioMixerBase::setParameter(name, target, param, value);break;case FORMAT: {audio_format_t format = static_cast<audio_format_t>(valueInt);if (track->mFormat != format) {ALOG_ASSERT(audio_is_linear_pcm(format), "Invalid format %#x", format);track->mFormat = format;ALOGV("setParameter(TRACK, FORMAT, %#x)", format);track->prepareForReformat();invalidate();}} break;case MIXER_FORMAT: {audio_format_t format = static_cast<audio_format_t>(valueInt);if (track->mMixerFormat != format) {track->mMixerFormat = format;ALOGV("setParameter(TRACK, MIXER_FORMAT, %#x)", format);if (track->mKeepContractedChannels) {track->prepareForAdjustChannels(mFrameCount);}}} break;case MIXER_CHANNEL_MASK: {const audio_channel_mask_t mixerChannelMask =static_cast<audio_channel_mask_t>(valueInt);if (setChannelMasks(name, static_cast<audio_channel_mask_t>(track->channelMask | track->mHapticChannelMask),mixerChannelMask)) {ALOGV("setParameter(TRACK, MIXER_CHANNEL_MASK, %#x)", mixerChannelMask);invalidate();}} break;
...default:LOG_ALWAYS_FATAL("setParameter track: bad param %d", param);}break;case RESAMPLE:case RAMP_VOLUME:case VOLUME:AudioMixerBase::setParameter(name, target, param, value);break;case TIMESTRETCH:switch (param) {case PLAYBACK_RATE: {const AudioPlaybackRate *playbackRate =reinterpret_cast<AudioPlaybackRate*>(value);
...} break;default:LOG_ALWAYS_FATAL("setParameter timestretch: bad param %d", param);}break;default:LOG_ALWAYS_FATAL("setParameter: bad target %d", target);}
}

函数的主要结构就是一个switch,首先通过trackId找到对应的track对象,然后去设置对应track的parameter参数,例如 CHANNEL_MASK、FORMAT、MAIN_BUFFER等。

这只是设置参数,那混音在哪里呢?我们继续往下看process

void process() {preProcess();(this->*mHook)();postProcess();
}

这里主要就是调用mHook,mHook是一个函数指针,他会根据不同的场景分别调用不同的函数。

  • process__nop:初始值
  • process__genericResampling:对两路以上的track进行重采样操作
  • process__genericNoResampling:对两路以上的track不进行重采样操作
  • process__validate:这个函数就是根据当前的不同情况将mHook指向不同的函数
  • process__oneTrack16BitsStereoNoResampling:只有一路track,16bit,立体声的时候不进行重采样
process_hook_t mHook = &AudioMixerBase::process__nop;

mHook初始化的时候指向的是process__nop

void invalidate() {mHook = &AudioMixerBase::process__validate;}

process__validate是在invalidate函数里幅值给了mHook 指针。

void AudioMixerBase::process__validate()
{// select the processing hooksmHook = &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 {// we keep temp arrays around.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) {// The check prevents a muted track from acquiring a process hook.//// This is dangerous if the track is MONO as that requires// special case handling due to implicit channel duplication.// Stereo or Multichannel should actually be fine here.mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat,t->useStereoVolume());}}}}}
}

这个函数首先使用while循环来遍历每一个track,然后通过 NEEDS_RESAMPLE、NEEDS_AUX、NEEDS_CHANNEL_1、NEEDS_MUTE等判断,最终得到resampling、all16BitsStereoNoResample、volumeRamp的值,然后基于这几个值来决定调用,mHook来指向哪一个函数。

至于音频流数据是如何混到一起的,我们后面章节再来进一步分析。

相关文章:

Android AudioFlinger(五)—— 揭开AudioMixer面纱

前言&#xff1a; 在 Android 音频系统中&#xff0c;AudioMixer 是音频框架中一个关键的组件&#xff0c;用于处理多路音频流的混音操作。它主要存在于音频回放路径中&#xff0c;是 AudioFlinger 服务的一部分。 上一节我们讲threadloop的时候&#xff0c;提到了一个函数pr…...

分类学习(加入半监督学习)

#随机种子固定&#xff0c;随机结果也固定 def seed_everything(seed):torch.manual_seed(seed)torch.cuda.manual_seed(seed)torch.cuda.manual_seed_all(seed)torch.backends.cudnn.benchmark Falsetorch.backends.cudnn.deterministic Truerandom.seed(seed)np.random.see…...

Serilog: 强大的 .NET 日志库

Serilog 是一个功能强大的日志记录库&#xff0c;专为 .NET 平台设计。它提供了丰富的 API 和可插拔的输出器及格式化器&#xff0c;使得开发者能够轻松定制和扩展日志记录功能。在本文中&#xff0c;我们将探索 Serilog 的基础知识、API 使用、配置和一些常见的示例。 1. 日志…...

Matlab——添加坐标轴虚线网格的方法

第一步&#xff1a;在显示绘制图的窗口&#xff0c;点击左上角 “编辑”&#xff0c;然后选“坐标区属性” 第二步&#xff1a;点 “网格”&#xff0c;可以看到添加网格的方框了...

π0及π0_fast的源码解析——一个模型控制7种机械臂:对开源VLA sota之π0源码的全面分析,含我司微调π0的部分实践

前言 ChatGPT出来后的两年多&#xff0c;也是我疯狂写博的两年多(年初deepseek更引爆了下)&#xff0c;比如从创业起步时的15年到后来22年之间 每年2-6篇的&#xff0c;干到了23年30篇、24年65篇、25年前两月18篇&#xff0c;成了我在大模型和具身的原始技术积累 如今一转眼已…...

TCP7680端口是什么服务

WAF上看到有好多tcp7680端口的访问信息 于是上网搜索了一下&#xff0c;确认TCP7680端口是Windows系统更新“传递优化”功能的服务端口&#xff0c;个人理解应该是Windows利用这个TCP7680端口&#xff0c;直接从内网已经具备更新包的主机上共享下载该升级包&#xff0c;无需从微…...

服务器python项目部署

角色&#xff1a;root, 其他用户应该也可以 1. 安装python3环境 #如果是新机器&#xff0c;尽量执行&#xff0c;避免未知报错 yum -y update python -v yum install python3 python3 -v2. 使用virtualenvwrapper 创建虚拟环境,并使用workon切换不同的虚拟环境 # 安装virtua…...

Hive-优化(语法优化篇)

列裁剪与分区裁剪 在生产环境中&#xff0c;会面临列很多或者数据量很大时&#xff0c;如果使用select * 或者不指定分区进行全列或者全表扫描时效率很低。Hive在读取数据时&#xff0c;可以只读取查询中所需要的列&#xff0c;忽视其他的列&#xff0c;这样做可以节省读取开销…...

C语言100天练习题【记录本】

C语言经典100题&#xff08;手把手 编程&#xff09; 可以在哔哩哔哩找到&#xff08;url:C语言经典100题&#xff08;手把手 编程&#xff09;_哔哩哔哩_bilibili&#xff09; 已解决的天数&#xff1a;一&#xff0c;二&#xff0c;五&#xff0c;六&#xff0c;八&#xf…...

记录排查服务器CPU负载过高

1.top 命令查看cpu占比过高的进程id 这里是 6 2. 查看进程中占用CPU过高的线程 id 这里是9 top -H -p 6 ps -mp 6 -o THREAD,tid,time 使用jstack 工具 产看进程的日志 需要线程id转换成16进制 jstack 6 | grep “0x9” 4.jstack 6 可以看进程的详细日志 查看日志发现是 垃圾回…...

Spring Boot 项目中 Redis 常见问题及解决方案

目录 缓存穿透缓存雪崩缓存击穿Redis 连接池耗尽Redis 序列化问题总结 1. 缓存穿透 问题描述 缓存穿透是指查询一个不存在的数据&#xff0c;由于缓存中没有该数据&#xff0c;请求会直接打到数据库上&#xff0c;导致数据库压力过大。 解决方案 缓存空值&#xff1a;即使…...

基于Spring Boot的校园失物招领系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

10 【HarmonyOS NEXT】 仿uv-ui组件开发之Avatar头像组件开发教程(一)

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; 目录 第一篇&#xff1a;Avatar 组件基础概念与设计1. 组件概述2. 接口设计2.1 形状类型定义2.2 尺寸类型定义2.3 组件属性接口 3. 设计原则4. 使用…...

OpenHarmony 5.0.0 Release

OpenHarmony 5.0.0 Release 版本概述 OpenHarmony 5.0.0 Release版本标准系统能力持续完善。相比OpenHarmony 5.0 Beta1&#xff0c;Release版本做出了如下特性新增或增强&#xff1a; 应用框架新增更多生命周期管理能力、提供子进程相关能力&#xff0c;可以对应用运行时的…...

RSA的理解运用与Pycharm组装Cryptodome库

1、RSA的来源 RSA通常指基于RSA算法的密码系统&#xff0c;令我没想到的是&#xff0c;其名字的来源竟然不是某个含有特别意义的单词缩写而成&#xff08;比如PHP&#xff1a;Hypertext Preprocessor(超文本预处理器)&#xff09;&#xff0c;而是由1977年提出该算法的三个歪果…...

Android 多用户相关

Android 多用户相关 本文主要记录下android 多用户相关的adb 命令操作. 1: 获取用户列表 命令: adb shell pm list users 输出如下: Users:UserInfo{0:机主:c13} running默认只有一个用户, id为0 &#xff0c;用户状态为运行 2: 创建新用户 命令&#xff1a; adb shell …...

第三课:异步编程核心:Callback、Promise与Async/Await

Node.js 是一个基于事件驱动的非阻塞 I/O 模型&#xff0c;这使得它非常适合处理高并发的网络请求。在 Node.js 中&#xff0c;异步编程是一项非常重要的技能。理解和掌握异步编程的不同方式不仅能提高代码的效率&#xff0c;还能让你更好地应对复杂的开发任务。本文将深入探讨…...

红果短剧安卓+IOS双端源码,专业短剧开发公司

给大家拆解一下红果短剧/河马短剧&#xff0c;这种看光解锁视频&#xff0c;可以挣金币的短剧APP。给大家分享一个相似的短剧APP源码&#xff0c;这个系统已接入穿山甲广告、百度广告、快手广告、腾讯广告等&#xff0c;类似红果短剧的玩法&#xff0c;可以看剧赚钱&#xff0c…...

C# ArrayPool

ArrayPool<T> 的作用ArrayPool<T> 的使用方式共享数组池自定义数组池 注意事项应用场景 在C#中&#xff0c;ArrayPool<T> 是一个非常有用的工具类&#xff0c;主要用于高效地管理数组的分配和回收&#xff0c;以减少内存分配和垃圾回收的压力。它属于 System…...

Conda 生态系统介绍

引言 Conda 是一个开源的包管理和环境管理系统,最初由 Continuum Analytics 开发,现为 Anaconda 公司维护。它在数据科学和 Python/R 生态中占据核心地位,因其能跨平台(Linux/Windows/macOS)管理依赖关系,并通过虚拟环境隔离不同项目的开发环境。Conda 的生态系统包含多…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

回溯算法学习

一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

华为OD机考-机房布局

import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...

Qemu arm操作系统开发环境

使用qemu虚拟arm硬件比较合适。 步骤如下&#xff1a; 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载&#xff0c;下载地址&#xff1a;https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...

C语言中提供的第三方库之哈希表实现

一. 简介 前面一篇文章简单学习了C语言中第三方库&#xff08;uthash库&#xff09;提供对哈希表的操作&#xff0c;文章如下&#xff1a; C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...

nnUNet V2修改网络——暴力替换网络为UNet++

更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...