1. RGB与YUV格式互转
1.1 相关API
创建上下文
struct SwsContext *sws_getCachedContext(
struct SwsContext *context, //转换上下文,NULL新创建,非NULL判断与现有参数是否一致,一致则返回,不一致则重新创建
int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, //选择支持变化的算法,双线性插值
SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param //过滤器,赋空即可
);
转换格式
int sws_scale(
struct SwsContext *c,
const uint8_t *const srcSlice[], const int srcStride[],
int srcSliceY, int srcSliceH, //Y为厚度,赋空即可,H为Heigh
uint8_t *const dst[], const int dstStride[]
);
1.2 实例
步骤:
- 申请三个空间分别存放Y、U、V数据、申请一个空间存放RGBA数据
- 读取文件中的YUV数据存放到申请的空间
- 通过API将YUV数据空间的数据转到RGBA空间数据
- 将RGBA空间的数据存储到输出文件
注意:将YUV的三个空间存入文件时,无法每帧写入一次,因为YUV再文件中的存储方式不是按帧存储的。
#include <iostream>
#include <fstream>
extern "C"
{
#include <libswscale/swscale.h>
}
#pragma comment(lib, "swscale.lib")
using namespace std;
int main()
{
//打开文件
ifstream ifs;
ofstream ofs;
/*YUV to RGBA*/
ifs.open("400_300_25.yuv", ios::binary);
ofs.open("800_600_25.rgba", ios::binary);
//申请YUV空间
int yuv_width = 400;
int yuv_height = 300;
int rgba_width = 800;
int rgba_height = 600;
unsigned char* yuv_data[3] = { 0 };
yuv_data[0] = new unsigned char[yuv_width * yuv_height];
yuv_data[1] = new unsigned char[yuv_width * yuv_height / 4];
yuv_data[2] = new unsigned char[yuv_width * yuv_height / 4];
int yuv_linesize[3] = { yuv_width, yuv_width / 2, yuv_width / 2 };
//申请RGBA空间
unsigned char* rgba_data[1] = { new unsigned char[rgba_width * rgba_height * 4] };
int rgba_linesize[1] = { rgba_width * 4 };
//创建上下文
SwsContext* sws_ctx = nullptr;
sws_ctx = sws_getCachedContext(sws_ctx,
yuv_width, yuv_height,
AV_PIX_FMT_YUV420P,
rgba_width, rgba_height,
AV_PIX_FMT_RGBA,
SWS_BILINEAR,
0, 0, 0
);
//开始转换
for(int i = 0 ; i < 25; i++)
{
//读取YUV
ifs.read((char *)yuv_data[0], yuv_width * yuv_height); //读Y
ifs.read((char *)yuv_data[1], yuv_width * yuv_height / 4); //读U
ifs.read((char *)yuv_data[2], yuv_width * yuv_height / 4); //读V
if (ifs.gcount() == 0)break;
//开始转换
int ret = sws_scale(sws_ctx,
yuv_data, yuv_linesize,
0, yuv_height,
rgba_data, rgba_linesize
);
std::cout << ret << " ";
ofs.write((char *)rgba_data[0], rgba_width * rgba_height * 4);
}
//关闭文件
ifs.close();
ofs.close();
/*RGBA to YUV*/
//打开文件
ifs.open("800_600_25.rgba", ios::binary);
//ofs.open("400_300_25_new.yuv", ios::binary);
memset(rgba_data[0], 0, rgba_width * rgba_height * 4);
memset(yuv_data[0], 0, yuv_width * yuv_height);
memset(yuv_data[1], 0, yuv_width * yuv_height / 4);
memset(yuv_data[2], 0, yuv_width * yuv_height / 4);
//创建上下文
sws_ctx = sws_getCachedContext(
sws_ctx,
rgba_width, rgba_height,
AV_PIX_FMT_RGBA,
yuv_width, yuv_height,
AV_PIX_FMT_YUV420P,
SWS_BILINEAR,
nullptr, nullptr, nullptr
);
//开始转换
while (true)
{
//读取数据
ifs.read((char *)rgba_data[0], rgba_width * rgba_height * 4);
if (ifs.gcount() == 0)
{
break;
}
//开始转换
int ret = sws_scale(
sws_ctx,
rgba_data, rgba_linesize,
0, rgba_height,
yuv_data, yuv_linesize
);
std::cout << ret << " " << flush;
//ofs.write((char*)yuv_data[0], yuv_height * yuv_height);
//ofs.write((char*)yuv_data[1], yuv_height * yuv_height / 4);
//ofs.write((char*)yuv_data[2], yuv_height * yuv_height / 4);
}
ifs.close();
//ofs.close();
sws_freeContext(sws_ctx);
delete yuv_data[0];
delete yuv_data[1];
delete yuv_data[2];
delete rgba_data[0];
}
2. 多路播放器
上述画面应该是对齐产生的问题
如何支持多路渲染
- 创建多个XVideoView对象,每个对象都对应一个WinId创建的窗口。
- 渲染时,两个对象调用各自的接口进行渲染。
- 若要实现不同的频率,可以通过时间轮定时器的方法,也可以通过对不同任务遍历次数的不同来实现。
- 在读取文件时直接创建AVFrame,并把内容读到其中,然后根据文件的参数就可以直接进行初始化,初始化结束后就可以返回含有数据的AVFrame
#include "xvideo.h"
#include <qspinbox.h>
#include "XVideoView.h"
#include <qfiledialog.h>
#include <sstream>
#include <iostream>
#include <vector>
#pragma comment(lib, "SDL2.lib")
using namespace std;
static vector<XVideoView *> view;
XVideo::XVideo(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
connect(this, SIGNAL(view_sig()), this, SLOT(view_slot()));
//创建多个xview对象
view.push_back(XVideoView::createXvideo());
view.push_back(XVideoView::createXvideo());
view[0]->setWinId((void*)ui.video1->winId());
view[1]->setWinId((void*)ui.video2->winId());
//cout << "current index" << ui.pix_fmt->currentIndex() << endl;
//创建线程
th_ = std::thread(&XVideo::Main, this);
}
/*打开文件、根据设置的参数初始化渲染窗口*/
void XVideo::open(int i)
{
//创建打开文件窗口
QFileDialog fd;
auto file_name = fd.getOpenFileName();
if (file_name.isEmpty())
{
return;
}
cout << file_name.toLocal8Bit().data() << endl;
//根据名字在第i个窗口打开文件
view[i]->openFile(file_name.toStdString());
//初始化第i个渲染窗口
int width = 0;
int height = 0;
int pix_fmt_idx = 0;
if (i == 0) //获取第i个窗口 要打开文件的参数
{
width = ui.width->value();
height = ui.height->value();
pix_fmt_idx = ui.pix_fmt->currentIndex();
}
if (i == 1)
{
width = ui.width_2->value();
height = ui.height_2->value();
pix_fmt_idx = ui.pix_fmt_2->currentIndex();
}
auto fmt = XVideoView::XVIDEO_PIX_FMT_YUV420P;
switch (pix_fmt_idx)
{
case 0:
fmt = XVideoView::XVIDEO_PIX_FMT_YUV420P;
break;
case 1:
fmt = XVideoView::XVIDEO_PIX_FMT_RGBA;
break;
case 2:
fmt = XVideoView::XVIDEO_PIX_FMT_ARGB;
case 3:
fmt = XVideoView::XVIDEO_PIX_FMT_BGRA;
case 4:
fmt = XVideoView::XVIDEO_PIX_FMT_RGB24;
break;
default:
break;
}
//初始化第i个窗口
view[i]->xvideo_init(width, height, fmt);
}
void XVideo::Main()
{
while (!is_exit)
{
Msleep(10);
view_sig();
}
}
void XVideo::view_slot()
{
//cout << "this is view_slot" << endl;
//设置帧率:设定一个时间,若时间到则执行view[i],若未到则执行continue
//获取设定的帧率
static int fps_arr[32] = { 0 }; //可以共32路视频
static int last_time[32] = { 0 };
fps_arr[0] = ui.set_fps->value();
fps_arr[1] = ui.set_fps_2->value();
//cout << "fps_arr[0] " << fps_arr[0] << endl;
//cout << "fps_arr[1] " << fps_arr[1] << endl;
for (int i = 0; i < view.size(); i++)
{
if (fps_arr[i] <= 0) continue;
//若当前时间 - 上次渲染的时间 < 设定的时间,则等待
auto set_time = 1000 / fps_arr[i];
//cout << "NowTime() - last_time[i] " << NowTime() - last_time[i] << endl;
if (NowTime() - last_time[i] < set_time)
{
//cout << "set_time " << set_time << endl;
continue;
}
last_time[i] = NowTime();
AVFrame* frame = nullptr;
frame = view[i]->readFile();
if (!frame) {
//cout << i << " view_slot readFile->frame is nullptr" << endl;
continue;
};
view[i]->xvideo_draw(frame);
//显示帧率
stringstream ss;
ss << "fps: " << view[i]->render_fps();
if (i == 0)
{
ui.scale_fps->setText(ss.str().c_str());
}
else
{
ui.scale_fps_2->setText(ss.str().c_str());
}
}
}