本帖最后由 酒酿大丸子 于 2024-3-10 13:10 编辑

【简介】
本文实现了一个从tf卡播放视频的demo,用来测试GD32H759的视频处理能力。


【演示视频】



【MJPG编码简介】
Motion JPEG是一种基于静态图像JPEG 压缩标准的动态图像压缩标准,压缩时将连续图像的每一个帧视为一幅静止图像进行压缩,从而可以生成序列化运动图像。压缩时不对帧间的时间冗余进行压缩,也就是说没有H264那样的帧间编码,阵内预测编码也没有,所以较于实现,只是压缩效率较低。一个MJPG帧序列可以看出是N帧JPEG编码后的数据流连接而成。


【基于MJPG编码的AVI视频封装简介】
AVI是一种RIFF(Resource Interchange File Format)文件格式,多用于音视频捕捉、编辑、回放等应用程序。AVI包含三个部分:文件头、数据块和索引块。其中文件头包括文件的通用信息,定义数据格式,所用的压缩算法等参数。数据块包含实际数据流,即图像和声音序列数据。这是文件的主体,也是决定文件容量的主要部分。视频文件的大小等于该文件的数据率乘以该视频播放的时间长度。索引块包括数据块列表和它们在文件中的位置,以提供文件内数据随机存取能力。


【FatFs简介】
FatFs是一个通用的文件系统(FAT/exFAT)模块,用于在小型嵌入式系统中实现FAT文件系统。 FatFs 组件的编写遵循ANSI C(C89),完全分离于磁盘 I/O 层,因此不依赖于硬件平台。它可以嵌入到资源有限的微控制器中,如 8051, PIC, AVR, ARM, Z80, RX等等,不需要做任何修改。


【TF卡读写实现】
参考GD例子,初始化参考
#define BUSMODE_1BIT    0
  • #define BUSMODE_4BIT    1

  • /* SDIO bus switch */
  • /* config SDIO bus mode, select: BUSMODE_1BIT/BUSMODE_4BIT */
  • #define SDIO_BUSMODE        BUSMODE_1BIT
  • /* config SDIO speed mode, select: SD_SPEED_DEFAULT/SD_SPEED_HIGH */
  • #define SDIO_SPEEDMODE      SD_SPEED_DEFAULT
  • /* config data transfer mode, select: SD_POLLING_MODE/SD_DMA_MODE */
  • #define SDIO_DTMODE         SD_POLLING_MODE

  • sd_card_info_struct sd_cardinfo;      

  • sd_error_enum sd_io_init(void)
  • {
  •     sd_error_enum status = SD_OK;
  •     uint32_t cardstate = 0;

  •     status = sd_init();
  •     if(SD_OK == status) {
  •         status = sd_card_information_get(&sd_cardinfo);
  •     }
  •     if(SD_OK == status) {
  •         status = sd_card_select_deselect(sd_cardinfo.card_rca);
  •     }
  •     status = sd_cardstatus_get(&cardstate);
  •     if(cardstate & 0x02000000) {
  •         printf("\r\n the card is locked!");
  •         status = sd_lock_unlock(SD_UNLOCK);
  •         if(status != SD_OK) {
  •             return SD_LOCK_UNLOCK_FAILED;
  •         } else {
  •             printf("\r\n the card is unlocked successfully!");
  •         }
  •     }

  •     if((SD_OK == status) && (!(cardstate & 0x02000000))) {
  •         /* set bus mode */
  • #if (SDIO_BUSMODE == BUSMODE_4BIT)
  •         status = sd_bus_mode_config(SDIO_BUSMODE_4BIT, SDIO_SPEEDMODE);
  • #else
  •         status = sd_bus_mode_config(SDIO_BUSMODE_1BIT, SDIO_SPEEDMODE);
  • #endif
  •     }

  •     if(SD_OK == status) {
  •         /* set data transfer mode */
  •         status = sd_transfer_mode_config(SDIO_DTMODE);
  •     }
  •     return status;
  • }
  • 复制代码


    【FatFs实现】
    FatFS的移植十分简单,只要实现了对应的接口就可以。
    直接贴代码,不多说了。
    DSTATUS disk_status (
  • BYTE pdrv  /* Physical drive nmuber to identify the drive */
  • )
  • {
  • DSTATUS stat;
  •     switch (pdrv)
  •     {
  •         case SD_CARD :
  •             return 0;
  •         default:
  •             break;
  •     }
  •     return STA_NOINIT;
  • }

  • /*-----------------------------------------------------------------------*/
  • /* Inidialize a Drive                                                    */
  • /*-----------------------------------------------------------------------*/
  • DSTATUS disk_initialize (
  • BYTE pdrv    /* Physical drive nmuber to identify the drive */
  • )
  • {
  • DSTATUS stat;
  •     uint32_t cardstate = 0;
  •     switch (pdrv)
  •     {
  •         case SD_CARD:

  •             stat &= ~STA_NOINIT;
  •             return 0;
  •         default:
  •             break;
  •     }
  •     return STA_NOINIT;
  • }

  • /*-----------------------------------------------------------------------*/
  • /* Read Sector(s)                                                        */
  • /*-----------------------------------------------------------------------*/

  • DRESULT disk_read (
  • BYTE pdrv,  /* Physical drive nmuber to identify the drive */
  • BYTE *buff,  /* Data buffer to store read data */
  • LBA_t sector, /* Start sector in LBA */
  • UINT count  /* Number of sectors to read */
  • )
  • {
  •     sd_error_enum status = SD_ERROR;
  • DRESULT res;
  •     uint32_t *ptrd, *btrd;
  •     sd_error_enum SD_stat = SD_OK;
  •     switch (pdrv)
  •     {
  •         case SD_CARD :
  •             if(count > 1) {
  •                 SD_stat = sd_multiblocks_read((uint32_t *)buff, sector * sd_cardinfo.card_blocksize,
  •                 sd_cardinfo.card_blocksize, count);
  •             } else {
  •                 SD_stat = sd_block_read((uint32_t *)buff, sector * sd_cardinfo.card_blocksize,
  •                 sd_cardinfo.card_blocksize);
  •             }
  •             if(SD_stat == SD_OK) {
  •                 res = RES_OK ;
  •             } else {
  •                 res = RES_ERROR ;
  •             }
  •             return res;
  •         default:
  •             break;
  •     }

  • return RES_PARERR;
  • }

  • /*-----------------------------------------------------------------------*/
  • /* Write Sector(s)                                                       */
  • /*-----------------------------------------------------------------------*/

  • #if FF_FS_READONLY == 0
  • DRESULT disk_write (
  • BYTE pdrv,   /* Physical drive nmuber to identify the drive */
  • const BYTE *buff, /* Data to be written */
  • LBA_t sector,  /* Start sector in LBA */
  • UINT count   /* Number of sectors to write */
  • )
  • {
  • DRESULT res;
  •     sd_error_enum SD_stat = SD_OK;
  •     uint32_t address;
  •     uint32_t erase_counter;
  •     sd_error_enum status = SD_ERROR;

  •     switch (pdrv)
  •     {
  •         case SD_CARD :
  •             if(count > 1) {
  •                 SD_stat = sd_multiblocks_write((uint32_t *)buff, sector * sd_cardinfo.card_blocksize,
  •                 sd_cardinfo.card_blocksize, count);
  •             } else {
  •                 SD_stat = sd_block_write((uint32_t *)buff, sector * sd_cardinfo.card_blocksize,
  •                 sd_cardinfo.card_blocksize);
  •             }
  •             if(SD_stat == SD_OK) {
  •                 res = RES_OK ;
  •             } else {
  •                 res = RES_ERROR ;
  •             }
  •             return res;
  •         default:
  •             break;
  •     }
  •     return RES_PARERR;
  • }
  • #endif

  • /*-----------------------------------------------------------------------*/
  • /* Miscellaneous Functions                                               */
  • /*-----------------------------------------------------------------------*/

  • DRESULT disk_ioctl (
  • BYTE pdrv,  /* Physical drive nmuber (0..) */
  • BYTE cmd,  /* Control code */
  • void *buff  /* Buffer to send/receive control data */
  • )
  • {
  • DRESULT res;
  •     uint32_t capacity;
  •     switch (pdrv)
  •     {
  •         case SD_CARD :
  •         switch(cmd) {

  •             /*return sector number*/
  •             case GET_SECTOR_COUNT:
  •                 *(DWORD *)buff = sd_cardinfo.card_capacity / (sd_cardinfo.card_blocksize);
  •                 break;
  •             /*return each sector size*/
  •             case GET_SECTOR_SIZE:
  •                 *(WORD *)buff = sd_cardinfo.card_blocksize;
  •                 break;
  •             /*Returns the smallest unit of erased sector (unit 1)*/
  •             case GET_BLOCK_SIZE:
  •                 *(DWORD *)buff = SD_CARD_BLOCK_SIZE;
  •                 break;
  •         }
  •         res = RES_OK;
  •         return res;

  •         default:
  •             break;
  •     }
  •     return RES_PARERR;
  • }

  • DWORD get_fattime(void)
  • {
  •     return 0;
  • }
  • 复制代码


    【视频解码实现】
    因为GD32H7没有jpg硬件解码器,这里我使用libjpg这个库对jpg图片进行解码。
    libjpeg是一个完全用C语言编写的库,包含了被广泛使用的JPEG解码、JPEG编码和其他的JPEG功能的实现。这个库由独立JPEG工作组维护。
    移植的关键就在内存分配上,libjpg对内存的需求比较大,需要多注意这一部分。

    avi部分参考结构,手动解码就行。

    主要代码
         res = f_open(&favi, "0:1.avi", FA_READ);
  •     if (res)
  •     {
  •         printf("fread error:%d\r\n", res);
  •     }
  •      
  •     pbuf = (uint8_t *)avi_video_buf;
  •     res = f_read(&favi, pbuf, AVI_VIDEO_BUF_SIZE, &nr);      /* 开始读取 */
  •     if (res)
  •     {
  •         printf("fread error:%d\r\n", res);
  •     }

  •     /* 开始avi解析 */
  •     res = avi_init(pbuf, AVI_VIDEO_BUF_SIZE);               /* avi解析 */
  •     if (res)
  •     {
  •         printf("avi err:%d\r\n", res);
  •     }

  •     offset = avi_srarch_id(pbuf, AVI_VIDEO_BUF_SIZE, "movi");                   /* 寻找movi ID */
  •     avi_get_streaminfo(pbuf + offset + 4);                                      /* 获取流信息 */
  •     f_lseek(&favi, offset + 12);                                                 /* 跳过标志ID,读地址偏移到流数据开始处 */
  •     res = mjpegdec_init(0, 0);    /* JPG解码初始化 */

  •     while (1)          /* 播放循环 */
  •     {
  •         if (g_avix.StreamID == AVI_VIDS_FLAG)                                   /* 视频流 */
  •         {
  •             f_read(&favi, pbuf, g_avix.StreamSize + 8, &nr);                     /* 读入整帧+下一数据流ID信息 */
  •             res = mjpegdec_decode(pbuf, g_avix.StreamSize);
  •             if (res)
  •             {
  •                 printf("decode error!\r\n");
  •             }
  •         }
  •         else         /* 音频流 */
  •         {
  •             f_read(&favi, pbuf, g_avix.StreamSize + 8, &nr);    /* 填充p_avi_sai_buf */
  •         }  

  •         if (avi_get_streaminfo(pbuf + g_avix.StreamSize))   /* 读取下一帧 流标志 */
  •         {
  •             printf("g_frame error \r\n");
  •             break;
  •         }
  •     }
  •     mjpegdec_free();                                        /* 释放内存 */
  •     f_close(&favi);
  • 复制代码


    【演示】
    微信截图_20240310130947.png