赞
踩
视频镜像一般是指,以视频x轴中心点为对称轴,左右内容互相交换。实现视频镜像功能,可以从解码层、渲染层、显示层这三个层次入手。解码层需要对解码出来的每一帧进行镜像处理,以FFmpeg软解处理为例,比较耗时,也占用更多内存空间,从性能效率角度考虑不太可取。但是,可以同时添加滤镜、模糊效果、文字与动画贴纸等等。当然,渲染层使用openGL也可以实现这些功能,同时也可以做镜像。显示层如果使用TextureView,可以通过设置旋转实现镜像,用法最为简单。
视频播放一般使用SurfaceView、GLSurfaceView或者TextureView来做显示控件。SurfaceView有独立Surface,有前台、后台双缓冲,效率比较高,但不具有View属性,不能做旋转、平移、缩放、透明度等动画;GLSurfaceView继承于SurfaceView,封装一个GLThread作为渲染线程;TextureView继承于View,具有View的属性,可以做任意属性动画,但是存在缓存,有2、3帧延迟,渲染显示效率比较低一点。在这里针对TextureView控件,来实现视频镜像。
1、setRotationY
setRotationY属于View提供的方法,内部实现原理是使用RenderNode进行旋转,在旋转前后分别调用invalidateViewProperties方法,具体方法如下:
- public void setRotationY(float rotationY) {
- if (rotationY != getRotationY()) {
- invalidateViewProperty(true, false);
- mRenderNode.setRotationY(rotationY);
- invalidateViewProperty(false, true);
-
- invalidateParentIfNeededAndWasQuickRejected();
- notifySubtreeAccessibilityStateChangedIfNeeded();
- }
- }
外部只需一行代码即可实现镜像:
mView.setRotationY(180);
取消镜像只需:把旋转角度设为0:
mView.setRotationY(0);
2、带翻转动画的ObjectAnimator
如果单纯是镜像不够酷,我们可以加个3D翻转动画,使用ObjectAnimator实现:
- Animator animator = ObjectAnimator.ofFloat(mView, "rotationY", 180);
- animator.setDuration(500).start();
同样地,取消镜像只需把旋转角度设为0:
- Animator animator = ObjectAnimator.ofFloat(mView, "rotationY", 0);
- animator.setDuration(500).start();
3、利用Matrix的setScale
setScale是Matrix矩阵提供的方法,然后使用TextureView的setTransform方法把Matrix矩阵设置进去,实现左右镜像需要View的宽度的一半作为镜像中心轴:
- Matrix matrix = textureView.getMatrix();
- matrix.setScale(-1, 1, textureView.getWidth()/2, 0);
- textureView.setTransform(matrix);
取消镜像只要-1改为1,去掉x轴中心点:
- Matrix matrix = textureView.getMatrix();
- matrix.setScale(1, 1);
- textureView.setTransform(matrix);
如果时使用openGL进行渲染, 可以修改顶点着色器的顶点坐标,取x坐标点与1的补数:
- attribute vec4 vPosition;
- attribute vec2 vCoord;
- varying vec2 fCoord;
-
- void main() {
- gl_Position = vPosition;
- fCoord = vec2(1-vCoord.x, vCoord.y);
- }
取消镜像只要把vCoord直接赋值给fCoord:
- attribute vec4 vPosition;
- attribute vec2 vCoord;
- varying vec2 fCoord;
-
- void main() {
- gl_Position = vPosition;
- fCoord = vCoord;
- }
但是,openGL实现镜像有个不便之处是,需要重新加载shader,把新的glsl语言加载进去,链接到program程序中。存在销毁重新创建过程,也就是导致一刹那黑屏或者卡顿。相关的glsl语言语法,可以参考:glsl语言的基本语法
如果是使用FFmpeg软解码,可以利用AVFilter模块进行视频镜像,对每一个视频帧进行处理,同时filter可以叠加,做滤镜、贴纸、模糊等特效。filter的使用可参考:AVFilter模块使用步骤
1、初始化AVFilter
- int init_filters(const char *filters_descr) {
- char args[512];
- int ret = 0;
- AVFilter *buffersrc = avfilter_get_by_name("buffer");
- AVFilter *buffersink = avfilter_get_by_name("buffersink");
- AVFilterInOut *outputs = avfilter_inout_alloc();
- AVFilterInOut *inputs = avfilter_inout_alloc();
- AVRational time_base = pFormatCtx->streams[video_stream_index]->time_base;
- enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE};
-
- filter_graph = avfilter_graph_alloc();
- if (!outputs || !inputs || !filter_graph) {
- ret = AVERROR(ENOMEM);
- goto end;
- }
-
- /* buffer video source: the decoded frames from the decoder will be inserted here. */
- snprintf(args, sizeof(args),
- "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
- pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
- time_base.num, time_base.den,
- pCodecCtx->sample_aspect_ratio.num, pCodecCtx->sample_aspect_ratio.den);
-
- ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
- args, NULL, filter_graph);
- if (ret < 0) {
- LOGE(TAG, "Cannot create buffer source\n");
- goto end;
- }
-
- /* buffer video sink: to terminate the filter chain. */
- ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
- NULL, NULL, filter_graph);
- if (ret < 0) {
- LOGE(TAG, "Cannot create buffer sink\n");
- goto end;
- }
-
- ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
- AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
- if (ret < 0) {
- LOGE(TAG, "Cannot set output pixel format\n");
- goto end;
- }
-
- outputs->name = av_strdup("in");
- outputs->filter_ctx = buffersrc_ctx;
- outputs->pad_idx = 0;
- outputs->next = NULL;
-
- inputs->name = av_strdup("out");
- inputs->filter_ctx = buffersink_ctx;
- inputs->pad_idx = 0;
- inputs->next = NULL;
-
- if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
- &inputs, &outputs, NULL)) < 0)
- goto end;
-
- if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
- goto end;
-
- end:
- avfilter_inout_free(&inputs);
- avfilter_inout_free(&outputs);
-
- return ret;
- }
2、添加进filter队列
av_buffersrc_add_frame_flags(buffersrc_ctx, pFrame, AV_BUFFERSRC_FLAG_KEEP_REF);
3、从filter队列读取
av_buffersink_get_frame(buffersink_ctx, filter_frame);
4、释放资源
- avfilter_free(buffersrc_ctx);
- avfilter_free(buffersink_ctx);
- avfilter_graph_free(&filter_graph);
以上是实现视频镜像的几种方案,各位客官可以根据实际场景来选择。没有最好的,只有最合适的。音视频学习和音视频处理项目可参考:https://github.com/xufuji456/FFmpegAndroid
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。