#include "recorderwidget.h" #include #include #include #include #include #include #include #include // 静态实例指针 RecorderWidget* RecorderWidget::s_instance = nullptr; RecorderWidget::RecorderWidget(QWidget *parent) : QWidget(parent) , m_mainLayout(nullptr) , m_previewWidget(nullptr) , m_recordButton(nullptr) , m_streamButton(nullptr) , m_settingsButton(nullptr) , m_audioDeviceButton(nullptr) , m_drawCursorCheckBox(nullptr) , m_syncRecordCheckBox(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; // 初始化默认设置 m_settings.liveUrl = "rtmp://106.55.186.74:1935/stream/V1"; m_settings.liveName = "stream"; 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_previewWidget = new QOpenGLWidget(this); m_previewWidget->setMinimumSize(640, 480); m_previewWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 控制区域 QWidget* controlWidget = new QWidget(this); QHBoxLayout* controlLayout = new QHBoxLayout(controlWidget); // 左侧设备选择区 QGroupBox* deviceGroup = new QGroupBox("设备选择", this); QVBoxLayout* deviceLayout = new QVBoxLayout(deviceGroup); // 编码器选择 QHBoxLayout* encoderLayout = new QHBoxLayout(); encoderLayout->addWidget(new QLabel("编码器:")); m_encoderComboBox = new QComboBox(this); encoderLayout->addWidget(m_encoderComboBox); deviceLayout->addLayout(encoderLayout); // 麦克风选择 QHBoxLayout* micLayout = new QHBoxLayout(); micLayout->addWidget(new QLabel("麦克风:")); m_micComboBox = new QComboBox(this); micLayout->addWidget(m_micComboBox); deviceLayout->addLayout(micLayout); // 扬声器选择 QHBoxLayout* speakerLayout = new QHBoxLayout(); speakerLayout->addWidget(new QLabel("扬声器:")); m_speakerComboBox = new QComboBox(this); speakerLayout->addWidget(m_speakerComboBox); deviceLayout->addLayout(speakerLayout); // 音频设备按钮 m_audioDeviceButton = new QPushButton("刷新设备", this); deviceLayout->addWidget(m_audioDeviceButton); // 右侧操作区 QGroupBox* actionGroup = new QGroupBox("操作", this); QVBoxLayout* actionLayout = new QVBoxLayout(actionGroup); // 选项 m_drawCursorCheckBox = new QCheckBox("绘制鼠标指针", this); m_drawCursorCheckBox->setChecked(true); m_syncRecordCheckBox = new QCheckBox("推流时同步录制", this); actionLayout->addWidget(m_drawCursorCheckBox); actionLayout->addWidget(m_syncRecordCheckBox); // 按钮 m_recordButton = new QPushButton("开始录制", this); m_streamButton = new QPushButton("开始推流", this); m_settingsButton = new QPushButton("设置", this); actionLayout->addWidget(m_recordButton); actionLayout->addWidget(m_streamButton); actionLayout->addWidget(m_settingsButton); // 添加到控制布局 controlLayout->addWidget(deviceGroup, 1); controlLayout->addWidget(actionGroup, 1); controlLayout->addStretch(); // 状态栏 initStatusBar(); // 添加到主布局 m_mainLayout->addWidget(m_previewWidget, 1); m_mainLayout->addWidget(controlWidget, 0); m_mainLayout->addWidget(m_statusBar, 0); // 连接信号槽 connect(m_recordButton, &QPushButton::clicked, this, &RecorderWidget::onRecordButtonClicked); connect(m_streamButton, &QPushButton::clicked, this, &RecorderWidget::onStreamButtonClicked); connect(m_settingsButton, &QPushButton::clicked, this, &RecorderWidget::onSettingsButtonClicked); connect(m_audioDeviceButton, &QPushButton::clicked, this, &RecorderWidget::onAudioDeviceButtonClicked); } 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_statusBar->addWidget(m_statusLabel); m_statusBar->addPermanentWidget(m_encoderLabel); 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]() { refreshAudioDevices(); 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 = onPreviewYUVCallback; m_callbacks.func_preview_audio = onPreviewAudioCallback; } void RecorderWidget::refreshAudioDevices() { // 释放之前的设备数组 if (m_micDevices) { recorder_free_array(m_micDevices); m_micDevices = nullptr; } if (m_speakerDevices) { recorder_free_array(m_speakerDevices); m_speakerDevices = nullptr; } // 获取麦克风设备 try { m_micCount = recorder_get_mics(&m_micDevices); m_micComboBox->clear(); for (int i = 0; i < m_micCount; ++i) { QString deviceName = QString::fromUtf8(m_micDevices[i].name); m_micComboBox->addItem(deviceName); if (m_micDevices[i].is_default) { m_micComboBox->setCurrentIndex(i); } } } catch (...) { m_micCount = 0; qWarning() << "Failed to get microphone devices"; } // 获取扬声器设备 try { m_speakerCount = recorder_get_speakers(&m_speakerDevices); m_speakerComboBox->clear(); for (int i = 0; i < m_speakerCount; ++i) { QString deviceName = QString::fromUtf8(m_speakerDevices[i].name); m_speakerComboBox->addItem(deviceName); if (m_speakerDevices[i].is_default) { m_speakerComboBox->setCurrentIndex(i); } } } catch (...) { m_speakerCount = 0; qWarning() << "Failed to get speaker devices"; } } void RecorderWidget::refreshVideoEncoders() { // 释放之前的编码器数组 if (m_encoders) { recorder_free_array(m_encoders); m_encoders = nullptr; } // 获取视频编码器 try { m_encoderCount = recorder_get_vencoders(&m_encoders); m_encoderComboBox->clear(); for (int i = 0; i < m_encoderCount; ++i) { QString encoderName = QString::fromUtf8(m_encoders[i].name); m_encoderComboBox->addItem(encoderName); } // 默认选择第一个编码器 if (m_encoderCount > 0) { m_encoderComboBox->setCurrentIndex(0); m_encoderLabel->setText(QString("编码器: %1").arg(m_encoders[0].name)); } } catch (...) { m_encoderCount = 0; qWarning() << "Failed to get video encoders"; m_encoderLabel->setText("编码器: 获取失败"); } } void RecorderWidget::setSettings(const Settings& settings) { m_settings = settings; } 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->currentIndex(); if (encoderIndex >= 0 && encoderIndex < m_encoderCount) { 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()); // 音频设备设置 int micIndex = m_micComboBox->currentIndex(); if (micIndex >= 0 && micIndex < m_micCount) { strcpy_s(m_recorderSetting.a_mic.id, m_micDevices[micIndex].id); strcpy_s(m_recorderSetting.a_mic.name, m_micDevices[micIndex].name); m_recorderSetting.a_mic.is_default = m_micDevices[micIndex].is_default; } int speakerIndex = m_speakerComboBox->currentIndex(); if (speakerIndex >= 0 && speakerIndex < m_speakerCount) { strcpy_s(m_recorderSetting.a_speaker.id, m_speakerDevices[speakerIndex].id); strcpy_s(m_recorderSetting.a_speaker.name, m_speakerDevices[speakerIndex].name); m_recorderSetting.a_speaker.is_default = m_speakerDevices[speakerIndex].is_default; } // 异步初始化和启动录制器,避免主线程阻塞 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); m_recordButton->setText("开始录制"); m_streamButton->setEnabled(true); m_statusLabel->setText("状态: 就绪"); }, Qt::QueuedConnection); return; } m_isInitialized = true; // 启用预览 recorder_set_preview_enabled(1); // 开始录制 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; m_recordButton->setText("开始录制"); m_streamButton->setEnabled(true); m_statusLabel->setText("状态: 就绪"); }, Qt::QueuedConnection); return; } // 成功启动录制 QMetaObject::invokeMethod(this, [this]() { m_isRecording = true; m_recordStartTime = QTime::currentTime(); // 开始预览定时器 m_previewTimer->start(33); // 约30fps 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; // 停止预览定时器 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->currentIndex(); if (encoderIndex >= 0 && encoderIndex < m_encoderCount) { m_recorderSetting.v_enc_id = m_encoders[encoderIndex].id; } // 推流地址 QString streamUrl = QString::fromStdString(m_settings.liveUrl + "/" + m_settings.liveName); strcpy_s(m_recorderSetting.output, streamUrl.toUtf8().constData()); // 音频设备设置 int micIndex = m_micComboBox->currentIndex(); if (micIndex >= 0 && micIndex < m_micCount) { strcpy_s(m_recorderSetting.a_mic.id, m_micDevices[micIndex].id); strcpy_s(m_recorderSetting.a_mic.name, m_micDevices[micIndex].name); m_recorderSetting.a_mic.is_default = m_micDevices[micIndex].is_default; } int speakerIndex = m_speakerComboBox->currentIndex(); if (speakerIndex >= 0 && speakerIndex < m_speakerCount) { strcpy_s(m_recorderSetting.a_speaker.id, m_speakerDevices[speakerIndex].id); strcpy_s(m_recorderSetting.a_speaker.name, m_speakerDevices[speakerIndex].name); m_recorderSetting.a_speaker.is_default = m_speakerDevices[speakerIndex].is_default; } // 异步初始化和启动推流,避免主线程阻塞 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); m_streamButton->setText("开始推流"); m_recordButton->setEnabled(true); m_statusLabel->setText("状态: 就绪"); }, Qt::QueuedConnection); return; } m_isInitialized = true; // 启用预览 recorder_set_preview_enabled(1); // 开始推流 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; m_streamButton->setText("开始推流"); m_recordButton->setEnabled(true); m_statusLabel->setText("状态: 就绪"); }, Qt::QueuedConnection); return; } // 成功启动推流 QMetaObject::invokeMethod(this, [this]() { m_isStreaming = true; m_recordStartTime = QTime::currentTime(); // 开始预览定时器 m_previewTimer->start(33); // 约30fps emit streamingStarted(); }, Qt::QueuedConnection); }); // 立即更新UI状态,显示正在初始化 m_statusLabel->setText("状态: 连接中..."); return true; } void RecorderWidget::stopStreaming() { if (!m_isStreaming) { return; } recorder_stop(); if (m_isInitialized) { recorder_release(); m_isInitialized = false; } m_isStreaming = false; // 停止预览定时器 m_previewTimer->stop(); emit streamingStopped(); } void RecorderWidget::onRecordButtonClicked() { if (!m_isRecording) { if (startRecording()) { m_recordButton->setText("停止录制"); m_streamButton->setEnabled(false); m_statusLabel->setText("状态: 录制中"); } } else { stopRecording(); m_recordButton->setText("开始录制"); m_streamButton->setEnabled(true); m_statusLabel->setText("状态: 就绪"); } } void RecorderWidget::onStreamButtonClicked() { if (!m_isStreaming) { if (startStreaming()) { m_streamButton->setText("停止推流"); m_recordButton->setEnabled(!m_syncRecordCheckBox->isChecked()); m_statusLabel->setText("状态: 推流中"); } } else { stopStreaming(); m_streamButton->setText("开始推流"); m_recordButton->setEnabled(true); m_statusLabel->setText("状态: 就绪"); } } void RecorderWidget::onSettingsButtonClicked() { // 打开设置对话框 QMessageBox::information(this, "设置", "设置功能待实现"); } void RecorderWidget::onAudioDeviceButtonClicked() { refreshAudioDevices(); QMessageBox::information(this, "音频设备", "设备列表已刷新"); } void RecorderWidget::updatePreview() { // 预览更新在回调函数中处理 if (m_previewWidget && m_previewWidth > 0 && m_previewHeight > 0) { m_previewWidget->update(); } } 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"); } } // 静态回调函数实现 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) { if (s_instance) { // 设备变化时刷新设备列表 QMetaObject::invokeMethod(s_instance, "refreshAudioDevices", Qt::QueuedConnection); } } 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() { // 音频预览回调,暂时不处理 }