feat: 新增视频进度条控制功能

map
cbwu 1 week ago
parent 99ceb0ea24
commit 3b11048e1b

@ -357,7 +357,14 @@ void MainWindow::openSavedVideoDirSlot() {
ui->videoControlWidget->updateVideoSlider(
currentTime, duration);
});
int i = 0;
connect(ui->videoControlWidget,
&VideoControl::seekToVideoSignal, ui->videoWidget,
&VideoWidget::seekToVideo, Qt::UniqueConnection);
connect(ui->videoControlWidget,
&VideoControl::changePlaySpeedSignal, ui->videoWidget,
&VideoWidget::setPlaySpeed, Qt::UniqueConnection);
}
}
}
@ -477,6 +484,8 @@ void MainWindow::closeVideoSlot() {
ui->videoWidget3->stopPlay();
ui->videoWidget4->stopPlay();
ui->videoControlWidget->resetVideoSlider();
emit sendErrorMessage("视频流已关闭!", NotifyType::SUCCESS);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -30,5 +30,12 @@
<file>images/playFast.png</file>
<file>images/playSlow.png</file>
<file>images/map3D.png</file>
<file>images/play0.5X.png</file>
<file>images/play0.75X.png</file>
<file>images/play1.5X.png</file>
<file>images/play1.25X.png</file>
<file>images/play1.75X.png</file>
<file>images/play1X.png</file>
<file>images/play2X.png</file>
</qresource>
</RCC>

@ -24,6 +24,11 @@ void AVPacketQueueManager::enqueuePushPacket(AVPacket *pkt) {
if (clonedPacket) m_pushQueue.enqueue(clonedPacket);
}
void AVPacketQueueManager::enqueueSeekTime(int64_t time) {
QMutexLocker locker(&m_seekMutex);
m_seekQueue.enqueue(time);
}
AVPacket *AVPacketQueueManager::dequeueDecodePacket() {
QMutexLocker locker(&m_decodeMutex);
if (!m_decodeQueue.isEmpty()) {
@ -51,6 +56,11 @@ AVPacket *AVPacketQueueManager::dequeuePushPacket() {
return nullptr;
}
int64_t AVPacketQueueManager::dequeueSeekTime() {
QMutexLocker locker(&m_seekMutex);
return m_seekQueue.dequeue();
}
void AVPacketQueueManager::clearDecodeQueue() {
QMutexLocker locker(&m_decodeMutex);
m_decodeQueue.clear();
@ -66,6 +76,11 @@ void AVPacketQueueManager::clearPushQueue() {
m_pushQueue.clear();
}
void AVPacketQueueManager::clearSeekQueue() {
QMutexLocker locker(&m_seekMutex);
m_seekQueue.clear();
}
bool AVPacketQueueManager::isEmptyDecodeQueue() {
QMutexLocker locker(&m_decodeMutex);
return m_decodeQueue.isEmpty();
@ -75,3 +90,8 @@ bool AVPacketQueueManager::isEmptySaveQueue() {
QMutexLocker locker(&m_saveMutex);
return m_saveQueue.isEmpty();
}
bool AVPacketQueueManager::isEmptySeekQueue() {
QMutexLocker locker(&m_seekMutex);
return m_seekQueue.isEmpty();
}

@ -13,26 +13,37 @@ public:
void enqueueDecodePacket(AVPacket* pkt);
void enqueueSavePacket(AVPacket* pkt);
void enqueuePushPacket(AVPacket* pkt);
void enqueueSeekTime(int64_t time);
AVPacket* dequeueDecodePacket();
AVPacket* dequeueSavePacket();
AVPacket* dequeuePushPacket();
int64_t dequeueSeekTime();
void clearDecodeQueue();
void clearSaveQueue();
void clearPushQueue();
void clearSeekQueue();
bool isEmptyDecodeQueue();
bool isEmptySaveQueue();
bool isEmptySeekQueue();
public:
std::atomic<bool> m_isSeeking{false}; // 是否正在跳转
std::atomic<bool> m_isReadEnd{false};
std::atomic<bool> m_isDecodeEnd{false};
private:
int QUEUECAPACITY = 100;
QQueue<AVPacket*> m_decodeQueue;
QQueue<AVPacket*> m_saveQueue;
QQueue<AVPacket*> m_pushQueue;
QQueue<int64_t> m_seekQueue; // 存储目标时间戳
QMutex m_decodeMutex; // 共享的互斥锁
QMutex m_saveMutex; // 共享的互斥锁
QMutex m_pushMutex; // 共享的互斥锁
QMutex m_seekMutex; // 共享的互斥锁
};
#endif // AVPACKETQUEUEMANAGER_H

@ -1,6 +1,6 @@
#include "decodestream.h"
DecodeStream::DecodeStream(QObject *parent) : QObject{parent} {}
DecodeStream::DecodeStream(QObject *parent) : QObject{parent}, m_playSpeed{1} {}
bool DecodeStream::init(AVPacketQueueManager *queueManager,
AVFormatContext *formatContext, int videoIndex) {
@ -17,6 +17,12 @@ bool DecodeStream::init(AVPacketQueueManager *queueManager,
} else {
m_isFile = true;
}
m_fps = av_q2d(formatContext->streams[videoIndex]->avg_frame_rate);
if (m_fps <= 0) {
m_fps = 30.0; // 默认帧率
}
return initDecoder(formatContext, videoIndex);
}
@ -34,31 +40,98 @@ void DecodeStream::startDecode() {
return;
}
bool isPlayEnd = false;
// m_firstFramePTS = AV_NOPTS_VALUE;
// m_startDecodeTime = AV_NOPTS_VALUE;
int64_t lastPts = AV_NOPTS_VALUE;
auto lastDisplayTime =
std::chrono::high_resolution_clock::now(); // 记录上一帧的显示时间
m_queueManager->m_isDecodeEnd = false;
double startTime = m_startTime / static_cast<double>(AV_TIME_BASE);
int64_t duration = static_cast<int64_t>(m_duration / AV_TIME_BASE);
while (m_start) {
try {
// 检查跳转状态
if (m_queueManager->m_isSeeking) {
m_queueManager->clearDecodeQueue();
// 清空解码器缓冲区
avcodec_flush_buffers(m_codecContext);
continue;
}
AVPacket *inputPacket = m_queueManager->dequeueDecodePacket();
if (inputPacket) {
AVFrame *frame = decodePacket(inputPacket);
if (frame) {
// 播放本地视频
if (m_isFile) {
// 计算帧间隔
if (lastPts != AV_NOPTS_VALUE &&
frame->pts != AV_NOPTS_VALUE) {
int64_t ptsDiff = frame->pts - lastPts;
if (ptsDiff < 0) {
// 如果 ptsDiff 为负数,说明 pts
// 不连续,跳过该帧
continue;
}
double frameDuration = av_q2d(m_outStreamTimeBase) *
ptsDiff; // 转换为秒
// 根据播放速度调整
double adjustedDuration =
frameDuration / m_playSpeed;
// 计算实际延迟
auto now =
std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<
std::chrono::milliseconds>(
now - lastDisplayTime)
.count() /
1000.0;
double sleepTime = adjustedDuration - elapsed;
if (sleepTime > 0) {
std::this_thread::sleep_for(
std::chrono::microseconds(
static_cast<int64_t>(
sleepTime *
1000000))); // 微秒级延迟
}
// qDebug() << "delay:" << sleepTime * 1000;
}
lastPts = frame->pts;
lastDisplayTime = std::chrono::high_resolution_clock::
now(); // 更新上一帧的显示时间
// qDebug() << frame->best_effort_timestamp;
// int64_t aaa = frame->best_effort_timestamp;
// 更新当前播放时间进度
if (frame && m_isFile) {
double currentTime = frame->best_effort_timestamp *
av_q2d(m_outStreamTimeBase);
double startTime =
m_startTime / static_cast<double>(AV_TIME_BASE);
int64_t duration =
static_cast<int64_t>(m_duration / AV_TIME_BASE);
qDebug() << currentTime - startTime;
// qDebug() << currentTime - startTime;
int64_t currentTime1 =
static_cast<int64_t>(currentTime - startTime);
emit updateVideoCurrentTime(currentTime1, duration);
if (m_queueManager->isEmptyDecodeQueue()) isPlayEnd = true;
if (m_queueManager->isEmptyDecodeQueue() &&
m_queueManager->m_isReadEnd) {
isPlayEnd = true;
}
}
emit repaintSignal(frame);
}
av_packet_unref(inputPacket);
av_packet_free(&inputPacket);
inputPacket = nullptr;
if (isPlayEnd && m_isFile) break;
if (isPlayEnd && m_isFile && !m_queueManager->m_isSeeking) {
m_queueManager->m_isDecodeEnd = true;
break;
}
} else {
// QThread::usleep(1000);
// av_usleep(1000);
@ -70,9 +143,15 @@ void DecodeStream::startDecode() {
free();
qDebug() << "Decoding Thread End!";
emit decodeEndSignal();
emit sendErrorMessageSignal("视频解码结束!", 1);
}
void DecodeStream::changePlaySpeedSlot(double speed) {
m_playSpeed = speed;
qDebug() << "receive changePlaySpeedSlot:" << m_playSpeed;
}
void DecodeStream::close() {
m_start = false;
qDebug() << "decode Stream close!" << m_start;

@ -7,6 +7,7 @@
#include <QObject>
#include <QQueue>
#include <QThread>
#include <chrono>
#include "avpacketqueuemanager.h"
#include "ffmpeginclude.h"
@ -23,12 +24,13 @@ public:
AVCodecParserContext *getParserContext();
public slots:
void startDecode();
void changePlaySpeedSlot(double speed);
signals:
void repaintSignal(AVFrame *frame); // 重绘
void startDecodeSignal();
void sendErrorMessageSignal(QString message, int type);
void updateVideoCurrentTime(int currentTime, int duration);
void decodeEndSignal();
private:
bool initObject(); // 初始化对象
@ -54,6 +56,11 @@ private:
int64_t m_startTime;
int64_t m_duration;
bool m_isFile = false;
int m_fps;
std::atomic<double> m_playSpeed;
int64_t m_firstFramePTS;
int64_t m_startDecodeTime;
AVCodecParserContext *m_parser = nullptr;
};

@ -92,7 +92,10 @@ bool ReadStream::openFile(const QString &url) {
m_videoDuration = -1;
}
m_startTime = -1;
m_startTime = m_formatContext->start_time;
m_inStreamTimeBase = m_formatContext->streams[m_videoIndex]->time_base;
// m_startTime = -1;
m_pullURL = url;
return initObject();
}
@ -216,6 +219,20 @@ double ReadStream::getVideoDuration() {
return m_videoDuration;
}
void ReadStream::seekToVideo(int64_t targetTimestamp) {
qDebug() << "Receive targetTimeStamp!**************";
if (m_formatContext) {
// double starttime = m_startTime / static_cast<double>(AV_TIME_BASE);
int64_t aaa =
targetTimestamp * static_cast<double>(AV_TIME_BASE) + m_startTime;
int64_t bbb = av_rescale_q(aaa, AV_TIME_BASE_Q, m_inStreamTimeBase);
// double tmp = (targetTimestamp + starttime) /
// av_q2d(m_inStreamTimeBase);
qDebug() << "tmp:" << bbb;
m_queueManager.enqueueSeekTime(bbb);
};
}
void ReadStream::startPullStream() {
// 如果没有打开则返回
if (!m_formatContext) {
@ -282,6 +299,93 @@ void ReadStream::startPullStream() {
qDebug() << "read Stream End!";
}
void ReadStream::startReadFileStream() {
// 如果没有打开则返回
if (!m_formatContext) {
return;
}
qDebug() << "readStreamThreadID:" << QThread::currentThreadId();
m_start = true;
int readRet;
// 读取数据包
while (m_start) {
// 检查跳转请求
if (!m_queueManager.isEmptySeekQueue()) {
int64_t targetTimestamp = m_queueManager.dequeueSeekTime();
// 设置跳转状态
m_queueManager.m_isSeeking = true;
// 尝试跳转
// ;
// av_seek_frame(m_formatContext, -1, targetTimestamp,
// AVSEEK_FLAG_ANY);
if (avformat_seek_file(m_formatContext, m_videoIndex, INT64_MIN,
targetTimestamp, INT64_MAX,
AVSEEK_FLAG_ANY) >= 0) {
m_queueManager.clearDecodeQueue();
m_queueManager.m_isReadEnd = false;
} else {
}
// 恢复读取
m_queueManager.m_isSeeking = false;
}
readRet = av_read_frame(m_formatContext, m_inputPacket);
if (readRet == AVERROR(EAGAIN)) { // 暂时没有数据流
qDebug() << "No Stream Data";
av_packet_unref(m_inputPacket);
av_usleep(30000); // 等待 30 毫秒
continue;
} else if (readRet == AVERROR_EOF) { // 流文件结束
av_packet_unref(m_inputPacket);
m_queueManager.m_isReadEnd = true;
if (m_queueManager.m_isDecodeEnd) {
qDebug() << "Stream End";
close();
break;
} else {
av_usleep(2000);
continue;
}
} else if (readRet < 0) { // 发生错误
qDebug() << "Stream Error" << readRet;
if (m_streamDecoder) {
m_streamDecoder->close();
}
if (m_streamPusher) {
m_streamPusher->close();
}
m_queueManager.m_isReadEnd = true;
break;
}
if (m_inputPacket->stream_index == m_videoIndex &&
readRet != AVERROR_EOF) {
if (isValidAVPacket(m_inputPacket)) {
// if (test) {
// qDebug() << m_inputPacket->pts;
// int i = 0;
// }
if (m_decodeStreamFlag) {
m_queueManager.enqueueDecodePacket(m_inputPacket);
}
if (m_pushStreamFlag) {
m_queueManager.enqueuePushPacket(m_inputPacket);
}
}
}
av_packet_unref(m_inputPacket);
// QThread::usleep(2000); // 等待 2 毫秒
av_usleep(1000);
}
clear();
free();
qDebug() << "read Stream End!";
}
void ReadStream::startPullUDPStream() {
initObject();
// parser = av_parser_init(AV_CODEC_ID_H264);

@ -34,8 +34,11 @@ public:
bool initSavedRawFile(QString fileDir = "./dat", QString uavName = "fp98");
double getVideoDuration();
void seekToVideo(int64_t targetTimestamp);
public slots:
void startPullStream();
void startReadFileStream();
void startPullUDPStream();
void closeUDPConnectionSLot();
signals:
@ -76,12 +79,11 @@ private:
qreal m_frameRate = 0; // 视频帧率
QSize m_size; // 视频分辨率大小
uchar *m_buffer = nullptr;
bool m_start = true;
bool m_end = false;
int64_t m_startTime;
std::atomic<bool> m_start{true};
std::atomic<bool> m_end{false};
// int64_t m_startTime;
AVFormatContext *m_formatContext = nullptr; // 解封装上下文
AVCodecContext *m_codecContext = nullptr; // 解码器上下文
// SwsContext* m_swsContext = nullptr; // 图像转换上下文
AVPacket *m_inputPacket = nullptr; // 数据包
int m_videoIndex = 0; // 视频流索引
@ -94,6 +96,11 @@ private:
// QMutex m_mutex;
AVPacketQueueManager m_queueManager;
AVRational m_inStreamTimeBase;
int64_t m_startTime;
bool test = false;
private:
QFile m_saveFile;
QUdpSocket *udpSocket = nullptr;

@ -52,6 +52,11 @@ VideoWidget::~VideoWidget() {
}
bool VideoWidget::play(const QString &url, bool bSave) {
if (this->getPlayStatus()) {
emit sendErrorMessageSignal("视频窗口已占用!", 2);
return false;
}
if (url.isEmpty()) return false;
if (!m_pullFlag) {
m_pullFlag = pullStream(url, bSave);
@ -73,7 +78,13 @@ bool VideoWidget::play(const QString &url, bool bSave) {
if (!bSave) {
connect(decodeStreamer, &DecodeStream::updateVideoCurrentTime, this,
&VideoWidget::receiveVideoCurrentTime);
&VideoWidget::receiveVideoCurrentTime, Qt::UniqueConnection);
connect(this, &VideoWidget::changePlaySpeedSignal, decodeStreamer,
&DecodeStream::changePlaySpeedSlot, Qt::UniqueConnection);
connect(decodeStreamer, &DecodeStream::decodeEndSignal, this,
&VideoWidget::stopPlay, Qt::UniqueConnection);
}
decodeStreamer->moveToThread(&decodeStreamThread);
@ -92,6 +103,11 @@ bool VideoWidget::play(const QString &url, bool bSave) {
}
bool VideoWidget::udpPlay(QString ip, int port) {
if (this->getPlayStatus()) {
emit sendErrorMessageSignal("视频窗口已占用!", 2);
return false;
}
if (!m_pullFlag) {
m_pullFlag = pullUDPStream(ip, port);
if (!m_pullFlag) {
@ -229,6 +245,20 @@ double VideoWidget::getVideoDuration() {
return -1;
}
bool VideoWidget::seekToVideo(int64_t targetTime) {
if (readStreamer) {
readStreamer->seekToVideo(targetTime);
}
return true;
}
void VideoWidget::setPlaySpeed(double speed) {
if (decodeStreamer) {
decodeStreamer->changePlaySpeedSlot(speed);
}
emit changePlaySpeedSignal(speed);
}
void VideoWidget::repaint(AVFrame *frame) {
try {
QMutexLocker locker(&m_mutex);
@ -585,8 +615,16 @@ bool VideoWidget::pullStream(const QString &url, bool bSave) {
if (url.isEmpty()) return false;
if (!readStreamer) {
readStreamer = new ReadStream;
connect(readStreamer, &ReadStream::startPullStreamSignal, readStreamer,
&ReadStream::startPullStream, Qt::UniqueConnection);
if (bSave) {
connect(readStreamer, &ReadStream::startPullStreamSignal,
readStreamer, &ReadStream::startPullStream,
Qt::UniqueConnection);
} else {
connect(readStreamer, &ReadStream::startPullStreamSignal,
readStreamer, &ReadStream::startReadFileStream,
Qt::UniqueConnection);
}
connect(readStreamer, &ReadStream::sendErrorMessageSignal, this,
&VideoWidget::receiveErrorMessage, Qt::UniqueConnection);
// connect(this, &VideoWidget::startPullSignal, readStreamer,
@ -662,7 +700,8 @@ void VideoWidget::receiveErrorMessage(QString message, int type) {
emit sendErrorMessageSignal(message, type);
}
void VideoWidget::receiveVideoCurrentTime(int currentTime, int duration) {
void VideoWidget::receiveVideoCurrentTime(int64_t currentTime,
int64_t duration) {
emit updateVideoCurrentTimeSignal(currentTime, duration);
}

@ -54,6 +54,9 @@ public:
bool getPlayStatus();
double getVideoDuration();
bool seekToVideo(int64_t targetTime);
void setPlaySpeed(double speed);
protected:
void initializeGL() override; // 初始化gl
void resizeGL(int w, int h) override; // 窗口尺寸变化
@ -91,7 +94,7 @@ private:
bool pullStream(const QString &url, bool bSave = true);
bool pullUDPStream(QString ip, int port);
void receiveErrorMessage(QString message, int type);
void receiveVideoCurrentTime(int currentTime, int duration);
void receiveVideoCurrentTime(int64_t currentTime, int64_t duration);
private:
QString m_pullURL;
@ -121,7 +124,8 @@ signals:
void startPullSignal();
void sendErrorMessageSignal(QString message, int type);
void closeUDPConnectionSignal();
void updateVideoCurrentTimeSignal(int currentTime, int duration);
void updateVideoCurrentTimeSignal(int64_t currentTime, int64_t duration);
void changePlaySpeedSignal(double speed);
public:
QSizeF getCurImgSize();

@ -2,11 +2,13 @@
#include "ui_videoControl.h"
VideoControl::VideoControl(QWidget *parent)
VideoControl::VideoControl(QWidget* parent)
: QWidget(parent), ui(new Ui::VideoControl) {
ui->setupUi(this);
// 其他初始化代码...
initIconButton();
m_playSpeed = 1.0;
}
VideoControl::~VideoControl() {
@ -27,6 +29,12 @@ void VideoControl::updateVideoSlider(int currentTime, int duration) {
ui->videoSlider->setValue(currentTime);
}
void VideoControl::resetVideoSlider() {
ui->label->setText("00:00:00");
ui->durationLabel->setText("00:00:00");
ui->videoSlider->setValue(0);
}
void VideoControl::initIconButton() {
ui->pbPlayer->setIcon(QIcon(":/images/playMedio.png"));
ui->pbPlayer->setIconSize(QSize(32, 32));
@ -148,3 +156,65 @@ void VideoControl::on_pbStop_clicked() {
emit stopVideoSignal();
ui->pbStop->setDisabled(false);
}
void VideoControl::on_videoSlider_sliderReleased() {
m_bSliderPressed = false;
int currentValue = ui->videoSlider->value();
if (currentValue != m_sliderPressedValue) {
emit seekToVideoSignal(currentValue);
}
}
void VideoControl::on_videoSlider_sliderPressed() {
m_bSliderPressed = true;
m_sliderPressedValue = ui->videoSlider->value();
}
void VideoControl::on_videoSlider_actionTriggered(int action) {
onSliderPressed();
if (m_bSliderPressed) {
} else {
qDebug() << "actionTriggered Value:" << ui->videoSlider->value();
emit seekToVideoSignal(ui->videoSlider->value());
}
}
void VideoControl::onSliderPressed() {
QSlider* slider = ui->videoSlider;
// 获取鼠标位置
QPoint pos = slider->mapFromGlobal(QCursor::pos());
// 计算点击位置对应的值
int value = slider->minimum() + (slider->maximum() - slider->minimum()) *
pos.x() / slider->width();
slider->setValue(value);
}
void VideoControl::on_pbFast_clicked() {
ui->pbSlow->setIcon(QIcon(":/images/playSlow.png"));
if (m_playSpeed <= 1.0) {
ui->pbFast->setIcon(QIcon(":/images/play1.5X.png"));
m_playSpeed = 1.5;
} else if (m_playSpeed == 1.5) {
ui->pbFast->setIcon(QIcon(":/images/play2X.png"));
m_playSpeed = 2.0;
} else if (m_playSpeed == 2.0) {
ui->pbFast->setIcon(QIcon(":/images/playFast.png"));
m_playSpeed = 1.0;
}
emit changePlaySpeedSignal(m_playSpeed);
}
void VideoControl::on_pbSlow_clicked() {
ui->pbFast->setIcon(QIcon(":/images/playFast.png"));
if (m_playSpeed >= 1.0) {
ui->pbSlow->setIcon(QIcon(":/images/play0.75X.png"));
m_playSpeed = 0.75;
} else if (m_playSpeed == 0.75) {
ui->pbSlow->setIcon(QIcon(":/images/play0.5X.png"));
m_playSpeed = 0.5;
} else if (m_playSpeed == 0.5) {
ui->pbSlow->setIcon(QIcon(":/images/playSlow.png"));
m_playSpeed = 1.0;
}
emit changePlaySpeedSignal(m_playSpeed);
}

@ -19,9 +19,12 @@ public:
void updateVideoDuration(int duration);
void updateVideoSlider(int currentTime, int duration);
void resetVideoSlider();
private:
void initIconButton();
QString secondsToHMS(int totalSeconds);
void onSliderPressed();
signals:
void startConnectionSignal(QString ip, int port);
void stopConnectionSignal();
@ -29,6 +32,8 @@ signals:
void changeVideoLayout(int index);
void stopVideoSignal();
void seekToVideoSignal(int64_t targetTime);
void changePlaySpeedSignal(double speed);
private slots:
void receiveMessageSlots(QString message, int type);
@ -40,11 +45,26 @@ private slots:
void on_pbStop_clicked();
void on_videoSlider_sliderReleased();
void on_videoSlider_sliderPressed();
void on_videoSlider_actionTriggered(int action);
void on_pbFast_clicked();
void on_pbSlow_clicked();
private:
Ui::VideoControl *ui;
QString m_remoteIP;
int m_remotePort;
bool m_bSliderPressed = false;
int m_sliderPressedValue;
double m_playSpeed;
};
#endif // VIDEOCONTROL_H

Loading…
Cancel
Save