簡單實現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: 0x800706f4
 18 avcodec info: Using DXVA2 (Intel(R) HD Graphics, vendor Intel(8086), device 402, revision 6) for hardware decoding
 19 direct3d9 error: SetThumbNailClip failed: 0x800706f4
 20 這應當是我們忽略了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 由於是極簡封裝,存在一些發現或未發現的小問題,比如PCR
 29 PC端VLC不能正常播放。
 30 手機瀏覽器和視頻播放器可以正常的播放。(貌似現在手機都兼容了safari瀏覽器?)
 31 html5沒有測試過。
 32 —————————————————————————————————————
 33 written by dyan1024
 34 date:2019-1-2
 35 */
 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 _WIN32
 44 #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);//查找開始字符0x000001
 54 static int FindStartCode3(unsigned char *Buf);//查找開始字符0x00000001
 55 
 56 static int info2 = 0, info3 = 0;
 57 
 58 #define BUF_SIZE 1024*1024*3
 59 #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

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM