一、如何在QT下利用网络传输视频/图片?

1.获取视频:

由于我是在正点原子的阿尔法开发板上使用OV2460而这个摄像头不是usb驱动,使用QT的QCarema类不能驱动,所以我还是像前面的震动检测项目一样使用Opencv来获取摄像头数据。

创建一个继承与QObject的类,并在类中利用opencv打开摄像头,利用定时器将一帧帧图片的QImage发送出去:

打开摄像头:

Camera::Camera(QObject *parent) :
    QObject(parent)
{
    /* 实例化 */
    capture = new cv::VideoCapture();
    timer = new QTimer(this);
    /* 信号槽连接 */
    connect(timer, SIGNAL(timeout()), this, SLOT(OnTimeOut()));
}

发送QImage:

void Camera::OnTimeOut()
{
    /* 如果摄像头没有打开,停止定时器,返回 */
    if (!capture->isOpened()) {
        timer->stop();
        return;
    }
    static cv :: Mat frame;
    *capture >> frame;
    if (frame.cols)
    {
        /* 发送图片信号 */
        emit readyImage(matToQImage(frame));
    }
}
2.传输视频:

前面我们通过实现网络聊天室已经解决了网络的问题。现在的问题是我们该如何利用网络传输视频(其实也是一帧一帧的图片)

通过搜索发现可以使用Qimage->QByteArray的办法:

//QImage->QByteArray 服务端
    QByteArray byte1;
    QBuffer buff(&byt1);
    img->save(&buff,"jpeg",-1);
	//QByteArray base64Byte = byte.toBase64(); 
	//发送byte
//QByteArray->QImage 客户端
	QByteArray byte2;
	//decbyte = QByteArray::fromBase64(base64Byte);
	QImage image;
	image.loadFromData(byte2,"jpeg");

注:其实要不要base64加密都可以。并且一定要转化成jpeg格式,数据量更少,如果转化成png那么就需要传输较大量数据照成卡顿。

2.尝试时发生的错误:(沾包)

按道理上面的方法能够实现客户端与服务端视频文件的传输,但是事实往往不是那么简单。

尝试之后发现根本不行,原因在于:

一帧视频文件的Qimage高达90k字节。而一包TCP最多传输的字节远远小于这个数据,而这就要开启,TCP默认的分包发送机制了。

但是又有一个问题出现了:

由于我们客户端时要对服务端传过来的数据进行解码也就是转化回Qimage的,而不完整的QByteArray转化回Qimage再渲染这个肯定会报错!!!

但是问题在于我们根本不知道服务端那边发完一整帧的QByteArray要几次。

解决方法:

我们可以在服务端发送前将每一帧的QByteArray后面加上我们需要的标识符,在客户端那边通过识别到这个标识符就可以知道一帧已经结束,可以转化成QImage进行渲染了。

server:
void MainWindow::TranImage(const QImage &image)
{
    QByteArray byte;
    QBuffer buffer(&byte);
    image.save(&buffer,"jpeg");
    //视频数据标识符
    byte.append("#$#$#");
    sendData(byte);
}
Client:
//获取视频
void MainWindow::GetVideo(void)
{
    QByteArray ImageArr;
    QImage WholeImage;
    //接受视频
    for(qint64 i=0;i<AllByte.count()-4;++i)
    {
        if(AllByte.at(i)=='#'&&AllByte.at(i+1)=='$'&&AllByte.at(i+2)=='#'&&AllByte.at(i+3)=='$'
                &&AllByte.at(i+4)=='#')
        {
           ImageArr = AllByte.left(i);
           AllByte.remove(0,i+5);
           WholeImage.loadFromData(ImageArr,"jpeg");
           ui->showlabel->setScaledContents(true);
           ui->showlabel->setPixmap(QPixmap::fromImage(WholeImage));
        }
    }
}

二、如何将聊天信息与视频信息分开

由于我们也要有聊天的功能,而聊天发送的信息也是QByteArray,视频信息也是QByteArray。

解决办法:

我们可以在服务端接收客户端的聊天信息后再将这个QByteArray的两端加上聊天信息专有的标识符,而客户端可以根据这个标识符提取出聊天消息。

注意:在客户端提取出聊天消息后还需要将标识符以及聊天消息一并删除掉,以免影响到后面的视频解析。

server:
//主线程接受客户端消息
void MainWindow::readData(const QByteArray& data)
{
    //显示聊天消息
    ui->listWidget->scrollToBottom();
    ui->listWidget->addItem(data);

    //将聊天消息插入到数据库中
    InsertDB(data);

    //发送消息
    QByteArray Send;
    //聊天消息标识符
    Send = "@!@!@"+data+"@!@!@";
    sendData(Send);
}
Client:
void MainWindow::GetChat(void)
{
    QByteArray CharArr;
    //先提取聊天消息
    for(qint64 i=0,left=0,right=0;i<AllByte.count()-5;++i)
    {
        if(AllByte.at(i)=='@'&&AllByte.at(i+1)=='!'&&AllByte.at(i+2)=='@'&&AllByte.at(i+3)=='!'
                &&AllByte.at(i+4)=='@')
        {
            left = i+5;
            do
            {
              ++i;
            }
            while(!(AllByte.at(i)=='@'&&AllByte.at(i+1)=='!'&&AllByte.at(i+2)=='@'&&AllByte.at(i+3)=='!'
            &&AllByte.at(i+4)=='@')||i==AllByte.count()-4);

            if(AllByte.at(i)=='@'&&AllByte.at(i+1)=='!'&&AllByte.at(i+2)=='@'&&AllByte.at(i+3)=='!'
                    &&AllByte.at(i+4)=='@')
            {
                right = i-1;
                CharArr = AllByte.mid(left,right-left+1);
                AllByte.remove(left-5,right-left+10);//将聊天消息移除
                ui->listWidget->scrollToBottom();
                ui->listWidget->addItem(CharArr);
            }
        }
    }
}

三、套用聊天室多线程发生的错误:

一开始想到要使用多线程的原因是因为考虑到了,如果视频传输是一个挺耗时的事情,如果开多个客户端那么一定会照成卡顿。事实也是如此,我在实现了视频传输的功能后进行测试,我开了5个客户端左右就很卡了。

但是当我按前面聊天室实现的多线程搬移到这里后,再进行测试发现,和原来每用多线程一样,开多个客户端还是会视频卡顿。

卡顿原因:

虽然有多个线程,但是由于主线程仍然还是一个线程,而我们要实现视频传输的任务还是得经过主线程,而我主线程发送视频数据得逻辑是,给每个线程发送一个。这样当然会照成卡顿,其实与单线程无异了,最重得视频传输任务还得一个一个发送给每个客户端。

//转发消息给所有客户端
for(int i=0;i<ClientList.size();++i)
{
	emit sendData(ClientList.at(i),data);
}
解决方法:

之所以要给每个线程发送一遍数据的原因还是在于这里:

void MySocket::sendData(int id, const QByteArray &data)
{
    if (id == m_sockDesc && !data.isEmpty()) {
        this->write(data);
    }
}

在最底层的MySocket中要进行id判断,其实大可不必,因为在建立连接时,每个线程都与main建立了连接,而只要主线程发送一个信号,那么所有与主线程建立连接的线程都应该能够收到相应的信号。

最终改为:
void MySocket::sendData(const QByteArray &data)
{
    if (!data.isEmpty()) {
        this->write(data);
    }
}

传输视频:

void MainWindow::TranImage(const QImage &image)
{
    QByteArray byte;
    QBuffer buffer(&byte);
    image.save(&buffer,"jpeg");
    //视频数据标识符
    byte.append("#$#$#");
    emit sendData(byte);//只需要传输一遍消息
}

传输聊天信息:

//主线程接受客户端消息
void MainWindow::readData(const QByteArray& data)
{
    //显示聊天消息
    ui->listWidget->scrollToBottom();
    ui->listWidget->addItem(data);

    //将聊天消息插入到数据库中
    InsertDB(data);

    //发送消息
    QByteArray Send;
    //聊天消息标识符
    Send = "@!@!@"+data+"@!@!@";
    emit sendData(Send);//只需要传输一遍消息
}

四、聊天信息存储到数据库

其实一开始我还走了一点弯路,因为我一开始就像到利用Mysql而QT与Mysql连接前还需要编译Mysql的驱动。十分麻烦。

最后我突然看到了sqlite,这个数据库这是适用于嵌入式的轻量级数据库,而且是QT自己的,我们不需要再进行配置,走起。

在这里插入图片描述

QT利用数据库,将聊天信息加入:
头文件
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
MainWindow
//安装sqlite驱动
sqlDatabase=QSqlDatabase::addDatabase("QSQLITE","chat");//第二个参数表示链接标识符
//设置数据库名
sqlDatabase.setDatabaseName("chat.db");
//打开数据库
sqlDatabase.open();
//创建表格
QSqlQuery sql_query(sqlDatabase);
sql_query.exec("create table chat(name char , content char)");
readData中
void MainWindow::InsertDB(void)
{
    //数据库中的标项
    QString name;
    QString content;
    //将聊天消息记录到数据库中
    QSqlQuery sql_query(sqlDatabase);
    for(int i=0;i<data.count();++i)
    {
        if(data.at(i)==':')
        {
            name = QString::fromUtf8(data.left(i));
            content = QString::fromUtf8(data.right(data.count()-i-1));
            break;
        }
    }
    QString insert = QString("insert into chat values('%1','%2')").arg(name).arg(content);
    //插入数据
    sql_query.exec(insert);
}
点击按钮清空表
void MainWindow::on_CleardbButton_clicked()
{
    //清空表格
    QSqlQuery sql_query(sqlDatabase);
    sql_query.exec("delete from chat");
}
命令行下操作sqlite3:
系统命令(以点开头)

1.列出当前使用的数据库

.database

2.帮助

.help

3.退出

.exit

4.列出创建的表格

.schema

5.打开数据库/创建数据库

.open xx.db

sql命令(要以;结尾)

1.建库(**一定要先建库 **)

1.进入时
	sqlite3 xx.db
2.进入后.open xx.db

2.建表(一定要在库中建表,不然会丢失)

create table stu(id Integer , name char , score Integer);

3.插入数据

1.插入全部数据
	insert into stu values(1001,'cww',100);
2.插入部分数据
	insert into stu(name ,score)values("hktk",99);

4.查看表格

1.查看所有字段
	select * from stu;
2.查看部分字段
	select name from stu;
3.条件查询
	select * from stu where score=90 and id='cww';
	select * from stu where score=90 or id='cww';

5.删除表格数据

delete from stu where xx;

6.更改数据

update stu set name='hktk',score=99 where id=1001;

7.删除表格

drop table stu;

最终在命令行下检查,发现确实建了表并且存储了数据。至此项目完毕!


源码地址:(在gittee下载)

https://gitee.com/hktk-cww/qt-tcp-carema.git
https://gitee.com/hktk-cww/qt-tcp-carema.git

更多推荐