#include "recorderwidget.h" #include #include #include #include #include #include #include #include #include "../config/networkconfig.h" // 静态实例指针 RecorderWidget* RecorderWidget::s_instance = nullptr; RecorderWidget::RecorderWidget(QWidget *parent) : QWidget(parent) , m_mainLayout(nullptr) , m_previewWidget(nullptr) , m_encoderComboBox(nullptr) , m_micComboBox(nullptr) , m_speakerComboBox(nullptr) , m_statusBar(nullptr) , m_statusLabel(nullptr) , m_timeLabel(nullptr) , m_encoderLabel(nullptr) , m_previewTimer(nullptr) , m_statusTimer(nullptr) , m_micDevices(nullptr) , m_speakerDevices(nullptr) , m_encoders(nullptr) , m_micCount(0) , m_speakerCount(0) , m_encoderCount(0) , m_isRecording(false) , m_isStreaming(false) , m_isInitialized(false) , m_previewWidth(0) , m_previewHeight(0) { // 设置静态实例指针 s_instance = this; // rtmp://106.55.186.74:1935/stream/V1/0198da41-cdb6-78e3-879d-2ea32d58f73f // 设置初始大小,与AVPlayerWidget保持一致 setMinimumSize(640, 480); resize(800, 600); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 初始化默认设置 m_settings.liveUrl = NetworkConfig::instance().getStreamUrl(); m_settings.liveName = "0198da41-cdb6-78e3-879d-2ea32d58f73f"; m_settings.outputDir = QStandardPaths::writableLocation(QStandardPaths::MoviesLocation).toStdString(); m_settings.videoBitRate = 8000000; m_settings.videoFrameRate = 30; m_settings.videoQuality = 100; m_settings.audioBitRate = 128000; initUI(); initRecorder(); // 创建定时器(预览已移除,不再创建预览定时器) // m_previewTimer = new QTimer(this); // connect(m_previewTimer, &QTimer::timeout, this, &RecorderWidget::updatePreview); m_statusTimer = new QTimer(this); connect(m_statusTimer, &QTimer::timeout, this, &RecorderWidget::updateStatus); m_statusTimer->start(1000); // 每秒更新一次状态 } RecorderWidget::~RecorderWidget() { releaseRecorder(); s_instance = nullptr; } void RecorderWidget::initUI() { m_mainLayout = new QVBoxLayout(this); m_mainLayout->setContentsMargins(0, 0, 0, 0); m_mainLayout->setSpacing(0); // 上部分:空白区域,占据主要空间 QWidget* blankWidget = new QWidget(this); blankWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // blankWidget->setStyleSheet("background-color: #2b2b2b; border: 1px solid #555;"); //blankWidget->setVisible(false); // 隐藏但仍占据布局空间 // 可以在空白区域添加一些提示信息 QVBoxLayout* blankLayout = new QVBoxLayout(blankWidget); QLabel* hintLabel = new QLabel("录制预览区域", this); hintLabel->setAlignment(Qt::AlignCenter); hintLabel->setText(""); // hintLabel->setStyleSheet("color: #888; font-size: 14px; border: none;"); blankLayout->addWidget(hintLabel); // 状态栏 initStatusBar(); // 添加到主布局:上部分空白区域占据主要空间,下部分状态栏 m_mainLayout->addWidget(blankWidget, 1); // 拉伸因子为1,占据主要空间 m_mainLayout->addWidget(m_statusBar, 0); // 拉伸因子为0,固定高度 } void RecorderWidget::initStatusBar() { m_statusBar = new QStatusBar(this); m_statusLabel = new QLabel("状态: 就绪", this); m_timeLabel = new QLabel("00:00:00", this); m_encoderLabel = new QLabel("编码器: 未选择", this); m_vrbLabel = new QLabel("V丢0/积0", this); m_arbLabel = new QLabel("A丢0/积0", this); m_statusBar->addWidget(m_statusLabel); m_statusBar->addPermanentWidget(m_encoderLabel); m_statusBar->addPermanentWidget(m_vrbLabel); m_statusBar->addPermanentWidget(m_arbLabel); m_statusBar->addPermanentWidget(m_timeLabel); } void RecorderWidget::initRecorder() { // 设置回调函数 setupCallbacks(); // 设置录制器日志路径 QString logPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/recorder.log"; recorder_set_logpath(logPath.toUtf8().constData()); // 延迟刷新编码器列表,避免在构造函数中调用可能导致异常的API QTimer::singleShot(100, this, [this]() { refreshVideoEncoders(); }); } void RecorderWidget::releaseRecorder() { if (m_isRecording || m_isStreaming) { recorder_stop(); } if (m_isInitialized) { recorder_release(); m_isInitialized = false; } // 释放设备数组 if (m_micDevices) { recorder_free_array(m_micDevices); m_micDevices = nullptr; } if (m_speakerDevices) { recorder_free_array(m_speakerDevices); m_speakerDevices = nullptr; } if (m_encoders) { recorder_free_array(m_encoders); m_encoders = nullptr; } } void RecorderWidget::setupCallbacks() { m_callbacks.func_duration = onDurationCallback; m_callbacks.func_error = onErrorCallback; m_callbacks.func_device_change = onDeviceChangeCallback; // 预览已移除,不再设置视频/音频预览回调 m_callbacks.func_preview_yuv = nullptr; m_callbacks.func_preview_audio = nullptr; } void RecorderWidget::refreshAudioDevices() { // 不再维护内部设备选择 UI,此处仅确保释放旧的查询结果 if (m_micDevices) { recorder_free_array(m_micDevices); m_micDevices = nullptr; } if (m_speakerDevices) { recorder_free_array(m_speakerDevices); m_speakerDevices = nullptr; } m_micCount = 0; m_speakerCount = 0; } void RecorderWidget::refreshVideoEncoders() { // 释放之前的编码器数组 if (m_encoders) { recorder_free_array(m_encoders); m_encoders = nullptr; } // 获取视频编码器 try { m_encoderCount = recorder_get_vencoders(&m_encoders); if (m_encoderComboBox) m_encoderComboBox->clear(); for (int i = 0; i < m_encoderCount; ++i) { QString encoderName = QString::fromUtf8(m_encoders[i].name); if (m_encoderComboBox) m_encoderComboBox->addItem(encoderName); } // 同步默认或外部选择 if (m_encoderCount > 0) { if (m_selectedEncoderId >= 0) { // 若已由外部选择,则优先匹配该ID int matchIndex = -1; for (int i = 0; i < m_encoderCount; ++i) { if (m_encoders[i].id == m_selectedEncoderId) { matchIndex = i; break; } } if (matchIndex >= 0 && m_encoderComboBox) { m_encoderComboBox->setCurrentIndex(matchIndex); } if (m_encoderLabel) { const char* nm = (matchIndex >= 0) ? m_encoders[matchIndex].name : nullptr; m_encoderLabel->setText(QString("编码器: %1").arg(nm ? nm : "已选择")); } } else { // 否则选择第一个并更新状态 if (m_encoderComboBox) m_encoderComboBox->setCurrentIndex(0); if (m_encoderLabel) m_encoderLabel->setText(QString("编码器: %1").arg(m_encoders[0].name)); } } } catch (...) { m_encoderCount = 0; qWarning() << "Failed to get video encoders"; if (m_encoderLabel) m_encoderLabel->setText("编码器: 获取失败"); } } void RecorderWidget::setSettings(const Settings& settings) { m_settings = settings; } void RecorderWidget::setMicDevice(const AMRECORDER_DEVICE& device) { m_selectedMicDevice = device; qDebug() << "[RecorderWidget] 设置麦克风设备:" << device.name; } void RecorderWidget::setSpeakerDevice(const AMRECORDER_DEVICE& device) { m_selectedSpeakerDevice = device; qDebug() << "[RecorderWidget] 设置扬声器设备:" << device.name; } // 新增:外部设置视频编码器ID void RecorderWidget::setVideoEncoderId(int encId) { m_selectedEncoderId = encId; // 同步状态栏显示与内部下拉框(若存在) const char* encName = nullptr; if (m_encoders) { for (int i = 0; i < m_encoderCount; ++i) { if (m_encoders[i].id == encId) { encName = m_encoders[i].name; if (m_encoderComboBox) { m_encoderComboBox->blockSignals(true); m_encoderComboBox->setCurrentIndex(i); m_encoderComboBox->blockSignals(false); } break; } } } if (m_encoderLabel) { if (encName) m_encoderLabel->setText(QString("编码器: %1").arg(encName)); else m_encoderLabel->setText(QString("编码器: %1").arg(encId)); } } static void copyDevice(AMRECORDER_DEVICE &dst, const AMRECORDER_DEVICE &src) { strcpy_s(dst.id, src.id); strcpy_s(dst.name, src.name); dst.is_default = src.is_default; } static void chooseDefaultIfEmpty(AMRECORDER_DEVICE &dstMic, AMRECORDER_DEVICE &dstSpeaker) { // 若未设置,尝试从系统枚举中选择默认设备 if (dstMic.id[0] == '\0') { AMRECORDER_DEVICE *mics = nullptr; int micCount = 0; try { micCount = recorder_get_mics(&mics); } catch(...) { micCount = 0; } if (micCount > 0 && mics) { int idx = 0; for (int i = 0; i < micCount; ++i) { if (mics[i].is_default) { idx = i; break; } } copyDevice(dstMic, mics[idx]); } if (mics) recorder_free_array(mics); } if (dstSpeaker.id[0] == '\0') { AMRECORDER_DEVICE *speakers = nullptr; int spkCount = 0; try { spkCount = recorder_get_speakers(&speakers); } catch(...) { spkCount = 0; } if (spkCount > 0 && speakers) { int idx = 0; for (int i = 0; i < spkCount; ++i) { if (speakers[i].is_default) { idx = i; break; } } copyDevice(dstSpeaker, speakers[idx]); } if (speakers) recorder_free_array(speakers); } } bool RecorderWidget::startRecording() { if (m_isRecording) { return true; } // 准备录制设置 memset(&m_recorderSetting, 0, sizeof(m_recorderSetting)); // 视频设置 m_recorderSetting.v_left = 0; m_recorderSetting.v_top = 0; m_recorderSetting.v_width = GetSystemMetrics(SM_CXSCREEN); m_recorderSetting.v_height = GetSystemMetrics(SM_CYSCREEN); m_recorderSetting.v_qb = m_settings.videoQuality; m_recorderSetting.v_bit_rate = m_settings.videoBitRate; m_recorderSetting.v_frame_rate = m_settings.videoFrameRate; // 选择编码器 int encoderIndex = m_encoderComboBox ? m_encoderComboBox->currentIndex() : -1; if (m_selectedEncoderId >= 0) { m_recorderSetting.v_enc_id = m_selectedEncoderId; } else if (encoderIndex >= 0 && encoderIndex < m_encoderCount && m_encoders) { m_recorderSetting.v_enc_id = m_encoders[encoderIndex].id; } // 输出文件 QString fileName = QString::fromStdString(m_settings.outputDir); if (!fileName.endsWith("/") && !fileName.endsWith("\\")) { fileName += "/"; } fileName += QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss") + ".mp4"; strcpy_s(m_recorderSetting.output, fileName.toUtf8().constData()); // 音频设备设置:优先使用外部指定,否则选默认 AMRECORDER_DEVICE mic = m_selectedMicDevice; AMRECORDER_DEVICE spk = m_selectedSpeakerDevice; chooseDefaultIfEmpty(mic, spk); if (mic.id[0] != '\0') copyDevice(m_recorderSetting.a_mic, mic); if (spk.id[0] != '\0') copyDevice(m_recorderSetting.a_speaker, spk); // 异步初始化和启动录制器,避免主线程阻塞 QFuture future = QtConcurrent::run([this]() { // 初始化录制器 int result = recorder_init(m_recorderSetting, m_callbacks); if (result != 0) { QString error = QString("初始化录制器失败: %1").arg(recorder_err2str(result)); QMetaObject::invokeMethod(this, [this, error]() { emit errorOccurred(error); if (m_statusLabel) { m_statusLabel->setText("状态: 就绪"); } }, Qt::QueuedConnection); return; } m_isInitialized = true; // 关闭预览 recorder_set_preview_enabled(0); // 重置环形缓冲区丢帧计数 recorder_reset_video_rb_dropped(); recorder_reset_audio_rb_dropped(); // 限制环形缓冲区最大帧数(降低积压与延迟):视频2帧、音频3帧 recorder_set_video_rb_max(2); recorder_set_audio_rb_max(3); // 开始录制 result = recorder_start(); if (result != 0) { QString error = QString("开始录制失败: %1").arg(recorder_err2str(result)); QMetaObject::invokeMethod(this, [this, error]() { emit errorOccurred(error); recorder_release(); m_isInitialized = false; if (m_statusLabel) { m_statusLabel->setText("状态: 就绪"); } }, Qt::QueuedConnection); return; } // 成功启动录制 QMetaObject::invokeMethod(this, [this]() { m_isRecording = true; m_recordStartTime = QTime::currentTime(); // 预览已移除,不再启动预览定时器 // if (m_previewTimer) m_previewTimer->start(33); emit recordingStarted(); }, Qt::QueuedConnection); }); // 立即更新UI状态,显示正在初始化 m_statusLabel->setText("状态: 初始化中..."); return true; } void RecorderWidget::stopRecording() { if (!m_isRecording) { return; } recorder_stop(); if (m_isInitialized) { recorder_release(); m_isInitialized = false; } m_isRecording = false; // 预览已移除,不再停止预览定时器 // if (m_previewTimer) m_previewTimer->stop(); emit recordingStopped(); } bool RecorderWidget::startStreaming() { if (m_isStreaming) { return true; } // 准备推流设置 memset(&m_recorderSetting, 0, sizeof(m_recorderSetting)); // 视频设置 m_recorderSetting.v_left = 0; m_recorderSetting.v_top = 0; m_recorderSetting.v_width = GetSystemMetrics(SM_CXSCREEN); m_recorderSetting.v_height = GetSystemMetrics(SM_CYSCREEN); m_recorderSetting.v_qb = m_settings.videoQuality; m_recorderSetting.v_bit_rate = m_settings.videoBitRate; m_recorderSetting.v_frame_rate = m_settings.videoFrameRate; // 选择编码器 int encoderIndex = m_encoderComboBox ? m_encoderComboBox->currentIndex() : -1; if (m_selectedEncoderId >= 0) { m_recorderSetting.v_enc_id = m_selectedEncoderId; } else if (encoderIndex >= 0 && encoderIndex < m_encoderCount && m_encoders) { m_recorderSetting.v_enc_id = m_encoders[encoderIndex].id; } // 推流地址 QString streamUrl = m_settings.liveUrl + "/" + QString::fromStdString(m_settings.liveName); strcpy_s(m_recorderSetting.output, streamUrl.toUtf8().constData()); // 音频设备设置:优先使用外部指定,否则选默认 AMRECORDER_DEVICE mic = m_selectedMicDevice; AMRECORDER_DEVICE spk = m_selectedSpeakerDevice; chooseDefaultIfEmpty(mic, spk); if (mic.id[0] != '\0') copyDevice(m_recorderSetting.a_mic, mic); if (spk.id[0] != '\0') copyDevice(m_recorderSetting.a_speaker, spk); qDebug() << m_recorderSetting.output; // 异步初始化和启动推流,避免主线程阻塞 QFuture future = QtConcurrent::run([this]() { // 初始化录制器 int result = recorder_init(m_recorderSetting, m_callbacks); if (result != 0) { QString error = QString("初始化推流失败: %1").arg(recorder_err2str(result)); QMetaObject::invokeMethod(this, [this, error]() { emit errorOccurred(error); if (m_statusLabel) { m_statusLabel->setText("状态: 就绪"); } }, Qt::QueuedConnection); return; } m_isInitialized = true; // 关闭预览 recorder_set_preview_enabled(0); // 重置环形缓冲区丢帧计数 recorder_reset_video_rb_dropped(); recorder_reset_audio_rb_dropped(); // 限制环形缓冲区最大帧数(降低积压与延迟):视频2帧、音频3帧 recorder_set_video_rb_max(2); recorder_set_audio_rb_max(3); // 开始推流 result = recorder_start(); if (result != 0) { QString error = QString("开始推流失败: %1").arg(recorder_err2str(result)); QMetaObject::invokeMethod(this, [this, error]() { emit errorOccurred(error); recorder_release(); m_isInitialized = false; if (m_statusLabel) { m_statusLabel->setText("状态: 就绪"); } }, Qt::QueuedConnection); return; } // 成功启动推流 QMetaObject::invokeMethod(this, [this]() { m_isStreaming = true; m_recordStartTime = QTime::currentTime(); // 预览已移除,不再启动预览定时器 // if (m_previewTimer) m_previewTimer->start(33); emit streamingStarted(); }, Qt::QueuedConnection); }); // 立即更新UI状态,显示正在初始化 m_statusLabel->setText("状态: 连接中..."); return true; } void RecorderWidget::stopStreaming() { if (!m_isStreaming && !m_isInitialized) { return; } if (m_statusLabel) { m_statusLabel->setText("状态: 正在停止..."); } QtConcurrent::run([this]() { // 停止推流并释放资源(与 startStreaming 对称) recorder_stop(); if (m_isInitialized) { recorder_release(); } QMetaObject::invokeMethod(this, [this]() { m_isStreaming = false; m_isInitialized = false; if (m_statusLabel) m_statusLabel->setText("状态: 就绪"); emit streamingStopped(); }, Qt::QueuedConnection); }); } void RecorderWidget::setLiveName(const QString& name) { m_settings.liveName = name.toStdString(); } // 新增:供MainPanel调用的按钮功能 void RecorderWidget::onRecordButtonClicked() { if (m_isRecording) { stopRecording(); } else { startRecording(); } } void RecorderWidget::onStreamButtonClicked() { if (!m_isStreaming) { if (startStreaming()) { if (m_statusLabel) { m_statusLabel->setText("状态: 推流中"); } } } else { stopStreaming(); if (m_statusLabel) { m_statusLabel->setText("状态: 就绪"); } } } void RecorderWidget::onSettingsButtonClicked() { // 打开设置对话框 QMessageBox::information(this, "设置", "设置功能待实现"); } void RecorderWidget::updatePreview() { if (m_previewWidget && m_previewWidth > 0 && m_previewHeight > 0) { m_previewWidget->update(); } } // 预览显示控制方法实现 void RecorderWidget::showPreview() { if (m_previewWidget) { m_previewWidget->show(); } } void RecorderWidget::hidePreview() { if (m_previewWidget) { m_previewWidget->hide(); } } bool RecorderWidget::isPreviewVisible() const { return m_previewWidget ? m_previewWidget->isVisible() : false; } void RecorderWidget::updateStatus() { if (m_isRecording || m_isStreaming) { QTime currentTime = QTime::currentTime(); int elapsed = m_recordStartTime.secsTo(currentTime); int hours = elapsed / 3600; int minutes = (elapsed % 3600) / 60; int seconds = elapsed % 60; QString timeStr = QString("%1:%2:%3") .arg(hours, 2, 10, QChar('0')) .arg(minutes, 2, 10, QChar('0')) .arg(seconds, 2, 10, QChar('0')); m_timeLabel->setText(timeStr); } else { m_timeLabel->setText("00:00:00"); } // 更新环形缓冲区统计(仅在初始化后) if (m_isInitialized) { uint64_t vdrop = recorder_get_video_rb_dropped(); int vpend = recorder_get_video_rb_pending(); uint64_t adrop = recorder_get_audio_rb_dropped(); int apend = recorder_get_audio_rb_pending(); if (m_vrbLabel) m_vrbLabel->setText(QString("V丢%1/积%2").arg(vdrop).arg(vpend)); if (m_arbLabel) m_arbLabel->setText(QString("A丢%1/积%2").arg(adrop).arg(apend)); } else { if (m_vrbLabel) m_vrbLabel->setText("V丢0/积0"); if (m_arbLabel) m_arbLabel->setText("A丢0/积0"); } } // 静态回调函数实现 void RecorderWidget::onDurationCallback(uint64_t duration) { if (s_instance) { // 可以在这里处理持续时间更新 qDebug() << "Recording duration:" << duration << "ms"; } } void RecorderWidget::onErrorCallback(int error) { if (s_instance) { QString errorStr = QString("录制错误: %1").arg(recorder_err2str(error)); QMetaObject::invokeMethod(s_instance, "errorOccurred", Qt::QueuedConnection, Q_ARG(QString, errorStr)); } } void RecorderWidget::onDeviceChangeCallback(int type) { Q_UNUSED(type); // 不再自动刷新内部设备列表,由主界面设备选择器负责 } void RecorderWidget::onPreviewYUVCallback(const unsigned char *data, unsigned int size, int width, int height, int type) { if (s_instance && data && size > 0) { QMutexLocker locker(&s_instance->m_previewMutex); s_instance->m_previewData = QByteArray(reinterpret_cast(data), size); s_instance->m_previewWidth = width; s_instance->m_previewHeight = height; } } void RecorderWidget::onPreviewAudioCallback() { // 音频预览回调,暂时不处理 }