HLS(HTTP Live Streaming)是Apple的動態碼率自適應技術。主要用於PC和Apple終端的音視頻服務。包括一個m3u(8)的索引文件,TS媒體分片文件和key加密串文件。
HLS的關鍵其實是生成m3u8索引文件和TS媒體分片,下面我將通過以下幾個步驟講述m3u8及TS媒體分片的生成:
第一步---獲取TS文件:
TS(Transport Stream)既傳輸流,標准制定於mpeg2文檔協議中,當時TS格式主要是為了數字電視傳輸而制定,制定的年限相當早,在網上能找到很完備的mpeg2文檔介紹。大家可以參考mpege-2文檔標准中TS流介紹學習該格式。
現在的我們下載的高清電影以mkv格式居多,早期的的電影可能一rmvb和avi居多,更早的甚至還有mpg格式,現在流行的視頻網站下載的視頻基本都是flv格式。這些格式都是非TS格式,不過不要緊,現在視頻轉碼的軟件也非常多,我們可以通過以下兩種方式進行轉碼。
1,通過格式工廠軟件,這是一個比較成熟的軟件,網上百度下載即可,不過只有軟件,不利於后期源碼的直接開發;
下載地址:http://www.pcfreetime.com/CN/index.html
2,通過ffmpeg進行格式轉換,該工程為開源項目,我們在實際開發的過程中可以直接集成該源碼,(具體的集成方式該篇文章不講解,后期將對怎么封裝調用ffmpeg做出相應介紹)。目前我們只是想獲取TS文件用於生產m3u8索引文件和TS分片而已,直接下載ffmpeg的可執行程序,通過ffmpeg.exe轉換即可:
下載地址:http://ffmpeg.org/
通過命令行模式進入到ffmpeg.exe所在的目錄,在命令行中輸入:ffmpeg.exe -i XXX.flv xxx.ts 即可,如下圖:
圖1
第二步--生成m3u8索引文件和TS媒體分片
1, m3u8 源碼下,
下載地址:
https://github.com/johnf/m3u8-segmenter/archive/master.zip 該地址的源碼主要是在linux系統編譯,不過也能修改成在windows下編譯。
windows的源碼下載 :
官網: http://www.espend.de/artikel/iphone-ipad-ipod-http-streaming-segmenter-and-m3u8-windows.html 源碼地址http://code.google.com/p/httpsegmenter/ 不過也要依賴ffmpeg庫,稍微修改下即可。
其實以上兩個路徑的源碼其實是一樣滴,下面那個是德國人修改寫的,看后綴de就知道了,可能需要翻牆才能打開。
下面是截取segmenter.c中的代碼分片片段:
do { double segment_time = 0.0; AVPacket packet; double packetStartTime = 0.0; double packetDuration = 0.0; if (!decode_done) { decode_done = av_read_frame(ic, &packet); if (!decode_done) { if (packet.stream_index != video_index && packet.stream_index != audio_index) { av_free_packet(&packet); continue; } timeStamp = (double)(packet.pts) * (double)(ic->streams[packet.stream_index]->time_base.num) / (double)(ic->streams[packet.stream_index]->time_base.den); if (av_dup_packet(&packet) < 0) { fprintf(stderr, "Could not duplicate packet\n"); av_free_packet(&packet); break; } insertPacket(streamLace, &packet, timeStamp); } } if (countPackets(streamLace) < 50 && !decode_done) { /* allow the queue to fill up so that the packets can be sorted properly */ continue; } if (!removePacket(streamLace, &packet)) { if (decode_done) { /* the queue is empty, we are done */ break; } assert(decode_done); continue; } packetStartTime = (double)(packet.pts) * (double)(ic->streams[packet.stream_index]->time_base.num) / (double)(ic->streams[packet.stream_index]->time_base.den); packetDuration = (double)(packet.duration) * (double)(ic->streams[packet.stream_index]->time_base.num) / (double)(ic->streams[packet.stream_index]->time_base.den); #if !defined(NDEBUG) && (defined(DEBUG) || defined(_DEBUG)) if (av_log_get_level() >= AV_LOG_VERBOSE) fprintf(stderr, "stream %i, packet [%f, %f)\n", packet.stream_index, packetStartTime, packetStartTime + packetDuration); #endif segment_duration = packetStartTime + packetDuration - prev_segment_time; // NOTE: segments are supposed to start on a keyframe. // If the keyframe interval and segment duration do not match // forcing the segment creation for "better seeking behavior" // will result in decoding artifacts after seeking or stream switching. if (packet.stream_index == video_index && (packet.flags & AV_PKT_FLAG_KEY || strict_segment_duration)) { segment_time = packetStartTime; } else if (video_index < 0) { segment_time = packetStartTime; } else { segment_time = prev_segment_time; } if (segment_time - prev_segment_time + segment_duration_error_tolerance > target_segment_duration + extra_duration_needed) { avio_flush(oc->pb); avio_close(oc->pb); // Keep track of accumulated rounding error to account for it in later chunks. segment_duration = segment_time - prev_segment_time; rounded_segment_duration = (int)(segment_duration + 0.5); extra_duration_needed += (double)rounded_segment_duration - segment_duration; updatePlaylist(playlist, playlist_filename, output_filename, output_index, rounded_segment_duration); _snprintf(output_filename, strlen(output_prefix) + 15, "%s-%u.ts", output_prefix, ++output_index); if (avio_open(&oc->pb, output_filename, AVIO_FLAG_WRITE) < 0) { fprintf(stderr, "Could not open '%s'\n", output_filename); break; } // close when we find the 'kill' file if (kill_file) { FILE* fp = fopen("kill", "rb"); if (fp) { fprintf(stderr, "user abort: found kill file\n"); fclose(fp); remove("kill"); decode_done = 1; removeAllPackets(streamLace); } } prev_segment_time = segment_time; } ret = av_interleaved_write_frame(oc, &packet); if (ret < 0) { fprintf(stderr, "Warning: Could not write frame of stream\n"); } else if (ret > 0) { fprintf(stderr, "End of stream requested\n"); av_free_packet(&packet); break; } av_free_packet(&packet); } while (!decode_done || countPackets(streamLace) > 0);
2, 把下載下來的源碼直接在vs中編譯生成exe即可, 如我生成的exe為m3u8.exe:
圖2
3, 通過命令行進入該目錄,並在命令行中輸入: m3u8.exe -d 10 -x m3u8list.m3u8 即可生成.m3u8文件和ts分片文件,如圖2目錄文件的m3u8list.m3u8 和-1.ts、-2.ts和-3.ts文件。
圖3
4, 如以圖2的目錄列表,直接用VLC播放器就可以播放m3u8list.m3u8文件, 用寫字板查看m3u8文件內容為:
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXTINF:10,
-1.ts
#EXTINF:10,
-2.ts
#EXTINF:9,
-3.ts
#EXT-X-ENDLIST
好了,大功告成! 我們可以直接播放m3u8list.m3u8 和-1.ts、-2.ts、-3.ts文件 , 也可以直接用http協議傳輸這些文件,就成了hls協議了