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
引用的是同一資料快取空間。這時需要注意的是資料快取空間的釋放問題,(淺複製)
(2)兩個
Packet
的
buf
引用不同的資料快取空間。每個
Packet
都有資料快取空間的copy(深複製)
我們主要是基於第一種方式進行介紹。
對於多個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
;
這裡的兩個核心物件——
AVBuffer
和
AVBufferRef
。
VBuffer
表示資料緩衝區本身;它是不透明的(非常類似與
private
),不應被訪問或由呼叫方(
AVPacket
/
AVFrame)
直接呼叫,只能透過
AVBufferRef
訪問。
但是,呼叫者可以透過比較兩個
AVBuffer
指標,檢查兩個不同引用是否指向同一資料緩衝區。
AVBufferRef
表示單個對
AVBuffer
的引用,它是一個可以由呼叫者直接呼叫的物件。
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
);
}
透過除錯可以發現, 在新建
packet
的時候
buf
是空的,只有當計數器置為
1
的時候才會給
buf
分配記憶體。當執行
av_packet_unref
後
pkt
的
buf
就會置空,此時計數器 -1。
此外。透過檢視原始碼可以發現其實
av_packet_free
內部已經呼叫了
av_packet_unref
,所以程式中第 28 行可以不呼叫,但是如果重複呼叫
av_packet_unref
也並不會出問題,
av_packet_unref
內部的
av_buffer_unref
函式中對
buf
進行了判斷,如果
buf
已經為空就會直接
return
回去。
示例 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
,則此處會崩潰)。
示例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
);
}
執行結果:
自己也在學習階段,以上僅是自己的理解和總結,有不對的地方歡迎指正