记一次gstreamer解码存图绿线问题排查

简介: 记一次gstreamer解码存图绿线问题排查

背景

业务需求需要将某些解码后的视频帧保存为图片,大部分情况下图片都是正常的,更换了某些视频流后,在保存的图片顶部就会出现一条绿线,现记录下解决过程。

部分代码如下

解码回调如下,完整代码可参考之前的文章Gstreamer 硬解码Rtsp流及代码实现

最终排查结果是:

有些相机本身推的视频帧的大小与gstreame解码出来的 width 和 height不匹配导致的。如:gstreamer实际解出来的width = 1920, height = 1080, 但实际上一帧的数据是:1920 * 1088,多出来了8个字节。如果用获取到的width 和 height去计算拷贝帧的大小,在相机推送不标准的情况下就会出现该问题。

采用opencv保存图片,先将YUV转成NV12,再调用cv::imwrite() 。以19201080为例:
修复前,使用 解码得到的width 和 height计算帧大小:预期帧大小为:1920
1080*3 / 2 = 3,110,400

{
  ......
  cv::Mat img;
  // 创建并初始化原始YUV Mat对象
  yuvNV12.create(height * 3 / 2, width, CV_8UC1);
  memcpy(yuvNV12.data, map.data, width * height * 3 / 2);
  // yuv to img Mat
  cv::cvtColor(yuvNV12, img, cv::COLOR_YUV2BGR_NV12);
  ......
}

实际帧大小 map.size = 3,133,440:

{
  ......
  cv::Mat img;
  // 创建并初始化原始YUV Mat对象
  yuvNV12.create((map.size / width), width, CV_8UC1);
  memcpy(yuvNV12.data, map.data, map.size);
  // yuv to img Mat
  cv::cvtColor(yuvNV12, img, cv::COLOR_YUV2BGR_NV12);
}
GstFlowReturn ReadvideoFrame_callback(GstElement *sink, gpointer user_data)
{
    CustomData *data = (CustomData *)user_data;
    char video_format[32] = {0};
    int framerate[2] = {0};
    unsigned long long ts = 0;
    GstSample *sample;
    GstBuffer *buffer;
    GstCaps *caps;
    GstStructure *s;
    gint width, height; // 图片的尺寸
    // 使用pull-sample拉取视频帧,并映射到map变量,通过map拷贝出frame数据
    g_signal_emit_by_name(sink, "pull-sample", &sample);
    // g_print("new_sample succeeded (type '%d').\n", sample);
    if (sample)
    {
        caps = gst_sample_get_caps(sample);
        if (!caps)
        {
            g_print("gst_sample_get_caps fail\n");
            gst_sample_unref(sample);
            return GST_FLOW_ERROR;
        }
        s = gst_caps_get_structure(caps, 0);
        gboolean res;
        res = gst_structure_get_int(s, "width", &width); // 获取图片的宽
        // g_print("width: %d,  ", width);
        res |= gst_structure_get_int(s, "height", &height); // 获取图片的高
        // g_print("height: %d \n", height);
        if (!res)
        {
            g_print("gst_structure_get_int fail\n");
            gst_sample_unref(sample);
            return GST_FLOW_ERROR;
        }
        const char *format = gst_structure_get_string(s, "format");
        strcpy(video_format, format);
        gst_structure_get_fraction(s, "framerate", &framerate[0], &framerate[1]);
        // 获取视频的一帧buffer,注意,这个buffer是无法直接用的,它不是char类型
        buffer = gst_sample_get_buffer(sample);
        if (!buffer)
        {
            g_print("gst_sample_get_buffer fail\n");
            gst_sample_unref(sample);
            return GST_FLOW_ERROR;
        }
        GstMapInfo map;
        // 把buffer映射到map,这样我们就可以通过map.data取到buffer的数据
        auto rett = gst_buffer_map(buffer, &map, GST_MAP_READ);
        if (rett)
        {
            cv::Mat yuvNV12;
            cv::Mat img;
            // 创建并初始化原始YUV Mat对象
            // 有问题代码如下:
            // yuvNV12.create(height * 3 / 2, width, CV_8UC1);
            // memcpy(yuvNV12.data, map.data, width * height * 3 / 2);
            //修复如下:
            yuvNV12.create((map.size / width), width, CV_8UC1);
            memcpy(yuvNV12.data, map.data, map.size);
            // yuv to img Mat
            cv::cvtColor(yuvNV12, img, cv::COLOR_YUV2BGR_NV12);
            gst_buffer_unmap(buffer, &map); // 解除映射
        }
        else
        {
            g_print("gst_buffer_map failed!, %d\n", rett);
        }
        // release sample reference
        gst_sample_unref(sample);
    }
    else
    {
        g_print("sample is null...\n");
    }
    return GST_FLOW_OK;
}


推荐一个零声学院免费教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,点击立即学习:

相关文章
|
2月前
|
算法 数据处理 开发者
FFmpeg库的使用与深度解析:解码音频流流程
FFmpeg库的使用与深度解析:解码音频流流程
42 0
|
存储 缓存 安全
为什么你的Opus编码出来的数据有杂音(解决Android平台架构问题)
Gradle插件分为脚本插件和对象插件,脚本插件就是在普通的gradle中写一系列task,然后在别的gradle构建脚本中通过 apply from: 'xx.gradle' 引用这个脚本插件,下面主要介绍一下对象插件对象插件是指实现了org.gradle.api.Plugin接口的类。并且需要实现void apply(T target)这个方法,该方法中的泛型指的是此Plugin可以应用到的对象,而我们通常是将其应用到Project对象上。 编写对象插件常见创建方式
377 0
|
存储 缓存 编解码
FFmpeg开发笔记(五):ffmpeg解码的基本流程详解(ffmpeg3新解码api)
FFmpeg开发笔记(五):ffmpeg解码的基本流程详解(ffmpeg3新解码api)
FFmpeg开发笔记(五):ffmpeg解码的基本流程详解(ffmpeg3新解码api)
|
存储 缓存 编解码
FFmpeg开发笔记(四):ffmpeg解码的基本流程详解
FFmpeg开发笔记(四):ffmpeg解码的基本流程详解
FFmpeg开发笔记(四):ffmpeg解码的基本流程详解
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(一)
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(一)
198 0
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(一)
|
编解码 内存技术 容器
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(二)
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(二)
211 0
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(二)
|
数据采集 传感器 编解码
【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )(一)
【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )(一))
195 0
【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )(一)
|
数据采集 Ubuntu Shell
【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )(二)
【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )(二)
265 0
【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )(二)
|
数据采集 编解码 Ubuntu
FFMPEG音视频开发: Linux下采集音频(alsa-lib库)与视频(V4L2框架)实时同步编码保存为MP4文件(视频录制)
FFMPEG音视频开发: Linux下采集音频(alsa-lib库)与视频(V4L2框架)实时同步编码保存为MP4文件(视频录制)
820 0
FFMPEG音视频开发: Linux下采集音频(alsa-lib库)与视频(V4L2框架)实时同步编码保存为MP4文件(视频录制)