简单实现h264转ts

news/2024/12/1 0:23:20/

https://www.cnblogs.com/dyan1024/p/10224538.html

简单实现h264转ts

转载注明出处:https://www.cnblogs.com/dyan1024/p/10224538.html

最近一个项目中需要在项目中临时嵌入h264裸流编码成ts的代码,但是以前从没接触过视频,先是在网上搜索了2~3天(主要是想找个能用demo看看编码流程借鉴下)。只找到ffmpeg命令行转码的,ffmpeg也没接触过没那时间去分析怎么转的,而且我们不想使用ffmpeg这个库,之后还要将编码过程从ffmpeg中提取出来,又是一个比较大的难点。总之没头苍蝇一样搜索心累,耐心用完了,算了算了看看格式自己编好了。

然后又用了2~3天初步完成了转码,因为网上很难找到这样的demo可以参考,所以有了这篇文章。

以下是参考博客及规范链接:

hls之m3u8、ts、h264、AAC流格式详解

将H264与AAC打包Ipad可播放的TS流的总结

TS协议解析第一部分(PAT)

TS协议解析第二部分(PMT)

TS协议解析第三部分(PES)

TS协议解析第四部分(adaptation field)

ISO.IEC-13818-1-中文版规范

ts crc32 验证与计算

参考TS协议解析4连是因为手头没有ts格式的二进制文件的实际参考,主要是为了对比实际生成的二进制文件与自己理解的文件格式是否有差别。

其中第二个参考博客那张图对于直观理解hls的ts包格式很有帮助,因此将这张图片复制了过来,还读了一些其他博客,以上只是个人感觉对于完成手动编码比较关键的部分。

下面直接上demo代码,注释已经比较详细(令人发指)。需要特别注意的是这个demo没用到PCR

复制代码

  1 /*2 这个demo实现的是将h264裸流封装成ts文件(也可以是m3u8和ts文件)3 由于我们的摄像头数据只含有以下帧类型4 PPS  (0x00 00 00 01 68),5 SPS  (0x00 00 00 01 67),6 SEI  (0x00 00 00 01 66),7 IDR  (0x00 00 00 01 65),8 非IDR(0x00 00 00 01 41),9 帧只有帧开始(0x00 00 00 01)没有帧中(0x00 00 01),10 因此这个demo不会处理其他帧情况,如果输入文件含有其他帧会有问题。11 重要:这个demo没有PCR信息,其他信息能省则省,毕竟规范文件内容太多。12 测试1.——————————————————————————————————13 输出结果.ts文件通过VLC播放器本地播放有跳帧的情况,VLC播放器给出的信息是:14 main error: Invalid PCR value in ES_OUT_SET_(GROUP_)PCR !15 ......16 main error: Invalid PCR value in ES_OUT_SET_(GROUP_)PCR !17 direct3d9 error: SetThumbNailClip failed: 0x800706f418 avcodec info: Using DXVA2 (Intel(R) HD Graphics, vendor Intel(8086), device 402, revision 6) for hardware decoding19 direct3d9 error: SetThumbNailClip failed: 0x800706f420 这应当是我们忽略了PCR导致的,不过使用android(版本8.1.0,手头没有其他版本的android机测试)和ios手机浏览器或视频播放器可以正常播放。21 造成VLC播放异常的主要是PCR,这个PCR没怎么去了解。22 测试2.——————————————————————————————————23 .m3u8文件在本地貌似播放不了(试过一些播放器都不行)24 然后用nginx的rtmp模块搭了一个hls点播服务器25 1.使用pc端的VLC播放器打开网络串流,画面一闪而过,看似只有前面的1帧或几帧播放成功,后面都是黑屏26 2.使用手机的浏览器打开能够正常播放27 综述.——————————————————————————————————28 由于是极简封装,存在一些发现或未发现的小问题,比如PCR29 PC端VLC不能正常播放。30 手机浏览器和视频播放器可以正常的播放。(貌似现在手机都兼容了safari浏览器?)31 html5没有测试过。32 —————————————————————————————————————33 written by dyan102434 date:2019-1-235 */36 37 #include "stdafx.h"    //在linux下注释掉这一行38 #include <stdio.h>39 #include <stdlib.h>40 #include <string.h>41 #include <list>42 #include <math.h>43 #ifdef _WIN3244 #include <winsock.h>45 #pragma comment(lib,"ws2_32.lib")46 #else    47 #include <arpa/inet.h>48 #endif /*_WIN32*/49 using namespace std;50 FILE *bits = NULL;                //.h264输入文件51 FILE *fw = NULL;                  //.ts输出文件52 FILE *m3u8 = NULL;                  //.m3u8输出文件53 static int FindStartCode2(unsigned char *Buf);//查找开始字符0x00000154 static int FindStartCode3(unsigned char *Buf);//查找开始字符0x0000000155 56 static int info2 = 0, info3 = 0;57 58 #define BUF_SIZE 1024*1024*359 #define Program_map_PID 0x0003    //pmt节目pid,也会关联到pat表60 #define Elementary_PID 0x0012    //pes流类型对应的pid,也会关联到pmt表61 #define TS_PACKET_LEN 188    //ts包长度固定188字节62 #define M3U8_TS_LEN 32    //m3u8文件每行最大长度63 #define IDR_PER_TSFILE 10 //生成hls时每多少个关键帧一个.ts文件(ts包和.ts文件不同,.ts包含很多ts包,而一个ts包固定188字节)64 /*用于存一帧h264数据*/65 struct frame_buf {66     unsigned char * p_buf;67     int len;68     int max;69     frame_buf() {70         p_buf = new unsigned char[BUF_SIZE];//我们摄像头图像大小是640*480*3,就算不压缩,这个数组完全足够存一帧71         len = 0;72         max = BUF_SIZE;73     }74     ~frame_buf()75     {76         delete[] p_buf;77         p_buf = NULL;78     }79 };80 /*用于存一个ts包(188字节)*/81 struct pes_packet {82     unsigned char *p_buf;83     int max;84     pes_packet() {85         p_buf = new unsigned char[TS_PACKET_LEN];86         max = TS_PACKET_LEN;87     }88     ~pes_packet()89     {90         delete[] p_buf;91         p_buf = NULL;92     }93 };94 /*用于存m3u8文件中标记.ts文件时长及文件名*/95 struct m3u8_ts {96     float ts_time;97     char ts_name[M3U8_TS_LEN];98     m3u8_ts() {99         memset(&ts_name, 0, M3U8_TS_LEN);
100     }
101 };
102 /*用于缓存m3u8文件内容*/
103 struct m3u8_text {
104     char extm3u[M3U8_TS_LEN];
105     char ext_x_version[M3U8_TS_LEN];
106     char ext_x_allow_cache[M3U8_TS_LEN];
107     char ext_x_targetduration[M3U8_TS_LEN];
108     char ext_x_media_sequence[M3U8_TS_LEN];
109     char ext_x_playlist_type[M3U8_TS_LEN];
110     list<m3u8_ts> l_m3u8_ts;
111     char extinf[M3U8_TS_LEN];
112     char ext_x_endlist[M3U8_TS_LEN];
113     m3u8_text() {
114         memset(extm3u, 0, M3U8_TS_LEN);
115         memcpy(extm3u, "#EXTM3U\n", strlen("#EXTM3U\n"));
116         memset(ext_x_version, 0, M3U8_TS_LEN);
117         memcpy(ext_x_version, "#EXT-X-VERSION:3\n", strlen("#EXT-X-VERSION:3\n"));
118         memset(ext_x_allow_cache, 0, M3U8_TS_LEN);
119         memcpy(ext_x_allow_cache, "#EXT-X-ALLOW-CACHE:YES\n", strlen("#EXT-X-ALLOW-CACHE:YES\n"));
120         memset(ext_x_targetduration, 0, M3U8_TS_LEN);
121         memcpy(ext_x_targetduration, "#EXT-X-TARGETDURATION:%d\n", strlen("#EXT-X-TARGETDURATION:%d\n"));
122         memset(ext_x_media_sequence, 0, M3U8_TS_LEN);
123         memcpy(ext_x_media_sequence, "#EXT-X-MEDIA-SEQUENCE:%d\n", strlen("#EXT-X-MEDIA-SEQUENCE:%d\n"));
124         memset(ext_x_playlist_type, 0, M3U8_TS_LEN);
125         memcpy(ext_x_playlist_type, "#EXT-X-PLAYLIST-TYPE:VOD\n", strlen("#EXT-X-PLAYLIST-TYPE:VOD\n"));    //点播
126         memset(extinf, 0, M3U8_TS_LEN);
127         memcpy(extinf, "#EXTINF:%f,\n", strlen("#EXTINF:%f,\n"));
128         memset(ext_x_endlist, 0, M3U8_TS_LEN);
129         memcpy(ext_x_endlist, "#EXT-X-ENDLIST\n", strlen("#EXT-X-ENDLIST\n"));
130     }
131 };
132 
133 frame_buf *p_pesframe = NULL;
134 unsigned long long pts = 1; //初始值似乎是随便定义的,第一帧最好不要为0,这个初始值pts+pts_per_frame不要等于0就行
135 unsigned int frame_rate = 90;    //视频帧率不太清楚,随便给个值在这里,只是影响播放时快慢的问题(在VLC播放器上还会影响播放效果,比如设置为20,播放时不是跳帧,而是从头到尾只有1帧,估计还是PCR的问题)
136 unsigned int pts_per_frame = 90000 / frame_rate;
137 unsigned long long pts_max = pow(2, 33);
138 
139 unsigned int crc32Table[256] = { 0 };
140 int MakeTable(unsigned int *crc32_table)
141 {
142     for (unsigned int i = 0; i < 256; i++) {
143         unsigned int k = 0;
144         for (unsigned int j = (i << 24) | 0x800000; j != 0x80000000; j <<= 1) {
145             k = (k << 1) ^ (((k ^ j) & 0x80000000) ? 0x04c11db7 : 0);
146         }
147 
148         crc32_table[i] = k;
149     }
150     return 0;
151 }
152 unsigned int Crc32Calculate(u_char *buffer, unsigned int size, unsigned int *crc32_table)
153 {
154     unsigned int crc32_reg = 0xFFFFFFFF;
155     for (unsigned int i = 0; i < size; i++) {
156         crc32_reg = (crc32_reg << 8) ^ crc32_table[((crc32_reg >> 24) ^ *buffer++) & 0xFF];
157     }
158     return crc32_reg;
159 }
160 
161 void OpenBitstreamFile(char *fn)
162 {
163     if (NULL == (bits = fopen(fn, "rb")))
164     {
165         printf("open file error\n");
166         exit(0);
167     }
168 }
169 void CloseBitstreamFile() {
170     fclose(bits);
171     bits = NULL;
172 }
173 void OpenWriteFile(char *fn) {
174     if (NULL == (fw = fopen(fn, "wb")))
175     {
176         printf("open write file error\n");
177         exit(0);
178     }
179 }
180 void CloseWriteFile() {
181     fclose(fw);
182     fw = NULL;
183 }
184 
185 /*从文件中读取一帧h264裸流数据(I帧或P帧,没有B帧)
186 @Buf,    [out]已分配内存空间的指针,用于存储一帧图像
187 @len,    [out]返回Buf的有效长度
188 return,    -1(异常),0(IDR),1(非IDR),2(最后一帧,不区分帧类型)*/
189 int GetOneFrame(unsigned char* Buf, int &len) {
190     int StartCodeFound = 0;
191     int rewind;
192     int nal_unit_type;
193     int forbidden_bit;
194     int nal_reference_idc;
195     len = 0;
196 
197     int startcodeprefix_len = 3;//初始化码流序列的开始字符为3个字节
198 
199     //从我们摄像头截取的h264数据获知只有帧开始(0x00000001)没有帧中(0x000001),不处理帧中。
200     if (4 != fread(Buf+len, 1, 4, bits))//从码流中读4个字节
201     {
202         return -1;
203     }
204     info3 = FindStartCode3(Buf+len);//判断是否为0x00000001 
205     if (info3 != 1)//如果不是,返回-1
206     {
207         return -1;
208     }
209     else {//如果是0x00000001,得到开始字符为4个字节
210         len += 4;
211         startcodeprefix_len = 4;
212     }
213     while (!StartCodeFound) {
214         //到文件末尾,认为当前帧结束
215         if (feof(bits)) {
216             break;
217         }
218         //再读一个字节,知道找到下一个帧开始0x00000001
219         Buf[len++] = fgetc(bits);
220         info3 = FindStartCode3(&Buf[len - 4]);//判断是否为0x00000001
221         //if (info3 != 1)
222         //    info2 = FindStartCode2(&Buf[pos - 3]);//判断是否为0x000001
223         StartCodeFound = (/*info2 == 1 || */info3 == 1);
224         if (StartCodeFound) {
225             //到文件末尾,认为当前帧结束
226             if (feof(bits)) {
227                 break;
228             }
229             //再读一个字节判断帧类型
230             Buf[len++] = fgetc(bits);
231             //第1位h.264裸流肯定为0,不用管它
232             forbidden_bit = Buf[len-1] & 0x80; //(nalu->buf[0]>>7) & 1;
233             /*接下来2位,取值0~3,指示这个nalu的重要性,I帧、sps、pps通常取3,P帧通常取2,B帧通常取0.
234             对于封装pes包帧重要性关系不大,因为我们实际情况的帧类型比较简单(只有IDR(关键帧),非IDR,SPS,PPS,SEI),主要还是看帧类型,因为我们不管什么帧只要在帧前加个pes头就行了*/
235             nal_reference_idc = Buf[len-1] & 0x60; //(nalu->buf[0]>>5) & 3;
236             /*最后5位,帧类型:IDR(关键帧),非IDR,SPS,PPS,SEI,分解符,片分区A/B/C,序列结束,码流结束,填充等。
237             截取一段摄像头h264裸流分析后发现对于我们摄像头的实时h264裸流只有前5种。
238             而封装pes包sps,pps,sei,IDR封装为一个pes包,各个非IDR分别一个pes包,因此检测到下一个IDR或非IDR才返回。*/
239             nal_unit_type = (Buf[len-1]) & 0x1f;
240             //只要不是非IDR或IDR(SEI,SPS,PPS),不要截断数据,继续读取
241             if (1 != nal_unit_type && 5 != nal_unit_type) {
242                 StartCodeFound = 0;
243                 continue;
244             }
245             //检测到下一帧的开始,可以截取当前帧了
246             else {
247                 rewind = (info3 == 1) ? -5 : -4;    //回退一个h264头(4字节(或3字节,帧中我们不考虑))和一个帧类型(1字节)
248                 if (0 != fseek(bits, rewind, SEEK_CUR))//把文件指针向后退开始字节的字节数
249                 {
250                     printf("Cannot fseek in the bit stream file\n");
251                     return -1;
252                 }
253                 len += rewind;//帧长减下一帧头0x00000001(或0x000001)的长度
254                 //区分一下IDR和非IDR,可能要用
255                 int front_nal_unit_type = Buf[startcodeprefix_len] & 0x1f;
256                 //非IDR返回1,IDR返回0
257                 if (1 == front_nal_unit_type) {
258                     return 1;
259                 }
260                 else if (5 == front_nal_unit_type) {
261                     return 0;
262                 }
263             }
264         }
265     }
266     return 2;
267 }
268 
269 /*为一帧h264裸流加pes头(pts)
270 @p_pes,        [out]已提前分配存储空间的指针,用于存储加pes头的h264一帧数据
271 @Buf,        [int]一帧h264数据
272 @len,        [in]Buf数据的长度
273 return,    -1(异常),0(成功)*/
274 int PesHead_p(frame_buf *p_pes, unsigned char* Buf, int len) {
275     int temp_len = 0;
276     p_pes->len = 0;
277     memset(p_pes->p_buf, 0, BUF_SIZE);
278     /*最后我们是要按字节写的,为了避免大小端的问题最好一个字节一个字节存。或者转为网络字节序
279     比如pes start code存成*((unsigned int *)p_pes->p_buf) |= 0x00000100,看上去符合pes的开始标记 00 00 01 00,事实上写到本地后确是00 01 00 00,这是不对的
280     */
281     *(p_pes->p_buf + 2) = 0x01;    //pes start code,3字节 0x000001
282     *(p_pes->p_buf + 3) = 0xe0;    //stream id,1字节 0xe0
283                                 //*(p_pes->p_buf + 4) = 0x00;    
284                                 //*(p_pes->p_buf + 5) = 0x00;    //pes packet length,2字节 视频数据长度0x0000表示pes包不限制长度
285     *(p_pes->p_buf + 6) = 0x80;    //通常取值0x80,表示数据不加密、无优先级、备份的数据等(自己封装怎么简单怎么来)
286     *(p_pes->p_buf + 7) = 0x80;    //pts_dts_flags取值0x80(10 00 00 00)表示只含有pts(其中的前2bit(10)),取值0xc0(11 00 00 00)表示含有pts和dts,(前2bit(11))这个序列1越多后面描述越长(自己封装怎么简单怎么来)。
287     *(p_pes->p_buf + 8) = 0x05;    //因为我们只有pts,按协议就是5字节
288     temp_len += 9;
289     pts += pts_per_frame;    /*按协议pts/dts有效位33bit,不知道为什么33bit(看来需要长整形存储),就算我们只用33bit,可以长达约26.512((2^33-1)/ 90000 / 3600)个小时*/
290     pts %= pts_max;    /*如果超出33bit根据协议超过33bit记录后应该重新从0开始*/
291     /*取33bit的pts值的前3位(本来应该右移30,但又需要给最后的maker_bit预留一个位子,所以最后右移29位和0x0e(00 00 11 10)与运算)*/
292     char ptsn1_temp = 0x0e & pts >> 29;    //0x0e(00 00 11 10)
293     /*我们前面pts_dts_flags是0x80(前2位为10),则按协议这个字节前4位为0010,后跟pts的前3位,最后接一个maker_bit(1)*/
294     char ptsn1 = 0x21 | ptsn1_temp;    //0x21(00 10 00 01)
295     unsigned short ptsn2_temp = 0x0001 | (pts >> 14);    //按协议取接下来pts的15bit,后接一个位maker_bit(1),同上。额外注意的是这不是单字节数据类型,最后要转网络字节序
296     unsigned short ptsn2 = htons(ptsn2_temp);    //转网络字节序
297     unsigned short ptsn3_temp = 0x0001 | (pts << 1);    //按协议取最后pts的15bit,后接一个位maker_bit(1),同上。额外注意的是这不是单字节数据类型,最后要转网络字节序
298     unsigned short ptsn3 = htons(ptsn3_temp);    //转网络字节序
299     //将pts写入
300     *(p_pes->p_buf + temp_len) = ptsn1;
301     temp_len++;
302     *(unsigned short*)(p_pes->p_buf + temp_len) = ptsn2;
303     temp_len += 2;
304     *(unsigned short*)(p_pes->p_buf + temp_len) = ptsn3;
305     temp_len += 2;
306     //pes头结束,网上传言要在每个pes头后加个0x00000001 0x09(按h264格式这是B帧分解符?) 0x**(最后的**不是00就行)
307     unsigned int h264_head = htonl(1);
308     *(unsigned int*)(p_pes->p_buf + temp_len) = h264_head;
309     temp_len += 4;
310     *(p_pes->p_buf + temp_len) = 0x09;
311     temp_len++;
312     *(p_pes->p_buf + temp_len) = 0xff;
313     temp_len++;
314     //加上h264裸流,注意数组别越界
315     if (p_pes->max < (temp_len + len)) {
316         //TODO 
317         printf("pes buf size allocated is too small\n");
318         return -1;
319     }
320     memcpy(p_pes->p_buf + temp_len, Buf, len);
321     p_pes->len = len + temp_len;
322     return 0;
323 }
324 
325 /*为一帧h264裸流加pes头(pts和dts)
326 @p_pes,        [out]已提前分配存储空间的指针,用于存储加pes头的h264一帧数据
327 @Buf,        [int]一帧h264数据
328 @len,        [in]Buf数据的长度
329 return,    -1(异常),0(成功)*/
330 int PesHead_pd(frame_buf *p_pes, unsigned char* Buf, int len) {
331     int temp_len = 0;
332     p_pes->len = 0;
333     memset(p_pes->p_buf, 0, BUF_SIZE);
334     /*最后我们是要按字节写的,为了避免大小端的问题最好一个字节一个字节存。或者转为网络字节序
335     比如pes start code存成*((unsigned int *)p_pes->p_buf) |= 0x00000100,看上去符合pes的开始标记 00 00 01 00,事实上写到本地后确是00 01 00 00,这是不对的
336     */
337     *(p_pes->p_buf + 2) = 0x01;    //pes start code,3字节 0x000001
338     *(p_pes->p_buf + 3) = 0xe0;    //stream id,1字节 0xe0
339     //*(p_pes->p_buf + 4) = 0x00;    
340     //*(p_pes->p_buf + 5) = 0x00;    //pes packet length,2字节 视频数据长度0x0000表示pes包不限制长度
341     *(p_pes->p_buf + 6) = 0x80;    //通常取值0x80,表示数据不加密、无优先级、备份的数据等(自己封装怎么简单怎么来)
342     /*这里主要注意pts_dts_flag不同后面的pts和dts起始标识也会不同*/
343     *(p_pes->p_buf + 7) = 0xc0;    //pts_dts_flags取值0xc0(11 00 00 00)表示含有pts和dts(其中前2bit(11)),取值0x80表示只含有pts(其中的前2bit(10)),(前2bit(11))这个序列1越多后面描述越长(自己封装怎么简单怎么来,我们不要其他的信息了)。
344     *(p_pes->p_buf + 8) = 0x0a;    //因为我们有pts和dts,按协议就是10字节,我们这里将pts和dts设置为一样就行
345     temp_len += 9;
346     pts += pts_per_frame;    /*按协议pts/dts有效位33bit,不知道为什么33bit(看来需要长整形存储),就算我们只用33bit,可以长达约26.512((2^33-1)/ 90000 / 3600)个小时*/
347     pts %= pts_max;    /*如果超出33bit根据协议超过33bit记录后应该重新从0开始*/
348     /*取33bit的pts值的前3位(本来应该右移30,但又需要给最后的maker_bit预留一个位子,所以最后右移29位和0x0e(00 00 11 10)与运算)*/
349     char ptsn1_temp = 0x0e & pts >> 29;    //0x0e(00 00 11 10)
350     /*我们前面pts_dts_flags是0xc0(前2位为11),则按协议这个字节前4位为0011,后跟pts的前3位,最后接一个maker_bit(1)*/
351     char ptsn1 = 0x31 | ptsn1_temp;    //0x31(00 11 00 01)
352     unsigned short ptsn2_temp = 0x0001 | (pts >> 14);    //取接下来的15bit,后接一个位maker_bit(1),同上。额外注意的是这不是单字节数据类型,最后要转网络字节序
353     unsigned short ptsn2 = htons(ptsn2_temp);    //转网络字节序
354     unsigned short ptsn3_temp = 0x0001 | (pts << 1);    //取最后的15bit,后接一个位maker_bit(1),同上。额外注意的是这不是单字节数据类型,最后要转网络字节序
355     unsigned short ptsn3 = htons(ptsn3_temp);    //转网络字节序
356     //将pts写入
357     *(p_pes->p_buf + temp_len) = ptsn1;
358     temp_len++;
359     *(unsigned short*)(p_pes->p_buf + temp_len) = ptsn2;
360     temp_len += 2;
361     *(unsigned short*)(p_pes->p_buf + temp_len) = ptsn3;
362     temp_len += 2;
363     /*将dts设置为与pts相同*/
364     /*pts -= pts_per_frame;*/
365     ptsn1 = 0x11 | pts >> 29;    /*取前2bit,我们前面pts_dts_flags是0xc0(前2位为11),则按协议这个字节前4位为0001,后跟pts的前3位(由于我们是32位的unsigned int,最高位是没有的所以只取前2位,前一位置为0,应该右移30,但又需要给最后的maker_bit预留一个位子,所以最后右移29位),最后接一个位maker_bit(1)
366                                 0x11(00 01 00 01)*/
367     ptsn2_temp = 0x0001 | (pts >> 14);    //取接下来的15bit,后接一个位maker_bit(1),同上。额外注意的是这不是单字节数据类型,最后要转网络字节序
368     ptsn2 = htons(ptsn2_temp);    //转网络字节序
369     ptsn3_temp = 0x0001 | (pts << 1);    //取最后的15bit,后接一个位maker_bit(1),同上。额外注意的是这不是单字节数据类型,最后要转网络字节序
370     ptsn3 = htons(ptsn3_temp);    //转网络字节序
371     //将dts写入
372     *(p_pes->p_buf + temp_len) = ptsn1;
373     temp_len++;
374     *(unsigned short*)(p_pes->p_buf + temp_len) = ptsn2;
375     temp_len += 2;
376     *(unsigned short*)(p_pes->p_buf + temp_len) = ptsn3;
377     temp_len += 2;
378 
379     //pes头结束,网上传言要在每个pes头后加个0x00000001 0x09(按h264格式这是B帧分解符?) 0x**(最后的**不是00就行)
380     unsigned int h264_head = htonl(1);
381     *(unsigned int*)(p_pes->p_buf + temp_len) = h264_head;
382     temp_len += 4;
383     *(p_pes->p_buf + temp_len) = 0x09;
384     temp_len++;
385     *(p_pes->p_buf + temp_len) = 0xff;
386     temp_len++;
387     //加上h264裸流,注意数组别越界
388     if (p_pes->max < (temp_len + len)) {
389         //TODO 
390         printf("pes buf size allocated is too small\n");
391         return -1;
392     }
393     memcpy(p_pes->p_buf + temp_len, Buf, len);
394     p_pes->len = len + temp_len;
395     return 0;
396 }
397 
398 /*将加了pes头的h264拆分成一个个ts包(每个pes包使用独立递增计数器)
399 @l_pes,        [out]list引用,元素存储空间函数内部分配,用完后需要在外部释放
400 @Buf,        [in]加pes头的h264数据
401 return,    -1(异常),0(成功)*/
402 int PesPacket(list<struct pes_packet*> &l_pes,frame_buf *Buf) {
403     if (NULL == Buf || NULL == Buf->p_buf) {
404         printf(" frame_buf or frame_buf->p_buf is NULL when pespacket\n");
405         return -1;
406     }
407     unsigned char packet_pes = TS_PACKET_LEN - 4;    //每个ts包除包头后的长度
408     unsigned char packet_remain = Buf->len % packet_pes;    //每个ts包长度规定188字节,再去掉ts头长度
409     unsigned char packet_num = Buf->len / packet_pes;    //可以剩余可封装出的不含自适应区的包数
410 
411     while (!l_pes.empty()) {
412         l_pes.pop_front();
413     }
414     unsigned short temp_1, temp_2, temp_3;
415     unsigned int temp_len = 0;
416     unsigned int pos = 0;    //pes数据偏移量
417     unsigned char pes_count = 0;    //递增计数器(ts头最后4bit,0~f增加,到f后下一个为0)(这里定义一个局部变量,意思是每个pes包都重置递增计数器)
418 
419     pes_packet *p = new pes_packet;
420     *(p->p_buf) = 0x47;
421     temp_len++;
422     temp_1 = 0x4000 & 0xe000;
423     temp_2 = Elementary_PID & 0x1fff;
424     temp_3 = htons(temp_1 | temp_2);
425     *(unsigned short*)(p->p_buf + temp_len) = temp_3;
426     temp_len += 2;
427     
428     /*如果pes包长度对184取余数大于0,需要填充,第一个包既含有自适应区又含有有效载荷*/
429     if(packet_remain > 0){
430         /*2bit加扰控制(00,无加密),2bit带自适应域(11,带自适应区和有效负载),4bit递增计数器(0000)*/
431         *(p->p_buf + temp_len) = 0x30;    //00 11 00 00
432         temp_len++;
433         /**/
434         unsigned char stuff_num = TS_PACKET_LEN - 4 - 1 - packet_remain;    //填充字节数 = ts包长(188字节) - ts头(4字节) - 自适应域长度(1字节) - 有效载荷长度
435         *(p->p_buf + temp_len) = stuff_num;
436         temp_len++;
437         /*如果正好余下183字节,还只有一个调整位长度,前面我们已经设为0,接下来不要再填充*/
438         if (stuff_num == 0) {
439             //
440         }
441         else {
442             *(p->p_buf + temp_len) = 0x00;    //8bit填充信息flag,我们不要额外的设定信息,填充字节全部0xff
443             temp_len++;
444             memset(p->p_buf + temp_len, 0xff, stuff_num - 1);    //填充字节数还要再减去填充信息flag的长度
445             temp_len += (stuff_num - 1);
446         }
447         memcpy(p->p_buf + temp_len, Buf->p_buf + pos, packet_remain);
448         pos += packet_remain;
449     }
450     /*第一个包仅含有效载荷.仅含有效载荷,没有自适应区和自适应长度,ts头后直接跟pes数据*/
451     else {
452         /*2bit加扰控制(00,无加密),2bit带自适应域(01,有效负载),4bit递增计数器(0000)*/
453         *(p->p_buf + temp_len) = 0x10;    //00 01 00 00
454         temp_len++;
455         memcpy(p->p_buf + temp_len, Buf->p_buf + pos, packet_pes);
456         pos += packet_pes;
457         packet_num--;    //这个包不含自适应,剩余不含自适应区的包数量减一
458     }
459     /*将第一个包插入list链表*/
460     l_pes.push_back(p);
461     while (packet_num > 0) {
462         pes_packet *p_temp = new pes_packet;
463         unsigned int len = 0;
464         /*因为第一个包用了0000,所以这里递增计数器先自增*/
465         pes_count++;
466         pes_count &= 0x0f;    //0~f增加,到f后下一个为0
467         *(p_temp->p_buf) = 0x47;
468         len++;
469         temp_1 = 0x0000 & 0xe000;
470         temp_2 = Elementary_PID & 0x1fff;
471         temp_3 = htons(temp_1 | temp_2);
472         *(unsigned short*)(p_temp->p_buf + len) = temp_3;
473         len += 2;
474         unsigned char c_temp = 0x10;    //00 01 00 00
475         *(p_temp->p_buf + len) = c_temp | pes_count;
476         len++;
477         memcpy(p_temp->p_buf + len, Buf->p_buf + pos, packet_pes);
478         pos += packet_pes;
479         packet_num--;    //剩余不含自适应区的包数量减一
480         l_pes.push_back(p_temp);
481     }
482 
483     return 0;
484 }
485 
486 /*ts头+PAT表
487 return,        加ts头的pat表,固定188字节,需要在外部释放*/
488 unsigned char* PatPacket() {
489     int temp_len = 0;
490     int pat_len = 0;
491     int pat_before_len = 0;
492     unsigned char *p_pat = new unsigned char[188];
493     memset(p_pat, 0xff, 188);
494     *p_pat = 0x47;    //ts包起始字节
495     /*接下来1bit传输错误标识(0),1bit负载单元开始标识(1),1bit传输优先级(1),13bit为PID(pat表限定0),共计2字节*/
496     *(p_pat + 1) = 0x60;
497     *(p_pat + 2) = 0x00;
498     /*接下来2bit传输扰乱控制(00未加密),2bit是否包含自适应区(01只含有效载荷),4bit递增计数器(0000),共计1字节*/
499     *(p_pat + 3) = 0x10;
500     temp_len += 4;
501     /*因为前面负载开始标识为1,这里有一个调整字节,一般这个字节为0x00*/
502     *(p_pat + temp_len) = 0x00;
503     temp_len++;
504     pat_before_len = temp_len;
505     /*接下来是PAT表*/
506     /*table_id,对于PAT只能是0x00*/
507     *(p_pat + temp_len) = 0x00;
508     temp_len++;
509     /*固定4bit(1011),2bit必定为00,后10bit表示后续长度(9+4*PMT表数,注意其中也包括了crc_32校验码的长度,我们的节目只有视频所以这个长度为13:0x000d),共计2字节*/
510     unsigned short temp_1 = 0xb000;    //10 11 00 00 00 00 00 00
511     unsigned short temp_2 = 0x000d & 0x03ff; //10bit有效
512     unsigned short temp_3 = temp_1 | temp_2;
513     *(unsigned short*)(p_pat + temp_len) = htons(temp_3);
514     temp_len += 2;
515     *(unsigned short*)(p_pat + temp_len) = htons(0x0000);/*传输流ID,用户自定义(那就随便定义个0x0000)*/
516     temp_len += 2;
517     /*接下来2bit固定(11),5bit(00000,一旦PAT有变化,版本号加1),1bit(1,表示传送的PAT当前可以使用,若为0表示下一个表有效)*/
518     *(p_pat + temp_len) = 0xc1;    //11 00 00 01
519     temp_len++;
520     /*接下来一个字节(0x00,我们只有一个视频节目,不存在分段)给出了该分段的数目。在PAT中的第一个分段的section_number为0x00,PAT中每一分段将加1。*/
521     *(p_pat + temp_len) = 0x00;    
522     temp_len++;
523     /*接下来一个字节(0x00,我们只有一个视频节目,不存在分段)给出了该分段的数目。该字段指出了最后一个分段号。在整个PAT中即分段的最大数目。*/
524     *(p_pat + temp_len) = 0x00;
525     temp_len++;
526     
527     /*开始循环*/
528     /*接下来开始循环记录节目即PMT表(我们只有一个视频节目),每次循环4字节*/
529     /*循环的前2个字节program_number。0x0001:这个为PMT。该字段指出了节目对于那个Program_map_PID是可以使用的。如果是0x0000,那么后面的PID是网络PID,否则其他值由用户定义。*/
530     *(unsigned short*)(p_pat + temp_len) = htons(0x0001);    
531     temp_len += 2;
532     /*循环的后2个字节,3bit为固定值(111),13bit为节目号对应内容的PID值(即Program_map_PID,反正只有这一个节目,在全局中随便定义一个id,等会PMT表还要用到)*/
533     temp_1 = 0xe000;    //11 10 00 00 00 00 00 00
534     temp_2 = Program_map_PID;
535     temp_3 = temp_1 | temp_2;
536     *(unsigned short*)(p_pat + temp_len) = htons(temp_3);
537     temp_len += 2;
538     /*循环到这里就结束了*/
539 
540     /*最后是4字节的PAT表crc_32校验码。这个校验码从PAT表(不含ts头及调整字节)开始到crc_32校验码前的数据*/
541     //TODO 如果pat分表了,要考虑,我们这里不会分表不要考虑
542     pat_len = temp_len - pat_before_len;
543     unsigned int crc32_res = Crc32Calculate(p_pat + pat_before_len, pat_len, crc32Table);
544     *(unsigned int *)(p_pat + temp_len) = htonl(crc32_res);
545     temp_len += 4;
546     /*后续用0xff填充,我们在初始化时已经初始化为0xff了*/
547     return p_pat;
548 }
549 
550 /*ts头+PMT表
551 return,        加ts头的pmt表,固定188字节,需要在外部释放*/
552 unsigned char* PmtPacket() {
553     int temp_len = 0;
554     int pmt_len = 0;
555     int pmt_before_len = 0;
556     unsigned short temp_1, temp_2, temp_3;
557     unsigned char *p_pmt = new unsigned char[188];
558     memset(p_pmt, 0xff, 188);
559     *p_pmt = 0x47;    //ts包起始字节
560     temp_len++;
561     /*接下来1bit传输错误标识(0),1bit负载单元开始标识(1),1bit传输优先级(1),13bit为PID(用pat表中记录的Program_map_PID:0x0003),共计2字节*/
562     temp_1 = 0x6000 & 0xe000;
563     temp_2 = 0x0003 & 0x1fff;
564     temp_3 = htons(temp_1 | temp_2);
565     *(unsigned short*)(p_pmt + temp_len) = temp_3;
566     //*(p_pmt + 1) = 0x60;
567     //*(p_pmt + 2) = 0x03;
568     temp_len += 2;
569     /*接下来2bit传输扰乱控制(00未加密),2bit是否包含自适应区(01只含有效载荷),4bit递增计数器(0000),共计1字节*/
570     *(p_pmt + temp_len) = 0x10;    //00 01 00 00
571     temp_len++;
572     /*因为前面负载开始标识为1,这里有一个调整字节,一般这个字节为0x00*/
573     *(p_pmt + temp_len) = 0x00;
574     temp_len++;
575     pmt_before_len = temp_len;
576     /*接下来是PMT表*/
577     /*table_id,对于PMT表固定为0x02*/
578     *(p_pmt + temp_len) = 0x02;
579     temp_len++;
580     /*固定4bit(1011),2bit必定为00,后10bit表示后续长度(13+5*流类型数,我们的节目只有视频流所以这个长度为18:0x0012),共计2字节*/
581     temp_1 = 0xb000 & 0xf000;    //10 11 00 00 00 00 00
582     temp_2 = 0x0012 & 0x03ff;    //00 00 00 00 01 00 10
583     temp_3 = htons(temp_1 | temp_2);
584     *(unsigned short*)(p_pmt + temp_len) = temp_3;
585     temp_len += 2;
586     /*对应PAT中的program_number(我们的PAT表中只有一个PMT的program_number:0x0001),共计2字节*/
587     *(unsigned short*)(p_pmt + temp_len) = htons(0x0001);
588     temp_len += 2;
589     /*接下来2bit固定(11),5bit该字段指出了TS中program_map_section的版本号(00000,如果PAT有变化则版本号加1),1bit(1,当该字段置为1时,表示当前传送的program_map_section可用;当该字段置0时,表示当前传送的program_map_section不可用,下一个TS的program_map_section有效)*/
590     *(p_pmt + temp_len) = 0xc1;    //11 00 00 01
591     temp_len++;
592     /*接下来section_number,该字段总是置为0x00*/
593     *(p_pmt + temp_len) = 0x00;
594     temp_len++;
595     /*接下来last_section_number,该字段总是置为0x00*/
596     *(p_pmt + temp_len) = 0x00;
597     temp_len++;
598     /*接下来3bit固定(111),13bitPCR(节目参考时钟)所在TS分组的PID(指定为视频PID),我们暂时不想要pcr(ISO/IEC-13818-1中2.4.4.9对于PMT表中PCR_PID有一段描述“若任何PCR均与专用流的节目定义无关,则此字段应取值0x1fff”,不是太懂PCR,但是我们貌似可以忽略它),指定它为0x1fff,总计2字节*/
599     temp_1 = 0xe000 & 0xe000;    //11 10 00 00 00 00 00 00
600     temp_2 = 0x1fff & 0x1fff;    //00 01 11 11 11 11 11 11
601     temp_3 = htons(temp_1 | temp_2);
602     *(unsigned short*)(p_pmt + temp_len) = temp_3;
603     temp_len += 2;
604     /*接下来4bit固定(1111),12bit节目描述信息(指定为0x000表示没有),共计2字节*/
605     temp_1 = 0xf000 & 0xf000;    //11 11 00 00 00 00 00 00
606     temp_2 = 0x0000 & 0x0fff;    //00 00 00 00 00 00 00 00
607     temp_3 = htons(temp_1 | temp_2);
608     *(unsigned short*)(p_pmt + temp_len) = temp_3;
609     temp_len += 2;
610 
611     /*开始循环*/
612     /*接下来开始循环记录流类型、PID及描述信息,每次循环5字节*/
613     /*流类型,8bit(0x1B:表示这个流时h264格式的,通俗点就是视频,我们只有h264裸流)*/
614     *(p_pmt + temp_len) = 0x1b;
615     temp_len++;
616     /*3bit固定(111),13bit流类型对应的PID,表示该pid的ts包就是用来装该流类型的数据的(对应这里就是我们在pes封包中的为h264裸流指定的Elementary_PID),总计2字节*/
617     temp_1 = 0xe000 & 0xe000;
618     temp_2 = Elementary_PID & 0x1fff;
619     temp_3 = htons(temp_1 | temp_2);
620     *(unsigned short*)(p_pmt + temp_len) = temp_3;
621     temp_len += 2;
622     /*4bit固定(1111),12bit节目描述信息,指定为0x000表示没有,共计2字节*/
623     temp_1 = 0xf000 & 0xf000;
624     temp_2 = 0x0000 & 0x0fff;
625     temp_3 = htons(temp_1 | temp_2);
626     *(unsigned short*)(p_pmt + temp_len) = temp_3;
627     temp_len += 2;
628     /*循环结束*/
629 
630     /*最后是4字节的PMT表crc_32校验码。这个校验码从PMT表(不含ts头及调整字节)开始到crc_32校验码前的数据*/
631     pmt_len = temp_len - pmt_before_len;
632     unsigned int crc32_res = Crc32Calculate(p_pmt + pmt_before_len, pmt_len, crc32Table);
633     *(unsigned int *)(p_pmt + temp_len) = htonl(crc32_res);
634     temp_len += 4;
635     /*后续用0xff填充,我们在初始化时已经初始化为0xff了*/
636     return p_pmt;
637 }
638 
639 static int FindStartCode2(unsigned char *Buf)
640 {
641     if (Buf[0] != 0 || Buf[1] != 0 || Buf[2] != 1) return 0; //判断是否为0x000001,如果是返回1
642     else return 1;
643 }
644 
645 static int FindStartCode3(unsigned char *Buf)
646 {
647     if (Buf[0] != 0 || Buf[1] != 0 || Buf[2] != 0 || Buf[3] != 1) return 0;//判断是否为0x00000001,如果是返回1
648     else return 1;
649 }
650 
651 void write2file(unsigned char* buf, int len) {
652     fwrite(buf, 1, len, fw);
653 }
654 
655 void writeM3u8(m3u8_text text,int ts_time_max, int ts_start_num) {
656     char file_m3u8[] = "stream.m3u8";
657     OpenWriteFile(file_m3u8);
658     char targetduration[M3U8_TS_LEN] = { 0 };
659     memcpy(targetduration, text.ext_x_targetduration, M3U8_TS_LEN);
660     char media_sequence[M3U8_TS_LEN] = { 0 };
661     memcpy(media_sequence, text.ext_x_media_sequence, M3U8_TS_LEN);
662     sprintf(text.ext_x_targetduration, targetduration, ts_time_max);
663     sprintf(text.ext_x_media_sequence, media_sequence, ts_start_num);
664     write2file((unsigned char*)text.extm3u, strlen(text.extm3u));
665     write2file((unsigned char*)text.ext_x_version, strlen(text.ext_x_version));
666     write2file((unsigned char*)text.ext_x_allow_cache, strlen(text.ext_x_allow_cache));
667     write2file((unsigned char*)text.ext_x_targetduration, strlen(text.ext_x_targetduration));
668     write2file((unsigned char*)text.ext_x_media_sequence, strlen(text.ext_x_media_sequence));
669     write2file((unsigned char*)text.ext_x_playlist_type, strlen(text.ext_x_playlist_type));
670     while (!text.l_m3u8_ts.empty()) {
671         m3u8_ts temp = text.l_m3u8_ts.front();
672         text.l_m3u8_ts.pop_front();
673         char extinf[M3U8_TS_LEN] = { 0 };
674         sprintf(extinf, text.extinf, temp.ts_time);
675         write2file((unsigned char*)extinf, strlen(extinf));
676         write2file((unsigned char*)temp.ts_name, strlen(temp.ts_name));
677         write2file((unsigned char*)"\n", strlen("\n"));    //ts_name后是没有换行符的,这里要添加换行符
678     }
679     write2file((unsigned char*)text.ext_x_endlist, strlen(text.ext_x_endlist));
680     CloseWriteFile();
681 }
682 
683 /*输入h264裸流文件,输出ts文件*/
684 int H264ToTs()
685 {
686     char file_in[] = "./stream.h264";
687     OpenBitstreamFile(file_in);
688     MakeTable(crc32Table);
689     char file_out[] = "./stream.ts";
690     OpenWriteFile(file_out);
691     unsigned char* p_pat_res = PatPacket();
692     write2file(p_pat_res, TS_PACKET_LEN);
693     fflush(fw);
694     unsigned char* p_pmt_res = PmtPacket();
695     write2file(p_pmt_res, TS_PACKET_LEN);
696     fflush(fw);
697 
698     unsigned char* temp = new unsigned char[BUF_SIZE];
699     int temp_len = 0;
700     frame_buf *p_frame = new frame_buf;
701     list<pes_packet*> list_pes;
702     int frame_count = 0;
703     while (!feof(bits)) {
704         int read_res = GetOneFrame(temp,temp_len);
705         if (read_res < 0) {
706             continue;
707         }
708         frame_count++;
709         如果需要实时播放(直播)而不是每次都是都是从文件开始到结束,可以考虑在中间插入pat和pmt表,暂时我们也用不上
710         //else if (read_res == 0) {
711         //    write2file(p_pat_res, TS_PACKET_LEN);
712         //    fflush(fw);
713         //    write2file(p_pmt_res, TS_PACKET_LEN);
714         //    fflush(fw);
715         //}
716         int res = PesHead_pd(p_frame, temp, temp_len);
717         //int res = PesHead_pd_1(p_frame, temp, temp_len);
718         if (res != 0) {
719             printf("pesHead failed\n");
720             return -1;
721         }
722         res = PesPacket(list_pes, p_frame);
723         if (res != 0) {
724             printf("pesPacket failed\n");
725             return -1;
726         }
727         while (!list_pes.empty()) {
728             pes_packet* p = list_pes.front();
729             write2file(p->p_buf, TS_PACKET_LEN);
730             fflush(fw);
731             list_pes.pop_front();
732             delete p;
733             
734         }
735     }
736     delete[] p_pat_res;
737     delete[] p_pmt_res;
738     delete[] temp;
739     delete p_frame;
740     fclose(bits);
741     fclose(fw);
742     return 0;
743 }
744 
745 /*输入h264裸流文件,输出m3u8及多个ts文件*/
746 int H264ToM3u8()
747 {
748     char file_in[] = "./stream.h264";
749     OpenBitstreamFile(file_in);
750     MakeTable(crc32Table);
751     int ts_file_num = 100;
752     int ts_start_num = ts_file_num;
753     int ts_time_max = 0;
754     char ts_file_temp[M3U8_TS_LEN] = "./stream%d.ts";
755     char ts_file_current[M3U8_TS_LEN] = { 0 };
756     sprintf(ts_file_current, ts_file_temp, ts_file_num);
757 
758     OpenWriteFile(ts_file_current);
759     unsigned char* p_pat_res = PatPacket();
760     write2file(p_pat_res, TS_PACKET_LEN);
761     fflush(fw);
762     unsigned char* p_pmt_res = PmtPacket();
763     write2file(p_pmt_res, TS_PACKET_LEN);
764     fflush(fw);
765 
766     unsigned char* temp_frame = new unsigned char[BUF_SIZE];
767     int temp_frame_len = 0;
768     frame_buf *p_frame = new frame_buf;
769     list<pes_packet*> list_pes;
770     int IDR_count = 0;    
771     int frame_count = 0;    //用来计算一个.ts文件的时间
772     m3u8_text text;
773     while (!feof(bits)) {
774         int read_res = GetOneFrame(temp_frame, temp_frame_len);
775 
776         if (read_res < 0) {
777             continue;
778         }
779         //将一个长ts文件分成多个ts短文件,我们尽量让第一个帧为关键帧(没试第一帧为非关键帧的情况)
780         else if (0 == read_res) {
781             IDR_count++;
782             frame_count++;
783             if (0 == (IDR_count%IDR_PER_TSFILE)) {
784                 CloseWriteFile();
785                 /*将这个ts文件插入到m3u8_text中*/
786                 m3u8_ts temp;
787                 temp.ts_time = 1.0 * frame_count / frame_rate;
788                 frame_count = 0;    //ts包计数清零
789                 memcpy(temp.ts_name, ts_file_current, M3U8_TS_LEN);
790                 text.l_m3u8_ts.push_back(temp);
791                 int temp_time = ceil(temp.ts_time);
792                 if (ts_time_max < temp_time) {
793                     ts_time_max = temp_time;
794                 }
795                 /*开始下一个ts文件,文件号连续*/
796                 ts_file_num++;
797                 memset(ts_file_current, 0, M3U8_TS_LEN);
798                 sprintf(ts_file_current, ts_file_temp, ts_file_num);
799                 OpenWriteFile(ts_file_current);
800                 write2file(p_pat_res, TS_PACKET_LEN);
801                 fflush(fw);
802                 write2file(p_pmt_res, TS_PACKET_LEN);
803                 fflush(fw);
804             }
805         }
806         else {
807             frame_count++;
808         }
809         int res = PesHead_pd(p_frame, temp_frame, temp_frame_len);
810         //int res = PesHead_pd_1(p_frame, temp, temp_len);
811         if (res != 0) {
812             printf("pesHead failed\n");
813             return -1;
814         }
815         res = PesPacket(list_pes, p_frame);
816         if (res != 0) {
817             printf("pesPacket failed\n");
818             return -1;
819         }
820         while (!list_pes.empty()) {
821             pes_packet* p = list_pes.front();
822             write2file(p->p_buf, TS_PACKET_LEN);
823             fflush(fw);
824             list_pes.pop_front();
825             delete p;
826             
827         }
828     }
829     /*将最后一帧写入m3u8_text*/
830     CloseWriteFile();
831     m3u8_ts temp;
832     temp.ts_time = 1.0 * frame_count / frame_rate;
833     memcpy(temp.ts_name, ts_file_current, M3U8_TS_LEN);
834     text.l_m3u8_ts.push_back(temp);
835     int temp_time = ceil(temp.ts_time);
836     if (ts_time_max < temp_time) {
837         ts_time_max = temp_time;
838     }
839     /*使用m3u8_text完成最后的m3u8文件*/
840     writeM3u8(text, ts_time_max, ts_start_num);
841     /*释放内存*/
842     delete[] p_pat_res;
843     delete[] p_pmt_res;
844     delete[] temp_frame;
845     delete p_frame;
846     CloseBitstreamFile();
847     
848     return 0;
849 }
850 
851 int main() {
852     //return H264ToM3u8();
853     return H264ToTs();
854 }

复制代码

 

最后附上测试的h264数据

链接: https://pan.baidu.com/s/1TsmmuWtixhU5YEcUpNCg3A

提取码: 4a4b


http://www.ppmy.cn/news/726027.html

相关文章

Android视频播放

前言 随着音视频领域的火热&#xff0c;在很多领域&#xff08;教育&#xff0c;游戏&#xff0c;娱乐&#xff0c;体育&#xff0c;跑步&#xff0c;餐饮&#xff0c;音乐等&#xff09;尝试做音视频直播/点播功能&#xff0c;那么作为开发一个小白&#xff0c;如何快速学习音…

android直播音频开发准备

前言 随着音视频领域的火热&#xff0c;在很多领域&#xff08;教育&#xff0c;游戏&#xff0c;娱乐&#xff0c;体育&#xff0c;跑步&#xff0c;餐饮&#xff0c;音乐等&#xff09;尝试做音视频直播/点播功能&#xff0c;那么作为开发一个小白&#xff0c;如何快速学习音…

UnityWebGL2021 全屏--新版本

前言&#xff1a; 之前看的很多关于WebGL全屏的代码和帖子&#xff0c;但都是unity2018的版本&#xff0c;目前听说unity2020之后&#xff0c;webgl加载速度有优化&#xff0c;所以没敢换回低版本&#xff0c;索性在这个版本基础上修改&#xff0c;写的不好的地方&#xff0c;请…

与webview打交道中踩过的那些坑

随着HTML5被越来越多的用到web APP的开发当中&#xff0c;webview这一个神器便日渐凸显出重要地位。简要的说&#xff0c;webview能够在移动应用中开辟出一个窗口&#xff0c;在里面显示html页面&#xff0c;css以及js代码也可以被解析执行&#xff0c;它使用的是我们熟悉的web…

iphone/ipad/itouch进入DFU模式最简单的操作办法

“正确进入DFU模式”对于果粉新手来说是一个很难掌握的技巧&#xff08;越狱、降级都要用到它&#xff09;&#xff0c;特别是时间的把握上&#xff0c;多按一会儿不行&#xff0c;少按一会儿也不行&#xff0c;从网上看到这个办法&#xff0c;觉得不错&#xff0c;转载于此&am…

iPad 图标变小而分散 解决方法

想必各位都遇到过这种情况吧&#xff0c;当你装了某个不正常cydia插件以后&#xff0c;ipad变成iphone界面&#xff0c;图标小而分散&#xff0c;状态栏变得花屏&#xff0c;而任何程序包括cydia都打不开时&#xff0c;请大家不要急着重新恢复ipad&#xff0c;可以按照我这个办…

几款流行的ipad上的PDF阅读器评测

Author: Bin Zuo, Keqian Li 阅读体验 Stanza: 采用左右换页,比较流畅,页面清晰。可以改变字体、段落格式、背景颜色和背景图片,有夜间阅读模式。采用横向蓝色无极进度条,看上去和播放视频的进度条一样,拖动之后会先在屏幕中间显示正在第几页,然后再转到那一页。 这是…

解决cocos2d-x HD 移植 iPad 2闪屏问题

最近利用cocos2d-x做个小游戏&#xff0c;移植到Android平台没有问题&#xff0c;后来移植到iPad2出现闪屏问题。这是什么原因呢&#xff1f; 根据cocos2d-iphone的帖子”cocos2d and iPad 2” http://www.cocos2d-iphone.org/archives/1430 , cocos2d-x 游戏也有同样的问题&am…