recorderwidget.cpp 20 KB

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