HLS(HTTP Live Streaming)協議之m3u8文件生成方式


     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協議了  


免責聲明!

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



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