|
|
@@ -24,6 +24,9 @@
|
|
|
#include <QTextEdit>
|
|
|
#include <QSplitter>
|
|
|
#include <QDebug>
|
|
|
+#include <QDateTime>
|
|
|
+#include <QFile>
|
|
|
+#include <QTextStream>
|
|
|
|
|
|
using namespace av::player;
|
|
|
using namespace av::utils;
|
|
|
@@ -111,7 +114,7 @@ public:
|
|
|
|
|
|
// 设置窗口属性
|
|
|
setWindowTitle("AV Player Test with UI");
|
|
|
- resize(1200, 800);
|
|
|
+ resize(1280, 900); // 增加窗口大小以适应更多统计信息
|
|
|
|
|
|
// 启动更新定时器
|
|
|
m_updateTimer = new QTimer(this);
|
|
|
@@ -169,13 +172,12 @@ public slots:
|
|
|
|
|
|
void onPlayerPositionChanged(qint64 position) {
|
|
|
if (!m_seeking) {
|
|
|
+ // 更新位置标签(秒数显示)
|
|
|
double seconds = position / 1000000.0;
|
|
|
m_positionLabel->setText(QString("位置: %1s").arg(seconds, 0, 'f', 2));
|
|
|
|
|
|
- if (m_duration > 0) {
|
|
|
- int progress = static_cast<int>((position * 100) / m_duration);
|
|
|
- m_progressSlider->setValue(progress);
|
|
|
- }
|
|
|
+ // 我们不在这里更新进度条和时间标签,而是在updateUI中统一更新
|
|
|
+ // 这样可以避免UI更新过于频繁
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -190,6 +192,9 @@ public slots:
|
|
|
if (info.duration > 0) {
|
|
|
double durationSec = info.duration / 1000000.0;
|
|
|
infoText += QString("时长: %1s\n").arg(durationSec, 0, 'f', 2);
|
|
|
+
|
|
|
+ // 更新时间标签显示总时长
|
|
|
+ m_timeLabel->setText(QString("00:00:00 / %1").arg(formatTime(info.duration)));
|
|
|
}
|
|
|
|
|
|
// 更新视频流复选框状态
|
|
|
@@ -230,6 +235,16 @@ public slots:
|
|
|
|
|
|
void onPlayerEndOfFile() {
|
|
|
m_playButton->setText("播放");
|
|
|
+
|
|
|
+ // 重置进度条和时间显示
|
|
|
+ m_progressSlider->setValue(0);
|
|
|
+
|
|
|
+ // 如果有持续时间,显示0/总时长;否则显示0/0
|
|
|
+ if (m_duration > 0) {
|
|
|
+ m_timeLabel->setText(QString("00:00:00 / %1").arg(formatTime(m_duration)));
|
|
|
+ } else {
|
|
|
+ m_timeLabel->setText("00:00:00 / 00:00:00");
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private slots:
|
|
|
@@ -243,6 +258,98 @@ private slots:
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ void refreshStats() {
|
|
|
+ if (m_player) {
|
|
|
+ // 调用播放器的dumpStats方法输出详细统计信息到日志
|
|
|
+ m_player->dumpStats();
|
|
|
+
|
|
|
+ // 强制更新UI显示
|
|
|
+ updateUI();
|
|
|
+
|
|
|
+ // 显示提示信息
|
|
|
+ QMessageBox::information(this, "统计信息", "统计信息已刷新,详细信息已写入日志文件。");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void exportStats() {
|
|
|
+ if (!m_player) return;
|
|
|
+
|
|
|
+ // 获取保存文件名
|
|
|
+ QString fileName = QFileDialog::getSaveFileName(this,
|
|
|
+ "导出统计信息", "player_stats.txt",
|
|
|
+ "文本文件 (*.txt);;所有文件 (*.*)");
|
|
|
+
|
|
|
+ if (fileName.isEmpty()) return;
|
|
|
+
|
|
|
+ // 获取统计信息
|
|
|
+ auto stats = m_player->getStats();
|
|
|
+ auto mediaInfo = m_player->getMediaInfo();
|
|
|
+ auto debugInfo = QString::fromStdString(m_player->getDebugInfo());
|
|
|
+
|
|
|
+ // 创建文件
|
|
|
+ QFile file(fileName);
|
|
|
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
|
+ QMessageBox::critical(this, "错误", "无法创建文件: " + fileName);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ QTextStream out(&file);
|
|
|
+
|
|
|
+ // 写入时间戳
|
|
|
+ auto now = QDateTime::currentDateTime();
|
|
|
+ out << "播放器统计信息导出 - " << now.toString("yyyy-MM-dd HH:mm:ss") << "\n\n";
|
|
|
+
|
|
|
+ // 写入媒体信息
|
|
|
+ out << "=== 媒体信息 ===\n";
|
|
|
+ out << "文件: " << QString::fromStdString(mediaInfo.filename) << "\n";
|
|
|
+ out << "时长: " << (mediaInfo.duration / 1000000.0) << " 秒\n";
|
|
|
+ if (mediaInfo.hasVideo) {
|
|
|
+ out << "视频: " << mediaInfo.width << "x" << mediaInfo.height << " @ " << mediaInfo.fps << " fps\n";
|
|
|
+ }
|
|
|
+ if (mediaInfo.hasAudio) {
|
|
|
+ out << "音频: " << mediaInfo.sampleRate << " Hz, " << mediaInfo.channels << " 通道\n";
|
|
|
+ }
|
|
|
+ out << "比特率: " << (mediaInfo.bitrate / 1000.0) << " kbps\n\n";
|
|
|
+
|
|
|
+ // 写入播放统计
|
|
|
+ out << "=== 播放统计 ===\n";
|
|
|
+ out << "当前位置: " << (stats.currentTime / 1000000.0) << " 秒\n";
|
|
|
+ out << "总帧数: " << stats.totalFrames << "\n";
|
|
|
+ out << "丢帧数: " << stats.droppedFrames;
|
|
|
+ if (stats.totalFrames > 0) {
|
|
|
+ out << " (" << (stats.droppedFrames * 100.0 / stats.totalFrames) << "%)";
|
|
|
+ }
|
|
|
+ out << "\n";
|
|
|
+ out << "重复帧数: " << stats.duplicatedFrames << "\n";
|
|
|
+ out << "播放速度: " << stats.playbackSpeed << "x\n\n";
|
|
|
+
|
|
|
+ // 写入队列状态
|
|
|
+ out << "=== 队列状态 ===\n";
|
|
|
+ out << "视频帧队列: " << stats.queuedVideoFrames << "\n";
|
|
|
+ out << "音频帧队列: " << stats.queuedAudioFrames << "\n";
|
|
|
+ out << "数据包队列: " << stats.queuedPackets << "\n\n";
|
|
|
+
|
|
|
+ // 写入同步状态
|
|
|
+ out << "=== 同步状态 ===\n";
|
|
|
+ out << "当前同步误差: " << (stats.syncError * 1000) << " ms\n";
|
|
|
+ out << "平均同步误差: " << (stats.avgSyncError * 1000) << " ms\n";
|
|
|
+ out << "最大同步误差: " << (stats.maxSyncError * 1000) << " ms\n\n";
|
|
|
+
|
|
|
+ // 写入性能统计
|
|
|
+ out << "=== 性能统计 ===\n";
|
|
|
+ out << "CPU使用率: " << stats.cpuUsage << "%\n";
|
|
|
+ out << "内存使用: " << stats.memoryUsage << " MB\n";
|
|
|
+ out << "比特率: " << stats.bitrate << " kbps\n\n";
|
|
|
+
|
|
|
+ // 写入调试信息
|
|
|
+ out << "=== 调试信息 ===\n";
|
|
|
+ out << debugInfo << "\n";
|
|
|
+
|
|
|
+ file.close();
|
|
|
+
|
|
|
+ QMessageBox::information(this, "导出成功", "统计信息已成功导出到: " + fileName);
|
|
|
+ }
|
|
|
+
|
|
|
void playPause() {
|
|
|
if (!m_player) return;
|
|
|
|
|
|
@@ -269,10 +376,30 @@ private slots:
|
|
|
int value = m_progressSlider->value();
|
|
|
int64_t position = (m_duration * value) / 100;
|
|
|
m_player->seek(position);
|
|
|
+
|
|
|
+ // 添加一个延迟后再设置m_seeking为false
|
|
|
+ // 这样可以让seek操作有足够时间完成初始化和缓冲
|
|
|
+ QTimer::singleShot(500, this, [this]() {
|
|
|
+ m_seeking = false;
|
|
|
+ });
|
|
|
+ return; // 不要立即设置m_seeking为false
|
|
|
}
|
|
|
m_seeking = false;
|
|
|
}
|
|
|
|
|
|
+ void onProgressSliderMoved(int value) {
|
|
|
+ if (m_player && m_duration > 0) {
|
|
|
+ // 计算预览位置
|
|
|
+ int64_t previewPosition = (m_duration * value) / 100;
|
|
|
+
|
|
|
+ // 更新时间标签显示预览时间
|
|
|
+ QString timeText = QString("%1 / %2")
|
|
|
+ .arg(formatTime(previewPosition))
|
|
|
+ .arg(formatTime(m_duration));
|
|
|
+ m_timeLabel->setText(timeText);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
void onVolumeChanged(int value) {
|
|
|
if (m_player) {
|
|
|
double volume = value / 100.0;
|
|
|
@@ -281,16 +408,66 @@ private slots:
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 将微秒转换为时间字符串 (HH:MM:SS)
|
|
|
+ QString formatTime(int64_t timeUs) {
|
|
|
+ int totalSeconds = static_cast<int>(timeUs / 1000000);
|
|
|
+ int hours = totalSeconds / 3600;
|
|
|
+ int minutes = (totalSeconds % 3600) / 60;
|
|
|
+ int seconds = totalSeconds % 60;
|
|
|
+
|
|
|
+ return QString("%1:%2:%3")
|
|
|
+ .arg(hours, 2, 10, QChar('0'))
|
|
|
+ .arg(minutes, 2, 10, QChar('0'))
|
|
|
+ .arg(seconds, 2, 10, QChar('0'));
|
|
|
+ }
|
|
|
+
|
|
|
void updateUI() {
|
|
|
if (m_player) {
|
|
|
// 更新统计信息
|
|
|
auto stats = m_player->getStats();
|
|
|
- QString statsText = QString("帧数: %1\n丢帧: %2\n重复帧: %3\n速度: %4x")
|
|
|
+
|
|
|
+ // 基本播放统计
|
|
|
+ QString statsText = QString("帧数: %1\n丢帧: %2 (%3%)\n重复帧: %4\n速度: %5x")
|
|
|
.arg(stats.totalFrames)
|
|
|
.arg(stats.droppedFrames)
|
|
|
+ .arg(stats.totalFrames > 0 ? (stats.droppedFrames * 100.0 / stats.totalFrames) : 0, 0, 'f', 1)
|
|
|
.arg(stats.duplicatedFrames)
|
|
|
.arg(stats.playbackSpeed, 0, 'f', 2);
|
|
|
+
|
|
|
+ // 队列状态
|
|
|
+ statsText += QString("\n\n队列状态:\n视频帧: %1\n音频帧: %2\n数据包: %3")
|
|
|
+ .arg(stats.queuedVideoFrames)
|
|
|
+ .arg(stats.queuedAudioFrames)
|
|
|
+ .arg(stats.queuedPackets);
|
|
|
+
|
|
|
+ // 同步统计
|
|
|
+ statsText += QString("\n\n同步状态:\n当前误差: %1 ms\n平均误差: %2 ms\n最大误差: %3 ms")
|
|
|
+ .arg(stats.syncError * 1000, 0, 'f', 1)
|
|
|
+ .arg(stats.avgSyncError * 1000, 0, 'f', 1)
|
|
|
+ .arg(stats.maxSyncError * 1000, 0, 'f', 1);
|
|
|
+
|
|
|
+ // 性能统计
|
|
|
+ statsText += QString("\n\n性能:\nCPU: %1%\n内存: %2 MB\n比特率: %3 kbps")
|
|
|
+ .arg(stats.cpuUsage, 0, 'f', 1)
|
|
|
+ .arg(stats.memoryUsage, 0, 'f', 1)
|
|
|
+ .arg(stats.bitrate, 0, 'f', 0);
|
|
|
+
|
|
|
m_statsLabel->setText(statsText);
|
|
|
+
|
|
|
+ // 更新时间显示和进度条
|
|
|
+ if (!m_seeking && m_duration > 0) {
|
|
|
+ int64_t currentPosition = m_player->getCurrentTime();
|
|
|
+
|
|
|
+ // 更新进度条
|
|
|
+ int progress = static_cast<int>((currentPosition * 100) / m_duration);
|
|
|
+ m_progressSlider->setValue(progress);
|
|
|
+
|
|
|
+ // 更新时间标签
|
|
|
+ QString timeText = QString("%1 / %2")
|
|
|
+ .arg(formatTime(currentPosition))
|
|
|
+ .arg(formatTime(m_duration));
|
|
|
+ m_timeLabel->setText(timeText);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -335,10 +512,15 @@ private:
|
|
|
|
|
|
leftLayout->addLayout(controlLayout);
|
|
|
|
|
|
- // 进度条
|
|
|
+ // 进度条和时间显示
|
|
|
+ auto* progressLayout = new QHBoxLayout;
|
|
|
m_progressSlider = new QSlider(Qt::Horizontal);
|
|
|
m_progressSlider->setRange(0, 100);
|
|
|
- leftLayout->addWidget(m_progressSlider);
|
|
|
+ m_timeLabel = new QLabel("00:00:00 / 00:00:00");
|
|
|
+
|
|
|
+ progressLayout->addWidget(m_progressSlider);
|
|
|
+ progressLayout->addWidget(m_timeLabel);
|
|
|
+ leftLayout->addLayout(progressLayout);
|
|
|
|
|
|
// 音量控制
|
|
|
auto* volumeLayout = new QHBoxLayout;
|
|
|
@@ -369,7 +551,8 @@ private:
|
|
|
// 右侧信息面板
|
|
|
auto* rightWidget = new QWidget;
|
|
|
auto* rightLayout = new QVBoxLayout(rightWidget);
|
|
|
- rightWidget->setMaximumWidth(300);
|
|
|
+ rightWidget->setMinimumWidth(300);
|
|
|
+ rightWidget->setMaximumWidth(350);
|
|
|
|
|
|
// 状态信息
|
|
|
auto* statusGroup = new QGroupBox("状态信息");
|
|
|
@@ -391,7 +574,20 @@ private:
|
|
|
// 统计信息
|
|
|
auto* statsGroup = new QGroupBox("播放统计");
|
|
|
auto* statsLayout = new QVBoxLayout(statsGroup);
|
|
|
- m_statsLabel = new QLabel("帧数: 0\n丢帧: 0\n重复帧: 0\n速度: 1.0x");
|
|
|
+
|
|
|
+ // 添加刷新和导出按钮
|
|
|
+ auto* refreshLayout = new QHBoxLayout();
|
|
|
+ m_refreshStatsButton = new QPushButton("刷新统计");
|
|
|
+ m_exportStatsButton = new QPushButton("导出统计");
|
|
|
+ refreshLayout->addWidget(m_refreshStatsButton);
|
|
|
+ refreshLayout->addWidget(m_exportStatsButton);
|
|
|
+ refreshLayout->addStretch();
|
|
|
+ statsLayout->addLayout(refreshLayout);
|
|
|
+
|
|
|
+ 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性能:\nCPU: 0.0%\n内存: 0.0 MB\n比特率: 0 kbps");
|
|
|
+ m_statsLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
|
|
+ m_statsLabel->setWordWrap(true);
|
|
|
+ m_statsLabel->setMinimumHeight(300); // 设置最小高度以显示所有统计信息
|
|
|
statsLayout->addWidget(m_statsLabel);
|
|
|
rightLayout->addWidget(statsGroup);
|
|
|
|
|
|
@@ -439,14 +635,27 @@ private:
|
|
|
connect(m_playButton, &QPushButton::clicked, this, &PlayerWindow::playPause);
|
|
|
connect(m_stopButton, &QPushButton::clicked, this, &PlayerWindow::stop);
|
|
|
|
|
|
+ // 进度条信号连接
|
|
|
connect(m_progressSlider, &QSlider::sliderPressed, this, &PlayerWindow::onProgressSliderPressed);
|
|
|
connect(m_progressSlider, &QSlider::sliderReleased, this, &PlayerWindow::onProgressSliderReleased);
|
|
|
+ connect(m_progressSlider, &QSlider::sliderMoved, this, &PlayerWindow::onProgressSliderMoved);
|
|
|
+
|
|
|
+ // 添加进度条点击跳转功能
|
|
|
+ connect(m_progressSlider, &QSlider::valueChanged, [this](int value) {
|
|
|
+ if (m_seeking && m_player && m_duration > 0) {
|
|
|
+ onProgressSliderMoved(value);
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
connect(m_volumeSlider, &QSlider::valueChanged, this, &PlayerWindow::onVolumeChanged);
|
|
|
|
|
|
// 连接流控制复选框信号
|
|
|
connect(m_videoStreamCheckBox, &QCheckBox::toggled, this, &PlayerWindow::onVideoStreamToggled);
|
|
|
connect(m_audioStreamCheckBox, &QCheckBox::toggled, this, &PlayerWindow::onAudioStreamToggled);
|
|
|
+
|
|
|
+ // 连接统计按钮信号
|
|
|
+ connect(m_refreshStatsButton, &QPushButton::clicked, this, &PlayerWindow::refreshStats);
|
|
|
+ connect(m_exportStatsButton, &QPushButton::clicked, this, &PlayerWindow::exportStats);
|
|
|
}
|
|
|
|
|
|
void loadFile(const std::string& filename) {
|
|
|
@@ -474,6 +683,8 @@ private:
|
|
|
QPushButton* m_openButton;
|
|
|
QPushButton* m_playButton;
|
|
|
QPushButton* m_stopButton;
|
|
|
+ QPushButton* m_refreshStatsButton;
|
|
|
+ QPushButton* m_exportStatsButton;
|
|
|
QSlider* m_progressSlider;
|
|
|
QSlider* m_volumeSlider;
|
|
|
QLabel* m_stateLabel;
|
|
|
@@ -481,6 +692,7 @@ private:
|
|
|
QLabel* m_infoLabel;
|
|
|
QLabel* m_statsLabel;
|
|
|
QLabel* m_volumeLabel;
|
|
|
+ QLabel* m_timeLabel;
|
|
|
QCheckBox* m_videoStreamCheckBox;
|
|
|
QCheckBox* m_audioStreamCheckBox;
|
|
|
QTimer* m_updateTimer;
|