#include "av_recorder.h" #include #include #include AvRecorder::AvRecorder(QWidget* parent) : QWidget(parent) { setWindowTitle("Recorder"); m_settingsParam.audioParam.bitRate = 160'000; m_settingsParam.videoParam.bitRate = 8'000'000; m_settingsParam.videoParam.fps = 30; m_settingsParam.videoParam.name = Encoder::GetUsableEncoders().front(); m_settingsParam.outputDir = "."; m_settingsParam.liveUrl = "rtmp://192.168.3.76:1935/stream/V1"; m_settingsParam.liveName = "stream"; m_glWidget = new OpenGLVideoWidget(this); WgcCapturer::Init(); auto layout = new QVBoxLayout; auto hLayout = new QHBoxLayout; hLayout->addLayout(initAudioUi(), 2); hLayout->addLayout(initListUi(), 2); hLayout->addLayout(initOtherUi(), 1); initStatusBarUi(); updateCaptureList(); initConnect(); layout->addWidget(m_glWidget, 4); layout->addLayout(hLayout, 1); layout->addWidget(m_statusBar, 0); setLayout(layout); } void AvRecorder::initConnect() { connect(m_recordBtn, &QPushButton::released, [this] { if (!m_isRecord) { auto fileName = m_settingsParam.outputDir; if (fileName.back() != '\\') { fileName.push_back('\\'); } auto format = "mp4"; fileName += QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss").toStdString() + "." + format; // fileName += std::string("test.") + format; __CheckNo(startStream(fileName, format)); m_liveBtn->setEnabled(false); m_recordBtn->setText("停止录制"); } else { stopStream(); m_liveBtn->setEnabled(true); m_recordBtn->setText("开始录制"); } m_isRecord = !m_isRecord; }); connect(m_liveBtn, &QPushButton::released, [this] { if (!m_isLive) { auto fileName = m_settingsParam.liveUrl + "/" + m_settingsParam.liveName; bool isRtsp = m_settingsParam.liveUrl.find("rtsp") != std::string::npos; __CheckNo(startStream(fileName, isRtsp ? "rtsp" : "flv")); // 如果勾选了同步录像,则开始录像 if (m_syncRecordBox->isChecked()) { __CheckNo(startSyncRecord()); } m_recordBtn->setEnabled(false); m_liveBtn->setText("停止直播"); } else { // 先停止同步录像 stopSyncRecord(); // 再停止直播 stopStream(); m_recordBtn->setEnabled(true); m_liveBtn->setText("开始直播"); } m_isLive = !m_isLive; }); connect(m_microphoneWidget, &AudioWidget::SetVolumeScale, [this](float scale) { m_audioRecorder.SetVolumeScale(scale, MICROPHONE_INDEX); }); connect(m_speakerWidget, &AudioWidget::SetVolumeScale, [this](float scale) { m_audioRecorder.SetVolumeScale(scale, SPEAKER_INDEX); }); connect(m_updateListBtn, &QPushButton::released, [this] { updateCaptureList(); }); connect(m_captureListWidget, &QListWidget::currentTextChanged, [this](const QString& text) { if (text.isEmpty() || m_isLocked) { return; } m_isLocked = true; stopPreview(); stopCapture(); startCapture(VideoCapturer::WGC); startPreview(); m_isLocked = false; }); connect(m_isDrawCursorBox, &QCheckBox::stateChanged, [this] { m_videoRecorder.SetIsDrawCursor(m_isDrawCursorBox->isChecked()); }); connect(m_captureMethodBox, &QComboBox::currentTextChanged, [this](const QString& text) { if (m_isLocked || text.isEmpty()) { return; } stopPreview(); stopCapture(); if (text == "WGC") { startCapture(VideoCapturer::WGC); } else if (text == "DXGI") { startCapture(VideoCapturer::DXGI); } else { startCapture(VideoCapturer::GDI); } startPreview(); }); connect(m_settingsBtn, &QPushButton::released, [this] { auto settingsPage = std::make_unique(&m_settingsParam, this); settingsPage->exec(); m_isLocked = true; stopPreview(); stopCapture(); startCapture(VideoCapturer::WGC); startPreview(); m_isLocked = false; }); m_otherTimer.callOnTimeout([this] { if (windowState() == Qt::WindowMinimized) { return; } // 音频 auto info = m_audioRecorder.GetCaptureInfo(MICROPHONE_INDEX); m_microphoneWidget->ShowVolume(info == nullptr ? 0 : info->volume); info = m_audioRecorder.GetCaptureInfo(SPEAKER_INDEX); m_speakerWidget->ShowVolume(info == nullptr ? 0 : info->volume); // 状态栏 if (m_isRecord || m_isLive) { int interval = m_recordTime.secsTo(QTime::currentTime()); int sec = interval % 60; interval /= 60; int minute = interval % 60; int hour = interval / 60; m_captureTimeLabel->setText(QString("%1:%2:%3") .arg(hour, 2, 10, QChar('0')) .arg(minute, 2, 10, QChar('0')) .arg(sec, 2, 10, QChar('0'))); auto lossRate = m_videoRecorder.GetLossRate(); int num = lossRate * 10000; m_videolossRate->setText(QString("丢帧率: %1.%2%") .arg(num / 100, 2, 10, QChar('0')) .arg(num % 100, 2, 10, QChar('0'))); } else if (m_captureTimeLabel->text() != "00:00:00") { m_captureTimeLabel->setText("00:00:00"); } }); } AvRecorder::~AvRecorder() { stopSyncRecord(); stopStream(); stopPreview(); stopCapture(); WgcCapturer::Uninit(); } bool AvRecorder::start() { auto timer = new QTimer(this); connect(timer, &QTimer::timeout, [this, timer] { m_isLocked = true; stopPreview(); stopCapture(); startCapture(VideoCapturer::WGC); startPreview(); m_isLocked = false; timer->stop(); }); timer->start(100); return true; } void AvRecorder::startCapture(VideoCapturer::Method method) { if (m_isLocked) { m_captureMethodBox->clear(); m_captureMethodBox->addItem("WGC"); } QListWidgetItem* item = m_captureListWidget->currentItem(); if (!item) { return; } const QString type = item->data(Qt::UserRole + 1).toString(); // 判断是要捕获屏幕还是窗口 int idx = m_captureListWidget->currentRow(); if (idx < 0) { idx = 0; m_captureListWidget->setCurrentRow(idx); } int monitorCnt = (int) MonitorFinder::GetList().size(); if (idx < monitorCnt) { // 捕获屏幕 if (m_captureMethodBox->count() < 2) { m_captureMethodBox->addItem("DXGI"); } m_videoRecorder.Open(idx, m_settingsParam.videoParam, method); } else { if (m_captureMethodBox->count() < 2) { m_captureMethodBox->addItem("GDI"); } if (type == "window") { qintptr ptrHwnd = item->data(Qt::UserRole + 2).value(); if (::IsWindow((HWND) ptrHwnd)) { m_videoRecorder.Open((HWND) ptrHwnd, m_settingsParam.videoParam, method); } } // auto hwnd = WindowFinder::GetList()[idx - monitorCnt].hwnd; // m_videoRecorder.Open(hwnd, m_settingsParam.videoParam, method); } dealCapture(); m_isDrawCursorBox->setEnabled(true); m_recordBtn->setEnabled(true); m_liveBtn->setEnabled(true); m_videoRecorder.SetIsDrawCursor(m_isDrawCursorBox->isChecked()); m_audioRecorder.SetVolumeScale(m_microphoneWidget->GetVolume(), MICROPHONE_INDEX); m_audioRecorder.SetVolumeScale(m_speakerWidget->GetVolume(), SPEAKER_INDEX); } void AvRecorder::dealCapture() { __CheckNo(m_audioRecorder.Open({AudioCapturer::Microphone, AudioCapturer::Speaker}, m_settingsParam.audioParam)); m_microphoneWidget->setEnabled(m_audioRecorder.GetCaptureInfo(MICROPHONE_INDEX) != nullptr); m_speakerWidget->setEnabled(m_audioRecorder.GetCaptureInfo(SPEAKER_INDEX) != nullptr); m_fpsLabel->setText(QString("FPS: %1").arg(m_settingsParam.videoParam.fps)); m_videoEncodeLabel->setText(("编码器: " + m_settingsParam.videoParam.name).c_str()); } void AvRecorder::stopCapture() { m_videoRecorder.Close(); m_audioRecorder.Close(); } void AvRecorder::startPreview() { m_glWidget->Open(m_settingsParam.videoParam.width, m_settingsParam.videoParam.height); // 视频需要做到和帧率一样的渲染速度,QTimer 达不到要求 // 需要自己封装一个计时器 m_videoRenderTimer.Start(m_settingsParam.videoParam.fps, [this] { if (windowState() == Qt::WindowMinimized) { return; } // 视频 auto frame = m_videoRecorder.GetRenderFrame(); m_glWidget->Render(frame); }); // 刷新率设置为 25 m_otherTimer.start(40); } void AvRecorder::stopPreview() { m_videoRenderTimer.Stop(); m_otherTimer.stop(); } bool AvRecorder::startStream(std::string_view path, std::string_view format) { __CheckBool(m_avMuxer.Open(path, format)); __CheckBool(m_audioRecorder.LoadMuxer(m_avMuxer)); __CheckBool(m_videoRecorder.LoadMuxer(m_avMuxer)); __CheckBool(m_avMuxer.WriteHeader()); __CheckBool(m_audioRecorder.StartRecord()); __CheckBool(m_videoRecorder.StartRecord()); m_recordTime = QTime::currentTime(); m_captureStatusLabel->setText("状态: 正在工作"); m_settingsBtn->setEnabled(false); m_captureListWidget->setEnabled(false); m_updateListBtn->setEnabled(false); m_captureMethodBox->setEnabled(false); return true; } void AvRecorder::stopStream() { m_audioRecorder.StopRecord(); m_videoRecorder.StopRecord(); m_avMuxer.Close(); // 如果有同步录像,也需要关闭 if (m_isSyncRecord) { m_recordMuxer.Close(); m_isSyncRecord = false; } m_captureStatusLabel->setText("状态: 正常"); m_settingsBtn->setEnabled(true); m_captureListWidget->setEnabled(true); m_updateListBtn->setEnabled(true); m_captureMethodBox->setEnabled(true); } bool AvRecorder::startSyncRecord() { auto fileName = m_settingsParam.outputDir; if (fileName.back() != '\\') { fileName.push_back('\\'); } auto format = "mp4"; fileName += QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss").toStdString() + "_sync." + format; __CheckBool(m_recordMuxer.Open(fileName, format)); __CheckBool(m_audioRecorder.LoadMuxer(m_recordMuxer)); __CheckBool(m_videoRecorder.LoadMuxer(m_recordMuxer)); __CheckBool(m_recordMuxer.WriteHeader()); m_isSyncRecord = true; return true; } void AvRecorder::stopSyncRecord() { if (m_isSyncRecord) { m_recordMuxer.Close(); m_isSyncRecord = false; } } void AvRecorder::updateCaptureList() { m_captureListWidget->clear(); auto&& monitorList = MonitorFinder::GetList(true); for (auto&& monitor : monitorList) { QListWidgetItem* item = new QListWidgetItem("屏幕: " + QString::fromStdWString(monitor.title)); item->setData(Qt::UserRole + 1, "monitor"); item->setData(Qt::UserRole + 2, qintptr(monitor.monitor)); m_captureListWidget->addItem(item); } auto&& windowList = WindowFinder::GetList(true); for (auto&& window : windowList) { QListWidgetItem* item = new QListWidgetItem("窗口: " + QString::fromStdWString(window.title)); item->setData(Qt::UserRole + 1, "window"); item->setData(Qt::UserRole + 2, qintptr(window.hwnd)); m_captureListWidget->addItem(item); } } QVBoxLayout* AvRecorder::initListUi() { auto layout = new QVBoxLayout; m_captureListWidget = new QListWidget; layout->addWidget(m_captureListWidget); return layout; } QVBoxLayout* AvRecorder::initAudioUi() { m_microphoneWidget = new AudioWidget; m_speakerWidget = new AudioWidget; m_microphoneWidget->SetName("麦克风"); m_speakerWidget->SetName("扬声器"); auto layout = new QVBoxLayout; layout->addWidget(m_microphoneWidget); layout->addWidget(m_speakerWidget); return layout; } QVBoxLayout* AvRecorder::initOtherUi() { m_isDrawCursorBox = new QCheckBox("绘制鼠标指针"); m_isDrawCursorBox->setChecked(true); m_isDrawCursorBox->setEnabled(false); m_syncRecordBox = new QCheckBox("直播时同步录像"); m_syncRecordBox->setChecked(false); m_updateListBtn = new QPushButton("刷新窗口列表"); m_recordBtn = new QPushButton("开始录制"); m_recordBtn->setEnabled(false); m_liveBtn = new QPushButton("开始直播"); m_liveBtn->setEnabled(false); m_settingsBtn = new QPushButton("设置"); auto layout = new QVBoxLayout; layout->addWidget(m_isDrawCursorBox); layout->addWidget(m_syncRecordBox); layout->addWidget(m_updateListBtn); layout->addWidget(m_recordBtn); layout->addWidget(m_liveBtn); layout->addWidget(m_settingsBtn); return layout; } void AvRecorder::initStatusBarUi() { m_videoEncodeLabel = new QLabel; auto hLayout = new QHBoxLayout; hLayout->setContentsMargins(0, 0, 0, 0); hLayout->addWidget(new QLabel("捕获方式:")); m_captureMethodBox = new QComboBox; hLayout->addWidget(m_captureMethodBox); m_captureStatusLabel = new QLabel("状态: 正常"); m_captureTimeLabel = new QLabel("00:00:00"); m_videolossRate = new QLabel("丢帧率: 00.00%"); m_fpsLabel = new QLabel("FPS: 30"); // 创建状态栏并添加到布局中 m_statusBar = new QStatusBar(this); m_statusBar->setSizeGripEnabled(false); // 添加各个状态信息到状态栏 m_statusBar->addWidget(m_videoEncodeLabel); auto widget = new QWidget; widget->setLayout(hLayout); m_statusBar->addWidget(widget); m_statusBar->addWidget(m_videolossRate); m_statusBar->addWidget(m_captureStatusLabel); m_statusBar->addWidget(m_captureTimeLabel); m_statusBar->addWidget(m_fpsLabel); }