您當前的位置:首頁 > 詩詞

FFmpeg記憶體模型與API介紹(notes 2)

作者:由 零K同學 發表于 詩詞時間:2021-02-04

FFmpeg記憶體模型與API介紹(notes 2)

從上圖中可以看出

AVPacket

AVFrame

是儲存音影片解碼前後資料的重要結構體,我們使用

av_read_frame

將解封裝後的資料存入

AVPacket

,將

avcodec_receive_frame()

函式將解碼後的資料存入

AVFrame

,這部分必定會涉及到記憶體的分配和釋放問題。在 FFMpeg 中,記憶體 IO 叫做

buffered IO

,是指將一塊記憶體緩衝區用作 FFmpeg 的輸入或者輸出,與記憶體 IO 操作對應的是指定

URL

作為 FFmpeg 的輸入或輸出。

假如現在需要將一個

Packet1

的資料複製到一個新的

Packet2

裡面的,可以有兩種方式:

(1)兩個

Packet

buf

引用的是同一資料快取空間。這時需要注意的是資料快取空間的釋放問題,(淺複製)

FFmpeg記憶體模型與API介紹(notes 2)

(2)兩個

Packet

buf

引用不同的資料快取空間。每個

Packet

都有資料快取空間的copy(深複製)

FFmpeg記憶體模型與API介紹(notes 2)

我們主要是基於第一種方式進行介紹。

對於多個AVPacket共享同一個快取空間,FFmpeg使用的引用計數的機制來管理。

AVBuffer

FFmpeg

中的緩衝區,一開始時

AVBuffer

的引用計數(

refcount

)初始化為 0

當有新的

Packet

引用共享的快取空間時,就將引用計數再 +1

Packet

釋放掉對

AVBuffer

這塊共享快取空間的引用時,將引用計數 -1

只有當

refcount

為 0 的時候,才會釋放掉快取空間

AVBuffer

AVBuffer 和 AVBufferRef

我們首先來看以下這兩個的原始碼:

(1)AVBuffer

struct

AVBuffer

{

uint8_t

*

data

/**< data described by this buffer */

int

size

/**< size of data in bytes */

/**

* number of existing AVBufferRef instances referring to this buffer

*/

atomic_uint

refcount

//引用此緩衝區的現有AVBufferRef例項的數目

/**

* a callback for freeing the data

*/

void

*

free

)(

void

*

opaque

uint8_t

*

data

);

/**

* an opaque pointer, to be used by the freeing callback

*/

void

*

opaque

//一個不透明的指標,由釋放回調函式使用

/**

* A combination of BUFFER_FLAG_*

*/

int

flags

};

(2)AVBufferRef

typedef

struct

AVBufferRef

{

AVBuffer

*

buffer

/**

* The data buffer。 It is considered writable if and only if

* this is the only reference to the buffer, in which case

* av_buffer_is_writable() returns 1。

*/

//資料緩衝區。當且僅當這是對緩衝區的唯一引用時,才認為它是可寫的,在這種情況下,av_buffer_is_writable()返回1。

uint8_t

*

data

/**

* Size of data in bytes。

*/

int

size

}

AVBufferRef

FFmpeg記憶體模型與API介紹(notes 2)

這裡的兩個核心物件——

AVBuffer

AVBufferRef

VBuffer

表示資料緩衝區本身;它是不透明的(非常類似與

private

),不應被訪問或由呼叫方(

AVPacket

/

AVFrame)

直接呼叫,只能透過

AVBufferRef

訪問。

但是,呼叫者可以透過比較兩個

AVBuffer

指標,檢查兩個不同引用是否指向同一資料緩衝區。

AVBufferRef

表示單個對

AVBuffer

的引用,它是一個可以由呼叫者直接呼叫的物件。

FFmpeg記憶體模型與API介紹(notes 2)

AVFrame也是採用同樣的機制。

AVPacket 常用 API

AVPacket *av_packet_alloc(void);

: 分配一個

AVPacket

,並將其欄位設定為預設值。透過這個API分類的Packet必須由

av_packet_free

釋放

void av_packet_free(AVPacket **pkt);

: 釋放掉

Packet

,如果這個

Packet

有引用的

AVBuffer

,將會先釋放引用。

void av_init_packet(AVPacket *pkt);

: 初始化

Packet

,注意,這並不涉及

data

size

成員,它們必須分別初始化。

int av_new_packet(AVPacket *pkt, int size);

: 給

AVPacket

分配記憶體,這裡引用計數將會+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);

: 將

src

中的每個欄位移動到

dst

,並重置

src

AVPacket *av_packet_clone(const AVPacket *src);

: 克隆一個與

src

相同資料的

Packet

,等於

av_packet_alloc()+av_packet_ref()

AVFrame 常用的 API

AVFrame *av_frame_alloc(void);

: 分配一個

AVFrame

,並將其欄位設定為預設值。透過這個API分類的Packet必須由

av_frame_free()

釋放

void av_frame_free(AVFrame **frame);

: 釋放掉

AVFrame

,如果這個

Frame

有引用的

AVBuffer

,將會先釋放引用。

int av_frame_ref(AVFrame *dst, const AVFrame *src)

: 增加引用計數

void av_frame_unref(AVFrame *frame);

: 減少引用計數

void av_frame_move_ref(AVFrame *dst, AVFrame *src);

: 將

src

中的每個欄位移動到

dst

,並重置

src

int av_frame_get_buffer(AVFrame *frame, int align);

: 為音訊或影片資料分配新的緩衝區。在呼叫這個欄位之前必須在音影片幀上設定下面的欄位

format (pixel[畫素] format for video, sample format[取樣格式]for audio)

width and height for video

nb_samples and channel_layout for audio

程式碼示例

示例 1

void

av_packet_test1

()

{

AVPacket

*

pkt

=

NULL

int

ret

=

0

//分配一個AVPacket

pkt

=

av_packet_alloc

();

//給AVPacket分配記憶體,這裡引用計數將會+1

// int av_new_packet(AVPacket *pkt, int size);

ret

=

av_new_packet

pkt

MEM_ITEM_SIZE

);

//始化欄位,還為data分配了儲存空間 如果成功就返回0

/*

void * memccpy(void *dest, const void * src, int c, size_t n);

函式說明:memccpy()用來複製src 所指的記憶體內容前n 個位元組到dest 所指的地址上。

* */

memccpy

pkt

->

data

,(

void

*

&

av_packet_test1

1

MEM_ITEM_SIZE

);

//減少引用計數,只有當引用計數為0時,才呼叫av_packet_free()時釋放data的快取。

av_packet_unref

pkt

);

//釋放掉packet,如果packet被還引用計數,它將首先被取消引用。

av_packet_free

&

pkt

);

}

FFmpeg記憶體模型與API介紹(notes 2)

FFmpeg記憶體模型與API介紹(notes 2)

透過除錯可以發現, 在新建

packet

的時候

buf

是空的,只有當計數器置為

1

的時候才會給

buf

分配記憶體。當執行

av_packet_unref

pkt

buf

就會置空,此時計數器 -1。

FFmpeg記憶體模型與API介紹(notes 2)

此外。透過檢視原始碼可以發現其實

av_packet_free

內部已經呼叫了

av_packet_unref

,所以程式中第 28 行可以不呼叫,但是如果重複呼叫

av_packet_unref

也並不會出問題,

av_packet_unref

內部的

av_buffer_unref

函式中對

buf

進行了判斷,如果

buf

已經為空就會直接

return

回去。

FFmpeg記憶體模型與API介紹(notes 2)

示例 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_test2

1

MEM_ITEM_SIZE

);

// av_init_packet(pkt); //這個時候init就會導致記憶體無法釋放

av_packet_free

&

pkt

);

}

如果

free

之前呼叫了

init

init

會把

pkt

buf

置空,

free

中也會呼叫

init

。(

void av_init_packet

僅僅是把pkt的引數設為預設值,要求

pkt

的記憶體已經分配好了,如果為

NULL

,則此處會崩潰)。

FFmpeg記憶體模型與API介紹(notes 2)

示例3

void

av_frame_test1

()

{

AVFrame

*

frame

=

NULL

int

ret

=

0

//分配一個AVFrame

frame

=

av_frame_alloc

();

/*在呼叫av_frame_get_buffer這個欄位之前必須在音影片幀上設定下面的欄位*/

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

//1024 * 2 * (16/8)

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

//AV_SAMPLE_FMT_S16P格式 buf[1]才不為空

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

);

}

執行結果:

FFmpeg記憶體模型與API介紹(notes 2)

自己也在學習階段,以上僅是自己的理解和總結,有不對的地方歡迎指正

標簽: AV  Frame  packet  buf  AVPacket