Android Audio System 架构初探:库层(一)

1     库层(Native层)

1.1     MediaPlayer和MediaPlayerService

 

图表18MediaPlayer和MediaPlayerService

所以,MediaPlayerService和MediaPlayer之间是互为Bp,Bn的关系。MediaPlayer调用一个函数如getDuration实际上调的是MediaPlayerService create出来的一个player的getDuration,而这个player是一个MediaPlayerService的Client,它是一个BnMediaPlayer,然后加到mClients里。 所以MediaPlayer调用函数,会调用到MediaPlayerService的Client的同名函数。最终调用到Client里的变量mPlayer的同名函数。

从APP到AudioTrack

图表19创建stagefrightplayer的流程

图表20StagefrightPlayer上下文

playerType怎么拿的呢?从MediaPlayerFactory::getPlayerType(this, url)中拿来的,就是在sFactoryMap中拿得分最高的那个类型,sFactoryMap只在registerFactory_l被赋值,而MediaPlayerService构造函数中调用了registerBuiltinFactories,注册了几个Factory。

创建player(即Client里mPlayer)用到了简单工厂模式。有很多factory,这些factory创建了MediaPlayerBase,比如StagefrightPlayerFactory,new了一个player就是StagefrightPlayer(继承MediaPlayerInterface),而StagefrightPlaye在构造的时候直接给mPlayer赋值了一个new AwesomePlayer,在initAudioDecoder中,AwesomePlayer得到了Duration。在MPS中,还有个IMediaPlayerClient类型的 在setdataSourcepost中,赋值player给mPlayer

setAudioSink时可以设置AudioOutput或AudioCache。AudioPlayer::start的时候,mAudioSink->open,然后AudioOutput在Open的时候new了一个AudioTrack,这时候就到了AT和AF交互的地方了。BufferInfo *info =&mPortBuffers[kPortIndexOutput].editItemAt(index); *buffer =info->mMediaBuffer

最终在AudioPlayer的FillBuffer里memcpy到AT和AF的共享内存。

对AudioSink的操作,最终转化为对AudioOutput里AudioTrack的操作。但是,如果上层没有设置AudioSink的话,AudioPlayer会自己new一个AudioTrack并进行操作。

1.2     MediaExtractor

而Android是如何判断输入多媒体源的格式呢?在创建MediaExtractor时,传入一个DataSource,然后通过DataSource的sniff函数,找到mime类型。Sniffer是DataSource的内部类,在registerDefaultSniffers中,SniffFunc如SniffMPEG4被注册进去。通过mSniffers里的所有Sniffer给出对应DataSource的confidence值,也就是打分,然后找出confidence分值最高的mimeType。这种机制也用来选择播放器,比如到底选择StageFriaghtPlayer还是NuPlayer等。

1.3     编解码器

播放音频的是AudioPlayer,在initAudioDecoder里,调用OMXCodec::Create拿到mAudioSource,在需要软件解码情况下是mOmxSource,在offload模式下(无需软件解码)直接用mAudioTrack。Audio和Video都有一个TimedEventQueue,start的时候都create了一个thread,入口函数是threadEntry,这是个典型的事件驱动模型,对于一些耗时的操作,一个线程产生事件,通知另一个线程去处理事件,以保证第一个线程响应的及时性。TimedEventQueue就是一个新线程,

AwesomeEvent实现了Event里的抽象方法fire,即(mPlayer->*mMethod)()。AwesomePlayer在prepareAsync_l时,就是create了一个AwesomeEvent,并放入mQueue。然后在TimedEventQueue里去异步的执行。

在play_l中,若mAudioSource存在,则创建createAudioPlayer_l,并且mAudioPlayer->setSource(mAudioSource);然后startAudioPlayer_l。

一个StageFrightPlayer对应一个AwesomePlayer,若媒体源既有视频又有音频,则会创建两个OMXCodec实例,若只有视频会音频一样,则只创建一个OMXCodec实例。

AT的callback函数MediaPlayerService::AudioOutput::CallbackWrapper会调到AudioPlayer里的AudioSinkCallback,然后在fillBuffer里调用到了mSource->read(&mInputBuffer,&options);最终到了OMXCodec里的read,或者是mAudioTrack里的read。在调用OMXCodec中read的情况下,可以看出mInputBuffer其实是个输出参数,然后把Codec里的输出Buffer赋值给mInputBuffer。而这些Buffer都是在allocateBuffersOnPort里申请的。在InitOMXParams和initPort进行参数设置,一般都会分配4个InputBuffer,4个OutputBuffer。最后memcpyoutputBuffer里的数据到AT和AF的ShareBuffer。在调用mAudioTrack的read的情况下,mInputBuffer同样也是个输出参数,直接获取MediaBufferGroup里的MediaBuffer,最后也是memcpy到AT和AF的ShareBuffer。


图表21需要软解码的Buffer数据交换

 


图表22无需解码的Buffer数据交换


图表23OMX类图

OMXCodec是一个codec框架,实现者可以在这个框架中实现各种解码方式,包括硬件解码。OMXMaster 负责OMX中编解码器插件管理,软件解码和硬件解码都是使用OMX标准,挂载plugins的方式来进行管理。软解通过 addPlugin(new SoftOMXPlugin);会把这些编解码器的名字都放在mPluginByComponentName中。硬件编解码是通过 addVendorPlugin();加载libstagefrighthw.so.各个芯片平台可以遵循openmax 标准,生成libstagefrighthw.so的库来提供android应用。

OMXMaster::OMXMaster()

    :mVendorLibHandle(NULL) {

    addVendorPlugin();

    addPlugin(newSoftOMXPlugin);

}

在allocateNode的时候,SoftOMXPlugin调用makeComponentInstance,遍历所有的libstagefright_soft_*.so库,创建软件component,并将component和plugin一起存入mPluginByInstance。



图表24OMX的工作方式

OMX解码的方式很像流水线,由两个线程(工人)负责操作,一个decoder/encoder线程,一个dispatcher线程。对buffer的操作如下:OMXCodec使用draininputbuffer试图解码,调用IOMX的emptyBuffer,然后调到OMX的emptyBuffer,然后OMX找到node,调用OMX_EmptyThisBuffer,然后发消息KwhatEmptyThisBuffer给SoftComponent,此时进入编解码线程。component收到该命令后会读取inputport buffer中的数据,调用onQueueFilled,将其组装成帧进行解码,读取buffer中的数据完成后会调用EmptyBufferDone通知OMXNodeInstance,然后调用OMXNodeInstance中注册好的回调onEmptyBufferDone,然后调到OMX里的OnEmptyBufferDone。OMX发消息EMPTY_BUFFER_DONE给对应的Dispather线程。在Dispather的on_message中,调用drainInputBuffer。compoment使用EmptyBufferDone通知OMXCodec已完成inputportbuffer的读取,OMXCodec收到该命令后会通过mVideoTrack读取新的视频buffer到input port的buffer中,并调用OMX_EmptyThisBuffer通知component。OMXCodec使用OMX_FillThisBuffer传递空的bffer给component用于存储解码后的帧,component收到该命令后将解码好的帧数据复制到该buffer上,然后调用FillBufferDone通知OMXCodec,compoment使用FillBufferDone通知OMXCodec已完成outputportbuffer的填充OMXCodec收到该命令后将解码好的帧传递给mISurface进行图像绘制,绘制完毕后使用OMX_FillThisBuffer通知component有空的buffer可填充。OnEmptyBufferDone由Decoder线程执行,而EMPTY_BUFFER_DONE由Dispather线程执行,二者通过message进行通信。


图表25 OMX解码工作流程

1.1     NuPlayer

1.1.1       New MediaPlayer

需要注意的是上层怎么拿到下层的参数,上层(AS)通过Binder调用AF的registerClient,此函数根据调用者的进程号创建一个NotificationClient,并保存在AF中,然后给所有AF下的playbackThread发送AUDIO_OUTPUT_OPENED事件,并给所有的RecordThread发送AUDIO_INPUT_OPENED。每个Thread都会做一次ioConfigChanged,把自己的参数写到AudioIoDescriptor中,然后通过AF发给上层的调用者(根据pid甄别)。包括

    struct audio_patch mPatch;

    uint32_t mSamplingRate;

    audio_format_t mFormat;

    audio_channel_mask_t mChannelMask;

    size_t mFrameCount;

uint32_tmLatency;

上层的调用者通过AudioFlingerClient这个类来进行处理,将这些参数记录下来,并调用回调函数onAudioDeviceUpdate

记录使用key为ioDesc->mIoHandle,也就是说不同的playbackThread会分别单独存下来。


发布了198 篇原创文章 · 获赞 45 · 访问量 30万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览