打印
[STM32F4]

AVI文件存盘尾部索引双链表异常

[复制链接]
217|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 feiyinglala 于 2023-1-10 01:09 编辑

最近在用JPEG数据合成AVI文件,但是AVI视频存盘发现,索引块数据缺失较多(JPG数据200帧全部存盘成功的),我设置参数是捕获200帧图片数据,实际AVI文件尾部只有31帧的索引(仿真看时,双链表长度不止31)。我现在不知道该从哪里查问题,请各位指点一下。
AVI尾部索引详见下图


主函数和调用写文件函数代码如下
//**************main.c************************
#define JPEG_NUM        200                //捕获JPEG图片数据200张,合成AVI
        f_open(&fp_avi,"0:/sample-2.avi",FA_CREATE_ALWAYS|FA_WRITE);        //创建AVI文件
    jpeg2avi_start(&fp_avi);                                //写AVI文件头数据
        if(jpglen)        //捕获JPG数据正常
        {
        if(Video_i < JPEG_NUM)        //若捕获次数在指定JPEG数量内,数据写入当前AVI文件
                {
                        jpeg2avi_add_frame(&fp_avi, &jpgdat, jpglen);        //【主体】增加视频帧
                }
                else if(Video_i == JPEG_NUM)
                {//达到数量后则写文件结尾并关闭文件
                        jpeg2avi_end(&fp_avi, jpeg_img_size_tbl[3][0],jpeg_img_size_tbl[3][1], 1);        //最后一个数字为每秒帧速
                        f_close(&fp_avi);
                        printf("endn");                        
                }
                Video_i++;
        }                                


//*************Jpeg2AVI.c*******************
// 参考 https://article.itxueyuan.com/rQj30
#include "Jpeg2AVI.h"
#include "list.h"
#include <stdlib.h>
#include <string.h>
#include "ff.h"

static int nframes;           //总帧数
static int totalsize;         //帧的总大小
static struct list_head list; //保存各帧图像大小的链表,用于写索引块
unsigned int bww;
/*链表宿主结构,用于保存真正的图像大小数据*/
struct ListNode
{
    int value;
    struct list_head head;        //双链表
};
static void write_index_chunk(FIL *fp)
{unsigned char count;
    unsigned char index[4] = {'i', 'd', 'x', '1'};  //索引块ID
    unsigned int index_chunk_size = 16 * nframes;   //索引块大小
    unsigned int offset = 4;                        
    struct list_head *slider = NULL;                //
    struct list_head *tmpslider = NULL;                //
        f_write(fp,index,4,&bww);                                //写数据:索引字符(4B)
        f_write(fp,&index_chunk_size,4,&bww);        //写数据:索引大小字符(4B)
    list_for_each_safe(slider, tmpslider, &list)        //宏定义见下行
//for(slider=(head)->next,tmpslider=pos->next; pos!= (head);pos=tmpslider,tmpslider=pos->next)
    {//【运行次数存疑】
        unsigned char tmp[4] = {'0', '0', 'd', 'c'};  //00dc = 压缩的视频数据
        unsigned int keyframe = 0x10;                 //0x10表示当前帧为关键帧
        struct ListNode *node = list_entry(slider, struct ListNode, head);        //获取数值

                f_write(fp,tmp,4,&bww);                        //写数据:视频块前导码(4B)
                f_write(fp,&keyframe,4,&bww);    //写数据:关键帧(4B)
                f_write(fp,&offset,4,&bww);      //写数据:(4B)
                f_write(fp,&node->value,4,&bww);        //写数据:(4B)
        offset = offset + node->value + 8;        //计算偏移
        list_del(slider);                //从清单中删除对应入口
        free(node);                                //释放               
    }
}
static void back_fill_data(FIL *fp, int width, int height, int fps)
{//回填数据
    AVI_RIFF_HEAD riff_head =
    {
        {'R', 'I', 'F', 'F'},
                4 + sizeof(AVI_HDRL_LIST) + sizeof(AVI_LIST_HEAD) + nframes * 8 + totalsize,  
                {'A', 'V', 'I', ' '}
    };
    AVI_HDRL_LIST hdrl_list =
    {
        {'L', 'I', 'S', 'T'},
        sizeof(AVI_HDRL_LIST) - 8,
        {'h', 'd', 'r', 'l'},
        {//关于帧率,L63 L75是关键配置
            {'a', 'v', 'i', 'h'},
            sizeof(AVI_AVIH_CHUNK) - 8,      
                        1000000 / fps, 25000, 0, 0, nframes, 0, 1, 100000, width, height,
            {0, 0, 0, 0}
        },
        {
            {'L', 'I', 'S', 'T'},
            sizeof(AVI_STRL_LIST) - 8,
            {'s', 't', 'r', 'l'},
            {
                {'s', 't', 'r', 'h'},
                sizeof(AVI_STRH_CHUNK) - 8,
                {'v', 'i', 'd', 's'},
                {'J', 'P', 'E', 'G'},
                0, 0, 0, 0, 1, fps, 0, nframes, 100000, 0xFFFFFF, 0,
                {0, 0, width, height}
            },
            {
                {'s', 't', 'r', 'f'},
                sizeof(AVI_STRF_CHUNK) - 8,
                sizeof(AVI_STRF_CHUNK) - 8,
                width, height, 1, 24,
                {'J', 'P', 'E', 'G'},
                width * height * 3, 0, 0, 0, 0
            }
        }
    };
    AVI_LIST_HEAD movi_list_head =
    {
        {'L', 'I', 'S', 'T'},     
                4 + nframes * 8 + totalsize,           
        {'m', 'o', 'v', 'i'}   
    };
    //定位到文件头,回填各块数据
        f_lseek(fp, 0);

        f_write(fp,&riff_head,sizeof(riff_head),&bww);        //写RIFF头
        f_write(fp,&hdrl_list,sizeof(hdrl_list),&bww);        //写HDRL头
        f_write(fp,&movi_list_head,sizeof(movi_list_head),&bww);                //【关键数据写入位置】写MOVE_LIST头
}
void jpeg2avi_start(FIL *fp)
{//AVI文件头
    int offset1 = sizeof(AVI_RIFF_HEAD);  //riff head大小:C
    int offset2 = sizeof(AVI_HDRL_LIST);  //hdrl list大小:C8
    int offset3 = sizeof(AVI_LIST_HEAD);  //movi list head大小:C
    //AVI文件偏移量设置到movi list head后,从该位置向后依次写入JPEG数据
   
    f_lseek(fp, offset1 + offset2 + offset3);        //不确定具体改法
        //初始化链表
    list_head_init(&list);
    nframes = 0;
    totalsize = 0;
}
void jpeg2avi_add_frame(FIL *fp, void *data, unsigned int len)
{//        
    unsigned char tmp[4] = {'0', '0', 'd', 'c'};  //00dc = 压缩的视频数据
    struct ListNode *node = (struct ListNode *)malloc(sizeof(struct ListNode));
    /*JPEG图像大小4字节对齐*/
    while (len % 4)
    {
        len++;
    }

        f_write(fp,tmp,4,&bww);    //写入是否是压缩的视频数据信息
        f_write(fp,&len,4,&bww);   //写入4字节对齐后的JPEG图像大小
        f_write(fp,data,len,&bww); //写入真正的JPEG数据【干货数据】
    nframes += 1;
    totalsize += len;
    /*将4字节对齐后的JPEG图像大小保存在链表中*/
    if (node != NULL)
    {
        node->value = len;                //将长度值赋予链表项
        list_add_tail(&node->head, &list);        //【重点排查】插入到链表尾部,主要在list.c中
    }//所在链表,单链表list_head
}
void jpeg2avi_end(FIL *fp, int width, int height, int fps)
{//写索引块,
    write_index_chunk(fp);
    //从文件头开始,回填各块数据
    back_fill_data(fp, width, height, fps);
}
对应链表定义如下
//**************list.c************
#include "list.h"
#include <stdio.h>
static void __list_add(struct list_head *_new, struct list_head *prev, struct list_head *next)
{
    next->prev = _new;
    _new->next = next;
    _new->prev = prev;
    prev->next = _new;
}
static void __list_del(struct list_head *prev, struct list_head *next)
{
    next->prev = prev;
    prev->next = next;
}
void list_head_init(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}
/**
* list_add_tail - insert a new entry before the specified head
* @_new: new entry to be added
* @head: list head to add it before
*/
void list_add_tail(struct list_head *_new, struct list_head *head)
{
    __list_add(_new, head->prev, head);
}
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
*/
void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    entry->next = NULL;
    entry->prev = NULL;
}

//**************list.h***************
// 参考 https://article.itxueyuan.com/rQj30
#ifndef _LIST_H_
#define _LIST_H_
struct list_head
{
    struct list_head *next;
    struct list_head *prev;
};
void list_head_init(struct list_head *list);
void list_add_tail(struct list_head *_new, struct list_head *head);
void list_del(struct list_head *entry);
#ifndef offsetof
        #define offsetof(TYPE, MEMBER)         ((size_t) &((TYPE *)0)->MEMBER)
#endif
#ifndef container_of
        #define container_of(ptr, type, member) ((type *)((char *)ptr - offsetof(type,member)))
#endif
/**
* list_entry - get the struct for this entry
* @ptr:    the &struct list_head pointer.
* @type:    the type of the struct this is embedded in.
* @member:    the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member)  container_of(ptr, type, member)
/**
* list_for_each_safe - iterate over a list safe against removal of list entry
* @pos:    the &struct list_head to use as a loop cursor.
* @n:        another &struct list_head to use as temporary storage
* @head:    the head for your list.
*/
#define list_for_each_safe(pos, n, head)        for (pos = (head)->next, n = pos->next; pos != (head); pos = n, n = pos->next)
#endif //_LIST_H_

使用特权

评论回复
沙发
feiyinglala|  楼主 | 2023-1-12 23:22 | 只看该作者
经过我学计算机的朋友协助,逐渐排查问题定位,结论是:堆栈空间设置不足,导致在索引对应链表扩展时,出现溢出。
最开始我也怀疑过溢出,但是问题出现在末端,那就从末端一步步排查,先看主函数,分析SD存盘数据发现JPEG原始数据没问题,那先暂时跳过jpeg2avi_add_frame往后走,那就该分析写文件结尾的函数jpeg2avi_end了。
jpeg2avi_end中包含两部分,一是write_index_chunk,二是back_fill_data。write_index_chunk是直接写AVI的尾部索引的,嫌疑最大。继续往下走
list_for_each_safe的本质是一个for循环,就是分配临时指针,遍历整个链表。在其中设置调试命令,串口输出循环次数。只能循环到30次,问题可能还在之前的过程。
我计算机同学说,list_for_each_safe这个函数是很规范的遍历函数,出错可能性不大,建议我再往前捋一捋,让我在jpeg2avi_add_frame中增加串口调试命令,果然只能正常输出30次,第31次,就不满足(node != NULL)的条件了。
    if (node != NULL)
    {//会不会因为链表增加时遇到溢出了?
        node->value = len;                //将长度值赋予链表项
        list_add_tail(&node->head, &list);        //【重点排查】插入到链表尾部,主要在list.c中
printf("添加数据帧第%d次\r\n",tmp_count);
tmp_count++;
    }//所在链表,单链表list_head


接下来,根据可能性判定溢出可能性较大,我看网上分析,得看.map文件,我把map文件打开一看,妈呀,太长太长,看不懂。我只能仿真,耗费一个小时进行仿真,找寻关键线索。最开始没有头绪,后来我想,node是每次分配的,如果node分配正确,那我就找找正常的分配是什么规律,30次之后,node又发生了什么变化,仿真要关注memory的实际变换。

下面,我用图示的方式,将仿真过程中的最终追踪定位给大家做一分享。

使用特权

评论回复
板凳
feiyinglala|  楼主 | 2023-1-12 23:36 | 只看该作者
第一步,先看正常的分配,盯住node,根据地址,看在内存分配、链表更新时候,内存中值的变化。
第二步,从29次jpeg2avi_add_frame开始,不断观察node的值的变化,30次后值是0x20026a68

第三步,31次后值变成了0x00000000,那么就是这里跑偏了。

第四步,从.map文件中找线索,查0x20026a68无果,那就只查前6位,搜到了__heap_limit

heap_limit与heap_base差值应该就是堆的大小,0x20026a68在加一次就超过了heap_limit(0x20026a80)

4-31.JPG (456.92 KB )

4-31.JPG

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

20

主题

210

帖子

1

粉丝