FFmpeg內存模型
從現有的Packet拷貝一個新Packet的時候,有兩種情況:
- 兩個Packet的buf引用的是同一數據緩存空間,這時候要注意數據緩存空間的釋放問題;
- 兩個Packet的buf引用不同的數據緩存空間,每個Packet都有數據緩存空間的copy;
關系介紹
- AVBuffer:核心數據容器,存儲實際數據(
data
),通過refcount
實現引用計數,free()
用于定義內存釋放邏輯。 - AVBufferRef:作為 AVBuffer 的引用,允許不同組件(如 AVPacket、AVFrame)共享同一份數據。它內部的
buffer
指向 AVBuffer,data
和size
本質是對 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;
}
代碼解析
-
創建 AVBuffer:通過
av_buffer_alloc
分配內存,返回 AVBufferRef。此時,AVBufferRef 的buffer
指向內部的 AVBuffer,data
指向實際內存。 -
關聯 AVPacket:使用
av_buffer_ref
讓 AVPacket 的buf
引用 AVBufferRef。此時,AVPacket 的data
與 AVBuffer 的data
指向同一塊內存。 -
引用計數:每次
av_buffer_ref
會增加引用計數(refcount
),av_buffer_unref
減少計數。當計數為 0 時,AVBuffer 自動釋放內存,避免手動管理內存的繁瑣與風險。 -
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
結構體的指針,初始化為NULL
。AVPacket
是 FFmpeg 里用于存儲壓縮媒體數據(像視頻幀、音頻幀等)的結構體。ret
:用于存儲函數調用的返回值,初始化為 0。
2. 分配 AVPacket
結構體
pkt = av_packet_alloc();
- 調用
av_packet_alloc
函數,在堆上分配一個新的AVPacket
結構體實例,并且把結構體的成員初始化為默認值。若分配成功,pkt
會指向新分配的AVPacket
;若失敗,pkt
為NULL
。
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
設置為NULL
,av_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
結構體的指針pkt
和pkt2
,并初始化為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;
pkt
和pkt2
是指向AVPacket
結構體的指針,初始化為NULL
。pkt
用于后續創建和操作一個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_ref
將pkt
的數據引用復制到新的AVPacket
上。因此,調用該函數后,pkt
和pkt2
共享同一份數據緩沖區,數據緩沖區的引用計數變為 2。
4. 故意破壞 pkt
的引用管理
av_init_packet(pkt); // 這里是故意去做init的操作,讓這個函數出現內存泄漏
av_init_packet(pkt)
:該函數會將pkt
的成員重置為默認值,其中包括將pkt->buf
置為NULL
。pkt->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
置為NULL
,av_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
指針 pkt
和 pkt2
并初始化為 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->buf
為NULL
,所以不會打印引用計數。
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. 多次引用 pkt
到 pkt2
并檢查引用計數
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)
使pkt
和pkt2
共享數據緩沖區,數據緩沖區的引用計數變為 3(初始 1 次 + 兩次引用各加 1)。 - 若
pkt->buf
和pkt2->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. 釋放 pkt
和 pkt2
av_packet_free(&pkt);
av_packet_free(&pkt2);
使用 av_packet_free
分別釋放 pkt
和 pkt2
所指向的 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
結構體的指針 pkt
和 pkt2
,并初始化為 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
的數據指針data
和AVBufferRef
指針buf
。此時pkt
和pkt2
指向同一個數據緩沖區,且數據緩沖區的引用計數沒有增加。
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. 釋放 pkt
和 pkt2
av_packet_free(&pkt);
av_packet_free(&pkt2);
av_packet_free(&pkt)
:由于之前av_init_packet(pkt)
已經將pkt->buf
置為NULL
,av_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
。與 AVPacket
的 av_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
中設置的參數(如 format
、channel_layout
和 nb_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