【音視頻】FFmpeg內存模型

FFmpeg內存模型

從現有的Packet拷貝一個新Packet的時候,有兩種情況:

  • 兩個Packet的buf引用的是同一數據緩存空間,這時候要注意數據緩存空間的釋放問題;
  • 兩個Packet的buf引用不同的數據緩存空間,每個Packet都有數據緩存空間的copy;
    在這里插入圖片描述

在這里插入圖片描述

關系介紹

  • AVBuffer:核心數據容器,存儲實際數據(data),通過 refcount 實現引用計數,free() 用于定義內存釋放邏輯。
  • AVBufferRef:作為 AVBuffer 的引用,允許不同組件(如 AVPacket、AVFrame)共享同一份數據。它內部的 buffer 指向 AVBuffer,datasize 本質是對 AVBuffer 對應成員的“轉發”。
  • AVPacket/AVFrame:通過 AVBufferRef 管理數據。例如,AVPacket 的 buf 成員、AVFrame 的 buf 數組,都依賴 AVBufferRef 實現數據共享與內存管理。

代碼示例

以下代碼演示 AVBuffer、AVBufferRef、AVPacket 的關聯邏輯:

#include <libavutil/buffer.h>
#include <libavcodec/avcodec.h>int main() {// 1. 創建 AVBuffer(分配 1024 字節內存)AVBufferRef *buffer_ref = av_buffer_alloc(1024);if (!buffer_ref) {return -1;}AVBuffer *buffer = buffer_ref->buffer;  // 獲取關聯的 AVBufferuint8_t *data = buffer->data;           // AVBuffer 的數據指針// 2. 創建 AVPacket,并關聯 AVBufferRefAVPacket *pkt = av_packet_alloc();if (!pkt) {av_buffer_unref(&buffer_ref);return -1;}pkt->buf = av_buffer_ref(buffer_ref);  // AVPacket 引用 AVBufferRef// 此時,AVPacket 的數據指針 pkt->data 等同于 buffer->data(即同一份數據)// 3. 驗證引用計數printf("Initial refcount: %u\n", buffer->refcount);  // 輸出 2(AVBufferRef 自身 + AVPacket 引用)// 4. 釋放資源av_packet_free(&pkt);         // 減少 AVPacket 對 AVBufferRef 的引用av_buffer_unref(&buffer_ref);  // 若引用計數歸 0,AVBuffer 內存自動釋放return 0;
}

代碼解析

  1. 創建 AVBuffer:通過 av_buffer_alloc 分配內存,返回 AVBufferRef。此時,AVBufferRef 的 buffer 指向內部的 AVBuffer,data 指向實際內存。

  2. 關聯 AVPacket:使用 av_buffer_ref 讓 AVPacket 的 buf 引用 AVBufferRef。此時,AVPacket 的 data 與 AVBuffer 的 data 指向同一塊內存。

  3. 引用計數:每次 av_buffer_ref 會增加引用計數(refcount),av_buffer_unref 減少計數。當計數為 0 時,AVBuffer 自動釋放內存,避免手動管理內存的繁瑣與風險。

  4. AVFrame 同理:AVFrame 的 buf 數組使用類似邏輯,例如:

    AVFrame *frame = av_frame_alloc();
    frame->buf[0] = av_buffer_ref(buffer_ref);  // AVFrame 引用 AVBufferRef
    

通過這種設計,FFmpeg 實現了高效的內存共享與自動釋放,減少內存泄漏風險。

一個 AVBuffer 結構體通常存儲 一個分量 的數據,而非完整的 YUV 所有分量。以常見的平面格式(如 YUV420P)為例:

  • Y 分量:單獨由一個 AVBuffer 存儲,對應 AVFrame->buf[0] 關聯的 AVBufferRef 指向的 AVBuffer
  • U 分量:由另一個 AVBuffer 存儲,對應 AVFrame->buf[1] 關聯的 AVBufferRef 指向的 AVBuffer
  • V 分量:再由一個 AVBuffer 存儲,對應 AVFrame->buf[2] 關聯的 AVBufferRef 指向的 AVBuffer

這種設計下,每個 AVBuffer 負責管理單一數據平面(分量)的內存,通過 AVFrame->buf 數組中的多個 AVBufferRef,實現對 YUV 各分量的獨立內存管理(如分配、引用計數、釋放等)。若為打包格式(如 YUV420P 非平面形式),雖數據存儲方式不同,但 AVBuffer 仍遵循“單一內存塊管理”原則,不會同時存儲多個獨立分量的完整數據。

更為精確的模型

在這里插入圖片描述

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()

測試代碼

宏定義

#define MEM_ITEM_SIZE (20*1024*102)
#define AVPACKET_LOOP_COUNT 1000

測試1

void av_packet_test1()
{AVPacket *pkt = NULL;int ret = 0;pkt = av_packet_alloc();ret = av_new_packet(pkt, MEM_ITEM_SIZE); // 引用計數初始化為1memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);av_packet_unref(pkt);       // 要不要調用av_packet_free(&pkt);       // 如果不free將會發生內存泄漏,內部調用了 av_packet_unref
}

下面將對 av_packet_test1 函數的代碼進行詳細剖析:

代碼功能概述

av_packet_test1 函數的主要功能是創建一個 AVPacket 對象,為其分配數據緩沖區,嘗試向該緩沖區復制數據,之后釋放 AVPacket 及其關聯的數據緩沖區,以此避免內存泄漏。

代碼逐行分析

1. 變量聲明
AVPacket *pkt = NULL;
int ret = 0;
  • pkt:這是一個指向 AVPacket 結構體的指針,初始化為 NULLAVPacket 是 FFmpeg 里用于存儲壓縮媒體數據(像視頻幀、音頻幀等)的結構體。
  • ret:用于存儲函數調用的返回值,初始化為 0。
2. 分配 AVPacket 結構體
pkt = av_packet_alloc();
  • 調用 av_packet_alloc 函數,在堆上分配一個新的 AVPacket 結構體實例,并且把結構體的成員初始化為默認值。若分配成功,pkt 會指向新分配的 AVPacket;若失敗,pktNULL
3. 為 AVPacket 分配數據緩沖區
ret = av_new_packet(pkt, MEM_ITEM_SIZE); // 引用計數初始化為1
  • av_new_packet 函數的作用是為 AVPacket 分配一個大小為 MEM_ITEM_SIZE 字節的數據緩沖區。
  • 若分配成功,返回值 ret 為 0,同時 AVPacket 關聯的數據緩沖區的引用計數會初始化為 1。
  • 若分配失敗,ret 會是一個負的錯誤碼。
4. 復制數據到 AVPacket 的數據緩沖區
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
  • memccpy 是 C 標準庫中的函數,用于在內存之間復制數據。
  • pkt->data 指向 AVPacket 的數據緩沖區,也就是目標內存區域。
  • (void *)&av_packet_test1 是源內存區域的地址,av_packet_test1 可能是一個自定義的變量或結構體。
  • 1 是要查找的字符,當在源數據中碰到這個字符時,復制操作會停止。
  • MEM_ITEM_SIZE 是最多要復制的字節數。
5. 減少 AVPacket 引用計數
av_packet_unref(pkt);       // 要不要調用
  • av_packet_unref 函數會減少 AVPacket 關聯的數據緩沖區的引用計數。當引用計數降為 0 時,會釋放實際的數據緩沖區內存,同時把 AVPacket 結構體的成員重置為默認值。
  • 在此處,調用 av_packet_unref 并非必需,因為 av_packet_free 函數內部會自動調用 av_packet_unref。不過,調用它也不會有問題,只是會多執行一次減少引用計數的操作。
6. 釋放 AVPacket 結構體
av_packet_free(&pkt);       // 如果不free將會發生內存泄漏,內部調用了 av_packet_unref
  • av_packet_free 函數會釋放 AVPacket 結構體所占用的內存。在釋放之前,它會自動調用 av_packet_unref 處理 AVPacket 關聯的數據緩沖區,確保數據緩沖區的引用計數被正確處理,避免內存泄漏。
  • 該函數接收一個指向 AVPacket 指針的指針作為參數,釋放后會把傳入的指針置為 NULL,防止出現懸空指針。

測試2

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);printf("size1 = %p",pkt->buf);av_init_packet(pkt);        // 這個時候init就會導致內存無法釋放printf("size2 = %p",pkt->buf);av_packet_free(&pkt);
}

代碼功能概述

該函數的主要目的是創建一個 AVPacket 對象,為其分配數據緩沖區,向緩沖區復制數據,然后釋放 AVPacket 及其關聯的數據緩沖區。

代碼逐行分析

1. 變量聲明與 AVPacket 分配
AVPacket *pkt = NULL;
int ret = 0;pkt = av_packet_alloc();
  • 聲明一個指向 AVPacket 的指針 pkt 并初始化為 NULL,同時聲明一個用于存儲函數返回值的變量 ret
  • 調用 av_packet_alloc 函數在堆上分配一個新的 AVPacket 結構體實例,并將其地址賦給 pkt
2. 為 AVPacket 分配數據緩沖區
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
  • av_new_packet 函數為 AVPacket 分配一個大小為 MEM_ITEM_SIZE 字節的數據緩沖區。
  • 若分配成功,pkt->data 指向新分配的緩沖區,同時數據緩沖區的引用計數初始化為 1。
3. 復制數據到 AVPacket 的數據緩沖區
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
  • 使用 memccpy 函數將 av_packet_test1 指向的數據復制到 pkt->data 所指向的緩沖區中,最多復制 MEM_ITEM_SIZE 個字節,當遇到值為 1 的字符時停止復制。
4. 調用 av_init_packet(pkt)
av_init_packet(pkt);
  • 這一步是問題所在。av_init_packet 是一個已被棄用的函數,其作用是將 AVPacket 結構體的成員初始化為默認值。
  • 調用 av_init_packet 會將 pkt->buf 設置為 NULL,同時還會重置其他一些成員。
  • 由于 pkt->buf 被設置為 NULL,后續調用 av_packet_free 時,av_packet_unref 無法正確識別之前分配的數據緩沖區,從而導致數據緩沖區的引用計數無法正確處理,最終造成內存泄漏。
5. 釋放 AVPacket
av_packet_free(&pkt);
  • av_packet_free 函數會先調用 av_packet_unref 來處理 AVPacket 關聯的數據緩沖區,然后釋放 AVPacket 結構體本身的內存。
  • 但由于 av_init_packet 已經將 pkt->buf 設置為 NULLav_packet_unref 無法正確釋放數據緩沖區,只釋放了 AVPacket 結構體本身的內存。

測試3

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();   // 必須先allocav_packet_move_ref(pkt2, pkt);//內部其實也調用了av_init_packetav_init_packet(pkt);av_packet_free(&pkt);av_packet_free(&pkt2);
}

代碼逐行分析

1. 變量聲明
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;
  • 聲明了兩個指向 AVPacket 結構體的指針 pktpkt2,并初始化為 NULL
  • 聲明一個整型變量 ret,用于存儲函數調用的返回值。
2. 分配并初始化第一個 AVPacket
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
  • av_packet_alloc():分配一個新的 AVPacket 結構體,并將其地址賦給 pkt
  • av_new_packet(pkt, MEM_ITEM_SIZE):為 pkt 分配一個大小為 MEM_ITEM_SIZE 字節的數據緩沖區。如果分配成功,pkt->data 指向新分配的緩沖區,同時數據緩沖區的引用計數初始化為 1。返回值存儲在 ret 中,若返回值小于 0 則表示分配失敗。
  • memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE):將 av_packet_test1 指向的數據復制到 pkt->data 所指向的緩沖區中,最多復制 MEM_ITEM_SIZE 個字節,當遇到值為 1 的字符時停止復制。
3. 分配第二個 AVPacket
pkt2 = av_packet_alloc();   // 必須先alloc

使用 av_packet_alloc() 分配一個新的 AVPacket 結構體,并將其地址賦給 pkt2。在進行數據引用轉移之前,必須先為 pkt2 分配內存。

4. 轉移數據引用
av_packet_move_ref(pkt2, pkt); // 內部其實也調用了av_init_packet

av_packet_move_ref(pkt2, pkt) 函數將 pkt 的數據引用轉移到 pkt2 上。調用該函數后,pkt 不再擁有數據緩沖區的引用,其成員會被重置為默認值,而 pkt2 接管數據緩沖區的引用。

5. 再次調用 av_init_packet
av_init_packet(pkt);

這一步是多余且可能會帶來問題的操作。因為 av_packet_move_ref 已經將 pkt 的成員重置為默認值,再次調用 av_init_packet(pkt) 并不會有額外的作用。并且如果 av_packet_move_ref 出現異常,這一步操作可能會進一步破壞 pkt 的狀態。

6. 釋放 AVPacket
av_packet_free(&pkt);
av_packet_free(&pkt2);
  • av_packet_free(&pkt):釋放 pkt 所指向的 AVPacket 結構體。由于之前 av_packet_move_ref 已經將 pkt 的數據引用轉移走,此時 pkt 不持有數據緩沖區的引用,所以只會釋放 AVPacket 結構體本身的內存。
  • av_packet_free(&pkt2):釋放 pkt2 所指向的 AVPacket 結構體及其關聯的數據緩沖區。因為 pkt2 持有數據緩沖區的引用,調用 av_packet_free 會先調用 av_packet_unref 減少數據緩沖區的引用計數,當引用計數降為 0 時釋放數據緩沖區的內存,然后釋放 AVPacket 結構體本身的內存。

測試4

av_packet_test4 函數是一個用于測試 AVPacket 內存管理和引用計數機制的函數,其目的是展示由于不當使用 av_init_packet 而導致的內存泄漏問題。下面我們對該函數進行詳細的逐行分析。

1. 變量聲明

AVPacket *pkt = NULL;
// av_packet_alloc()沒有必要,因為av_packet_clone內部有調用 av_packet_alloc
AVPacket *pkt2 = NULL;
int ret = 0;
  • pktpkt2 是指向 AVPacket 結構體的指針,初始化為 NULLpkt 用于后續創建和操作一個 AVPacket 對象,pkt2 則用于克隆 pkt
  • ret 是一個整型變量,用于存儲函數調用的返回值,方便后續判斷操作是否成功。

2. 創建并初始化第一個 AVPacket

pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
  • av_packet_alloc():動態分配一個新的 AVPacket 結構體,并將其地址賦給 pkt
  • av_new_packet(pkt, MEM_ITEM_SIZE):為 pkt 分配一個大小為 MEM_ITEM_SIZE 字節的數據緩沖區。如果分配成功,pkt->data 指向該緩沖區,同時數據緩沖區的引用計數初始化為 1。ret 存儲該函數的返回值,若小于 0 則表示分配失敗。
  • memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE):將 av_packet_test1 指向的數據復制到 pkt->data 所指向的緩沖區中,最多復制 MEM_ITEM_SIZE 個字節,當遇到值為 1 的字符時停止復制。

3. 克隆 AVPacket

pkt2 = av_packet_clone(pkt); // av_packet_alloc()+av_packet_ref(), 調用該函數后,pkt和pkt2對應的buf引用計數變成2
  • av_packet_clone(pkt):該函數相當于先調用 av_packet_alloc 分配一個新的 AVPacket 結構體,再調用 av_packet_refpkt 的數據引用復制到新的 AVPacket 上。因此,調用該函數后,pktpkt2 共享同一份數據緩沖區,數據緩沖區的引用計數變為 2。

4. 故意破壞 pkt 的引用管理

av_init_packet(pkt);  // 這里是故意去做init的操作,讓這個函數出現內存泄漏
  • av_init_packet(pkt):該函數會將 pkt 的成員重置為默認值,其中包括將 pkt->buf 置為 NULLpkt->buf 是一個指向 AVBufferRef 的指針,用于管理數據緩沖區的引用計數。將其置為 NULL 會破壞 pkt 與數據緩沖區的引用關系,導致后續無法正確處理引用計數。

5. 釋放 pkt

av_packet_free(&pkt);   // pkt在調用av_init_packet后,對應的buf被置為NULL,在調用av_packet_free沒法做引用計數-1的操作
  • av_packet_free(&pkt):該函數會先調用 av_packet_unref 來減少 pkt 關聯的數據緩沖區的引用計數,然后釋放 pkt 結構體本身的內存。但由于之前 av_init_packet(pkt) 已經將 pkt->buf 置為 NULLav_packet_unref 無法找到對應的 AVBufferRef,也就無法減少數據緩沖區的引用計數。因此,數據緩沖區的引用計數仍然為 2。

6. 釋放 pkt2

av_packet_free(&pkt2); // 觸發引用計數變為1,但因為不是0,所以buf不會被釋放,導致內存泄漏
  • av_packet_free(&pkt2):同樣,該函數會先調用 av_packet_unref 來減少 pkt2 關聯的數據緩沖區的引用計數,然后釋放 pkt2 結構體本身的內存。由于 pkt 之前沒有正確減少引用計數,此時調用 av_packet_unref 只會將數據緩沖區的引用計數減為 1,而不是 0。因此,數據緩沖區的內存不會被釋放,從而導致內存泄漏。

測試5

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();   // 必須先allocav_packet_move_ref(pkt2, pkt); // av_packet_move_ref
//    av_init_packet(pkt);  //av_packet_move_refav_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);   // 將為2av_packet_unref(pkt);   // 做第二次是沒有用的if(pkt->buf)printf("pkt->buf沒有被置NULL\n");elseprintf("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);
}

代碼功能概述

該函數主要用于測試 FFmpeg 中 AVPacket 的內存分配、數據操作、引用轉移、引用計數管理以及釋放等操作,同時通過打印引用計數來觀察這些操作對引用計數的影響,以此展示引用計數管理不當可能導致的問題。

代碼逐行分析

1. 變量聲明
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;

聲明兩個 AVPacket 指針 pktpkt2 并初始化為 NULL,同時聲明一個整型變量 ret 用于存儲函數調用的返回值。

2. 分配 pkt 并檢查引用計數
pkt = av_packet_alloc(); 
if(pkt->buf)        
{    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}
  • av_packet_alloc() 分配一個新的 AVPacket 結構體給 pkt
  • 由于此時 pkt 還未分配數據緩沖區,pkt->bufNULL,所以不會打印引用計數。
3. 為 pkt 分配數據緩沖區并檢查引用計數
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
if(pkt->buf)        
{    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}
  • av_new_packet(pkt, MEM_ITEM_SIZE)pkt 分配一個大小為 MEM_ITEM_SIZE 的數據緩沖區,分配成功后 pkt->buf 指向該緩沖區,引用計數初始化為 1。
  • pkt->buf 不為 NULL,則打印當前 pkt 的引用計數。
4. 復制數據到 pkt 的數據緩沖區
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);

av_packet_test1 指向的數據復制到 pkt->data 所指向的緩沖區中,最多復制 MEM_ITEM_SIZE 個字節,遇到值為 1 的字符時停止復制。

5. 分配 pkt2 并轉移引用
pkt2 = av_packet_alloc();   
av_packet_move_ref(pkt2, pkt); 
  • av_packet_alloc() 分配一個新的 AVPacket 結構體給 pkt2
  • av_packet_move_ref(pkt2, pkt)pkt 的數據引用轉移到 pkt2 上,之后 pkt 不再持有數據引用,其成員被重置,pkt->buf 變為 NULL
6. 多次引用 pktpkt2 并檢查引用計數
av_packet_ref(pkt, pkt2);
av_packet_ref(pkt, pkt2);     
if(pkt->buf)        
{    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}
if(pkt2->buf)        
{    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));
}
  • 兩次調用 av_packet_ref(pkt, pkt2) 使 pktpkt2 共享數據緩沖區,數據緩沖區的引用計數變為 3(初始 1 次 + 兩次引用各加 1)。
  • pkt->bufpkt2->buf 不為 NULL,則分別打印它們的引用計數。
7. 兩次調用 av_packet_unref(pkt) 并檢查 pkt->buf
av_packet_unref(pkt);   
av_packet_unref(pkt);   
if(pkt->buf)printf("pkt->buf沒有被置NULL\n");
elseprintf("pkt->buf已經被置NULL\n");
  • 第一次調用 av_packet_unref(pkt) 時,引用計數減 1 變為 2。
  • 第二次調用 av_packet_unref(pkt) 時,由于第一次調用后 pkt->buf 已經被置為 NULL(當引用計數降為 0 時會發生),所以這次調用不會有實際效果。
  • 根據 pkt->buf 是否為 NULL 打印相應信息。
8. 檢查 pkt2 的引用計數并調用 av_packet_unref(pkt2)
if(pkt2->buf)        
{    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));
}
av_packet_unref(pkt2);
  • pkt2->buf 不為 NULL,則打印 pkt2 的引用計數,此時為 2。
  • 調用 av_packet_unref(pkt2) 使引用計數減 1 變為 1。
9. 釋放 pktpkt2
av_packet_free(&pkt);
av_packet_free(&pkt2);

使用 av_packet_free 分別釋放 pktpkt2 所指向的 AVPacket 結構體。

存在的問題

代碼存在內存泄漏問題。由于第二次調用 av_packet_unref(pkt) 無效,且只調用了一次 av_packet_unref(pkt2),數據緩沖區的引用計數最終仍為 1,沒有降為 0,導致數據緩沖區的內存無法被釋放。

測試6

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可以重新分配內存if(pkt->buf)        // 打印referenc-counted,必須保證傳入的是有效指針{    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));}av_init_packet(pkt);if(pkt2->buf)        // 打印referenc-counted,必須保證傳入的是有效指針{    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));}av_packet_free(&pkt);av_packet_free(&pkt2);
}

代碼功能概述

av_packet_test6 函數主要演示了 FFmpeg 中 AVPacket 的內存分配、數據復制、引用計數管理以及釋放等操作,同時通過打印引用計數來觀察不同操作對引用計數的影響

代碼逐行分析

1. 變量聲明
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;

聲明了兩個指向 AVPacket 結構體的指針 pktpkt2,并初始化為 NULL。同時聲明了一個整型變量 ret,用于存儲函數調用的返回值。

2. 分配并初始化 pkt
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
  • av_packet_alloc():分配一個新的 AVPacket 結構體,并將其地址賦給 pkt
  • av_new_packet(pkt, MEM_ITEM_SIZE):為 pkt 分配一個大小為 MEM_ITEM_SIZE 字節的數據緩沖區。如果分配成功,pkt->data 指向新分配的緩沖區,同時數據緩沖區的引用計數初始化為 1。返回值存儲在 ret 中,若返回值小于 0 則表示分配失敗。
  • memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE):將 av_packet_test1 指向的數據復制到 pkt->data 所指向的緩沖區中,最多復制 MEM_ITEM_SIZE 個字節,當遇到值為 1 的字符時停止復制。
3. 分配 pkt2 并復制 pkt 的內容
pkt2 = av_packet_alloc();   // 必須先alloc
*pkt2 = *pkt;   // 有點類似  pkt可以重新分配內存
  • av_packet_alloc():分配一個新的 AVPacket 結構體,并將其地址賦給 pkt2
  • *pkt2 = *pkt;:這行代碼直接將 pkt 的內容復制到 pkt2 中,包括 pkt 的數據指針 dataAVBufferRef 指針 buf。此時 pktpkt2 指向同一個數據緩沖區,且數據緩沖區的引用計數沒有增加。
4. 打印 pkt 的引用計數
if(pkt->buf)        // 打印referenc-counted,必須保證傳入的是有效指針
{    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}

如果 pkt->buf 不為 NULL,則打印 pkt 關聯的數據緩沖區的引用計數,此時引用計數仍為 1。

5. 調用 av_init_packet(pkt)
av_init_packet(pkt);

av_init_packet(pkt) 會將 pkt 的成員重置為默認值,其中包括將 pkt->buf 置為 NULL,這一步不影響引用計數

6. 打印 pkt2 的引用計數
if(pkt2->buf)        // 打印referenc-counted,必須保證傳入的是有效指針
{    printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));
}

如果 pkt2->buf 不為 NULL,則打印 pkt2 關聯的數據緩沖區的引用計數,此時引用計數仍為 1。

7. 釋放 pktpkt2
av_packet_free(&pkt);
av_packet_free(&pkt2);
  • av_packet_free(&pkt):由于之前 av_init_packet(pkt) 已經將 pkt->buf 置為 NULLav_packet_free 只會釋放 pkt 結構體本身的內存,不會對數據緩沖區的引用計數產生影響。
  • av_packet_free(&pkt2):釋放 pkt2 所指向的 AVPacket 結構體及其關聯的數據緩沖區。因為 pkt2 持有數據緩沖區的引用,調用 av_packet_free 會先調用 av_packet_unref 減少數據緩沖區的引用計數,因此這里的引用計數變成了0,會釋放數據緩沖區的內存,然后釋放 AVPacket 結構體本身的內存。

測試7

void av_frame_test1()
{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_S16frame->channel_layout = AV_CH_LAYOUT_STEREO;    //AV_CH_LAYOUT_MONO AV_CH_LAYOUT_STEREOret = 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 writableprintf("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);
}

1. 變量聲明

AVFrame *frame = NULL;
int ret = 0;

聲明一個指向 AVFrame 結構體的指針 frame 并初始化為 NULL,同時聲明一個整型變量 ret 用于存儲函數調用的返回值。

2. 分配 AVFrame 結構體

frame = av_frame_alloc();// 沒有類似的AVPacket的av_new_packet的API

調用 av_frame_alloc 函數在堆上分配一個新的 AVFrame 結構體,并將其地址賦給 frame。與 AVPacketav_new_packet 不同,av_frame_alloc 僅分配 AVFrame 結構體本身,不分配實際的數據緩沖區。

3. 設置 AVFrame 的參數

// 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
  • frame->nb_samples:設置音頻幀中的樣本數量為 1024。
  • frame->format:設置音頻樣本的格式為 AV_SAMPLE_FMT_S16,即 16 位有符號整數。
  • frame->channel_layout:設置音頻的聲道布局為立體聲(AV_CH_LAYOUT_STEREO)。

4. 為 AVFrame 分配數據緩沖區

ret = av_frame_get_buffer(frame, 0);    // 根據格式分配內存

調用 av_frame_get_buffer 函數根據 frame 中設置的參數(如 formatchannel_layoutnb_samples)為 AVFrame 分配數據緩沖區。如果分配成功,frame->buf 數組將指向分配的緩沖區。

5. 打印緩沖區大小

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等參數影響

如果 frame->buf[0]frame->buf[1] 不為 NULL,則打印它們的大小。緩沖區大小受 frame->format 等參數的影響。
因為AV_SAMPLE_FMT_S16格式只有一個通道,因此只有buf[0]有數據;如果是AV_SAMPLE_FMT_S16P有2個通道,即buf[0]buf[1]都有效

6. 打印初始引用計數

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]));

如果 frame->buf[0] 不為 NULL,則打印其引用計數。初始時,引用計數通常為 1。

7. 使 AVFrame 可寫

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

調用 av_frame_make_writable 函數確保 frame 是可寫的。如果 frame 已經是可寫的,則直接返回;否則,會復制一份數據并使 frame 指向新的可寫緩沖區,并且使引用計數減一。打印該函數的返回值,返回值為 0 表示成功。

8. 打印可寫操作后的引用計數

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]));

如果 frame->buf[0] 不為 NULL,則打印可寫操作后的引用計數。如果發生了數據復制,引用計數可能會改變。

9. 解除 AVFrame 的引用

av_frame_unref(frame);

調用 av_frame_unref 函數解除 frame 對其數據緩沖區的引用,減少數據緩沖區的引用計數。如果引用計數降為 0,則釋放數據緩沖區。

10. 打印解除引用后的引用計數

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]));

如果 frame->buf[0] 不為 NULL,則打印解除引用后的引用計數。通常情況下,引用計數應該為 0。

11. 釋放 AVFrame 結構體

av_frame_free(&frame);

調用 av_frame_free 函數釋放 frame 所指向的 AVFrame 結構體。

更多資料:https://github.com/0voice

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/902482.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/902482.shtml
英文地址,請注明出處:http://en.pswp.cn/news/902482.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

1.2軟考系統架構設計師:系統架構的定義與作用 - 練習題附答案及超詳細解析

系統架構定義與作用綜合知識單選題 題目覆蓋核心概念、發展歷程、設計原則、評估標準及易混淆點&#xff0c;附答案解析&#xff1a; 1. 系統架構的標準定義源自于以下哪個標準&#xff1f; A. ISO/IEC 9126 B. IEEE 1471-2000 C. TOGAF 9.2 D. ITIL v4 答案&#xff1a;B 簡…

go語言對http協議的支持

http&#xff1a;無狀態協議&#xff0c;是互聯網中使用http使用http實現計算機和計算機之間的請求和響應 使用純文本方式發送和接受協議數據&#xff0c;不需要借助專門工具進行分析就知道協議中的數據 服務器端的幾個概念 Request&#xff1a;用戶請求的信息&#xff0c;用…

iscsi服務端安裝及配置

1. 安裝targetcli軟件包 yum install -y targetcli 2. 啟動target服務 systemctl start target systemctl enable target 3. 配置防火墻 firewall-cmd --add-port"3260/tcp" 3. 準備一個物理分區&#xff08;或者邏輯分區&#xff09;…

解決 MongoDB 查詢中的 `InvalidMongoDbApiUsageException` 錯誤

您在使用 Spring Data MongoDB 時遇到了 InvalidMongoDbApiUsageException 異常&#xff0c;錯誤信息如下&#xff1a; “由于 com.mongodb.BasicDocument 的限制&#xff0c;您無法添加第二個 ‘null’ 條件。查詢已經包含 ‘{ “KaTeX parse error: Expected }, got EOF at e…

一個關于相對速度的假想的故事-4

回到公式&#xff0c; 正寫速度疊加和倒寫速度疊加的倒寫相等&#xff0c;這就是這個表達式所要表達的意思。但倒寫疊加用的是減法&#xff0c;而正寫疊加用的是加法。當然是這樣&#xff0c;因為正寫疊加要的是單位時間上完成更遠的距離&#xff0c;而倒寫疊加說的是單位距離需…

重學React(一):描述UI

背景&#xff1a;React現在已經更新到19了&#xff0c;文檔地址也做了全面的更新&#xff0c;上一次系統性的學習還是在16-17的大版本更新。所以&#xff0c;現在就開始重新學習吧&#xff5e; 學習內容&#xff1a; React官網教程&#xff1a;https://zh-hans.react.dev/lea…

AI大模型:(二)2.3 預訓練自己的模型

目錄 1.預訓練原理 2.預訓練范式 1.未標注數據 2.標注數據 3.有正確答案、也有錯誤答案 3.手撕transform模型 3.1.transform模型代碼 3.2.訓練數據集 3.3.預訓練 3.4.推理 4.如何選擇模型

gradle可用的下載地址(免費)

這幾天接手一個老項目&#xff0c;想找gradle老版本的&#xff0c;但一搜&#xff0c;雖然在CSDN上搜索出來一堆&#xff0c;但都是收費&#xff0c;有些甚至要幾十積分(吃相有點難看了)。 我找了一個能訪問的地址&#xff0c;特地分享出來&#xff0c;有需要的自取&#xff01…

vue3新增特性

一、Vue 3 新增特性 1. Composition API 概述: Composition API 提供了一種更靈活和強大的方式來組織和復用邏輯。適用于復雜組件和邏輯復用場景。主要功能: setup 函數:組件的入口點,用于定義響應式數據、方法、生命周期鉤子等。響應式 API:ref 和 reactive 提供更細粒度…

前端性能優化全攻略:JavaScript 優化、DOM 操作、內存管理、資源壓縮與合并、構建工具及性能監控

1 為什么需要性能優化&#xff1f; 1.1 性能優化的核心價值&#xff1a;用戶體驗與業務指標 性能優化不僅是技術層面的追求&#xff0c;更是直接影響用戶體驗和業務成敗的關鍵因素。 用戶體驗&#xff08;UX&#xff09;&#xff1a; 響應速度&#xff1a;用戶期望頁面加載時…

【Unity筆記】Unity + OpenXR項目無法啟動SteamVR的排查與解決全指南

圖片為AI生成 一、前言 隨著Unity在XR領域全面轉向OpenXR標準&#xff0c;越來越多的開發者選擇使用OpenXR來構建跨平臺的VR應用。但在項目實際部署中發現&#xff1a;打包成的EXE程序無法正常啟動SteamVR&#xff0c;或者SteamVR未能識別到該應用。本文將以“Unity OpenXR …

Curl用法解析

Curl 用法解析 簡介 Curl 是一個強大的命令行工具&#xff0c;主要用于從服務器發送 HTTP 請求并獲取數據。它廣泛應用于調試 RESTful API、文件上傳下載、模擬用戶交互等多種場景。下面是一些基本用法及常見參數的分析&#xff1a; 基礎用法 curl [options] [URL]其中最基…

C語言教程(十一):C 語言中四種主要作用域及作用域嵌套遮蔽

一、引言 在 C 語言里&#xff0c;作用域指的是程序中變量、函數、類型等標識符能夠被使用的范圍。C 語言里有四種主要的作用域&#xff1a;塊作用域、函數作用域、文件作用域和原型作用域&#xff0c;下面為你展開介紹&#xff1a; 二、塊作用域 定義&#xff1a;塊作用域是 C…

初次嘗試Ghidra

最近看京東讀書上有本書叫《Ghidra權威指南》&#xff0c;竟然是美國國家安全局出品的逆向工具&#xff0c;我真是孤陋寡聞&#xff0c;第一次聽說。趕緊試試。 Release Ghidra 11.3.2 NationalSecurityAgency/ghidra GitHub 最新版本竟然是上周發布的&#xff0c;看來很活…

樂視系列玩機---樂視2 x620 x628等系列線刷救磚以及刷寫第三方twrp 卡刷第三方固件步驟解析

樂視2 x620 x628 x626等,搭載了Helio X20處理器,mtk6797芯片。 通過博文了解?????? 1??????-----詳細解析樂視2 x620系列黑磚線刷救磚的步驟 2??????----官方兩種更新卡刷步驟以及刷寫第三方twrp過程與資源 3??????----樂視2 mtk系列機型救磚 刷…

web原生API AbortController網絡請求取消方法使用介紹:防止按鈕重復點擊提交得最佳方案

在前端開發中&#xff0c;取消網絡請求是一個常見的需求&#xff0c;尤其是在用戶頻繁操作或需要中斷長時間請求的場景下。 AbortController 主要用于 ?優雅地管理和取消異步操作&#xff1a; 瀏覽器原生 API 一、代碼解析 1. ?創建 AbortController 實例 const controlle…

2025智能駕駛趨勢評估

以下是對2025年智能駕駛趨勢的評估&#xff1a; 技術發展 ? 自動駕駛級別提升&#xff1a;2025年有望成為L3級自動駕駛的商用元年。L3級自動駕駛技術開始從高端車型向20萬元以下價格帶下沉&#xff0c;部分車企如江淮和華為合作的尊界S800、小鵬汽車等都在積極推進L3級自動駕駛…

Spring MVC DispatcherServlet 的作用是什么? 它在整個請求處理流程中扮演了什么角色?為什么它是核心?

DispatcherServlet 是 Spring MVC 框架的絕對核心和靈魂。它扮演著前端控制器&#xff08;Front Controller&#xff09;的角色&#xff0c;是所有進入 Spring MVC 應用程序的 HTTP 請求的統一入口點和中央調度樞紐。 一、 DispatcherServlet 的核心作用和職責&#xff1a; 請…

Linux 內核中 cgroup 子系統 cpuset 是什么?

cpuset 是 Linux 內核中 cgroup&#xff08;控制組&#xff09; 的一個子系統&#xff0c;用于將一組進程&#xff08;或任務&#xff09;綁定到特定的 CPU 核心和 內存節點&#xff08;NUMA 節點&#xff09;上運行。它通過限制進程的 CPU 和內存資源的使用范圍&#xff0c;優…

【MATLAB第115期】基于MATLAB的多元時間序列的ARIMAX的預測模型

【MATLAB第115期】基于MATLAB的多元時間序列的ARIMAX的預測模型 ?一、簡介 ARIMAX?&#xff08;Autoregressive Integrated Moving Average with eXogenous inputs&#xff09;模型是一種結合自回歸&#xff08;AR&#xff09;、差分&#xff08;I&#xff09;、移動平均&a…