(转)c 多张图片生成avi视频

https://www.cnblogs.com/songhe364826110/p/7619949.html

修改了几个参数,可以生成视频了。下载主要是为了学习avi视频格式。最后编一个摄像头生成视频的程序。

本程序把标准avi 视频格式的各种数据结构自定义在文件头(JpegAVI.h)中,所以就不用去下载借用ffmpeg,mplayer等的头文件,库文件了。现在发现,其实编写程序不难,难的是理解定义的各种数据结构和怎样按avi协议去用这些定义的结构。

现在有点明白了为什么学编程要数学好,数学不好就想不出这些数据结构和怎样构建这些文件体系。就象现在虽然有些c的编程知识,可什么也干不了,解决不了实际问题。想创新是不可能的,只有复制别人的成果。

图片文件名为数字,从0开始,不含后缀。

main.c

#include "Jpeg2AVI.h"
#include <string.h>

#define JPEG_MAX_SIZE 2000000   //JPEG图像最大字节数,这个数必须大于每一张图片的字节数
#define JPEG_NUM 59  //JPEG图像数量 这个数必须小于等于实际文件数

int main()
{
	FILE *fp_jpg;
	FILE *fp_avi;
	int filesize;
	unsigned char jpg_data[JPEG_MAX_SIZE];      
	
	char filename[10];
	int i = 0;
	
	fp_avi = fopen("sample.avi","wb");
	
	jpeg2avi_start(fp_avi);
	
	for (i = 0; i < JPEG_NUM; i++)
	{
		memset(filename, 0, 10);
		memset(jpg_data, 0, JPEG_MAX_SIZE);
		
		sprintf(filename, "%d", i);  //int转字符
		fp_jpg = fopen(filename, "rb");
		
		if (fp_jpg != NULL)
		{
			/*获取JPEG数据大小*/
			fseek(fp_jpg, 0, SEEK_END);
			filesize = ftell(fp_jpg);
			fseek(fp_jpg, 0, SEEK_SET);
			
			/*将JPEG数据读到缓冲区*/
			fread(jpg_data, filesize, 1, fp_jpg);
			
			/*将JPEG数据写入AVI文件*/
			jpeg2avi_add_frame(fp_avi, jpg_data, filesize);
		}
		
		fclose(fp_jpg);
	}
	
	jpeg2avi_end(fp_avi, 1280, 720,1);
	
	fclose(fp_avi);
	printf("end\n");
	
	return 0;
}

Jpeg2AVI.h

#ifndef _JPEG2AVI_H_
#define _JPEG2AVI_H_

#include <stdio.h>

void jpeg2avi_start(FILE *fp);
void jpeg2avi_add_frame(FILE *fp, void *data, unsigned int len);
void jpeg2avi_end(FILE *fp, int width, int height, int fps);

typedef struct avi_riff_head
{
	unsigned char id[4];
	unsigned int size;
	unsigned char type[4];
}AVI_RIFF_HEAD, AVI_LIST_HEAD;

typedef struct avi_avih_chunk
{
	unsigned char id[4];            //块ID,固定为avih
	unsigned int size;              //块大小,等于struct avi_avih_chunk去掉id和size的大小
	unsigned int us_per_frame;      //视频帧间隔时间(以微秒为单位)
	unsigned int max_bytes_per_sec; //AVI文件的最大数据率
	unsigned int padding;           //设为0即可
	unsigned int flags;             //AVI文件全局属性,如是否含有索引块、音视频数据是否交叉存储等
	unsigned int total_frames;      //总帧数
	unsigned int init_frames;       //为交互格式指定初始帧数(非交互格式应该指定为0)
	unsigned int streams;           //文件包含的流的个数,仅有视频流时为1
	unsigned int suggest_buff_size; //指定读取本文件建议使用的缓冲区大小,通常为存储一桢图像                                            //以及同步声音所需的数据之和,不指定时设为0
	unsigned int width;             //视频主窗口宽度(单位:像素)
	unsigned int height;            //视频主窗口高度(单位:像素)
	unsigned int reserved[4];       //保留段,设为0即可
}AVI_AVIH_CHUNK;

typedef struct avi_rect_frame
{
	short left;
	short top;
	short right;
	short bottom;
}AVI_RECT_FRAME;

typedef struct avi_strh_chunk
{
	unsigned char id[4];            //块ID,固定为strh
	unsigned int size;              //块大小,等于struct avi_strh_chunk去掉id和size的大小
	unsigned char stream_type[4];   //流的类型,vids表示视频流,auds表示音频流
	unsigned char codec[4];         //指定处理这个流需要的解码器,如JPEG
	unsigned int flags;             //标记,如是否允许这个流输出、调色板是否变化等,一般设为0即可
	unsigned short priority;        //流的优先级,视频流设为0即可
	unsigned short language;        //音频语言代号,视频流设为0即可
	unsigned int init_frames;       //为交互格式指定初始帧数(非交互格式应该指定为0)
	unsigned int scale;             //
	unsigned int rate;              //对于视频流,rate / scale = 帧率fps
	unsigned int start;             //对于视频流,设为0即可
	unsigned int length;            //对于视频流,length即总帧数
	unsigned int suggest_buff_size; //读取这个流数据建议使用的缓冲区大小
	unsigned int quality;           //流数据的质量指标
	unsigned int sample_size;       //音频采样大小,视频流设为0即可
	AVI_RECT_FRAME rcFrame;         //这个流在视频主窗口中的显示位置,设为{0,0,width,height}即可
}AVI_STRH_CHUNK;

/*对于视频流,strf块结构如下*/
typedef struct avi_strf_chunk
{
	unsigned char id[4];             //块ID,固定为strf
	unsigned int size;               //块大小,等于struct avi_strf_chunk去掉id和size的大小
	unsigned int size1;              //size1含义和值同size一样
	unsigned int width;              //视频主窗口宽度(单位:像素)
	unsigned int height;             //视频主窗口高度(单位:像素)
	unsigned short planes;           //始终为1
	unsigned short bitcount;         //每个像素占的位数,只能是1、4、8、16、24和32中的一个
	unsigned char compression[4];    //视频流编码格式,如JPEG、MJPG等
	unsigned int image_size;         //视频图像大小,等于width * height * bitcount / 8
	unsigned int x_pixels_per_meter; //显示设备的水平分辨率,设为0即可
	unsigned int y_pixels_per_meter; //显示设备的垂直分辨率,设为0即可
	unsigned int num_colors;         //含义不清楚,设为0即可
	unsigned int imp_colors;         //含义不清楚,设为0即可
}AVI_STRF_CHUNK;

typedef struct avi_strl_list
{
	unsigned char id[4];    //块ID,固定为LIST
	unsigned int size;      //块大小,等于struct avi_strl_list去掉id和size的大小
	unsigned char type[4];  //块类型,固定为strl
	AVI_STRH_CHUNK strh;
	AVI_STRF_CHUNK strf;
}AVI_STRL_LIST;

typedef struct avi_hdrl_list
{
	unsigned char id[4];    //块ID,固定为LIST
	unsigned int size;      //块大小,等于struct avi_hdrl_list去掉id和size的大小
	unsigned char type[4];  //块类型,固定为hdrl
	AVI_AVIH_CHUNK avih;
	AVI_STRL_LIST  strl;
}AVI_HDRL_LIST;

#endif

Jepg2AVI.c

#include "Jpeg2AVI.h"
#include "list.h"
#include <stdlib.h>
#include <string.h>

static int nframes;           //总帧数
static int totalsize;         //帧的总大小
static struct list_head list; //保存各帧图像大小的链表,用于写索引块

/*链表宿主结构,用于保存真正的图像大小数据*/
struct ListNode
{
	int value;
	struct list_head head;
};

static void write_index_chunk(FILE *fp)
{
	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;
	
	fwrite(index, 4, 1, fp);
	fwrite(&index_chunk_size, 4, 1, fp);
	
	list_for_each_safe(slider, tmpslider, &list)
	{
		unsigned char tmp[4] = {'0', '0', 'd', 'c'};  //00dc = 压缩的视频数据
		unsigned int keyframe = 0x10;                 //0x10表示当前帧为关键帧
		struct ListNode *node = list_entry(slider, struct ListNode, head);
		
		fwrite(tmp, 4, 1, fp);
		fwrite(&keyframe, 4, 1, fp);
		fwrite(&offset, 4, 1, fp);
		fwrite(&node->value, 4, 1, fp);
		offset = offset + node->value + 8;
		
		list_del(slider);
		free(node);
	}
}

static void back_fill_data(FILE *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'},
		{
			{'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, 23, 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'}
	};
	
	//定位到文件头,回填各块数据
	fseek(fp, 0, SEEK_SET);
	fwrite(&riff_head, sizeof(riff_head), 1, fp);
	fwrite(&hdrl_list, sizeof(hdrl_list), 1, fp);
	fwrite(&movi_list_head, sizeof(movi_list_head), 1, fp);
}

void jpeg2avi_start(FILE *fp)
{
	int offset1 = sizeof(AVI_RIFF_HEAD);  //riff head大小
	int offset2 = sizeof(AVI_HDRL_LIST);  //hdrl list大小
	int offset3 = sizeof(AVI_LIST_HEAD);  //movi list head大小
	
	//AVI文件偏移量设置到movi list head后,从该位置向后依次写入JPEG数据
	fseek(fp, offset1 + offset2 + offset3, SEEK_SET);
	
	//初始化链表
	list_head_init(&list);
	
	nframes = 0;
	totalsize = 0;
}

void jpeg2avi_add_frame(FILE *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++;
	}
	
	fwrite(tmp, 4, 1, fp);    //写入是否是压缩的视频数据信息
	fwrite(&len, 4, 1, fp);   //写入4字节对齐后的JPEG图像大小
	fwrite(data, len, 1, fp); //写入真正的JPEG数据
	
	nframes += 1;
	totalsize += len;
	
	/*将4字节对齐后的JPEG图像大小保存在链表中*/
	if (node != NULL)
	{
		node->value = len;
		list_add_tail(&node->head, &list);
	}
}

void jpeg2avi_end(FILE *fp, int width, int height, int fps)
{
	//写索引块
	write_index_chunk(fp);
	
	//从文件头开始,回填各块数据
	back_fill_data(fp, width, height, fps);
}

list.h

#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_

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;
}
相关推荐
安步当歌5 分钟前
【WebRTC】视频编码链路中各个类的简单分析——VideoStreamEncoder
音视频·webrtc·视频编解码·video-codec
顾北川_野6 分钟前
Android CALL关于电话音频和紧急电话设置和获取
android·音视频
顶呱呱程序9 分钟前
2-143 基于matlab-GUI的脉冲响应不变法实现音频滤波功能
算法·matlab·音视频·matlab-gui·音频滤波·脉冲响应不变法
小奥超人41 分钟前
PPT文件设置了修改权限,如何取消权?
windows·经验分享·microsoft·ppt·办公技巧
EasyCVR1 小时前
萤石设备视频接入平台EasyCVR多品牌摄像机视频平台海康ehome平台(ISUP)接入EasyCVR不在线如何排查?
运维·服务器·网络·人工智能·ffmpeg·音视频
runing_an_min1 小时前
ffmpeg 视频滤镜:屏蔽边框杂色- fillborders
ffmpeg·音视频·fillborders
flashman9113 小时前
python在word中插入图片
python·microsoft·自动化·word
徒步僧13 小时前
ThingsBoard规则链节点:RPC Call Reply节点详解
qt·microsoft·rpc
我喜欢就喜欢14 小时前
基于qt vs下的视频播放
开发语言·qt·音视频
安步当歌15 小时前
【WebRTC】视频采集模块中各个类的简单分析
音视频·webrtc·视频编解码·video-codec