Proceso detallado de decodificación de FFmpeg

introducir

  1. El módulo libavcodec de FFmpeg completa el módulo de códec de audio y video multimedia.
  2. La versión anterior de FFmpeg usa avcodec_decode_video2() como API de función de decodificación de video y avcodec_decode_audio4() como API de función de decodificación de audio; desde la versión 3.4, las dos se han marcado como API obsoletas (atributo_obsoleto ) .
  3. La nueva versión de FFmpeg usa avcodec_send_packet() y avcodec_receive_frame() como API de función de decodificación de audio y video, pero al mismo tiempo aún conserva la compatibilidad con las interfaces antiguas y llama a compat_decode() a través de avcodec_decode_video2() y avcodec_decode_audio4() para completar la empaquetado de la nueva 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.

Proceso detallado de decodificación de video

  1. Tome la decodificación del estándar H264 como ejemplo, desde la función principal hasta la decodificación del macrobloque MB final.
    inserte la descripción de la imagen aquí

Introducción a la API central de decodificación

avcodec_send_packet()

  1. Introducción a la Declaración 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. Análisis de definición de avcodec_send_packet()
    Esta API puede entenderse como la función central de FFmpeg para completar la decodificación de audio y video. Como se puede ver en la implementación de la función, se llamará primero a la función av_bsf_send_packet() para copiar el AVPacket de entrada a el búfer Finalmente, cuando buffer_frame esté vacío, llame a la función decode_receive_frame_internal() para completar la decodificación real.

    decode_receive_frame_internal() llamará a decode_simple_receive_frame() internamente para completar la decodificación.

    decode_simple_receive_frame() llama internamente a decode_simple_internal() para completar la decodificación.

    En decode_simple_internal(), el puntero de función (*decode()) apunta a diferentes decodificadores para completar el proceso de decodificación de diferentes estándares; si se trata de decodificación de subprocesos múltiples, también estará involucrado ff_thread_decode_frame(), y la decodificación de subprocesos múltiples no se introducirá en detalle.
    inserte la descripción de la imagen aquí

  2. Puntero de función (*decode)()
    En decode_simple_internal(), llama a diferentes decodificadores a través de ret = avctx->codec->decode(avctx, frame, &got_frame, pkt) para completar el proceso de decodificación específico.

    Tomando como ejemplo la decodificación h264, h264_decode_frame() completa el proceso de decodificación específico, y la función central es decode_nal_units() para la decodificación NAUL; entre ellos, ff_H2645_packet_split() realiza el análisis del flujo de código y ff_h264_execute_decode_slices() realiza la decodificación a nivel de segmento.

    La función central en ff_h264_execute_decode_slices() es decode_slice(), que decodifica cada Slice.

    decode_slice() es el proceso central de decodificación de datos de video, que se divide principalmente en cuatro módulos: decodificación de entropía , decodificación de macrobloques , filtrado de bucles y ocultación de errores . Entre ellos, ff_h264_decode_mb_cabac() completa la decodificación de entropía (si la decodificación de entropía es cavlc, ff_h264_decode_mb_cavlac completa la decodificación de entropía); ff_h264_decode_mb() completa la decodificación de MB de macrobloque; loop_filter() completa el filtrado de bucle; er_add_slice() completa el procesamiento de ocultación de errores.
    inserte la descripción de la imagen aquí

avcodec_receive_frame()

  1. Introducción a la Declaración 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. El análisis de definición de avcodec_receive_frame()
    se puede ver desde la implementación interna de la función avcodec_receive_frame(). La lógica central es relativamente simple. Primero, determine si hay datos en el buffer_frame. Si los hay, llame directamente av_frame_move_ref() para completar la proceso de copia de marco.Después de la decodificación, los datos se copian de buffer_frame al marco.( es decir, copie los datos de AVCodecContext a AVFrame ); si no hay datos en buffer_frame, debe llamar a decode_receive_frame_internal() para completar el proceso específico. decodificación Este paso es el mismo que el módulo de envío de paquetes.
    inserte la descripción de la imagen aquí

Ejemplo de decodificación oficial

/*
 * 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;
}

referencia

  1. http://ffmpeg.org/

Supongo que te gusta

Origin blog.csdn.net/yanceyxin/article/details/132104693
Recomendado
Clasificación