关于android:基于Android的音乐播放器的设计与实现

3次阅读

共计 8439 个字符,预计需要花费 22 分钟才能阅读完成。

本文基于 Android 音频 API 提供的四个层面的音频 API,说说 Android 零碎的音频架构。

上面先上这张经典的 Android 零碎架构图

从图上看 Andorid 整个零碎层面从下到上分以下四层:

  1. Linux Kernel
  2. 硬件适配层
  3. Framework 层(可分为 Java 层与 C ++ 层)
  4. APP 层

咱们下面介绍的四个层面的音频 API 实现均在 Framework 层,其余各层音频相干有哪些性能?当咱们调用某一 API 时最终是怎么驱动硬件工作的呢?上面咱们先看看零碎各层音频相干模块及性能。

1. 各层音频模块

1.1 Java 层

Java 层提供了 android.media API 与音频硬件进行交互。在外部,此代码会调用相应的 JNI 类,以拜访与音频硬件交互的原生代码。

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

1.2 JNI 层

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

在这里能够调用咱们上篇文章介绍的 AAudio 和 OpenSLES 接口。

1.3 Native framework 原生框架层

不论是 Java 层还是 JNI 层都只是对外提供的接口,真正的实现在原生框架层。原生框架可提供相当于 android.media 软件包的原生软件包,从而调用 Binder IPC 代理以拜访媒体服务器的特定于音频的服务。原生框架代码位于 frameworks/av/media/libmediaframeworks/av/media/libaudioclient 中(不同版本,地位有所扭转)。

1.4 Binder IPC

Binder IPC 代理用于促成逾越过程边界的通信。代理位于 frameworks/av/media/libmediaframeworks/av/media/libaudioclient 中,并以字母“I”结尾。

1.5 Audio Server

Audio 零碎在 Android 中负责音频方面的数据流传输和管制性能,也负责音频设备的治理。这个局部作为 Android 的 Audio 零碎的输出 / 输入档次,个别负责播放 PCM 声音输入和从内部获取 PCM 声音,以及治理声音设施和设置(留神:解码性能不在这里实现,在 android 零碎里音频视频的解码是 opencore 或 stagefright 实现的,在解码之后才调用音频系统的接口,创立音频流并播放)。Audio 服务在 Android N(7.0)之前存在于 mediaserver 中,Android N 开始以 audioserver 模式存在,这些音频服务是与 HAL 实现进行交互的理论代码。媒体服务器位于 frameworks/av/services/audioflingerframeworks/av/services/audiopolicy 中。

Audio 服务蕴含 AudioFlinger 和 AudioPolicyService:

  • AudioFlinger:次要负责音频流设施的治理以及音频流数据的解决传输,⾳量计算,重采样、混⾳、⾳效等。
  • AudioPolicyService: 次要负责⾳频策略相干,⾳量调节⽣效,设施抉择,⾳频通路抉择等。

1.6 HAL 层

HAL 定义了由音频服务调用且手机必须实现以确保音频硬件性能失常运行的标准接口。音频 HAL 接口位于 hardware/libhardware/include/hardware 中。详情可参阅 audio.h。

1.7 内核驱动层

音频驱动程序可与硬件和 HAL 实现进行交互。咱们能够应用高级 Linux 音频架构 (ALSA)、凋谢声音零碎 (OSS) 或自定义驱动程序(HAL 与驱动程序无关)。

留神:如果应用的是 ALSA,倡议将 external/tinyalsa 用于驱动程序的用户局部,因为它具备兼容的许可(规范的用户模式库已取得 GPL 许可)。

2. 音频系统架构的演进

一个好的零碎架构,须要尽可能地升高下层与具体硬件的耦合,这既是操作系统的设计目标,对于音频系统也是如此。音频系统的雏形框架能够简略的用下图来示意:

在这个图中,除去 Linux 自身的 Audio 驱动外,整个 Android 音频实现都被看成了 User。因此咱们能够认为 Audio Driver 就是下层与硬件间的“隔离板”。然而如果单纯采纳上图所示的框架来设计音频系统,对下层利用应用音频性能是不小的累赘,显然 Android 开发团队还会依据本身的理论状况来进一步细化“User”局部。具体该怎么细化呢?如果是让咱们去细化咱们该怎么做呢?

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

  1. 首先是耦合问题,接口和实现耦合,硬件层有任何变动都须要接口层适配,咱们减少一层硬件适配层;
  2. 资源对立治理的问题,如果多个 APP 调用雷同 API 应用硬件资源,改怎么调配?减少对立资源管理器,其实就是对应 Android 零碎的 Audio Lib 层。

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

咱们能够联合目前已有的常识,咱们剖析 Lib 层和 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 间接拜访的对象。这里体现了两方面的思考:

  • 一方面 AudioFlinger 并不间接调用底层的驱动程序;
  • 另一方面,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. 为名的各种库中)来填充这些“壳”,能够了解成是一种“多态”的实现。

3. Linux 平台下的两种次要的音频驱动架构介绍

下面咱们的示例提到了 ALSA,这个其实是 Linux 平台的一种音频驱动架构。上面介绍两种常见的 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), 罕用于游戏等成果的产生

更多详情,能够参考 OSS 的官网阐明

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 次要由下表所示的几个局部组成:

Element Description
alsa-driver 内核驱动包
alsa-lib 用户空间的函数库
alsa-utils 蕴含了很多实用的小程序,比方 alsactl: 用于保留设施设置 amixer: 是一个命令行程序,用于声量和其它声音管制 alsamixer:amixer 的 ncurses 版 acconnect 和 aseqview: 制作 MIDI 连贯,以及查看已连贯的端口列表 aplay 和 arecord: 两个命令行程序,别离用于播放和录制多种格局的音频
alsa-tools 蕴含一系列工具程序
alsa-firmware 音频固件反对包
alsa-plugins 插件包,比方 jack,pulse,maemo
alsa-oss 用于兼容 OSS 的模仿包
pyalsa 用于编译 Python 版本的 alsa lib

Alsa 次要的文件节点如下:

  1. Information Interface (/proc/asound)
  2. Control Interface (/dev/snd/controlCX)
  3. Mixer Interface (/dev/snd/mixerCXDX)
  4. PCM Interface (/dev/snd/pcmCXDX)
  5. Raw MIDI Interface (/dev/snd/midiCXDX)
  6. Sequencer Interface (/dev/snd/seq)
  7. Timer Interface (/dev/snd/timer)

Android 的 TinyALSA 是基于 Linux ALSA 根底革新而来。一看“Tiny”这个词,咱们应该能猜到这是一个 ALSA 的缩减版本。实际上在 Android 零碎的其它中央也能够看到相似的做法——既想用开源我的项目,又嫌工程太大太繁琐,怎么办?那就只能瘦身了,于是很多 Tiny-XXX 就呈现了。

在晚期版本中,Android 零碎的音频架构次要是基于 ALSA 的,其下层实现能够看做是 ALSA 的一种“利用”。起初可能是因为 ALSA 所存在的一些有余,Android 前期版本开始不再依赖于 ALSA 提供的用户空间层的实现。HAL 层最终依赖 alsa-lib 库与驱动层交互。

4. 一种新的录音形式实现

除了之前提到的零碎 API,咱们还有其余的录音形式吗?答案是必定的。下面咱们提到 HAL 层依赖 alsa-lib 库与驱动层交互,咱们间接应用 alsa-lib,绕开 HAL 层和 Framework 层不也能够做到吗(当然前提是要有零碎权限)?

为什么会有这种述求呢?在做家居和车载产品时,会有四麦、六麦、甚至八麦的场景。录制大于 2 麦的设施时须要在 HAL 层以及 Framework 层做适配,基于 AOSP 的批改会显得特地重,特地是一些像回声克制,声源定位等信号处理算法,如果集成在操作系统,会有更新降级麻烦的问题,咱们能够基于 alsa-lib 在应用层拿到多路数据调用信号处理算法,这样算法模块降级只须要降级 APP 即可,不须要降级整个零碎。

咱们先来看看 Android 零碎自带的 tinyX 系列工具。

4.1 tinymix 混响器

在 root 用户下调用 tinymix 能够查看硬件驱动反对的混响配置

root@android:/ # tinymix
Number of controls: 7
ctl    type    num    name                                     value
0    ENUM    1    Playback Path                            OFF
1    ENUM    1    Capture MIC Path                         MIC OFF
2    ENUM    1    Voice Call Path                          OFF
3    ENUM    1    Voip Path                                OFF
4    INT    2    Speaker Playback Volume                  0 0
5    INT    2    Headphone Playback Volume                0 0
6    ENUM    1    Modem Input Enable                       ON
root@android:/ #
复制代码

那么它外面的内容是什么意思呢?

  • 首先咱们要晓得,一个 mixer 通常有多个 controler,像这个,外面有 7 个,而后就别离列出每一个 controller 的信息;
  • 首先看第一个:它的编号为 0,类型是 ENUM 型,它目前的值是 OFF,它是用来管制音频输入通道;
  • 同理,第二个也管制音频输出通道;
  • 第三个,通话音频通道;
  • 第四个 IP 电话音频通道;
  • 第五个扬声器音量,和下层音量值无关;
  • 第六个耳机音量,和下层音量值无关;

个别 Playback Path 对应的枚举值有:

  1. OFF:敞开
  2. RCV
  3. SPK:扬声器
  4. HP:耳机带麦
  5. HP_NO_MIC:耳机无麦
  6. BT:蓝牙

那么我如果像扭转某一项的时候,要怎么设置呢?办法是 tinymix ctl value;如果 tinymix 只跟上控制器的编号,就会把控制器的以后状态显示进去:

# tinymix 7
Audio linein in: On
# tinymix 7 0
root@dolphin-fvd-p1:/ # **tinymix 7**
Audio linein in: Off
复制代码

4.2 tinycap 采集器

应用上面命令即可实现录制并保留到 sd 卡:

 tinycap 
Usage: tinycap file.wav [-D card] [-d device] [-c channels] [-r rate] [-b bits] [-p period_size] [-n n_periods] 
 tinycap /sdcard/rec.wav -D 0 -d 0 –c 4 –r 16000 –b 16 –p 1024 –n 3
复制代码

4.3 tinyplay 播放

tinyplay
Usage: tinyplay file.wav [-D card] [-d device] [-p period_size] [-n n_periods]
tinyplay /sdcard/test44.wav -D 0 -d 0 -p 1024 -n 3
复制代码

4.4 程序中集成

当初咱们曾经通过命令的形式实现了绕开 framework 的音频采集,咱们在本人的 app 中怎么应用呢?如果还是通过命令的形式只能录制到文件,无奈实现流式录制。

解决办法是咱们的 app 依赖 tinyalsa 库

        struct pcm_config config;
        config.channels = 4;
    config.rate = 16000;
    config.period_size = 1024;
    config.period_count = 4;
    config.start_threshold = 0;
    config.stop_threshold = 0;
    config.silence_threshold = 0;

    if (bitDepth == 32)
        config.format = PCM_FORMAT_S32_LE;
    else if (bitDepth == 16)
        config.format = PCM_FORMAT_S16_LE;
    pcm = pcm_open(0, device, PCM_IN, &config);
    if (!pcm || !pcm_is_ready(pcm)) {return -1;}
    int bufferSize = pcm_get_buffer_size(pcm);

    char *buffer = (char*)malloc(bufferSize);
    int i = pcm_read(pcm, buffer, bufferSize);
    if(i ==0){//success}
复制代码

5. 总结

本文介绍了 Andorid 零碎的整套音频架构,以及架构各层级的性能及作用。并介绍了一种绕开 framework 层的新的音频采集形式。其实 Andorid 的音频架构实现是更简单的一个过程,本文只是简略的对各个模块做了一些介绍,以助于更深刻了解上一篇提到的各个 API 的实现。其实 API 提供进去的音频接口,都是属于接口层,不论是 Java 接口还是 C ++ 接口,都隶属于利用过程。以采集为例,不管咱们调用哪个 API,咱们都会发现启动后利用过程会多出一个 AudioRecord 的线程:

咱们启动的录制线程调用 API 只是从 AudioRecord 线程写入到 Buffer 的数据的读取。

更多 Android 技术分享能够关注 @我, 也能够退出 QQ 群号:1078469822,学习交换 Android 开发技能。

作者:轻口味
链接:https://juejin.cn/post/701115…
起源:掘金
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。

正文完
 0