视频镜像一般是指,以视频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程序中。存在销毁重新创建过程,也就是导致一刹那黑屏或者卡顿。

三、解码层

如果是使用FFmpeg软解码,可以利用AVFilter模块进行视频镜像,对每一个视频帧进行处理,同时filter可以叠加,做滤镜、贴纸、模糊等特效。

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