当前位置:首页 » 《关注互联网》 » 正文

OpenGL特效视频编码保存到本地出现红屏、蓝屏、黄屏问题的分析解决过程记录_Engineer-Jsp的专栏

17 人参与  2021年09月07日 08:43  分类 : 《关注互联网》  评论

点击全文阅读


OpenGL特效视频编码保存到本地出现红屏、蓝屏、黄屏问题的分析解决过程记录

    • 一、问题的描述
      • 需求
      • 问题
    • 二、问题的分析
      • 原理
    • 三、问题的解决
      • 分析
      • 解决
    • 四、问题的总结
      • 总结

一、问题的描述

需求

在处理OpenGL视频特效项目问题时,需求是要将特效处理完后的视频外加音频保存至本地的录播功能,通过API glReadPixels拿到对应的RGBA视频数据编码至H264然后用AudioRecord录音并编码至AAC最终再通过MediaMuxer将两者合成写到本地,完成mp4视频的录制,当然如果是要推流则可以将数据通过PTSDTS同步后直接推送即可

问题

拿到RGBA数据之后将数据通过Google开源的libyuv进行缩放、旋转、镜像、编码等功能,首先是将其转码至I420然后进行缩放、旋转、镜像等操作,再将其还原到NV12NV21最后将其通过硬编码(产商限制只考虑使用硬编码)生成mp4文件。虽然编码之后的视频画面是正常的,但是画面上还有一层红色,尝试其他方式进行编码后又遇到了蓝色、黄色,效果如下图所示
在这里插入图片描述

二、问题的分析

原理

RGBA用四个字节(32bit)表示,分别代表RedGreenBlueAlpha,当然android提供的RGB格式也是很多的,常见的有RGB_8888RGB_24RGB_565等,需要注意的是数据源和编码时的取数据顺序,如果数据对不上,也就是大小端差异将会导致上述的红、黄、蓝屏问题,因为通道对不上,所以导致的这些色调问题

Android中最常见的硬编码颜色格式有两种,分别是COLOR_FormatYUV420PlanarCOLOR_FormatYUV420SemiPlanar下面会介绍他们的yuv格式和yuv的具体位置存储规则,如下图所示

在这里插入图片描述
在上层Android开发中,我们根据RGBA字面意思的理解就是RGBARGBARGBA这种顺序,但如果拿到的缓冲区数据不是如此恰好开发者按这个顺序去编解码的话就会出现色调问题,这里也顺带提一下libyuv的RGBA顺序是小端模式,Android输入的RGBA则是大端,如:Android中RGBA按大端顺序输入的话但在libyuv则处理的时候则为小端,即色道是相反的ABGR,这一点在处理的时候一定要注意,如果不确定数据源格式最好尝试其它的编解码函数

三、问题的解决

分析

刚开始遇到这个问题的时候,笔者猜测的原因可能是编码参数没设置对导致的,然后检查参数又没有问题,便开始编写各种JNI接口进行尝试,但依旧没有任何进展。最后一个朋友建议我把通道顺序调换试试,刚开始没有效果,也不知道是不是笔者JNI底层代码写的有问题,然后笔者开始去检查自己的C++代码

解决

笔者首先把封装的I420旋转、缩放、镜像功能全部去掉,直接调用Libyuv的RGBA转码NV12NV21函数,为了方便快速调试和验证问题,笔者封装了一个通用函数来做校验,把所有RGBA转换到NV12NV21格式的函数封装到一个通用函数中,调用时配置一个type即可实现动态调用函数,非常快捷高效,代码封装如下

/**
 * openGL rgba pixels data convert to yuv420sp(NV12/NV21) common func define
 * @return func
 */
static int
(*rgbaToYuv420spFunc[])(const uint8 *, int, uint8 *, int, uint8 *, int, int, int) ={
        libyuv::ARGBToNV12, libyuv::ARGBToNV21
};

为了快速验证问题,函数具体的处理逻辑笔者没有过多的去了解,下面贴上调用代码

/**
 * 
 * openGL rgba pixels data convert to android yuv420sp(NV12/NV21) func define 
 * @param env jni env
 * @param rgba openGL rgba pixels data
 * @param rgba_stride rgba stride
 * @param yuv yuv data
 * @param width src width
 * @param height src height
 * @param func func type
 * @return result > 0 means encode success
 */
int rgbaToYuv420sp(JNIEnv *env, jbyteArray rgba, jint rgba_stride,
                   jbyteArray yuv, jint width, jint height,
                   int (*func)(const uint8 *, int, uint8 *, int, uint8 *, int, int, int)) {
    size_t ySize = (size_t) (width * height);
    jbyte *rgbaData = env->GetByteArrayElements(rgba, JNI_FALSE);
    jbyte *yuvData = env->GetByteArrayElements(yuv, JNI_FALSE);
    int ret = func((const uint8 *) rgbaData, rgba_stride, (uint8 *) yuvData, width,
                   (uint8 *) (yuvData + ySize), width, width, height);
    env->ReleaseByteArrayElements(rgba, rgbaData, JNI_OK);
    env->ReleaseByteArrayElements(yuv, yuvData, JNI_OK);
    return ret;
}

写好C++处理函数后,接下来就是JNI方法的封装了

/**
 * jni method define
 * openGL rgba pixels data convert to android yuv420sp(NV12/NV21) , android support yuv420p(I420/YV12) and yuv420sp(NV12/NV21)
 * @param env jni env
 * @param clazz  java class object
 * @param type func type
 * @param rgba openGL rgba pixels data
 * @param yuv yuv420sp-nv12
 * @param width data width
 * @param height data height
 * @return result > 0 means encode success
 */
int
Jni_RgbaToYuv420sp(JNIEnv *env, jclass clazz, int type, jbyteArray rgba, jbyteArray yuv, jint width,
                   jint height) {
    // func type
    uint8 cType = (uint8) (type & 0x0F);
    // rgba use 4 byte
    int rgba_stride = 4 * width;
    // call c++ func
    return rgbaToYuv420sp(env, rgba, rgba_stride, yuv, width, height, rgbaToYuv420spFunc[cType]);
}

Java调用处代码

        if (mVideoColorFormat != MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar
                && mVideoColorFormat != MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar)
            return null;
        byte[] yuvData = new byte[width * height * 3 / 2];
        switch (mVideoColorFormat) {
            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
                YuvUtils.RgbaToI420(Key.ARGB_TO_I420, rgba, yuvData, width, height);
                return yuvData;
            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
                YuvUtils.RgbaToYuv420sp(Key.ARGBToNV12, rgba, yuvData, width, height);
                return yuvData;
            default:
                throw new IllegalStateException("Unsupported color format!");
        }

Key中定义的是对应C++函数定义的type,在底层根据type去选择性的调用想要调用的函数,高效快捷的调试代码
经过上述的处理后,录制的视频就没有再出现色调问题了,如下图
在这里插入图片描述
可以看到mp4视频画面已经正常了,但是还存在镜像问题,不过这都是小问题,问题不大!截图是视频中随机截的一张,使用的是测试手机拍摄另一个手机,至此问题已经解决

四、问题的总结

总结

在遇到问题的时候我们首先要列举一系列的可能导致的原因,然后一一的去进行验证,切记不要胡乱猜测那样只会增加你的工作范围而且有可能做很多无用功,也可以尝试询问朋友等,毕竟每个人的工作领域和知识广度不一样,都有自己擅长的地方,好了今天的博客就分享到此,有类似问题的老铁也可以借鉴和参考本篇文章,希望能给您带来帮助!


点击全文阅读


本文链接:http://zhangshiyu.com/post/27195.html

函数  编码  视频  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1