老早之前用qt集成ffmpeg 做过一个播放器 那个是基于sdl的命令行窗口
这次做成GUI的方式 直接做成一个播放器 可以输入rtsp或者rtmp流地址
效果图如下 那个长的输入框输入rtsp地址 然后点击下面的播放按钮
测试时再ubuntu下面测试的 只有视频 没有音频
由于ffmpeg是支持rtmp h265的 所以 这个也可以播放h265的rtmp流
注意 是ubuntu下面运行的
点击播放之后 按钮会变成停止模式
可以只有缩放 也会随着窗口变化而变化
双击支持全屏显示 再次双击会变回之前的模式
关闭是给出提示
代码 头文件:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <thread>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h"
}
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
int init();
void play();
private:
Ui::MainWindow *ui;
bool m_run_flag = false;
bool m_fullscreen_flag = false;
std::thread m_decodecThread;
AVFormatContext *pAVFormatCtx;
AVCodecContext *pAVCodecCtx;
SwsContext *pSwsCtx;
uint8_t *pRgbBuffer;
AVPacket packet;
AVFrame *pAVFrame = NULL;
AVFrame *pAVFrameRGB;
int iVideoIndex = -1;
QImage m_image;
bool isFinish =false;
void decodec();
signals:
void signalDraw();
protected:
void mouseDoubleClickEvent( QMouseEvent * e ) override;
void paintEvent(QPaintEvent *event) override;
void closeEvent(QCloseEvent * e) override;
private slots:
void slotDraw();
void slotButtonClicked();
void onPlayClicked();
void onExitClicked();
void onStopClicked();
};
#endif // MAINWINDOW_H
cpp源文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QString>
#include "button.h"
#include <QPainter>
#include <QHBoxLayout>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_run_flag = false;
connect(this,&MainWindow::signalDraw,this,&MainWindow::slotDraw);
connect(ui->buttonplay, SIGNAL(clicked()), this, SLOT(onPlayClicked()));
//connect(ui->buttonstop, SIGNAL(clicked()), this, SLOT(onStopClicked()));
//ui->groupVideo->installEventFilter(this);
ui->inputrtsp->setText("rtsp://uer:gd123456@192.168.2.126:554/Streaming/Channels/101");
ui->buttonplay->setIcon(QIcon(":/new/image/play.png"));
ui->buttonplay->setIconSize(QSize(50,50));
ui->buttonplay->setMinimumSize(50,50);
ui->buttonplay->setMaximumSize(50,50);
ui->buttonplay->setFlat(true);
//lay->addWidget(m_pBtnSearch); //。。。。 添加按钮。。。。。。。
//lay->setContentsMargins(0, 0, 0, 0);
//lay->setAlignment(Qt::AlignRight);
//ui->inputrtsp->setFrame(false);
ui->inputrtsp->setStyleSheet("QLineEdit{ background-color: rgba(128,128,128,120); }QLineEdit:focus{background-color: rgb(222,222,222)}"); // 设置样式
//ui->inputrtsp->setTextMargins(0, 0, 30, 0); // 注意这里的30 因为图一的输入框最右边有个按钮。不能让光标在此区域出现。。需要设置文字显示范围
//searchEdit->setPlaceholderText(tr("许嵩")); // 设置默认文字
//ui->buttonplay->setStyleSheet("background-color: yellow");
//ui->buttonstop->setStyleSheet("background-color: red");
#if 0
MyButton *play = new MyButton(this);
connect(play, SIGNAL(clicked()), this, SLOT(onPlayClicked()));
play->move(100,200);
QPushButton *test = new QPushButton(this);
connect(test,SIGNAL(clicked()),this,SLOT(slotButtonClicked()));
test->setText("test button");
test->move(200,300);
connect(ui->buttonexit, SIGNAL(clicked()), this, SLOT(onExitClicked()));
#endif
}
void MainWindow::closeEvent(QCloseEvent *e)
{
QMessageBox::StandardButton result=QMessageBox::question(this, "确认", "确定要退出本程序吗?",
QMessageBox::Yes|QMessageBox::No,
QMessageBox::No);
if (result==QMessageBox::Yes)
e->accept();
else
e->ignore();
}
#if 0
bool eventFilter(QObject *obj, QEvent *event)
{
int i = 0;
if (obj == ui->groupVideo)//当事件发生在u1(为Qlabel型)控件上
{
if (event->type() == QEvent::MouseButtonPress)//当为双击事件时
{
i++;
if (i % 2 == 0) //此处为双击一次全屏,再双击一次退出
{
ui->groupVideo->setWindowFlags(Qt::Dialog);
ui->groupVideo->showFullScreen();//全屏显示
}
else
{
ui->groupVideo->setWindowFlags(Qt::SubWindow);
ui->groupVideo->showNormal();//退出全屏
};
}
return QObject::eventFilter(obj, event);
}
}
#endif
int MainWindow::init()
{
//std::string file = "rtsp://uer:gd123456@192.168.2.121:554/Streaming/Channels/101";
QString file =ui->inputrtsp->text();
//描述多媒体文件的构成及其基本信息
pAVFormatCtx = avformat_alloc_context(); //Init handle
if (avformat_open_input(&pAVFormatCtx, file.toUtf8(), NULL, NULL) != 0)
{
qDebug() <<"open file fail";
avformat_free_context(pAVFormatCtx);
return -1;
}
//读取一部分视音频数据并且获得一些相关的信息
if (avformat_find_stream_info(pAVFormatCtx, NULL) < 0)
{
qDebug() <<"vformat find stream fail";
avformat_close_input(&pAVFormatCtx);
return -1;
}
// 根据解码器枚举类型找到解码器
AVCodec *pAVCodec;
int ret = av_find_best_stream(pAVFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pAVCodec, 0);
if (ret < 0) {
qDebug()<< "av_find_best_stream faliture";
avformat_close_input(&pAVFormatCtx);
return -1;
}
iVideoIndex = ret;
pAVCodec = avcodec_find_decoder(pAVFormatCtx->streams[iVideoIndex]->codecpar->codec_id);
if (pAVCodec == NULL)
{
qDebug()<<"not find decoder";
return -1;
}
qDebug()<<"avcodec_open2 pAVCodec->name:" << QString::fromStdString(pAVCodec->name);
if(pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.den != 0) {
float fps_ = pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.num / pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.den;
qDebug() <<"fps:" << fps_;
}
int64_t video_length_sec_ = pAVFormatCtx->duration/AV_TIME_BASE;
qDebug() <<"video_length_sec_:" << video_length_sec_;
pAVCodecCtx = avcodec_alloc_context3(pAVCodec);
if (pAVCodecCtx == NULL)
{
qDebug() <<"get pAVCodecCtx fail";
avformat_close_input(&pAVFormatCtx);
return -1;
}
ret = avcodec_parameters_to_context(pAVCodecCtx,pAVFormatCtx->streams[iVideoIndex]->codecpar);
if (ret < 0)
{
qDebug() <<"avcodec_parameters_to_context fail";
avformat_close_input(&pAVFormatCtx);
return -1;
}
if (avcodec_open2(pAVCodecCtx, pAVCodec, NULL) < 0)
{
qDebug()<<"avcodec_open2 fail";
return -1;
}
//为解码帧分配内存
//AVFrame 存放从AVPacket中解码出来的原始数据
pAVFrame = av_frame_alloc();
pAVFrameRGB = av_frame_alloc();
//用于视频图像的转换,将源数据转换为RGB32的目标数据
pSwsCtx = sws_getContext(pAVCodecCtx->width, pAVCodecCtx->height, pAVCodecCtx->pix_fmt,
pAVCodecCtx->width, pAVCodecCtx->height, AV_PIX_FMT_RGB32,
SWS_BICUBIC, NULL, NULL, NULL);
int m_size = av_image_get_buffer_size(AVPixelFormat(AV_PIX_FMT_RGB32), pAVCodecCtx->width, pAVCodecCtx->height, 1);
pRgbBuffer = (uint8_t *)(av_malloc(m_size));
//为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间
avpicture_fill((AVPicture *)pAVFrameRGB, pRgbBuffer, AV_PIX_FMT_BGR32, pAVCodecCtx->width, pAVCodecCtx->height);
//av_image_fill_arrays
//AVpacket 用来存放解码数据
av_new_packet(&packet, pAVCodecCtx->width * pAVCodecCtx->height);
return 0;
}
void MainWindow::play()
{
m_decodecThread = std::thread([this]()
{
init();
decodec();
});
m_decodecThread.detach();
}
void MainWindow::decodec()
{
//读取码流中视频帧
while (m_run_flag)
{
int ret = av_read_frame(pAVFormatCtx, &packet);
if(ret != 0)
{
qDebug()<<"file end";
isFinish = !isFinish;
return;
}
if (packet.stream_index != iVideoIndex)
{
av_packet_unref(&packet);
continue;
}
int iGotPic = AVERROR(EAGAIN);
// //解码一帧视频数据
iGotPic = avcodec_send_packet(pAVCodecCtx, &packet);
if(iGotPic!=0){
qDebug()<<"avcodec_send_packet error";
continue;
}
iGotPic = avcodec_receive_frame(pAVCodecCtx, pAVFrame);
if(iGotPic == 0){
//转换像素
sws_scale(pSwsCtx, (uint8_t const * const *)pAVFrame->data, pAVFrame->linesize, 0, pAVCodecCtx->height, pAVFrameRGB->data, pAVFrameRGB->linesize);
//构造QImage
QImage img(pRgbBuffer, pAVCodecCtx->width, pAVCodecCtx->height, QImage::Format_RGB32);
//qDebug()<<"decode img";
m_image = img;
emit signalDraw();
}else {
qDebug()<<"decode error";
}
av_packet_unref(&packet);
//std::this_thread::sleep_for(std::chrono::milliseconds(25));
}
//资源回收
av_free(pAVFrame);
av_free(pAVFrameRGB);
sws_freeContext(pSwsCtx);
avcodec_close(pAVCodecCtx);
avformat_close_input(&pAVFormatCtx);
}
void MainWindow::slotDraw()
{
update();
}
void MainWindow::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setBrush(Qt::black);
painter.drawRect(0, 0, this->width(), this->height());
int appWindowWidth = this->geometry().width();
int appWindowHeight = this->geometry().height();
int button_w = ui->buttonplay->geometry().width();
int button_h = ui->buttonplay->geometry().height();
int intpu_w = ui->groupInput->geometry().width();
int input_h = ui->groupInput->geometry().height();
auto center_x = (appWindowWidth-button_w)/2;
auto center_y = (appWindowHeight-button_h-50);
ui->buttonplay->setGeometry(center_x, center_y, button_w,button_h);
center_x = (appWindowWidth-intpu_w)/2;
center_y = (appWindowHeight-input_h-120);
ui->groupInput->setGeometry(center_x, center_y, intpu_w,input_h);
if(m_run_flag == false)
{
return ;
}
if (m_image.size().width() <= 0)
return;
//比例缩放
QImage img = m_image.scaled(this->size(),Qt::KeepAspectRatio);
int x = this->width() - img.width();
int y = this->height() - img.height();
x /= 2;
y /= 2;
//QPoint(x,y)为中心绘制图像
painter.drawImage(QPoint(x,y),img);
}
void MainWindow::slotButtonClicked()
{
QMessageBox::information(NULL,"Click","Double click",QMessageBox::Accepted);
}
void MainWindow::onPlayClicked()
{
if(m_run_flag == false)
{
ui->buttonplay->setIcon(QIcon(":/new/image/stop.png"));
ui->buttonplay->setIconSize(QSize(50,50));
ui->buttonplay->setMinimumSize(50,50);
ui->buttonplay->setMaximumSize(50,50);
ui->buttonplay->setFlat(true);
m_run_flag = true;
play();
}
else
{
m_run_flag = false;
ui->buttonplay->setIcon(QIcon(":/new/image/play.png"));
ui->buttonplay->setIconSize(QSize(50,50));
ui->buttonplay->setMinimumSize(50,50);
ui->buttonplay->setMaximumSize(50,50);
ui->buttonplay->setFlat(true);
//QMessageBox::information(NULL,"Click","Allready in playing...",QMessageBox::Accepted);
}
}
void MainWindow::onExitClicked()
{
QMessageBox::information(NULL,"Click","onexitClicked",QMessageBox::Accepted);
//ui->buttonexit->setText("I am exit button");
}
void MainWindow::onStopClicked()
{
//QMessageBox::information(NULL,"Click","onStopClicked",QMessageBox::Accepted);
m_run_flag = false;
update();
//qApp->exit();
}
void MainWindow::mouseDoubleClickEvent( QMouseEvent * e )
{
if ( e->button() == Qt::LeftButton )
{
m_fullscreen_flag = 1-m_fullscreen_flag;
if(m_fullscreen_flag)
{
showFullScreen();
}
else
{
showNormal();
}
}
// You may have to call the parent's method for other cases
QMainWindow::mouseDoubleClickEvent( e );
}
MainWindow::~MainWindow()
{
m_run_flag = false;
delete ui;
}
这个时个demo很多异常处理都没有做
算是QT入个门
项目配置文件 利用ffmpeg软解码
QMAKE_CXXFLAGS += -std=c++0x
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = qtplayer
TEMPLATE = app
SOURCES += main.cpp\
mainwindow.cpp \
button.cpp
INCLUDEPATH += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/include
INCLUDEPATH += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubunt_rmtp_h265/include
LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavformat.a
LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavcodec.a
LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavdevice.a
LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavfilter.a
LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavutil.a
LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libswresample.a
LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libswscale.a
LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubuntu/lib/libSDL2.a
LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubuntu/lib/libSDL2main.a
LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubuntu/lib/libSDL2_test.a
LIBS += -lz
HEADERS += mainwindow.h \
button.h
FORMS += mainwindow.ui
RESOURCES += \
image.qrc