ffmpeg中AVPacket與AVFrame中數據的傳遞與釋放

總結了一下AVPacket與AVFrame中拷貝和釋放相關操作。這裏我理解在AVFrame與AVPacket傳遞流轉的過程中並不會去每次創建和拷貝音視頻數據,音視頻數據被存儲在AVBuffer中,而AVFrame與AVPacket在流轉時進行淺拷貝,只有調用其對應unref時,會減少AVBuffer中的引用計數,最終釋放內部存儲音視頻數據的buffer。

目錄

1.av_freep釋放並置空雙重指針指向的那個指針

2.av_packet_unref

3.av_packet_ref

4.av_packet_move_ref 

5.av_packet_alloc與av_packet_free

6.av_free

7.av_frame_unref

8.av_frame_ref

9.av_frame_move_ref

10.av_frame_alloc與av_frame_free

1.av_freep釋放並置空雙重指針指向的那個指針
釋放內存並將指針置空

void av_freep(void *arg)
{
    void *val;
    //記錄arg值
    memcpy(&val, arg, sizeof(val));
    //arg 指向的的首地址置空 
    memcpy(arg, &(void *){ NULL }, sizeof(val));
    //真正釋放內存
    av_free(val);
}
示例

static void buffer_replace(AVBufferRef **dst, AVBufferRef **src)
{
    …
    if (src) {
        **dst = **src;
        //釋放 AVBufferRef **src 中指針指向的內容並將這個指針置空
        //這裏並沒有釋放AVBufferRef中的指針指向的內存
        av_freep(src);
    } 
    …
}
2.av_packet_unref
釋放pkt中在堆上的指針結構,復位內部指針,pkt中真正的音視頻buffer沒有被釋放。結果相當於傳入的這個AVPacket指針被掏空,音視頻數據依然可能被其他AVPacket持有。真正釋放AVPacket中音視頻數據buffer的操作也應該是在這裏。

void av_packet_unref(AVPacket *pkt)
{
    //置空packet中side_data相關指針,釋放相關結構(不釋放結構中指針指向的內容)
    av_packet_free_side_data(pkt);
    //釋放pkt->buf結構,不釋放這個結構指向的內容
    //buf的refcount原子減,如果AVBuffer引用計數爲1則調用AVBuffer的free
    av_buffer_unref(&pkt->buf);
    //復位packet內部指針
    av_init_packet(pkt);
    pkt->data = NULL;
    pkt->size = 0;
}
示例:

ffplay中釋放AVPacket

static void packet_queue_flush(PacketQueue *q)
{
    MyAVPacketList *pkt, *pkt1;
    ...
    for (pkt = q->first_pkt; pkt; pkt = pkt1) {
        pkt1 = pkt->next;
        //減少引用計數,符合要求時刪除buffer
        av_packet_unref(&pkt->pkt);
        av_freep(&pkt);
    }
    ...
}
3.av_packet_ref
有了av_packet_unref的經驗,理解av_packet_ref會相對容易一些。這個函數最終的效果是給dst的data淺拷貝一個值,還會給當前buffer增加引用計數。AVBuffer結構中存儲了真的音視頻數據,並且維護一個引用計數。

struct AVBuffer {
    uint8_t *data; /**< data described by this buffer */
    int      size; /**< size of data in bytes */
    atomic_uint refcount;
    void (*free)(void *opaque, uint8_t *data);
    void *opaque;
    int flags;
};
下面看一下av_packet_ref的實現。

int av_packet_ref(AVPacket *dst, const AVPacket *src)
{
    int ret;
 
    //拷貝各種屬性值,創建side_data指針並將src中的值賦值給它
    ret = av_packet_copy_props(dst, src);
    if (ret < 0)
        return ret;
 
    if (!src->buf) {
        //如果src的buf爲空 des的buf創建一塊空間 把src的data拷貝給這塊空間
        //創建buf指針並拷貝數據內容
        ret = packet_alloc(&dst->buf, src->size);
        if (ret < 0)
            goto fail;
        if (src->size)
            memcpy(dst->buf->data, src->data, src->size);
        //數據淺拷貝給dst
        dst->data = dst->buf->data;
    } else {
        //否則 src的 buffer->refcount 原子+1
        dst->buf = av_buffer_ref(src->buf);
        if (!dst->buf) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        //數據淺拷貝給dst
        dst->data = src->data;
    }
 
    dst->size = src->size;
 
    return 0;
fail:
    av_packet_free_side_data(dst);
    return ret;
}
4.av_packet_move_ref 
淺拷貝一切src的一切數據,掏空src,操作不改變AVBuffer的引用計數。ffplay在AVPacket加入隊列的時候使用=給結構體賦值。

void av_packet_move_ref(AVPacket *dst, AVPacket *src)
{
    *dst = *src;
    av_init_packet(src);
    src->data = NULL;
    src->size = 0;
}
5.av_packet_alloc與av_packet_free
av_packet_free清空當前AVPacket結構體指針,並減少引用計數,代表當前這個AVPacket結構體被釋放了,裏面的音視頻數據可能還在,需要等待最後一個引用這些音視頻數據的AVPacket被銷燬。如果直接在棧上定義AVPaceket結構體實例,使用av_packet_move_ref或者av_packet_ref賦值,用過之後av_packet_unref一下即可。

AVPacket *av_packet_alloc(void)
{
    AVPacket *pkt = av_mallocz(sizeof(AVPacket));
    if (!pkt)
        return pkt;
 
    av_packet_unref(pkt);
 
    return pkt;
}
 
void av_packet_free(AVPacket **pkt)
{
    if (!pkt || !*pkt)
        return;
 
    av_packet_unref(*pkt);
    av_freep(pkt);
}
區別av_free_packet

這個方法目前看已經廢棄,ffplay裏也沒有搜到調用

#if FF_API_AVPACKET_OLD_API
FF_DISABLE_DEPRECATION_WARNINGS
void av_free_packet(AVPacket *pkt)
{
    if (pkt) {
        if (pkt->buf)
            av_buffer_unref(&pkt->buf);
        pkt->data            = NULL;
        pkt->size            = 0;
 
        av_packet_free_side_data(pkt);
    }
}
FF_ENABLE_DEPRECATION_WARNINGS
#endif
6.av_free
對應malloc

void av_free(void *ptr)
{
#if HAVE_ALIGNED_MALLOC
    _aligned_free(ptr);
#else
    free(ptr);
#endif
}
7.av_frame_unref
這裏減少了當前AVFrame中各種buffer的數據引用,同時將當前傳入的frame掏空。這樣看真正釋放AVFrame音視頻數據的地方也應該在這裏。

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);
#if FF_API_FRAME_QP
    av_buffer_unref(&frame->qp_table_buf);
#endif
 
    av_buffer_unref(&frame->hw_frames_ctx);
 
    av_buffer_unref(&frame->opaque_ref);
    //置空當前frame中的值
    get_frame_defaults(frame);
}
8.av_frame_ref
主要是src各種淺拷貝加buffer賦值。要想把一個現有的AVFrame複製給新建的,可以使用這個方法,相當於直接淺拷貝並增加引用計數了。

int av_frame_ref(AVFrame *dst, const AVFrame *src)
{
    int i, ret = 0;
 
    av_assert1(dst->width == 0 && dst->height == 0);
    av_assert1(dst->channels == 0);
 
    dst->format         = src->format;
    dst->width          = src->width;
    dst->height         = src->height;
    dst->channels       = src->channels;
    dst->channel_layout = src->channel_layout;
    dst->nb_samples     = src->nb_samples;
 
    ret = frame_copy_props(dst, src, 0);
    if (ret < 0)
        return ret;
 
    /* duplicate the frame data if it's not refcounted */
    if (!src->buf[0]) {
        ret = av_frame_get_buffer(dst, 32);
        if (ret < 0)
            return ret;
 
        ret = av_frame_copy(dst, src);
        if (ret < 0)
            av_frame_unref(dst);
 
        return ret;
    }
 
    /* ref the buffers */
    for (i = 0; i < FF_ARRAY_ELEMS(src->buf); i++) {
        if (!src->buf[i])
            continue;
        dst->buf[i] = av_buffer_ref(src->buf[i]);
        if (!dst->buf[i]) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }
 
    if (src->extended_buf) {
        dst->extended_buf = av_mallocz_array(sizeof(*dst->extended_buf),
                                       src->nb_extended_buf);
        if (!dst->extended_buf) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        dst->nb_extended_buf = src->nb_extended_buf;
 
        for (i = 0; i < src->nb_extended_buf; i++) {
            dst->extended_buf[i] = av_buffer_ref(src->extended_buf[i]);
            if (!dst->extended_buf[i]) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
        }
    }
 
    if (src->hw_frames_ctx) {
        dst->hw_frames_ctx = av_buffer_ref(src->hw_frames_ctx);
        if (!dst->hw_frames_ctx) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }
 
    /* duplicate extended data */
    if (src->extended_data != src->data) {
        int ch = src->channels;
 
        if (!ch) {
            ret = AVERROR(EINVAL);
            goto fail;
        }
        CHECK_CHANNELS_CONSISTENCY(src);
 
        dst->extended_data = av_malloc_array(sizeof(*dst->extended_data), ch);
        if (!dst->extended_data) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        memcpy(dst->extended_data, src->extended_data, sizeof(*src->extended_data) * ch);
    } else
        dst->extended_data = dst->data;
 
    memcpy(dst->data,     src->data,     sizeof(src->data));
    memcpy(dst->linesize, src->linesize, sizeof(src->linesize));
 
    return 0;
 
fail:
    av_frame_unref(dst);
    return ret;
}
9.av_frame_move_ref
掏空一個AVFrame轉移數據到另一個,不涉及引用計數變化。

void av_frame_move_ref(AVFrame *dst, AVFrame *src)
{
    av_assert1(dst->width == 0 && dst->height == 0);
    av_assert1(dst->channels == 0);
 
    *dst = *src;
    if (src->extended_data == src->data)
        dst->extended_data = dst->data;
    memset(src, 0, sizeof(*src));
    get_frame_defaults(src);
}
10.av_frame_alloc與av_frame_free
av_frame_alloc與av_frame_free配合使用。av_frame_alloc之後需要av_frame_get_buffer真正開闢空間,av_frame_free會減少這些空間的引用計數。

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_alloc並沒有給AVFrame中buffer增加數據,需要進行如下操作申請buffer併爲其增加音視頻數據。而av_frame_free中av_frame_unref會去減少這些內存的引用並在最後釋放這些內存。

    frame->format = c->pix_fmt;
    frame->width  = c->width;
    frame->height = c->height;
    //這裏初始化了AVFrame中的buffer
    ret = av_frame_get_buffer(frame, 32);
 
    //確保當前frame可寫 如果不可寫的情況下會給frame搞一塊新的buffer
    ret = av_frame_make_writable(frame);
    if (ret < 0)
        exit(1);
 
    //給AVFrame中buffer賦值
    for (y = 0; y < c->height; y++) {
        for (x = 0; x < c->width; x++) {
            frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
        }
    }

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