文章目录

  • 前言
  • 一、Android和Linux的区别?
  • 二、Audio架构
    • 1. Audio音频子系统架构图
    • 2. Audio HAL层的功能以及理解
      • 2.1. Audio HAL层的框架分析
      • 2.2. Audio HAL层的源码分析
      • 2.3. Audio HAL层的过程总结
  • 总结

前言

自己现在岗位工作就是底层驱动开发,现在是学习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_outout = (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_inin = (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篇章(1)------Audio架构相关推荐

  1. Android底层开发之Audio HAL Android Audio Overview

    http://blog.csdn.net/kangear/article/details/44939429 Android底层开发之Audio HAL 在Android音频底层调试-基于tinyals ...

  2. Android底层开发之Audio HAL

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! Andr ...

  3. android物联网开发技术架构,Android 相关七种 CPU 架构适配,android七种

    Android 相关七种 CPU 架构适配,android七种 转载请注明出处:http://blog.csdn.net/kester_/article/details/71055901 NDK 开发 ...

  4. html5 audio 资源,HTML5 Audio(音频)

    原标题:HTML5 Audio(音频) HTML5 提供了播放音频文件的标准. 互联网上的音频 直到现在,仍然不存在一项旨在网页上播放音频的标准. 今天,大多数音频是通过插件(比如 Flash)来播放 ...

  5. Android从程序员到架构师之路3

    本文学习自高焕堂老师的Android从程序员到架构师之路系列教学视频 40 - 认识线程(Thread)模式a 1. 线程(Thread)概念 所谓线程(Thread) 是指一串连续的执行动作,以达成 ...

  6. Android Platform Architecture 安卓平台架构

    Android Platform Architecture 安卓平台架构 Android is an open source, Linux-based software stack created f ...

  7. 安卓模拟器 arm linux,让x86的android模拟器能模拟arm架构系统

    原标题:让x86的android模拟器能模拟arm架构系统 网上介绍共计三种模拟器比较常用,分别是bluestacks.andy和Genymotion,前者支持ARM架构,中者支持远程控制,后者启动速 ...

  8. Android : Camera之camx hal架构

    sheldon_blogs https://www.cnblogs.com/blogs-of-lxl/p/10668554.html Android : Camera之camx hal架构 一.cam ...

  9. Unity之iOS报错Audio effect Resonance Audio Renderer could not be found.

    Unity之iOS报错Audio effect Resonance Audio Renderer could not be found. 报错 解决方法 链接 报错 如题,报错的详细信息如下: Aud ...

最新文章

  1. 微信小程序开发的完整流程介绍,新手必读
  2. linux正则表达式BRE
  3. python画笑脸图案-如何用Python画滑稽笑脸
  4. (数论)51NOD 1135 原根
  5. Eclipse juno 中安装 JBoss Tools,集成Hibernate
  6. 空间到底是什么?---車粒子
  7. 如何用python生成表格_用 Python 生成 HTML 表格
  8. 消息中间件:RocketMQ 介绍(特性、术语、原理、优缺点、消息顺序、消息重复)
  9. Caused by: java.lang.ClassNotFoundException: Cannot find class: User
  10. discuz程序的阅读(1)
  11. 阿里安全的“自动逆向机器人”TimePlayer 究竟是什么
  12. 华为python面试题库_我收集了100道Python面试题,开源到了Github
  13. Centos7安装JDK8以及环境配置
  14. finebi跳转到本地html文件,组件跳转- FineReport帮助文档|报表开发|报表使用|学习教程...
  15. mui ajax的值php怎样获取,关于mui.ajax的设置,以及php取不到data值的问题的方法
  16. 【训练计划】--2019-04
  17. android 图片过长,【05-25求助】怎样无损加载一张过长的图片
  18. php5时区,PHP5 时区设置方法详解
  19. 计算机科学计算矩阵答案,计算机科学计算答案.doc
  20. 混沌者 pat basic 练习七十一 小赌怡情

热门文章

  1. 如何将AS/400英文界面改为中文界面?
  2. diskgenius 操作sd卡问题 内存变小 合并错误 盘符消失
  3. 单片机做的音乐盒c语言程序,简单音乐盒,利用单片机所学DIY一个满意的音乐播放器!...
  4. 欧氏距离和马氏距离简介
  5. SSL证书怎么删除私钥密码保护?
  6. form提交表单时本地下载
  7. php如何解压 .bz2文件,Linux_备份压缩--bzip2,功能说明:.bz2文件的压缩程序 - phpStudy...
  8. Udesk5.2.7-iOS引入
  9. 会计记忆总结之十:主要经济业务事项账务处理
  10. Android Studio各个版本的区别