NuPlayer基于StagefrightPlayer的基础类构建,利用了底层的ALooper/AHandler机制来进行异步解码播放,Alooper轮循队列中的消息,把消息推送到AHandler处理,最后通过handleMessage函数回调,做相应的逻辑处理
NuPlayer整体结构
MediaPlayerFactory通过工厂模式创建NuPlayerFactory,然后通过NuPlayerFactory创建NuPlayerDriver,接着通过NuPlayerDriver构建一个NuPlayer,NuPlayer作为播放器,其中设计数据解析、解码、渲染等过程
NuPlayer的构建过程
在上层调用setDataSource函数后,开始到达MediaPlayerService的setDataSource函数,通过getPlayerType函数获取播放器类型
播放器类型枚举如下
这时再回到MediaPlayerFactory中看看getPlayerType函数
接下来进入一个宏函数,宏的主要作用是替换,提高代码的执行效率,因为其省去了分配和释放栈帧、传参、传返回值等一系列工作
这个宏函数的标示遍历map中存放的播放器工厂类,调用scoreFactory可以得到播放器的播放能力,如果是NuPlayer,默认有一个0.8的值,返回的ret就是NuPlayerFactory对象,如果得到的值为0.0,就进入getDefaultPlayerType函数
默认也是NuPlayerFactory
针对NuPlayerFactory不会直接创建NuPlayer,而是在NuPlayerDriver的构造函数中创建一个NuPlayerDriver
在NuPlayerDriver的构造函数中,创建了一个NuPlayer,NuPlayerDriver是对NuPlayer的封装,继承MediaPlayerInterface接口。通过NuPlayer可以实现播放功能,主要用于通知上面的流程
NuPlayer继承自AHandler,并且引入了AMessage,通过ALooper来处理消息,比如它的prepareAsync函数
实际就是发送一个kWhatPrepare消息。在onMessageReceived函数中,收到消息并进行处理(该函数太长,仅摘取kWhatPrepare部分分析)
NuPlayer的数据解析模块
解析的模块主要是NuPlayerSource以及继承它的几个类,如HTTPLiveSource、RTSPSource、GenericSource等
在NuPlayer中调用setDataSourceAsync函数后
这里会根据不同协议选择不同的Source对象,有了这个Source对象后,发送kWhatSetDataSource消息(下图同样只是该函数部分截图)
通过具体的Source解析完数据,再把Source强制转换成mSource给Decoder使用,这时里面就包含了数据相关信息,其中的一个HTTPLiveSource,主要用于解析HLS协议
当调用HTTPLiveSource的prepareAsync函数时
内部主要构建了一个LiveSession对象,通过LiveSession内部的connectAsync函数,创建一个会话
查看onConnect函数
网络请求,开始取播放列表,在取到播放列表后进行回调(下图是回调函数)
上面的代码主要是根据URL返回的M3U文件,获取对应的BandwidthItem,如果熟悉M3U文件,可以知道M3U文件有一级索引和二级索引,下图是一些举例
其主要作用是动态码率适应,BANDWIDTH越大,分辨率越高。BINDWIDTH实际上就是带宽
若想获得某个Track的信息,得到视频、音频、字幕相关信息,可以通过LiveSession的getTrackInfo函数
到此数据解析模块完成,视频源的主要作用都是得到音视频数据信息,最终通过mSource变量传递给解码器
NuPlayer的解码模块
它引入了NuPlayerDecoderBase,这是一个基类,真正的解码器逻辑在NuPlayerDecoder.cpp中,它继承了这个基类,下面先查看实例化解码器
上面的代码构造了Decoder对象,且进行了初始化和构建解码器
由于NuPlayerDecoder继承自NuPlayerDecoderBase类,所以Configure函数会执行NuPlayerDecoder的Configure函数,最终回调onConfigure函数,其位于NuPlayerDecoder.cpp中
从onConfigure函数可以看到,NuPlayerDecoder引入了MediaCodec作为解码器。通过CreateByType/CreateByComponentName创建了Codec对象。
NuPlayer的渲染模块
主要功能
1.将音视频原始数据缓存到队列
2.音频数据消耗播放
3.视频数据消耗显示
4.音视频同步
5.播放器控制
将音视频原始数据缓存到队列,在NuPlayerRenderer.h中,存在一个QueueEntry结构体和两个队列
数据在onQueueBuffer函数中进行添加
而音频的播放在NuPlayerRenderer中,会先调用openAudioSink函数
发送一个
kWhatOpenAudioSink消息,收到消息后进行如下处理
调用onOpenAudioSink函数
这里打开了
AudioSink(音频后端)。当将音频数据放入音频队列时,会接着调用postDrainAudioQueue_l函数
上图代码发送了一个kWhatDrainAudioQueue
主要是有一个进行判断的onDrainAudioQueue函数,判断是否需要重新向AudioSink写入数据
到这里,这个音频播放流程如下:先打开音频后端,然后当向音频队列中发送数据,音频队列同时向音频后端写入数据,以供播放音频
视频显示的话,同样是在视频原始数据进入视频队列后,开始执行postDrainVideoQueue函数
这里先发送一个kWhatDrainVideoQueue的消息,然后进行音视频同步,发送消息后会出发调用onDrainVideoQueue
NuPlayer::Renderer使用的是以视频为基准的同步机制,解码后的音频数据时间戳如果大于视频数据时间戳,直接丢弃音频包,然后直接渲染视频。同步机制主要位于视频缓冲区处理部分的onDrainVideoQueue和音频缓冲区处理部分的onDrainVideoQueue中。音视频的渲染都采用类似定时器的机制,只不过视频显示需要依赖于实际解码器,音频播放需要依赖于AudioSink接口