Android板级支持与硬件相关子系统
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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的各个接口在上层被调用,相当于对硬件控制的钩子,在具体实现的过程中需要根据自身系统完成。