音视频-H264编码

H264编码原理和音视频-AAC编码原理几乎一样, 不同的是就buffer缓冲区的处理, 编码的事情都是通过H264编码器去实现

AAC编码的简略逻辑 :

源文件 ==》 AVFrame ==》编码器 ==》AVPacket ==> 输出文件

H264编码的简略逻辑

源文件 ==》 AVFrame ==》编码器 ==》AVPacket ==> 输出文件

是的, 两个是一样的。不同的是, YUV是以一帧一帧的数据读取。

#include "h264encodethread.h"
#include <QDebug>
#include <QFile>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/frame.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_YUV_FILEPATH "G:/BigBuckBunny_CIF_24fps.yuv"
    #define OUT_H264_FILEPATH "G:/BigBuckBunny_CIF_24fps_h264.h264"
    #define YUV_VIDEO_SIZE_WIDTH 352
    #define YUV_VIDEO_SIZE_HEIGH 288
#else
    #define IN_YUV_FILEPATH "/Users/liliguang/Desktop/in.yuv"
    #define OUT_H264_FILEPATH "/Users/liliguang/Desktop/out.h264"
    #define YUV_VIDEO_SIZE_WIDTH 352
    #define YUV_VIDEO_SIZE_HEIGH 288
#endif



H264EncodeThread::H264EncodeThread(QObject *parent) : QThread(parent) {
    // 当监听到线程结束时(finished),就调用deleteLater回收内存
    connect(this, &H264EncodeThread::finished,
            this, &H264EncodeThread::deleteLater);
}

H264EncodeThread::~H264EncodeThread() {
    // 断开所有的连接
    disconnect();
    // 内存回收之前,正常结束线程
    requestInterruption();
    // 安全退出
    quit();
    wait();
    qDebug() << this << "析构(内存被回收)";
}

/* check that a given pix_format is supported by the encoder */
static int check_pixel_fmt(const AVCodec *codec,
                           enum AVPixelFormat pix_fmt) {
    const enum AVPixelFormat *p = codec->pix_fmts;
    while (*p != AV_PIX_FMT_NONE) {
        if (*p == pix_fmt) {
            return 1;
        }
        p++;
    }
    return 0;
}


// H264编码
// 返回负数:中途出现了错误
// 返回0:编码操作正常完成
static int encode(AVCodecContext *ctx,
                  AVFrame *frame,
                  AVPacket *pkt,
                  QFile &outFile) {
    // 发送数据到编码器
    int ret = avcodec_send_frame(ctx, frame);
    if (ret < 0) {
        ERROR_BUF(ret);
        qDebug() << "avcodec_send_frame error" << errbuf;
        return ret;
    }

    // 不断从编码器中取出编码后的数据
    // while (ret >= 0)
    while (true) {
        ret = avcodec_receive_packet(ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            //  output is not available in the current state - user must try to send input
            // 继续读取数据到frame,然后送到编码器
            return 0;
        } else if (ret < 0) { // 其他错误
            return ret;
        }
        // 成功从编码器拿到编码后的数据
        // 将编码后的数据写入文件
        outFile.write((char *) pkt->data, pkt->size);

        // 释放pkt内部的资源
        av_packet_unref(pkt);
    }

}




void H264EncodeThread::run() {
    // H264编码的思路和Acc编码的思路差不多
    // 读取文件内容 -> buffer缓冲区 -> 一帧数据 -> 核心函数编码 -> 输出缓冲区 -> 输出文件

    qDebug() << "H264EncodeThread run ";

    // 输入输出文件
    const char *infilename;
    const char *outfilename;

    // 编码器
    const AVCodec *codec;
    // 编码器上下文
    AVCodecContext *codecCtx = nullptr;
    // 源文件数据源存储结构指针
    AVFrame *frame = nullptr;
    // 编码文件数据源存储结构指针
    AVPacket *pkt = nullptr;

    int check_pixel_fmt_Ret;
    int avcodec_open2_Ret;
    int av_image_alloc_ret;

    int infileOpen_Ret;
    int outfileOpen_Ret;

    int readFile_Ret;
    int encode_ret;

    int ptsIndex = 0;
    int imageSize = YUV_VIDEO_SIZE_WIDTH * YUV_VIDEO_SIZE_HEIGH * 1.5;

    infilename = IN_YUV_FILEPATH;
    outfilename = OUT_H264_FILEPATH;

    QFile inFile(infilename);
    QFile outFile(outfilename);


    // 编码器
    codec = avcodec_find_encoder_by_name("libx264"); // 用查找编码器的名称的方式。 默认的可能找到的不一样
    CHECK_IF_ERROR_BUF_END(!codec, "avcodec_find_encoder");

    // 创建编码器上下文
    codecCtx = avcodec_alloc_context3(codec);
    CHECK_IF_ERROR_BUF_END(!codecCtx, "avcodec_alloc_context3");

    // 设置编码器上下文对应信息
    codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
    codecCtx->width = YUV_VIDEO_SIZE_WIDTH;
    codecCtx->height = YUV_VIDEO_SIZE_HEIGH;
    // 这里要计算24 pfs
    // fps = codecCtx->time_base.den / codecCtx->time_base.num
    // 24 = 24 / 1
    codecCtx->time_base.num = 1;       //分子
    codecCtx->time_base.den = 24;         //分母

    // 检查编码器支持的样本格式
    check_pixel_fmt_Ret = check_pixel_fmt(codec, codecCtx->pix_fmt);
    CHECK_IF_ERROR_BUF_END(!check_pixel_fmt_Ret, "check_sample_fmt");

    // 打开编码器
    avcodec_open2_Ret = avcodec_open2(codecCtx, codec, nullptr);
    CHECK_IF_ERROR_BUF_END(avcodec_open2_Ret, "avcodec_open2");

    // 打开源文件
    infileOpen_Ret = !inFile.open(QFile::ReadOnly);
    CHECK_IF_ERROR_BUF_END(infileOpen_Ret, "sourceFile.open");

    // 打开源文件
    outfileOpen_Ret = !outFile.open(QFile::WriteOnly);
    CHECK_IF_ERROR_BUF_END(outfileOpen_Ret, "sourceFile.outFile");

    // 创建输出Packet
    pkt = av_packet_alloc();
    CHECK_IF_ERROR_BUF_END(!pkt, "av_packet_alloc");

    // 创建AVFrame结构体本身
    frame = av_frame_alloc();
    CHECK_IF_ERROR_BUF_END(!frame, "av_frame_alloc");

    // 为音频或视频数据分配新的缓冲区。
    // 在调用此函数之前,必须在框架上设置以下字段:
    // - 格式(视频的像素格式,音频的样本格式)
    // - 视频的宽度和高度
    // 设置frame必要信息
    frame->format = codecCtx->pix_fmt;//像素格式
    frame->width = codecCtx->width;//分辨率宽
    frame->height = codecCtx->height; //分辨率高

    // 创建一帧的buffer缓冲区
    av_image_alloc_ret = av_image_alloc(frame->data, frame->linesize, frame->width, frame->height, (AVPixelFormat)frame->format, 16);
    CHECK_IF_ERROR_BUF_END(av_image_alloc_ret < 0, "av_image_alloc");

    // 编码
    // 源文件 ==> (AVFrame)输入缓冲区 ==> 编码器 ==> (AVPacket)输出缓冲区 ==> 输出文件
    // 这里应该读取一帧的大小
    while( (readFile_Ret = inFile.read((char *)frame->data[0], imageSize )) > 0 ) {
        frame->pts = ptsIndex++;
        // 编码
        encode_ret = encode(codecCtx, frame, pkt, outFile);
        CHECK_IF_ERROR_BUF_END(encode_ret < 0, 
    }

    // 在读取最后一次, 冲刷缓冲区
    encode(codecCtx, nullptr, pkt, outFile);

end:
    // 关闭文件
    inFile.close();
    outFile.close();

    // 释放资源
    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&codecCtx);
    qDebug() << "AACEncodeThread end ";
}

注意点, 创建buffer缓冲区, 和音频的有点不一样,使用av_image_alloc

av_image_alloc_ret = av_image_alloc(frame->data, frame->linesize, frame->width, frame->height, (AVPixelFormat)frame->format, 16);

分别用命令行和程序运行进行H264编码

ffmpeg -video_size 352x288 -pixel_format yuv420p -framerate 24 -i .\BigBuckBunny_CIF_24fps.yuv -c:v libx264 .\ffmpeg_h264_out.h264

# -c:v libx264是指定使用libx264作为编码器

大小一致, 证明代码思路是没有问题, 现在再接着用ffplay播放一下

ffplay .\ffmpeg_h264_out.h264

ffmpeg_h264_out.h264

ffplay .\BigBuckBunny_CIF_24fps_h264.h264

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