Follow PCM node completion and keep seeking and replay consistent
This commit is contained in:
1 parent
7fe90be7b2
commit
ab37a06dad
1 file changed
+47
-22
@@ -89,6 +89,7 @@ void Worker::run() {
|
||||
else if (p->codec_type == AVMEDIA_TYPE_AUDIO && aidx < 0) aidx = (int)i;
|
||||
}
|
||||
if (vidx < 0) { worker_fail(this, QCoreApplication::translate("ChatGui", "no video stream: %1").arg(path)); cleanup(); return; }
|
||||
const bool audioExpected = aidx >= 0;
|
||||
|
||||
AVStream* vst = fmt->streams[vidx];
|
||||
int vw = vst->codecpar->width, vh = vst->codecpar->height;
|
||||
@@ -170,6 +171,9 @@ void Worker::run() {
|
||||
/* вернуть чтение в начало для видео */
|
||||
av_seek_frame(fmt, -1, 0, AVSEEK_FLAG_BACKWARD);
|
||||
emit audioDecoded(audioPcm, asr, ach, audioDurMs);
|
||||
} else if (audioExpected) {
|
||||
// opened сообщил о наличии трека; явный fallback запускает GUI-часы при отказе audio decoder/resampler.
|
||||
emit audioDecoded({}, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* ── 2. декодируем видео и шлём кадры ── */
|
||||
@@ -250,6 +254,10 @@ void Worker::run() {
|
||||
|
||||
int ret = av_read_frame(fmt, pkt);
|
||||
if (ret < 0) {
|
||||
if (ret != AVERROR_EOF) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "VideoEngine: read failed ret=%d epoch=%d", ret, gen);
|
||||
worker_fail(this, QCoreApplication::translate("ChatGui", "Cannot read video")); cleanup(); return;
|
||||
}
|
||||
avcodec_send_packet(vdec, nullptr);
|
||||
while (avcodec_receive_frame(vdec, frame) == 0) if (!emitFrame(frame)) { cleanup(); return; }
|
||||
eof = true;
|
||||
@@ -296,7 +304,7 @@ void VideoPlayerEngine::open(const QString& path) {
|
||||
m_abort.storeRelaxed(0);
|
||||
m_paused.storeRelaxed(0);
|
||||
m_epoch.storeRelaxed(0); m_seekTargetMs.storeRelaxed(0);
|
||||
m_decodeEof = false; m_videoEndMs = 0; m_audioFrames = 0; m_audioClock = false;
|
||||
m_decodeEof = false; m_videoEndMs = 0; m_audioFrames = 0; m_audioClock = false; m_drainReportAt = 0;
|
||||
m_buffered.storeRelaxed(0);
|
||||
clearPending();
|
||||
m_renderFrame = QImage();
|
||||
@@ -337,9 +345,9 @@ void VideoPlayerEngine::open(const QString& path) {
|
||||
}
|
||||
|
||||
void VideoPlayerEngine::play() {
|
||||
if (m_state != Paused && m_state != Playing) return;
|
||||
if (m_state != Paused) return;
|
||||
m_paused.storeRelaxed(0);
|
||||
if (m_hasAudio) SoundManager::instance()->resumePcm(m_audioPlayback);
|
||||
if (m_audioClock) SoundManager::instance()->resumePcm(m_audioPlayback);
|
||||
if (!m_audioClock) { m_elapsedBaseMs = m_positionMs; m_elapsed.restart(); }
|
||||
setState(Playing);
|
||||
m_presentTimer.start();
|
||||
@@ -347,8 +355,9 @@ void VideoPlayerEngine::play() {
|
||||
|
||||
void VideoPlayerEngine::pause() {
|
||||
if (m_state != Playing) return;
|
||||
m_positionMs = clockMs();
|
||||
m_paused.storeRelaxed(1);
|
||||
if (m_hasAudio) SoundManager::instance()->pausePcm(m_audioPlayback);
|
||||
if (m_audioClock) SoundManager::instance()->pausePcm(m_audioPlayback);
|
||||
setState(Paused);
|
||||
m_presentTimer.stop();
|
||||
}
|
||||
@@ -360,11 +369,14 @@ void VideoPlayerEngine::togglePlayPause() {
|
||||
}
|
||||
|
||||
void VideoPlayerEngine::seekMs(qint64 ms) {
|
||||
if (!m_worker || m_abort.loadRelaxed()) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "VideoEngine: seek ignored without active worker target=%lldms", (long long)ms); return;
|
||||
}
|
||||
if (ms < 0) ms = 0;
|
||||
if (m_durationMs > 0 && ms > m_durationMs) ms = m_durationMs;
|
||||
m_seekTargetMs.storeRelaxed(ms);
|
||||
m_epoch.fetchAndAddRelease(1);
|
||||
m_decodeEof = false; m_videoEndMs = 0;
|
||||
m_decodeEof = false; m_videoEndMs = 0; m_drainReportAt = 0;
|
||||
clearPending();
|
||||
if (m_hasAudio) {
|
||||
int sr = SoundManager::instance()->pcmSampleRate(m_audioPlayback);
|
||||
@@ -373,6 +385,8 @@ void VideoPlayerEngine::seekMs(qint64 ms) {
|
||||
onWorkerError(QCoreApplication::translate("ChatGui", "Cannot seek video")); return;
|
||||
}
|
||||
m_audioClock = (uint64_t)(ms * sr / 1000) < m_audioFrames;
|
||||
if (m_state == Playing && m_audioClock && !SoundManager::instance()->isPcmPlaying(m_audioPlayback))
|
||||
SoundManager::instance()->resumePcm(m_audioPlayback);
|
||||
}
|
||||
}
|
||||
m_elapsedBaseMs = ms; m_elapsed.restart();
|
||||
@@ -384,14 +398,15 @@ void VideoPlayerEngine::seekMs(qint64 ms) {
|
||||
void VideoPlayerEngine::stop() {
|
||||
++m_openGeneration;
|
||||
m_abort.storeRelaxed(1);
|
||||
m_decodeEof = false; m_videoEndMs = 0; m_audioClock = false; m_audioFrames = 0;
|
||||
m_decodeEof = false; m_videoEndMs = 0; m_audioClock = false; m_audioFrames = 0; m_drainReportAt = 0;
|
||||
m_presentTimer.stop();
|
||||
clearPending();
|
||||
m_renderFrame = QImage();
|
||||
if (m_hasAudio) SoundManager::instance()->stopPcm(m_audioPlayback);
|
||||
setState(Stopped);
|
||||
if (m_audioPlayback) SoundManager::instance()->stopPcm(m_audioPlayback);
|
||||
m_audioPlayback = 0; m_audioSampleRate = 0; m_hasAudio = false;
|
||||
m_positionMs = 0;
|
||||
m_durationMs = 0;
|
||||
setState(Stopped);
|
||||
}
|
||||
|
||||
void VideoPlayerEngine::close() {
|
||||
@@ -411,12 +426,14 @@ qint64 VideoPlayerEngine::clockMs() {
|
||||
if (m_audioClock) {
|
||||
auto* sound = SoundManager::instance();
|
||||
const uint64_t cursor = sound->pcmCursor(m_audioPlayback);
|
||||
if (cursor < m_audioFrames || sound->isPcmPlaying(m_audioPlayback))
|
||||
if (sound->isPcmPlaying(m_audioPlayback))
|
||||
return (qint64)(cursor * 1000 / (uint64_t)m_audioSampleRate);
|
||||
// Cursor измеряет время узла, а не точную длину PCM: при естественном EOF может остаться последний квант.
|
||||
m_audioClock = false;
|
||||
m_elapsedBaseMs = std::max(m_positionMs, (qint64)((m_audioFrames * 1000 + m_audioSampleRate - 1) / m_audioSampleRate));
|
||||
m_elapsed.restart();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "VideoEngine: audio ended, timer continues from=%lldms", (long long)m_elapsedBaseMs);
|
||||
m_elapsed.restart(); m_drainReportAt = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "VideoEngine: audio ended cursor=%llu/%llu timer_from=%lldms",
|
||||
(unsigned long long)cursor, (unsigned long long)m_audioFrames, (long long)m_elapsedBaseMs);
|
||||
}
|
||||
return m_elapsedBaseMs + m_elapsed.elapsed();
|
||||
}
|
||||
@@ -448,7 +465,8 @@ void VideoPlayerEngine::onWorkerOpened(int width, int height, qint64 durationMs,
|
||||
|
||||
void VideoPlayerEngine::onAudioDecoded(std::shared_ptr<std::vector<int16_t>> pcm,
|
||||
int sampleRate, int channels, qint64 durationMs) {
|
||||
if (pcm && !pcm->empty() && channels > 0) {
|
||||
const bool paused = m_state == Paused;
|
||||
if (pcm && !pcm->empty() && channels > 0 && sampleRate > 0) {
|
||||
m_audioSampleRate = sampleRate;
|
||||
m_audioFrames = pcm->size() / (size_t)channels;
|
||||
SoundManager::instance()->stopPcm(m_audioPlayback);
|
||||
@@ -461,7 +479,13 @@ void VideoPlayerEngine::onAudioDecoded(std::shared_ptr<std::vector<int16_t>> pcm
|
||||
m_audioFrames = 0; m_audioClock = false;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "VideoEngine: audio not playing, fallback to system clock");
|
||||
} else {
|
||||
m_audioClock = true;
|
||||
const uint64_t target = (uint64_t)(m_positionMs * sampleRate / 1000);
|
||||
if (target && !SoundManager::instance()->seekPcm(m_audioPlayback, target)) {
|
||||
onWorkerError(QCoreApplication::translate("ChatGui", "Cannot seek video")); return;
|
||||
}
|
||||
m_audioClock = target < m_audioFrames;
|
||||
if (paused || !m_audioClock) SoundManager::instance()->pausePcm(m_audioPlayback);
|
||||
else if (!SoundManager::instance()->isPcmPlaying(m_audioPlayback)) SoundManager::instance()->resumePcm(m_audioPlayback);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "VideoEngine: audio started rate=%d channels=%d frames=%llu",
|
||||
sampleRate, channels, (unsigned long long)m_audioFrames);
|
||||
}
|
||||
@@ -471,8 +495,7 @@ void VideoPlayerEngine::onAudioDecoded(std::shared_ptr<std::vector<int16_t>> pcm
|
||||
}
|
||||
m_elapsedBaseMs = m_positionMs;
|
||||
m_elapsed.restart();
|
||||
setState(Playing);
|
||||
m_presentTimer.start();
|
||||
if (!paused) { setState(Playing); m_presentTimer.start(); }
|
||||
}
|
||||
|
||||
void VideoPlayerEngine::onFrameDecoded(const QImage& image, qint64 ptsMs, qint64 endMs, int epoch) {
|
||||
@@ -486,9 +509,9 @@ void VideoPlayerEngine::onFrameDecoded(const QImage& image, qint64 ptsMs, qint64
|
||||
|
||||
void VideoPlayerEngine::onWorkerError(const QString& msg) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "VideoEngine: %s", qPrintable(msg));
|
||||
m_presentTimer.stop();
|
||||
setState(Stopped);
|
||||
emit error(msg);
|
||||
const uint64_t generation = m_openGeneration; QPointer<VideoPlayerEngine> guard(this);
|
||||
stop();
|
||||
if (guard && m_openGeneration == generation + 1) emit error(msg);
|
||||
}
|
||||
|
||||
void VideoPlayerEngine::onDecodeFinished(int epoch) {
|
||||
@@ -514,7 +537,11 @@ void VideoPlayerEngine::presentTick() {
|
||||
m_buffered.fetchAndSubRelaxed(1);
|
||||
have = true;
|
||||
}
|
||||
m_positionMs = clk;
|
||||
const bool audioDone = !m_hasAudio || !SoundManager::instance()->isPcmPlaying(m_audioPlayback);
|
||||
const qint64 audioEnd = m_audioSampleRate > 0 ? (qint64)((m_audioFrames * 1000 + m_audioSampleRate - 1) / m_audioSampleRate) : 0;
|
||||
const qint64 end = std::max(m_videoEndMs, audioEnd);
|
||||
const bool complete = m_decodeEof && m_pending.empty() && clk >= end && audioDone;
|
||||
m_positionMs = complete ? end : clk;
|
||||
if (have) {
|
||||
m_renderFrame = show; emit framePresented();
|
||||
if (!guard || generation != m_openGeneration || epoch != m_epoch.loadRelaxed() || m_state != Playing) return;
|
||||
@@ -522,8 +549,6 @@ void VideoPlayerEngine::presentTick() {
|
||||
emit positionChanged(m_positionMs);
|
||||
if (!guard || generation != m_openGeneration || epoch != m_epoch.loadRelaxed() || m_state != Playing) return;
|
||||
|
||||
const bool audioDone = !m_hasAudio || (!SoundManager::instance()->isPcmPlaying(m_audioPlayback) &&
|
||||
SoundManager::instance()->pcmCursor(m_audioPlayback) >= m_audioFrames);
|
||||
if (m_decodeEof && m_elapsed.elapsed() - m_drainReportAt >= 1000) {
|
||||
m_drainReportAt = m_elapsed.elapsed();
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO,
|
||||
@@ -532,7 +557,7 @@ void VideoPlayerEngine::presentTick() {
|
||||
(unsigned long long)SoundManager::instance()->pcmCursor(m_audioPlayback), (unsigned long long)m_audioFrames,
|
||||
SoundManager::instance()->isPcmPlaying(m_audioPlayback), m_audioClock);
|
||||
}
|
||||
if (m_decodeEof && m_pending.empty() && clk >= m_videoEndMs && audioDone) {
|
||||
if (complete) {
|
||||
m_presentTimer.stop();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "VideoEngine: playback finished epoch=%d position=%lldms video_end=%lldms audio_done=%d",
|
||||
epoch, (long long)m_positionMs, (long long)m_videoEndMs, audioDone);
|
||||
|
||||
Reference in new issue
Block a user