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
#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[];
unsigned int size;
unsigned char type[];
}AVI_RIFF_HEAD, AVI_LIST_HEAD; typedef struct avi_avih_chunk
{
unsigned char id[]; //块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[]; //保留段,设为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[]; //块ID,固定为strh
unsigned int size; //块大小,等于struct avi_strh_chunk去掉id和size的大小
unsigned char stream_type[]; //流的类型,vids表示视频流,auds表示音频流
unsigned char codec[]; //指定处理这个流需要的解码器,如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[]; //块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[]; //视频流编码格式,如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[]; //块ID,固定为LIST
unsigned int size; //块大小,等于struct avi_strl_list去掉id和size的大小
unsigned char type[]; //块类型,固定为strl
AVI_STRH_CHUNK strh;
AVI_STRF_CHUNK strf;
}AVI_STRL_LIST; typedef struct avi_hdrl_list
{
unsigned char id[]; //块ID,固定为LIST
unsigned int size; //块大小,等于struct avi_hdrl_list去掉id和size的大小
unsigned char type[]; //块类型,固定为hdrl
AVI_AVIH_CHUNK avih;
AVI_STRL_LIST strl;
}AVI_HDRL_LIST; #endif
Jpeg2AVI.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[] = {'i', 'd', 'x', ''}; //索引块ID
unsigned int index_chunk_size = * nframes; //索引块大小
unsigned int offset = ;
struct list_head *slider = NULL;
struct list_head *tmpslider = NULL; fwrite(index, , , fp);
fwrite(&index_chunk_size, , , fp); list_for_each_safe(slider, tmpslider, &list)
{
unsigned char tmp[] = {'', '', 'd', 'c'}; //00dc = 压缩的视频数据
unsigned int keyframe = 0x10; //0x10表示当前帧为关键帧
struct ListNode *node = list_entry(slider, struct ListNode, head); fwrite(tmp, , , fp);
fwrite(&keyframe, , , fp);
fwrite(&offset, , , fp);
fwrite(&node->value, , , fp);
offset = offset + node->value + ; 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'},
+ sizeof(AVI_HDRL_LIST) + sizeof(AVI_LIST_HEAD) + nframes * + totalsize,
{'A', 'V', 'I', ' '}
}; AVI_HDRL_LIST hdrl_list =
{
{'L', 'I', 'S', 'T'},
sizeof(AVI_HDRL_LIST) - ,
{'h', 'd', 'r', 'l'},
{
{'a', 'v', 'i', 'h'},
sizeof(AVI_AVIH_CHUNK) - ,
/ fps, , , , nframes, , , , width, height,
{, , , }
},
{
{'L', 'I', 'S', 'T'},
sizeof(AVI_STRL_LIST) - ,
{'s', 't', 'r', 'l'},
{
{'s', 't', 'r', 'h'},
sizeof(AVI_STRH_CHUNK) - ,
{'v', 'i', 'd', 's'},
{'J', 'P', 'E', 'G'},
, , , , , , , nframes, , 0xFFFFFF, ,
{, , width, height}
},
{
{'s', 't', 'r', 'f'},
sizeof(AVI_STRF_CHUNK) - ,
sizeof(AVI_STRF_CHUNK) - ,
width, height, , ,
{'J', 'P', 'E', 'G'},
width * height * , , , ,
}
}
}; AVI_LIST_HEAD movi_list_head =
{
{'L', 'I', 'S', 'T'},
+ nframes * + totalsize,
{'m', 'o', 'v', 'i'}
}; //定位到文件头,回填各块数据
fseek(fp, , SEEK_SET);
fwrite(&riff_head, sizeof(riff_head), , fp);
fwrite(&hdrl_list, sizeof(hdrl_list), , fp);
fwrite(&movi_list_head, sizeof(movi_list_head), , 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 = ;
totalsize = ;
} void jpeg2avi_add_frame(FILE *fp, void *data, unsigned int len)
{
unsigned char tmp[] = {'', '', 'd', 'c'}; //00dc = 压缩的视频数据
struct ListNode *node = (struct ListNode *)malloc(sizeof(struct ListNode)); /*JPEG图像大小4字节对齐*/
while (len % )
{
len++;
} fwrite(tmp, , , fp); //写入是否是压缩的视频数据信息
fwrite(&len, , , fp); //写入4字节对齐后的JPEG图像大小
fwrite(data, len, , fp); //写入真正的JPEG数据 nframes += ;
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 *))->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;
}
main.c
#include "Jpeg2AVI.h"
#include <string.h> #define JPEG_MAX_SIZE 100000 //JPEG图像最大字节数
#define JPEG_NUM 13800 //JPEG图像数量 int main()
{
FILE *fp_jpg;
FILE *fp_avi;
int filesize;
unsigned char jpg_data[JPEG_MAX_SIZE];
char filename[];
int i = ; fp_avi = fopen("sample.avi","wb"); jpeg2avi_start(fp_avi); for (i = ; i < JPEG_NUM; i++)
{
memset(filename, , );
memset(jpg_data, , JPEG_MAX_SIZE); sprintf(filename, "%d.jpg", i + );
fp_jpg = fopen(filename, "rb"); if (fp_jpg != NULL)
{
/*获取JPEG数据大小*/
fseek(fp_jpg, , SEEK_END);
filesize = ftell(fp_jpg);
fseek(fp_jpg, , SEEK_SET); /*将JPEG数据读到缓冲区*/
fread(jpg_data, filesize, , fp_jpg); /*将JPEG数据写入AVI文件*/
jpeg2avi_add_frame(fp_avi, jpg_data, filesize);
} fclose(fp_jpg);
} jpeg2avi_end(fp_avi, , , ); fclose(fp_avi);
printf("end\n"); return ;
}
JPEG流封装AVI视频的更多相关文章
- 【视频编解码·学习笔记】4. H.264的码流封装格式
一.码流封装格式简单介绍: H.264的语法元素进行编码后,生成的输出数据都封装为NAL Unit进行传递,多个NAL Unit的数据组合在一起形成总的输出码流.对于不同的应用场景,NAL规定了一种通 ...
- 【视频编解码·学习笔记】4. H.264的码流封装格式 & 提取NAL有效数据
一.码流封装格式简单介绍: H.264的语法元素进行编码后,生成的输出数据都封装为NAL Unit进行传递,多个NAL Unit的数据组合在一起形成总的输出码流.对于不同的应用场景,NAL规定了一种通 ...
- Matlab从一系列图片导出AVI视频,导出GIF动图
平台:Win7,Matlab 2014a 从一系列图片导出AVI视频的M代码如下: clear all; % 清除变量 % 官方示例,命令窗口输入“doc VideoWriter” writerObj ...
- 从H264码流中获取视频宽高 (SPS帧) 升级篇
之前写过 <从H264码流中获取视频宽高 (SPS帧)> . 但发现很多局限性,而且有时解出来是错误的. 所以重新去研究了. 用了 官方提供的代码库来解析. 花了点时间,从代码库里单独把解 ...
- Nginx模块之————RTMP模块在Ubuntu上以串流直播HLS视频
Nginx的安装在Ubuntu上以串流直播HLS视频 https://www.vultr.com/docs/setup-nginx-on-ubuntu-to-stream-live-hls-video
- OpenCV学习 2:播放AVI视频
原创文章,欢迎转载,转载请注明出处 第二个程序,播放视频.用opencv做起来是如此的简单..哈哈. 学Opencv,只是为了在它的基础上实现工程应用,而它里面高深的理论我等屌丝只 ...
- 1.0.1-学习Opencv与MFC混合编程之---播放AVI视频
资源源代码:http://download.csdn.net/detail/nuptboyzhb/3961639 版本1.0.1新增内容 Ø 新建菜单项,Learning OpenCV——> ...
- opencv::将两幅图像合并后,在同一个窗口显示;并将合并的图像流保存成视频文件
/** * @file main-opencv.cpp * @date July 2014 * @brief An exemplative main file for the use of ViBe ...
- ASP.NET MVC应用程序播放AVI视频
前面Insus.NET实现一系列在MVC应用程序播放SWF, FLV, WMV, RM, RMVB视频.每篇使用不同的方法方式,大同小异.这篇中,为了MVC应用程序播放AVI视频,用纯M, V, C来 ...
随机推荐
- jmeter系列------参数关联
接口请求中的一个变量需要用上一个接口道服务器返回响应的动态值(上个请求). 遇到这样的情况,我们就需要用到关联 例如用户A发表了一个一条微信朋友圈,用户B想对这条朋友圈进行评论,就需要先获取到这个朋友 ...
- appium python api收集
1.contexts contexts(self): Returns the contexts within the current session. 返回当前会话中的上下文,使用后可以识别H5页面的 ...
- Android studio 安装的安装一些问题
在国内如何更新android sdk? 由于众所周知的某些原因,我们无法直接连接android sdk的更新服务更新sdk,所以可以通过国内的ftp站点把常用的sdk组件如android platfo ...
- Akka(23): Stream:自定义流构件功能-Custom defined stream processing stages
从总体上看:akka-stream是由数据源头Source,流通节点Flow和数据流终点Sink三个框架性的流构件(stream components)组成的.这其中:Source和Sink是stre ...
- .Net Mvc实现各种表格随意切换插件
一套Js代码,.只要改参数 在3种表格之间任意切换-(使用Js面向对象封装,可重写方法) 任意表格皮肤随便切换 flextgrid/bootstrapt/jqgrid 1 001 @{ 002 ...
- ABP从入门到精通(6):快速重命名解决方案
SolutionRenamer SolutionRenamer 是一个解决方案快速重命名工具.经测试重命名一个全新asp.net zero core项目(ABP asp.net zero,.net c ...
- quartz源码分析——执行引擎和线程模型
title: quartz源码分析--执行引擎和线程模型 date: 2017-09-09 23:14:48 categories: quartz tags: [quartz, 源码分析] --- - ...
- Java企业微信开发_09_身份验证之移动端网页授权(有完整项目源码)
注: 源码已上传github: https://github.com/shirayner/WeiXin_QiYe_Demo 一.本节要点 1.1 授权回调域(可信域名) 在开始使用网页授权之前,需要先 ...
- 团队作业4——第一次项目冲刺(Alpha版本)4.25
团队作业4--第一次项目冲刺(Alpha版本) Day four: 会议照片 每日站立会议: 项目进展 今天是项目的Alpha敏捷冲刺的第四天,先大概整理下昨天已完成的任务以及今天计划完成的任务.今天 ...
- 第06周-接口、内部类与Swing
1. 本周作业简评与建议 作业简评 Q1.覆盖clone需要:a.要implements标记接口 Cloneable接口.b.要区分浅拷贝与深拷贝.c.一般来说要调用super.clone,然后在此基 ...