├── README.md ├── VideoPlayer_2.pro ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── videoplayer.cpp └── videoplayer.h /README.md: -------------------------------------------------------------------------------- 1 | # FFmpeg-QT实现摄像头rtsp实时显示 2 | 1. 程序运行平台:(1)win10 64bit (2)Qt Creator 4.3.1 3 | 2. 程序需要的库:(1)Qt 5.9.1(MSVC 2015,32bit) (2)FFmpeg 2.5.2 4 | 3. 程序流程图: 5 | 程序流程图如图1所示,图中所示的主函数部分主要完成界面的构建、播放线程的建立以及参考坐标系的建立。图1展示了整个程序的运行流程。 6 | 7 | ![图1 程序流程图](https://git.oschina.net/uploads/images/2017/0905/143250_0efc807a_1477507.jpeg "视频播放流程图.jpg") 8 | 9 | 4. 程序界面: 10 | 程序运行界面如图2所示。 11 | 12 | ![图2 程序运行界面](https://git.oschina.net/uploads/images/2017/0905/143322_08a2b5af_1477507.jpeg "运行界面.jpg") 13 | 14 | 5. 主要功能: 15 | 该界面主要实现四个功能: 16 | (1).读取摄像头视频流(rtsp),并实时显示到主界面上(注:存在0.7s左右的延时,延时测试过程如图3所示); 17 | 18 | ![图3 延时测试过程](https://git.oschina.net/uploads/images/2017/0905/143346_70845a4b_1477507.png "延时测试.png") 19 | 20 | (2).将rtsp视频流经过FFmpeg解码后的YUV数据转化成RGB32数据,提取其中的R(红色)通道,并在界面中的小窗显示(如图2中的左上角部分); 21 | (3).将水下机器人的横滚角反映在界面上(如图2中,中间部分的虚线“十字”为水平和竖直参考位置;实线“十字”为横滚运动后机器人相对参考位置的角度变化,图示为模拟横滚角为10度的情形)。 22 | (4).若程序掉电,再次上电后能够自动地建立连接。 23 | -------------------------------------------------------------------------------- /VideoPlayer_2.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = VideoPlayer_2 12 | TEMPLATE = app 13 | 14 | 15 | SOURCES += src/main.cpp \ 16 | src/videoplayer/videoplayer.cpp \ 17 | src/mainwindow.cpp 18 | 19 | HEADERS += \ 20 | src/videoplayer/videoplayer.h \ 21 | src/mainwindow.h 22 | 23 | FORMS += \ 24 | src/mainwindow.ui 25 | 26 | INCLUDEPATH += $$PWD/ffmpeg/include \ 27 | $$PWD/src 28 | 29 | LIBS += $$PWD/ffmpeg/lib/avcodec.lib \ 30 | $$PWD/ffmpeg/lib/avdevice.lib \ 31 | $$PWD/ffmpeg/lib/avfilter.lib \ 32 | $$PWD/ffmpeg/lib/avformat.lib \ 33 | $$PWD/ffmpeg/lib/avutil.lib \ 34 | $$PWD/ffmpeg/lib/postproc.lib \ 35 | $$PWD/ffmpeg/lib/swresample.lib \ 36 | $$PWD/ffmpeg/lib/swscale.lib 37 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 |  2 | /** 3 | * 李震 4 | * 我的码云:https://git.oschina.net/git-lizhen 5 | * 我的CSDN博客:http://blog.csdn.net/weixin_38215395 6 | * 联系:QQ1039953685 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #include "mainwindow.h" 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | QApplication a(argc, argv); 17 | 18 | QTextCodec *codec = QTextCodec::codecForName("UTF-8"); //设置编码格式为UTF-8 19 | QTextCodec::setCodecForLocale(codec); 20 | 21 | MainWindow w; 22 | w.show(); 23 | 24 | return a.exec(); 25 | } 26 | 27 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * 李震 3 | * 我的码云:https://git.oschina.net/git-lizhen 4 | * 我的CSDN博客:http://blog.csdn.net/weixin_38215395 5 | * 联系:QQ1039953685 6 | */ 7 | 8 | #include "mainwindow.h" 9 | #include "ui_mainwindow.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | using namespace std; 17 | MainWindow::MainWindow(QWidget *parent) : 18 | QMainWindow(parent), 19 | ui(new Ui::MainWindow) 20 | { 21 | ui->setupUi(this); 22 | 23 | mPlayer = new VideoPlayer; 24 | connect(mPlayer,SIGNAL(sig_GetOneFrame(QImage)),this,SLOT(slotGetOneFrame(QImage))); 25 | ///2017.8.11---lizhen 26 | connect(mPlayer,SIGNAL(sig_GetRFrame(QImage)),this,SLOT(slotGetRFrame(QImage))); 27 | ///2017.8.12---lizhen 28 | connect(ui->Open_red,&QAction::triggered,this,&MainWindow::slotOpenRed); 29 | connect(ui->Close_Red,&QAction::triggered,this,&MainWindow::slotCloseRed); 30 | 31 | mPlayer->startPlay(); 32 | } 33 | 34 | MainWindow::~MainWindow() 35 | { 36 | delete ui; 37 | } 38 | 39 | void MainWindow::paintEvent(QPaintEvent *event) 40 | { 41 | 42 | QPainter painter(this); 43 | 44 | painter.setBrush(Qt::white); 45 | painter.drawRect(0, 0, this->width(), this->height()); //先画成白色 46 | 47 | if (mImage.size().width() <= 0) return; 48 | 49 | ///将图像按比例缩放成和窗口一样大小 50 | QImage img = mImage.scaled(this->size(),Qt::KeepAspectRatio); 51 | 52 | int x = this->width() - img.width(); 53 | int y = this->height() - img.height(); 54 | 55 | x /= 2; 56 | y /= 2; 57 | 58 | painter.drawImage(QPoint(x,y),img); //画出图像 59 | 60 | if(open_red==true){ 61 | ///2017.8.12 62 | QWidget *red_video=new QWidget(this); 63 | red_video->resize(this->width()/3,this->height()/3); 64 | ///2017.8.11---lizhen 65 | //提取出图像中的R数据 66 | painter.setBrush(Qt::white); 67 | painter.drawRect(0, 0, red_video->width(),red_video->height()); //先画成白色 68 | 69 | if (R_mImage.size().width() <= 0) return; 70 | 71 | ///将图像按比例缩放成和窗口一样大小 72 | QImage R_img = R_mImage.scaled(red_video->size(),Qt::KeepAspectRatio); 73 | 74 | int R_x = red_video->width() - R_img.width(); 75 | int R_y = red_video->height() - R_img.height(); 76 | 77 | R_x /= 2; 78 | R_y /= 2; 79 | 80 | painter.drawImage(QPoint(R_x,R_y),R_img); //画出图像 81 | } 82 | 83 | ///2017.8.10---lizhen 84 | //获取图像中心点 85 | double x0=this->width()/2; 86 | double y0=this->height()/2; 87 | 88 | //载体偏移角度,可从设备处获得 89 | double alpha=2; //横滚角alpha 90 | int length=60; 91 | 92 | //设备偏移后的“水平”参考坐标 93 | //横滚角产生 94 | double x_Horizental_right=length*qCos(alpha); 95 | double y_Horizental_right=-length*qSin(alpha); 96 | double x_Horizental_left=-length*qCos(alpha); 97 | double y_Horizental_left=length*qSin(alpha); 98 | double x_Vertical_up=length*qSin(alpha); 99 | double y_Vertical_up=length*qCos(alpha); 100 | double x_Vertical_down=-length*qSin(alpha); 101 | double y_Vertical_down=-length*qCos(alpha); 102 | 103 | ///水平参考坐标系,2017.8.7---lizhen 104 | painter.setPen(QPen(Qt::blue,1,Qt::DotLine)); 105 | painter.drawLine( x0-40,y0, x0+40,y0); 106 | painter.drawLine( x0,y0-40, x0,y0+40); 107 | 108 | ///横滚运动-偏移坐标系,2017.8.7---lizhen 109 | if(alpha!=0) 110 | { 111 | painter.setPen(QPen(Qt::blue,3)); 112 | painter.drawLine( x0+x_Horizental_left,y0+y_Horizental_left, x0+x_Horizental_right,y0+y_Horizental_right); 113 | painter.drawLine( x0+x_Vertical_up,y0+y_Vertical_up, x0+x_Vertical_down,y0+y_Vertical_down); 114 | } 115 | } 116 | 117 | void MainWindow::slotGetOneFrame(QImage img) 118 | { 119 | mImage = img; 120 | update(); //调用update将执行 paintEvent函数 121 | } 122 | ///小窗口显示 123 | void MainWindow::slotGetRFrame(QImage img) 124 | { 125 | R_mImage = img; 126 | update(); //调用update将执行 paintEvent函数 127 | } 128 | ///显示图像红色通道,2017.8.12---lizhen 129 | bool MainWindow::slotOpenRed() 130 | { 131 | open_red=true; 132 | return open_red; 133 | } 134 | ///关闭图像红色通道,2017.8.12 135 | bool MainWindow::slotCloseRed() 136 | { 137 | open_red=false; 138 | return open_red; 139 | } 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | /** 2 | * 李震 3 | * 我的码云:https://git.oschina.net/git-lizhen 4 | * 我的CSDN博客:http://blog.csdn.net/weixin_38215395 5 | * 联系:QQ1039953685 6 | */ 7 | 8 | #ifndef MAINWINDOW_H 9 | #define MAINWINDOW_H 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "videoplayer/videoplayer.h" 17 | 18 | namespace Ui { 19 | class MainWindow; 20 | } 21 | 22 | class MainWindow : public QMainWindow 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | explicit MainWindow(QWidget *parent = 0); 28 | ~MainWindow(); 29 | 30 | protected: 31 | void paintEvent(QPaintEvent *event); 32 | 33 | private: 34 | Ui::MainWindow *ui; 35 | 36 | VideoPlayer *mPlayer; //播放线程 37 | 38 | QImage mImage; //记录当前的图像 39 | QImage R_mImage; //2017.8.11---lizhen 40 | 41 | QString url; 42 | 43 | bool open_red=false; 44 | 45 | private slots: 46 | void slotGetOneFrame(QImage img); 47 | void slotGetRFrame(QImage img);///2017.8.11---lizhen 48 | bool slotOpenRed(); ///2017.8.12---lizhen 49 | bool slotCloseRed(); ///2017.8.12 50 | 51 | }; 52 | 53 | #endif // MAINWINDOW_H 54 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | true 7 | 8 | 9 | 10 | 0 11 | 0 12 | 506 13 | 399 14 | 15 | 16 | 17 | 18 | 0 19 | 0 20 | 21 | 22 | 23 | MainWindow 24 | 25 | 26 | Qt::LeftToRight 27 | 28 | 29 | 30 | 24 31 | 24 32 | 33 | 34 | 35 | 36 | 37 | 38 | 0 39 | 0 40 | 506 41 | 23 42 | 43 | 44 | 45 | 46 | 红色分量开关 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Open 56 | 57 | 58 | 59 | 60 | Open(&O) 61 | 62 | 63 | 64 | 65 | Close(&C) 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /videoplayer.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * 李震 3 | * 我的码云:https://git.oschina.net/git-lizhen 4 | * 我的CSDN博客:http://blog.csdn.net/weixin_38215395 5 | * 联系:QQ1039953685 6 | */ 7 | 8 | #include "videoplayer.h" 9 | 10 | extern "C" 11 | { 12 | #include "libavcodec/avcodec.h" 13 | #include "libavformat/avformat.h" 14 | #include "libavutil/pixfmt.h" 15 | #include "libswscale/swscale.h" 16 | } 17 | 18 | #include 19 | #include 20 | using namespace std; 21 | VideoPlayer::VideoPlayer() 22 | { 23 | 24 | } 25 | 26 | VideoPlayer::~VideoPlayer() 27 | { 28 | 29 | } 30 | 31 | void VideoPlayer::startPlay() 32 | { 33 | ///调用 QThread 的start函数 将会自动执行下面的run函数 run函数是一个新的线程 34 | this->start(); 35 | 36 | } 37 | 38 | void VideoPlayer::run() 39 | { 40 | AVFormatContext *pFormatCtx; 41 | AVCodecContext *pCodecCtx; 42 | AVCodec *pCodec; 43 | AVFrame *pFrame, *pFrameRGB; 44 | AVPacket *packet; 45 | uint8_t *out_buffer; 46 | 47 | static struct SwsContext *img_convert_ctx; 48 | 49 | int videoStream, i, numBytes; 50 | int ret, got_picture; 51 | 52 | avformat_network_init(); ///初始化FFmpeg网络模块,2017.8.5---lizhen 53 | av_register_all(); //初始化FFMPEG 调用了这个才能正常适用编码器和解码器 54 | 55 | 56 | //Allocate an AVFormatContext. 57 | pFormatCtx = avformat_alloc_context(); 58 | 59 | ///2017.8.5---lizhen 60 | AVDictionary *avdic=NULL; 61 | char option_key[]="rtsp_transport"; 62 | char option_value[]="tcp"; 63 | av_dict_set(&avdic,option_key,option_value,0); 64 | char option_key2[]="max_delay"; 65 | char option_value2[]="100"; 66 | av_dict_set(&avdic,option_key2,option_value2,0); 67 | ///rtsp地址,可根据实际情况修改 68 | char url[]="rtsp://admin:admin@192.168.1.18:554/h264/ch1/main/av_stream"; 69 | 70 | if (avformat_open_input(&pFormatCtx, url, NULL, &avdic) != 0) { 71 | printf("can't open the file. \n"); 72 | return; 73 | } 74 | 75 | if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { 76 | printf("Could't find stream infomation.\n"); 77 | return; 78 | } 79 | 80 | videoStream = -1; 81 | 82 | ///循环查找视频中包含的流信息,直到找到视频类型的流 83 | ///便将其记录下来 保存到videoStream变量中 84 | ///这里我们现在只处理视频流 音频流先不管他 85 | for (i = 0; i < pFormatCtx->nb_streams; i++) { 86 | if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { 87 | videoStream = i; 88 | } 89 | } 90 | 91 | ///如果videoStream为-1 说明没有找到视频流 92 | if (videoStream == -1) { 93 | printf("Didn't find a video stream.\n"); 94 | return; 95 | } 96 | 97 | ///查找解码器 98 | pCodecCtx = pFormatCtx->streams[videoStream]->codec; 99 | pCodec = avcodec_find_decoder(pCodecCtx->codec_id); 100 | ///2017.8.9---lizhen 101 | pCodecCtx->bit_rate =0; //初始化为0 102 | pCodecCtx->time_base.num=1; //下面两行:一秒钟25帧 103 | pCodecCtx->time_base.den=10; 104 | pCodecCtx->frame_number=1; //每包一个视频帧 105 | 106 | if (pCodec == NULL) { 107 | printf("Codec not found.\n"); 108 | return; 109 | } 110 | 111 | ///打开解码器 112 | if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { 113 | printf("Could not open codec.\n"); 114 | return; 115 | } 116 | 117 | pFrame = av_frame_alloc(); 118 | pFrameRGB = av_frame_alloc(); 119 | 120 | ///这里我们改成了 将解码后的YUV数据转换成RGB32 121 | img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, 122 | pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 123 | PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL); 124 | 125 | numBytes = avpicture_get_size(PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height); 126 | 127 | out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t)); 128 | avpicture_fill((AVPicture *) pFrameRGB, out_buffer, PIX_FMT_RGB32, 129 | pCodecCtx->width, pCodecCtx->height); 130 | 131 | int y_size = pCodecCtx->width * pCodecCtx->height; 132 | 133 | packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet 134 | av_new_packet(packet, y_size); //分配packet的数据 135 | 136 | while (1) 137 | { 138 | if (av_read_frame(pFormatCtx, packet) < 0) 139 | { 140 | break; //这里认为视频读取完了 141 | } 142 | 143 | if (packet->stream_index == videoStream) { 144 | ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet); 145 | 146 | if (ret < 0) { 147 | printf("decode error.\n"); 148 | return; 149 | } 150 | 151 | if (got_picture) { 152 | sws_scale(img_convert_ctx, 153 | (uint8_t const * const *) pFrame->data, 154 | pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, 155 | pFrameRGB->linesize); 156 | //把这个RGB数据 用QImage加载 157 | QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32); 158 | QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示 159 | emit sig_GetOneFrame(image); //发送信号 160 | ///2017.8.11---lizhen 161 | //提取出图像中的R数据 162 | for(int i=0;iwidth;i++) 163 | { 164 | for(int j=0;jheight;j++) 165 | { 166 | QRgb rgb=image.pixel(i,j); 167 | int r=qRed(rgb); 168 | image.setPixel(i,j,qRgb(r,0,0)); 169 | } 170 | } 171 | emit sig_GetRFrame(image); 172 | } 173 | } 174 | av_free_packet(packet); //释放资源,否则内存会一直上升 175 | 176 | ///2017.8.7---lizhen 177 | msleep(0.02); //停一停 不然放的太快了 178 | } 179 | av_free(out_buffer); 180 | av_free(pFrameRGB); 181 | avcodec_close(pCodecCtx); 182 | avformat_close_input(&pFormatCtx); 183 | } 184 | -------------------------------------------------------------------------------- /videoplayer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * 李震 3 | * 我的码云:https://git.oschina.net/git-lizhen 4 | * 我的CSDN博客:http://blog.csdn.net/weixin_38215395 5 | * 联系:QQ1039953685 6 | */ 7 | 8 | #ifndef VIDEOPLAYER_H 9 | #define VIDEOPLAYER_H 10 | 11 | #include 12 | #include 13 | 14 | //2017.8.10---lizhen 15 | class VlcInstance; 16 | class VlcMedia; 17 | class VlcMediaPlayer; 18 | 19 | class VideoPlayer : public QThread 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | explicit VideoPlayer(); 25 | ~VideoPlayer(); 26 | 27 | void startPlay(); 28 | 29 | signals: 30 | void sig_GetOneFrame(QImage); //每获取到一帧图像 就发送此信号 31 | void sig_GetRFrame(QImage); ///2017.8.11---lizhen 32 | 33 | protected: 34 | void run(); 35 | 36 | private: 37 | QString mFileName; 38 | 39 | //2017.8.10---lizhen 40 | VlcInstance *_instance; 41 | VlcMedia *_media; 42 | VlcMediaPlayer *_player; 43 | }; 44 | 45 | #endif // VIDEOPLAYER_H 46 | --------------------------------------------------------------------------------