前言
由于项目需要,需要在一块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