zoukankan      html  css  js  c++  java
  • 设备、线程Android音频系统之AudioFlinger(二)by小雨

    最近一直在研究设备、线程-之类的问题,下午正好有机会和大家共享一下.

        

    1.1.1 音频设备的管理

        虽然AudioFlinger实体已胜利建创并初始化,但到现在为止它还是一块静态的内存空间,没有及涉到体具的任务。

        从能职分布上来讲,AudioPolicyService是策略的制定者,比如什么时候打开音频接口设备、某种Stream类型的音频对应什么设备等等。而AudioFlinger则是策略的执行者,例如体具如何与音频设备信通,如何维护现有统系中的音频设备,以及多个音频流的音混如何理处等等都得由它来实现。

        现在Audio统系中支撑的音频设备接口(Audio Interface)分为三大类,即:

        

    /*frameworks/av/services/audioflinger/AudioFlinger.cpp*/

    static const char * const audio_interfaces[] = {

       AUDIO_HARDWARE_MODULE_ID_PRIMARY, //主音频设备,必须存在

       AUDIO_HARDWARE_MODULE_ID_A2DP, //牙蓝A2DP音频

       AUDIO_HARDWARE_MODULE_ID_USB, //USB音频,初期的版本不支撑

    };

        每种音频设备接口由一个对应的so库供提支撑。那么AudioFlinger怎么会晓得以后设备中支撑上述的哪些接口,每种接口又支撑哪些体具的音频设备呢?这是AudioPolicyService的义务之一,即根据用户置配来指点AudioFlinger加载设备接口。

        当AudioPolicyManagerBase(AudioPolicyService中持有的Policy管理者,面后大节有具体绍介)造构时,它会读取厂商关于音频设备的描述件文(audio_policy.conf),然后据此来打开以上类三音频接口(如果存在的话)。这一进程终究会用调loadHwModule@AudioFlinger,如下所示:

        

    /*frameworks/av/services/audioflinger*/

    audio_module_handle_t AudioFlinger::loadHwModule(const char *name)/*name就是面前audio_interfaces 组数

                                                                成员中的字符串*/

    {

        if (!settingsAllowed()) {

            return 0;

        }

        Mutex::Autolock _l(mLock);

        returnloadHwModule_l(name);

    }

        这个函数没有做实质性的任务,只是执行了加锁动作,然后接着用调面下的函数:

        

    audio_module_handle_t AudioFlinger::loadHwModule_l(const char *name)

    {

                    /*Step 1. 否是已添加了这个interface?*/

        for (size_t i = 0; i <mAudioHwDevs.size(); i++) {

            if(strncmp(mAudioHwDevs.valueAt(i)->moduleName(), name, strlen(name)) == 0) {

               ALOGW("loadHwModule() module %s already loaded", name);

                returnmAudioHwDevs.keyAt(i);

            }

        }

                   

                    /*Step 2. 加载audio interface*/

        audio_hw_device_t *dev;

        int rc =load_audio_interface(name, &dev);   

    /*Step 3. 初始化*/

    mHardwareStatus = AUDIO_HW_INIT;

    rc = dev->init_check(dev);

    mHardwareStatus = AUDIO_HW_IDLE;

    if ((mMasterVolumeSupportLvl !=MVS_NONE) && (NULL != dev->set_master_volume)) {

        AutoMutex lock(mHardwareLock);

        mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;

        dev->set_master_volume(dev, mMasterVolume);

        mHardwareStatus = AUDIO_HW_IDLE;

    }

    /*Step 4. 添加到全局量变中*/

        audio_module_handle_t  handle = nextUniqueId();

        mAudioHwDevs.add(handle,new AudioHwDevice(name, dev));

        return handle;

    }

        Step1@ loadHwModule_l. 首先查找mAudioHwDevs否是已添加了量变name所指示的audio interface,如果是的话直接回返。第一次进入时mAudioHwDevs的size为0,所以还会继承往下执行。

        Step2@ loadHwModule_l. 加载指定的audiointerface,比如“primary”、“a2dp”或者“usb”。函数load_audio_interface用来加载设备所需的库件文,然后打开设备并建创一个audio_hw_device_t例实。音频接口设备所对应的库件文名称是有定一格式的,比如a2dp的块模名多是audio.a2dp.so或者audio.a2dp.default.so等等。查找路径要主有两个,即:

        

    /** Base path of the hal modules */

    #define HAL_LIBRARY_PATH1 "/system/lib/hw"

    #define HAL_LIBRARY_PATH2 "/vendor/lib/hw"

        当然,因为Android是全完源开的,各开发商可以根据自己的须要来行进应相的修改,比如面下是某手机设备的音频库截图:

                 

        图 13‑12 音频库例实

        Step3@ loadHwModule_l,行进初始化操纵。其中init_check是为了定确这个audio interface否是已胜利初始化,0是胜利,其它值表现失败。接下来如果这个device支撑主音量,我们还须要通过set_master_volume行进置设。在每次操纵device前,都要先转变mHardwareStatus的态状值,操纵结束后将其复原为AUDIO_HW_IDLE(根据码源中的注释,这样做是为了便利dump时确正输出外部态状,这里我们就不去究深了)。

        Step4@ loadHwModule_l. 把加载后的设备添参加mAudioHwDevs键值对中,其中key的值是由nextUniqueId成生的,这样做保障了这个audiointerface有拥全局独一的id号。

        实现了audiointerface的块模加载只是万里长征的第一步。因为个一每interface含包的设备平日不止一个,Android统系现在支撑的音频设备如下列表所示:

        表格 13‑4 Android统系支撑的音频设备(输出)

        

    Device Name

    Description

    AUDIO_DEVICE_OUT_EARPIECE

    听筒

    AUDIO_DEVICE_OUT_SPEAKER

    喇叭

    AUDIO_DEVICE_OUT_WIRED_HEADSET

    带话筒的耳机

    AUDIO_DEVICE_OUT_WIRED_HEADPHONE

    耳机

    AUDIO_DEVICE_OUT_BLUETOOTH_SCO

    SCO 牙蓝

    AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET

    SCO 牙蓝耳机

    AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT

    SCO 车载套件

    AUDIO_DEVICE_OUT_BLUETOOTH_A2DP

    A2DP 牙蓝

    AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES

    A2DP 牙蓝耳机

    AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER

    A2DP 牙蓝喇叭

    AUDIO_DEVICE_OUT_AUX_DIGITAL

    AUX IN

    AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET

    模拟dock headset

    AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET

    字数dock headset

    AUDIO_DEVICE_OUT_USB_ACCESSORY

    USB配件

    AUDIO_DEVICE_OUT_USB_DEVICE

    USB设备

    AUDIO_DEVICE_OUT_DEFAULT

    默许设备

    AUDIO_DEVICE_OUT_ALL

    上述每种设备只占int值一个bit位,这里是指上述设备的集合

    AUDIO_DEVICE_OUT_ALL_A2DP

    上述设备中与A2DP牙蓝相干的设备集合

    AUDIO_DEVICE_OUT_ALL_SCO

    上述设备中与SCO牙蓝相干的设备集合

    AUDIO_DEVICE_OUT_ALL_USB

    上述设备中与USB相干的设备集合

        大家可能会有疑问:

        Ø 这么多的输出设备,那么当我们回音放频流(音录也是类似的情况)时,该选择哪种呢?

        Ø 而且以后统系中audio interface也能可很不止一个,应当如何选择?

        然显这些决策任务将由AudioPolicyService来实现,我们会在下一大节做具体论述。这里先给大家分析下,AudioFlinger是如何打开一个Output道通的(一个audiointerface可能含包若干个output)。

        打开音频输出道通(output)在AF中对应的接口是openOutput(),即:

        

    audio_io_handle_t  AudioFlinger::openOutput(audio_module_handle_tmodule, audio_devices_t *pDevices,

                                              uint32_t *pSamplingRate,audio_format_t *pFormat,

                                              audio_channel_mask_t *pChannelMask,

                                              uint32_t *pLatencyMs, audio_output_flags_t flags)

    {

                    /*入参中的module是由面前的loadHwModule 取得的,它是一个audiointerface的id号,可以通过此id在mAudioHwDevs中查找到对应的AudioHwDevice象对*/

        status_t status;

        PlaybackThread *thread =NULL;

    audio_stream_out_t  *outStream = NULL;

        audio_hw_device_t*outHwDev;

                    …

        /*Step 1. 查找应相的audio interface

        outHwDev = findSuitableHwDev_l(module, *pDevices);     

                    …

                    /*Step 2. 为设备打开一个输出流*/

    mHardwareStatus =AUDIO_HW_OUTPUT_OPEN;

        status = outHwDev->open_output_stream(outHwDev, id, *pDevices,(audio_output_flags_t)flags,

                                              &config, &outStream);

        mHardwareStatus =AUDIO_HW_IDLE;

        …

    if (status == NO_ERROR &&outStream != NULL) {

     /*Step 3.成生AudioStreamOut*/

          AudioStreamOut *output = newAudioStreamOut(outHwDev, outStream);

                     /*Step 4.建创PlaybackThread*/

          if ((flags & AUDIO_OUTPUT_FLAG_DIRECT) ||(config.format != AUDIO_FORMAT_PCM_16_BIT) ||

               (config.channel_mask != AUDIO_CHANNEL_OUT_STEREO)) {

                thread = new DirectOutputThread(this, output, id, *pDevices);

          } else {

                thread = new MixerThread(this, output, id, *pDevices);

          }

          mPlaybackThreads.add(id,thread); //添加播放线程

          …

          /*Step 5.Primary output情况下的理处*/

          if ((mPrimaryHardwareDev== NULL) &&flags & AUDIO_OUTPUT_FLAG_PRIMARY)) {

                ALOGI("Usingmodule %d has the primary audio interface", module);

               mPrimaryHardwareDev = outHwDev;

                AutoMutexlock(mHardwareLock);

                mHardwareStatus =AUDIO_HW_SET_MODE;

               outHwDev->set_mode(outHwDev, mMode);

                                                    …

                float initialVolume = 1.0;

               mMasterVolumeSupportLvl = MVS_NONE;

                mHardwareStatus = AUDIO_HW_GET_MASTER_VOLUME; //试测设备否是支撑主音量取获

                if ((NULL !=outHwDev->get_master_volume) &&

                    (NO_ERROR ==outHwDev->get_master_volume (outHwDev, &initialVolume))) {

                   mMasterVolumeSupportLvl = MVS_FULL;

                } else {

                   mMasterVolumeSupportLvl = MVS_SETONLY;

                    initialVolume= 1.0;

                }

                mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;//试测否是支撑主音量置设

                if ((NULL ==outHwDev->set_master_volume) ||

                    (NO_ERROR !=outHwDev->set_master_volume (outHwDev, initialVolume))) {

                   mMasterVolumeSupportLvl = MVS_NONE;

                }

                for (size_t i = 0; i <mAudioHwDevs.size(); i++) {

                   audio_hw_device_t *dev = mAudioHwDevs.valueAt(i)->hwDevice();

                    if ((dev !=mPrimaryHardwareDev) &&

                        (NULL !=dev->set_master_volume)) {

                        dev->set_master_volume(dev,initialVolume);

                    }

                }

                mHardwareStatus =AUDIO_HW_IDLE;

                mMasterVolumeSW =(MVS_NONE == mMasterVolumeSupportLvl)? initialVolume: 1.0;

                mMasterVolume   = initialVolume;

            }

            return id;

        }

        return 0;

    }

        下面这段代码中,颜色加深的分部是我们接下来分析的重点,要主还是绕围outHwDev这个量变所做的一系列操纵,即:

        ·        查找适合的音频接口设备(findSuitableHwDev_l)

        ·        建创音频输出流(通过open_output_stream取得一个audio_stream_out_t)

        ·        用利AudioStreamOut来装封audio_stream_out_t与audio_hw_device_t

        ·        建创播放线程(PlaybackThread)

        ·        如果以后设备是主设备,则还须要行进应相的置设,含包模式、主音量等等

        然显,outHwDev用于记载一个打开的音频接口设备,它的数据类型是audio_hw_device_t,是由HAL划定的一个音频接口设备所应有具的属性集合,如下所示:

        struct audio_hw_device {

        struct hw_device_t common;

         …

        int (*set_master_volume)(struct audio_hw_device *dev, float volume);

        int (*set_mode)(struct audio_hw_device *dev, audio_mode_t mode);

        int (*open_output_stream)(struct audio_hw_device *dev,

                                 audio_io_handle_t handle,

                                 audio_devices_t devices,

                                 audio_output_flags_t flags,

                                 struct audio_config *config,

                                 struct audio_stream_out **stream_out);

        …}

        其中common代表了HAL层有所设备的共有属性;set_master_volume、set_mode、open_output_stream分离为我们置设audio interface的主音量、置设音频模式类型(比如AUDIO_MODE_RINGTONE、AUDIO_MODE_IN_CALL等等)、打开输出数据流供提了接口。

        接下来我们分步来论述。

        Step1@ AudioFlinger::openOutput. 在openOutput中,设备outHwDev是通过查找以后统系来失掉的,代码如下:

        

    audio_hw_device_t*  AudioFlinger::findSuitableHwDev_l(audio_module_handle_tmodule, uint32_t devices)

    {

        if (module == 0) {

            for (size_t i = 0; i< ARRAY_SIZE(audio_interfaces); i++) {

               loadHwModule_l(audio_interfaces[i]);

            }

        } else {

            AudioHwDevice*audioHwdevice = mAudioHwDevs.valueFor(module);

            if (audioHwdevice !=NULL) {

                returnaudioHwdevice->hwDevice();

            }

        }

        // then try to find amodule supporting the requested device.

        for (size_t i = 0; i <mAudioHwDevs.size(); i++) {

            audio_hw_device_t *dev= mAudioHwDevs.valueAt(i)->hwDevice();

            if((dev->get_supported_devices(dev) & devices) == devices)

                return dev;

        }

        return NULL;

    }

        量变module值为0的情况,是为了兼容之前的Audio Policy而别特做的理处。当module于等0时,首先加载有所已知的音频接口设备,然后再根据devices来定确其中符合要求的。入参devices的值实际上来源于“表格 13‑4 Android统系支撑的音频设备(输出)”所示的设备。可以看到,enum中每一个设备类型都对应一个特定的比特位,因而上述代码段中可以通过“与算运”来找到配匹的设备。

        当modules为非0值时,说明Audio Policy指定了体具的设备id号,这时就通过查找全局的mAudioHwDevs量变来认确否是存在符合要求的设备。

        

    DefaultKeyedVector<audio_module_handle_t, AudioHwDevice*>  mAudioHwDevs;

        量变mAudioHwDevs是一个Vector,以audio_module_handle_t为key,个一每handle值独定一确了已添加的音频设备。那么在什么时候添加设备呢?

        一种情况就是面前看到的modules为0时,会load有所在潜设备,另一种情况就是AudioPolicyManagerBase在造构时会预加载有所audio_policy.conf中所描述的output。不论是哪种情况,终究会都用调loadHwModuleàloadHwModule_l,这个函数我们扫尾就分析过了。

        如果modules为非0,且从mAudioHwDevs中也找不到符合要求的设备,程序并不会就此闭幕——它会退而求其次,历遍组数中的有所素元找寻支撑devices的任何一个audio interface。

        Step2@ AudioFlinger::openOutput,用调open_output_stream打开一个audio_stream_out_t。如果直接解讲这个函数的用作,大家可能觉得很抽象,所以这里我们供提一个体具硬件案方上的实现。原生态代码中就含包了一些体具音频设备的实现,如samsung的tuna,其码源实现如下:

        

    /*device/samsung/tuna/audio/Audio_hw.c*/

    static int  adev_open_output_stream(…structaudio_stream_out **stream_out)

    {

        struct tuna_audio_device*ladev = (struct tuna_audio_device *)dev;

        struct tuna_stream_out *out;

        …

        *stream_out = NULL;

        out = (structtuna_stream_out *)calloc(1, sizeof(struct tuna_stream_out));

    … 

    out->stream.common.set_parameters = out_set_parameters;… 

        *stream_out =&out->stream;

        …

    }

        我们去掉了其中的大分部代码,只留下核心分部。可以看到,tuna_stream_out类型含包了audio_stream_out,后者就是最后要回返的结果。种这方法在HAL层实现中非常多见,读者应当要悉熟这样的写法。而对于audio_stream_out的操纵,非无就是根据入参须要,为它的函数指针做初始化,比如set_parameters的实现就终究向指了out_set_parameters。接下来的实现就及涉linux驱动了,我们这里先不往下分析,面后音量调节大节还会再碰到这个函数。

        Step3@ AudioFlinger::openOutput,成生AudioStreamOut象对。这个量变没什么别特的,它把audio_hw_device_t和audio_stream_out_t做为一个体整来装封。

        Step4@ AudioFlinger::openOutput. 既然道通已打开,那么由谁往来道通里放西东呢?这就是PlaybackThread。这里分两种不同的情况:

        ·        DirectOutput

        如果不须要音混

        ·        Mixer

        须要音混

        这两种情况分离对应DirectOutputThread和MixerThread两种线程。我们以后者为例来分析下PlaybackThread的任务模式,也会面后大节打下基本。

        

        图 13‑13 Playback各线程类系关

        如上图所示,用于Playback的线程种类不少,它们的类基都是Thread。

        AudioFlinger中用于记载Record和Playback线程的有两个全局量变,如下:

        

    DefaultKeyedVector< audio_io_handle_t, sp<PlaybackThread>>  mPlaybackThreads;

    DefaultKeyedVector< audio_io_handle_t, sp<RecordThread>>    mRecordThreads;

        在openOutput中,参加mPlaybackThreads的是一个新建的线程类例实,比如MixerThread。它的造构函数如下:

        

    AudioFlinger::MixerThread::MixerThread(…): PlaybackThread(audioFlinger,output, id, device, type),…   

    {   …

        mAudioMixer = new AudioMixer(mNormalFrameCount, mSampleRate);

        if (mChannelCount == 1) {

            ALOGE("Invalidaudio hardware channel count");

        }

        mOutputSink = newAudioStreamOutSink(output->stream);

        …

        if (initFastMixer) {

                                    …

        } else {

            mFastMixer = NULL;

        }

        …

    }

        首先成生一个AudioMixer象对,这是音混理处的症结,我们会在面后有具体绍介。然后检查道声数量,在Mixer情况下确定不止一个道声。接着建创一个NBAIO(Non-blockingaudio I/O interface) sink(即AudioStreamOutSink),并行进negotiate。最后根据置配(initFastMixer)来判断否是应用fast mixer。

        可以象想一下,一个音放线程的任务就是一直理处层上的数据请求,然后将其传递到下一层,终究写入硬件设备。但是在下面这个函数中,乎似并没有看到程序去启动一个新线程,也没有看到进入线程循环的方地,或者去用调其它可能起引线程建创的函数。那么究竟在什么情况下MixerThread才会真正进入线程循环呢?

        不知大家有无注意到之前mPlaybackThreads的定义,我们再次列出如下:

        DefaultKeyedVector< audio_io_handle_t, sp<PlaybackThread> > mPlaybackThreads;

        它实际上是由audio_io_handle_t和PlaybackThread强指针所成组的键值对。同时也可以判断出,PlaybackThread类的先祖中定一会有RefBase。体具来说,就是它的父类Thread承继自RefBase:

        

    /*frameworks/native/include/utils/Thread.h*/

    class Thread : virtual public RefBase

    {…

        根据强指针的性特,目标象对在第一次被引用时是会用调onFirstRef的,这点在面前大节分析AudioFlinger时我们也见过。这个函数实现如下:

        

    void AudioFlinger::PlaybackThread::onFirstRef()

    {

        run(mName,ANDROID_PRIORITY_URGENT_AUDIO);

    }

        很简单,只是用调了run方法,从而启动一个新线程并接间用调threadLoop,一直地理处Mix务业。这样我们就白明了一个PlaybackThread是如何进入线程循环的了,至于循环中须要做些什么,留在下一大节做具体绍介。

        Step5@ AudioFlinger::openOutput,到现在为止,我们已胜利的建立起一个音频道通,就等着AudioTrack往里丢数据了。不过假如以后的output是“primary”的,则还有一些额定的任务要做。程序接着会对此音频设备置设主音量,前提是mMasterVolumeSupportLvl不为MVS_NONE(表现既不支撑主音量的置设和取获。另外MVS_SETONLY表现只支撑置设不能取获,MVS_FULL表现同时支撑置设和取获)。

        “试测设备否是支撑主音量置设/取获”分部的代码很简单,我们就不具体说白明。不过要注意的是,当定确了主音量后,须要自动为统系以后已存在的音频置设主音量(也就是openOutput最后的for循环分部)。这和loadHwModule_l中的置设主音量并不矛盾,试想一下主音量在什么时候被置设是不定确的,因而一旦设定后就先将统系已有的设备先做主音量置设,而后加的设备则由loadHwModule_l来实现。

        我们来整顿下这个大节所论述的内容。

        ·        当AudioPolicyManagerBase造构时,它会根据用户供提的audio_policy.conf来分析统系中有哪些audio interface(primary,a2dp以及usb),然后通过AudioFlinger::loadHwModule加载各audio interface对应的库件文,并次依打开其中的output(openOutput)和input(openInput)

        ·        我们具体分析了openOutput所做的任务,含包打开一个audio_stream_out_t道通,成生AudioStreamOut象对,以及新建PlaybackThread等等。此时“万事俱备,只欠东风”,只要AudioTrack一直和AudioFlinger传递数据,个整音频回放就开始了。当然,这其中还及涉很多态状的管理、路由切换、以及数据的跨进程交互等等,这些都是我们面后内容所要决解的。

    文章结束给大家分享下程序员的一些笑话语录: 不会,Intel会维持高利润,也会维持竞争局面,国外的竞争不是打死对方的那种。你看日本有尼康,佳能,索尼,都做相机,大家都过得很滋润。别看一堆厂,其实真正控制的是后面的那几个财团——有些竞争对手,后面其实是一家人。

  • 相关阅读:
    各个版本的iPhone SDK下载地址
    [置顶]JavaScript类型总览(图)
    自定义UIPageControl 控件(一)
    用自定义协议调用自己的程序
    【Android游戏开发二十六】追加简述SurfaceView 与 GLSurfaceView效率!
    【Cocos2d游戏开发之三】CCScene切换的所有特效(27种)以及设置屏幕横竖屏!
    【Cocos2d游戏开发之五】多触点与触屏事件详解(单一监听、事件分发)
    【Cocos2d游戏开发之六】对触屏事件追加讲解,解决无法触发ccTouchMoved事件[重要!]
    使用UIView实现自动登录
    苹果推送通知服务(APNs)编程
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3022860.html
Copyright © 2011-2022 走看看