MediaCodec是Android平台上的一个多媒体编解码器,用于对音频和视频数据进行编解码。它可以实现高效的音视频编解码,并且可以与硬件加速器结合使用,提高编解码性能。MediaCodec可以用于录制和播放音视频,以及进行实时的音视频通信等场景。
MediaCodec常用的方法:
- createDecoderByType(String mimeType):根据指定的MIME类型创建解码器。
- createEncoderByType(String mimeType):根据指定的MIME类型创建编码器。
- configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags):配置解码器或编码器的参数,包括媒体格式、渲染表面、加密等。
- start():启动解码器或编码器。
- flush():清空解码器或编码器的输入和输出缓冲区。
- release():释放解码器或编码器的资源。
MediaCodec解码过程:
- 创建MediaCodec对象:首先需要创建一个MediaCodec对象,并指定要进行的编解码操作(编码或解码)以及要使用的编解码器类型。
- 配置MediaFormat:接下来需要配置MediaFormat,即指定要解码的媒体数据的格式,包括媒体类型(音频或视频)、采样率、比特率等参数。
- 配置Surface(可选):如果是视频解码,可以通过设置Surface来将解码后的视频数据直接渲染到Surface上,以实现视频播放。
- 启动MediaCodec:配置完成后,可以调用start()方法启动MediaCodec。
- 输入数据:接下来需要将要解码的媒体数据传递给MediaCodec进行解码。可以通过调用queueInputBuffer()方法将媒体数据传递给MediaCodec。
- 获取解码数据:MediaCodec会将解码后的数据输出到指定的Surface或ByteBuffer中,可以通过调用dequeueOutputBuffer()方法获取解码后的数据。
- 渲染(可选):如果是视频解码并且使用了Surface,解码后的视频数据会直接渲染到Surface上,如果是音频解码或者视频解码但不使用Surface,需要将解码后的数据进行渲染或播放。
- 释放资源:解码完成后,需要释放MediaCodec对象及相关资源。
MediaCodec解码的过程包括配置、启动、输入数据、获取解码数据和渲染等步骤,通过这些步骤可以实现高效的音视频解码。
播放视频
使用MediaCodec解码本地h264文件并播放视频。
- 创建一个MediaExtractor来读取h264文件的数据流。
- 通过MediaFormat获取视频文件的格式信息,包括视频的宽、高、帧率等参数。
- 创建一个MediaCodec来进行视频解码。
- 将解码后的视频帧渲染到Surface上进行播放。
// 创建MediaExtractor并指定要解码的文件路径 MediaExtractor extractor = new MediaExtractor(); extractor.setDataSource(path); // 获取视频文件的格式信息 MediaFormat format = null; for (int i = 0; i < extractor.getTrackCount(); i++) { format = extractor.getTrackFormat(i); String mime = format.getString(MediaFormat.KEY_MIME); if (mime.startsWith("video/")) { extractor.selectTrack(i); break; } } // 创建MediaCodec并配置解码器 MediaCodec codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)); codec.configure(format, surface, null, 0); codec.start(); // 读取并解码视频帧 ByteBuffer[] inputBuffers = codec.getInputBuffers(); ByteBuffer[] outputBuffers = codec.getOutputBuffers(); MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); boolean isEOS = false; while (!isEOS) { int inputBufferIndex = codec.dequeueInputBuffer(10000); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; int sampleSize = extractor.readSampleData(inputBuffer, 0); if (sampleSize < 0) { codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); isEOS = true; } else { codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0); extractor.advance(); } } int outputBufferIndex = codec.dequeueOutputBuffer(info, 10000); if (outputBufferIndex >= 0) { codec.releaseOutputBuffer(outputBufferIndex, true); } } // 释放资源 codec.stop(); codec.release(); extractor.release();
具体实现:
// 解码工具类 public class H264Player implements Runnable { // 本地 h264 文件路径 private String path; private Surface surface; private MediaCodec mediaCodec; private Context context; public H264Player(Context context, String path, Surface surface) { this.context = context; this.path = path; this.surface = surface; try { this.mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); // 视频宽高暂时写死 MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 368, 384); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15); mediaCodec.configure(mediaFormat, surface, null, 0); } catch (IOException e) { // 解码芯片不支持,走软解 e.printStackTrace(); } } public void play() { mediaCodec.start(); new Thread(this::run).start(); } @Override public void run() { // 解码 h264 decodeH264(); } private void decodeH264() { byte[] bytes = null; try { bytes = getBytes(path); } catch (IOException e) { e.printStackTrace(); } // 获取队列 ByteBuffer[] byteBuffers = mediaCodec.getInputBuffers(); int startIndex = 0; int nextFrameStart; int totalCount = bytes.length; while (true) { if (startIndex >= totalCount) { break; } MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); nextFrameStart = findFrame(bytes, startIndex+1,totalCount); // 往 ByteBuffer 中塞入数据 int index = mediaCodec.dequeueInputBuffer(10 * 1000); Log.e("index",index+""); // 获取 dsp 成功 if (index >= 0) { // 拿到可用的 ByteBuffer ByteBuffer byteBuffer = byteBuffers[index]; byteBuffer.clear(); byteBuffer.put(bytes, startIndex, nextFrameStart - startIndex); // 识别分隔符,找到分隔符对应的索引 mediaCodec.queueInputBuffer(index, 0, nextFrameStart - startIndex, 0, 0); startIndex = nextFrameStart; }else { continue; } // 从 ByteBuffer 中获取解码好的数据 int outIndex = mediaCodec.dequeueOutputBuffer(info,10 * 1000); if (outIndex > 0){ try { Thread.sleep(33); } catch (InterruptedException e) { e.printStackTrace(); } mediaCodec.releaseOutputBuffer(outIndex, true); } } } private int findFrame(byte[] bytes, int startIndex, int totalSize) { for (int i = startIndex; i < totalSize - 4; i++) { if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x00 && bytes[i + 3] == 0x01) { return i; } } return -1; } /** * 一次性读取文件 * * @param path * @return * @throws IOException */ public byte[] getBytes(String path) throws IOException { InputStream is = new DataInputStream(new FileInputStream(new File(path))); int len; int size = 1024; byte[] buf; ByteArrayOutputStream bos = new ByteArrayOutputStream(); buf = new byte[size]; while ((len = is.read(buf, 0, size)) != -1) bos.write(buf, 0, len); buf = bos.toByteArray(); return buf; } public void destroy(){ if (mediaCodec != null) { mediaCodec.stop(); mediaCodec.release(); } } }
播放视频:
public class MainActivity extends AppCompatActivity { private H264Player h264Player; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); checkPermission(); initSurface(); } private void initSurface() { SurfaceView surfaceView = findViewById(R.id.surface); SurfaceHolder surfaceHolder = surfaceView.getHolder(); surfaceHolder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { h264Player = new H264Player(MainActivity.this, new File(Environment.getExternalStorageDirectory(), "test.h264").getAbsolutePath(), surfaceHolder.getSurface()); h264Player.play(); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { } }); } private boolean checkPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE }, 1); } return false; } @Override protected void onDestroy() { super.onDestroy(); // 停止解码器 h264Player.destroy(); } }
上述代码使用MediaCodec来解码视频流,并将解码后的视频渲染到SurfaceView上,在Activity销毁时释放MediaCodec资源。
原文地址:https://mp.weixin.qq.com/s/tiq1aCacgQX2CEEgTohRmw