Quellcode durchsuchen

V1.6.0

从零开始学习音视频编程技术(二十一) 录屏软件开发之最终完善
叶海辉 vor 6 Jahren
Ursprung
Commit
fff427d93d

+ 12 - 10
VideoRecorder.pro

@@ -1,25 +1,30 @@
 #-------------------------------------------------
 #
-# Project created by QtCreator 2015-01-06T22:20:29
+# Project created by QtCreator 2015-04-01T17:15:51
 #
 #-------------------------------------------------
 
-QT       += core gui
+QT       += core gui network
 
 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 
 TARGET = VideoRecorder
 TEMPLATE = app
 
-
 SOURCES += src/main.cpp\
         src/mainwindow.cpp \
-    src/savevideofile.cpp \
-    src/screenrecorder.cpp
+    src/video/savevideofile.cpp \
+    src/video/screenrecorder.cpp \
+    src/widget/selectrect.cpp \
+    src/widget/pushpoint.cpp \
+    src/video/getvideothread.cpp
 
 HEADERS  += src/mainwindow.h \
-    src/savevideofile.h \
-    src/screenrecorder.h
+    src/video/savevideofile.h \
+    src/video/screenrecorder.h \
+    src/widget/selectrect.h \
+    src/widget/pushpoint.h \
+    src/video/getvideothread.h
 
 FORMS    += src/mainwindow.ui
 
@@ -36,6 +41,3 @@ LIBS += $$PWD/lib/ffmpeg/lib/avcodec.lib \
         $$PWD/lib/ffmpeg/lib/swresample.lib \
         $$PWD/lib/ffmpeg/lib/swscale.lib \
         $$PWD/lib/SDL2/lib/x86/SDL2.lib
-
-LIBS += -lwinmm
-LIBS += -lws2_32

+ 9 - 2
src/main.cpp

@@ -11,17 +11,24 @@
 #include <QTextCodec>
 
 #undef main
+
 int main(int argc, char *argv[])
 {
     QApplication a(argc, argv);
 
-    QTextCodec *codec = QTextCodec::codecForName("UTF-8");
+    QTextCodec *codec = QTextCodec::codecForName("UTF-8"); //获取系统编码
     QTextCodec::setCodecForLocale(codec);
     QTextCodec::setCodecForCStrings(codec);
     QTextCodec::setCodecForTr(codec);
 
     MainWindow w;
-    w.show();
+
+    //第二个参数为录制文件的路径
+    if (argc >= 2)
+    {
+        QString str = QString(argv[1]);
+        w.setSaveFile(str);
+    }
 
     return a.exec();
 }

+ 718 - 23
src/mainwindow.cpp

@@ -8,8 +8,14 @@
 #include "mainwindow.h"
 #include "ui_mainwindow.h"
 
-#include <QDebug>
+#include <QUrl>
+#include <QTimer>
+#include <QProcess>
 #include <QMessageBox>
+#include <QDesktopWidget>
+#include <QDesktopServices>
+
+#include <QFileDialog>
 
 MainWindow::MainWindow(QWidget *parent) :
     QMainWindow(parent),
@@ -17,51 +23,740 @@ MainWindow::MainWindow(QWidget *parent) :
 {
     ui->setupUi(this);
 
+    av_register_all();
+    avformat_network_init();
+    avdevice_register_all();
 
-    int devNums = waveInGetNumDevs();
-
-    for(int i=0;i<devNums;i++)
-    {
-        WAVEINCAPSW p;
-        waveInGetDevCaps(i,&p,sizeof(WAVEINCAPS));
-        ui->comboBox_audiodeviceList->addItem(QString::fromWCharArray(p.szPname));
+    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
+        fprintf(stderr,"Could not initialize SDL - %s. \n", SDL_GetError());
+        exit(1);
     }
 
-    m_screenRecorder = new ScreenRecorder;
+    setWindowFlags(Qt::WindowStaysOnTopHint|Qt::FramelessWindowHint);  //使窗口的标题栏隐藏
+    setAttribute(Qt::WA_TranslucentBackground, true);
+
+    AppDataPath = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
+    QString dirName = AppDataPath + "\\ScreenRecorder\\etc";
+    SettingFile = dirName + "\\set.conf";
+
+    dirName.replace("/","\\");
+
+    QDir dir;
+    dir.mkpath(dirName);
+
+    connect(ui->startButton,SIGNAL(clicked()),this,SLOT(slotBtnClicked()));
+    connect(ui->pauseButton,SIGNAL(clicked()),this,SLOT(slotBtnClicked()));
+    connect(ui->stopButton,SIGNAL(clicked()),this,SLOT(slotBtnClicked()));
+    connect(ui->pushButton_playBack,SIGNAL(clicked()),this,SLOT(slotBtnClicked()));
+
+    connect(ui->selectRectButton,SIGNAL(clicked()),this,SLOT(slotSelectRectBtnClick()));
+    connect(ui->editRectButton,SIGNAL(clicked()),this,SLOT(slotEditRectBtnClick()));
+    connect(ui->hideRectButton,SIGNAL(clicked()),this,SLOT(slotHideRectBtnClick()));
+
+    m_screenRecorder = NULL;
+    isLeftBtnPressed = false;
+    m_recordeState = Stop;
+
+    rect = QRect(0,0,0,0);
+
+    selectRectWidget = new SelectRect(NULL,SelectRect::RecordGif);
+    QDesktopWidget* desktopWidget = QApplication::desktop();
+    deskRect = desktopWidget->screenGeometry();//获取可用桌面大小
+    m_rate = deskRect.height() * 1.0 / deskRect.width();
+    selectRectWidget->setRate(m_rate);
+
+    connect(selectRectWidget,SIGNAL(finished(QRect)),this,SLOT(slotSelectRectFinished(QRect)));
+    connect(selectRectWidget,SIGNAL(rectChanged(QRect)),this,SLOT(slotSelectRectFinished(QRect)));
+
+    initDev();
+    loadFile();
+
+    connect(ui->toolButton_video,SIGNAL(clicked(bool)),this,SLOT(slotToolBtnToggled(bool)));
+    connect(ui->toolButton_audio,SIGNAL(clicked(bool)),this,SLOT(slotToolBtnToggled(bool)));
+    connect(ui->toolButton_file,SIGNAL(clicked(bool)),this,SLOT(slotToolBtnToggled(bool)));
+
+    ///动画类 用来实现窗体从上方慢慢出现
+    animation = new QPropertyAnimation(this, "geometry");
+    animation->setDuration(1000);
+
+    ui->toolButton_setting->setChecked(false);
+    ui->widget_extern->hide();
+    QTimer::singleShot(50,this,SLOT(showOut()));
 
-    connect(ui->pushButton_start,SIGNAL(clicked()),this,SLOT(slotBtnClick()));
-    connect(ui->pushButton_stop,SIGNAL(clicked()),this,SLOT(slotBtnClick()));
+    m_timer = new QTimer;
+    connect(m_timer,SIGNAL(timeout()),this,SLOT(slotTimerTimeOut()));
+    m_timer->setInterval(500);
+
+    connect(ui->checkBox,SIGNAL(clicked(bool)),this,SLOT(slotCheckBoxClick(bool)));
+
+    if (ui->toolButton_video->isChecked())
+    {
+        selectRectWidget->show();
+        selectRectWidget->setPointHide();
+        ui->hideRectButton->setText("隐藏");
+    }
 }
 
 MainWindow::~MainWindow()
 {
     delete ui;
+    delete selectRectWidget;
+}
+
+void MainWindow::showOut()
+{
+    show();
+    move(deskRect.width() / 2 - width() / 2,0-height());
+    animation->setStartValue(QRect(deskRect.width() / 2 - width() / 2,0-height(),width(),height()));
+    animation->setEndValue(QRect(deskRect.width() / 2 - width() / 2,0,width(),height()));
+    animation->start();
+}
+
+void MainWindow::closeEvent(QCloseEvent *event)
+{
+    if (m_screenRecorder)
+    {
+        m_screenRecorder->stopRecord();
+    }
+
+    selectRectWidget->close();
+}
+
+void MainWindow::mousePressEvent(QMouseEvent * event)
+{
+    if (event->button() == Qt::LeftButton)
+    {
+        isLeftBtnPressed = true;
+         dragPosition=event->globalPos()-frameGeometry().topLeft();
+         event->accept();
+    }
+}
+
+void MainWindow::mouseMoveEvent(QMouseEvent * event)
+{  //实现鼠标移动窗口
+    if (event->buttons() & Qt::LeftButton)
+    {
+        if (isLeftBtnPressed)
+        {
+            move(event->globalPos() - dragPosition);
+            event->accept();
+        }
+    }
+}
+
+void MainWindow::mouseReleaseEvent(QMouseEvent * event)
+{
+    isLeftBtnPressed = false;
+    event->accept();
+}
+
+void MainWindow::setSaveFile(QString fileName)
+{
+
+    QString str = fileName;
+
+    saveFileName = str.replace("/","\\\\");
+
+    ui->lineEdit_filename->setText(saveFileName);
+    QString dirName = saveFileName.left(saveFileName.lastIndexOf("\\")-1);
+
+    QDir dir;
+    dir.mkpath(dirName);
+
+    saveFile();
+}
+
+void MainWindow::loadFile()
+{
+
+    QFile file(SettingFile);
+    if (file.open(QIODevice::ReadOnly))
+    {
+        QTextStream fileOut(&file);
+        fileOut.setCodec("GBK");  //unicode UTF-8  ANSI
+        while (!fileOut.atEnd())
+        {
+            QString str = fileOut.readLine();
+//            qDebug()<<str;
+
+            str = str.remove(" ");
+
+            if (str.isEmpty())
+            {
+                continue;
+            }
+
+            if (str.contains("area="))
+            {
+                str = str.remove("area=");
+
+                QStringList strList = str.split(",");
+
+                if (strList.size() == 4)
+                {
+                    rect = QRect(strList.at(0).toInt(),strList.at(1).toInt(),strList.at(2).toInt(),strList.at(3).toInt());
+                }
+            }
+            else if (str.contains("mode="))
+            {
+                str = str.remove("mode=");
+                if (str == "video")
+                {
+                    ui->toolButton_video->setChecked(true);
+                }
+                else
+                {
+                    ui->toolButton_audio->setChecked(true);
+                    ui->startButton->setEnabled(true);
+                }
+            }
+            else if (str.contains("framerate="))
+            {
+                str = str.remove("framerate=");
+
+                int rate = str.toInt();
+
+                if (rate > 0 && rate <= 25)
+                {
+                    ui->comboBox_framerate->setCurrentIndex(rate-1);
+                }
+            }
+            else if (str.contains("filename="))
+            {
+                str = str.remove("filename=");
+                str.replace("/","\\\\");
+                saveFileName = str;
+                ui->lineEdit_filename->setText(saveFileName);
+
+                QString dirName = saveFileName.left(saveFileName.lastIndexOf("\\")-1);
+
+                QDir dir;
+                dir.mkpath(dirName);
+            }
+        }
+        file.close();
+    }
+    else
+    {
+        ui->toolButton_video->setChecked(true);
+    }
+
+    if (rect.width() <= 0 || rect.height() <= 0 || !deskRect.contains(rect))
+    {
+        rect = deskRect;
+    }
+
+    selectRectWidget->setRect(rect);
+    ui->startButton->setEnabled(true);
+    selectRectWidget->setVisible(false);
+    ui->hideRectButton->setText("显示");
+    ui->startButton->setEnabled(true);
+}
+
+void MainWindow::saveFile()
+{
+
+//    QDir dir(AppDataPath);
+//    dir.mkdir("etc");
+
+    QFile file(SettingFile);
+    if (file.open(QIODevice::WriteOnly))
+    {
+        QTextStream fileOut(&file);
+        fileOut.setCodec("GBK");  //unicode UTF-8  ANSI
+
+        if (!ui->comboBox_audio->currentText().isEmpty())
+        {
+            fileOut<<QString("audio=%1").arg(ui->comboBox_audio->currentText());
+            fileOut<<"\n";
+        }
+
+        QString areaStr = QString("area=%1,%2,%3,%4").arg(rect.x()).arg(rect.y()).arg(rect.width()).arg(rect.height());
+        QString modeStr = "mode=audio";
+        if (ui->toolButton_video->isChecked())
+        {
+            modeStr = "mode=video";
+        }
+
+        QString fileStr = QString("filename=%1").arg(saveFileName);
+        QString rateStr = QString("framerate=%1").arg(ui->comboBox_framerate->currentText().toInt());
+
+        fileOut<<areaStr;
+        fileOut<<"\n";
+        fileOut<<modeStr;
+        fileOut<<"\n";
+        fileOut<<rateStr;
+        fileOut<<"\n";
+        fileOut<<fileStr;
+        fileOut<<"\n";
+
+        file.close();
+    }
+}
+
+void MainWindow::initDev()
+{
+    QString videoDevName;
+    QString audioDevName;
+    QFile devFile(SettingFile);
+    if (devFile.open(QIODevice::ReadOnly))
+    {
+        QTextStream fileOut(&devFile);
+        fileOut.setCodec("GBK");  //unicode UTF-8  ANSI
+
+        while (!fileOut.atEnd())
+        {
+            QString str = fileOut.readLine();
+            str = str.remove("\r");
+            str = str.remove("\n");
+            if (str.contains("video="))
+            {
+                videoDevName = str.remove("video=");
+            }
+            else if (str.contains("audio="))
+            {
+                audioDevName = str.remove("audio=");
+            }
+        }
+
+        devFile.close();
+    }
+
+    /// 执行ffmpeg命令行 获取音视频设备
+    /// 请将ffmpeg.exe和程序放到同一个目录下
+
+    QString ffmpegPath = QCoreApplication::applicationDirPath() + "/ffmpeg.exe";
+    ffmpegPath.replace("/","\\\\");
+    ffmpegPath = QString("\"%1\" -list_devices true -f dshow -i dummy 2>a.txt \n").arg(ffmpegPath);
+
+     QProcess p(0);
+     p.start("cmd");
+     p.waitForStarted();
+     p.write(ffmpegPath.toLocal8Bit());
+     p.closeWriteChannel();
+     p.waitForFinished();
+
+
+    QFile file("a.txt");
+    if (file.open(QIODevice::ReadOnly))
+    {
+        QTextStream fileOut(&file);
+        fileOut.setCodec("UTF-8");  //unicode UTF-8  ANSI
+
+        bool isVideoBegin = false;
+        bool isAudioBegin = false;
+
+        while (!fileOut.atEnd())
+        {
+            QString str = fileOut.readLine();
+
+            if (str.contains("DirectShow video devices") && str.contains("[dshow @"))
+            {
+                isVideoBegin = true;
+                isAudioBegin = false;
+                continue;
+            }
+
+            if (str.contains("DirectShow audio devices") && str.contains("[dshow @"))
+            {
+                isAudioBegin = true;
+                isVideoBegin = false;
+                continue;
+            }
+
+            if (str.contains("[dshow @"))
+            {
+                int index = str.indexOf("\"");
+                str = str.remove(0,index);
+                str = str.remove("\"");
+                str = str.remove("\n");
+                str = str.remove("\r");
+
+                if (isVideoBegin)
+                {
+//                    if ("screen-capture-recorder" != str)
+//                    ui->comboBox_camera->addItem(str);
+                }
+                else if (isAudioBegin)
+                {
+                    if ("virtual-audio-capturer" != str)
+                    ui->comboBox_audio->addItem(str);
+                }
+            }
+
+        }
+
+        file.close();
+    }
+    else
+    {
+        qDebug()<<"open a.txt failed!";
+    }
+
+    QFile::remove("a.txt");
+
+    for (int i=0;i<ui->comboBox_audio->count();i++)
+    {
+        if (ui->comboBox_audio->itemText(i) == audioDevName)
+        {
+            ui->comboBox_audio->setCurrentIndex(i);
+            break;
+        }
+    }
+
+}
+
+void MainWindow::slotBtnClicked()
+{
+    if (QObject::sender() == ui->startButton)
+    {
+        startRecord();
+    }
+    else if (QObject::sender() == ui->pauseButton)
+    {
+        pauseRecord();
+    }
+    else if (QObject::sender() == ui->stopButton)
+    {
+        stopRecord();
+    }
+    else if (QObject::sender() == ui->pushButton_playBack)
+    {
+        QString path = saveFileName;
+        path.replace("\\\\","/");
+        path="file:///" + path;
+        qDebug()<<QUrl(path)<<path;
+        QDesktopServices::openUrl(QUrl(path));
+    }
+}
+
+void MainWindow::slotToolBtnToggled(bool isChecked)
+{
+    if (QObject::sender() == ui->toolButton_video)
+    {
+        if (isChecked)
+        {
+            ui->toolButton_audio->setChecked(!isChecked);
+            selectRectWidget->show();
+        }
+        else
+        {
+            ui->toolButton_video->setChecked(!isChecked);
+        }
+    }
+    else if (QObject::sender() == ui->toolButton_audio)
+    {
+        if (isChecked)
+        {
+            ui->toolButton_video->setChecked(!isChecked);
+            selectRectWidget->hide();
+        }
+        else
+        {
+            ui->toolButton_audio->setChecked(!isChecked);
+        }
+    }
+    else if (QObject::sender() == ui->toolButton_file)
+    {
+        QString s = QFileDialog::getSaveFileName(
+                   this, "选择保存文件的路劲",
+                       saveFileName,//初始目录
+                    "视频文件 (*.mp4);;");
+         if (!s.isEmpty())
+         {
+             saveFileName = s.replace("/","\\\\");
+
+             ui->lineEdit_filename->setText(saveFileName);
+
+             saveFile();
+         }
+    }
 }
 
+void MainWindow::slotSelectRectFinished(QRect re)
+{
+    /// 1.传给ffmpeg编码的图像宽高必须是偶数。
+    /// 2.图像裁剪的起始位置和结束位置也必须是偶数
+    /// 而手动选择的区域很有可能会是奇数,因此需要处理一下 给他弄成偶数
+    /// 处理的方法很简答:其实就是往前或者往后移一个像素
+    /// 一个像素的大小肉眼基本也看不出来啥区别。
+
+    int x = re.x();
+    int y = re.y();
+    int w = re.width();
+    int h = re.height();
 
-void MainWindow::slotBtnClick()
+    if (x % 2 != 0)
+    {
+        x--;
+        w++;
+    }
+
+    if (y % 2 != 0)
+    {
+        y--;
+        h++;
+    }
+
+    if (w % 2 != 0)
+    {
+        w++;
+    }
+
+    if (h % 2 != 0)
+    {
+        h++;
+    }
+
+    rect = QRect(x,y,w,h);
+
+    QString str = QString("==当前区域==\n\n起点(%1,%2)\n\n大小(%3 x %4)")
+            .arg(rect.left()).arg(rect.left()).arg(rect.width()).arg(rect.height());
+
+    ui->showRectInfoLabel->setText(str);
+
+    ui->startButton->setEnabled(true);
+    ui->editRectButton->setEnabled(true);
+    ui->hideRectButton->setEnabled(true);
+    ui->hideRectButton->setText("隐藏");
+
+    saveFile();
+
+}
+
+bool MainWindow::startRecord()
 {
-    if (QObject::sender() == ui->pushButton_start)
+    int ret = 0;
+    QString msg = "ok";
+    if (m_recordeState == Recording)
+    {
+        ret = -1;
+        msg = "is already start!";
+    }
+    else if (m_recordeState == Pause)
+    {
+        ui->startButton->setEnabled(false);
+        ui->pauseButton->setEnabled(true);
+        ui->stopButton->setEnabled(true);
+
+        m_screenRecorder->restoreRecord();
+
+        m_recordeState = Recording;
+        m_timer->start();
+    }
+    else if (m_recordeState == Stop)
     {
 
-        ErroCode code = m_screenRecorder->init(ui->comboBox_audiodeviceList->currentText());
+        if (rect.width() <= 0 || rect.height() <= 0 || !deskRect.contains(rect))
+        {
+            rect = deskRect;
+        }
 
-        if (code == SUCCEED)
+        if (saveFileName.remove(" ").isEmpty())
         {
-            m_screenRecorder->startRecord();
-            ui->pushButton_start->setEnabled(false);
-            ui->pushButton_stop->setEnabled(true);
-//            SDL_CreateThread(putAudioThread, "parse_thread", NULL);
+            ret = -2;
+            msg = "filepath not set";
+            QMessageBox::critical(this,"提示","请先设置保存文件路径");
         }
         else
         {
-            QMessageBox::critical(NULL,"提示","出错了,初始化失败。");
+            saveFile();
+
+            QString audioDevName = ui->comboBox_audio->currentText();
+
+            if (audioDevName.isEmpty())
+            {
+                QMessageBox::critical(this,"提示","出错了,音频或视频设备未就绪,程序无法运行!");
+
+                ret = -3;
+                msg = "audio device not set";
+                goto end;
+            }
+
+            if (m_screenRecorder)
+                delete m_screenRecorder;
+
+            m_screenRecorder = new ScreenRecorder;
+            m_screenRecorder->setFileName(saveFileName.toLocal8Bit().data());
+            m_screenRecorder->setVideoFrameRate(ui->comboBox_framerate->currentText().toInt());
+
+            if (ui->toolButton_video->isChecked())
+            {
+                if (m_screenRecorder->init("screen-capture-recorder",true,audioDevName,true) == SUCCEED)
+                {
+                    m_screenRecorder->setPicRange(rect.x(),rect.y(),rect.width(),rect.height());
+                    m_screenRecorder->startRecord();
+                }
+                else
+                {
+                    QMessageBox::critical(this,"提示","出错了,初始化录屏设备失败!");
+
+                    ret = -4;
+                    msg = "init screen device failed!";
+                    goto end;
+
+                }
+            }
+            else
+            {
+                if (m_screenRecorder->init("",false,audioDevName,true) == SUCCEED)
+                {
+//                    qDebug()<<rect;
+                    m_screenRecorder->setPicRange(rect.x(),rect.y(),rect.width(),rect.height());
+                    m_screenRecorder->startRecord();
+                }
+                else
+                {
+                    QMessageBox::critical(this,"提示","出错了,初始化音频设备失败!");
+                    ret = -5;
+                    msg = "init audio device failed!";
+                    goto end;
+                }
+            }
+
+            ui->startButton->setEnabled(false);
+            ui->pauseButton->setEnabled(true);
+            ui->stopButton->setEnabled(true);
+
+            ui->selectRectButton->setEnabled(false);
+            ui->editRectButton->setEnabled(false);
+            //ui->hideRectButton->setEnabled(false);
+
+            ui->comboBox_audio->setEnabled(false);
+        //    ui->comboBox_recordeMode->setEnabled(false);
+
+            ui->toolButton_video->setEnabled(false);
+            ui->toolButton_audio->setEnabled(false);
+
+            m_recordeState = Recording;
+            m_timer->start();
         }
+
+        ui->pushButton_playBack->setEnabled(false);
+    }
+
+end:
+
+//    m_erroMsg = QString("\"ret\":%1,\"msg\":\"%2\"").arg(ret).arg(msg);
+    return ret == 0;
+}
+
+bool MainWindow::pauseRecord()
+{
+    int ret = 0;
+    QString msg = "ok";
+    if (m_recordeState == Recording)
+    {
+        m_timer->stop();
+
+        ui->startButton->setEnabled(true);
+        ui->pauseButton->setEnabled(false);
+
+        ui->selectRectButton->setEnabled(false);
+        ui->editRectButton->setEnabled(false);
+        //ui->hideRectButton->setEnabled(false);
+
+        m_screenRecorder->pauseRecord();
+
+        m_recordeState = Pause;
     }
-    else if (QObject::sender() == ui->pushButton_stop)
+    else
+    {
+        ret = -6;
+        msg = "is not started!";
+    }
+
+//    m_erroMsg = QString("\"ret\":%1,\"msg\":\"%2\"").arg(ret).arg(msg);
+    return ret == 0;
+}
+
+bool MainWindow::stopRecord()
+{
+    int ret = 0;
+    QString msg = "ok";
+    if (m_recordeState != Stop)
     {
+        m_timer->stop();
+        m_recordeState = Stop;
+        ui->label_time->setText("00:00:00");
         m_screenRecorder->stopRecord();
-        ui->pushButton_stop->setEnabled(false);
-        ui->pushButton_start->setEnabled(true);
+
+        ui->startButton->setEnabled(true);
+        ui->pauseButton->setEnabled(false);
+        ui->stopButton->setEnabled(false);
+
+        ui->selectRectButton->setEnabled(true);
+        ui->editRectButton->setEnabled(true);
+
+        ui->comboBox_audio->setEnabled(true);
+        ui->toolButton_video->setEnabled(true);
+        ui->toolButton_audio->setEnabled(true);
+
+        ui->pushButton_playBack->setEnabled(true);
+
+    }
+    else
+    {
+        ret = -7;
+        msg = "not started!";
+    }
+
+//    m_erroMsg = QString("\"ret\":%1,\"msg\":\"%2\"").arg(ret).arg(msg);
+
+    return ret == 0;
+}
+
+void MainWindow::slotSelectRectBtnClick()
+{
+    selectRectWidget->getReadyToSelect();
+}
+
+void MainWindow::slotEditRectBtnClick()
+{
+    selectRectWidget->showFullScreen();
+    selectRectWidget->editRect();
+}
+
+void MainWindow::slotHideRectBtnClick()
+{
+    if (selectRectWidget->isVisible())
+    {
+       selectRectWidget->setVisible(false);
+       ui->hideRectButton->setText("显示");
+    }
+    else
+    {
+       selectRectWidget->setVisible(true);
+       ui->hideRectButton->setText("隐藏");
     }
 }
+
+void MainWindow::slotTimerTimeOut()
+{
+    qint64 audioPts = 0;
+    if (m_screenRecorder)
+    {
+        audioPts = m_screenRecorder->getAudioPts();
+    }
+
+    QString hStr = QString("00%1").arg(audioPts/3600);
+    QString mStr = QString("00%1").arg(audioPts%3600/60);
+    QString sStr = QString("00%1").arg(audioPts%60);
+
+    QString str = QString("%1:%2:%3").arg(hStr.right(2)).arg(mStr.right(2)).arg(sStr.right(2));
+    ui->label_time->setText(str);
+
+}
+
+void MainWindow::slotCheckBoxClick(bool checked)
+{
+    if (checked)
+    {
+        selectRectWidget->setRate(m_rate);
+    }
+    else
+    {
+        selectRectWidget->setRate(-1);
+    }
+}
+

+ 74 - 4
src/mainwindow.h

@@ -8,11 +8,19 @@
 #ifndef MAINWINDOW_H
 #define MAINWINDOW_H
 
-#include <QMainWindow>
+#include <QUdpSocket>
+#include <QHostInfo>
+#include <QMessageBox>
+#include <QScrollBar>
+#include <QDateTime>
+#include <QNetworkInterface>
+#include <QProcess>
 
-#include "screenrecorder.h"
+#include <QMainWindow>
+#include <QPropertyAnimation>
 
-#include <windows.h>
+#include "widget/selectrect.h"
+#include "video/screenrecorder.h"
 
 namespace Ui {
 class MainWindow;
@@ -23,16 +31,78 @@ class MainWindow : public QMainWindow
     Q_OBJECT
 
 public:
+
+    enum RecoderState
+    {
+        Recording,
+        Pause,
+        Stop
+    };
+
     explicit MainWindow(QWidget *parent = 0);
     ~MainWindow();
 
+    /// 设置保存的视频文件的路径
+    void setSaveFile(QString fileName);
+
+public slots:
+    void showOut();
+
+protected:
+    void closeEvent(QCloseEvent *);
+
+protected:
+    void mousePressEvent(QMouseEvent *);
+    void mouseMoveEvent(QMouseEvent *);
+    void mouseReleaseEvent(QMouseEvent *);
+
 private:
     Ui::MainWindow *ui;
 
+    bool isLeftBtnPressed;
+    QPoint dragPosition;
+
+    QPropertyAnimation *animation; //动画类 用来实现窗体从上方慢慢出现
+
+    QString AppDataPath; //保存配置文件的目录
+    QString SettingFile; //保存配置文件的路径
+
+    QString saveFileName; //视频路径
+
     ScreenRecorder *m_screenRecorder;
+    QRect deskRect; //可用桌面大小
+    SelectRect *selectRectWidget;  //选择区域的控件
+    QRect rect; //当前录制的区域
+    float m_rate; //屏幕宽高比
+
+    QTimer * m_timer; //定时器 用于获取时间
+
+    RecoderState m_recordeState;
+
+    void initDev(); //获取录音设备的列表
+
+    void loadFile(); //加载配置文件
+    void saveFile(); //写入配置文件
+
+//    QString m_erroMsg;
+    bool startRecord();
+    bool pauseRecord();
+    bool stopRecord();
 
 private slots:
-    void slotBtnClick();
+    void slotToolBtnToggled(bool);
+    void slotBtnClicked();
+
+    ///选择录屏区域相关 - Begin
+    void slotSelectRectBtnClick();
+    void slotEditRectBtnClick();
+    void slotHideRectBtnClick();
+    void slotSelectRectFinished(QRect);
+    ///选择录屏区域相关 - End
+
+    void slotTimerTimeOut();
+
+    void slotCheckBoxClick(bool checked);
 };
 
 #endif // MAINWINDOW_H

+ 735 - 44
src/mainwindow.ui

@@ -6,63 +6,754 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>383</width>
-    <height>185</height>
+    <width>313</width>
+    <height>690</height>
    </rect>
   </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
   <property name="windowTitle">
    <string>MainWindow</string>
   </property>
   <widget class="QWidget" name="centralWidget">
-   <widget class="QPushButton" name="pushButton_start">
-    <property name="geometry">
-     <rect>
-      <x>30</x>
-      <y>90</y>
-      <width>131</width>
-      <height>41</height>
-     </rect>
+   <layout class="QVBoxLayout" name="verticalLayout_3">
+    <property name="spacing">
+     <number>0</number>
     </property>
-    <property name="text">
-     <string>start</string>
+    <property name="leftMargin">
+     <number>0</number>
     </property>
-   </widget>
-   <widget class="QPushButton" name="pushButton_stop">
-    <property name="geometry">
-     <rect>
-      <x>200</x>
-      <y>90</y>
-      <width>121</width>
-      <height>41</height>
-     </rect>
+    <property name="topMargin">
+     <number>0</number>
     </property>
-    <property name="text">
-     <string>stop</string>
+    <property name="rightMargin">
+     <number>0</number>
     </property>
-   </widget>
-   <widget class="QComboBox" name="comboBox_audiodeviceList">
-    <property name="geometry">
-     <rect>
-      <x>20</x>
-      <y>20</y>
-      <width>332</width>
-      <height>41</height>
-     </rect>
+    <property name="bottomMargin">
+     <number>0</number>
     </property>
-   </widget>
-  </widget>
-  <widget class="QMenuBar" name="menuBar">
-   <property name="geometry">
-    <rect>
-     <x>0</x>
-     <y>0</y>
-     <width>383</width>
-     <height>23</height>
-    </rect>
-   </property>
+    <item>
+     <widget class="QWidget" name="widget_contain" native="true">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="styleSheet">
+       <string notr="true">QWidget#widget_contain
+{
+background-color: rgb(255, 255, 255);
+border:2px solid rgba(0, 0, 0, 50);
+border-radius: 1px; 
+/*border-top-style: solid; 
+border-top-width: 32px; 
+border-top-color: rgb(56, 129, 173); 
+*/
+}</string>
+      </property>
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <property name="spacing">
+        <number>0</number>
+       </property>
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QWidget" name="widget" native="true">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>0</width>
+           <height>160</height>
+          </size>
+         </property>
+         <widget class="QToolButton" name="toolButton_audio">
+          <property name="geometry">
+           <rect>
+            <x>144</x>
+            <y>50</y>
+            <width>80</width>
+            <height>45</height>
+           </rect>
+          </property>
+          <property name="text">
+           <string>音频</string>
+          </property>
+          <property name="checkable">
+           <bool>true</bool>
+          </property>
+         </widget>
+         <widget class="QPushButton" name="startButton">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="geometry">
+           <rect>
+            <x>40</x>
+            <y>110</y>
+            <width>45</width>
+            <height>32</height>
+           </rect>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>32</height>
+           </size>
+          </property>
+          <property name="text">
+           <string>开始</string>
+          </property>
+         </widget>
+         <widget class="QToolButton" name="toolButton_video">
+          <property name="geometry">
+           <rect>
+            <x>40</x>
+            <y>50</y>
+            <width>80</width>
+            <height>45</height>
+           </rect>
+          </property>
+          <property name="text">
+           <string>视频</string>
+          </property>
+          <property name="checkable">
+           <bool>true</bool>
+          </property>
+         </widget>
+         <widget class="QPushButton" name="pauseButton">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="geometry">
+           <rect>
+            <x>110</x>
+            <y>110</y>
+            <width>45</width>
+            <height>32</height>
+           </rect>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>32</height>
+           </size>
+          </property>
+          <property name="text">
+           <string>暂停</string>
+          </property>
+         </widget>
+         <widget class="QPushButton" name="stopButton">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="geometry">
+           <rect>
+            <x>180</x>
+            <y>110</y>
+            <width>45</width>
+            <height>32</height>
+           </rect>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>32</height>
+           </size>
+          </property>
+          <property name="text">
+           <string>停止</string>
+          </property>
+         </widget>
+         <widget class="QToolButton" name="toolButton_setting">
+          <property name="geometry">
+           <rect>
+            <x>260</x>
+            <y>40</y>
+            <width>41</width>
+            <height>51</height>
+           </rect>
+          </property>
+          <property name="text">
+           <string>设置</string>
+          </property>
+          <property name="checkable">
+           <bool>true</bool>
+          </property>
+         </widget>
+         <widget class="QLabel" name="label_time">
+          <property name="geometry">
+           <rect>
+            <x>110</x>
+            <y>10</y>
+            <width>111</width>
+            <height>21</height>
+           </rect>
+          </property>
+          <property name="styleSheet">
+           <string notr="true">font-family:&quot;微软雅黑&quot;;
+font-size:23px;
+font-weight:bold;
+color: rgb(0, 0, 255);</string>
+          </property>
+          <property name="text">
+           <string>00:00:00</string>
+          </property>
+         </widget>
+         <widget class="QPushButton" name="closeButton">
+          <property name="geometry">
+           <rect>
+            <x>290</x>
+            <y>5</y>
+            <width>20</width>
+            <height>20</height>
+           </rect>
+          </property>
+          <property name="cursor">
+           <cursorShape>PointingHandCursor</cursorShape>
+          </property>
+          <property name="toolTip">
+           <string>关闭</string>
+          </property>
+          <property name="styleSheet">
+           <string notr="true">QPushButton{ 
+border:none;
+color:rgb(56, 129, 173); 
+font-weight:bold;
+font-family:&quot;微软雅黑&quot;;
+font-size:18px;
+} 
+QPushButton:hover{ 
+color:rgb(255, 255, 255); 
+border: 1px solid rgb(255,0,0); 
+background-color:rgb(255,0,0); 
+border-style: solid; 
+border-radius:9px; 
+} 
+QPushButton:pressed{ 
+background-color:#EAF0FF; 
+border: 1px solid #AAB4C4; 
+width: 40px; 
+height:20px; 
+padding:0 0px; 
+border-radius:1px; 
+}
+</string>
+          </property>
+          <property name="text">
+           <string>╳</string>
+          </property>
+         </widget>
+         <widget class="QPushButton" name="pushButton_playBack">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="geometry">
+           <rect>
+            <x>250</x>
+            <y>110</y>
+            <width>45</width>
+            <height>32</height>
+           </rect>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>32</height>
+           </size>
+          </property>
+          <property name="styleSheet">
+           <string notr="true"/>
+          </property>
+          <property name="text">
+           <string>回放</string>
+          </property>
+         </widget>
+        </widget>
+       </item>
+       <item>
+        <widget class="QWidget" name="widget_extern" native="true">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>0</width>
+           <height>330</height>
+          </size>
+         </property>
+         <widget class="QWidget" name="layoutWidget">
+          <property name="geometry">
+           <rect>
+            <x>20</x>
+            <y>50</y>
+            <width>234</width>
+            <height>111</height>
+           </rect>
+          </property>
+          <layout class="QHBoxLayout" name="horizontalLayout_5">
+           <item>
+            <widget class="QLabel" name="showRectInfoLabel">
+             <property name="text">
+              <string>==当前区域==
+
+起点(0,0)
+
+大小(0 x 0)</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <layout class="QVBoxLayout" name="verticalLayout_2">
+             <item>
+              <widget class="QPushButton" name="selectRectButton">
+               <property name="styleSheet">
+                <string notr="true"/>
+               </property>
+               <property name="text">
+                <string>选择区域</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QCheckBox" name="checkBox">
+               <property name="text">
+                <string>按比例</string>
+               </property>
+               <property name="checked">
+                <bool>true</bool>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QPushButton" name="editRectButton">
+               <property name="enabled">
+                <bool>false</bool>
+               </property>
+               <property name="text">
+                <string>编辑</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QPushButton" name="hideRectButton">
+               <property name="enabled">
+                <bool>false</bool>
+               </property>
+               <property name="text">
+                <string>隐藏</string>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="layoutWidget">
+          <property name="geometry">
+           <rect>
+            <x>20</x>
+            <y>210</y>
+            <width>314</width>
+            <height>34</height>
+           </rect>
+          </property>
+          <layout class="QHBoxLayout" name="horizontalLayout_2">
+           <property name="spacing">
+            <number>0</number>
+           </property>
+           <item>
+            <widget class="QLabel" name="label_2">
+             <property name="text">
+              <string>麦克风:</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QComboBox" name="comboBox_audio">
+             <property name="minimumSize">
+              <size>
+               <width>230</width>
+               <height>32</height>
+              </size>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_2">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="layoutWidget">
+          <property name="geometry">
+           <rect>
+            <x>20</x>
+            <y>20</y>
+            <width>258</width>
+            <height>22</height>
+           </rect>
+          </property>
+          <layout class="QHBoxLayout" name="horizontalLayout_3">
+           <item>
+            <widget class="QLabel" name="label_4">
+             <property name="text">
+              <string>视频帧率</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QComboBox" name="comboBox_framerate">
+             <property name="currentIndex">
+              <number>9</number>
+             </property>
+             <item>
+              <property name="text">
+               <string>1</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>2</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>3</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>4</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>5</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>6</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>7</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>7</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>8</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>10</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>11</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>12</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>13</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>14</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>15</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>16</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>17</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>18</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>19</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>20</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>21</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>22</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>23</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>24</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>25</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_3">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QLabel" name="label_5">
+          <property name="geometry">
+           <rect>
+            <x>10</x>
+            <y>1</y>
+            <width>295</width>
+            <height>1</height>
+           </rect>
+          </property>
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="styleSheet">
+           <string notr="true">background-color: rgb(11, 47, 255);</string>
+          </property>
+          <property name="text">
+           <string/>
+          </property>
+         </widget>
+         <widget class="QToolButton" name="toolButton_file">
+          <property name="geometry">
+           <rect>
+            <x>280</x>
+            <y>290</y>
+            <width>21</width>
+            <height>20</height>
+           </rect>
+          </property>
+          <property name="text">
+           <string>...</string>
+          </property>
+         </widget>
+         <widget class="QLabel" name="label_6">
+          <property name="geometry">
+           <rect>
+            <x>20</x>
+            <y>270</y>
+            <width>81</width>
+            <height>16</height>
+           </rect>
+          </property>
+          <property name="text">
+           <string>录屏保存位置:</string>
+          </property>
+         </widget>
+         <widget class="QLineEdit" name="lineEdit_filename">
+          <property name="geometry">
+           <rect>
+            <x>10</x>
+            <y>290</y>
+            <width>261</width>
+            <height>20</height>
+           </rect>
+          </property>
+          <property name="styleSheet">
+           <string notr="true"/>
+          </property>
+          <property name="frame">
+           <bool>true</bool>
+          </property>
+          <property name="readOnly">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </item>
+    <item>
+     <spacer name="verticalSpacer_5">
+      <property name="orientation">
+       <enum>Qt::Vertical</enum>
+      </property>
+      <property name="sizeHint" stdset="0">
+       <size>
+        <width>20</width>
+        <height>40</height>
+       </size>
+      </property>
+     </spacer>
+    </item>
+    <item>
+     <spacer name="verticalSpacer_4">
+      <property name="orientation">
+       <enum>Qt::Vertical</enum>
+      </property>
+      <property name="sizeHint" stdset="0">
+       <size>
+        <width>20</width>
+        <height>40</height>
+       </size>
+      </property>
+     </spacer>
+    </item>
+    <item>
+     <spacer name="verticalSpacer_2">
+      <property name="orientation">
+       <enum>Qt::Vertical</enum>
+      </property>
+      <property name="sizeHint" stdset="0">
+       <size>
+        <width>20</width>
+        <height>40</height>
+       </size>
+      </property>
+     </spacer>
+    </item>
+    <item>
+     <spacer name="verticalSpacer_3">
+      <property name="orientation">
+       <enum>Qt::Vertical</enum>
+      </property>
+      <property name="sizeHint" stdset="0">
+       <size>
+        <width>20</width>
+        <height>40</height>
+       </size>
+      </property>
+     </spacer>
+    </item>
+    <item>
+     <spacer name="verticalSpacer">
+      <property name="orientation">
+       <enum>Qt::Vertical</enum>
+      </property>
+      <property name="sizeHint" stdset="0">
+       <size>
+        <width>20</width>
+        <height>40</height>
+       </size>
+      </property>
+     </spacer>
+    </item>
+   </layout>
   </widget>
  </widget>
  <layoutdefault spacing="6" margin="11"/>
  <resources/>
- <connections/>
+ <connections>
+  <connection>
+   <sender>toolButton_setting</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>widget_extern</receiver>
+   <slot>setVisible(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>298</x>
+     <y>68</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>312</x>
+     <y>421</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>closeButton</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>300</x>
+     <y>11</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>247</x>
+     <y>660</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
 </ui>

+ 0 - 36
src/savevideofile.h

@@ -1,36 +0,0 @@
-
-/**
- * 叶海辉
- * QQ群121376426
- * http://blog.yundiantech.com/
- */
-
-#ifndef SAVEVIDEOFILE_H
-#define SAVEVIDEOFILE_H
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <math.h>
-
-extern"C"
-{
-#include "libavutil/mathematics.h"
-#include "libavformat/avformat.h"
-#include "libswscale/swscale.h"
-}
-
-#include "SDL.h"
-#include "SDL_thread.h"
-#include "SDL_events.h"
-
-#include <QDebug>
-
-void setWidth(int width,int height);
-bool startEncode();
-bool stopEncode();
-
-void videoDataQuene_Input(uint8_t * buffer,int size);
-void audioDataQuene_Input(uint8_t * buffer,int size);
-
-#endif // SAVEVIDEOFILE_H

+ 0 - 266
src/screenrecorder.cpp

@@ -1,266 +0,0 @@
-
-/**
- * 叶海辉
- * QQ群121376426
- * http://blog.yundiantech.com/
- */
-
-#include "screenrecorder.h"
-
-#include <QDateTime>
-#include <QDebug>
-
-extern int audio_input_frame_size;
-
-ScreenRecorder::ScreenRecorder()
-{
-    m_isRun = false;
-}
-
-ScreenRecorder::~ScreenRecorder()
-{
-
-}
-
-ErroCode ScreenRecorder::init(QString audioDevName)
-{
-
-    AVCodec			*pCodec = NULL;
-    AVCodec			*aCodec = NULL;
-
-    av_register_all();
-    avformat_network_init();
-    avdevice_register_all();  //Register Device
-
-    pFormatCtx = avformat_alloc_context();
-
-    AVInputFormat *ifmt = av_find_input_format("dshow");
-
-    QString audioDevOption = QString("audio=%1").arg(audioDevName);
-    if(avformat_open_input(&pFormatCtx,audioDevOption.toUtf8(),ifmt,NULL)!=0){
-        fprintf(stderr,"Couldn't open input stream audio.(无法打开输入流)\n");
-        return AudioOpenFailed;
-    }
-
-
-    if(avformat_open_input(&pFormatCtx,"video=screen-capture-recorder",ifmt,NULL)!=0){
-        fprintf(stderr,"Couldn't open input stream video.(无法打开输入流)\n");
-        return VideoOpenFailed;
-    }
-
-    videoindex=-1;
-    for(i=0; i<pFormatCtx->nb_streams; i++)
-        if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
-        {
-            videoindex=i;
-            break;
-        }
-    if(videoindex==-1)
-    {
-        printf("Didn't find a video stream.(没有找到视频流)\n");
-        return VideoOpenFailed;
-    }
-
-    pCodecCtx = pFormatCtx->streams[videoindex]->codec;
-    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
-    if(pCodec == NULL)
-    {
-        printf("video Codec not found.\n");
-        return VideoDecoderOpenFailed;
-    }
-
-    if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)
-    {
-        printf("Could not open video codec.\n");
-        return VideoDecoderOpenFailed;
-    }
-
-    audioindex = -1;
-    aCodecCtx = NULL;
-
-    for(i=0; i<pFormatCtx->nb_streams; i++)
-        if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO)
-        {
-            audioindex=i;
-            break;
-        }
-    if(audioindex==-1)
-    {
-        printf("Didn't find a video stream.(没有找到视频流)\n");
-        return AudioOpenFailed;
-    }
-
-    aCodecCtx = pFormatCtx->streams[audioindex]->codec;
-    aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
-    if(aCodec == NULL)
-    {
-        printf("audio Codec not found.\n");
-        return AudioDecoderOpenFailed;
-    }
-
-    if(avcodec_open2(aCodecCtx, aCodec,NULL)<0)
-    {
-        printf("Could not open video codec.\n");
-        return AudioDecoderOpenFailed;
-    }
-
-
-    aFrame=avcodec_alloc_frame();
-    pFrame=avcodec_alloc_frame();
-    pFrameYUV=avcodec_alloc_frame();
-    out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
-    avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
-
-    setWidth(pCodecCtx->width, pCodecCtx->height);
-
-    return SUCCEED;
-}
-
-void ScreenRecorder::deInit()
-{
-    av_free(out_buffer);
-    av_free(aFrame);
-    av_free(pFrame);
-    av_free(pFrameYUV);
-    avcodec_close(pCodecCtx);
-    if (aCodecCtx)
-        avcodec_close(aCodecCtx);
-
-///下面这2个释放这里会奔溃 这里先无视 后面再完善它
-//    avformat_close_input(&pFormatCtx);
-//    avformat_free_context(pFormatCtx);
-}
-
-void ScreenRecorder::startRecord()
-{
-    m_isRun = true;
-//    m_writeFile->startWrite();
-    startEncode();
-    start();
-}
-
-void ScreenRecorder::stopRecord()
-{
-    m_isRun = false;
-
-    stopEncode();
-}
-
-void ScreenRecorder::run()
-{
-
-    int ret, got_frame;
-
-    AVPacket *packet=(AVPacket *)av_malloc(sizeof(AVPacket));
-//    //Output Information-----------------------------
-//    printf("File Information(文件信息)---------------------\n");
-//    av_dump_format(pFormatCtx,0,NULL,0);
-//    printf("-------------------------------------------------\n");
-
-    struct SwsContext *img_convert_ctx;
-    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
-    //------------------------------
-
-
-    int y_size = pCodecCtx->width * pCodecCtx->height;
-
-    int size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
-
-
-    qint64 firstTime = QDateTime::currentMSecsSinceEpoch();
-
-    while(m_isRun )
-    {
-        if (av_read_frame(pFormatCtx, packet)<0)
-        {
-            msleep(10);
-            continue;
-        }
-
-        if(packet->stream_index==videoindex)
-        {
-            qint64 secondTime = QDateTime::currentMSecsSinceEpoch();
-
-            if ((secondTime - firstTime) >= 100)
-            {
-                firstTime = secondTime;
-                ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_frame, packet);
-                if(ret < 0)
-                {
-                    printf("video Decode Error.(解码错误)\n");
-                    return;
-                }
-
-                if(got_frame)
-                {
-                    sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
-
-                    uint8_t * picture_buf = (uint8_t *)av_malloc(size);
-                    memcpy(picture_buf,pFrameYUV->data[0],y_size);
-                    memcpy(picture_buf+y_size,pFrameYUV->data[1],y_size/4);
-                    memcpy(picture_buf+y_size+y_size/4,pFrameYUV->data[2],y_size/4);
-
-
-                    videoDataQuene_Input(picture_buf,y_size*3/2);
-                }
-            }
-        }
-        else if(packet->stream_index == audioindex)
-        {
-
-            ret = avcodec_decode_audio4(aCodecCtx, aFrame, &got_frame, packet);
-            if(ret < 0)
-            {
-                fprintf(stderr,"video Audio Error.\n");
-                return;
-            }
-
-            if (got_frame)
-            {
-
-                int size = av_samples_get_buffer_size(NULL,aCodecCtx->channels, aFrame->nb_samples,aCodecCtx->sample_fmt, 1);
-
-                int index = 0;
-
-                int ONEAudioSize = audio_input_frame_size * 4;//4096
-
-                for (int i=0;i<(size/ONEAudioSize);i++)
-                {
-
-                    int framSize = ONEAudioSize;
-                    if (i==size/ONEAudioSize)
-                    {
-                        framSize = size%ONEAudioSize;
-                    }
-
-                    if (framSize<=0){
-                        break;
-                    }
-
-                    uint8_t * audio_buf = (uint8_t *)malloc(4096*2);
-                    memcpy(audio_buf, aFrame->data[0]+index, framSize);
-
-                    audioDataQuene_Input((uint8_t*)audio_buf,ONEAudioSize);
-
-                    index += framSize;
-
-                }
-            }
-
-        }
-
-        av_free_packet(packet);
-    }
-
-    qDebug()<<"record stopping...";
-
-//    m_writeFile->stopWrite();
-
-    qDebug()<<"record finished!";
-
-    sws_freeContext(img_convert_ctx);
-
-
-    deInit();
-
-}

+ 0 - 68
src/screenrecorder.h

@@ -1,68 +0,0 @@
-
-/**
- * 叶海辉
- * QQ群121376426
- * http://blog.yundiantech.com/
- */
-
-#ifndef SCREENRECORDER_H
-#define SCREENRECORDER_H
-
-#include <QThread>
-
-extern "C"
-{
-#include "libavcodec/avcodec.h"
-#include "libavformat/avformat.h"
-#include "libswscale/swscale.h"
-#include "libavdevice/avdevice.h"
-//SDL
-#include "SDL.h"
-#include "SDL_thread.h"
-}
-
-#include "savevideofile.h"
-
-enum ErroCode
-{
-    AudioOpenFailed = 0,
-    VideoOpenFailed,
-    AudioDecoderOpenFailed,
-    VideoDecoderOpenFailed,
-    SUCCEED
-};
-
-class ScreenRecorder : public QThread
-{
-    Q_OBJECT
-
-public:
-    explicit ScreenRecorder();
-    ~ScreenRecorder();
-
-    ErroCode init(QString audioDevName);
-    void deInit();
-
-    void startRecord();
-    void stopRecord();
-
-protected:
-    void run();
-
-private:
-
-
-    AVFormatContext	*pFormatCtx;
-    int				i, videoindex ,audioindex;
-    AVCodecContext	*pCodecCtx,*aCodecCtx;
-
-
-    AVFrame	*pFrame,*aFrame,*pFrameYUV;
-    uint8_t *out_buffer;
-
-    bool m_isRun;
-
-
-};
-
-#endif // SCREENRECORDER_H

+ 428 - 0
src/video/getvideothread.cpp

@@ -0,0 +1,428 @@
+
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#include "getvideothread.h"
+
+#include <QTimer>
+#include <QDateTime>
+#include <QDebug>
+
+void Yuv420Cut(int x,int y,int desW,int desH,int srcW,int srcH,uint8_t *srcBuffer,uint8_t *desBuffer)
+{
+    int tmpRange;
+    int bufferIndex;
+
+    int yIndex = 0;
+    bufferIndex = 0 + x + y*srcW;
+    tmpRange = srcW * desH;
+    for (int i=0;i<tmpRange;) //逐行拷贝Y分量数据
+    {
+        memcpy(desBuffer+yIndex,srcBuffer+bufferIndex+i,desW);
+        i += srcW;
+        yIndex += desW;
+    }
+
+    int uIndex = desW * desH;
+    int uIndexStep = srcW/2;
+    int uWidthCopy = desW/2;
+    bufferIndex = srcW * srcH+x/2 + y /2 *srcW / 2;
+    tmpRange = srcW * desH / 4;
+    for (int i=0;i<tmpRange;) //逐行拷贝U分量数据
+    {
+        memcpy(desBuffer+uIndex,srcBuffer+bufferIndex+i,uWidthCopy);
+        i += uIndexStep;
+        uIndex += uWidthCopy;
+    }
+
+
+    int vIndex = desW * desH +  desW * desH /4;
+    int vIndexStep = srcW/2;
+    int vWidthCopy = desW/2;
+    bufferIndex = srcW*srcH + srcW*srcH/4 + x/2 + y /2 *srcW / 2;
+    tmpRange = srcW * desH / 4;
+    for (int i=0;i<tmpRange;) //逐行拷贝V分量数据
+    {
+        memcpy(desBuffer+vIndex,srcBuffer+bufferIndex+i,vWidthCopy);
+        i += vIndexStep;
+        vIndex += vWidthCopy;
+    }
+}
+
+GetVideoThread::GetVideoThread()
+{
+    m_isRun = false;
+
+    pFormatCtx = NULL;
+    out_buffer = NULL;
+    aFrame = NULL;
+    pFrame = NULL;
+    pFrameYUV = NULL;
+    pCodecCtx = NULL;
+    aCodecCtx = NULL;
+
+    m_pause = false;
+
+    m_saveVideoFileThread = NULL;
+
+    connect(this,SIGNAL(withChanged(int,int)),this,SLOT(slotWithChanged(int,int)),Qt::BlockingQueuedConnection);
+    connect(this,SIGNAL(loading(bool)),this,SLOT(slotLoading(bool)),Qt::BlockingQueuedConnection);
+
+}
+
+
+GetVideoThread::~GetVideoThread()
+{
+
+}
+
+ErroCode GetVideoThread::init(QString videoDevName, bool useVideo, QString audioDevName, bool useAudio)
+{
+
+    AVCodec			*pCodec = NULL;
+    AVCodec			*aCodec = NULL;
+
+    AVFormatContext *pFormatCtx2 = avformat_alloc_context();
+    AVDictionary* options = NULL;
+    av_dict_set(&options,"list_devices","true",0);
+    AVInputFormat *iformat = av_find_input_format("dshow");
+    fprintf(stderr,"Device Info=============\n");
+    avformat_open_input(&pFormatCtx2,"video=dummy",iformat,&options);
+    fprintf(stderr,"========================\n");
+
+    pFormatCtx = avformat_alloc_context();
+
+    AVInputFormat *ifmt = av_find_input_format("dshow");
+
+    if (useAudio)
+    {
+        QString audioDevOption = QString("audio=%1").arg(audioDevName);
+
+        if(avformat_open_input(&pFormatCtx,audioDevOption.toUtf8(),ifmt,NULL)!=0){
+            fprintf(stderr,"Couldn't open input stream audio.(无法打开输入流)\n");
+            return AudioOpenFailed;
+        }
+
+    }
+
+
+    if (useVideo)
+    {
+        QString videoDevOption = QString("video=%1").arg(videoDevName);
+        if(avformat_open_input(&pFormatCtx,videoDevOption.toUtf8(),ifmt,NULL)!=0){
+            qDebug()<<"Couldn't open input stream video.(无法打开输入流)\n";
+            return VideoOpenFailed;
+        }
+    }
+
+    videoindex=-1;
+    pCodecCtx = NULL;
+    if (useVideo)
+    {
+        for(i=0; i<pFormatCtx->nb_streams; i++)
+            if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
+            {
+                videoindex=i;
+                break;
+            }
+        if(videoindex==-1)
+        {
+            printf("Didn't find a video stream.(没有找到视频流)\n");
+            return VideoOpenFailed;
+        }
+
+        pCodecCtx = pFormatCtx->streams[videoindex]->codec;
+        pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
+        if(pCodec == NULL)
+        {
+            printf("video Codec not found.\n");
+            return VideoDecoderOpenFailed;
+        }
+
+        if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)
+        {
+            printf("Could not open video codec.\n");
+            return VideoDecoderOpenFailed;
+        }
+
+        pFrame=avcodec_alloc_frame();
+        pFrameYUV=avcodec_alloc_frame();
+        out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
+        avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
+
+        setPicRange(0,0,pCodecCtx->width, pCodecCtx->height);
+    }
+
+    audioindex = -1;
+    aCodecCtx = NULL;
+    if (useAudio)
+    {
+
+        for(i=0; i<pFormatCtx->nb_streams; i++)
+            if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO)
+            {
+                audioindex=i;
+                break;
+            }
+        if(audioindex==-1)
+        {
+            printf("Didn't find a video stream.(没有找到视频流)\n");
+            return AudioOpenFailed;
+        }
+
+        aCodecCtx = pFormatCtx->streams[audioindex]->codec;
+
+        aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
+        if(aCodec == NULL)
+        {
+            printf("audio Codec not found.\n");
+            return AudioDecoderOpenFailed;
+        }
+
+        if(avcodec_open2(aCodecCtx, aCodec,NULL)<0)
+        {
+            printf("Could not open video codec.\n");
+            return AudioDecoderOpenFailed;
+        }
+
+        qDebug()<<"audio info:";
+                qDebug()<<"audio info:"<<aCodecCtx->bit_rate<<aCodecCtx->sample_fmt<<aCodecCtx->bit_rate<<aCodecCtx->sample_rate<<aCodecCtx->channels;
+
+        aFrame=avcodec_alloc_frame();
+
+    }
+
+    return SUCCEED;
+}
+
+void GetVideoThread::deInit()
+{
+    if (out_buffer)
+    {
+        av_free(out_buffer);
+        out_buffer = NULL;
+    }
+
+    if (aFrame)
+    {
+        av_free(aFrame);
+        aFrame = NULL;
+    }
+
+    if (pFrame)
+    {
+        av_free(pFrame);
+        pFrame = NULL;
+    }
+
+    if (pFrameYUV)
+    {
+        av_free(pFrameYUV);
+        pFrameYUV = NULL;
+    }
+
+    if (pCodecCtx)
+        avcodec_close(pCodecCtx);
+    if (aCodecCtx)
+        avcodec_close(aCodecCtx);
+
+    avformat_close_input(&pFormatCtx);
+
+    avformat_free_context(pFormatCtx);
+
+}
+
+void GetVideoThread::startRecord()
+{
+    m_isRun = true;
+    start();
+}
+
+void GetVideoThread::pauseRecord()
+{
+    m_pause = true;
+}
+
+void GetVideoThread::restoreRecord()
+{
+    m_pause = false;
+}
+
+void GetVideoThread::stopRecord()
+{
+    m_isRun = false;
+}
+
+void GetVideoThread::setPicRange(int x,int y,int w,int h)
+{
+    pic_x = x;
+    pic_y = y;
+    pic_w = w;
+    pic_h = h;
+}
+
+void GetVideoThread::setSaveVideoFileThread(SaveVideoFileThread * p)
+{
+    m_saveVideoFileThread = p;
+}
+
+void GetVideoThread::run()
+{
+
+    struct SwsContext *img_convert_ctx = NULL;
+
+    int y_size = 0;
+    int yuvSize = 0;
+    int size = 0;
+
+    if (pCodecCtx)
+    {
+
+        //-------------------------------------------------------------//
+
+        int numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width,pCodecCtx->height);
+        out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
+        avpicture_fill((AVPicture *) pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height);
+
+        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);
+        //------------------------------
+
+        y_size = pCodecCtx->width * pCodecCtx->height;
+        yuvSize = pic_w * pic_h;
+        size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
+
+    }
+
+    int ret, got_frame;
+
+    AVPacket *packet=(AVPacket *)av_malloc(sizeof(AVPacket));
+//    //Output Information-----------------------------
+//    printf("File Information(文件信息)---------------------\n");
+//    av_dump_format(pFormatCtx,0,NULL,0);
+//    printf("-------------------------------------------------\n");
+
+    qint64 firstTime = QDateTime::currentMSecsSinceEpoch();
+
+    bool m_getFirst = false;
+    qint64 timeIndex = 0;
+
+    while(m_isRun )
+    {
+        if (av_read_frame(pFormatCtx, packet)<0)
+        {
+            qDebug()<<"read failed!";
+            msleep(10);
+            continue;
+        }
+
+        if (m_pause)
+        {
+            av_free_packet(packet);
+            msleep(10);
+            continue;
+        }
+
+        if(packet->stream_index==videoindex)
+        {
+            long time = 0;
+            if (m_saveVideoFileThread)
+            {
+                if (m_getFirst)
+                {
+                    qint64 secondTime = QDateTime::currentMSecsSinceEpoch();
+
+                    time = secondTime - firstTime + timeIndex;
+                }
+                else
+                {
+                    firstTime = QDateTime::currentMSecsSinceEpoch();
+                    timeIndex = m_saveVideoFileThread->getVideoPts()*1000;
+                    m_getFirst = true;
+                }
+
+            }
+
+            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_frame, packet);
+
+            if(ret < 0)
+            {
+                printf("video Decode Error.(解码错误)\n");
+                return;
+            }
+
+            if(got_frame && pCodecCtx)
+            {
+
+                sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
+
+                if (m_saveVideoFileThread)
+                {
+                    uint8_t * picture_buf = (uint8_t *)av_malloc(size);
+                    memcpy(picture_buf,pFrameYUV->data[0],y_size);
+                    memcpy(picture_buf+y_size,pFrameYUV->data[1],y_size/4);
+                    memcpy(picture_buf+y_size+y_size/4,pFrameYUV->data[2],y_size/4);
+                    uint8_t * yuv_buf = (uint8_t *)av_malloc(size);
+
+                    ///将YUV图像裁剪成目标大小
+                    Yuv420Cut(pic_x,pic_y,pic_w,pic_h,pCodecCtx->width,pCodecCtx->height,picture_buf,yuv_buf);
+                    m_saveVideoFileThread->videoDataQuene_Input(yuv_buf,yuvSize*3/2,time);
+                    av_free(picture_buf);
+                }
+            }
+
+        }
+        else if(packet->stream_index == audioindex)
+        {
+            ret = avcodec_decode_audio4(aCodecCtx, aFrame, &got_frame, packet);
+            if(ret < 0)
+            {
+                fprintf(stderr,"video Audio Error.\n");
+                return;
+            }
+
+            if (got_frame)
+            {
+                if (m_saveVideoFileThread)
+                {
+                    int size = av_samples_get_buffer_size(NULL,aCodecCtx->channels, aFrame->nb_samples,aCodecCtx->sample_fmt, 1);
+                    int index = 0;
+                    int ONEAudioSize = m_saveVideoFileThread->audio_input_frame_size * 4;//4096
+                    for (int i=0;i<(size/ONEAudioSize);i++)
+                    {
+
+                        int framSize = ONEAudioSize;
+                        if (i==size/ONEAudioSize)
+                        {
+                            framSize = size%ONEAudioSize;
+                        }
+
+                        if (framSize<=0){
+                            break;
+                        }
+
+                        uint8_t * audio_buf = (uint8_t *)malloc(4096*2);
+                        memcpy(audio_buf, aFrame->data[0]+index, framSize);
+                        m_saveVideoFileThread->audioDataQuene_Input((uint8_t*)audio_buf,ONEAudioSize);
+                        index += framSize;
+                    }
+                }
+            }
+        }
+
+        av_free_packet(packet);
+    }
+
+    sws_freeContext(img_convert_ctx);
+
+    qDebug()<<"record stopping...";
+
+    m_pause = false;
+
+    deInit();
+
+    qDebug()<<"record finished!";
+
+}

+ 76 - 0
src/video/getvideothread.h

@@ -0,0 +1,76 @@
+
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef GetVideoThread_H
+#define GetVideoThread_H
+
+#include <QThread>
+
+extern "C"
+{
+    #include "libavcodec/avcodec.h"
+    #include "libavformat/avformat.h"
+    #include "libswscale/swscale.h"
+    #include "libavdevice/avdevice.h"
+}
+
+#include "savevideofile.h"
+
+enum ErroCode
+{
+    AudioOpenFailed = 0,
+    VideoOpenFailed,
+    AudioDecoderOpenFailed,
+    VideoDecoderOpenFailed,
+    SUCCEED
+};
+
+class GetVideoThread : public QThread
+{
+    Q_OBJECT
+
+public:
+    explicit GetVideoThread();
+    ~GetVideoThread();
+
+    ErroCode init(QString videoDevName,bool useVideo,QString audioDevName,bool useAudio);
+    void deInit();
+
+    void startRecord();
+    void pauseRecord();
+    void restoreRecord();
+    void stopRecord();
+
+    void setPicRange(int x,int y,int w,int h);
+
+    void setSaveVideoFileThread(SaveVideoFileThread * p);
+
+protected:
+    void run();
+
+private:
+
+    AVFormatContext	*pFormatCtx;
+    int				i, videoindex ,audioindex;
+    AVCodecContext	*pCodecCtx,*aCodecCtx;
+
+    AVFrame	*pFrame,*aFrame,*pFrameYUV;
+    uint8_t *out_buffer;
+
+    int pic_x;
+    int pic_y;
+    int pic_w;
+    int pic_h;
+
+    bool m_isRun;
+    bool m_pause;
+
+    SaveVideoFileThread * m_saveVideoFileThread;
+
+};
+
+#endif // GetVideoThread_H

+ 348 - 133
src/savevideofile.cpp → src/video/savevideofile.cpp

@@ -1,4 +1,4 @@
-
+
 /**
  * 叶海辉
  * QQ群121376426
@@ -7,44 +7,78 @@
 
 #include "savevideofile.h"
 
-uint8_t picture_buf[2000*2000*4];
-bool isStop = false;
+#define STREAM_PIX_FMT PIX_FMT_YUV420P /* default pix_fmt */
+
+static int writeVideoThreadFunc(void *arg);
+
+SaveVideoFileThread::SaveVideoFileThread()
+{
+    isStop = false;
+
+    m_containsVideo = true;
+    m_containsAudio = true;
 
-static float t, tincr, tincr2;
-static int16_t *samples;
-static uint8_t *audio_outbuf;
-static int audio_outbuf_size;
-int audio_input_frame_size;
+    videoMutex = SDL_CreateMutex();
+    videoDataQueneHead = NULL;
+    videoDataQueneTail = NULL;
 
-int WIDTH;
-int HEIGHT;
+    audioMutex = SDL_CreateMutex();
+    AudioDataQueneHead = NULL;
+    AudioDataQueneTail = NULL;
 
+    videoBufferCount = 0;
+    audioBufferCount = 0;
 
-/**************************************************************/
-/* audio output */
+    m_videoFrameRate = 15;
 
+    lastVideoNode = NULL;
+
+    mBitRate = 450000;
+
+    audio_pts = 0;
+    video_pts = 0;
+
+}
 
-struct BufferDataNode
+SaveVideoFileThread::~SaveVideoFileThread()
 {
-    uint8_t * buffer;
-    int bufferSize;
-    BufferDataNode * next;
-};
 
-SDL_mutex *videoMutex = SDL_CreateMutex();
-BufferDataNode * videoDataQueneHead = NULL;
-BufferDataNode * videoDataQueneTail = NULL;
+}
+
+void SaveVideoFileThread::setContainsVideo(bool value)
+{
+    m_containsVideo = value;
+}
 
-SDL_mutex *audioMutex = SDL_CreateMutex();
-BufferDataNode * AudioDataQueneHead = NULL;
-BufferDataNode * AudioDataQueneTail = NULL;
+void SaveVideoFileThread::setContainsAudio(bool value)
+{
+    m_containsAudio = value;
+}
 
-void videoDataQuene_Input(uint8_t * buffer,int size)
+void SaveVideoFileThread::setVideoFrameRate(int value)
 {
+    m_videoFrameRate = value;
+}
+
+void SaveVideoFileThread::setFileName(char *str)
+{
+    memset(filename,0x0,128);
+    strcpy(filename,str);
+}
+
+void SaveVideoFileThread::setQuantity(int value)
+{
+    mBitRate = 450000 + (value - 5) * 50000;
+}
+
+void SaveVideoFileThread::videoDataQuene_Input(uint8_t * buffer,int size,long time)
+{
+//    qDebug()<<"void SaveVideoFileThread::videoDataQuene_Input(uint8_t * buffer,int size,long time)"<<time;
     BufferDataNode * node = (BufferDataNode*)malloc(sizeof(BufferDataNode));
 //    node->buffer = (uint8_t *)malloc(size);
     node->bufferSize = size;
     node->next = NULL;
+    node->time = time;
 
     node->buffer = buffer;
 //    memcpy(node->buffer,buffer,size);
@@ -62,10 +96,89 @@ void videoDataQuene_Input(uint8_t * buffer,int size)
 
     videoDataQueneTail = node;
 
+    videoBufferCount++;
+
     SDL_UnlockMutex(videoMutex);
 }
 
-static BufferDataNode *videoDataQuene_get()
+//BufferDataNode *SaveVideoFileThread::videoDataQuene_get(double time)
+//{
+//    BufferDataNode * node = NULL;
+
+//    SDL_LockMutex(videoMutex);
+
+//    if (videoDataQueneHead != NULL)
+//    {
+//        node = videoDataQueneHead;
+
+////qDebug()<<"111:"<<time<<node->time<<videoBufferCount;
+//        if (time >= node->time)
+//        {
+////            qDebug()<<"000";
+//            if (videoDataQueneHead->next != NULL)
+//            {
+////                qDebug()<<"111";
+//                while(node != NULL)
+//                {
+////                    qDebug()<<"222";
+//                    if (node->next == NULL)
+//                    {
+//                        break;
+//                    }
+////qDebug()<<"333"<<time << node->next->time;
+//                    if (time < node->next->time)
+//                    {
+//                        break;
+//                    }
+
+//                    BufferDataNode * tmp = node;
+////qDebug()<<"222:"<<node->time<<time;
+//                    node = node->next;
+
+//                    videoBufferCount--;
+
+//                    av_free(tmp->buffer);
+//                    free(tmp);
+//                }
+//            }
+//        }
+//        else
+//        {
+//            node = lastVideoNode;
+//        }
+
+//        if (videoDataQueneTail == node)
+//        {
+//            videoDataQueneTail = NULL;
+//        }
+
+//        if (node != NULL && node != lastVideoNode)
+//        {
+////            qDebug()<<"00000000";
+//            videoDataQueneHead = node->next;
+//            videoBufferCount--;
+//        }
+
+//    }
+////    qDebug()<<videoBufferCount;
+//    SDL_UnlockMutex(videoMutex);
+////qDebug()<<"00000000999";
+//    return node;
+//}
+
+/**
+ * @brief SaveVideoFileThread::videoDataQuene_get
+ * 由于采集屏幕获取到的图像每秒钟的张数是不固定的
+ * 而我们是用固定帧率的方式来写视频
+ * 因此需要保证每秒钟的视频图像张数都是固定的
+ * 下面这个函数就是做了这个操作:
+ * 1.视频数据不足的时候重复使用上一帧
+ * 2.视频数据太多的时候 丢掉一些
+ * @param time
+ * @return
+ */
+
+BufferDataNode *SaveVideoFileThread::videoDataQuene_get(double time)
 {
     BufferDataNode * node = NULL;
 
@@ -74,13 +187,56 @@ static BufferDataNode *videoDataQuene_get()
     if (videoDataQueneHead != NULL)
     {
         node = videoDataQueneHead;
+        if (time >= node->time)
+        {
+            while(node != NULL)
+            {
+                if (node->next == NULL)
+                {
+                    if (isStop)
+                    {
+                        break;
+                    }
+                    else
+                    {
+                        //队列里面才一帧数据 先不处理
+                        SDL_UnlockMutex(videoMutex);
+                        return NULL;
+                    }
+                }
+
+                if (time < node->next->time)
+                {
+                    break;
+                }
+
+                BufferDataNode * tmp = node;
+                node = node->next;
+
+                videoDataQueneHead = node;
+
+                videoBufferCount--;
+                av_free(tmp->buffer);
+                free(tmp);
+            }
 
-        if (videoDataQueneTail == videoDataQueneHead)
+        }
+        else
+        {
+            node = lastVideoNode;
+        }
+
+        if (videoDataQueneTail == node)
         {
             videoDataQueneTail = NULL;
         }
 
-        videoDataQueneHead = videoDataQueneHead->next;
+        if (node != NULL && node != lastVideoNode)
+        {
+            videoDataQueneHead = node->next;
+            videoBufferCount--;
+        }
+
     }
 
     SDL_UnlockMutex(videoMutex);
@@ -88,13 +244,14 @@ static BufferDataNode *videoDataQuene_get()
     return node;
 }
 
-void audioDataQuene_Input(uint8_t * buffer,int size)
+void SaveVideoFileThread::audioDataQuene_Input(uint8_t * buffer,int size)
 {
     BufferDataNode * node = (BufferDataNode*)malloc(sizeof(BufferDataNode));
-    node->buffer = (uint8_t *)malloc(size);
+//    node->buffer = buffer;
     node->bufferSize = size;
     node->next = NULL;
 
+    node->buffer = (uint8_t *)malloc(size);
     memcpy(node->buffer,buffer,size);
 
     SDL_LockMutex(audioMutex);
@@ -110,10 +267,12 @@ void audioDataQuene_Input(uint8_t * buffer,int size)
 
     AudioDataQueneTail = node;
 
+    audioBufferCount++;
+
     SDL_UnlockMutex(audioMutex);
 }
 
-static BufferDataNode *audioDataQuene_get()
+BufferDataNode *SaveVideoFileThread::audioDataQuene_get()
 {
     BufferDataNode * node = NULL;
 
@@ -129,6 +288,9 @@ static BufferDataNode *audioDataQuene_get()
         }
 
         AudioDataQueneHead = AudioDataQueneHead->next;
+
+        audioBufferCount--;
+
     }
 
     SDL_UnlockMutex(audioMutex);
@@ -136,10 +298,11 @@ static BufferDataNode *audioDataQuene_get()
     return node;
 }
 
+
 /*
  * add an audio output stream
  */
-static AVStream *add_audio_stream(AVFormatContext *oc, AVCodecID codec_id)
+AVStream *SaveVideoFileThread::add_audio_stream(AVFormatContext *oc, AVCodecID codec_id)
 {
     AVCodecContext *c;
     AVStream *st;
@@ -157,10 +320,14 @@ static AVStream *add_audio_stream(AVFormatContext *oc, AVCodecID codec_id)
 
     /* put sample parameters */
     c->sample_fmt = AV_SAMPLE_FMT_S16;
-    c->bit_rate = 44100;
-    c->sample_rate = 44100;
+    c->bit_rate = 64000;
+    c->sample_rate = 22050;
     c->channels = 2;
 
+//    c->bit_rate = 9600;
+//    c->sample_rate = 11025;
+//    c->channels = 1;
+
     // some formats want stream headers to be separate
     if (oc->oformat->flags & AVFMT_GLOBALHEADER)
         c->flags |= CODEC_FLAG_GLOBAL_HEADER;
@@ -168,7 +335,7 @@ static AVStream *add_audio_stream(AVFormatContext *oc, AVCodecID codec_id)
     return st;
 }
 
-static void open_audio(AVFormatContext *oc, AVStream *st)
+void SaveVideoFileThread::open_audio(AVFormatContext *oc, AVStream *st)
 {
     AVCodecContext *c;
     AVCodec *codec;
@@ -216,7 +383,7 @@ static void open_audio(AVFormatContext *oc, AVStream *st)
     samples = (int16_t *)av_malloc(audio_input_frame_size * 2 * c->channels);
 }
 
-static void write_audio_frame(AVFormatContext *oc, AVStream *st)
+bool SaveVideoFileThread::write_audio_frame(AVFormatContext *oc, AVStream *st)
 {
     AVCodecContext *c;
     AVPacket pkt;
@@ -228,17 +395,18 @@ static void write_audio_frame(AVFormatContext *oc, AVStream *st)
 
     if (node == NULL)
     {
-        SDL_Delay(1); //延时1ms
-        return;
+        return false;
     }
     else
     {
         memcpy(samples,node->buffer, node->bufferSize);
-
+//    qDebug()<<"size:"<<node->bufferSize<<audio_input_frame_size;
         free(node->buffer);
         free(node);
     }
 
+//    memset(samples,0,audio_input_frame_size * 2 * c->channels); //静音
+
 //    fread(samples, 1, audio_input_frame_size*4, pcmInFp);
 
     pkt.size = avcodec_encode_audio(c, audio_outbuf, audio_outbuf_size, samples);
@@ -247,16 +415,21 @@ static void write_audio_frame(AVFormatContext *oc, AVStream *st)
         pkt.pts= av_rescale_q(c->coded_frame->pts, c->time_base, st->time_base);
     pkt.flags |= AV_PKT_FLAG_KEY;
     pkt.stream_index = st->index;
+    pkt.dts = pkt.pts;
     pkt.data = audio_outbuf;
 
+//    pkt.pts = 0;
+//qDebug()<<"audio pts"<<pkt.duration<<pkt.pos<<pkt.dts<<pkt.pts;
     /* write the compressed frame in the media file */
     if (av_interleaved_write_frame(oc, &pkt) != 0) {
         fprintf(stderr, "Error while writing audio frame\n");
         exit(1);
     }
+
+    return true;
 }
 
-static void close_audio(AVFormatContext *oc, AVStream *st)
+void SaveVideoFileThread::close_audio(AVFormatContext *oc, AVStream *st)
 {
     avcodec_close(st->codec);
 
@@ -264,15 +437,9 @@ static void close_audio(AVFormatContext *oc, AVStream *st)
     av_free(audio_outbuf);
 }
 
-/**************************************************************/
-/* video output */
-
-static AVFrame *picture, *tmp_picture;
-static uint8_t *video_outbuf;
-static int video_outbuf_size;
 
 /* add a video output stream */
-static AVStream *add_video_stream(AVFormatContext *oc, AVCodecID codec_id)
+AVStream *SaveVideoFileThread::add_video_stream(AVFormatContext *oc, AVCodecID codec_id)
 {
     AVCodecContext *c;
     AVStream *st;
@@ -297,7 +464,7 @@ static AVStream *add_video_stream(AVFormatContext *oc, AVCodecID codec_id)
     c->codec_id = codec_id;
 
     /* put sample parameters */
-    c->bit_rate = 400000;
+    c->bit_rate = mBitRate;
     /* resolution must be a multiple of two */
     c->width = WIDTH;
     c->height = HEIGHT;
@@ -305,10 +472,14 @@ static AVStream *add_video_stream(AVFormatContext *oc, AVCodecID codec_id)
        of which frame timestamps are represented. for fixed-fps content,
        timebase should be 1/framerate and timestamp increments should be
        identically 1. */
-    c->time_base.den = 10;
+    c->time_base.den = m_videoFrameRate;
     c->time_base.num = 1;
-    c->gop_size = 12; /* emit one intra frame every twelve frames at most */
-    c->pix_fmt = PIX_FMT_YUV420P;
+//    c->gop_size = 12; /* emit one intra frame every twelve frames at most */
+    c->gop_size = m_videoFrameRate;
+    c->pix_fmt = STREAM_PIX_FMT;
+
+    c->b_frame_strategy = 0;
+
     if (c->codec_id == CODEC_ID_MPEG2VIDEO) {
         /* just for testing, we also add B frames */
         c->max_b_frames = 2;
@@ -346,15 +517,13 @@ static AVFrame *alloc_picture(enum PixelFormat pix_fmt, int width, int height)
     return picture;
 }
 
-static void open_video(AVFormatContext *oc, AVStream *st)
+void SaveVideoFileThread::open_video(AVFormatContext *oc, AVStream *st)
 {
     AVCodec *codec;
     AVCodecContext *c;
 
     c = st->codec;
 
-
-
     // Set Option
     AVDictionary *param = 0;
     //H.264
@@ -362,19 +531,18 @@ static void open_video(AVFormatContext *oc, AVStream *st)
     av_dict_set(&param, "preset", "superfast", 0);
     av_dict_set(&param, "tune", "zerolatency", 0);  //实现实时编码
 
-qDebug()<<c->codec_id;
     codec = avcodec_find_encoder(c->codec_id);
     if (!codec){
-      printf("Can not find video encoder!\n");
+      fprintf(stderr,"Can not find video encoder!\n");
       exit(1);
     }
-qDebug()<<"333";
+
 int ret = 0;
     if (ret = avcodec_open2(c, codec,&param) < 0){
-      qDebug()<<("Failed to open video encoder!\n")<<ret;
+      fprintf(stderr,"Failed to open video encoder!\n");
       exit(1);
     }
-qDebug()<<"333";
+
 //    /* find the video encoder */
 //    codec = avcodec_find_encoder(c->codec_id);
 //    if (!codec) {
@@ -414,7 +582,7 @@ qDebug()<<"333";
     }
 }
 
-static void write_video_frame(AVFormatContext *oc, AVStream *st)
+bool SaveVideoFileThread::write_video_frame(AVFormatContext *oc, AVStream *st, double time)
 {
 
     int out_size, ret;
@@ -422,12 +590,26 @@ static void write_video_frame(AVFormatContext *oc, AVStream *st)
 
     c = st->codec;
 
-    BufferDataNode *node = videoDataQuene_get();
+    BufferDataNode *node = videoDataQuene_get(time);
+
+    if (node != NULL)
+    {
+        if (node != lastVideoNode)
+        {
+            if (lastVideoNode != NULL)
+            {
+                av_free(lastVideoNode->buffer);
+                free(lastVideoNode);
+            }
+
+            lastVideoNode = node;
+        }
+    }
 
+    ///没有视频数据 则先返回 等待视频数据
     if (node == NULL)
     {
-        SDL_Delay(1); //延时1ms
-        return;
+        return false;
     }
     else
     {
@@ -435,9 +617,6 @@ static void write_video_frame(AVFormatContext *oc, AVStream *st)
 
         memcpy(picture_buf,node->buffer, y_size*3/2);
 
-        av_free(node->buffer);
-        free(node);
-
         picture->data[0] = picture_buf;  // 亮度Y
         picture->data[1] = picture_buf+ y_size;  // U
         picture->data[2] = picture_buf+ y_size*5/4; // V
@@ -473,6 +652,7 @@ static void write_video_frame(AVFormatContext *oc, AVStream *st)
 
             /* write the compressed frame in the media file */
             ret = av_interleaved_write_frame(oc, &pkt);
+
         } else {
             ret = 0;
         }
@@ -483,12 +663,14 @@ static void write_video_frame(AVFormatContext *oc, AVStream *st)
         exit(1);
     }
 
+    return true;
+
 }
 
-static void close_video(AVFormatContext *oc, AVStream *st)
+void SaveVideoFileThread::close_video(AVFormatContext *oc, AVStream *st)
 {
     avcodec_close(st->codec);
-    av_free(picture->data[0]);
+//    av_free(picture_buf);
     av_free(picture);
     if (tmp_picture) {
         av_free(tmp_picture->data[0]);
@@ -497,28 +679,63 @@ static void close_video(AVFormatContext *oc, AVStream *st)
     av_free(video_outbuf);
 }
 
-int encode_thread(void *arg)
+double SaveVideoFileThread::getVideoPts()
+{
+    return video_pts;
+}
+
+double SaveVideoFileThread::getAudioPts()
+{
+    return audio_pts;
+}
+
+void SaveVideoFileThread::setWidth(int width,int height)
+{
+    WIDTH = width;
+    HEIGHT = height;
+}
+
+
+bool SaveVideoFileThread::startWrite()
+{
+    isStop = false;
+
+    ///创建一个线程专门用来解码视频
+    SDL_CreateThread(writeVideoThreadFunc, "writeVideoThreadFunc", this);
+
+    return true;
+}
+
+bool SaveVideoFileThread::stopWrite()
 {
-    const char *filename;
+    isStop = true;
+
+    return true;
+}
+
+int writeVideoThreadFunc(void *arg)
+{
+    SaveVideoFileThread *pointer = (SaveVideoFileThread *) arg;
+
+//    const char *filename;
     AVOutputFormat *fmt;
     AVFormatContext *oc;
     AVStream *audio_st, *video_st;
-    double audio_pts, video_pts;
-    int i;
 
-    /* initialize libavcodec, and register all codecs and formats */
-    av_register_all();
+    int i;
 
-    filename = "a.mp4";
+    /// ffmpegg可以根据文件名称自动获取视频格式
+    /// 如需换别的格式 只需要改文件名和对应的format_name(就是下面的Mp4)即可
+    /// 因此无需了解具体的视频封装格式
 
     /* allocate the output media context */
-    avformat_alloc_output_context2(&oc, NULL, "mp4", filename);
+    avformat_alloc_output_context2(&oc, NULL, "mp4", pointer->filename);
     if (!oc) {
-        qDebug()<<("Could not deduce output format from file extension: using MPEG.\n");
-        avformat_alloc_output_context2(&oc, NULL, "mpeg", filename);
+        fprintf(stderr,"Could not deduce output format from file extension: using MPEG.\n");
+        avformat_alloc_output_context2(&oc, NULL, "mpeg", pointer->filename);
     }
     if (!oc) {
-        return 1;
+        return -1;
     }
     fmt = oc->oformat;
 
@@ -527,29 +744,37 @@ int encode_thread(void *arg)
     video_st = NULL;
     audio_st = NULL;
 
-    qDebug()<<fmt->video_codec;
-    if (fmt->video_codec != CODEC_ID_NONE) {
-        video_st = add_video_stream(oc, fmt->video_codec);
+    pointer->picture = NULL;
+    pointer->isStop = false;
+
+    if (pointer->m_containsVideo)
+    {
+        if (fmt->video_codec != CODEC_ID_NONE) {
+            video_st = pointer->add_video_stream(oc, AV_CODEC_ID_H264);
+        }
     }
-    qDebug()<<"333";
-    if (fmt->audio_codec != CODEC_ID_NONE) {
-        audio_st = add_audio_stream(oc, fmt->audio_codec);
+
+    if (pointer->m_containsAudio)
+    {
+        if (fmt->audio_codec != CODEC_ID_NONE) {
+            audio_st = pointer->add_audio_stream(oc, AV_CODEC_ID_AAC);
+        }
     }
-qDebug()<<"444";
-    av_dump_format(oc, 0, filename, 1);
+
+    av_dump_format(oc, 0, pointer->filename, 1);
 
     /* now that all the parameters are set, we can open the audio and
        video codecs and allocate the necessary encode buffers */
     if (video_st)
-        open_video(oc, video_st);
+        pointer->open_video(oc, video_st);
     if (audio_st)
-        open_audio(oc, audio_st);
-qDebug()<<"777";
+        pointer->open_audio(oc, audio_st);
+
     /* open the output file, if needed */
     if (!(fmt->flags & AVFMT_NOFILE)) {
-        if (avio_open(&oc->pb, filename, AVIO_FLAG_WRITE) < 0) {
-            fprintf(stderr, "Could not open '%s'\n", filename);
-            return 1;
+        if (avio_open(&oc->pb, pointer->filename, AVIO_FLAG_WRITE) < 0) {
+            fprintf(stderr,"Could not open %s",pointer->filename);
+            return -1;
         }
     }
 
@@ -557,31 +782,43 @@ qDebug()<<"777";
 //    av_write_header(oc);
     avformat_write_header(oc,NULL);
 
-    picture->pts = 0;
-    while(!isStop)
+    if (pointer->picture)
+    {
+        pointer->picture->pts = 0;
+    }
+
+    while(1)
     {
         /* compute current audio and video time */
         if (audio_st)
-            audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den;
+            pointer->audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den;
         else
-            audio_pts = 0.0;
+            pointer->audio_pts = 0.0;
 
         if (video_st)
-            video_pts = (double)video_st->pts.val * video_st->time_base.num / video_st->time_base.den;
+            pointer->video_pts = (double)video_st->pts.val * video_st->time_base.num / video_st->time_base.den;
         else
-            video_pts = 0.0;
-
-        qDebug()<<audio_pts<<video_pts;
-
-//        if ((!audio_st || audio_pts >= STREAM_DURATION) &&
-//            (!video_st || video_pts >= STREAM_DURATION))
-//            break;
+            pointer->video_pts = 0.0;
 
         /* write interleaved audio and video frames */
-        if (!video_st || (video_st && audio_st && audio_pts < video_pts)) {
-            write_audio_frame(oc, audio_st);
+        if (!video_st || (video_st && audio_st && pointer->audio_pts < pointer->video_pts)) {
+            if (!pointer->write_audio_frame(oc, audio_st))
+            {
+                if (pointer->isStop)
+                {
+                    break;
+                }
+                SDL_Delay(1); //延时1ms
+            }
         } else {
-            write_video_frame(oc, video_st);
+            if (!pointer->write_video_frame(oc, video_st,pointer->video_pts*1000))
+            {
+                if (pointer->isStop)
+                {
+                    break;
+                }
+                SDL_Delay(1); //延时1ms
+            }
         }
     }
 
@@ -589,9 +826,9 @@ qDebug()<<"777";
 
     /* close each codec */
     if (video_st)
-        close_video(oc, video_st);
+        pointer->close_video(oc, video_st);
     if (audio_st)
-        close_audio(oc, audio_st);
+        pointer->close_audio(oc, audio_st);
 
     /* free the streams */
     for(i = 0; i < oc->nb_streams; i++) {
@@ -607,30 +844,8 @@ qDebug()<<"777";
     /* free the stream */
     av_free(oc);
 
-    return 0;
-}
-
-void setWidth(int width,int height)
-{
-    WIDTH = width;
-    HEIGHT = height;
-}
-
-bool startEncode()
-{
-    SDL_Thread *encodeThreadId = SDL_CreateThread(encode_thread, "parse_thread", NULL);
-
-    if (!encodeThreadId)
-    {
-        return false;
-    }
+    pointer->audio_pts = 0;
+    pointer->video_pts = 0;
 
-    return true;
 }
 
-bool stopEncode()
-{
-    isStop = true;
-
-    return true;
-}

+ 131 - 0
src/video/savevideofile.h

@@ -0,0 +1,131 @@
+
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef SAVEVIDEOFILE_H
+#define SAVEVIDEOFILE_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+extern"C"
+{
+#include "libavutil/mathematics.h"
+#include "libavformat/avformat.h"
+#include "libswscale/swscale.h"
+
+
+#include "SDL.h"
+#include "SDL_thread.h"
+#include "SDL_events.h"
+
+}
+
+struct BufferDataNode
+{
+    uint8_t * buffer;
+    int bufferSize;
+    long time;
+    BufferDataNode * next;
+};
+
+/**
+ * @brief The SaveVideoFileThread class
+ * 保存生成视频
+ * 主要参考了 ffmpeg的output_example.c
+ * output_example.c可以自行百度下载到
+ */
+
+class SaveVideoFileThread
+{
+public:
+    explicit SaveVideoFileThread();
+    ~SaveVideoFileThread();
+
+    void setFileName(char *str);
+
+    void setQuantity(int value);
+    void setWidth(int width,int height);
+
+    bool startWrite();
+    bool stopWrite();
+
+    void videoDataQuene_Input(uint8_t * buffer, int size, long time);
+    BufferDataNode *videoDataQuene_get(double time);
+
+    void audioDataQuene_Input(uint8_t * buffer,int size);
+    BufferDataNode *audioDataQuene_get();
+
+    int audio_input_frame_size;
+
+    void setContainsVideo(bool);
+    void setContainsAudio(bool);
+
+    void setVideoFrameRate(int value);
+
+    double getVideoPts();
+    double getAudioPts();
+
+
+//private:
+
+    char filename[128];
+
+    int m_videoFrameRate;
+
+    uint8_t picture_buf[2000*2000*4]; //这个大小只要够存一帧h264就行,这里实际上不需要这么大
+    bool isStop;
+
+    float t, tincr, tincr2;
+    int16_t *samples;
+    uint8_t *audio_outbuf;
+    int audio_outbuf_size;
+
+    AVFrame *picture, *tmp_picture;
+    uint8_t *video_outbuf;
+    int video_outbuf_size;
+
+    double audio_pts, video_pts;
+
+    int mBitRate; //video bitRate
+
+    int WIDTH;
+    int HEIGHT;
+
+    bool m_containsVideo;
+    bool m_containsAudio;
+
+    SDL_mutex *videoMutex;
+    BufferDataNode * videoDataQueneHead;
+    BufferDataNode * videoDataQueneTail;
+
+    SDL_mutex *audioMutex;
+    BufferDataNode * AudioDataQueneHead;
+    BufferDataNode * AudioDataQueneTail;
+
+    BufferDataNode * lastVideoNode; //上一次的帧(帧不足的时候用上一次的帧来补全)
+    int videoBufferCount;
+    int audioBufferCount;
+
+    void open_audio(AVFormatContext *oc, AVStream *st);
+    void close_audio(AVFormatContext *oc, AVStream *st);
+
+    void open_video(AVFormatContext *oc, AVStream *st);
+    void close_video(AVFormatContext *oc, AVStream *st);
+
+    AVStream *add_video_stream(AVFormatContext *oc, AVCodecID codec_id);
+    AVStream *add_audio_stream(AVFormatContext *oc, AVCodecID codec_id);
+
+    bool write_audio_frame(AVFormatContext *oc, AVStream *st);
+    bool write_video_frame(AVFormatContext *oc, AVStream *st, double time);
+
+};
+
+
+
+#endif // SAVEVIDEOFILE_H

+ 192 - 0
src/video/screenrecorder.cpp

@@ -0,0 +1,192 @@
+
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#include "screenrecorder.h"
+
+#include <QDateTime>
+#include <QDebug>
+
+ScreenRecorder::ScreenRecorder()
+{
+    m_videoThread = new GetVideoThread();
+    m_audioThread = new GetVideoThread();
+
+    m_saveVideoFileThread = new SaveVideoFileThread;
+
+    m_videoThread->setSaveVideoFileThread(m_saveVideoFileThread);
+    m_audioThread->setSaveVideoFileThread(m_saveVideoFileThread);
+
+}
+
+
+ScreenRecorder::~ScreenRecorder()
+{
+    delete m_videoThread;
+    delete m_audioThread;
+
+    if (m_saveVideoFileThread)
+    {
+        delete m_saveVideoFileThread;
+    }
+
+}
+
+void ScreenRecorder::setFileName(char *str)
+{
+    if (m_saveVideoFileThread != NULL)
+        m_saveVideoFileThread->setFileName(str);
+}
+
+ErroCode ScreenRecorder::init(QString videoDevName, bool useVideo, QString audioDevName, bool useAudio)
+{
+    m_useVideo = useVideo;
+    m_useAudio = useAudio;
+
+    if (useVideo)
+    {
+        ErroCode code = m_videoThread->init(videoDevName,useVideo,"",false);
+        if (code != SUCCEED)
+        {
+            qDebug()<<"视频初始化失败";
+            return code;
+        }
+    }
+
+    if (useAudio)
+    {
+        ErroCode code = m_audioThread->init("",false,audioDevName,useAudio);
+        if (code != SUCCEED)
+        {
+            qDebug()<<"音频初始化失败";
+            return code;
+        }
+    }
+
+
+    if (m_saveVideoFileThread != NULL)
+    {
+        m_saveVideoFileThread->setContainsVideo(useVideo);
+        m_saveVideoFileThread->setContainsAudio(useAudio);
+    }
+
+    return SUCCEED;
+}
+
+void ScreenRecorder::startRecord()
+{
+    if (m_saveVideoFileThread != NULL)
+        m_saveVideoFileThread->startWrite();
+
+    if (m_useVideo)
+    {
+        m_videoThread->startRecord();
+    }
+
+    if (m_useAudio)
+    {
+        m_audioThread->startRecord();
+    }
+}
+
+void ScreenRecorder::pauseRecord()
+{
+    if (m_useVideo)
+    {
+        m_videoThread->pauseRecord();
+    }
+
+    if (m_useAudio)
+    {
+        m_audioThread->pauseRecord();
+    }
+}
+
+void ScreenRecorder::restoreRecord()
+{
+    if (m_useVideo)
+    {
+        m_videoThread->restoreRecord();
+    }
+
+    if (m_useAudio)
+    {
+        m_audioThread->restoreRecord();
+    }
+}
+
+void ScreenRecorder::stopRecord()
+{    
+    if (m_useVideo)
+    {
+        m_videoThread->stopRecord();
+    }
+
+    if (m_useVideo)
+    {
+        while(m_videoThread->isRunning())
+        {
+            SDL_Delay(10);
+        }
+    }
+
+    if (m_useAudio)
+    {
+        m_audioThread->stopRecord();
+    }
+
+    if (m_useAudio)
+    {
+        while(m_audioThread->isRunning())
+        {
+            SDL_Delay(10);
+        }
+    }
+
+    if (m_saveVideoFileThread != NULL)
+        m_saveVideoFileThread->stopWrite();
+
+}
+
+void ScreenRecorder::setPicRange(int x,int y,int w,int h)
+{
+    m_videoThread->setPicRange(x,y,w,h);
+
+    if (m_saveVideoFileThread != NULL)
+    m_saveVideoFileThread->setWidth(w, h);
+
+}
+
+void ScreenRecorder::setVideoFrameRate(int value)
+{
+    if (m_saveVideoFileThread != NULL)
+        m_saveVideoFileThread->setVideoFrameRate(value);
+}
+
+double ScreenRecorder::getVideoPts()
+{
+    if (m_saveVideoFileThread != NULL)
+    {
+        return m_saveVideoFileThread->getVideoPts();
+    }
+    else
+    {
+        return 0;
+    }
+
+}
+
+double ScreenRecorder::getAudioPts()
+{
+    if (m_saveVideoFileThread != NULL)
+    {
+        return m_saveVideoFileThread->getAudioPts();
+    }
+    else
+    {
+        return 0;
+    }
+}

+ 53 - 0
src/video/screenrecorder.h

@@ -0,0 +1,53 @@
+
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef SCREENRECORDER_H
+#define SCREENRECORDER_H
+
+#include <QThread>
+
+#include "getvideothread.h"
+#include "savevideofile.h"
+
+class ScreenRecorder : public QObject
+{
+    Q_OBJECT
+
+public:
+    explicit ScreenRecorder();
+    ~ScreenRecorder();
+
+    void setFileName(char* str);
+
+    ErroCode init(QString videoDevName,bool useVideo,QString audioDevName,bool useAudio);
+
+    void startRecord();
+    void pauseRecord();
+    void restoreRecord();
+    void stopRecord();
+
+    void setPicRange(int x,int y,int w,int h); //设置录制屏幕的区域
+    void setVideoFrameRate(int value);
+
+    double getVideoPts();
+    double getAudioPts();
+
+private:
+    SaveVideoFileThread * m_saveVideoFileThread; //保存成视频文件的线程
+
+    /// 把视频和音频放到一起获取
+    /// avformat_free_context释放的时候会奔溃
+    /// 无奈,只能把他们放到2个线程中执行
+    GetVideoThread *m_videoThread; //获取视频的线程
+    GetVideoThread *m_audioThread; //获取音频的线程
+
+    bool m_useVideo;
+    bool m_useAudio;
+
+};
+
+#endif // SCREENRECORDER_H

+ 122 - 0
src/widget/myscrollarea.cpp

@@ -0,0 +1,122 @@
+#include "myscrollarea.h"
+#include "ui_myscrollarea.h"
+
+#include <QDebug>
+#include <QResizeEvent>
+
+MyScrollArea::MyScrollArea(QWidget *parent) :
+    QWidget(parent),
+    ui(new Ui::MyScrollArea)
+{
+    ui->setupUi(this);
+
+    popMenu = new QMenu;
+
+    popOutAction = new QAction("窗口弹出",this);
+    popOutAction->setCheckable(true);
+    popMenu->addAction(popOutAction);
+//    popMenu->addSeparator();       //添加分离器
+
+    connect(popOutAction,SIGNAL(triggered(bool)),this,SLOT(slotActionTriggered(bool)));
+
+    ui->widget_toolBtn->hide();
+    ui->widget_video->setUpdatesEnabled(false);
+
+    connect(ui->pushButton_popout,SIGNAL(clicked()),this,SLOT(slotBtnClick()));
+
+}
+
+MyScrollArea::~MyScrollArea()
+{
+    delete ui;
+}
+
+void MyScrollArea::setLoading(bool value)
+{
+    ui->widget_contain->setVisible(!value);
+    ui->label_loading->setVisible(value);
+}
+
+void MyScrollArea::resizeEvent(QResizeEvent* event)
+{
+//    ui->widget_contain->resize(event->size());
+//    ui->label_loading->setMinimumSize(event->size());
+
+    qDebug()<<"void MyScrollArea::resizeEvent(QResizeEvent* event)"<<event->size();
+
+    if (this->isVisible())
+    {
+        emit resizeSignal(event->size());
+    }
+
+
+}
+
+void MyScrollArea::enterEvent(QEvent*)
+{
+//    if (this->parent() == NULL) return;
+//    ui->widget_toolBtn->show();
+}
+
+void MyScrollArea::leaveEvent(QEvent*)
+{
+//    ui->widget_toolBtn->hide();
+}
+
+QWidget * MyScrollArea::getVideoContainWidget()
+{
+    return ui->widget_video;
+}
+
+void MyScrollArea::mousePressEvent(QMouseEvent*event)
+{
+    if (event->button() == Qt::RightButton)
+    {
+//        popMenu->exec(QCursor::pos());
+    }
+}
+
+void MyScrollArea::closeEvent(QCloseEvent *event)
+{
+    popOutAction->setChecked(false);
+    emit btnClick(popIn);
+
+    event->ignore();
+
+}
+
+void MyScrollArea::setVideoWidth(int width,int height)
+{
+    ui->widget_contain->setMinimumSize(width,height);
+    ui->widget_contain->setMaximumSize(width,height);
+
+//    ui->widget_video->resize(width,height);
+
+//    ui->widget_video->setMinimumSize(width,height);
+//    ui->widget_video->setMaximumSize(width,height);
+}
+
+
+void MyScrollArea::slotBtnClick()
+{
+    if (QObject::sender() == ui->pushButton_popout)
+    {
+        ui->widget_toolBtn->hide();
+        emit btnClick(popOut);
+    }
+}
+
+void MyScrollArea::slotActionTriggered(bool isTriggered)
+{
+    if (QObject::sender() == popOutAction)
+    {
+        if (isTriggered)
+        {
+            emit btnClick(popOut);
+        }
+        else
+        {
+            emit btnClick(popIn);
+        }
+    }
+}

+ 53 - 0
src/widget/myscrollarea.h

@@ -0,0 +1,53 @@
+#ifndef MYSCROLLAREA_H
+#define MYSCROLLAREA_H
+
+#include <QMenu>
+#include <QWidget>
+
+namespace Ui {
+class MyScrollArea;
+}
+
+class MyScrollArea : public QWidget
+{
+    Q_OBJECT
+
+public:
+
+    enum BtnClickType
+    {
+        popOut = 0,
+        popIn,
+    };
+
+    explicit MyScrollArea(QWidget *parent = 0);
+    ~MyScrollArea();
+
+    void setLoading(bool);
+
+    QWidget * getVideoContainWidget();
+    void setVideoWidth(int width,int height);
+
+signals:
+    void btnClick(MyScrollArea::BtnClickType);
+    void resizeSignal(QSize size);  //回显视频的窗口改变了大小
+protected:
+    void resizeEvent(QResizeEvent* event);
+    void enterEvent(QEvent*);
+    void leaveEvent(QEvent*);
+    void mousePressEvent(QMouseEvent*);
+    void closeEvent(QCloseEvent *);
+
+private:
+    Ui::MyScrollArea *ui;
+
+    QMenu*popMenu;
+    QAction *popOutAction;
+
+private slots:
+    void slotBtnClick();
+    void slotActionTriggered(bool);
+
+};
+
+#endif // MYSCROLLAREA_H

+ 195 - 0
src/widget/myscrollarea.ui

@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MyScrollArea</class>
+ <widget class="QWidget" name="MyScrollArea">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>1167</width>
+    <height>785</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <property name="styleSheet">
+   <string notr="true">QWidget#MyScrollArea{background-color: rgba(0, 0, 0,0);}</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <property name="margin">
+    <number>0</number>
+   </property>
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <property name="spacing">
+      <number>0</number>
+     </property>
+     <property name="topMargin">
+      <number>0</number>
+     </property>
+     <item>
+      <widget class="QWidget" name="widget_toolBtn" native="true">
+       <property name="minimumSize">
+        <size>
+         <width>0</width>
+         <height>32</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>16777215</width>
+         <height>32</height>
+        </size>
+       </property>
+       <property name="styleSheet">
+        <string notr="true">QWidget#widget_toolBtn{background-color: transparent}</string>
+       </property>
+       <widget class="QPushButton" name="pushButton_popout">
+        <property name="geometry">
+         <rect>
+          <x>20</x>
+          <y>5</y>
+          <width>41</width>
+          <height>23</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>弹出</string>
+        </property>
+       </widget>
+      </widget>
+     </item>
+     <item>
+      <widget class="QScrollArea" name="scrollArea">
+       <property name="widgetResizable">
+        <bool>true</bool>
+       </property>
+       <widget class="QWidget" name="scrollAreaWidgetContents">
+        <property name="geometry">
+         <rect>
+          <x>0</x>
+          <y>0</y>
+          <width>1163</width>
+          <height>749</height>
+         </rect>
+        </property>
+        <property name="styleSheet">
+         <string notr="true">QWidget#scrollAreaWidgetContents{background-color: rgb(0, 0, 0);}</string>
+        </property>
+        <layout class="QVBoxLayout" name="verticalLayout_3">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <property name="margin">
+          <number>0</number>
+         </property>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout">
+           <property name="spacing">
+            <number>0</number>
+           </property>
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+           <item>
+            <spacer name="horizontalSpacer">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QWidget" name="widget_contain" native="true">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="styleSheet">
+              <string notr="true"/>
+             </property>
+             <widget class="QWidget" name="widget_video" native="true">
+              <property name="geometry">
+               <rect>
+                <x>0</x>
+                <y>0</y>
+                <width>2000</width>
+                <height>2000</height>
+               </rect>
+              </property>
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="styleSheet">
+               <string notr="true"/>
+              </property>
+             </widget>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_2">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <widget class="QLabel" name="label_loading">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="styleSheet">
+            <string notr="true">background-color: rgba(0, 0, 0,150);
+color:rgb(255,255,255);
+font-size:25px;</string>
+           </property>
+           <property name="text">
+            <string>正在加载,请稍后...</string>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignCenter</set>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 101 - 0
src/widget/pushpoint.cpp

@@ -0,0 +1,101 @@
+
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#include "pushpoint.h"
+
+PushPoint::PushPoint(QWidget *parent)
+{
+    setParent(parent);
+    setWindowFlags(Qt::FramelessWindowHint);  //使窗口标题栏隐藏
+    setMouseTracking(true);
+    QPixmap pixmap = QPixmap(QSize(POINT_SIZE,POINT_SIZE));  //构建一个QPixmap
+    pixmap.fill(Qt::red);   //用红色填充这个pixmap
+    setPixmap(pixmap);
+    show();
+}
+
+PushPoint::~PushPoint()
+{
+
+}
+
+void PushPoint::setLocation(LocationPoint Loa)
+{
+    location = Loa;
+    setMouseCursor();
+}
+
+void PushPoint::mousePressEvent(QMouseEvent * event)
+{
+    if (event->button() == Qt::LeftButton)
+    {
+         dragPosition=event->globalPos()-frameGeometry().topLeft();
+         event->accept();
+    }
+}
+void PushPoint::mouseMoveEvent(QMouseEvent * event)
+{
+    if (event->buttons() & Qt::LeftButton)
+    {
+        QPoint point;
+        int x;
+        int y;
+        switch (location)
+        {
+        case TopLeft:
+        case BottomRight:
+        case TopRight:
+        case BottomLeft:
+            point = event->globalPos() - dragPosition;
+            break;
+        case TopMid:
+        case BottomMid:
+            y = (event->globalPos() - dragPosition).y();
+            x = pos().x();
+            point = QPoint(x,y);
+            break;
+        case MidLeft:
+        case MidRight:
+            x = (event->globalPos() - dragPosition).x();
+            y = pos().y();
+            point = QPoint(x,y);
+            break;
+        default:
+            point = event->globalPos() - dragPosition;
+            break;
+        }
+        move(point);
+        emit moved(point);
+        event->accept();
+    }
+}
+
+void PushPoint::setMouseCursor()
+{
+    switch (location)
+    {
+    case TopLeft:
+    case BottomRight:
+        setCursor(Qt::SizeFDiagCursor);
+        break;
+    case TopRight:
+    case BottomLeft:
+        setCursor(Qt::SizeBDiagCursor);
+        break;
+    case TopMid:
+    case BottomMid:
+        setCursor(Qt::SizeVerCursor);
+        break;
+    case MidLeft:
+    case MidRight:
+        setCursor(Qt::SizeHorCursor);
+        break;
+    default:
+        setCursor(Qt::ArrowCursor);
+        break;
+    }
+}

+ 56 - 0
src/widget/pushpoint.h

@@ -0,0 +1,56 @@
+
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef PUSHPOINT_H
+#define PUSHPOINT_H
+
+#include <QLabel>
+#include <QMouseEvent>
+
+
+#define POINT_SIZE 7  //改变范围的八个点的大小
+
+
+/***
+ ***改变范围时,显示的边框中的八个红点
+ ***其中的一个点的类
+ ***/
+class PushPoint : public QLabel
+{
+    Q_OBJECT
+    
+public:
+    explicit PushPoint(QWidget *parent = 0);
+    ~PushPoint();
+    enum LocationPoint //此点所在的位置
+    {
+        TopLeft = 0, //左上
+        TopRight= 1, //右上
+        TopMid  = 2, //上中
+        BottomLeft= 3, //左下
+        BottomRight= 4, //右下
+        BottomMid= 5, //下中
+        MidLeft= 6, //左中
+        MidRight= 7 //右中
+    };
+    void setLocation(LocationPoint);
+    LocationPoint locPoint(){return location;}
+protected:
+    void mousePressEvent(QMouseEvent *);
+    void mouseMoveEvent(QMouseEvent *);
+
+signals:
+    void moved(QPoint);
+private:
+    QPoint dragPosition;
+
+    LocationPoint location; //此点的位置
+
+    void setMouseCursor(); //设置鼠标形状
+};
+
+#endif // PUSHPOINT_H

+ 439 - 0
src/widget/selectrect.cpp

@@ -0,0 +1,439 @@
+
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#include "selectrect.h"
+#include <QPainter>
+
+SelectRect::SelectRect(QWidget *parent,Mode m) :
+    QLabel(parent),
+    runningMode(m)
+{
+    setWindowFlags(Qt::FramelessWindowHint|Qt::WindowStaysOnTopHint|Qt::SubWindow);  //使窗口额标题栏隐藏并置顶
+    setAttribute(Qt::WA_TranslucentBackground, true);
+    setStyleSheet("background:transparent;");
+    setMouseTracking(true);
+    showFullScreen();
+    hide();
+
+    m_showBorderLabel = new QLabel(this);
+    m_showBorderLabel->resize(0,0);
+    m_showBorderLabel->hide();
+    m_showBorderLabel->setStyleSheet("background:transparent; \
+                                     border: 1px solid #FF0000; \
+                                     border-style: solid; \
+                                     border-radius:1px;");
+
+    initLocationPoint();
+
+    rate = -1.0;
+}
+
+SelectRect::~SelectRect()
+{
+
+}
+
+void SelectRect::setPointHide()
+{
+    int i;
+    for (i = 0; i < 8; i++)
+    {
+        locPoint[i]->move(-100,-100);
+    }
+
+//    m_showBorderLabel->hide();
+}
+
+void SelectRect::getReadyToSelect()
+{
+    showFullScreen();
+    setCursor(Qt::CrossCursor);
+    QPixmap pix = QPixmap(this->size());
+    pix.fill(QColor(0,0,0,1));
+    setPixmap(pix);
+
+    FINISHED = false;
+
+    int i;
+    for (i = 0; i < 8; i++)
+    {
+        locPoint[i]->move(-100,-100);
+    }
+}
+
+void SelectRect::editRect()
+{
+    showFullScreen();
+    setCursor(Qt::CrossCursor);
+    QPixmap pix = QPixmap(this->size());;
+    pix.fill(QColor(0,0,0,1));
+    setPixmap(pix);
+
+    updateRect();
+}
+
+void SelectRect::mouseDoubleClickEvent(QMouseEvent *event)
+{
+    if (runningMode == ScreenShot)
+    {
+        if (event->button() == Qt::LeftButton)
+        {
+            if (rect.contains(event->pos()))
+            {
+                event->ignore();
+            }
+        }
+    }
+    else if(runningMode == RecordGif)
+    {
+        if (rect.contains(event->pos()))
+        {
+            QPixmap pix = QPixmap(this->size());;
+            pix.fill(QColor(0,0,0,0));
+
+            QPainter painter(&pix);
+            painter.setPen(QPen(Qt::red, 1, Qt::SolidLine, Qt::RoundCap));
+
+            QRect rect = rectangle;
+//            rect.setWidth(rect.width()+1);
+//            rect.setHeight(rect.height()+1);
+            painter.drawRect(rect);
+            setPixmap(pix);
+
+            int i;
+            for (i = 0; i < 8; i++)
+            {
+                locPoint[i]->move(-100,-100);
+            }
+
+            m_showBorderLabel->hide();
+            emit finished(rect);
+        }
+        event->accept();
+    }
+
+}
+
+void SelectRect::mousePressEvent(QMouseEvent * event)
+{
+    if (event->button() == Qt::LeftButton)
+    {
+        RELEASE = true;
+        if (FINISHED == false)
+        {
+            rect.setLeft(event->x());
+            rect.setTop(event->y());
+            event->accept();
+        }
+        else
+        {
+            if (rect.contains(event->globalPos()))
+            {
+                dragPosition = event->globalPos()-frameGeometry().topLeft();
+                zeroPoint = rect.topLeft();
+                DRAG = true;
+            }
+            else
+            {
+                DRAG = false;
+            }
+        }
+
+    }
+    else if (event->button() == Qt::RightButton)
+    {
+        event->ignore();
+    }
+}
+
+//void SelectRect::mouseMoveEvent(QMouseEvent * event)
+//{
+//    RELEASE = false;
+//    if (event->buttons() & Qt::LeftButton)
+//    {
+//        if (FINISHED == false)
+//        {
+//            rect.setWidth(event->x()-rect.x());
+//            rect.setHeight(event->y()-rect.y());
+//            updateRect();
+//        }
+//        else
+//        {
+//            if ( DRAG == true)
+//            {
+//                int width = rect.width();
+//                int height= rect.height();
+
+//                QPoint p =event->globalPos() - dragPosition + zeroPoint;
+//                if(p.x() < 0) p.setX(0);
+//                if(p.y() < 0) p.setY(0);
+//                if((p.x()+width)>this->width()) p.setX(this->width()-width);
+//                if((p.y()+height)>this->height())p.setY(this->height()-height);
+
+//                rect.setTopLeft(p);
+//                rect.setWidth(width);
+//                rect.setHeight(height);
+//                updateRect();
+//            }
+//        }
+//        emit rectChanged(rect);
+//    }
+//    if (FINISHED == true)
+//    {
+//        if (rect.contains(event->pos()))
+//        {
+//            setCursor(Qt::SizeAllCursor);
+//        }
+//        else
+//        {
+//            setCursor(Qt::ArrowCursor);
+//        }
+//    }
+//}
+
+
+void SelectRect::mouseMoveEvent(QMouseEvent * event)
+{
+    RELEASE = false;
+    if (event->buttons() & Qt::LeftButton)
+    {
+        if (FINISHED == false)
+        {
+            if (rate <= 0)
+            {
+                rect.setWidth(event->x()-rect.x());
+                rect.setHeight(event->y()-rect.y());
+            }
+            else
+            {
+                int width = event->x()-rect.x();
+                int height = width * rate;
+                rect.setWidth(width);
+                rect.setHeight(height);
+            }
+
+            updateRect();
+        }
+        else
+        {
+            if ( DRAG == true)
+            {
+                int width = rect.width();
+                int height= rect.height();
+
+                QPoint p = event->globalPos() - dragPosition + zeroPoint;
+                if(p.x() < 0) p.setX(0);
+                if(p.y() < 0) p.setY(0);
+                if((p.x()+width)>this->width()) p.setX(this->width()-width);
+                if((p.y()+height)>this->height())p.setY(this->height()-height);
+
+                rect.setTopLeft(p);
+                rect.setWidth(width);
+                rect.setHeight(height);
+                updateRect();
+            }
+        }
+        emit rectChanged(rect);
+    }
+    if (FINISHED == true)
+    {
+        if (rect.contains(event->pos()))
+        {
+            setCursor(Qt::SizeAllCursor);
+        }
+        else
+        {
+            setCursor(Qt::ArrowCursor);
+        }
+    }
+}
+
+
+void SelectRect::mouseReleaseEvent(QMouseEvent * event)
+{
+    if (event->button() == Qt::LeftButton)
+    {
+        if (FINISHED) return;
+        if (RELEASE) emit releaseWithoutMove();
+        int minWidth = POINT_SIZE*3;
+        if (rect.width() < minWidth || rect.height() < minWidth)
+        { //防止手抖动,不小心选到了一个很小的区域
+            emit releaseWithoutMove();
+        }
+        else
+        {
+            FINISHED = true;
+        }
+    }
+}
+
+void SelectRect::initLocationPoint()
+{
+    int i;
+    for (i = 0; i < 8; i++)
+    {
+        locPoint[i] = new PushPoint(this);
+        locPoint[i]->setLocation((PushPoint::LocationPoint)i);
+        connect(locPoint[i],SIGNAL(moved(QPoint)),this,SLOT(slotPointMoved(QPoint)));
+        locPoint[i]->move(-100,-100);
+    }
+}
+
+
+void SelectRect::layoutLocPoint()
+{
+
+    int start_x = rectangle.x();
+    int start_y = rectangle.y();
+    int P_width = locPoint[0]->width();
+    int p_height= locPoint[0]->height();
+    int x = rectangle.x() + rectangle.width();
+    int y = rectangle.y() + rectangle.height();
+    int half_x = rectangle.x() + (rectangle.width()-P_width) / 2;//一半
+    int half_y = rectangle.y() + (rectangle.height()-p_height) / 2;//一半
+
+    locPoint[PushPoint::TopLeft]->move(start_x,start_y);
+    locPoint[PushPoint::TopRight]->move(x - P_width,start_y);
+    locPoint[PushPoint::TopMid]->move(half_x,start_y);;
+    locPoint[PushPoint::BottomLeft]->move(start_x,y - p_height);
+    locPoint[PushPoint::BottomRight]->move(x - P_width,y - p_height);
+    locPoint[PushPoint::BottomMid]->move(half_x,y - p_height);
+    locPoint[PushPoint::MidLeft]->move(start_x,half_y);
+    locPoint[PushPoint::MidRight]->move(x - P_width,half_y);
+
+    m_showBorderLabel->move(start_x,start_y);
+    m_showBorderLabel->show();
+    m_showBorderLabel->resize(rectangle.width(),rectangle.height());
+
+}
+
+void SelectRect::slotPointMoved(QPoint p)
+{
+    PushPoint * curPoint = (PushPoint*)QObject::sender();
+    int lpoint = curPoint->locPoint();
+    int width = rect.width();
+    int height = rect.height();
+    int x = p.x();
+    int y = p.y();
+    if (x < 0) x=0;
+    if (y < 0) y=0;
+    int minWidth = curPoint->width()*5;
+    int minHeight= curPoint->height()*5;
+    if (lpoint == PushPoint::TopLeft)
+    {
+        int BR_x = rect.x() + width; //右下方点的坐标
+        int BR_y = rect.y() + height; //右下方点的坐标
+        if (x > (BR_x - minWidth))
+            x = BR_x - minWidth;
+        if (y > (BR_y - minHeight))
+            y = BR_y - minHeight;
+
+        curPoint->move(x,y);
+        width = BR_x - x;
+        height= BR_y - y;
+        rect.setX(BR_x - width);
+        rect.setY(BR_y - height);
+    }
+    else if (lpoint == PushPoint::TopRight)
+    {
+        int BL_x = rect.x(); //左下方点的坐标
+        int BL_y = rect.y() + height; //左下方点的坐标
+        if (x < (BL_x + minWidth))
+            x = BL_x + minWidth;
+        if (y > (BL_y - minHeight))
+            y = BL_y - minHeight;
+        curPoint->move(x,y);
+        width = x - BL_x;
+        height = BL_y - y;
+        rect.setY(BL_y - height);
+    }
+    else if (lpoint == PushPoint::TopMid)
+    {
+        int B_y = rect.y() + height; //最下方的纵坐标
+        if (y > (B_y - minHeight))
+            y = B_y - minHeight;
+        curPoint->move(x,y);
+        height = B_y - y;
+        rect.setY(B_y - height);
+    }
+    else if (lpoint == PushPoint::BottomLeft)
+    {
+        int TR_x = rect.x() + width; //右上方点的坐标
+        int TR_y = rect.y(); //右上方点的坐标
+        if (x > (TR_x - minWidth))
+            x = TR_x - minWidth;
+        if (y < (TR_y + minHeight))
+            y = TR_y + minHeight;
+        curPoint->move(x,y);
+        width = TR_x - x;
+        height= y - TR_y;
+        rect.setX(TR_x - width);
+    }
+    else if (lpoint == PushPoint::BottomMid)
+    {
+        if (y < (rect.y() + minHeight))
+            y = rect.y() + minHeight;
+        curPoint->move(x,y);
+        height = y - rect.y();
+    }
+    else if (lpoint == PushPoint::BottomRight)
+    {
+        if (x < (rect.x() + minWidth))
+            x = rect.x() + minWidth;
+        if (y < (rect.y() + minHeight))
+            y = rect.y() + minHeight;
+        curPoint->move(x,y);
+        width = x - rect.x();
+        height= y - rect.y();
+    }
+    else if (lpoint == PushPoint::MidLeft)
+    {
+        int R_x = rect.x() + width; //最右方点的横坐标
+        if (x > (R_x - minWidth))
+            x = R_x - minWidth;
+        curPoint->move(x,y);
+        width = R_x - x;
+        rect.setX(R_x - width);
+    }
+    else if (lpoint == PushPoint::MidRight)
+    {
+        if (x < (rect.x() + minWidth))
+            x = rect.x() + minWidth;
+        curPoint->move(x,y);
+        width = x - rect.x();
+    }
+
+    if (rate > 0)
+    {
+        height = width * rate;
+    }
+
+
+    rect.setWidth(width);
+    rect.setHeight(height);
+
+    updateRect();
+    emit rectChanged(rect);
+}
+
+void SelectRect::updateRect()
+{
+    rectangle.setTopLeft(QPoint(rect.x()-1,rect.y()-1));
+    rectangle.setWidth(rect.width()+1);
+    rectangle.setHeight(rect.height()+1);
+
+    if (rectangle.x() < 0 || rectangle.y() < 0) rectangle = rect;
+
+//    QPixmap pix = QPixmap(this->size());;
+//    pix.fill(QColor(0,0,0,1));
+//    QPainter painter(&pix);
+//    painter.setPen(QPen(Qt::red, 1, Qt::SolidLine, Qt::RoundCap));
+//    painter.drawRect(rectangle);
+//    setPixmap(pix);
+
+    layoutLocPoint();
+}

+ 69 - 0
src/widget/selectrect.h

@@ -0,0 +1,69 @@
+
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef SELECTRECT_H
+#define SELECTRECT_H
+
+#include <QLabel>
+#include <QMouseEvent>
+#include "pushpoint.h"
+
+/*选取截图范围*/
+
+class SelectRect : public QLabel
+{
+    Q_OBJECT
+    
+public:
+    enum Mode //模式
+    {
+        ScreenShot = 0, //截图模式
+        RecordGif = 1 //录屏模式
+    };
+    explicit SelectRect(QWidget *parent = 0,Mode m = ScreenShot);
+    ~SelectRect();
+
+    void setPointHide();
+    void setMode(Mode m){runningMode = m;}
+    void getReadyToSelect();
+    void editRect();
+    void setRate(float value){rate=value;}
+    void setRect(QRect re){rect = re;updateRect(); emit finished(rect);}
+protected:
+    void mouseDoubleClickEvent(QMouseEvent *event);
+    void mousePressEvent(QMouseEvent * event);
+    void mouseMoveEvent(QMouseEvent * event);
+    void mouseReleaseEvent(QMouseEvent * event);
+signals:
+    void rectChanged(QRect); //范围发生改变(提供给截图使用)
+    void releaseWithoutMove();//按下鼠标没移动就放开了(提供给截图使用:选中自动选择的区域)
+    void finished(QRect);    //区域选取完毕(提供给录屏使用)
+private:
+    Mode runningMode;
+
+    QRect rect;
+    QRect rectangle;
+    float rate;
+
+    QPoint dragPosition;
+    QPoint zeroPoint;
+
+    bool FINISHED;
+    bool DRAG;
+    bool RELEASE;
+
+    QLabel *m_showBorderLabel;
+
+    PushPoint *locPoint[8]; //改变边框用的八个点
+    void initLocationPoint();
+    void layoutLocPoint(); //布局显示范围的8个点
+    void updateRect();//更新显示矩形区域
+private slots:
+    void slotPointMoved(QPoint); //点被移动了
+};
+
+#endif // SELECTRECT_H

+ 2 - 2
说明.txt

@@ -1,4 +1,4 @@
-从零开始学习音视频编程技术(二十) 录屏软件开发之录屏生成MP4
+从零开始学习音视频编程技术(二十一) 录屏软件开发之最终完善
 
 
 这是Qt的工程,建议使用Qt Creator 打开
@@ -12,7 +12,7 @@ FFMPEG
 
 
 关于代码的解释 请参考:
-http://blog.yundiantech.com/?log=blog&id=27
+http://blog.yundiantech.com/?log=blog&id=28
 
 
 Qt开发环境的搭建 请参考: