安卓面经_音视频面经(8/20)Android音频系统框架

牛客高级系列专栏:

安卓(安卓系统开发也要掌握)


嵌入式


  • 本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人从嵌入式Linux转Android系统开发过程中对常见安卓系统开发面试题的理解;
  • 1份外卖价格助您提高安卓面试准备效率,为您面试保驾护航!!

正文开始⬇

面试题预览

1)简述声卡的添加流程。⭐⭐⭐
2)简述Android音频系统框架。⭐⭐⭐⭐
3)调节音量,系统流程是怎么走的?⭐⭐⭐⭐⭐
4)简述插入一个音频设备,系统流程是怎么走的?天翼云 ⭐⭐⭐⭐⭐
5)如果插入一个设备没有声音,怎么debug?天翼云 ⭐⭐⭐⭐⭐

1 概述

在讲解Audio系统架构前,为了方便理解,先简要介绍下Android系统架构,如下图所示:

alt

Android分为以下几层:

  • 应用框架(Framework): 应用框架最常被应用开发者使用。作为硬件开发者,您应该非常了解开发者 API,因为很多此类 API 都可以直接映射到底层 HAL 接口,并可提供与实现驱动程序相关的实用信息。这里用的是Java程序写的
  • Binder IPC: Binder 进程间通信 (IPC) 机制允许应用框架跨越进程边界并调用 Android 系统服务代码,这使得高级框架 API 能与 Android 系统服务进行交互。在应用框架级别,开发者无法看到此类通信的过程,但一切似乎都在 “按部就班地运行”。
  • 系统服务(Systerm Services): 系统服务是专注于特定功能的模块化组件,例如窗口管理器、搜索服务或通知管理器。应用框架 API 所提供的功能可与系统服务通信,以访问底层硬件。Android 包含两组服务:“系统”(诸如窗口管理器和通知管理器之类的服务)和“媒体”(涉及播放和录制媒体的服务)。
  • 硬件抽象层 (HAL): HAL 可定义一个标准接口以供硬件供应商实现,这可让 Android 忽略较低级别的驱动程序实现。借助 HAL,您可以顺利实现相关功能,而不会影响或更改更高级别的系统。HAL 实现会被封装成模块,并会由 Android 系统适时地加载。如需了解详情,请参阅硬件抽象层 (HAL)。这里用的是C/C++程序写的。
  • Linux 内核 (Kernel): 开发设备驱动程序与开发典型的 Linux 设备驱动程序类似。Android 使用的 Linux 内核版本包含一些特殊的补充功能,例如低内存终止守护进程(一个内存管理系统,可更主动地保留内存)、唤醒锁定(一种 PowerManager 系统服务)、Binder IPC 驱动程序,以及对移动嵌入式平台来说非常重要的其他功能。这些补充功能主要用于增强系统功能,不会影响驱动程序开发。您可以使用任意版本的内核,只要它支持所需功能(如 Binder 驱动程序)即可。不过,我们建议您使用 Android 内核的最新版本。如需了解详情,请参阅构建内核一文。全部利用C程序写的。

2 Audio架构

2.1 Audio音频子系统架构

系统架构如下图:

2.2 架构说明

2.2.1 应用框架(Application Framework)

整个音频体系的最上层。应用程序负责客户业务需求的逻辑实现。

alt

2.2.2 Framework

应用框架包含应用代码,该代码可使用 android.media API 与音频硬件进行交互。在内部,此代码会调用相应的 JNI 粘合类,以访问与音频硬件交互的原生代码。 源代码目录:frameworks/base/media/java/android/media/

  • AudioManager:音频管理器,包括音量管理、AudioFocus管理、音频设备管理、模式管理;
  • 录音:AudioRecord、MediaRecorder,采集音频数据;
  • 播放:AudioTrack、MedaiPlayer、SoundPool、ToneGenerator,播放数据api;
  • 编解码:MediaCodec,音视频数据 编解码接口。

Mediaplayer,Audiotrack,AudioService,AudioManager,AudioRecord和MediaRecorder为Android audio framework层对上层提供的接口。

  • Mediaplayer 和AudioTrack是我们播放音频时供应用选择的接口,这两者有什么区别呢?mediaplayer运用比较广泛了,它可以把未解码的媒体文件进行解码,然后交给设备去输出,而AudioTrack的功能就比较单一了,它只能播放PCM流的文件(即解码后的文件)。
  • AudioRecord和MediaRecorder是AndroidSDK提供了两套音频录制的API。其中MediaRecorder是更加上层的API,他可以直接对手机麦克风录入的音频数据进行压缩编码(比如 mp3),并存储为文件。而AudioRecord更底层些,让开发者能够得到内存中的PCM音频流数据,适用于需要对音频做进一步处理(比如,音效,第三方编码库进行压缩,或者网络传输等)。
  • MediaRecorder内部也是调用了AudioRecord与Framework层的AudioFlinger进行交互。
  • AudioService监听来自HDMI, FM等应用的intent,通知audiosystem,它其实也监控者音量,实现音量在UI上的同步。
  • AudioManger给上层提供了访问音量的接口,并控制ringer mode。AudioSystem 相当于AudioManager 和AudioService的内部类,只供他俩调,设置phone的状态。

2.2.3 JNI

与 android.media 关联的 JNI 代码可调用较低级别的原生代码,以访问音频硬件。JNI 位于 frameworks/base/core/jni/ 和 frameworks/base/media/jni 中。

2.2.4 Native framework 原生框架

原生框架可提供相当于 android.media 软件包的原生软件包,从而调用 Binder IPC 代理以访问媒体服务器的特定于音频的服务。原生框架代码位于 frameworks/av/media/libmedia 或frameworks/av/media/libaudioclient中(不同版本,位置有所改变)。

2.2.5 Binder IPC

Binder IPC 代理用于促进跨越进程边界的通信。代理位于 frameworks/av/media/libmedia或frameworks/av/media/libaudioclient 中,并以字母“I”开头。

2.2.6 server

Audio服务在Android N(7.0)之前存在于mediaserver中,Android N开始以audioserver形式存在,这些音频服务是与您的 HAL 实现进行交互的实际代码。

媒体服务器位于 frameworks/av/services/audioflinger和frameworks/av/services/audiopolicy中。 Audio服务包含AudioFlinger 和AudioPolicyService:

  • AudioFlinger:主要负责音频流设备的管理以及音频流数据的处理传输,⾳量计算,重采样、混⾳、⾳效等。接收多个APP的数据,合并下发;是策略的执行者,例如具体如何与音频设备通信,如何维护现有系统中的音频设备,以及多个音频流的混音如何处理等等都得由它来完成。 (1) 管理者整个audio的输入输出设备。 (2) 把多个audiostream整合成一个PCM audio流,指向安排好的输出设备去输出。
  • AudioPolicyService:主要负责⾳频策略相关,⾳量调节⽣效,设备选择,⾳频通路选择等。决定选择哪个设备输出,接上耳机用耳机,接上蓝牙设备用蓝牙;是策略的制定者,比如什么时候打开音频接口设备、某种Stream类型的音频对应什么设备等等。

2.2.7 HAL

硬件抽象层顾名思义为适配不同硬件而独立封装的一层,音频硬件抽象层的任务是将AudioFlinger/AudioPolicyService真正地与硬件设备关联起来由厂商自定义的Audio HAL或者是安卓自带的tinyalsa程序与Linux kernel 驱动程序构建成了安卓系统与硬件交互的最底层软件程序。其中tinyalsa是对linux的音频子系统ALSA架构的一种裁剪,Android的TinyALSA是基于Linux ALSA基础改造而来。

音频 HAL 接口位于 hardware/libhardware/include/hardware 中。如需了解详情,请参阅 audio.h。

2.3 Audio音频数据流流转流程

Audio 音频数据流整体上经过 APP,framework,hal,kernel driver四个部分,从应用端发起,不管调用 audio 还是 media 接口,最终还是会由 AudioTrack 将数据往下传,经由 AudioFlinger 启动 MixThread 或 DirectThread 等,将多个 APP 的声音混合到一起,将声音传输到 hal 层。

系统会根据音频流类型 stream 和音频策略 strategy 来选择对应的 output,从而找到对应的 module,将音频数据传输给 hal 层音频库 so 做声音相关的处理并传给 audio driver。 音频流传输路径图: alt

从上述的音频流流程可以看到,我们首先要确认,当前音频流是经由哪一个 hal 层库做处理,是 primary,usb 还是三方 so 等,然后可以在对应的节点抓取相应的音频信息分析。 可以根据自己的需要在音频流的部分节点埋下相应的 dump 指令,将 pcm 写入到相应的节点当中。

3 Audio音量控制详解

3.1 引言

Android的音量控制是典型的audiopolicy和audioflinger协作的例子,本节针对音量调节进行详细的解析。需要提一句的是,音量控制是设备厂商的适配重点,通常分为软音量和硬件音量,所谓软音量,就是Android原生针对audiotrack中的数据进行设置,而硬件音量则是设置对应芯片的寄存器,两者结合为Android音量的最终体现。

3.1.1 音量设置流程框图及其初始化

alt

开机的时候,系统会从数据库中更新当前的音量值给各个音频流

// 根据数据库的配置创建流的状态
private void createStreamStates() {
    int numStreamTypes = AudioSystem.getNumStreamTypes();
    VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
 
    for (int i = 0; i < numStreamTypes; i++) {
        streams[i] = new VolumeStreamState(System.VOLUME_SETTINGS[mStreamVolumeAlias[i]], i);  // VolumeStreamState 构造
    }
 
    checkAllAliasStreamVolumes();   // 更新音量到设备中
}
 
// 在数据库中读取每个设备的流音量
private VolumeStreamState(String settingName, int streamType) {
 
    mVolumeIndexSettingName = settingName;
 
    mStreamType = streamType;
    mIndexMax = MAX_STREAM_VOLUME[streamType];
    AudioSystem.initStreamVolume(streamType, 0, mIndexMax);
    mIndexMax *= 10;
 
    // mDeathHandlers must be created before calling readSettings()
    mDeathHandlers = new ArrayList<VolumeDeathHandler>();
 
    readSettings();         // 从数据库中读取数据,每个设备有自己独立的音量
}
 
// 数据库中键值的拼接方法,exp: volume_music_speaker
public String getSettingNameForDevice(int device) {
    String name = mVolumeIndexSettingName;
    String suffix = AudioSystem.getDeviceName(device);
    if (suffix.isEmpty()) {
        return name;
    }
    return name + "_" + suffix;
}
 
// 更新音量到设备中
private void checkAllAliasStreamVolumes() {
    int numStreamTypes = AudioSystem.getNumStreamTypes();
    for (int streamType = 0; streamType < numStreamTypes; streamType++) {
        if (streamType != mStreamVolumeAlias[streamType]) {
            mStreamStates[streamType].
                                setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]]);
        }
        // apply stream volume
        if (!mStreamStates[streamType].isMuted()) {
            mStreamStates[streamType].applyAllVolumes();  // 更新设备音量
        }
    }
}

3.1.2 流程详述

本节将结合代码详解解析Android音频系统的音量控制流程部分,嫌代码麻烦的可先看总结图:

alt

音量控制流程主要分为两层。一是Java层,用于响应音量调节,记录音量值,更新UI等操作;二是Native层,用于计算音量并执行。概述如下:
1)接收按键响应或者接受应用直接调用的设置音量的接口,这两个行为都是从调入AudioService中开始的,进行音量值记录、更新UI,并调入Native层的AudioSystem的setStreamVolumeIndex函数;
2)native层的策略是记录当前流类型对应对设备和index值,根据index计算出真正的音量值,然后再去调用AudioFlinger(AF)执行;
3)AF回调用Audio线程根据音量改变音频数据,这是软音量的调节过程;
4)软音量调完后,还可以根据需要,调用HAL层的接口,这是硬件音量。

3.2 代码分析

3.2.1 Java层分析

我们按下音量键或者触屏音量调节之后,会由Android系统响应按键,由于不同系统和每个公司的策略做的不一样,所以,在响应上逻辑上,不尽相同,但是,最终都会调入到AudioManager.java中:

**********
public void handleKeyDown(KeyEvent event, int stream) {
	...
	switch (keyCode) {
	            case KeyEvent.KEYCODE_VOLUME_UP:
	            case KeyEvent.KEYCODE_VOLUME_DOWN:
	            ...
	            adjustSuggestedStreamVolume(...);
	            ...
	}
}

响应的函数是adjustSuggestedStreamVolume,首先会获取java层的binder服务,然后调入到AudioService.java中: **********

private void adjustStreamVolume(int streamType, int direction, int flags,
            String callingPackage, int uid) {
	...
	/* 确定streamType对应的Alias组别 */
	int streamTypeAlias = mStreamVolumeAlias[streamType];
	...
	/* 获取对应的device */
	final int device = getDeviceForStream(streamTypeAlias);
	...
	/* java层消息机制:调节音量 */
	sendMsg(mAudioHandler,
                        MSG_SET_DEVICE_VOLUME,
                        SENDMSG_QUEUE,
                        device,
                        0,
                        streamState,
                        0);
	...
	/* UI更新相关 */
	int index = mStreamStates[streamType].getIndex(device);
        sendVolumeUpdate(streamType, oldIndex, index, flags);	
}

因为不同的streamtype可能有相同的策略,所以,这里要先去匹配Alias组别,然后去获取到device,之后我们看到是使用了java中的消息机制,通知需要调节音量,代码最后跟UI更新相关,这里不去重点关注,我们主要看消息机制这里,handler发送了消息之后,处理是在handleMessage中,调用的是setDeviceVolume方法:

private void setDeviceVolume(VolumeStreamState streamState, int device) {
	...
	synchronized (VolumeStreamState.class) {
		streamState.applyDeviceVolume_syncVSS(device);
		...
	}
}

这里可以看到继续调用的applyDeviceVolume_syncVSS,需要注意在调节了当前streamtype的音量之后,还会去调节其他的类型,因此在调试中,会看到很多类型的打印,我们只关注streamType == 3,applyDeviceVolume_syncVSS会去计算index值,这个index值指UI的刻度值,比如music的话共15个进度,不同的码流类型进度总值可能不一样,方法重点是去调用了AudioSystem.setStreamVolumeIndex(mStreamType, index, device);

三个参数,参数一为流类型,参数二为index(因为native也需要记录这个值),参数三为输出的设备,这是个native方法,接下来,将正式进入native分析。

3.2.2.native层分析

native层的策略是首先根据index计算出真正的音量值,然后再去调用audioflinger执行,先看AudioSystem:

status_t AudioSystem::setStreamVolumeIndex(audio_stream_type_t stream,
                                           int index,
                                           audio_devices_t device)
{
    const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
    if (aps == 0) return PERMISSION_DENIED;
    return aps->setStreamVolumeIndex(stream, index, device);
}

之前的本节已经分析了,这里是通过IAudioPolicyService去进一步调用,IAudioPolicyService的Bn端在AudioPolicyInterfaceImpl.cpp中:

status_t AudioPolicyService::setStreamVolumeIndex(audio_stream_type_t stream,
                                                  int index,
                                                  audio_devices_t device)
{
	...
    return mAudioPolicyManager->setStreamVolumeIndex(stream,
                                                    index,
                                                    device);	
}

前面本节说过,AudioPolicyManager是audiopolicyservice的持有者,APS不会直接与AF交互,我们看下APM中做了什么:

status_t AudioPolicyManager::setStreamVolumeIndex(audio_stream_type_t stream,
                                                      int index,
                                                      audio_devices_t device)
{
	...
	/* 记录当前流类型对应的设备和index值 */
	mStreams[stream].mIndexCur.add(device, index);
	/* 获取设备策略 */
    audio_devices_t strategyDevice = getDeviceForStrategy(getStrategy(stream), true /*fromCache*/);	
	...
	/* 检查并准备往AF中设置了 */
	status_t volStatus = checkAndSetVolume(stream, index, mOutputs.keyAt(i), curDevice);

}

java层分析的时候说过,index值也会传入到native层,这里便是记录的地方,我们重点关注checkAndSetVolume函数,很多厂商自己的适配也是在这里做的:

status_t AudioPolicyManager::checkAndSetVolume(audio_stream_type_t stream,
                                                   int index,
                                                   audio_io_handle_t output,
                                                   audio_devices_t device,
                                                   int delayMs,
                                                   bool force)
{
	...
    float driverVol[6]= {0.00,0.02,0.03,0.04,0.05,0.06};
    /* 计算音量值 */
    float volume = computeVolume(stream, index, output, device);
    /* 如果index值为前六个,则重新赋值 */
    if( index < 6)
    {
        volume = driverVol[index];
    }
    ...
    /* 这里最终会调入到AF中 */
    mpClientInterface->setStreamVolume(stream, volume, output, delayMs);
}

我所使用的平台是海思厂商,因为计算出来的音量值是绝对音量,所以如果index值太小的话,音量的变化并不会很明显,因此,这里对前六的index值进行了重新赋值,computeVolume为每个厂商自己的计算方法,不尽相同,海思的如下:

float amplification = exp( decibels * 0.115129f); // exp( dB * ln(10) / 20 )

实际上整个函数目的很简单,由index换算成真正的音量值,然后启动AF去设置,代码最后看起来跟AF没有关系,但实际上却是会调入到AF中,这里需要追一下代码:mpClientInterface对应的类型是AudioPolicyClientInterface,在AudioPolicyClientImpl.cpp中能找到到对应的实现:

status_t AudioPolicyService::AudioPolicyClient::setStreamVolume(audio_stream_type_t stream,
                     float volume, audio_io_handle_t output,
                     int delay_ms)
{
    return mAudioPolicyService->setStreamVolume(stream, volume, output,
                                               delay_ms);
}

这里又会调入到AudioPolicyService中,回想一下前面,AudioPolicyService是通过index到下面的,现在由index确定了真正的音量值之后,又返回了回来,我们看下AudioPolicyService下一步又会怎么调用:

**********

int AudioPolicyService::setStreamVolume(audio_stream_type_t stream,
                                        float volume,
                                        audio_io_handle_t output,
                                        int delayMs)
{
    return (int)mAudioCommandThread->volumeCommand(stream, volume,
                                                   output, delayMs);
}

这段代码初看有点懵,mAudioCommandThread是什么鬼?这是一个audio的命令接收线程,那么,这个线程在什么时候创建的呢?其实就是在第一次引用AudioPolicyService的强指针时候创建的,我们看下AudioPolicyService::onFirstRef():

void AudioPolicyService::onFirstRef()
{
	...
	    // start tone playback thread
        mTonePlaybackThread = new AudioCommandThread(String8("ApmTone"), this);
        // start audio commands thread
        mAudioCommandThread = new AudioCommandThread(String8("ApmAudio"), this);
        // start output activity command thread
        mOutputCommandThread = new AudioCommandThread(String8("ApmOutput"), this);	
	...
}

可以看到这里一共创建了三个命令线程,AudioCommandThread的构造函数并没有做什么实质性的事,联想前几篇博客的分析,我们看一下AudioCommandThread的onFirstRef函数:

void AudioPolicyService::AudioCommandThread::onFirstRef()
{
    run(mName.string(), ANDROID_PRIORITY_AUDIO);
}

果然,当第一次引用AudioCommandThread的强指针时,线程就会开始转起来,不停地接受指令了,那么,哪个地方是第一次引用强指针的呢?搜了下代码,发现只有一个地方使用了AudioCommandThread的强指针,就是AudioPolicyClient声明了此成员:

class AudioPolicyClient : public AudioPolicyClientInterface
{
	...
	sp<AudioCommandThread> mAudioCommandThread;     // audio commands thread
    sp<AudioCommandThread> mTonePlaybackThread;     // tone playback thread
    sp<AudioCommandThread> mOutputCommandThread;	
	...
}

基本我们就可以确认了,在第一次实例化AudioPolicyClient对象的时候,就会为成员变量分配指针空间,这里就相当于第一次引用了AudioCommandThread的强指针,接收命令的线程也就转起来了。 花了比较大的工夫分析了mAudioCommandThread线程的创建过程,我们去看下它的volumeCommand:

status_t AudioPolicyService::AudioCommandThread::volumeCommand(audio_stream_type_t stream,
                                                               float volume,
                                                               audio_io_handle_t output,
                                                               int delayMs)
{
    sp<AudioCommand> command = new AudioCommand();
    command->mCommand = SET_VOLUME;
    sp<VolumeData> data = new VolumeData();
    data->mStream = stream;
    data->mVolume = volume;
    data->mIO = output;
    command->mParam = data;
    command->mWaitStatus = true;
    ALOGV("AudioCommandThread() adding set volume stream %d, volume %f, output %d",
            stream, volume, output);
    return sendCommand(command, delayMs);	
}

封装数据,返回sendCommand,到这里似乎就跟不下去了,但不要忘了mAudioCommandThread是一个线程,并且已经run起来了,那么我们需要去看下它的threadloop干了什么:

bool AudioPolicyService::AudioCommandThread::threadLoop()
{
	...
	while (!exitPending())
    {
    	...
		switch (command->mCommand) {
        ...
	        case SET_VOLUME: {
	           VolumeData *data = (VolumeData *)command->mParam.get();
	           ALOGV("AudioCommandThread() processing set volume stream %d, \
	                   volume %f, output %d", data->mStream, data->mVolume, data->mIO);
	           command->mStatus = AudioSystem::setStreamVolume(data->mStream,
	                                                           data->mVolume,
	                                                           data->mIO);       
        ...
        }
	}
	...
}

一切如我们所想的,这里面的case语句指引了我们,饶了一大圈,还是到AudioSystem了,但是,看到AudioSystem就应该兴奋起来了,因为马上要到audioflinger了,看吧:

status_t AudioSystem::setStreamVolume(audio_stream_type_t stream, float value,
        audio_io_handle_t output)
{
    if (uint32_t(stream) >= AUDIO_STREAM_CNT) return BAD_VALUE;
    const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
    if (af == 0) return PERMISSION_DENIED;
    af->setStreamVolume(stream, value, output);
    return NO_ERROR;
}

剥丝抽茧,果然还是会由audioflinger来完成,看下audioflinger又会做什么:

status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value,
        audio_io_handle_t output)
{
	...
	/* 1.根据output找到thread */
	PlaybackThread *thread = NULL;
    if (output != AUDIO_IO_HANDLE_NONE) {
        thread = checkPlaybackThread_l(output);
        if (thread == NULL) {
            return BAD_VALUE;
        }
    }
    /* 2.记录当前的流类型的音量值 */
    mStreamTypes[stream].volume = value;
	...
	/* 3.进入到thread中去设置音量 */
	thread->setStreamVolume(stream, value);
	/* 4.厂商定制化:设置硬件音量 */
#if defined (PRODUCT_STB)
    for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
        AudioHwDevice *dev = mAudioHwDevs.valueAt(i);
        mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
        ALOGE("setStreamVolume by set_master_volume");
        if(stream == 3)//music type,only this type can adjust hardware volum
            dev->hwDevice()->set_master_volume(dev->hwDevice(),value);
        mHardwareStatus = AUDIO_HW_IDLE;
    }
    ALOGV("setStreamVolume value over");
#endif	
}

audioflinger中的setStreamVolume真是信息量巨大,首先,它会通过output找到对应的thread,因为之前分析audioflinger和audiotrack的博客时,已经知道,audioflinger会创建两个线程,一个是PlaybackThread,一个是RecordThread,而PlaybackThread的子类有MixerThread,DirectOutputThread等,所以,这里首先需要根据output确认到底是哪一个thread,之后,记录当前的流类型的音量值,这个值,后面会使用到,注释三为什么会到thread中去设置呢?我们这里需要清楚,thread掌管的是track,其实就是到track里面去设置音量,也就是说,这里设置音量实际上改变的是数据,简单地说,对数据进行幅值强弱处理达到音量调节的目的,这也正是我开篇所说的软音量调节, 因为是对数据进行处理,并没有真正地设置到硬件中去,但注释四就是设置到硬件中了,可以看到,这里是芯片厂商自己加的,通过hal层直接设置到芯片的sdk,然后设置到寄存器中,海思的硬件音量设置是在audioflinger中做的,各个厂商的实现策略可能不一样,但大家一定要区别设置软音量和硬件音量的区别。

硬件音量的设置我们就先不分析了,我们看软件音量上又是怎么走的,去到setStreamVolume

void AudioFlinger::PlaybackThread::setStreamVolume(audio_stream_type_t stream, float value)
{
    Mutex::Autolock _l(mLock);
    mStreamTypes[stream].volume = value;
    broadcast_l();
}

其实这里跟前面的audioflinger中做了重复工作,多赋值一次,前面audioflinger中的赋值应该是可以去掉的(从binder机制上我觉得是可以的),言归正传,似乎路再一次的堵死了,这里面通过广播告诉thread有事情要处理,我们需要到threadloop里面去看看,以mixthread为例,我们关注下它的threadloop,threadloop“三把斧”的第一把prepareTracks_l就告诉了我们答案:

AudioFlinger::MixerThread::**********

{
	...
	float typeVolume = mStreamTypes[track->streamType()].volume;
	float v = masterVolume * typeVolume;	
}

这里的typeVolume 就是前面赋值的音量值,下面一句话告诉了我们软音量的总值为masterVolume 与设置index计算出来的音量之积,所以,在实际问题的处理中,如果音量调节全程都很小,请注意是否是masterVolume 太小导致的,masterVolume 也是通过上层java接口来设置的。计算了总音量之后,后续就会去mixer中进行混音了,软音量的设置也完成了。

至此,音量设置的分析基本就完成了,回顾一下,音量设置首先是java层计算,存储index值,更新UI等操作,然后将流类型和index传入native层,native层通过audiopolicy进行计算之后,转交由audioflinger去设置,而原生audioflinger中是通过处理track数据来达到音量调节的目的。

3.3 总结

Android原生音量调节略微复杂,再引用本节开篇的音量调节流程图来比较完成的概括一下(原图较大,可自行下载保存):

alt

1)不同的厂商按键响应策略不一样,但最终都会调入到AudioService中;
2)AudioPolicyService在音量值计算的过程中承载的工作比较大,首先是通过binder服务调用AudioPolicyManager去计算音量值,然后是通过自己创建的AudioCommandThread去接收audio音量调节的指令;
3)AudioPolicyManager如何设置到AudioFlinger略微有点绕,因为中间有一个AudioPolicyService自己创建的AudioCommandThread线程;
4)audioflinger中原生场景会去设置软件音量,不同的厂商硬件音量的策略不同,所以红色虚线圈起来的为硬件音量设置,需视平台而定,但是一定要区分软件音量与硬件音量的区别;
5)软件音量是通过改变track中数据实现的音量调节,硬件音量是通过修改寄存器值实现的调节;
6)原生中所说的二级音量设置就是prepareTracks_l中的masterVolume*typeVolume,因为软件总音量是二者之积,所以,音量值太小时请确认软件总音量值;

4 音频设备切换流程详解

4.1 引言

音频输出的方式有很多种,外放即扬声器(Speaker)、听筒(Telephone Receiver)、有线耳机(WiredHeadset)、蓝牙音箱(Bluetooth A2DP)、USBSpeaker、SPDIF、LineOut、HDMI ARC等。在电话免提、插拔耳机、连接断开蓝牙设备,插入USB扬声器等操作时,操作系统都会自动切换Audio音频到相应的输出设备上。但是Android系统自动切换的这些策略,有时候并不能满足我们的需求,比如耳机在播放音乐时拔出,原有的系统切换流程会切换到扬声器播放,这很容易形成声音外放的尴尬,这种场景一般要求要声音播放暂停。

那么这些切换的流程是怎么样呢?下面以插入USB扬声器为例,来一起详细分析下系统的音频设备切换里流程。

4.2 音频设备切换流程

alt

4.2.1 Framework层

4.2.1.1 UsbHostManager

当设备拔插usb 扬声器时候,UsbHostManager的native monitorUsbHostBus函数会在USB驱动层监测到拔插usb设备事件,然后调用usbDeviceAdded函数或者调用usbDeviceRemoved函数。

private native void monitorUsbHostBus();

alt alt

以插入为例,那么USBHostManage的usbDeviceAdded函数会先检查USB设备的描述符,如果检查USB 音频设备描述符是能够被正确解析的并唯一,那么就会将设备添加到音频设备列表中,具体添加过程会调到UsbAlsaManager.usbDeviceAdded函数。

    /* Called from JNI in monitorUsbHostBus() to report new USB devices
       Returns true if successful, i.e. the USB Audio device descriptors are
       correctly parsed and the unique device is added to the audio device list.
     */
    @SuppressWarnings("unused")
    private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass,
            byte[] descriptors) {
        //....
        UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors);
        if (deviceClass == UsbConstants.USB_CLASS_PER_INTERFACE
                && !checkUsbInterfacesDenyListed(parser)) {
            return false;
        }

        // Potentially can block as it may read data from the USB device.
        logUsbDevice(parser);

        synchronized (mLock) {
            // 检查设备是否已经存在了
            if (mDevices.get(deviceAddress) != null) {
                Slog.w(TAG, "device already on mDevices list: " + deviceAddress);
                //TODO If this is the same peripheral as is being connected, replace
                // it with the new connection.
                return false;
            }

            //....
            UsbDevice.Builder newDeviceBuilder = parser.toAndroidUsbDeviceBuilder();
            if (newDeviceBuilder == null) {
                Slog.e(TAG, "Couldn't create UsbDevice object.");
                // Tracking
                addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT_BADDEVICE,
                        parser.getRawDescriptors());
            } else {
                UsbSerialReader serialNumberReader = new UsbSerialReader(mContext,
                        mPermissionManager, newDeviceBuilder.serialNumber);
                UsbDevice newDevice = newDeviceBuilder.build(serialNumberReader);
                serialNumberReader.setDevice(newDevice);

                mDevices.put(deviceAddress, newDevice);
                Slog.d(TAG, "Added device " + newDevice);
                // It is fine to call this only for the current user as all broadcasts are
                // sent to all profiles of the user and the dialogs should only show once.
                ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
                if (usbDeviceConnectionHandler == null) {
                    getCurrentUserSettings().deviceAttached(newDevice);
                } else {
                    getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
                            usbDeviceConnectionHandler);
                }
                //调用UsbAlsaManager.usbDeviceAdded
                mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser);

        //....
        if (DEBUG) {
            Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end");
        }

        return true;
    }

4.2.1.2 UsbAlsaManager

void usbDeviceAdded(String deviceAddress, UsbDevice usbDevice,
        UsbDescriptorParser parser) {
    //...
    if (hasInput || hasOutput) {
        
        UsbAlsaDevice alsaDevice =
                    new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
                                      deviceAddress, hasOutput, hasInput,
                                      isInputHeadset, isOutputHeadset);
            if (alsaDevice != null) {
                alsaDevice.setDeviceNameAndDescription(
                          cardRec.getCardName(), cardRec.getCardDescription());
                mAlsaDevices.add(0, alsaDevice);
                selectAlsaDevice(alsaDevice);
            }
    }
}

private synchronized void selectAlsaDevice(UsbAlsaDevice alsaDevice) {
    //.......
    mSelectedDevice = alsaDevice;
    alsaDevice.start();
}

在UsbAlsaManager.usbDeviceAdded函数里首先会检查USB设备的描述符,查看是否有输入或者输出,然后再

  • 检查输入输出的类型
  • 创建USBAlsaDevice,并设置信息
  • 设置为mSelectedDevice
  • 调用UsbAlsaDevice start,更新设备连接状态;

4.2.1.3 UsbAlsaDevcie.java

接着看UsbAlsaDevice start

public synchronized void start() {
    mSelected = true;
    mInputState = 0;
    mOutputState = 0;
    startJackDetect();
    updateWiredDeviceConnectionState(true);
}

public synchronized void updateWiredDeviceConnectionState(boolean enable) {
    //.......
     if (mHasOutput) {
         mAudioService.setWiredDeviceConnectionState(device, outputState,
                                                                alsaCardDeviceString,
                                                                mDeviceName, TAG);
     }
    
    if (mHasInput) {
        mAudioService.setWiredDeviceConnectionState(
                            device, inputState, alsaCardDeviceString,
                            mDeviceName, TAG);
    }
}

调用AudioService,设置设备连接状态。

4.2.1.3 AudioService.java

接下来分析AudioService中流程

public void setWiredDeviceConnectionState(int type, int state, String address, String name,
        String caller) {
    synchronized (mConnectedDevices) {
        if (DEBUG_DEVICES) {
            Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
                    + address + ")");
        }
        int delay = 

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Android系统面试题全解析 文章被收录于专栏

2020年研究生毕业后,工作重心由嵌入式Linux转为安卓系统,Android发展已经很多年,网上面向中初级Android系统开发的面经还比较少,也不够集中,因此梳理出本专栏,本专栏收集了本人工作中持续积累的众多安卓系统知识,持续更新中。

全部评论

相关推荐

6 34 评论
分享
牛客网
牛客企业服务