当前位置:首页 » 《关注互联网》 » 正文

Android Audio知识梳理 看完这一篇就够了!_NEY10MAR的博客

29 人参与  2021年08月23日 08:43  分类 : 《关注互联网》  评论

点击全文阅读


~~~~~~~~~~~~~~~~~~~~~~~~~~~~zzZ

文章目录

  • 前言
  • 一、Audio基础
    • 1.音频基础属性
    • 2.音频格式
    • 3.音频处理
  • 二、整体架构
    • 1.概述
    • 2.Audio架构
  • 三、重要模块
    • 1.概述
    • 2.AudioTrack
    • 3.AudioRecord
    • 4.AudioManager
    • 5.AudioService
    • 6.AudioSystem
    • 7.AudioPolicyService
    • 8.AudioFlinger
  • 四、项目实例(汽车)
    • 1.前言
    • 2.CarAudio
    • 3.CarAudioManager
    • 4.CarAudioService
  • 总结


前言

Audio是安卓里面非常重要的模块,对于学习安卓开发不管是做APP或是系统层以及BSP的同行都可以进行学习,拓宽知识维度,为以后的工作提供便利。
本文语言通俗易懂,内容由简入繁,看不懂的地方可以反复琢磨,多多动脑思考。


坚持看完哦!

一、Audio基础

1.音频基础属性

音频指人耳可以听到的声音频率在 20HZ~20kHz 之间的声波。

声音的三要素:

1、音量(Volume)
也叫做响度(Loudness),人耳对声音强弱的主观感觉就是响度,响度和声波 振动的幅度有关。

2、音调(Pitch)
人耳对声音高低的感觉称为音调(也叫音频),音调主要与声波的频率有关。声波的频率高,则音调也高。

3、音色(Quality)
不同的发声体由于其材料、结构不同,则发出声音的音色也不同。

2.音频格式

音频格式是指要在计算机内播放或是处理音频文件,是对声音文件进行数、模转换的过程。

常见音频格式:

1、WAV
是微软公司专门为Windows开发的一种标准数字音频文件,该文件能记录各种单声道或立体声的声音信息,并能保证声音不失真。但是,占用空间太大。

2、MIDI
MIDI是Musical Instrument Digital Interface的缩写,又称作乐器数字接口,是数字音乐/电子合成乐器的统一国际标准。

MIDI 传输的不是声音信号, 而是音符、控制参数等指令, 它指示MIDI 设备要做什么,怎么做, 如演奏哪个音符、多大音量等。它们被统一表示成MIDI 消息(MIDI Message) 。

3、MP3
MP3是利用人耳对高频声音信号不敏感的特性,将时域波形信号转换成频域信号,并划分成多个频段,对不同的频段使用不同的压缩率,对高频加大压缩比(甚至忽略信号)对低频信号使用小压缩比,保证信号不失真。从而将声音用1∶10甚至1∶12的压缩率压缩。

这种压缩方式的全称叫MPEG Audio Player3,简称为MP3。

4、AAC
出现于1997年,基于MPEG-2的音频编码技术。由Fraunhofer IIS、杜比实验室、AT&T、索尼等公司共同开发,目的是取代MP3格式。

AAC,全称Advanced Audio Coding,是一种专为声音数据设计的文件压缩格式。与MP3不同,它采用了全新的算法进行编码,更加高效,具有更高的“性价比”。利用AAC格式,可使人感觉声音质量没有明显降低的前提下,更加小巧。苹果ipod、诺基亚手机支持AAC格式的音频文件。

3.音频处理

日常生活中我们听到的声波波形信号都是时间连续的,我们称这种信号为模拟信号,模拟信号需要量化成数字信号(离散、不连续的)以后才能被我们的计算机识别。

1、音频的量化
可以简单分为五个步骤:

(1)模拟信号采集
现实生活中的声音表现为连续的、平滑的波形,其横坐标为时间轴,纵坐标表示声音的强弱。

(2)采样
按照一定的时间间隔在连续的波上进行采样取值。

(3)量化
将采样得到的值进行量化处理,也就是给纵坐标定一个刻度,记录下每个采样的纵坐标的值。

(4)编码
将每个量化后的样本值转换成二进制编码,可以看到模拟信号经过采样、量化、编码后形成的二进制序列就是数字音频信号。

(5)数字信号
将所有样本二进制编码连起来存储在计算机上就形成了数字信号。

在这里插入图片描述
2、音频处理参数

(1)采样率
单位时间内对模拟信号的采样次数,也就是采样频率,采样频率越高,声音的还原就越真实越自然,当然数据量就越大。

我们日常生活中常见的采样率:

5kHz:仅能满足人们讲话的声音质量

8KHz:电话所用采样率, 对于人的说话已经足够

22.05KHz:达到 FM 广播的声音品质(适用于语音和中等品质的音乐)

44.1KHz:最常见的采样率标准,理论上的 CD 音质界限,可以达到很好的听觉效果

48KHz:比 CD 音质更加精确一些

对于高于 48KHz 的采样频率人耳已无法辨别出来了,所以在电脑上没有多少使用价值。

(2)采样位数
每个采样点能够表示的数据范围,用多少个 bit 表示。采样位数通常有 8 bits 或 16 bits 两种,采样位数越大,所能记录声音的变化度就越细腻,相应的数据量就越大。8 位字长量化(低品质)和 16 位字长量化(高品质),16 bit 是最常见的采样精度。

采样位数也被叫做采样精度、量化级、量化数据位数等。

比较一下,一段相同的音乐信息,16位声卡能把它分为64K个精度单位进行处理,而8位声卡只能处理256个精度单位, 造成了较大的信号损失,最终的采样效果自然是无法相提并论的。

(3)通道数
为了播放声音时能够还原真实的声场,在录制声音时在前后左右几个不同的方位同时获取声音,每个方位的声音就是一个声道。声道数是声音录制时的音源数量或回放时相应的扬声器数量,有单声道、双声道、多声道。

(4)码率
码率高低直接影响音质,码率高音质好,码率低音质差。

码率就是数据传输时单位时间传送的数据位数,一般我们用的单位是kbps即千位每秒。

就是一种音乐每秒播放的数据量,单位用bit表示,也就是二进制位。 bps就是比特率。一个字节相当于8个二进制位。也就是说128bps的4分钟的歌曲的文件大小是这样计算的:

码率 = 采样率 * 采样位数 * 声道数
(128/8)460=3840kB=3.8MB

(5)帧
音频数据是流式的,本身没有明确的一帧帧的概念,在实际的应用中,为了音频算法处理/传输的方便,一般约定俗成取 2.5ms~60ms 为单位的数据量为一帧音频。这个时间被称之为 “采样时间”,其长度没有特别的标准,它是根据编解码器和具体应用的需求来决定的。

音频帧的播放时间 = 一个AAC帧对应的采样样本的个数 / 采样频率(单位为s)。

音频在量化得到二进制的码字后,需要进行变换,而变换(MDCT)是以块为单位(block)进行的,一个块由多个(120或128)样本组成。而一帧内会包含一个或者多个块。帧的常见大小有960、1024、2048、4096等。一帧记录了一个声音单元,它的长度是样本长度和声道数的乘积。

例如,最常见的音频格式 MP3 的数据通常由两部分组成,一部分为 “ID3” 用来存储歌名、演唱者、专辑、音轨数等信息,另一部分为音频数据。音频数据部分以帧(frame)为单位存储,每个音频都有自己的帧头,每个帧头中存储了采样率等解码必须的信息,所以每一个帧都可以独立于文件存在和播放,这个特性加上高压缩比使得 MP3 文件成为了音频流播放的主流格式。

最后给出一个公式:
文件时长 = (文件总大小 - 头信息)/ (采样频率 * 采样位数 * 通道数 / 8)

3、音频编码
从信息论的观点来看,描述信源的数据是信息和数据冗余之和,即:数据=信息+数据冗余。音频信号在时域和频域上具有相关性,也即存在数据冗余。将音频作为一个信源,音频编码的实质是减少音频中的冗余。

根据编码方式的不同,音频编码技术分为三种:波形编码、参数编码和混合编码。

一般来说,波形编码的话音质量高,但编码率也很高;参数编码的编码率很低,产生的合成语音的音质不高;混合编码使用参数编码技术和波形编码技术,编码率和音质介于它们之间。

在这里插入图片描述
PCM:Pulse Code Modulation,脉冲编码调制。
ADPCM:Adaptive Differential Pulse Code Modulation,自适应差分脉冲编码调制。
SB-ADPCM:Sub band Adaptive Differential Pulse Code Modulation,子带-自适应差分脉冲编码调制。
LPC:Linear Predictive Coding,线性预测编码。
CELPC:Code Excited Linear Predictive Coding,码激励线性预测编码。
VSELPC:Vector Sum Excited Linear Predictive Coding,矢量和激励线性预测编码。
RPE-LTP:Regular Pulse Excited-Long Term Predictive,规则脉冲激励长时预测。
LD-CELP:Low Delay-Code Excited Linear Predictive,低时延码激励线性预测。
MPE:Multi-Pulse Excited,多脉冲激励。
PSTN:Public Switched Telephone Network,公共交换电话网。
ISDN:Integrated Services Digital Network,综合业务数字网。

二、整体架构

1.概述

Audio系统在Android中负责音频方面的数据流传输和控制功能,也负责音频设备的管理。这个部分作为Android的Audio系统的输入/输出层次,一般负责播放PCM声音输出和从外部获取PCM声音,以及管理声音设备和设置,注意,解码功能不在这里实现,在android系统里音频视频的解码是 opencore 或 stagefright 完成的,在解码之后才调用音频系统的接口,创建音频流并播放。

1、功能
·音频管理:负责音量调节、音频设备选择、响铃模式选择等;
·声音播放:负责一个音频流的创建、参数设置、播放、暂停、释放;
·声音录音:负责一个录音音轨的创建、管理;
·声音音效:负责控制声音的效果。

(Android 系统对audio的实现是比较复杂的,但实现的方法还是对音频系统的抽象)

2、服务
Audio是安卓系统中一个非常重要的模块,所有音频的操作都要经过它。它不仅为用户提供服务,而且还为应用开发者提供服务。要时刻记住所做的一切开发工作都是为了别人能更好的使用这个模块,服务好别人,并且保证我们的应用和系统能够稳定运行,但做到这些也不是一件简单的事。

从系统开机的那一刻起,Audio系统服务就随之启动,伴随其运行。当被需要时,要求实时做出回应来,给底层和用户一个合理的“交代”。

比如说:用户听歌时觉得音量过小,就要按手机上音量+键,Audio系统需要处理好用户的这次请求,做到准确,迅速。

再比如说,汽车正在播放歌曲,来了导航播报,多媒体需要和导航混音并且降低音量,然后再来蓝牙电话,多媒体和导航都需要停止播放,这些都是需要Audio系统来协调处理。

2.Audio架构

在这里插入图片描述
android 音频播放从app开始:

在framework层创建播放器

在audio library层做音频流和输出流控制

在Hal层将音频数据写入到输出设备进行声音输出。

其中audio library层是音频处理的核心。

App --> Frameworks --> Audio Library --> HAL

音频播放/录音流程图:

在这里插入图片描述
Audio系统的各层次介绍:
1、JAVA接口层

·AudioManager.java
为APP提供音量控制和响铃模式控制等功能,是音频管理器。

·AudioTrack .java
为APP提供管理和播放一个音频流的功能(必须是PCM流,因为不提供解码功能)。

·AudioRecord.java
为APP提供录音功能。

·AudioEffect.java
为APP提供控制音效的功能。

2、JAVA服务层
音频在java层的服务只有AudioService,他实现了IAudioService接口,AudioManager 是这个服务的客户端。

AudioService服务向下调用AudioSystem.java,AudioSystem.java再通过JNI调用到本地层。

Q1:AudioTrack 、AudioRecord和AudioEffect的调用情况?

他们是直接调用本地接口的。

Q2:为什么AudioTrack 、AudioRecord和AudioEffect没有java服务层?

这是因为一般的音频文件比如mp3、ogg等都是压缩的音频流,需要经过解码才能得到pcm数据流,解码是opencore或stagefright完成的,一般是播放器的服务,比如MediaPlayer,先解码音频文件,然后去创建AudioTrack、设置音效等,然后播放音乐。这些内容的实现一般是在本地服务层,所以AudioTrack 、AudioRecord和AudioEffect的服务层在本地层的AudioFlinger里实现就够了。

3、JNI层
Android的Audio部分通过JNI向Java层提供接口,在Java层可以通过JNI接口完成Audio系统的大部分操作。Audio JNI部分的代码路径为:frameworks/base/core/jni。

其中,主要实现的3个文件为:android_media_AudioSystem.cpp、android_media_Audio Track.cpp和android_media_AudioRecord.cpp,它们分别对应了Android Java框架中的3个类的支持:

·android.media.AudioSystem:负责Audio系统的总体控制;

·android.media.AudioTrack:负责Audio系统的输出环节;

·android.media.AudioRecorder:负责Audio系统的输入环节。

提示:
	现在基本都用HIDL代替了。

4、NATIVE层
Native层的代码和逻辑是整个音频模块的核心!!!

Android的Audio系统的核心框架在media库中提供,对上面主要实现AudioSystem、AudioTrack和AudioRecorder三个类,这其实是Audio系统的本地接口层。

AudioFlinger提供了IAudioFlinger类接口,在这个类中,可以获得IAudioTrack和IAudioRecorder两个接口,分别用于声音的播放和录制。AudioTrack和AudioRecorder分别通过调用IAudioTrack和IAudioRecorder来实现。

Native层采用了C/S的架构方式,AudioTrack是属于Client端的,AudioFlinger和AudioPolicyService是属于服务端的,AudioFlinger是唯一可以调用HAL层接口的地方,而AudioPolicyService主要是对一些音频策略的选择和一些逻辑调用的中转。

5、硬件抽象层(HAL)
HAL 定义了音频服务会调用且您必须实现才能使音频硬件正常运行的标准接口

android audio framework中的audio flinger是通过操作audio hal层对间接的对底层设备进行操作的。(音频数据的读写以及各种参数的设定)。

音频 HAL 由以下接口组成:

hardware/libhardware/include/hardware/audio.h 表示音频设备的主函数。

hardware/libhardware/include/hardware/audio_effect.h 表示可应用于音频的效果,如缩混、回音或噪音抑制。

	现在HIDL用的很广泛了,HAL层也不用做一些重复的开发了,减轻了一些负担。

6、驱动层
Audio的驱动是基于Linux标准的音频驱动:OSS(Open Sound System)或者ALSA(Advanced Linux Sound Architecture)驱动程序来实现的。

手机上用的也有用TINYALSA,是ALSA的精简版,主要功能都在。

三、重要模块

1.概述

之前了解了安卓整个Audio系统的分层,整体架构是怎么样的,大概在心里也已经有了一些初步的轮廓。现在开始细化,挑选出几个重要的模块,逐个分析,找出其中的联系,并且知道其工作原理,目的就算达到了。

在这几个重要的类中,有提供给apk开发者使用的接口,还有核心的几个Audio系统服务。

当我们要使用Audio的API时,要回想一下第一章提到过的那几个音频参数的具体含义。

2.AudioTrack

AudioTrack是最灵活、高级的音频API,用它可以实现将原始音频数据直接送到硬件的操作。是管理和播放单一音频资源的类,仅仅能播放已经解码的PCM流,用于PCM音频流的回放。

想使用好这个API播放音频数据,可以分为五个步骤:
1、 配置基本参数
(1)StreamType音频流类型:最主要的几种STREAM

		AudioManager.STREAM_MUSIC:用于音乐播放的音频流。	
		AudioManager.STREAM_SYSTEM:用于系统声音的音频流。	
		AudioManager.STREAM_RING:用于电话铃声的音频流。
		AudioManager.STREAM_VOICE_CALL:用于电话通话的音频流。
		AudioManager.STREAM_ALARM:用于警报的音频流。
		AudioManager.STREAM_NOTIFICATION:用于通知的音频流。
		AudioManager.STREAM_BLUETOOTH_SCO:用于连接到蓝牙电话的手机音频流。
		AudioManager.STREAM_SYSTEM_ENFORCED:在某些国家实施的系统声音的音频流。
		AudioManager.STREAM_DTMF:DTMF音调的音频流。
		AudioManager.STREAM_TTS:文本到语音转换(TTS)的音频流。	

(2)MODE模式(static和stream两种)
AudioTrack.MODE_STREAM:
STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到AudioTrack中。

AudioTrack.MODE_STATIC:
STATIC就是数据一次性交付给接收方。

(3)采样率:mSampleRateInHz
采样率 (MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)

(4)通道数目:mChannelConfig
声道设置:android支持双声道立体声和单声道。MONO单声道,STEREO立体声.

CHANNEL_OUT_MONO;
CHANNEL_OUT_STEREO。

(5)音频量化位数:mAudioFormat(只支持8bit和16bit两种)
ENCODING_PCM_16BIT;
ENCODING_PCM_8BIT;

采样大小越大,那么信息量越多,音质也越高,现在主流的采样大小都是16bit,在低质量的语音传输的时候8bit足够了。

2、获取最小缓冲区大小
根据采样率,采样精度,单双声道来得到frame的大小。

计算最小缓冲区:
AudioTrack.getMinBufferSize(mSampleRateInHz,mChannelConfig, mAudioFormat);

注意,按照数字音频的知识,这个算出来的是一秒钟buffer的大小。**

3、创建AudioTrack对象
取到mMinBufferSize后,我们就可以创建一个AudioTrack对象了。它的构造函数原型是:
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode);

构造函数里面的参数我们之前都讲过了,所以创建时根据需求自行指定即可。

4、获取PCM文件,转成DataInputStream
根据存放PCM的路径获取到PCM文件。

	File file = new File(path);
	mDis = new DataInputStream(new FileInputStream(file));

5、开启/停止播放
(1)开始播放
先把音频数据放到AudioTrack获取到的缓冲中。
readCount = mDis.read(MinBufferSize);
先调用AudioTrack.play();
之后就可以写数据了,AudioTrack.write(MinBufferSize, 0, readCount);

(2)停止播放
停止播放音频数据,如果是STREAM模式,会等播放完最后写入buffer的数据才会停止。如果立即停止,要调用pause()方法,然后调用flush方法,会舍弃还没有播放的数据。

(3)释放资源
AudioTrack.release();

3.AudioRecord

看完AudioTrack了,再看AudioRecord的话就会非常简单。
一般情况下录音实现的简单流程如下:(实现不难,不具体说了)
1、创建一个数据流。
2、构造一个AudioRecord对象,其中需要的最小录音缓存buffer大小可以通getMinBufferSize方法得到。如果buffer容量过小,将导致对象构造的失败。
3、初始化一个buffer,该buffer大于等于AudioRecord对象用于写声音数据的buffer大小。
4、开始录音。
5、从AudioRecord中读取声音数据到初始化buffer,将buffer中数据导入数据流。
6、停止录音。
7、关闭数据流。

4.AudioManager

AudioManager实现了AudioSystem类与JAVA层系统服务AudioService的封装,主要作用为向应用程序层提供Audio系统的控制接口。提供了丰富的API让开发者对应用的音量和铃声模式等进行控制以及访问。主要内容涉及到音频流、声音、蓝牙、扩音器、耳机等等。

常用功能:
1、音量控制
调音量有两个比较重要的接口。

(1)adjustStreamVolume(int streamType, int direction, int flags);
这个方法主要是用于按键调音,音量+/-。按照规定好的步进值和方向调整指定流的音量大小。

streamType:是你要调节的流。比如说,音乐,铃声,闹钟等。

direction:调节的方向,也就是增大/减小,或者保持不变。(ADJUST_LOWER;
ADJUST_RAISE;ADJUST_SAME)。

flags:常用就是是否在调节音量时显示UI。

(2)setStreamVolume(int streamType, int index, int flags);

这函数是可以直接设置特定流的音量值,主要是用在系统设置中进行调节。

这个函数和上一个就一个参数不同,但是很简单。

index代表的就是设置的音量值。

2、静音控制

(1) setMasterMute(boolean mute, int flags);

这个函数的作用是设置全局静音,一直设置到BSP。

参数一个是mute的布尔值,另一个flags之前说过。

(2)setStreamMute(int streamType, boolean state);
设置指定流静音,其内部静音实现就是在FW把该流的音量设置为0。

3、音频模式/状态
AudioMode用于描述整个Audio部分的当前状态。

			MODE_NORMAL:正常状态(默认)。
			MODE_RINGTONE:响铃状态(来电)。
			MODE_IN_CALL:呼叫电话状态(拨出未接通)。
			MODE_IN_COMMUNICATION:电话状态(接通)。

这些状态的改变会影响音频数据在Audio各输出设备中的路由选择。
还有各种铃声状态也都很常用:

			RINGER_MODE_NORMAL:正常模式,可以听到铃声。
			RINGER_MODE_SILENT:静音模式,无铃声,无震动。
			RINGER_MODE_VIBRATE:震动模式,无铃声,有震动。

4、音频焦点
(1)目的:
AudioFocus机制是为解决系统中Audio资源的竞争问题而引入的。

(2)要求:
需要所有参与Audio竞争的竞争者主动去遵循,Audio Focus机制才能更好的工作。

(3)申请焦点:
现在只说安卓8.0以上的接口。
public int requestAudioFocus(@NonNull AudioFocusRequest focusRequest);

可以看到参数只有一个AudioFocusRequest,那么重点肯定就在这里边了,其中一定设置了许多参数,包括音源类型、监听器等。

所以先看这个AudioFocusRequest。

AudioFocusRequest类是一个封装有关音频焦点请求的信息的类。
看个例子更加直观:

		 mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                			.setWillPauseWhenDucked(true)
                			.setAcceptsDelayedFocusGain(true)
               			 	.setOnAudioFocusChangeListener(mListener, mHandler)
               				 .setAudioAttributes(mAttribute)
                			.build();
                			
	AUDIOFOCUS_GAIN:当前声音的唯一的音频来源,持续多长时间未知,结束后也不希望恢复另一个音频流。
	AUDIOFOCUS_GAIN_TRANSIENT:应用程序暂时从当前所有者那里获取焦点,音频播放完后恢复以前的音频播放, 表示请求焦点时已经有人在持有焦点
	AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:和上面的一样临时获取焦点,但他可以在拥有焦点期间,另一个应用可以降低音量继续播放,俗称 躲闪,
	AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:也是临时请求焦点,在拥有焦点期间不希望设备播放任何内容。就是在持有焦点期间,在有请求焦点时禁止把焦点分给其他人。

setWillPauseWhenDucked():声明应用程序在音频回避方面的预期行为,也就是说如果我不希望系统自动给我降低音量,而是想自己暂停音频相关的工作,可以调这个函数取消系统的默认行为。这样通过监听音频焦点变化,来自己处理。

setAcceptsDelayedFocusGain():这个是为了能够延迟获取到焦点的必须条件,但是同时也必须要设置AudioManager.OnAudioFocusChangeListener才能得知何时获取到焦点。

setOnAudioFocusChangeListener():设置音频焦点变化监听器。值得一提的是这个方法有个重载的方法,有一个重载方法有两个参数,第二个参数为Handler对象,看到Handler应该明白了,是为了使用它的消息队列来顺序处理这个回调。

setAudioAttributes(): 这个方法是用来描述app的使用情况。这方法需要传入一个AudioAttributes对象,这个对象也是使用Builder模式来构造。

例如使用AudioAttributes.Builder.setUsage()来描述使用这个音频来干什么,我们可以传入一个AudioAttributes.USAGE_MEDIA来表明用这个音频来作为媒体文件来播放,也可以传入一个AudioAttributes.USAGE_ALARM来表明用这个来作为闹铃。

(4)释放焦点:
public int abandonAudioFocus(OnAudioFocusChangeListener l);
参数就是之前说过的音频焦状态变化监听器。

5.AudioService

AudioService这个系统服务包含或者使用了几乎所有与音频有关的内容,AudioService是音频系统在java层的大本营

1、介绍
(1)音频系统在java层中基本上不参与数据流的,AudioService这个系统服务包含或者使用了几乎所有与音频有关的内容,所以说AudioService是音频系统在java层的大本营。

(2)AudioManager拥有AudioService的Bp端,是AudioService在客户端的一个代理,几乎所有客户端对AudioManager进行的请求,最终都会交由AudioService实现。

(3)AudioService的功能实现依赖于AudioSystem类,AudioSystem无法实例化,它是java层到native层的代理,AudioService通过它与AudioPolicyService以及AudioFlinger进行通信。

2、音量管理
(1)AudioService音量管理的核心是VolumeStreamState,它保存了一个流类型所有的音量信息。

(2)VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的。

所以进行音量设置需要做两件事情:更新VolumeStreamState存储的音量值,设置音量到Audio底层系统。

6.AudioSystem

AudioSystem在Audio系统中属于一个承上启下的类,在java和native都有它,所以我们在上层调用的很多功能都经过它,再经过它流转到native服务。

1、介绍
(1)AudioSystem是Audio子系统面向framework的接口,这里面有很多一竿子戳到底的函数。同样,Audio子系统内部也往往使用AudioSystem进行通信,比如AF和APM。

(2)AudioService通过对AudioSystem进行函数调用与Audio系统进行通信。

(3)AudioSystem和AudioFlinger以及AudioPolicyService的双向通信机制。

7.AudioPolicyService

我们知道,在 Audio系统中,核心的两块是 AudioPolicy 和 AudioFlinger,其中 AudioPolicy 相当于军师的角色,专门来制定 Audio 播放时的相关的策略及设定相关的参数,而 AudioFlinger 相当于将军,会根据军师的策略来执行。
所以重点都在这两个服务上,那么就先看AudioPolicyService这个负责“指挥“的服务,最好的办法就是看它启动时都分别干了什么。

启动流程分析:
1、AudioServer
(1)main_audioserver.cpp会编译成可执行文件audioserver,在主线程中,主要是监控子进程的状态,而在它的子进程中分别调用 AudioFlinger::instantiate() 和 AudioPolicyService::instantiate(),分别对AudioFlinger 和 AudioPolicyService 进行初始化。

(2)同样会被它初始化的还有语音识别 SoundTriggerHwService::instantiate()和 VR 语音模块 VRAudioServiceNative::instantiate(),不过这些不是我们目前的重点,后续再看。

具体代码如下:
xref: /frameworks/av/media/audioserver/main_audioserver.cpp

int main(int argc __unused, char **argv)
{
    // TODO: update with refined parameters
    limitProcessMemory(
        "audio.maxmem", /* "ro.audio.maxmem", property that defines limit */
        (size_t)512 * (1 << 20), /* SIZE_MAX, upper limit in bytes */
        20 /* upper limit as percentage of physical RAM */);

    signal(SIGPIPE, SIG_IGN);

    bool doLog = (bool) property_get_bool("ro.test_harness", 0);

    pid_t childPid;
    // FIXME The advantage of making the process containing media.log service the parent process of
    // the process that contains the other audio services, is that it allows us to collect more
    // detailed information such as signal numbers, stop and continue, resource usage, etc.
    // But it is also more complex.  Consider replacing this by independent processes, and using
    // binder on death notification instead.
    if (doLog && (childPid = fork()) != 0) {
        // media.log service
        //prctl(PR_SET_NAME, (unsigned long) "media.log", 0, 0, 0);
        // unfortunately ps ignores PR_SET_NAME for the main thread, so use this ugly hack
        strcpy(argv[0], "media.log");
        sp<ProcessState> proc(ProcessState::self());
        MediaLogService::instantiate();
        ProcessState::self()->startThreadPool();
        IPCThreadState::self()->joinThreadPool();
       	......
        sp<ProcessState> proc(ProcessState::self());
        sp<IServiceManager> sm = defaultServiceManager();
        ALOGI("ServiceManager: %p", sm.get());
        AudioFlinger::instantiate();
        AudioPolicyService::instantiate();

        // AAudioService should only be used in OC-MR1 and later.
        // And only enable the AAudioService if the system MMAP policy explicitly allows it.
        // This prevents a client from misusing AAudioService when it is not supported.
        aaudio_policy_t mmapPolicy = property_get_int32(AAUDIO_PROP_MMAP_POLICY,
                                                        AAUDIO_POLICY_NEVER);
        if (mmapPolicy == AAUDIO_POLICY_AUTO || mmapPolicy == AAUDIO_POLICY_ALWAYS) {
            AAudioService::instantiate();
        }

        SoundTriggerHwService::instantiate();
        ProcessState::self()->startThreadPool();
        IPCThreadState::self()->joinThreadPool();
    }

2、AudioPolicyService::instantiate()
首先注册一个名为 media.audio_policy 的服务,在注册服务过程中会首先调用 AudioPolicyService::onFirstRef() 函数,它是我们 AudioPolicySerivce 启动时的核心代码。

3、AudioPolicyService::onFirstRef()
在该代码中,主要工作如下:
(1)创建了三个AudioCommandThread,名字分别为"ApmTone",“ApmAudio”,“ApmOutput”。

(2)实例化 AudioPolicyClient 对象

(3)初始化 AudioPolicyManager ,传参就是 AudioPolicyClient对象。

(4)AudioPolicyEffects 音效初始化。

void AudioPolicyService::onFirstRef()
{
    {
        Mutex::Autolock _l(mLock);

        // start tone playback thread,用于播放tone音,tone是音调的意思
        mTonePlaybackThread = new AudioCommandThread(String8("ApmTone"), this);
        // start audio commands thread,用于执行audio命令
        mAudioCommandThread = new AudioCommandThread(String8("ApmAudio"), this);
        // start output activity command thread,用于执行audio输出命令
        mOutputCommandThread = new AudioCommandThread(String8("ApmOutput"), this);
        //实例化AudioPolicyClient对象
        mAudioPolicyClient = new AudioPolicyClient(this);
        //实例化AudioPolicyManager对象
        mAudioPolicyManager = createAudioPolicyManager(mAudioPolicyClient);
    }
    // load audio processing modules
    //初始化音效相关
    sp<AudioPolicyEffects>audioPolicyEffects = new AudioPolicyEffects();
    {
               mAudioPolicyEffects = audioPolicyEffects;
    }

}

4、在 AudioPolicyManager 初始化过程中
(1)解析Audio 配置文件,audio_policy.xml 和 audio_policy.conf,解析配置文件中当前系统所支持的输出设备mAvailableOutputDevices、输入设备mAvailableInputDevices、默认输出设备mDefaultOutputDevice。

(2)调用 config.setDeafult() 初始化 mHwModules ,配置 Module 的名字为 “primary”,配置默认输出设备为 AUDIO_DEVICE_OUT_SPEAKER,默认输入设备为 AUDIO_DEVICE_IN_BUILTIN_MIC。

(3)通过 getName() 加载模块,调用loadHwModule_l 函数初始化 HwModule 模块,调用 openDevice 初始化 hardware 层的audio配置,hardware 层会返回给上层 Audio 操作方法,及初始化 输入链表 streams_input_cfg_list 和输出流链表 streams_output_cfg_list ,

(4)初始化默认音量解级参数,包括 通话、系统、铃声、闹钟、通知、蓝牙、拨号盘等音量等级。

(5)给每一个输入 和 输出设备,都分配一个线程,及对应的输入 和 输出 stream 流,根据 audio类型调用不同的函数(Playback 、 OFFLOAD 、 DIRECT 类型等。

(6) 最终所有的信息保存在 HwModule[ ] 中,将所有的输出设备保存在 mDeviceForStrategy[ ] 中。

	至此,所有的输入设备和输出设备 都有对应的stream及单独的 thread 。

5、AudioPolicyEffects 音效初始化

解析audio_effects.conf 文件,得到并加载 系统支持的音效库。初始化各个音效对应的参数,将各音效和 对应的输入 和输出流绑定在一起,这样,当上层要初始化使用音效时,就会在对应的threadloop中调用process_l音效处理函数。创建一个AudioFlinger客户端,将 effect 和 AudioFlinger 客户端绑定在一起。

	注:
	声音通路配置,sources 是声音输信端口,sink 是声音输出端口	
	播放:source(MixerPort)->sink(DevicePort)
	录音:source(DevicePort)->sink(MixerPort)

(借用几张图片)
加载完系统定义的所有音频接口,并生成相应的数据对象,如👇图所示:

在这里插入图片描述
打开音频输出后,在AudioFlinger与AudioPolicyService中的表现形式如👇图:
在这里插入图片描述
在这里插入图片描述
6、总结
打开音频输出时创建一个audio_stream_out通道,并创建AudioStreamOut对象以及新建PlaybackThread播放线程。

打开音频输入时创建一个audio_stream_in通道,并创建AudioStreamIn对象以及创建RecordThread录音线程。

8.AudioFlinger

AudioFlinger 是音频系统策略的执行者,负责音频流设备的管理及音频流数据的处理传输,所以 AudioFlinger 也被认为是 Android 音频系统的引擎。

最近的版本AudioFlinger这一块变得更加模块化了,把其中的子类提取出为独立的文件,而AudioFlinger.cpp只包含对外提供的服务接口了。另外相比以前,增加更多的功能特性。先来看看这些被提取出来的独立的文件,有个印象。

	AudioResampler.cpp:
		重采样处理类,可进行采样率转换和声道转换;由录制线程 AudioFlinger::RecordThread 直接使用。
	AudioMixer.cpp:
		混音处理类,包括重采样、音量调节、声道转换等,其中的重采样复用了 AudioResampler;由回放线程 AudioFlinger::MixerThread 直接使用。
	Effects.cpp:
		音效处理类。
	Tracks.cpp:
		音频流管理类,可控制音频流的状态,如 start、stop、pause。
	Threads.cpp:
		回放线程和录制线程类;回放线程从 FIFO 读取回放数据并混音处理,然后写数据到输出流设备;录制线程从输入流设备读取录音数据并重采样处理,然后写数据到 FIFO。
	AudioFlinger.cpp:
		AudioFlinger 对外提供的服务接口。

AudioFlinger 对外提供的主要的服务接口如下:
在这里插入图片描述
可以归纳出 AudioFlinger 响应的服务请求主要有:
① 获取硬件设备的配置信息
② 音量调节
③ 静音操作
④ 音频模式切换
⑤ 音频参数设置
⑥ 输入输出流设备管理
⑦ 音频流管理

现在,我们介绍了AudioFlinger它的各个子类以及它作为服务自身对外提供的接口,之前在说AudioPolicyService的时候也知道了它们都是在audioserver里启动的,所以我们要看AudioFlinger这个服务时启动时都做了什么。

AudioFlinger::instantiate() 调用如下:
1、注册 AudioFlinger 服务名为 “media.audio_flinger”。

2、调用 AudioFlinger 构造函数。在构造函数中,主要是做一下初始化,初始化了 mDevicesFactoryHal 设备接口 和 mEffectsFactoryHal 音效接口。

3、调用 AudioFlinger::onFirstRef 函数。在其中构造一个 PatchPanel 对象;修改 mode 为 AUDIO_MODE_NORMAL。

AudioFlinger 服务启动后,其他进程可以通过 ServiceManager 来获取其代理对象 IAudioFlinger,通过 IAudioFlinger 可以向 AudioFlinger 发出各种服务请求,从而完成自己的音频业务。

看来它在初始化时做的工作没有AudioPolicyService那么复杂,但是AudioFlinger有着更重要的工作,我们继续来看。

AndioFlinger 作为 Android 的音频系统引擎,重任之一是负责输入输出流设备的管理及音频流数据的处理传输,这是由回放线程(PlaybackThread 及其派生的子类)和录制线程(RecordThread)进行的。

我们简单看看回放线程和录制线程类关系:

在这里插入图片描述

	ThreadBase:PlaybackThread 和 RecordThread 的基类。
	RecordThread:录制线程类,由 ThreadBase 派生。
	PlaybackThread:回放线程基类,同由 ThreadBase 派生。
	MixerThread:混音回放线程类,由 PlaybackThread 派生,负责处理标识为 AUDIO_OUTPUT_FLAG_PRIMARY、AUDIO_OUTPUT_FLAG_FAST、AUDIO_OUTPUT_FLAG_DEEP_BUFFER 的音频流,MixerThread 可以把多个音轨的数据混音后再输出。
	DirectOutputThread:直输回放线程类,由 PlaybackThread 派生,负责处理标识为 AUDIO_OUTPUT_FLAG_DIRECT 的音频流,不需要软件混音,直接输出到音频设备即可。
	DuplicatingThread:复制回放线程类,由 MixerThread 派生,负责复制音频流数据到其他输出设备,使用场景如主声卡设备、蓝牙耳机设备、USB 声卡设备同时输出。
	OffloadThread:硬解回放线程类,由 DirectOutputThread 派生,负责处理标识为 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音频流,这种音频流未经软件解码的(一般是 MP3、AAC 等格式的数据),需要输出到硬件解码器,由硬件解码器解码成 PCM 数据。

从 Audio HAL 中,我们通常看到如下 4 种输出流设备,分别对应着不同的播放场景:

	primary_out: 主输出流设备,用于铃声类声音输出,对应着标识为 AUDIO_OUTPUT_FLAG_PRIMARY 的音频流和一个 MixerThread 回放线程实例。
	low_latency: 低延迟输出流设备,用于按键音、游戏背景音等对时延要求高的声音输出,对应着标识为 AUDIO_OUTPUT_FLAG_FAST 的音频流和一个 MixerThread 回放线程实例。
	deep_buffer:音乐音轨输出流设备,用于音乐等对时延要求不高的声音输出,对应着标识为 AUDIO_OUTPUT_FLAG_DEEP_BUFFER 的音频流和一个 MixerThread 回放线程实例。
	compress_offload:硬解输出流设备,用于需要硬件解码的数据输出,对应着标识为 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音频流和一个 OffloadThread 回放线程实例。

其中 primary_out 设备是必须声明支持的,而且系统启动时就已经打开 primary_out 设备并创建好对应的 MixerThread 实例。其他类型的输出流设备并非必须声明支持的,主要是看硬件上有无这个能力。

下图简单描述 AudioTrack、PlaybackThread、输出流设备三者的对应关系:
在这里插入图片描述
输出流设备决定了它对应的 PlaybackThread 是什么类型,只有支持了该类型的输出流设备,那么该类型的 PlaybackThread 才有可能被创建。

应用进程与 AudioFlinger 并不在一个进程上,这就需要 AudioFlinger 提供音频流管理功能,并提供一套通讯接口可以让应用进程跨进程控制 AudioFlinger 中的音频流状态。
AudioFlinger 音频流管理由 AudioFlinger::PlaybackThread::Track 实现,Track 与 AudioTrack 是一对一的关系,一个 AudioTrack 创建后,那么 AudioFlinger 会创建一个 Track 与之对应,PlaybackThread 与 AudioTrack/Track 是一对多的关系,一个 PlaybackThread 可以挂着多个 Track。

AudioTrack 创建后,AudioPolicyManager 根据 AudioTrack 的输出标识和流类型,找到对应的输出流设备和 PlaybackThread(如果没有找到的话,则系统会打开对应的输出流设备并新建一个 PlaybackThread),然后创建一个 Track 并挂到这个 PlaybackThread 下面。

PlaybackThread 有两个私有成员向量与此强相关:

	mTracks:该 PlaybackThread 创建的所有 Track 均添加保存到这个向量中。
	mActiveTracks:只有需要播放(设置了 ACTIVE 状态)的 Track 会添加到这个向量中。

PlaybackThread 会从该向量上找到所有设置了 ACTIVE 状态的 Track,把这些 Track 数据混音后写到输出流设备。

音频流控制最常用的三个接口:

	AudioFlinger::PlaybackThread::Track::start:开始播放:把该 Track 置 ACTIVE 状态,然后添加到 mActiveTracks 向量中,最后调用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变。
	AudioFlinger::PlaybackThread::Track::stop:停止播放:把该 Track 置 STOPPED 状态,最后调用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变。
	AudioFlinger::PlaybackThread::Track::pause:暂停播放:把该 Track 置 PAUSING 状态,最后调用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变。
	AudioFlinger::PlaybackThread::threadLoop() 得悉情况有变后,调用 prepareTracks_l() 重新准备音频流和混音器:ACTIVE 状态的 Track 会添加到 mActiveTracks,此外的 Track 会从 mActiveTracks 上移除出来,然后重新准备 AudioMixer。

可见这三个音频流控制接口是非常简单的,主要是设置一下 Track 的状态,然后发个事件通知 PlaybackThread 就行,复杂的处理都在AudioFlinger::PlaybackThread::threadLoop() 中了。

总结:
AudioFlinger里面涉及和包含的东西很多很多,在这里只是冰山一角,没有深入到具体的代码中去看,只是给大家介绍了一下其工作原理。

四、项目实例(汽车)

1.前言

目前移动操作系统主要分为Android和IOS两大阵营,IOS系统从出现的时候就给出了人们很惊艳的感觉,其内部细节的打磨以及用户体验都是有口皆碑。而安卓在一经推出到现在也一直是人们“讨论”的对象,主要是一开始的系统生硬,BUG居多,交互不够人性化此等印象在人们的脑海中先入为主。但是代码开源,linux内核这两个重要的原因给了安卓系统庞大的成长空间和生态基础。

时光荏苒,移动互联网成为世界科技的主旋律,谷歌开始重视起来自己当时收购的这个“玩意“,不断更迭,不断优化,不断成长。世界已经不是那个世界了,安卓系统成为了我们生活中必不可少的组成部分,在移动设备上(例如手机)发光发热了近十年光景,但是它的使命还没有结束。

汽车,属于传统产业,如今人们对它的要求也日益提高,自动驾驶概念炒的火热,互联网公司当然亦不会放过这次机会。

安卓系统的加入也仿佛是理所当然,谷歌公司更加确切了未来的方向,面向汽车提供了大量的功能接口,而在Audio模块里面,就有这么一个新加入的“成员”。

2.CarAudio

CarAudioService,是谷歌专门为汽车音频这块专门建的一个服务,处理一些汽车上专门的设置和项目定制化的一些东西。

但是,由于刚刚推出还不是很成熟,因此每个版本变更时Car这部分都会有很大变化,为了方便理解,基于Android9.0(P)简单分析下CarAudio这部分。

先来看看这个服务的启动流程:
1、SystemServer
startOtherServices()中启动CarService,代码如下:

            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
                traceBeginAndSlog("StartCarServiceHelperService");
                mSystemServiceManager.startService(CAR_SERVICE_HELPER_SERVICE_CLASS);
                traceEnd();
            }

其实就是调用了service的onstart方法,那么我们去CarServiceHelperService中看看onstart方法中做了什么。

2、 CarServiceHelperService

    @Override
    public void onStart() {
        Intent intent = new Intent();
        intent.setPackage("com.android.car");
        intent.setAction(CAR_SERVICE_INTERFACE);
        if (!getContext().bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE,
                UserHandle.SYSTEM)) {
            Slog.wtf(TAG, "cannot start car service");
        }
        System.loadLibrary("car-framework-service-jni");
    }

在这里绑定的CarService,而在里面重点是new 了一个 ICarImpl对象,所以直接看这个类。
android/packages/services/Car/service/src/com/android/car/ICarImpl.java

3、 ICarImpl
在其构造方法中:

		 mCarAudioService = new CarAudioService(serviceContext,mCarPowerManagementService, mCarPropertyService);
		 allServices.add(mCarAudioService);
		 mAllServices = allServices.toArray(new CarServiceBase[allServices.size()]);

再看init函数里面:

    @MainThread
    void init() {
        traceBegin("VehicleHal.init");
        mHal.init();
        traceEnd();
        traceBegin("CarService.initAllServices");
        for (CarServiceBase service : mAllServices) {
            service.init();
        }
        traceEnd();
    }

整个启动流程很清晰也很简单,也可以推算出其他Car服务的启动流程,所以了解这一块还是很有用的,也为我们接下来的内容做好了前期准备。

3.CarAudioManager

CarAudioManager是专门用于处理特定于汽车的音频用例的API。

提供了一组CAR_AUDIO_USAGE_*的常量,根据这些常量将音频路由到汽车。重要程度超出了正常的AudioManager类方法,因为它处理多声道音频。

包括一些例子,例如仅将电话音频数据在驾驶位播放,而不是通过所有扬声器播放。

用户和开发者要通过CarAudioManager来调用CarAudioService里面的功能,可以看一下它的构造函数,里面获得了CarAudioService,不难,很清晰,因为重点都在这个服务里面。

    /** @hide */
    public CarAudioManager(IBinder service, Context context, Handler handler) {
        mContentResolver = context.getContentResolver();
        mService = ICarAudio.Stub.asInterface(service);
    }
}

4.CarAudioService

CarAudioService实现了ICarAudio.aidl接口,目的也是为了方便远程调用以及跨进程通信。主要看这里面都有什么方法,还有就是它都做了什么工作。
1、init

    /**
     * Dynamic routing and volume groups are set only if
     * {@link #mUseDynamicRouting} is {@code true}. Otherwise, this service runs in legacy mode.
     */
    @Override
    public void init() {
        synchronized (mImplLock) {
            if (!mUseDynamicRouting) {
                Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not configured, run in legacy mode");
                setupLegacyVolumeChangedListener();
            } else {
                setupDynamicRouting();
                setupVolumeGroups();
            }
        }
    }

其中有个变量mUseDynamicRouting是控制是否使用动态路由,默认关闭,需要时要在配置文件中改为true。

如果使用了动态路由那么就要执行setupDynamicRouting()和setupVolumeGroups()这两个方法了,我们一个一个来看。

2、setupDynamicRouting

    private void setupDynamicRouting() {
        final IAudioControl audioControl = getAudioControl();
        if (audioControl == null) {
            return;
        }
        AudioPolicy audioPolicy = getDynamicAudioPolicy(audioControl);
        int r = mAudioManager.registerAudioPolicy(audioPolicy);
        if (r != AudioManager.SUCCESS) {
            throw new RuntimeException("registerAudioPolicy failed " + r);
        }
        mAudioPolicy = audioPolicy;
    }

这个AudioControl是HIDL定义的Hal服务,看名字就是音频控制相关的,现在知道是啥就行,后续项目一般都会在这做定制化功能。

AudioPolicy 是Android很重要的一个组成部分,做为native对外的api,我们可以直接拿来使用,定制自己声音路由策略,以及音频焦点优先级策略。

这部分代码逻辑不是很复杂,只是将AudioControl的实例传给了getDynamicAudioPolicy()方法,继续分析getDynamicAudioPolicy()这个方法。

3、 getDynamicAudioPolicy
这块的代码很长,就不粘贴出来了,用文字叙述一下,大家了解大致流程即可。

(1)获取device是输出设备的的设备信息,而这些设备的信息,是存在audiopolicy配置文件中的, 在Android7.0之后使用的是audio_policy_configuration.xml配置文件。拿到这些输出的outdevice信息后,继续过滤出device是AUDIO_DEVICE_OUT_BUS的设备信息。

创建CarAudioDeviceInfo 并将这些放入集合中,BusNumber就是audio_policy_configuration中device标签下 address属性里bus后面的那个数字,一般定义都是BUS1,BUS2或者BUS001,BUS002等BUS后面的数字最多3位。

(2)开始做路由bus策略的映射。可以简单理解为AudioAttribute的usage与AUDIO_OUT_DEVICE_BUS的映射。将contextNumber通过AudioControl与busNumber做map映射,同时存入集合。

(3)我们拿到了out的device,又拿到了contextNumber和busNumber的映射,那么第三部分就是真正实现他们的组装。

(4)最后一步就是设置动态路由音量回调。

上述核心的东西就是把AudioAttribute和AUDIO_DEVICE_OUT_BUS映射起来,存入AudioMix并通过mAudioManager.registerAudioPolicy(audioPolicy)注册下去,最终会在Audio PolicyManagerj进行路由策略时优先对应我们注册下来的这些策略。

使用外部音频路由策略对比安卓原生可以缩短开发风险和难度,因为原生在追加定义时要从AudioStream开始,一步一步通过AudioTrack到jni到AudioPolicy,到Engine的strategy。很麻烦也不容易理解。但是使用外部音频路由也有缺点,那就是不能再使用软件调音量了。

4、setupVolumeGroups

    private void setupVolumeGroups() {
        Preconditions.checkArgument(mCarAudioDeviceInfos.size() > 0,
                "No bus device is configured to setup volume groups");
        final CarVolumeGroupsHelper helper = new CarVolumeGroupsHelper(
                mContext, R.xml.car_volume_groups);
        mCarVolumeGroups = helper.loadVolumeGroups();
        for (CarVolumeGroup group : mCarVolumeGroups) {
            for (int contextNumber : group.getContexts()) {
                int busNumber = mContextToBus.get(contextNumber);
                group.bind(contextNumber, busNumber, mCarAudioDeviceInfos.get(busNumber));
            }

            // Now that we have all our contexts, ensure the HAL gets our intial value
            group.setCurrentGainIndex(group.getCurrentGainIndex());

            Log.v(CarLog.TAG_AUDIO, "Processed volume group: " + group);
        }
        // Perform validation after all volume groups are processed
        if (!validateVolumeGroups()) {
            throw new RuntimeException("Invalid volume groups configuration");
        }
    }

这个方法先构造一个CarVolumeGroupsHelper对象:
final CarVolumeGroupsHelper helper = new CarVolumeGroupsHelper(mContext, R.xml.car_volume_groups);

这个对象会去解析car_volume_groups.xml文件的音频别名对应输出设备的分组,通过helper.loadVolumeGroups可以得到分组的具体情况,最后再调用group.bind(contextNumber, busNumber, mCarAudioDeviceInfos.get(busNumber))将CONTEXT - OUTPUT - CarAudioDeviceInfo关联起来。

5、setGroupVolume
最后看一个比较重要的接口,这个是用来调音量的,从名字上看区别于原生的地方是Group这个词(原生用的是Stream),还有之前说的,一个是软调,一个是硬调。

上一个方法我们了解了音频是怎么分组的,现在我们调节音量的时候会直接把groupId当作参数,代码如下:

    /**
     * @see {@link android.car.media.CarAudioManager#setGroupVolume(int, int, int)}
     */
    @Override
    public void setGroupVolume(int groupId, int index, int flags) {
        synchronized (mImplLock) {
            enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);

            callbackGroupVolumeChange(groupId, flags);
            // For legacy stream type based volume control
            if (!mUseDynamicRouting) {
                mAudioManager.setStreamVolume(STREAM_TYPES[groupId], index, flags);
                return;
            }

            CarVolumeGroup group = getCarVolumeGroup(groupId);
            group.setCurrentGainIndex(index);
        }
    }

这样通过setGroupVolume的参数groupId就可以方便的控制调节哪些音频类型对应的输出设备的硬件音量了。

硬件音量曲线可以在CarVolumeGroup.java中实现,也可以在Hal层中实现。在CarAudioService.java中。定义mUseDynamicRouting会使得setGroupVolume方法走上截然不同的道路——设置硬件音量,在CarVolumeGroup.java中根据setGroupVolume方法传入的值和audio_Policy_Configuration.xml中提供的信息,进行计算得到音量gainInMillibels,之后将gainInMillibels一路传到Hal层,在Hal层根据音频曲线再次计算音量值,最后调用BSP提供的接口设置音量。

最后还是想说,掌握前三章才是重中之重,它们是Audio的主题,树干。所以希望可以好好理解前面的内容,可以自己查看这部分源码。

总结

坚持看完的同学我这里提出表扬,Audio开发是一件很重要的工作,庖丁解牛似的研究代码,知其所以然,在实际工作中才会更加得心应手。

安卓从一开始推出,经过代代更迭,代码重构,但其基本框架还在那,未来也不会变,掌握其背后核心组成的东西才是关键。即使我们无法记住具体的某一行代码的实现,但是学习以上这些,我们却能更清晰的看待问题,定位问题,解决问题。也可以使其它模块的同行更加了解Audio系统,从而在未来的工作当中会互相理解,从全局的视角来看待事物,提高工作效率。


觉得看完有帮助的请一键三连吧!
您的支持将是我继续下去的动力


点击全文阅读


本文链接:http://zhangshiyu.com/post/25941.html

音频  采样  声音  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1