內存分析(一)AVPacket

先拋一個球,從一段代碼開始

AVPacket packet;
av_init_packet(&packet);
packet->data = NULL;
packet->size = 0;
while (av_read_frame(ifmt_ctx, &packet) >= 0) {
        av_free_packet(&packet);
}

這段代碼一般也是經常用到了。那麼問題來了,爲什麼packet已經釋放了,av_read_frame裏面還能繼續使用這個packet。不知道大家有沒有想過這個問題。

 

先講個故事,假設有一個avpacket,另一個人想拷貝,它怎麼辦呢,再開闢一個空間,然後把avpacket的data copy給他?這太浪費內存了,因爲data裏面存的是視頻解壓縮數據,數據量通常都很大的,幾千字節到上萬字節不等,我們完全沒必要做這麼愚蠢的copy,因爲data是一個指針,我們完全可以讓新的avpacket的data也指向src avpacket data指針指向的數據區就可以了。但是這又引入了新的問題,就是這麼多人都指向了數據區的內存,萬一有人想釋放data空間了,他可不知道誰還要繼續使用這塊內存。所以data數據空間變成了燙手山芋,沒人敢釋放了。所以爲了解決這個問題,ffmpeg引入了引用計數。專門找個人來計數,每個人想釋放的時候,這個計數值refcount就-1,如果增加了新的人引用它,refcount就+1,如果某個時刻refcount等於0 了,就表示現在可以安全釋放了。

下面具體看ffmpeg怎麼實現的,先看AVPacket相關的結構體,我只展示和我們主題有關的東西

AVPacket{
    /**
     * A reference to the reference-counted buffer where the packet data is
     * stored.
     * May be NULL, then the packet data is not reference-counted.
     */
	AVBufferRef *buf;
	uint8_t *data;
	int   size;
}

AVBufferRef {
    AVBuffer *buffer;
    uint8_t *data;
    int      size;
} 

AVBuffer {
    uint8_t *data; 
    int      size;
    volatile int refcount;
}

buf這段註釋是什麼意思呢。意思是說buf指向引用計數緩衝區,如果buf爲NULL,就說明這個avpacket是無引用計數的,說白了,就是現在只要他一個人在使用data指向的數據空間。換個角度,buf不爲NULL,就說明這個avpacket是ref的,有引用計數的

我們再看av_init_packet()幹了什麼

pts,dts等一系列字段全部給初始化,buf=NULL,data無分配空間。說明剛開始出來,還是unref的 avpacket

av_read_frame先不看,看av_free_packet()。

這裏我們關心的主要做了兩件事,第一件事是如果pkt->buf不爲NULL,會調用av_buffer_unref, 第二件事是就是把data置爲NULL, 這裏注意,packet本身沒釋放。所以這個函數是內存泄漏隱患的。肯定內存泄漏的是avpacket結構體本身空間未釋放,其次因爲這裏只是把data置空,所以data可能內存泄漏,因爲是存在一種情況buf爲NULL,但是data分配了空間。比如看下面的代碼

AVPacket *packet=(AVPacket*)av_malloc(sizeof(AVPacket));
av_init_packet(packet);
packet->data=(uint8_t*)malloc(128);
packet->size=128;
av_free_packet(packet);
av_freep(&packet);//釋放packet並將其置空

 

 

這段代碼的packet的data 調用av_free_packet()就不會被釋放。需要我們手動釋放。那麼什麼時候data釋放,什麼時候data不釋放呢.這個問題下面慢慢講。

繼續分析av_free_packet,如果pkt->buf不爲空,那麼執行av_buffer_unref

 

分析這個代碼前,先看下我上面列的AVPacket相關的結構體。文字描述就是,

avpacket裏面有一個buf,有一個data.

這個buf又有一個buffer,有一個data. 

這個buffer也有一個data.

我們注意到avpacket裏面有data,buf裏面有data,buffer裏面也有data,其實這三個data都是同一個data,

即avpacket->data=buf->data=buffer->data。每個結構體都存了一份data的地址,實際是方便釋放的時候,知道數據空間的首地址.

回過頭看av_buffer_unref();

首先要滿足buf不爲空,如果buf爲空,直接滾出,所以不存在還有釋放data這回事。所以上面那個內存泄漏就是這麼來的。

假設我們buf不爲空,繼續分析。

b=(*dst)->buffer.  這裏dst就是packet的buf,這句話就是先把buffer的地址暫存起來,

src爲NULL,所以走av_freep(dst);這句話就是釋放了packet的buf,注意啊,av_freep實際就是free+置空。所以這裏只是釋放結構體本身的內存空間,裏面的data堆空間是不會被處理的。

所以我們在釋放這個結構體之前,剛剛就是提前把他裏面的指針地址給取出來。

繼續分析,執行avpriv_ato...()函數,這裏就是將buffer的refcount減1,然後值返回,如果refcount變成了0,

通過b->free函數釋放data數據空間,然後釋放buffer

所以我們看到真正釋放data,不是直接free的avpacket裏面的那個data,而是曲曲折折free的是avpacket->buf->bufer->data

這個層級關係中,只有有誰是空的,data都不會被釋放。

總結:av_free_packet(packet) 不能釋放packet本身的內存空間,所以需要手動調用av_freep(&packet);

注意,av_freep需要傳指針的地址

其次,av_free_packet能夠自動釋放data,取決於需要同時滿足packet的ref不爲NULL且引用計數變爲0 

 

再說個有些人會搞混的,就是packet->data是否爲空,和data指向的堆空間是否被釋放,是完全兩碼事,不要搞錯了啊。

data本身是一個棧內存上的指針變量,data你不手動把它置爲NULL, 就算你free了data, data也不會變成NULL的。free的是它指向的那塊內存,對它自己是不會有影響的。

還有一個問題,就是你把data置爲NULL,不是說free它了。這個c和java不同,java裏面的虛擬機是根據內存引用計數來的,沒有引用了,垃圾回收器就回收了。但是c裏面,沒有垃圾回收器自動回收這麼一說。每塊堆內存都需要我們自己去管理釋放。

data沒有調用free(),就直接置爲NULL,那是妥妥的內存泄漏。free()的本質就是告訴系統,這塊內存我現在不用了,請標記爲未分配狀態。相當於free()是一個通知系統的操作。你不free(),直接置NULL, 在系統那,那塊堆內存就一直是分配狀態,無法得到回收。

 

av_read_frame代碼很複雜,我們採用debug方式觀察執行完av_read_frame以後,buf是不是爲NULL,

結論是不爲空,說明av_read_frame會生產一個ref的avpacket, (且內部調用了av_init_packet)。

此時refcount=1,所以free的時候,滿足refcount-1=0,能夠釋放掉data空間。

下面繼續分析增加一個data引用,內存中會發生什麼事。

涉及到的函數是av_packet_ref()

...
while (av_read_frame(format_context, &packet) >= 0) {
    AVPacket dst;
    av_packet_ref(&dst, &packet);
    av_free_packet(&packet);
}

av_packet_copy_props,把一些pts,dts等字段進行copy

 判斷src是不是ref的,如果src不是ref的,先給dst的buf分配內存空間,然後把src的data數據拷貝給它。

注意,此時dst的data和src的data是兩塊內存。

如果src是ref的,執行av_buffer_ref

給dst的buf分配內存空間(即這裏的ret),並且把src的buf的值賦給它。

這裏*ret=*buf,是關鍵。

一個指針p1把值賦給另一個指針p2,如果賦值內容是指針,就copy指針地址,如果是基礎數據類型,比如int,字符串類型,就copy值。

此時buf中的內容是AVBuffer,data,size. 前兩個都是指針。所以是copy指針地址,即av_buffer_ref實際上兩者共享同一個data堆空間。且只要buf釋放自己的data,則ret的data也會被釋放。但是buf改變自己的size的值,不會影響ret的size的值。(string類型或者int類型的數據不會受影響)

繼續分析代碼,執行到avpriv_atomic_int_add_and_fetch ,這裏是把buf->buffer->refcount++了, 因爲refcount屬於buffer指針的,和dst是共享的一份,所以等同於dst的refcount++了。

這樣就能保證,每個人在操作引用計數的時候,都操作的是同一個。

和av_packet_ref對應的函數,是av_packet_unref

這裏av_buffer_unref 也都已經分析過。

 

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