前面一篇记录了最后实现的像素数据播放器方案,在实现最后一版前还有两个版本,在这里记录一下。
方案一
利用Qpainter实现画图,但是经查阅资料,QPainte实现rYUV / RGB 转换费CPU(QLabel也存在这个问题);QPainter大面积绘制效率不高。
功能:播放、暂停、停止
界面设计:一共三个按键 播放、暂停、停止
多线程部分与前一篇博客一致,这里只记录qt播放器的代码
qtwidegt.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);//
private:
VideoPlayer* mPlayer;
int w=848;//设置像素数据的宽高
int h=480;
int rate=25;//设置播放速度
QImage mImage;
private slots:
void slotGetOneFrame(QImage img);
void play();
void stop();
void threadPR();
};
cpp文件
#include "QtWidgetsApplication3.h"
#include<QPainter>
#include<QFileDialog>
#include<QResizeEvent>
#include<QHBoxLayout>
#include <QDebug>
QtWidgetsApplication3::QtWidgetsApplication3(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
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函数
}
void QtWidgetsApplication3::play() {
//QString url = ui.urlEdit->displayText();
mPlayer->setFileName("E:/Thinking-in-AV-master/video/sin848_480.yuv");//文件路径
mPlayer->setwh(w, h);
mPlayer->startPlay();
return;
}
void QtWidgetsApplication3::stop() {
qDebug()<<"停止";
mPlayer->stopPlay();
}
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();
}
void QtWidgetsApplication3::paintEvent(QPaintEvent* event) {
QPainter painter(this);
painter.setBrush(Qt::black);
painter.drawRect(0, 0, this->width(), this->height()); //先画成黑色
if (mImage.size().width() <= 0) return;
///将图像按比例缩放成和窗口一样大小
QImage img = mImage.scaled(this->size(), Qt::KeepAspectRatio);
int x = this->width() - img.width();
int y = this->height() - img.height();
x /= 2;
y /= 2;
painter.drawImage(QPoint(x, y), img); //画出图像
}
main函数与前面也没有变化,结果如下所示
方案二 SDL+ffmpeg实现MP4播放
这个方案是最开始的版本,直接利用ffmpeg和SDL实现渲染,最后将SDL写入到Qlabel里,在ui界面创建三个按钮(播放、暂停、停止)以及一个qlabel文本框
头文件
#include <QtWidgets/QMainWindow>
#include "ui_QtWidgetsApplication3.h"
class QtWidgetsApplication3 : public QMainWindow
{
Q_OBJECT
public:
QtWidgetsApplication3(QWidget *parent = Q_NULLPTR);
Ui::QtWidgetsApplication3Class ui;
protected:
void resizeEvent(QResizeEvent*);
private slots:
int stop();
int play();
int pause();
};
cpp文件
#include "QtWidgetsApplication3.h"
#include<QResizeEvent>
#include <QDebug>
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL2/SDL.h"
}
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)//定义事件
#define SFM_BREAK_EVENT (SDL_USEREVENT + 2)
int thread_exit;//SDL线程的退出标志,设置为全局变量
int thread_pause;//SDL线程的暂停标志,设置为全局变量
QtWidgetsApplication3::QtWidgetsApplication3(QWidget *parent)
: QMainWindow(parent)
{
thread_exit = 0;
thread_pause = 0;
ui.setupUi(this);
connect(ui.playButton, SIGNAL(clicked()), this, SLOT(play()));
connect(ui.pauseButton, SIGNAL(clicked()), this, SLOT(pause()));
connect(ui.stopButton, SIGNAL(clicked()), this, SLOT(stop()));
}
//SDL线程控制
int sfp_refresh_thread(void* opaque) {
thread_exit = 0;
thread_pause = 0;
while (!thread_exit) {
if (!thread_pause) {
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
}
SDL_Delay(40);
}
thread_exit = 0;
thread_pause = 0;
//Break
SDL_Event event;
event.type = SFM_BREAK_EVENT;
SDL_PushEvent(&event);
return 0;
}
//停止播放槽函数
int QtWidgetsApplication3::stop() {
thread_exit = 1;
return 0;
}
//暂停播放槽函数
int QtWidgetsApplication3::pause() {
if (ui.pauseButton->text() == "PAUSE") {
thread_pause = 1;
ui.pauseButton->setText("CONTINUE");
}
else{
thread_pause = 0;
ui.pauseButton->setText("PAUSE");
}
return 0;
}
//播放槽函数
int QtWidgetsApplication3::play() {
AVFormatContext* pFormatCtx;
int i, videoindex;
AVCodecContext* pCodecCtx;
AVCodec* pCodec;
AVFrame* pFrame, * pFrameYUV;
unsigned char* out_buffer;
AVPacket* packet;
int ret, got_picture;
//------------SDL----------------
int screen_w, screen_h;
SDL_Window* screen;
SDL_Renderer* sdlRenderer;
SDL_Texture* sdlTexture;
SDL_Rect sdlRect;
SDL_Thread* video_tid;
SDL_Event event;
struct SwsContext* img_convert_ctx;
const char* filepath = "E://Thinking-in-AV-master//video//nxn.mp4";
pFormatCtx = avformat_alloc_context();
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
qDebug() << "Couldn't open input stream.\n";
return -1;
}
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
qDebug()<<"Couldn't find stream information.\n";
return -1;
}
videoindex = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
if (videoindex == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
AVCodecParameters* pPmt;
pPmt = pFormatCtx->streams[videoindex]->codecpar;
pCodec = avcodec_find_decoder(pPmt->codec_id);//获得codec
pCodecCtx = avcodec_alloc_context3(pCodec);///构造AVCodecContext ,并将vcodec填入AVCodecContext中
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoindex]>codecpar);//初始化AVCodecContext
if (pCodec == NULL) {
printf("Codec not found.\n");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec.\n");
return -1;
}
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
out_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,
AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
//Output Info-----------------------------
//qDebug()>>"---------------- File Information ---------------\n";
//v_dump_format(pFormatCtx, 0, filepath, 0);
//qDebug()>>"-------------------------------------------------\n";
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf("Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
//SDL 2.0 Support for multiple windows
screen_w = pCodecCtx->width;
screen_h = pCodecCtx->height;
//重设播放器位置和大小
ui.displaylabel->resize(pCodecCtx->width, pCodecCtx->height);
this->resize(pCodecCtx->width, pCodecCtx->height + 50);
ui.playButton->setGeometry((pCodecCtx->width/2)-25,pCodecCtx->height+15,50,20);
ui.stopButton->setGeometry((pCodecCtx->width / 2)-75, pCodecCtx->height+15, 50, 20);
ui.pauseButton->setGeometry((pCodecCtx->width / 2)+25, pCodecCtx->height+15, 50, 20);
screen = SDL_CreateWindowFrom((void*)ui.displaylabel->winId());//将画面嵌入到label种
//screen = SDL_CreateWindow("Simplest ffmpeg player's Window"SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,screen_w, screen_h, SDL_WINDOW_OPENGL);//这种的话SDL不会嵌入到播放器,会重新弹出一个窗口
if (!screen) {
//qDebug()>>"SDL: could not create window - exiting:%s\n">>SDL_GetError();
return -1;
}
sdlRenderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = 2 * screen_w;
sdlRect.h = 2 * screen_h;
packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//创建多线程
video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);//函数指针,把一个函数名作为参数
//------------SDL End------------
//Event Loop
SDL_Delay(100);
for (;;) {//进入循环
//Wait
SDL_WaitEvent(&event);//执行到这里不动,等待消息,接受事件,执行子线程
if (event.type == SFM_REFRESH_EVENT) {//自定义事件,不断刷新屏幕
while (1) {
if (av_read_frame(pFormatCtx, packet) < 0)
thread_exit = 1;
if (packet->stream_index == videoindex)
break;
}
ret = avcodec_send_packet(pCodecCtx, packet);
got_picture = avcodec_receive_frame(pCodecCtx, pFrame);
if (ret < 0) {
printf("Decode Error.\n");
return -1;
}
if (!got_picture) {
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
//SDL---------------------------
SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
SDL_RenderClear(sdlRenderer);
//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
SDL_RenderPresent(sdlRenderer);
//SDL End-----------------------
}
av_packet_unref(packet);
}
else if (event.type == SDL_KEYDOWN) {
//Pause
if (event.key.keysym.sym == SDLK_SPACE)
thread_pause = !thread_pause;
}
else if (event.type == SDL_QUIT) {//触发窗口×的功能(退出)
thread_exit = 1;
}
else if (event.type == SDL_WINDOWEVENT) {
SDL_GetWindowSize(screen, &screen_w, &screen_h);//实现自适应变换窗口大小,系统自带事件
}
else if (event.type == SFM_BREAK_EVENT) {
break;
}
}
sws_freeContext(img_convert_ctx);
SDL_Quit();
//--------------
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
//ui.label_4->setText("播放完毕");
//ui.label_4->setVisible(true);
return 0;
}
主函数
#include "QtWidgetsApplication3.h"
#include <QtWidgets/QApplication>
#include<QTextCodec>
#pragma execution_character_set("utf-8")
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QtWidgetsApplication3 widget;
QTextCodec* codec = QTextCodec::codecForName("GBK");//解决中文问题
QTextCodec::setCodecForLocale(codec);
//设置窗口的标题
widget.setWindowTitle("视频播放器");
widget.show();
return a.exec();//回调函数不断监测窗口事件
}