一、前言說明
近期收到幾個需求都是做音視頻通話,很多人會選擇用webrtc的方案,這個當然是個不錯的方案,但是依賴的東西太多,而且相關組件代碼量很大,開發難度大。所以最終選擇自己屬性的方案,那就是推流拉流,采集端負責采集本地攝像頭或者桌面,編碼推流到流媒體服務器,然后要拉取對方的視音頻,就是播放對應的rtsp地址即可,其實也可以是rtmp等地址,一般流媒體服務程序還提供各種http/flv等格式的拉流,方便各種場景需求,比如網頁上可以直接播放flv或者webrtc的流。
為了使得使用更方便,還特意增加了可自定義懸浮畫面位置,指定左上角、右上角、左下角、右下角、自定義位置和大小。還支持固定畫中畫功能,可交換主畫面和浮窗畫面,可設置畫面左右排列等布局方式。考慮到客戶的實際需求,還支持自定義水印,包括文字和圖片水印,支持多個水印,指定任意位置。
二、效果圖
三、相關代碼
#include "frmconfig.h"
#include "frmmain.h"
#include "ui_frmmain.h"
#include "qthelper.h"
#include "apphelper.h"
#include "osdgraph.h"
#include "ffmpegthread.h"
#include "ffmpegthreadcheck.h"frmMain::frmMain(QWidget *parent) : QWidget(parent), ui(new Ui::frmMain)
{ui->setupUi(this);this->initForm();this->installEventFilter(this);QMetaObject::invokeMethod(this, "formChanged", Qt::QueuedConnection);
}frmMain::~frmMain()
{delete ui;
}void frmMain::savePos()
{AppConfig::FormMax = this->isMaximized();if (!AppConfig::FormMax) {AppConfig::FormGeometry = this->geometry();}AppConfig::writeConfig();
}bool frmMain::eventFilter(QObject *watched, QEvent *event)
{//尺寸發生變化或者窗體移動位置記住窗體位置int type = event->type();if (type == QEvent::Resize || type == QEvent::Move) {QMetaObject::invokeMethod(this, "savePos", Qt::QueuedConnection);} else if (type == QEvent::Close) {audioInput->stop(false);audioOutput->stop(false);exit(0);}//尺寸發生變化重新調整小預覽窗體的位置if (this->isVisible() && type == QEvent::Resize) {this->formChanged();}return QWidget::eventFilter(watched, event);
}void frmMain::initForm()
{//初始化輸入輸出視頻控件int decodeType = AppConfig::DecodeType;AppHelper::initVideoWidget(ui->videoInput, decodeType);AppHelper::initVideoWidget(ui->videoOutput, decodeType);//初始化輸入輸出音頻線程audioInput = new FFmpegThread(this);audioOutput = new FFmpegThread(this);checkInput = new FFmpegThreadCheck(audioInput, this);AppHelper::initAudioThread(audioInput, ui->levelInput, decodeType);AppHelper::initAudioThread(audioOutput, ui->levelOutput, decodeType);//輸入打開成功后立即推流connect(audioInput, SIGNAL(receivePlayStart(int)), this, SLOT(receivePlayStart(int)));connect(ui->videoInput, SIGNAL(sig_receivePlayStart(int)), this, SLOT(receivePlayStart(int)));ui->ckInput->setChecked(AppConfig::MuteInput ? Qt::Checked : Qt::Unchecked);ui->ckOutput->setChecked(AppConfig::MuteOutput ? Qt::Checked : Qt::Unchecked);if (AppConfig::StartServer) {on_btnStart_clicked();}
}void frmMain::clearLevel()
{ui->levelInput->setLevel(0);ui->levelOutput->setLevel(0);
}void frmMain::formChanged()
{AppHelper::changeWidget(ui->videoInput, ui->videoOutput, ui->gridLayout, NULL);
}void frmMain::receivePlayStart(int time)
{QObject *obj = sender();if (obj == ui->videoInput) {
#ifdef betaversionOsdGraph::testOsd(ui->videoInput);
#endifui->videoInput->recordStart(AppConfig::VideoPush);} else if (obj == audioInput) {audioInput->recordStart(AppConfig::AudioPush);}
}void frmMain::on_btnStart_clicked()
{if (ui->btnStart->text() == "啟動服務") {if (AppConfig::VideoUrl == "video=" || AppConfig::AudioUrl == "audio=") {QtHelper::showMessageBoxError("請先打開系統設置, 選擇對應的視音頻設備");//return;}ui->videoInput->open(AppConfig::VideoUrl);ui->videoOutput->open(AppConfig::VideoPull);audioInput->setMediaUrl(AppConfig::AudioUrl);audioOutput->setMediaUrl(AppConfig::AudioPull);audioInput->play();audioOutput->play();checkInput->start();ui->btnStart->setText("停止服務");} else {ui->videoInput->stop();ui->videoOutput->stop();audioInput->stop();audioOutput->stop();checkInput->stop();ui->btnStart->setText("啟動服務");QMetaObject::invokeMethod(this, "clearLevel", Qt::QueuedConnection);}AppConfig::StartServer = (ui->btnStart->text() == "停止服務");AppConfig::writeConfig();
}void frmMain::on_btnConfig_clicked()
{static frmConfig *config = NULL;if (!config) {config = new frmConfig;connect(config, SIGNAL(formChanged()), this, SLOT(formChanged()));}config->show();config->activateWindow();
}void frmMain::on_ckInput_stateChanged(int arg1)
{bool muted = (arg1 != 0);audioInput->setMuted(muted);AppConfig::MuteInput = muted;AppConfig::writeConfig();
}void frmMain::on_ckOutput_stateChanged(int arg1)
{bool muted = (arg1 != 0);audioOutput->setMuted(muted);AppConfig::MuteOutput = muted;AppConfig::writeConfig();
}
四、相關地址
- 國內站點:https://gitee.com/feiyangqingyun
- 國際站點:https://github.com/feiyangqingyun
- 個人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
- 文件地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取碼:01jf 文件名:bin_video_call。
五、功能特點
- 支持局域網和外網音視頻實時通話,延遲極低,資源占用極低。
- 自動獲取本地所有視音頻輸入設備,本地攝像頭設備自動羅列所有支持的分辨率、幀率、采集格式等信息。
- 可以指定采集的視頻設備和音頻輸入設備,自由組合,視頻設備可以設置不同的分辨率、幀率、采集格式。
- 支持本地桌面屏幕作為視頻設備采集,支持多個屏幕,自動識別屏幕分辨率。
- 可以選擇不同的聲卡設備播放聲音。
- 內置自動重連機制,視音頻設備支持熱插拔。
- 支持固定畫中畫功能,可交換主畫面和浮窗畫面,可設置畫面左右排列等布局方式。
- 可自定義懸浮畫面位置,指定左上角、右上角、左下角、右下角、自定義位置和大小。
- 內置流媒體服務程序,程序啟動后自動啟動流媒體服務,自動推拉流。
- 視音頻流數據支持rtsp/rtmp/http/webrtc等方式拉流,可以直接網頁上打開視頻畫面。
- 實時顯示本地音頻振幅和遠程音量振幅,可以分別對輸入輸出音量設置靜音,方便測試。
- 支持自定義水印,包括文字和圖片水印,支持多個水印,指定任意位置。
- 支持不同的視音頻設備組合,比如本地攝像頭加電腦麥克風而不是攝像頭的麥克風,比如本地電腦桌面屏幕加攝像頭的麥克風等。
- 純Qt+ffmpeg編寫,支持windows和linux以及macos等系統,支持所有Qt版本、所有系統、所有編譯器。
- 支持嵌入式linux板子和樹莓派香橙派等,以及國產linux系統。