Android音视频录制(4)——变速录制
来源:互联网 发布:淘宝投诉失败 编辑:程序博客网 时间:2024/05/18 12:04
概述
在看本篇文章之前请务必先查看这面三篇文章:
第一篇:Android音视频录制概述
第二篇Android音视频录制(1)——Surface录制
第三篇Android音视频录制(2)——Buffer录制
视频变速是一个非常有趣的东西,在我们平时看电影的时候,导演对某些镜头进行快放(比如动作片的拳脚片段),某些镜头进行慢放(比如一些火山喷发之类的),从而造成非常震撼的影视效果。最近非常火的一些app,能让普通群众都能拍出很精彩的快速/慢速的视频,而很多人对这种视频效果都感觉很赞,下面我就来讲述下视频录制过程中如何变速录制。
下面我先说下视频变速的原理:快速录制就是“丢”帧,慢速录制就是“加”帧,但帧率都保持不变,变的是时长。比如我4秒的视频,帧率是20帧/秒,那一共是80帧,把每一帧都编码0,1,2…,78,79,假设我定义的快速即为2倍变速,即4秒最后变成2秒的视频,视频帧的变化就是丢弃掉一半的帧,只取0, 2, 4…76, 78合成2秒的视频,帧率依然是20帧/秒。慢速录制也以1/2速度为例,不过慢速录制相对复杂些,毕竟删除总是比创建容易,4秒的视频最终要变成8秒的视频,帧率不变,所以肯定要“加”帧,其实就是复制帧,依然是0,1,2…78,79的视频,对每一帧复制一遍,重新编码,最后编程0,0A,1,1A….78,78A,79,79A一共160帧的8秒视频。这其中最最核心的点在哪里?三个字:时间戳。快速录制的时候,你需要把正常第2n的时间戳设置为n, 慢速录制的时候,需要把时间戳为n的帧变成2n。当然,talk is cheap, show me the code。下面我们看看如何实现。
代码的实现也是分两部分,第一部分是,Surface变速录制,第二部分是,Buffer变速录制。快速变速以2倍速为例,慢速变速以1/2倍速为例
Surface变速录制
在Android音视频录制(1)——Surface录制一文中并没有说到任何关于时间戳的代码,其实因为surface录制的时候egl默认给我们加上了时间戳,但是我们依然可以通过egl设置我们指定的时间戳,最终达到我们的目的。
首先定义几种模式:
public enum Speed{ NORMAL,//正常速度 SLOW,//慢速:0.5倍速 FAST//快速:2倍速 }
然后在VideoSurfaceEncoder中加入几个变量:具体看注释
private Speed mSpeed;//模式:快速/慢速/常速 private int mFrameIndex = 0;//实际编码器渲染帧数 private long mFirstTime;//第一帧渲染时间 private long mCurrPTS;//当前正在渲染的帧的时间戳 private int mDrainIndex = 0;//摄像头传递过来帧数
egl绘制的时候代码修改为如下,快速录制即每两次丢弃一次,慢速录制则是每次绘制重复绘制多一次
//egl 绘制 public void render(float[] surfaceTextureMatrix, float[] mvpMatrix) { if(mSpeed == Speed.NORMAL) {//常速录制 draw(surfaceTextureMatrix, mvpMatrix); }else if(mSpeed == Speed.SLOW){//慢速录制,则绘制两次 mCurrPTS = getPTS(); draw(surfaceTextureMatrix, mvpMatrix); mCurrPTS = getPTS(); draw(surfaceTextureMatrix, mvpMatrix); }else if(mSpeed == Speed.FAST){ if(mDrainIndex % 2 == 0){//快速录制 mCurrPTS = getPTS(); draw(surfaceTextureMatrix, mvpMatrix); } } mDrainIndex++; }
每次绘制,绘制帧数要加1:
private void draw(float[] surfaceTextureMatrix, float[] mvpMatrix) { if(isAllKeyFrame()){ requestKeyFrame(); } mRenderer.draw(surfaceTextureMatrix, mvpMatrix); if(isAllKeyFrame()){ requestKeyFrame(); } mFrameIndex++;//绘制帧加1 }
当然最重要的是时间戳的设定:常速的时候直接返回就好了,快速录制就是根据第一帧的时间戳,得出当前帧对应的当前时间与第一帧时间差的一半,加上第一帧的时间戳,即为正确的时间戳。慢速录制的时候时间戳就是第一帧时间戳,加上egl已经渲染的帧数乘上帧间隔即可。
private long getPTS() { long time = System.nanoTime(); if(mFirstTime == -1){ mFirstTime = time; } if(mSpeed == Speed.NORMAL){ return time / 1000; } if(mSpeed == Speed.FAST){ return mFirstTime + (time - mFirstTime) / 2; } if(mSpeed == Speed.SLOW){ return mFirstTime + mFrameIndex * mFrameInterval; } return time / 1000; }
opengl绘制的时候设置时间戳:在SurfaceEncoderRenderer每次绘制完之后,设置时间戳,之后再进行swap操作,时间戳才能真正写入到编码器:
while (mEncoder.isRecording()){ mLock.lock(); try { Log.d(TAG, "await~~~~"); mDrawCondition.await(); mEgl.makeCurrent(); //makeCurrent表明opengl的操作是在egl环境下 // clear screen with yellow color so that you can see rendering rectangle GLES20.glClearColor(1.0f, 1.0f, 0.0f, 1.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); mDrawer.setMatrix(mMatrix, 16); mDrawer.draw(mTextureId, mMatrix); if(!mEncoder.isNormalSpeed()) { mEgl.setPTS(mEncoder.getCurrPTS());//设置时间戳 } mEgl.swapBuffers(); mEncoder.singalOutput();//通知编码器线程要输出数据啦 Log.d(TAG, "draw------------textureId=" + mTextureId); }finally { mLock.unlock(); }
MEgl中设置时间戳:
/** *设置时间戳 * @param pts 纳秒 */ public void setPTS(long pts){ EGLExt.eglPresentationTimeANDROID(mEglDisplay, mEGLSurface, pts); }
这样surface变速录制就已经完成。
Buffer 变速录制
理解了surface的变速录制,buffer录制原理也一样
VideoEncoder需要增加下面的变量:
private Speed mSpeed = Speed.NORMAL; private int mFrameIndex = 0; private int mDrainIndex = 0; private long mFirstFramePTS = 0;
摄像头提供帧数据:
public void addFrame(byte[] data){ Log.d(TAG, "drain frame-" + mDrainIndex + " frameIndex=" + mFrameIndex); if(mSpeed == Speed.FAST){ if(mDrainIndex % 2 == 0){ addFrame(data, getPTS());//快速录制 } }else if(mSpeed == Speed.SLOW){ addFrame(data, getPTS()); addFrame(data, getPTS());//慢速录制 }else{//normal addFrame(data, getPTS());//正常录制 } mDrainIndex++; }
获取时间戳:这里和surface录制有区别,surface录制时间戳是纳秒,surface录制的时间戳是微妙
public long getPTS(){ if(mFrameIndex == 0){ mFirstFramePTS = System.nanoTime() / 1000; return mFirstFramePTS; } long time = System.nanoTime() / 1000; if(mSpeed == Speed.FAST){ return mFirstFramePTS + (time - mFirstFramePTS) / 2;//快速录制 }else if(mSpeed == Speed.NORMAL){//正常录制 return time; }else if(mSpeed == Speed.SLOW){//慢速录制 return mFirstFramePTS + mFrameIndex * mFrameInterval; } return System.nanoTime() / 1000; }
每次绘制的时候绘制帧都需要加1:
public void addFrame(Frame frame){ try { mLock.lock(); mFrameList.add(frame); mFrameIndex++;//绘制帧+1 Log.d(TAG, "add frame-" + frame.mTime + " frameIndex=" + mFrameIndex + " interval=" + mFrameInterval); mCondition.signal(); }finally { mLock.unlock(); } }
自此,变速录制的就讲解完了,各位小伙伴有什么疑问的,欢迎反馈。
- Android音视频录制(4)——变速录制
- Android 音视频录制(2)——Buffer录制
- Adroid音视频录制(1)——Surface录制
- Android 音视频录制(3)——全关键帧视频录制(视频编辑必备)
- android 音视频录制
- android-音视频录制
- Android 实时视频采集—MediaRecoder录制
- Android 音视频录制概述
- Android录制视频,仿微信小视频录制(一)
- Android录制视频,仿微信小视频录制(二)
- Android录制视频,仿微信小视频录制(三)
- 视频录制(一)—mediaRecorder介绍
- android视频录制(调用系统视频录制)
- 音视频开发——iOS音频录制(六)
- Android 视频录制
- android视频录制
- android视频录制例子
- Android录制视频(五)
- No repository found containing 问题的处理
- HDU Find a way
- bzoj 4811: [Ynoi2017]由乃的OJ
- Kafka文件存储机制那些事
- Shell 自动化编译打包
- Android音视频录制(4)——变速录制
- 探索者TSSD结构CAD3.0(包含单机版和网络版)带破解
- UE4如何上传文件
- 分治---循环赛日程表
- Android第三方库:Glide源码解析
- maven https behind proxy
- 【微信小程序】仿京东商品分类界面
- ViewStub
- NS-3之Tracing System