FFmpeg DXVA2硬件解码实践

你好 本文是我的FFmpeg文章(继续Visual Studio )的延续 在这里,我们开始进行全高清RTSP流的硬件解码。 我会事先说,即使是Intel ATOM Z8350也可以轻松地完成此任务。

目标:硬件解码并在RAM中记录多达4帧,以便通过RTSP h.264 IP摄像机进行后续并行处理(四个处理器内核)。 我使用WinAPI函数显示已处理的帧。 结果,我们得到了用于并行模式下RTSP流的计算机处理的高速系统。 接下来,您可以连接计算机视觉算法来处理实时帧。

图片

参赛作品


为什么需要硬件解码? 您想使用性能低廉且价格便宜的处理器解码实时视频,或者要尽可能多地卸载处理器,那么该是时候熟悉硬件解码了。

DirectX视频加速 (DXVA)是一种API,用于使用硬件加速来加速GPU的视频处理。 DXVA 2.0允许您将更多操作重定向到GPU,包括视频捕获和视频处理操作。

在写完上一篇文章之后,我被问了很多问题:“为什么使用FFmpeg?” 我将从问题开始。 硬件解码的主要困难是将解码后的帧写入RAM。 对于全高清,这是1920 x 1080 x 3 = 6,220,800字节。 即使考虑到帧是以NV12格式存储的事实,这也是很多1920 x 1080 x 1.5 = 3110400字节。 对于任何处理器而言,每秒覆盖75 MB都是一项艰巨的任务。 为解决此问题,英特尔增加了SSE 4命令,使您无需处理器即可重写数据。 不幸的是,并不是所有的库都实现了这一功能。 我已经测试了以下库:

  1. ffmpeg
  2. VLC
  3. Opencv的

VLC-通过硬件解码(非常低的处理器负载)与IP摄像机配合使用,仅用10行代码即可构建原始的RTSP流播放器,但是在RAM中接收解码的帧会占用过多的处理器时间。

OpenCV -RTSP使用FFmpeg来处理流,因此决定在没有中介的情况下工作,即 使用FFmpeg库。 此外,默认情况下安装的FFmpeg是在OpenCV中内置的,无需硬件解码。

FFmpeg-在我看来,效果很好,运行稳定。 唯一的缺点是无法在Windows中使用X86版的WEB相机(X64似乎允许您工作)来实现。

硬件视频解码很容易


实际上,使用FFmpeg库进行硬件解码并不比软件复杂。 项目设置与软件实施相同,框图保持不变。

您可以显示FFmpeg支持的硬件解码方法的列表。

fprintf(stderr, " %s", av_hwdevice_get_type_name(type)); 

我们需要做的第一件事是告诉FFmpeg您要使用哪个硬件解码器解码视频。 就我而言,Windows10 + Intel Atom Z8350仅保留DXVA2:

 type = av_hwdevice_find_type_by_name("dxva2"); 

您可以选择CUDA,D3D11VA,QSV或VAAPI(仅Linux)作为硬件解码器。 因此,您应该具有此硬件解决方案,并且应该在其支持下构建FFmpeg。

打开视频流:

 avformat_open_input(&input_ctx, filename, NULL, NULL; 

我们获得有关视频流的信息:

 av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0); 

分配内存:

 frame = av_frame_alloc(); //       sw_frame = av_frame_alloc(); //        

此函数覆盖RAM中的解码文件:

 av_hwframe_transfer_data(sw_frame, frame, 0); 

关于NV12格式的一些知识


因此,我们在sw_frame结构中获得了一个框架。 接收到的帧以NV12格式存储。 此格式由Microsoft发明。 它允许您以12位存储像素信息。 其中8位是强度,4位描述了颜色(或者,立即为4个相邻的2x2像素描述了颜色)。 此外,sw_frame.data [0]-强度被存储,而sw_frame.data [1]-颜色被存储。 要将NV-12转换为RGB,可以使用以下功能:

从NV12到RGB的C ++转换
 void SaveFrame(uint8_t * f1, uint8_t * f2, int iFrame) { FILE *pFile; char szFilename[32]; int x, i, j; // char buff[1920 * 1080 * 3]; uint8_t *buff = new uint8_t(1920*3*2); int u=0, v=0, y=0; // Open file sprintf(szFilename, "frame%d.ppm", iFrame); pFile = fopen(szFilename, "wb"); if (pFile == NULL) return; //    fprintf(pFile, "P6\n%d %d\n255\n", 1920, 1080); for (j = 0; j < 1080 / 2; j++) { for (i = 0; i < 1920; i +=2) { // 1  rgb y = *(f1 + j * 1920 * 2 + i); v = *(f2 + j * 1920 + i) - 128; u = *(f2 + j * 1920 + i + 1) - 128; x = round(y + 1.370705 * v); if (x < 0) x = 0; if (x > 255) x = 255; // if (j > 34) printf("%i, ",(j * 1920 * 2 + i) * 3); buff[i * 3 + 2] = x; x = round(y - 0.698001 * v - 0.337633 * u); if (x < 0) x = 0; if (x > 255) x = 255; buff[i * 3 + 1] = x; x = round(y + 1.732446 * u); if (x < 0) x = 0; if (x > 255) x = 255; buff[i * 3] = x; // 2  rgb y = *(f1 + j * 1920 * 2 + i + 1); x = y + 1.370705 * v; if (x < 0) x = 0; if (x > 255) x = 255; buff[i * 3 + 5] = x; x = y - 0.698001 * v - 0.337633 * u; if (x < 0) x = 0; if (x > 255) x = 255; buff[i * 3 + 4] = x; x = y + 1.732446 * u; if (x < 0) x = 0; if (x > 255) x = 255; buff[i * 3 + 3] = x; // 3  rgb y = *(f1 + j * 1920 * 2 + 1920 + i); x = y + 1.370705 * v; if (x < 0) x = 0; if (x > 255) x = 255; buff[(1920 + i) * 3 + 2] = x; x = y - 0.698001 * v - 0.337633 * u; if (x < 0) x = 0; if (x > 255) x = 255; buff[(1920 + i) * 3 + 1] = x; x = y + 1.732446 * u; if (x < 0) x = 0; if (x > 255) x = 255; buff[(1920 + i) * 3 + 0] = x; // 4  rgb y = *(f1 + j * 1920 * 2 + 1920 + i + 1); x = y + 1.370705 * v; if (x < 0) x = 0; if (x > 255) x = 255; buff[(1920 + i) * 3 + 5] = x; x = y - 0.698001 * v - 0.337633 * u; if (x < 0) x = 0; if (x > 255) x = 255; buff[(1920 + i) * 3 + 4] = x; x = y + 1.732446 * u; if (x < 0) x = 0; if (x > 255) x = 255; buff[(1920 + i) * 3 + 3] = x; // printf("%i, ", i); } // for i fwrite(buff, 1, 1920 * 3 * 2, pFile); printf("\n %i\n", j); } // for j // printf("Save4\n"); // Write pixel data // fwrite(buff, 1, 1920*1080*3, pFile); // Close file printf("close\n"); fclose(pFile); printf("exit\n"); delete buff; // return; } 


尽管使用NV12可以使您加快诸如模糊,Retinex和获取灰度图像的过程(仅通过丢弃颜色)即可。 在我的任务中,我不会将NV12格式转换为RGB,因为这会花费额外的时间。

因此,我们学习了如何在硬件中解码视频文件并将其显示在窗口中。 我们遇到了NV12格式,以及如何将其转换为熟悉的RGB。

DLL硬件解码


FFmpeg在40毫秒后发出帧(每秒25帧)。 通常,处理全高清帧会花费更长的时间。 这需要多线程以最大化所有4个处理器内核的负载。 实际上,我只启动6个线程,不再删除它们,这大大简化了工作并提高了程序的可靠性。 工作方案如图。 1个

图片
图1使用FFmpeg构建多线程程序的方案

我将解码器写为* .dll (FFmpegD.DLL),以便包含在我的项目中。 这使您可以减少项目的代码,从而增加对代码的理解,并将其包括在任何编程语言中,直至汇编语言(已验证:))。 使用它,我们将从IP摄像机编写RTSP流播放器。

要开始使用DLL,您需要将指针传递给int [13]数组,新帧到达事件的HANDLE,开始处理来自相机的新数据包的HANDLE以及相机地址的char数组。

表1给出了数组结构。

图片

呼叫之前,必须重设帧号1-4。

DLL将采取所有必要的步骤来初始化FFmpeg,并将记录指针和帧号。 之后,将事件设置为“新帧到达”。 只需要处理传入的帧并写入0而不是帧号(这意味着该帧已被处理并且不再使用)。

在下面,您将找到带有源代码的示例播放器。 该示例是ShowDib3 Charles Petzold。

随项目存档
FFmpegD.dll存档

结果:即使在Intel Atom Z8350上,FFmpeg硬件运动检测器也可通过连接的运动检测器实时解码h264 Full HD,处理器负载高达20%。


英特尔ATOM Z8350上的运动检测器操作示例。 前30秒是背景的计算。 此后,运动检测器通过减去背景的方法工作。

PS您还可以解码视频文件(压缩的h.264)!

参考文献:

  1. 有关FFmpeg的其他有用信息
  2. 有关使用FFmpeg提供的各种库的信息
  3. 有关格式和转换为RGB的信息

Source: https://habr.com/ru/post/zh-CN461735/


All Articles