內存分析(二) AVFrame

AVFrame結構體內有很多成員變量,我們肯定不可能都分析,只關心我們需要的,從實際應用場景出發,用到avframe只要有4個場景,1,init,2,decode,3 encode 4,free

從decode說起,decode涉及的函數是avcodec_decode_video2(),這個函數代碼較長,我就不粘了,其實我們關心的點很簡單,

它就做了2件事,先調用了av_frame_unref(picture); 然後decode, 填充picture

void av_frame_unref(AVFrame *frame)
{
    int i;

    if (!frame)
        return;

    wipe_side_data(frame);

    for (i = 0; i < FF_ARRAY_ELEMS(frame->buf); i++)
        av_buffer_unref(&frame->buf[i]);
    for (i = 0; i < frame->nb_extended_buf; i++)
        av_buffer_unref(&frame->extended_buf[i]);
    av_freep(&frame->extended_buf);
    av_dict_free(&frame->metadata);
    av_buffer_unref(&frame->qp_table_buf);

    get_frame_defaults(frame);
}

這裏確定了,我們需要關心的點是avframe的buf這個成員.

/**
     * AVBuffer references backing the data for this frame. If all elements of
     * this array are NULL, then this frame is not reference counted. This array
     * must be filled contiguously -- if buf[i] is non-NULL then buf[j] must
     * also be non-NULL for all j < i.
     *
     * There may be at most one AVBuffer per data plane, so for video this array
     * always contains all the references. For planar audio with more than
     * AV_NUM_DATA_POINTERS channels, there may be more buffers than can fit in
     * this array. Then the extra AVBufferRef pointers are stored in the
     * extended_buf array.
     */
AVBufferRef *buf[AV_NUM_DATA_POINTERS];

這個buf也是用來標記是否是ref的,注意這裏buf是一個數組,數組名buf本身不爲null,但是子元素值默認是null

av_frame_unref()函數就是針對frame的buf數組 逐個調用av_buffer_unref()。av_buffer_unref之前也講過了。就是引用計數變爲0,就釋放data,否則只釋放結構體自身內存。 注意,前提是buf[i] 不能是null.

基本都和avpacket裏面一樣。

AVFrame *av_frame_alloc(void)
{
    AVFrame *frame = av_mallocz(sizeof(*frame));

    if (!frame)
        return NULL;

    frame->extended_data = NULL;
    get_frame_defaults(frame);

    return frame;
}

void av_frame_free(AVFrame **frame)
{
    if (!frame || !*frame)
        return;

    av_frame_unref(*frame);
    av_freep(frame);

}

注意看av_frame_free(),多了一個av_freep(frame),這說明這個函數是帶了釋放avframe結構體自身內存的。(對比下avpacket的釋放函數)

再看初始化,buf默認是0,即初始化函數得到的是一個unref的frame 

我們知道傳給decode函數的frame是只需要初始化函數初始化就行。不需要設置寬高等。

但是encode函數傳入的frame需要額外設置寬高,格式,還要設置data,linesize.

一般有兩種設置方式。

AVFrame *enc_frame_v = av_frame_alloc();
enc_frame_v->width = 1280;
enc_frame_v->height = 720;
enc_frame_v->format = AV_PIX_FMT_YUV420P;
/**
*方式一
av_frame_get_buffer(enc_frame_v,1); 
*/

/**
*方式二
uint8_t *enc_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, 1280, 720));
avpicture_fill((AVPicture *)enc_frame_v, enc_buffer, AV_PIX_FMT_YUV420P, 1280, 720);

*/

設置方式的不同,直接關係到如何釋放frame,確保內存不泄露。很重要。

先看方式一,涉及到的函數是av_frame_get_buffer,

int av_frame_get_buffer(AVFrame *frame, int align)
{
    if (frame->format < 0)
        return AVERROR(EINVAL);

    if (frame->width > 0 && frame->height > 0)
        return get_video_buffer(frame, align);
    else if (frame->nb_samples > 0 && (frame->channel_layout || frame->channels > 0))
        return get_audio_buffer(frame, align);

    return AVERROR(EINVAL);
}

static int get_video_buffer(AVFrame *frame, int align)
{
    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
    int ret, i;

    if (!desc)
        return AVERROR(EINVAL);

    if ((ret = av_image_check_size(frame->width, frame->height, 0, NULL)) < 0)
        return ret;

    if (!frame->linesize[0]) {
        for(i=1; i<=align; i+=i) {
            ret = av_image_fill_linesizes(frame->linesize, frame->format,
                                          FFALIGN(frame->width, i));
            if (ret < 0)
                return ret;
            if (!(frame->linesize[0] & (align-1)))
                break;
        }

        for (i = 0; i < 4 && frame->linesize[i]; i++)
            frame->linesize[i] = FFALIGN(frame->linesize[i], align);
    }

    for (i = 0; i < 4 && frame->linesize[i]; i++) {
        int h = FFALIGN(frame->height, 32);
        if (i == 1 || i == 2)
            h = FF_CEIL_RSHIFT(h, desc->log2_chroma_h);

        frame->buf[i] = av_buffer_alloc(frame->linesize[i] * h + 16 + 16/*STRIDE_ALIGN*/ - 1);
        if (!frame->buf[i])
            goto fail;

        frame->data[i] = frame->buf[i]->data;
    }
    if (desc->flags & AV_PIX_FMT_FLAG_PAL || desc->flags & AV_PIX_FMT_FLAG_PSEUDOPAL) {
        av_buffer_unref(&frame->buf[1]);
        frame->buf[1] = av_buffer_alloc(1024);
        if (!frame->buf[1])
            goto fail;
        frame->data[1] = frame->buf[1]->data;
    }

    frame->extended_data = frame->data;

    return 0;
fail:
    av_frame_unref(frame);
    return AVERROR(ENOMEM);
}

從函數分析,可以看出,video使用av_frame_get_buffer,最後一個參數必須傳1,只有傳1,linesize纔會被初始化,buf子元素纔會被初始化,且frame->buf[i] 是分別的alloc的內存空間,說明這個av_frame_get_buffer函數得到的frame的data數組,內存不是連續的(假設pix_fmt=yuv420p)。我們還看到frame->data[i]=frame->buf[i]->data,這裏也看出了frame的data和buf的data之間的聯繫。

這說明什麼呢,說明了通過方式一,得到的是ref的frame,最後av_frame_free可以完美釋放frame和它的data。

但是我們還需要考量一點,就是frame進入decode函數後,data的內存空間地址沒有變。即decode會不會給它重新分配內存空間。

場景1: 傳入decode的frame是局部變量,初始化只有av_frame_alloc, 每次使用完,我們即調用av_frame_free, 從這個流程可以推斷出,decode肯定是會給frame分配空間的。

場景2:傳入decode的frame的是全局變量,那麼每次使用完,我們可以調用av_frame_free嗎,肯定不行,因爲前面提過,av_frame_free會把frame置爲NULL, 那麼問題來了,我們不釋放,複用frame,而decode是會給frame的data重新分配空間嗎。

經過debug方式測驗發現,是會重新分配的,即我們在decode函數調用前後分別打印frame->data[0]的地址。發現是不同的。

從decode函數源碼角度來解釋也好解釋,如果複用frame,每次進decode,之前講過,會先走unref函數,這個函數直接就把buf數組的data釋放了,所以重新分配內存空間肯定是地址會變的。也正是因爲decode裏面每次進來都先走unref函數,確保了frame使用全局的方式,不用擔心每次重新分配內存空間,而內存泄漏。我們也不用關心複用時要去釋放。

avcodec_decode_audio4規則也是一樣。

 

再回到方式二,用avpicture_fill方式,我們自己分配內存空間,填充frame的data和linesize

這種方式好處是保證了frame的data是內存連續的。但是avpicture_fill 並沒有填充buf[i] ,

也就是說av_frame_free並不能釋放data. 

該方式需要我們手動釋放data

uint8_t* p=frame->data[0];
av_free(p);
av_frame_free(&frame);

av_free()要和上文的av_malloc()對應,如果上文用到的是malloc(),這裏就用free()

 

最後是encode函數。avcodec_encode_video2()函數傳入的frame, 只是作爲數據源,不會被encode函數修改data,所以進去是什麼樣,出來還是什麼樣。avcodec_encode_audio2也是一樣。

下面分析audio的decode,同樣frame也是兩種方式。

AVFrame* enc_frame_a = av_frame_alloc();
enc_frame_a->nb_samples = 1024;
enc_frame_a->format = AV_SAMPLE_FMT_S16;
enc_frame_a->channels = 2;
enc_frame_a->channel_layout = av_get_default_channel_layout(2);
enc_frame_a->sample_rate =44100;
/**
*方式一
av_frame_get_buffer(enc_frame_a,1); 
*/

/**
*方式二
int nPcmLen = av_samples_get_buffer_size(NULL, 2, 1024, AV_SAMPLE_FMT_S16, 1);
uint8_t* adata = (uint8_t*)av_malloc(nPcmLen);
avcodec_fill_audio_frame(enc_Aframe,2, S16, (const uint8_t*)adata, nPcmLen, 1);


*/

av_frame_get_buffer此時調用的是get_audio_buffer(AVFrame *frame, int align) ,最後一個參數align標識是否使用內存對齊,

和video不同,audio這裏可以傳0,也可以傳1 ; 0是默認對齊,1表示不對齊。(對齊可以提高存取數據的性能,不對齊可以精準計算size)

其他規則,和video一樣。

avcodec_fill_audio_frame,規則也和video一樣。

 

 

 

 

發佈了38 篇原創文章 · 獲贊 29 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章