Jetson视频解码

本文详细介绍了NVIDIAJetson系列的JetsonMultimediaAPI,特别是其在视频解码方面的应用,包括使用v4l2框架的过程,以及如何利用GPU硬件加速实现高效处理。文章还提到JetPackSDK提供的示例和支持,使得开发者能快速上手开发基于Jetson的人工智能和计算机视觉应用。
摘要由CSDN通过智能技术生成

一、Jetson介绍

        Jetson 是由 NVIDIA 开发的嵌入式计算平台系列,旨在提供高性能的人工智能(AI)计算能力,适用于嵌入式系统、机器人、自动驾驶汽车和其他边缘计算应用。Jetson 平台通常集成了 NVIDIA 的 GPU 和其他硬件加速器,能够在低功耗的环境下执行复杂的深度学习和计算任务。
        Jetson Multimedia API(Jetson 多媒体 API)是 NVIDIA 提供的一组软件工具和接口,用于在 Jetson 平台上进行视频和图像处理。这个 API 包括了对视频解码、编码、图像处理和视觉算法的支持。通过 Jetson Multimedia API,开发人员可以轻松地实现视频流的采集、处理和显示,以及应用于计算机视觉的功能,比如对象检测、目标跟踪和人脸识别等。
        Jetson Multimedia API 提供了丰富的功能,使开发者能够利用 Jetson 平台的硬件加速器(如 GPU 和硬件编解码器)来实现高效的视频处理和计算机视觉任务。这包括基于硬件加速的视频解码(如 H.264、H.265 等格式)、编码、图像处理和视觉算法。Jetson Multimedia API 通常与 JetPack SDK 一起发布,提供了一系列的示例代码和开发工具,帮助开发者快速上手和开发应用。

二、Jetson视频解码

        本文主要对Jetson的视频解码流程进行详细的介绍,文中代码对应的Jetpack版本5.0.2中的测试代码,代码路径:/usr/src/jetson_multimedia_api/samples/02_video_dec_cuda/。了解了02_video_dec_cuda的解码流程,samples中的其他解码demo就都理解了,过程都是类似的。

        Jetson实际上是使用v4l2框架进行的视频编解码、Jetson Multimedia API就是对v4l2编解码的封装,所以学习Jetson Multimedia API之前要先了解v4l2的编解码流程,v4l2的解码流程可以参考我的另外一篇文章:v4l2视频解码 。

         Jetson解码流程(02_video_dec_cuda)如下图所示:

        下面对解码过程进行详细的说明:

1、打开解码器

ctx.dec = NvVideoDecoder::createVideoDecoder("dec0");

        打开解码器实际就是用open()函数打开解码设备,类似于open()打开/dev/video0。

2、订阅分辨率变化事件

 ret = ctx.dec->subscribeEvent(V4L2_EVENT_RESOLUTION_CHANGE, 0, 0);

        这一步的目的是让dec_capture_loop_fcn线程监听到分辨率变化事件之后设置和分配输出缓冲区队列,之后从输出队列中获取解码后的视频,以及解码过程中分辨率发生变化之后重新配置输出队列。

3、设置输入队列格式

ret = ctx.dec->setOutputPlaneFormat(ctx.decoder_pixfmt, CHUNK_SIZE);

        设置输入缓冲区队列格式,H264:V4L2_PIX_FMT_H264 H265:V4L2_PIX_FMT_H265 ,OutputPlane中的缓冲区类型对应v4l2中的type就是V4L2_BUF_TYPE_VIDEO_OUTPUT。

4、设置数据输入格式

if (ctx.input_nalu)
{
    nalu_parse_buffer = new char[CHUNK_SIZE];
    ret = ctx.dec->setFrameInputMode(0);
    TEST_ERROR(ret < 0,
            "Error in decoder setFrameInputMode", cleanup);
}

        如果输入的数据是NALU,则setFrameInputMode参数传入0,否则传入1。

5、请求分配输入缓冲区

ret = ctx.dec->output_plane.setupPlane(V4L2_MEMORY_MMAP, 10, true, false);

        请求分配输入缓冲区(v4l2_requestbuffers),type为V4L2_MEMORY_MMAP表示内存映射缓冲区。

6、开始解码

ret = ctx.dec->output_plane.setStreamStatus(true);

        就是调用ioctl()开启解码,请求类型为VIDIOC_STREAMON。

7、创建解码数据接收线程

pthread_create(&ctx.dec_capture_loop, NULL, dec_capture_loop_fcn, &ctx); 

        创建解码数据接收线程,dec_capture_loop_fcn主要是从输出队列中获取解码后的视频。

8、查询输入队列缓冲buffer信息

while (!eos && !ctx.got_error && !ctx.dec->isInError() &&
            i < ctx.dec->output_plane.getNumBuffers())
{
	struct v4l2_buffer v4l2_buf;
	struct v4l2_plane planes[MAX_PLANES];
	NvBuffer *buffer;

	memset(&v4l2_buf, 0, sizeof(v4l2_buf));
	memset(planes, 0, sizeof(planes));

	buffer = ctx.dec->output_plane.getNthBuffer(i);
	if (ctx.input_nalu)
	{
		read_decoder_input_nalu(ctx.in_file, buffer, nalu_parse_buffer,
				CHUNK_SIZE);
	}
	else
	{
		read_decoder_input_chunk(ctx.in_file, buffer);
	}

	v4l2_buf.index = i;
	v4l2_buf.m.planes = planes;
	v4l2_buf.m.planes[0].bytesused = buffer->planes[0].bytesused;

	/* It is necessary to queue an empty buffer to signal EOS to the decoder
	   i.e. set v4l2_buf.m.planes[0].bytesused = 0 and queue the buffer */
	ret = ctx.dec->output_plane.qBuffer(v4l2_buf, NULL);
	if (ret < 0)
	{
		cerr << "Error Qing buffer at output plane" << endl;
		abort(&ctx);
		break;
	}
	if (v4l2_buf.m.planes[0].bytesused == 0)
	{
		eos = true;
		cout << "Input file read complete" << endl;
		break;
	}
	i++;
}

        这里的目的是询缓冲buffer信息,填充 H264/H265 NALU到buffer中,并把buffer送入解码器输入队列(output_plane)。

9、不断地向解码器送入H264/H265数据

while (!eos && !ctx.got_error && !ctx.dec->isInError())
{
	struct v4l2_buffer v4l2_buf;
	struct v4l2_plane planes[MAX_PLANES];
	NvBuffer *buffer;

	memset(&v4l2_buf, 0, sizeof(v4l2_buf));
	memset(planes, 0, sizeof(planes));

	v4l2_buf.m.planes = planes;

	ret = ctx.dec->output_plane.dqBuffer(v4l2_buf, &buffer, NULL, -1);
	if (ret < 0)
	{
		cerr << "Error DQing buffer at output plane" << endl;
		abort(&ctx);
		break;
	}

	if (ctx.input_nalu)
	{
		read_decoder_input_nalu(ctx.in_file, buffer, nalu_parse_buffer,
				CHUNK_SIZE);
	}
	else
	{
		read_decoder_input_chunk(ctx.in_file, buffer);
	}
	v4l2_buf.m.planes[0].bytesused = buffer->planes[0].bytesused;
	ret = ctx.dec->output_plane.qBuffer(v4l2_buf, NULL);
	if (ret < 0)
	{
		cerr << "Error Qing buffer at output plane" << endl;
		abort(&ctx);
		break;
	}
	if (v4l2_buf.m.planes[0].bytesused == 0)
	{
		eos = true;
		cout << "Input file read complete" << endl;
		break;
	}
}

        这里是不断地从输入队列output_plane中获取缓冲buffer、填充H264/H265 NALU、把缓冲buffer重新送入输入队列,这里就是开启正式的解码循环。

10、资源释放

        关闭之前需要把输入队列output_plane中的缓冲buffer全部从队列中取出,目的是解除用户进程和内核的内存关联关系,代码如下:

while (ctx.dec->output_plane.getNumQueuedBuffers() > 0 &&
           !ctx.got_error && !ctx.dec->isInError())
{
	struct v4l2_buffer v4l2_buf;
	struct v4l2_plane planes[MAX_PLANES];

	memset(&v4l2_buf, 0, sizeof(v4l2_buf));
	memset(planes, 0, sizeof(planes));

	v4l2_buf.m.planes = planes;
	ret = ctx.dec->output_plane.dqBuffer(v4l2_buf, NULL, NULL, -1);
	if (ret < 0)
	{
		cerr << "Error DQing buffer at output plane" << endl;
		abort(&ctx);
		break;
	}
}

        之后就是一些资源释放,close掉解码器等。

11、dec_capture_loop_fcn线程

        dec_capture_loop_fcn首先监听v4l2事件,等待解码器准备解码输出。代码如下:

ret = dec->dqEvent(ev, 1000);

        之后查询、设置解码器输出缓冲区、并把缓冲buffer送入输出队列capture_plane,输出缓冲区类型对应v4l2中的type就是V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE。具体过程和输入缓冲区队列output_plane类似,query_and_set_capture函数实现了该过程,这里就不具体展开了,理解v4l2解码过程,这个也就很好理解了。函数调用代码如下:

query_and_set_capture(ctx);

        之后while循环从解码器输出队列中获取准备好的缓冲buffer(已解码的视频)、从buffer中读取视频帧进行处理、把buffer重新送入解码器输出队列中。代码如下,由于代码较多,这里只给出关键代码:

while (!(ctx->got_error || dec->isInError() || ctx->got_eos))
{
	NvBuffer *dec_buffer;

	/* Check for resolution change again */
	ret = dec->dqEvent(ev, false);
	if (ret == 0)
	{
		switch (ev.type)
		{
			case V4L2_EVENT_RESOLUTION_CHANGE:
				query_and_set_capture(ctx); // 分辨率发生变化后重新设置输出队列
				continue;
		}
	}

	 /* Decoder capture loop */
	while (1)
	{
		struct v4l2_buffer v4l2_buf;
		struct v4l2_plane planes[MAX_PLANES];

		memset(&v4l2_buf, 0, sizeof(v4l2_buf));
		memset(planes, 0, sizeof(planes));
		v4l2_buf.m.planes = planes;

		// 获取缓冲buffer
		if (dec->capture_plane.dqBuffer(v4l2_buf, &dec_buffer, NULL, 0))
		{
			if (errno == EAGAIN)
			{
				usleep(1000);
			}
			else
			{
				abort(ctx);
				cerr << "Error while calling dequeue at capture plane" <<
					endl;
			}
			break;
		}
        
        /* 解码后的数据就放在v4l2_buf中 */
		/* ... */

		// 缓冲buffer重新入队
		if (dec->capture_plane.qBuffer(v4l2_buf, NULL) < 0)
		{
			abort(ctx);
			cerr <<
				"Error while queueing buffer at decoder capture plane"
				<< endl;
			break;
		}
	}
}

三、总结

        Jetson Multimedia API偏向底层,使用v4l2框架,比较晦涩。如果理解了v4l2的视频采集、视频编解码流程,Jetson Multimedia API还是比较好理解的。关于v4l2可以看我另外两个文章:v4l2采集视频 、v4l2视频解码 

        

         我的开源:        

         1、Nvidia视频硬解码、渲染、软/硬编码并写入MP4文件。项目地址:https://github.com/BreakingY/Nvidia-Video-Codec
        2、Jetson Jetpack5.x视频编解码。项目地址:https://github.com/BreakingY/jetpack-dec-enc
        3、ffmpeg音视频(H264/H265/AAC)封装、解封装、编解码pipeline,支持NVIDIA硬编解码。项目地址:https://github.com/BreakingY/FFmpeg-Media-Codec-Pipeline
        4、simple rtsp server,小而高效的rtsp服务器,支持H264、H265、AAC、PCMA;支持TCP、UDP;支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-server

        5、simple rtp client,rtsp客户端,支持TCP、UDP、H264、H265、AAC、PCMA,支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-client

  • 36
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值