MyException - 我的异常网
当前位置:我的异常网» C语言 » JPEG源封装AVI视频

JPEG源封装AVI视频

www.MyException.Cn  网友分享于:2013-10-16  浏览:0次
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\n");
49  
50     return 0;
51 }
View Code

 

文章评论

Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
老程序员的下场
老程序员的下场
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
程序员都该阅读的书
程序员都该阅读的书
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
那些争议最大的编程观点
那些争议最大的编程观点
代码女神横空出世
代码女神横空出世
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
我是如何打败拖延症的
我是如何打败拖延症的
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
旅行,写作,编程
旅行,写作,编程
每天工作4小时的程序员
每天工作4小时的程序员
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
 程序员的样子
程序员的样子
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
程序员和编码员之间的区别
程序员和编码员之间的区别
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
程序员必看的十大电影
程序员必看的十大电影
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
Java程序员必看电影
Java程序员必看电影
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
中美印日四国程序员比较
中美印日四国程序员比较
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
我的丈夫是个程序员
我的丈夫是个程序员
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
如何成为一名黑客
如何成为一名黑客
漫画:程序员的工作
漫画:程序员的工作
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
一个程序员的时间管理
一个程序员的时间管理
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
程序员应该关注的一些事儿
程序员应该关注的一些事儿
程序员的鄙视链
程序员的鄙视链
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
鲜为人知的编程真相
鲜为人知的编程真相
10个调试和排错的小建议
10个调试和排错的小建议
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有