Processus détaillé de décodage FFmpeg

introduire

  1. Le module libavcodec de FFmpeg vient compléter le module codec du multimédia audio et vidéo.
  2. L'ancienne version de FFmpeg utilise avcodec_decode_video2() comme API de fonction de décodage vidéo et avcodec_decode_audio4() comme API de fonction de décodage audio ; depuis la version 3.4, les deux ont été marquées comme des API obsolètes ( attribute_deprecated ) .
  3. La nouvelle version de FFmpeg utilise avcodec_send_packet() et avcodec_receive_frame() comme API de fonctions de décodage audio et vidéo, mais conserve en même temps la compatibilité avec les anciennes interfaces et appelle compat_decode() via avcodec_decode_video2() et avcodec_decode_audio4() pour compléter le packaging de la nouvelle API.
//具体可以参考 FFmpeg 中 doc/APIchanges 中的记录.

2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
  Add a new audio/video encoding and decoding API with decoupled input
  and output -- avcodec_send_packet(), avcodec_receive_frame(),
  avcodec_send_frame() and avcodec_receive_packet().
  
2017-09-26 - b1cf151c4d - lavc 57.106.102 - avcodec.h
  Deprecate AVCodecContext.refcounted_frames. This was useful for deprecated
  API only (avcodec_decode_video2/avcodec_decode_audio4). The new decode APIs
  (avcodec_send_packet/avcodec_receive_frame) always work with reference
  counted frames.

Processus détaillé de décodage vidéo

  1. Prenons comme exemple le décodage de la norme H264, de la fonction principale au décodage final du macrobloc MB.
    insérez la description de l'image ici

Introduction à l'API de base du décodage

avcodec_send_packet()

  1. Introduction à la déclaration d'API
 * @param avctx codec context
 * @param[in] avpkt The input AVPacket. Usually, this will be a single video
 *                  frame, or several complete audio frames.
 *                  Ownership of the packet remains with the caller, and the
 *                  decoder will not write to the packet. The decoder may create
 *                  a reference to the packet data (or copy it if the packet is
 *                  not reference-counted).
 *                  Unlike with older APIs, the packet is always fully consumed,
 *                  and if it contains multiple frames (e.g. some audio codecs),
 *                  will require you to call avcodec_receive_frame() multiple
 *                  times afterwards before you can send a new packet.
 *                  It can be NULL (or an AVPacket with data set to NULL and
 *                  size set to 0); in this case, it is considered a flush
 *                  packet, which signals the end of the stream. Sending the
 *                  first flush packet will return success. Subsequent ones are
 *                  unnecessary and will return AVERROR_EOF. If the decoder
 *                  still has frames buffered, it will return them after sending
 *                  a flush packet.
 *
 * @return 0 on success, otherwise negative error code:
 *      AVERROR(EAGAIN):   input is not accepted in the current state - user
 *                         must read output with avcodec_receive_frame() (once
 *                         all output is read, the packet should be resent, and
 *                         the call will not fail with EAGAIN).
 *      AVERROR_EOF:       the decoder has been flushed, and no new packets can
 *                         be sent to it (also returned if more than 1 flush
 *                         packet is sent)
 *      AVERROR(EINVAL):   codec not opened, it is an encoder, or requires flush
 *      AVERROR(ENOMEM):   failed to add packet to internal queue, or similar
 *      other errors: legitimate decoding errors
 */
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
  1. Analyse de la définition de avcodec_send_packet()
    Cette API peut être comprise comme la fonction principale de FFmpeg pour compléter le décodage audio et vidéo. Comme le montre l'implémentation de la fonction, la fonction av_bsf_send_packet() sera appelée en premier pour copier l'entrée AVPacket dans le tampon. Enfin, lorsque le buffer_frame est vide, appelez la fonction decode_receive_frame_internal() pour terminer le décodage proprement dit.

    decode_receive_frame_internal() appellera decode_simple_receive_frame() en interne pour terminer le décodage.

    decode_simple_receive_frame() appelle en interne decode_simple_internal() pour terminer le décodage.

    Dans decode_simple_internal(), le pointeur de fonction (*decode()) pointe vers différents décodeurs pour terminer le processus de décodage de décodage de différentes normes ; si le décodage multi-thread est impliqué, ff_thread_decode_frame() sera également impliqué, et le décodage multi-thread sera pas être présenté en détail.
    insérez la description de l'image ici

  2. (*decode)() pointeur de fonction
    Dans decode_simple_internal(), appelez différents décodeurs via ret = avctx->codec->decode(avctx, frame, &got_frame, pkt) pour terminer le processus de décodage spécifique.

    En prenant le décodage h264 comme exemple, h264_decode_frame() complète le processus de décodage spécifique, et la fonction principale est decode_nal_units() pour le décodage NAUL ; parmi eux, ff_H2645_packet_split() effectue l'analyse du flux de code et ff_h264_execute_decode_slices() effectue le décodage au niveau de la tranche.

    La fonction principale de ff_h264_execute_decode_slices() est decode_slice(), qui décode chaque Slice.

    decode_slice() est le processus de décodage de base des données vidéo, qui est principalement divisé en quatre modules : décodage entropique , décodage de macroblocs , filtrage de boucle et masquage d'erreurs . Parmi eux, ff_h264_decode_mb_cabac() complète le décodage entropique (si le décodage entropique est cavlc, ff_h264_decode_mb_cavlac complète le décodage entropique) ; ff_h264_decode_mb() complète le décodage du macrobloc MB ; loop_filter() complète le filtrage de boucle ; er_add_slice() complète le traitement de dissimulation des erreurs.
    insérez la description de l'image ici

avcodec_receive_frame()

  1. Introduction à la déclaration d'API
/**
 * Return decoded output data from a decoder.//从解码器返回解码后的输出数据
 *
 * @param avctx codec context
 * @param frame This will be set to a reference-counted video or audio
 *              frame (depending on the decoder type) allocated by the
 *              decoder. Note that the function will always call
 *              av_frame_unref(frame) before doing anything else.
 *
 * @return
 *      0:                 success, a frame was returned
 *      AVERROR(EAGAIN):   output is not available in this state - user must try
 *                         to send new input
 *      AVERROR_EOF:       the decoder has been fully flushed, and there will be
 *                         no more output frames
 *      AVERROR(EINVAL):   codec not opened, or it is an encoder
 *      AVERROR_INPUT_CHANGED:   current decoded frame has changed parameters
 *                               with respect to first decoded frame. Applicable
 *                               when flag AV_CODEC_FLAG_DROPCHANGED is set.
 *      other negative values: legitimate decoding errors
 */
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
  1. L'analyse de la définition de avcodec_receive_frame()
    peut être vue à partir de l'implémentation interne de la fonction avcodec_receive_frame(). La logique de base est relativement simple. Tout d'abord, déterminez s'il y a des données dans le buffer_frame. Si c'est le cas, appelez directement av_frame_move_ref() pour terminer le processus de copie de trame. Après le décodage, les données sont copiées de buffer_frame vers la trame. ( c'est-à-dire, copiez les données de AVCodecContext vers AVFrame ); s'il n'y a pas de données dans buffer_frame, vous devez appeler decode_receive_frame_internal() pour terminer le processus spécifique Cette étape est la même que pour le module d'envoi de paquets.
    insérez la description de l'image ici

Exemple de décodage officiel

/*
 * Copyright (c) 2001 Fabrice Bellard
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * @file
 * video decoding with libavcodec API example
 *
 * @example decode_video.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavcodec/avcodec.h>

#define INBUF_SIZE 4096

static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize,
                     char *filename)
{
    
    
    FILE *f;
    int i;

    f = fopen(filename,"w");
    fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
    for (i = 0; i < ysize; i++)
        fwrite(buf + i * wrap, 1, xsize, f);
    fclose(f);
}

static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
                   const char *filename)
{
    
    
    char buf[1024];
    int ret;

    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret < 0) {
    
    
        fprintf(stderr, "Error sending a packet for decoding\n");
        exit(1);
    }

    while (ret >= 0) {
    
    
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
    
    
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }

        printf("saving frame %3d\n", dec_ctx->frame_number);
        fflush(stdout);

        /* the picture is allocated by the decoder. no need to
           free it */
        snprintf(buf, sizeof(buf), "%s-%d", filename, dec_ctx->frame_number);
        pgm_save(frame->data[0], frame->linesize[0],
                 frame->width, frame->height, buf);
    }
}

int main(int argc, char **argv)
{
    
    
    const char *filename, *outfilename;
    const AVCodec *codec;
    AVCodecParserContext *parser;
    AVCodecContext *c= NULL;
    FILE *f;
    AVFrame *frame;
    uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *data;
    size_t   data_size;
    int ret;
    AVPacket *pkt;

    if (argc <= 2) {
    
    
        fprintf(stderr, "Usage: %s <input file> <output file>\n"
                "And check your input file is encoded by mpeg1video please.\n", argv[0]);
        exit(0);
    }
    filename    = argv[1];
    outfilename = argv[2];

    pkt = av_packet_alloc();
    if (!pkt)
        exit(1);

    /* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */
    memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);

    /* find the MPEG-1 video decoder */
    codec = avcodec_find_decoder(AV_CODEC_ID_MPEG1VIDEO);
    if (!codec) {
    
    
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    parser = av_parser_init(codec->id);
    if (!parser) {
    
    
        fprintf(stderr, "parser not found\n");
        exit(1);
    }

    c = avcodec_alloc_context3(codec);
    if (!c) {
    
    
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    /* For some codecs, such as msmpeg4 and mpeg4, width and height
       MUST be initialized there because this information is not
       available in the bitstream. */

    /* open it */
    if (avcodec_open2(c, codec, NULL) < 0) {
    
    
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    f = fopen(filename, "rb");
    if (!f) {
    
    
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }

    frame = av_frame_alloc();
    if (!frame) {
    
    
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    while (!feof(f)) {
    
    
        /* read raw data from the input file */
        data_size = fread(inbuf, 1, INBUF_SIZE, f);
        if (!data_size)
            break;

        /* use the parser to split the data into frames */
        data = inbuf;
        while (data_size > 0) {
    
    
            ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
                                   data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
            if (ret < 0) {
    
    
                fprintf(stderr, "Error while parsing\n");
                exit(1);
            }
            data      += ret;
            data_size -= ret;

            if (pkt->size)
                decode(c, frame, pkt, outfilename);
        }
    }

    /* flush the decoder */
    decode(c, frame, NULL, outfilename);

    fclose(f);

    av_parser_close(parser);
    avcodec_free_context(&c);
    av_frame_free(&frame);
    av_packet_free(&pkt);

    return 0;
}

référence

  1. http://ffmpeg.org/

Je suppose que tu aimes

Origine blog.csdn.net/yanceyxin/article/details/132104693
conseillé
Classement