【正点原子STM32连载】 第五十四章 视频播放器实验 摘自【正点原子】APM32F407最小系统板使用指南

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 例程功能

  1. 程序运行后,在LCD上播放SD卡中特定目录下的AVI视频文件
  2. 按下KEY0或KEY_UP按键,可分别进行切换上一个视频和切换下一个视频的操作
  3. USART1会在播放一个视频前输出视频的信息,并在视频播放中实时输出处理视频数据的帧率
  4. LED1闪烁,提示处理完一帧视频数据
  5. LED0闪烁,指示程序正在运行
    54.1.2 硬件资源
  6. LED
    LED0 - PF9
    LED1 - PF10
  7. 按键
    KEY0 - PE4
    KEY_UP - PA0
  8. USART1(PA9、PA10连接至板载USB转串口芯片上)
  9. 正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  10. SD卡(SDIO驱动)
  11. NOR Flash(SPI驱动)
  12. 基本定时器6
  13. 基本定时器7
    54.1.3 原理图
    本章实验主要涉及libjpeg软件库的使用和AVI文件的解析,因此没有对应的连接原理图。
    54.2 程序设计
    54.2.1 libjpeg库的使用
    本章实验要实现一个简单的视频播放器,以播放AVI格式的视频,AVI格式的视频数据可以采用MJPG进行编码,因此在播放时,需要对MJPG数据流进行解码,这样便可以从MJPG数据流中获得视频的逐帧画面,只需要在LCD上连续显示这些画面,就能够实现视频播放。
    在第五十二章“图片显示实验”中使用TjpgDec解码JPEG图片,虽然TjpgDec占用资源少,但解码速度慢,若用于解码MJPG数据流,会导致视频播放不够流畅,因此本章实验使用解码速度更快的libjpeg库解码MJPG数据流。
    关于libjpeg库的移植和使用,请读者自行查看libjpeg源码中的介绍文件,可以重点看readme.txt、filelist.txt、install.txt和libjpeg.txt等文件,可以参考本章实验配套实验例程进行移植和使用。
    为了更方便地使用libjpeg实现本章实验的功能,正点原子提供了mjpeg.c和mjpeg.h这两个文件,这两个文件提供了初始化、解码等三个函数,大大简化了libjpeg的使用流程,函数原型,如下所示:
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文件夹中的上一个或下一个视频。

你可能感兴趣的:(stm32,音视频,嵌入式硬件)