6.7.5. 设计说明
6.7.5.1. 源码说明
本模块源代码在内核目录 source/zx/gst1-plugins-aic 下,目录结构如下:
source/zx/gst1-plugins-aic
├── gstaicfb.c // fb_sink 插件
├── gstaicfb.h
├── gstfbsink.c
├── gstfbsink.h
├── gstmppallocator.c // mpp_frame 内存申请模块
├── gstmppallocator.h
├── gstplugin.c
├── gstvedec.c // ve 解码插件
├── gstvedec.h
└── meson.build
6.7.5.2. 模块架构
Gstreamer 视频播放软件框图如下所示:
可分为 3 个模块:
demux 模块,文件解封装模块,实现 ts、MP4、mkv、flv 等文件格式解析;
decoder 模块,视频解码模块,完成视频解码功能;
sink 模块,视频显示模块,完成视频显示功能;
Gstreamer 已经实现了 3 个模块的调用流程并提供了模块接口,开发者只需按照 Gstreamer 定义的接口来实现一个插件就可以完成视频播放的功能。
ZX 平台提供了视频解码加速硬件模块(VE)和图像显示硬件模块(DE),为了在 Gstreamer 软件上发挥我们的硬件优势, 我们定义了 2 个插件:gstvedec 和 gstfbsink,分别作为 VE 和 DE 的 Gstreamer 插件。
同时,为了实现 VE 和 DE 共享 buffer,GstVideoDecoder 和 GstVideoSink 之间传递的 GstBuffer 是由自定义的 gstmppallocator 模块进行内存管理。
6.7.5.2.1. gstmppallocator 设计
gstmppallocator 模块主要用于管理视频帧 buffer,实现 gstvedec 和 gstfbsink 的 buffer 共享。
GstMppAllocator 继承于 GstAllocator 对象,用于管理分配和释放内存;分配的内存 GstMppMemory 则继承于 GstMemory 对象。
Gstreamer 中大多数对象都是通过引用计数进行管理,该对象在某个地方使用时,对其引用计数进行加 1 操作, 不使用时再将引用计数减 1,如果最终引用计数为 0,则表示该对象不会再被使用,应该释放该对象。 GstMppMemory 也遵循该原则。
主要功能包括以下几点:
通过 gstmppallocator 模块申请的 buffer 只能存放 mpp_frame 结构体,它的长度也只能是 mpp_frame 结构体的大小;
申请 buffer 的调用只会在 gstvedec 模块中,每次获取到一个解码帧后调用该接口;
当 buffer 的引用计数为 0 时,会触发 buffer 释放接口的调用,因此释放 buffer 可能发生在 gstfbsink 模块或其它模块中(比如丢帧时释放 buffer 是在 GstVideoDecoder 中调用);
调用释放 buffer 时,需要将 buffer 对应的 mpp_frame 还给解码器;
关键实现代码如下:
static void gst_mpp_allocator_free (GstAllocator * alloc, GstMemory * mem)
{
GstMppAllocator *allocator = (GstMppAllocator*) alloc;
GstMppMemory *mpp_mem = (GstMppMemory *)mem;
struct mpp_frame *mframe = mpp_mem->frame;
mpp_decoder_put_frame(allocator->decoder, mframe);
GST_DEBUG_OBJECT(allocator, "return mpp_frame (%d)", mframe->id);
free(mpp_mem);
}
static GstMemory *gst_mpp_allocator_alloc (GstAllocator *alloc, gsize size,
GstAllocationParams * params)
{
int slice_size = 0;
guint8 *data;
GstMppMemory *mem = NULL;
GstMppAllocator *allocator = (GstMppAllocator*) alloc;
if (size != sizeof(struct mpp_frame)) {
GST_ERROR_OBJECT(allocator, "memory size(%lu) is not right", size);
return NULL;
}
slice_size = sizeof(GstMppMemory) + size;
mem = (GstMppMemory*)malloc(slice_size);
data = (guint8 *) mem + sizeof (GstMppMemory);
mem->frame = (struct mpp_frame*)data;
gst_memory_init (GST_MEMORY_CAST (mem),
0, alloc, NULL, size, 0, 0, size);
GST_DEBUG_OBJECT(allocator, "alloc mpp size(%lu), ref_count: %d",
size, GST_MINI_OBJECT_REFCOUNT_VALUE(mem));
return (GstMemory*)mem;
}
6.7.5.2.2. gstvedec 插件设计
gstvedec 插件是对 mpp_decoder 接口的二次封装。mpp_decoder 接口实现 3 个功能:
把视频码流数据送给解码器
调用硬件解码视频码流数据,并输出视频帧
从解码器取视频帧
gstvedec 主要功能是使用 mpp_decoder 提供的这 3 个功能对接 GstVideoDecoder 的接口。
6.7.5.2.2.1. GstVideoDecoder 的接口说明
GstVideoDecoder 最重要的两个接口函数如下:
gboolean (*set_format) (GstVideoDecoder *decoder, GstVideoCodecState * state);
GstFlowReturn (*handle_frame) (GstVideoDecoder *decoder, GstVideoCodecFrame *frame);
set_format 主要用于传递解码器所需的元信息,包括 extradata (MP4等帧格式文件会传递 extradata,而 TS 等流格式文件不传递)。
handle_frame 主要用于传递视频码流数据。
6.7.5.2.2.2. 对接设计
对应于 mpp_decoder 的 3 个功能,3 个功能分别在不同线程中调用,实现方式如下:
1)GstVideoDecoder 的 handle_frame 接口主要实现把码流数据传递到 mpp_decoder;
int ret = mpp_decoder_get_packet(dec->mpp_dec, &packet, input_minfo.size);
packet.size = input_minfo.size;
memcpy(packet.data, input_minfo.data, input_minfo.size);
packet.pts = in_frame->pts;
mpp_decoder_put_packet(dec->mpp_dec, &packet);
2)gstvedec 初始化时创建一个解码线程,该线程只完成解码功能;
3)gstvedec 的 srcpad 上创建一个显示线程,该线程的任务有两个:
通过 GstMppAllocator 申请一个 buffer,用于传递视频帧信息;
从 mpp_decoder 中获取一帧解码后的 mpp_frame,并把 mpp_frame 信息拷贝到 buffer(注意:此时不能把 mpp_frame 还给解码器);
调用 gst_video_decoder_finish_frame 将 buffer 送到 sink 中。
6.7.5.2.3. gstfbsink 插件设计
gstfbsink 插件是对 Linux 标准的 framebuffer 接口的二次封装,对接 GstVideoSink 接口。
GstVideoSink 最重要的接口函数 render 如下,主要用于显示一帧视频数据。
GstFlowReturn (*render)(GstBaseSink * bsink, GstBuffer * buffer);
gstfbsink 插件设计的关键点是:必须避免同一个视频帧 VE 和 DE 同时读写的情况,否则会出现画面异常问题。
设计方案:
将当前 buffer 引用计数加 1;
通过 framebuffer 接口显示当前视频帧;
将上一个 buffer 引用计数减 1(释放上一个buffer);
代码实现如下:
gst_memory_ref(mem);
gst_aicfb_render(fbsink->aicfb, &(mframe->buf), mframe->id);
if (fbsink->prev_mem != NULL) {
gst_memory_unref(fbsink->prev_mem);
}
fbsink->prev_mem = mem;