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硬解的理解

猜你喜欢

转载自blog.csdn.net/a287574014/article/details/120221867