注意点:

  1. MediaCodec 解码后的原始数据,格式为yuv,而OpenGL所能渲染的格式为rgb,因此我们需要使用扩展库中的扩展纹理

    GLES11Ext.GL_TEXTURE_EXTERNAL_OES

而它的作用就是实现YUV格式到RGB的自动转化。

片段着色器中需使用扩展采样器:

uniform samplerExternalOES sTexture
  1. COLOR_FormatYUV420Flexible

YUV420Flexible并不是一种确定的YUV420格式,而是包含COLOR_FormatYUV411Planar, COLOR_FormatYUV411PackedPlanar, COLOR_FormatYUV420Planar, COLOR_FormatYUV420PackedPlanar, COLOR_FormatYUV420SemiPlanar和COLOR_FormatYUV420PackedSemiPlanar。在API 21引入YUV420Flexible的同时,它所包含的这些格式都deprecated掉了。

那么为什么所有的解码器都支持YUV420Flexible呢?官方没有说明这点,但我猜测,只要解码器支持YUV420Flexible中的任意一种格式,就会被认为支持YUV420Flexible格式。也就是说,几乎所有的解码器都支持YUV420Flexible代表的格式中的一种或几种。

此处引用:
https://www.polarxiong.com/archives/Android-MediaCodec视频文件硬件解码-高效率得到YUV格式帧-快速保存JPEG图片-不使用OpenGL.html

原理步骤:

  1. 生成一个oes纹理,并且以此得到一个SurfaceTexture,并设置帧可用回调监听,可用时请求渲染,再得到一个 Surface,并将它回调给外部MediaCodec 配置并启动解码器。(在ESL上下文环境中生成,因此需要通过回调给外部使用)
  2. 渲染完一帧后,需调用surfaceTexture.updateTexImage() //更新纹理数据
  3. onFrameAvailable可用时,请求GLSurfaceView渲染重绘即可

解码器

MediaCodec的准备:

  1. 使用MediaExtract选择相应的视频/音频轨道,获取相应的格式信息(MediaFormat)。
  2. 使用MdiaCodec的类接口创建codec对象,配置音视频格式以及surface。( OpenGL回调出来的 Surface surface = new Surface(surfaceTexture);)
  3. 启动解码器。
  4. 启动一个线程,循环从MediaExtract读取媒体文件数据到MdiaCodec的输入缓冲区,直至文件尾。
  5. 从MdiaCodec中取出被成功解码的buffer的 index 和 buffer的信息。index无误则把此buffer渲染到surface上去。

MediaCodec代码如下:

package com.example.myapplication.ui.video;import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;import android.util.Log;
import android.view.Surface;import java.io.IOException;
import java.nio.ByteBuffer;public class VideoCodec {private static final long TIMEOUT_USEC = 10000;private String TAG = "VideoCodec";private MediaCodec videoDecoder;private MediaExtractor mediaExtractor;private boolean isAvailable = false;//视频路径private String path;//OpenGL回调出来的 Surface Surface surface = new Surface(surfaceTexture);//surfaceTexture设置帧可用监听//用于解码后数据的输出,交给OpenGL渲染private Surface surface;public VideoCodec(String path,Surface surface) {mediaExtractor = new MediaExtractor();this.path = path;this.surface = surface;}//传入指定的视频或音频格式,例如:video/、audio///返回当前格式轨道的index public int getTrackIndex(String mime) {int trackCount = mediaExtractor.getTrackCount();int videoIndex=-1;for (int i = 0; i < trackCount; i++) {MediaFormat format = mediaExtractor.getTrackFormat(i);if (format.getString(MediaFormat.KEY_MIME).startsWith(mime)) {videoIndex = i;break;}}return videoIndex;}private boolean isPlaying = false;public void start(){isPlaying = true;DecoderMP4Thread thread = new DecoderMP4Thread();thread.start();}//视频的解码放到子线程中进行,防止阻塞主线程private class DecoderMP4Thread extends Thread {@Overridepublic void run() {try {//首先设置待解码文件的路径mediaExtractor.setDataSource(path);int videoIndex = getTrackIndex("video/");//当前index无效则直接退出if (videoIndex < 0) {return;}//选取当前轨道mediaExtractor.selectTrack(videoIndex);//获取视频的格式信息MediaFormat mediaFormat = mediaExtractor.getTrackFormat(videoIndex);//设置解码支持的格式mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatRGBFlexible);String mime = mediaFormat.getString(MediaFormat.KEY_MIME);//通过MIME创建解码器videoDecoder = MediaCodec.createDecoderByType(mime);//配置videoDecoder.configure(mediaFormat, surface, null, 0);} catch (IOException e) {e.printStackTrace();}//启动解码器videoDecoder.start();int frameIndex = 0;boolean isVideoOver = false;// 开始循环,一直到视频资源结束MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();// 开始的时间long startMs = System.currentTimeMillis();  // 当前Thread 没有被中断while (!Thread.interrupted()) {if (!isPlaying) {continue;}if (!isVideoOver) {// 视频没有结束  提取一个单位的视频资源放到 解码器(mediaCodec) 缓冲区中isVideoOver = putBufferToMediaCodec(mediaExtractor, videoDecoder);}// 返回一个被成功解码的buffer的 index 或者是一个信息  同时更新 videoBufferInfo 的数据int outputBufferIndex = videoDecoder.dequeueOutputBuffer(videoBufferInfo, TIMEOUT_USEC);switch (outputBufferIndex) {case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:Log.v(TAG, "format changed");break;case MediaCodec.INFO_TRY_AGAIN_LATER:Log.v(TAG, "超时");break;case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED://outputBuffers = videoCodec.getOutputBuffers();Log.v(TAG, "output buffers changed");break;default://延时操作//如果缓冲区里的可展示时间>当前视频播放的总时间,就休眠一下 展示当前的帧,sleepRender(videoBufferInfo, startMs);//渲染为true就会渲染到surface   configure() 设置的surfacevideoDecoder.releaseOutputBuffer(outputBufferIndex, true);frameIndex ++;Log.v(TAG, "frameIndex   " + frameIndex);break;}if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {Log.v(TAG, "buffer stream end");break;}}//释放资源videoDecoder.stop();videoDecoder.release();mediaExtractor.release();}}/*** 将缓冲区传递至解码器* 如果到了文件末尾,返回true;否则返回false*/private boolean putBufferToMediaCodec(MediaExtractor extractor, MediaCodec decoder) {boolean isMediaEOS = false;// 解码器  要填充有效数据的输入缓冲区的索引 —————— 此id的缓冲区可以被使用int inputBufferIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);if (inputBufferIndex >= 0) {ByteBuffer inputBuffer = decoder.getInputBuffer(inputBufferIndex);// MediaExtractor读取媒体文件的数据,存储到缓冲区中。并返回大小。结束为-1int sampleSize = extractor.readSampleData(inputBuffer, 0);if (sampleSize < 0) {decoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);isMediaEOS = true;Log.v(TAG, "media eos");} else {// 在输入缓冲区添加数据之后,把它告诉 MediaCodec (解码)decoder.queueInputBuffer(inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0);// MediaExtractor 准备下一个 单位的数据extractor.advance();}}return isMediaEOS;}private void sleepRender(MediaCodec.BufferInfo audioBufferInfo, long startMs) {// 这里的时间是 毫秒  presentationTimeUs 的时间是累加的 以微秒进行一帧一帧的累加// audioBufferInfo 是改变的while (audioBufferInfo.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {try {// 10 毫秒Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();break;}}}
}

滤镜类(BaseFilter)

OpenGL ES:配合MediaCodec硬解码渲染(视频加滤镜播放)相关推荐

  1. Android MediaCodec硬解码AAC音频文件并播放

    在这里简单介绍一下,如何利用Android MediaCodec解码AAC音频文件或者实时AAC音频帧并通过AudioTrack来播放.主要的思路就是从文件或者网络获取一帧帧的AAC的数据,送入解码器 ...

  2. OpenGL ES:视频加滤镜后导出

    视频加滤镜播放: MediaCodec解码-->OpenGL es--> GLSurfaceView 视频滤镜合成导出: MediaCodec解码-->OpenGL es--> ...

  3. Android使用MediaCodec硬解码播放H264格式视频文件

    前些时间,通过各种搜索加请教了好几个同行的朋友,在他们的指点下实现: RTSP+H264实时视频播放播放及把实时视频流保存到手机SD卡中,再对保存的H264格式文件进行播放等基本功能.非常感谢这些朋友 ...

  4. SDL2源码分析之OpenGL ES在windows上的渲染过程

    SDL2源码分析之OpenGL ES在windows上的渲染过程 更新于2018年11月4日. 更新于2018年11月21日. ffmpeg + SDL2实现的简易播放器 ffmpeg和SDL非常强大 ...

  5. iOS上用GPUImage给视频加滤镜

    最近在做一个需要给已有视频加滤镜的app,不是实时滤镜,而是给已经存在的视频加滤镜. 虽然网上有很多关于GPUImage的博客,但大多都是给图片加上滤镜或者是直接在摄像头上加上的实时滤镜,找了很久之后 ...

  6. 两分钟让你知道哪些可以给视频加滤镜的软件

    视频与图片一样,前期拍摄的时候我们会进行画面的构造,并且还会寻找合适的拍摄角度,后期还要对视频进行剪辑,让它的画面更加有质感,当然这其中就少不了给视频加上滤镜了,如今市面上的视频加滤镜软件也是越来越多 ...

  7. html5 手机拍视频滤镜,如何给手机里拍摄的视频添加滤镜效果?在手机上给视频加滤镜|手机视频编辑器...

    狸窝是帮助用户解决问题 提供教程解决方案 在这个过程中有使用我们自己开发的软件 也有网上找的工具 只要帮助用户解决问题就好!同意即往下继续了解下载 ... 眼看着2018快要结束啦,今日气温骤降,小编 ...

  8. 使用MediaCodec硬解码h.265视频及音频进行播放

    h.265这个视频是很多播放器不支持的,就算是bilibili开源的ijkplayer也不能直接播放,需要自己去重新编译 才可以支持. 这里通过这个demo来演示一下如何硬解码视频,播放h.265视频 ...

  9. OpenGL ES之基本简介和渲染流程

    简介 OpenGL ES (OpenGL for Embedded Systems) 是以⼿持和嵌入式为目标的高级3D图形应用程序编程接口(API). OpenGL ES是目前智能手机中占据统治地位的 ...

最新文章

  1. 【转】C++ vector的reserve和resize详解
  2. 编译maven_头条一面竟然问我Maven?
  3. 编译32位_玩转Android10源码开发定制(11)内核篇之安卓内核模块开发编译
  4. 为什么不可以使用哈曼顿距离_请对比下欧式距离和曼哈顿距离的差别
  5. 解决交通拥堵、监测核辐射、野外搜救…无人机将有哪些神应用?
  6. 乒乓球(洛谷-P1042)
  7. LCFinder 0.3.0 Beta 发布,图像标注与目标检测工具
  8. java eden区_(转)可能是把Java内存区域讲的最清楚的一篇文章
  9. WDLINUX (Centos5.8) 安装 bcmath
  10. 深度解读SSH免密登录
  11. 突击计划——给定三角形边长,求面积
  12. C++ 资源大全整理
  13. 软件测试管理要素分析
  14. iOS自定义下拉列表
  15. python实现局域网内传输文件
  16. c++的极乐净土的实现
  17. 新型城镇化3.0时代 数据交换是“智慧城市”的核心
  18. 微信红包服务器卡死,东大跨年红包记--并发案例分析
  19. 在蹉跎中一路前行---谈Microsoft .NET战略
  20. mysql中分层查询_在mysql中连接任意数量的字符串行(分层查询)

热门文章

  1. .net GridView中 A标签runat=server问题
  2. HQChart实战教程34-A股日K线数据对接-小程序版本
  3. 一文了解什么是NFT
  4. (附源码)Springboot电子病历管理 毕业设计 010350
  5. 统计库存物料呆置时长的简单方法(SAP ABAP)
  6. echarts 实现环形进度图
  7. T1209 铺砖——递推
  8. busybox 安装mysql_busybox怎么安装
  9. 文字超出部分显示...
  10. java二叉树求权值_二叉树中的权值是什么?