Exoplayer源码解析2

Posted 白嫩豆腐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Exoplayer源码解析2相关的知识,希望对你有一定的参考价值。

前言

上一篇介绍了MediaSource的大概流程,这里里介绍一下渲染的核心部分其实就是Renderer来管理数据以及输出部分,这里只介绍音频输出。

正文

Renderer是管理解码器以及输出部件的核心部分,接收Mediasource传过来的数据,然后解码,最终输出。我们这里简要分析一下。

方法作用简介
getCapabilities确认此renderer是否支持特定多媒体格式
enable传入特定的SampleStream,控制此Renderer
replaceStream替换sampleStream。主要解决HLS的多子文件
render控制解码输出内容

getCapabilities

这是返回一个此Renderer能力的一个控制类,确认是否支持特定多媒体格式。在BaseRenderer中,其实就是返回自身,并且在BaseRenderer中实现了RendererCapabilities接口,核心方法是supportsFormat测试此Renderer是否支持此种格式。

enable

这个是配置数据源,并且控制Renderer状态,

render

这个是控制声音输出或者视频绘制的接口,是需要循环调用的,大概就是根据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的逻辑还是很复杂的,这里只能通过特定的模块慢慢的分析整个流程,下一步再分析具体过程。

以上是关于Exoplayer源码解析2的主要内容,如果未能解决你的问题,请参考以下文章

Exoplayer源码解析2

Exoplayer源码解析3之解封装器解析

Exoplayer源码解析3之解封装器解析

Exoplayer源码解析3之解封装器解析

Gson源码解析

Gson源码解析