上一篇介绍了MediaSource的大概流程,这里里介绍一下渲染的核心部分其实就是Renderer来管理数据以及输出部分,这里只介绍音频输出。
Renderer是管理解码器以及输出部件的核心部分,接收Mediasource传过来的数据,然后解码,最终输出。我们这里简要分析一下。
方法 | 作用简介 |
---|---|
getCapabilities | 确认此renderer是否支持特定多媒体格式 |
enable | 传入特定的SampleStream,控制此Renderer |
replaceStream | 替换sampleStream。主要解决HLS的多子文件 |
render | 控制解码输出内容 |
这是返回一个此Renderer能力的一个控制类,确认是否支持特定多媒体格式。在BaseRenderer中,其实就是返回自身,并且在BaseRenderer中实现了RendererCapabilities接口,核心方法是supportsFormat测试此Renderer是否支持此种格式。
这个是配置数据源,并且控制Renderer状态,
这个是控制声音输出或者视频绘制的接口,是需要循环调用的,大概就是根据Renderer状态,判断是否停止数据模块,或者初始化解码器,最终控制输出,一个典型的Renderer实现如下:
@Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
//判断是否结束
if (outputStreamEnded) {
....
}
// Try and read a format if we don't have one already.
if (inputFormat == null) {
// 这里代码看着比较复杂,但是只是为了获取format
......
}
// If we don't have a decoder yet, we need to instantiate one.
//核心是这个,加载真的解码器,核心是调用子类的createDecoder
maybeInitDecoder();
//这里其实就是拿到解码器的数据以及输出,把上次缓冲的数据处理完,直到没有数据,或者数据异常跳出
while (drainOutputBuffer()) {}
//需要为下次render准备数据,其实就是把数据交给decoder,其实就是一次缓存
while (feedInputBuffer()) {}
}
下面看一下drainOutputBuffer
private boolean drainOutputBuffer()
throws ExoPlaybackException, DecoderException, AudioSink.ConfigurationException,
AudioSink.InitializationException, AudioSink.WriteException {
//拿到数据,这是为了处理audiotrack上次数据未处理完,不能吧数据丢掉,
if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer();
}
//结束的话,就是释放
if (outputBuffer.isEndOfStream()) {
}
//格式变化,或者第一次初始化,
if (audioTrackNeedsConfigure) {
Format outputFormat =
getOutputFormat(decoder)
.buildUpon()
.setEncoderDelay(encoderDelay)
.setEncoderPadding(encoderPadding)
.build();
audioSink.configure(outputFormat, /* specifiedBufferSize= */ 0, /* outputChannels= */ null);
audioTrackNeedsConfigure = false;
}
//真的写数据到audiotrack。如果写完成,就释放必要的资源,然后返true,再次回来吧上次缓存的
//解码后的数据处理完(特殊状态可能有多帧缓存数据)
if (audioSink.handleBuffer(
outputBuffer.data, outputBuffer.timeUs, /* encodedAccessUnitCount= */ 1)) {
decoderCounters.renderedOutputBufferCount++;
outputBuffer.release();
outputBuffer = null;
return true;
}
//假如audiotrack数据因为特殊原因只处理一部分数据,则不会通过render函数的drainOutputBuffer循环调用实现retry,把剩余数据写完,
//优点是因为audiotrack的缓存已经满了,没必要等待,提高性能,但是代码逻辑边复杂了这里就造成了,缓冲数据有多帧的情况
return false;
}
大概思路注释中已经解释清楚了,这里其实通过两个循环,一层是Player层面的,一层是render函数层面的。两个循环,更好的保证性能。
private boolean feedInputBuffer() throws DecoderException, ExoPlaybackException {
//拿到需要加载的数据容器,
if (inputBuffer == null) {
inputBuffer = decoder.dequeueInputBuffer();
if (inputBuffer == null) {
return false;
}
}
//保证可以加载多个解码器
if (decoderReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) {
}
//加载数据,交个decoder
FormatHolder formatHolder = getFormatHolder();
switch (readSource(formatHolder, inputBuffer, /* readFlags= */ 0)) {
case C.RESULT_BUFFER_READ:
.....
inputBuffer.flip();
inputBuffer.format = inputFormat;
//传输数据给decoder,通知解码线程
onQueueInputBuffer(inputBuffer);
decoder.queueInputBuffer(inputBuffer);
decoderReceivedBuffers = true;
decoderCounters.inputBufferCount++;
inputBuffer = null;
return true;
default:
throw new IllegalStateException();
}
}
一个完整的解码流程大概就是这样
ExoPlayerImplInternal的逻辑还是很复杂的,这里只能通过特定的模块慢慢的分析整个流程,下一步再分析具体过程。