
Jetson视频解码
Jetson 是由 NVIDIA 开发的嵌入式计算平台系列,旨在提供高性能的人工智能(AI)计算能力,适用于嵌入式系统、机器人、自动驾驶汽车和其他边缘计算应用。Jetson 平台通常集成了 NVIDIA 的 GPU 和其他硬件加速器,能够在低功耗的环境下执行复杂的深度学习和计算任务。Jetson Multimedia API(Jetson 多媒体 API)是 NVIDIA 提供的一组软件工具和接口
一、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、音视频(H264/H265/AAC)封装、解封装、编解码pipeline,支持NVIDIA、昇腾DVPP硬编解码。项目地址:https://github.com/BreakingY/Media-Codec-Pipeline
4、simple rtsp server,小而高效的rtsp服务器,支持H264、H265、AAC、PCMA;支持TCP、UDP;支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-server
5、simple rtsp client,rtsp客户端,支持TCP、UDP、H264、H265、AAC、PCMA,支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-client
6、libflv,flv muxer/demuxer,支持H264/H265、AAC。项目地址:https://github.com/BreakingY/libflv
7、mpeg2 ts ps muxer/demuxer,支持H264/H265/MPEG1 audio/MP3/AAC/AAC_LATM/G711。项目地址:https://github.com/BreakingY/libmpeg2core
更多推荐
所有评论(0)