• 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()) {}
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    下面看一下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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    大概思路注释中已经解释清楚了,这里其实通过两个循环,一层是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();
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    一个完整的解码流程大概就是这样
    在这里插入图片描述

    后记

    ExoPlayerImplInternal的逻辑还是很复杂的,这里只能通过特定的模块慢慢的分析整个流程,下一步再分析具体过程。

  • 相关阅读:
    Spring Security6 用户身份认证
    Springboot晋韵戏剧点播网站毕业设计源码112304
    [工具]工控机磁盘容量监控通知工具
    衔尾法解决当无法使用空闲中断以及DMA中断时配置DMA接收串口不定长数据
    使用 Angular 14 的 inject 函数优化对 Ngrx 的使用方式
    LeetCode --- 1979. Find Greatest Common Divisor of Array 解题报告
    力扣283. 移动零
    linux 安装nginx
    python:基础知识
    [vue3+elementuiplus]el-select下拉框会自动触发校验规则的最强解决方案
  • 原文地址:https://blog.csdn.net/qq_28282317/article/details/126756070