Android视频编辑器(三)给本地视频加水印和美颜滤镜
前言
预览本地视频的时候加上水印和美白效果
public class VideoPreviewView extends GLSurfaceView{}
然后在初始化函数中,设置进行OpenGL初始化
private void init(Context context) {setEGLContextClientVersion(2);setRenderer(this);setRenderMode(RENDERMODE_WHEN_DIRTY);setPreserveEGLContextOnPause(false);setCameraDistance(100);mDrawer = new VideoDrawer(context,getResources());//初始化Drawer和VideoPlayermMediaPlayer = new MediaPlayerWrapper();mMediaPlayer.setOnCompletionListener(this);}
上面代码中的VideoDrawer和MediaPlayerWrapper就是控制OpenGL绘制和视频播放的重点类,其实VideoPreviewView类和我们之前的CameraView类是完全类似的,只不过一个是从摄像头获取数据,一个是从视频解码器获取数据而已。下面我们来分别说说mediaPlayerWrapper和VideoDrawer类。
public class VideoDrawer implements GLSurfaceView.Renderer {}
然后,在构造函数中初始化要用到的Filter,包括美颜的MagicBeautyFilter和加水印的WaterMarkFilter
public VideoDrawer(Context context,Resources res){mPreFilter = new RotationOESFilter(res);//旋转相机操作mShow = new NoFilter(res);mBeFilter = new GroupFilter(res);mBeautyFilter = new MagicBeautyFilter();mProcessFilter=new ProcessFilter(res); WaterMarkFilter waterMarkFilter = new WaterMarkFilter(res);waterMarkFilter.setWaterMark(BitmapFactory.decodeResource(res, R.mipmap.watermark)); waterMarkFilter.setPosition(0,70,0,0);mBeFilter.addFilter(waterMarkFilter);}
上面这段代码就不进行过多解释了,在该系列文章的第二篇中,有比较详细的解释,包括美白Filter和水印Filter的实现原理,不懂的童鞋,请翻阅上篇文章。
@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {int texture[]=new int[1];GLES20.glGenTextures(1,texture,0);GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES ,texture[0]);GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);surfaceTexture = new SurfaceTexture(texture[0]);mPreFilter.create();mPreFilter.setTextureId(texture[0]);mBeFilter.create();mProcessFilter.create();mShow.create();mBeautyFilter.init();mBeautyFilter.setBeautyLevel(3);//默认设置3级的美颜}
在onSurfaceChanged函数中,设置视图、纹理、滤镜的宽高
@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {viewWidth=width;viewHeight=height;GLES20.glDeleteFramebuffers(1, fFrame, 0);GLES20.glDeleteTextures(1, fTexture, 0);GLES20.glGenFramebuffers(1,fFrame,0);EasyGlUtils.genTexturesWithParameter(1,fTexture,0, GLES20.GL_RGBA,viewWidth,viewHeight);mBeFilter.setSize(viewWidth,viewHeight);mProcessFilter.setSize(viewWidth,viewHeight);mBeautyFilter.onDisplaySizeChanged(viewWidth,viewHeight);mBeautyFilter.onInputSizeChanged(viewWidth,viewHeight);}
然后在onDrawFrame中,对每一帧的视频数据进行处理,并且显示
@Overridepublic void onDrawFrame(GL10 gl) {surfaceTexture.updateTexImage();EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]);GLES20.glViewport(0,0,viewWidth,viewHeight);mPreFilter.draw();EasyGlUtils.unBindFrameBuffer();mBeFilter.setTextureId(fTexture[0]);mBeFilter.draw();if (mBeautyFilter != null && isBeauty && mBeautyFilter.getBeautyLevel() != 0){EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]);GLES20.glViewport(0,0,viewWidth,viewHeight);mBeautyFilter.onDrawFrame(mBeFilter.getOutputTexture());EasyGlUtils.unBindFrameBuffer();mProcessFilter.setTextureId(fTexture[0]);}else {mProcessFilter.setTextureId(mBeFilter.getOutputTexture());}mProcessFilter.draw();GLES20.glViewport(0,0,viewWidth,viewHeight);mShow.setTextureId(mProcessFilter.getOutputTexture());mShow.draw();}
然后就是对外提供的美颜效果的开关
/**切换开启美白效果*/public void switchBeauty(){isBeauty = !isBeauty;}
VideoDrawer类基本上就是这些,跟CameraDrawer的添加水印和美白效果的方式完全一样,但是少了视频录制的相关过程,因为我们对本地视频的处理并不是实时录制的,而是后面才进行录制,所以其实更加简单了。
private List<MediaPlayer> mPlayerList; //player listprivate List<String> mSrcList; //video src listprivate List<VideoInfo> mInfoList; //video info listpublic MediaPlayerWrapper() {mPlayerList = new ArrayList<>();mInfoList = new ArrayList<>();}
提供了setDataSource方法,用于设置视频的播放地址
public void setDataSource(List<String> dataSource) {this.mSrcList = dataSource;MediaMetadataRetriever retr = new MediaMetadataRetriever();for (int i = 0; i < dataSource.size(); i++) {VideoInfo info = new VideoInfo();String path=dataSource.get(i);retr.setDataSource(path);String rotation = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);String width = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);String height = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);String duration = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);info.path=path;info.rotation = Integer.parseInt(rotation);info.width = Integer.parseInt(width);info.height = Integer.parseInt(height);info.duration = Integer.parseInt(duration);mInfoList.add(info);}}
这里的VideoInfo,其实就是自定义的一个视频信息的bean
public void prepare() throws IOException {for (int i = 0; i < mSrcList.size(); i++) {MediaPlayer player = new MediaPlayer();player.setAudioStreamType(AudioManager.STREAM_MUSIC);player.setOnCompletionListener(this);player.setOnErrorListener(this);player.setOnPreparedListener(this);player.setDataSource(mSrcList.get(i));player.prepare();mPlayerList.add(player);if (i == 0) {mCurMediaPlayer = player;if (mCallback != null) {mCallback.onVideoChanged(mInfoList.get(0));}}}}
然后是视频的start、pause和stop,我们有多个MediaPlayer当然不可能同时进行播放,所以有一个mCurmediaPlayer,用来控制当前播放的是哪个视频。
public void start() {mCurMediaPlayer.setSurface(surface);mCurMediaPlayer.start();if (mCallback != null) {mCallback.onVideoStart();}}public void pause() {mCurMediaPlayer.pause();if (mCallback != null) {mCallback.onVideoPause();}}public void stop() {mCurMediaPlayer.stop();}
然后,就是不同播放器的切换,当播放完一个之后,通过switchPlayer切换到下一个播放器
@Overridepublic void onCompletion(MediaPlayer mp) {curIndex++;if (curIndex >= mSrcList.size()) {curIndex = 0;if (mCallback != null) {mCallback.onCompletion(mp);}}switchPlayer(mp);}private void switchPlayer(MediaPlayer mp) {mp.setSurface(null);if (mCallback != null) {mCallback.onVideoChanged(mInfoList.get(curIndex));}mCurMediaPlayer = mPlayerList.get(curIndex);mCurMediaPlayer.setSurface(surface);mCurMediaPlayer.start();}
上面代码中,我们看到给当前的player设置了一个显示表面,surface,而这个surface就是在VideoPreviewView中设置的,将MediaPlayer和OpenGL联系起来的关键
public void setSurface(Surface surface) {this.surface = surface;}
然后我们的MediaPlayerWrapper类,还有就是一些接口的定义,基本上就是这样了。
@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {mDrawer.onSurfaceCreated(gl,config);SurfaceTexture surfaceTexture = mDrawer.getSurfaceTexture();surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {@Overridepublic void onFrameAvailable(SurfaceTexture surfaceTexture) {requestRender();}});Surface surface = new Surface(surfaceTexture);mMediaPlayer.setSurface(surface);try {mMediaPlayer.prepare();} catch (IOException e) {e.printStackTrace();}mMediaPlayer.start();}
然后在onSurfaceChanged和onDrawFrame方法中,主要是调用了VideoDrawer的方法
@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {mDrawer.onSurfaceChanged(gl,width,height);}@Overridepublic void onDrawFrame(GL10 gl) {mDrawer.onDrawFrame(gl);}
如此方式,我们就很容易的实现了利用MediaPlayer解码视频,然后利用OpenGL对视频数据进行二次处理,再显示到我们的GLSurfaceView上面。
当然我们这里是一个视频播放的控件,肯定还有一些对外提供的接口和回调函数。就不一一解释了。然后就是在预览界面的使用 ,这个也不多说了,主要将控件写在xml中,然后给该控件设置视频的播放地址,然后进行播放即可。
本地视频解码,OpenGL美颜,视频数据编码成文件
我们的主要思路是,通过MediaCodec的解码器,将原视频解码成帧数据,然后通过OpenGL对视频数据进行处理,再通过MediaCodec的编码器对处理后的数据进行编码,保存成一个视频文件。
public VideoClipper() {try {videoDecoder = MediaCodec.createDecoderByType("video/avc");videoEncoder = MediaCodec.createEncoderByType("video/avc");audioDecoder = MediaCodec.createDecoderByType("audio/mp4a-latm");audioEncoder = MediaCodec.createEncoderByType("audio/mp4a-latm");} catch (IOException e) {e.printStackTrace();}}
通过名字就可以看出来,我们分别初始化了音频、视频的解码器和编码器,这篇我们主要讲的是视频的操作,所以暂时不管音频。
private void initVideoCodec() {//不对视频进行压缩int encodeW = videoWidth;int encodeH = videoHeight;//设置视频的编码参数MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", encodeW, encodeH);mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 3000000);mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);videoEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);inputSurface = new InputSurface(videoEncoder.createInputSurface());inputSurface.makeCurrent();videoEncoder.start();VideoInfo info = new VideoInfo();info.width = videoWidth;info.height = videoHeight;info.rotation = videoRotation;outputSurface = new OutputSurface(info);outputSurface.isBeauty(isOpenBeauty);videoDecoder.configure(videoFormat, outputSurface.getSurface(), null, 0);videoDecoder.start();//解码器启动}
在上面的代码中,就包括了很关键的代码,就是创建了一个InputSurface和一个OutputSurface。而这两个类原型其实来自于谷歌工程师编写的一个音视频处理的项目grafika。然后我们进行了一些改造。
/** 开启美颜 */public void showBeauty(){isOpenBeauty = true;}
然后我们去看OutputSurface的isBeauty方法
public void isBeauty(boolean isBeauty){mDrawer.isOpenBeauty(isBeauty);}
是不是发现了一个眼熟的东西,对就是mDrawer。该mDrawer其实就是,在OuputSurface初始化的时候创建的一个VideoDrawer
mDrawer = new VideoDrawer(MyApplication.getContext(),MyApplication.getContext().getResources());
而这个VideoDrawer在OutputSurface中的主要用法如下,首先在setup函数中进行初始化
private void setup(VideoInfo info) {mDrawer = new VideoDrawer(MyApplication.getContext(),MyApplication.getContext().getResources());mDrawer.onSurfaceCreated(null,null);mDrawer.onSurfaceChanged(null,info.width,info.height);//在VideoDrawer创建surfaceTexture之后,提供出来mSurfaceTexture = mDrawer.getSurfaceTexture();mSurfaceTexture.setOnFrameAvailableListener(this);mSurface = new Surface(mSurfaceTexture);}
主要就是先初始化,然后把他内部创建绑定的纹理,提供出来,并且创建一个Surface,这个Surface,通过下面的代码提供出去
/** Returns the Surface that we draw onto.*/public Surface getSurface() {return mSurface;}
并且,最终是设置在了解码器里面。
videoDecoder.configure(videoFormat, outputSurface.getSurface(), null, 0);
如此一来,我们通过解码器解码的数据,就会经过OutputSurface和VideoDrawer,然后不多说,和上面一样的,在VideoDrawer里面对数据进行处理。加上美白和水印等
/** Draws the data from SurfaceTexture onto the current EGL surface.*/public void drawImage() {mDrawer.onDrawFrame(null);}
其实这里的原理和上面的预览视频是一样的
boolean doRender = (info.size != 0 && info.presentationTimeUs - firstSampleTime > startPosition);decoder.releaseOutputBuffer(index, doRender);if (doRender) {// This waits for the image and renders it after it arrives.outputSurface.awaitNewImage();outputSurface.drawImage();// Send it to the encoder.inputSurface.setPresentationTime(info.presentationTimeUs * 1000);inputSurface.swapBuffers();}
相应的,音视频的编解码代码,网上有许多了,我们这里暂时不做讲解。可能会在后面的文章中,对这部分的内容进行补充。
本地视频预览效果
处理后视频播放效果
总结
Android视频编辑器(三)给本地视频加水印和美颜滤镜相关推荐
- 一个 Android 的视频编辑器,包括了视频录制、剪切、增加 bgm、美白、加滤镜、加水印等多种功能
VideoEditor-For-Android 项目地址:qqchenjian318/VideoEditor-For-Android 简介:一个 Android 的视频编辑器,包括了视频录制.剪切. ...
- Android VR Player(全景视频播放器) [6]:视频列表的实现-本地视频
Android VR Player(全景视频播放器) [6]:视频列表的实现-本地视频 (本篇博客参考<Android第一行代码(第二版)>中关于RecyclerView的部分) 列表的实 ...
- python播放本地视频_python opencv 读取本地视频文件 修改ffmpeg的方法
Python + opencv 读取视频的三种情况: 情况一:通过摄像头采集视频 情况二:通过本地视频文件获取视频 情况三:通过摄像头录制视频,再读取录制的视频 摄像头采集.本地视频文件的读取.写视频 ...
- 腾讯视频如何设置关闭本地视频后自动打开主界
1.点击主页面右上角的三条横杠 腾讯视频如何设置关闭本地视频后自动打开主界面 2.点击[设置] 腾讯视频如何设置关闭本地视频后自动打开主界面 3.点击左侧的[高级设置] 腾讯视频如何设置关闭本地视频后 ...
- PC电脑 屏幕竖直截长屏、本地视频转码、本地视频转gif动画、gif压缩等
PC电脑 屏幕竖直截长屏.本地视频转码.本地视频转gif动画.gif压缩等 写博客贵在坚持.为了让自己和一同进步的人,能够快速理解,并看懂自己的博客. 需要自己对博客编撰尽量完善,要配图.配代码.配动 ...
- C# Umeditor 编辑器上传本地视频、本地文件
Umeditor 最近在做一个网站,涉及到网站文章的编辑.Umeditor是一个很好的选择,但是看了一下Umeditor,发现不能上传本地视频和本地文件,而项目又要得比较急,所以以我觉得最快的方式修改 ...
- ffmpeg分割视频,制作gif图,加水印、去水印,视频拼接
一.分割视频 进入ffmpeg的目录: (1)执行从0分钟开始,剪切5分钟: ./ffmpeg -ss 00:00:00 -i /111/Movies/a2009.mp4 -t 00:05:00 a2 ...
- 史林枫:C#.NET利用ffmpeg操作视频实战(格式转换,加水印 一步到位)
ffmpeg.exe是大名鼎鼎的视频处理软件,以命令行参数形式运行.网上也有很多关于ffmpeg的资料介绍.但是在用C#做实际开发时,却遇到了几个问题及注意事项,比如如何无损处理视频?如何在转换格式的 ...
- 给图片加上水印php视频,如何使用PHP给图片加水印
为了防止辛苦做出来的图片被盗用,很多照片都会加上水印,可以直接用图片工具添加水印再上传,但PHP中就可以实现给图片加水印的功能,本文章向码农们介绍 php 给图片加水印的两种方法,感兴趣的码农可以参考 ...
最新文章
- 属于python文件的操作有_Python的文件操作
- Verizon部署美国最大小型基站系统
- mysql concat 变量_MySQL 字符串连接CONCAT()函数
- Key/Value之王Memcached初探:一、掀起Memcached的盖头来
- maven常见问题处理(3-3)Gradle编译时下载依赖失败解决方法
- wordpress api ajax,Woocommerce rest api - 在wordpress中通过ajax创建产品
- Golang学习:生成GIF动画
- 黑马程序员之Web前端全栈 · 阶段一 前端开发基础 (1)
- matlab imcrop 用法
- win10系统hp笔记本开机黑屏一段时间解决方法
- IP和子网掩码和网关的关系
- Python 3 《List》入门练习
- token 微信access 过期_.Net微信开发之如何解决access_token过期问题
- 2021-2027全球与中国可待因药品市场现状及未来发展趋势
- 2022年汽车配件市场分析
- Oracle 状告 Google 侵犯专利
- Group by 分组详解
- android获取手机型号和手机厂商
- 项目四 CentOS使用kubeadm部署工具部署测试环境的K8s集群---Kubectl命令使用以及安装dashboard界面
- NC15446 	 wyh的物品
热门文章
- ionic升华过程8-cordova插件+mui小案例
- 别再盲目复制Compound代码了,Defi借贷项目漏洞分析
- [JVM]了断局: 堆外内存无法 [ -XX:MaxDirectMemorySize ] 限制
- ORA-00204报错
- 极智Coding | C 和 C++ 读存 bin 文件方法
- 【油猴Tampermonkey】脚本安装教程+自用脚本推荐
- 儒略历--Java代码(附带发现了一些问题)
- 3个月前被裁员了,心情跌落谷底,直到学姐给了我这份面试文档…
- 谷歌地图启用全新卫星图:细节更清晰,色彩更丰富
- Python利用百度地图获取两地距离 最详细过程和源代码