Android音视频开发之openGL视频录制
Posted 初一十五啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android音视频开发之openGL视频录制相关的知识,希望对你有一定的参考价值。
前言
周五了,说实话根本不想动,今天就来的时间就来水一下度过八。
新增:Flutter番外篇:Flutter面试-项目实战-电子书
关注公众号:初一十五a
解锁 《Android十一大板块文档》
音视频大合集,从初中高到面试应有尽有;让学习更贴近未来实战。已形成PDF版
十一个模块内容如下:
1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试应有尽有
3.Android车载应用大合集,从零开始一起学
4.性能优化大合集,告别优化烦恼
5.Framework大合集,从里到外分析的明明白白
6.Flutter大合集,进阶Flutter高级工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对工作需求
10.Android基础篇大合集,根基稳固高楼平地起
11.Flutter番外篇:Flutter面试+项目实战+电子书
整理不易,关注一下吧。开始进入正题,ღ( ´・ᴗ・` ) 🤔
在仿抖音的第一步中封装了ScreenFilter类来实现渲染屏幕的操作,我们都知道在抖音的视频录制过程中,可以添加很多的效果进行显示,比如说磨皮、美颜、大眼以及滤镜等效果,如果把这些效果都放在ScreenFilter中,就需要使用很多的if else来进行判断是否开启效果,显而易见,这样的会显得项目结构不是很美好,我们可以将每种效果都写成一个Filter,并且在ScreenFilter之前的效果,都可以不用显示
到屏幕当中去,所以可以使用FBO来实现这个需求。
但是这里有一个问题,就是在摄像头画面经过FBO缓冲,我们再从FBO中绘制到屏幕上去,这里的ScreenFilter获取的纹理是来自于FBO中的纹理,也就是OpenGL ES中的,所以不再需要额外扩展的纹理类型了,可以直接使用sampler2D类型,也就意味着ScrennFilter,
\\1. 开启效果:使用sampler2D
\\2. 未开启效果:使用samplerExternalOES
那么就需要ScreenFilter使用if else去判断,很麻烦,所以我们可以不管摄像头是否开启效果都先将摄像头数据写到FBO中,这样的话,ScreenFilter的采样数据始终都可以是sampler2D了。也就是下面这种结构:
需求:
长按按钮进行视频的录制,视频有5种速度的录制,极慢、慢、正常、快、以及极快,抬起手指时候停止录制,并将视频保存以MP4格式保存在sdcard中。(抖音的视频录制在录制完成以后显示的时候都是正常速度,这里我为了看到效果,保存下来的时候是用当前选择的速度进行显示的)。
分析需求:
想要录制视频,就需要对视频进行编码,摄像头采集到的视频数据一般为AVC格式的,这里我们需要将AVC格式的数据,编码成h.264的,然后再封装为MP4格式的数据。对于速度的控制,可以在写出到MP4文件格式之前,修改它的时间戳,就可以了。
实现需求:
MediaCodec
MediaCodec是android4.1.2(API 16)提供的一套编解码的API,之前试过使用FFmpeg来进行编码,效果不如这个,这个也比较简单,这次视频录制就使用它来进行编码。MediaCodec使用很简单,它存在一个输入缓冲区和一个输出缓冲区,我们把要编码的数据塞到输入缓冲区,它就可以进行编码了,然后从输出缓冲区取编码后的数据就可以了。
还有一种方式可以告知MediaCodec需要编码的数据,
/**
*Requests a surface to use as the input to an encoder, in place of input buffers,需要一个 Surface作为接口
*/
@NonNull
public native final surface createInputsurface();
这个接口是用来创建一个Surface的,Surface是用来干啥的呢,就是用来"画画"的,也就是说我们只要在这个Surface上画出我们需要的图像,MediaCodec就会自动帮我们编码这个Surface上面的图像数据,我们可以直接从输出缓冲区中获取到编码后的数据。之前的时候我们是使用OpenGL绘画显示到屏幕上去,我们可以同时将这个画面绘制到MediaCodec#createInputSurface()
中去,这样就可以了。
那怎么样才能绘制到MediaCodec的Surface当中去呢,我们知道录制视频是在一个线程中,显示图(GLSurfaceView)是在另一个GLThread线程中进行的,所以这两者的EGL环境也不同,但是两者又共享上下文资源,录制现场中画面的绘制需要用到显示线程中的texture等,那么这个线程就需要我们做这些:
1.配置录制使用的EGL环境(可以参照GLSurfaceView怎么配置的)
2.将显示的图像绘制到MediaCodec中的Surface中> 3. 编码(h.264)与复用(mp4)的工作
代码实现
MediaRecorder.java
视频编码类
/**
*开始录制视频
*@param speed
*/
public void start (float speed) throws IOException
mSpeed = speed;
/**
* 配置MediaCodec 编辑器
*/
//视频格式
// 类型(avc高级编码 H264) 编辑出的宽丶高
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,mWidtl,mHeight);
//参数配置
//1500kbs码率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, value:1500_00);
//帧率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, value:20);
//关键帧间隔
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, value:20);
//颜色格式
//从Surface当中获取的
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
//编码器
mMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
//将参数配置给编码器
mMediaCodec.configure(mediaFormat, surfacenull; null, crypto;null,MediaCodec.CONFIGURE_FLAG_ENCODE);
//交给虚拟屏幕 通过OpenGL 将预览的纹理 会知道这一个虚拟屏幕中
//这样MediaCodec就会自动编码InputSurface中的图像了
mInputSurface = mMediaCodec.createInputSurface();
// H.264
// 播放
// MP4 -> 解复用(解封装) -> 解码 -> 绘制
//封装器 复用器
// 一个 mp4 的封装器 将h.264 通过它写出到文件就可以了
mMediaMuxer = new MediaMuxer(mpath,MediaMuxer.outputFormat,MUXER_OUTPUT_MPEG_4);
/**
*配置EGL 环境
*/
//Handler : 线程通信
// Handler : 子线程通知主线程
Looper.loop();
HandlerThread handlerThread = new HandlerThread( name;''VideoCoder'');
handlerThread.start();
Looper looper = handlerThread.getLooper();
// 用于其他线程 通知子线程
mHandler = new Handler(looer);
//子线程: EGL的绑定线程,对我们自己创建的EGL环境的opengl操作都在这个线程中执行
mHandler.post()
//创建我们的EGL环境(虚拟设备丶EGL上下文等)
//mEglBase = new EGLBase(mcontext,mWidth,nHeight,mInputsurface,mEglcontext);
//启动编辑器
mMeddiacodec.starat();
isStart = true;
/**
* 传递 纹理进来
* 相当于调用一次就有一个新的图像
*/
public void encodeFrame(final int textureId, final long timestamp)
if (!isStart)
ruturn
mHandler.post()
//把图像画到虚拟屏幕
mEglBase.draw(textureId,timestamp):
//从编码器的输出缓冲区获取编码后的数据就OK了
getCodec(endofstream;false);
* 获取编码后的数据
*
* @param endofstream 标记是否结束录制
*/
private void getCodec(boolean endofstream)
//不录了,给mediacodec一个标记
if(endofstream)
mMediaCodec.signalEndofInputstream();
//输出缓冲区
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
// 希望将已经编码完的数据都获取到然后写到mp4文件
while(true)
//等待10ms
int status = mMediaCodec.dequeueoutputBuffer(bufferInfo,timeoutUs;10_000);
//让我们重试 1丶需要更多数据 2丶可能还没编码完(需要更多时间)
if (status == MediaCodec.INFO_TRY_AGAIN_LATER)
// 如果是停止 我继续循环
// 继续循环 就表现不会接收到新的等待编码的图像
// 相当于保证mediacodec中所有的待编码的数据都编码完成了,不断地重试 取出编码器中编码好的数据
// 标记不是停止,我们退出,下一轮接收到更多数据再来取输出编码后的数据
if(!endofstream)
//不写这个 会卡太久了,没有必要 你还是继续录制的,还能调用这个方法的!
break;
//否则继续
else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)
//开始编码 就会调用一次
MediaFormat outputFormat = mMediaCdec.getoutputFormat();
//配置封装器
// 增加一路指定格式的媒体流 视频
index = mMediaMuxer.addTrack(outputFormat);
mMediaMuxer.start();
这里的status==MediaCodec.INFO_TRY_AGAIN_LATER可以看下图理解
else if (status = = MediaCodec,INFO_OUTPUT_BUFFERS_CHANGED)
//忽略
else if
//成功 取出一个有效的输出
ByteBuffer outputBuffer = mMediaCodec.getoutputBuffer(status);
//如果获取的ByteBuffer是配置信息,不需要写到mp4
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0)
bufferInfo.size = 0
if (bufferInfo.size != 0)
bufferInfo.presentationTimeUs =(long) (bufferInfo.presentationTimeUs / mSpeed);
//写到mp4
//根据偏移定位
outputBuffer.position(bufferInfo.offset);
//ByteBuffer 可读写总长度
outputBuffer.limit(bufferInfo.offset +bufferInfo.size);
//写出
mMediaMuxer.writeSampleData(index,outputBuffer,bufferInfo);
//输出缓冲区 我们就使用完了,可以回收了,让mediacodec继续使用
//结束
if((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0)
break
下文讲:openSL ES
关注公众号:初一十五a
解锁 《Android十一大板块文档》
音视频大合集,从初中高到面试应有尽有;让学习更贴近未来实战。已形成PDF版
新增:Flutter番外篇:Flutter面试-项目实战-电子书
十一个模块内容如下:
1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试应有尽有
3.Android车载应用大合集,从零开始一起学
4.性能优化大合集,告别优化烦恼
5.Framework大合集,从里到外分析的明明白白
6.Flutter大合集,进阶Flutter高级工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对工作需求
10.Android基础篇大合集,根基稳固高楼平地起
11.Flutter番外篇:Flutter面试+项目实战+电子书
整理不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔
以上是关于Android音视频开发之openGL视频录制的主要内容,如果未能解决你的问题,请参考以下文章
Android Studio App开发之使用摄像机录制视频和从视频库中选取视频的讲解及实战(附源码)