檔案參考
http://www.diybl.com/course/6_system/linux/Linuxjs/2008924/145267.html

FFMpeg MPEG2 TS 串流解碼的流程分析

FFMpeg對MPEG2 TS流解碼的流程分析

1.引言

每天只要是快睡覺或剛起床,頭暈腦漲,不過功課還是要做的,是吧。

2.從簡單說起

說到具體的音頻或者視頻格式,一上來就是理論,那是國內混資歷的所謂教授的做為,

對於我們,不合適,還是用自己的模式理解這些晦澀不已的理論吧。

其實MPEG2是一種協議,至少已經成為ISO標準的就有以下幾部分:

ISO/IEC-13818-1︰系統部分;
ISO/IEC-13818-2︰視頻編碼格式;
ISO/IEC-13818-3︰音頻編碼格式;
ISO/IEC-13818-4︰一致性測試;
ISO/IEC-13818-5︰軟體部分;
ISO/IEC-13818-6︰數字存儲媒體命令與控制;
ISO/IEC-13818-7︰進階音頻編碼;
ISO/IEC-13818-8︰系統解碼實時界面;

我不是很想說實際的音視頻編碼格式,畢竟協議已經很清楚了,

我主要想說說這些部分怎麼組合起來在實際應用中工作的。

第一部分(系統部分)很重要,是構成以MPEG2為基礎的應用的基礎。

簡單解釋一下:

比如DVD實際上是以系統部分定義的PS流為基礎,加上版權管理等其他技術構成的。

而我們的故事主角,則是另外一種流格式,TS流,它在現階段最大的應用是在數位電視節目的傳輸與存儲上。

因此你可以理解TS實際上是一種傳輸協議,與實際傳輸的負載關係不大,只是在TS中傳輸了音頻,視頻或者其他數據。

先說一下為什麼會有這兩種格式的出現。

PS適用於沒有損耗的環境下面存儲,而TS則適用於可能出現損耗或者錯誤的各種物理網路環境。

比如你在公交上看到的電視,很有可能就是基於TS的DVB-T的應用

我們再來看MPEG2協議中的一些概念,為理解代碼做好功課:


ES(Elementary Stream):

wiki上說 "An elementary stream (ES) is defined by MPEG communication protocol is usually the output of an audio or video encoder"

恩,很簡單吧,就是編碼器編出的一組數據,可能是音頻的,視頻的,或者其他數據。

說到這,其實可以對編碼器的流程思考一下,無非是執行:採樣,量化,編碼這3個步驟中的編碼而已(有些設備可能會包含前面的採樣和量化)。

關於視頻編碼的基本理論,還是請參考其它的資料。


PES(Packetized Elementary Stream):

wiki上說"allows an Elementary stream to be divided into packets"

其實可以理解成,把一個源源不斷的數據(音頻,視頻或者其他)流,打斷成一段一段,以便處理。


TS(Transport Stream):


PS(Program Stream):

這兩個上面已經有所提及,後面會詳細分析TS,我對PS格式興趣不大。


3.步入正題

進入正題,直接看Code。

前面說過,TS是一種傳輸協議,因此對應到FFmpeg,可以認為他是一種封裝格式。

對應的代碼可以先去libavformat裡面找,就是mpegts.c

逐步看下去:

[libavformat/utils.c]

int av_open_input_file(AVFormatContext **ic_ptr, const char *filename, AVInputFormat *fmt, int buf_size, AVFormatParameters *ap)
{
    int err, probe_size;
    AVProbeData probe_data, *pd = &probe_data;

    ByteIOContext *pb = NULL;
    pd->filename = "";
    if (filename)
    pd->filename = filename;
    pd->buf = NULL;
    pd->buf_size = 0;
    #########################################################################
    【1】這段代碼其實是為了針對不需要Open檔案的容器Format的探測,其實就是使用
    AVFMT_NOFILE標記的容器格式單獨處理,現下只有使用了該標記的Demuxer很少,
    只有image2_demuxer,rtsp_demuxer,因此我們分析TS時候可以不考慮這部分
    #########################################################################

    if( !fmt ) {
        /* guess format if no file can be opened */
        fmt = av_probe_input_format(pd, 0);
    }
    /* Do not open file if the format does not need it. XXX: specific

    hack needed to handle RTSP/TCP */

    if( !fmt !(fmt->flags & AVFMT_NOFILE) ) {
        /* if no file needed do not try to open one */
        #####################################################################
        【2】這個函數似乎很好理解,無非是帶緩衝的IO的封裝,不過我們既然到此了
        ,不妨跟蹤下去,看看別人對帶緩沖的IO操作封裝的實現
        #####################################################################

        if( (err=url_fopen(&pb, filename, URL_RDONLY)) < 0 ) {
            goto fail;
        }

        if( buf_size > 0 ) {
            url_setbufsize(pb, buf_size);
        }

        for( probe_size= PROBE_BUF_MIN; probe_size<=PROBE_BUF_MAX && !fmt; probe_size<<=1 ) {
             int score= probe_size < PROBE_BUF_MAX ? AVPROBE_SCORE_MAX/4 : 0;

             /* read probe data */
             pd->buf= av_realloc(pd->buf, probe_size + AVPROBE_PADDING_SIZE);
             ##################################################################
             【3】真正將檔案讀入到pd的buffer的地方,實際上最終呼叫 FILE protocol
             的file_read(),將內容讀入到pd的buf,具體代碼如果有興趣可以自己跟蹤
             ##################################################################

             pd->buf_size = get_buffer(pb, pd->buf, probe_size);
             memset(pd->buf+pd->buf_size, 0, AVPROBE_PADDING_SIZE);
             if( url_fseek(pb, 0, SEEK_SET) < 0 ) {
                 url_fclose(pb);
                 if( url_fopen(&pb, filename, URL_RDONLY) < 0 ) {
                     pb = NULL;
                     err = AVERROR(EIO);
                     goto fail;
                 }
             }
             ##################################################################
             【4】此時的pd已經有了需要分析的原始檔案,只需要查找相應容器format
             的Tag比較,以判斷這裡讀入的究竟為什麼容器格式
             ##################################################################

             /* guess file format */
             fmt = av_probe_input_format2(pd, 1, &score);
        }
        av_freep(&pd->buf);
    }

    /* if still no format found, error */
    if( !fmt ) {
        err = AVERROR_NOFMT;
        goto fail;
    }

    /* check filename in case an image number is expected */
    if( fmt->flags & AVFMT_NEEDNUMBER ) {
        if( !av_filename_number_test(filename) ) {
            err = AVERROR_NUMEXPECTED;
            goto fail;
        }
    }

    err = av_open_input_stream(ic_ptr, pb, filename, fmt, ap);

    if( err ) {
        goto fail;
    }

    return 0;

    fail:
        av_freep(&pd->buf);

    if( pb ) {
        url_fclose(pb);
    }

    *ic_ptr = NULL;

    return err;
}


【2】帶緩衝IO的封裝的實現
[liavformat/aviobuf.c]
int url_fopen(ByteIOContext **s, const char *filename, int flags)
{
    URLContext *h;
    int err;
    err = url_open(&h, filename, flags);

    if( err < 0 )
        return err;

    err = url_fdopen(s, h);

    if( err < 0 ) {
        url_close(h);
        return err;
    }

    return 0;
}


可以看到下面的這個函數,先查找是否是FFmpeg支援的protocol的格式,

如果檔案名不符合,則預設是FILE protocol格式。

很顯然,這裡protocol判斷是以URL的模式判讀的,因此基本上所有的IO界面函數都是url_xxx的形式。

在這也可以看到,FFmpeg支援的protocol有︰

/* protocols */
REGISTER_PROTOCOL (FILE, file);
REGISTER_PROTOCOL (HTTP, http);
REGISTER_PROTOCOL (PIPE, pipe);
REGISTER_PROTOCOL (RTP, rtp);
REGISTER_PROTOCOL (TCP, tcp);
REGISTER_PROTOCOL (UDP, udp);

而大部分情況下,如果你不指明類似 file://xxx,http: //xxx 格式,它都以 FILE protocol 來處理。


[liavformat/avio.c]
int url_open(URLContext **puc, const char *filename, int flags)
{
    URLProtocol *up;
    const char *p;
    char proto_str[128], *q;

    p = filename;
    q = proto_str;

    while( *p != '\0' && *p != ':' ) {
        /* protocols can only contain alphabetic chars */
        if( !isalpha(*p) )
            goto file_proto;
        if( (q - proto_str) < sizeof(proto_str) - 1 )
            *q++ = *p;
        p++;
    }

    /* if the protocol has length 1, we consider it is a dos drive */
    if( *p == '\0' || (q - proto_str) <= 1) {
        file_proto:
            strcpy(proto_str, "file");
    } else {
        *q = '\0';
    }

    up = first_protocol;

    while( up != NULL ) {
        if( !strcmp(proto_str, up->name) ) {
            #################################################################
            很顯然,此時已經知道up,filename,flags
            #################################################################

            return url_open_protocol (puc, up, filename, flags);
        }

       up = up->next;
    }

    *puc = NULL;
    return AVERROR(ENOENT);
}


[libavformat/avio.c]
int url_open_protocol (URLContext **puc, struct URLProtocol *up, const char *filename, int flags)
{
    URLContext *uc;
    int err;
    ##########################################################################
    【a】? 為什麼這樣分發空間
    ##########################################################################

    uc = av_malloc(sizeof(URLContext) + strlen(filename) + 1);

    if( !uc ) {
        err = AVERROR(ENOMEM);
        goto fail;
    }

    #if LIBAVFORMAT_VERSION_MAJOR >= 53
    uc->av_class = &urlcontext_class;
    #endif
    ##########################################################################
    【b】? 這樣的用意又是為什麼
    ##########################################################################

    uc->filename = (char *) &uc[1];
    strcpy(uc->filename, filename);
    uc->prot = up;
    uc->flags = flags;
    uc->is_streamed = 0; /* default = not streamed */
    uc->max_packet_size = 0; /* default: stream file */
    err = up->url_open(uc, filename, flags);

    if( err < 0 ) {
        av_free(uc);
        *puc = NULL;
        return err;
    }
    //We must be carefull here as url_seek() could be slow, for example for http

    if( (flags & (URL_WRONLY URL_RDWR)) || !strcmp(up->name, "file") )
        if( !uc->is_streamed && url_seek(uc, 0, SEEK_SET) < 0 )
            uc->is_streamed= 1;

    *puc = uc;
    return 0;

    fail:
        *puc = NULL;

    return err;
}

上面這個函數不難理解,但有些地方頗值得玩味,比如上面給出問號的地方,

你明白為什麼這樣Coding嗎?

很顯然,此時up->url_open()實際上調用的是file_open()[libavformat/file.c],

看完這個函數,對上面的內存分發,是否恍然大悟。

上面只是分析了url_open(),還沒有分析url_fdopen(s, h);

這部分代碼,也留給有好奇心的你了。

恩,為了追蹤這個流程,走得有些遠,但不是全然無用。

終於來到了【4】,我們來看MPEG TS格式的偵測過程,這其實才是我們今天的主角。


4. MPEG TS格式的探測過程

[liavformat/mpegts.c]
static int mpegts_probe(AVProbeData *p)
{
    #if 1
        const int size= p->buf_size;
        int score, fec_score, dvhs_score;
        #define CHECK_COUNT 10
       
        if( size < (TS_FEC_PACKET_SIZE * CHECK_COUNT) ) {
            return -1;
        }
        score = analyze(p->buf, TS_PACKET_SIZE *CHECK_COUNT, TS_PACKET_SIZE, NULL);
        dvhs_score = analyze(p->buf, TS_DVHS_PACKET_SIZE *CHECK_COUNT, TS_DVHS_PACKET_SIZE, NULL);
        fec_score= analyze(p->buf, TS_FEC_PACKET_SIZE*CHECK_COUNT, TS_FEC_PACKET_SIZE, NULL);
        // av_log(NULL, AV_LOG_DEBUG, "score: %d, dvhs_score: %d, fec_score: %d \n", score, dvhs_score, fec_score);
        // we need a clear definition for the returned score otherwise things will become messy sooner or later

        if( score > fec_score && score > dvhs_score && score > 6 ) {
            return AVPROBE_SCORE_MAX + score - CHECK_COUNT;
        } else if( dvhs_score > score && dvhs_score > fec_score && dvhs_score > 6 ) {
            return AVPROBE_SCORE_MAX + dvhs_score - CHECK_COUNT;
        } else if( fec_score > 6 ) {
            return AVPROBE_SCORE_MAX + fec_score - CHECK_COUNT;
        } else {
            return -1;
        }
    #else
        /* only use the extension for safer guess */
        if( match_ext(p->filename, "ts") ) {
            return AVPROBE_SCORE_MAX;
        } else {
            return 0;
        }
    #endif
}

之所以會出現3種格式,主要原因是︰

TS標準是188Bytes,而小日本自己又弄了一個192Bytes的DVH-S格式,

第三種的204Bytes則是在188Bytes的基礎上,加上16Bytes的FEC(前向糾錯)。

static int analyze(const uint8_t *buf, int size, int packet_size, int *index)
{
    int stat[packet_size];
    int i;
    int x=0;
    int best_score=0;
    memset(stat, 0, packet_size*sizeof(int));
    ##########################################################################
    由於查找的特定格式至少3個Bytes,因此,至少最後3個Bytes不用查找
    ##########################################################################

    for( x=i=0; i<size-3; i++ ) {
        ######################################################################
        參看後面的協議說明
        ######################################################################

        if( buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30) ) {
            stat[x]++;
            if( stat[x] > best_score ) {
                best_score= stat[x];
                if( index )
                    *index= x;
            }
        }
        x++;
        if( x == packet_size )
            x= 0;
    }
    return best_score;
}

這個函數簡單說來,是在size大小的buf中,尋找滿足特定格式,

長度為packet_size的packet的個數,顯然返回的值越大,越可能是相應的格式(188/192/204)。

其中的這個特定格式,其實就是協議的規定格式: Syntax No. of bits Mnemonic

transport_packet()
{
    sync_byte                        8  bslbf
    transport_error_indicator        1  bslbf
    payload_unit_start_indicator     1  bslbf
    transport_priority               1  bslbf
    PID                             13  uimsbf
    transport_scrambling_control     2  bslbf
    adaptation_field_control         2  bslbf
    continuity_counter               4  uimsbf
    if( adaptation_field_control=='10' || adaptation_field_control=='11' ) {
        adaptation_field();
    }

    if( adaptation_field_control=='01' || adaptation_field_control=='11') {
        for (i=0;i<N;i++) {
            data_byte 8 bslbf
        };
    }
}

其中的sync_byte固定為0x47,即上面的: buf[i] == 0x47。

由於transport_error_indicator為1的TS Packet實際有錯誤,表示攜帶的數據無意義,

這樣的Packet顯然沒什麼意義,因此: !(buf[i+1] & 0x80)

對於adaptation_field_control,如果為取值為0x00,則表示為未來保留。

現下不用,因此: buf[i+3] & 0x30

這就是MPEG TS的偵測過程。


參考:
1.http://en.wikipedia.org/wiki/MPEG_transport_stream
2.ISO/IEC-13818-1

arrow
arrow
    全站熱搜

    BB 發表在 痞客邦 留言(5) 人氣()