1、ffplay PacketQueue源碼分析

ffplay PacketQueue源碼分析

一、數據結構

typedef struct PacketQueue {
    MyAVPacketList *first_pkt, *last_pkt;//串聯AVPacket鏈表,分別指向隊頭和隊尾
    int nb_packets;//隊列中元素的個數
    int size;//隊列持有數據的字節大小
    int64_t duration;//隊列AVPacket總時長
    int abort_request;
    int serial;
    SDL_mutex *mutex;//隊列被兩個線程同時操作,添加AVPacket和移除AVPacket,採用鎖機制保證線程安全,防止數據錯亂
    SDL_cond *cond;
} PacketQueue;


typedef struct MyAVPacketList {
    AVPacket pkt;
    struct MyAVPacketList *next;
    int serial; //序列號與PacketQueue中的serial對應,作爲是否丟棄數據幀的依據
} MyAVPacketList;

二、操作函數

包含如圖所示的方法
PacketQueue方法

主要分析packet_queue_put和packet_queue_get

1、添加數據

由read_thread調用,從網絡或磁盤讀取數據並封裝爲AVPacket,加入PacketQueue

static int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{
    int ret;

    SDL_LockMutex(q->mutex);//操作前加鎖
    ret = packet_queue_put_private(q, pkt);//若添加pkt到隊列失敗,返回值<0。在packet_queue_put_private中將完成pkt掛載到鏈表上以及PacketQueue其他數據結構的填充
    SDL_UnlockMutex(q->mutex);//解鎖

    if (pkt != &flush_pkt && ret < 0)
        av_packet_unref(pkt);//添加數據失敗時,減小pkt的引用;ffmpeg內部使用引用計數機制,當AVPackt引用計數變爲0時,釋放關聯的相關數據

    return ret;
}

static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
{
    MyAVPacketList *pkt1;

    if (q->abort_request)//播放終止,返回-1。確保pkt內存釋放
       return -1;

    pkt1 = av_malloc(sizeof(MyAVPacketList));
    if (!pkt1)
        return -1;
    pkt1->pkt = *pkt;
    pkt1->next = NULL;
    if (pkt == &flush_pkt)
        q->serial++;//flush_pkt是全局變量,主要是標識作用。如果pkt是flush_pkt說明是新的一輪開始了,將PacktQueue的數列好+1。用於解碼線程判斷,若MyAVPacketList節點的serial小於當前隊列的serial,該幀數據將不再參與解碼,直接丟棄
    pkt1->serial = q->serial;//新添加的節點serial設置爲當前隊列的serial

    if (!q->last_pkt)
        q->first_pkt = pkt1;//若隊列爲空,添加到隊列頭部
    else
        q->last_pkt->next = pkt1;//否則將pkt添加到隊列的尾部
    q->last_pkt = pkt1;//更新隊尾指針
    q->nb_packets++;//隊列中元素的個數++
    q->size += pkt1->pkt.size + sizeof(*pkt1);//隊列持有數據的字節大小
    q->duration += pkt1->pkt.duration;
    /* XXX: should duplicate packet data in DV case */
    SDL_CondSignal(q->cond);//喚醒因隊列爲空,而阻塞的解碼線程
    return 0;
}

2、從隊列中獲取數據

/* return < 0 if aborted, 0 if no packet and > 0 if packet.  */

//1、參數block表示當對了爲空時函數如何處理,0表示不阻塞返回0,否則阻塞,直到被喚醒
//2、參數serial 獲取當前AVPacket的serial
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
{
    MyAVPacketList *pkt1;
    int ret;

    SDL_LockMutex(q->mutex);//獲取隊列的鎖

    for (;;) {
        if (q->abort_request) {
            ret = -1;
            break;
        }

        pkt1 = q->first_pkt;
        if (pkt1) {
            q->first_pkt = pkt1->next;
            if (!q->first_pkt)
                q->last_pkt = NULL;
            q->nb_packets--;
            q->size -= pkt1->pkt.size + sizeof(*pkt1);
            q->duration -= pkt1->pkt.duration;
            *pkt = pkt1->pkt;
            if (serial)
                *serial = pkt1->serial;
            av_free(pkt1);
            ret = 1;
            break;
        } else if (!block) {
            ret = 0;
            break;
        } else {
            SDL_CondWait(q->cond, q->mutex);
        }
    }
    SDL_UnlockMutex(q->mutex);//釋放隊列的鎖
    return ret;
}
       SDL_CondWait(q->cond, q->mutex);
    }
}
SDL_UnlockMutex(q->mutex);//釋放隊列的鎖
return ret;

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章