对最近学习音视频的一点成果汇总,前面对雷神的一些项目进行了复现只是将平台从MFC变为了QT,利用FFMPEGle+SDL可以直接播放MP4等文件。后面想直接做一个像素数据的播放器可以播放YUV+RGB。然后查找资料,做了一个像素数据播放器。
1.实现的播放器的功能
(1)从文件夹获取文件路径;
(2)读取yuv与rgb文件;
(3)将像素数据一帧一帧播放出来;
(4)设置视频的宽高以及播放速度
(5)视频暂停和退出;
2.涉及知识点
(1)QT的基础知识点(创建ui、控件、布局、控件设置)
(2)YUV数据和rgb数据的存储格式、读取以及yuv和rgb的转换
(3)QT信号与槽函数
(4)QT多线程
3.方案设计
在设计播放器的时候,我们首先考虑用Qlabel的QPIXMAP来显示图像,但是QPIXMAP只能显示rgb数据不能直接显示YUV数据,所以需要把YUV转换为RGB,rgb数据也不是直接给Qlabel显示,要经过QImage传递。转换的方法有三种,第一种是用公式法直接转换,第二种是用ffmpeg里的sws_scale函数来转换,第三种方法是采用查表法,在这里我实验了第一种和第二种,第三种比较麻烦没有尝试。经查阅资料发现还可以用QPaint来渲染图像,这里没有采用这种方法。
4.存在问题
第一:功能不够完整,没有实现进度条,
第二:播放完一个文件再播放另一个文件时存在一些bug,有时会卡死,怀疑是多线程的问题,还没解决。
5.实现代码
(1)多线程类videoplayer.h
#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H
#include <QThread>
#include <QImage>
#include <QDebug>
#include<QMutex>
class VideoPlayer: public QThread
{
Q_OBJECT
public:
explicit VideoPlayer();
~VideoPlayer();
void setFileName(QString path) {
mFileName = path;
qDebug() << mFileName;
}
void setwh(int w,int h) {
vwidth = w;
vheight = h;
qDebug() << vwidth << vheight;
}
void setrate(int rate) {
vtime = 1000/rate;
}
void startPlay();
void pausePlay();
void stopPlay();
void resume();
void threadPR();//切换
signals:
void sig_GetOneFrame(QImage); //没获取到一帧图像 就发送此信号
protected:
void run();
private:
QString mFileName;
int vwidth;
int vheight;
int vtime;
bool m_buttonState;//if pause m_buttonState=false,else =true
QMutex m_mutex;//互斥量
int m_i;
};
#endif // VIDEOPLAYER_H
(2)videoplayer.cpp
#include "qtread.h"
#include"QtWidgetsApplication3.h"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
#include <stdio.h>
VideoPlayer::VideoPlayer()
{
m_buttonState=false;//if pause m_buttonState=false,else =true
m_i=0;
}
VideoPlayer::~VideoPlayer()
{
}
void VideoPlayer::startPlay()
{
///调用 QThread 的start函数 将会自动执行下面的run函数 run函数是一个新的线程
this->start();
}
void VideoPlayer::pausePlay()
{
qDebug() << QString("pause :%1").arg(m_buttonState);
this->m_mutex.lock();
this->m_buttonState = false;
qDebug() << QString("pause");
}
void VideoPlayer::resume()
{
qDebug() << QString("resume :%1").arg(m_buttonState);
this->m_mutex.unlock();
this->m_buttonState = true;
qDebug() << QString("resume");
}
void VideoPlayer::stopPlay() {
//this->exit(0);
qDebug() << QString("stop");
//this->wait();
this->terminate();
this->m_mutex.unlock();
}
void VideoPlayer::threadPR() {
if (m_buttonState)
{
pausePlay();
}
else
{
resume();
}
}
void VideoPlayer::run() {
m_buttonState = true;
while (1) {
AVFrame* pFrameYUV, * pFrameRGB;
QString url = mFileName;
qDebug() << url;
QByteArray ba2;
ba2.append(url); //也可以 ba2 = s2.toLatin1();
char* filepath = ba2.data();
qDebug() << filepath;
int len = strlen(filepath);
int width = vwidth;
int height = vheight;
int time = vtime;
qDebug() << "time" << time;
qDebug() << "321" << &filepath[len - 3];
pFrameRGB = av_frame_alloc();
pFrameYUV = av_frame_alloc();
unsigned char* rgb_buffer;
struct SwsContext* img_convert_ctx;
FILE* fp = fopen(filepath, "rb+");
if (strcmp(&filepath[len - 3], "yuv") == 0) {
rgb_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGB32, width, height, 1));
unsigned char* yuv_buffer = (unsigned char*)malloc(width * height * 3 / 2);
av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, rgb_buffer, AV_PIX_FMT_RGB32, width, height, 1);//填充上下文
img_convert_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV420P, width, height, AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
qDebug() << "doit";
int index = 0;
while (1) {
qDebug() << "dit";
m_i++;
m_mutex.lock();
qDebug() << "dt";
fread(yuv_buffer, 1, width * height * 3 / 2, fp);
if (yuv_buffer <= 0)
break;
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, yuv_buffer, AV_PIX_FMT_YUV420P, width, height, 1);//填充上下文
qDebug() << "it";
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrameYUV->data, pFrameYUV->linesize, 0, height, pFrameRGB->data, pFrameRGB->linesize);
QImage tmpImg((uchar*)rgb_buffer, width, height, QImage::Format_RGB32);
QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
emit sig_GetOneFrame(image);
QThread::msleep(time);
m_mutex.unlock();
}
}
else if (strcmp(&filepath[len - 3], "rgb") == 0) {
unsigned char* rgb_buffer = (unsigned char*)malloc(width * height * 3);
while (1) {
m_i++;
m_mutex.lock();
fread(rgb_buffer, 1, width * height * 3, fp);
if (rgb_buffer <= 0)
break;
QImage tmpImg((uchar*)rgb_buffer, width, height, QImage::Format_RGB888);
QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
emit sig_GetOneFrame(image);
QThread::msleep(time);//休眠10ms
m_mutex.unlock();
}
}
else
{
qDebug() << QString::fromUtf8("输入文件不符合规定");
}
fclose(fp);
}
};
/*yuv转RGB的第二种方法
void VideoPlayer::run() {
m_buttonState = true;
QString url = mFileName;
qDebug() << url;
QByteArray ba2;
ba2.append(url); //也可以 ba2 = s2.toLatin1();
char *filepath = ba2.data();
qDebug() << filepath;
int len=strlen(filepath);
int width = vwidth;
int height = vheight;
int time = vtime;
qDebug() << "time" << time;
qDebug() << "321" << &filepath[len - 3];
FILE* fp = fopen(filepath,"rb+");
if (strcmp(&filepath[len - 3],"yuv") == 0) {
qDebug() << "doit";
unsigned char* outbuffer = (unsigned char*)malloc(width * height * 3 / 2);
unsigned char* rgb_buffer = (unsigned char*)malloc(width * height * 3);
int index = 0;
while (1) {
qDebug() << "dit";
m_i++;
m_mutex.lock();
fread(outbuffer, 1, width * height * 3 / 2, fp);
if (outbuffer <= 0)
break;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int indexY = y * width + x;
int indexU = width * height + y / 2 * width / 2 + x / 2;
int indexV = width * height + width * height / 4 + y / 2 * width / 2 + x / 2;
unsigned char Y = outbuffer[indexY];
unsigned char U = outbuffer[indexU];
unsigned char V = outbuffer[indexV];
rgb_buffer[index++] = Y + 1.402 * (V - 128); //R
rgb_buffer[index++] = Y - 0.34413 * (U - 128) - 0.71414 * (V - 128); //G
rgb_buffer[index++] = Y + 1.772 * (U - 128); //B
}
}
index = 0;
QImage tmpImg((uchar*)rgb_buffer, width, height, QImage::Format_RGB888);
QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
emit sig_GetOneFrame(image);
QThread::msleep(time);
m_mutex.unlock();
}
}
else if (strcmp(&filepath[len - 3], "rgb") == 0) {
unsigned char* rgb_buffer = (unsigned char*)malloc(width * height * 3);
while (1) {
m_i++;
m_mutex.lock();
fread(rgb_buffer, 1, width * height * 3, fp);
if (rgb_buffer <= 0)
break;
QImage tmpImg((uchar*)rgb_buffer, width, height, QImage::Format_RGB888);
QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
emit sig_GetOneFrame(image);
QThread::msleep(time);//休眠10ms
m_mutex.unlock();
}
}
else
{
qDebug ()<< QString::fromUtf8( "输入文件不符合规定");
}
fclose(fp);
};*/
*/
(3)创建QT类QtWidgetsApplication3.h
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_QtWidgetsApplication3.h"
#include "qtread.h"
//#include <QPainter.h>
//#include<qpixmap.h>
class QtWidgetsApplication3 : public QMainWindow
{
Q_OBJECT
public:
QtWidgetsApplication3(QWidget *parent = Q_NULLPTR);
Ui::QtWidgetsApplication3Class ui;
//void paintEvent(QPaintEvent* event);
protected:
void resizeEvent(QResizeEvent*);
signals:
void sig_GetOneFrame(QImage);
public slots:
void size();
private:
VideoPlayer* mPlayer;
int w;
int h;
int rate;
QImage mImage;
private slots:
void slotGetOneFrame(QImage img);
QString OnBtOpen1();
void play();
void stop();
void changeButton();
void threadPR();
};
4.QtWidgetsApplication3.cpp
#include "QtWidgetsApplication3.h"
#include<QPainter>
#include<QFileDialog>
#include<QResizeEvent>
#include<QHBoxLayout>
#include <QDebug>
QtWidgetsApplication3::QtWidgetsApplication3(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
QHBoxLayout* Layout_frame = new QHBoxLayout;//水平布局
Layout_frame->addWidget(ui.label_2);
Layout_frame->addWidget(ui.rateEdit);
Layout_frame->addWidget(ui.label_5);
QHBoxLayout* Layout_rate = new QHBoxLayout;
Layout_rate->addWidget(ui.label_7);
Layout_rate ->addWidget(ui.weightEdit);
Layout_rate->addWidget(ui.label_10);
Layout_rate->addWidget(ui.highEdit);
Layout_rate->addWidget(ui.pushButton);
QHBoxLayout* Layout_button = new QHBoxLayout;
Layout_button->addWidget(ui.stopButton);
Layout_button->addWidget(ui.playButton);
Layout_button->addWidget(ui.pauseButton);
QHBoxLayout* Layout_file = new QHBoxLayout;
Layout_file->addWidget(ui.label);
Layout_file->addWidget(ui.urlEdit);
Layout_file->addWidget(ui.geteurlButton);
QVBoxLayout* Layout_1 = new QVBoxLayout;
Layout_1->addLayout(Layout_frame);
Layout_1->addLayout(Layout_rate);
QHBoxLayout* Layout_2 = new QHBoxLayout;
Layout_2->addLayout(Layout_1);
Layout_2->addLayout(Layout_button);
QVBoxLayout* Layout_3 = new QVBoxLayout;
Layout_3->addLayout(Layout_2);
Layout_3->addLayout(Layout_file);
QVBoxLayout* Layout_main = new QVBoxLayout;
Layout_main->addWidget(ui.displaylabel);
Layout_main->addLayout(Layout_3);
ui.centralWidget->setLayout(Layout_main);
ui.displaylabel->setAlignment(Qt::AlignCenter);
mPlayer = new VideoPlayer;
connect(mPlayer, SIGNAL(sig_GetOneFrame(QImage)), this, SLOT(slotGetOneFrame(QImage)));
}
void QtWidgetsApplication3::resizeEvent(QResizeEvent *event) {
event->oldSize();
qDebug() << event->size();
}
void QtWidgetsApplication3::slotGetOneFrame(QImage img)
{
//mImage = img;
//update(); //调用update将执行 paintEvent函数
ui.displaylabel->setPixmap(QPixmap::fromImage(img));
}
//获取文件路径槽函数
//
QString QtWidgetsApplication3::OnBtOpen1() {
QString filepath1 = QFileDialog::getOpenFileName(this, tr("Select a File"), ".", tr("Images (*.yuv *.rgb)"));
if (filepath1.isEmpty() ==false)//为空时表示取消操作
{
ui.urlEdit->setText(filepath1);
}
return filepath1;
}
void QtWidgetsApplication3::size() {
w = (ui.weightEdit->displayText()).toInt();
h = (ui.highEdit->displayText()).toInt();
rate = (ui.rateEdit->displayText()).toInt();
mPlayer->setrate(rate);
//ui.centralWidget->resize(w, h + 200);
ui.displaylabel->setGeometry(0, 0, w, h);
this->resize(w,h+200);
}
void QtWidgetsApplication3::play() {
QString url = ui.urlEdit->displayText();
//"E:/Thinking-in-AV-master/video/sin848_480.yuv"//"E:/Thinking-in-AV-master/video/nxndnv.rgb"
mPlayer->setFileName(url);
mPlayer->setwh(w, h);
mPlayer->startPlay();
return;
}
void QtWidgetsApplication3::stop() {
qDebug()<<"停止";
mPlayer->stopPlay();
//nPlayer->terminate();
}
void QtWidgetsApplication3::changeButton() {
if (!QString::compare(ui.pauseButton->text(), "PAUSE"))//QString::fromUtf8
{
ui.pauseButton->setText("CONTINUE");
}
else
{
ui.pauseButton->setText("PAUSE");
}
}
void QtWidgetsApplication3::threadPR() {
mPlayer->threadPR();
//nPlayer->threadPR();
}
(5)main.cpp
#include "QtWidgetsApplication3.h"
#include <QtWidgets/QApplication>
#include<QTextCodec>
#pragma execution_character_set("utf-8")
using namespace std;
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QtWidgetsApplication3 widget;
QTextCodec* codec = QTextCodec::codecForName("GBK");//解决中文问题
QTextCodec::setCodecForLocale(codec);
//设置窗口的标题
widget.setWindowTitle("视频播放器");
widget.show();
widget.resize(500,500);
return a.exec();
}