Qt开发笔记之编码x264码流并封装mp4(三):Qt使用x264库对.yuv文件编码为.h264文件

简介: Qt开发笔记之编码x264码流并封装mp4(三):Qt使用x264库对.yuv文件编码为.h264文件

Qt开发笔记之编码x264码流并封装mp4

Qt开发笔记之编码h264码流并封装mp4(一):x264介绍、windows平台x264库编译

Qt开发笔记之编码h264码流并封装mp4(二):windows平台x264添加mp4支持,gpac库的介绍与编译

Qt开发笔记之编码h264码流并封装mp4(三):Qt使用x264库对.yuv文件编码为.h264文件

Qt开发笔记之编码h264码流并封装mp4(四):mp4v2库的介绍和windows平台编译

Qt开发笔记之编码h264码流并封装mp4(五):ubuntu平台编译x264》

Qt开发笔记之编码h264码流并封装mp4(六):ubuntu平台编译mp4v2并封装mp4

 

前言

      在Qt中使用x264库将.yuv文件编码为.h264文件。

 

YUV格式详解

概述

yuv是一种类似rgb的颜色模型,起源于黑白和彩电的过渡时期。其中Y代表亮度,uv组合起来可以表示色度。yuv信息只有y的信息就足以显示黑白的图片,yuv和YCbCr表示相同的东西,且Cb严格对应U,Cr严格对应V,yuv和rgb可以通过固定公式进行转换。

拓展

Yuv转rgb

Rgb转Yuv

 

H264编码

概述

      H.264是ITU(International Telecommunication Union,国际通信联盟)和MPEG(Motion Picture Experts Group,运动图像专家组)联合制定的视频编码标准。而x264是一个开源的H.264/MPEG-4 AVC视频编码函数库,是最好的有损视频编码器之一

功能

  • x264提供了一个命令行接口以及一组应用程序接口(API)。前者在许多图形用户界面软件中得到应用,如Staxrip和MeGUI。后者也在许多其他类型到软件中调用,如HandBrake和FFmpeg。
  • x264具备的H.264标准定义的特性,较之其他H.264编码器而言多出许多。
  • x264包含有一些心理视觉增强技术,以增强编码视频的主观质量。
  • x264能够在普通计算机上实现多路高清视频的实时编码。
  • x264也是第一个免费的蓝光视频编码器。2010年4月,x264项目组宣布蓝光格式视频编码功能完成,使得x264成为首个免费的蓝光视频编码器。

性价比

同样的片源、码率、编码器默认设置下,用x264编码出的视频(一般是mkv或者mp4格式)一定会比rmvb画质更好(264高级参数发挥的作用)。还有请扔掉“mkv版一定会很大,rmvb一定会很小”的错误观念,一般来说用于收藏的mkv版才会很大! H.264是一种编码方式,而RMVB是一种封装格式完全没有可比性 常见的编码方式和封装格式有

  • h263:FLV
  • x264:MP4/MKV/F4V
  • R10/R9/R40:RMVB/RM
  • XVID/DIVX:AVI

码率

码率就是数据传输时单位时间传送的数据位数,一般我们用的单位是kbps即千位每秒。通俗一点的理解就是取样率,单位时间内取样率越大,精度就越高,处理出来的文件就越接近原始文件,但是文件体积与取样率是成正比的,所以几乎所有的编码格式重视的都是如何用最低的码率达到最少的失真,围绕这个核心衍生出来的cbr(固定码率)与vbr(可变码率),都是在这方面做的文章,不过事情总不是绝对的,从音频方面来说,码率越高,被压缩的比例越小,音质损失越小,与音源的音质越接近。

“码率”就是失真度,码率越高越清晰,反之则画面粗糙而多马赛克。计算机中的信息都是二进制的0和1来表示,其中每一个0或1被称作一个位,用小写b表示,即bit(位);大写B表示byte,即字节,一个字节=八个位,即1B=8b;前面的大写K表示千的意思,即千个位(Kb)或千个字节(KB)。表示文件的大小单位,一般都使用字节(KB)来表示文件的大小。

Kbps:首先要了解的是,ps指的是/s,即每秒。Kbps指的是网络速度,也就是每秒钟传送多少个千位的信息(K表示千位,Kb表示的是多少千个位),为了在直观上显得网络的传输速度较快,一般公司都使用kb(千位)来表示,如果是KBps,则表示每秒传送多少千字节。1KBps=8Kbps。ADSL上网时的网速是512Kbps,如果转换成字节,就是512/8=64KBps(即64千字节每秒)。

帧数

帧数简单地说,帧数就是在1秒钟时间里传输的图片的帧数,也可以理解为图形处理器每秒钟能够刷新几次,通常用fps(Frames Per Second)表示。每一帧都是静止的图象,快速连续地显示帧便形成了运动的假象。高的帧率可以得到更流畅、更逼真的动画。每秒钟帧数 (fps) 愈多,所显示的动作就会愈流畅。一般来说30fps是可以接受的,所以要避免动作不流畅的最低fps是30。除了30fps外,有些计算机视频格式,例如AVI,每秒只能提供15帧。我们之所以能够利用摄像头来看到连续不断的影像,是因为影像传感器不断摄取画面并传输到屏幕上来,当传输速度达到一定的水平时,人眼就无法辨别画面之间的时间间隙,所以大家可以看到连续动态的画面。

每秒的帧数(fps)或者说帧率表示图形处理器场景时每秒钟能够更新几次。高的帧率可以得到更流畅、更逼真的动画。一般来说30fps就是可以接受的,但是将性能提升至60fps则可以明显提升交互感和逼真感,但是一般来说超过75fps一般就不容易察觉到有明显的流畅度提升了。如果帧率超过屏幕刷新率只会浪费图形处理的能力,因为显示器不能以这么快的速度更新,这样超过刷新率的帧率就浪费掉了。

一般用“FPS(frame per second,每秒钟画面更新的数量)”来表示该项指标。在欣赏电视、电影画面时,只要画面的刷新率达到24帧/秒,就能满足人们的需要。同样的,在玩普通的游戏时,如果刷新率达到24帧/秒即可,但在一些高速游戏中(例如射击游戏),如果画面的刷新率还是只有24帧/秒,那么就会感觉到画面比较迟钝,不够流畅。

  虽然理论上画面的刷新率越快越好,但过高的刷新率并没有实际意义——当画面的FPS达到60帧/秒时,已经能满足绝大部分应用需求。一般情况下,如果能够保证游戏画面的平均FPS能够达到30帧/秒,那么画面已经基本流畅;能够达到50帧/秒,就基本可以体会到行云流水的感觉了。一般人很难分辨出60帧/秒与100帧/秒有什么不同。

  既然刷新率越快越好,为什么还要强调没必要追求太高的刷新率呢?其中原因是在显示分辨率不变的情况下,FPS越高,则对显卡的处理能力要求越高。

  电脑中所显示的画面,都是由显卡来进行输出的,因此屏幕上每个像素的填充都得由显卡来进行计算、输出。当画面的分辨率是1024×768时,画面的刷新率要达到24帧/秒,那么显卡在一秒钟内需要处理的像素量就达到了“1024×768×24=18874368”。如果要求画面的刷新率达到50帧/秒,则数据量一下子提升到了“1024×768×50=39321600”。

  FPS与分辨率、显卡处理能力的关系如下:处理能力=分辨率×刷新率。这也就是为什么在玩游戏时,分辨率设置得越大,画面就越不流畅的原因了。

MPEG-1压缩(H264位MPEG-1的第十部分)

MPEG-1压缩的基本思想帧内压缩和帧间压缩。

其次,时间相关性的统计分析:统计的结果表明,在间隔1~2帧的图像中,各像素只有10%以下的点,其亮度差值变化超过2%,而色度差值的变化只有1%以下。

采用的压缩方法:分组,把几帧图像分为一组(GOP),为防止运动变化,帧数不宜取多。

定义帧:将每组内各帧图像定义为三种类型,即I帧、B帧和P帧;

预测帧:以I帧做为基础帧,以I帧预测P帧,再由I帧和P帧预测B帧;

数据传输:最后将I帧数据与预测的差值信息进行存储和传输。

IPB帧

I帧:帧内编码帧

I帧是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输,解码时仅用I帧的数据就可重构完整图像;

I帧特点:

  • I帧描述了图像背景和运动主体的详情;
  • I帧不需要参考其他画面而生;
  • I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);
  • I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧;
  • I帧不需要考虑运动矢量;
  • I帧所占数据的信息量比较大。

P帧:前向预测编码帧。

P帧的预测与重构:P帧是以I帧为参考帧,在I帧中找出P帧“某点”的预测值和运动矢量,取预测差值和运动矢量一起传送。在接收端根据运动矢量从I帧中找出P帧“某点”的预测值并与差值相加以得到P帧“某点”样值,从而可得到完整的P帧。

P帧特点:

  • P帧是I帧后面相隔1~2帧的编码帧;
  • P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差);
  • 解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像;
  • P帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧;
  • P帧可以是其后面P帧的参考帧,也可以是其前后的B帧的参考帧;
  • 由于P帧是参考帧,它可能造成解码错误的扩散;
  • 由于是差值传送,P帧的压缩比较高。

B帧:双向预测内插编码帧。

B帧的预测与重构:B帧以前面的I或P帧和后面的P帧为参考帧,“找出”B帧“某点”的预测值和两个运动矢量,并取预测差值和运动矢量传送。接收端根据运动矢量在两个参考帧中“找出(算出)”预测值并与差值求和,得到B帧“某点”样值,从而可得到完整的B帧。

B帧特点:

  • B帧是由前面的I或P帧和后面的P帧来进行预测的;
  • B帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量;
  • B帧是双向预测编码帧;
  • B帧压缩比最高,因为它只反映丙参考帧间运动主体的变化情况,预测比较准确;
  • B帧不是参考帧,不会造成解码错误的扩散。

注:I、B、P各帧是根据压缩算法的需要,是人为定义的,它们都是实实在在的物理帧,至于图像中的哪一帧是I帧,是随机的,一但确定了I帧,以后的各帧就严格按规定顺序排列。

 

Demo的编码转换效果对比

      原始文件为20250KB,使用x264库编码为h264后大小为150KB,压缩了约的大小99.7%。

      测试文件下载地址:https://download.csdn.net/download/qq21497936/12010079

 

关键代码

转换函数

bool X264Manager::testYuv2h264(QString yuvFile, QString destFile)
{
    bool ret = false;
    // 检测输入文件
    if(!yuvFile.endsWith(".yuv"))
    {
        qDebug() << __FILE__ << __LINE__ << "Failed to recgnize ext:" << yuvFile;
        return ret;
    }
    // 检测输出文件
    if(destFile.isEmpty())
    {
        destFile = yuvFile;
        destFile.truncate(destFile.lastIndexOf(".yuv"));
        destFile += ".h264";
    }else if(!destFile.endsWith(".h264"))
    {
        qDebug() << __FILE__ << __LINE__ << "Failed to recgnize ext:" << destFile;
        return ret;
    }
    qDebug() << __FILE__ << __LINE__ << yuvFile << "to" << destFile;
    // 打开输入文件
    FILE *fpSrc = fopen(yuvFile.toUtf8().data(), "rb");
    if(!fpSrc)
    {
        qDebug() << __FILE__ << __LINE__ << "Failed to open file:" << yuvFile;
    }
    // 打开输出文件
    FILE *fpDst = fopen(destFile.toUtf8().data(), "wb");
    if(!fpDst)
    {
        qDebug() << __FILE__ << __LINE__ << "Failed to open file:" << destFile;
    }
    // 编码参数
    int width = 640;                    // 宽度
    int height = 360;                   // 高度
    x264_param_t x264ParamT;            // 编码参数
    x264_picture_t x264PictureTIn;        // 帧缓存
    x264_picture_t x264PictureTOut;     // 帧缓存
    int inFrames = 0;
    x264_nal_t * pX264NalT;
    int x264NalT;
    // 给结构体赋默认值
    x264_param_default(&x264ParamT);
    // 初始化编码参数
    // 日志登记
    x264ParamT.i_log_level = X264_LOG_DEBUG;
    // 自动选择最佳前瞻线程缓冲区大小
    x264ParamT.i_threads  = X264_SYNC_LOOKAHEAD_AUTO;
    // 颜色深度
    x264ParamT.i_bitdepth = 8;
    // 输入为420
    x264ParamT.i_csp = X264_CSP_I420;
    // 宽度
    x264ParamT.i_width  = width;
    // 高度
    x264ParamT.i_height = height;
    // 设置帧率(分子)
    x264ParamT.i_fps_num = 25;
    // 设置帧率时间1s(分母)
    x264ParamT.i_fps_den = 1;
    //在此间隔设置IDR关键帧的数量
    x264ParamT.i_keyint_max = 10;
//     码率控制方法,CQP(恒定质量),CRF(恒定码率,缺省值23),ABR(平均码率)
//    x264ParamT.rc.i_rc_method = X264_RC_CQP;  // 设置后,会导致参数应用失败
    x264ParamT.rc.i_rc_method = X264_RC_CRF;  // 设置后,会导致参数应用失败 或者 编码器打开失败
//    x264ParamT.rc.i_rc_method = X264_RC_CQP;  // 设置后,会导致参数应用失败
    // 应用参数
    if(x264_param_apply_profile(&x264ParamT, "baseline") < 0)
    {
        qDebug() << __FILE__ << __LINE__ << "Failed to x264_param_apply_profile";
        return ret;
    }
    // 初始化删除
    x264_picture_init(&x264PictureTOut);
    // 分配帧缓存
    if(x264_picture_alloc(&x264PictureTIn, x264ParamT.i_csp, x264ParamT.i_width, x264ParamT.i_height) < 0)
    {
        qDebug() << __FILE__ << __LINE__ << "Failed to x264_picture_alloc";
        return ret;
    }
    // 获取编码器
    x264_t *pX264T = 0;
    pX264T = x264_encoder_open(&x264ParamT);
    if(!pX264T)
    {
        qDebug() << __FILE__ << __LINE__ << "Failed to x264_encoder_open";
        x264_picture_clean(&x264PictureTIn);
        return ret;
    }
    // 总点大小
    int framesPixelCount = x264ParamT.i_width * x264ParamT.i_height;
    // 判断帧的数量(文件大小/每帧大小)
    if(inFrames == 0)
    {
        fseek(fpSrc, 0, SEEK_END);
        switch (x264ParamT.i_csp)
        {
        case X264_CSP_I444:
            inFrames = ftell(fpSrc) / (framesPixelCount * 3);
            break;
        case X264_CSP_I422:
            inFrames = ftell(fpSrc) / (framesPixelCount * 2);
            break;
        case X264_CSP_I420:
            inFrames = ftell(fpSrc) / (framesPixelCount * 3 / 2);
            break;
        default:
            break;
        }
        fseek(fpSrc, 0, SEEK_SET);
    }
    // 循环编码
    for(int index = 0; index < inFrames; index++)
    {
        switch (x264ParamT.i_csp)
        {
        case X264_CSP_I444:
            fread(x264PictureTIn.img.plane[0], framesPixelCount, 1, fpSrc);   // Y
            fread(x264PictureTIn.img.plane[1], framesPixelCount, 1, fpSrc);   // U
            fread(x264PictureTIn.img.plane[2], framesPixelCount, 1, fpSrc);   // V
            break;
        case X264_CSP_I422:
            {
                int i = 0;
                int y_i=0,u_i=0,v_i=0;
                for(i = 0 ; i < framesPixelCount * 2 ;)
                {
                    fread(&x264PictureTIn.img.plane[0][y_i++], 1, 1, fpSrc);   //Y
                    index++;
                    fread(&x264PictureTIn.img.plane[1][u_i++], 1, 1, fpSrc);   //U
                    index++;
                    fread(&x264PictureTIn.img.plane[0][y_i++], 1, 1, fpSrc);   //Y
                    index++;
                    fread(&x264PictureTIn.img.plane[2][v_i++], 1, 1, fpSrc);   //V
                    index++;
                }break;
            }
            break;
        case X264_CSP_I420:
            fread(x264PictureTIn.img.plane[0], framesPixelCount,   1, fpSrc);   // Y
            fread(x264PictureTIn.img.plane[1], framesPixelCount/4, 1, fpSrc);   // U
            fread(x264PictureTIn.img.plane[2], framesPixelCount/4, 1, fpSrc);   // V
            break;
        default:
            break;
        }
        // 当前帧数
        x264PictureTIn.i_pts = index;
        qDebug() << __FILE__ << __LINE__ << "Succeed to get frame:" << index << "/" << inFrames;
        int iRet = x264_encoder_encode(pX264T, &pX264NalT, &x264NalT, &x264PictureTIn, &x264PictureTOut);
        if(iRet < 0)
        {
            qDebug() << __FILE__ << __LINE__ << "Failed to x264_encoder_encode";
            return ret;
        }
        qDebug() << __FILE__ << __LINE__ << "Succeed to encode frame:" << index;
#if 1
        // 此处是一边拿到编码器一边编码写入到文件中
        int i = 0;
        qDebug() << __FILE__ << __LINE__ << i << inFrames << iRet;
        // 编码器编码存入文件
        for(int j = 0; j < x264NalT; j++)
        {
            fwrite(pX264NalT[j].p_payload, 1, pX264NalT[j].i_payload, fpDst);
        }
#endif
    }
#if 0
    // 此处是全部拿到编码器里面后,再循环拿取所有,然后写入
    int i = 0;
    while(true)
    {
        int iRet = x264_encoder_encode(pX264T, &pX264NalT, &x264NalT, NULL, &x264PictureTOut);
        if(iRet == 0)
        {
            break;
        }
        qDebug() << __FILE__ << __LINE__ << i << inFrames << iRet;
        // 编码器编码存入文件
        for(int j = 0; j < x264NalT; j++)
        {
            fwrite(pX264NalT[j].p_payload, 1, pX264NalT[j].i_payload, fpDst);
        }
        i++;
    }
#endif
    qDebug() << __FILE__ << __LINE__ << "It's finished!!!";
    x264_picture_clean(&x264PictureTIn);
    x264_encoder_close(pX264T);
    pX264T = 0;
    fclose(fpSrc);
    fclose(fpDst);
}

 

工程模板v1.1.0

      Windows下x264的工程模板V1.1,0

 

参考

https://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.html


相关文章
|
5月前
|
数据安全/隐私保护 C++ 计算机视觉
Qt(C++)开发一款图片防盗用水印制作小工具
文本水印是一种常用的防盗用手段,可以将文本信息嵌入到图片、视频等文件中,用于识别和证明文件的版权归属。在数字化和网络化的时代,大量的原创作品容易被不法分子盗用或侵犯版权,因此加入文本水印成为了保护原创作品和维护知识产权的必要手段。 通常情况下,文本水印可以包含版权声明、制作者姓名、日期、网址等信息,以帮助识别文件的来源和版权归属。同时,为了增强防盗用效果,文本水印通常会采用字体、颜色、角度等多种组合方式,使得水印难以被删除或篡改,有效地降低了盗用意愿和风险。 开发人员可以使用图像处理技术和编程语言实现文本水印的功能,例如使用Qt的QPainter类进行文本绘制操作,将文本信息嵌入到图片中,
199 1
|
4月前
【qt】如何读取文件并拆分信息?
【qt】如何读取文件并拆分信息?
48 0
|
4月前
|
监控 C++ 容器
【qt】MDI多文档界面开发
【qt】MDI多文档界面开发
118 0
|
5月前
|
C++
基于QT实现的拷贝文件以及实时进度条(简易版)
1.基于按钮或者菜单栏的槽里去写逻辑函数(我这边用的是菜单栏),ui实现的进度条 2.创建两个对象,一个是源文件,一个是目标文件分别用getopenfileName、getsavefileName函数即可。 3.利用QFile类去实现对两个文件的创建,因为QFile中可以获取文件的属性已经读写等。 4.循环的去读取源文件中的数据,然后写入目标文件
566 6
|
5月前
|
Linux iOS开发 开发者
Qt问题(二):无法定位程序输入点于动态链接库
动态链接库(Dynamic Link Library,简称DLL)是一种可执行文件格式,常见于Windows操作系统中,而在Linux和macOS等其他操作系统中,相似的概念通常被称为共享库(Shared Library)。动态链接库允许程序在运行时加载所需的代码和数据,而不是在编译时静态链接到应用程序中。这种方式带来了几个重要的优点:
526 3
|
3月前
|
开发工具 C++
qt开发技巧与三个问题点
本文介绍了三个Qt开发中的常见问题及其解决方法,并提供了一些实用的开发技巧。
|
3月前
|
4月前
|
C++
C++ Qt开发:QUdpSocket网络通信组件
QUdpSocket是Qt网络编程中一个非常有用的组件,它提供了在UDP协议下进行数据发送和接收的能力。通过简单的方法和信号,可以轻松实现基于UDP的网络通信。不过,需要注意的是,UDP协议本身不保证数据的可靠传输,因此在使用QUdpSocket时,可能需要在应用层实现一些机制来保证数据的完整性和顺序,或者选择在适用的场景下使用UDP协议。
206 2
Qt开发网络嗅探器02
Qt开发网络嗅探器02
|
4月前
|
存储 运维 监控
Qt开发网络嗅探器01
Qt开发网络嗅探器01