【学习】从零开始的Android音视频开发(8)——AwesomePlayer的渲染输出过程及总结

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会取得两个时间戳mPositionTimeMediaUsmPositionTimeRealUs。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函数来提交事件,用于停止解码,还可以选择是否继续缓冲数据

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值