|
|
@@ -0,0 +1,648 @@
|
|
|
+#include "recorderwidget.h"
|
|
|
+#include <QApplication>
|
|
|
+#include <QDateTime>
|
|
|
+#include <QDebug>
|
|
|
+#include <QMessageBox>
|
|
|
+#include <QFileDialog>
|
|
|
+#include <QStandardPaths>
|
|
|
+#include <QOpenGLFunctions>
|
|
|
+#include <QPainter>
|
|
|
+
|
|
|
+// 静态实例指针
|
|
|
+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<void> 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<void> 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<const char*>(data), size);
|
|
|
+ s_instance->m_previewWidth = width;
|
|
|
+ s_instance->m_previewHeight = height;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void RecorderWidget::onPreviewAudioCallback()
|
|
|
+{
|
|
|
+ // 音频预览回调,暂时不处理
|
|
|
+}
|