Android使用OpenGL和MediaCodec渲染视频

目录

一,借助MediaCodec封装解码工具类VideoCodec

二,使用OpenGl绘制视频封装SoulFilter


一,借助MediaCodec封装解码工具类VideoCodec

/*** 解码工具类* 解码完成后的数据 通过 ISurface 回调出去*/
public class VideoCodec {private ISurface mISurface;private String mPath;private MediaExtractor mMediaExtractor;private int mWidth;private int mHeight;private int mFps;private MediaCodec mMediaCodec;private boolean isCodeing;private byte[] outData;private CodecTask mCodecTask;/*** 要在prepare之前调用* @param surface*/public void setDisplay(ISurface surface){mISurface = surface;}/*** 设置要解码的视频地址* @param path*/public void setDataSource(String path){mPath = path;}/**** 准备方法*/public void prepare(){//MediaMuxer:复用器 封装器//解复用(解封装)mMediaExtractor = new MediaExtractor();try {//把视频给到 解复用器mMediaExtractor.setDataSource(mPath);} catch (IOException e) {e.printStackTrace();}int videoIndex = -1;MediaFormat videoMediaFormat = null;// mp4 1路音频 1路视频int trackCount = mMediaExtractor.getTrackCount();for (int i = 0; i < trackCount; i++) {//获得这路流的格式MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(i);//选择视频 获得格式// video/  audio/String mime = mediaFormat.getString(MediaFormat.KEY_MIME);if(mime.startsWith("video/")){videoIndex = i;videoMediaFormat = mediaFormat;break;}}//默认是-1if (null != videoMediaFormat){//解码 videoIndex 这一路流mWidth = videoMediaFormat.getInteger(MediaFormat.KEY_WIDTH);mHeight = videoMediaFormat.getInteger(MediaFormat.KEY_HEIGHT);mFps = 20;if (videoMediaFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {mFps = videoMediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);}// 个别手机  小米(x型号) 解码出来不是yuv420p//所以设置 解码数据格式 指定为yuv420videoMediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);try {//创建一个解码器mMediaCodec = MediaCodec.createDecoderByType(videoMediaFormat.getString(MediaFormat.KEY_MIME));mMediaCodec.configure(videoMediaFormat,null,null,0);} catch (IOException e) {e.printStackTrace();}//选择流 后续读取这个流mMediaExtractor.selectTrack(videoIndex);}if (null != mISurface){mISurface.setVideoParamerters(mWidth,mHeight,mFps);}}/*** 开始解码*/public void start(){isCodeing = true;//接收 解码后的数据 yuv数据大小是 w*h*3/2outData = new byte[mWidth * mHeight * 3 / 2];mCodecTask = new CodecTask();mCodecTask.start();}/*** 停止*/public void stop(){isCodeing = false;if (null != mCodecTask && mCodecTask.isAlive()){try {mCodecTask.join(3_000);} catch (InterruptedException e) {e.printStackTrace();}//3s后线程还没结束if (mCodecTask.isAlive()){//中断掉mCodecTask.interrupt();}mCodecTask = null;}}/*** 解码线程*/private class CodecTask extends Thread{@Overridepublic void run() {if (null == mMediaCodec) {return;}// 开启mMediaCodec.start();boolean isEOF = false;MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();//是否中断线程while(!isInterrupted()){if (!isCodeing){break;}// 如果 eof是true 就表示读完了,就不执行putBuffer2Codec方法了//并不代表解码完了if (!isEOF) {isEOF = putBuffer2Codec();}//...//从输出缓冲区获取数据  解码之后的数据int status = mMediaCodec.dequeueOutputBuffer(bufferInfo, 100);//获取到有效的输出缓冲区 意味着能够获取到解码后的数据了if (status >= 0){ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(status);if (bufferInfo.size == outData.length){//取出数据 存到outData yuv420outputBuffer.get(outData);if (null != mISurface){mISurface.offer(outData);}}//交付掉这个输出缓冲区 释放mMediaCodec.releaseOutputBuffer(status,false);}//干完活了 ,全部解码完成了if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){//解码完了break;}}mMediaCodec.stop();mMediaCodec.release();mMediaCodec = null;mMediaExtractor.release();mMediaExtractor = null;}/**** @return true:没有更多数据了*           false:还有*/private boolean putBuffer2Codec(){// -1 就一直等待int status = mMediaCodec.dequeueInputBuffer(100);//有效的输入缓冲区 indexif (status >=0 ){//把待解码数据加入MediaCodecByteBuffer inputBuffer = mMediaCodec.getInputBuffer(status);//清理脏数据inputBuffer.clear();// ByteBuffer当成byte数组 ,读数据存入 ByteBuffer 存到byte数组的第0个开始存int size = mMediaExtractor.readSampleData(inputBuffer, 0);//没读到数据 已经没有数据可读了if (size < 0){//给个标记 表示没有更多数据可以从输出缓冲区获取了mMediaCodec.queueInputBuffer(status,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);return true;}else{//把噻了数据的输入缓冲区噻回去mMediaCodec.queueInputBuffer(status,0,size,mMediaExtractor.getSampleTime(), 0);//丢掉已经加入解码的数据 (不丢就会读重复的数据)mMediaExtractor.advance();}}return false;}}}

二,使用OpenGl绘制视频封装SoulFilter

/*** 灵魂出窍滤镜*/
public class SoulFilter extends AbstractFilter {private  int[] mTextures;//肉体private  GLImage bodyImage;//灵魂private  GLImage soulImage;private  int mAlpha;private  int mSamplerV;private  int mSamplerU;private  int mSamplerY;private int mFps;private float[] matrix = new float[16];private int interval;public SoulFilter(Context context) {super(context, R.raw.soul_vertex, R.raw.soul_frag);bodyImage = new GLImage();soulImage = new GLImage();mSamplerY = GLES20.glGetUniformLocation(mGLProgramId, "sampler_y");mSamplerU = GLES20.glGetUniformLocation(mGLProgramId,"sampler_u");mSamplerV = GLES20.glGetUniformLocation(mGLProgramId,"sampler_v");mAlpha = GLES20.glGetUniformLocation(mGLProgramId, "alpha");//3个纹理 yuvmTextures = new int[3];OpenGLUtils.glGenTextures(mTextures);}public void onReady2(int width,int height,int fps){super.onReady(width,height);mFps = fps;bodyImage.initSize(width,height);soulImage.initSize(width,height);}public void onDrawFrame(byte[] yuv) {//把yuv分离出来 保存在 image中的 y、u、v三个变量中bodyImage.initData(yuv);//分离出的数据有效if (!bodyImage.hasImage()){return;}//启用着色器程序GLES20.glUseProgram(mGLProgramId);//初始化矩阵 不进行任何缩放平移Matrix.setIdentityM(matrix,0);//给肉体的 无变化矩阵GLES20.glUniformMatrix4fv(vMatrix,1,false,matrix,0);//透明度 肉体不透明GLES20.glUniform1f(mAlpha,1);//传值//画画onDrawBody(bodyImage);//混合灵魂onDrawSoul(yuv);}private void onDrawBody(GLImage image){//传递坐标mGLVertexBuffer.position(0);GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, mGLVertexBuffer);GLES20.glEnableVertexAttribArray(vPosition);mGLTextureBuffer.position(0);GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, mGLTextureBuffer);GLES20.glEnableVertexAttribArray(vCoord);//传递yuv数据GLES20.glActiveTexture(GLES20.GL_TEXTURE0);GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);//把y数据与 0纹理绑定//  GL_LUMINANCE: yuv 给这个GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D,0,GLES20.GL_LUMINANCE,mOutputWidth,mOutputHeight,0,GLES20.GL_LUMINANCE,GLES20.GL_UNSIGNED_BYTE,image.getY());GLES20.glUniform1i(mSamplerY, 0);//u数据GLES20.glActiveTexture(GLES20.GL_TEXTURE1);GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[1]);//把y数据与 0纹理绑定//  GL_LUMINANCE: yuv 给这个GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D,0,GLES20.GL_LUMINANCE,mOutputWidth/2,mOutputHeight/2,0,GLES20.GL_LUMINANCE,GLES20.GL_UNSIGNED_BYTE,image.getU());GLES20.glUniform1i(mSamplerU, 1);GLES20.glActiveTexture(GLES20.GL_TEXTURE2);GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[2]);//把y数据与 0纹理绑定//  GL_LUMINANCE: yuv 给这个GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D,0,GLES20.GL_LUMINANCE,mOutputWidth/2,mOutputHeight/2,0,GLES20.GL_LUMINANCE,GLES20.GL_UNSIGNED_BYTE,image.getV());GLES20.glUniform1i(mSamplerV, 2);GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);}private void onDrawSoul(byte[] yuv){interval++;//没保存一个灵魂 或者使用次数已经达到上限了// 灵魂只能使用x次 使用完了之后就要更新灵魂if (!soulImage.hasImage() || interval > mFps){//次数重置为1interval = 1;//记录新灵魂soulImage.initData(yuv);}if (!soulImage.hasImage()){return;}//画灵魂GLES20.glEnable(GLES20.GL_BLEND);//1:源 灵魂  GL_ONE:画灵魂自己//2: 肉体  也是肉体自己//两个都是用自己原本的颜色去混合
//        GLES20.glBlendFunc(GLES20.GL_ONE,GLES20.GL_ONE);//让灵魂整体颜色变淡// GL_SRC_ALPHA: 取源(灵魂)的alpha 作为因子// 假设alpha是0.2 rgb都是1 -> 混合就是用 rgb都是 0.2*1 整体变淡GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA,GLES20.GL_ONE);//初始化矩阵 不进行任何缩放平移Matrix.setIdentityM(matrix,0);//设置缩放大小 本次放大为 1+当前灵魂次数占总次数*2的比例//不一次放太大 为了达到较好的表现效果 fps*2//所以这里值为 1+1/60 ---> 1+20/40 1.025... ---> 1.5float scale = 1.0f + interval / (mFps * 2.f);Matrix.scaleM(matrix,0,scale,scale,0);//给肉体的 无变化矩阵GLES20.glUniformMatrix4fv(vMatrix,1,false,matrix,0);//传递透明度 透明度值为0-1 渐渐降低 0.1+x/100 x为fps-[0~fps]//这里值为0.29 ---> 0.1GLES20.glUniform1f(mAlpha, 0.1f + (mFps - interval) / 100.f);//画灵魂onDrawBody(soulImage);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/75787.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day39——输入操作:多值输入

数组输入&#xff1a; int main() {//***** 1、多值输入&#xff08;C&#xff09;/*输入&#xff1a;3 --> 3个值5 4 9*/int n;cin >> n; //输入个数const int MAX_SIZE 0xFFFF;//限定最大个数int a[MAX_SIZE];for (int i 0; i < n; i) {//用 n 作控制输入…

第九课:LoRA模型的原理及应用

文章目录 Part.01 3种LoRA的使用方式Part.02 5种LoRA的应用方向Part.01 3种LoRA的使用方式 LoRA能够在家用级设备上训练,实现对Checkpoint在某些方面的微调使用Lora的三种方式:放置Lora模型到目录中,然后作为提示词的一部分输入。点击生成按钮下面的“画”,然后打开Additio…

Cortex-M3 NVIC可以控制异常向量表的哪些部分

Cortex-M3 的 NVIC(嵌套向量中断控制器)不直接控制整个异常向量表,但可以管理向量表中与中断相关的部分行为。以下是 NVIC 对异常向量表的具体控制范围和相关机制: 1. NVIC 直接控制的部分 NVIC 主要管理 外部中断(IRQ) 和部分 系统异常 的行为,但对向量表本身的存储位…

双向链表增删改查的模拟实现

本章目标 0.双向链表的基本结构 1.双向链表的初始化 2.头插尾插 3.头删尾删 4.查找与打印 5.在指定位置之前插入数据/在指定位置之后插入数据 6.在指定位置之前删除数据/在指定位置之后删除数据 7.销毁链表 0.双向链表的基本结构 本章所实现的双向链表是双向循环带头链表,是…

实战交易策略 篇十四:江南神鹰捕捉热点和熊市生存交易策略

文章目录 系列文章捕捉热点是股市最大的掘金术市场温度不低于50是热点产生的必要条件题材的大小和新颖程度决定热点的持续时间和涨幅炒作热点的3个阶段捕捉热点的方法与步骤操作实战案例熊市生存术“熊市最好的做法是离开股市”的说法是一句空话熊市盈利模式:不轻言底部,超跌…

Linux错误(6)X64向量指令访问地址未对齐引起SIGSEGV

Linux错误(6)X64向量指令访问地址未对齐引起SIGSEGV Author: Once Day Date: 2025年4月4日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: Linux实…

解码 __iter__ 和 itertools.islice - 迭代的艺术

文章目录 前言一、`_iter__`:自定义迭代的钥匙1.1 什么是 __iter__?1.2 基本用法1.3 高级用法:独立迭代器二、itertools.islice:迭代切片的利器2.1 什么是 itertools.islice?2.2 基本用法2.3 处理无限序列2.4 实际应用三、`__iter__` 与 `islice` 的结合六、为什么需要它们…

使用VSCode编写C#程序

目录 一、环境搭建&#xff1a;构建高效开发基础1. 安装VSCode2. 配置.NET SDK3. 安装核心扩展 二、项目开发全流程1. 创建项目2. 代码编辑技巧3. 调试配置4. 高级调试技巧5. 编译与运行 三、常见问题解决指南1. 项目加载失败2. IntelliSense失效3. 代码格式化4. 典型编译错误&…

日本汽车规模性经济计划失败,日产三大品牌的合并合作共赢,还是绝地求生?本田与日产合并确认失败,将成为世界第三大汽车集团愿景失败

本田与日产(含三菱汽车)的合并计划最终因核心矛盾无法调和而宣告失败,这一事件揭示了传统车企在行业变革期的深层困境。以下从合并动机、失败原因、本质判断及未来影响等方面综合分析: 一、合并的初衷:生存压力主导的被动策略 市场危机与财务困境 中国市场溃败:日系品牌在…

AutoCAD2026中文版下载安装教程

AutoCAD是一款由Autodesk公司开发的计算机辅助设计软件&#xff0c;被广泛应用于建筑设计、机械设计、电气设计、土木工程、装饰装潢等多个领域。AutoCAD2026中文版在原有的基础上进行了多项改进和优化&#xff0c;为用户提供了更为高效、便捷的绘图和设计体验。这里我给大家分…

Latex语法入门之数学公式

Latex是一种高质量的排版系统&#xff0c;尤其擅长于数学公式的排版。本文我将带大家深入了解Latex在数学公式排版中的应用。从基础的数学符号到复杂的公式布局&#xff0c;我们都会一一讲解&#xff0c;通过本文的学习&#xff0c;你将能够轻松编写出清晰、美观的数学公式&…

洛谷 P3214 [HNOI2011] 卡农

题目传送门 前言 再次败在 d p dp dp 手下&#xff0c;但是数据范围这么小应该是可以看出是 d p dp dp 的&#xff08;毕竟对于其他组合数的问题数据范围都是 1 0 9 10^9 109 起步&#xff09;。 思路 题意简化 现有 1 , 2 , 3 , . . . , n − 1 , n 1, 2, 3, ... , n -…

【年份数据类型及使用】

在数据分析中,年份的处理需要根据具体场景选择合适的数据类型,以确保后续分析的准确性和效率。以下是常见的年份数据类型及使用场景: 1. 数值类型(整数或浮点数) 适用场景: 仅需存储年份数值(如 2020, 2023),无需进行日期计算。需要将年份作为连续变量参与数学运算(如…

详解七大排序

目录 一.直接插入排序 &#xff08;1&#xff09;基本思想 &#xff08;2&#xff09;算法步骤 &#xff08;3&#xff09;代码实现 &#xff08;4&#xff09;算法特性 &#xff08;5&#xff09;算法优化 &#xff08;6&#xff09;示例演示 二.希尔排序 &#xff08…

YOLOv12 训练从这里开始:LabelImg 标注数据集

视频讲解&#xff1a; YOLOv12 训练从这里开始&#xff1a;LabelImg 标注数据集 labelimg https://github.com/tzutalin/labelImg sudo apt-get install pyqt5-dev-tools pip3 install lxml git clone https://github.com/tzutalin/labelImg.git cd labelImg 开始编译 make…

Day2:前端项目uniapp壁纸实战

先来做一个轮番图。 效果如下&#xff1a; common-style.css view,swiper,swiper-item{box-sizing: border-box; } index.vue <template><view class"homeLayout"><view class"banner"><swiper circular indicator-dots autoplay…

SAP-ABAP:ABAP `LEAVE LIST-PROCESSING` 深度解析

ABAP LEAVE LIST-PROCESSING 深度解析 核心机制 模式切换(Dialog → List) 中断屏幕流 强制终止当前Dialog程序的PBO/PAI处理,脱离屏幕序列控制(如事务码SE38执行的程序)。触发报表事件 激活类报表程序的事件链:INITIALIZATION → AT SELECTION-SCREEN → START-OF-SEL…

在PyTorch中使用GPU加速:从基础操作到模型部署

本文将通过具体代码示例&#xff0c;详细介绍如何在PyTorch中利用GPU进行张量计算和模型训练&#xff0c;包含设备查询、数据迁移以及模型部署等完整流程。 1. 查看GPU硬件信息 使用 nvidia-smi 命令检查GPU状态和进程信息&#xff1a; # 查看GPU信息 !nvidia-smi 输出示例&…

lib-zo,C语言另一个协程库,dns协程化, gethostbyname

lib-zo,C语言另一个协程库,dns协程化, gethostbyname 另一个 C 协程库 https://blog.csdn.net/eli960/article/details/146802313 本协程库 支持 DNS查询 协程化. 禁用所有 UDP 协程化 zvar_coroutine_disable_udp 1;禁用 53 端口的UDP 协程化 zvar_coroutine_disable_ud…

Java第三节:新手如何用idea创建java项目

作者往期文章&#xff1a; Java第一节&#xff1a;debug如何调试程序&#xff08;附带源代码&#xff09;-CSDN博客 Java第二节&#xff1a;debug如何调试栈帧链&#xff08;附带源代码&#xff09;-CSDN博客 步骤一 ​ 步骤二 ​ 步骤三 创建src文件夹包含main文件&#…