Audio and video development fourteen: decoding multimedia video data

logical flow

The general steps of video decoding are:

  1. Get output multimedia file context
  2. Find video stream from multimedia file
  3. find codec
  4. Create a decoder context
  5. Set decoder parameters
  6. open codec
  7. Create AVFrame
  8. Create AVPacket
  9. Read data from multimedia files
  10. Send the compressed packet to the decoder
  11. Get the decoded data frame

The specific logic flow chart is as follows:

Code

Read the video stream of the mp4 file, decode it into the original video frame, and save each video frame as a picture

#include <stdio.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>


// 将视频帧数据保存为图片
static void savePic(unsigned char* buf, int linesize, int width, int height, char* name) {
    
    
    FILE* f;

    f = fopen(name, "wb");
    // 向文件中写入一个P5格式的图像头信息,
    // 该图像的宽度为width像素,高度为height像素,每个像素的最大灰度值为255。
    fprintf(f, "P5\n%d %d\n%d\n", width, height, 255);
    /*
    * 使用for循环遍历每一行的像素数据,
    其中buf是图像数据的首地址,linesize是每行数据的字节数。通过将buf的位置向后偏移i*linesize,获取当前行的像素数据,
    然后使用fwrite函数将该行像素数据写入文件中。
    fwrite函数中,第一个参数是要写入的数据的首地址,第二个参数是每个数据的大小,这里为1,第三个参数是要写入的数据数量,这里为图像的宽度。
    因此,每次循环写入一个完整的行的像素数据。
    */
    for (int i = 0; i < height; i++) {
    
    
        fwrite(buf + i * linesize, 1, width, f);
    }
    fclose(f);
}

// 解码上下文,解码后存放的数据,解码数据包,输出文件名
static int decode(AVCodecContext* ctx, AVFrame* frame, AVPacket* pkt, const char* fileName) {
    
    
    
    int ret = -1;
    char buf[1024];

    ret = avcodec_send_packet(ctx, pkt);
    if (ret < 0) {
    
    
        av_log(NULL, AV_LOG_ERROR, "Failed to send frame to decoder!\n");
        goto _END;
    }

    while (ret >= 0) {
    
    
        ret = avcodec_receive_frame(ctx, frame); // 从编解码器的输出队列中获取解码后的输出帧
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
    
    
            return 0;
        }
        else if (ret < 0) {
    
    
            return -1; //退出程序
        }

        // 没有问题的话,把frame中的内容保存成一张张图片
        //snprintf(buf, sizeof(buf), "%s-%d.bmp", fileName, ctx->frame_number);// 通过这个方法为每一张图片构造一个名字
        snprintf(buf, sizeof(buf), "%s-%d", fileName, ctx->frame_number);// 通过这个方法为每一张图片构造一个名字
        //saveBMP(swsCtx, frame, 640, 360, buf);


        savePic(frame->data[0],
            frame->linesize[0],
            frame->width,
            frame->height,
            buf);

        if (pkt) {
    
    
            av_packet_unref(pkt);
        }

    }
_END:
    return 0;
}

// 对多媒体文件中的视频流进行解码,生成一张张黑白图像
int decode_video() {
    
    
    int ret = -1;
    int idx = -1;

    //1. 处理一些参数;
    char* src;
    char* dst;

    const AVCodec* codec = NULL;
    AVCodecContext* ctx = NULL;

    AVFormatContext* pFmtCtx = NULL;

    AVStream* inStream = NULL;

    AVFrame* frame = NULL;
    AVPacket* pkt = NULL;

    struct SwsContext* swsCtx = NULL;

    av_log_set_level(AV_LOG_DEBUG);


    src = "F:\\test_data\\crop_jiuzhe_summer.mp4";
    dst = "F:\\test_data\\photo\\out";

    //2. 打开多媒体文件
    if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL)) < 0) {
    
    
        av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));
        exit(-1);
    }

    //3. 从多媒体文件中找到视频流
    idx = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (idx < 0) {
    
    
        av_log(pFmtCtx, AV_LOG_ERROR, "Does not include audio stream!\n");
        goto _ERROR;
    }

    inStream = pFmtCtx->streams[idx];

    //4. 查找解码器 找到源文件的编码器id
    codec = avcodec_find_decoder(inStream->codecpar->codec_id);
    if (!codec) {
    
    
        av_log(NULL, AV_LOG_ERROR, "Could not find libx264 Codec");
        goto _ERROR;
    }

    //5. 创建解码器上下文
    ctx = avcodec_alloc_context3(NULL);
    if (!ctx) {
    
    
        av_log(NULL, AV_LOG_ERROR, "NO MEMRORY\n");
        goto _ERROR;
    }

    // 解码器的一些参数信息 可以通过源文件的来获取
    ret = avcodec_parameters_to_context(ctx, inStream->codecpar);
    if (ret < 0) {
    
    
        av_log(ctx, AV_LOG_ERROR, "Could not copyt codecpar to codec ctx!\n");
        goto _ERROR;
    }

    //5. 解码器与解码器上下文绑定到一起
    ret = avcodec_open2(ctx, codec, NULL);
    if (ret < 0) {
    
    
        av_log(ctx, AV_LOG_ERROR, "Don't open codec: %s \n", av_err2str(ret));
        goto _ERROR;
    }


    //6. 创建AVFrame
    frame = av_frame_alloc();
    if (!frame) {
    
    
        av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
        goto _ERROR;
    }

    //7. 创建AVPacket
    pkt = av_packet_alloc();
    if (!pkt) {
    
    
        av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
        goto _ERROR;
    }

    //8. 从源多媒体文件中读到视频数据
    while (av_read_frame(pFmtCtx, pkt) >= 0) {
    
    
        if (pkt->stream_index == idx) {
    
    
            decode(ctx, frame, pkt, dst);
        }
    }
    // 对于解码器和编码器存在同样的问题,有些数据可能还没有解出来,强制让它输出
    decode(ctx, frame, NULL, dst);

    //9. 将申请的资源释放掉
_ERROR:
    if (pFmtCtx) {
    
    
        avformat_close_input(&pFmtCtx);
        pFmtCtx = NULL;
    }

    if (ctx) {
    
    
        avcodec_free_context(&ctx);
        ctx = NULL;
    }

    if (frame) {
    
    
        av_frame_free(&frame);
        frame = NULL;
    }

    if (pkt) {
    
    
        av_packet_free(&pkt);
        pkt = NULL;
    }


    printf("hello, world!\n");
    return 0;
}

Guess you like

Origin blog.csdn.net/qq_38056514/article/details/130190812