1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html##
本章将介绍使用APM32F407实现一个简单的视频播放器应用。通过本章的学习,读者将学习到使用libjpeg库软解JPEG实现播放AVI视频的使用。
本章分为如下几个小节:
54.1 硬件设计
54.2 程序设计
54.3 下载验证
54.1 硬件设计
54.1.1 例程功能
uint8_t mjpegdec_init(uint16_t offx,uint16_t offy);
void mjpegdec_free(void);
uint8_t mjpegdec_decode(uint8_t* buf,uint32_t bsize);
这三个函数的使用也非常简单,光看函数名也能猜出各个函数的作用了,因此具体的使用方法,请读者自行查看本章实验的配套实验例程。
54.2.2 AVI文件解析
有关AVI文件格式的介绍和解析方式,请感兴趣的读者自行查阅相关的资料。对于AVI文件的解析,正点原子提供了avi.c和avi.h两个文件,请读者结合本章实验配套的实验例程进行查看。
54.2.3 实验应用代码
本章实验的应用代码,如下所示:
int main(void)
{
/* 必要初始化,代码省略 */
while (1)
{
video_play();
}
}
可以看到,本章实验在完成一些必要的软硬件初始化后,便调用函数video_play()进行视频播放,该函数如下所示:
/**
* @brief 播放视频
* @param 无
* @retval 无
*/
void video_play(void)
{
uint8_t res;
DIR vdir;
FILINFO *vfileinfo;
uint8_t *pname;
uint16_t totavinum;
uint16_t curindex;
uint32_t *voffsettbl;
uint8_t key;
/* 检查VIDEO文件夹是否存在,代码省略 */
/* 检查是否有视频文件,代码省略 */
/* 申请内存,代码省略 */
/* 记录视频文件的索引,代码省略 */
curindex = 0;
res = (uint8_t)f_opendir(&vdir, "0:/VIDEO");/* 打开目录 */
while (res == 0)
{
/* 索引到待播放的视频文件 */
dir_sdi(&vdir, voffsettbl[curindex]);
res = (uint8_t)f_readdir(&vdir, vfileinfo);
if ((res != 0) || (vfileinfo->fname[0] == 0))
{
break;
}
/* 生成视频文件路径信息 */
strcpy((char *)pname, "0:/VIDEO/");
strcat((char *)pname, (const char *)vfileinfo->fname);
/* 显示视频基本信息 */
lcd_clear(WHITE);
video_bmsg_show((uint8_t *)vfileinfo->fname, curindex + 1, totavinum);
/* 播放视频 */
key = video_play_mjpeg(pname);
/* 根据键值,切换播放上(下)一个视频文件 */
}
/* 释放内存,代码省略 */
}
从上面的代码中可以看出,主要就是调用函数video_play_mjpeg()来播放AVI视频文件,该函数如下所示:
/**
* @brief 播放MJPEG视频
* @param pname: 视频文件名
* @retval 按键键值
* KEY0_PRES: 上一个视频
* WKUP_PRES: 下一个视频
* 其他值: 错误代码
*/
static uint8_t video_play_mjpeg(uint8_t *pname)
{
uint8_t *framebuf;
uint8_t *pbuf;
uint8_t res = 0;
uint16_t offset;
uint32_t nr;
uint8_t key;
FIL *favi;
uint8_t *psaibuf;
/* 申请内存,代码省略 */
while (res == 0)
{
/* 打开文件 */
res = (uint8_t)f_open(favi, (const TCHAR *)pname, FA_READ);
if (res == 0)
{
pbuf = framebuf;
/* 读取文件 */
res = (uint8_t)f_read(favi, pbuf, AVI_VIDEO_BUF_SIZE, &nr);
if (res != 0)
{
printf("fread error:%d\r\n", res);
break;
}
/* AVI初始化解析,获取AVI视频的帧间隔时间、总帧数等信息 */
res = avi_init(pbuf, AVI_VIDEO_BUF_SIZE);
if (res != 0)
{
printf("avi error:%d\r\n", res);
break;
}
/* 显示AVI初始化解析得到的部分信息 */
video_info_show(&g_avix);
/* 初始化TMR7以AVI视频的帧间隔时间为周期产生中断,
* 以逐帧显示AVI视频画面
*/
btmr_tmr7_int_init(g_avix.SecPerFrame / 100 - 1, 8400 - 1);
/* 获取第一个MJPG数据流位置 */
offset = avi_srarch_id(pbuf, AVI_VIDEO_BUF_SIZE, "movi");
avi_get_streaminfo(pbuf + offset + 4);
f_lseek(favi, offset + 12);
/* 初始化libjpeg解码MJPG数据流 */
res = mjpegdec_init((lcddev.width - g_avix.Width) / 2,
10 + (lcddev.height - 110 - g_avix.Height) / 2);
while (1)
{
/* 视频流 */
if (g_avix.StreamID == AVI_VIDS_FLAG)
{
pbuf = framebuf;
/* 读取当前视频流的整帧数据和下一帧数据流的ID信息 */
f_read(favi, pbuf, g_avix.StreamSize + 8, &nr);
/* 解码当前视频流数据,并在LCD上进行显示 */
res = mjpegdec_decode(pbuf, g_avix.StreamSize);
if (res != 0)
{
printf("decode error!\r\n");
}
/* 等待TMR7中断,以播放下一帧 */
while (frameup == 0);
frameup = 0;
frame++;
}
/* 其他数据流,如音频流等 */
else
{
/* 显示当前播放时间 */
video_time_show(favi, &g_avix);
/* 读取当前数据流的整帧数据和下一帧数据流的ID信息 */
f_read(favi, psaibuf, g_avix.StreamSize + 8, &nr);
pbuf = psaibuf;
}
/* 按键按下,提前退出 */
key = key_scan(0);
if (key != 0)
{
res = key;
break;
}
/* 解析下一帧数据流的ID信息,以判断是否为视频流 */
if (avi_get_streaminfo(pbuf + g_avix.StreamSize) != 0)
{
printf("g_frame error\r\n");
res = WKUP_PRES;
break;
}
}
/* 关闭当前视频播放,代码省略 */
}
}
/* 释放内存,代码省略 */
return res;
}
从上面的代码中可以看出,主要就是逐帧的读取AVI文件的数据流和下一帧数据流的ID信息,如果当前数据流为视频流则调用函数mjpegdec_decode()对齐进行解码并在LCD上进行显示,若不是视频流则直接跳过,不进行处理,并且在每一帧数据流处理完毕后,都会检测一次按键,以进行上(下)一个视频切换播放的操作。
54.3 下载验证
在完成编译和烧录操作后,将根目录存放了A盘5,SD卡根目录文件中文件的SD卡插入开发板板载的SD卡卡座后,便能看到LCD上显示了SD卡VIDEO文件夹中的视频信息,并且在LCD上自动播放了该视频文件,此时,若按下KEY0按键或KEY_UP按键可以切换LCD显示播放SD卡VIDEO文件夹中的上一个或下一个视频。