美文网首页
视频解码流程随笔

视频解码流程随笔

作者: xx_G | 来源:发表于2019-02-11 18:29 被阅读0次

由于解码属于耗时操作,以下流程需要在线程进行

  1. 构造读取视频数据的对象并设置视频path
mMediaExtractor = new MediaExtractor();
mMediaExtractor.setDataSource(path);
  1. 遍历所有轨道track,找到并选中video/开头的轨道
int videoTrackIndex = -1;
MediaFormat videoFormat = null;
int trackCount = mMediaExtractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
   MediaFormat trackFormat = mMediaExtractor.getTrackFormat(i);
   String mime = trackFormat.getString(MediaFormat.KEY_MIME);
   if (!TextUtils.isEmpty(mime) && mime.startsWith("video/")) {
      videoFormat = trackFormat;
      videoTrackIndex = i;
}
mMediaExtractor.unselectTrack(i);
}
mMediaExtractor.selectTrack(videoTrackIndex);
  1. 根据选中轨道trackMediaFormat,生成MediaCodec解码器
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
String decoderName = mediaCodecList.findDecoderForFormat(videoFormat);
if (decoderName != null) {
    mMediaDecoder = MediaCodec.createByCodecName(decoderName);
}
  1. 配置MediaCodec,例如指定类型format、载体surface
if(mMediaDecoder != null) {
   mMediaDecoder.configure(videoFormat, surface, null, 0);
   mMediaDecoder.start(); // 启动解码器,并未开始解码
}
  1. while(true)读取数据-渲染数据,直至MediaCodec.BUFFER_FLAG_END_OF_STREAM,过程是生产(读取)-消费者(渲染)模式
MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
long firstFrameTime = 0;
final int TIME_OUT_ESC = 10000;
boolean inputDone = false;
boolean outputDone = false;
// 伪代码
while (!outputDone) {
    if (!inputDone) {  
        // 读取数据放入队列
        ...
    }
    if (!outputDone) {
        // 队列取出数据,交给 surface 渲染
        // 如果 MediaCodec.BUFFER_FLAG_END_OF_STREAM 跳出循环
        ...
    }
}
  1. 生产者流程
int inputBufferIndex = decoder.dequeueInputBuffer(TIME_OUT_ESC);
if (inputBufferIndex >= 0) {
    ByteBuffer inputBuffer = decoder.getInputBuffer(inputBufferIndex);
    if (inputBuffer != null) {
        int sampleDataSize = extractor.readSampleData(inputBuffer, 0);
        if (sampleDataSize > 0) {
            long presentationTimeUs = extractor.getSampleTime();
            decoder.queueInputBuffer(inputBufferIndex, 0, sampleDataSize, presentationTimeUs, 0);
            extractor.advance();
        } else {
            decoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            inputDone = true;
        }
     }
  }
  1. 消费者流程
int outputBufferIndex = decoder.dequeueOutputBuffer(outputBufferInfo, TIME_OUT_ESC);
if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
    Log.d(TAG, "no output from decoder available");
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    MediaFormat outputFormat = decoder.getOutputFormat();
    Log.d(TAG, "output format changed, new format is == " + outputFormat.toString());
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
    Log.d(TAG, "decoder output buffers changed");
} else if (outputBufferIndex < 0) {
    Log.d(TAG, "decoder output buffers < 0");
} else {
    if (firstFrameTime == 0) {
        firstFrameTime = System.nanoTime();
    }
    // 计算帧率,如果没有,播放速度将取决于硬件解码速度
    long dNs = outputBufferInfo.presentationTimeUs * 1000L - (System.nanoTime() - firstFrameTime);
    long millis = dNs / 1000000L;
    int nanos = (int) (dNs & 1000L);
    if (millis >= 0 && nanos >= 0) {
        Thread.sleep(millis, nanos);
    }
    // 通过这个值,通知 surface 是否需要渲染
    boolean render = outputBufferInfo.size != 0;
    decoder.releaseOutputBuffer(outputBufferIndex, render);
    if ((outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
        outputDone = true;
    }
}

相关文章

网友评论

      本文标题:视频解码流程随笔

      本文链接:https://www.haomeiwen.com/subject/hmnqeqtx.html