写在前面:
编写完视频的编码转码程序之后,就需要将整个程序重新封装一下,以便于后续的工作,这里对应视频课程中的3-7~3-10。由于之前没怎么接触过C++,所以蛮多东西都不是很懂,所以笔记只能原原本本的记录下来了,以后要是懂点的话,在进行修正吧。
1、创建类
为了让代码更简洁,这时候就需重构FFmpeg类,这里我们创建一个虚析构函数。
为什么要创建虚虚构函数?
在继承之后,如果没有设置虚析构函数,对父类指针删除的时候,不会自动调用释放内存的函数。
然后将之前main.cpp文件里面的函数逐步移动到新创建的这个类里面,我们可以将extern “C”{}这个C库函数放在FFmpeg.h文件里面,但是不能将库(lib)文件放到FFmepg中。而应该放在FFmpeg.cpp中,因为.h文件会被多次调用。
2、XFFmpeg对象
我们做的只是简单的视频播放器,所以暂时使用单件模式,只有一个对象,所以没必要让用户去维护这个对象的生命周期。
另外我们将构造函数放在保护类型protected里面,这是为了不让外部生成对象,保证内部的唯一性。
在XFFmpeg类里面添加open函数,但是open过程中有可能会失败,所以还应该添加错误打印信息,那么就直接在protected保护类型里面创建一个errorbuf存错误信息,然后再定义一个获取错误信息的函数。
用string GetError();为什么要用string,主要考虑到错误信息有可能被多个线程应用,为了线程安全,再复制一份。
由于后期还要用到多线程处理,才能加快解码速度,那么多线程访问就需要用到互斥变量,所以我们直接添加QT自带的互斥变量#include <QMutex>,然后再保护类型protected里面追加QMutex mutex。
然后再添加close函数。
另外我们将构造函数放在保护类型protected里面,这是为了不让外部生成对象,保证内部的唯一性。
在XFFmpeg类里面添加open函数,但是open过程中有可能会失败,所以还应该添加错误打印信息,那么就直接在protected保护类型里面创建一个errorbuf存错误信息,然后再定义一个获取错误信息的函数。
用string GetError();为什么要用string,主要考虑到错误信息有可能被多个线程应用,为了线程安全,再复制一份。
由于后期还要用到多线程处理,才能加快解码速度,那么多线程访问就需要用到互斥变量,所以我们直接添加QT自带的互斥变量#include <QMutex>,然后再保护类型protected里面追加QMutex mutex。
然后再添加close函数。
3、Open、Close、GetError函数的实现和使用
open函数的内容和之前未封装的内容一致,但是要注意的是,由于接下来可能用到多线程,使用open和close的时候要注意上锁,open之前也要注意先close一下,以防止之前被人打开过。
close函数则做对已申请的空间进行释放的工作。
geterror函数,用来打印错误消息的,函数内部定义一个std::string re = this->errorbuf;使用这种方法返回出去的re会被多创建一份,那样返回出去的是一份复制的空间,就不会造成多线程的异常。
调用open函数:XFFmpeg::Get()->Open("my.mp4")
调用geterror的方法XFFmpeg::Get()->GetError().c_str(),通过Get函数获取XFFmpeg类的地址,然后再指向GetError函数,由于GetError函数返回的是string类型的,是不能直接打印的,所以要转成char*,通过.c_str()函数来实现。4、Read函数的实现
read函数是用于读取视频里面的帧数据的,然后返回读取读取的视频的pkt数据包,那么这里就需要考虑到,读取完数据包之后,要在哪释放掉这个数据包所占用的空间,这里我们选择在外部释放掉。
为什么不在函数内部实现每次释放掉上一帧的空间呢,由于要考虑到之后多线程处理,有可能在读取下一帧数据的时候,上一帧数据还未被解码。5、Decode函数的实现
Decode函数是用于将读取到的帧,也就是将pkt解码成yuv。要讲读取到的帧转换成yuv格式的话,那么我们在调用avcodec_send(receive)_packet函数前,要记得先打开这个解码器,所以我们需要将之前的打开解码器函数放在open函数里面。
解码完就需要将yuv格式转换成rgb格式,那么就需要创建转码的函数。
以下是代码:
XXFmpeg.h
#pragma once
extern "C"{
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}
#include <string>
#include <QMutex>
class XFFmpeg
{
public:
static XFFmpeg *Get()
{
static XFFmpeg ff;
return &ff;
}
///打开视频文件,如果上次已经打开就会先关闭
///@path path 视频文件路径
///@return bool 成功失败 失败信息通过GetError获取
bool Open(const char *path);
void Close();
///返回值需要用户清理
AVPacket Read();
AVFrame *Decode(const AVPacket *pkt);
bool ToRGB(const AVFrame *yuv,char *out,int outwidth, int outheight);
std::string GetError();
virtual ~XFFmpeg();
int totalMs = 0; //时长
int videoStream = 0;
protected:
char errorbuf[1024];
AVFormatContext *ic = NULL; //存放视频(流)的文件信息
AVFrame *yuv = NULL;
SwsContext *cCtx = NULL; //创建转码器的容器
QMutex mutex;
XFFmpeg();
};
XFFmpeg.cpp
#include "XFFmpeg.h"
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"swscale.lib")
bool XFFmpeg::Open(const char *path)
{
int re = 0;
int err = 0;
Close();
mutex.lock();
re = avformat_open_input(&ic, path, 0, 0); //打开
if (re != 0)
{
mutex.unlock();
av_strerror(re, errorbuf, sizeof(errorbuf));
printf("open failed %s\n", path, errorbuf);
return false;
}
totalMs = (ic->duration / AV_TIME_BASE)*1000;
for (int i = 0; i < ic->nb_streams; i++)
{
AVCodecContext *enc = ic->streams[i]->codec;
if (enc->codec_type == AVMEDIA_TYPE_VIDEO)//表示为一个视频
{
videoStream = i;
AVCodec *codec = avcodec_find_decoder(enc->codec_id); //判断系统有没有这个解码器
if (!codec)
{
mutex.unlock();
printf("video code not find!!\n");
return false;
}
err = avcodec_open2(enc, codec,NULL); //找到系统中这个解码器的话,就打开这个解码器。
if (err != 0)
{
mutex.unlock();
char buf[1024] = { 0 };
av_strerror(re, errorbuf, sizeof(errorbuf));
printf("buf\n", path, errorbuf);
return false;
}
printf("open avcodec_open2 success!!!\n");
}
}
mutex.unlock();
return true;
}
AVPacket XFFmpeg::Read()
{
AVPacket pkt;
int err = 0;
memset(&pkt,0,sizeof(AVPacket));
mutex.lock();
if (!ic) //判断视频文件被打开没,没被打开的话,直接退出。
{
mutex.unlock();
return pkt; //这时候的pkt也是空的
}
err = av_read_frame(ic, &pkt);
if (err != 0)
{
mutex.unlock();
av_strerror(err, errorbuf, sizeof(errorbuf));
}
mutex.unlock();
return pkt;
}
void XFFmpeg::Close()
{
mutex.lock();
if (ic) avformat_close_input(&ic);
if (yuv) av_frame_free(&yuv);
if (cCtx)
{
sws_freeContext(cCtx);
cCtx = NULL;
}
mutex.unlock();
}
std::string XFFmpeg::GetError()
{
mutex.lock();
std::string re = this->errorbuf;
mutex.unlock();
return re;
}
AVFrame *XFFmpeg::Decode(const AVPacket *pkt)
{
int re = 0;
mutex.lock();
if (!ic) //判断视频文件被打开没,没被打开的话,直接退出。
{
mutex.unlock();
return NULL; //这时候的pkt也是空的
}
if (yuv == NULL)
{
yuv = av_frame_alloc();
}
re = avcodec_send_packet(ic->streams[pkt->stream_index]->codec,pkt);
if (re != 0)
{
mutex.unlock();
//av_packet_unref(&pkt);
return NULL;
}
re = avcodec_receive_frame(ic->streams[pkt->stream_index]->codec,yuv);
if (re != 0)
{
mutex.unlock();
//av_packet_unref(&pkt);
return NULL;
}
mutex.unlock();
return yuv;
}
bool XFFmpeg::ToRGB(const AVFrame *yuv, char *out, int outwidth, int outheight)
{
mutex.lock();
if (!ic) //判断视频文件被打开没,没被打开的话,直接退出。
{
mutex.unlock();
return false;
}
AVCodecContext *videoCtx = ic->streams[this->videoStream]->codec;//在ic中的streams数组中找到这个codec,
//打开转码器
cCtx = sws_getCachedContext(cCtx, videoCtx->width,
videoCtx->height,
videoCtx->pix_fmt,
outwidth, outheight,
AV_PIX_FMT_BGRA,
SWS_BICUBIC,
NULL, NULL, NULL);
if (!cCtx)
{
mutex.unlock();
printf("sws_getCachedContext failed!!\n");
return false;
}
uint8_t *data[AV_NUM_DATA_POINTERS] = { 0 }; //转码的目标数据
data[0] = (uint8_t *)out;
int linesize[AV_NUM_DATA_POINTERS] = { 0 };
linesize[0] = outwidth * 4;
int h = sws_scale(cCtx,yuv->data,yuv->linesize,0,videoCtx->height,
data,
linesize);
if (h > 0)
printf("(%d)", h);
mutex.unlock();
return true;
}
XFFmpeg::XFFmpeg()
{
errorbuf[0] = '\0';
av_register_all(); //注册所有的编解码器和文件格式。
}
XFFmpeg::~XFFmpeg()
{
}
main.cpp
#include "xplay.h"
#include <QtWidgets/QApplication>
#include "XFFmpeg.h"
#define OUT_WIDTH 800
#define OUT_HEIGHT 600
int main(int argc, char *argv[])
{
char *rgb = new char[OUT_WIDTH*OUT_HEIGHT * 4]; //图像的数据RGBA
if (XFFmpeg::Get()->Open("my.mp4"))
{
printf("open success!\n");
}
else
{
printf("open failed!%s", XFFmpeg::Get()->GetError().c_str());
getchar();
return -1;
}
while (1)
{
AVPacket pkt = XFFmpeg::Get()->Read();
if (pkt.size == 0)
break;
printf("pts=%lld\n", pkt.pts);
if (pkt.stream_index != XFFmpeg::Get()->videoStream)
{
av_packet_unref(&pkt); //如果不是视频格式的 就释放掉
continue;
}
AVFrame *yuv = XFFmpeg::Get()->Decode(&pkt); //解码视频
if (yuv)
{
printf("[D]");
XFFmpeg::Get()->ToRGB(yuv, rgb, OUT_WIDTH, OUT_HEIGHT);
}
av_packet_unref(&pkt);
}
QApplication a(argc, argv);
Xplay w;
w.show();
return a.exec();
}