7.3 音频BSP的结构
在Android系统中,Audio标准化部分是硬件抽象层的接口,因此针对特定平台,Audio系统的移植包括Audio驱动程序和Audio硬件抽象层。
Audio驱动程序需要在Linux内核中实现,虽然实现方式各异,然而在通常情况下,Audio的驱动程序都需要提供用于音量控制等的控制类接口,用于PCM输入、输出的数据类接口。
Audio硬件抽象层是Audio驱动程序和Audio本地框架类AudioFlinger的接口。根据Android系统的接口定义,Audio硬件抽象层是C++类的接口,实现Audio硬件抽象层需要继承接口中定义的三个类,分别用于总体的控制、输出和输入。
↘7.3.1 Audio驱动程序
Audio驱动程序为Linux用户空间提供Audio系统控制和数据的接口。
Linux系统中,Audio驱动程序的框架有标准的OSS(Open Sound System,开放声音系统)和ALSA(Advanced Linux Sound Architecture,高级Linux声音体系)框架。但是在具体实现的时候,还有各种非标准的方式。
从目前各个基于Android系统产品的情况来看,Audio系统的实现方式也是各种各样的,并无统一的标准。但是各种不同的Audio驱动程序的功能大同小异,基本都需要包含以下两个方面的功能。
·控制接口:音量控制、静音、通道控制等功能。
·数据接口:需要支持PCM(脉冲编码调制)类型的输入和输出。
在某些系统中,Audio系统还是和硬件编解码结合在一起的,因此可以直接进行编码音频的输出和输入。例如,直接输出和输入MP3、AMR格式的编码音频流。
1.OSS驱动程序
OSS用于播放或录制数字化的声音,其指标主要有采样速率(例如电话为8Kbps,DVD为96Kbps)、channel数目(单声道、立体声)、采样分辨率(8bit、16bit)。
OSS驱动是字符设备,其主设备号为14,次设备号由各个设备单独定义。OSS主要有以下几种设备文件。
·/dev/mixer:次设备号为0,访问声卡中内置的mixer,调整音量大小,选择音源。
·/dev/sndstat:次设备号为6,测试声卡,读这个文件可以显示声卡驱动的信息。
·/dev/dsp(/dev/dspW、/dev/audio):次设备号为3,读此设备进行录音,写此设备进行放音。区别在于采样的编码不同,dsp使用8位无符号数的线性编码,Audio使用μ律编码(用于与SunOS的兼容),dspW使用16位有符号数的线性编码。
·/dev/sequencer:次设备号为1,访问声卡内置的或者连接在MIDI端口的合成器。
·/dev/midiXX:次设备号为2、18、34,MIDI端口。
在用户空间中,最常用的是使用/dev/mixer节点进行音量大小等控制,使用ioctl接口,/dev/dsp用于音频数据操作,write用于放音,read用于录音。
OSS音频驱动的架构如图7-2所示。
图7-2 OSS音频驱动的架构
OSS驱动程序的主要头文件如下所示。
·include/linux/soundcard.h:OSS驱动的主要头文件。
·include/linux/sound.h:定义OSS驱动的次设备号和注册函数。
OSS驱动程序为以下文件:sound/sound_core.c。
sound.h用于OSS各种设备的注册,定义如下所示。
extern int register_sound_mixer(const struct file_operations *fops, int dev); extern int register_sound_midi(const struct file_operations *fops, int dev); extern int register_sound_dsp(const struct file_operations *fops, int dev); typedef int __bitwise snd_device_type_t;
基于OSS驱动程序架构中,用户空间主要使用/dev/mixer进行控制,使用/dev/dsp进行数据流的输入和输出。
2.ALSA驱动程序
ALSA是为音频系统提供驱动的Linux内核组件,以替代原先的OSS。
ALSA是一个完全开放源代码的音频驱动程序集,除了像OSS那样提供一组内核驱动程序模块之外,ALSA还专门为简化应用程序的编写提供相应的函数库,与OSS提供的基于ioctl等原始编程接口相比,ALSA函数库使用起来要更加方便一些。利用该函数库,开发人员可以方便、快捷地开发出自己的应用程序,细节则留给函数库进行内部处理。ALSA也提供了类似于OSS的系统接口。ALSA的开发者建议应用程序开发者使用音频函数库,而不是直接调用驱动程序。
ALSA驱动提供字符设备的接口,其主设备号是116,次设备号由各个设备单独定义,主要的设备节点如下所示。
·/dev/snd/control<N>:主控制
·/dev/snd/pcm<N>c:PCM捕获(capture)通道
·/dev/snd/pcm<N>p:PCM播放(play)通道
·/dev/snd/seq:顺序器
·/dev/snd/timer:定时器
在用户空间中,ALSA驱动通常配合ALSA库来使用,ALSA库通过ioctl等接口调用ALSA驱动程序的设备节点。对于ALSA驱动的调用,通常调用用户空间的ALSA库的接口,而不是直接调用ALSA驱动程序。
提示:ALSA驱动程序可以支持模拟OSS驱动对用户空间的接口,打开两个内核选项之后,可以同时提供OSS的dsp和mixer设备节点。
ALSA音频驱动的架构如图7-3所示。
图7-3 ALSA音频驱动的架构
ALSA驱动程序的头文件如下。
·include/sound/asound.h:ALSA驱动的主要头文件。
·include/sound/core.h:ALSA驱动核心数据结构和具体驱动的注册函数。
·include/sound/pcm.h:用于数据通道PCM的头文件。
·include/sound/control.h:用于控制的头文件。
·include/sound/soc.h:芯片层的头文件。
ALSA驱动程序的核心实现包括在sound/core/目录中的sound.c、pcm.c和control.c等几个文件中。
对ALSA在用户空间的设备文件的操作主要使用读、写和ioctl,asound.h中相关的内容如下所示:
#define SNDRV_DEV_TOPLEVEL ((__force snd_device_type_t) 0) #define SNDRV_DEV_CONTROL ((__force snd_device_type_t) l) #define SNDRV_DEV_LOWLEVEL_PRE ((__force snd_device_type_t) 2) #define SNDRV_DEV_LOWLEVEL_NORMAL ((__force snd_device_type_t) 0xl000) #define SNDRV_DEV_PCM ((__force snd_device_type_t) 0xl00l) #define SNDRV_DEV_RAWMIDI ((__force snd_device_type_t) 0xl002) #define SNDRV_DEV_TIMER ((__force snd_device_type_t) 0xl003) #define SNDRV_DEV_SEQUENCER ((__force snd_device_type_t) 0xl004) #define SNDRV_DEV_HWDEP ((__force snd_device_type_t) 0xl005) #define SNDRV_DEV_INFO ((__force snd_device_type_t) 0xl006) #define SNDRV_DEV_BUS ((__force snd_device_type_t) 0xl007) #define SNDRV_DEV_CODEC ((__force snd_device_type_t) 0xl008) #define SNDRV_DEV_JACK ((__force snd_device_type_t) 0xl009) #define SNDRV_DEV_LOWLEVEL ((__force snd_device_type_t) 0x2000) int snd_register_device_for_dev(int type, struct snd_card *card, int dev, const struct file_operations *f_ops, void *private_data, const char *name, struct device *device); static inline int snd_register_device(int type, struct snd_card *card, int dev, const struct file_operations *f_ops, void *private_data, const char *name){} int snd_unregister_device(int type, struct snd_card *card, int dev);
ALSA设备实现后,使用snd_register_device_for_dev()进行注册,用户空间的设备节点则根据实现的情况产生。
soc.h中定义芯片级别的实现,几个核心结构如下所示。
·snd_soc_pcm_stream结构:表示声卡的数据流。
·snd_soc_ops结构:表示声卡的操作,包括开、关、参数设置等。
·snd_soc_codec结构:表示声卡的编解码,包括了主要的数据结构。
·snd_soc_dai_link结构:表示声卡中DA的接口,其中包含了snd_soc_ops。
·snd_soc_card结构:表示一个声卡,其中包含了snd_soc_dai_link的数组。
·snd_soc_device结构:表示一个声卡的设备接口。
一个具体平台的声音驱动层的实现,要根据soc.h中定义的内容完成。例如,SOC的通用函数snd_soc_new_pcms()调用了snd_pcm_new(),进而调用ALSA核心建立PCM设备,snd_soc_cnew()则调用snd_ctl_new1()函数建立控制设备。
↘7.3.2 硬件抽象层的内容
1.Audio硬件抽象层的主要接口
Audio的硬件抽象层是AudioFlinger和Audio硬件之间的层次,在各个系统的移植过程中可以有不同的实现方式。Audio硬件抽象层和AudioFlinger中使用的枚举类型在Audio框架类的头文件AudioSystem.h中定义。
AudioHardwareInterface.h中定义了三个类:用于数据输出的AudioStreamOut、用于数据输入的AudioStreamIn和用于核心管理的AudioHardwareInterface。
AudioStreamOut类用于描述音频输出的设备,这个接口的主要定义如下所示:
class AudioStreamOut { public: virtual ~AudioStreamOut() = 0; // ...... 省略获取函数:sampleRate() bufferSize() channels() format() frameSize() virtual status_t setVolume(float left, float right) = 0; virtual ssize_t write(const void* buffer, size_t bytes) = 0; virtual status_t standby() = 0; virtual status_t dump(int fd, const Vector<Stringl6>& args) = 0; virtual status_t setParameters(const String8& keyValuePairs) = 0; virtual String8 getParameters(const String8& keys) = 0; virtual status_t getRenderPosition(uint32_t *dspFrames) = 0; };
AudioStreamOut主要的函数是write(),其参数就是一个内存的指针和长度,表示用于输出的音频数据。由实现者通过实际的音频硬件设备,将这块内存输出,也就实现了音频的播放。这块内存的内容是不可被实现者更改的(因此为const)。
AudioStreamIn类用于描述音频的输入设备,这个接口的主要定义如下所示:
class AudioStreamIn { public: virtual ~AudioStreamIn() = 0; // 省略获取函数: sampleRate() bufferSize() channels() format() frameSize() virtual status_t setGain(float gain) = 0; virtual ssize_t read(void* buffer, ssize_t bytes) = 0; virtual status_t dump(int fd, const Vector<Stringl6>& args) = 0; virtual status_t standby() = 0; virtual status_t setParameters(const String8& keyValuePairs) = 0; virtual String8 getParameters(const String8& keys) = 0; virtual unsigned int getInputFramesLost() const = 0; // 获得丢失的帧数目 };
AudioStreamOut主要的函数是read(),其参数就是一个内存的指针和长度,表示用于输入的音频数据。由实现者从实际的音频硬件设备中获取音频数据,填充这块内存。
AudioStreamOut和AudioStreamIn这两个类都需要通过Audio的硬件抽象层核心AudioHardwareInterface接口类得到。AudioHardwareInterface类的定义如下所示:
class AudioHardwareInterface { public: virtual ~AudioHardwareInterface() {} virtual status_t initCheck() = 0; virtual status_t setVoiceVolume(float volume) = 0; // 设置音量 virtual status_t setMasterVolume(float volume) = 0; virtual status_t setMode(int mode) = 0; // 包括正常、铃声和电话模式 virtual status_t setMicMute(bool state) = 0; // 设置麦克风的静音 virtual status_t getMicMute(bool* state) = 0; // 设置各种参数 virtual status_t setParameters(const String8& keyValuePairs) = 0; virtual String8 getParameters(const String8& keys) = 0; virtual size_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount) = 0; virtual AudioStreamOut* openOutputStream(uint32_t devices, // 打开输出流 int *format=0, uint32_t *channels=0, uint32_t *sampleRate=0, status_t *status=0) = 0; virtual void closeOutputStream(AudioStreamOut* out) = 0; virtual AudioStreamIn* openInputStream( uint32_t devices, // 打开输入流 int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status, AudioSystem::audio_in_acoustics acoustics) = 0; virtual void closeInputStream(AudioStreamIn* in) = 0; virtual status_t dumpState(int fd, const Vector<Stringl6>& args) = 0; static AudioHardwareInterface* create(); };
AudioHardwareInterface的openOutputStream()和openInputStream()两个函数分别用于获取AudioStreamOut和AudioStreamIn的实例,它们作为音频输出和输入设备来使用,共同的参数包括音频的格式、通道、采样率等几个方面。setMode()是一个重要的设置参数,NORMAL用于正常的音频播放,RINGTONE用于铃声播放,IN_CALL用于电话呼叫过程。
AudioHardwareInterface.h定义了C语言的接口来获取一个AudioHardwareInterface类型的指针,此函数如下所示:
extern "C" AudioHardwareInterface* createAudioHardware(void);
如果实现一个Android的硬件抽象层,则需要实现以上的这三个类,将代码编译生成动态库libauido.so。在正常情况下,AudioFlinger会链接这个动态库,并调用其中的createAudioHardware()函数来获取所实现的AudioHardwareInterface接口。
Audio的硬件抽象层就是要继承实现接口中的AudioHardwareInterface、AudioStreamIn和AudioStreamOut这三个类。AudioHardwareInterface负责总控,AudioStreamIn负责数据流的输入,AudioStreamOut负责数据流的输出。
Audio硬件抽象层的实现通常需要生成动态库libaudio.so。根据Audio系统的特点,硬件抽象层也需要考虑数据流和控制流两个部分。相对传感器、GPS等系统,Audio系统的数据流是比较大的PCM数据。Audio系统的控制接口最主要的部分是音量控制,根据Audio系统的不同,还包含了各种不同参数的设置。
Audio硬件抽象层的实现有以下几个内容。
·Audio参数的问题
AudioHardwareInterface、AudioStreamIn和AudioStreamOut这三个类都包含了setParameters和getParameters接口设置和获取系统的参数,一些标准参数由AudioSystem.h中的AudioParameter类来表示:主要包含了Audio路径设备、采样率、格式、通道、帧数目等。在AudioStreamIn和AudioStreamOut中,如果不支持某种类型的参数,需要返回INVALID_OPERATION。涉及参数能否更改、能否立刻生效等问题,都需要根据硬件的具体情况来处理。
·AudioHardwareInterface::setMode()实现的问题
setMode()用于设置系统的模式,由AudioSystem.h中的AudioSystem::audio_mode来表示,包含了MODE_NORMAL(通常表示音乐播放)、MODE_RINGTONE(铃声)、MODE_IN_CALL(呼入电话)等数值。电话和铃声的部分实际上已经涉及Audio硬件之外的硬件。设置的效果由具体平台的硬件控制情况决定。
·Audio BufferSize的问题
在实现Audio系统时,根据音频格式、采样率、通道数目,可以得到每一个帧(frame)的大小和码率。BufferSize的大小决定可以缓冲多长时间。如果BufferSize过小,有延迟的时候将会产生声音间断的问题;如果BufferSize过大,将会产生控制不灵敏的问题。因此,需要根据系统的实际情况,确定BufferSize的大小。
·与蓝牙的关系
从Android的AudioFlinger实现情况来看,蓝牙部分涉及音频的功能可以由一个名称为A2dpAudioInterface的类调用Bluez接口来处理,它本身也是一个AudioHardwareInterface的继承实现者。在以前的Android版本中,蓝牙的A2dpAudioInterface与主Audio硬件抽象层并列。AudioFlinger也分别处理了主Audio硬件抽象层和A2dpAudioInterface的情况。在较新的Android版本中,主Audio硬件抽象层可以有选择地利用这个文件实现蓝牙方面的功能。A2dpAudioInterface的功能不是单独存在的,属于附加的功能,它将封装调用主Audio硬件抽象层,并“截流”一些功能使用自己的实现。
2.Audio硬件抽象层的策略接口
Aduio的策略是一个辅助Audio系统的功能模块,其内容在AudioPolicyInterface.h文件中定义。Audio策略接口由类似硬件抽象层的模块实现,被AudioFlinger调用。
AudioPolicyInterface.h文件中定义了AudioPolicyInterface和AudioPolicyInterfaceClient这两个接口类,两个对外的接口如下所示:
extern "C" AudioPolicyInterface* createAudioPolicyManager( AudioPolicyClientInterface *clientInterface); extern "C" void destroyAudioPolicyManager(AudioPolicyInterface *interface);
接口的调用逻辑是使用AudioPolicyInterfaceClient作为参数来创建AudioPolicyInterface类,AudioPolicyInterface是音频策略操作的主要接口。
AudioPolicyInterface接口类的定义如下所示:
class AudioPolicyInterface{ public: virtual ~AudioPolicyInterface() {} virtual status_t setDeviceConnectionState(AudioSystem::audio_devices device, AudioSystem::device_connection_state state, const char *device_address) = 0; virtual AudioSystem::device_connection_state getDeviceConnectionState(AudioSystem::audio_devices device, const char *device_address) = 0; virtual void setPhoneState(int state) = 0; // 设置电话的状态 virtual void setRingerMode(uint32_t mode, uint32_t mask) = 0; // 设置铃声状态 virtual void setForceUse(AudioSystem::force_use usage, // 设置强行使用的通道 AudioSystem::forced_config config) = 0; virtual AudioSystem::forced_config getForceUse( AudioSystem::force_use usage) = 0; virtual void setSystemProperty(const char* property, const char* value) = 0; // Audio输入和输出路径相关功能 virtual audio_io_handle_t getOutput(AudioSystem::stream_type stream, uint32_t samplingRate = 0, uint32_t format = AudioSystem::FORMAT_DEFAULT, uint32_t channels = 0, AudioSystem::output_flags flags = AudioSystem::OUTPUT_FLAG_INDIRECT) = 0; virtual status_t startOutput(audio_io_handle_t output, AudioSystem::stream_type stream) = 0; virtual status_t stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream) = 0; virtual void releaseOutput(audio_io_handle_t output) = 0; virtual audio_io_handle_t getInput(int inputSource, uint32_t samplingRate = 0, uint32_t Format = AudioSystem::FORMAT_DEFAULT, uint32_t channels = 0, AudioSystem::audio_in_acoustics acoustics = (AudioSystem::audio_in_acoustics)0) = 0; virtual status_t startInput(audio_io_handle_t input) = 0; virtual status_t stopInput(audio_io_handle_t input) = 0; virtual void releaseInput(audio_io_handle_t input) = 0; // 省略音量控制和其他操作函数 };
AudioPolicyInterface类由一些抽象接口来实现,这个类主要包含了基本配置、路径设置、音量设置几个方面的功能。AudioPolicyInterface的大部分接口是按照设置—获取(set和get)成对的关系。其中实现的一个核心函数为setForceUse(),其中的第一个表示指定所使用的类型(force_use),第二个参数为被强行使用的配置(forced_config),基本就是音频数据流的通道。
几个和输入/输出相关的函数使用audio_io_handle_t类型表示一个音频输入设备或者输出设备的句柄。输入/输出环节的start和stop函数将在Audio的上层核心环节中被调用。
AudioPolicyInterface隔离Audio系统的核心部分和辅助性的功能部分。例如,由于Audio系统和电话系统有关系,因此setPhoneState()接口是相关电话的设置部分;Audio系统可以有多个输出和输入,setForceUse()接口的功能强行设置输出和输入的通道。
提示:Android系统的框架层会根据场景调用Audio策略的设置接口,该设置能否生效则由硬件抽象层的实现决定。
实现AudioPolicyInterface需要结合自身系统硬件的特点来实现,不仅涉及Audio系统本身,还涉及蓝牙系统(有关A2DP)、电话部分,以及系统特点的外围电路等内容。AudioPolicyInterface的各个接口在上层被调用,相当于对硬件控制的钩子,在具体实现的过程中需要根据自身系统完成。