------Audio架构

Posted 我不想再敲代码了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了------Audio架构相关的知识,希望对你有一定的参考价值。

文章目录


前言

自己现在岗位工作就是底层驱动开发,现在是学习audio的底层驱动,想利用平常的空闲时间对自己的学习做一个总结归纳。


一、android和Linux的区别?

Android 继承于 Linux,Android 是基于 Linux 的内核基础上运行的,提供的核心系统服务包括安全、内存管理、进程管理、网络组和驱动模型等内容。但是,严格来说,Android 不算是 Linux 操作系统。Android是基于Linux内核开发的操作系统,安卓在Linux的基础上提供了驱动以及用户编程接口。

在Android系统上,为了避免Linux系统的对于软件开源的具体要求,将传统的设备驱动程序进行了再次拆分为驱动程序以及用户空间的驱动程序(HAL)。通过这种设计,部分硬件厂商就可以将具有自主产权的设计功能放在HAL层实现,只需要向外提供接口以及动态库就可以,不必将源码开源。随着安卓系统的逐步发展,这种方式在安卓系统上以及成为主流。(部分商用Linux系统项目也将这部分内容借鉴过来了)。
对于用户程序,安卓系统设计了一条自己的框架,通过使用这套框架,应用程序开发人员就可以不用关注于底层特别复杂的逻辑设计,只要关注于自己应用业务就可以了。如下图所示,通过HAL层提供向上调用kernel的底层驱动的接口,然后呢Framwork层实现对HAL层的控制

根据上图所示,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程序写的。

二、Audio架构

1. Audio音频子系统架构图

系统架构图如下图:


软件架构图如下图:

【架构说明】:

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

  2. AudioPolicyAndroidFlinger为核心构建了Android audio framework层,直接与最底层程序进行交互。

    主要职责为:
      
    ★ 向HAL层写入音频数据进行音频播放。从HAL层采集音频数据进行音频数据传输或者保存。(audioFlinger)
    ★ 根据应用场景以及具体配置,对音频通路的控制,即在什么场景之下,声音应该从哪个设备上发声(audioPolicy通过控制audioFlinger来进行实现)
    AudioFlinger:主要负责音频流设备的管理以及音频流数据的处理传输,⾳量计算,重采样、混⾳、⾳效等。接收多个APP的数据,合并下发;是策略的执行者,例如具体如何与音频设备通信,如何维护现有系统中的音频设备,以及多个音频流的混音如何处理等等都得由它来完成。
    (1) 管理者整个audio的输入输出设备。
    (2) 把多个audiostream整合成一个PCM audio流,指向安排好的输出设备去输出。

    AudioPolicyService:主要负责⾳频策略相关,⾳量调节⽣效,设备选择,⾳频通路选择等。决定选择哪个设备输出,接上耳机用耳机,接上蓝牙设备用蓝牙;是策略的制定者,比如什么时候打开音频接口设备、某种Stream类型的音频对应什么设备等等。

  3. MediaplayerAudiotrackAudioServiceAudioManagerAudioRecordMediaRecorder为Android audio framework层对上层提供的接口。

    MediaplayerAudioTrack是我们播放音频时供应用选择的接口,这两者有什么区别呢?mediaplayer运用比较广泛了,它可以把未解码的媒体文件进行解码,然后交给设备去输出,而AudioTrack的功能就比较单一了,它只能播放PCM流的文件(即解码后的文件)。

    AudioRecordMediaRecorder是AndroidSDK提供了两套音频录制的API。其中MediaRecorder是更加上层的API,他可以直接对手机麦克风录入的音频数据进行压缩编码(比如 mp3),并存储为文件。而AudioRecord更底层些,让开发者能够得到内存中的PCM音频流数据,适用于需要对音频做进一步处理(比如,音效,第三方编码库进行压缩,或者网络传输等)。
    MediaRecorder内部也是调用了AudioRecord与Framework层的AudioFlinger进行交互。

    AudioService监听来自HDMIFM等应用的intent,通知audiosystem,它其实也监控者音量,实现音量在UI上的同步。

    AudioManger给上层提供了访问音量的接口,并控制ringer mode。

    AudioSystem 相当于AudioManager 和AudioService的内部类,只供他俩调,设置phone的状态。

  4. 应用程序负责客户业务需求的逻辑实现。

这样由下到上就构筑了andriod audio的基本框架。

2. Audio HAL层的功能以及理解

AudioFlinger是通过操作Audio HAL层 间接的对底层设备进行操作的。(音频数据的读写以及各种参数的设定)
Audio HAL的代码文件名为:audio_hw_hal.cpp,先了解HAL层具备哪些能力(数据流),然后再分析上层使用怎么样的策略对底层进行控制(控制流),这样能极大的便于我们快速的了解控制流的逻辑与实现。

2.1. Audio HAL层的框架分析

1. HAL层的两个部分:
有audio的HAL,也有Audio_policy的HAL(Audio_policy的HAL我们不关心,基本废弃)。HAL层下一层使用TiniAlsa(AlSA库裁剪版)。
2. 关键类与结构体:
为了方便,HAL要向上层提供统一的接口,操作硬件也会有一组接口/一个类。
★ 向上提供接口struct audio_hw_device:struct audio_hw_device接口在audio_hw_hal.cpp文件中,它是对hw_device_t结构的封装。hw_device_t结构的定义在路径 hardware/libhardware/include/hardware/hardware.h中
★ 向下访问硬件class AudioHardware:一般在device/“平台厂商”/common/libaudio/audioHardware.cpp中实现, 由厂商提供,里面使用到了tinyalsa库的接口。厂商实现硬件的访问接口,Android指定了一套接口给它。
3. HAL的数据结构:

	/* 上下衔接
	 * Audio HAL的调用流程总结上层应用程序调用
	 * audio_hw_hal.cpp中的legacy_adev_open()
	 * 会得到一个struct audio_hw_device结构体,
	 * 这个结构体代表上层使用硬件的接口,这个
	 * 结构体中的函数都依赖于厂家提供的
	 * struct AudioHardwareInterface结构。
	 */
	struct legacy_audio_device 
	    struct audio_hw_device device;      //规范了向上提供的接口
	    struct AudioHardwareInterface *hwif;//向下访问硬件,指向厂家的AudioHardware
	;
	 
	/*由于HAL对上层直接提供的接口中没有read/write函数
	 *因此,应用程序想要录音或播放声音的时候需要先打开
	 *output或input(audio_hw_device中的open_output_stream/open_input_stream),
	 *进而使用其里面的write/read函数通过声卡硬件读/写音频数据。
	 *这也就是audio_hw_device与audio_stream_out/audio_stream_in之间的关系
	 */
	struct legacy_stream_out 
	    struct audio_stream_out stream; //规范了向上提供的接口
	    AudioStreamOut *legacy_out;     //向下访问硬件,指向厂家的AudioStreamOutALSA
	;
	 
	struct legacy_stream_in 
	    struct audio_stream_in stream;//规范了向上提供的接口
	    AudioStreamIn *legacy_in;     //向下访问硬件,指向厂家的AudioStreamInALSA
	;

struct audio_hw_device device;
struct audio_stream_out stream;
struct audio_stream_in stream;
这三个结构体,均在路径 hardware/libhardware/include/hardware/audio.h 下声明定义。

2.2. Audio HAL层的源码分析

这里主要分析 open设备写数据操作读数据操作获取参数设置参数

1. open设备:
AudioFlinger 会对设备进行加载open,过程如下:

AudioFlinger::loadHwModule
->AudioFlinger::loadHwModule_l
-->load_audio_interface
--->audio_hw_device_open(mod, dev);//获取audio_hw_device_t结构体以及它的操作
---->module->methods->open //这里对应的是legacy_adev_open

AudioFlinger 调用到了HAL层的legacy_adev_open方法,这里对open设备的各种功能方法进行初始化,实现硬件设备与第三方平台硬件接口进行连接,代码实现如下:

static int legacy_adev_open(const hw_module_t* module, const char* name,
                            hw_device_t** device)

    struct legacy_audio_device *ladev;
    int ret;
 
    if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0)
        return -EINVAL;
 
    ladev = (struct legacy_audio_device *)calloc(1, sizeof(*ladev));
    if (!ladev)
        return -ENOMEM;
 
    //结构体赋值
    ladev->device.common.tag = HARDWARE_DEVICE_TAG;
    ladev->device.common.version = AUDIO_DEVICE_API_VERSION_2_0;
    ladev->device.common.module = const_cast<hw_module_t*>(module);
    ladev->device.common.close = legacy_adev_close;
    ladev->device.init_check = adev_init_check;
    ladev->device.set_voice_volume = adev_set_voice_volume;
    ladev->device.set_master_volume = adev_set_master_volume;
    ladev->device.get_master_volume = adev_get_master_volume;
    ladev->device.set_mode = adev_set_mode;
    ladev->device.set_mic_mute = adev_set_mic_mute;
    ladev->device.get_mic_mute = adev_get_mic_mute;
    ladev->device.set_parameters = adev_set_parameters;
    ladev->device.get_parameters = adev_get_parameters;
    ladev->device.get_input_buffer_size = adev_get_input_buffer_size;
    ladev->device.open_output_stream = adev_open_output_stream;
    ladev->device.close_output_stream = adev_close_output_stream;
    ladev->device.open_input_stream = adev_open_input_stream;
    ladev->device.close_input_stream = adev_close_input_stream;
    ladev->device.dump = adev_dump;
    /* 关键点:
     * audio_hw_device_t结构体 和 hwif(hardwareInterface)接口之间建立联系
     * 这里通过createAudioHardware 获取 实现hardwareInterface接口的厂商指针
     * 后面调用hwif的相关操作 <=等价=> 使用厂商的库函数中的方法
     */
    ladev->hwif = createAudioHardware();
    if (!ladev->hwif) 
        ret = -EIO;
        goto err_create_audio_hw;
    
    *device = &ladev->device.common;
    return 0;
 
err_create_audio_hw:
    free(ladev);
    return ret;

通过上面的分析,这里从Framework Native层到HAL层框架,再到第三方平台厂商库的调用,他们之间的关系就打通了。

2. 写数据操作:
从Framework的Native层开始,audio_hw_device_t 结构体首先会通过adev_open_output_stream获取audio_stream_out,因此从它开始分析。代码如下:

static int adev_open_output_stream(struct audio_hw_device *dev,
                                   audio_io_handle_t handle,
                                   audio_devices_t devices,
                                   audio_output_flags_t flags,
                                   struct audio_config *config,
                                   struct audio_stream_out **stream_out,
                                   const char *address __unused)

    struct legacy_audio_device *ladev = to_ladev(dev);
    status_t status;
    struct legacy_stream_out *out;
    int ret;
 
    //这里的legacy_stream_out <=结构体类型 等价=>audio_stream_out
    out = (struct legacy_stream_out *)calloc(1, sizeof(*out));
    if (!out)
        return -ENOMEM;
 
    devices = convert_audio_device(devices, HAL_API_REV_2_0, HAL_API_REV_1_0);
    //这里将audio_stream_out与ladev->hwif之间建立联系
    out->legacy_out = ladev->hwif->openOutputStreamWithFlags(devices, flags,
                                                    (int *) &config->format,
                                                    &config->channel_mask,
                                                    &config->sample_rate, &status);
    if (!out->legacy_out) 
        ret = status;
        goto err_open;
    
 
    out->stream.common.get_sample_rate = out_get_sample_rate;
    out->stream.common.set_sample_rate = out_set_sample_rate;
    out->stream.common.get_buffer_size = out_get_buffer_size;
    out->stream.common.get_channels = out_get_channels;
    out->stream.common.get_format = out_get_format;
    out->stream.common.set_format = out_set_format;
    out->stream.common.standby = out_standby;
    out->stream.common.dump = out_dump;
    out->stream.common.set_parameters = out_set_parameters;
    out->stream.common.get_parameters = out_get_parameters;
    out->stream.common.add_audio_effect = out_add_audio_effect;
    out->stream.common.remove_audio_effect = out_remove_audio_effect;
    out->stream.get_latency = out_get_latency;
    out->stream.set_volume = out_set_volume;
    out->stream.write = out_write;
    out->stream.get_render_position = out_get_render_position;
    out->stream.get_next_write_timestamp = out_get_next_write_timestamp;
 
    //将out->stream写回到参数 stream_out中
    *stream_out = &out->stream;
    return 0;
 
err_open:
    free(out);
    *stream_out = NULL;
    return ret;

这里通过hwif获取了音频输出流(audio_stream_out类型),然后对out->stream进行初始化,注册了 写入函数out_write 之后将其返回给传递进来的指针变量stream_out(audio_stream_out类型),当上层进行写操作时,就会执行这个out_write函数,代码如下:

static ssize_t out_write(struct audio_stream_out *stream, const void* buffer,
                         size_t bytes)

    struct legacy_stream_out *out = reinterpret_cast<struct legacy_stream_out *>(stream);
    return out->legacy_out->write(buffer, bytes);

这里直接调用到 第三方平台厂商库的write(out->legacy_out->write)方法(如果这里是 高通平台,则所谓的hwif就是AudioHardwareALSA,所谓的write就是AudioStreamOutALSA的write方法,最终会带哦用pcm_write进行写数据操作)。

3. 读数据操作:
从Framework的Native层开始,audio_hw_device_t 结构体首先会通过adev_open_Input_stream获取audio_stream_in,因此从它开始分析。代码如下:

 
/** This method creates and opens the audio hardware input stream */
static int adev_open_input_stream(struct audio_hw_device *dev,
                                  audio_io_handle_t handle,
                                  audio_devices_t devices,
                                  struct audio_config *config,
                                  struct audio_stream_in **stream_in,
                                  audio_input_flags_t flags __unused,
                                  const char *address __unused,
                                  audio_source_t source __unused)

    struct legacy_audio_device *ladev = to_ladev(dev);
    status_t status;
    struct legacy_stream_in *in;
    int ret;
	//这里的legacy_stream_in <=结构体类型 等价=>audio_stream_in
    in = (struct legacy_stream_in *)calloc(1, sizeof(*in));
    if (!in)
        return -ENOMEM;
 
    devices = convert_audio_device(devices, HAL_API_REV_2_0, HAL_API_REV_1_0);
	//这里将audio_stream_in与ladev->hwif之间建立联系
    in->legacy_in = ladev->hwif->openInputStream(devices, (int *) &config->format,
                                                 &config->channel_mask, &config->sample_rate,
                                                 &status, (AudioSystem::audio_in_acoustics)0);
    if (!in->legacy_in) 
        ret = status;
        goto err_open;
    
 
    in->stream.common.get_sample_rate = in_get_sample_rate;
    in->stream.common.set_sample_rate = in_set_sample_rate;
    in->stream.common.get_buffer_size = in_get_buffer_size;
    in->stream.common.get_channels = in_get_channels;
    in->stream.common.get_format = in_get_format;
    in->stream.common.set_format = in_set_format;
    in->stream.common.standby = in_standby;
    in->stream.common.dump = in_dump;
    in->stream.common.set_parameters = in_set_parameters;
    in->stream.common.get_parameters = in_get_parameters;
    in->stream.common.add_audio_effect = in_add_audio_effect;
    in->stream.common.remove_audio_effect = in_remove_audio_effect;
    in->stream.set_gain = in_set_gain;
    in->stream.read = in_read;
    in->stream.get_input_frames_lost = in_get_input_frames_lost;
	//将in->stream写回到参数 stream_in中
    *stream_in = &in->stream;
    return 0;
 
err_open:
    free(in);
    *stream_in = NULL;
    return ret;

这里通过hwif获取了音频输入流(audio_stream_in类型),然后对in->stream进行初始化,注册了 写入函数in_read 之后将其返回给传递进来的指针变量stream_in(audio_stream_in类型),当上层进行写操作时,就会执行这个in_read函数,代码如下:

static ssize_t in_read(struct audio_stream_in *stream, void* buffer,
                       size_t bytes)

    struct legacy_stream_in *in =
        reinterpret_cast<struct legacy_stream_in *>(stream);
    return in->legacy_in->read(buffer, bytes);

这里直接调用到 第三方平台厂商库的read(out->legacy_in->read)方法(如果这里是 高通平台,则所谓的hwif就是AudioHardwareALSA,所谓的read就是AudioStreamInALSA的read方法,最终会带哦用pcm_read进行读数据操作)。

4. 获取参数:
从Framework的Native层开始,audio_hw_device_t 结构体的获取参数的操作最后一定会调用到 adev_get_parameters,因此从它开始分析。代码如下:

static char * adev_get_parameters(const struct audio_hw_device *dev,
                                  const char *keys)

    const struct legacy_audio_device *ladev = to_cladev(dev);
    String8 s8;
 
    s8 = ladev->hwif->getParameters(String8(keys));
    return strdup(s8.string());

这里直接调用到 第三方平台厂商库的getParameters方法。

5. 设置参数:
从Framework的Native层开始,audio_hw_device_t 结构体的设置参数的操作最后一定会调用到 adev_set_parameters,因此从它开始分析。代码如下:

static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)

    struct legacy_audio_device *ladev = to_ladev(dev);
    return ladev->hwif->setParameters(String8(kvpairs));

这里直接调用到 第三方平台厂商库的setParameters方法。

2.3. Audio HAL层的过程总结

  1. 从配置文件确定库文件的名字。
    HAL文件一般位于/system/lib/hardware下面,音频中操作硬件的库文件名称在(厂商提供的HAL部分)在/system/etc/policy_config中指定。

  2. 加载库(*.so)文件。打开HAL文件中的open函数,在HAL中会构造audio_hw_device结构体, 该结构体中有各类函数,
    特别是 open_output_stream /
    open_input_stream。AudioFlinger根据audio_hw_device结构体构造一个AudioHwDev对象并放入mAudioHwDevs。

  3. 调用HAL结构体audio_hw_device的open_input_stream/open_output_stream,它会构造audio_stream_in/audio_stream_out结构体。

  4. 写入/读出数据,调用tinyALSA的一些操作,直接使用系统调用控制声卡的是tinyalsa库,位于目录/external/tinyalsa下,编译生成库文件libtinyalsa.so(只涉及两个文件mixer.c,pcm.c),编译生成工具tinycap,tinymix,tinypcminfo,tinyplay,可用于直接控制音频通道,进行录音播音测试。使用pcm_XXX操作来操作声卡,是驱动层的封装。

  5. tinyALSA库再操作音频驱动程序,音频驱动程序再操作硬件声卡设备。

总结

在弄清楚了Audio的软件架构之后,对后面的分层次的理解、学习是不可或缺呢,但是呢,前提可能看不大明白,大致有个印象就可以了,等后面在接触一段时间,回过头来在看一下,就会发现软件框架的更多巧妙之处。

参考链接:
https://blog.csdn.net/vviccc/article/details/105417542
https://www.cnblogs.com/ouyshy/p/13445250.html

Android 音频(Audio)架构

一、概述

Android 的音频硬件抽象层 (HAL) 可将 android.media 中特定于音频的较高级别的框架 API 连接到底层音频驱动程序和硬件。本部分介绍了有关提升性能的实现说明和提示。

Android 音频架构定义了音频功能的实现方式,并指出实现中所涉及的相关源代码。

应用框架

应用框架包含应用代码,该代码使用 android.media API 与音频硬件进行交互。在内部,此代码会调用相应的 JNI 粘合类来访问与音频硬件互动的原生代码。

  • 源代码目录:frameworks/base/media/java/android/media/
  • AudioManager:音频管理器,包括音量管理、AudioFocus管理、音频设备管理、模式管理;
  • 录音:AudioRecord、MediaRecorder;
  • 播放:AudioTrack、MedaiPlayer、SoundPool、ToneGenerator;
  • 编解码:MediaCodec,音视频数据 编解码接口。

JNI

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

Native framework 原生框架

原生框架提供相当于 android.media 软件包的原生软件包,它调用 Binder IPC 代理来访问媒体服务器的音频专属服务。 原生框架代码位于 frameworks/av/media/libmedia 中。

原生框架代码位于 frameworks/av/media/libmedia 或frameworks/av/media/libaudioclient中(不同版本,位置有所改变)。

Binder IPC

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

Audio Server 媒体服务器

Audio系统在Android中负责音频方面的数据流传输和控制功能,也负责音频设备的管理。这个部分作为Android的Audio系统的输入/输出层次,一般负责播放PCM声音输出和从外部获取PCM声音,以及管理声音设备和设置(注意:解码功能不在这里实现,在android系统里音频视频的解码是opencore或stagefright完成的,在解码之后才调用音频系统的接口,创建音频流并播放)。Audio服务在Android N(7.0)之前存在于mediaserver中,Android N开始以audioserver形式存在,这些音频服务是与HAL 实现进行交互的实际代码。媒体服务器位于 frameworks/av/services/audioflinger 和frameworks/av/services/audiopolicy中。

Audio服务包含AudioFlinger 和AudioPolicyService:

  • AudioFlinger:主要负责音频流设备的管理以及音频流数据的处理传输,⾳量计算,重采样、混⾳、⾳效等。
  • AudioPolicyService:主要负责⾳频策略相关,⾳量调节⽣效,设备选择,⾳频通路选择等。

HAL

HAL 定义了音频服务会调用且您必须实现才能使音频硬件正常运行的标准接口。音频 HAL 接口位于 hardware/libhardware/include/hardware 中。详情可参阅 audio.h。

内核驱动程序

音频驱动程序用于同您的硬件和 HAL 实现进行交互。可以使用高级 Linux 声音架构 (ALSA)、开放声音系统 (OSS) 或自定义驱动程序(HAL 与驱动程序无关)。

注意:如果使用的是 ALSA,建议将 external/tinyalsa 用于驱动程序的用户部分,因为它具有兼容的许可(标准的用户模式库已获得 GPL 许可)。

二、音频系统架构的演进

一个好的系统架构,需要尽可能地降低上层与具体硬件的耦合,这既是操作系统的设计目的,对于音频系统也是如此。音频系统的雏形框架可以简单的用下图来表示:
在这个图中,除去Linux本身的Audio驱动外,整个Android音频实现都被看成了User。因而我们可以认为Audio Driver就是上层与硬件间的“隔离板”。但是如果单纯采用上图所示的框架来设计音频系统,对上层应用使用音频功能是不小的负担,显然Android开发团队还会根据自身的实际情况来进一步细化“User”部分。具体该怎么细化呢?如果是让我们去细化我们该怎么做呢?

首先作为一个操作系统要对外提供可用的API,供应用开发者调用。APP开发者开发的应用我们称APP,我们提供的API姑且叫Framework。如果Framework直接和驱动交互有什么问题呢?

  1. 首先是耦合问题,接口和实现耦合,硬件层有任何变动都需要接口层适配,我们增加一层硬件适配层;

  2. 资源统一管理的问题,如果多个APP调用相同API使用硬件资源,改怎么分配?增加统一资源管理器,其实就是对应Android系统的Audio Lib层。

细化后我们发现,整个结构对应的就就是Android的几个层次结构,包括应用层、framework层、库层以及HAL层,如下图所示:

2.1 Lib层

framework层的大多数类,其实只是应用程序使用Android库文件的“中介”,它只是个壳子。因为Android应用采用java语言编写,它们需要最直接的java接口的支持,如果我们的Android系统支持另一种语言的运行时,那么可以提供另一种语言的接口支持(比如Go),这就是framework层存在的意义之一。但是作为“中介”,它们并不会真正去实现具体的功能,或者只实现其中的一部分功能,而把主要重心放在核心库中来完成。比如上面的AudioTrack、AudioRecorder、MediaPlayer和MediaRecorder等等在库中都能找到相对应的类,这些多数是C++语言编写的。

我们再从另一个线索来思考这个问题:我们提供的API供应用层调用,那么这个API最终运行在应用的进程中。如果多个应用同时使用这个功能就会冲突;再一个允许任何一个进程操作硬件也是个危险的行为。那么真相就浮出了水面:我们需要一个有权限管理和硬件交互的进程,需要调用某个硬件服务必须和我这个服务打交道。这就是Android系统的很常用的C/S结构以及Binder存在的主要原因。Android系统中的Server就是一个个系统服务,比如ServiceManager、LocationManagerService、ActivityManagerService等等,以及管理图像合成的SurfaceFlinger,和今天我们今天介绍的音频服务AudioFlinger和AudioPolicyService。它们的代码放置在frameworks/av/services/audioflinger,生成的最主要的库叫做libaudioflinger。

这里也提到了分析源码除以模块为线索外的另一种线索以进程为线索。库并不代表一个进程,但是进程则依赖于库来运行。虽然有的类是在同一个库中实现的,但并不代表它们会在同一个进程中被调用。比如AudioFlinger和AudioPolicyService都驻留于名为mediaserver的系统进程中;而AudioTrack/AudioRecorder和MediaPlayer/MediaRecorder只是应用进程的一部分,它们通过binder服务来与其它audioflinger等系统进程通信

2.2 HAL层

硬件抽象层顾名思义为适配不同硬件而独立封装的一层,音频硬件抽象层的任务是将AudioFlinger/AudioPolicyService真正地与硬件设备关联起来,但又必须提供灵活的结构来应对变化。

从设计上来看,硬件抽象层是AudioFlinger直接访问的对象。这里体现了两方面的考虑:

  1. 一方面AudioFlinger并不直接调用底层的驱动程序;

  2. 另一方面,AudioFlinger上层(包括和它同一层的MediaPlayerService)的模块只需要与它进行交互就可以实现音频相关的功能了。

AudioFlinger和HAL是整个架构解耦的核心层,通过HAL层的audio.primary等库抹平音频设备间的差异,无论硬件如何变化,不需要大规模地修改上层实现,保证系统对外暴露的上层API不需要修改,达成高内聚低耦合。而对厂商而言,在定制时的重点就是如何在这部分库中进行高效实现了。

举个例子,以前Android系统中的Audio系统依赖于ALSA-lib,但后期就变为了tinyalsa,这样的转变不应该对上层造成破坏。因而Audio HAL提供了统一的接口来定义它与AudioFlinger/AudioPolicyService之间的通信方式,这就是audio_hw_device、audio_stream_in及audio_stream_out等等存在的目的,这些Struct数据类型内部大多只是函数指针的定义,是一个个句柄。当AudioFlinger/AudioPolicyService初始化时,它们会去寻找系统中最匹配的实现(这些实现驻留在以audio.primary.*,audio.a2dp.*为名的各种库中)来填充这些“壳”,可以理解成是一种“多态”的实现。

三、Linux平台下的两种主要的音频驱动架构接收

3.1 OSS(Open Sound System)

早期Linux版本采用的是OSS框架,它也是Unix及类Unix系统中广泛使用的一种音频体系。OSS既可以指OSS接口本身,也可以用来表示接口的实现。OSS的作者是Hannu Savolainen,就职于4Front Technologies公司。由于涉及到知识产权问题,OSS后期的支持与改善不是很好,这也是Linux内核最终放弃OSS的一个原因。

另外,OSS在某些方面也遭到了人们的质疑,比如:

  • 对新音频特性的支持不足;
  • 缺乏对最新内核特性的支持等等。

当然,OSS做为Unix下统一音频处理操作的早期实现,本身算是比较成功的。它符合“一切都是文件”的设计理念,而且做为一种体系框架,其更多地只是规定了应用程序与操作系统音频驱动间的交互,因而各个系统可以根据实际的需求进行定制开发。总的来说,OSS使用了如下表所示的设备节点:

设备节点说明
/dev/dsp向此文件写数据à输出到外放Speaker向此文件读数据à从Microphone进行录音
/dev/mixer混音器,用于对音频设备进行相关设置,比如音量调节
/dev/midi00第一个MIDI端口,还有midi01,midi02等等
/dev/sequencer用于访问合成器(synthesizer),常用于游戏等效果的产生

3.2 ALSA(Advanced Linux Sound Architecture)

ALSA是Linux社区为了取代OSS而提出的一种框架,是一个源代码完全开放的系统(遵循GNU GPL和GNU LGPL)。ALSA在Kernel 2.5版本中被正式引入后,OSS就逐步被排除在内核之外。当然,OSS本身还是在不断维护的,只是不再为Kernel所采用而已。

ALSA相对于OSS提供了更多,也更为复杂的API接口,因而开发难度相对来讲加大了一些。为此,ALSA专门提供了一个供开发者使用的工具库,以帮助他们更好地使用ALSA的API。根据官方文档的介绍,ALSA有如下特性:

  • 高效支持大多数类型的audio interface(不论是消费型或者是专业型的多声道声卡)
  • 高度模块化的声音驱动
  • SMP及线程安全(thread-safe)设计
  • 在用户空间提供了alsa-lib来简化应用程序的编写
  • 与OSS API保持兼容,这样子可以保证老的OSS程序在系统中正确运行

ALSA主要由下表所示的几个部分组成:

ElimentDescription
alsa-driver内核驱动包
alsa-tools包含一系列工具程序
alsa-utils包含了很多使用的小程序,比如alsactl:用于保存设备设置amixer:是一个命令行程序,用于音量和其他声音控制alsamixer的ncurses版acconnect和aseqview:制作MIDI连接,以及检查已经连接的端口列表,aplay和arecord:两个命令行程序,分别用于播放和录制多种格式的音频
alsa-plugins插件包,比如jack,pulse,maemo
alsa-oss用于兼容OSS的模拟包
pyalsa用于编译python版本的alsa lib
alsa-lib用户空间函数
alsa-firmware音频固件支持包
Alsa主要的文件节点如下:
  • Information Interface (/proc/asound)
  • Control Interface (/dev/snd/controlCX)
  • Mixer Interface (/dev/snd/mixerCXDX)
  • PCM Interface (/dev/snd/pcmCXDX)
  • Raw MIDI Interface (/dev/snd/midiCXDX)
  • Sequencer Interface (/dev/snd/seq)
  • Timer Interface (/dev/snd/timer)

Android的TinyALSA是基于Linux ALSA基础改造而来。一看“Tiny”这个词,我们应该能猜到这是一个ALSA的缩减版本。实际上在Android系统的其它地方也可以看到类似的做法——既想用开源项目,又嫌工程太大太繁琐,怎么办?那就只能瘦身了,于是很多Tiny-XXX就出现了。

在早期版本中,Android系统的音频架构主要是基于ALSA的,其上层实现可以看做是ALSA的一种“应用”。后来可能是由于ALSA所存在的一些不足,Android后期版本开始不再依赖于ALSA提供的用户空间层的实现。HAL层最终依赖alsa-lib库与驱动层交互。

以上是关于------Audio架构的主要内容,如果未能解决你的问题,请参考以下文章

Linux audio驱动模型

Android 9 Audio系统笔记:Audio系统架构

Android 9 Audio系统笔记:Audio系统架构

RK3588平台开发系列讲解(AUDIO篇)CODEC移植

Android audio介绍

Android深度探索与驱动开发