telechips平台gstreamer实现pango字幕混入视频功能

本文介绍在Telechips897x平台上的GStreamer中,如何将Pango字幕混入NV12_TC格式的视频数据。文章详细记录了修改代码的过程,包括处理自定义格式、映射视频帧数据及实现字幕混入等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

gstreamer原生的代码就是支持pango字幕混入视频数据的功能的。而且在使用软件解码时,默认也是把pango字幕混入视频一起显示的。

但是,在telechips 897x 平台上,telechips针对自己硬件解码器输出的 video/x-raw-tc(NV12_TC) 格式,在输出字幕这一块,做了自己的处理。
telechips的处理方式是:
首先,映射了一块物理内存,用于存放图形化之后的字幕数据(ARGB格式)。
然后,在NV12_TC格式的的GstBuffer数据中设置了字幕数据的地址,然后做了标记。
之后,在v4l2sink中把字幕数据设置到v4l2的 vbuffer.m.plains[1] 中传递到v4l2驱动中。
最后,在驱动中会使用单独的视频通道来渲染ARGB的字幕数据,并显示在视频数据的上层。
这种方式的话,性能方面肯定是没啥问题了。但是为了在车机平台上实现后排视频播放功能,重新配置了视频通道之后,资源不够用了,无法显示字幕了。

没办法啊,没有资源,但是功能还是要的。
所以想到自己修改代码,还是通过把字幕混入到视频数据中的方式来显示pango字幕。这样做的另一个好处是,后排视频也自动带上字幕了。(当然性能就…)
因为gstreamer 原本就是支持字幕混入视频数据的。所以主要修改的思路就是把对 NV12_TC 格式的处理,对接到原先gstreamer的处理流程中。

简单记录一下自己的修改流程:
经过对相关代码的研究,发现NV12_TC的格式是一个1024字节的自定义结构。其中特定字节的值,分别对应着解码后raw yuv数据的y,u,v物理地址和对应映射地址,stride值,等等信息。
其中,y,u,v的地址为物理地址,是在硬件解码器中映射的(这一块代码看不到)

首先,找到 gstbasetextoverlay.c 这个是 textoverlay element 的实现文件。这个 element 有2个sink pad,分别接收 video 和 subtitle 数据。 然后通过一个 src pad 发出去。
先进行必要的修改,让字幕处理流程恢复到咱们 gstreamer 原本的处理流程上。
找到 tcc_subtitle_is_tcc_mime_type() 这个函数,所有telechips的修改都围绕着这个判断。当视频数据为 video/x-tc-raw 时,都会走这个流程。

#define TCC_VIDEO_MIME_TYPE_STR "video/x-tc-raw"
static void tcc_subtitle_check_is_tc_mime_type_from_caps(GstCaps * caps) {
#if 0
    if (strstr((gst_caps_to_string(caps)), TCC_VIDEO_MIME_TYPE_STR) != NULL) {
        g_subtitle_info.is_tc_mimetype = TRUE;
    } else {
        g_subtitle_info.is_tc_mimetype = FALSE;
    }
#endif
  g_subtitle_info.is_tc_mimetype = FALSE;  // 方式比较粗暴
}

static gboolean tcc_subtitle_is_tcc_mime_type() {
    return g_subtitle_info.is_tc_mimetype;
}

当然对应的代码都要谨慎的处理一下,这一块代码修改后,再来找gstreamer的流程里混入字幕的地方。

在这里, gst_base_text_overlay_push_frame(),这个函数中使用 gst_video_overlay_composition_blend() 来把字幕混入到视频帧中。
第一个参数 GstVideoOverlayComposition 中就是图形化之后的字幕数据,第二参数是Gstbuffer通过gst_video_frame_map()得到的 GstVideoFrame 类型帧数据。
这个函数最终会调用 gst_video_blend()来执行混入功能。混入功能是针对两个GstVideoFrame 类型的数据进行操作的。

在回来看 gst_video_frame_map() 函数,这个函数根据视频帧的格式类型,把帧数据中的 YUV地址,stride值 (以YUV类型为例)映射到 GstVideoFrame 结构中。
因为咱们的 NV12_TC 是一个telechips的自定义格式,所以需要咱们自己修改一下才进行正确的映射。
GstVideoFrame.data[0] 是 Y 分量的地址
GstVideoFrame.data[1] 是 CbCr分量的地址
GstVideoFrame.info.stride[0] 是 Y 分量的 stride值
GstVideoFrame.info.stride[1] 是 CbCr分量的 stride 值
这几个值是需要咱们对应的填好的。填好之后,gst_video_blend() 才能进行正确的处理。

gboolean gst_video_frame_map_id(GstVideoFrame *frame, GstVideoInfo *info, GstBuffer *buffer, gint id, GstMapFlags flags)
{
    //...
    else
    {
        /* no metadata, we really need to have the metadata when the id is
        * specified. */
        if(id != -1)
            goto no_metadata;

        frame->id = id;
        frame->flags = 0;

        if(!gst_buffer_map(buffer, &frame->map[0], flags))
            goto map_failed;

        /* do some sanity checks */
    if( GST_VIDEO_INFO_FORMAT(info) != 54 /*NV12_TC*/)   // 因为NV12_TC是固定1024字节的,这里我们要针对修改一下
    {
        if(frame->map[0].size < info->size)
        goto invalid_size;
    }

    /* set up pointers */
    if( GST_VIDEO_INFO_FORMAT(info) != 54 /*NV12_TC*/)    // 帧数据的映射,也要特殊处理一下
    {
      for(i = 0; i < info->finfo->n_planes; i++)
      {
        frame->data[i] = frame->map[0].data + info->offset[i];
        GST_ERROR("frame->data[%d]: 0x%x", i, frame->data[i]);
      }
    }
    else
    {
      char* ptr = frame->map[0].data;
      char* y_addr;
      char* u_addr;
      char* v_addr;
      char* yuv_addr;
      guint stridey = 0;
      guint stridecbcr = 0;
      memcpy(&y_addr, (ptr + 4), 4);
      memcpy(&u_addr, (ptr + 8), 4);
      memcpy(&v_addr, (ptr + 12), 4);
      memcpy(&yuv_addr, (ptr + 16), 4);    // user_Y_addr
      memcpy(&stridey, (ptr + 76), 4);          // stride y
      memcpy(&stridecbcr, (ptr + 80), 4);        // stride cbcr
      guint y_len = u_addr - y_addr;
      guint uv_len = (v_addr - u_addr)*2;
      guint raw_size = (y_len+uv_len);
      frame->data[0] = yuv_addr;
      frame->data[1] = yuv_addr + y_len;
      frame->info.stride[0] = stridey;
      frame->info.stride[1] = stridecbcr;
    }
  }
   //...
}

最后,在执行实际混入操作时,需要对应格式绑定的 unpack 和 pack 函数。这两个函数是解包和打包 单行指定格式数据。在执行混入时,和先解包指定原数据的指定行,然后解包混入数据指定行,然后matrix函数混合数据,最后打包混合后的数据到指定的行。
我发现,这个在定义 NV12_TC 格式类型的时候,并没有定义这两个函数。
在 video-format.c 中,按照格式定义的方式,直接加一下就好了,实现的话,直接参考 标准的NV12就好了。

#define PACK_NV12_TC GST_VIDEO_FORMAT_AYUV, unpack_NV12_TC, 1, pack_NV12_TC
void unpack_NV12_TC (const GstVideoFormatInfo * info, GstVideoPackFlags flags,
    gpointer dest, const gpointer data[GST_VIDEO_MAX_PLANES],
    const gint stride[GST_VIDEO_MAX_PLANES], gint x, gint y, gint width){
    //...
}
void pack_NV12_TC (const GstVideoFormatInfo * info, GstVideoPackFlags flags,
    const gpointer src, gint sstride, gpointer data[GST_VIDEO_MAX_PLANES],
    const gint stride[GST_VIDEO_MAX_PLANES], GstVideoChromaSite chroma_site,
    gint y, gint width){
    //...
}

static VideoFormat formats[] = {
    //...
    { GST_MAKE_FOURCC('T', 'C', 'V', '0'), { GST_VIDEO_FORMAT_I420_TC, "I420_TC", "raw video", GST_VIDEO_FORMAT_FLAG_YUV|GST_VIDEO_FORMAT_FLAG_ADDRESS, DPTH888, PSTR111, PLANE0, OFFS0 } }, 
    { GST_MAKE_FOURCC('T', 'C', 'V', '1'), { GST_VIDEO_FORMAT_NV12_TC, "NV12_TC", "raw video", GST_VIDEO_FORMAT_FLAG_YUV|GST_VIDEO_FORMAT_FLAG_ADDRESS, DPTH888, PSTR122, PLANE0, OFFS0 , SUB420, PACK_NV12_TC  /*这里增加 unpack和pack 函数*/ }} ,
};

(这里因为,我之前已经在gst_video_frame_map()中做了NV12_TC格式的处理,当帧数据映射到GstVideoFrame 时,已经可以看作是标准的nv12格式了,所以这里我们直接用nv12的pack和unpack函数就好了)

修改过这些之后,字幕数据混入其实已经没问题了,播放一下试试看~
字幕显示错误

嗯…这个…
追踪了一下问题,发现在混入字幕后,解码器输出的帧数据出问题了…
想了一下,视频是h264编码的,应该是解码后的参考帧数据被我改了,硬件解码器输出的数据,应该不能直接改,后续解码还需要用到最为参考。

也尝试了一些方法,最终方案,还是把解码器输出的数据做了拷贝。(只拷贝哪些需要混入字幕的帧数据,在新的buf里进行混入,并送入v4l2sink进行渲染)
空间的话,正好就利用了telechips原先subtitle方案中给subtitle预留的那块物理内存空间,有16M(2个1080P的RGB格式数据)。
nv12格式1080P的数据量是3M,16M的话基本也够用了。

代码实现的话:
就是初始化的时候,把这快subtitle的内存按照当前的图像size划分成多块,用于循环利用。
然后在有需要混入字幕的视频数据来到textoverlay时,先拷贝硬件解码输出的raw video到subtitle的内存空间里,然后混入字幕并标记一下这个块内存已使用,修改原先GstBuffer中的对应地址值。
最后在v4l2驱动dqbuf之后,把这一块subtitle的空间标记一下重新可用即可。

涉及的一些问题:

  1. basesink中做同步的时候是有可能直接drop buffer的,所以如果包含字幕的buffer被直接drop掉了,那subtitle的内存标记也要处理,因为这块空间是循环利用的,不处理就对应不上了。
  2. 毕竟软件混入字幕效果还是可能存在问题的,如果分辨率较高,或者字幕数据区域较大的话,效率跟不上,同步的时候就会丢帧,导致播放卡顿。处理方式的话,pack函数尽量用orc加速的那个函数。在混入字幕和同步的地方也可以做一些策略,来保证播放尽量的流畅。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值