【简介】
本文实现了一个从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);
复制代码【演示】