chatgui: нативный move/resize (Wayland) + фикс замирания видео при перемотке
- окно: startSystemMove/startSystemResize вместо ручного move/setGeometry (Wayland игнорирует позицию окна) — драг и resize за левый/верхний край работают - пропорции: снэп к videoAspect после нативного resize (дебаунс 150мс) - перемотка: backpressure по m_buffered (декод не убегает вперёд и воркер живёт для seek), seek через av_seek_frame(-1, us), пропуск кадров с AV_NOPTS_VALUE - SeekSlider: клик по шкале = прыжок к позиции - крестик закрытия справа вверху; курсор контролов — стрелка - test_video_engine: проверка seek
This commit is contained in:
1 parent
f2b34459c3
commit
645591bb64
5 files changed
+139
-122
No files matched your search
@@ -18,7 +18,7 @@ extern "C" {
|
||||
}
|
||||
#endif
|
||||
|
||||
static const int kMaxInFlight = 24;
|
||||
static const int kMaxBuffered = 30;
|
||||
|
||||
/* ── Worker: демюкс + декод в отдельном потоке ── */
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
QAtomicInt* paused = nullptr;
|
||||
QAtomicInt* seekReq = nullptr;
|
||||
QAtomicInteger<qint64>* seekTargetMs = nullptr;
|
||||
QAtomicInt* inFlight = nullptr;
|
||||
QAtomicInt* buffered = nullptr;
|
||||
QAtomicInt* epoch = nullptr;
|
||||
|
||||
signals:
|
||||
@@ -184,35 +184,45 @@ void Worker::run() {
|
||||
SWS_BILINEAR, nullptr, nullptr, nullptr);
|
||||
if (!sws) { worker_fail(this, "sws init failed"); cleanup(); return; }
|
||||
|
||||
int gen = epoch->loadRelaxed();
|
||||
auto emitFrame = [&](AVFrame* fr) {
|
||||
if (fr->pts == AV_NOPTS_VALUE) return; /* кадр без pts ломает очередь */
|
||||
QImage img(vw, vh, QImage::Format_RGB888);
|
||||
uint8_t* dst[1] = { img.bits() };
|
||||
int dstStride[1] = { (int)img.bytesPerLine() };
|
||||
sws_scale(sws, fr->data, fr->linesize, 0, vh, dst, dstStride);
|
||||
qint64 ptsMs = av_rescale_q(fr->pts, vst->time_base, AVRational{1, 1000});
|
||||
inFlight->fetchAndAddRelaxed(1);
|
||||
emit frameDecoded(img, ptsMs, epoch->loadRelaxed());
|
||||
buffered->fetchAndAddRelaxed(1);
|
||||
emit frameDecoded(img, ptsMs, gen);
|
||||
};
|
||||
|
||||
bool eof = false;
|
||||
while (!abort->loadRelaxed() && !eof) {
|
||||
while (!abort->loadRelaxed()) {
|
||||
if (seekReq->loadRelaxed()) {
|
||||
qint64 targetMs = seekTargetMs->loadRelaxed();
|
||||
qint64 ts = av_rescale_q(targetMs, AVRational{1, 1000}, vst->time_base);
|
||||
av_seek_frame(fmt, vidx, ts, AVSEEK_FLAG_BACKWARD);
|
||||
int sret = av_seek_frame(fmt, -1, targetMs * 1000, AVSEEK_FLAG_BACKWARD);
|
||||
if (sret < 0)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "VideoEngine: seek to %lldms failed ret=%d",
|
||||
(long long)targetMs, sret);
|
||||
else
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "VideoEngine: seek to %lldms", (long long)targetMs);
|
||||
avcodec_flush_buffers(vdec);
|
||||
gen = epoch->loadRelaxed();
|
||||
eof = false;
|
||||
seekReq->storeRelaxed(0);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "VideoEngine: seek to %lldms", (long long)targetMs);
|
||||
}
|
||||
if (paused->loadRelaxed()) { QThread::msleep(10); continue; }
|
||||
if (inFlight->loadRelaxed() >= kMaxInFlight) { QThread::msleep(3); continue; }
|
||||
if (eof) { QThread::msleep(10); continue; } /* ждём seek или abort */
|
||||
if (buffered->loadRelaxed() >= kMaxBuffered) { QThread::msleep(3); continue; }
|
||||
|
||||
int ret = av_read_frame(fmt, pkt);
|
||||
if (ret < 0) {
|
||||
avcodec_send_packet(vdec, nullptr);
|
||||
while (avcodec_receive_frame(vdec, frame) == 0) emitFrame(frame);
|
||||
eof = true;
|
||||
break;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "VideoEngine: decode finished");
|
||||
emit finished();
|
||||
continue;
|
||||
}
|
||||
if (pkt->stream_index == vidx) {
|
||||
avcodec_send_packet(vdec, pkt);
|
||||
@@ -220,11 +230,6 @@ void Worker::run() {
|
||||
}
|
||||
av_packet_unref(pkt);
|
||||
}
|
||||
|
||||
if (!abort->loadRelaxed()) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "VideoEngine: decode finished");
|
||||
emit finished();
|
||||
}
|
||||
}
|
||||
|
||||
cleanup();
|
||||
@@ -258,7 +263,7 @@ void VideoPlayerEngine::open(const QString& path) {
|
||||
m_abort.storeRelaxed(0);
|
||||
m_paused.storeRelaxed(0);
|
||||
m_seekReq.storeRelaxed(0);
|
||||
m_inFlight.storeRelaxed(0);
|
||||
m_buffered.storeRelaxed(0);
|
||||
clearPending();
|
||||
m_renderFrame = QImage();
|
||||
m_durationMs = 0; m_positionMs = 0; m_hasAudio = false;
|
||||
@@ -272,7 +277,7 @@ void VideoPlayerEngine::open(const QString& path) {
|
||||
m_worker->paused = &m_paused;
|
||||
m_worker->seekReq = &m_seekReq;
|
||||
m_worker->seekTargetMs = &m_seekTargetMs;
|
||||
m_worker->inFlight = &m_inFlight;
|
||||
m_worker->buffered = &m_buffered;
|
||||
m_worker->epoch = &m_epoch;
|
||||
m_worker->moveToThread(m_thread);
|
||||
|
||||
@@ -282,7 +287,6 @@ void VideoPlayerEngine::open(const QString& path) {
|
||||
connect(m_worker, &Worker::frameDecoded, this, &VideoPlayerEngine::onFrameDecoded);
|
||||
connect(m_worker, &Worker::error, this, &VideoPlayerEngine::onWorkerError);
|
||||
connect(m_worker, &Worker::finished, this, &VideoPlayerEngine::onWorkerFinished);
|
||||
connect(m_worker, &Worker::finished, m_thread, &QThread::quit);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "VideoEngine: open %s", qPrintable(path));
|
||||
m_thread->start();
|
||||
@@ -326,7 +330,6 @@ void VideoPlayerEngine::seekMs(qint64 ms) {
|
||||
m_elapsed.restart();
|
||||
}
|
||||
m_positionMs = ms;
|
||||
m_inFlight.storeRelaxed(0);
|
||||
if (m_state == Finished) setState(Paused);
|
||||
emit positionChanged(ms);
|
||||
}
|
||||
@@ -372,6 +375,7 @@ void VideoPlayerEngine::setState(State s) {
|
||||
}
|
||||
|
||||
void VideoPlayerEngine::clearPending() {
|
||||
m_buffered.fetchAndSubRelaxed((int)m_pending.size());
|
||||
m_pending.clear();
|
||||
}
|
||||
|
||||
@@ -414,8 +418,10 @@ void VideoPlayerEngine::onAudioDecoded(std::shared_ptr<std::vector<int16_t>> pcm
|
||||
}
|
||||
|
||||
void VideoPlayerEngine::onFrameDecoded(const QImage& image, qint64 ptsMs, int epoch) {
|
||||
m_inFlight.fetchAndSubRelaxed(1);
|
||||
if (epoch != m_epoch.loadRelaxed()) return; /* устаревший кадр после seek */
|
||||
if (epoch != m_epoch.loadRelaxed()) {
|
||||
m_buffered.fetchAndSubRelaxed(1); /* устаревший кадр после seek — не будет показан */
|
||||
return;
|
||||
}
|
||||
m_pending.push_back({ image, ptsMs });
|
||||
}
|
||||
|
||||
@@ -442,6 +448,7 @@ void VideoPlayerEngine::presentTick() {
|
||||
while (!m_pending.empty() && m_pending.front().second <= clk) {
|
||||
show = m_pending.front().first;
|
||||
m_pending.pop_front();
|
||||
m_buffered.fetchAndSubRelaxed(1);
|
||||
have = true;
|
||||
}
|
||||
if (have) { m_renderFrame = show; emit framePresented(); }
|
||||
|
||||
Reference in new issue
Block a user