音视频-H264解码

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容