精华内容
下载资源
问答
  • 随着MP3、WMA等数字音频的流行,以及大容量存储介质的迅速发展,一种新的可直接播放存储器中音乐数据的数字音频系统的设计方案开始崭露头角。  以往在设计数字音频系统时,需要工程师具备相关的知识和经验,并且在...
  • 行业分类-作业装置-音频系统数据同步方法以及音频组件.zip
  • 在录音扩声或音频传输过程中噪声是具有一定频率的纹波电压通过电源线路窜入音频...甚至使得音频系统无法进行正常的录音或扩声工作。音频系统噪声形成的机理较为复杂,针对系统噪声产生的主要原因和解决办法尤其重要。
  • 音频编解码芯片WM8731因其高性能、低功耗等优点在很多音频产品中得到...本文提出了WM8731与FPGA的音频编解码系统,并嵌入大功率D类功放技术作为音频系统的功率放大应用,使得本系统效率高,体积小,音质高,性能显著。
  • 音频系统设计包括软件设计和硬件设计两方面,在硬件上使用了基于IIS总线的音频系统体系结构。IIS(Inter-IC Sound bus)又称I2S,是菲利浦公司提出的串行数字音频总线协议。目前很多音频芯片和MCU都提供了
  • Android音频系统

    千次阅读 2017-08-24 17:37:54
    Android音频系统 Audio驱动程序 硬件抽象层 Audio本地框架层 Audio 应用框架层 Dump 功能的Audio硬件抽象层概述: 基础音频知识音频的录制、存储和回放音频采集数据->模拟信号->数模转换器ADC-&gt...

    概述: 基础音频知识

    音频的录制、存储和回放

    音频采集数据->模拟信号->数模转换器ADC->二进制数据->渲染处理(音效调整/过滤)->音频数据压缩处理 编码

    采样定律

    奈奎斯特采样理论 中心思想: 当被采样的模拟信号被还原时,其最高频率只有采样频率的一半
    人声音范围20—20kHz 选择的采样频率就应该在40kHz左右

    音频文件格式

    不压缩格式:PCM 数据 wav aiff后缀
    无损压缩格式:FLAC m4a APE WV
    有损压缩格式:mp3 aac

    两种Linux平台下的音频驱动架构
    OSS:早期
    ALSA:取代OSS,主要文件节点, 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 系统的音频架构主要基于ALSA
    现在 TinyALSA 只支持其中两种Interface 像 Raw MIDI Interface Sequencer Interface Timer Interface 则没有涉及

    Android系统中的音频框架

    Android音频系统

    这里写图片描述

        从上到下依次是app层、java框架层(SoundPool\AudioTrack\AudioManager等)、本地框架层(framework/av)、硬件抽象层、驱动层
    

    Java应用层 :app
    Audio的应用框架层:主要是一些java框架类 在framework/base下面
    Audio本地框架层:包括AudioRecord.cpp AudioTrack.cpp等libmedia类,向上通过jni(有些称之为jni层)与应用框架层的java类相互调用,向下通过binder机制与audioflinger和audiopolicy等AudioServer进程或者MediaServer进行通信
    Audio硬件抽象层:与上层通过AudioFlinger建立连接,向上服务于AudioFlinger,向下与硬件进行交互
    Audio驱动程序层:ALSA

    这里写图片描述

    Audio驱动程序

    驱动程序的框架有ALSA\OSS 等
    包含两方面的功能: 控制接口:音量控制、静音、通道控制等
    数据接口:需要支持PCM类型的输入输出
    在有些系统中,还是和硬件解码结合在一起

    硬件抽象层

    Audio HAL: 音频的硬件抽象层, 服务的对象是AudioFlinger,是AudioFlinger和Audio硬件之间的层次

    Audio HAL 层由主要以下接口组成:

    /android_o/hardware/libhardware/include/hardware/audio.h
    /android_o/hardware/libhardware/include/hardware/audio_effect.h
    

    需要去实现这些接口

    Audio HAL 提供了统一的接口定义它与AudioFlinger/AudioPolicyService之间的通信方式;
    这就是 audio_hw_device, audio_stream_in, audio_stream_out等结构体。
    由 audio.primary.和audio.a2dp.为名的各种库来填充,产品不同,音频设备存在很大的差异,在Android的音频架构中,这些差异是由HAL层的audio.primary等库解决的

    Audio本地框架层

    AudioFlinger

        AudioFlinger 把所有的(最多 32 个)AudioTrack 进行混音(Mixer),然后输送 PCM 到 Audiohardware 中进行播放
    

    上层Audio系统的核心由 AudioFlinger AudioPolicyService 和AudioTrack/AudioRecorder 三者构建而成。

    其中 AudioFlinger AudioPolicyService 属于System Service,驻留在audioserver进程中,负责不断的处理AudioTrack/AudioRecorder的请求

    Audio硬件抽象层以上的部分是Audio的公共部分,一般不会出现问题,更多的是一些客制化的需求;调试Audio的重点在硬件抽象层,在驱动程序有保证的情况下,首先需要调试通过数据流的输入输出逐一调试各种控制类的接口。

    新的变化

    Android 7.0以后本地框架层有了变化,从原来的MediaServer中分离出几个新的进程,如图所示:

    这里写图片描述

    Android O下Google将media部分的代码目录又进行了整理,从下面的目录看
    新建了libaudioclient这个目录将AudioTrack、AudioEffect、AudioSystem.cpp、IAudioTrack.cpp、IAudioFlingerClient.cpp、ToneGenerator.cpp等放进去,在结构上与binder的C/S架构显得更加一致;

    从Android4.1(jellybean) 开始Google从framework/base下的git库下将media的c/c++部分的多媒体框架单独设立framework/av的目录,给它开辟了一个Git库,到如今进一步的变化可见Google的煞费苦心以及多媒体部分的重要性。

    这里写图片描述

    当然多出来的不止libaudioclient,还有libaudiohal、libaaudio等,后续针对性的在研究下libaaudio

    Audio 应用框架层

    上层接口类型

    AudioManager 是一个 java 类,是 android API 中 android.media.*的一部分。提供对音量和 ringer mode(包括铃声和振动)的设置。向下依赖于 AudioService 和 AudioSystem(java)。

    AudioService 是一个 java 层系统服务,向底层转发所有用户发起的调用,同时负责监听 audio 动作的消息,如 audio 设备插入拔出,开关屏等。向下依赖于 AudioSystem(java)。

    AudioSystem 由两部分组成,分别是 AudioSystem(java)和 AudioSystem(native)。
    AudioSystem(java)提供管理 AudioSystem(native)的接口,只由 AudioService 内部调用,不对用 户开放。AudioSystem(native)提供对音频系统的控制,如音量、设备状态、forceUse 等。

    AudioTrack 是 android API 中 android.media.*的一部分,提供用户从 java 层直接输出 PCM 数据和部分播放控制接口。AudioTrack 可以直接播放“音源为 PCM” 的音频数据。

    AudioRecord:这个主要是用来录音的

    AudioService是Android上层音频的核心服务,在SystemServer中启动,为所有音频相关设置提供服务。AudioManager和AudioService是通过Binder机制进行通信。AudioManager拥有AudioService的Bp端,是AudioService在客户端的一个代理。几乎所有的对AudioManager进行的请求,最终都会交给AudioService去处理。而AudioService的实现主要依赖于AudioSystem。AudioSystem是java层到native层的代理。

    展开全文
  • 将一段音频经过系统生成3D音频(基于耳机重放的三维虚拟音频系统设计)
  • 音频编解码芯片WM8731因其高性能、低功耗等优点在很多音频产品中得到...本文提出了WM8731与FPGA的音频编解码系统,并嵌入大功率D类功放技术作为音频系统的功率放大应用,使得本系统效率高,体积小,音质高,性能显著。
  • 摘 要:字时钟同步方式广泛运用于数字音频系统之中,为稳定系统的信号传输质量起到了极大的作用。基于字时钟同步方式的基本理论以及字时钟信号在数字音频系统中的传输方式,介绍了在音乐录音棚中如何应用字时钟信号...
  • Android音频系统之音频框架

    千次阅读 2018-06-14 15:44:05
    Android的音频系统在很长一段时间内都是外界诟病的焦点。的确,早期的Android系统在音频处理上相比于IOS有一定的差距,这也是很多专业的音乐播放软件开发商没有推出Android平台产品的一个重要原因。但这并不代表它的...

    1.1 音频框架


    Android的音频系统在很长一段时间内都是外界诟病的焦点。的确,早期的Android系统在音频处理上相比于IOS有一定的差距,这也是很多专业的音乐播放软件开发商没有推出Android平台产品的一个重要原因。但这并不代表它的音频框架一无是处,相反,基于Linux系统的Android平台有很多值得我们学习的地方。

    1.1.1 Linux下的音频框架

    在计算机发展的早期,电脑的声音处理设备是由一个非常简易的loudspeaker外加发声器(Tone Generator)构成的,功能相对局限。后来人们想到了以plug-in的形式来扩展音频设备,“Sound blaster”就是其中很有名的一个。这种早期的声卡以插件方式连接到电脑主板上,并提供了更多复杂的音频设备。可想而知,独立的硬件设计也意味着成本的增加,于是随着技术的发展,便又出现了板载声卡,也就是我们俗称的“集成声卡”。板载声卡又分为“软声卡”和“硬声卡”。如果声卡本身没有主处理芯片,而只有解码芯片,需要通过CPU运算来执行处理工作,那么就是“软声卡”,反之就是“硬声卡”。通常面向低端市场的计算机都会包含一个集成的声卡设备以降低成本。

    一个典型的声卡通常包含三个部分:

    ·          Connectors

    用于声卡与外放设备,如扬声器、耳机的连接,又被称为“jacks”

    ·          Audio Circuits

    声卡的主要实现体,它负责信号的放大、混音、以及模拟数字转换等操作

    ·          Interface

    连接声卡与计算机总线的单元,比如PCI总线

    我们可以通过“cat/proc/asound/cards”命令来查看计算机中安装的声卡设备,如下例子所示:

    目前市面上声卡的种类众多,既有复杂的高性能的,也有低端的简易的,那么对于一个操作系统来说,它如何管理这些音频设备,并向上层应用提供统一的接口呢?

    Android严格来讲只是一个Linux系统,它依赖于内核提供的各种驱动支持,包括音频驱动。因此我们有必要先花点时间来学习下Linux平台下的两种主要的音频驱动架构:

     

    ·        OSS (Open Sound System)

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

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

    对新音频特性的支持不足;

    缺乏对最新内核特性的支持等等。

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

    表格 13‑1 OSS采用的设备节点

    设备节点

    说明

    /dev/dsp

    向此文件写数据à输出到外放Speaker

    向此文件读数据à从Microphone进行录音

    /dev/mixer

    混音器,用于对音频设备进行相关设置,比如音量调节

    /dev/midi00

    第一个MIDI端口,还有midi01,midi02等等

    /dev/sequencer

    用于访问合成器(synthesizer),常用于游戏等效果的产生

    更多详情,可以参考OSS的官方说明:http://www.opensound.com/

     

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

    表格 13‑2 Alsa-project Package

    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)

     

    关于ALSA的更多知识,建议大家可以自行参阅相关文档,这对于后面理解整个Audio系统是不无裨益的。

    1.1.2 TinyAlsa

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

    在早期版本中,Android系统的音频架构主要是基于ALSA的,其上层实现可以看做是ALSA的一种“应用”。后来可能是由于ALSA所存在的一些不足,Android后期版本开始不再依赖于ALSA提供的用户空间层的实现,因而我们在它的库文件夹中已经找不到alsa相关的lib了,如下图所示:

     

     

        

    图 13‑7 Android4.1与早期版本在音频库上的区别

     

    而取代它的是tinyalsa相关的库文件,如下图所示:


     

    同时我们可以看到externl目录下多了一个“tinyalsa”文件夹,其中包含了为数不多的几个源码文件,如下所示:

     

     

     

    表格 13‑3 Tiny-alsa工程文件

    Source File

    Description

    Android.mk

    makefile

    mixer.c

    Mixer Interface实现

    pcm.c

    PCM Interface实现

    tinycap.c

    Caputer工具

    tinymix.c

    Mixer工具

    tinyplay.c

    Play工具

    include/tinyalsa/asoundlib.h

    头文件

    可见TinyAlsa与原版Alsa的差异还是相当大的,它只是部分支持了其中的两种Interface,而像Raw MIDI、Sequencer、Timer等Interface则没有涉及到——当然这对于一般的嵌入式设备还是足够了。

    TinyAlsa作为Alsa-lib的一个替代品,自面世已来得到的公众评价有褒有贬,不能一概而论——对于每个厂商来说,合适自己的就是最好的。而且各厂商也可以在此基础上扩展自己的功能,真正的把ALSA利用到极致。

    1.1.3 Android系统上的音频框架

     

    一个好的系统架构,需要尽可能地降低上层与具体硬件的耦合,这既是操作系统的设计目的,对于音频系统也是如此。音频系统的雏形框架可以简单的用下图来表示:

     


    图 13‑8 音频系统的雏形框架

     

    在这个图中,除去Linux本身的Audio驱动外,整个Android音频实现都被看成了User。因而我们可以认为Audio Driver就是上层与硬件间的“隔离板”。但是如果单纯采用上图所示的框架来设计音频系统,对上层应用使用音频功能是不小的负担,显然Android开发团队还会根据自身的实际情况来进一步细化“User”部分。

    细化的根据自然还是Android的几个层次结构,包括应用层、framework层、库层以及HAL层,如下图所示:

     


    图 13‑9 音频框架在Android系统中的进一步细化

     

    我们可以结合目前已有的知识,想一下每一个层次都会包含哪些模块(先不考虑蓝牙音频部分)?

    ·        APP

    这是整个音频体系的最上层,因而并不是Android系统实现的重点。比如厂商根据特定需求自己写的一个音乐播放器,游戏中使用到声音,或者调节音频的一类软件等等。

     

    ·        Framework

    相信大家可以马上想到MediaPlayer和MediaRecorder,因为这是我们在开发音频相关产品时使用最广泛的两个类。实际上,Android也提供了另两个相似功能的类,即AudioTrack和AudioRecorder,MediaPlayerService内部的实现就是通过它们来完成的,只不过MediaPlayer/MediaRecorder提供了更强大的控制功能,相比前者也更易于使用。我们后面还会有详细介绍。

    除此以外,Android系统还为我们控制音频系统提供了AudioManager、AudioService及AudioSystem类。这些都是framework为便利上层应用开发所设计的。

     

    ·        Libraries

    我们知道,framework层的很多类,实际上只是应用程序使用Android库文件的“中介”而已。因为上层应用采用java语言编写,它们需要最直接的java接口的支持,这就是framework层存在的意义之一。而作为“中介”,它们并不会真正去实现具体的功能,或者只实现其中的一部分功能,而把主要重心放在库中来完成。比如上面的AudioTrack、AudioRecorder、MediaPlayer和MediaRecorder等等在库中都能找到相对应的类。

    这一部分代码集中放置在工程的frameworks/av/media/libmedia中,多数是C++语言编写的。

    除了上面的类库实现外,音频系统还需要一个“核心中控”,或者用Android中通用的实现来讲,需要一个系统服务(比如ServiceManager、LocationManagerService、ActivityManagerService等等),这就是AudioFlinger和AudioPolicyService。它们的代码放置在frameworks/av/services/audioflinger,生成的最主要的库叫做libaudioflinger。

    音频体系中另一个重要的系统服务是MediaPlayerService,它的位置在frameworks/av/media/libmediaplayerservice。

    因为涉及到的库和相关类是非常多的,建议大家在理解的时候分为两条线索。

    其一,以库为线索。比如AudioPolicyService和AudioFlinger都是在libaudioflinger库中;而AudioTrack、AudioRecorder等一系列实现则在libmedia库中。

    其二,以进程为线索。库并不代表一个进程,进程则依赖于库来运行。虽然有的类是在同一个库中实现的,但并不代表它们会在同一个进程中被调用。比如AudioFlinger和AudioPolicyService都驻留于名为mediaserver的系统进程中;而AudioTrack/AudioRecorder和MediaPlayer/MediaRecorder一样实际上只是应用进程的一部分,它们通过binder服务来与其它系统进程通信。

    在分析源码的过程中,一定要紧抓这两条线索,才不至于觉得混乱。

     

    ·        HAL

    从设计上来看,硬件抽象层是AudioFlinger直接访问的对象。这说明了两个问题,一方面AudioFlinger并不直接调用底层的驱动程序;另一方面,AudioFlinger上层(包括和它同一层的MediaPlayerService)的模块只需要与它进行交互就可以实现音频相关的功能了。因而我们可以认为AudioFlinger是Android音频系统中真正的“隔离板”,无论下面如何变化,上层的实现都可以保持兼容。

    音频方面的硬件抽象层主要分为两部分,即AudioFlinger和AudioPolicyService。实际上后者并不是一个真实的设备,只是采用虚拟设备的方式来让厂商可以方便地定制出自己的策略。

    抽象层的任务是将AudioFlinger/AudioPolicyService真正地与硬件设备关联起来,但又必须提供灵活的结构来应对变化——特别是对于Android这个更新相当频繁的系统。比如以前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.*为名的各种库中)来填充这些“壳”。

    根据产品的不同,音频设备存在很大差异,在Android的音频架构中,这些问题都是由HAL层的audio.primary等等库来解决的,而不需要大规模地修改上层实现。换句话说,厂商在定制时的重点就是如何提供这部分库的高效实现了。

     

     

    基于上面的分析,我们给出一个完整的Android音频系统框架来给大家参考(没有列出Linux层的实现,比如ALSADriver等等),如下所示:


     图 13‑10 Android音频系统框架全图

     

    展开全文
  • 然而,近年来这种状况正在迅速发生变化,由于具有独特的性能优势、越来越高的性价比以及便利简化的设计工具支持,越来越多的专业音频系统、汽车音响、消费音频设备采用数字信号处理器技术。  关注专业音频产品的...
  •  本设计中的音频驱动采用Unified Audio模型实现,基于Intel Xscale PXA272处理器和TI 的TSC2101音频芯片,使用了基于I2S(Inter-IC Sound)总线的音频系统体系结构,系统原理图如图1所示。Intel Xsc
  • 1 引言  随着 Internet技术和多媒体技术的快速...该嵌入式音频系统硬件部分采用基于IIS总线的音频系统体系结构,其主要硬件电路后文作了详细的介绍。软件上,笔者以嵌入式Linux操作系统作为平台,重点介绍该音频系
  • 本文在分析演播室音频系统设计原则的基础上,指出了演播室音频系统的相关技术要求,对如何进行演播室音频系统的及连接进行了分析,并提出了新闻演播室音频应急系统与方案。
  • 本文利用ATMEL公司的AT91RM9200型微处理器和Philips公司的UDA1341型立体声音频编解码器设计了一种嵌入式音频系统。该嵌入式音频系统硬件部分采用基于IIS总线的音频系统体系结构,其主要硬件电路后文作了详细的介绍。...
  • Android的音频系统

    2010-11-19 14:35:47
    Android的音频系统 第一部分 Audio系统综述 第二部分 Audio系统和上层接口 第三部分 Audio的硬件抽象层
  • 本文提出了一种智能多接口的全硬件音频系统的解决方案,使音频系统的扩展性进一步增强。
  • Red5的Web视频、音频系统开发
  •  本设计中的音频驱动采用Unified Audio模型实现,基于Intel Xscale PXA272处理器和TI 的TSC2101音频芯片,使用了基于I2S(Inter-IC Sound)总线的音频系统体系结构,系统原理图如图1所示。Intel Xsc
  • S3C2410是Samsung公司一款基于ARM920T核的微处理器,通过IIS音频总线与UDA1341型CODEC构成一种嵌入式音频系统,实现音频的播放和采集。给出相关硬件电路的说明及Linux下音频驱动程序的设计要点。
  • Android音频系统之二音频框架

    千次阅读 2016-09-09 16:29:23
    Android的音频系统在很长一段时间内都是外界诟病的焦点。的确,早期的Android系统在音频处理上相比于IOS有一定的差距,这也是很多专业的音乐播放软件开发商没有推出Android平台产品的一个重要原因。但这并不代表它的...

    Android的音频系统在很长一段时间内都是外界诟病的焦点。的确,早期的Android系统在音频处理上相比于IOS有一定的差距,这也是很多专业的音乐播放软件开发商没有推出Android平台产品的一个重要原因。但这并不代表它的音频框架一无是处,相反,基于Linux系统的Android平台有很多值得我们学习的地方。

    2.1 Linux下的音频框架

    在计算机发展的早期,电脑的声音处理设备是由一个非常简易的loudspeaker外加发声器(Tone Generator)构成的,功能相对局限。后来人们想到了以plug-in的形式来扩展音频设备,“Sound blaster”就是其中很有名的一个。这种早期的声卡以插件方式连接到电脑主板上,并提供了更多复杂的音频设备。可想而知,独立的硬件设计也意味着成本的增加,于是随着技术的发展,便又出现了板载声卡,也就是我们俗称的“集成声卡”。板载声卡又分为“软声卡”和“硬声卡”。如果声卡本身没有主处理芯片,而只有解码芯片,需要通过CPU运算来执行处理工作,那么就是“软声卡”,反之就是“硬声卡”。通常面向低端市场的计算机都会包含一个集成的声卡设备以降低成本。一个典型的声卡通常包含三个部分:

    1、Connectors

    用于声卡与外放设备,如扬声器、耳机的连接,又被称为“jacks”

    2、Audio Circuits

    声卡的主要实现体,它负责信号的放大、混音、以及模拟数字转换等操作

    3、Interface

    连接声卡与计算机总线的单元,比如PCI总线

    我们可以通过“cat/proc/asound/cards”命令来查看计算机中安装的声卡设备,如下例子所示:

    目前市面上声卡的种类众多,既有复杂的高性能的,也有低端的简易的,那么对于一个操作系统来说,它如何管理这些音频设备,并向上层应用提供统一的接口呢?Android严格来讲只是一个Linux系统,它依赖于内核提供的各种驱动支持,包括音频驱动。因此我们有必要先花点时间来学习下Linux平台下的两种主要的音频驱动架构。

    2.1.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),常用于游戏等效果的产生


    2.1.2ALSA(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)
    2.1.3 Tinyalsa

    一看“Tiny”这个词,大家应该能猜到这是一个ALSA的缩减版本。实际上在Android系统的其它地方也可以看到类似的做法——既想用开源项目,又嫌工程太大太繁琐,怎么办?那就只能瘦身了,于是很多Tiny-XXX就出现了。在早期版本中,Android系统的音频架构主要是基于ALSA的,其上层实现可以看做是ALSA的一种“应用”。后来可能是由于ALSA所存在的一些不足,Android后期版本开始不再依赖于ALSA提供的用户空间层的实现,因而我们在它的库文件夹中已经找不到alsa相关的lib了,而取代它的是tinyalsa相关的库文件。

    同时我们可以看到externl目录下多了一个“tinyalsa”文件夹,其中包含了为数不多的几个源码文件,如下所示:

    Source File

    Description

    Android.mk

    makefile

    mixer.c

    Mixer Interface实现

    pcm.c

    PCM Interface实现

    tinycap.c

    Caputer工具

    tinymix.c

    Mixer工具

    tinyplay.c

    Play工具

    include/tinyalsa/asoundlib.h

    头文件

    可见TinyAlsa与原版Alsa的差异还是相当大的,它只是部分支持了其中的两种Interface,而像Raw MIDI、Sequencer、Timer等Interface则没有涉及到,当然这对于一般的嵌入式设备还是足够了。TinyAlsa作为Alsa-lib的一个替代品,自面世已来得到的公众评价有褒有贬,不能一概而论——对于每个厂商来说,合适自己的就是最好的。而且各厂商也可以在此基础上扩展自己的功能,真正的把ALSA利用到极致。

    2.2 Android系统上音频架构

    一个好的系统架构,需要尽可能地降低上层与具体硬件的耦合,这既是操作系统的设计目的,对于音频系统也是如此。音频系统的雏形框架可以简单的用下图来表示:

    在这个图中,除去Linux本身的Audio驱动外,整个Android音频实现都被看成了User。因而我们可以认为Audio Driver就是上层与硬件间的“隔离板”。但是如果单纯采用上图所示的框架来设计音频系统,对上层应用使用音频功能是不小的负担,显然Android开发团队还会根据自身的实际情况来进一步细化“User”部分。细化的根据自然还是Android的几个层次结构,包括应用层、framework层、库层以及HAL层,如下图所示:

    我们可以结合目前已有的知识,想一下每一个层次都会包含哪些模块(先不考虑蓝牙音频部分)?

    1、APP

    这是整个音频体系的最上层,因而并不是Android系统实现的重点。比如厂商根据特定需求自己写的一个音乐播放器,游戏中使用到声音,或者调节音频的一类软件等等。 

    2、Framework

    相信大家可以马上想到MediaPlayer和MediaRecorder,因为这是我们在开发音频相关产品时使用最广泛的两个类。实际上,Android也提供了另两个相似功能的类,即AudioTrack和AudioRecorder,MediaPlayerService内部的实现就是通过它们来完成的,只不过MediaPlayer/MediaRecorder提供了更强大的控制功能,相比前者也更易于使用。我们后面还会有详细介绍。除此以外,Android系统还为我们控制音频系统提供了AudioManager、AudioService及AudioSystem类。这些都是framework为便利上层应用开发所设计的。

    3、Libraries

    我们知道,framework层的很多类,实际上只是应用程序使用Android库文件的“中介”而已。因为上层应用采用java语言编写,它们需要最直接的java接口的支持,这就是framework层存在的意义之一。而作为“中介”,它们并不会真正去实现具体的功能,或者只实现其中的一部分功能,而把主要重心放在库中来完成。比如上面的AudioTrack、AudioRecorder、MediaPlayer和MediaRecorder等等在库中都能找到相对应的类。这一部分代码集中放置在工程的frameworks/av/media/libmedia中,多数是C++语言编写的。

    除了上面的类库实现外,音频系统还需要一个“核心中控”,或者用Android中通用的实现来讲,需要一个系统服务(比如ServiceManager、LocationManagerService、ActivityManagerService等等),这就是AudioFlinger和AudioPolicyService。它们的代码放置在frameworks/av/services/audioflinger,生成的最主要的库叫做libaudioflinger。

    音频体系中另一个重要的系统服务是MediaPlayerService,它的位置在frameworks/av/media/libmediaplayerservice。

    因为涉及到的库和相关类是非常多的,建议大家在理解的时候分为两条线索。

    其一,以库为线索。比如AudioPolicyService和AudioFlinger都是在libaudioflinger库中;而AudioTrack、AudioRecorder等一系列实现则在libmedia库中

    其二,以进程为线索。库并不代表一个进程,进程则依赖于库来运行。虽然有的类是在同一个库中实现的,但并不代表它们会在同一个进程中被调用。比如AudioFlinger和AudioPolicyService都驻留于名为mediaserver的系统进程中;AudioTrack/AudioRecorder和MediaPlayer/MediaRecorder一样实际上只是应用进程的一部分,它们通过binder服务来与其它系统进程通信

    在分析源码的过程中,一定要紧抓这两条线索,才不至于觉得混乱。

     4、HAL

    从设计上来看,硬件抽象层是AudioFlinger直接访问的对象。这说明了两个问题,一方面AudioFlinger并不直接调用底层的驱动程序;另一方面,AudioFlinger上层(包括和它同一层的MediaPlayerService)的模块只需要与它进行交互就可以实现音频相关的功能了。因而我们可以认为AudioFlinger是Android音频系统中真正的“隔离板”,无论下面如何变化,上层的实现都可以保持兼容。

    音频方面的硬件抽象层主要分为两部分,即AudioFlinger和AudioPolicyService。实际上后者并不是一个真实的设备,只是采用虚拟设备的方式来让厂商可以方便地定制出自己的策略。

    抽象层的任务是将AudioFlinger/AudioPolicyService真正地与硬件设备关联起来,但又必须提供灵活的结构来应对变化——特别是对于Android这个更新相当频繁的系统。比如以前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.*为名的各种库中)来填充这些“壳”。

    根据产品的不同,音频设备存在很大差异,在Android的音频架构中,这些问题都是由HAL层的audio.primary等等库来解决的,而不需要大规模地修改上层实现。换句话说,厂商在定制时的重点就是如何提供这部分库的高效实现了。基于上面的分析,我们给出一个完整的Android音频系统框架来给大家参考(没有列出Linux层的实现,比如ALSADriver等等),如下所示:



    每个公司根据自己产品的特点,各自定义符合自己产品的音频架构图。

    接下来的小节,我们将分别介绍上述框架图中的几个重点模块,包括AudioFlinger,AudioTrack/AudioRecorder,AudioManager/AudioPo

    展开全文
  • 音频系统底层API

    千次阅读 2018-09-17 11:14:49
    Windows Vista、Windows 7、Windows server 2008等系统音频系统相比之前的系统有很大的变化,产生了一套新的底层API即Core Audio APIs。该低层API为高层API( 如Media Foundation(将要取代DirectShow等高层API)等 )...

     

    Windows Vista、Windows 7、Windows server 2008等系统音频系统相比之前的系统有很大的变化,产生了一套新的底层API即Core Audio APIs。该低层API为高层API( 如Media Foundation(将要取代DirectShow等高层API)等 )提供服务。

    微软参考文档

    几个概念,参考自 Windows下Core Audio APIS 音频应用开发

        1. IMMDevice : 创建音频设备终端,我们可以把它简单的理解为设备对象

        2. IAudioClient : 创建一个用来管理音频数据流的对象,应用程序通过这个对象可以获取的音频设备里的数据,我们可以把它想象成一个大水池,里面都是一些数据

        3.  IAudioCaptureClient : 很明显,专用于获取采集数据的对象,它还有个兄弟IAudioRenderClient

     


    Core Audio APIs 在系统中的位置:

     

     

    About MMDevice API ï¼MSDNææ¡£ç解ï¼_Windows


     

    一 Core Audio APIs的组成: 

    1) Multimedia Device (MMDevice) API  //创建音频终端设备

    2) EndpointVolume API

    3) Windows Audio Session API (WASAPI)  

    4) DeviceTopology API

     

    1.1 Multimedia Device (MMDevice) API

         什么是MMDeivice?

         全名:The Windows MultiMediaDevice(MMDevice) API

         头文件: Mmdeviceapi.h defines the interfaces in the MMDevice API

         作用:

        The Windows Multimedia Device (MMDevice) API enables audio clients to discover audio endpoint devicesdetermine their capabilities, and create driver instances for those devices.

         该API用于枚举系统中的音频终端设备(Audio Endpoint Devices)。告诉音频客户端程序有哪些音频终端设备以及它们的性能,并且为这些设备创建驱动实例(driver instances)。是最基本的Core Audio API,为其他三个API提供服务。

       主要接口:

         IMMDeviceEnumerator 用来列举音频终端设备。

         IMMDevice                  代表一个音频设备(audio device)。

         IMMEndpoint               代表一个音频终端设备(audio endpoint device),只有一个方法GetDataFlow,用来识别一个音频终端设备是一个输出设备(rendering device)还是一个输入设备(capture device)。

        IMMDeviceCollection           代表一个音频终端设备的集合  

        


        示例一:获取默认的扬声器设备

        HRESULT hr = S_OK;
        IMMDeviceEnumerator *pMMDeviceEnumerator;

        // activate a device enumerator
        hr = CoCreateInstance(
            __uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 
            __uuidof(IMMDeviceEnumerator),
            (void**)&pMMDeviceEnumerator
        );

        if (FAILED(hr)) {
            ERR(L"CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x", hr);
            return hr;
        }

        // get the default render endpoint  //
        hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, ppMMDevice);
        if (FAILED(hr)) {
            ERR(L"IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: hr = 0x%08x", hr);
            return hr;
        }

       enum __MIDL___MIDL_itf_mmdeviceapi_0000_0000_0001
        {  

         eRender    = 0,                          //扬声器
         eCapture    = ( eRender + 1 ) , //麦克风
         eAll    = ( eCapture + 1 ) ,
         EDataFlow_enum_count    = ( eAll + 1 ) 
        }     EDataFlow;

    HRESULT get_default_device(IMMDevice **ppMMDevice) {
        HRESULT hr = S_OK;
        IMMDeviceEnumerator *pMMDeviceEnumerator;
    
        // activate a device enumerator
        hr = CoCreateInstance(
            __uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 
            __uuidof(IMMDeviceEnumerator),
            (void**)&pMMDeviceEnumerator
        );
        if (FAILED(hr)) {
            ERR(L"CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x", hr);
            return hr;
        }
        ReleaseOnExit releaseMMDeviceEnumerator(pMMDeviceEnumerator);
    
        // get the default render endpoint  
    	//eRender   扬声器
    	//eCapture  麦克风
        hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, ppMMDevice);
        if (FAILED(hr)) {
            ERR(L"IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: hr = 0x%08x", hr);
            return hr;
        }
    
        return S_OK;
    }

      示例二:枚举所有的音频设备

    HRESULT list_devices() {
        HRESULT hr = S_OK;
    
        // get an enumerator
        IMMDeviceEnumerator *pMMDeviceEnumerator;
    
        hr = CoCreateInstance(
            __uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 
            __uuidof(IMMDeviceEnumerator),
            (void**)&pMMDeviceEnumerator
        );
        if (FAILED(hr)) {
            ERR(L"CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x", hr);
            return hr;
        }
        ReleaseOnExit releaseMMDeviceEnumerator(pMMDeviceEnumerator);
    
        IMMDeviceCollection *pMMDeviceCollection;
    
        // get all the active render endpoints
        // eRender设备
        hr = pMMDeviceEnumerator->EnumAudioEndpoints(
            eRender, DEVICE_STATE_ACTIVE, &pMMDeviceCollection
        );
        if (FAILED(hr)) {
            ERR(L"IMMDeviceEnumerator::EnumAudioEndpoints failed: hr = 0x%08x", hr);
            return hr;
        }
        ReleaseOnExit releaseMMDeviceCollection(pMMDeviceCollection);
    
        UINT count;
        hr = pMMDeviceCollection->GetCount(&count);
        if (FAILED(hr)) {
            ERR(L"IMMDeviceCollection::GetCount failed: hr = 0x%08x", hr);
            return hr;
        }
        LOG(L"Active render endpoints found: %u", count);
    
        for (UINT i = 0; i < count; i++) {
            IMMDevice *pMMDevice;
    
            // get the "n"th device
            hr = pMMDeviceCollection->Item(i, &pMMDevice);
            if (FAILED(hr)) {
                ERR(L"IMMDeviceCollection::Item failed: hr = 0x%08x", hr);
                return hr;
            }
            ReleaseOnExit releaseMMDevice(pMMDevice);
    
            // open the property store on that device
            IPropertyStore *pPropertyStore;
            hr = pMMDevice->OpenPropertyStore(STGM_READ, &pPropertyStore);
            if (FAILED(hr)) {
                ERR(L"IMMDevice::OpenPropertyStore failed: hr = 0x%08x", hr);
                return hr;
            }
            ReleaseOnExit releasePropertyStore(pPropertyStore);
    
            // get the long name property
            PROPVARIANT pv; PropVariantInit(&pv);
            hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
            if (FAILED(hr)) {
                ERR(L"IPropertyStore::GetValue failed: hr = 0x%08x", hr);
                return hr;
            }
            PropVariantClearOnExit clearPv(&pv);
    
            if (VT_LPWSTR != pv.vt) {
                ERR(L"PKEY_Device_FriendlyName variant type is %u - expected VT_LPWSTR", pv.vt);
                return E_UNEXPECTED;
            }
    
            LOG(L"    %ls", pv.pwszVal);
        }    
        
        return S_OK;
    }

     

     示例3: 获取指定设备

    HRESULT get_specific_device(LPCWSTR szLongName, IMMDevice **ppMMDevice) {
        HRESULT hr = S_OK;
    
        *ppMMDevice = NULL;
        
        // get an enumerator
        IMMDeviceEnumerator *pMMDeviceEnumerator;
    
        hr = CoCreateInstance(
            __uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 
            __uuidof(IMMDeviceEnumerator),
            (void**)&pMMDeviceEnumerator
        );
        if (FAILED(hr)) {
            ERR(L"CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x", hr);
            return hr;
        }
        ReleaseOnExit releaseMMDeviceEnumerator(pMMDeviceEnumerator);
    
        IMMDeviceCollection *pMMDeviceCollection;
    
        // get all the active render endpoints
        hr = pMMDeviceEnumerator->EnumAudioEndpoints(
            eRender, DEVICE_STATE_ACTIVE, &pMMDeviceCollection
        );
        if (FAILED(hr)) {
            ERR(L"IMMDeviceEnumerator::EnumAudioEndpoints failed: hr = 0x%08x", hr);
            return hr;
        }
        ReleaseOnExit releaseMMDeviceCollection(pMMDeviceCollection);
    
        UINT count;
        hr = pMMDeviceCollection->GetCount(&count);
        if (FAILED(hr)) {
            ERR(L"IMMDeviceCollection::GetCount failed: hr = 0x%08x", hr);
            return hr;
        }
    
        for (UINT i = 0; i < count; i++) {
            IMMDevice *pMMDevice;
    
            // get the "n"th device
            hr = pMMDeviceCollection->Item(i, &pMMDevice);
            if (FAILED(hr)) {
                ERR(L"IMMDeviceCollection::Item failed: hr = 0x%08x", hr);
                return hr;
            }
            ReleaseOnExit releaseMMDevice(pMMDevice);
    
            // open the property store on that device
            IPropertyStore *pPropertyStore;
            hr = pMMDevice->OpenPropertyStore(STGM_READ, &pPropertyStore);
            if (FAILED(hr)) {
                ERR(L"IMMDevice::OpenPropertyStore failed: hr = 0x%08x", hr);
                return hr;
            }
            ReleaseOnExit releasePropertyStore(pPropertyStore);
    
            // get the long name property
            PROPVARIANT pv; PropVariantInit(&pv);
            hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
            if (FAILED(hr)) {
                ERR(L"IPropertyStore::GetValue failed: hr = 0x%08x", hr);
                return hr;
            }
            PropVariantClearOnExit clearPv(&pv);
    
            if (VT_LPWSTR != pv.vt) {
                ERR(L"PKEY_Device_FriendlyName variant type is %u - expected VT_LPWSTR", pv.vt);
                return E_UNEXPECTED;
            }
    
            // is it a match?
            if (0 == _wcsicmp(pv.pwszVal, szLongName)) {
                // did we already find it?
                if (NULL == *ppMMDevice) {
                    *ppMMDevice = pMMDevice;
                    pMMDevice->AddRef();
                } else {
                    ERR(L"Found (at least) two devices named %ls", szLongName);
                    return E_UNEXPECTED;
                }
            }
        }
        
        if (NULL == *ppMMDevice) {
            ERR(L"Could not find a device named %ls", szLongName);
            return HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
        }
    
        return S_OK;
    }

      示例4:

      如需将扬声器的声音记录到文件中,需创建一个声音文件

    HRESULT open_file(LPCWSTR szFileName, HMMIO *phFile) {
        MMIOINFO mi = {0};
    
        *phFile = mmioOpen(
            // some flags cause mmioOpen write to this buffer
            // but not any that we're using
            const_cast<LPWSTR>(szFileName),
            &mi,
            MMIO_WRITE | MMIO_CREATE
        );
    
        if (NULL == *phFile) {
            ERR(L"mmioOpen(\"%ls\", ...) failed. wErrorRet == %u", szFileName, mi.wErrorRet);
            return E_FAIL;
        }
    
        return S_OK;
    }

     

    1.2  WASAPI(Windows Audio Stream API)

    The Windows Audio Session API (WASAPI) enables client applications to manage the flow of audio data between the application and an audio endpoint device.
    Header files Audioclient.h and Audiopolicy.h define the WASAPI interfaces.

    用于创建、管理进出音频端节点设备的音频流。

    程序可通过audio engine,以共享模式访问audio endpoint device(比如麦克风 或Speakers)
    audio engine在endpoint buffer和endpoint device之间传输数据。
    当播放音频数据时,程序向rendering endpoint buffer周期性写入数据。
    当采集音频数据时,程序从capture endpoint buffer周期性读取数据。

    WASAPI 接口:

    IAudioClient接口

    创建管理音频数据流的对象

    涉及的几个方法:

    1)、Activate 

    要访问WASAPI接口,
    客户端首先通过调用IMMDevice :: Activate方法获取对音频终端设备的IAudioClient接口的引用。

    IMMDevice::Activate来获取an audio endpoint device的IAudioClient interface引用。

        // activate an IAudioClient
        IAudioClient *pAudioClient;
        hr = pMMDevice->Activate(
            __uuidof(IAudioClient),
            CLSCTX_ALL, NULL,
            (void**)&pAudioClient
        );

    In Windows Vista, which supports endpoint devices, the process of connecting to the same endpoint device is much simpler:

    • Select a microphone from a collection of endpoint devices.
    • Activate an audio-capture interface on that microphone.

    The operating system does all the work necessary to identify and enable the endpoint device. For example, if the data path from the microphone includes a multiplexer, the system automatically selects the microphone input to the multiplexer.

    Windows Vista,以音频采集为例,连接到endpoint device就两步:
    (1) 从设备集合中选择一个麦克风。
    (2) 用Activate激活该麦克风的音频采集接口。

    IAudioClient        *pAudioClient   = NULL;
    ...
    hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient);
    

    2) IAudioClient::GetDevicePeriod

       The GetDevicePeriod method retrieves the length of the periodic interval separating successive processing passes by the audio engine on the data in the endpoint buffer.

       HRESULT GetDevicePeriod(
         REFERENCE_TIME *phnsDefaultDevicePeriod,
         REFERENCE_TIME *phnsMinimumDevicePeriod
      );

       Pointer to a REFERENCE_TIME variable into which the method writes a time value specifying the default interval between periodic processing passes by the audio engine. The time is expressed in 100-nanosecond units. For information about REFERENCE_TIME, see the Windows SDK documentation.

      可以认为是音频频率周期。

      音频设备本身会有个设备周期,而另外一方面,我们在初始化Core Audio 音频管理对象的时候,也可以设置一个周期,这个是指处理音频数据的周期。这两个时间异常重要,一旦我们设定的处理周期大于设备周期一定范围时,采集出来的数据就会出现丢帧的现象,所以我们要根据设备周期来设置我们的处理周期。

     

      示例:

      音频设置频率周期

        // get the default device periodicity
        REFERENCE_TIME hnsDefaultDevicePeriod;
        hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, NULL);
        if (FAILED(hr)) {
            ERR(L"IAudioClient::GetDevicePeriod failed: hr = 0x%08x", hr);
            return hr;
        }

     自己处理音频数据周期

        // set the waitable timer
        LARGE_INTEGER liFirstFire;
        liFirstFire.QuadPart = -hnsDefaultDevicePeriod / 2; // negative means relative time
        LONG lTimeBetweenFires = (LONG)hnsDefaultDevicePeriod / 2 / (10 * 1000); // convert to milliseconds
        BOOL bOK = SetWaitableTimer(
            hWakeUp,
            &liFirstFire,
            lTimeBetweenFires,
            NULL, NULL, FALSE
        );

     

    又如:

     UINT32 BufferSizePerPeriod()
        {
            REFERENCE_TIME defaultDevicePeriod, minimumDevicePeriod;
            HRESULT hr = _AudioClient->GetDevicePeriod(&defaultDevicePeriod, &minimumDevicePeriod);
            if (FAILED(hr))
            {
    WriteLog(hr, "Unable to retrieve device period: %x\n");
                    return 0;
            }
        double devicePeriodInSeconds = defaultDevicePeriod / (10000.0*1000.0);
        return  devicePeriodInSeconds*1000;//这里返回的就是我们需要的时间
        }

     

     

     3)IAudioClient::GetMixFormat
         The GetMixFormat method retrieves the stream format that the audio engine uses for its internal processing of shared-mode streams.

          音频引擎内部处理的音频流格式。

        // get the default device format
        WAVEFORMATEX *pwfx;
        hr = pAudioClient->GetMixFormat(&pwfx);
        if (FAILED(hr)) {
            ERR(L"IAudioClient::GetMixFormat failed: hr = 0x%08x", hr);
    
            return hr;
        }

         The mix format is the format that the audio engine uses internally for digital processing of shared-mode streams. This format is not necessarily a format that the audio endpoint device supports. Thus, the caller might not succeed in creating an exclusive-mode stream with a format obtained by calling GetMixFormat.

       经常见到这样的描述: 44100HZ 16bit stereo 或者 22050HZ 8bit mono 等等.

       44100HZ 16bit stereo: 每秒钟有 44100 次采样, 采样数据用 16 位(2字节)记录, 双声道(立体声);

       22050HZ 8bit  mono: 每秒钟有 22050 次采样, 采样数据用 8 位(1字节)记录, 单声道;

       当然也可以有 16bit 的单声道或 8bit 的立体声, 等等。

      如果需要将音频流格式,转成16位,可以进行如下操作

     

            // coerce int-16 wave format
            // can do this in-place since we're not changing the size of the format
            // also, the engine will auto-convert from float to int for us
            switch (pwfx->wFormatTag) {
                case WAVE_FORMAT_IEEE_FLOAT:
                    pwfx->wFormatTag = WAVE_FORMAT_PCM;
                    pwfx->wBitsPerSample = 16;
                    pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
                    pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
                    break;
    
                case WAVE_FORMAT_EXTENSIBLE:
                    {
                        // naked scope for case-local variable
                        PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(pwfx);
                        if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) {
                            pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
                            pEx->Samples.wValidBitsPerSample = 16;
                            pwfx->wBitsPerSample = 16;
                            pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
                            pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
                        } else {
                            printf("Don't know how to coerce mix format to int-16\n");
                            CoTaskMemFree(pwfx);
                            pAudioClient->Release();
                            return E_UNEXPECTED;
                        }
                    }
                    break;
    
                default:
                    printf("Don't know how to coerce WAVEFORMATEX with wFormatTag = 0x%08x to int-16\n", pwfx->wFormatTag);
                    CoTaskMemFree(pwfx);
                    pAudioClient->Release();
                    return E_UNEXPECTED;
            }

     

     4)IAudioClient::Initialize

      客户端调用IAudioClient :: Initialize方法来初始化终端设备上的流

       IAudioClient::Initialize用来在endpoint device初始化流

        // call IAudioClient::Initialize
        // note that AUDCLNT_STREAMFLAGS_LOOPBACK and AUDCLNT_STREAMFLAGS_EVENTCALLBACK
        // do not work together...
        // the "data ready" event never gets set
        // so we're going to do a timer-driven loop
        hr = pAudioClient->Initialize(
            AUDCLNT_SHAREMODE_SHARED,
            AUDCLNT_STREAMFLAGS_LOOPBACK,
            0, 0, pwfx, 0
        );

     官方demo中   

    采集时

    示例1、采集麦克风:

       

     HRESULT hr = _AudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 
            AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, 
            _EngineLatencyInMS*10000, 0, _MixFormat, NULL);
     
        if (FAILED(hr))
        {
            printf("Unable to initialize audio client: %x.\n", hr);
            return false;
        }
    

      AUDCLNT_STREAMFLAGS_EVENTCALLBACK 表示当audio buffer数据就绪时,会给系统发个信号,也就是事件触发。

      示例2、混音

      希望同时采集本机声卡上的默认麦克风和默认render的数据, 用AUDCLNT_STREAMFLAGS_LOOPBACK参数来设置

    hr = pAudioClient->Initialize(
            AUDCLNT_SHAREMODE_SHARED,
            AUDCLNT_STREAMFLAGS_LOOPBACK, 
            hnsRequestedDuration,
            0,
            pwfx,
            NULL);
    

       AUDCLNT_STREAMFLAGS_LOOPBACK模式下,音频engine会将rending设备正在播放的音频流, 拷贝一份到音频的endpoint buffer。这样的话,WASAPI client可以采集到the stream。

       如果AUDCLNT_STREAMFLAGS_LOOPBACK被设置, IAudioClient::Initialize会尝试在rending设备开辟一块capture buffer。

       AUDCLNT_STREAMFLAGS_LOOPBACK只对rending设备有效,

       Initialize仅在AUDCLNT_SHAREMODE_SHARED时才可以使用, 否则Initialize会失败。

       Initialize成功后,可以用IAudioClient::GetService可获取该rending设备的IAudioCaptureClient接口

    /*
        The AUDCLNT_STREAMFLAGS_LOOPBACK flag enables loopback recording. 
        In loopback recording, the audio engine copies the audio stream 
        that is being played by a rendering endpoint device into an audio endpoint buffer 
        so that a WASAPI client can capture the stream. 
        If this flag is set, the IAudioClient::Initialize method attempts to open a capture buffer on the rendering device. 
        This flag is valid only for a rendering device 
        and only if the Initialize call sets the ShareMode parameter to AUDCLNT_SHAREMODE_SHARED. 
        Otherwise the Initialize call will fail. 
        If the call succeeds, 
        the client can call the IAudioClient::GetService method 
        to obtain an IAudioCaptureClient interface on the rendering device. 
        For more information, see Loopback Recording.
    */
    
    • render时
    int _EngineLatencyInMS = 50;
    ...
    HRESULT hr = _AudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 
            AUDCLNT_STREAMFLAGS_NOPERSIST, 
            _EngineLatencyInMS*10000, 
            0, 
            _MixFormat, 
            NULL);
     
        if (FAILED(hr))
        {
            printf("Unable to initialize audio client: %x.\n", hr);
            return false;
        }
    

    5)IAudioClient::GetService

     初始化流后,客户端可以通过调用IAudioClient :: GetService方法获取对其他WASAPI接口的引用

        // activate an IAudioCaptureClient
        IAudioCaptureClient *pAudioCaptureClient;
        hr = pAudioClient->GetService(
            __uuidof(IAudioCaptureClient),
            (void**)&pAudioCaptureClient
        );

    IAudioClient::GetService

    初始化流之后,可调用IAudioClient::GetService来获取其它 WASAPI interfaces的引用

    • 采集时
     IAudioCaptureClient *pCaptureClient = NULL;
    ...
    hr = pAudioClient->GetService(
                             IID_IAudioCaptureClient,
                             (void**)&pCaptureClient);
        EXIT_ON_ERROR(hr)
    
    • render时
    IAudioRenderClient *pRenderClient = NULL;
    ...
     hr = pAudioClient->GetService(
                             IID_IAudioRenderClient,
                             (void**)&pRenderClient);
        EXIT_ON_ERROR(hr)
    

    Start is a control method that the client calls to start the audio stream.
    Starting the stream causes the IAudioClient object to begin streaming data between the endpoint buffer and the audio engine.
    It also causes the stream's audio clock to resume counting from its current position.

    6)AudioClient::Start

    Start is a control method that the client calls to start the audio stream.
    Starting the stream causes the IAudioClient object to begin streaming data between the endpoint buffer and the audio engine.
    It also causes the stream's audio clock to resume counting from its current position.

        // call IAudioClient::Start
        hr = pAudioClient->Start();
        if (FAILED(hr)) {
            ERR(L"IAudioClient::Start failed: hr = 0x%08x", hr);
            return hr;
        }

    7) IAudioCaptureClient::GetNextPacketSize

    The GetNextPacketSize method retrieves the number of frames in the next data packet in the capture endpoint buffer.

    这里有两个注意的。
      (1) 单位为audio frame
      (2) 注意是采集buffer(capture endpoint buffer)

    (3)仅在共享模式下生效,独占模式下无效。Use this method only with shared-mode streams. It does not work with exclusive-mode streams.

     

    Before calling the IAudioCaptureClient::GetBuffer method to retrieve the next data packet, the client can call GetNextPacketSize to retrieve the number of audio frames in the next packet.
    The count reported by GetNextPacketSize matches the count retrieved in the GetBuffer call (through the pNumFramesToRead output parameter) that follows the GetNextPacketSize call.

            UINT32 nNextPacketSize;
            for (
                hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize);
                SUCCEEDED(hr) && nNextPacketSize > 0;
                hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize)
            )

     

    8)IAudioCaptureClient::GetBuffer

        https://msdn.microsoft.com/en-us/library/windows/desktop/dd370859(v=vs.85).aspx

        Retrieves a pointer to the next available packet of data in the capture endpoint buffer.

        获得终端缓存中,下一个可用的数据包

    HRESULT GetBuffer(
      [out] BYTE   **ppData,
      [out] UINT32 *pNumFramesToRead,
      [out] DWORD  *pdwFlags,
      [out] UINT64 *pu64DevicePosition,
      [out] UINT64 *pu64QPCPosition
    );
    

     

    ppData: 可读的下个数据包的起始地址。
    pNumFramesToRead : 下个数据包的长度(单位为audio frames)。客户端或者全部读取或者一个也不读。
    pdwFlags:

    The method writes either 0 or the bitwise-OR combination of one or more of the following [**_AUDCLNT_BUFFERFLAGS**](https://msdn.microsoft.com/en-us/library/windows/desktop/dd371458(v=vs.85).aspx) enumeration values:
     
    AUDCLNT_BUFFERFLAGS_SILENT
     
    AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY
     
    AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR
     
    

     

    可利用flag来判断是否为静音。

     

    enum _AUDCLNT_BUFFERFLAGS
        {   AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY  = 0x1,
        AUDCLNT_BUFFERFLAGS_SILENT  = 0x2,
        AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR = 0x4
        } ;
    其中
    AUDCLNT_BUFFERFLAGS_SILENT
    Treat all of the data in the packet as silence and ignore the actual data values. 
    
    //
    //  The flags on capture tell us information about the data.
    //
    //  We only really care about the silent flag 
    //  since we want to put frames of silence into the buffer
    //  when we receive silence.  
    //  We rely on the fact that a logical bit 0 is silence for both float and int formats.
    //
    if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
    {
        //
        //  Fill 0s from the capture buffer to the output buffer.
        //
        ZeroMemory(&_CaptureBuffer[_CurrentCaptureIndex], framesToCopy*_FrameSize);
    }
    else
    {
        //
        //  Copy data from the audio engine buffer to the output buffer.
        //
        CopyMemory(&_CaptureBuffer[_CurrentCaptureIndex], pData, framesToCopy*_FrameSize);
    }                    
    

     

      一个周期所处理的音频数据

      nBlockAlign  音频流每帧数据的大小

    typedef struct tWAVEFORMATEX
    {
        WORD        wFormatTag;         /* format type */
        WORD        nChannels;          /* number of channels (i.e. mono, stereo...) */
        DWORD       nSamplesPerSec;     /* sample rate */
        DWORD       nAvgBytesPerSec;    /* for buffer estimation */
        WORD        nBlockAlign;        /* block size of data */
        WORD        wBitsPerSample;     /* number of bits per sample of mono data */
        WORD        cbSize;             /* the count in bytes of the size of */
                                        /* extra information (after cbSize) */
    } WAVEFORMATEX, *PWAVEFORMATEX, NEAR *NPWAVEFORMATEX, FAR *LPWAVEFORMATEX;

     

                // get the captured data
                BYTE *pData;
                UINT32 nNumFramesToRead;
                DWORD dwFlags;
    
                hr = pAudioCaptureClient->GetBuffer(
                    &pData,
                    &nNumFramesToRead,
                    &dwFlags,
                    NULL,
                    NULL
                    );
                if (FAILED(hr)) {
                    ERR(L"IAudioCaptureClient::GetBuffer failed on pass %u after %u frames: hr = 0x%08x", nPasses, *pnFrames, hr);
                    return hr;
                }
                LONG lBytesToWrite = nNumFramesToRead * nBlockAlign; //要处理的音频数据大小
    
                LONG lBytesWritten = mmioWrite(hFile, reinterpret_cast<PCHAR>(pData), lBytesToWrite);

     

     

     

    总结:

     

     

    后记:

    采集到了本端音频数据,但将音频数据发送到远端时候,如果10ms数据量一传,则呲呲声噪音,如果调大,比如200ms 则听起来断断续续。

     

    而且,音频SDK传送数据,传送的是单声道,所以需要将多声道转成单声道数据。

    多声道转单声道,就是略过其余声道的数据, 应该是各个声道的数据都是一样的

    std::string MutliChannelSoundToSingleEx(const int16_t *buff, int32_t size, int32_t channels)
    {
    	std::string sample_data;
    	for (int i = 0; i < size && channels > 0; i += channels)
    	{
    		sample_data.append((char*)(&buff[i]), 2);
    	}
    	return sample_data;
    }
    
    std::string MutliChannelSoundToSingle(const std::string &src_data, int32_t channels)
    {
    	if (channels == 1)
    	{
    		return src_data;
    	}
    	else
    	{
    		int16_t *buffer = (int16_t*)src_data.c_str();
    		int size = src_data.size() / 2;
    		return MutliChannelSoundToSingleEx(buffer, size, channels);
    	}
    }
    

     

                BYTE *pData;
                UINT32 nNumFramesToRead;
                DWORD dwFlags;
    
                hr = pAudioCaptureClient->GetBuffer(
                    &pData,
                    &nNumFramesToRead,
                    &dwFlags,
                    NULL,
                    NULL
                    );
    
                LONG lBytesToWrite = nNumFramesToRead * nBlockAlign;
    
    			
    			if (pAudioCB)
    			{
    				std::string pdata(reinterpret_cast<const char*>(pData), lBytesToWrite);
    				std::string result_data = pdata;
    
    				result_data = MutliChannelSoundToSingle(pdata, pwfx->nChannels);
    
    				pAudioCB(result_data.c_str(), result_data.size(), netImNeedLength, pwfx->nSamplesPerSec);
    				
    			}
    			

     

    补充:

    For PCM audio data on no more than two channels and with 8-bit or 16-bit samples, use the WAVEFORMATEX structure to specify the data format.

    The following example shows how to set up a WAVEFORMATEX structure for 11.025 kilohertz (kHz) 8-bit mono and for 44.1 kHz 16-bit stereo. After setting up WAVEFORMATEX, the example calls the IsFormatSupported function to verify that the PCM waveform output device supports the format. The source code for IsFormatSupported is shown in an example in Determining Nonstandard Format Support.

    UINT wReturn; 
    WAVEFORMATEX pcmWaveFormat; 
     
    // Set up WAVEFORMATEX for 11 kHz 8-bit mono. 
     
    pcmWaveFormat.wFormatTag = WAVE_FORMAT_PCM; 
    pcmWaveFormat.nChannels = 1; 
    pcmWaveFormat.nSamplesPerSec = 11025L; 
    pcmWaveFormat.nAvgBytesPerSec = 11025L; 
    pcmWaveFormat.nBlockAlign = 1; 
    pcmWaveFormat.wBitsPerSample = 8; 
    pcmWaveFormat.cbSize = 0;
     
    // See if format is supported by any device in system. 
     
    wReturn = IsFormatSupported(&pcmWaveFormat, WAVE_MAPPER); 
     
    // Report results. 
     
    if (wReturn == 0) 
         MessageBox(hMainWnd, "11 kHz 8-bit mono is supported.", 
           "", MB_ICONINFORMATION); 
    else if (wReturn == WAVERR_BADFORMAT) 
         MessageBox(hMainWnd, "11 kHz 8-bit mono NOT supported.", 
           "", MB_ICONINFORMATION); 
    else 
         MessageBox(hMainWnd, "Error opening waveform device.", 
           "Error", MB_ICONEXCLAMATION); 
     
    // Set up WAVEFORMATEX for 44.1 kHz 16-bit stereo. 
     
    pcmWaveFormat.wFormatTag = WAVE_FORMAT_PCM; 
    pcmWaveFormat.nChannels = 2; 
    pcmWaveFormat.nSamplesPerSec = 44100L; 
    pcmWaveFormat.nAvgBytesPerSec = 176400L; 
    pcmWaveFormat.nBlockAlign = 4; 
    pcmWaveFormat.wBitsPerSample = 16; 
    pcmWaveFormat.cbSize = 0;
     
    // See if format is supported by any device in the system. 
     
    wReturn = IsFormatSupported(&pcmWaveFormat, WAVE_MAPPER); 
     
    // Report results. 
     
    if (wReturn == 0) 
        MessageBox(hMainWnd, "44.1 kHz 16-bit stereo is supported.", 
          "", MB_ICONINFORMATION); 
    else if (wReturn == WAVERR_BADFORMAT) 
        MessageBox(hMainWnd, "44.1 kHz 16-bit stereo NOT supported.", 
          "", MB_ICONINFORMATION); 
    else 
        MessageBox(hMainWnd, "Error opening waveform device.", 
          "Error", MB_ICONEXCLAMATION);

     

     

    参考资料:

    https://docs.microsoft.com/en-us/windows/desktop/multimedia/using-the-waveformatex-structure

    WASAPI 01 采集默认设备的音频

    About MMDevice API

    Win7音频系统底层API

    Core Audio APIS in Vista/Win7实现

    Core Audio APIs

    展开全文
  • 音:Android音频系统之音频框架

    千次阅读 2019-03-27 10:09:43
    Android音频系统之音频框架 2013年04月13日 11:58:12 林学森 阅读数:36305更多 https://blog.csdn.net/xuesen_lin/article/details/8796492#commentsedit 个人分类: Android专栏 所属专栏: Thinking In ...
  • 深度剖析 Android 音频系统解析与改进 Android 音频系统的改进设想和展望 拥有 Beats 音效的 HTC One X 当然无需担心其音质目前 Tegra 3 最高端的机器就是已 经升级至 HTC Sense 4.0 华丽界面和 Android 4.0.3 ICS ...
  • Linux 音频系统简析

    千次阅读 2013-12-20 11:22:22
    linux音频系统架构问题由来已久……远远比你想像的复杂。如果你想理 清从读取音频文件到最终从你的扬声器中播放出来这一过程中所用到的技术之间 的关系的话,纸上的结构图足以像炸酱面一样混乱,而你根本找不到任何...
  • Android音频系统之音频基础

    万次阅读 2014-01-15 14:59:14
    第1章 音频系统 转载请注明:LXS, http://blog.csdn.net/uiop78uiop78/article/details/8787779 对于一部嵌入式设备来说,除了若干基础功能外(比如手机通话、短信),最重要的可能就是多媒体了——那么一个最...
  • 基于微处理器和UDAl34l的嵌入式音频系统设计,S3C24lO是Samsung公司一款基于ARM920T核的微处理器,通过ⅡS音频总线与UDAl341型CODEC构成一种嵌入式音频系统,实现音频的播放和采集。给出相关硬件电路的说明及Linux下...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 215,708
精华内容 86,283
关键字:

音频系统