• 用QT做一个rtsp / rtmp实时流的播放器 ffmpeg


    老早之前用qt集成ffmpeg 做过一个播放器 那个是基于sdl的命令行窗口  

    这次做成GUI的方式 直接做成一个播放器  可以输入rtsp或者rtmp流地址

    效果图如下 那个长的输入框输入rtsp地址 然后点击下面的播放按钮

    测试时再ubuntu下面测试的 只有视频  没有音频

    由于ffmpeg是支持rtmp h265的 所以 这个也可以播放h265的rtmp流

    注意 是ubuntu下面运行的

    点击这里可以下载 编译的可执行文件

     点击播放之后 按钮会变成停止模式

     可以只有缩放 也会随着窗口变化而变化

     双击支持全屏显示 再次双击会变回之前的模式

     关闭是给出提示

    代码 头文件:

    1. #ifndef MAINWINDOW_H
    2. #define MAINWINDOW_H
    3. #include
    4. #include
    5. extern "C"
    6. {
    7. #include "libavcodec/avcodec.h"
    8. #include "libavformat/avformat.h"
    9. #include "libswscale/swscale.h"
    10. #include "libavdevice/avdevice.h"
    11. #include "libswresample/swresample.h"
    12. #include "libavutil/imgutils.h"
    13. }
    14. namespace Ui {
    15. class MainWindow;
    16. }
    17. class MainWindow : public QMainWindow
    18. {
    19. Q_OBJECT
    20. public:
    21. explicit MainWindow(QWidget *parent = 0);
    22. ~MainWindow();
    23. int init();
    24. void play();
    25. private:
    26. Ui::MainWindow *ui;
    27. bool m_run_flag = false;
    28. bool m_fullscreen_flag = false;
    29. std::thread m_decodecThread;
    30. AVFormatContext *pAVFormatCtx;
    31. AVCodecContext *pAVCodecCtx;
    32. SwsContext *pSwsCtx;
    33. uint8_t *pRgbBuffer;
    34. AVPacket packet;
    35. AVFrame *pAVFrame = NULL;
    36. AVFrame *pAVFrameRGB;
    37. int iVideoIndex = -1;
    38. QImage m_image;
    39. bool isFinish =false;
    40. void decodec();
    41. signals:
    42. void signalDraw();
    43. protected:
    44. void mouseDoubleClickEvent( QMouseEvent * e ) override;
    45. void paintEvent(QPaintEvent *event) override;
    46. void closeEvent(QCloseEvent * e) override;
    47. private slots:
    48. void slotDraw();
    49. void slotButtonClicked();
    50. void onPlayClicked();
    51. void onExitClicked();
    52. void onStopClicked();
    53. };
    54. #endif // MAINWINDOW_H

    cpp源文件

    1. #include "mainwindow.h"
    2. #include "ui_mainwindow.h"
    3. #include
    4. #include
    5. #include "button.h"
    6. #include
    7. #include
    8. #include
    9. MainWindow::MainWindow(QWidget *parent) :
    10. QMainWindow(parent),
    11. ui(new Ui::MainWindow)
    12. {
    13. ui->setupUi(this);
    14. m_run_flag = false;
    15. connect(this,&MainWindow::signalDraw,this,&MainWindow::slotDraw);
    16. connect(ui->buttonplay, SIGNAL(clicked()), this, SLOT(onPlayClicked()));
    17. //connect(ui->buttonstop, SIGNAL(clicked()), this, SLOT(onStopClicked()));
    18. //ui->groupVideo->installEventFilter(this);
    19. ui->inputrtsp->setText("rtsp://uer:gd123456@192.168.2.126:554/Streaming/Channels/101");
    20. ui->buttonplay->setIcon(QIcon(":/new/image/play.png"));
    21. ui->buttonplay->setIconSize(QSize(50,50));
    22. ui->buttonplay->setMinimumSize(50,50);
    23. ui->buttonplay->setMaximumSize(50,50);
    24. ui->buttonplay->setFlat(true);
    25. //lay->addWidget(m_pBtnSearch); //。。。。 添加按钮。。。。。。。
    26. //lay->setContentsMargins(0, 0, 0, 0);
    27. //lay->setAlignment(Qt::AlignRight);
    28. //ui->inputrtsp->setFrame(false);
    29. ui->inputrtsp->setStyleSheet("QLineEdit{ background-color: rgba(128,128,128,120); }QLineEdit:focus{background-color: rgb(222,222,222)}"); // 设置样式
    30. //ui->inputrtsp->setTextMargins(0, 0, 30, 0); // 注意这里的30 因为图一的输入框最右边有个按钮。不能让光标在此区域出现。。需要设置文字显示范围
    31. //searchEdit->setPlaceholderText(tr("许嵩")); // 设置默认文字
    32. //ui->buttonplay->setStyleSheet("background-color: yellow");
    33. //ui->buttonstop->setStyleSheet("background-color: red");
    34. #if 0
    35. MyButton *play = new MyButton(this);
    36. connect(play, SIGNAL(clicked()), this, SLOT(onPlayClicked()));
    37. play->move(100,200);
    38. QPushButton *test = new QPushButton(this);
    39. connect(test,SIGNAL(clicked()),this,SLOT(slotButtonClicked()));
    40. test->setText("test button");
    41. test->move(200,300);
    42. connect(ui->buttonexit, SIGNAL(clicked()), this, SLOT(onExitClicked()));
    43. #endif
    44. }
    45. void MainWindow::closeEvent(QCloseEvent *e)
    46. {
    47. QMessageBox::StandardButton result=QMessageBox::question(this, "确认", "确定要退出本程序吗?",
    48. QMessageBox::Yes|QMessageBox::No,
    49. QMessageBox::No);
    50. if (result==QMessageBox::Yes)
    51. e->accept();
    52. else
    53. e->ignore();
    54. }
    55. #if 0
    56. bool eventFilter(QObject *obj, QEvent *event)
    57. {
    58. int i = 0;
    59. if (obj == ui->groupVideo)//当事件发生在u1(为Qlabel型)控件上
    60. {
    61. if (event->type() == QEvent::MouseButtonPress)//当为双击事件时
    62. {
    63. i++;
    64. if (i % 2 == 0) //此处为双击一次全屏,再双击一次退出
    65. {
    66. ui->groupVideo->setWindowFlags(Qt::Dialog);
    67. ui->groupVideo->showFullScreen();//全屏显示
    68. }
    69. else
    70. {
    71. ui->groupVideo->setWindowFlags(Qt::SubWindow);
    72. ui->groupVideo->showNormal();//退出全屏
    73. };
    74. }
    75. return QObject::eventFilter(obj, event);
    76. }
    77. }
    78. #endif
    79. int MainWindow::init()
    80. {
    81. //std::string file = "rtsp://uer:gd123456@192.168.2.121:554/Streaming/Channels/101";
    82. QString file =ui->inputrtsp->text();
    83. //描述多媒体文件的构成及其基本信息
    84. pAVFormatCtx = avformat_alloc_context(); //Init handle
    85. if (avformat_open_input(&pAVFormatCtx, file.toUtf8(), NULL, NULL) != 0)
    86. {
    87. qDebug() <<"open file fail";
    88. avformat_free_context(pAVFormatCtx);
    89. return -1;
    90. }
    91. //读取一部分视音频数据并且获得一些相关的信息
    92. if (avformat_find_stream_info(pAVFormatCtx, NULL) < 0)
    93. {
    94. qDebug() <<"vformat find stream fail";
    95. avformat_close_input(&pAVFormatCtx);
    96. return -1;
    97. }
    98. // 根据解码器枚举类型找到解码器
    99. AVCodec *pAVCodec;
    100. int ret = av_find_best_stream(pAVFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pAVCodec, 0);
    101. if (ret < 0) {
    102. qDebug()<< "av_find_best_stream faliture";
    103. avformat_close_input(&pAVFormatCtx);
    104. return -1;
    105. }
    106. iVideoIndex = ret;
    107. pAVCodec = avcodec_find_decoder(pAVFormatCtx->streams[iVideoIndex]->codecpar->codec_id);
    108. if (pAVCodec == NULL)
    109. {
    110. qDebug()<<"not find decoder";
    111. return -1;
    112. }
    113. qDebug()<<"avcodec_open2 pAVCodec->name:" << QString::fromStdString(pAVCodec->name);
    114. if(pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.den != 0) {
    115. float fps_ = pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.num / pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.den;
    116. qDebug() <<"fps:" << fps_;
    117. }
    118. int64_t video_length_sec_ = pAVFormatCtx->duration/AV_TIME_BASE;
    119. qDebug() <<"video_length_sec_:" << video_length_sec_;
    120. pAVCodecCtx = avcodec_alloc_context3(pAVCodec);
    121. if (pAVCodecCtx == NULL)
    122. {
    123. qDebug() <<"get pAVCodecCtx fail";
    124. avformat_close_input(&pAVFormatCtx);
    125. return -1;
    126. }
    127. ret = avcodec_parameters_to_context(pAVCodecCtx,pAVFormatCtx->streams[iVideoIndex]->codecpar);
    128. if (ret < 0)
    129. {
    130. qDebug() <<"avcodec_parameters_to_context fail";
    131. avformat_close_input(&pAVFormatCtx);
    132. return -1;
    133. }
    134. if (avcodec_open2(pAVCodecCtx, pAVCodec, NULL) < 0)
    135. {
    136. qDebug()<<"avcodec_open2 fail";
    137. return -1;
    138. }
    139. //为解码帧分配内存
    140. //AVFrame 存放从AVPacket中解码出来的原始数据
    141. pAVFrame = av_frame_alloc();
    142. pAVFrameRGB = av_frame_alloc();
    143. //用于视频图像的转换,将源数据转换为RGB32的目标数据
    144. pSwsCtx = sws_getContext(pAVCodecCtx->width, pAVCodecCtx->height, pAVCodecCtx->pix_fmt,
    145. pAVCodecCtx->width, pAVCodecCtx->height, AV_PIX_FMT_RGB32,
    146. SWS_BICUBIC, NULL, NULL, NULL);
    147. int m_size = av_image_get_buffer_size(AVPixelFormat(AV_PIX_FMT_RGB32), pAVCodecCtx->width, pAVCodecCtx->height, 1);
    148. pRgbBuffer = (uint8_t *)(av_malloc(m_size));
    149. //为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间
    150. avpicture_fill((AVPicture *)pAVFrameRGB, pRgbBuffer, AV_PIX_FMT_BGR32, pAVCodecCtx->width, pAVCodecCtx->height);
    151. //av_image_fill_arrays
    152. //AVpacket 用来存放解码数据
    153. av_new_packet(&packet, pAVCodecCtx->width * pAVCodecCtx->height);
    154. return 0;
    155. }
    156. void MainWindow::play()
    157. {
    158. m_decodecThread = std::thread([this]()
    159. {
    160. init();
    161. decodec();
    162. });
    163. m_decodecThread.detach();
    164. }
    165. void MainWindow::decodec()
    166. {
    167. //读取码流中视频帧
    168. while (m_run_flag)
    169. {
    170. int ret = av_read_frame(pAVFormatCtx, &packet);
    171. if(ret != 0)
    172. {
    173. qDebug()<<"file end";
    174. isFinish = !isFinish;
    175. return;
    176. }
    177. if (packet.stream_index != iVideoIndex)
    178. {
    179. av_packet_unref(&packet);
    180. continue;
    181. }
    182. int iGotPic = AVERROR(EAGAIN);
    183. // //解码一帧视频数据
    184. iGotPic = avcodec_send_packet(pAVCodecCtx, &packet);
    185. if(iGotPic!=0){
    186. qDebug()<<"avcodec_send_packet error";
    187. continue;
    188. }
    189. iGotPic = avcodec_receive_frame(pAVCodecCtx, pAVFrame);
    190. if(iGotPic == 0){
    191. //转换像素
    192. sws_scale(pSwsCtx, (uint8_t const * const *)pAVFrame->data, pAVFrame->linesize, 0, pAVCodecCtx->height, pAVFrameRGB->data, pAVFrameRGB->linesize);
    193. //构造QImage
    194. QImage img(pRgbBuffer, pAVCodecCtx->width, pAVCodecCtx->height, QImage::Format_RGB32);
    195. //qDebug()<<"decode img";
    196. m_image = img;
    197. emit signalDraw();
    198. }else {
    199. qDebug()<<"decode error";
    200. }
    201. av_packet_unref(&packet);
    202. //std::this_thread::sleep_for(std::chrono::milliseconds(25));
    203. }
    204. //资源回收
    205. av_free(pAVFrame);
    206. av_free(pAVFrameRGB);
    207. sws_freeContext(pSwsCtx);
    208. avcodec_close(pAVCodecCtx);
    209. avformat_close_input(&pAVFormatCtx);
    210. }
    211. void MainWindow::slotDraw()
    212. {
    213. update();
    214. }
    215. void MainWindow::paintEvent(QPaintEvent *event)
    216. {
    217. QPainter painter(this);
    218. painter.setBrush(Qt::black);
    219. painter.drawRect(0, 0, this->width(), this->height());
    220. int appWindowWidth = this->geometry().width();
    221. int appWindowHeight = this->geometry().height();
    222. int button_w = ui->buttonplay->geometry().width();
    223. int button_h = ui->buttonplay->geometry().height();
    224. int intpu_w = ui->groupInput->geometry().width();
    225. int input_h = ui->groupInput->geometry().height();
    226. auto center_x = (appWindowWidth-button_w)/2;
    227. auto center_y = (appWindowHeight-button_h-50);
    228. ui->buttonplay->setGeometry(center_x, center_y, button_w,button_h);
    229. center_x = (appWindowWidth-intpu_w)/2;
    230. center_y = (appWindowHeight-input_h-120);
    231. ui->groupInput->setGeometry(center_x, center_y, intpu_w,input_h);
    232. if(m_run_flag == false)
    233. {
    234. return ;
    235. }
    236. if (m_image.size().width() <= 0)
    237. return;
    238. //比例缩放
    239. QImage img = m_image.scaled(this->size(),Qt::KeepAspectRatio);
    240. int x = this->width() - img.width();
    241. int y = this->height() - img.height();
    242. x /= 2;
    243. y /= 2;
    244. //QPoint(x,y)为中心绘制图像
    245. painter.drawImage(QPoint(x,y),img);
    246. }
    247. void MainWindow::slotButtonClicked()
    248. {
    249. QMessageBox::information(NULL,"Click","Double click",QMessageBox::Accepted);
    250. }
    251. void MainWindow::onPlayClicked()
    252. {
    253. if(m_run_flag == false)
    254. {
    255. ui->buttonplay->setIcon(QIcon(":/new/image/stop.png"));
    256. ui->buttonplay->setIconSize(QSize(50,50));
    257. ui->buttonplay->setMinimumSize(50,50);
    258. ui->buttonplay->setMaximumSize(50,50);
    259. ui->buttonplay->setFlat(true);
    260. m_run_flag = true;
    261. play();
    262. }
    263. else
    264. {
    265. m_run_flag = false;
    266. ui->buttonplay->setIcon(QIcon(":/new/image/play.png"));
    267. ui->buttonplay->setIconSize(QSize(50,50));
    268. ui->buttonplay->setMinimumSize(50,50);
    269. ui->buttonplay->setMaximumSize(50,50);
    270. ui->buttonplay->setFlat(true);
    271. //QMessageBox::information(NULL,"Click","Allready in playing...",QMessageBox::Accepted);
    272. }
    273. }
    274. void MainWindow::onExitClicked()
    275. {
    276. QMessageBox::information(NULL,"Click","onexitClicked",QMessageBox::Accepted);
    277. //ui->buttonexit->setText("I am exit button");
    278. }
    279. void MainWindow::onStopClicked()
    280. {
    281. //QMessageBox::information(NULL,"Click","onStopClicked",QMessageBox::Accepted);
    282. m_run_flag = false;
    283. update();
    284. //qApp->exit();
    285. }
    286. void MainWindow::mouseDoubleClickEvent( QMouseEvent * e )
    287. {
    288. if ( e->button() == Qt::LeftButton )
    289. {
    290. m_fullscreen_flag = 1-m_fullscreen_flag;
    291. if(m_fullscreen_flag)
    292. {
    293. showFullScreen();
    294. }
    295. else
    296. {
    297. showNormal();
    298. }
    299. }
    300. // You may have to call the parent's method for other cases
    301. QMainWindow::mouseDoubleClickEvent( e );
    302. }
    303. MainWindow::~MainWindow()
    304. {
    305. m_run_flag = false;
    306. delete ui;
    307. }

    这个时个demo很多异常处理都没有做

    算是QT入个门

    项目配置文件  利用ffmpeg软解码

    1. QMAKE_CXXFLAGS += -std=c++0x
    2. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    3. TARGET = qtplayer
    4. TEMPLATE = app
    5. SOURCES += main.cpp\
    6. mainwindow.cpp \
    7. button.cpp
    8. INCLUDEPATH += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/include
    9. INCLUDEPATH += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubunt_rmtp_h265/include
    10. LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavformat.a
    11. LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavcodec.a
    12. LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavdevice.a
    13. LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavfilter.a
    14. LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavutil.a
    15. LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libswresample.a
    16. LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libswscale.a
    17. LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubuntu/lib/libSDL2.a
    18. LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubuntu/lib/libSDL2main.a
    19. LIBS += /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubuntu/lib/libSDL2_test.a
    20. LIBS += -lz
    21. HEADERS += mainwindow.h \
    22. button.h
    23. FORMS += mainwindow.ui
    24. RESOURCES += \
    25. image.qrc

  • 相关阅读:
    栈——火车进出栈问题(卡特兰数,组合计数+高精度乘法+筛质数+求n的阶乘里的质因数次数(模板))
    基于OXC的光电联动全光网组网方案研究与实践
    STM32CubeMX教程8 TIM 通用定时器 - 输出比较
    电脑提示找不到msvcp140.dll无法继续执行代码的4种解决办法
    Redis入门完整教程:Bitmaps
    详解析构函数、拷贝构造函数
    运算符与分支、循环语句
    计算机毕业设计ssm+vue基本微信小程序的今日菜谱系统
    数据类型内置方法理论以及相关操作
    Spring中Bean的作用域和生命周期
  • 原文地址:https://blog.csdn.net/baoecit/article/details/126667712