1. 程式人生 > 其它 >(音視訊學習筆記):FFmpeg記憶體模型及AVPacket、AVFrame常用API

(音視訊學習筆記):FFmpeg記憶體模型及AVPacket、AVFrame常用API

技術標籤:音視訊開發學習筆記

目錄

FFmpeg記憶體模型

FFmpeg記憶體模型-引用計數

AVPacket常用API

AVFrame常用API

API應用簡介

FFmpeg記憶體模型

  • 從av_read_frame讀取到一個AVPacket後怎麼放入解碼器佇列?
  • 從avcodec_recevice_frame讀取到一個AVFrame後又怎麼放入解壓後的幀佇列?
  • 從現有的Packet拷貝一個新Packet的時候,有兩種情況
    • ①兩個Packet的buf引用的是同一資料快取空間,這時候要注意資料快取空間的釋放問題。
    • ②兩個Packet的buf引用不同的資料快取空間,每個Packet都有資料快取空間的copy。

  • 記憶體模型

  • 更為精確的模型

FFmpeg記憶體模型-引用計數

  • 對於多個AVPacket共享同一個快取空間, FFmpeg使用的引用計數的機制(reference-count) :
    • 初始化引用計數為0,只有真正分配AVBuffer的時候,引用計數初始化為1
    • 當有新的Packet引用共享的快取空間時, 就將引用計數+1
    • 釋放了引用共享空間的Packet,就將引用計數-1
    • 引用計數為0時,就釋放掉引用的快取空間AVBuffer
  • AVFrame也是採用同樣的機制。

AVPacket常用API

AVPacket *av_packet_alloc
(void);
分配AVPacket這個時候和buffer沒有關係
void av_packet_free(AVPacket **pkt);釋放AVPacket和_alloc對應
void av_init_packet(AVPacket *pkt);初始化AVPacket只是單純初始化pkt欄位
int av_new_packet(AVPacket *pkt, int size);給AVPacket的buf分配記憶體, 引用計數初始化為1
int av_packet_ref(AVPacket *dst, const AVPacket *src);增加引用計數
void av_packet_unref(AVPacket *pkt);
減少引用計數
void av_packet_move_ref(AVPacket *dst, AVPacket *src);轉移引用計數
AVPacket *av_packet_clone(const AVPacket *src);等於av_packet_alloc()+av_packet_ref(

AVFrame常用API

AVFrame *av_frame_alloc(void);分配AVFrame
void av_frame_free(AVFrame **frame);釋放AVFrame
int av_frame_ref(AVFrame *dst, const AVFrame *src);增加引用計數
void av_frame_unref(AVFrame *frame);減少引用計數
void av_frame_move_ref(AVFrame *dst, AVFrame *src);轉移引用計數
int av_frame_get_buffer(AVFrame *frame, int align);根據AVFrame分配記憶體
AVFrame *av_frame_clone(const AVFrame *src);等於av_frame_alloc()+av_frame_ref()

API應用簡介

  • av_packet_alloc 簡單的建立一個AVPacket,將其欄位設為預設值(data為空,沒有資料快取空間),data的指標需要另外去賦值
AVPacket *av_packet_alloc(void);
int av_new_packet(AVPacket *pkt, int size);
  • 釋放使用av_packet_alloc建立的AVPacket,如果該Packet有引用計數(packet->buf不為空),則先呼叫av_packet_unref(&packet)
    • 只有當引用計數為0時,才會在呼叫av_packet_free()時釋放data的快取。
void av_packet_free(AVPacket **pkt);
  • av_init_packet 初始化packet的值為預設值,該函式不會影響buffer引用的資料快取空間和size,需要單獨處理。
    • AVPacket中的buf為空。
    • 比如 av_get_packet裡呼叫av_init_packet
void av_init_packet(AVPacket *pkt);
  • av_packet_ref 使用引用計數的淺拷貝
    • 該函式會先拷貝所有非快取類資料,然後建立一個src->buf的新的引用計數。
    • 如果src已經設定了引用計數發(src->buf不為空),則直接將其引用計數+1。
    • 如果src沒有設定引用計數(src->buf為空),則為dst建立一個新的引用計數buf,並複製src->data到buf->buffer中。
    • 最後,複製src的其他欄位到dst中。所以av_packet_ref()是將2個AVPacket共用一個快取的。
int av_packet_ref(AVPacket *dst, const AVPacket *src);
  • av_packet_unref 使用引用計數清理資料
    • 將快取空間的引用計數-1,並將Packet中的其他欄位設為初始值。
    • 如果引用計數為0,自動的釋放快取空間
void av_packet_unref(AVPacket *pkt);
  • av_packet_move_ref把src整個結構體直接賦值給dst,所以引用計數沒有發生變化,並且src被av_init_packet重置
    • 其功能是 av_packet_alloc() + av_packet_ref();
void av_packet_move_ref(AVPacket *dst, AVPacket *src);
  • av_packet_clone 先建立一個新的AVPacket,然後再進行計數引用+資料拷貝,使得新的AVPacket指向老的AVPacket同一個data。
AVPacket *av_packet_clone(const AVPacket *src);
  • AVPacket案例:
/**
 * @brief 測試av_packet_alloc和av_packet_free的配對使用
 */
void av_packet_test1()
{
    AVPacket *pkt = NULL;
    int ret = 0;

    pkt = av_packet_alloc();
    ret = av_new_packet(pkt, MEM_ITEM_SIZE); // 引用計數初始化為1
    memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
    av_packet_unref(pkt);       // 要不要呼叫
    av_packet_free(&pkt);       // 如果不free將會發生記憶體洩漏,內部呼叫了 av_packet_unref
}

/**
 * @brief 測試誤用av_init_packet將會導致記憶體洩漏
 */
void av_packet_test2()
{
    AVPacket *pkt = NULL;
    int ret = 0;

    pkt = av_packet_alloc();
    ret = av_new_packet(pkt, MEM_ITEM_SIZE);
    memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
//    av_init_packet(pkt);        // 這個時候init就會導致記憶體無法釋放
    av_packet_free(&pkt);
}

/**
 * @brief 測試av_packet_move_ref後,可以av_init_packet
 */
void av_packet_test3()
{
    AVPacket *pkt = NULL;
    AVPacket *pkt2 = NULL;
    int ret = 0;

    pkt = av_packet_alloc();
    ret = av_new_packet(pkt, MEM_ITEM_SIZE);
    memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
    pkt2 = av_packet_alloc();   // 必須先alloc
    av_packet_move_ref(pkt2, pkt);//內部其實也呼叫了av_init_packet
    av_init_packet(pkt);
    av_packet_free(&pkt);
    av_packet_free(&pkt2);
}
/**
 * @brief 測試av_packet_clone
 */
void av_packet_test4()
{
    AVPacket *pkt = NULL;
    // av_packet_alloc()沒有必要,因為av_packet_clone內部有呼叫 av_packet_alloc
    AVPacket *pkt2 = NULL;
    int ret = 0;

    pkt = av_packet_alloc();
    ret = av_new_packet(pkt, MEM_ITEM_SIZE);
    memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
    pkt2 = av_packet_clone(pkt); // av_packet_alloc()+av_packet_ref()
    av_init_packet(pkt);
    av_packet_free(&pkt);
    av_packet_free(&pkt2);
}

/**
 * @brief 測試av_packet_ref
 */
void av_packet_test5()
{
    AVPacket *pkt = NULL;
    AVPacket *pkt2 = NULL;
    int ret = 0;

    pkt = av_packet_alloc(); //
    if(pkt->buf)        // 列印referenc-counted,必須保證傳入的是有效指標
    {    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
               av_buffer_get_ref_count(pkt->buf));
    }

    ret = av_new_packet(pkt, MEM_ITEM_SIZE);
    if(pkt->buf)        // 列印referenc-counted,必須保證傳入的是有效指標
    {    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
               av_buffer_get_ref_count(pkt->buf));
    }
    memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);

    pkt2 = av_packet_alloc();   // 必須先alloc
    av_packet_move_ref(pkt2, pkt); // av_packet_move_ref
//    av_init_packet(pkt);  //av_packet_move_ref

    av_packet_ref(pkt, pkt2);
    av_packet_ref(pkt, pkt2);     // 多次ref如果沒有對應多次unref將會記憶體洩漏
    if(pkt->buf)        // 列印referenc-counted,必須保證傳入的是有效指標
    {    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
               av_buffer_get_ref_count(pkt->buf));
    }
    if(pkt2->buf)        // 列印referenc-counted,必須保證傳入的是有效指標
    {    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
               av_buffer_get_ref_count(pkt2->buf));
    }
    av_packet_unref(pkt);   // 將為2
    av_packet_unref(pkt);   // 做第二次是沒有用的
    if(pkt->buf)
        printf("pkt->buf沒有被置NULL\n");
    else
        printf("pkt->buf已經被置NULL\n");
    if(pkt2->buf)        // 列印referenc-counted,必須保證傳入的是有效指標
    {    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
               av_buffer_get_ref_count(pkt2->buf));
    }
    av_packet_unref(pkt2);


    av_packet_free(&pkt);
    av_packet_free(&pkt2);
}

/**
 * @brief 測試AVPacket整個結構體賦值, 和av_packet_move_ref類似
 */
void av_packet_test6()
{
    AVPacket *pkt = NULL;
    AVPacket *pkt2 = NULL;
    int ret = 0;

    pkt = av_packet_alloc();
    ret = av_new_packet(pkt, MEM_ITEM_SIZE);
    memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);

    pkt2 = av_packet_alloc();   // 必須先alloc
    *pkt2 = *pkt;   // 有點類似  pkt可以重新分配記憶體
    av_init_packet(pkt);

    av_packet_free(&pkt);
    av_packet_free(&pkt2);
}
  • AVFrame案例測試
void av_frame_test()
{
    AVFrame *frame = NULL;
    int ret = 0;

    frame = av_frame_alloc();// 沒有類似的AVPacket的av_new_packet的API
    // 1024 *2 * (16/8) =
    frame->nb_samples     = 1024;
    frame->format         = AV_SAMPLE_FMT_S16;//AV_SAMPLE_FMT_S16P AV_SAMPLE_FMT_S16
    frame->channel_layout = AV_CH_LAYOUT_STEREO;    //AV_CH_LAYOUT_MONO AV_CH_LAYOUT_STEREO
    ret = av_frame_get_buffer(frame, 0);    // 根據格式分配記憶體
    if(frame->buf && frame->buf[0])
        printf("%s(%d) 1 frame->buf[0]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[0]->size);    //受frame->format等引數影響
    if(frame->buf && frame->buf[1])
        printf("%s(%d) 1 frame->buf[1]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[1]->size);    //受frame->format等引數影響

    if(frame->buf && frame->buf[0])        // 列印referenc-counted,必須保證傳入的是有效指標
        printf("%s(%d) ref_count1(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));

    ret = av_frame_make_writable(frame);    // 當frame本身為空時不能make writable
    printf("av_frame_make_writable ret = %d\n", ret);

    if(frame->buf && frame->buf[0])        // 列印referenc-counted,必須保證傳入的是有效指標
        printf("%s(%d) ref_count2(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));

    av_frame_unref(frame);
    if(frame->buf && frame->buf[0])        // 列印referenc-counted,必須保證傳入的是有效指標
        printf("%s(%d) ref_count3(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));

    av_frame_free(&frame);
}