视频镜像一般是指,以视频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, mView.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);
以上是实现视频镜像的几种方案,各位客官可以根据实际场景来选择。没有最好的,只有最合适的。