音视频项目-异地情侣影院-可写简历
视频讲解及源码领取:https://www.bilibili.com/video/BV1PSareYE9L/
1 编译器环境
- 可以正常编译运行的环境:QT6.5.3和QT6.6.3 mingw64编译器
- 有异常的环境:QT6.7有bug,不能正常初始化MediaPlayer
2 项目框架图
这里我们重点讲解播放链接推送、播放控制推送和文字聊天推送,语音聊天大家可以自行研究。
server有QList<QTcpSocket*> clients; 保存客户端列表。
以play播放命令为例,播放器1⻆色为server和client1,由播放器1发起play播放命令,则流程如下所示(播放器2也一样可以发起play播放命令)
3 信令分析
信令主要类型:
src:播放源推送
- 网络流链接推送:src+net+url
- 本地文件路径推送:src+lcl+ur
snc:播放控制命令
- 播放命令 snc+play+position,范例: sncplay12693
- 暂停命令 snc+paus+position,范例: sncpaus12693
- 跳转命令 snc+seek+position,范例: sncseek12693
- 同步命令 snc+sync+position,范例: sncsync12693,每秒server发送一次给所有client
- 停止命令 snc+stop,范例: sncstop
msg:文字聊天
- 文字聊天推送:msg+username: + content
后续的讲解,只讲解client的推送和接收数据,server直接参考前面项目框架图的讲解。
3.1 播放链接推送
src:播放链接
1.推送网络流链接命令src+net+url,范例:
srcnethttps://stream7.iqilu.com/10339/upload_transcode/202002/09/2020020910490
2N3v5Vpxuvb.mp4
2.推送本地文件链接命令src+lcl+url,此时需要各个端本地有对应的文件路径,范例:
srclcl/E:/rec_video/00-音视频高级教程-课程简介.mp
这里我们只讲解网络流链接。
推送
操作:文件->打开链接->触发
void MainWindow::on_actionOpen_URL_triggered()
{
openURL* urlWindow = new openURL(this); //退出对话框
urlWindow->setWindowTitle("Open URL");
urlWindow->show();
// 绑定信号槽,如果对话框点击ok将触发setVideoSource函数的调用
connect(urlWindow, SIGNAL(urlSet(QString)), this, SLOT(setVideoSource(QString)));
}
这个是弹出的对话框
在对话框输入可以播放的url,然后点击ok,点击ok后触发openURL::on_openURLbuttonBox_accepted(),在该函数里获取url并发送信号urlSet(url)。
void openURL::on_openURLbuttonBox_accepted()
{
QString url = ui->urlInput->text();
emit urlSet(url);
}
MainWindow::setVideoSource()响应,这里把url播放链接推送给所有的client。
void MainWindow::setVideoSource(QString url)
{
qDebug() << "MainWindow::setVideoSource url: " << url;
if (client) client->writeToServer(url, "srcnet");
else player->setSource(QUrl(url));
}
接收
接收端
connect(client, &Client::remoteSetVideoSource, this,
&MainWindow::remoteSetVideoSource);
void Client::readFromServer()
{
while (socket->canReadLine())
{
QByteArray buffer = socket->readLine();
QString header = buffer.mid(0, 3);
QString content = buffer.mid(3).trimmed();
qInfo() << "Client::readFromServer -- header: " << header << ", content: " << content;
if (header == "snc")
{
..........
}
else if (header == "msg")
{
...........
}
else if (header == "src")
{
QString srcType = content.mid(0, 3);
content = content.mid(3);
if (srcType == "net") //⽹络流
{
emit remoteSetVideoSource(content);
}
if (srcType == "lcl") //本地⽂件
{
emit remoteSetLocalVideoSource(content);
}
emit newChatMsg("Souce set:" + content);
}
MainWindow::remoteSetVideoSource
void MainWindow::remoteSetVideoSource(QString src)
{
player->setSource(QUrl(src));
}
3.2 播放控制推送
snc:播放控制
1. 播放命令 snc+play+position,范例: sncplay12693
2. 暂停命令 snc+paus+position,范例: sncpaus12693
3. 跳转命令 snc+seek+position,范例: sncseek12693
4. 同步命令 snc+sync+position,范例: sncsync12693,每秒server发送一次给所有client
5. 停止命令 snc+stop,范例: sncstop
播放、暂停、跳转、停止我们都很容易理解,重点是同步命令snc+sync+position,server怎么做的,client收到sync命令后又是怎么处理的
推送
难点在于同步推送,作为server的一端,每秒推送一次当前进度给其他client
server每秒获取一次当前播放进度并推送给所有的client
// 时间戳线程,server端定时将播放进度发送给所有client
void MainWindow::sendTimestampThreaded()
{
if (server)
{
QThread *sendTimestampThread = QThread::create(&MainWindow::sendTimestamp, this);
connect(this, &MainWindow::destroyed, sendTimestampThread, &QThread::quit);
connect(sendTimestampThread, &QThread::finished, sendTimestampThread, &QThread::deleteLater);
sendTimestampThread->start();
}
}
// server每秒获取当前播放进度推送给其他client
void MainWindow::sendTimestamp()
{
while(true)
{
// qInfo() << player->position();
server->writeToClients(QString::number(player->position()), "sncsync");
QObject().thread()->usleep(1000*1000*1); //every 1 second send sync info
}
}
接收
client收到snssync 同步信息
//client接收server的信息
void Client::readFromServer()
{
while (socket->canReadLine())
{
QByteArray buffer = socket->readLine();
QString header = buffer.mid(0, 3);
QString content = buffer.mid(3).trimmed();
qInfo() << "Client::readFromServer -- header: " << header << ", content: " << content;
if (header == "snc")
{
qInfo() << "Sync info from Server: " << content;
QString syncType = content.mid(0, 4);
qint64 position = content.mid(4).toLongLong();
if (syncType == "play")
{
emit remotePlay(position);
}
else if (syncType == "paus")
{
emit remotePause(position);
}
else if (syncType == "stop")
{
emit remoteStop();
}
else if (syncType == "seek")
{
emit remoteSeek(position);
}
else if (syncType == "sync")
{
emit remoteSync(position);
}
}
............
remoteSync 找到这个绑定的响应函数 MainWindow::remoteSync,具体的同步算法也很简单,误差200ms内不做处理,超过200ms则seek到对应的位置。
void MainWindow::remoteSync(qint64 position)
{
//误差200ms内不做处理
if (abs(player->position() - position) > 200) player->setPosition(position);
}
3.3 文字聊天推送
聊天消息命令:msg+username: + content,⽐如username为server,范例为msgserver: 来了,显示的时候只需要显示server: 来了
client 推送 -> server ,server通过tcp发送给每个client -> client接收。
这里我们只讲client 推送 和 client接收。
推送
void MainWindow::on_chatInput_returnPressed()
{
if (ui->chatInput->text() != "")
{
if (client) client->writeToServer(ui->chatInput->text(), "msg");
ui->chatInput->clear();
}
}
接收
client收到消息
void Client::readFromServer()
{
while (socket->canReadLine())
{
QByteArray buffer = socket->readLine();
QString header = buffer.mid(0, 3);
QString content = buffer.mid(3).trimmed();
qInfo() << "Client::readFromServer -- header: " << header << ", content: " << content;
if (header == "snc")
{
............
}
else if (header == "msg")
{
qInfo() << "Message from Server: " << content;
emit newChatMsg(content); //收到消息
}
.......
}
}
然后触发void MainWindow::newChatMsg(QString msg)函数的调用
void MainWindow::newChatMsg(QString msg)
{
QListWidgetItem* item = new QListWidgetItem(ui->chatWidget);
item->setText(msg);
ui->chatWidget->addItem(item); //显示当条消息
ui->chatWidget->scrollToBottom();
}
4 扩展思路
1. 当前server其实也是在一个播放端上的,可以考虑把server抽取出来部署到公网,此时就需要选择其中一个client作为master,由这个master 发送同步信息。
2. 将mediaplayer改成使用ffmpeg
3. 增加变速机制等
4. 支持播放列表拉取
5 语音聊天框架
待续,比较降噪
#简历被挂麻了,求建议##秋招##校招##c++##简历中的项目经历要怎么写#
深信服公司福利 896人发布