zoukankan      html  css  js  c++  java
  • JPEG流封装AVI视频

    前言:前几天工作任务,要把JPEG流封装为AVI视频,就找了些AVI文件结构资料和示例代码研究了下,现将学习总结及最终完成的可用代码分享出来,由于本人也是现学现用,如有不恰当或错误之处,欢迎提出!

    1  AVI文件结构

    AVI采用RIFF文件结构方式,RIFF是微软定义的一种用于管理windows环境中多媒体数据的文件格式,波形音频wave、MIDI和数字视频AVI都采用这种格式存储,构造RIFF文件的基本单元叫做数据块(Chunk),每个数据块包含3个部分:

    (1)4字节的数据块标记(Chunk ID)

    (2)4字节的数据块大小

    (3)数据

    整个RIFF文件可以看成一个ID为RIFF的数据块,RIFF块包含一系列子块,其中有一种子块的ID为LIST,称为LIST块,LIST块中可以再包含一系列子块,但除了LIST块的其他所有子块都不能再包含子块。

    RIFF和LIST块分别比普通的数据块多一个被称为形式类型(Form Type)和列表类型(List Type)的数据域,其组成如下:

    (1)4字节的数据块标记(Chunk ID)

    (2)4字节的数据块大小

    (3)4字节的形式类型(对于RIFF块)或列表类型(对于LIST块)

    (4)数据

    AVI文件是最复杂的RIFF文件,它能够同时存储音频和视频数据(注:本文档不涉及音频相关内容,只针对视频数据进行介绍),AVI文件RIFF块的形式类型是AVI ,它包含以下3个子块:

    (1)信息块,ID为hdrl的LIST块,用于定义AVI文件的数据格式

    (2)数据块,ID为movi的LIST块,用于存储音视频数据

    (3)索引块,ID为idxl的数据块,用于定义音视频数据的索引,是可选块

    AVI文件结构如图1所示

                                        图1  AVI文件结构

    1.1 信息块

    信息块包含两个子块:一个ID为avih的子块和一个ID为strl的LIST块。

    1.1.1 avih块

                                                      图2  avih块结构

    avih块可用如图2所示的struct avi_avih_chunk结构体定义,图中已对结构体各变量的含义进行了解释,以下是对其中几个变量的补充说明:

    (1)max_bytes_per_sec

    max_bytes_per_sec用于控制视频的最大码率,即每秒传输的最大数据量。但实际上,给这个变量赋值并不能影响视频的码率,原因如下:设JPEG流图像总帧数为nframes,视频帧率为fps,各帧图像平均大小为len,则封装的AVI视频时长、文件大小和视频码率分别为

    time = nframes / fps

    video_size = nframes * len(实际大小还要加上文件头和文件尾的数据)

    rate = video_size / time = fps * len

    由此可见,在固定的帧率fps下,视频码率完全取决于JPEG各帧图像的大小,和max_bytes_per_sec的值没有关系,所以这个变量设为0即可。

    (2)flags

    flags表示AVI文件的全局属性,如是否含有索引块、是否即有音频数据又有视频数据等,不进行任何标记时flags值为0,若含有索引块,则flags值为0x00000010。

    (3)init_frames

    AVI文件若同时存储了音频和视频数据,则音频数据和视频数据是交叉存储的,init_frames仅在这种情况下使用,对于只有视频流的情况,该变量的值为0。

    (4)width、height

    这里的width和height不是JPEG图像的宽和高,而是用播放器打开AVI文件时视频主窗口的宽和高,举个例子,JPEG图像大小为1920*1080,width和height分别设为960和540,用QQ影音打开AVI文件,则QQ影音会以960*540的窗口大小进行播放。

    1.1.2 strl块

    strl块由图3所示的结构体定义,它包含strh和strf两个子块。

                                                 图3  strl块结构

    1、strh块结构

                                                    图4  strh块结构

    图4所示为strh块结构定义,下面是对结构体内一些变量含义的补充说明:

    (1)codec

    codec是一个长度为4的字符数组,用于指定数据流的编码格式,也就是播放器播放这个流时需要的解码器,对于JPEG编码的视频流,codec数组内容就是'J', 'P', 'E', 'G',而不能随意指定,否则播放器播放时会无法解码。

    (2)scale、rate

    对于视频流,rate除以scale等于视频帧率,因此这两个变量可赋值为scale = 1、rate = fps。

    2、strf块结构

    strf块结构根据strh块中stream_type是视频流还是音频流而有所不同,对于视频流,strf块结构如图5所示,其中bitcount表示每个图像像素占的位数,其值根据视频流的实际情况而定,但只能是1、4、8、16、24和32之一,常用的有1(黑白二值化图像)、8(256阶灰度图)和24(RGB图像)。

                                                  图5  针对视频流的strf块结构

    1.2 数据块

    由图1可知,数据块是一个ID为movi的LIST列表,也称为movi块,在仅有视频流时,该部分存储的就是一帧一帧的图像数据,图6展示了视频流movi块的详细结构。

                            图6 仅有视频流的movi块结构

    可以看到,movi块首先是一个固定结构的LIST列表头,包括块ID、块大小和块类型,其中块ID固定为LIST,块类型固定为movi,块大小为movi块去掉开头8字节后的大小。

    然后是movi块数据,也就是各帧视频图像对应的数据块,每一帧图像的数据块都包含三部分:

    (1)4字节ID:可以为00dc或00db,00dc表示压缩的视频数据,00db表示未压缩的视频数据,根据视频流的实际情况来选择赋值。

    (2)4字节frame length:图像数据长度(单位:字节),该长度必须是4的整数倍,如果不是,则需要将其修正到4的整数倍,比如frame length原始数据为99,则需将其加到100。

    (3)frame data:真正的图像数据。

    1.3 索引块

    索引块是AVI文件结构的可选部分,它是一个ID等于idxl的数据块,索引块提供了movi块中存储各帧图像的数据块在AVI文件中的位置索引,作用是提高AVI文件的读写速度,提高视频播放时的体验效果。

                                         图7 索引块结构

    如图7所示为索引块结构,包括块ID、块大小和块数据三部分,其中块ID固定为idxl,块大小等于索引块数据的大小。

    索引块数据是movi块中存储各帧图像数据块的索引,每一帧图像的索引都是一个16字节的数据结构,具体如下:

    (1)4字节ChunkID:即movi块各帧图像数据块的ID,00dc或00db

    (2)4字节ChunkFlag:表示该帧图像是否是关键帧,0x10代表关键帧,0x00代表非关键帧

    (3)4字节ChunkOffset:图像数据块相对于“movi”标示符(图6红色箭头所指处)的偏移量,由图6可得,各帧图像索引ChunkOffset的值为:

    第一帧图像索引àChunkOffset1 = 4;

    第二帧图像索引àChunkOffset2 = ChunkOffset1+8+第一帧图像数据长度

    第三帧图像索引àChunkOffset3 = ChunkOffset2+8+第二帧图像数据长度

    …… (后面各帧图像索引以此类推,其中各帧图像数据长度指的是修正到4的整数倍后的长度)

    (4)4字节ChunkLength:修正到4的整数倍后的各帧图像数据长度

    2  JPEG流封装AVI步骤

    JPEG流封装AVI视频的本质是按照AVI结构进行文件读写,操作流程大体上可分为三个步骤:

    步骤1:创建空白AVI文件,设置文件偏移量到数据块movi标示符后面

    (1)创建AVI文件,以二进制写方式打开

    (2)计算文件偏移量offset,等于RIFF文件头12字节 + hdrl块大小 + movi LIST头12字节

    (3)设置AVI文件偏移量为offset

    步骤2:从offset偏移量处开始,向AVI文件中逐帧写入JPEG数据

    (1)将当前JPEG图像数据长度加到4的整数倍,用length表示

    (2)JPEG图像是压缩过的图像数据,故写入'0', '0', 'd', 'c'

    (3)写入当前JPEG图像数据长度length

    (4)写入当前JPEG图像数据,写入长度为length

    (5)循环上述过程,完成逐帧图像数据的写入

    步骤3:JPEG数据写完后,先继续向后写索引块,再定位到文件头回填各块数据

    (1)写索引块

    - 先写块ID  'i', 'd', 'x', 'l'

    - 再写块大小 16 * nframes

    - 最后写各帧图像的索引

    (2)从文件头开始,回填各块数据

    - 设置文件偏移量为0

    - 按照AVI文件结构,写入步骤1跳过的各块数据

    需要注意的是,步骤3写索引块时需要各帧图像的数据长度和总帧数,回填各块数据时也需要总帧数和所有帧的总大小,因此步骤2写入JPEG数据时需要保存它们的值。

    3  代码分享

     我完成的代码,是以若干张JPEG图片作为JPEG流,先将图片数据读入内存,再写入AVI文件,共包含五个文件:

    1、list.h和list.c,双向循环链表,作用是保存各帧图像大小,用于写索引块

    2、Jpeg2AVI.h和Jpeg2AVI.c,用于将JPEG流封装为AVI视频

    3、main.c,测试程序

    Jpeg2AVI.h

     1 #ifndef _JPEG2AVI_H_
     2 #define _JPEG2AVI_H_
     3 
     4 #include <stdio.h>
     5 
     6 void jpeg2avi_start(FILE *fp);
     7 void jpeg2avi_add_frame(FILE *fp, void *data, unsigned int len);
     8 void jpeg2avi_end(FILE *fp, int width, int height, int fps);
     9 
    10 typedef struct avi_riff_head
    11 {
    12     unsigned char id[4];        
    13     unsigned int size;           
    14     unsigned char type[4];   
    15 }AVI_RIFF_HEAD, AVI_LIST_HEAD;
    16 
    17 typedef struct avi_avih_chunk
    18 {
    19     unsigned char id[4];            //块ID,固定为avih
    20     unsigned int size;              //块大小,等于struct avi_avih_chunk去掉id和size的大小
    21     unsigned int us_per_frame;      //视频帧间隔时间(以微秒为单位)
    22     unsigned int max_bytes_per_sec; //AVI文件的最大数据率
    23     unsigned int padding;           //设为0即可
    24     unsigned int flags;             //AVI文件全局属性,如是否含有索引块、音视频数据是否交叉存储等
    25     unsigned int total_frames;      //总帧数
    26     unsigned int init_frames;       //为交互格式指定初始帧数(非交互格式应该指定为0)
    27     unsigned int streams;           //文件包含的流的个数,仅有视频流时为1
    28     unsigned int suggest_buff_size; //指定读取本文件建议使用的缓冲区大小,通常为存储一桢图像                                            //以及同步声音所需的数据之和,不指定时设为0
    29     unsigned int width;             //视频主窗口宽度(单位:像素)
    30     unsigned int height;            //视频主窗口高度(单位:像素)
    31     unsigned int reserved[4];       //保留段,设为0即可
    32 }AVI_AVIH_CHUNK;
    33 
    34 typedef struct avi_rect_frame
    35 {
    36     short left;
    37     short top;
    38     short right;
    39     short bottom;    
    40 }AVI_RECT_FRAME;
    41 
    42 typedef struct avi_strh_chunk
    43 {    
    44     unsigned char id[4];            //块ID,固定为strh
    45     unsigned int size;              //块大小,等于struct avi_strh_chunk去掉id和size的大小
    46     unsigned char stream_type[4];   //流的类型,vids表示视频流,auds表示音频流
    47     unsigned char codec[4];         //指定处理这个流需要的解码器,如JPEG
    48     unsigned int flags;             //标记,如是否允许这个流输出、调色板是否变化等,一般设为0即可
    49     unsigned short priority;        //流的优先级,视频流设为0即可
    50     unsigned short language;        //音频语言代号,视频流设为0即可
    51     unsigned int init_frames;       //为交互格式指定初始帧数(非交互格式应该指定为0)
    52     unsigned int scale;             //
    53     unsigned int rate;              //对于视频流,rate / scale = 帧率fps
    54     unsigned int start;             //对于视频流,设为0即可
    55     unsigned int length;            //对于视频流,length即总帧数
    56     unsigned int suggest_buff_size; //读取这个流数据建议使用的缓冲区大小
    57     unsigned int quality;           //流数据的质量指标
    58     unsigned int sample_size;       //音频采样大小,视频流设为0即可
    59     AVI_RECT_FRAME rcFrame;         //这个流在视频主窗口中的显示位置,设为{0,0,width,height}即可
    60 }AVI_STRH_CHUNK;
    61 
    62 /*对于视频流,strf块结构如下*/
    63 typedef struct avi_strf_chunk
    64 {
    65     unsigned char id[4];             //块ID,固定为strf
    66     unsigned int size;               //块大小,等于struct avi_strf_chunk去掉id和size的大小
    67     unsigned int size1;              //size1含义和值同size一样
    68     unsigned int width;              //视频主窗口宽度(单位:像素)
    69     unsigned int height;             //视频主窗口高度(单位:像素)
    70     unsigned short planes;           //始终为1  
    71     unsigned short bitcount;         //每个像素占的位数,只能是1、4、8、16、24和32中的一个
    72     unsigned char compression[4];    //视频流编码格式,如JPEG、MJPG等
    73     unsigned int image_size;         //视频图像大小,等于width * height * bitcount / 8
    74     unsigned int x_pixels_per_meter; //显示设备的水平分辨率,设为0即可
    75     unsigned int y_pixels_per_meter; //显示设备的垂直分辨率,设为0即可
    76     unsigned int num_colors;         //含义不清楚,设为0即可   
    77     unsigned int imp_colors;         //含义不清楚,设为0即可
    78 }AVI_STRF_CHUNK;
    79 
    80 typedef struct avi_strl_list
    81 {
    82     unsigned char id[4];    //块ID,固定为LIST    
    83     unsigned int size;      //块大小,等于struct avi_strl_list去掉id和size的大小        
    84     unsigned char type[4];  //块类型,固定为strl
    85     AVI_STRH_CHUNK strh;      
    86     AVI_STRF_CHUNK strf;      
    87 }AVI_STRL_LIST;
    88 
    89 typedef struct avi_hdrl_list
    90 {
    91     unsigned char id[4];    //块ID,固定为LIST    
    92     unsigned int size;      //块大小,等于struct avi_hdrl_list去掉id和size的大小        
    93     unsigned char type[4];  //块类型,固定为hdrl
    94     AVI_AVIH_CHUNK avih;
    95     AVI_STRL_LIST  strl;
    96 }AVI_HDRL_LIST;
    97 
    98 #endif
    View Code

    Jpeg2AVI.c

      1 #include "Jpeg2AVI.h"
      2 #include "list.h"
      3 #include <stdlib.h>
      4 #include <string.h>
      5 
      6 static int nframes;           //总帧数
      7 static int totalsize;         //帧的总大小
      8 static struct list_head list; //保存各帧图像大小的链表,用于写索引块
      9 
     10 /*链表宿主结构,用于保存真正的图像大小数据*/
     11 struct ListNode
     12 {
     13     int value;
     14     struct list_head head;
     15 };
     16 
     17 static void write_index_chunk(FILE *fp)
     18 {
     19     unsigned char index[4] = {'i', 'd', 'x', '1'};  //索引块ID
     20     unsigned int index_chunk_size = 16 * nframes;   //索引块大小
     21     unsigned int offset = 4;                        
     22     struct list_head *slider = NULL;
     23     struct list_head *tmpslider = NULL;
     24 
     25     fwrite(index, 4, 1, fp);
     26     fwrite(&index_chunk_size, 4, 1, fp);
     27 
     28     list_for_each_safe(slider, tmpslider, &list)
     29     {
     30         unsigned char tmp[4] = {'0', '0', 'd', 'c'};  //00dc = 压缩的视频数据
     31         unsigned int keyframe = 0x10;                 //0x10表示当前帧为关键帧
     32         struct ListNode *node = list_entry(slider, struct ListNode, head);
     33 
     34         fwrite(tmp, 4, 1, fp);
     35         fwrite(&keyframe, 4, 1, fp);    
     36         fwrite(&offset, 4, 1, fp);        
     37         fwrite(&node->value, 4, 1, fp);
     38         offset = offset + node->value + 8;
     39 
     40         list_del(slider);
     41         free(node);
     42     }
     43 }
     44 
     45 static void back_fill_data(FILE *fp, int width, int height, int fps)
     46 {
     47     AVI_RIFF_HEAD riff_head = 
     48     {
     49         {'R', 'I', 'F', 'F'},     
     50         4 + sizeof(AVI_HDRL_LIST) + sizeof(AVI_LIST_HEAD) + nframes * 8 + totalsize,  
     51         {'A', 'V', 'I', ' '}
     52     };
     53 
     54     AVI_HDRL_LIST hdrl_list = 
     55     {
     56         {'L', 'I', 'S', 'T'},
     57         sizeof(AVI_HDRL_LIST) - 8,
     58         {'h', 'd', 'r', 'l'},
     59         {
     60             {'a', 'v', 'i', 'h'},
     61             sizeof(AVI_AVIH_CHUNK) - 8,       
     62             1000000 / fps, 25000, 0, 0, nframes, 0, 1, 100000, width, height, 
     63             {0, 0, 0, 0}
     64         },
     65         {
     66             {'L', 'I', 'S', 'T'},
     67             sizeof(AVI_STRL_LIST) - 8,
     68             {'s', 't', 'r', 'l'},
     69             {
     70                 {'s', 't', 'r', 'h'},
     71                 sizeof(AVI_STRH_CHUNK) - 8,
     72                 {'v', 'i', 'd', 's'},
     73                 {'J', 'P', 'E', 'G'},
     74                 0, 0, 0, 0, 1, 23, 0, nframes, 100000, 0xFFFFFF, 0,
     75                 {0, 0, width, height}
     76             },
     77             {
     78                 {'s', 't', 'r', 'f'},
     79                 sizeof(AVI_STRF_CHUNK) - 8,
     80                 sizeof(AVI_STRF_CHUNK) - 8,
     81                 width, height, 1, 24,
     82                 {'J', 'P', 'E', 'G'},
     83                 width * height * 3, 0, 0, 0, 0
     84             }
     85         }
     86     };
     87 
     88     AVI_LIST_HEAD movi_list_head = 
     89     {
     90         {'L', 'I', 'S', 'T'},     
     91         4 + nframes * 8 + totalsize,           
     92         {'m', 'o', 'v', 'i'}    
     93     };
     94 
     95     //定位到文件头,回填各块数据
     96     fseek(fp, 0, SEEK_SET);
     97     fwrite(&riff_head, sizeof(riff_head), 1, fp);
     98     fwrite(&hdrl_list, sizeof(hdrl_list), 1, fp);
     99     fwrite(&movi_list_head, sizeof(movi_list_head), 1, fp);
    100 }
    101 
    102 void jpeg2avi_start(FILE *fp)
    103 {
    104     int offset1 = sizeof(AVI_RIFF_HEAD);  //riff head大小
    105     int offset2 = sizeof(AVI_HDRL_LIST);  //hdrl list大小 
    106     int offset3 = sizeof(AVI_LIST_HEAD);  //movi list head大小
    107 
    108     //AVI文件偏移量设置到movi list head后,从该位置向后依次写入JPEG数据
    109     fseek(fp, offset1 + offset2 + offset3, SEEK_SET); 
    110 
    111     //初始化链表
    112     list_head_init(&list);
    113 
    114     nframes = 0;
    115     totalsize = 0;
    116 }
    117 
    118 void jpeg2avi_add_frame(FILE *fp, void *data, unsigned int len)
    119 {
    120     unsigned char tmp[4] = {'0', '0', 'd', 'c'};  //00dc = 压缩的视频数据
    121     struct ListNode *node = (struct ListNode *)malloc(sizeof(struct ListNode));
    122 
    123     /*JPEG图像大小4字节对齐*/
    124     while (len % 4)
    125     {
    126         len++;
    127     }
    128 
    129     fwrite(tmp, 4, 1, fp);    //写入是否是压缩的视频数据信息    
    130     fwrite(&len, 4, 1, fp);   //写入4字节对齐后的JPEG图像大小
    131     fwrite(data, len, 1, fp); //写入真正的JPEG数据
    132 
    133     nframes += 1;
    134     totalsize += len;
    135 
    136     /*将4字节对齐后的JPEG图像大小保存在链表中*/
    137     if (node != NULL)
    138     {
    139         node->value = len;
    140         list_add_tail(&node->head, &list);
    141     }
    142 }
    143 
    144 void jpeg2avi_end(FILE *fp, int width, int height, int fps)
    145 { 
    146     //写索引块
    147     write_index_chunk(fp);
    148 
    149     //从文件头开始,回填各块数据
    150     back_fill_data(fp, width, height, fps);
    151 }
    View Code

    list.h

     1 #ifndef _LIST_H_
     2 #define _LIST_H_
     3 
     4 struct list_head
     5 {
     6     struct list_head *next;
     7     struct list_head *prev;
     8 };
     9 
    10 void list_head_init(struct list_head *list);
    11 void list_add_tail(struct list_head *_new, struct list_head *head);
    12 void list_del(struct list_head *entry);
    13 
    14 #ifndef offsetof
    15 #define offsetof(TYPE, MEMBER) 
    16     ((size_t) &((TYPE *)0)->MEMBER)
    17 #endif
    18 
    19 #ifndef container_of
    20 #define container_of(ptr, type, member) 
    21     ((type *)((char *)ptr - offsetof(type,member)))
    22 #endif
    23 
    24 /**
    25  * list_entry - get the struct for this entry
    26  * @ptr:    the &struct list_head pointer.
    27  * @type:    the type of the struct this is embedded in.
    28  * @member:    the name of the list_struct within the struct.
    29  */
    30 #define list_entry(ptr, type, member) 
    31     container_of(ptr, type, member)
    32 
    33 /**
    34  * list_for_each_safe - iterate over a list safe against removal of list entry
    35  * @pos:    the &struct list_head to use as a loop cursor.
    36  * @n:        another &struct list_head to use as temporary storage
    37  * @head:    the head for your list.
    38  */
    39 #define list_for_each_safe(pos, n, head) 
    40     for (pos = (head)->next, n = pos->next; pos != (head); 
    41         pos = n, n = pos->next)
    42 
    43 #endif //_LIST_H_
    View Code

    list.c

     1 #include "list.h"
     2 #include <stdio.h>
     3 
     4 static void __list_add(struct list_head *_new, struct list_head *prev, struct list_head *next)
     5 {
     6     next->prev = _new;
     7     _new->next = next;
     8     _new->prev = prev;
     9     prev->next = _new;
    10 }
    11 
    12 static void __list_del(struct list_head *prev, struct list_head *next)
    13 {
    14     next->prev = prev;
    15     prev->next = next;
    16 }
    17 
    18 void list_head_init(struct list_head *list)
    19 {
    20     list->next = list;
    21     list->prev = list;
    22 }
    23 
    24 /**
    25  * list_add_tail - insert a new entry before the specified head
    26  * @_new: new entry to be added
    27  * @head: list head to add it before
    28  */
    29 void list_add_tail(struct list_head *_new, struct list_head *head)
    30 {
    31     __list_add(_new, head->prev, head);
    32 }
    33 
    34 /**
    35  * list_del - deletes entry from list.
    36  * @entry: the element to delete from the list.
    37  */
    38 void list_del(struct list_head *entry)
    39 {
    40     __list_del(entry->prev, entry->next);
    41     entry->next = NULL;
    42     entry->prev = NULL;
    43 }
    View Code

    main.c

     1 #include "Jpeg2AVI.h"
     2 #include <string.h>
     3 
     4 #define JPEG_MAX_SIZE 100000   //JPEG图像最大字节数
     5 #define JPEG_NUM 13800         //JPEG图像数量
     6 
     7 int main()
     8 {
     9     FILE *fp_jpg;
    10     FILE *fp_avi;  
    11     int filesize;
    12     unsigned char jpg_data[JPEG_MAX_SIZE];  
    13     char filename[10];   
    14     int i = 0;
    15 
    16     fp_avi = fopen("sample.avi","wb");
    17 
    18     jpeg2avi_start(fp_avi);
    19 
    20     for (i = 0; i < JPEG_NUM; i++)
    21     {
    22         memset(filename, 0, 10);
    23         memset(jpg_data, 0, JPEG_MAX_SIZE);
    24 
    25         sprintf(filename, "%d.jpg", i + 1);
    26         fp_jpg = fopen(filename, "rb");
    27         
    28         if (fp_jpg != NULL)
    29         {
    30             /*获取JPEG数据大小*/
    31             fseek(fp_jpg, 0, SEEK_END);
    32             filesize = ftell(fp_jpg);
    33             fseek(fp_jpg, 0, SEEK_SET);
    34 
    35             /*将JPEG数据读到缓冲区*/
    36             fread(jpg_data, filesize, 1, fp_jpg);
    37 
    38             /*将JPEG数据写入AVI文件*/
    39             jpeg2avi_add_frame(fp_avi, jpg_data, filesize);
    40         }
    41 
    42         fclose(fp_jpg);
    43     }
    44 
    45     jpeg2avi_end(fp_avi, 1920, 1080, 23);
    46 
    47     fclose(fp_avi);
    48     printf("end
    ");
    49  
    50     return 0;
    51 }
    View Code
  • 相关阅读:
    网络摄像头Androi端显示(mjpeg)源码分析
    STM32F103 MQTT智能配网ESP07S WIFI 手机app控制继电器EMQ温湿度
    安装云服务器 ECS CentOS 7 图形化桌面
    PT100多路分布式LORA无线测温传感器工业设备老化温度监控记录仪
    把qt 程序打包成一个exe可以执行的文件
    STM32L051C8T6 HAL DMA IDLE串口不定长接收遇到的问题
    Project ERROR: Cannot run target compiler '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++'. Output:
    更换下平台
    H5单页面应用(SPA)架构总结
    财务系统功能开发要点概述
  • 原文地址:https://www.cnblogs.com/songhe364826110/p/7619949.html
Copyright © 2011-2022 走看看