• Ubuntu18使用FFMPEG实现QSV硬解 转载文章


    前言
    由于项目需要,需要在一块I7-8850H上进行H264解码成YUV并显示的功能。由于系统是Ubuntu18,故打算使用QT+FFMPEG来实现。先前的一路软解发现CPU占用率去到了20%以上,我们需要同时进行四路解码,这个占用率是无法接受的,故打算使用FFMPEG进行硬解。由于只有I7的集显,所以只能使用QSV。前面已经完成了环境的安装(具体教程在Ubuntu18上安装QSV+FFMPEG环境 ) ,此文章将展示如何在QT中实现ffmpeg的硬解,使用的库的路径全部基于安装教程里面的路径和版本。此教程假设观看者有一定的C++和QT开发经验。

    使用的Qt Creator版本信息如下图


    一、.pro文件的配置
    在.pro文件添加includpath跟lib,之所以添加这么多,是因为有各自库之间有相互引用,添加少了虽然不报错,但是解不了码。

    INCLUDEPATH += /opt/intel/mediasdk/include
    INCLUDEPATH += /usr/local/include
    INCLUDEPATH += /opt/intel/mediasdk/include/mfx
    INCLUDEPATH +=/usr/include/x86_64-linux-gnu/

    LIBS += -Llibavcodec -Llibavdevice -Llibavfilter -Llibavformat -Llibavutil -Llibpostproc -Llibswscale -Llibswresample -lavdevice
    LIBS += -lavfilter -lavformat -lavcodec -lswresample -lswscale -lavutil -lm -lxcb -lxcb-shm -lxcb-shape -lxcb-xfixes -pthread -lm
    LIBS += -L/usr/local/lib -lva -L/opt/intel/mediasdk/lib -lmfx -lstdc++ -ldl -lm -lbz2 -lz -pthread -lm -lz -L/usr/local/lib -lva
    LIBS += -L/opt/intel/mediasdk/lib -lmfx -lstdc++ -ldl -lm -lm -pthread -L/usr/local/lib -lva-drm -lva -L/usr/local/lib -lva-x11 -lva -lm
    LIBS += -L/opt/intel/mediasdk/lib -lmfx -lstdc++ -ldl -L/usr/local/lib -lva -lX11


    二、具体代码实现
    1、创建一个HW_H264Decoder

    2、在.h文件里添加ffmpeg的头文件引用

    #include <stdio.h>
    #include <string>
    #include <unistd.h>
    extern "C" {
    #include "libavformat/avformat.h"
    #include "libavformat/avio.h"
    #include "libavcodec/avcodec.h"
    #include "libavutil/buffer.h"
    #include "libavutil/error.h"
    #include "libavutil/hwcontext.h"
    #include "libavutil/hwcontext_qsv.h"
    #include "libavutil/mem.h"
    #include "libavutil/imgutils.h"
    #include <libswscale/swscale.h>
    #include <libavutil/imgutils.h>
    }
    ffmpeg都是用C写的,连官方demo都是C,所以导致在C++中,include的时候需要用extern "C" 把ffmpeg的头文件引用给包起来,不然编译的时候会报错。

    3、在.h文件里定义几个需要用到方法

    /**
    * 初始化
    *
    * @param width : 视频的宽度
    * @param height : 视频的高度
    *
    * @return 错误代码 0:成功
    *
    **/
    int init(int width,int height);
    /**
    * 传入H264数据,并获取解码后的NV12数据 (QSV好像只能解成NV12)
    *
    * @param h264Data : H264数据
    * @param size : H264数据的长度
    * @param nv12Data:存放NV12数据的缓冲
    *
    * @return NV12数据的长度
    *
    **/
    int decodeToNV12(uint8_t *h264Data,int size,unsigned char *nv12Data);
    /**
    * 释放资源
    *
    **/
    void relese();
    4、在.h文件里面定义需要用到的类变量(个人习惯,可不在.h里定义)

    //解码需要用到的上下文
    AVCodecContext *decoder_ctx = NULL;
    //解码器对象
    const AVCodec *decoder;
    //放H264数据包的对象
    AVPacket *pkt;
    //指向GPU内存的帧对象
    AVFrame *frame = NULL;
    //指向CPU内存的帧对象
    AVFrame *sw_frame = NULL;
    //解码使用的buffer指针
    AVBufferRef *device_ref = NULL;
    //存放sps的数组
    char sps[50] = {0x00,0x00,0x00,0x01,0x67,0x4d,0x00,0x1f,(char)0xe9,(char)0x80,(char)0xa0,0x0b,0x74,(char)0xa4,0x14,0x18,0x18,0x1b,0x42,(char)0x84,(char)0xa7};
    //存放PPS的数组
    char pps[50] = {0x00,0x00,0x00,0x01,0x68,(char)0xee,0x06,(char)0xe2};
    //sps的长度
    int spsLen=0;
    //pps的长度
    int ppsLen=0;
    5、在.cpp文件中实现 init方法,实现对ffmpeg解码相关操作的初始化

    static AVPixelFormat get_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts)
    {
    while (*pix_fmts != AV_PIX_FMT_NONE) {
    if (*pix_fmts == AV_PIX_FMT_QSV) {
    return AV_PIX_FMT_QSV;
    }

    pix_fmts++;
    }

    fprintf(stderr, "The QSV pixel format not offered in get_format()\n");

    return AV_PIX_FMT_NONE;
    }

    int HW_H264Decoder::init(int width,int height){
    int ret;
    /* 打开QSV设备 */
    ret = av_hwdevice_ctx_create(&device_ref, AV_HWDEVICE_TYPE_QSV,"auto", NULL, 0);
    /* 检查下是否打开成功,失败则直接跳出去 */
    if (ret < 0) {
    fprintf(stderr, "Cannot open the hardware device\n");
    return -1;
    }
    /* 打开解码器,这里是使用qsv对H264进行硬解,故使用h264_qsv */
    decoder = avcodec_find_decoder_by_name("h264_qsv");
    /* 检查下是否打开成功,失败则直接跳出去 */
    if (!decoder) {
    fprintf(stderr, "The QSV decoder is not present in libavcodec\n");
    return -1;
    }
    /* 根据解码器初始化解码器的上下文 */
    decoder_ctx = avcodec_alloc_context3(decoder);
    /* 检查下是否初始化打开成功,失败则直接跳出去 */
    if (!decoder_ctx) {
    return -1;
    }
    /* 设置下解码类型 */
    decoder_ctx->codec_id = AV_CODEC_ID_H264;
    /* 开辟解码器使用的缓冲 */
    decoder_ctx->hw_device_ctx = av_buffer_ref(device_ref);
    /* 设置下QSV格式 之所以这么写,是为了确定是否有QSV */
    decoder_ctx->get_format = get_format;
    /* 把解码器的上下文跟解码器绑定并初始化 */
    ret = avcodec_open2(decoder_ctx, decoder, NULL);
    /* 检查下是否初始化成功,失败则直接跳出去 */
    if (ret < 0) {
    fprintf(stderr, "Error opening the decoder: ");
    return -1;
    }
    /* 初始化用到的帧对象 */
    frame = av_frame_alloc();
    sw_frame = av_frame_alloc();
    /* 初始化存放H264包的对象 */
    pkt = av_packet_alloc();
    /* 检查下是否初始化成功,失败则直接跳出去 */
    if (!frame || !sw_frame || !pkt) {
    ret = AVERROR(ENOMEM);
    return -1;
    }
    return 0;
    }
    5、在.cpp文件中实现decode方法,实现对传入H264数据后解码成NV12数据出来

    int HW_H264Decoder::decodeToNV12(uint8_t *h264Data, int size, unsigned char *nv12Data){
    int ret = 0;
    /* 由于I帧需要在前面家SPSPPS 不然会出现无法解码的情况,故开辟个临时缓冲区 */
    uint8_t *h264DataTemp = NULL;
    /* 如果是SPS 则存起来,不喂给解码器,不然会导致解不出东西 */
    if(h264Data[4] == 0x67){
    memcpy(sps,h264Data,size);
    spsLen = size;
    return 0;
    }
    /* 如果是pps 则存起来,不喂给解码器,不然会导致解不出东西 */
    if(h264Data[4] == 0x68){
    memcpy(pps,h264Data,size);
    ppsLen = size;
    return 0;
    }
    /* 判断下是否是I帧 是I帧的话需要在前面加上spspps,不然会导致解不出东西 */
    if(h264Data[4] == 0x65){
    /* 现在是I帧,需要加上SPSPPS,故计算下加上spspps之后的长度是多少 */
    int spsppsIframeLen = size+spsLen+ppsLen;
    /* 开辟出需要的内存 */
    h264DataTemp = new uint8_t[spsppsIframeLen];
    /* 先复制SPS进去 */
    memcpy(h264DataTemp,sps,spsLen);
    /* 再复制PPS进去 */
    memcpy(h264DataTemp+spsLen,pps,ppsLen);
    /* 最后再把I帧复制进去 */
    memcpy(h264DataTemp+spsLen+ppsLen,h264Data,size);
    /* 把pkt对象的数据指针直接指向整理好的加上了spspps的数据 */
    pkt->data = h264DataTemp;
    pkt->size = spsppsIframeLen;

    }else{
    /* 不是I帧 直接开辟出传进来的H264数据大小的内存就行了 */
    h264DataTemp = new uint8_t[size];
    /* 把H264数据复制进去 */
    memcpy(h264DataTemp,h264Data,size);
    /* 把pkt对象的数据指针直接指向H264数据 */
    pkt->data = h264DataTemp;
    pkt->size = size;
    }
    /* 定义解码后的数据的长度 */
    int outputSize = 0;
    /* 把H264数据喂给解码器 */
    ret = avcodec_send_packet(decoder_ctx, pkt);
    /* 判断下是否喂成功了,失败则直接退出 */
    if (ret < 0) {
    fprintf(stderr, "Error during decoding str:%s \n",strerror(errno));
    return ret;
    }
    /* 循环去读是否有已经解完码的数据 */
    while (ret >= 0) {
    int i, j;
    /* 读取已经解完码的数据 */
    ret = avcodec_receive_frame(decoder_ctx, frame);
    /* 如果是没有数据或者需要新的输入才能解码 则直接退出循环 */
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
    break;
    else if (ret < 0) {
    /* 还在解码中 直接退出等下次喂完再看看 */
    fprintf(stderr, "Error during decoding\n");
    return ret;
    }
    /* 拿到解完码的数据对象frame了,但是这份数据不在CPU里,需要av_hwframe_transfer_data转到CPU里,然后赋值给sw_frame */
    ret = av_hwframe_transfer_data(sw_frame, frame, 0);
    /* 看看是否转成功了,失败则退出 */
    if (ret < 0) {
    fprintf(stderr, "Error transferring the data to system memory\n");
    return ret;
    }

    /* 把NV12数据复制到给定的内存空间,NV12数据在AVFrame中的存储存储方式为->data[0]里面存着Y分量 ->data[1]厘米存着UV分量 */
    for (i = 0; i < FF_ARRAY_ELEMS(sw_frame->data) && sw_frame->data[i]; i++){
    /* 把数据全部读出来 */
    for (j = 0; j < (sw_frame->height >> (i > 0)); j++){
    memcpy(nv12Data+writeLen,sw_frame->data[i] + j * sw_frame->linesize[i], sw_frame->width);
    /* 复制出来的长度需要累计,最后返回给调用者,才知道解码出来的数据大小 */
    writeLen+=sw_frame->width;
    }
    }

    }
    outputSize = writeLen;
    /* 释放一下内存 */
    av_packet_unref(pkt);
    delete []h264DataTemp;

    return outputSize;
    }
    6、在.cpp文件中实现relese方法,销毁释放内存

    void HW_H264Decoder::relese(){
    av_frame_free(&frame);
    av_frame_free(&sw_frame);
    avcodec_free_context(&decoder_ctx);
    av_packet_free(&pkt);
    av_buffer_unref(&device_ref);;
    }
    至此,通过FFMPEG实现QSV硬解的相关代码就写完了。

    下面是完整的.h跟.cpp文件

    hw_h264decoder.h

    #ifndef HW_H264DECODER_H
    #define HW_H264DECODER_H

    #include <stdio.h>
    #include <string>
    #include <unistd.h>
    extern "C" {
    #include "libavformat/avformat.h"
    #include "libavformat/avio.h"
    #include "libavcodec/avcodec.h"

    #include "libavutil/buffer.h"
    #include "libavutil/error.h"
    #include "libavutil/hwcontext.h"
    #include "libavutil/hwcontext_qsv.h"
    #include "libavutil/mem.h"
    #include "libavutil/imgutils.h"
    #include <libswscale/swscale.h>
    #include <libavutil/imgutils.h>

    }


    class HW_H264Decoder
    {
    public:
    HW_H264Decoder();
    ~HW_H264Decoder();
    /**
    * 初始化
    *
    * @param width : 视频的宽度
    * @param height : 视频的高度
    *
    * @return 错误代码 0:成功
    *
    **/
    int init(int width,int height);
    /*************************************************
    Function:decode 转成NV12再转成RGB32的数据
    Description:初始化
    Input:h264Data-H264图像数据
    Return:错误代码
    Others:无
    *************************************************/
    int decodeToRGB32(uint8_t *h264Data,int size,unsigned char *buf);
    /**
    * 传入H264数据,并获取解码后的NV12数据 (QSV好像只能解成NV12)
    *
    * @param h264Data : H264数据
    * @param size : H264数据的长度
    * @param nv12Data:存放NV12数据的缓冲
    *
    * @return NV12数据的长度
    *
    **/
    int decodeToNV12(uint8_t *h264Data,int size,unsigned char *nv12Data);
    /**
    * 释放资源
    *
    **/
    void relese();

    private:
    //解码需要用到的上下文
    AVCodecContext *decoder_ctx = NULL;
    //解码器对象
    const AVCodec *decoder;
    //放H264数据包的对象
    AVPacket *pkt;
    //指向GPU内存的帧对象 指向CPU内存的帧对象 存储RGB32数据的帧对象
    AVFrame *frame = NULL, *sw_frame = NULL,*frameRGB = NULL;
    //解码使用的buffer指针
    AVBufferRef *device_ref = NULL;
    //输出的图像数据的大小
    int outImgSize;
    //转换格式使用的内存
    unsigned char *out_buffer;
    //用于视频图像转换的对象
    struct SwsContext *img_convert_ctx;
    //sps 这里先设置测试用的数据的SPS
    char sps[50] = {0x00,0x00,0x00,0x01,0x67,0x4d,0x00,0x1f,(char)0xe9,(char)0x80,(char)0xa0,0x0b,0x74,(char)0xa4,0x14,0x18,0x18,0x1b,0x42,(char)0x84,(char)0xa7};
    //pps 这里先设置测试用的数据的pps
    char pps[50] = {0x00,0x00,0x00,0x01,0x68,(char)0xee,0x06,(char)0xe2};
    //拿到的sps的长度
    int spsLen=0;
    //拿到的pps的长度
    int ppsLen=0;

    };

    #endif // HW_H264DECODER_H

    hw_h264decoder.cpp

    #include "hw_h264decoder.h"



    static AVPixelFormat get_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts)
    {
    while (*pix_fmts != AV_PIX_FMT_NONE) {
    if (*pix_fmts == AV_PIX_FMT_QSV) {
    return AV_PIX_FMT_QSV;
    }

    pix_fmts++;
    }

    fprintf(stderr, "The QSV pixel format not offered in get_format()\n");

    return AV_PIX_FMT_NONE;
    }

    HW_H264Decoder::HW_H264Decoder()
    {

    }
    HW_H264Decoder::~HW_H264Decoder()
    {

    }


    int HW_H264Decoder::init(int width,int height){
    int ret;
    /* 打开QSV设备 */
    ret = av_hwdevice_ctx_create(&device_ref, AV_HWDEVICE_TYPE_QSV,"auto", NULL, 0);
    /* 检查下是否打开成功,失败则直接跳出去 */
    if (ret < 0) {
    fprintf(stderr, "Cannot open the hardware device\n");
    return -1;

    }
    /* 打开解码器,这里是使用qsv对H264进行硬解,故使用h264_qsv */
    decoder = avcodec_find_decoder_by_name("h264_qsv");
    /* 检查下是否打开成功,失败则直接跳出去 */
    if (!decoder) {
    fprintf(stderr, "The QSV decoder is not present in libavcodec\n");
    return -1;
    }
    /* 根据解码器初始化解码器的上下文 */
    decoder_ctx = avcodec_alloc_context3(decoder);
    /* 检查下是否初始化打开成功,失败则直接跳出去 */
    if (!decoder_ctx) {
    return -1;
    }
    /* 设置下解码类型 */
    decoder_ctx->codec_id = AV_CODEC_ID_H264;
    /* 开辟解码器使用的缓冲 */
    decoder_ctx->hw_device_ctx = av_buffer_ref(device_ref);
    /* 设置下QSV格式 之所以这么写,是为了确定是否有QSV */
    decoder_ctx->get_format = get_format;
    /* 虽然说QSV只有解出NV12 但是这里设置一下好了 */
    decoder_ctx->pix_fmt = AV_PIX_FMT_NV12;
    /* 把解码器的上下文跟解码器绑定并初始化 */
    ret = avcodec_open2(decoder_ctx, decoder, NULL);
    /* 检查下是否初始化成功,失败则直接跳出去 */
    if (ret < 0) {
    fprintf(stderr, "Error opening the decoder: ");
    return -1;
    }
    /* 初始化用到的帧对象 */
    frame = av_frame_alloc();
    sw_frame = av_frame_alloc();
    frameRGB = av_frame_alloc();
    /* 初始化存放H264包的对象 */
    pkt = av_packet_alloc();
    /* 检查下是否初始化成功,失败则直接跳出去 */
    if (!frame || !sw_frame || !frameRGB || !pkt) {
    ret = AVERROR(ENOMEM);
    return -1;
    }

    // 创建动态内存,创建存储RGB32图像数据的空间(av_image_get_buffer_size获取一帧图像需要的大小)
    outImgSize = av_image_get_buffer_size(AV_PIX_FMT_RGB32, width, height, 1);
    out_buffer = (unsigned char *)av_malloc(outImgSize);
    // 存储一帧像素数据缓冲区
    av_image_fill_arrays(frameRGB->data, frameRGB->linesize, out_buffer,AV_PIX_FMT_RGB32, width,height, SWS_FAST_BILINEAR );
    // 初始化img_convert_ctx结构 配置转换成RGB32
    img_convert_ctx = sws_getContext(width,height, decoder_ctx->pix_fmt,width, height, AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr);


    return 0;
    }

    void HW_H264Decoder::relese(){
    av_frame_free(&frame);
    av_frame_free(&sw_frame);
    av_frame_free(&frameRGB);
    avcodec_free_context(&decoder_ctx);
    av_packet_free(&pkt);
    av_buffer_unref(&device_ref);
    sws_freeContext(img_convert_ctx);
    }

    int HW_H264Decoder::decodeToRGB32(uint8_t *h264Data,int size,unsigned char *buf){
    int ret = 0;
    /* 由于I帧需要在前面家SPSPPS 不然会出现无法解码的情况,故开辟个临时缓冲区 */
    uint8_t *h264DataTemp = NULL;
    /* 如果是SPS 则存起来,不喂给解码器,不然会导致解不出东西 */
    if(h264Data[4] == 0x67){
    memcpy(sps,h264Data,size);
    spsLen = size;
    return 0;
    }
    /* 如果是pps 则存起来,不喂给解码器,不然会导致解不出东西 */
    if(h264Data[4] == 0x68){
    memcpy(pps,h264Data,size);
    ppsLen = size;
    return 0;
    }
    /* 判断下是否是I帧 是I帧的话需要在前面加上spspps,不然会导致解不出东西 */
    if(h264Data[4] == 0x65){
    /* 现在是I帧,需要加上SPSPPS,故计算下加上spspps之后的长度是多少 */
    int spsppsIframeLen = size+spsLen+ppsLen;
    /* 开辟出需要的内存 */
    h264DataTemp = new uint8_t[spsppsIframeLen];
    /* 先复制SPS进去 */
    memcpy(h264DataTemp,sps,spsLen);
    /* 再复制PPS进去 */
    memcpy(h264DataTemp+spsLen,pps,ppsLen);
    /* 最后再把I帧复制进去 */
    memcpy(h264DataTemp+spsLen+ppsLen,h264Data,size);
    /* 把pkt对象的数据指针直接指向整理好的加上了spspps的数据 */
    pkt->data = h264DataTemp;
    pkt->size = spsppsIframeLen;

    }else{
    /* 不是I帧 直接开辟出传进来的H264数据大小的内存就行了 */
    h264DataTemp = new uint8_t[size];
    /* 把H264数据复制进去 */
    memcpy(h264DataTemp,h264Data,size);
    /* 把pkt对象的数据指针直接指向H264数据 */
    pkt->data = h264DataTemp;
    pkt->size = size;
    }
    /* 定义解码后的数据的长度 */
    int outputSize = 0;
    /* 把H264数据喂给解码器 */
    ret = avcodec_send_packet(decoder_ctx, pkt);
    /* 判断下是否喂成功了,失败则直接退出 */
    if (ret < 0) {
    fprintf(stderr, "Error during decoding str:%s \n",strerror(errno));
    return ret;
    }
    /* 循环去读是否有已经解完码的数据 */
    while (ret >= 0) {
    int i, j;
    /* 读取已经解完码的数据 */
    ret = avcodec_receive_frame(decoder_ctx, frame);
    /* 如果是没有数据或者需要新的输入才能解码 则直接退出循环 */
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
    break;
    else if (ret < 0) {
    /* 还在解码中 直接退出等下次喂完再看看 */
    fprintf(stderr, "Error during decoding\n");
    return ret;
    }
    /* 拿到解完码的数据对象frame了,但是这份数据不在CPU里,需要av_hwframe_transfer_data转到CPU里,然后赋值给sw_frame */
    ret = av_hwframe_transfer_data(sw_frame, frame, 0);
    /* 看看是否转成功了,失败则退出 */
    if (ret < 0) {
    fprintf(stderr, "Error transferring the data to system memory\n");
    return ret;
    }
    /* 把拿出来的NV12转成RGB32 */
    ret = sws_scale(img_convert_ctx,sw_frame->data, sw_frame->linesize, 0, sw_frame->height,frameRGB->data, frameRGB->linesize);
    /* 把RGB32复制到输出的缓冲 */
    memcpy(buf,frameRGB->data[0],outImgSize);
    /* 输出的RGB32的大小 */
    outputSize = outImgSize;


    }/* 释放一下内存 */
    av_packet_unref(pkt);
    delete []h264DataTemp;

    return outputSize;
    }


    int HW_H264Decoder::decodeToNV12(uint8_t *h264Data, int size, unsigned char *nv12Data){
    int ret = 0;
    /* 由于I帧需要在前面家SPSPPS 不然会出现无法解码的情况,故开辟个临时缓冲区 */
    uint8_t *h264DataTemp = NULL;
    /* 如果是SPS 则存起来,不喂给解码器,不然会导致解不出东西 */
    if(h264Data[4] == 0x67){
    memcpy(sps,h264Data,size);
    spsLen = size;
    return 0;
    }
    /* 如果是pps 则存起来,不喂给解码器,不然会导致解不出东西 */
    if(h264Data[4] == 0x68){
    memcpy(pps,h264Data,size);
    ppsLen = size;
    return 0;
    }
    /* 判断下是否是I帧 是I帧的话需要在前面加上spspps,不然会导致解不出东西 */
    if(h264Data[4] == 0x65){
    /* 现在是I帧,需要加上SPSPPS,故计算下加上spspps之后的长度是多少 */
    int spsppsIframeLen = size+spsLen+ppsLen;
    /* 开辟出需要的内存 */
    h264DataTemp = new uint8_t[spsppsIframeLen];
    /* 先复制SPS进去 */
    memcpy(h264DataTemp,sps,spsLen);
    /* 再复制PPS进去 */
    memcpy(h264DataTemp+spsLen,pps,ppsLen);
    /* 最后再把I帧复制进去 */
    memcpy(h264DataTemp+spsLen+ppsLen,h264Data,size);
    /* 把pkt对象的数据指针直接指向整理好的加上了spspps的数据 */
    pkt->data = h264DataTemp;
    pkt->size = spsppsIframeLen;

    }else{
    /* 不是I帧 直接开辟出传进来的H264数据大小的内存就行了 */
    h264DataTemp = new uint8_t[size];
    /* 把H264数据复制进去 */
    memcpy(h264DataTemp,h264Data,size);
    /* 把pkt对象的数据指针直接指向H264数据 */
    pkt->data = h264DataTemp;
    pkt->size = size;
    }
    /* 定义解码后的数据的长度 */
    int outputSize = 0;
    /* 把H264数据喂给解码器 */
    ret = avcodec_send_packet(decoder_ctx, pkt);
    /* 判断下是否喂成功了,失败则直接退出 */
    if (ret < 0) {
    fprintf(stderr, "Error during decoding str:%s \n",strerror(errno));
    return ret;
    }
    /* 循环去读是否有已经解完码的数据 */
    while (ret >= 0) {
    int i, j;
    /* 读取已经解完码的数据 */
    ret = avcodec_receive_frame(decoder_ctx, frame);
    /* 如果是没有数据或者需要新的输入才能解码 则直接退出循环 */
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
    break;
    else if (ret < 0) {
    /* 还在解码中 直接退出等下次喂完再看看 */
    fprintf(stderr, "Error during decoding\n");
    return ret;
    }
    /* 拿到解完码的数据对象frame了,但是这份数据不在CPU里,需要av_hwframe_transfer_data转到CPU里,然后赋值给sw_frame */
    ret = av_hwframe_transfer_data(sw_frame, frame, 0);
    /* 看看是否转成功了,失败则退出 */
    if (ret < 0) {
    fprintf(stderr, "Error transferring the data to system memory\n");
    return ret;
    }

    /* 把NV12数据复制到给定的内存空间,NV12数据在AVFrame中的存储存储方式为->data[0]里面存着Y分量 ->data[1]厘米存着UV分量 */
    for (i = 0; i < FF_ARRAY_ELEMS(sw_frame->data) && sw_frame->data[i]; i++){
    /* 把数据全部读出来 */
    for (j = 0; j < (sw_frame->height >> (i > 0)); j++){
    memcpy(nv12Data+outputSize ,sw_frame->data[i] + j * sw_frame->linesize[i], sw_frame->width);
    /* 复制出来的长度需要累计,最后返回给调用者,才知道解码出来的数据大小 */
    outputSize +=sw_frame->width;
    }
    }

    }
    /* 释放一下内存 */
    av_packet_unref(pkt);
    delete []h264DataTemp;

    return outputSize;
    }



    调用的时候直接先init初始化一下,然后就调用decode把H264数据传进去就可以实现解码了(前三包必须是sps->ssp->I帧),程序结束的时候调用relese就销毁就行了。

    /* 从文件中读取H264数据并进行解码 解码成功后发到UI线程显示 */
    void ShowCH1Thread::run(){

    int ret;
    /* 实例化解码类的对象 */
    HW_H264Decoder mHWh264Decodec;
    /* 初始化解码类 */
    ret = mHWh264Decodec.init(1920,1080);
    if(ret != 0){
    printf("init h264 decodec fialed! %s\n",strerror(errno));

    }
    /* 开辟需要用到的内存空间 */
    char h264Data[4096] = {0x00};
    uint8_t *grbData = (uint8_t *)malloc(1024*1024*10);
    uint8_t *H264Cache = (uint8_t*)malloc(1024*1024*1);
    int cacheindex = 0;
    /* 打开一个h264文件用来读H264数据进行解码 */
    int h264File = open("/home/vis/qsvLIb/H264Player/test.h264",O_RDWR);
    if(!h264Data){
    printf("open h264 file failed! %s\n",strerror(errno));
    }
    int len = -1;
    while(true){
    /* 先读一个直接出来 看看是不是01 主要用来判断NAL标识头 00000001 */
    len = read(h264File,h264Data,1);
    if(len > 0){
    /* 读到01了 长度也大于等于3 那么可能是一个NAL标识头 */
    if(h264Data[0] == 0x01 && cacheindex >= 3){
    /* 判断下是不是一个NAL头 */
    if(H264Cache[cacheindex-1] == 0x00 && H264Cache[cacheindex-2] == 0x00 && H264Cache[cacheindex-3] == 0x00){
    /* 是NAL头 那么把这帧数据拿去解码 */
    int h264len = cacheindex-3;
    if(h264len > 5){
    ret = mHWh264Decodec.decodeToRGB32(H264Cache,h264len,grbData);
    /* ret大于0则说明解码成功 ret就是解码出来的数据的长度 发到UI线程进行显示 */
    if(ret > 0){
    emit sigShowCH1ToUI(grbData);
    usleep(40*1000);
    }
    }
    memset(H264Cache,0x00,cacheindex);
    cacheindex = 3;
    H264Cache[cacheindex] = h264Data[0];
    cacheindex++;


    }else{
    /* 还没找到头 继续读 */
    H264Cache[cacheindex] = h264Data[0];
    cacheindex++;
    }

    }else{
    /* 还没找到头 继续读 */
    H264Cache[cacheindex] = h264Data[0];
    cacheindex++;
    }
    }else{
    printf("read h264 file failed! len=%d %s\n",len,strerror(errno));
    break;
    }

    }

    /* 释放一下 */
    mHWh264Decodec.relese();
    close(h264File);
    free(h264Data);
    free(grbData);
    free(H264Cache);
    }
    这个实现过程主要是参考了ffmpeg/doc/examples/qsvdec.c文件,实现得比较粗糙,需要的话可以去看看这个文件的内容,可以加深对QSV硬解的理解
    ————————————————
    版权声明:本文为CSDN博主「qiu旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/a287574014/article/details/120221867

  • 相关阅读:
    5.User Interface/Custom Components
    5.User Interface/Styles and Themes
    5.User Interface/Accessibility
    5.User Interface/Drag and Drop
    5.User Interface/Notifications
    5.User Interface/Dialogs
    Menu综合运用
    5.User Interface/ActionBar
    5.User Interface/Menu
    5.User Interface/Input Controls
  • 原文地址:https://www.cnblogs.com/eastgeneral/p/16719089.html
Copyright © 2020-2023  润新知