recorderwidget.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. #include "recorderwidget.h"
  2. #include <QApplication>
  3. #include <QDateTime>
  4. #include <QDebug>
  5. #include <QMessageBox>
  6. #include <QFileDialog>
  7. #include <QStandardPaths>
  8. #include <QOpenGLFunctions>
  9. #include <QPainter>
  10. #include "../config/networkconfig.h"
  11. // 静态实例指针
  12. RecorderWidget* RecorderWidget::s_instance = nullptr;
  13. RecorderWidget::RecorderWidget(QWidget *parent)
  14. : QWidget(parent)
  15. , m_mainLayout(nullptr)
  16. , m_previewWidget(nullptr)
  17. , m_encoderComboBox(nullptr)
  18. , m_micComboBox(nullptr)
  19. , m_speakerComboBox(nullptr)
  20. , m_statusBar(nullptr)
  21. , m_statusLabel(nullptr)
  22. , m_timeLabel(nullptr)
  23. , m_encoderLabel(nullptr)
  24. , m_previewTimer(nullptr)
  25. , m_statusTimer(nullptr)
  26. , m_micDevices(nullptr)
  27. , m_speakerDevices(nullptr)
  28. , m_encoders(nullptr)
  29. , m_micCount(0)
  30. , m_speakerCount(0)
  31. , m_encoderCount(0)
  32. , m_isRecording(false)
  33. , m_isStreaming(false)
  34. , m_isInitialized(false)
  35. , m_previewWidth(0)
  36. , m_previewHeight(0)
  37. {
  38. // 设置静态实例指针
  39. s_instance = this;
  40. // rtmp://106.55.186.74:1935/stream/V1/0198da41-cdb6-78e3-879d-2ea32d58f73f
  41. // 设置初始大小,与AVPlayerWidget保持一致
  42. setMinimumSize(640, 480);
  43. resize(800, 600);
  44. setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  45. // 初始化默认设置
  46. m_settings.liveUrl = NetworkConfig::instance().getStreamUrl();
  47. m_settings.liveName = "0198da41-cdb6-78e3-879d-2ea32d58f73f";
  48. m_settings.outputDir = QStandardPaths::writableLocation(QStandardPaths::MoviesLocation).toStdString();
  49. m_settings.videoBitRate = 8000000;
  50. m_settings.videoFrameRate = 30;
  51. m_settings.videoQuality = 100;
  52. m_settings.audioBitRate = 128000;
  53. initUI();
  54. initRecorder();
  55. // 创建定时器(预览已移除,不再创建预览定时器)
  56. // m_previewTimer = new QTimer(this);
  57. // connect(m_previewTimer, &QTimer::timeout, this, &RecorderWidget::updatePreview);
  58. m_statusTimer = new QTimer(this);
  59. connect(m_statusTimer, &QTimer::timeout, this, &RecorderWidget::updateStatus);
  60. m_statusTimer->start(1000); // 每秒更新一次状态
  61. }
  62. RecorderWidget::~RecorderWidget()
  63. {
  64. releaseRecorder();
  65. s_instance = nullptr;
  66. }
  67. void RecorderWidget::initUI()
  68. {
  69. m_mainLayout = new QVBoxLayout(this);
  70. m_mainLayout->setContentsMargins(0, 0, 0, 0);
  71. m_mainLayout->setSpacing(0);
  72. // 上部分:空白区域,占据主要空间
  73. QWidget* blankWidget = new QWidget(this);
  74. blankWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  75. // blankWidget->setStyleSheet("background-color: #2b2b2b; border: 1px solid #555;");
  76. //blankWidget->setVisible(false); // 隐藏但仍占据布局空间
  77. // 可以在空白区域添加一些提示信息
  78. QVBoxLayout* blankLayout = new QVBoxLayout(blankWidget);
  79. QLabel* hintLabel = new QLabel("录制预览区域", this);
  80. hintLabel->setAlignment(Qt::AlignCenter);
  81. hintLabel->setText("");
  82. // hintLabel->setStyleSheet("color: #888; font-size: 14px; border: none;");
  83. blankLayout->addWidget(hintLabel);
  84. // 状态栏
  85. initStatusBar();
  86. // 添加到主布局:上部分空白区域占据主要空间,下部分状态栏
  87. m_mainLayout->addWidget(blankWidget, 1); // 拉伸因子为1,占据主要空间
  88. m_mainLayout->addWidget(m_statusBar, 0); // 拉伸因子为0,固定高度
  89. }
  90. void RecorderWidget::initStatusBar()
  91. {
  92. m_statusBar = new QStatusBar(this);
  93. m_statusLabel = new QLabel("状态: 就绪", this);
  94. m_timeLabel = new QLabel("00:00:00", this);
  95. m_encoderLabel = new QLabel("编码器: 未选择", this);
  96. m_vrbLabel = new QLabel("V丢0/积0", this);
  97. m_arbLabel = new QLabel("A丢0/积0", this);
  98. m_statusBar->addWidget(m_statusLabel);
  99. m_statusBar->addPermanentWidget(m_encoderLabel);
  100. m_statusBar->addPermanentWidget(m_vrbLabel);
  101. m_statusBar->addPermanentWidget(m_arbLabel);
  102. m_statusBar->addPermanentWidget(m_timeLabel);
  103. }
  104. void RecorderWidget::initRecorder()
  105. {
  106. // 设置回调函数
  107. setupCallbacks();
  108. // 设置录制器日志路径
  109. QString logPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/recorder.log";
  110. recorder_set_logpath(logPath.toUtf8().constData());
  111. // 延迟刷新编码器列表,避免在构造函数中调用可能导致异常的API
  112. QTimer::singleShot(100, this, [this]() {
  113. refreshVideoEncoders();
  114. });
  115. }
  116. void RecorderWidget::releaseRecorder()
  117. {
  118. if (m_isRecording || m_isStreaming) {
  119. recorder_stop();
  120. }
  121. if (m_isInitialized) {
  122. recorder_release();
  123. m_isInitialized = false;
  124. }
  125. // 释放设备数组
  126. if (m_micDevices) {
  127. recorder_free_array(m_micDevices);
  128. m_micDevices = nullptr;
  129. }
  130. if (m_speakerDevices) {
  131. recorder_free_array(m_speakerDevices);
  132. m_speakerDevices = nullptr;
  133. }
  134. if (m_encoders) {
  135. recorder_free_array(m_encoders);
  136. m_encoders = nullptr;
  137. }
  138. }
  139. void RecorderWidget::setupCallbacks()
  140. {
  141. m_callbacks.func_duration = onDurationCallback;
  142. m_callbacks.func_error = onErrorCallback;
  143. m_callbacks.func_device_change = onDeviceChangeCallback;
  144. // 预览已移除,不再设置视频/音频预览回调
  145. m_callbacks.func_preview_yuv = nullptr;
  146. m_callbacks.func_preview_audio = nullptr;
  147. }
  148. void RecorderWidget::refreshAudioDevices()
  149. {
  150. // 不再维护内部设备选择 UI,此处仅确保释放旧的查询结果
  151. if (m_micDevices) { recorder_free_array(m_micDevices); m_micDevices = nullptr; }
  152. if (m_speakerDevices) { recorder_free_array(m_speakerDevices); m_speakerDevices = nullptr; }
  153. m_micCount = 0;
  154. m_speakerCount = 0;
  155. }
  156. void RecorderWidget::refreshVideoEncoders()
  157. {
  158. // 释放之前的编码器数组
  159. if (m_encoders) {
  160. recorder_free_array(m_encoders);
  161. m_encoders = nullptr;
  162. }
  163. // 获取视频编码器
  164. try {
  165. m_encoderCount = recorder_get_vencoders(&m_encoders);
  166. if (m_encoderComboBox) m_encoderComboBox->clear();
  167. for (int i = 0; i < m_encoderCount; ++i) {
  168. QString encoderName = QString::fromUtf8(m_encoders[i].name);
  169. if (m_encoderComboBox) m_encoderComboBox->addItem(encoderName);
  170. }
  171. // 同步默认或外部选择
  172. if (m_encoderCount > 0) {
  173. if (m_selectedEncoderId >= 0) {
  174. // 若已由外部选择,则优先匹配该ID
  175. int matchIndex = -1;
  176. for (int i = 0; i < m_encoderCount; ++i) {
  177. if (m_encoders[i].id == m_selectedEncoderId) { matchIndex = i; break; }
  178. }
  179. if (matchIndex >= 0 && m_encoderComboBox) {
  180. m_encoderComboBox->setCurrentIndex(matchIndex);
  181. }
  182. if (m_encoderLabel) {
  183. const char* nm = (matchIndex >= 0) ? m_encoders[matchIndex].name : nullptr;
  184. m_encoderLabel->setText(QString("编码器: %1").arg(nm ? nm : "已选择"));
  185. }
  186. } else {
  187. // 否则选择第一个并更新状态
  188. if (m_encoderComboBox) m_encoderComboBox->setCurrentIndex(0);
  189. if (m_encoderLabel) m_encoderLabel->setText(QString("编码器: %1").arg(m_encoders[0].name));
  190. }
  191. }
  192. } catch (...) {
  193. m_encoderCount = 0;
  194. qWarning() << "Failed to get video encoders";
  195. if (m_encoderLabel) m_encoderLabel->setText("编码器: 获取失败");
  196. }
  197. }
  198. void RecorderWidget::setSettings(const Settings& settings)
  199. {
  200. m_settings = settings;
  201. }
  202. void RecorderWidget::setMicDevice(const AMRECORDER_DEVICE& device)
  203. {
  204. m_selectedMicDevice = device;
  205. qDebug() << "[RecorderWidget] 设置麦克风设备:" << device.name;
  206. }
  207. void RecorderWidget::setSpeakerDevice(const AMRECORDER_DEVICE& device)
  208. {
  209. m_selectedSpeakerDevice = device;
  210. qDebug() << "[RecorderWidget] 设置扬声器设备:" << device.name;
  211. }
  212. // 新增:外部设置视频编码器ID
  213. void RecorderWidget::setVideoEncoderId(int encId)
  214. {
  215. m_selectedEncoderId = encId;
  216. // 同步状态栏显示与内部下拉框(若存在)
  217. const char* encName = nullptr;
  218. if (m_encoders) {
  219. for (int i = 0; i < m_encoderCount; ++i) {
  220. if (m_encoders[i].id == encId) {
  221. encName = m_encoders[i].name;
  222. if (m_encoderComboBox) {
  223. m_encoderComboBox->blockSignals(true);
  224. m_encoderComboBox->setCurrentIndex(i);
  225. m_encoderComboBox->blockSignals(false);
  226. }
  227. break;
  228. }
  229. }
  230. }
  231. if (m_encoderLabel) {
  232. if (encName) m_encoderLabel->setText(QString("编码器: %1").arg(encName));
  233. else m_encoderLabel->setText(QString("编码器: %1").arg(encId));
  234. }
  235. }
  236. static void copyDevice(AMRECORDER_DEVICE &dst, const AMRECORDER_DEVICE &src)
  237. {
  238. strcpy_s(dst.id, src.id);
  239. strcpy_s(dst.name, src.name);
  240. dst.is_default = src.is_default;
  241. }
  242. static void chooseDefaultIfEmpty(AMRECORDER_DEVICE &dstMic, AMRECORDER_DEVICE &dstSpeaker)
  243. {
  244. // 若未设置,尝试从系统枚举中选择默认设备
  245. if (dstMic.id[0] == '\0') {
  246. AMRECORDER_DEVICE *mics = nullptr;
  247. int micCount = 0;
  248. try { micCount = recorder_get_mics(&mics); } catch(...) { micCount = 0; }
  249. if (micCount > 0 && mics) {
  250. int idx = 0;
  251. for (int i = 0; i < micCount; ++i) { if (mics[i].is_default) { idx = i; break; } }
  252. copyDevice(dstMic, mics[idx]);
  253. }
  254. if (mics) recorder_free_array(mics);
  255. }
  256. if (dstSpeaker.id[0] == '\0') {
  257. AMRECORDER_DEVICE *speakers = nullptr;
  258. int spkCount = 0;
  259. try { spkCount = recorder_get_speakers(&speakers); } catch(...) { spkCount = 0; }
  260. if (spkCount > 0 && speakers) {
  261. int idx = 0;
  262. for (int i = 0; i < spkCount; ++i) { if (speakers[i].is_default) { idx = i; break; } }
  263. copyDevice(dstSpeaker, speakers[idx]);
  264. }
  265. if (speakers) recorder_free_array(speakers);
  266. }
  267. }
  268. bool RecorderWidget::startRecording()
  269. {
  270. if (m_isRecording) {
  271. return true;
  272. }
  273. // 准备录制设置
  274. memset(&m_recorderSetting, 0, sizeof(m_recorderSetting));
  275. // 视频设置
  276. m_recorderSetting.v_left = 0;
  277. m_recorderSetting.v_top = 0;
  278. m_recorderSetting.v_width = GetSystemMetrics(SM_CXSCREEN);
  279. m_recorderSetting.v_height = GetSystemMetrics(SM_CYSCREEN);
  280. m_recorderSetting.v_qb = m_settings.videoQuality;
  281. m_recorderSetting.v_bit_rate = m_settings.videoBitRate;
  282. m_recorderSetting.v_frame_rate = m_settings.videoFrameRate;
  283. // 选择编码器
  284. int encoderIndex = m_encoderComboBox ? m_encoderComboBox->currentIndex() : -1;
  285. if (m_selectedEncoderId >= 0) {
  286. m_recorderSetting.v_enc_id = m_selectedEncoderId;
  287. } else if (encoderIndex >= 0 && encoderIndex < m_encoderCount && m_encoders) {
  288. m_recorderSetting.v_enc_id = m_encoders[encoderIndex].id;
  289. }
  290. // 输出文件
  291. QString fileName = QString::fromStdString(m_settings.outputDir);
  292. if (!fileName.endsWith("/") && !fileName.endsWith("\\")) {
  293. fileName += "/";
  294. }
  295. fileName += QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss") + ".mp4";
  296. strcpy_s(m_recorderSetting.output, fileName.toUtf8().constData());
  297. // 音频设备设置:优先使用外部指定,否则选默认
  298. AMRECORDER_DEVICE mic = m_selectedMicDevice;
  299. AMRECORDER_DEVICE spk = m_selectedSpeakerDevice;
  300. chooseDefaultIfEmpty(mic, spk);
  301. if (mic.id[0] != '\0') copyDevice(m_recorderSetting.a_mic, mic);
  302. if (spk.id[0] != '\0') copyDevice(m_recorderSetting.a_speaker, spk);
  303. // 异步初始化和启动录制器,避免主线程阻塞
  304. QFuture<void> future = QtConcurrent::run([this]() {
  305. // 初始化录制器
  306. int result = recorder_init(m_recorderSetting, m_callbacks);
  307. if (result != 0) {
  308. QString error = QString("初始化录制器失败: %1").arg(recorder_err2str(result));
  309. QMetaObject::invokeMethod(this, [this, error]() {
  310. emit errorOccurred(error);
  311. if (m_statusLabel) {
  312. m_statusLabel->setText("状态: 就绪");
  313. }
  314. }, Qt::QueuedConnection);
  315. return;
  316. }
  317. m_isInitialized = true;
  318. // 关闭预览
  319. recorder_set_preview_enabled(0);
  320. // 重置环形缓冲区丢帧计数
  321. recorder_reset_video_rb_dropped();
  322. recorder_reset_audio_rb_dropped();
  323. // 开始录制
  324. result = recorder_start();
  325. if (result != 0) {
  326. QString error = QString("开始录制失败: %1").arg(recorder_err2str(result));
  327. QMetaObject::invokeMethod(this, [this, error]() {
  328. emit errorOccurred(error);
  329. recorder_release();
  330. m_isInitialized = false;
  331. if (m_statusLabel) {
  332. m_statusLabel->setText("状态: 就绪");
  333. }
  334. }, Qt::QueuedConnection);
  335. return;
  336. }
  337. // 成功启动录制
  338. QMetaObject::invokeMethod(this, [this]() {
  339. m_isRecording = true;
  340. m_recordStartTime = QTime::currentTime();
  341. // 预览已移除,不再启动预览定时器
  342. // if (m_previewTimer) m_previewTimer->start(33);
  343. emit recordingStarted();
  344. }, Qt::QueuedConnection);
  345. });
  346. // 立即更新UI状态,显示正在初始化
  347. m_statusLabel->setText("状态: 初始化中...");
  348. return true;
  349. }
  350. void RecorderWidget::stopRecording()
  351. {
  352. if (!m_isRecording) {
  353. return;
  354. }
  355. recorder_stop();
  356. if (m_isInitialized) {
  357. recorder_release();
  358. m_isInitialized = false;
  359. }
  360. m_isRecording = false;
  361. // 预览已移除,不再停止预览定时器
  362. // if (m_previewTimer) m_previewTimer->stop();
  363. emit recordingStopped();
  364. }
  365. bool RecorderWidget::startStreaming()
  366. {
  367. if (m_isStreaming) {
  368. return true;
  369. }
  370. // 准备推流设置
  371. memset(&m_recorderSetting, 0, sizeof(m_recorderSetting));
  372. // 视频设置
  373. m_recorderSetting.v_left = 0;
  374. m_recorderSetting.v_top = 0;
  375. m_recorderSetting.v_width = GetSystemMetrics(SM_CXSCREEN);
  376. m_recorderSetting.v_height = GetSystemMetrics(SM_CYSCREEN);
  377. m_recorderSetting.v_qb = m_settings.videoQuality;
  378. m_recorderSetting.v_bit_rate = m_settings.videoBitRate;
  379. m_recorderSetting.v_frame_rate = m_settings.videoFrameRate;
  380. // 选择编码器
  381. int encoderIndex = m_encoderComboBox ? m_encoderComboBox->currentIndex() : -1;
  382. if (m_selectedEncoderId >= 0) {
  383. m_recorderSetting.v_enc_id = m_selectedEncoderId;
  384. } else if (encoderIndex >= 0 && encoderIndex < m_encoderCount && m_encoders) {
  385. m_recorderSetting.v_enc_id = m_encoders[encoderIndex].id;
  386. }
  387. // 推流地址
  388. QString streamUrl = m_settings.liveUrl + "/" + QString::fromStdString(m_settings.liveName);
  389. strcpy_s(m_recorderSetting.output, streamUrl.toUtf8().constData());
  390. // 音频设备设置:优先使用外部指定,否则选默认
  391. AMRECORDER_DEVICE mic = m_selectedMicDevice;
  392. AMRECORDER_DEVICE spk = m_selectedSpeakerDevice;
  393. chooseDefaultIfEmpty(mic, spk);
  394. if (mic.id[0] != '\0') copyDevice(m_recorderSetting.a_mic, mic);
  395. if (spk.id[0] != '\0') copyDevice(m_recorderSetting.a_speaker, spk);
  396. qDebug() << m_recorderSetting.output;
  397. // 异步初始化和启动推流,避免主线程阻塞
  398. QFuture<void> future = QtConcurrent::run([this]() {
  399. // 初始化录制器
  400. int result = recorder_init(m_recorderSetting, m_callbacks);
  401. if (result != 0) {
  402. QString error = QString("初始化推流失败: %1").arg(recorder_err2str(result));
  403. QMetaObject::invokeMethod(this, [this, error]() {
  404. emit errorOccurred(error);
  405. if (m_statusLabel) {
  406. m_statusLabel->setText("状态: 就绪");
  407. }
  408. }, Qt::QueuedConnection);
  409. return;
  410. }
  411. m_isInitialized = true;
  412. // 关闭预览
  413. recorder_set_preview_enabled(0);
  414. // 重置环形缓冲区丢帧计数
  415. recorder_reset_video_rb_dropped();
  416. recorder_reset_audio_rb_dropped();
  417. // 开始推流
  418. result = recorder_start();
  419. if (result != 0) {
  420. QString error = QString("开始推流失败: %1").arg(recorder_err2str(result));
  421. QMetaObject::invokeMethod(this, [this, error]() {
  422. emit errorOccurred(error);
  423. recorder_release();
  424. m_isInitialized = false;
  425. if (m_statusLabel) {
  426. m_statusLabel->setText("状态: 就绪");
  427. }
  428. }, Qt::QueuedConnection);
  429. return;
  430. }
  431. // 成功启动推流
  432. QMetaObject::invokeMethod(this, [this]() {
  433. m_isStreaming = true;
  434. m_recordStartTime = QTime::currentTime();
  435. // 预览已移除,不再启动预览定时器
  436. // if (m_previewTimer) m_previewTimer->start(33);
  437. emit streamingStarted();
  438. }, Qt::QueuedConnection);
  439. });
  440. // 立即更新UI状态,显示正在初始化
  441. m_statusLabel->setText("状态: 连接中...");
  442. return true;
  443. }
  444. void RecorderWidget::stopStreaming()
  445. {
  446. if (!m_isStreaming && !m_isInitialized) {
  447. return;
  448. }
  449. if (m_statusLabel) {
  450. m_statusLabel->setText("状态: 正在停止...");
  451. }
  452. QtConcurrent::run([this]() {
  453. // 停止推流并释放资源(与 startStreaming 对称)
  454. recorder_stop();
  455. if (m_isInitialized) {
  456. recorder_release();
  457. }
  458. QMetaObject::invokeMethod(this, [this]() {
  459. m_isStreaming = false;
  460. m_isInitialized = false;
  461. if (m_statusLabel)
  462. m_statusLabel->setText("状态: 就绪");
  463. emit streamingStopped();
  464. }, Qt::QueuedConnection);
  465. });
  466. }
  467. void RecorderWidget::setLiveName(const QString& name)
  468. {
  469. m_settings.liveName = name.toStdString();
  470. }
  471. // 新增:供MainPanel调用的按钮功能
  472. void RecorderWidget::onRecordButtonClicked()
  473. {
  474. if (m_isRecording) {
  475. stopRecording();
  476. } else {
  477. startRecording();
  478. }
  479. }
  480. void RecorderWidget::onStreamButtonClicked()
  481. {
  482. if (!m_isStreaming) {
  483. if (startStreaming()) {
  484. if (m_statusLabel) {
  485. m_statusLabel->setText("状态: 推流中");
  486. }
  487. }
  488. } else {
  489. stopStreaming();
  490. if (m_statusLabel) {
  491. m_statusLabel->setText("状态: 就绪");
  492. }
  493. }
  494. }
  495. void RecorderWidget::onSettingsButtonClicked()
  496. {
  497. // 打开设置对话框
  498. QMessageBox::information(this, "设置", "设置功能待实现");
  499. }
  500. void RecorderWidget::updatePreview()
  501. {
  502. if (m_previewWidget && m_previewWidth > 0 && m_previewHeight > 0) {
  503. m_previewWidget->update();
  504. }
  505. }
  506. // 预览显示控制方法实现
  507. void RecorderWidget::showPreview()
  508. {
  509. if (m_previewWidget) {
  510. m_previewWidget->show();
  511. }
  512. }
  513. void RecorderWidget::hidePreview()
  514. {
  515. if (m_previewWidget) {
  516. m_previewWidget->hide();
  517. }
  518. }
  519. bool RecorderWidget::isPreviewVisible() const
  520. {
  521. return m_previewWidget ? m_previewWidget->isVisible() : false;
  522. }
  523. void RecorderWidget::updateStatus()
  524. {
  525. if (m_isRecording || m_isStreaming) {
  526. QTime currentTime = QTime::currentTime();
  527. int elapsed = m_recordStartTime.secsTo(currentTime);
  528. int hours = elapsed / 3600;
  529. int minutes = (elapsed % 3600) / 60;
  530. int seconds = elapsed % 60;
  531. QString timeStr = QString("%1:%2:%3")
  532. .arg(hours, 2, 10, QChar('0'))
  533. .arg(minutes, 2, 10, QChar('0'))
  534. .arg(seconds, 2, 10, QChar('0'));
  535. m_timeLabel->setText(timeStr);
  536. } else {
  537. m_timeLabel->setText("00:00:00");
  538. }
  539. // 更新环形缓冲区统计(仅在初始化后)
  540. if (m_isInitialized) {
  541. uint64_t vdrop = recorder_get_video_rb_dropped();
  542. int vpend = recorder_get_video_rb_pending();
  543. uint64_t adrop = recorder_get_audio_rb_dropped();
  544. int apend = recorder_get_audio_rb_pending();
  545. if (m_vrbLabel) m_vrbLabel->setText(QString("V丢%1/积%2").arg(vdrop).arg(vpend));
  546. if (m_arbLabel) m_arbLabel->setText(QString("A丢%1/积%2").arg(adrop).arg(apend));
  547. } else {
  548. if (m_vrbLabel) m_vrbLabel->setText("V丢0/积0");
  549. if (m_arbLabel) m_arbLabel->setText("A丢0/积0");
  550. }
  551. }
  552. // 静态回调函数实现
  553. void RecorderWidget::onDurationCallback(uint64_t duration)
  554. {
  555. if (s_instance) {
  556. // 可以在这里处理持续时间更新
  557. qDebug() << "Recording duration:" << duration << "ms";
  558. }
  559. }
  560. void RecorderWidget::onErrorCallback(int error)
  561. {
  562. if (s_instance) {
  563. QString errorStr = QString("录制错误: %1").arg(recorder_err2str(error));
  564. QMetaObject::invokeMethod(s_instance, "errorOccurred", Qt::QueuedConnection, Q_ARG(QString, errorStr));
  565. }
  566. }
  567. void RecorderWidget::onDeviceChangeCallback(int type)
  568. {
  569. Q_UNUSED(type);
  570. // 不再自动刷新内部设备列表,由主界面设备选择器负责
  571. }
  572. void RecorderWidget::onPreviewYUVCallback(const unsigned char *data, unsigned int size, int width, int height, int type)
  573. {
  574. if (s_instance && data && size > 0) {
  575. QMutexLocker locker(&s_instance->m_previewMutex);
  576. s_instance->m_previewData = QByteArray(reinterpret_cast<const char*>(data), size);
  577. s_instance->m_previewWidth = width;
  578. s_instance->m_previewHeight = height;
  579. }
  580. }
  581. void RecorderWidget::onPreviewAudioCallback()
  582. {
  583. // 音频预览回调,暂时不处理
  584. }