美文网首页音视频
音视频-H264解码

音视频-H264解码

作者: li_礼光 | 来源:发表于2021-08-19 00:52 被阅读0次

    H264解码原理和音视频-AAC解码原理几乎一样, 不同的是就decode 里面数据的处理, 解码的事情都是通过H264解码器去实现

    AAC解码的简略逻辑 :

    AAC源文件 ==> (AVPacket)输入缓冲区 ==> (AVCodec)解码器 ==> (AVFrame)输出缓冲区 ==> 输出文件

    H264解码的简略逻辑

    H264源文件 ==> (AVPacket)输入缓冲区 ==> (AVCodec)解码器 ==> (AVFrame)输出缓冲区 ==> 输出文件

    核心代码

    #include "h264DecodeThread.h"
    
    #include <QDebug>
    #include <QFile>
    
    extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavutil/imgutils.h>
    }
    
    #define ERROR_BUF(ret) \
        char errbuf[1024]; \
        av_strerror(ret, errbuf, sizeof (errbuf));
    
    
    #define CHECK_IF_ERROR_BUF_END(ret, funcStr) \
        if (ret) { \
            ERROR_BUF(ret); \
            qDebug() << #funcStr << " error :" << errbuf; \
            goto end; \
        }
    
    
    
    #ifdef Q_OS_WIN
        #define IN_H264_FILEPATH "G:/BigBuckBunny_CIF_24fps_h264.h264"
        #define OUT_H264_FILEPATH "G:/BigBuckBunny_CIF_24fps_h264_out.yuv"
        #define IMGW 352
        #define IMGH 288
    #else
        #define IN_H264_FILEPATH "/Users/liliguang/Desktop/dstYuv.h264"
        #define OUT_H264_FILEPATH "/Users/liliguang/Desktop/h264_out.yuv"
        #define IMGW 352
        #define IMGH 288
    #endif
    
    #define VIDEO_INBUF_SIZE 4096
    
    H264DecodeThread::H264DecodeThread(QObject *parent) : QThread(parent) {
        // 当监听到线程结束时(finished),就调用deleteLater回收内存
        connect(this, &H264DecodeThread::finished,
                this, &H264DecodeThread::deleteLater);
    }
    
    H264DecodeThread::~H264DecodeThread() {
        // 断开所有的连接
        disconnect();
        // 内存回收之前,正常结束线程
        requestInterruption();
        // 安全退出
        quit();
        wait();
        qDebug() << this << "析构(内存被回收)";
    }
    
    static int frameIdx = 0;
    
    // 音频解码
    // 返回负数:中途出现了错误
    // 返回0:解码操作正常完成
    static int decode(AVCodecContext *ctx,
                      AVFrame *frame,
                      AVPacket *pkt,
                      QFile &outFile) {
    
        // 发送数据到解码 , sent_ret = 0 为sucesss
        int ret = avcodec_send_packet(ctx, pkt);
    
        if (ret < 0) {
            ERROR_BUF(ret);
            qDebug() << "avcodec_send_packet error" << errbuf;
            return ret;
        }
    
        while (1) {
            // 从解码器中获取到数据到frame
            ret = avcodec_receive_frame(ctx, frame);
            qDebug() << "avcodec_receive_frame : " << ret ;
    
    
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF ) {
                return ret;
            } else  if (ret < 0) {
                qDebug() << "ret < 0" << ret ;
                return ret;
            }
    
            qDebug() << "解码出第" << ++frameIdx << "帧";
            // 将解码后的数据写入文件
    
            qDebug() << "frame->linesize[0]" << frame->linesize[0] ;
            qDebug() << "frame->linesize[1]" << frame->linesize[1] ;
            qDebug() << "frame->linesize[2]" << frame->linesize[2] ;
            qDebug() << "frame->linesize[3]" << frame->linesize[3] ;
            qDebug() << "ctx->width" << ctx->width ;
            qDebug() << "ctx->height" << ctx->height ;
            qDebug() << "ctx->pix_fmt" << ctx->pix_fmt ;
    
            qDebug() << "frame->format" << frame->format ;
            qDebug() << "av_image_get_buffer_size " << av_image_get_buffer_size(ctx->pix_fmt, ctx->width, ctx->height, 0) ;
    
    
            //yuv420p   yyyy yyyy uu vv
            //一帧yuv420p   352 * 288  * 1.5 = 152064
            // y分量 :152064 * (8/12) = 152064 * 0.6666 = 101376
            // u分量 :152064 * (2/12) = 152064 * 0.1666 =  25344
            // v分量 :152064 * (2/12) = 152064 * 0.1666 =  25344
            // 字节流中存储样式 :
            // y1y2y3.....y101376 u1u2u3......u25344 v1v2v3......v25344
    
    
    //        qDebug() << "frame->data[0]" << frame->data[0] ;
    //        qDebug() << "frame->data[1]" << frame->data[1] ;
    //        qDebug() << "frame->data[2]" << frame->data[2] ;
    //        qDebug() << "frame->data[3]" << frame->data[3] ;
    
    
    
    
            // 写入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);
        }
    }
    
    
    
    
    void H264DecodeThread::run() {
        qDebug() << "H264DecodeThread run ";
    
        // 解码器
        const AVCodec *codec = nullptr;
        // 解码器上下文
        AVCodecContext *codecCtx = nullptr;
        // Parser上下文
        AVCodecParserContext *codecParserCtx = nullptr;
        // 源文件数据源存储结构指针
        AVFrame *frame = nullptr;
        // 编码文件数据源存储结构指针
        AVPacket *pkt = nullptr;
    
        int avcodec_open2_Ret;
    
        // 输入输出文件
        const char *infilename;
        const char *outfilename;
    
        infilename = IN_H264_FILEPATH;
        outfilename = OUT_H264_FILEPATH;
    
        QFile inFile(infilename);
        QFile outFile(outfilename);
    
        int infileOpen_Ret;
        int outfileOpen_Ret;
    
        int av_image_alloc_ret;
    
        // 加上AV_INPUT_BUFFER_PADDING_SIZE是为了防止某些优化过的reader一次性读取过多导致越界.
        char inDataArray[VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];   // 输入缓冲区
        char *inData = inDataArray;                                          // 指向输入缓冲区指针
    
        int inLen; // 读取到文件的数据大小
        bool inEnd = false;
    
        int inParserRet;
        int decode_ret;
        // ============================================================
        // 解码逻辑  源文件 ==> 解析器 ==> (AVPacket)输入缓冲区 ==> 解码器 ==> (AVFrame)输出缓冲区 ==> 输出文件
    
        // 输入文件
        infileOpen_Ret = inFile.open(QFile::ReadOnly);
        CHECK_IF_ERROR_BUF_END(!infileOpen_Ret, "inFile.open");
        // 输出文件
        outfileOpen_Ret = outFile.open(QFile::WriteOnly);
        CHECK_IF_ERROR_BUF_END(!outfileOpen_Ret, "outFile.open");
    
        // 创建输入Packet
        pkt = av_packet_alloc();
        CHECK_IF_ERROR_BUF_END(!pkt, "av_packet_alloc");
    
        // 创建输出rame
        frame = av_frame_alloc();
        CHECK_IF_ERROR_BUF_END(!frame, "av_frame_alloc");
    
        // 解码器
        codec = avcodec_find_decoder_by_name("h264");
        CHECK_IF_ERROR_BUF_END(!codec, "avcodec_find_decoder");
    
        // Parser解析器上下文
        codecParserCtx = av_parser_init(codec->id);
        CHECK_IF_ERROR_BUF_END(!codecParserCtx, "av_parser_init");
    
    
    
        // 解码器上下文
        codecCtx = avcodec_alloc_context3(codec);
        CHECK_IF_ERROR_BUF_END(!codecCtx, "avcodec_alloc_context3");
    
        // 打开解码器
        avcodec_open2_Ret = avcodec_open2(codecCtx, codec, nullptr);
        CHECK_IF_ERROR_BUF_END(avcodec_open2_Ret, "avcodec_open2");
    
        do {
            // 只要还没有到文件结尾, 每次都读取一次文件
            inLen = inFile.read(inDataArray, VIDEO_INBUF_SIZE);
            inEnd = !inLen;
            // 每次将inData的位置重置为buffer缓冲区的首位置
            inData = inDataArray;
    
            // 如果不是文件结尾
            while (inLen > 0 || inEnd) {
    
                // 传给parser
                inParserRet = av_parser_parse2(codecParserCtx,
                                               codecCtx,
                                               &pkt->data,
                                               &pkt->size,
                                               (uint8_t *)inData,
                                               inLen,
                                               AV_NOPTS_VALUE,
                                               AV_NOPTS_VALUE,
                                               0);
    
    
                // 如果经过parser 处理返回的内容大于0, 那么就是解码成功
                CHECK_IF_ERROR_BUF_END(inParserRet < 0, "av_parser_parse2");
    
                inData += inParserRet;
                inLen  -= inParserRet;
    
                qDebug() << "inLen : " << inLen << "inEnd : " << inEnd << " pkt->size : " << pkt->size << "inParserRet : " << inParserRet;
    
                if (pkt->size) {
                    decode_ret = decode(codecCtx, frame,  pkt, outFile);
                    CHECK_IF_ERROR_BUF_END( (decode_ret != AVERROR(EAGAIN) && decode_ret != AVERROR_EOF && decode_ret < 0), "decode");
                }
    
    
                // 如果到了文件尾部
                if (inEnd) {
                    break;
                }
    
    
            }
            qDebug() << " " ;
            qDebug() << "下一次读取" ;
    
        } while (!inEnd);
    
    
    
        // 冲刷最后一次缓冲区
        decode_ret = decode(codecCtx, frame, nullptr, outFile);
        qDebug() << "H264DecodeThread Last Decode " << decode_ret;
        CHECK_IF_ERROR_BUF_END(decode_ret < 0, "decode");
    
    
    
    end:
        // 关闭文件
        inFile.close();
        outFile.close();
    
        // 释放资源
        av_frame_free(&frame);
        av_packet_free(&pkt);
    
        avcodec_free_context(&codecCtx);
        av_parser_close(codecParserCtx);
        qDebug() << "H264DecodeThread end ";
    }
    

    关于 Win h264编码

    下载地址 :http://trace.eas.asu.edu/yuv/index.html
    视频内容 : Big Buck Bunny
    像素格式 :yuv420p 
    分辨率 :352X288 
    帧率 :24 
    文件大小 : 2.02 GB (2,176,796,160 字节)
    
    命令行播放 :ffplay -video_size 352X288 -pixel_format yuv420p -framerate 24 .\BigBuckBunny_CIF_24fps_h264_out.yuv
    
    h264编码后
    BigBuckBunny_CIF_24fps_h264.h264 
    文件大小 :18.4 MB (19,313,821 字节)
    
    h264解码后 : 
    BigBuckBunny_CIF_24fps_h264_out.yuv
    文件大小 :2.21 GB (2,374,686,720 字节)
    

    命令行播放效果 = 花屏

    ffplay -video_size 352X288 -pixel_format yuv420p -framerate 24 .\BigBuckBunny_CIF_24fps_h264_out.yuv


    这里遇到一个问题是, 通过win的h264解码后, 得到的linesize居然是
    frame->linesize[0] 384
    frame->linesize[1] 192
    frame->linesize[2] 192
    frame->linesize[3] 0
    

    00->15 总共22行有数据 , 一行16个字节
    22 * 16 = 352 ,
    下面多出了两行全为0的空白数据
    24 * 16 = 384,



    这里就很神奇了,分辨率 :352X288 YUV420p对应的应该是

    //yuv420p   yyyy yyyy uu vv
    //一帧yuv420p   352 * 288  * 1.5 = 152064
    // y分量 :152064 * (8/12) = 152064 * 0.6666 = 101376
    // u分量 :152064 * (2/12) = 152064 * 0.1666 =  25344
    // v分量 :152064 * (2/12) = 152064 * 0.1666 =  25344
    // 字节流中存储样式 :
    // y1y2y3.....y101376 u1u2u3......u25344 v1v2v3......v25344
    
    
    一行有 352个 y, 总共有288行     352 * 288 = 101,376
    一行有 176个 u, 总共有144行     176 * 144 = 25,344
    一行有 176个 v, 总共有144行     176 * 144 = 25,344
    

    按理应该是

    // 写入Y平面
    outFile.write((char *) frame->data[0], 101376);
    // 写入U平面
    outFile.write((char *) frame->data[1], 25344);
    // 写入V平面
    outFile.write((char *) frame->data[2], 25344);
    

    找了半天的代码逻辑也没发现异常, 从源码上找

      ret = av_image_fill_linesizes(linesize, avctx->pix_fmt, w);
        if (ret < 0)
          goto fail;
      w += w & ~(w - 1);
    

    这里对w做了一次运算, 不知道为什么,可能是因为内存对齐的关系?或者是其他的关系? 也有可能是因为视频的编解码 跟 录制视频时候, 需要固定的分辨率搭配像素格式一样。 但是这里确实是一个坑, 不能随便拿一个视频就直接进行h264编码

    H264编解码 , 可能对分辨率的规格有做了什么限制,于是乎做了一个大胆的猜想
    352X288 YUV420p做一次音视频-像素格式转换, 转换为1280*720 YUV420p

    • 原始YUV :BigBuckBunny_CIF_24fps.yuv
    • 像素格式转换 : BigBuckBunny_CIF_24fps2.yuv
    • h264编码 : BigBuckBunny_CIF_24fps2.h264
    • h264解码 : BigBuckBunny_CIF_24fps2_h264_out.yuv

    得到的linesize数据

    frame->linesize[0] 1280
    frame->linesize[1] 640
    frame->linesize[2] 640
    frame->linesize[3] 0
    ctx->width 1280
    ctx->height 720
    

    命令行播放 :

    ffplay -video_size 1280X720 -pixel_format yuv420p -framerate 24 .\BigBuckBunny_CIF_24fps2_h264_out.yuv

    image.png

    相关文章

      网友评论

        本文标题:音视频-H264解码

        本文链接:https://www.haomeiwen.com/subject/kwzvbltx.html