stm32h743单片机嵌入式学习笔记8-avi视频解码

版权声明:本文为博主原创文章,未经博主允许不得转载。欢迎联系我qq2488890051 https://blog.csdn.net/kangkanglhb88008/article/details/89511783

* AVI 格式允许视频和音频交错在一起同步播放,支持 256 色和 RLE 压缩,但 AVI 文件并
未限定压缩标准,AVI 仅仅是一个容器,用不同压缩算法生成的 AVI 文件,必须使用相应的解
压缩算法才能播放出来。比如本章,我们使用的 AVI,其音频数据采用 16 位线性 PCM 格式(未
压缩),而视频数据,则采用 MJPG 编码方式。

* 视频文件信息头这里有很多我们要用到的信息,比如 SecPerFrame,通过该参数,我们可以知道每秒钟的帧
率,也就知道了每秒钟需要解码多少帧图片,才能正常播放。TotalFrame 告诉我们整个视频有
多少帧,结合 SecPerFrame 参数,就可以很方便计算整个视频的时间了。

* 而且它告诉我们视频的分辨率(Width 和 Height),以及视频所用的编码器(Compression),因此它
决定了视频的解码。本章例程仅支持解码视频分辨率小于屏幕分辨率,且编解码器必须是 MJPG
的视频格式。

* 本结构体对音频数据解码起决定性的作用,他告诉我们音频信号的编码方式(FormatTag)、
声道数(Channels)和采样率(SampleRate)等重要信息。本章例程仅支持 PCM 格式
(FormatTag=0X0001)的音频数据解码。

也就是说avi视频是由许多帧组成的(每一帧就是一张图片),但是都是avi格式的视频的帧图片和音频却都可以采用不同的编码方式, 比如的A.avi视频的每一帧可能就是用的.png格式编码的,音频采用.wav。B.avi视频的每一帧可能就是用的.jpg格式编码的,音频采用.mp3。但是他们都是.avi格式的视频,所以解码的时候我们读取他的信息头数据,就可以知道应该用什么方式解码了。

由于stm32h743带的是JPG/JPEG图片硬件解码器,所以只能快速解码由 MJPG 编码的 AVI 格式视频播放

那也就是说,为了这个单片机能获得快速的解码,我们播放的视频得提前在电脑上用软件处理为MJPG编码的.avi视频,且音频必须是 PCM 格式(因为阿波罗开发板另外带的是这个解码芯片,我的水星板没有带,那就不播放声音了呗)

程序过程:

最外层函数

//播放一个mjpeg文件
//pname:文件名
//返回值:
//KEY0_PRES:下一曲
//KEY1_PRES:上一曲
//其他:错误
u8 video_play_mjpeg(u8 *pname)

即可,那它里面到底做了啥呢

1.读取这个avi视频文件结构体头信息,得到帧率等参数

            //开始avi解析
            res=avi_init(pbuf,AVI_VIDEO_BUF_SIZE);    //avi解析

这里就会把读取到的参数都赋值给全局变量频文件结构体变量avix了

2.显示出这个视频结构体的参数信息,按照帧率开启定时器定时时间,即按照帧率的时间间隔,来读取视频的数据流,从而实现按照视频指定帧率播放该视频

            video_info_show(&avix); 
            TIM6_Int_Init(avix.SecPerFrame/100-1,20000-1);//10Khz计数频率,加1是100us 

当然还会对视频的音频进行初始化

            if(avix.SampleRate)                            //有音频信息,才初始化
            {
                WM8978_I2S_Cfg(2,0);    //飞利浦标准,16位数据长度
                SAIA_Init(0,1,4);        //设置SAI,主发送,16位数据 
                SAIA_SampleRate_Set(avix.SampleRate);    //设置采样率
                SAIA_TX_DMA_Init(saibuf[1],saibuf[2],avix.AudioBufSize/2,1);//配置DMA
                sai_tx_callback=audio_sai_dma_callback;    //回调函数指向SAI_DMA_Callback
                saiplaybuf=0;
                saisavebuf=0; 
                SAI_Play_Start(); //开启sai播放 
            }
 

3.就是循环读取每一帧数据了,读取到每一帧(就是一张jpg图片)就调用单片机JPEG硬件进行解码,同时就在这个解码函数内显示在液晶屏上了(这个跟前面学习的fpg图片硬件解码过程一模一样的),解码完一帧后,不急着读下一帧,而是延时一个帧率时间间隔while(frameup==0);即这句代码,然后再读取下一帧且解码,如此循环。。。图片帧数读完且解码完之后,就退出就行了,代码如下

             while(1)//播放循环
            {                    
                if(avix.StreamID==AVI_VIDS_FLAG)        //视频流
                {
                    pbuf=framebuf; 
                    f_read(favi,pbuf,avix.StreamSize+8,&nr);        //读入整帧+下一数据流ID信息  
                    res=mjpeg_decode(pbuf,avix.StreamSize);
                    if(res)
                    {
                        printf("decode error!\r\n");
                    } 
                    while(frameup==0);    //等待时间到达(在TIM6的中断里面设置为1),即按照帧率速度
                    frameup=0;            //标志清零
                    frame++; 
。。。读完了所有帧,这里就退出这个死循环了,完成这个视频播放

我们来看看上面这个底层解码函数res=mjpeg_decode(pbuf,avix.StreamSize);关键性的做了啥

    JPEG_Decode_Init(&mjpeg);                        //初始化硬件JPEG解码器    

    JPEG_IN_DMA_Init((u32)mjpeg.inbuf[0].buf,mjpeg.inbuf[0].size);    //配置输入DMA 

if(mjpeg.state==JPEG_STATE_FINISHED)    //解码完成了
    {
        mjpeg_fill_color(imgoffx,imgoffy,mjpeg.Conf.ImageWidth,mjpeg.Conf.ImageHeight,rgb565buf);  
    } 
可以看到里面是进行调用JPEG硬件解码,然后配合DMA进行数据高速传输到JPEG硬件和从JPEG硬件直接传输到RGB屏幕的显存区域(必须是横屏模式,但是本例程是竖屏模式,所以DMA无法直接传输到显存)(显存就是sdram的一块内存,480*800的屏幕大概需要2MB显存,这里填啥,屏幕就显示啥了)(横屏这样的DMA方式显示速度是最快的),最后解码出的就是图片的像素点颜色数据了,然后进行调用液晶屏底层的函数显示再屏幕上即可

结果对比:对于 480*272 的视频,本例程最高能播放 30 帧左右的视频,我用了480*800的25帧视频,单片机播放出来大概只有20帧(我的水星开发板液晶屏测试都是480*800的电容屏),但是教材又说了

附 STM32H743 硬件 JPEG 视频解码性能:
对 800*480 分辨率,可达 50 帧
对 1024*600 分辨率,可达 20 帧
对 1280*800 分辨率,可达 10 帧

那为什么我的只有20帧呢,原因应该是因为这个例程是用竖屏模式,DMA无法直接填充颜色数据到显存,还经过了一些转换,所以耗时了,有兴趣的可以直接看看改成横屏模式,搞到50帧(好像综合例程就是横屏模式,可以参考改一下)
如果要想提高帧率,有几个办法:1,降低分辨率;2,降低比特率;3,降低音频采样率。

-------------------------------------------------------------------------------------------------------------------------------------------------------

最后再来分析一下控制帧率的定时器原理:

TIM6_Int_Init(avix.SecPerFrame/100-1,20000-1);//10Khz计数频率,加1是100us,单独开启了定时器6

然后设置了中断方式运行,中断函数:

vu8 frameup; 
//定时器6中断服务程序     
void TIM6_DAC_IRQHandler(void)
{                                       
    if(TIM6->SR&0X0001)//溢出中断
    { 
        frameup=1;
    }                   
    TIM6->SR&=~(1<<0);//清除中断标志位         
}

就是每隔帧率时间就中断了一下(然后定时器会自动重装载定时值),更新了frameup=1;这个变量,然后解码过程函数就会来查询这个变量是否为1,是的话就说明下一帧到了,赶紧开始解码,同时 frameup=0;            //标志清零

那我们来分析一下,如果单片机的一帧解码速度够快,说明解码完上一帧但是下一帧时间还没到,所以就会进行等待,那么就是完全准确的按照帧率播放该视频的。

如果单片机解码一帧速度不够,那么刚解码完一帧,此时一查询frameup是1(新的一帧指示变量早就在定时器中断里面已经更新为1了),就得继续读取且解码下一帧(所以cpu使用率会非常的高),那么这样的话就会出现掉帧的情况:要么是某些帧没有播放到(视频看起来不是连续的变化),要么就是视频时间到了就停止播放所以后面的帧没有播放出来(视频后面部分没有播出来),要么就是播放有卡顿,总播放时间就变长了罢了(这个看程序是怎么写的了,就会有不同的现象)。总体来说,就是感觉卡顿的

             while(1)//播放循环
            {                    
                if(avix.StreamID==AVI_VIDS_FLAG)        //视频流
                {
                    pbuf=framebuf; 
                    f_read(favi,pbuf,avix.StreamSize+8,&nr);        //读入整帧+下一数据流ID信息  
                    res=mjpeg_decode(pbuf,avix.StreamSize);
                    if(res)
                    {
                        printf("decode error!\r\n");
                    } 
                    while(frameup==0);    //等待时间到达(在TIM6的中断里面设置为1),即按照帧率速度
                    frameup=0;            //标志清零
                    frame++; 
。。。读完了所有帧,这里就退出这个死循环了,完成这个视频播放

猜你喜欢

转载自blog.csdn.net/kangkanglhb88008/article/details/89511783