前言
视频绘制使用的qt的QOpenGLWidget,QOpenGLWidget已经对OpenGL做了封装处理,这里主要介绍代码的处理。
YUV转RGB
sws_getContext
功能
多路码流转换,为每个不同的码流都指定一个不同的转换上下文。
函数
struct SwsContext *sws_getContext(
int srcW, /* 输入图像的宽度 */
int srcH, /* 输入图像的宽度 */
enum AVPixelFormat srcFormat, /* 输入图像的像素格式 */
int dstW, /* 输出图像的宽度 */
int dstH, /* 输出图像的高度 */
enum AVPixelFormat dstFormat, /* 输出图像的像素格式 */
int flags, /* 选择缩放算法(只有当输入输出图像大小不同时有效),一般选择SWS_FAST_BILINEAR */
SwsFilter *srcFilter, /* 输入图像的滤波器信息, 若不需要传NULL */
SwsFilter *dstFilter, /* 输出图像的滤波器信息, 若不需要传NULL */
const double *param /* 特定缩放算法需要的参数(?),默认为NULL */
);
sws_getCachedContext
功能
用于一路码流转换。
函数
struct SwsContext *sws_getCachedContext(struct SwsContext *context,
int srcW, // 输入图像的宽度
int srcH, // 输入图像的高度
enum AVPixelFormat srcFormat, // 输入图像的像素格式
int dstW, // 输出图像的宽度
int dstH, // 输出图像的高度
enum AVPixelFormat dstFormat, // 输出图像的像素格式
int flags, // 选择缩放算法(只有当输入输出图像大小不同时有效),一般选择SWS_FAST_BILINEAR
SwsFilter *srcFilter, // 输入图像的滤波器信息, 若不需要传NULL
SwsFilter *dstFilter, // 输出图像的滤波器信息, 若不需要传NULL
const double *param); // 特定缩放算法需要的参数,默认为NULL
sws_freeContext
功能
释放sws_getContext 。
函数
void sws_freeContext(struct SwsContext *swsContext);
sws_scale
功能
视频像素格式和分辨率的转换。
函数
int sws_scale(struct SwsContext *c, // 转换格式的上下文
const uint8_t *const srcSlice[], // 输入图像的每个颜色通道的数据指针
const int srcStride[], // 输入图像的每个颜色通道的跨度
int srcSliceY, // 图像处理区域的起始位置
int srcSliceH, // 图像处理区域的行数
uint8_t *const dst[], // 输出图像信息: 输出的每个颜色通道数据指针
const int dstStride[]); // 输出图像信息: 每个颜色通道行字节数
代码处理
bool XFFmpeg::ToRGB(char *out, int outwidth, int outheight)
{
mutex.lock();
//未打开视频文件或者未解码
if (!ic||!yuv)
{
mutex.unlock();
return false;
}
AVCodecContext *videoCtx = ic->streams[this->videoStream]->codec;
// 初始化一个SwsContext
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; //第一位输出RGB
int linesize[AV_NUM_DATA_POINTERS] = { 0 };
linesize[0] = outwidth * 4; //一行的宽度,32位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;
}
视频绘制
视频帧的绘制使用的是Qt的paintEvent事件。
构造函数
VideoWidget::VideoWidget(QWidget *parent) :QOpenGLWidget(parent)
{
// 设置定时器
startTimer(20);
// 启动线程读取数据帧
XVideoThread::getInstance()->start();
}
paintEvent
void VideoWidget::paintEvent(QPaintEvent *e)
{
static QImage *image = nullptr;
static int w = 0;
static int h = 0;
if (w != width() || h != height())//当缩小窗口或者方法窗口时,删除image,重新绘制
{
if (image)
{
delete image->bits();
delete image;
image = nullptr;
}
}
if (nullptr == image)
{
//存放解码后的视频空间
uchar *buf = new uchar[width()*height() * 4];
image = new QImage(buf, width(), height(), QImage::Format_ARGB32);
}
//将解码后的视频帧转化为RGB
XFFmpeg::getInstance()->ToRGB((char *)image->bits(), width(), height());
//绘制FFMpeg解码后的视频
QPainter painter;
painter.begin(this);//清理屏幕
painter.drawImage(QPoint(0, 0), *image);
painter.end();
}
读取线程
void XVideoThread::run()
{
char out[10000] = {0};
while (!bExit)
{
// 如果为暂停状态,不处理
if (!XFFmpeg::getInstance()->bPlay)
{
msleep(10);
continue;
}
// 确定list中是否有AVpacket包
while (videos.size() > 0)
{
AVPacket pack = videos.front(); // 每次取出list中的第一个AVPack包
int pts = XFFmpeg::getInstance()->GetPts(&pack); // 获得该包的pts
if (pts > apts) // 若视频包大于音频包的pts,结束
{
break;
}
// 解码视频帧
XFFmpeg::getInstance()->Decode(&pack);
// 清理该AVPacket包
av_packet_unref(&pack);
// 从list链表中删除
videos.pop_front();
}
// 获取缓冲区大小
int free = XAudioPlay::getInstance()->GetFree();
if (free < 10000)
{
msleep(1);
continue;
}
// 读取数据帧
AVPacket pkt = XFFmpeg::getInstance()->Read();
if (pkt.size <= 0)
{
msleep(10);
continue;
}
if (pkt.stream_index == XFFmpeg::Get()->audioStream)
{
// 解码音频
apts = XFFmpeg::getInstance()->Decode(&pkt);
// 释放pkt包
av_packet_unref(&pkt);
// 重采样音频
int len = XFFmpeg::getInstance()->ToPCM(out);
//写入音频
XAudioPlay::getInstance()->Write(out, len);
continue;
}
videos.push_back(pkt);
}
}