zoukankan      html  css  js  c++  java
  • 【秒懂音视频开发】25_H.264解码实战

    本文的主要内容:对H.264数据进行解码(解压缩)。

    如果是命令行的操作,非常简单。

    ffmpeg -c:v h264 -i in.h264 out.yuv
    # -c:v h264是指定使用h264作为解码器
    

    接下来主要讲解如何通过代码的方式解码H.264数据,用到了avcodecavutil两个库,整体过程跟《AAC解码实战》类似。

    类的声明

    extern "C" {
    #include <libavutil/avutil.h>
    }
    
    typedef struct {
        const char *filename;
        int width;
        int height;
        AVPixelFormat pixFmt;
        int fps;
    } VideoDecodeSpec;
    
    class FFmpegs {
    public:
        FFmpegs();
    
        static void h264Decode(const char *inFilename,
                               VideoDecodeSpec &out);
    };
    

    类的使用

    VideoDecodeSpec out;
    out.filename = "F:/res/out.yuv";
    
    FFmpegs::h264Decode("F:/res/in.h264", out);
    
    qDebug() << out.width << out.height
             << out.fps << av_get_pix_fmt_name(out.pixFmt);
    

    宏定义

    extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavutil/avutil.h>
    #include <libavutil/imgutils.h>
    }
    
    #define ERROR_BUF(ret) 
        char errbuf[1024]; 
        av_strerror(ret, errbuf, sizeof (errbuf));
    
    // 输入缓冲区的大小
    #define IN_DATA_SIZE 4096
    

    变量定义

    // 返回结果
    int ret = 0;
    
    // 用来存放读取的输入文件数据(h264)
    char inDataArray[IN_DATA_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    char *inData = inDataArray;
    
    // 每次从输入文件中读取的长度(h264)
    // 输入缓冲区中,剩下的等待进行解码的有效数据长度
    int inLen;
    // 是否已经读取到了输入文件的尾部
    int inEnd = 0;
    
    // 文件
    QFile inFile(inFilename);
    QFile outFile(out.filename);
    
    // 解码器
    AVCodec *codec = nullptr;
    // 上下文
    AVCodecContext *ctx = nullptr;
    // 解析器上下文
    AVCodecParserContext *parserCtx = nullptr;
    
    // 存放解码前的数据(h264)
    AVPacket *pkt = nullptr;
    // 存放解码后的数据(yuv)
    AVFrame *frame = nullptr;
    

    初始化

    // 获取解码器
    //    codec = avcodec_find_decoder_by_name("h264");
    codec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (!codec) {
        qDebug() << "decoder not found";
        return;
    }
    
    // 初始化解析器上下文
    parserCtx = av_parser_init(codec->id);
    if (!parserCtx) {
        qDebug() << "av_parser_init error";
        return;
    }
    
    // 创建上下文
    ctx = avcodec_alloc_context3(codec);
    if (!ctx) {
        qDebug() << "avcodec_alloc_context3 error";
        goto end;
    }
    
    // 创建AVPacket
    pkt = av_packet_alloc();
    if (!pkt) {
        qDebug() << "av_packet_alloc error";
        goto end;
    }
    
    // 创建AVFrame
    frame = av_frame_alloc();
    if (!frame) {
        qDebug() << "av_frame_alloc error";
        goto end;
    }
    
    // 打开解码器
    ret = avcodec_open2(ctx, codec, nullptr);
    if (ret < 0) {
        ERROR_BUF(ret);
        qDebug() << "avcodec_open2 error" << errbuf;
        goto end;
    }
    

    解码

    // 打开文件
    if (!inFile.open(QFile::ReadOnly)) {
        qDebug() << "file open error:" << inFilename;
        goto end;
    }
    if (!outFile.open(QFile::WriteOnly)) {
        qDebug() << "file open error:" << out.filename;
        goto end;
    }
    
    // 读取文件数据
    do {
        inLen = inFile.read(inDataArray, IN_DATA_SIZE);
        // 设置是否到了文件尾部
        inEnd = !inLen;
    
        // 让inData指向数组的首元素
        inData = inDataArray;
    
        // 只要输入缓冲区中还有等待进行解码的数据
        while (inLen > 0 || inEnd) {
            // 到了文件尾部(虽然没有读取任何数据,但也要调用av_parser_parse2,修复bug)
            // 经过解析器解析
            ret = av_parser_parse2(parserCtx, ctx,
                                   &pkt->data, &pkt->size,
                                   (uint8_t *) inData, inLen,
                                   AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
    
            if (ret < 0) {
                ERROR_BUF(ret);
                qDebug() << "av_parser_parse2 error" << errbuf;
                goto end;
            }
    
            // 跳过已经解析过的数据
            inData += ret;
            // 减去已经解析过的数据大小
            inLen -= ret;
    
            qDebug() << inEnd << pkt->size << ret;
    
            // 解码
            if (pkt->size > 0 && decode(ctx, pkt, frame, outFile) < 0) {
                goto end;
            }
    
            // 如果到了文件尾部
            if (inEnd) break;
        }
    } while (!inEnd);
    
    // 刷新缓冲区
    //    pkt->data = nullptr;
    //    pkt->size = 0;
    //    decode(ctx, pkt, frame, outFile);
    decode(ctx, nullptr, frame, outFile);
    
    // 赋值输出参数
    out.width = ctx->width;
    out.height = ctx->height;
    out.pixFmt = ctx->pix_fmt;
    // 用framerate.num获取帧率,并不是time_base.den
    out.fps = ctx->framerate.num;
    
    end:
    inFile.close();
    outFile.close();
    av_packet_free(&pkt);
    av_frame_free(&frame);
    av_parser_close(parserCtx);
    avcodec_free_context(&ctx);
    

    decode函数的实现如下所示:

    static int decode(AVCodecContext *ctx,
                      AVPacket *pkt,
                      AVFrame *frame,
                      QFile &outFile) {
        // 发送压缩数据到解码器
        int ret = avcodec_send_packet(ctx, pkt);
        if (ret < 0) {
            ERROR_BUF(ret);
            qDebug() << "avcodec_send_packet error" << errbuf;
            return ret;
        }
    
        while (true) {
            // 获取解码后的数据
            ret = avcodec_receive_frame(ctx, frame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                return 0;
            } else if (ret < 0) {
                ERROR_BUF(ret);
                qDebug() << "avcodec_receive_frame error" << errbuf;
                return ret;
            }
    
            // 将解码后的数据写入文件
            // 写入Y平面
            outFile.write((char *) frame->data[0],
                          frame->linesize[0] * ctx->height);
            // 写入U平面
            outFile.write((char *) frame->data[1],
                          frame->linesize[1] * ctx->height >> 1);
            // 写入V平面
            outFile.write((char *) frame->data[2],
                          frame->linesize[2] * ctx->height >> 1);
        }
    }
    

    回收资源

    end:
        inFile.close();
        outFile.close();
        av_packet_free(&pkt);
        av_frame_free(&frame);
        av_parser_close(parserCtx);
        avcodec_free_context(&ctx);
    
  • 相关阅读:
    阿里云盾证书服务助力博客装逼成功
    内容安全策略(CSP)_防御_XSS_攻击的好助手
    Spring-beans架构设计原理
    Httpclient核心架构设计
    第四章 数据类型—字典(dict)、集合(set)
    第三章 数据类型 — 列表(list)、元组(tuple)
    第三章 数据类型 — int、bool 和 str
    第二章 Python基础(二)
    pycharm快捷键
    第二章 Python基础(一)
  • 原文地址:https://www.cnblogs.com/mjios/p/14810647.html
Copyright © 2011-2022 走看看