瑞芯微mpph264enc输出FHD的尺寸故障追踪

34 篇文章 1 订阅
23 篇文章 0 订阅

我们在尝试瑞芯微的mppencoder时,遇到了麻烦,输出的视频会有一条边。咨询了瑞芯微的工作人员,它们回复说,本质的原因是:

the mpp requires 1920x1080 with 16-aligned strides.(for example 1920x1088)

事实上,前期引入硬件解码器,来企图解决乱码或者绿边时,我们确实是按照这个思路走的。并且在瑞芯微自己的解码测试例程里,我们确实看到了与最终问题解决相关的这一组孪生变量:

    width    = mpp_frame_get_width(frame);
    height   = mpp_frame_get_height(frame);
    h_stride = mpp_frame_get_hor_stride(frame);
    v_stride = mpp_frame_get_ver_stride(frame);

在编码时,我们的目标是要组装正确的符合h_stride, v_stride要求的数据帧,并且要能够把正确的width, height, h_stride, v_stride传入。

然后拿到了瑞芯微自己的编解码示例代码,在这里:

git clone https://github.com/rockchip-linux/mpp.git

对应的mpp_enc的示例代码(mpp_enc_test.c) 中,我们可以看一下这里的参数传递和相关的函数调用过程:

    // get paramter from cmd
    p->width        = cmd->width;
    p->height       = cmd->height;
    p->hor_stride   = (cmd->hor_stride) ? (cmd->hor_stride) :
                      (MPP_ALIGN(cmd->width, 16));
    p->ver_stride   = (cmd->ver_stride) ? (cmd->ver_stride) :
                      (MPP_ALIGN(cmd->height, 16));

这里有一个自动的stride计算过程,如果hor_stride是没有配置,是缺省的0,那么,有一个用来生成正确的,stride 的计算宏,就是在这里,1920x1080转变为1920*1088。

接下来的各个层级的调用中:

  1.  p->mdinfo_size  = (MPP_ALIGN(p->hor_stride, 64) >> 6) *
                          (MPP_ALIGN(p->ver_stride, 64) >> 6) * 32;
  2. p->cam_ctx = camera_source_init(cmd->file_input, 4, p->width, p->height, p->fmt);
  3.  switch (p->fmt & MPP_FRAME_FMT_MASK) {
        case MPP_FMT_YUV420SP:
        case MPP_FMT_YUV420P: {
            p->frame_size = MPP_ALIGN(p->hor_stride, 64) * MPP_ALIGN(p->ver_stride, 64) * 3 / 2;
        } break;
  4.    case MPP_FMT_RGBA8888 : {
            p->frame_size = MPP_ALIGN(p->hor_stride, 64) * MPP_ALIGN(p->ver_stride, 64);
        } break; //这段代码是错的。
    
    default: {
            p->frame_size = MPP_ALIGN(p->hor_stride, 64) * MPP_ALIGN(p->ver_stride, 64) * 4;
        } break;
  5.     if (MPP_FRAME_FMT_IS_FBC(p->fmt)) {
            if ((p->fmt & MPP_FRAME_FBC_MASK) == MPP_FRAME_FBC_AFBC_V1)
                p->header_size = MPP_ALIGN(MPP_ALIGN(p->width, 16) * MPP_ALIGN(p->height, 16) / 16, SZ_4K);
         framer里可能包含一个header.
  6.  mpp_enc_cfg_set_s32(cfg, "prep:width", p->width);
        mpp_enc_cfg_set_s32(cfg, "prep:height", p->height);
        mpp_enc_cfg_set_s32(cfg, "prep:hor_stride", p->hor_stride);
        mpp_enc_cfg_set_s32(cfg, "prep:ver_stride", p->ver_stride);
  7. 视频编码, 组装frame的过程:
    void *buf = mpp_buffer_get_ptr(p->frm_buf);
    ret = read_image(buf, p->fp_input, p->width, p->height,
                                 p->hor_stride, p->ver_stride, p->fmt);
    ret = fill_image(buf, p->width, p->height, p->hor_stride,
                                     p->ver_stride, p->fmt, p->frame_count);
    ret = mpp_frame_init(&frame);
    mpp_frame_set_width(frame, p->width);
            mpp_frame_set_height(frame, p->height);
            mpp_frame_set_hor_stride(frame, p->hor_stride);
            mpp_frame_set_ver_stride(frame, p->ver_stride);
            mpp_frame_set_fmt(frame, p->fmt);
    if (p->fp_input && feof(p->fp_input))
                mpp_frame_set_buffer(frame, NULL);
            else if (cam_buf)
                mpp_frame_set_buffer(frame, cam_buf);
            else
                mpp_frame_set_buffer(frame, p->frm_buf)

从上面代码,可以看到mppencording,手搓C代码时,需要传入width, height,h_stride, v_stride,然后用这些参数来初始化与decode对应的frame结构。

接下来,因为瑞芯微在与gstreamer集成时,也必须在gstreamer相应接口中,传入这些变量,我们先看看瑞芯微的提供的gstreamer的encoding接口规约:

>gst-inspect-1.0 | grep encoder 
可以打印系统所有的编码器

然后使用:
>gst_inspect-1.0 mpph264enc
可以打印h264硬件编码器的信息,我们可以在打印的信息里看到它实现的gstreamer接口:

下一步我们到GstMppH264Enc的地方,看看有没有相应的参数传递接口。然后看看如何在gstreamer配置里添加这些我们已知的配置(width, height, h_stride, v_stride) 

查看git的代码,可以看到:stride是计算来的:

#define GST_MPP_VIDEO_INFO_HSTRIDE(i) GST_VIDEO_INFO_PLANE_STRIDE(i, 0)
#define GST_MPP_VIDEO_INFO_VSTRIDE(i) \
    (GST_VIDEO_INFO_N_PLANES(i) == 1 ? GST_VIDEO_INFO_HEIGHT(i) : \
    (gint) (GST_VIDEO_INFO_PLANE_OFFSET(i, 1) / GST_MPP_VIDEO_INFO_HSTRIDE(i)))

现在的逻辑归结到:  

 GST_VIDEO_INFO_PLANE_STRIDE

这个宏隶属gstreamer,它是直接取了GstVideoInfo的一个变量,这个定义在这里:

GstVideoInfo

它只是简单的取了(GstVideoInfo*)指向对象的stride对象的值。并且,HStride是直接取了之前设置的值,所以,设置的位置还要靠前。继续按照stride来搜索:

前一个的源头,明显还是同样的源头,后一个源码中展开:

  mpp_enc_cfg_set_s32 (self->mpp_cfg, "prep:width", width);
  mpp_enc_cfg_set_s32 (self->mpp_cfg, "prep:height", height);
  mpp_enc_cfg_set_s32 (self->mpp_cfg, "prep:hor_stride",
      GST_MPP_VIDEO_INFO_HSTRIDE (info));
  mpp_enc_cfg_set_s32 (self->mpp_cfg, "prep:ver_stride",
      GST_MPP_VIDEO_INFO_VSTRIDE (info));

仍然是同一个源头。注意目前为止,我们所搜索的数据结构都是源自GstVideoInfo* info。

我们需要定位到它的初始化的部分。这次代码搜索GstVideoInfo*


我们需要在这组搜索结果中,遍历找到那个赋值的位置。

暂时的结论:

GstVideoInfo * stride设置的位置居然不在这里。所以问题的答案在gstreamer的在线文档里,因为GstVideoInfo* info是由前级的缓冲区设置预设好,传过来的。

=>查找GstVideoInfo* info的修改接口。只要能改掉hstride就能达到目的。然后一个便捷的做法,就是直接改mpph264enc,但是不到万不得已,我不会这么做。

查询chatgpt,问是否可以在进入encorder前修改GstVideoInfo*,答案是可以的:

》》》我可以定义一个自定义插件,得到GstVideoInfo对象,然后修改它,然后重新向后传输?

是的,你可以通过自定义 GStreamer 插件来获取和修改 GstVideoInfo 对象,然后将修改后的信息传递到管道的下游。要实现这一点,你可以继承自 Gst.BaseTransform 或 Gst.BaseFilter,并在插件中处理视频数据。以下是如何在 Python 中实现这一点的步骤和示例代码。

自定义插件的步骤

  1. 创建插件类: 创建一个自定义插件类,继承自 Gst.BaseTransform 或 Gst.BaseFilter,并实现必要的方法。

  2. 获取和修改 GstVideoInfo: 在 transform_frame 方法中,获取帧的 GstVideoInfo 对象,修改其属性。

  3. 重新传输修改后的帧: 将修改后的帧传输到下游元素。

示例代码

下面是一个 Python 示例,展示了如何创建一个自定义插件来获取、修改 GstVideoInfo 对象,并将修改后的帧传递到下游。

import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject

Gst.init(None)

class VideoInfoTransform(Gst.BaseTransform):
    def __init__(self):
        super(VideoInfoTransform, self).__init__()

    def transform_frame(self, frame):
        # 获取视频信息
        video_info = Gst.Video.VideoInfo()
        if not video_info.from_caps(frame.get_caps()):
            return Gst.FlowReturn.ERROR

        # 修改视频信息(例如调整分辨率)
        video_info.width = 640
        video_info.height = 480
        
        # 更新帧的属性
        frame.set_caps(Gst.Caps.from_string(video_info.to_caps_string()))

        # 继续处理其他帧数据
        return Gst.FlowReturn.OK

# 创建 GStreamer 管道
pipeline = Gst.parse_launch('videotestsrc ! videoconvert ! video/x-raw ! videoinfo_transform ! x264enc ! rtspclientsink location=rtsp://localhost:8554/test')

# 获取管道中的自定义插件元素
videoinfo_transform = pipeline.get_by_name('videoinfo_transform')
if not videoinfo_transform:
    videoinfo_transform = VideoInfoTransform()
    pipeline.add(videoinfo_transform)

# 启动管道
pipeline.set_state(Gst.State.PLAYING)

# 运行主循环
loop = GObject.MainLoop()
loop.run()

解释

  1. 插件创建: VideoInfoTransform 继承自 Gst.BaseTransform。在 transform_frame 方法中,你可以获取和修改 GstVideoInfo 对象。

  2. 修改视频信息: 你可以使用 Gst.Video.VideoInfo() 来获取视频帧的详细信息,并根据需要修改属性。然后,使用 Gst.Caps 将修改后的信息应用到帧上。

  3. 管道配置: 使用 Gst.parse_launch 创建 GStreamer 管道,并将自定义插件添加到管道中。

  4. 运行管道: 设置管道状态为 Gst.State.PLAYING 并运行主循环。

注意事项

  • 调试和测试: 自定义插件可能需要调试和测试,以确保它在实际数据流中按预期工作。

  • 性能考虑: 修改视频帧可能会影响性能,尤其是在高分辨率或高帧率的情况下。需要确保插件的实现效率。

通过这种方式,你可以在 GStreamer 管道中插入自定义处理步骤,调整 GstVideoInfo 并在视频数据流向编码器之前进行所需的修改。

我试试这种方式,如果可能,我觉得这个方法既可以适用于encorder,也可以适用于decorder,把这个width,height 与stride的不一致校准过来。

本文终结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子正

thanks, bro...

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值