发布时间:2026-07-25阅读(1)
最近在学习ing,加油
硬件和软件编解码器在介绍之前,我们需要知道什么是硬编解码器和软编解码器?
1.软编解码器:使用软件本身或使用CPU的方式对原始视频进行编解码。
优点:兼容性好。.
缺点:CPU占用率高,app内存占用较高,可能是因为CPU发热导致降频、卡顿、无法流畅录制、播放视频等。
2.硬编解码器:使用非CPU编码,如显卡GPU、专用DSP芯片、厂商芯片等。一般编解码算法是固定的,所以采用芯片处理。
优点:非常快速高效,CPU占用率低,即使长时间录制高清视频,手机也不会发热。
缺点:但是兼容性不好,经常出现画面不够精细的问题(但是看不出来)。
MediaCodec 硬编解码器一般安卓直播采集终端/短视频编辑软件默认使用硬编解码,如果手机不支持软编解码。硬编解码器为王。
在 AndroidMediaCodec类中使用的是编码和解码。MediaCodec它是什么??MediaCodec是 Android 音视频编解码类,它通过访问底层codec来实现编解码的功能,比如需要把摄像头的视频 yuv 数据编码为h264/h265,pcm编码为aac,h264/h265解码等待yuv,aac解码pcm等待。MediaCodec是 Android 4.1 API16 引入了,在 Android 5.0 API21 中加入了异步模式。
MediaCodec Call是系统注册的编解码器,硬件厂商在系统中注册自己的硬编解码器,称为硬编解码器,如果硬件厂商注册软件编解码器,就是软件编解码器。通常不同的硬件制造商是不同的。然后 MediaCodec 负责调用。
获取手机支持的编解码器不同的手机支持的编解码器不同,如何获取手机支持的编解码器?如下:
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) private fun getSupportCodec() { val list = MediaCodecList(MediaCodecList.REGULAR_CODECS) val codecs = list.codecInfos Log.d(TAG, "Decoders:") for (codec in codecs) { if (!codec.isEncoder) Log.d(TAG, codec.name) } Log.d(TAG, "Encoders:") for (codec in codecs) { if (codec.isEncoder) Log.d(TAG, codec.name) } }
输出
2020-12-25 19:16:00.914 13115-13115/com.bj.gxz.h264decoderdemo D/H264: Decoders:2020-12-25 19:16:00.914 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.aac.decoder2020-12-25 19:16:00.914 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.amrnb.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.amrwb.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.flac.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.g711.alaw.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.g711.mlaw.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.gsm.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.mp3.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.opus.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.raw.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.vorbis.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.hisi.video.decoder.avc2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.h264.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.h263.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.hisi.video.decoder.hevc2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.hevc.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.hisi.video.decoder.mpeg22020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.hisi.video.decoder.mpeg42020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.mpeg4.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.hisi.video.decoder.vp82020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.vp8.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.vp9.decoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: Encoders:2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.aac.encoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.amrnb.encoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.amrwb.encoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.flac.encoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.hisi.video.encoder.avc2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.h264.encoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.h263.encoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.hisi.video.encoder.hevc2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.mpeg4.encoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.vp8.encoder2020-12-25 19:16:00.915 13115-13115/com.bj.gxz.h264decoderdemo D/H264: OMX.google.vp9.encoder
看一下命名,软解码器通常都是OMX.google以开头的,就像上面那个一样OMX.google.h264.decoder。硬解码器是基于OMX.[hardware_vendor]一开始的,像上面那个应该是海思芯片OMX.hisi.video.decoder.avc 。hisi当然也有一些不遵守这个规则的,系统会认为它是软解码器。编码器的名称是相同的。来自Android系统的源码可以判断规则,源码地址:http ://androidos.net.cn/android/6.0.1_r16/xref/frameworks/av/media/libstagefright/OMXCodec.cpp
static bool IsSoftwareCodec(const char *componentName) { if (!strncmp("OMX.google.", componentName, 11)) { return true; } if (!strncmp("OMX.", componentName, 4)) { return false; } return true;}
MediaCodec 非常强大,支持的编解码数据类型有:压缩音频数据、压缩视频数据、原始音频数据和原始视频数据,并且支持不同封装格式的编解码,如前所述,如果是硬解码,当然,也需要手机厂商的支持。您可以通过设置 Surface 来获取/呈现原始视频数据。MediaCodec 关于 API 每个方法和参数都有自己的含义。你可以慢慢深入地使用它。
MediaCodec 的编解码过程下图是Android官方文档提供的,官方文档很详细。https://developer.android.google.cn/reference/android/media/MediaCodec?hl=en

MediaCodec 处理输入数据产生输出数据,异步处理数据时,使用一组输入输出byteBuffer。该过程通常是
需要注意的是, Buffer 并不是我们自己对 MediaCodec 的新对象,它是 MediaCodec 为了更好地控制 Buffer 来处理,我们需要使用 MediaCodec 提供的方法来获取,然后将数据插入其中并获取数据。
媒体编解码器 API从上面的api我可以看到电影MediaCodec编解码器API的生命周期,可以再看官网。
MediaCodec 的同步和异步编解码器同步方式官方样本
MediaCodec codec = MediaCodec.createByCodecName(name); codec.configure(format, …); MediaFormat outputFormat = codec.getOutputFormat(); // option B codec.start(); for (;;) { int inputBufferId = codec.dequeueInputBuffer(timeoutUs); if (inputBufferId >= 0) { ByteBuffer inputBuffer = codec.getInputBuffer(…); // fill inputBuffer with valid data … codec.queueInputBuffer(inputBufferId, …); } int outputBufferId = codec.dequeueOutputBuffer(…); if (outputBufferId >= 0) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A // bufferFormat is identical to outputFormat // outputBuffer is ready to be processed or rendered. … codec.releaseOutputBuffer(outputBufferId, …); } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // Subsequent data will conform to new format. // Can ignore if using getOutputFormat(outputBufferId) outputFormat = codec.getOutputFormat(); // option B } } codec.stop(); codec.release();
流程如下:
- Create and configure MediaCodec object - Loop until its done : - If input buffer Be on it - Read a piece of input , Fill it in with input buffer Encoding and decoding are performed in the system - If the output buffer Be on it : - From output buffer Get the data after encoding and decoding for processing .- After processing , The destruction MediaCodec object .
在Android 5.0,API21,引入异步模式。官方样品:
MediaCodec codec = MediaCodec.createByCodecName(name); MediaFormat mOutputFormat; // member variable codec.setcallback(new MediaCodec.Callback() { @Override void onInputBufferAvailable(MediaCodec mc, int inputBufferId) { ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId); // fill inputBuffer with valid data … codec.queueInputBuffer(inputBufferId, …); } @Override void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A // bufferFormat is equivalent to mOutputFormat // outputBuffer is ready to be processed or rendered. … codec.releaseOutputBuffer(outputBufferId, …); } @Override void onOutputFormatChanged(MediaCodec mc, MediaFormat format) { // Subsequent data will conform to new format. // Can ignore if using getOutputFormat(outputBufferId) mOutputFormat = format; // option B } @Override void onError(…) { … } }); codec.configure(format, …); mOutputFormat = codec.getOutputFormat(); // option B codec.start(); // wait for processing to complete codec.stop(); codec.release();
- Create and configure MediaCodec object .- to MediaCodec Object settings callback MediaCodec.Callback- stay onInputBufferAvailable Calling back : - Read a piece of input , Fill it in with input buffer Encoding and decoding are performed in the system - stay onOutputBufferAvailable Calling back : - From output buffer After encoding and decoding, the data is processed .- After processing , The destruction MediaCodec object .
让我们解码视频中的一个h264(摘自tiktok的一段话。/葵h264文件).h265是一回事,只要了解h264.h265的编码方式和原理以及码流结构,都是小菜一碟。为了更好地理解 h264 的比特流数据,我们演示了一次只读取内存中的文件 byte 数据。
我们从两个方面来处理,都可以正常播放,只是我们对h264 Stream数据有了更深入的了解。
首先初始化MediaCodec
var bytes: ByteArray? = null var mediaCodec: MediaCodec init { // demo test , To facilitate one-time access to memory bytes = FileUtil.getBytes(path) // video/avc Namely H264, Create decoder mediaCodec = MediaCodec.createDecoderByType("video/avc") val mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height) mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15) mediaCodec.configure(mediaFormat, surface, null, 0) }
方式一:划分NAL单元(NALU)方式
private fun decodeSplitNalu() { if (bytes == null) { return } // Data start subscribing var startFrameIndex = 0 val totalSizeIndex = bytes!!.size - 1 Log.i(TAG, "totalSize=$totalSizeIndex") val inputBuffers = mediaCodec.inputBuffers val info = MediaCodec.BufferInfo() while (true) { // 1ms=1000us subtle val inIndex = mediaCodec.dequeueInputBuffer(10_000) if (inIndex >= 0) { // Split a frame of data if (totalSizeIndex == 0 || startFrameIndex >= totalSizeIndex) { Log.e(TAG, "startIndex >= totalSize-1 ,break") break } val nextFrameStartIndex: Int = findNextFrame(bytes!!, startFrameIndex 1, totalSizeIndex) if (nextFrameStartIndex == -1) { Log.e(TAG, "nextFrameStartIndex==-1 break") break } // Fill in the data val byteBuffer = inputBuffers[inIndex] byteBuffer.clear() byteBuffer.put(bytes!!, startFrameIndex, nextFrameStartIndex - startFrameIndex) mediaCodec.queueInputBuffer(inIndex, 0, nextFrameStartIndex - startFrameIndex, 0, 0) startFrameIndex = nextFrameStartIndex } var outIndex = mediaCodec.dequeueOutputBuffer(info, 10_000) while (outIndex >= 0) { // Heres a simple temporal way to keep the video alive fps, Otherwise the video will play very fast // demo Of H264 File is 30fps try { sleep(33) } catch (e: InterruptedException) { e.printStackTrace() } // Parameters 2 Render to surface On ,surface Namely mediaCodec.configure Parameters of 2 mediaCodec.releaseOutputBuffer(outIndex, true) outIndex = mediaCodec.dequeueOutputBuffer(info, 0) } } }
NALU分割方法
private fun findNextFrame(bytes: ByteArray, startIndex: Int, totalSizeIndex: Int): Int { for (i in startIndex..totalSizeIndex) { // 00 00 00 01 H264 Start code for if (bytes[i].toInt() == 0x00 && bytes[i 1].toInt() == 0x00 && bytes[i 2].toInt() == 0x00 && bytes[i 3].toInt() == 0x01) {// Log.e(TAG, "bytes[i 4]=0X${Integer.toHexString(bytes[i 4].toInt())}")// Log.e(TAG, "bytes[i 4]=${(bytes[i 4].toInt().and(0X1F))}") return i // 00 00 01 H264 Start code for } else if (bytes[i].toInt() == 0x00 && bytes[i 1].toInt() == 0x00 && bytes[i 2].toInt() == 0x01) {// Log.e(TAG, "bytes[i 3]=0X${Integer.toHexString(bytes[i 3].toInt())}")// Log.e(TAG, "bytes[i 3]=${(bytes[i 3].toInt().and(0X1F))}") return i } } return -1 }
方式一:固定字节数据填充
private fun findNextFrameFix(bytes: ByteArray, startIndex: Int, totalSizeIndex: Int): Int { // Every time, its better to make the data bigger , Otherwise, its like a weak network , Slow data flow leads to video card val len = startIndex 40000 return if (len > totalSizeIndex) totalSizeIndex else len }
说明:在实际项目中,通常是web/数据流塞进去的方式,只是给大家demo演示MediaCodec解码h264文件播放。
保存 decode h264 视频 yuv 数据为图片我们保存在哪里呢,前面说了,解码后一定是h264保存,解码后的数据就是yuv数据。也就是说dequeueOutputBuffer然后取出解码后的数据,然后使用YuvImageClass compressToJpegSave as Jpeg Just图片。我们 3s 保持一个。本地代码:
// 3s Save a picture if (System.currentTimeMillis() - saveImage > 3000) { saveImage = System.currentTimeMillis() val byteBuffer: ByteBuffer = mediaCodec.outputBuffers[outIndex] byteBuffer.position(info.offset) byteBuffer.limit(info.offset info.size) val ba = ByteArray(byteBuffer.remaining()) byteBuffer.get(ba) try { val parent = File(Environment.getExternalStorageDirectory().absolutePath "/h264pic/") if (!parent.exists()) { parent.mkdirs() Log.d(TAG, "parent=${parent.absolutePath}") } // take NV21 Format picture , With quality 70 Compressed into Jpeg val path = "${parent.absolutePath}/${System.currentTimeMillis()}-frame.jpg" Log.e(TAG, "path:$path") val fos = FileOutputStream(File(path)) val yuvImage = YuvImage(ba, ImageFormat.NV21, width, height, null) yuvImage.compressToJpeg( Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 80, fos) fos.flush() fos.close() } catch (e: IOException) { e.printStackTrace() } }
最后,硬解码速度很快,效率很高,播放视频需要PTS时间戳处理.demo最好的办法就是让它渲染慢一点(demo视频文件是30fps,也就是说1000ms/30= 33ms一帧yuv数据),所以在mediaCodec.releaseOutputBuffer(outIndex, true)休眠前(33ms)达到正常播放速度。
Copyright © 2024 有趣生活 All Rights Reserve吉ICP备19000289号-5 TXT地图HTML地图XML地图