MediaCodec解码h264

一准备

SurfaceView:作为Mediacodec的图像显示的绑定对象,需要事先布局:

<SurfaceView         
    android:id="@+id/surface_view"       
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  
    android:layout_centerInParent="true"/>   

当Surface被创建成功后才能进行配置Mediacodec 解码器的动作

二、查看手机是否存在video/avc解码器

我们可以使用MediaCodecInfo相关的api来查看是否支持;

private void checkMediaDecoder() {
  int numCodecs = MediaCodecList.getCodecCount();
  MediaCodecInfo mediaCodecInfo = null;
  for (int i = 0; i < numCodecs && mediaCodecInfo == null; i++) {
      MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
      if (info.isEncoder()) {
          continue;
      }
      String[] types = info.getSupportedTypes();
      boolean found = false;
      for (int j = 0; j < types.length && !found; j++) {
          if (types[j].equals("video/avc")) {
              System.out.println("found");
              found = true;
              isSupportHardWare = found;
              return;
          }
      }
      if (!found) {
          continue;
      }
      mediaCodecInfo = info;
  }
	mediaCodecInfo.getCapabilitiesForType("video/avc").colorFormats);
}

三、初始化解码器

public void configure(int width, int height, ByteBuffer sps, ByteBuffer pps) {
    // width & height 需要从H264流开头的sps中解析出
    MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
    // 这里直接把sps和pps传进去就可以了
    mediaFormat.setByteBuffer("csd-0", sps);
    mediaFormat.setByteBuffer("cud-1", pps);
    String mime = mediaFormat.getString(MediaFormat.KET_MIME);
    try {
      mediaCodec = MediaCodec.createDecoderByType(mime);
    } catch (IOException e) {
        ...
    }
    mediaCodec.configure(mediaFormat, surface, null, 0);
    mediaCodec.start();
    bufferInfo = new MediaCodec.BufferInfo();
    isConfigured = true;
}

我们可以看到,在解码h264数据时,需要BUFFER_FLAG_CODEC_CONFIG 标记一些参数数据,这些数据我们可以在调用queueInputBuffer函数时添加上这个标记,也可以通过ByteBuffer的形式写到"csd-0"和"csd-1"这两个key对应的value上,设置到MediaFormat中(如上述代码所示);在configure函数,我们需要传入一个surface,这样MediaCodec在解码完成时就会将解码数据释放到surface上,这个surface我们推荐使用GLSurfaceView通过OpenGL ES创建的textureID来构建,这样MediaCodec就可以和OpenGL ES共享一个纹理ID;当然也可以用SurfaceView来获取Surface,但这样有一个缺陷,如果SurfaceView的生命周期走到destory,MediaCodec正在解码,则会报出一个Surface is destory异常。

四、填充数据

for(;;) {
  // 这里解释一下  传0是不等待 传-1是一直等待 但是传-1会在很多机器上挂掉,所以还是用0吧 丢帧总比挂掉强
  int inputBufferIndex = mediaCodec.dequeueInputBuffer(0);
  if (inputBufferIndex >= 0) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
      // 从输入队列里去空闲buffer
      inputBuffer = mediaCodec.getInputBuffers()[inputBufferIndex];
      inputBuffer.clear();
    } else {
      // SDK_INT > LOLLIPOP
      inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex);
    }
    if (null != inputBuffer) {
      // h264 data, offset, length
      inputBuffer.put(h264Data, 0, h264Data.length);
      // data from object send to mediacodec's input queue
      switch (h264Data[4] & 0x1f) {
        case KEY_FRAME:
            _mediaDecoder.queueInputBuffer(inputBufferIndex, 0, h264Data.length, 0L, MediaCodec.BUFFER_FLAG_KEY_FRAME);
            break;
        case SPS_FRAME:
        case PPS_FRAME:
            mediaCodec.queueInputBuffer(inputBufferIndex, 0, h264Data.length, 0L, MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
            break;
        default:
            mediaCodec.queueInputBuffer(inputBufferIndex, 0, h264Data.length, 0L, 0);
            break;
      }
    }
    int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
    while (outputBufferIndex > 0){
      if (surface.isValid()) {
          // 将解码后数据渲染到surface上
         mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
      }
      outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
    }
  }
}

最后,MediaCodec是Android暴露的一个比较接近底层解码器的API,和MediaPlayer差不多但比MediaPlayer更自由;在我们通过一系列的MediaService调度后,通过libstagefriht调度到ACodec编解码器,来为我们完成音视频的编解码,在配合OpenGL ES后,能做到一些非常强大的功能,比如视频剪切、合成、音频添加、视频的实时特效处理。这里写的不是特别的详细,如果有纰漏欢迎补充

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×