test_player_with_ui.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. #include <chrono>
  2. #include <string>
  3. #include <thread>
  4. #include <iomanip>
  5. #include "code/base/logger.h"
  6. #include "code/player/player_core_v2.h"
  7. #include "../../AvRecorder/ui/opengl_video_widget.h"
  8. #include "qobject.h"
  9. extern "C" {
  10. #include <libavutil/frame.h>
  11. #include <libavutil/imgutils.h>
  12. }
  13. #include <QApplication>
  14. #include <QWidget>
  15. #include <QVBoxLayout>
  16. #include <QHBoxLayout>
  17. #include <QPushButton>
  18. #include <QLabel>
  19. #include <QSlider>
  20. #include <QFileDialog>
  21. #include <QMessageBox>
  22. #include <QTimer>
  23. #include <QProgressBar>
  24. #include <QGroupBox>
  25. #include <QSpinBox>
  26. #include <QCheckBox>
  27. #include <QComboBox>
  28. #include <QTextEdit>
  29. #include <QSplitter>
  30. #include <QDebug>
  31. #include <QDateTime>
  32. #include <QFile>
  33. #include <QTextStream>
  34. #include <QMetaObject>
  35. using namespace av::player;
  36. using namespace av::utils;
  37. // 播放器事件回调类
  38. class UIPlayerCallback : public PlayerEventCallback {
  39. public:
  40. UIPlayerCallback(QWidget* parent, OpenGLVideoWidget* videoRenderer)
  41. : m_parent(parent), m_videoRenderer(videoRenderer) {}
  42. void onStateChanged(PlayerState newState) override {
  43. std::string stateStr;
  44. switch (newState) {
  45. case PlayerState::Idle: stateStr = "Idle"; break;
  46. case PlayerState::Opening: stateStr = "Opening"; break;
  47. case PlayerState::Stopped: stateStr = "Stopped"; break;
  48. case PlayerState::Playing: stateStr = "Playing"; break;
  49. case PlayerState::Paused: stateStr = "Paused"; break;
  50. case PlayerState::Seeking: stateStr = "Seeking"; break;
  51. case PlayerState::Error: stateStr = "Error"; break;
  52. }
  53. Logger::instance().info("[EVENT] State changed to: " + stateStr);
  54. // 发送信号到UI线程
  55. QMetaObject::invokeMethod(m_parent, "onPlayerStateChanged", Qt::QueuedConnection,
  56. Q_ARG(int, static_cast<int>(newState)));
  57. }
  58. void onPositionChanged(int64_t position) override {
  59. // 发送位置更新到UI线程
  60. QMetaObject::invokeMethod(m_parent, "onPlayerPositionChanged", Qt::QueuedConnection,
  61. Q_ARG(qint64, position));
  62. }
  63. void onMediaInfoChanged(const MediaInfo& info) override {
  64. Logger::instance().info("[EVENT] Media info changed:");
  65. Logger::instance().info(" File: " + info.filename);
  66. Logger::instance().info(" Duration: " + std::to_string(info.duration / 1000000.0) + "s");
  67. Logger::instance().info(" Has Video: " + std::string(info.hasVideo ? "Yes" : "No"));
  68. Logger::instance().info(" Has Audio: " + std::string(info.hasAudio ? "Yes" : "No"));
  69. if (info.hasVideo) {
  70. Logger::instance().info(" Video: " + std::to_string(info.width) + "x" + std::to_string(info.height) + " @ " + std::to_string(info.fps) + " fps");
  71. }
  72. if (info.hasAudio) {
  73. Logger::instance().info(" Audio: " + std::to_string(info.sampleRate) + " Hz, " + std::to_string(info.channels) + " channels");
  74. }
  75. // 发送媒体信息到UI线程
  76. QMetaObject::invokeMethod(m_parent, "onMediaInfoChanged", Qt::QueuedConnection);
  77. }
  78. void onErrorOccurred(const std::string& error) override {
  79. Logger::instance().error("[ERROR] " + error);
  80. // QMetaObject::invokeMethod(m_parent, "onPlayerError", Qt::QueuedConnection,
  81. // Q_ARG(QString, QString::fromStdString(error)));
  82. }
  83. void onFrameDropped(int64_t totalDropped) override {
  84. Logger::instance().warning("[WARNING] Frame dropped, total: " + std::to_string(totalDropped));
  85. }
  86. void onSyncError(double error, const std::string& reason) override {
  87. Logger::instance().warning("[WARNING] Sync error: " + std::to_string(error * 1000) + "ms, reason: " + reason);
  88. }
  89. void onEndOfFile() override {
  90. Logger::instance().info("[EVENT] End of file reached");
  91. QMetaObject::invokeMethod(m_parent, "onPlayerEndOfFile", Qt::QueuedConnection);
  92. }
  93. // 新增的视频渲染回调函数
  94. void onVideoFrameReady(AVFrame* frameData) override
  95. {
  96. if (m_videoRenderer) {
  97. // 将 frameData 转换为 AVFrame* 并进行深度拷贝
  98. AVFrame* originalFrame = static_cast<AVFrame*>(frameData);
  99. AVFrame* copiedFrame = av_frame_clone(originalFrame);
  100. if (copiedFrame) {
  101. // 使用 QueuedConnection 避免死锁
  102. // 当播放器暂停时,如果使用 BlockingQueuedConnection 可能导致死锁
  103. // 因为暂停操作会等待播放线程停止,而播放线程可能正在等待UI线程响应
  104. bool renderResult = false;
  105. QMetaObject::invokeMethod(m_videoRenderer,
  106. "Render",
  107. Qt::QueuedConnection,
  108. Q_ARG(AVFrame*, copiedFrame));
  109. // 注意:使用QueuedConnection时,copiedFrame的释放需要在Render方法内部处理
  110. // 这里不能立即释放,因为调用是异步的
  111. // OpenGLVideoWidget::Render方法需要负责释放传入的AVFrame*
  112. }
  113. }
  114. }
  115. void onVideoRendererInitRequired(int width, int height) override
  116. {
  117. if (m_videoRenderer) {
  118. Logger::instance().info("[EVENT] Video renderer init required: " +
  119. std::to_string(width) + "x" + std::to_string(height));
  120. // QMetaObject::invokeMethod(m_videoRenderer, "Open", Qt::QueuedConnection,
  121. // Q_ARG(int, width),
  122. // Q_ARG(int, height),
  123. // Q_ARG(int, pixelFormat),
  124. // Q_ARG(double, fps));
  125. }
  126. }
  127. void onVideoRendererCloseRequired() override
  128. {
  129. if (m_videoRenderer) {
  130. Logger::instance().info("[EVENT] Video renderer close required");
  131. // QMetaObject::invokeMethod(m_videoRenderer, "Close", Qt::QueuedConnection);
  132. }
  133. }
  134. private:
  135. QWidget* m_parent;
  136. OpenGLVideoWidget* m_videoRenderer;
  137. };
  138. // 主播放器窗口类
  139. class PlayerWindow : public QWidget {
  140. Q_OBJECT
  141. public:
  142. PlayerWindow(QWidget* parent = nullptr) : QWidget(parent) {
  143. setupUI();
  144. setupPlayer();
  145. connectSignals();
  146. // 设置窗口属性
  147. setWindowTitle("AV Player Test with UI");
  148. resize(1280, 900); // 增加窗口大小以适应更多统计信息
  149. // 启动更新定时器
  150. m_updateTimer = new QTimer(this);
  151. connect(m_updateTimer, &QTimer::timeout, this, &PlayerWindow::updateUI);
  152. m_updateTimer->start(100); // 100ms更新一次
  153. m_player->openFile("C:/Users/zhuizhu/Videos/2.mp4");
  154. // playPause();
  155. }
  156. ~PlayerWindow() {
  157. if (m_player) {
  158. m_player->stop();
  159. }
  160. }
  161. public slots:
  162. void onPlayerStateChanged(int state) {
  163. PlayerState playerState = static_cast<PlayerState>(state);
  164. switch (playerState) {
  165. case PlayerState::Idle:
  166. m_stateLabel->setText("状态: 空闲");
  167. m_playButton->setText("播放");
  168. m_playButton->setEnabled(false);
  169. break;
  170. case PlayerState::Opening:
  171. m_stateLabel->setText("状态: 打开中...");
  172. m_playButton->setEnabled(false);
  173. break;
  174. case PlayerState::Stopped:
  175. m_stateLabel->setText("状态: 已停止");
  176. m_playButton->setText("播放");
  177. m_playButton->setEnabled(true);
  178. break;
  179. case PlayerState::Playing:
  180. m_stateLabel->setText("状态: 播放中");
  181. m_playButton->setText("暂停");
  182. m_playButton->setEnabled(true);
  183. break;
  184. case PlayerState::Paused:
  185. m_stateLabel->setText("状态: 已暂停");
  186. m_playButton->setText("播放");
  187. m_playButton->setEnabled(true);
  188. break;
  189. case PlayerState::Seeking:
  190. m_stateLabel->setText("状态: 跳转中...");
  191. break;
  192. case PlayerState::Error:
  193. m_stateLabel->setText("状态: 错误");
  194. m_playButton->setEnabled(false);
  195. break;
  196. }
  197. }
  198. void onPlayerPositionChanged(qint64 position) {
  199. if (!m_seeking) {
  200. // 更新位置标签(秒数显示)
  201. double seconds = position / 1000000.0;
  202. m_positionLabel->setText(QString("位置: %1s").arg(seconds, 0, 'f', 2));
  203. // 我们不在这里更新进度条和时间标签,而是在updateUI中统一更新
  204. // 这样可以避免UI更新过于频繁
  205. }
  206. }
  207. void onMediaInfoChanged() {
  208. if (m_player) {
  209. auto info = m_player->getMediaInfo();
  210. m_duration = info.duration;
  211. QString infoText = QString("文件: %1\n")
  212. .arg(QString::fromStdString(info.filename));
  213. if (info.duration > 0) {
  214. double durationSec = info.duration / 1000000.0;
  215. infoText += QString("时长: %1s\n").arg(durationSec, 0, 'f', 2);
  216. // 更新时间标签显示总时长
  217. m_timeLabel->setText(QString("00:00:00 / %1").arg(formatTime(info.duration)));
  218. }
  219. // 更新视频流复选框状态
  220. m_videoStreamCheckBox->setEnabled(info.hasVideo);
  221. if (!info.hasVideo) {
  222. m_videoStreamCheckBox->setChecked(false);
  223. }
  224. if (info.hasVideo) {
  225. infoText += QString("视频: %1x%2 @ %3fps\n")
  226. .arg(info.width).arg(info.height).arg(info.fps, 0, 'f', 2);
  227. }
  228. // 更新音频流复选框状态
  229. m_audioStreamCheckBox->setEnabled(info.hasAudio);
  230. if (!info.hasAudio) {
  231. m_audioStreamCheckBox->setChecked(false);
  232. }
  233. if (info.hasAudio) {
  234. infoText += QString("音频: %1Hz, %2ch\n")
  235. .arg(info.sampleRate).arg(info.channels);
  236. }
  237. m_infoLabel->setText(infoText);
  238. m_playButton->setEnabled(true);
  239. }
  240. }
  241. void onPlayerError(const QString& error) {
  242. QMessageBox::critical(this, "播放器错误", error);
  243. }
  244. void onPlayerEndOfFile() {
  245. m_playButton->setText("播放");
  246. // 重置进度条和时间显示
  247. m_progressSlider->setValue(0);
  248. // 如果有持续时间,显示0/总时长;否则显示0/0
  249. if (m_duration > 0) {
  250. m_timeLabel->setText(QString("00:00:00 / %1").arg(formatTime(m_duration)));
  251. } else {
  252. m_timeLabel->setText("00:00:00 / 00:00:00");
  253. }
  254. }
  255. private slots:
  256. void openFile() {
  257. QString fileName = QFileDialog::getOpenFileName(this,
  258. "选择媒体文件", "",
  259. "媒体文件 (*.mp4 *.avi *.mkv *.mov *.wmv *.flv *.webm *.mp3 *.wav *.aac *.flac);;所有文件 (*.*)");
  260. if (!fileName.isEmpty()) {
  261. loadFile(fileName.toStdString());
  262. }
  263. }
  264. void refreshStats() {
  265. if (m_player) {
  266. // 调用播放器的dumpStats方法输出详细统计信息到日志
  267. m_player->dumpStats();
  268. // 强制更新UI显示
  269. updateUI();
  270. // 显示提示信息
  271. QMessageBox::information(this, "统计信息", "统计信息已刷新,详细信息已写入日志文件。");
  272. }
  273. }
  274. void playPause()
  275. {
  276. if (!m_player) return;
  277. auto state = m_player->getState();
  278. if (state == PlayerState::Playing) {
  279. m_player->pause();
  280. } else if (state == PlayerState::Paused || state == PlayerState::Stopped) {
  281. m_player->play();
  282. }
  283. }
  284. void stop()
  285. {
  286. if (m_player) {
  287. m_player->stop();
  288. }
  289. }
  290. void onProgressSliderPressed() { m_seeking = true; }
  291. void onProgressSliderReleased() {
  292. if (m_player && m_duration > 0) {
  293. int value = m_progressSlider->value();
  294. int64_t position = (m_duration * value) / 100;
  295. m_player->seek(position);
  296. // 添加一个延迟后再设置m_seeking为false
  297. // 这样可以让seek操作有足够时间完成初始化和缓冲
  298. QTimer::singleShot(500, this, [this]() {
  299. m_seeking = false;
  300. });
  301. return; // 不要立即设置m_seeking为false
  302. }
  303. m_seeking = false;
  304. }
  305. void onProgressSliderMoved(int value) {
  306. if (m_player && m_duration > 0) {
  307. // 计算预览位置
  308. int64_t previewPosition = (m_duration * value) / 100;
  309. // 更新时间标签显示预览时间
  310. QString timeText = QString("%1 / %2")
  311. .arg(formatTime(previewPosition))
  312. .arg(formatTime(m_duration));
  313. m_timeLabel->setText(timeText);
  314. }
  315. }
  316. void onVolumeChanged(int value) {
  317. if (m_player) {
  318. double volume = value / 100.0;
  319. m_player->setVolume(volume);
  320. m_volumeLabel->setText(QString("音量: %1%").arg(value));
  321. }
  322. }
  323. // 将微秒转换为时间字符串 (HH:MM:SS)
  324. QString formatTime(int64_t timeUs) {
  325. int totalSeconds = static_cast<int>(timeUs / 1000000);
  326. int hours = totalSeconds / 3600;
  327. int minutes = (totalSeconds % 3600) / 60;
  328. int seconds = totalSeconds % 60;
  329. return QString("%1:%2:%3")
  330. .arg(hours, 2, 10, QChar('0'))
  331. .arg(minutes, 2, 10, QChar('0'))
  332. .arg(seconds, 2, 10, QChar('0'));
  333. }
  334. void updateUI() {
  335. if (m_player) {
  336. // 更新统计信息
  337. auto stats = m_player->getStats();
  338. // 基本播放统计
  339. QString statsText = QString("帧数: %1\n丢帧: %2 (%3%)\n重复帧: %4\n速度: %5x")
  340. .arg(stats.totalFrames)
  341. .arg(stats.droppedFrames)
  342. .arg(stats.totalFrames > 0 ? (stats.droppedFrames * 100.0 / stats.totalFrames) : 0, 0, 'f', 1)
  343. .arg(stats.duplicatedFrames)
  344. .arg(stats.playbackSpeed, 0, 'f', 2);
  345. // 队列状态
  346. statsText += QString("\n\n队列状态:\n视频帧: %1\n音频帧: %2\n数据包: %3")
  347. .arg(stats.queuedVideoFrames)
  348. .arg(stats.queuedAudioFrames)
  349. .arg(stats.queuedPackets);
  350. // 同步统计 - 获取更详细的同步信息
  351. QString syncText = "\n\n同步状态:\n当前误差: %1 ms\n平均误差: %2 ms\n最大误差: %3 ms";
  352. // 如果播放器有同步器,获取详细的时钟信息
  353. if (m_player) {
  354. auto debugInfo = QString::fromStdString(m_player->getDebugInfo());
  355. // 解析调试信息中的时钟数据
  356. QStringList lines = debugInfo.split('\n');
  357. QString audioClockStr = "N/A", videoClockStr = "N/A", externalClockStr = "N/A", masterClockStr = "N/A";
  358. QString masterTypeStr = "N/A", syncStrategyStr = "N/A";
  359. for (const QString& line : lines) {
  360. if (line.trimmed().startsWith("Audio:")) {
  361. QStringList parts = line.split(":");
  362. if (parts.size() >= 2) {
  363. bool ok;
  364. double value = parts[1].trimmed().toDouble(&ok);
  365. if (ok) {
  366. audioClockStr = QString::number(value, 'f', 3) + "s";
  367. }
  368. }
  369. } else if (line.trimmed().startsWith("Video:")) {
  370. QStringList parts = line.split(":");
  371. if (parts.size() >= 2) {
  372. bool ok;
  373. double value = parts[1].trimmed().toDouble(&ok);
  374. if (ok) {
  375. videoClockStr = QString::number(value, 'f', 3) + "s";
  376. }
  377. }
  378. } else if (line.trimmed().startsWith("External:")) {
  379. QStringList parts = line.split(":");
  380. if (parts.size() >= 2) {
  381. bool ok;
  382. double value = parts[1].trimmed().toDouble(&ok);
  383. if (ok) {
  384. externalClockStr = QString::number(value, 'f', 3) + "s";
  385. }
  386. }
  387. } else if (line.trimmed().startsWith("Master:")) {
  388. QStringList parts = line.split(":");
  389. if (parts.size() >= 2) {
  390. bool ok;
  391. double value = parts[1].trimmed().toDouble(&ok);
  392. if (ok) {
  393. masterClockStr = QString::number(value, 'f', 3) + "s";
  394. }
  395. }
  396. } else if (line.contains("Master Clock Type:")) {
  397. QStringList parts = line.split(":");
  398. if (parts.size() >= 2) {
  399. masterTypeStr = parts[1].trimmed();
  400. }
  401. } else if (line.contains("Sync Strategy:")) {
  402. QStringList parts = line.split(":");
  403. if (parts.size() >= 2) {
  404. syncStrategyStr = parts[1].trimmed();
  405. }
  406. }
  407. }
  408. statsText += debugInfo;
  409. syncText = QString("\n\n同步状态:\n当前误差: %1 ms\n平均误差: %2 ms\n最大误差: %3 "
  410. "ms\n\n时钟信息:\n音频时钟: %4\n视频时钟: %5\n外部时钟: "
  411. "%6\n主时钟: %7\n\n同步配置:\n主时钟类型: %8\n同步策略: %9")
  412. .arg(stats.syncError * 1000, 0, 'f', 1)
  413. .arg(stats.avgSyncError * 1000, 0, 'f', 1)
  414. .arg(stats.maxSyncError * 1000, 0, 'f', 1)
  415. .arg(audioClockStr)
  416. .arg(videoClockStr)
  417. .arg(externalClockStr)
  418. .arg(masterClockStr)
  419. .arg(masterTypeStr)
  420. .arg(syncStrategyStr);
  421. } else {
  422. syncText = syncText.arg(stats.syncError * 1000, 0, 'f', 1)
  423. .arg(stats.avgSyncError * 1000, 0, 'f', 1)
  424. .arg(stats.maxSyncError * 1000, 0, 'f', 1);
  425. }
  426. statsText += syncText;
  427. // 性能统计
  428. // statsText += QString("\n\n性能:\nCPU: %1%\n内存: %2 MB\n比特率: %3 kbps")
  429. // .arg(stats.cpuUsage, 0, 'f', 1)
  430. // .arg(stats.memoryUsage, 0, 'f', 1)
  431. // .arg(stats.bitrate, 0, 'f', 0);
  432. m_statsLabel->setText(statsText);
  433. // 更新时间显示和进度条
  434. if (!m_seeking && m_duration > 0) {
  435. int64_t currentPosition = m_player->getCurrentTime();
  436. // 更新进度条
  437. int progress = static_cast<int>((currentPosition * 100) / m_duration);
  438. m_progressSlider->setValue(progress);
  439. // 更新时间标签
  440. QString timeText = QString("%1 / %2")
  441. .arg(formatTime(currentPosition))
  442. .arg(formatTime(m_duration));
  443. m_timeLabel->setText(timeText);
  444. }
  445. }
  446. }
  447. void onVideoStreamToggled(bool enabled) {
  448. if (m_player) {
  449. m_player->enableVideoStream(enabled);
  450. Logger::instance().info(QString("视频流%1").arg(enabled ? "启用" : "禁用").toStdString());
  451. }
  452. }
  453. void onAudioStreamToggled(bool enabled) {
  454. if (m_player) {
  455. m_player->enableAudioStream(enabled);
  456. Logger::instance().info(QString("音频流%1").arg(enabled ? "启用" : "禁用").toStdString());
  457. }
  458. }
  459. private:
  460. void setupUI() {
  461. auto* mainLayout = new QHBoxLayout(this);
  462. // 左侧视频区域
  463. auto* leftWidget = new QWidget;
  464. auto* leftLayout = new QVBoxLayout(leftWidget);
  465. // 视频渲染器
  466. m_videoRenderer = new OpenGLVideoWidget;
  467. m_videoRenderer->setMinimumSize(640, 480);
  468. leftLayout->addWidget(m_videoRenderer, 1);
  469. // 控制按钮
  470. auto* controlLayout = new QHBoxLayout;
  471. m_openButton = new QPushButton("打开文件");
  472. m_playButton = new QPushButton("播放");
  473. m_stopButton = new QPushButton("停止");
  474. m_playButton->setEnabled(false);
  475. controlLayout->addWidget(m_openButton);
  476. controlLayout->addWidget(m_playButton);
  477. controlLayout->addWidget(m_stopButton);
  478. controlLayout->addStretch();
  479. leftLayout->addLayout(controlLayout);
  480. // 进度条和时间显示
  481. auto* progressLayout = new QHBoxLayout;
  482. m_progressSlider = new QSlider(Qt::Horizontal);
  483. m_progressSlider->setRange(0, 100);
  484. m_timeLabel = new QLabel("00:00:00 / 00:00:00");
  485. progressLayout->addWidget(m_progressSlider);
  486. progressLayout->addWidget(m_timeLabel);
  487. leftLayout->addLayout(progressLayout);
  488. // 音量控制
  489. auto* volumeLayout = new QHBoxLayout;
  490. volumeLayout->addWidget(new QLabel("音量:"));
  491. m_volumeSlider = new QSlider(Qt::Horizontal);
  492. m_volumeSlider->setRange(0, 100);
  493. m_volumeSlider->setValue(100);
  494. m_volumeSlider->setMaximumWidth(200);
  495. m_volumeLabel = new QLabel("音量: 100%");
  496. volumeLayout->addWidget(m_volumeSlider);
  497. volumeLayout->addWidget(m_volumeLabel);
  498. volumeLayout->addStretch();
  499. leftLayout->addLayout(volumeLayout);
  500. // 流控制
  501. auto* streamControlLayout = new QHBoxLayout;
  502. m_videoStreamCheckBox = new QCheckBox("启用视频");
  503. m_audioStreamCheckBox = new QCheckBox("启用音频");
  504. m_videoStreamCheckBox->setChecked(true);
  505. m_audioStreamCheckBox->setChecked(true);
  506. streamControlLayout->addWidget(m_videoStreamCheckBox);
  507. streamControlLayout->addWidget(m_audioStreamCheckBox);
  508. streamControlLayout->addStretch();
  509. leftLayout->addLayout(streamControlLayout);
  510. mainLayout->addWidget(leftWidget, 2);
  511. // 右侧信息面板
  512. auto* rightWidget = new QWidget;
  513. auto* rightLayout = new QVBoxLayout(rightWidget);
  514. rightWidget->setMinimumWidth(300);
  515. rightWidget->setMaximumWidth(350);
  516. // 状态信息
  517. auto* statusGroup = new QGroupBox("状态信息");
  518. auto* statusLayout = new QVBoxLayout(statusGroup);
  519. m_stateLabel = new QLabel("状态: 空闲");
  520. m_positionLabel = new QLabel("位置: 0s");
  521. statusLayout->addWidget(m_stateLabel);
  522. statusLayout->addWidget(m_positionLabel);
  523. rightLayout->addWidget(statusGroup);
  524. // 媒体信息
  525. auto* infoGroup = new QGroupBox("媒体信息");
  526. auto* infoLayout = new QVBoxLayout(infoGroup);
  527. m_infoLabel = new QLabel("未加载文件");
  528. m_infoLabel->setWordWrap(true);
  529. infoLayout->addWidget(m_infoLabel);
  530. rightLayout->addWidget(infoGroup);
  531. // 统计信息
  532. auto* statsGroup = new QGroupBox("播放统计");
  533. auto* statsLayout = new QVBoxLayout(statsGroup);
  534. // 添加刷新和导出按钮
  535. auto* refreshLayout = new QHBoxLayout();
  536. m_refreshStatsButton = new QPushButton("刷新统计");
  537. refreshLayout->addWidget(m_refreshStatsButton);
  538. refreshLayout->addStretch();
  539. statsLayout->addLayout(refreshLayout);
  540. m_statsLabel = new QLabel("帧数: 0\n丢帧: 0 (0.0%)\n重复帧: 0\n速度: 1.0x\n\n队列状态:\n视频帧: 0\n音频帧: 0\n数据包: 0\n\n同步状态:\n当前误差: 0.0 ms\n平均误差: 0.0 ms\n最大误差: 0.0 ms\n\n时钟信息:\n音频时钟: N/A\n视频时钟: N/A\n外部时钟: N/A\n主时钟: N/A\n\n同步配置:\n主时钟类型: N/A\n同步策略: N/A\n\n性能:\nCPU: 0.0%\n内存: 0.0 MB\n比特率: 0 kbps");
  541. m_statsLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
  542. m_statsLabel->setWordWrap(true);
  543. m_statsLabel->setMinimumHeight(450); // 增加最小高度以显示更多同步信息
  544. statsLayout->addWidget(m_statsLabel);
  545. rightLayout->addWidget(statsGroup);
  546. rightLayout->addStretch();
  547. mainLayout->addWidget(rightWidget);
  548. }
  549. void setupPlayer() {
  550. // 创建同步配置
  551. SyncConfigV2 syncConfig;
  552. syncConfig.strategy = SyncStrategy::VIDEO_MASTER;
  553. syncConfig.syncThresholdMin = 0.040;
  554. syncConfig.syncThresholdMax = 0.100;
  555. syncConfig.frameDropThreshold = 0.100;
  556. syncConfig.frameDupThreshold = 0.100;
  557. syncConfig.audioDiffThreshold = 0.100;
  558. syncConfig.audioDiffAvgCoef = 0.01;
  559. syncConfig.audioDiffAvgCount = 20;
  560. syncConfig.clockSpeedMin = 0.9;
  561. syncConfig.clockSpeedMax = 1.1;
  562. syncConfig.clockSpeedStep = 0.001;
  563. syncConfig.minFramesForSync = 2;
  564. syncConfig.maxFramesForSync = 10;
  565. syncConfig.noSyncThreshold = 10.0;
  566. // 创建播放器实例
  567. m_player = std::make_unique<PlayerCoreV2>(syncConfig);
  568. // 注意:不再直接设置视频渲染器,而是通过回调机制处理
  569. // m_player->setOpenGLVideoRenderer(m_videoRenderer); // 已废弃
  570. // 设置初始音量
  571. m_player->setVolume(m_volumeSlider->value() / 100.0);
  572. // 创建事件回调,传入视频渲染器
  573. m_callback = std::make_unique<UIPlayerCallback>(this, m_videoRenderer);
  574. m_player->setEventCallback(m_callback.get());
  575. m_duration = 0;
  576. m_seeking = false;
  577. }
  578. void connectSignals() {
  579. connect(m_openButton, &QPushButton::clicked, this, &PlayerWindow::openFile);
  580. connect(m_playButton, &QPushButton::clicked, this, &PlayerWindow::playPause);
  581. connect(m_stopButton, &QPushButton::clicked, this, &PlayerWindow::stop);
  582. // 进度条信号连接
  583. connect(m_progressSlider, &QSlider::sliderPressed, this, &PlayerWindow::onProgressSliderPressed);
  584. connect(m_progressSlider, &QSlider::sliderReleased, this, &PlayerWindow::onProgressSliderReleased);
  585. connect(m_progressSlider, &QSlider::sliderMoved, this, &PlayerWindow::onProgressSliderMoved);
  586. // 添加进度条点击跳转功能
  587. connect(m_progressSlider, &QSlider::valueChanged, [this](int value) {
  588. if (m_seeking && m_player && m_duration > 0) {
  589. onProgressSliderMoved(value);
  590. }
  591. });
  592. connect(m_volumeSlider, &QSlider::valueChanged, this, &PlayerWindow::onVolumeChanged);
  593. // 连接流控制复选框信号
  594. connect(m_videoStreamCheckBox, &QCheckBox::toggled, this, &PlayerWindow::onVideoStreamToggled);
  595. connect(m_audioStreamCheckBox, &QCheckBox::toggled, this, &PlayerWindow::onAudioStreamToggled);
  596. // 连接统计按钮信号
  597. connect(m_refreshStatsButton, &QPushButton::clicked, this, &PlayerWindow::refreshStats);
  598. }
  599. void loadFile(const std::string& filename) {
  600. if (!m_player) return;
  601. Logger::instance().info("Loading file: " + filename);
  602. // 在打开文件前,根据复选框状态设置流启用状态
  603. m_player->enableVideoStream(m_videoStreamCheckBox->isChecked());
  604. m_player->enableAudioStream(m_audioStreamCheckBox->isChecked());
  605. auto result = m_player->openFile(filename);
  606. if (result != ErrorCode::SUCCESS) {
  607. QString error = QString("无法打开文件: %1\n错误代码: %2")
  608. .arg(QString::fromStdString(filename))
  609. .arg(static_cast<int>(result));
  610. QMessageBox::critical(this, "文件打开失败", error);
  611. Logger::instance().error("Failed to open file: " + std::to_string(static_cast<int>(result)));
  612. }
  613. }
  614. private:
  615. // UI组件
  616. OpenGLVideoWidget* m_videoRenderer;
  617. QPushButton* m_openButton;
  618. QPushButton* m_playButton;
  619. QPushButton* m_stopButton;
  620. QPushButton* m_refreshStatsButton;
  621. QSlider* m_progressSlider;
  622. QSlider* m_volumeSlider;
  623. QLabel* m_stateLabel;
  624. QLabel* m_positionLabel;
  625. QLabel* m_infoLabel;
  626. QLabel* m_statsLabel;
  627. QLabel* m_volumeLabel;
  628. QLabel* m_timeLabel;
  629. QCheckBox* m_videoStreamCheckBox;
  630. QCheckBox* m_audioStreamCheckBox;
  631. QTimer* m_updateTimer;
  632. // 播放器相关
  633. std::unique_ptr<PlayerCoreV2> m_player;
  634. std::unique_ptr<UIPlayerCallback> m_callback;
  635. int64_t m_duration;
  636. bool m_seeking;
  637. };
  638. int main(int argc, char* argv[]) {
  639. QApplication app(argc, argv);
  640. // 初始化日志系统
  641. Logger::instance().initialize("test.log", LogLevel::DEBUG, false, false);
  642. Logger::instance().info("PlayerCoreV2 Example Started");
  643. qRegisterMetaType<AVFrame*>("AVFrame*");
  644. // try {
  645. // 创建播放器窗口
  646. PlayerWindow window;
  647. window.show();
  648. // 如果有命令行参数,自动加载文件
  649. // if (argc > 1) {
  650. // QTimer::singleShot(500, [&window, argv]() {
  651. // QMetaObject::invokeMethod(&window, "loadFile", Qt::QueuedConnection,
  652. // Q_ARG(std::string, std::string(argv[1])));
  653. // });
  654. // } else {
  655. // // 默认加载测试文件
  656. // QTimer::singleShot(500, [&window]() {
  657. // std::string testFile = "C:/Users/zhuizhu/Videos/2.mp4";
  658. // QMetaObject::invokeMethod(&window, "loadFile", Qt::QueuedConnection,
  659. // Q_ARG(std::string, testFile));
  660. // });
  661. // }
  662. return app.exec();
  663. // } catch (const std::exception& e) {
  664. // Logger::instance().error("Exception: " + std::string(e.what()));
  665. // QMessageBox::critical(nullptr, "错误", QString("发生异常: %1").arg(e.what()));
  666. // return 1;
  667. // } catch (...) {
  668. // Logger::instance().error("Unknown exception occurred");
  669. // QMessageBox::critical(nullptr, "错误", "发生未知异常");
  670. // return 1;
  671. // }
  672. }
  673. #include "test_player_with_ui.moc"