YUV420

  • yuv420p:yv12(YYYYYYYY VV UU)、I420(YYYYYYYY UU VV)
  • yuv420sp:nv12(YYYYYYYY UV UV)、nv21(YYYYYYYY VU VU)

同样是三种方式

(1)基于缓存(ByteBuffer)的同步编码
(2)基于缓存(ByteBuffer)的异步编码
(3)基于缓存数组的同步编码(废弃,可能效率没前面两种高吧)

基于缓存(ByteBuffer)的同步解码

(1)解码出来的是COLOR_FormatYUV420Flexible,利用Image的Plane,[0]是步长为1的Y,[1]是步长为2的U,[2]是步长为2的V,再利用对应的数组操作既可以得到对应的YUV420数据。
(2)解码送进去的时候,需要使用MediaExtractor解析Mp4文件,判断类型(编码的时候,头信息、关键帧、P帧信息)

主要方法

设置参数

public void setDecoderParams(String yuvPath, String mp4Path, int fileType) throws IOException {if (fileType != FILE_TypeI420 && fileType != FILE_TypeNV21 && fileType != FILE_TypeJPEG) {throw new IllegalArgumentException("only support FILE_TypeI420 " + "and FILE_TypeNV21 " + "and FILE_TypeJPEG");}File mp4File = new File(mp4Path);File yuvFile = new File(yuvPath);if (!mp4File.exists()) {throw new RuntimeException("mp4 file do not exist");}if (mp4File.isDirectory()) {throw new IllegalArgumentException("mp4Path is not a mp4 file , it is a directory");}if (yuvFile.isDirectory()) {throw new IllegalArgumentException("yuvPath is not a yuv file , it is a directory");}INPUT_FILE_PATH = mp4Path;outputImageFileType = fileType;OUTPUT_FILE_PATH = yuvPath;if (!yuvFile.getParentFile().exists()) {yuvFile.getParentFile().mkdirs();}RandomAccessFile acf = new RandomAccessFile(yuvFile, "rw");fc_out = acf.getChannel();File videoFile = new File(INPUT_FILE_PATH);extractor = new MediaExtractor();extractor.setDataSource(videoFile.getPath());int trackIndex = selectTrack(extractor);if (trackIndex < 0) {throw new RuntimeException("No video track found in " + INPUT_FILE_PATH);}extractor.selectTrack(trackIndex);mediaFormat = extractor.getTrackFormat(trackIndex);String mime = mediaFormat.getString(MediaFormat.KEY_MIME);decoder = MediaCodec.createDecoderByType(mime);showSupportedColorFormat(decoder.getCodecInfo().getCapabilitiesForType(mime));if (isColorFormatSupported(decodeColorFormat, decoder.getCodecInfo().getCapabilitiesForType(mime))) {mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, decodeColorFormat);Log.i(TAG, "set decode color format to type " + decodeColorFormat);} else {Log.i(TAG, "unable to set decode color format, color format type " + decodeColorFormat + " not supported");}}

设置了输入输出路径,初始化MediaExtractor去解析Mp4,根据编码类型创建对应的MediaCodec解码器。

解码

public void videoDecode() throws IOException {try {decodeFramesToYUV(decoder, extractor, mediaFormat);decoder.stop();} finally {if (decoder != null) {decoder.stop();decoder.release();decoder = null;}if (extractor != null) {extractor.release();extractor = null;}}}private void decodeFramesToYUV(MediaCodec decoder, MediaExtractor extractor, MediaFormat mediaFormat) throws IOException {MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();boolean sawInputEOS = false;boolean sawOutputEOS = false;decoder.configure(mediaFormat, null, null, 0);decoder.start();final int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);final int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);int outputFrameCount = 0;while (!sawOutputEOS) {if (!sawInputEOS) {int inputBufferId = decoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);if (inputBufferId >= 0) {ByteBuffer inputBuffer = decoder.getInputBuffer(inputBufferId);int sampleSize = extractor.readSampleData(inputBuffer, 0);if (sampleSize < 0) {decoder.queueInputBuffer(inputBufferId, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);sawInputEOS = true;} else {long presentationTimeUs = extractor.getSampleTime();decoder.queueInputBuffer(inputBufferId, 0, sampleSize, presentationTimeUs, 0);extractor.advance();}}}int outputBufferId = decoder.dequeueOutputBuffer(info, DEFAULT_TIMEOUT_US);if (outputBufferId >= 0) {if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {sawOutputEOS = true;try {fc_out.close();} catch (IOException e) {e.printStackTrace();}}boolean doRender = (info.size != 0);if (doRender) {Image image = decoder.getOutputImage(outputBufferId);
//                    System.out.println("image format: " + image.getFormat());if (outputImageFileType != -1) {switch (outputImageFileType) {case FILE_TypeI420:byte[] data = getDataFromImage(image, COLOR_FormatI420);data = rotationYuvByOpenCV(data, width, height, 1);MappedByteBuffer outMappedBuffer = fc_out.map(FileChannel.MapMode.READ_WRITE, (long) outputFrameCount * data.length, (long) data.length);outMappedBuffer.put(data);final int finalOutputFrameCount = outputFrameCount;executorPools.submit(new Runnable() {@Overridepublic void run() {decodeProgressListener.publishProgress(finalOutputFrameCount);}});outputFrameCount++;
//                                dumpFile(OUTPUT_FILE_PATH, data);break;case FILE_TypeNV21:
//                                dumpFile(OUTPUT_FILE_PATH, getDataFromImage(image, COLOR_FormatNV21));break;case FILE_TypeJPEG:compressToJpeg(OUTPUT_FILE_PATH, image);break;}
//                        Log.d(TAG, "完成第" + outputFrameCount + "帧");}image.close();decoder.releaseOutputBuffer(outputBufferId, false);}}}}

没什么好说的,类似于编码,送进入,读出来。

从Image读取YUV数据

    private byte[] getDataFromImage(Image image, int colorFormat) {if (colorFormat != COLOR_FormatI420 && colorFormat != COLOR_FormatNV21) {throw new IllegalArgumentException("only support COLOR_FormatI420 " + "and COLOR_FormatNV21");}if (!isImageFormatSupported(image)) {throw new RuntimeException("can't convert Image to byte array, format " + image.getFormat());}Rect crop = image.getCropRect();int format = image.getFormat();int width = crop.width();int height = crop.height();Image.Plane[] planes = image.getPlanes();byte[] data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];byte[] rowData = new byte[planes[0].getRowStride()];if (VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes");int channelOffset = 0;int outputStride = 1;for (int i = 0; i < planes.length; i++) {switch (i) {case 0:channelOffset = 0;outputStride = 1;break;case 1:if (colorFormat == COLOR_FormatI420) {channelOffset = width * height;outputStride = 1;} else if (colorFormat == COLOR_FormatNV21) {channelOffset = width * height + 1;outputStride = 2;}break;case 2:if (colorFormat == COLOR_FormatI420) {channelOffset = (int) (width * height * 1.25);outputStride = 1;} else if (colorFormat == COLOR_FormatNV21) {channelOffset = width * height;outputStride = 2;}break;}ByteBuffer buffer = planes[i].getBuffer();int rowStride = planes[i].getRowStride();int pixelStride = planes[i].getPixelStride();if (VERBOSE) {Log.v(TAG, "pixelStride " + pixelStride);Log.v(TAG, "rowStride " + rowStride);Log.v(TAG, "width " + width);Log.v(TAG, "height " + height);Log.v(TAG, "buffer size " + buffer.remaining());}int shift = (i == 0) ? 0 : 1;int w = width >> shift;int h = height >> shift;buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));for (int row = 0; row < h; row++) {int length;if (pixelStride == 1 && outputStride == 1) {length = w;buffer.get(data, channelOffset, length);channelOffset += length;} else {length = (w - 1) * pixelStride + 1;buffer.get(rowData, 0, length);for (int col = 0; col < w; col++) {data[channelOffset] = rowData[col * pixelStride];channelOffset += outputStride;}}if (row < h - 1) {buffer.position(buffer.position() + rowStride - length);}}if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);}return data;}

得益于Image的实现:
plane[0]就是Y数据,步长为1;
plane[1]就是U数据,步长为2;
plane[2]就是V数据,步长为2;
具体的可以根据plane的内置属性进行判断。

YUV(I420)数据旋转

由于Mp4带有rotation属性,解码出来的数据是顺时针旋转90读的。利用Opencv的API,先转置再镜像即可完成旋转。

    private byte[] rotationYuvByOpenCV(byte[] oldData, int width, int height, int rotation) {byte[] newData = new byte[(int) (width * height * 1.5)];Mat mat = new Mat((int) (height * 1.5), width, CvType.CV_8UC1);mat.put(0, 0, oldData);Imgproc.cvtColor(mat, mat, Imgproc.COLOR_YUV2BGR_I420);Core.transpose(mat, mat);// 翻转模式,flipCode == 0垂直翻转(沿X轴翻转),flipCode>0水平翻转(沿Y轴翻转),flipCode<0水平垂直翻转(先沿X轴翻转,再沿Y轴翻转,等价于旋转180°)Core.flip(mat, mat, 1);if (isCoverFace) {Mat black = Mat.zeros(coverFaceHeight, mat.cols(), mat.type());Mat roi = new Mat(mat, new org.opencv.core.Rect(0, coverFaceY, black.width(), coverFaceHeight));black.copyTo(roi);}Imgproc.cvtColor(mat, mat, Imgproc.COLOR_BGR2YUV_I420);mat.get(0, 0, newData);return newData;}

逆时针旋转90度,其他的度数大同小异。

小结

之前苦于不知道怎么去判定MP4的帧,怎么送进去。查了一些Mp4的封装方式,参考了别人的blog,发现有MediaExtractor这个专用的解析器,可以按轨道按Sample去读取数据,将数据送进解析器即可。
上面是同步式的解码,异步式的只需要改动decodeFramesToYUV方法,将下面的同步方法增加到callback中即可。

源码地址:https://github.com/shen511460468/MediaCodecDemo

Android项目小结——硬解码(MediaCodec实现[MP4]转YUV420各种格式)相关推荐

  1. Android 音视频编解码 MediaCodec

    MediaCodec 简介 Android中的MediaCodec是一个用于音视频编解码功能的API,使用它可以实现对音视频数据进行压缩.解压缩.编辑和转换.以下是MediaCodec的主要功能: 支 ...

  2. EasyPlayerPro:安卓视频播放器Android H.265硬解码方案(内含代码)

    背景介绍 H.265是ITU-TVCEG继H.264之后所制定的新的视频编码标准.H.265标准围绕着现有的视频编码标准H.264,保留原来的某些技术,同时对一些相关的技术加以改进.H.265使用先进 ...

  3. Android H.265硬解码EasyPlayerPro

    H.265编码算法作为新一代视频编码标准,在编码效果上有了很大的进步,同样清晰度的视频,265要比264有着更低的码率.关于265对比264的优越性,网上有更专业的文章来作分析,我也仅对这两种算法略知 ...

  4. EasyPlayerPro安卓流媒体播放器实现Android H.265硬解码流程

    本文转自EasyDarwin团队成员John的博客:http://blog.csdn.net/jyt0551/article/details/74502627 H.265编码算法作为新一代视频编码标准 ...

  5. 判断android图片是否硬解码(方法)

    2019独角兽企业重金招聘Python工程师标准>>> 在oncreate方面的setContentView(R.layout.main);     前面,添加如下代码: getWi ...

  6. android jni 硬解码,Android 硬解码 MediaCodec 遇到的(部分手机绿屏)API21

    MediaCodec 是android用来做音视频编解码 下面是遇到其它的问题(解码几分钟后一直dequeueInputBuffer-1) http://blog.csdn.net/qq3773663 ...

  7. Android自带硬解码解码类型说明MediaCodec使用必看

    一.背景 随着Android系统手机性能的不断提升,现阶段大部分手机都自带GPU(承担图形显示的专门硬件),大幅度提高手机显示性能,在视频显示.游戏画面刷新,和高分辨图像显示方面必须使用GPU.GOO ...

  8. Android项目小结——可对焦的视频录制(MediaRecorder与TextureView实现)

    一直在做安卓的项目,想着找个时间总结一下,可能太懒了,一直没总结. 代码(尤其是对焦框显示)参考了许多Blog和Github,修修补补改改挺多地方,记录一下,侵删私信或注明出处. 录制 主要的类 Ca ...

  9. Android项目小结---闹铃备忘录小开发知识点(附有:源码下载)

    一.闹铃功能介绍: 1.为每条备忘添加闹铃 2.备忘内容和闹铃信息存在SQL中 3.可删除每天记录和闹铃或者删除所有 4.到点闹铃启动,包括锁屏时和重开机 5.在桌面的创建一个widget类似便签那样 ...

最新文章

  1. 太难了…期待一切都能赶快好起来吧
  2. python埋点自动化_iOS自动化埋点的实现
  3. HDU 3397 Sequence operation
  4. 【Qt】Qt中QJsonObject类
  5. Cookie中不能有空格_PHP 使用cookie
  6. 计算机如何用vb文本加密,信息加密与隐藏工具的设计与实现VB231
  7. 根据某个字段判断是否添加条件
  8. 系列学习 Gateway 之第 1 篇 —— SpringCloud Gateway 简介,Gateway 入门实例
  9. 基于python mediapipe的视频或者图片更换背景
  10. 比尔盖茨的十条“金玉良言”
  11. 最大连续子数组和python_连续子数组的最大和(python)
  12. DNS 隧道通信特征与检测
  13. 第7章 面向对象技术
  14. 筛选后系列填充_Excel2013里筛选后复制粘贴制作成绩表方法大剖析,3分钟搞定...
  15. matlab ax=b x=,matlab 求解 Ax=B 时所用算法
  16. nginx光速入门到进阶
  17. 视频监控RTSP 客户端
  18. 2021年技术自媒体经验分享 —— 开始尝试认真做 CSDN 的一年后的复盘
  19. OA系统实现(请假审批,mybatis)-2
  20. 老板:你们和外包有什么区别?

热门文章

  1. ueditor(vue-ueditor-wrap)集成秀米全过程以及遇到的问题
  2. c语言比matlab慢很多,为什么我的Python脚本与Matlab相比速度太慢?
  3. 【设计模式】结构型模式之代理模式
  4. 2022年车险攻略:1分钟告诉你什么是车险怎么买?
  5. 自考知识电脑图计算机组成原理,自考“计算机组成原理”考试大纲
  6. 流体机械原理及设计07
  7. excel 错位插入_EXCEL单元格错位如何对齐:excel表格数据错位
  8. 2.牛批了 Android 2021中高级面试题 一线大厂和二线大厂面试真题精选 (京东 附答案)第二套 22k+
  9. 微信小程序 第三方平台授权小程序业务
  10. 【疲劳检测】基于形态学实现疲劳检测附matlab代码