AwesomePlayer的渲染输出过程
图示数据处理过程
解码组件OMXCodec
读取MediaSource
中的数据进行解码,VideoRender
读取解码后的视频数据进行渲染,AudioPlayer
获取解码后的音频数据进行播放
视频渲染器构建过程
视频渲染器的构造过程是在通知解码完成的事件回调函数中,下图这一行代码在AwesomePlayer的构造函数中
在其构造函数中构造一个AwesomeEvent时,传入了onVideoEvent
的引用地址
以上代码最后会调用initRenderer_l
函数
总结:AwesomeRemoteRenderer的本质是由OMX::createRenderer
建立一个硬件渲染器(hardware renderer),逻辑为如果时硬解码则构建AwesomeNativeWindowRenderer
渲染器,如果是软解码则构建AwesomeLocalRenderer
渲染器
AwesomeNativeWindowRenderer
渲染器
以上代码最重要的是render
函数,把要渲染的数据入队到NativeWindow
(本地服务窗口),通过NativeWindow来渲染数据,而另一个AwesomeLocalRenderer渲染器主要用于软解码时进行渲染
它的本质是由OMX开始创建的Renderer,createRenderer
函数用于建立一个渲染器。如果video decoder是software component,则建立一个AwesomeLocalRenderer
作为mVideoRenderer。AwesomeLocalRenderer的构造函数会呼叫本身的init
函数,其所做的事和OMX::createRenderer
一摸一样,可理解为把读取到的数据显示在渲染器中。
将音频数据放到Buffer的过程
在前面渲染器已经渲染出了画面,而MediaExtractor把音视频分开,那么之后要怎么让解码后的音视频数据保持同步?
无论是音频还是视频,它们都是bufferdata
,音频或视频总有一个维持时间线的流。在OpenCore框架中设置了一个主时钟,而音频和视频就分别参考主时钟作为输出的依据,而在StageFright框架中,音频的输出是通过回调函数来驱动的,视频则根据音频的时间戳来做同步
音频相关播放过程
在StageFright框架中,音频部分是交由AudioPlayer来处理的,它是在AwesomePlayer的play_l
函数中被构建的
然后调用createAudioPlayer_l()
创建AudioPlayer
然后查看startAudioPlayer_l
函数
然后查看mAudioPlayer->start(true)
的操作
上图代码中有一个mAudioSink
变量,当它不为null时,AudioPlayer会将其传入构造函数,而且AudioPlayer中的播放操作都会依靠mAudioSink来完成。此处的mAudioSink是从MediaPlayerService
注册而来的AudioOut
对象,具体代码在MediaPlayerService中
间接地调用了StagefrightPlayer->setAudioSink
,最终回到AwesomePlayer中
构造AudioPlayer时用到的就是mAudioSink成员变量,因此后面在分析传入的mAudioSink操作时,记住实际的对象为AudioOut
对象,其在MediaPlayerService中定义
AudioPlayer在AwesomePlayer中的运行过程
查看AudioPlayer的构造函数
该构造函数主要用于初始化,并将传入的audioSink引用赋值给mAudioSink成员变量。再回到前面调用AudioPlayer的start函数
总结start函数内部的过程
1.调用mSource->read
启动解码,解码第一帧相当于启动了解码循环
2.获取音频参数:采样率、声道数以及量化位数(这里只支持PCM_16_BIT)
3.启动输出:这里若mAudioSink非空,则启动mAudioSink进行输出,否则构造一个AudioTrack进行音频输出。这里的AudioTrack是比较底层的接口,AudioOut是AudioTrack的封装
在start函数中主要是调用mAudioSink进行工作的
前面说过mAudioSink是AudioOut对象,查看其实现(代码在mediaplayerservice.cpp中)。首先分析mAudioSink的open
函数,要注意传入的参数中有一个函数指针AudioPlayer::AudioSinkCallback
,在AudioPlayer中播放PCM音频原始数据时会定期调用此回调函数填充到解码后的缓冲队列中
总结
1.处理传入的参数,回调函数保存在mCallback中cookie
代表的是AudioPlayer对象指针类型,接下来根据采样率、声道数等计算frameCount
2.构造AudioTrack
对象,并且赋值给t
3.将AudioTrack对象存储在mTrack
成员变量中
当以上过程完成,继续分析AudioPlayer的start函数时,最后都会实例化一个AudioTrack
对象,然后获取音频帧大小、采样率等信息,接着调用AudioTrack中的start函数时,最后都会实例化一个AudioTrack对象,最后到达MediaPlayerService服务类,进行音频输出
在调用start函数后,AudioTrack启动后就会周期性地调用回调函数从解码器获取数据
音视频同步
通过fillBuffer
,不断填充Buffer
总结:当callback函数回调AudioPlayer读取解码后的数据时,AudioPlayer会取得两个时间戳mPositionTimeMediaUs
和mPositionTimeRealUs
。MpositionTimeMediaUs是数据里面所持有的时间戳(timestamp),mPositionTimeRealUs则是播放此数据的实际时间(依据帧数目以及采样率得出)
总结
1.在构造AudioPlayer的时候会执行mTimeSource=mAudioPlayer
,理解为将AudioPlayer作为参考时钟
2.上述代码中成员变量mSeekTimeUs由CHECK(mVideoBuffer->meta_data()->findInt64(kKeyTime,&timeUs))
获得
3.realTimeOffset=getRealTimeUsLocked()-mPositionTimeRealUs
,表示当显示画面是第一帧时,当前音频的播放时间与第一帧视频渲染出的时间差值
4.其中变量mPositionTimeRealUs时通过在AudioPlayer的getMediaTimeMapping
函数中复制得到的
跟踪getMediaTimeMapping函数,实际上就是对象AudioPlayer里面的mPositionTimeRealUs-mPositionTimeMediaUs
.mPositionTimeRealUs实际上是在AudioPlayer::fillBuffer里面被赋值的,((mNumramesPlayed+size_done/mFrameSize)*1000000)/mSampleRate
就是目前已经播放完的数据的时间,也就是理想情况下此帧需要被播放的事件。mPositionTimeMediaUs实际上就是CHECK(mInputBuffer->meta_data()->findInt64(kKeyTime,&mPositionTimeMediaUs))
,即此音频帧在媒体文件中的时间戳
这样mTimeSourceDeltaUs=realTimeUs-mediaTimeUs-mediaTimeUs
,所表示的含义就是该帧音频实际需要播放的时间点和所标注的时间戳的误差
getMediaMapping
二者的差值表示这一包PCM数据已经播放了多少。StageFright中的Vedio便依据从AudioPlayer得出的两个时间戳的插值作为播放的依据
音视频输出
最后回到onVedioEvent
中,调用VedioRenderer的render函数,传入视频数据后,就开始通过渲染器渲染出视频,在应用层上可以通过SurfaceView或者TextureView显示视频画面
分离出的mVideoTrack送达MOXCodec后,调用OMX组件进行解码,而mVedioSource不断从OMXCodec读取解码后的数据,然后发给mVideoRenderer进行渲染
概要总结
1.设置DataSource,数据源可以分为URI和FD。URI可以是http://
等,FD是一个本地文件描述符,通过FD可以找到对应的文件
2.由DataSource生成MediaExtractor。通过sp extractor=MediaExtractor::Create(dataSource)
来实现。MediaExtractor::Create(dataSource)会根据不同的数据内容创建不同的数据读取对象
3.通过调用setVideoSource,由MediaExtractor分解生成音频数据流(mAudioTrack)和视频数据流(mVideoTrack)
4.当使用onPreparedAsyncEvent
时,如果DataSource是URL,根据地址获取数据,并开始缓冲,直到获取mVideoTrack和mAudioTrack。mVideoTrack和mAudioTrack通过调用initVideo/AudioDecoder
来生成mVideoSource和mAudioSource这两个音视频解码器,然后调用postBufferingEvent_l
函数提交事件开启缓冲
5.数据缓冲的执行函数是onBufferingUpdate
,当缓冲区有足够的数据可以播放时,调用play_l
函数开始播放,play_l中的关键是调用了postVideoEvent_l
函数,提交了mVideoEvent
。这个事件在执行时会调用函数onVideoEvent
,这个函数通过调用mVideoSource->read(&mVideoBuffer,&options)
进行视频解码,而音频解码通过mAudioPlayer实现
6.视频解码器解码后通过mVideoSource->read
读取一帧帧的数据,将数据放到mVideoBuffer
中,最后通过mVideoRenderer->render(mVideoBuffer)
把视频数据发送到显示模块。当需要暂停或停止时,调用cancelPlayerEvents
函数来提交事件,用于停止解码,还可以选择是否继续缓冲数据