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的逻辑还是很复杂的,这里只能通过特定的模块慢慢的分析整个流程,下一步再分析具体过程。

猜你喜欢

转载自blog.csdn.net/qq_28282317/article/details/126756070