[STM32N6] 【STM32N6570-DK测评】做个视频播放器

[复制链接]
 楼主| xhackerustc 发表于 2025-5-10 16:16 | 显示全部楼层 |阅读模式
<
@21小跑堂#申请原创#

接上回jpeg解码显示,jpeg成功解码显示后我们就可以做个简单的视频播放器啦。而且还是所谓“硬解”。原理很简单,无数张jpeg组成一种mjpeg流,放在一个avi容器中,在代码里解析此AVI即可知道每一帧的jpeg地址和大小(它们分开其实就是一张张jpeg照片),按照avi容器中的码率显示此jpeg序列即可。

做个demo需要解决如下问题:符合要求的视频流、视频放在什么地方、AVI解析、mjpeg播放、如何按照AVI视频文件的码率显示jpeg序列。

符合要求的视频流
视频要求:AVI格式、mjpeg编码、最好800x480分辨率。这个视频可以自己做,但STM32Cube_FW_H7下其实有现成的视频可以做下测试,笔者用的文件位于
  1. Utilities/Media/Video/video2_800_480.avi


视频放在什么地方
理想位置可以是sd卡或ospi flash,但要集中精力攻打目标关卡,STM32N6这么大的sram,可以放个七、八百KB的视频是没问题的,所以笔者为方便起见把视频放在sram中了,当然最终要做成播放器成品是需要实现下SD卡和fatfs的,这个留在后续TODO中

AVI解析
笔者咨询了下deepseek,AVI格式其实蛮简单的,但是有没有现成的代码可以复用呢?还真让笔者找到了,从STM32Cube_FW_H7中如下位置copy过来即可
  1. Projects/STM32H743I-EVAL/Examples/JPEG/JPEG_MJPEG_VideoDecodingFromQSPI/Src/AVI_parser.c

  2. Projects/STM32H743I-EVAL/Examples/JPEG/JPEG_MJPEG_VideoDecodingFromQSPI/Inc/AVI_parser.h


mjpeg播放主流程
获取一帧,如果是视频帧,说明这个帧就是一个jpeg数组,调用上章实现的jpeg解码和显示到LCD的代码即可。如此循环往复,直到AVI中所有的帧遍历完毕。当然这个过程还涉及到双缓冲,一缓冲区用于显示、一缓冲区用于解码。

如何按照AVI视频文件的码率显示jpeg序列
STM32N6性能很强,很有可能一帧解码显示完毕只需很少的时间,所以如果不加延时,最后的效果很有可能像快进一样,所以在帧解码并显示结束后,需要做适量的延时。

主流程代码如下:
  1. if(AVI_ParserInit(&AVI_Handel, AVI_FILE_ADDRESS) != 0)
  2.     Error_Handler();

  3.   isfirstFrame = 1;
  4.   FrameRate = 0;
  5.   startTime = HAL_GetTick();

  6.   do
  7.   {
  8.     /*##-4- Get a Frame from the AVI file ##################################*/
  9.     FrameType = AVI_GetFrame(&AVI_Handel);

  10.     if(FrameType == AVI_VIDEO_FRAME)
  11.     {
  12.       AVI_Handel.CurrentImage ++;

  13.       /*##-5- Start decoding the current JPEG frame with DMA (Not Blocking ) Method ################*/
  14.       memcpy((void*)jpgBuf, AVI_Handel.pVideoBuffer, AVI_Handel.FrameSize);
  15.       JPEG_Decode_DMA(&hjpeg, jpgBuf, AVI_Handel.FrameSize, jpegOutDataAdreess);
  16. // JPEG_Decode_DMA(&hjpeg, AVI_FILE_ADDRESS, 117813, jpegOutDataAdreess);

  17.       /*##-6- Wait till end of JPEG decoding ###############################*/
  18.       while(Jpeg_HWDecodingEnd == 0)
  19.       {
  20.       }

  21.       if(isfirstFrame == 1)
  22.       {
  23.         isfirstFrame = 0;
  24.         /*##-7- Get JPEG Info  #############################################*/
  25.         HAL_JPEG_GetInfo(&hjpeg, &JPEG_Info);

  26.         /*##-8- Initialize the DMA2D #######################################*/
  27.         DMA2D_Init(JPEG_Info.ImageWidth, JPEG_Info.ImageHeight, JPEG_Info.ChromaSubsampling);
  28.       }
  29.       /*##-9- Copy the Decoded frame to the display frame buffer using the DMA2D #*/      
  30.       xPos = (LCD_X_Size - JPEG_Info.ImageWidth)/2;
  31.       yPos = (LCD_Y_Size - JPEG_Info.ImageHeight)/2;
  32.       DMA2D_CopyBuffer((uint32_t *)jpegOutDataAdreess, (uint32_t *)LCD_LAYER_0_ADDRESS, xPos , yPos, JPEG_Info.ImageWidth, JPEG_Info.ImageHeight);

  33.       jpegOutDataAdreess = (jpegOutDataAdreess == (uint32_t)rawBuffer) ? (uint32_t)rawBuffer2: (uint32_t)rawBuffer;

  34. #ifdef USE_FRAMERATE_REGULATION
  35.       /* Regulate the frame rate to the video native frame rate by inserting delays */
  36.       FrameRate =  (HAL_GetTick() - startTime) + 1;
  37.       if(FrameRate < ((AVI_Handel.aviInfo.SecPerFrame/1000) * AVI_Handel.CurrentImage))
  38.       {
  39.         HAL_Delay(((AVI_Handel.aviInfo.SecPerFrame /1000) * AVI_Handel.CurrentImage) - FrameRate);
  40.       }
  41. #endif /* USE_FRAMERATE_REGULATION */
  42.     }
  43.   }while(AVI_Handel.CurrentImage  <  AVI_Handel.aviInfo.TotalFrame);

  44.   HAL_DMA2D_PollForTransfer(&hdma2d, 50);  /* wait for the Last DMA2D transfer to ends */

  45.   if(AVI_Handel.CurrentImage > 0)
  46.   {
  47.     /*##-10- Calc the average decode frame rate #*/
  48.     FrameRate =  (AVI_Handel.CurrentImage * 1000)/(HAL_GetTick() - startTime);
  49.     /* Display decoding info */
  50.     LCD_BriefDisplay();
  51.   }

注:如下这个位置是个坑,就是好像STM32N6的JPEG解码DMA有个对齐要求,如果mjpeg流里jpeg帧的位置不满足对齐就会花屏,笔者这里堵了好久。笔者的解决方案是:copy到jpeg buff中再解码,当然如果能解决这个对齐要求可以少一次copy就更好了,看看后续CubeFW是不是有更新吧,或者读者有更好的方法
麻烦告知哦。
  1.       memcpy((void*)jpgBuf, AVI_Handel.pVideoBuffer, AVI_Handel.FrameSize);
  2.       JPEG_Decode_DMA(&hjpeg, jpgBuf, AVI_Handel.FrameSize, jpegOutDataAdreess);

播放完毕显示的一些参数:
IMG_20250510_160550.jpg

探讨一些潜在的优化方法:
1.如上文所述,JPEG DMA有没有对齐要求,如何解决从而避免一次拷贝
2.现在DMA所涉及的buf,笔者统统都是在MPU中设置成Non-Cacheable的,限制是不是过于强悍了,有没有可能利用上cpu的cache?
3.psram用起来
4.SD卡bring up
5.更多编码格式支持,比如移植ffmpeg解码?
6.能否基于zephyr RTOS LVGL做此播放器,这样一来以后ST家的其它MCU甚至友商的平台直接复用代码
笔者相信,如果这六点做完这个播放器完成度就比较完整了。


视频如下供欣赏:
https://www.bilibili.com/video/BV11wVRz2Exx/



probedog 发表于 2025-6-5 17:33 | 显示全部楼层
支持支持。。。
AdaMaYun 发表于 2025-6-8 23:39 | 显示全部楼层
学习一下
gejigeji521 发表于 2025-6-11 12:03 | 显示全部楼层
avi体积太大了,没啥意义。
Ketose 发表于 2025-7-8 15:58 | 显示全部楼层
收一块STM32N6开发板。
szt1993 发表于 2025-7-8 19:35 | 显示全部楼层
视频播放器注意哪些细节呢?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

42

主题

165

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部