(音視訊學習筆記):FFmpeg記憶體模型及AVPacket、AVFrame常用API
阿新 • • 發佈:2021-01-19
技術標籤:音視訊開發學習筆記
- 【說明】課程學習地址:https://ke.qq.com/course/468797
目錄
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); }