├── 1.jpg ├── 2.wav ├── 3.gif ├── img ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 云会议.vsdx └── architecture.png ├── AudioInput.cpp ├── AudioOutput.cpp ├── screen.h ├── resource.qrc ├── main.cpp ├── partner.h ├── screen.cpp ├── logqueue.h ├── recvsolve.h ├── AudioInput.h ├── README.md ├── AudioOutput.h ├── sendimg.h ├── sendtext.h ├── partner.cpp ├── mytextedit.h ├── recvsolve.cpp ├── .gitignore ├── myvideosurface.h ├── mytcpsocket.h ├── netheader.cpp ├── chatmessage.h ├── logqueue.cpp ├── CloudMeeting.pro ├── myvideosurface.cpp ├── widget.h ├── netheader.h ├── sendimg.cpp ├── sendtext.cpp ├── mytextedit.cpp ├── chatmessage.cpp ├── mytcpsocket.cpp ├── widget.cpp └── widget.ui /1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/1.jpg -------------------------------------------------------------------------------- /2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/2.wav -------------------------------------------------------------------------------- /3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/3.gif -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/img/1.png -------------------------------------------------------------------------------- /img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/img/2.png -------------------------------------------------------------------------------- /img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/img/3.png -------------------------------------------------------------------------------- /img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/img/4.png -------------------------------------------------------------------------------- /img/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/img/5.png -------------------------------------------------------------------------------- /img/云会议.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/img/云会议.vsdx -------------------------------------------------------------------------------- /AudioInput.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/AudioInput.cpp -------------------------------------------------------------------------------- /AudioOutput.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/AudioOutput.cpp -------------------------------------------------------------------------------- /img/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muleimulei/CloudMeeting/HEAD/img/architecture.png -------------------------------------------------------------------------------- /screen.h: -------------------------------------------------------------------------------- 1 | #ifndef SCREEN_H 2 | #define SCREEN_H 3 | 4 | 5 | class Screen 6 | { 7 | public: 8 | static int width; 9 | static int height; 10 | 11 | static void init(); 12 | }; 13 | 14 | #endif // SCREEN_H 15 | -------------------------------------------------------------------------------- /resource.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.jpg 4 | 3.gif 5 | 6 | 7 | 2.wav 8 | 9 | 10 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "widget.h" 3 | #include "screen.h" 4 | #include 5 | int main(int argc, char* argv[]) 6 | { 7 | QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF8")); 8 | QApplication app(argc, argv); 9 | Screen::init(); 10 | 11 | Widget w; 12 | w.show(); 13 | return app.exec(); 14 | } 15 | -------------------------------------------------------------------------------- /partner.h: -------------------------------------------------------------------------------- 1 | #ifndef PARTNER_H 2 | #define PARTNER_H 3 | 4 | #include 5 | 6 | class Partner : public QLabel 7 | { 8 | Q_OBJECT 9 | private: 10 | quint32 ip; 11 | 12 | void mousePressEvent(QMouseEvent *ev) override; 13 | int w; 14 | public: 15 | Partner(QWidget * parent = nullptr, quint32 = 0); 16 | void setpic(QImage img); 17 | signals: 18 | void sendip(quint32); //发送ip 19 | }; 20 | 21 | #endif // PARTNER_H 22 | -------------------------------------------------------------------------------- /screen.cpp: -------------------------------------------------------------------------------- 1 | #include "screen.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int Screen::width = -1; 8 | int Screen::height = -1; 9 | 10 | 11 | void Screen::init() 12 | { 13 | QScreen *s = QGuiApplication::primaryScreen(); 14 | 15 | 16 | Screen::width = s->geometry().width(); 17 | Screen::height = s->geometry().height(); 18 | 19 | // qDebug() << Screen::width << " " << Screen::height; 20 | } 21 | -------------------------------------------------------------------------------- /logqueue.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGQUEUE_H 2 | #define LOGQUEUE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "netheader.h" 8 | 9 | class LogQueue : public QThread 10 | { 11 | private: 12 | void run(); 13 | QMutex m_lock; 14 | bool m_isCanRun; 15 | 16 | QUEUE_DATA log_queue; 17 | FILE *logfile; 18 | public: 19 | explicit LogQueue(QObject *parent = nullptr); 20 | void stopImmediately(); 21 | void pushLog(Log*); 22 | }; 23 | 24 | #endif // LOGQUEUE_H 25 | -------------------------------------------------------------------------------- /recvsolve.h: -------------------------------------------------------------------------------- 1 | #ifndef RECVSOLVE_H 2 | #define RECVSOLVE_H 3 | #include "netheader.h" 4 | #include 5 | #include 6 | /* 7 | * 接收线程 8 | * 从接收队列拿取数据 9 | */ 10 | class RecvSolve : public QThread 11 | { 12 | Q_OBJECT 13 | public: 14 | RecvSolve(QObject *par = NULL); 15 | void run() override; 16 | private: 17 | QMutex m_lock; 18 | bool m_isCanRun; 19 | signals: 20 | void datarecv(MESG *); 21 | public slots: 22 | void stopImmediately(); 23 | }; 24 | 25 | #endif // RECVSOLVE_H 26 | -------------------------------------------------------------------------------- /AudioInput.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | class AudioInput : public QObject 7 | { 8 | Q_OBJECT 9 | private: 10 | QAudioInput *audio; 11 | QIODevice* inputdevice; 12 | char* recvbuf; 13 | public: 14 | AudioInput(QObject *par = 0); 15 | ~AudioInput(); 16 | private slots: 17 | void onreadyRead(); 18 | void handleStateChanged(QAudio::State); 19 | QString errorString(); 20 | void setVolumn(int); 21 | public slots: 22 | void startCollect(); 23 | void stopCollect(); 24 | signals: 25 | void audioinputerror(QString); 26 | }; 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudMeeting 2 | 客户端基于QT,可以传输视频,音频,同时可以传输文本。涉及信号与槽,多线程,socket通信,Tcp/IP包解析. 服务器端地址:https://github.com/muleimulei/CloudMeeting_Server 3 | 4 | ## 初始界面 5 | ![](./img/1.png) 6 | 7 | #### 输入服务器端地址与端口,点击连接按钮就可以连接到服务器, 然后可以点击创建会议按钮或者加入会议按钮。当点击创建会议按钮时,服务器会发送一个空闲的房间号(其实就是进程号)。然后把房间号给别人,别人就可以连接服务器,加入会议 8 | 9 | ![](./img/4.png) 10 | 11 | #### 点击打开视频,就可以开启摄像头,向别人共享你的图像 12 | 13 | ![](./img/3.png) 14 | 15 | #### 点击打开音频,就可以打开输入音频,向别人说话。(默认打开输出音频,别人说的话自己可以听到) 16 | 17 | ![](./img/2.png) 18 | 19 | #### 点击聊天窗口,用户可以输入信息,发往会议里的所有人 20 | 21 | ![](./img/2.png) 22 | 23 | #### 用户也可以@特定用户,特定用户端会发出提示音 24 | 25 | ![](./img/5.png) 26 | 27 | 28 | ## 项目结构图 29 | 30 | ![](./img/architecture.png) -------------------------------------------------------------------------------- /AudioOutput.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | class AudioOutput : public QThread 8 | { 9 | Q_OBJECT 10 | private: 11 | QAudioOutput* audio; 12 | QIODevice* outputdevice; 13 | QMutex device_lock; 14 | 15 | volatile bool is_canRun; 16 | QMutex m_lock; 17 | void run(); 18 | QString errorString(); 19 | public: 20 | AudioOutput(QObject *parent = 0); 21 | ~AudioOutput(); 22 | void stopImmediately(); 23 | void startPlay(); 24 | void stopPlay(); 25 | private slots: 26 | void handleStateChanged(QAudio::State); 27 | void setVolumn(int); 28 | void clearQueue(); 29 | signals: 30 | void audiooutputerror(QString); 31 | void speaker(QString); 32 | }; 33 | -------------------------------------------------------------------------------- /sendimg.h: -------------------------------------------------------------------------------- 1 | #ifndef SENDIMG_H 2 | #define SENDIMG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | class SendImg : public QThread 13 | { 14 | Q_OBJECT 15 | private: 16 | QQueue imgqueue; 17 | QMutex queue_lock; 18 | QWaitCondition queue_waitCond; 19 | void run() override; 20 | QMutex m_lock; 21 | volatile bool m_isCanRun; 22 | public: 23 | SendImg(QObject *par = NULL); 24 | 25 | void pushToQueue(QImage); 26 | public slots: 27 | void ImageCapture(QImage); //捕获到视频帧 28 | void clearImgQueue(); //线程结束时,清空视频帧队列 29 | void stopImmediately(); 30 | }; 31 | 32 | #endif // SENDIMG_H 33 | -------------------------------------------------------------------------------- /sendtext.h: -------------------------------------------------------------------------------- 1 | #ifndef SENDTEXT_H 2 | #define SENDTEXT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "netheader.h" 8 | 9 | struct M 10 | { 11 | QString str; 12 | MSG_TYPE type; 13 | 14 | M(QString s, MSG_TYPE e) 15 | { 16 | str = s; 17 | type = e; 18 | } 19 | }; 20 | 21 | //发送文本数据 22 | class SendText : public QThread 23 | { 24 | Q_OBJECT 25 | private: 26 | QQueue textqueue; 27 | QMutex textqueue_lock; //队列锁 28 | QWaitCondition queue_waitCond; 29 | void run() override; 30 | QMutex m_lock; 31 | bool m_isCanRun; 32 | public: 33 | SendText(QObject *par = NULL); 34 | ~SendText(); 35 | public slots: 36 | void push_Text(MSG_TYPE, QString str = ""); 37 | void stopImmediately(); 38 | }; 39 | 40 | #endif // SENDTEXT_H 41 | -------------------------------------------------------------------------------- /partner.cpp: -------------------------------------------------------------------------------- 1 | #include "partner.h" 2 | #include 3 | #include 4 | #include 5 | Partner::Partner(QWidget *parent, quint32 p):QLabel(parent) 6 | { 7 | // qDebug() <<"dsaf" << this->parent(); 8 | ip = p; 9 | 10 | this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); 11 | w = ((QWidget *)this->parent())->size().width(); 12 | this->setPixmap(QPixmap::fromImage(QImage(":/myImage/1.jpg").scaled(w-10, w-10))); 13 | this->setFrameShape(QFrame::Box); 14 | 15 | this->setStyleSheet("border-width: 1px; border-style: solid; border-color:rgba(0, 0 , 255, 0.7)"); 16 | // this->setToolTipDuration(5); 17 | 18 | this->setToolTip(QHostAddress(ip).toString()); 19 | } 20 | 21 | 22 | void Partner::mousePressEvent(QMouseEvent *) 23 | { 24 | emit sendip(ip); 25 | } 26 | void Partner::setpic(QImage img) 27 | { 28 | this->setPixmap(QPixmap::fromImage(img.scaled(w-10, w-10))); 29 | } 30 | -------------------------------------------------------------------------------- /mytextedit.h: -------------------------------------------------------------------------------- 1 | #ifndef MYTEXTEDIT_H 2 | #define MYTEXTEDIT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class Completer: public QCompleter 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit Completer(QWidget *parent= nullptr); 16 | }; 17 | 18 | class MyTextEdit : public QWidget 19 | { 20 | Q_OBJECT 21 | private: 22 | QPlainTextEdit *edit; 23 | Completer *completer; 24 | QVector > ipspan; 25 | public: 26 | explicit MyTextEdit(QWidget *parent = nullptr); 27 | QString toPlainText(); 28 | void setPlainText(QString); 29 | void setPlaceholderText(QString); 30 | void setCompleter(QStringList ); 31 | private: 32 | QString textUnderCursor(); 33 | bool eventFilter(QObject *, QEvent *); 34 | 35 | private slots: 36 | void changeCompletion(QString); 37 | public slots: 38 | 39 | void complete(); 40 | signals: 41 | }; 42 | 43 | #endif // MYTEXTEDIT_H 44 | -------------------------------------------------------------------------------- /recvsolve.cpp: -------------------------------------------------------------------------------- 1 | #include "recvsolve.h" 2 | #include 3 | #include 4 | #include 5 | extern QUEUE_DATA queue_recv; 6 | 7 | void RecvSolve::stopImmediately() 8 | { 9 | QMutexLocker locker(&m_lock); 10 | m_isCanRun = false; 11 | } 12 | 13 | RecvSolve::RecvSolve(QObject *par):QThread(par) 14 | { 15 | qRegisterMetaType(); 16 | m_isCanRun = true; 17 | } 18 | 19 | void RecvSolve::run() 20 | { 21 | WRITE_LOG("start solving data thread: 0x%p", QThread::currentThreadId()); 22 | for(;;) 23 | { 24 | { 25 | QMutexLocker locker(&m_lock); 26 | if (m_isCanRun == false) 27 | { 28 | WRITE_LOG("stop solving data thread: 0x%p", QThread::currentThreadId()); 29 | return; 30 | } 31 | } 32 | MESG * msg = queue_recv.pop_msg(); 33 | if(msg == NULL) continue; 34 | /*else free(msg); 35 | qDebug() << "取出队列:" << msg->msg_type;*/ 36 | emit datarecv(msg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | -------------------------------------------------------------------------------- /myvideosurface.h: -------------------------------------------------------------------------------- 1 | #ifndef MYVIDEOSURFACE_H 2 | #define MYVIDEOSURFACE_H 3 | 4 | #include 5 | 6 | 7 | class MyVideoSurface : public QAbstractVideoSurface 8 | { 9 | Q_OBJECT 10 | public: 11 | MyVideoSurface(QObject *parent = 0); 12 | 13 | //支持的像素格式 14 | QList supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const override; 15 | 16 | //检测视频流的格式是否合法,返回bool 17 | bool isFormatSupported(const QVideoSurfaceFormat &format) const override; 18 | bool start(const QVideoSurfaceFormat &format) override; 19 | bool present(const QVideoFrame &frame) override; 20 | QRect videoRect() const; 21 | void updateVideoRect(); 22 | void paint(QPainter *painter); 23 | 24 | //private: 25 | // QWidget * widget_; 26 | // QImage::Format imageFormat_; 27 | // QRect targetRect_; 28 | // QSize imageSize_; 29 | // QVideoFrame currentFrame_; 30 | 31 | signals: 32 | void frameAvailable(QVideoFrame); 33 | 34 | }; 35 | 36 | #endif // MYVIDEOSURFACE_H 37 | -------------------------------------------------------------------------------- /mytcpsocket.h: -------------------------------------------------------------------------------- 1 | #ifndef MYTCPSOCKET_H 2 | #define MYTCPSOCKET_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "netheader.h" 8 | #ifndef MB 9 | #define MB (1024 * 1024) 10 | #endif 11 | 12 | typedef unsigned char uchar; 13 | 14 | 15 | class MyTcpSocket: public QThread 16 | { 17 | Q_OBJECT 18 | public: 19 | ~MyTcpSocket(); 20 | MyTcpSocket(QObject *par=NULL); 21 | bool connectToServer(QString, QString, QIODevice::OpenModeFlag); 22 | QString errorString(); 23 | void disconnectFromHost(); 24 | quint32 getlocalip(); 25 | private: 26 | void run() override; 27 | qint64 readn(char *, quint64, int); 28 | QTcpSocket *_socktcp; 29 | QThread *_sockThread; 30 | uchar *sendbuf; 31 | uchar* recvbuf; 32 | quint64 hasrecvive; 33 | 34 | QMutex m_lock; 35 | volatile bool m_isCanRun; 36 | private slots: 37 | bool connectServer(QString, QString, QIODevice::OpenModeFlag); 38 | void sendData(MESG *); 39 | void closeSocket(); 40 | 41 | public slots: 42 | void recvFromSocket(); 43 | void stopImmediately(); 44 | void errorDetect(QAbstractSocket::SocketError error); 45 | signals: 46 | void socketerror(QAbstractSocket::SocketError); 47 | void sendTextOver(); 48 | }; 49 | 50 | #endif // MYTCPSOCKET_H 51 | -------------------------------------------------------------------------------- /netheader.cpp: -------------------------------------------------------------------------------- 1 | #include "netheader.h" 2 | #include "logqueue.h" 3 | #include 4 | #include 5 | 6 | QUEUE_DATA queue_send; //�ı�����Ƶ���Ͷ��� 7 | QUEUE_DATA queue_recv; //���ն��� 8 | QUEUE_DATA audio_recv; //��Ƶ���ն��� 9 | 10 | LogQueue *logqueue = nullptr; 11 | 12 | void log_print(const char *filename, const char *funcname, int line, const char *fmt, ...) 13 | { 14 | Log *log = (Log *) malloc(sizeof (Log)); 15 | if(log == nullptr) 16 | { 17 | qDebug() << "malloc log fail"; 18 | } 19 | else 20 | { 21 | memset(log, 0, sizeof (Log)); 22 | log->ptr = (char *) malloc(1 * KB); 23 | if(log->ptr == nullptr) 24 | { 25 | free(log); 26 | qDebug() << "malloc log.ptr fail"; 27 | return; 28 | } 29 | else 30 | { 31 | memset(log->ptr, 0, 1 * KB); 32 | time_t t = time(NULL); 33 | int pos = 0; 34 | int m = strftime(log->ptr + pos, KB - 2 - pos, "%F %H:%M:%S ", localtime(&t)); 35 | pos += m; 36 | 37 | m = snprintf(log->ptr + pos, KB - 2 - pos, "%s:%s::%d>>>", filename, funcname, line); 38 | pos += m; 39 | 40 | va_list ap; 41 | va_start(ap, fmt); 42 | m = _vsnprintf(log->ptr + pos, KB - 2 - pos, fmt, ap); 43 | pos += m; 44 | va_end(ap); 45 | strcat_s(log->ptr+pos, KB-pos, "\n"); 46 | log->len = strlen(log->ptr); 47 | logqueue->pushLog(log); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /chatmessage.h: -------------------------------------------------------------------------------- 1 | #ifndef CHATMESSAGE_H 2 | #define CHATMESSAGE_H 3 | 4 | #include 5 | #include 6 | 7 | class ChatMessage:public QWidget 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit ChatMessage(QWidget *parent = nullptr); 12 | 13 | enum User_Type{ 14 | User_System,//系统 15 | User_Me, //自己 16 | User_She, //用户 17 | User_Time, //时间 18 | }; 19 | void setTextSuccess(); 20 | void setText(QString text, QString time, QSize allSize, QString ip = "", User_Type userType = User_Time); 21 | 22 | QSize getRealString(QString src); 23 | QSize fontRect(QString str); 24 | 25 | inline QString text() {return m_msg;} 26 | inline QString time() {return m_time;} 27 | inline User_Type userType() {return m_userType;} 28 | protected: 29 | void paintEvent(QPaintEvent *event); 30 | private: 31 | QString m_msg; 32 | QString m_time; 33 | QString m_curTime; 34 | QString m_ip; 35 | 36 | QSize m_allSize; 37 | User_Type m_userType = User_System; 38 | 39 | int m_kuangWidth; 40 | int m_textWidth; 41 | int m_spaceWid; 42 | int m_lineHeight; 43 | 44 | QRect m_ipLeftRect; 45 | QRect m_ipRightRect; 46 | QRect m_iconLeftRect; 47 | QRect m_iconRightRect; 48 | QRect m_sanjiaoLeftRect; 49 | QRect m_sanjiaoRightRect; 50 | QRect m_kuangLeftRect; 51 | QRect m_kuangRightRect; 52 | QRect m_textLeftRect; 53 | QRect m_textRightRect; 54 | QPixmap m_leftPixmap; 55 | QPixmap m_rightPixmap; 56 | QLabel* m_loading = Q_NULLPTR; 57 | QMovie* m_loadingMovie = Q_NULLPTR; 58 | bool m_isSending = false; 59 | }; 60 | 61 | #endif // CHATMESSAGE_H 62 | -------------------------------------------------------------------------------- /logqueue.cpp: -------------------------------------------------------------------------------- 1 | #include "logqueue.h" 2 | #include 3 | 4 | LogQueue::LogQueue(QObject *parent) : QThread(parent) 5 | { 6 | 7 | } 8 | 9 | void LogQueue::pushLog(Log* log) 10 | { 11 | log_queue.push_msg(log); 12 | } 13 | 14 | void LogQueue::run() 15 | { 16 | m_isCanRun = true; 17 | for(;;) 18 | { 19 | { 20 | QMutexLocker lock(&m_lock); 21 | if(m_isCanRun == false) 22 | { 23 | fclose(logfile); 24 | return; 25 | } 26 | } 27 | Log *log = log_queue.pop_msg(); 28 | if(log == NULL || log->ptr == NULL) continue; 29 | 30 | //----------------write to logfile------------------- 31 | errno_t r = fopen_s(&logfile, "./log.txt", "a"); 32 | if(r != 0) 33 | { 34 | qDebug() << "打开文件失败:" << r; 35 | continue; 36 | } 37 | 38 | 39 | qint64 hastowrite = log->len; 40 | qint64 ret = 0, haswrite = 0; 41 | while ((ret = fwrite( (char*)log->ptr + haswrite, 1 ,hastowrite - haswrite, logfile)) < hastowrite) 42 | { 43 | if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) 44 | { 45 | ret = 0; 46 | } 47 | else 48 | { 49 | qDebug() << "write logfile error"; 50 | break; 51 | } 52 | haswrite += ret; 53 | hastowrite -= ret; 54 | } 55 | 56 | //free 57 | if(log->ptr) free(log->ptr); 58 | if(log) free(log); 59 | 60 | fflush(logfile); 61 | fclose(logfile); 62 | } 63 | } 64 | 65 | void LogQueue::stopImmediately() 66 | { 67 | QMutexLocker lock(&m_lock); 68 | m_isCanRun = false; 69 | } 70 | -------------------------------------------------------------------------------- /CloudMeeting.pro: -------------------------------------------------------------------------------- 1 | QT += core gui multimedia multimediawidgets network 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | 14 | win32-msvc*:QMAKE_CXXFLAGS += /wd"4819" /utf-8 15 | 16 | # You can also make your code fail to compile if it uses deprecated APIs. 17 | # In order to do so, uncomment the following line. 18 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 19 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 20 | 21 | SOURCES += \ 22 | AudioInput.cpp \ 23 | AudioOutput.cpp \ 24 | chatmessage.cpp \ 25 | logqueue.cpp \ 26 | main.cpp \ 27 | mytcpsocket.cpp \ 28 | mytextedit.cpp \ 29 | myvideosurface.cpp \ 30 | netheader.cpp \ 31 | partner.cpp \ 32 | recvsolve.cpp \ 33 | screen.cpp \ 34 | sendimg.cpp \ 35 | sendtext.cpp \ 36 | widget.cpp 37 | 38 | HEADERS += \ 39 | AudioInput.h \ 40 | AudioOutput.h \ 41 | chatmessage.h \ 42 | logqueue.h \ 43 | mytcpsocket.h \ 44 | mytextedit.h \ 45 | myvideosurface.h \ 46 | netheader.h \ 47 | partner.h \ 48 | recvsolve.h \ 49 | screen.h \ 50 | sendimg.h \ 51 | sendtext.h \ 52 | widget.h 53 | 54 | FORMS += \ 55 | widget.ui 56 | 57 | # Default rules for deployment. 58 | qnx: target.path = /tmp/$${TARGET}/bin 59 | else: unix:!android: target.path = /opt/$${TARGET}/bin 60 | !isEmpty(target.path): INSTALLS += target 61 | 62 | RESOURCES += \ 63 | resource.qrc 64 | -------------------------------------------------------------------------------- /myvideosurface.cpp: -------------------------------------------------------------------------------- 1 | #include "myvideosurface.h" 2 | #include 3 | #include 4 | MyVideoSurface::MyVideoSurface(QObject *parent):QAbstractVideoSurface(parent) 5 | { 6 | 7 | } 8 | 9 | QList MyVideoSurface::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const 10 | { 11 | if(handleType == QAbstractVideoBuffer::NoHandle) 12 | { 13 | return QList() << QVideoFrame::Format_RGB32 14 | << QVideoFrame::Format_ARGB32 15 | << QVideoFrame::Format_ARGB32_Premultiplied 16 | << QVideoFrame::Format_RGB565 17 | << QVideoFrame::Format_RGB555; 18 | } 19 | else 20 | { 21 | return QList(); 22 | } 23 | } 24 | 25 | 26 | bool MyVideoSurface::isFormatSupported(const QVideoSurfaceFormat &format) const 27 | { 28 | // imageFormatFromPixelFormat: 返回与视频帧像素格式等效的图像格式 29 | //pixelFormat: 返回视频流中的像素格式 30 | return QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat()) != QImage::Format_Invalid; 31 | } 32 | 33 | //将视频流中像素格式转换成格式对等的图片格式 34 | bool MyVideoSurface::start(const QVideoSurfaceFormat &format) 35 | { 36 | if(isFormatSupported(format) && !format.frameSize().isEmpty()) 37 | { 38 | QAbstractVideoSurface::start(format); 39 | return true; 40 | } 41 | return false; 42 | } 43 | 44 | 45 | //捕获每一帧视频,每一帧都会到present 46 | bool MyVideoSurface::present(const QVideoFrame &frame) 47 | { 48 | if(!frame.isValid()) 49 | { 50 | stop(); 51 | return false; 52 | } 53 | if(frame.isMapped()) 54 | { 55 | emit frameAvailable(frame); 56 | } 57 | else 58 | { 59 | QVideoFrame f(frame); 60 | f.map(QAbstractVideoBuffer::ReadOnly); 61 | emit frameAvailable(f); 62 | } 63 | 64 | return true; 65 | } 66 | -------------------------------------------------------------------------------- /widget.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_H 2 | #define WIDGET_H 3 | 4 | #include "AudioInput.h" 5 | #include 6 | #include 7 | #include 8 | #include "mytcpsocket.h" 9 | #include 10 | #include "sendtext.h" 11 | #include "recvsolve.h" 12 | #include "partner.h" 13 | #include "netheader.h" 14 | #include 15 | #include "AudioOutput.h" 16 | #include "chatmessage.h" 17 | #include 18 | 19 | QT_BEGIN_NAMESPACE 20 | namespace Ui { class Widget; } 21 | QT_END_NAMESPACE 22 | 23 | class QCameraImageCapture; 24 | class MyVideoSurface; 25 | class SendImg; 26 | class QListWidgetItem; 27 | class Widget : public QWidget 28 | { 29 | Q_OBJECT 30 | private: 31 | static QRect pos; 32 | quint32 mainip; //主屏幕显示的IP图像 33 | QCamera *_camera; //摄像头 34 | QCameraImageCapture *_imagecapture; //截屏 35 | bool _createmeet; //是否创建会议 36 | bool _joinmeet; // 加入会议 37 | bool _openCamera; //是否开启摄像头 38 | 39 | MyVideoSurface *_myvideosurface; 40 | 41 | QVideoFrame mainshow; 42 | 43 | SendImg *_sendImg; 44 | QThread *_imgThread; 45 | 46 | RecvSolve *_recvThread; 47 | SendText * _sendText; 48 | QThread *_textThread; 49 | //socket 50 | MyTcpSocket * _mytcpSocket; 51 | void paintEvent(QPaintEvent *event); 52 | 53 | QMap partner; //用于记录房间用户 54 | Partner* addPartner(quint32); 55 | void removePartner(quint32); 56 | void clearPartner(); //退出会议,或者会议结束 57 | void closeImg(quint32); //根据IP重置图像 58 | 59 | void dealMessage(ChatMessage *messageW, QListWidgetItem *item, QString text, QString time, QString ip ,ChatMessage::User_Type type); //用户发送文本 60 | void dealMessageTime(QString curMsgTime); //处理时间 61 | 62 | //音频 63 | AudioInput* _ainput; 64 | QThread* _ainputThread; 65 | AudioOutput* _aoutput; 66 | 67 | QStringList iplist; 68 | 69 | public: 70 | Widget(QWidget *parent = nullptr); 71 | ~Widget(); 72 | 73 | private slots: 74 | void on_createmeetBtn_clicked(); 75 | void on_exitmeetBtn_clicked(); 76 | 77 | void on_openVedio_clicked(); 78 | void on_openAudio_clicked(); 79 | void on_connServer_clicked(); 80 | void cameraError(QCamera::Error); 81 | void audioError(QString); 82 | // void mytcperror(QAbstractSocket::SocketError); 83 | void datasolve(MESG *); 84 | void recvip(quint32); 85 | void cameraImageCapture(QVideoFrame frame); 86 | void on_joinmeetBtn_clicked(); 87 | 88 | void on_horizontalSlider_valueChanged(int value); 89 | void speaks(QString); 90 | 91 | void on_sendmsg_clicked(); 92 | 93 | void textSend(); 94 | 95 | signals: 96 | void pushImg(QImage); 97 | void PushText(MSG_TYPE, QString = ""); 98 | void stopAudio(); 99 | void startAudio(); 100 | void volumnChange(int); 101 | private: 102 | Ui::Widget *ui; 103 | }; 104 | #endif // WIDGET_H 105 | -------------------------------------------------------------------------------- /netheader.h: -------------------------------------------------------------------------------- 1 | #ifndef NETHEADER_H 2 | #define NETHEADER_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #define QUEUE_MAXSIZE 1500 9 | #ifndef MB 10 | #define MB 1024*1024 11 | #endif 12 | 13 | #ifndef KB 14 | #define KB 1024 15 | #endif 16 | 17 | #ifndef WAITSECONDS 18 | #define WAITSECONDS 2 19 | #endif 20 | 21 | #ifndef OPENVIDEO 22 | #define OPENVIDEO "打开视频" 23 | #endif 24 | 25 | #ifndef CLOSEVIDEO 26 | #define CLOSEVIDEO "关闭视频" 27 | #endif 28 | 29 | #ifndef OPENAUDIO 30 | #define OPENAUDIO "打开音频" 31 | #endif 32 | 33 | #ifndef CLOSEAUDIO 34 | #define CLOSEAUDIO "关闭音频" 35 | #endif 36 | 37 | 38 | #ifndef MSG_HEADER 39 | #define MSG_HEADER 11 40 | #endif 41 | 42 | enum MSG_TYPE 43 | { 44 | IMG_SEND = 0, 45 | IMG_RECV, 46 | AUDIO_SEND, 47 | AUDIO_RECV, 48 | TEXT_SEND, 49 | TEXT_RECV, 50 | CREATE_MEETING, 51 | EXIT_MEETING, 52 | JOIN_MEETING, 53 | CLOSE_CAMERA, 54 | 55 | CREATE_MEETING_RESPONSE = 20, 56 | PARTNER_EXIT = 21, 57 | PARTNER_JOIN = 22, 58 | JOIN_MEETING_RESPONSE = 23, 59 | PARTNER_JOIN2 = 24, 60 | RemoteHostClosedError = 40, 61 | OtherNetError = 41 62 | }; 63 | Q_DECLARE_METATYPE(MSG_TYPE); 64 | 65 | struct MESG //消息结构体 66 | { 67 | MSG_TYPE msg_type; 68 | uchar* data; 69 | long len; 70 | quint32 ip; 71 | }; 72 | Q_DECLARE_METATYPE(MESG *); 73 | 74 | //------------------------------- 75 | 76 | template 77 | struct QUEUE_DATA //消息队列 78 | { 79 | private: 80 | QMutex send_queueLock; 81 | QWaitCondition send_queueCond; 82 | QQueue send_queue; 83 | public: 84 | void push_msg(T* msg) 85 | { 86 | send_queueLock.lock(); 87 | while(send_queue.size() > QUEUE_MAXSIZE) 88 | { 89 | send_queueCond.wait(&send_queueLock); 90 | } 91 | send_queue.push_back(msg); 92 | send_queueLock.unlock(); 93 | send_queueCond.wakeOne(); 94 | } 95 | 96 | T* pop_msg() 97 | { 98 | send_queueLock.lock(); 99 | while(send_queue.size() == 0) 100 | { 101 | bool f = send_queueCond.wait(&send_queueLock, WAITSECONDS * 1000); 102 | if(f == false) 103 | { 104 | send_queueLock.unlock(); 105 | return NULL; 106 | } 107 | } 108 | T* send = send_queue.front(); 109 | send_queue.pop_front(); 110 | send_queueLock.unlock(); 111 | send_queueCond.wakeOne(); 112 | return send; 113 | } 114 | 115 | void clear() 116 | { 117 | send_queueLock.lock(); 118 | send_queue.clear(); 119 | send_queueLock.unlock(); 120 | } 121 | }; 122 | 123 | struct Log 124 | { 125 | char *ptr; 126 | uint len; 127 | }; 128 | 129 | 130 | 131 | void log_print(const char *, const char *, int, const char *, ...); 132 | #define WRITE_LOG(LOGTEXT, ...) do{ \ 133 | log_print(__FILE__, __FUNCTION__, __LINE__, LOGTEXT, ##__VA_ARGS__);\ 134 | }while(0); 135 | 136 | 137 | 138 | #endif // NETHEADER_H 139 | -------------------------------------------------------------------------------- /sendimg.cpp: -------------------------------------------------------------------------------- 1 | #include "sendimg.h" 2 | #include "netheader.h" 3 | #include 4 | #include 5 | #include 6 | 7 | extern QUEUE_DATA queue_send; 8 | 9 | SendImg::SendImg(QObject *par):QThread(par) 10 | { 11 | 12 | } 13 | 14 | //消费线程 15 | void SendImg::run() 16 | { 17 | WRITE_LOG("start sending picture thread: 0x%p", QThread::currentThreadId()); 18 | m_isCanRun = true; 19 | for(;;) 20 | { 21 | queue_lock.lock(); //加锁 22 | 23 | while(imgqueue.size() == 0) 24 | { 25 | //qDebug() << this << QThread::currentThreadId(); 26 | bool f = queue_waitCond.wait(&queue_lock, WAITSECONDS * 1000); 27 | if (f == false) //timeout 28 | { 29 | QMutexLocker locker(&m_lock); 30 | if (m_isCanRun == false) 31 | { 32 | queue_lock.unlock(); 33 | WRITE_LOG("stop sending picture thread: 0x%p", QThread::currentThreadId()); 34 | return; 35 | } 36 | } 37 | } 38 | 39 | QByteArray img = imgqueue.front(); 40 | // qDebug() << "取出队列:" << QThread::currentThreadId(); 41 | imgqueue.pop_front(); 42 | queue_lock.unlock();//解锁 43 | queue_waitCond.wakeOne(); //唤醒添加线程 44 | 45 | 46 | //构造消息体 47 | MESG* imgsend = (MESG*)malloc(sizeof(MESG)); 48 | if (imgsend == NULL) 49 | { 50 | WRITE_LOG("malloc error"); 51 | qDebug() << "malloc imgsend fail"; 52 | } 53 | else 54 | { 55 | memset(imgsend, 0, sizeof(MESG)); 56 | imgsend->msg_type = IMG_SEND; 57 | imgsend->len = img.size(); 58 | qDebug() << "img size :" << img.size(); 59 | imgsend->data = (uchar*)malloc(imgsend->len); 60 | if (imgsend->data == nullptr) 61 | { 62 | free(imgsend); 63 | WRITE_LOG("malloc error"); 64 | qDebug() << "send img error"; 65 | continue; 66 | } 67 | else 68 | { 69 | memset(imgsend->data, 0, imgsend->len); 70 | memcpy_s(imgsend->data, imgsend->len, img.data(), img.size()); 71 | //加入发送队列 72 | queue_send.push_msg(imgsend); 73 | } 74 | } 75 | } 76 | } 77 | 78 | //添加线程 79 | void SendImg::pushToQueue(QImage img) 80 | { 81 | //压缩 82 | QByteArray byte; 83 | QBuffer buf(&byte); 84 | buf.open(QIODevice::WriteOnly); 85 | img.save(&buf, "JPEG"); 86 | QByteArray ss = qCompress(byte); 87 | QByteArray vv = ss.toBase64(); 88 | // qDebug() << "加入队列:" << QThread::currentThreadId(); 89 | queue_lock.lock(); 90 | while(imgqueue.size() > QUEUE_MAXSIZE) 91 | { 92 | queue_waitCond.wait(&queue_lock); 93 | } 94 | imgqueue.push_back(vv); 95 | queue_lock.unlock(); 96 | queue_waitCond.wakeOne(); 97 | } 98 | 99 | void SendImg::ImageCapture(QImage img) 100 | { 101 | pushToQueue(img); 102 | } 103 | 104 | void SendImg::clearImgQueue() 105 | { 106 | qDebug() << "清空视频队列"; 107 | QMutexLocker locker(&queue_lock); 108 | imgqueue.clear(); 109 | } 110 | 111 | 112 | void SendImg::stopImmediately() 113 | { 114 | QMutexLocker locker(&m_lock); 115 | m_isCanRun = false; 116 | } 117 | -------------------------------------------------------------------------------- /sendtext.cpp: -------------------------------------------------------------------------------- 1 | #include "sendtext.h" 2 | #include 3 | 4 | extern QUEUE_DATA queue_send; 5 | #ifndef WAITSECONDS 6 | #define WAITSECONDS 2 7 | #endif 8 | SendText::SendText(QObject *par):QThread(par) 9 | { 10 | 11 | } 12 | 13 | SendText::~SendText() 14 | { 15 | 16 | } 17 | 18 | void SendText::push_Text(MSG_TYPE msgType, QString str) 19 | { 20 | textqueue_lock.lock(); 21 | while(textqueue.size() > QUEUE_MAXSIZE) 22 | { 23 | queue_waitCond.wait(&textqueue_lock); 24 | } 25 | textqueue.push_back(M(str, msgType)); 26 | 27 | textqueue_lock.unlock(); 28 | queue_waitCond.wakeOne(); 29 | } 30 | 31 | 32 | void SendText::run() 33 | { 34 | m_isCanRun = true; 35 | WRITE_LOG("start sending text thread: 0x%p", QThread::currentThreadId()); 36 | for(;;) 37 | { 38 | textqueue_lock.lock(); //加锁 39 | while(textqueue.size() == 0) 40 | { 41 | bool f = queue_waitCond.wait(&textqueue_lock, WAITSECONDS * 1000); 42 | if(f == false) //timeout 43 | { 44 | QMutexLocker locker(&m_lock); 45 | if(m_isCanRun == false) 46 | { 47 | textqueue_lock.unlock(); 48 | WRITE_LOG("stop sending text thread: 0x%p", QThread::currentThreadId()); 49 | return; 50 | } 51 | } 52 | } 53 | 54 | M text = textqueue.front(); 55 | 56 | // qDebug() << "取出队列:" << QThread::currentThreadId(); 57 | 58 | textqueue.pop_front(); 59 | textqueue_lock.unlock();//解锁 60 | queue_waitCond.wakeOne(); //唤醒添加线程 61 | 62 | //构造消息体 63 | MESG* send = (MESG*)malloc(sizeof(MESG)); 64 | if (send == NULL) 65 | { 66 | WRITE_LOG("malloc error"); 67 | qDebug() << __FILE__ <<__LINE__ << "malloc fail"; 68 | continue; 69 | } 70 | else 71 | { 72 | memset(send, 0, sizeof(MESG)); 73 | 74 | if (text.type == CREATE_MEETING || text.type == CLOSE_CAMERA) 75 | { 76 | send->len = 0; 77 | send->data = NULL; 78 | send->msg_type = text.type; 79 | queue_send.push_msg(send); 80 | } 81 | else if (text.type == JOIN_MEETING) 82 | { 83 | send->msg_type = JOIN_MEETING; 84 | send->len = 4; //房间号占4个字节 85 | send->data = (uchar*)malloc(send->len + 10); 86 | 87 | if (send->data == NULL) 88 | { 89 | WRITE_LOG("malloc error"); 90 | qDebug() << __FILE__ << __LINE__ << "malloc fail"; 91 | free(send); 92 | continue; 93 | } 94 | else 95 | { 96 | memset(send->data, 0, send->len + 10); 97 | quint32 roomno = text.str.toUInt(); 98 | memcpy(send->data, &roomno, sizeof(roomno)); 99 | //加入发送队列 100 | 101 | queue_send.push_msg(send); 102 | } 103 | } 104 | else if(text.type == TEXT_SEND) 105 | { 106 | send->msg_type = TEXT_SEND; 107 | QByteArray data = qCompress(QByteArray::fromStdString(text.str.toStdString())); //压缩 108 | send->len = data.size(); 109 | send->data = (uchar *) malloc(send->len); 110 | if(send->data == NULL) 111 | { 112 | WRITE_LOG("malloc error"); 113 | qDebug() << __FILE__ << __LINE__ << "malloc error"; 114 | free(send); 115 | continue; 116 | } 117 | else 118 | { 119 | memset(send->data, 0, send->len); 120 | memcpy_s(send->data, send->len, data.data(), data.size()); 121 | queue_send.push_msg(send); 122 | } 123 | } 124 | } 125 | } 126 | } 127 | void SendText::stopImmediately() 128 | { 129 | QMutexLocker locker(&m_lock); 130 | m_isCanRun = false; 131 | } 132 | -------------------------------------------------------------------------------- /mytextedit.cpp: -------------------------------------------------------------------------------- 1 | #include "mytextedit.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | Completer::Completer(QWidget *parent): QCompleter(parent) 9 | { 10 | 11 | } 12 | 13 | MyTextEdit::MyTextEdit(QWidget *parent): QWidget(parent) 14 | { 15 | QVBoxLayout *layout = new QVBoxLayout(this); 16 | layout->setContentsMargins(0, 0, 0, 0); 17 | edit = new QPlainTextEdit(); 18 | edit->setPlaceholderText(QString::fromUtf8("输入@可以向对应的IP发送数据")); 19 | layout->addWidget(edit); 20 | completer = nullptr; 21 | connect(edit, SIGNAL(textChanged()), this, SLOT(complete())); 22 | edit->installEventFilter(this); 23 | } 24 | 25 | QString MyTextEdit::textUnderCursor() 26 | { 27 | QTextCursor tc = edit->textCursor(); 28 | tc.select(QTextCursor::WordUnderCursor); 29 | return tc.selectedText(); 30 | } 31 | 32 | void MyTextEdit::complete() 33 | { 34 | if(edit->toPlainText().size() == 0 || completer == nullptr) return; 35 | QChar tail = edit->toPlainText().at(edit->toPlainText().size()-1); 36 | if(tail == '@') 37 | { 38 | completer->setCompletionPrefix(tail); 39 | QAbstractItemView *view = completer->popup(); 40 | view->setCurrentIndex(completer->completionModel()->index(0, 0)); 41 | QRect cr = edit->cursorRect(); 42 | QScrollBar *bar = completer->popup()->verticalScrollBar(); 43 | cr.setWidth(completer->popup()->sizeHintForColumn(0) + bar->sizeHint().width()); 44 | completer->complete(cr); 45 | } 46 | } 47 | 48 | void MyTextEdit::changeCompletion(QString text) 49 | { 50 | QTextCursor tc = edit->textCursor(); 51 | int len = text.size() - completer->completionPrefix().size(); 52 | tc.movePosition(QTextCursor::EndOfWord); 53 | tc.insertText(text.right(len)); 54 | edit->setTextCursor(tc); 55 | completer->popup()->hide(); 56 | 57 | QString str = edit->toPlainText(); 58 | int pos = str.size() - 1; 59 | while(str.at(pos) != '@') pos--; 60 | 61 | tc.clearSelection(); 62 | tc.setPosition(pos, QTextCursor::MoveAnchor); 63 | tc.setPosition(str.size(), QTextCursor::KeepAnchor); 64 | // tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, str.size() - pos); 65 | 66 | QTextCharFormat fmt = tc.charFormat(); 67 | QTextCharFormat fmt_back = fmt; 68 | fmt.setForeground(QBrush(Qt::white)); 69 | fmt.setBackground(QBrush(QColor(0, 160, 233))); 70 | tc.setCharFormat(fmt); 71 | tc.clearSelection(); 72 | tc.setCharFormat(fmt_back); 73 | 74 | tc.insertText(" "); 75 | edit->setTextCursor(tc); 76 | 77 | ipspan.push_back(QPair(pos, str.size()+1)); 78 | 79 | } 80 | 81 | QString MyTextEdit::toPlainText() 82 | { 83 | return edit->toPlainText(); 84 | } 85 | 86 | void MyTextEdit::setPlainText(QString str) 87 | { 88 | edit->setPlainText(str); 89 | } 90 | 91 | void MyTextEdit::setPlaceholderText(QString str) 92 | { 93 | edit->setPlaceholderText(str); 94 | } 95 | 96 | void MyTextEdit::setCompleter(QStringList stringlist) 97 | { 98 | if(completer == nullptr) 99 | { 100 | completer = new Completer(this); 101 | completer->setWidget(this); 102 | completer->setCompletionMode(QCompleter::PopupCompletion); 103 | completer->setCaseSensitivity(Qt::CaseInsensitive); 104 | connect(completer, SIGNAL(activated(QString)), this, SLOT(changeCompletion(QString))); 105 | } 106 | else 107 | { 108 | delete completer->model(); 109 | } 110 | QStringListModel * model = new QStringListModel(stringlist, this); 111 | completer->setModel(model); 112 | } 113 | 114 | bool MyTextEdit::eventFilter(QObject *obj, QEvent *event) 115 | { 116 | if(obj == edit) 117 | { 118 | if(event->type() == QEvent::KeyPress) 119 | { 120 | QKeyEvent *keyevent = static_cast(event); 121 | QTextCursor tc = edit->textCursor(); 122 | int p = tc.position(); 123 | int i; 124 | for(i = 0; i < ipspan.size(); i++) 125 | { 126 | if( (keyevent->key() == Qt::Key_Backspace && p > ipspan[i].first && p <= ipspan[i].second ) || (keyevent->key() == Qt::Key_Delete && p >= ipspan[i].first && p < ipspan[i].second) ) 127 | { 128 | tc.setPosition(ipspan[i].first, QTextCursor::MoveAnchor); 129 | if(p == ipspan[i].second) 130 | { 131 | tc.setPosition(ipspan[i].second, QTextCursor::KeepAnchor); 132 | } 133 | else 134 | { 135 | tc.setPosition(ipspan[i].second + 1, QTextCursor::KeepAnchor); 136 | } 137 | tc.removeSelectedText(); 138 | ipspan.removeAt(i); 139 | return true; 140 | } 141 | else if(p >= ipspan[i].first && p <= ipspan[i].second) 142 | { 143 | QTextCursor tc = edit->textCursor(); 144 | tc.setPosition(ipspan[i].second); 145 | edit->setTextCursor(tc); 146 | } 147 | } 148 | } 149 | } 150 | return QWidget::eventFilter(obj, event); 151 | } 152 | -------------------------------------------------------------------------------- /chatmessage.cpp: -------------------------------------------------------------------------------- 1 | #include "chatmessage.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | ChatMessage::ChatMessage(QWidget *parent) : QWidget(parent) 11 | { 12 | QFont te_font = this->font(); 13 | te_font.setFamily("MicrosoftYaHei"); 14 | te_font.setPointSize(12); 15 | this->setFont(te_font); 16 | m_leftPixmap = QPixmap(":/myImage/1.jpg"); 17 | m_rightPixmap = QPixmap(":/myImage/1.jpg"); 18 | 19 | m_loadingMovie = new QMovie(this); 20 | m_loadingMovie->setFileName(":/myImage/3.gif"); 21 | m_loading = new QLabel(this); 22 | m_loading->setMovie(m_loadingMovie); 23 | m_loading->setScaledContents(true); 24 | m_loading->resize(40,40); 25 | m_loading->setAttribute(Qt::WA_TranslucentBackground , true); 26 | } 27 | 28 | void ChatMessage::setTextSuccess() 29 | { 30 | m_loading->hide(); 31 | m_loadingMovie->stop(); 32 | m_isSending = true; 33 | } 34 | 35 | void ChatMessage::setText(QString text, QString time, QSize allSize, QString ip,ChatMessage::User_Type userType) 36 | { 37 | m_msg = text; 38 | m_userType = userType; 39 | m_time = time; 40 | m_curTime = QDateTime::fromTime_t(time.toInt()).toString("ddd hh:mm"); 41 | m_allSize = allSize; 42 | m_ip = ip; 43 | if(userType == User_Me) { 44 | if(!m_isSending) { 45 | m_loading->move(m_kuangRightRect.x() - m_loading->width() - 10, m_kuangRightRect.y()+m_kuangRightRect.height()/2- m_loading->height()/2); 46 | // m_loading->move(0, 0); 47 | m_loading->show(); 48 | m_loadingMovie->start(); 49 | } 50 | } else { 51 | m_loading->hide(); 52 | } 53 | 54 | this->update(); 55 | } 56 | 57 | QSize ChatMessage::fontRect(QString str) 58 | { 59 | m_msg = str; 60 | int minHei = 30; 61 | int iconWH = 40; 62 | int iconSpaceW = 20; 63 | int iconRectW = 5; 64 | int iconTMPH = 10; 65 | int sanJiaoW = 6; 66 | int kuangTMP = 20; 67 | int textSpaceRect = 12; 68 | m_kuangWidth = this->width() - kuangTMP - 2*(iconWH+iconSpaceW+iconRectW); 69 | m_textWidth = m_kuangWidth - 2*textSpaceRect; 70 | m_spaceWid = this->width() - m_textWidth; 71 | m_iconLeftRect = QRect(iconSpaceW, iconTMPH + 10, iconWH, iconWH); 72 | m_iconRightRect = QRect(this->width() - iconSpaceW - iconWH, iconTMPH + 10, iconWH, iconWH); 73 | 74 | 75 | QSize size = getRealString(m_msg); // 整个的size 76 | 77 | qDebug() << "fontRect Size:" << size; 78 | int hei = size.height() < minHei ? minHei : size.height(); 79 | 80 | m_sanjiaoLeftRect = QRect(iconWH+iconSpaceW+iconRectW, m_lineHeight/2 + 10, sanJiaoW, hei - m_lineHeight); 81 | m_sanjiaoRightRect = QRect(this->width() - iconRectW - iconWH - iconSpaceW - sanJiaoW, m_lineHeight/2+10, sanJiaoW, hei - m_lineHeight); 82 | 83 | if(size.width() < (m_textWidth+m_spaceWid)) { 84 | m_kuangLeftRect.setRect(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), m_lineHeight/4*3 + 10, size.width()-m_spaceWid+2*textSpaceRect, hei-m_lineHeight); 85 | m_kuangRightRect.setRect(this->width() - size.width() + m_spaceWid - 2*textSpaceRect - iconWH - iconSpaceW - iconRectW - sanJiaoW, 86 | m_lineHeight/4*3 + 10, size.width()-m_spaceWid+2*textSpaceRect, hei-m_lineHeight); 87 | } else { 88 | m_kuangLeftRect.setRect(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), m_lineHeight/4*3 + 10, m_kuangWidth, hei-m_lineHeight); 89 | m_kuangRightRect.setRect(iconWH + kuangTMP + iconSpaceW + iconRectW - sanJiaoW, m_lineHeight/4*3 + 10, m_kuangWidth, hei-m_lineHeight); 90 | } 91 | m_textLeftRect.setRect(m_kuangLeftRect.x()+textSpaceRect, m_kuangLeftRect.y()+iconTMPH, 92 | m_kuangLeftRect.width()-2*textSpaceRect, m_kuangLeftRect.height()-2*iconTMPH); 93 | m_textRightRect.setRect(m_kuangRightRect.x()+textSpaceRect, m_kuangRightRect.y()+iconTMPH, 94 | m_kuangRightRect.width()-2*textSpaceRect, m_kuangRightRect.height()-2*iconTMPH); 95 | 96 | 97 | m_ipLeftRect.setRect(m_kuangLeftRect.x(), m_kuangLeftRect.y()+iconTMPH - 20, 98 | m_kuangLeftRect.width()-2*textSpaceRect + iconWH*2, 20); 99 | m_ipRightRect.setRect(m_kuangRightRect.x(), m_kuangRightRect.y()+iconTMPH - 30, 100 | m_kuangRightRect.width()-2*textSpaceRect + iconWH*2 , 20); 101 | return QSize(size.width(), hei + 15); 102 | } 103 | 104 | QSize ChatMessage::getRealString(QString src) 105 | { 106 | QFontMetricsF fm(this->font()); 107 | m_lineHeight = fm.lineSpacing(); 108 | int nCount = src.count("\n"); 109 | int nMaxWidth = 0; 110 | if(nCount == 0) { 111 | nMaxWidth = fm.width(src); 112 | QString value = src; 113 | if(nMaxWidth > m_textWidth) { 114 | nMaxWidth = m_textWidth; 115 | int size = m_textWidth / fm.width(" "); 116 | int num = fm.width(value) / m_textWidth; 117 | num = ( fm.width(value) ) / m_textWidth; 118 | nCount += num; 119 | QString temp = ""; 120 | for(int i = 0; i < num; i++) { 121 | temp += value.mid(i*size, (i+1)*size) + "\n"; 122 | } 123 | src.replace(value, temp); 124 | } 125 | } else { 126 | for(int i = 0; i < (nCount + 1); i++) { 127 | QString value = src.split("\n").at(i); 128 | nMaxWidth = fm.width(value) > nMaxWidth ? fm.width(value) : nMaxWidth; 129 | if(fm.width(value) > m_textWidth) { 130 | nMaxWidth = m_textWidth; 131 | int size = m_textWidth / fm.width(" "); 132 | int num = fm.width(value) / m_textWidth; 133 | num = ((i+num)*fm.width(" ") + fm.width(value)) / m_textWidth; 134 | nCount += num; 135 | QString temp = ""; 136 | for(int i = 0; i < num; i++) { 137 | temp += value.mid(i*size, (i+1)*size) + "\n"; 138 | } 139 | src.replace(value, temp); 140 | } 141 | } 142 | } 143 | return QSize(nMaxWidth+m_spaceWid, (nCount + 1) * m_lineHeight+2*m_lineHeight); 144 | } 145 | 146 | void ChatMessage::paintEvent(QPaintEvent *event) 147 | { 148 | Q_UNUSED(event); 149 | 150 | QPainter painter(this); 151 | painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);//消锯齿 152 | painter.setPen(Qt::NoPen); 153 | painter.setBrush(QBrush(Qt::gray)); 154 | 155 | if(m_userType == User_Type::User_She) { // 用户 156 | //头像 157 | // painter.drawRoundedRect(m_iconLeftRect,m_iconLeftRect.width(),m_iconLeftRect.height()); 158 | painter.drawPixmap(m_iconLeftRect, m_leftPixmap); 159 | 160 | //框加边 161 | QColor col_KuangB(234, 234, 234); 162 | painter.setBrush(QBrush(col_KuangB)); 163 | painter.drawRoundedRect(m_kuangLeftRect.x()-1,m_kuangLeftRect.y()-1 + 10,m_kuangLeftRect.width()+2,m_kuangLeftRect.height()+2,4,4); 164 | //框 165 | QColor col_Kuang(255,255,255); 166 | painter.setBrush(QBrush(col_Kuang)); 167 | painter.drawRoundedRect(m_kuangLeftRect,4,4); 168 | 169 | //三角 170 | QPointF points[3] = { 171 | QPointF(m_sanjiaoLeftRect.x(), 40), 172 | QPointF(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), 35), 173 | QPointF(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), 45), 174 | }; 175 | QPen pen; 176 | pen.setColor(col_Kuang); 177 | painter.setPen(pen); 178 | painter.drawPolygon(points, 3); 179 | 180 | //三角加边 181 | QPen penSanJiaoBian; 182 | penSanJiaoBian.setColor(col_KuangB); 183 | painter.setPen(penSanJiaoBian); 184 | painter.drawLine(QPointF(m_sanjiaoLeftRect.x() - 1, 30), QPointF(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), 24)); 185 | painter.drawLine(QPointF(m_sanjiaoLeftRect.x() - 1, 30), QPointF(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), 36)); 186 | 187 | //ip 188 | //ip 189 | QPen penIp; 190 | penIp.setColor(Qt::darkGray); 191 | painter.setPen(penIp); 192 | QFont f = this->font(); 193 | f.setPointSize(10); 194 | QTextOption op(Qt::AlignHCenter | Qt::AlignVCenter); 195 | painter.setFont(f); 196 | painter.drawText(m_ipLeftRect, m_ip, op); 197 | 198 | //内容 199 | QPen penText; 200 | penText.setColor(QColor(51,51,51)); 201 | painter.setPen(penText); 202 | QTextOption option(Qt::AlignLeft | Qt::AlignVCenter); 203 | option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); 204 | painter.setFont(this->font()); 205 | painter.drawText(m_textLeftRect, m_msg,option); 206 | } else if(m_userType == User_Type::User_Me) { // 自己 207 | //头像 208 | // painter.drawRoundedRect(m_iconRightRect,m_iconRightRect.width(),m_iconRightRect.height()); 209 | painter.drawPixmap(m_iconRightRect, m_rightPixmap); 210 | 211 | //框 212 | QColor col_Kuang(75,164,242); 213 | painter.setBrush(QBrush(col_Kuang)); 214 | painter.drawRoundedRect(m_kuangRightRect,4,4); 215 | 216 | //三角 217 | QPointF points[3] = { 218 | QPointF(m_sanjiaoRightRect.x()+m_sanjiaoRightRect.width(), 40), 219 | QPointF(m_sanjiaoRightRect.x(), 35), 220 | QPointF(m_sanjiaoRightRect.x(), 45), 221 | }; 222 | QPen pen; 223 | pen.setColor(col_Kuang); 224 | painter.setPen(pen); 225 | painter.drawPolygon(points, 3); 226 | 227 | 228 | //ip 229 | QPen penIp; 230 | penIp.setColor(Qt::black); 231 | painter.setPen(penIp); 232 | QFont f = this->font(); 233 | f.setPointSize(10); 234 | QTextOption op(Qt::AlignHCenter | Qt::AlignVCenter); 235 | painter.setFont(f); 236 | painter.drawText(m_ipRightRect, m_ip, op); 237 | 238 | //内容 239 | QPen penText; 240 | penText.setColor(Qt::white); 241 | painter.setPen(penText); 242 | QTextOption option(Qt::AlignLeft | Qt::AlignVCenter); 243 | option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); 244 | painter.setFont(this->font()); 245 | painter.drawText(m_textRightRect,m_msg,option); 246 | } else if(m_userType == User_Type::User_Time) { // 时间 247 | QPen penText; 248 | penText.setColor(QColor(153,153,153)); 249 | painter.setPen(penText); 250 | QTextOption option(Qt::AlignCenter); 251 | option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); 252 | QFont te_font = this->font(); 253 | te_font.setFamily("MicrosoftYaHei"); 254 | te_font.setPointSize(10); 255 | painter.setFont(te_font); 256 | painter.drawText(this->rect(),m_curTime,option); 257 | } 258 | }; 259 | -------------------------------------------------------------------------------- /mytcpsocket.cpp: -------------------------------------------------------------------------------- 1 | #include "mytcpsocket.h" 2 | #include "netheader.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | extern QUEUE_DATA queue_send; 9 | extern QUEUE_DATA queue_recv; 10 | extern QUEUE_DATA audio_recv; 11 | 12 | void MyTcpSocket::stopImmediately() 13 | { 14 | { 15 | QMutexLocker lock(&m_lock); 16 | if(m_isCanRun == true) m_isCanRun = false; 17 | } 18 | //关闭read 19 | _sockThread->quit(); 20 | _sockThread->wait(); 21 | } 22 | 23 | void MyTcpSocket::closeSocket() 24 | { 25 | if (_socktcp && _socktcp->isOpen()) 26 | { 27 | _socktcp->close(); 28 | } 29 | } 30 | 31 | MyTcpSocket::MyTcpSocket(QObject *par):QThread(par) 32 | { 33 | qRegisterMetaType(); 34 | _socktcp = nullptr; 35 | 36 | _sockThread = new QThread(); //发送数据线程 37 | this->moveToThread(_sockThread); 38 | connect(_sockThread, SIGNAL(finished()), this, SLOT(closeSocket())); 39 | sendbuf =(uchar *) malloc(4 * MB); 40 | recvbuf = (uchar*)malloc(4 * MB); 41 | hasrecvive = 0; 42 | 43 | } 44 | 45 | 46 | void MyTcpSocket::errorDetect(QAbstractSocket::SocketError error) 47 | { 48 | qDebug() <<"Sock error" <msg_type = RemoteHostClosedError; 60 | } 61 | else 62 | { 63 | msg->msg_type = OtherNetError; 64 | } 65 | queue_recv.push_msg(msg); 66 | } 67 | } 68 | 69 | 70 | 71 | void MyTcpSocket::sendData(MESG* send) 72 | { 73 | if (_socktcp->state() == QAbstractSocket::UnconnectedState) 74 | { 75 | emit sendTextOver(); 76 | if (send->data) free(send->data); 77 | if (send) free(send); 78 | return; 79 | } 80 | quint64 bytestowrite = 0; 81 | //构造消息头 82 | sendbuf[bytestowrite++] = '$'; 83 | 84 | //消息类型 85 | qToBigEndian(send->msg_type, sendbuf + bytestowrite); 86 | bytestowrite += 2; 87 | 88 | //发送者ip 89 | quint32 ip = _socktcp->localAddress().toIPv4Address(); 90 | qToBigEndian(ip, sendbuf + bytestowrite); 91 | bytestowrite += 4; 92 | 93 | if (send->msg_type == CREATE_MEETING || send->msg_type == AUDIO_SEND || send->msg_type == CLOSE_CAMERA || send->msg_type == IMG_SEND || send->msg_type == TEXT_SEND) //创建会议,发送音频,关闭摄像头,发送图片 94 | { 95 | //发送数据大小 96 | qToBigEndian(send->len, sendbuf + bytestowrite); 97 | bytestowrite += 4; 98 | } 99 | else if (send->msg_type == JOIN_MEETING) 100 | { 101 | qToBigEndian(send->len, sendbuf + bytestowrite); 102 | bytestowrite += 4; 103 | uint32_t room; 104 | memcpy(&room, send->data, send->len); 105 | qToBigEndian(room, send->data); 106 | } 107 | 108 | //将数据拷入sendbuf 109 | memcpy(sendbuf + bytestowrite, send->data, send->len); 110 | bytestowrite += send->len; 111 | sendbuf[bytestowrite++] = '#'; //结尾字符 112 | 113 | //----------------write to server------------------------- 114 | qint64 hastowrite = bytestowrite; 115 | qint64 ret = 0, haswrite = 0; 116 | while ((ret = _socktcp->write((char*)sendbuf + haswrite, hastowrite - haswrite)) < hastowrite) 117 | { 118 | if (ret == -1 && _socktcp->error() == QAbstractSocket::TemporaryError) 119 | { 120 | ret = 0; 121 | } 122 | else if (ret == -1) 123 | { 124 | qDebug() << "network error"; 125 | break; 126 | } 127 | haswrite += ret; 128 | hastowrite -= ret; 129 | } 130 | 131 | _socktcp->waitForBytesWritten(); 132 | 133 | if(send->msg_type == TEXT_SEND) 134 | { 135 | emit sendTextOver(); //成功往内核发送文本信息 136 | } 137 | 138 | 139 | if (send->data) 140 | { 141 | free(send->data); 142 | } 143 | //free 144 | if (send) 145 | { 146 | free(send); 147 | } 148 | } 149 | 150 | /* 151 | * 发送线程 152 | */ 153 | void MyTcpSocket::run() 154 | { 155 | //qDebug() << "send data" << QThread::currentThreadId(); 156 | m_isCanRun = true; //标记可以运行 157 | /* 158 | *$_MSGType_IPV4_MSGSize_data_# // 159 | * 1 2 4 4 MSGSize 1 160 | *底层写数据线程 161 | */ 162 | for(;;) 163 | { 164 | { 165 | QMutexLocker locker(&m_lock); 166 | if(m_isCanRun == false) return; //在每次循环判断是否可以运行,如果不行就退出循环 167 | } 168 | 169 | //构造消息体 170 | MESG * send = queue_send.pop_msg(); 171 | if(send == NULL) continue; 172 | QMetaObject::invokeMethod(this, "sendData", Q_ARG(MESG*, send)); 173 | } 174 | } 175 | 176 | 177 | qint64 MyTcpSocket::readn(char * buf, quint64 maxsize, int n) 178 | { 179 | quint64 hastoread = n; 180 | quint64 hasread = 0; 181 | do 182 | { 183 | qint64 ret = _socktcp->read(buf + hasread, hastoread); 184 | if(ret < 0) 185 | { 186 | return -1; 187 | } 188 | if(ret == 0) 189 | { 190 | return hasread; 191 | } 192 | hasread += ret; 193 | hastoread -= ret; 194 | }while(hastoread > 0 && hasread < maxsize); 195 | return hasread; 196 | } 197 | 198 | 199 | void MyTcpSocket::recvFromSocket() 200 | { 201 | 202 | //qDebug() << "recv data socket" <bytesAvailable(); 207 | if (availbytes <=0 ) 208 | { 209 | return; 210 | } 211 | qint64 ret = _socktcp->read((char *) recvbuf + hasrecvive, availbytes); 212 | if (ret <= 0) 213 | { 214 | qDebug() << "error or no more data"; 215 | return; 216 | } 217 | hasrecvive += ret; 218 | 219 | //数据包不够 220 | if (hasrecvive < MSG_HEADER) 221 | { 222 | return; 223 | } 224 | else 225 | { 226 | quint32 data_size; 227 | qFromBigEndian(recvbuf + 7, 4, &data_size); 228 | if ((quint64)data_size + 1 + MSG_HEADER <= hasrecvive) //收够一个包 229 | { 230 | if (recvbuf[0] == '$' && recvbuf[MSG_HEADER + data_size] == '#') //且包结构正确 231 | { 232 | MSG_TYPE msgtype; 233 | uint16_t type; 234 | qFromBigEndian(recvbuf + 1, 2, &type); 235 | msgtype = (MSG_TYPE)type; 236 | qDebug() << "recv data type: " << msgtype; 237 | if (msgtype == CREATE_MEETING_RESPONSE || msgtype == JOIN_MEETING_RESPONSE || msgtype == PARTNER_JOIN2) 238 | { 239 | if (msgtype == CREATE_MEETING_RESPONSE) 240 | { 241 | qint32 roomNo; 242 | qFromBigEndian(recvbuf + MSG_HEADER, 4, &roomNo); 243 | 244 | MESG* msg = (MESG*)malloc(sizeof(MESG)); 245 | 246 | if (msg == NULL) 247 | { 248 | qDebug() << __LINE__ << " CREATE_MEETING_RESPONSE malloc MESG failed"; 249 | } 250 | else 251 | { 252 | memset(msg, 0, sizeof(MESG)); 253 | msg->msg_type = msgtype; 254 | msg->data = (uchar*)malloc((quint64)data_size); 255 | if (msg->data == NULL) 256 | { 257 | free(msg); 258 | qDebug() << __LINE__ << "CREATE_MEETING_RESPONSE malloc MESG.data failed"; 259 | } 260 | else 261 | { 262 | memset(msg->data, 0, (quint64)data_size); 263 | memcpy(msg->data, &roomNo, data_size); 264 | msg->len = data_size; 265 | queue_recv.push_msg(msg); 266 | } 267 | 268 | } 269 | } 270 | else if (msgtype == JOIN_MEETING_RESPONSE) 271 | { 272 | qint32 c; 273 | memcpy(&c, recvbuf + MSG_HEADER, data_size); 274 | 275 | MESG* msg = (MESG*)malloc(sizeof(MESG)); 276 | 277 | if (msg == NULL) 278 | { 279 | qDebug() << __LINE__ << "JOIN_MEETING_RESPONSE malloc MESG failed"; 280 | } 281 | else 282 | { 283 | memset(msg, 0, sizeof(MESG)); 284 | msg->msg_type = msgtype; 285 | msg->data = (uchar*)malloc(data_size); 286 | if (msg->data == NULL) 287 | { 288 | free(msg); 289 | qDebug() << __LINE__ << "JOIN_MEETING_RESPONSE malloc MESG.data failed"; 290 | } 291 | else 292 | { 293 | memset(msg->data, 0, data_size); 294 | memcpy(msg->data, &c, data_size); 295 | 296 | msg->len = data_size; 297 | queue_recv.push_msg(msg); 298 | } 299 | } 300 | } 301 | else if (msgtype == PARTNER_JOIN2) 302 | { 303 | MESG* msg = (MESG*)malloc(sizeof(MESG)); 304 | if (msg == NULL) 305 | { 306 | qDebug() << "PARTNER_JOIN2 malloc MESG error"; 307 | } 308 | else 309 | { 310 | memset(msg, 0, sizeof(MESG)); 311 | msg->msg_type = msgtype; 312 | msg->len = data_size; 313 | msg->data = (uchar*)malloc(data_size); 314 | if (msg->data == NULL) 315 | { 316 | free(msg); 317 | qDebug() << "PARTNER_JOIN2 malloc MESG.data error"; 318 | } 319 | else 320 | { 321 | memset(msg->data, 0, data_size); 322 | uint32_t ip; 323 | int pos = 0; 324 | for (int i = 0; i < data_size / sizeof(uint32_t); i++) 325 | { 326 | qFromBigEndian(recvbuf + MSG_HEADER + pos, sizeof(uint32_t), &ip); 327 | memcpy_s(msg->data + pos, data_size - pos, &ip, sizeof(uint32_t)); 328 | pos += sizeof(uint32_t); 329 | } 330 | queue_recv.push_msg(msg); 331 | } 332 | 333 | } 334 | } 335 | } 336 | else if (msgtype == IMG_RECV || msgtype == PARTNER_JOIN || msgtype == PARTNER_EXIT || msgtype == AUDIO_RECV || msgtype == CLOSE_CAMERA || msgtype == TEXT_RECV) 337 | { 338 | //read ipv4 339 | quint32 ip; 340 | qFromBigEndian(recvbuf + 3, 4, &ip); 341 | 342 | if (msgtype == IMG_RECV) 343 | { 344 | //QString ss = QString::fromLatin1((char *)recvbuf + MSG_HEADER, data_len); 345 | QByteArray cc((char *) recvbuf + MSG_HEADER, data_size); 346 | QByteArray rc = QByteArray::fromBase64(cc); 347 | QByteArray rdc = qUncompress(rc); 348 | //将消息加入到接收队列 349 | // qDebug() << roomNo; 350 | 351 | if (rdc.size() > 0) 352 | { 353 | MESG* msg = (MESG*)malloc(sizeof(MESG)); 354 | if (msg == NULL) 355 | { 356 | qDebug() << __LINE__ << " malloc failed"; 357 | } 358 | else 359 | { 360 | memset(msg, 0, sizeof(MESG)); 361 | msg->msg_type = msgtype; 362 | msg->data = (uchar*)malloc(rdc.size()); // 10 = format + width + width 363 | if (msg->data == NULL) 364 | { 365 | free(msg); 366 | qDebug() << __LINE__ << " malloc failed"; 367 | } 368 | else 369 | { 370 | memset(msg->data, 0, rdc.size()); 371 | memcpy_s(msg->data, rdc.size(), rdc.data(), rdc.size()); 372 | msg->len = rdc.size(); 373 | msg->ip = ip; 374 | queue_recv.push_msg(msg); 375 | } 376 | } 377 | } 378 | } 379 | else if (msgtype == PARTNER_JOIN || msgtype == PARTNER_EXIT || msgtype == CLOSE_CAMERA) 380 | { 381 | MESG* msg = (MESG*)malloc(sizeof(MESG)); 382 | if (msg == NULL) 383 | { 384 | qDebug() << __LINE__ << " malloc failed"; 385 | } 386 | else 387 | { 388 | memset(msg, 0, sizeof(MESG)); 389 | msg->msg_type = msgtype; 390 | msg->ip = ip; 391 | queue_recv.push_msg(msg); 392 | } 393 | } 394 | else if (msgtype == AUDIO_RECV) 395 | { 396 | //解压缩 397 | QByteArray cc((char*)recvbuf + MSG_HEADER, data_size); 398 | QByteArray rc = QByteArray::fromBase64(cc); 399 | QByteArray rdc = qUncompress(rc); 400 | 401 | if (rdc.size() > 0) 402 | { 403 | MESG* msg = (MESG*)malloc(sizeof(MESG)); 404 | if (msg == NULL) 405 | { 406 | qDebug() << __LINE__ << "malloc failed"; 407 | } 408 | else 409 | { 410 | memset(msg, 0, sizeof(MESG)); 411 | msg->msg_type = AUDIO_RECV; 412 | msg->ip = ip; 413 | 414 | msg->data = (uchar*)malloc(rdc.size()); 415 | if (msg->data == nullptr) 416 | { 417 | free(msg); 418 | qDebug() << __LINE__ << "malloc msg.data failed"; 419 | } 420 | else 421 | { 422 | memset(msg->data, 0, rdc.size()); 423 | memcpy_s(msg->data, rdc.size(), rdc.data(), rdc.size()); 424 | msg->len = rdc.size(); 425 | msg->ip = ip; 426 | audio_recv.push_msg(msg); 427 | } 428 | } 429 | } 430 | } 431 | else if(msgtype == TEXT_RECV) 432 | { 433 | //解压缩 434 | QByteArray cc((char *)recvbuf + MSG_HEADER, data_size); 435 | std::string rr = qUncompress(cc).toStdString(); 436 | if(rr.size() > 0) 437 | { 438 | MESG* msg = (MESG*)malloc(sizeof(MESG)); 439 | if (msg == NULL) 440 | { 441 | qDebug() << __LINE__ << "malloc failed"; 442 | } 443 | else 444 | { 445 | memset(msg, 0, sizeof(MESG)); 446 | msg->msg_type = TEXT_RECV; 447 | msg->ip = ip; 448 | msg->data = (uchar*)malloc(rr.size()); 449 | if (msg->data == nullptr) 450 | { 451 | free(msg); 452 | qDebug() << __LINE__ << "malloc msg.data failed"; 453 | } 454 | else 455 | { 456 | memset(msg->data, 0, rr.size()); 457 | memcpy_s(msg->data, rr.size(), rr.data(), rr.size()); 458 | msg->len = rr.size(); 459 | queue_recv.push_msg(msg); 460 | } 461 | } 462 | } 463 | } 464 | } 465 | } 466 | else 467 | { 468 | qDebug() << "package error"; 469 | } 470 | memmove_s(recvbuf, 4 * MB, recvbuf + MSG_HEADER + data_size + 1, hasrecvive - ((quint64)data_size + 1 + MSG_HEADER)); 471 | hasrecvive -= ((quint64)data_size + 1 + MSG_HEADER); 472 | } 473 | else 474 | { 475 | return; 476 | } 477 | } 478 | } 479 | 480 | 481 | MyTcpSocket::~MyTcpSocket() 482 | { 483 | delete sendbuf; 484 | delete _sockThread; 485 | } 486 | 487 | 488 | 489 | bool MyTcpSocket::connectServer(QString ip, QString port, QIODevice::OpenModeFlag flag) 490 | { 491 | if(_socktcp == nullptr) _socktcp = new QTcpSocket(); //tcp 492 | _socktcp->connectToHost(ip, port.toUShort(), flag); 493 | connect(_socktcp, SIGNAL(readyRead()), this, SLOT(recvFromSocket()), Qt::UniqueConnection); //接受数据 494 | //处理套接字错误 495 | connect(_socktcp, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(errorDetect(QAbstractSocket::SocketError)),Qt::UniqueConnection); 496 | 497 | if(_socktcp->waitForConnected(5000)) 498 | { 499 | return true; 500 | } 501 | _socktcp->close(); 502 | return false; 503 | } 504 | 505 | 506 | bool MyTcpSocket::connectToServer(QString ip, QString port, QIODevice::OpenModeFlag flag) 507 | { 508 | _sockThread->start(); // 开启链接,与接受 509 | bool retVal; 510 | QMetaObject::invokeMethod(this, "connectServer", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, retVal), 511 | Q_ARG(QString, ip), Q_ARG(QString, port), Q_ARG(QIODevice::OpenModeFlag, flag)); 512 | 513 | if (retVal) 514 | { 515 | this->start() ; //写数据 516 | return true; 517 | } 518 | else 519 | { 520 | return false; 521 | } 522 | } 523 | 524 | QString MyTcpSocket::errorString() 525 | { 526 | return _socktcp->errorString(); 527 | } 528 | 529 | void MyTcpSocket::disconnectFromHost() 530 | { 531 | //write 532 | if(this->isRunning()) 533 | { 534 | QMutexLocker locker(&m_lock); 535 | m_isCanRun = false; 536 | } 537 | 538 | if(_sockThread->isRunning()) //read 539 | { 540 | _sockThread->quit(); 541 | _sockThread->wait(); 542 | } 543 | 544 | //清空 发送 队列,清空接受队列 545 | queue_send.clear(); 546 | queue_recv.clear(); 547 | audio_recv.clear(); 548 | } 549 | 550 | 551 | quint32 MyTcpSocket::getlocalip() 552 | { 553 | if(_socktcp->isOpen()) 554 | { 555 | return _socktcp->localAddress().toIPv4Address(); 556 | } 557 | else 558 | { 559 | return -1; 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /widget.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | #include "ui_widget.h" 3 | #include "screen.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "myvideosurface.h" 10 | #include "sendimg.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "logqueue.h" 18 | #include 19 | #include 20 | #include 21 | #include 22 | QRect Widget::pos = QRect(-1, -1, -1, -1); 23 | 24 | extern LogQueue *logqueue; 25 | 26 | Widget::Widget(QWidget *parent) 27 | : QWidget(parent) 28 | , ui(new Ui::Widget) 29 | { 30 | 31 | 32 | //开启日志线程 33 | logqueue = new LogQueue(); 34 | logqueue->start(); 35 | 36 | qDebug() << "main: " <(); 38 | 39 | WRITE_LOG("-------------------------Application Start---------------------------"); 40 | WRITE_LOG("main UI thread id: 0x%p", QThread::currentThreadId()); 41 | //ui界面 42 | _createmeet = false; 43 | _openCamera = false; 44 | _joinmeet = false; 45 | Widget::pos = QRect(0.1 * Screen::width, 0.1 * Screen::height, 0.8 * Screen::width, 0.8 * Screen::height); 46 | 47 | ui->setupUi(this); 48 | 49 | ui->openAudio->setText(QString(OPENAUDIO).toUtf8()); 50 | ui->openVedio->setText(QString(OPENVIDEO).toUtf8()); 51 | 52 | this->setGeometry(Widget::pos); 53 | this->setMinimumSize(QSize(Widget::pos.width() * 0.7, Widget::pos.height() * 0.7)); 54 | this->setMaximumSize(QSize(Widget::pos.width(), Widget::pos.height())); 55 | 56 | 57 | ui->exitmeetBtn->setDisabled(true); 58 | ui->joinmeetBtn->setDisabled(true); 59 | ui->createmeetBtn->setDisabled(true); 60 | ui->openAudio->setDisabled(true); 61 | ui->openVedio->setDisabled(true); 62 | ui->sendmsg->setDisabled(true); 63 | mainip = 0; //主屏幕显示的用户IP图像 64 | 65 | //-------------------局部线程---------------------------- 66 | //创建传输视频帧线程 67 | _sendImg = new SendImg(); 68 | _imgThread = new QThread(); 69 | _sendImg->moveToThread(_imgThread); //新起线程接受视频帧 70 | _sendImg->start(); 71 | //_imgThread->start(); 72 | //处理每一帧数据 73 | 74 | //-------------------------------------------------- 75 | 76 | 77 | //数据处理(局部线程) 78 | _mytcpSocket = new MyTcpSocket(); // 底层线程专管发送 79 | connect(_mytcpSocket, SIGNAL(sendTextOver()), this, SLOT(textSend())); 80 | //connect(_mytcpSocket, SIGNAL(socketerror(QAbstractSocket::SocketError)), this, SLOT(mytcperror(QAbstractSocket::SocketError))); 81 | 82 | 83 | //---------------------------------------------------------- 84 | //文本传输(局部线程) 85 | _sendText = new SendText(); 86 | _textThread = new QThread(); 87 | _sendText->moveToThread(_textThread); 88 | _textThread->start(); // 加入线程 89 | _sendText->start(); // 发送 90 | 91 | connect(this, SIGNAL(PushText(MSG_TYPE,QString)), _sendText, SLOT(push_Text(MSG_TYPE,QString))); 92 | //----------------------------------------------------------- 93 | 94 | //配置摄像头 95 | _camera = new QCamera(this); 96 | //摄像头出错处理 97 | connect(_camera, SIGNAL(error(QCamera::Error)), this, SLOT(cameraError(QCamera::Error))); 98 | _imagecapture = new QCameraImageCapture(_camera); 99 | _myvideosurface = new MyVideoSurface(this); 100 | 101 | 102 | connect(_myvideosurface, SIGNAL(frameAvailable(QVideoFrame)), this, SLOT(cameraImageCapture(QVideoFrame))); 103 | connect(this, SIGNAL(pushImg(QImage)), _sendImg, SLOT(ImageCapture(QImage))); 104 | 105 | 106 | //监听_imgThread退出信号 107 | connect(_imgThread, SIGNAL(finished()), _sendImg, SLOT(clearImgQueue())); 108 | 109 | 110 | //------------------启动接收数据线程------------------------- 111 | _recvThread = new RecvSolve(); 112 | connect(_recvThread, SIGNAL(datarecv(MESG*)), this, SLOT(datasolve(MESG*)), Qt::BlockingQueuedConnection); 113 | _recvThread->start(); 114 | 115 | //预览窗口重定向在MyVideoSurface 116 | _camera->setViewfinder(_myvideosurface); 117 | _camera->setCaptureMode(QCamera::CaptureStillImage); 118 | 119 | //音频 120 | _ainput = new AudioInput(); 121 | _ainputThread = new QThread(); 122 | _ainput->moveToThread(_ainputThread); 123 | 124 | 125 | _aoutput = new AudioOutput(); 126 | _ainputThread->start(); //获取音频,发送 127 | _aoutput->start(); //播放 128 | 129 | connect(this, SIGNAL(startAudio()), _ainput, SLOT(startCollect())); 130 | connect(this, SIGNAL(stopAudio()), _ainput, SLOT(stopCollect())); 131 | connect(_ainput, SIGNAL(audioinputerror(QString)), this, SLOT(audioError(QString))); 132 | connect(_aoutput, SIGNAL(audiooutputerror(QString)), this, SLOT(audioError(QString))); 133 | connect(_aoutput, SIGNAL(speaker(QString)), this, SLOT(speaks(QString))); 134 | //设置滚动条 135 | ui->scrollArea->verticalScrollBar()->setStyleSheet("QScrollBar:vertical { width:8px; background:rgba(0,0,0,0%); margin:0px,0px,0px,0px; padding-top:9px; padding-bottom:9px; } QScrollBar::handle:vertical { width:8px; background:rgba(0,0,0,25%); border-radius:4px; min-height:20; } QScrollBar::handle:vertical:hover { width:8px; background:rgba(0,0,0,50%); border-radius:4px; min-height:20; } QScrollBar::add-line:vertical { height:9px;width:8px; border-image:url(:/images/a/3.png); subcontrol-position:bottom; } QScrollBar::sub-line:vertical { height:9px;width:8px; border-image:url(:/images/a/1.png); subcontrol-position:top; } QScrollBar::add-line:vertical:hover { height:9px;width:8px; border-image:url(:/images/a/4.png); subcontrol-position:bottom; } QScrollBar::sub-line:vertical:hover { height:9px;width:8px; border-image:url(:/images/a/2.png); subcontrol-position:top; } QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical { background:rgba(0,0,0,10%); border-radius:4px; }"); 136 | ui->listWidget->setStyleSheet("QScrollBar:vertical { width:8px; background:rgba(0,0,0,0%); margin:0px,0px,0px,0px; padding-top:9px; padding-bottom:9px; } QScrollBar::handle:vertical { width:8px; background:rgba(0,0,0,25%); border-radius:4px; min-height:20; } QScrollBar::handle:vertical:hover { width:8px; background:rgba(0,0,0,50%); border-radius:4px; min-height:20; } QScrollBar::add-line:vertical { height:9px;width:8px; border-image:url(:/images/a/3.png); subcontrol-position:bottom; } QScrollBar::sub-line:vertical { height:9px;width:8px; border-image:url(:/images/a/1.png); subcontrol-position:top; } QScrollBar::add-line:vertical:hover { height:9px;width:8px; border-image:url(:/images/a/4.png); subcontrol-position:bottom; } QScrollBar::sub-line:vertical:hover { height:9px;width:8px; border-image:url(:/images/a/2.png); subcontrol-position:top; } QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical { background:rgba(0,0,0,10%); border-radius:4px; }"); 137 | 138 | QFont te_font = this->font(); 139 | te_font.setFamily("MicrosoftYaHei"); 140 | te_font.setPointSize(12); 141 | 142 | ui->listWidget->setFont(te_font); 143 | 144 | ui->tabWidget->setCurrentIndex(1); 145 | ui->tabWidget->setCurrentIndex(0); 146 | } 147 | 148 | 149 | void Widget::cameraImageCapture(QVideoFrame frame) 150 | { 151 | // qDebug() << QThread::currentThreadId() << this; 152 | 153 | if(frame.isValid() && frame.isReadable()) 154 | { 155 | QImage videoImg = QImage(frame.bits(), frame.width(), frame.height(), QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat())); 156 | 157 | QTransform matrix; 158 | matrix.rotate(180.0); 159 | 160 | QImage img = videoImg.transformed(matrix, Qt::FastTransformation).scaled(ui->mainshow_label->size()); 161 | 162 | if(partner.size() > 1) 163 | { 164 | emit pushImg(img); 165 | } 166 | 167 | if(_mytcpSocket->getlocalip() == mainip) 168 | { 169 | ui->mainshow_label->setPixmap(QPixmap::fromImage(img).scaled(ui->mainshow_label->size())); 170 | } 171 | 172 | Partner *p = partner[_mytcpSocket->getlocalip()]; 173 | if(p) p->setpic(img); 174 | 175 | //qDebug()<< "format: " << videoImg.format() << "size: " << videoImg.size() << "byteSIze: "<< videoImg.sizeInBytes(); 176 | } 177 | frame.unmap(); 178 | } 179 | 180 | Widget::~Widget() 181 | { 182 | //终止底层发送与接收线程 183 | 184 | if(_mytcpSocket->isRunning()) 185 | { 186 | _mytcpSocket->stopImmediately(); 187 | _mytcpSocket->wait(); 188 | } 189 | 190 | //终止接收处理线程 191 | if(_recvThread->isRunning()) 192 | { 193 | _recvThread->stopImmediately(); 194 | _recvThread->wait(); 195 | } 196 | 197 | if(_imgThread->isRunning()) 198 | { 199 | _imgThread->quit(); 200 | _imgThread->wait(); 201 | } 202 | 203 | if(_sendImg->isRunning()) 204 | { 205 | _sendImg->stopImmediately(); 206 | _sendImg->wait(); 207 | } 208 | 209 | if(_textThread->isRunning()) 210 | { 211 | _textThread->quit(); 212 | _textThread->wait(); 213 | } 214 | 215 | if(_sendText->isRunning()) 216 | { 217 | _sendText->stopImmediately(); 218 | _sendText->wait(); 219 | } 220 | 221 | if (_ainputThread->isRunning()) 222 | { 223 | _ainputThread->quit(); 224 | _ainputThread->wait(); 225 | } 226 | 227 | if (_aoutput->isRunning()) 228 | { 229 | _aoutput->stopImmediately(); 230 | _aoutput->wait(); 231 | } 232 | WRITE_LOG("-------------------Application End-----------------"); 233 | 234 | //关闭日志 235 | if(logqueue->isRunning()) 236 | { 237 | logqueue->stopImmediately(); 238 | logqueue->wait(); 239 | } 240 | 241 | delete ui; 242 | } 243 | 244 | void Widget::on_createmeetBtn_clicked() 245 | { 246 | if(false == _createmeet) 247 | { 248 | ui->createmeetBtn->setDisabled(true); 249 | ui->openAudio->setDisabled(true); 250 | ui->openVedio->setDisabled(true); 251 | ui->exitmeetBtn->setDisabled(true); 252 | emit PushText(CREATE_MEETING); //将 “创建会议"加入到发送队列 253 | WRITE_LOG("create meeting"); 254 | } 255 | } 256 | 257 | void Widget::paintEvent(QPaintEvent *event) 258 | { 259 | Q_UNUSED(event); 260 | /* 261 | * 触发事件(3条, 一般使用第二条进行触发) 262 | * 1. 窗口部件第一次显示时,系统会自动产生一个绘图事件。从而强制绘制这个窗口部件,主窗口起来会绘制一次 263 | * 2. 当重新调整窗口部件的大小时,系统也会产生一个绘制事件--QWidget::update()或者QWidget::repaint() 264 | * 3. 当窗口部件被其它窗口部件遮挡,然后又再次显示出来时,就会对那些隐藏的区域产生一个绘制事件 265 | */ 266 | } 267 | 268 | 269 | //退出会议(1,加入的会议, 2,自己创建的会议) 270 | void Widget::on_exitmeetBtn_clicked() 271 | { 272 | if(_camera->status() == QCamera::ActiveStatus) 273 | { 274 | _camera->stop(); 275 | } 276 | 277 | ui->createmeetBtn->setDisabled(true); 278 | ui->exitmeetBtn->setDisabled(true); 279 | _createmeet = false; 280 | _joinmeet = false; 281 | //----------------------------------------- 282 | //清空partner 283 | clearPartner(); 284 | // 关闭套接字 285 | 286 | //关闭socket 287 | _mytcpSocket->disconnectFromHost(); 288 | _mytcpSocket->wait(); 289 | 290 | ui->outlog->setText(tr("已退出会议")); 291 | 292 | ui->connServer->setDisabled(false); 293 | ui->groupBox_2->setTitle(QString("主屏幕")); 294 | // ui->groupBox->setTitle(QString("副屏幕")); 295 | //清除聊天记录 296 | while(ui->listWidget->count() > 0) 297 | { 298 | QListWidgetItem *item = ui->listWidget->takeItem(0); 299 | ChatMessage *chat = (ChatMessage *) ui->listWidget->itemWidget(item); 300 | delete item; 301 | delete chat; 302 | } 303 | iplist.clear(); 304 | ui->plainTextEdit->setCompleter(iplist); 305 | 306 | 307 | WRITE_LOG("exit meeting"); 308 | 309 | QMessageBox::warning(this, "Information", "退出会议" , QMessageBox::Yes, QMessageBox::Yes); 310 | 311 | //----------------------------------------- 312 | } 313 | 314 | void Widget::on_openVedio_clicked() 315 | { 316 | if(_camera->status() == QCamera::ActiveStatus) 317 | { 318 | _camera->stop(); 319 | WRITE_LOG("close camera"); 320 | if(_camera->error() == QCamera::NoError) 321 | { 322 | _imgThread->quit(); 323 | _imgThread->wait(); 324 | ui->openVedio->setText("开启摄像头"); 325 | emit PushText(CLOSE_CAMERA); 326 | } 327 | closeImg(_mytcpSocket->getlocalip()); 328 | } 329 | else 330 | { 331 | _camera->start(); //开启摄像头 332 | WRITE_LOG("open camera"); 333 | if(_camera->error() == QCamera::NoError) 334 | { 335 | _imgThread->start(); 336 | ui->openVedio->setText("关闭摄像头"); 337 | } 338 | } 339 | } 340 | 341 | 342 | void Widget::on_openAudio_clicked() 343 | { 344 | if (!_createmeet && !_joinmeet) return; 345 | if (ui->openAudio->text().toUtf8() == QString(OPENAUDIO).toUtf8()) 346 | { 347 | emit startAudio(); 348 | ui->openAudio->setText(QString(CLOSEAUDIO).toUtf8()); 349 | } 350 | else if(ui->openAudio->text().toUtf8() == QString(CLOSEAUDIO).toUtf8()) 351 | { 352 | emit stopAudio(); 353 | ui->openAudio->setText(QString(OPENAUDIO).toUtf8()); 354 | } 355 | } 356 | 357 | void Widget::closeImg(quint32 ip) 358 | { 359 | if (!partner.contains(ip)) 360 | { 361 | qDebug() << "close img error"; 362 | return; 363 | } 364 | Partner * p = partner[ip]; 365 | p->setpic(QImage(":/myImage/1.jpg")); 366 | 367 | if(mainip == ip) 368 | { 369 | ui->mainshow_label->setPixmap(QPixmap::fromImage(QImage(":/myImage/1.jpg").scaled(ui->mainshow_label->size()))); 370 | } 371 | } 372 | 373 | void Widget::on_connServer_clicked() 374 | { 375 | QString ip = ui->ip->text(), port = ui->port->text(); 376 | ui->outlog->setText("正在连接到" + ip + ":" + port); 377 | repaint(); 378 | 379 | QRegExp ipreg("((2{2}[0-3]|2[01][0-9]|1[0-9]{2}|0?[1-9][0-9]|0{0,2}[1-9])\\.)((25[0-5]|2[0-4][0-9]|[01]?[0-9]{0,2})\\.){2}(25[0-5]|2[0-4][0-9]|[01]?[0-9]{1,2})"); 380 | 381 | QRegExp portreg("^([0-9]|[1-9]\\d|[1-9]\\d{2}|[1-9]\\d{3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$"); 382 | QRegExpValidator ipvalidate(ipreg), portvalidate(portreg); 383 | int pos = 0; 384 | if(ipvalidate.validate(ip, pos) != QValidator::Acceptable) 385 | { 386 | QMessageBox::warning(this, "Input Error", "Ip Error", QMessageBox::Yes, QMessageBox::Yes); 387 | return; 388 | } 389 | if(portvalidate.validate(port, pos) != QValidator::Acceptable) 390 | { 391 | QMessageBox::warning(this, "Input Error", "Port Error", QMessageBox::Yes, QMessageBox::Yes); 392 | return; 393 | } 394 | 395 | if(_mytcpSocket ->connectToServer(ip, port, QIODevice::ReadWrite)) 396 | { 397 | ui->outlog->setText("成功连接到" + ip + ":" + port); 398 | ui->openAudio->setDisabled(true); 399 | ui->openVedio->setDisabled(true); 400 | ui->createmeetBtn->setDisabled(false); 401 | ui->exitmeetBtn->setDisabled(true); 402 | ui->joinmeetBtn->setDisabled(false); 403 | WRITE_LOG("succeeed connecting to %s:%s", ip.toStdString().c_str(), port.toStdString().c_str()); 404 | QMessageBox::warning(this, "Connection success", "成功连接服务器" , QMessageBox::Yes, QMessageBox::Yes); 405 | ui->sendmsg->setDisabled(false); 406 | ui->connServer->setDisabled(true); 407 | } 408 | else 409 | { 410 | ui->outlog->setText("连接失败,请重新连接..."); 411 | WRITE_LOG("failed to connenct %s:%s", ip.toStdString().c_str(), port.toStdString().c_str()); 412 | QMessageBox::warning(this, "Connection error", _mytcpSocket->errorString() , QMessageBox::Yes, QMessageBox::Yes); 413 | } 414 | } 415 | 416 | 417 | void Widget::cameraError(QCamera::Error) 418 | { 419 | QMessageBox::warning(this, "Camera error", _camera->errorString() , QMessageBox::Yes, QMessageBox::Yes); 420 | } 421 | 422 | void Widget::audioError(QString err) 423 | { 424 | QMessageBox::warning(this, "Audio error", err, QMessageBox::Yes); 425 | } 426 | 427 | void Widget::datasolve(MESG *msg) 428 | { 429 | if(msg->msg_type == CREATE_MEETING_RESPONSE) 430 | { 431 | int roomno; 432 | memcpy(&roomno, msg->data, msg->len); 433 | 434 | if(roomno != 0) 435 | { 436 | QMessageBox::information(this, "Room No", QString("房间号:%1").arg(roomno), QMessageBox::Yes, QMessageBox::Yes); 437 | 438 | ui->groupBox_2->setTitle(QString("主屏幕(房间号: %1)").arg(roomno)); 439 | ui->outlog->setText(QString("创建成功 房间号: %1").arg(roomno) ); 440 | _createmeet = true; 441 | ui->exitmeetBtn->setDisabled(false); 442 | ui->openVedio->setDisabled(false); 443 | ui->joinmeetBtn->setDisabled(true); 444 | 445 | WRITE_LOG("succeed creating room %d", roomno); 446 | //添加用户自己 447 | addPartner(_mytcpSocket->getlocalip()); 448 | mainip = _mytcpSocket->getlocalip(); 449 | ui->groupBox_2->setTitle(QHostAddress(mainip).toString()); 450 | ui->mainshow_label->setPixmap(QPixmap::fromImage(QImage(":/myImage/1.jpg").scaled(ui->mainshow_label->size()))); 451 | } 452 | else 453 | { 454 | _createmeet = false; 455 | QMessageBox::information(this, "Room Information", QString("无可用房间"), QMessageBox::Yes, QMessageBox::Yes); 456 | ui->outlog->setText(QString("无可用房间")); 457 | ui->createmeetBtn->setDisabled(false); 458 | WRITE_LOG("no empty room"); 459 | } 460 | } 461 | else if(msg->msg_type == JOIN_MEETING_RESPONSE) 462 | { 463 | qint32 c; 464 | memcpy(&c, msg->data, msg->len); 465 | if(c == 0) 466 | { 467 | QMessageBox::information(this, "Meeting Error", tr("会议不存在") , QMessageBox::Yes, QMessageBox::Yes); 468 | ui->outlog->setText(QString("会议不存在")); 469 | WRITE_LOG("meeting not exist"); 470 | ui->exitmeetBtn->setDisabled(true); 471 | ui->openVedio->setDisabled(true); 472 | ui->joinmeetBtn->setDisabled(false); 473 | ui->connServer->setDisabled(true); 474 | _joinmeet = false; 475 | } 476 | else if(c == -1) 477 | { 478 | QMessageBox::warning(this, "Meeting information", "成员已满,无法加入" , QMessageBox::Yes, QMessageBox::Yes); 479 | ui->outlog->setText(QString("成员已满,无法加入")); 480 | WRITE_LOG("full room, cannot join"); 481 | } 482 | else if (c > 0) 483 | { 484 | QMessageBox::warning(this, "Meeting information", "加入成功" , QMessageBox::Yes, QMessageBox::Yes); 485 | ui->outlog->setText(QString("加入成功")); 486 | WRITE_LOG("succeed joining room"); 487 | //添加用户自己 488 | addPartner(_mytcpSocket->getlocalip()); 489 | mainip = _mytcpSocket->getlocalip(); 490 | ui->groupBox_2->setTitle(QHostAddress(mainip).toString()); 491 | ui->mainshow_label->setPixmap(QPixmap::fromImage(QImage(":/myImage/1.jpg").scaled(ui->mainshow_label->size()))); 492 | ui->joinmeetBtn->setDisabled(true); 493 | ui->exitmeetBtn->setDisabled(false); 494 | ui->createmeetBtn->setDisabled(true); 495 | _joinmeet = true; 496 | } 497 | } 498 | else if(msg->msg_type == IMG_RECV) 499 | { 500 | QHostAddress a(msg->ip); 501 | qDebug() << a.toString(); 502 | QImage img; 503 | img.loadFromData(msg->data, msg->len); 504 | if(partner.count(msg->ip) == 1) 505 | { 506 | Partner* p = partner[msg->ip]; 507 | p->setpic(img); 508 | } 509 | else 510 | { 511 | Partner* p = addPartner(msg->ip); 512 | p->setpic(img); 513 | } 514 | 515 | if(msg->ip == mainip) 516 | { 517 | ui->mainshow_label->setPixmap(QPixmap::fromImage(img).scaled(ui->mainshow_label->size())); 518 | } 519 | repaint(); 520 | } 521 | else if(msg->msg_type == TEXT_RECV) 522 | { 523 | QString str = QString::fromStdString(std::string((char *)msg->data, msg->len)); 524 | //qDebug() << str; 525 | QString time = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); 526 | ChatMessage *message = new ChatMessage(ui->listWidget); 527 | QListWidgetItem *item = new QListWidgetItem(); 528 | dealMessageTime(time); 529 | dealMessage(message, item, str, time, QHostAddress(msg->ip).toString() ,ChatMessage::User_She); 530 | if(str.contains('@' + QHostAddress(_mytcpSocket->getlocalip()).toString())) 531 | { 532 | QSound::play(":/myEffect/2.wav"); 533 | } 534 | } 535 | else if(msg->msg_type == PARTNER_JOIN) 536 | { 537 | Partner* p = addPartner(msg->ip); 538 | if(p) 539 | { 540 | p->setpic(QImage(":/myImage/1.jpg")); 541 | ui->outlog->setText(QString("%1 join meeting").arg(QHostAddress(msg->ip).toString())); 542 | iplist.append(QString("@") + QHostAddress(msg->ip).toString()); 543 | ui->plainTextEdit->setCompleter(iplist); 544 | } 545 | } 546 | else if(msg->msg_type == PARTNER_EXIT) 547 | { 548 | removePartner(msg->ip); 549 | if(mainip == msg->ip) 550 | { 551 | ui->mainshow_label->setPixmap(QPixmap::fromImage(QImage(":/myImage/1.jpg").scaled(ui->mainshow_label->size()))); 552 | } 553 | if(iplist.removeOne(QString("@") + QHostAddress(msg->ip).toString())) 554 | { 555 | ui->plainTextEdit->setCompleter(iplist); 556 | } 557 | else 558 | { 559 | qDebug() << QHostAddress(msg->ip).toString() << "not exist"; 560 | WRITE_LOG("%s not exist",QHostAddress(msg->ip).toString().toStdString().c_str()); 561 | } 562 | ui->outlog->setText(QString("%1 exit meeting").arg(QHostAddress(msg->ip).toString())); 563 | } 564 | else if (msg->msg_type == CLOSE_CAMERA) 565 | { 566 | closeImg(msg->ip); 567 | } 568 | else if (msg->msg_type == PARTNER_JOIN2) 569 | { 570 | uint32_t ip; 571 | int other = msg->len / sizeof(uint32_t), pos = 0; 572 | for (int i = 0; i < other; i++) 573 | { 574 | memcpy_s(&ip, sizeof(uint32_t), msg->data + pos , sizeof(uint32_t)); 575 | pos += sizeof(uint32_t); 576 | Partner* p = addPartner(ip); 577 | if (p) 578 | { 579 | p->setpic(QImage(":/myImage/1.jpg")); 580 | iplist << QString("@") + QHostAddress(ip).toString(); 581 | } 582 | } 583 | ui->plainTextEdit->setCompleter(iplist); 584 | ui->openVedio->setDisabled(false); 585 | } 586 | else if(msg->msg_type == RemoteHostClosedError) 587 | { 588 | 589 | clearPartner(); 590 | _mytcpSocket->disconnectFromHost(); 591 | _mytcpSocket->wait(); 592 | ui->outlog->setText(QString("关闭与服务器的连接")); 593 | ui->createmeetBtn->setDisabled(true); 594 | ui->exitmeetBtn->setDisabled(true); 595 | ui->connServer->setDisabled(false); 596 | ui->joinmeetBtn->setDisabled(true); 597 | //清除聊天记录 598 | while(ui->listWidget->count() > 0) 599 | { 600 | QListWidgetItem *item = ui->listWidget->takeItem(0); 601 | ChatMessage *chat = (ChatMessage *)ui->listWidget->itemWidget(item); 602 | delete item; 603 | delete chat; 604 | } 605 | iplist.clear(); 606 | ui->plainTextEdit->setCompleter(iplist); 607 | if(_createmeet || _joinmeet) QMessageBox::warning(this, "Meeting Information", "会议结束" , QMessageBox::Yes, QMessageBox::Yes); 608 | } 609 | else if(msg->msg_type == OtherNetError) 610 | { 611 | QMessageBox::warning(NULL, "Network Error", "网络异常" , QMessageBox::Yes, QMessageBox::Yes); 612 | clearPartner(); 613 | _mytcpSocket->disconnectFromHost(); 614 | _mytcpSocket->wait(); 615 | ui->outlog->setText(QString("网络异常......")); 616 | } 617 | if(msg->data) 618 | { 619 | free(msg->data); 620 | msg->data = NULL; 621 | } 622 | if(msg) 623 | { 624 | free(msg); 625 | msg = NULL; 626 | } 627 | } 628 | 629 | Partner* Widget::addPartner(quint32 ip) 630 | { 631 | if (partner.contains(ip)) return NULL; 632 | Partner *p = new Partner(ui->scrollAreaWidgetContents ,ip); 633 | if (p == NULL) 634 | { 635 | qDebug() << "new Partner error"; 636 | return NULL; 637 | } 638 | else 639 | { 640 | connect(p, SIGNAL(sendip(quint32)), this, SLOT(recvip(quint32))); 641 | partner.insert(ip, p); 642 | ui->verticalLayout_3->addWidget(p, 1); 643 | 644 | //当有人员加入时,开启滑动条滑动事件,开启输入(只有自己时,不打开) 645 | if (partner.size() > 1) 646 | { 647 | connect(this, SIGNAL(volumnChange(int)), _ainput, SLOT(setVolumn(int)), Qt::UniqueConnection); 648 | connect(this, SIGNAL(volumnChange(int)), _aoutput, SLOT(setVolumn(int)), Qt::UniqueConnection); 649 | ui->openAudio->setDisabled(false); 650 | ui->sendmsg->setDisabled(false); 651 | _aoutput->startPlay(); 652 | } 653 | return p; 654 | } 655 | } 656 | 657 | void Widget::removePartner(quint32 ip) 658 | { 659 | if(partner.contains(ip)) 660 | { 661 | Partner *p = partner[ip]; 662 | disconnect(p, SIGNAL(sendip(quint32)), this, SLOT(recvip(quint32))); 663 | ui->verticalLayout_3->removeWidget(p); 664 | delete p; 665 | partner.remove(ip); 666 | 667 | //只有自已一个人时,关闭传输音频 668 | if (partner.size() <= 1) 669 | { 670 | disconnect(_ainput, SLOT(setVolumn(int))); 671 | disconnect(_aoutput, SLOT(setVolumn(int))); 672 | _ainput->stopCollect(); 673 | _aoutput->stopPlay(); 674 | ui->openAudio->setText(QString(OPENAUDIO).toUtf8()); 675 | ui->openAudio->setDisabled(true); 676 | } 677 | } 678 | } 679 | 680 | void Widget::clearPartner() 681 | { 682 | ui->mainshow_label->setPixmap(QPixmap()); 683 | if(partner.size() == 0) return; 684 | 685 | QMap::iterator iter = partner.begin(); 686 | while (iter != partner.end()) 687 | { 688 | quint32 ip = iter.key(); 689 | iter++; 690 | Partner *p = partner.take(ip); 691 | ui->verticalLayout_3->removeWidget(p); 692 | delete p; 693 | p = nullptr; 694 | } 695 | 696 | //关闭传输音频 697 | disconnect(_ainput, SLOT(setVolumn(int))); 698 | disconnect(_aoutput, SLOT(setVolumn(int))); 699 | //关闭音频播放与采集 700 | _ainput->stopCollect(); 701 | _aoutput->stopPlay(); 702 | ui->openAudio->setText(QString(CLOSEAUDIO).toUtf8()); 703 | ui->openAudio->setDisabled(true); 704 | 705 | 706 | //关闭图片传输线程 707 | if(_imgThread->isRunning()) 708 | { 709 | _imgThread->quit(); 710 | _imgThread->wait(); 711 | } 712 | ui->openVedio->setText(QString(OPENVIDEO).toUtf8()); 713 | ui->openVedio->setDisabled(true); 714 | } 715 | 716 | void Widget::recvip(quint32 ip) 717 | { 718 | if (partner.contains(mainip)) 719 | { 720 | Partner* p = partner[mainip]; 721 | p->setStyleSheet("border-width: 1px; border-style: solid; border-color:rgba(0, 0 , 255, 0.7)"); 722 | } 723 | if (partner.contains(ip)) 724 | { 725 | Partner* p = partner[ip]; 726 | p->setStyleSheet("border-width: 1px; border-style: solid; border-color:rgba(255, 0 , 0, 0.7)"); 727 | } 728 | ui->mainshow_label->setPixmap(QPixmap::fromImage(QImage(":/myImage/1.jpg").scaled(ui->mainshow_label->size()))); 729 | mainip = ip; 730 | ui->groupBox_2->setTitle(QHostAddress(mainip).toString()); 731 | qDebug() << ip; 732 | } 733 | 734 | /* 735 | * 加入会议 736 | */ 737 | 738 | void Widget::on_joinmeetBtn_clicked() 739 | { 740 | QString roomNo = ui->meetno->text(); 741 | 742 | QRegExp roomreg("^[1-9][0-9]{1,4}$"); 743 | QRegExpValidator roomvalidate(roomreg); 744 | int pos = 0; 745 | if(roomvalidate.validate(roomNo, pos) != QValidator::Acceptable) 746 | { 747 | QMessageBox::warning(this, "RoomNo Error", "房间号不合法" , QMessageBox::Yes, QMessageBox::Yes); 748 | } 749 | else 750 | { 751 | //加入发送队列 752 | emit PushText(JOIN_MEETING, roomNo); 753 | } 754 | } 755 | 756 | void Widget::on_horizontalSlider_valueChanged(int value) 757 | { 758 | emit volumnChange(value); 759 | } 760 | 761 | void Widget::speaks(QString ip) 762 | { 763 | ui->outlog->setText(QString(ip + " 正在说话").toUtf8()); 764 | } 765 | 766 | void Widget::on_sendmsg_clicked() 767 | { 768 | QString msg = ui->plainTextEdit->toPlainText().trimmed(); 769 | if(msg.size() == 0) 770 | { 771 | qDebug() << "empty"; 772 | return; 773 | } 774 | qDebug()<plainTextEdit->setPlainText(""); 776 | QString time = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); 777 | ChatMessage *message = new ChatMessage(ui->listWidget); 778 | QListWidgetItem *item = new QListWidgetItem(); 779 | dealMessageTime(time); 780 | dealMessage(message, item, msg, time, QHostAddress(_mytcpSocket->getlocalip()).toString() ,ChatMessage::User_Me); 781 | emit PushText(TEXT_SEND, msg); 782 | ui->sendmsg->setDisabled(true); 783 | } 784 | 785 | void Widget::dealMessage(ChatMessage *messageW, QListWidgetItem *item, QString text, QString time, QString ip ,ChatMessage::User_Type type) 786 | { 787 | ui->listWidget->addItem(item); 788 | messageW->setFixedWidth(ui->listWidget->width()); 789 | QSize size = messageW->fontRect(text); 790 | item->setSizeHint(size); 791 | messageW->setText(text, time, size, ip, type); 792 | ui->listWidget->setItemWidget(item, messageW); 793 | } 794 | 795 | void Widget::dealMessageTime(QString curMsgTime) 796 | { 797 | bool isShowTime = false; 798 | if(ui->listWidget->count() > 0) { 799 | QListWidgetItem* lastItem = ui->listWidget->item(ui->listWidget->count() - 1); 800 | ChatMessage* messageW = (ChatMessage *)ui->listWidget->itemWidget(lastItem); 801 | int lastTime = messageW->time().toInt(); 802 | int curTime = curMsgTime.toInt(); 803 | qDebug() << "curTime lastTime:" << curTime - lastTime; 804 | isShowTime = ((curTime - lastTime) > 60); // 两个消息相差一分钟 805 | // isShowTime = true; 806 | } else { 807 | isShowTime = true; 808 | } 809 | if(isShowTime) { 810 | ChatMessage* messageTime = new ChatMessage(ui->listWidget); 811 | QListWidgetItem* itemTime = new QListWidgetItem(); 812 | ui->listWidget->addItem(itemTime); 813 | QSize size = QSize(ui->listWidget->width() , 40); 814 | messageTime->resize(size); 815 | itemTime->setSizeHint(size); 816 | messageTime->setText(curMsgTime, curMsgTime, size); 817 | ui->listWidget->setItemWidget(itemTime, messageTime); 818 | } 819 | } 820 | 821 | void Widget::textSend() 822 | { 823 | qDebug() << "send text over"; 824 | QListWidgetItem* lastItem = ui->listWidget->item(ui->listWidget->count() - 1); 825 | ChatMessage* messageW = (ChatMessage *)ui->listWidget->itemWidget(lastItem); 826 | messageW->setTextSuccess(); 827 | ui->sendmsg->setDisabled(false); 828 | } 829 | -------------------------------------------------------------------------------- /widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Widget 4 | 5 | 6 | true 7 | 8 | 9 | 10 | 0 11 | 0 12 | 1296 13 | 784 14 | 15 | 16 | 17 | 18 | 0 19 | 0 20 | 21 | 22 | 23 | 24 | 900 25 | 600 26 | 27 | 28 | 29 | 云会议 30 | 31 | 32 | 33 | 3 34 | 35 | 36 | 3 37 | 38 | 39 | 3 40 | 41 | 42 | 3 43 | 44 | 45 | 3 46 | 47 | 48 | 49 | 50 | 9 51 | 52 | 53 | 5 54 | 55 | 56 | 6 57 | 58 | 59 | 5 60 | 61 | 62 | 3 63 | 64 | 65 | 66 | 67 | 68 | 0 69 | 0 70 | 71 | 72 | 73 | ArrowCursor 74 | 75 | 76 | 加入会议 77 | 78 | 79 | 80 | 3 81 | 82 | 83 | 3 84 | 85 | 86 | 3 87 | 88 | 89 | 3 90 | 91 | 92 | 3 93 | 94 | 95 | 96 | 97 | 会议编号: 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | ArrowCursor 108 | 109 | 110 | 加入 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 0 122 | 0 123 | 124 | 125 | 126 | ArrowCursor 127 | 128 | 129 | 创建会议 130 | 131 | 132 | 133 | 3 134 | 135 | 136 | 3 137 | 138 | 139 | 3 140 | 141 | 142 | 3 143 | 144 | 145 | 3 146 | 147 | 148 | 149 | 150 | ArrowCursor 151 | 152 | 153 | 创建 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 0 165 | 0 166 | 167 | 168 | 169 | ArrowCursor 170 | 171 | 172 | 退出会议 173 | 174 | 175 | 176 | 3 177 | 178 | 179 | 3 180 | 181 | 182 | 3 183 | 184 | 185 | 3 186 | 187 | 188 | 3 189 | 190 | 191 | 192 | 193 | ArrowCursor 194 | 195 | 196 | 退出 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 连接服务器 207 | 208 | 209 | 210 | 3 211 | 212 | 213 | 3 214 | 215 | 216 | 3 217 | 218 | 219 | 3 220 | 221 | 222 | 3 223 | 224 | 225 | 226 | 227 | IP: 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 0 236 | 0 237 | 238 | 239 | 240 | 241 | 0 242 | 0 243 | 244 | 245 | 246 | 192.168.1.126 247 | 248 | 249 | 250 | 251 | 252 | 253 | Port: 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 0 262 | 0 263 | 264 | 265 | 266 | 267 | 300 268 | 16777215 269 | 270 | 271 | 272 | 8888 273 | 274 | 275 | 276 | 277 | 278 | 279 | 连接 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 1 292 | 293 | 294 | 295 | 296 | 297 | 0 298 | 0 299 | 300 | 301 | 302 | 303 | 400 304 | 0 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 255 314 | 0 315 | 0 316 | 317 | 318 | 319 | 320 | 321 | 322 | 255 323 | 0 324 | 0 325 | 326 | 327 | 328 | 329 | 330 | 331 | 255 332 | 0 333 | 0 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 255 343 | 0 344 | 0 345 | 346 | 347 | 348 | 349 | 350 | 351 | 255 352 | 0 353 | 0 354 | 355 | 356 | 357 | 358 | 359 | 360 | 255 361 | 0 362 | 0 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 120 372 | 120 373 | 120 374 | 375 | 376 | 377 | 378 | 379 | 380 | 120 381 | 120 382 | 120 383 | 384 | 385 | 386 | 387 | 388 | 389 | 0 390 | 0 391 | 0 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | QTabWidget::Rounded 400 | 401 | 402 | 1 403 | 404 | 405 | true 406 | 407 | 408 | 409 | 副屏幕 410 | 411 | 412 | 413 | 0 414 | 415 | 416 | 0 417 | 418 | 419 | 0 420 | 421 | 422 | 0 423 | 424 | 425 | 0 426 | 427 | 428 | 429 | 430 | 0 431 | 432 | 433 | true 434 | 435 | 436 | 437 | 438 | 0 439 | 0 440 | 392 441 | 585 442 | 443 | 444 | 445 | 446 | 0 447 | 0 448 | 449 | 450 | 451 | 452 | 3 453 | 454 | 455 | 0 456 | 457 | 458 | 0 459 | 460 | 461 | 0 462 | 463 | 464 | 0 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 0 476 | 0 477 | 478 | 479 | 480 | 聊天窗口 481 | 482 | 483 | 484 | 2 485 | 486 | 487 | 0 488 | 489 | 490 | 0 491 | 492 | 493 | 0 494 | 495 | 496 | 0 497 | 498 | 499 | 500 | 501 | Qt::NoFocus 502 | 503 | 504 | Qt::ScrollBarAlwaysOff 505 | 506 | 507 | QAbstractScrollArea::AdjustToContents 508 | 509 | 510 | 0 511 | 512 | 513 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignRight|Qt::AlignTrailing 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 0 522 | 0 523 | 524 | 525 | 526 | 527 | 16777215 528 | 100 529 | 530 | 531 | 532 | 533 | 0 534 | 535 | 536 | 0 537 | 538 | 539 | 0 540 | 541 | 542 | 0 543 | 544 | 545 | 0 546 | 547 | 548 | 549 | 550 | 551 | 0 552 | 0 553 | 554 | 555 | 556 | 557 | 16777215 558 | 80 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 0 568 | 0 569 | 0 570 | 571 | 572 | 573 | 574 | 575 | 576 | 0 577 | 0 578 | 0 579 | 580 | 581 | 582 | 583 | 584 | 585 | 0 586 | 0 587 | 0 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 0 597 | 0 598 | 0 599 | 600 | 601 | 602 | 603 | 604 | 605 | 0 606 | 0 607 | 0 608 | 609 | 610 | 611 | 612 | 613 | 614 | 0 615 | 0 616 | 0 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 120 626 | 120 627 | 120 628 | 629 | 630 | 631 | 632 | 633 | 634 | 120 635 | 120 636 | 120 637 | 638 | 639 | 640 | 641 | 642 | 643 | 0 644 | 0 645 | 0 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | Microsoft YaHei 655 | 12 656 | 657 | 658 | 659 | 输入@可以向对应的IP发送数据 660 | 661 | 662 | 663 | 664 | 665 | 666 | 发送 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 0 682 | 0 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 255 692 | 0 693 | 0 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 255 703 | 0 704 | 0 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 120 714 | 120 715 | 120 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 主屏幕 724 | 725 | 726 | 727 | 0 728 | 729 | 730 | 0 731 | 732 | 733 | 0 734 | 735 | 736 | 0 737 | 738 | 739 | 0 740 | 741 | 742 | 743 | 744 | 745 | 0 746 | 0 747 | 748 | 749 | 750 | 751 | 752 | 753 | false 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 0 769 | 0 770 | 771 | 772 | 773 | 视频 774 | 775 | 776 | 777 | 778 | 779 | 开启摄像头 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 音频 790 | 791 | 792 | 793 | 794 | 795 | 796 | 0 797 | 0 798 | 799 | 800 | 801 | 开启音频 802 | 803 | 804 | 805 | 806 | 807 | 808 | 音量: 809 | 810 | 811 | 812 | 813 | 814 | 815 | Qt::Horizontal 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 0 829 | 0 830 | 831 | 832 | 833 | 输出日志 834 | 835 | 836 | 837 | 6 838 | 839 | 840 | 9 841 | 842 | 843 | 9 844 | 845 | 846 | 9 847 | 848 | 849 | 9 850 | 851 | 852 | 853 | 854 | 855 | 0 856 | 0 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 255 866 | 0 867 | 0 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 255 877 | 0 878 | 0 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 120 888 | 120 889 | 120 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | MyTextEdit 909 | QPlainTextEdit 910 |
mytextedit.h
911 |
912 |
913 | 914 | 915 |
916 | --------------------------------------------------------------------------------