Jetson视频编码

学习Jetson视频编码之前,先看以下文章:

v4l2采集视频

v4l2视频解码

Jetson视频解码

        本文对Jetpack 5.0.2的03_video_cuda_enc编码流程进行详细介绍,代码路径:/usr/src/jetson_multimedia_api/samples/03_video_cuda_enc/。之前的文章介绍了Jetson视频解码,地址:Jetson视频解码 编码和解码除了具体定义不一样,过程基本相似。同样理解了03_video_cuda_enc编码流程,samples中的其他编码demo就都理解了,如下图所示,为Jetson视频编码流程(03_video_cuda_enc):

        下面对编码流程进行详细介绍:

1、打开编码器

ctx.enc = NvVideoEncoder::createVideoEncoder("enc0");

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

2、设置输出缓冲区

ctx.enc->setCapturePlaneFormat(ctx.encoder_pixfmt, ctx.width,ctx.height, 2 * 1024 * 1024);

        设置输出缓冲区格式,输出缓冲区类型对应v4l2的type就是V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,encoder_pixfmt->H264:V4L2_PIX_FMT_H264;H265:V4L2_PIX_FMT_H265。

3、设置输入缓冲区

ctx.enc->setOutputPlaneFormat(V4L2_PIX_FMT_YUV420M, ctx.width, ctx.height)

        设置输入缓冲区格式,输出缓冲区类型对应v4l2的type就是V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,输入数据格式为V4L2_PIX_FMT_YUV420M,就是YUV420P。

4、设置H264/H265编码参数

ret = ctx.enc->setBitrate(ctx.bitrate);
TEST_ERROR(ret < 0, "Could not set bitrate", cleanup);

if (ctx.encoder_pixfmt == V4L2_PIX_FMT_H264)
{
	ret = ctx.enc->setProfile(V4L2_MPEG_VIDEO_H264_PROFILE_HIGH);
}
else
{
	ret = ctx.enc->setProfile(V4L2_MPEG_VIDEO_H265_PROFILE_MAIN);
}
TEST_ERROR(ret < 0, "Could not set encoder profile", cleanup);

if (ctx.encoder_pixfmt == V4L2_PIX_FMT_H264)
{
	ret = ctx.enc->setLevel(V4L2_MPEG_VIDEO_H264_LEVEL_5_0);
	TEST_ERROR(ret < 0, "Could not set encoder level", cleanup);
}

ret = ctx.enc->setFrameRate(ctx.fps_n, ctx.fps_d);
TEST_ERROR(ret < 0, "Could not set framerate", cleanup);

        设置H264/H265编码格式,码率、Profile、Level、帧率等。

5、请求分配输入缓冲区

ctx.enc->output_plane.setupPlane(V4L2_MEMORY_MMAP, 10, true, false);

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

6、请求分配输出缓冲区

ctx.enc->capture_plane.setupPlane(V4L2_MEMORY_MMAP, 6, true, false);

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

7、编码器输入流开启

ctx.enc->output_plane.setStreamStatus(true);

        就是调用ioctl(),请求类型为VIDIOC_STREAMON。

8、编码器输出流开启

ctx.enc->capture_plane.setStreamStatus(true);

         就是调用ioctl(),请求类型为VIDIOC_STREAMON。

9、设置编码器输出回调函数

ctx.enc->capture_plane.setDQThreadCallback(encoder_capture_plane_dq_callback);

        这里是设置回调函数,后面会开启一个线程用于获取编码器输出缓冲buffer,之后就会调用这里设置的回调函数,回调函数的工作就是获取buffer中已编码好的视频。

10、开启输出线程

ctx.enc->capture_plane.startDQThread(&ctx);

        开启一个线程,用于从编码器输出缓冲队列capture_plane中获取准备好的缓冲buffer。

11、输出缓冲区入队

for (uint32_t i = 0; i < ctx.enc->capture_plane.getNumBuffers(); i++)
{
	struct v4l2_buffer v4l2_buf;
	struct v4l2_plane planes[MAX_PLANES];

	memset(&v4l2_buf, 0, sizeof(v4l2_buf));
	memset(planes, 0, MAX_PLANES * sizeof(struct v4l2_plane));

	v4l2_buf.index = i;
	v4l2_buf.m.planes = planes;

	ret = ctx.enc->capture_plane.qBuffer(v4l2_buf, NULL);
	if (ret < 0)
	{
		cerr << "Error while queueing buffer at capture plane" << endl;
		abort(&ctx);
		goto cleanup;
	}
}

        编码器输出缓冲区capture_plane全部送入编码器输出队列,准备接收编码后的视频数据。

12、获取输入的所有缓冲buffer(output_plane)、填充视频帧、缓冲buffer送入输入队列

for (uint32_t i = 0; i < ctx.enc->output_plane.getNumBuffers() &&
            !ctx.got_error; i++)
{
	struct v4l2_buffer v4l2_buf;
	struct v4l2_plane planes[MAX_PLANES];
	NvBuffer *buffer = ctx.enc->output_plane.getNthBuffer(i);

	memset(&v4l2_buf, 0, sizeof(v4l2_buf));
	memset(planes, 0, MAX_PLANES * sizeof(struct v4l2_plane));

	v4l2_buf.index = i;
	v4l2_buf.m.planes = planes;

	if (read_video_frame(ctx.in_file, *buffer) < 0)
	{
		cerr << "Could not read complete frame from input file" << endl;
		v4l2_buf.m.planes[0].bytesused = 0;
	}
	/**
	 * buffer is touched by CPU in read_video_frame(), so NvBufSurfaceSyncForDevice()
	 * is needed to flash cached data to memory.
	 */
	ret = sync_buf (buffer);
	if (ret < 0)
	{
		cerr << "Error while sync_buf" << endl;
		abort(&ctx);
		goto cleanup;
	}


	/* render rectangle by CUDA */
	ret = render_rect (&ctx, buffer);
	if (ret < 0)
	{
		cerr << "Error while render_rect" << endl;
		abort(&ctx);
		goto cleanup;
	}


	ret = ctx.enc->output_plane.qBuffer(v4l2_buf, NULL);
	if (ret < 0)
	{
		cerr << "Error while queueing buffer at output plane" << endl;
		abort(&ctx);
		goto cleanup;
	}

	if (v4l2_buf.m.planes[0].bytesused == 0)
	{
		cerr << "File read complete." << endl;
		eos = true;
		break;
	}
}

        这里的目的是询缓冲buffer信息,填充 YUV数据到buffer中,并把buffer送入编码器输入队列(output_plane)。

13、不断的从输入队列output_plane中获取空闲缓冲buffer、填充视频帧、重新送入编码器

while (!ctx.got_error && !ctx.enc->isInError() && !eos)
{
	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;

	if (ctx.enc->output_plane.dqBuffer(v4l2_buf, &buffer, NULL, 10) < 0)
	{
		cerr << "ERROR while DQing buffer at output plane" << endl;
		abort(&ctx);
		goto cleanup;
	}

	if (read_video_frame(ctx.in_file, *buffer) < 0)
	{
		cerr << "Could not read complete frame from input file" << endl;
		v4l2_buf.m.planes[0].bytesused = 0;
	}
	/**
	 * buffer is touched by CPU in read_video_frame(), so NvBufSurfaceSyncForDevice()
	 * is needed to flash cached data to memory.
	 */
	ret = sync_buf (buffer);
	if (ret < 0)
	{
		cerr << "Error while sync_buf" << endl;
		abort(&ctx);
		goto cleanup;
	}

	/* render rectangle by CUDA */
	ret = render_rect (&ctx, buffer);
	if (ret < 0)
	{
		cerr << "Error while render_rect" << endl;
		abort(&ctx);
		goto cleanup;
	}


	ret = ctx.enc->output_plane.qBuffer(v4l2_buf, NULL);
	if (ret < 0)
	{
		cerr << "Error while queueing buffer at output plane" << endl;
		abort(&ctx);
		goto cleanup;
	}

	if (v4l2_buf.m.planes[0].bytesused == 0)
	{
		cerr << "File read complete." << endl;
		eos = true;
		break;
	}
}

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

14、编码结束、释放资源

        首先结束输出工作线程:

ctx.enc->capture_plane.waitForDQThread(2000)

        之后就是释放资源、关闭编码设备。

15、encoder_capture_plane_dq_callback回调函数

static bool
encoder_capture_plane_dq_callback(struct v4l2_buffer *v4l2_buf, NvBuffer * buffer,
                                  NvBuffer * shared_buffer, void *arg)
{
    context_t *ctx = (context_t *) arg;
    NvVideoEncoder *enc = ctx->enc;

    if (!v4l2_buf)
    {
        cerr << "Failed to dequeue buffer from encoder capture plane" << endl;
        abort(ctx);
        return false;
    }
    // 把buffer中已编码的视频保存到文件中
    write_encoder_output_frame(ctx->out_file, buffer);

    /* qBuffer on the capture plane */
    // 把buffer重新入队
    if (enc->capture_plane.qBuffer(*v4l2_buf, NULL) < 0)
    {
        cerr << "Error while Qing buffer at capture plane" << endl;
        abort(ctx);
        return false;
    }

    /* GOT EOS from encoder. Stop dqthread. */
    if (buffer->planes[0].bytesused == 0)
    {
        return false;
    }

    return true;
}

        回调函数的作用把输出队列工作线程capture_plane获取的buffer中保存的编码后视频保存到文件。capture_plane工作线程从队列中获取缓冲buffer,需要在回调函数里面重新入队。

  • 10
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
OpenCV Jetson GST编码是指在Jetson平台上使用GStreamer(GST)框架对图像和视频进行编码的OpenCV扩展。GStreamer是一个功能丰富且灵活的多媒体框架,它可以在Jetson平台上实现高效的图像和视频处理。 通过使用OpenCV Jetson GST编码,我们可以利用GStreamer提供的丰富的编码器来对图像和视频进行压缩,并将其保存到硬盘上或通过网络传输。这样可以减小数据大小,提高传输效率,并保留图像和视频的质量。 OpenCV Jetson GST编码的使用步骤如下: 1. 首先,需要安装OpenCV和GStreamer的相关库和依赖项。可以通过Jetson平台的包管理工具或源代码进行安装。 2. 在代码中引入OpenCV和GStreamer的头文件。 3. 创建一个GStreamer的pipeline。Pipeline是一个数据流处理的序列,它由多个元素(element)组成,每个元素负责特定的任务。例如,可以创建一个包含摄像头捕捉、编码和保存到文件的pipeline。 4. 设置GStreamer的元素属性。可以设置元素的属性,如编码格式、帧率、分辨率等。这些属性可以根据需要进行自定义。 5. 将OpenCV的图像或视频数据通过OpenCV的API传递给GStreamer pipeline。 6. 启动pipeline并开始进行编码和保存。 7. 在需要时,停止pipeline和释放资源。 通过使用OpenCV Jetson GST编码,可以实现高效的图像和视频编码,并且可以根据需要对编码器进行自定义配置。这对于Jetson平台上的图像处理和视频流处理应用程序非常有用,可以提高处理速度和资源利用率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值