• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

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

android 搞代码 4年前 (2022-03-01) 61次浏览 已收录 0个评论
文章目录[隐藏]

本文基于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…
起源:掘金
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:基于Android的音乐播放器的设计与实现

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址