diff options
| author | 2025-02-04 21:05:18 -0500 | |
|---|---|---|
| committer | 2025-02-04 21:05:18 -0500 | |
| commit | 0060d4f3df9b264e4a4adb77da67336709b41b6b (patch) | |
| tree | a59d4e2f218cec89af56bdb2036d0a2ecd488349 /src/buffer | |
| parent | cdd1f574312722ab305cba6771d9bcc5da0b0c38 (diff) | |
| download | camu-0060d4f3df9b264e4a4adb77da67336709b41b6b.tar.gz camu-0060d4f3df9b264e4a4adb77da67336709b41b6b.tar.bz2 camu-0060d4f3df9b264e4a4adb77da67336709b41b6b.zip | |
Audio buffer and sink fixes, unsigned width/height
- Screen feature testing
- Older FFmpeg support
- Build configuration fixes
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/buffer')
| -rw-r--r-- | src/buffer/audio.c | 147 | ||||
| -rw-r--r-- | src/buffer/audio.h | 4 | ||||
| -rw-r--r-- | src/buffer/frame_queue.h | 2 | ||||
| -rw-r--r-- | src/buffer/video.c | 79 | ||||
| -rw-r--r-- | src/buffer/video.h | 2 |
5 files changed, 126 insertions, 108 deletions
diff --git a/src/buffer/audio.c b/src/buffer/audio.c index 2e65a97..eaac16f 100644 --- a/src/buffer/audio.c +++ b/src/buffer/audio.c @@ -14,13 +14,15 @@ #define BUFFER_MARK_BUFFERED 1.0 #ifdef CAMU_AUDIO_BUFFER_FADE -#define FADE_STEP(fmt) (1.75f / (fmt)->sample_rate) +#define FADE_STEP(fmt) (2.50f / (fmt)->sample_rate) +#define FADE_MIN(fmt) (8000.f / (fmt)->sample_rate) #endif enum { PAUSE_PAUSED = 0, #ifdef CAMU_AUDIO_BUFFER_FADE - PAUSE_FADING_OUT, + PAUSE_FADING, + PAUSE_FADING_COMPLETE, #endif PAUSE_PLAYING }; @@ -29,12 +31,8 @@ static void reset_buffer_state(struct camu_audio_buffer *buf) { al_atomic_store(f64)(&buf->pts, -1.0, AL_ATOMIC_RELAXED); buf->pause = PAUSE_PAUSED; - al_atomic_store(s32)(&buf->unpause, 0, AL_ATOMIC_RELAXED); - al_atomic_store(s32)(&buf->volume.set, 0, AL_ATOMIC_RELAXED); -#ifdef CAMU_AUDIO_BUFFER_FADE - buf->fade.offset = 0; - buf->fade.volume = -1.f; -#endif + al_atomic_store(u32)(&buf->unpause, 0, AL_ATOMIC_RELAXED); + al_atomic_store(u32)(&buf->volume.set, 0, AL_ATOMIC_RELAXED); buf->buffered = false; al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED); al_atomic_store(ptrdiff_t)(&buf->uncork_at, 0, AL_ATOMIC_RELAXED); @@ -47,6 +45,10 @@ bool camu_audio_buffer_init(struct camu_audio_buffer *buf, struct camu_clock *cl buf->ignore_desync = false; al_atomic_store(bool)(&buf->no_video, false, AL_ATOMIC_RELAXED); reset_buffer_state(buf); +#ifdef CAMU_AUDIO_BUFFER_FADE + // Persist fade volume across resets. + buf->fade.volume = -1.f; +#endif #ifdef CAMU_MIXER_THREADED al_atomic_store(u8)(&buf->ref, 0, AL_ATOMIC_RELAXED); #endif @@ -58,6 +60,10 @@ bool camu_audio_buffer_configure(struct camu_audio_buffer *buf, struct camu_code { camu_audio_format_copy(&buf->fmt.in, &stream->audio.fmt); camu_mixer_pick_format(mixer, &buf->fmt); + + struct camu_audio_format *in = &buf->fmt.in; + struct camu_audio_format *req = &buf->fmt.req; + buf->fmt.resampler_needed = !camu_resampler_format_matches(&buf->fmt); if (buf->fmt.resampler_needed) { #ifdef CAMU_HAVE_FFMPEG @@ -71,18 +77,16 @@ bool camu_audio_buffer_configure(struct camu_audio_buffer *buf, struct camu_code #endif } - const char *in_format_name = camu_audio_format_name(buf->fmt.in.format); - const char *req_format_name = camu_audio_format_name(buf->fmt.req.format); al_log_info("audio_buffer", "Stream: %s (%dch) %dHz -> %s (%dch) %dHz.", - in_format_name, buf->fmt.in.channel_count, buf->fmt.in.sample_rate, - req_format_name, buf->fmt.req.channel_count, buf->fmt.req.sample_rate); + camu_audio_format_name(in->format), in->channel_count, in->sample_rate, + camu_audio_format_name(req->format), req->channel_count, req->sample_rate); - buf->size = (ptrdiff_t)camu_audio_format_sec_to_bytes(&buf->fmt.req, BUFFER_SIZE); + buf->size = (ptrdiff_t)camu_audio_format_sec_to_bytes(req, BUFFER_SIZE); buf->data = (u8 *)al_malloc(buf->size); al_ring_buffer_init(&buf->rb, buf->data, buf->size); - buf->mark.min = (ptrdiff_t)camu_audio_format_sec_to_bytes(&buf->fmt.req, BUFFER_MARK_MIN); - buf->mark.buffered = (ptrdiff_t)camu_audio_format_sec_to_bytes(&buf->fmt.req, BUFFER_MARK_BUFFERED); + buf->mark.min = (ptrdiff_t)camu_audio_format_sec_to_bytes(req, BUFFER_MARK_MIN); + buf->mark.buffered = (ptrdiff_t)camu_audio_format_sec_to_bytes(req, BUFFER_MARK_BUFFERED); camu_peak_buffer_init(&buf->peak, KB(16)); buf->stream = stream; @@ -93,7 +97,7 @@ bool camu_audio_buffer_configure(struct camu_audio_buffer *buf, struct camu_code void camu_audio_buffer_set_volume(struct camu_audio_buffer *buf, f32 volume) { al_atomic_store(f32)(&buf->volume.queued, volume, AL_ATOMIC_RELAXED); - al_atomic_add(s32)(&buf->volume.set, 1, AL_ATOMIC_RELAXED); + al_atomic_add(u32)(&buf->volume.set, 1, AL_ATOMIC_RELAXED); } void camu_audio_buffer_set_latency(struct camu_audio_buffer *buf, f64 latency) @@ -111,13 +115,6 @@ void camu_audio_buffer_set_no_video(struct camu_audio_buffer *buf, bool no_video al_atomic_store(bool)(&buf->no_video, no_video, AL_ATOMIC_RELAXED); } -#ifdef _DEBUG_ -#define BUFFERED_SECONDS_DEBUG(buf) \ - camu_audio_format_bytes_to_sec(&buf->fmt.req, al_ring_buffer_occupied(&buf->rb)) -#else -#define BUFFERED_SECONDS_DEBUG(buf) 0 -#endif - // A return value of false signals that we pushed to the peak buffer. static bool push_internal(struct camu_audio_buffer *buf, f64 pts, u8 **data, s32 sample_count) { @@ -153,7 +150,8 @@ static bool push_internal(struct camu_audio_buffer *buf, f64 pts, u8 **data, s32 ptrdiff_t space = al_ring_buffer_space(&buf->rb); if (!buf->buffered && buf->size - space > buf->mark.buffered) { - al_log_debug("audio_buffer", "Buffered (mark: %.2fs).", BUFFERED_SECONDS_DEBUG(buf)); + al_log_debug("audio_buffer", "Buffered (mark: %.2fs).", + camu_audio_format_bytes_to_sec(&buf->fmt.req, buf->size - space)); buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED); buf->buffered = true; } @@ -166,7 +164,7 @@ static bool push_internal(struct camu_audio_buffer *buf, f64 pts, u8 **data, s32 peak += have; if (peak >= buf->mark.min) { // If this happens the writer of this buffer is taking way too long to stop. - al_log_warn("audio_buffer", "Overrun likely, discarding peak buffer (audio will be desynced)."); + al_log_warn("audio_buffer", "Overrun likely, discarding peak buffer (audio will desync)."); camu_peak_buffer_flush(&buf->peak); peak = 0; } @@ -232,7 +230,8 @@ void camu_audio_buffer_flush(struct camu_audio_buffer *buf) } if (!buf->buffered) { - al_log_debug("audio_buffer", "Buffered (mark: %.2fs).", BUFFERED_SECONDS_DEBUG(buf)); + al_log_debug("audio_buffer", "Buffered (mark: %.2fs).", + camu_audio_format_bytes_to_sec(&buf->fmt.req, al_ring_buffer_occupied(&buf->rb))); buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED); buf->buffered = true; } @@ -253,44 +252,45 @@ void camu_audio_buffer_reset(struct camu_audio_buffer *buf) void camu_audio_buffer_unpause(struct camu_audio_buffer *buf) { - al_atomic_add(s32)(&buf->unpause, 1, AL_ATOMIC_RELAXED); + al_atomic_add(u32)(&buf->unpause, 1, AL_ATOMIC_RELAXED); } // PAUSE_PAUSED signifies that the last read was silence. // This means we can skip around in the buffer without worrying about pops. ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdiff_t req) { - if (al_atomic_load(s32)(&buf->volume.set, AL_ATOMIC_ACQUIRE) > 0) { - buf->volume.user = al_atomic_load(f32)(&buf->volume.queued, AL_ATOMIC_RELAXED); -#ifdef CAMU_AUDIO_BUFFER_FADE - if (buf->fade.volume == -1.f) { - buf->fade.volume = buf->volume.user; - } -#endif - al_atomic_sub(s32)(&buf->volume.set, 1, AL_ATOMIC_RELEASE); - } + struct camu_audio_format *fmt = &buf->fmt.req; + ptrdiff_t ret, signal = req; + f64 base_pts = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); bool set_clock = al_atomic_load(bool)(&buf->no_video, AL_ATOMIC_RELAXED); f64 pts = camu_clock_get_pts(buf->clock, buf->latency, set_clock); if (pts == -1.0) { #ifdef CAMU_AUDIO_BUFFER_FADE if (buf->pause == PAUSE_PLAYING) { - buf->pause = PAUSE_FADING_OUT; + buf->pause = PAUSE_FADING; + // fade offset should only ever be read when pause = FADING. buf->fade.offset = 0; - } else if (buf->pause == PAUSE_PAUSED || buf->fade.volume == 0.f) { + } else if (buf->pause == PAUSE_PAUSED || buf->pause == PAUSE_FADING_COMPLETE || + (buf->fade.volume == 0.f)) { al_memset(data, 0, req); - if (buf->pause != PAUSE_PAUSED) { - // Fade out finished. - buf->pause = PAUSE_PAUSED; + if (buf->pause == PAUSE_FADING_COMPLETE) { buf->callback(buf->userdata, CAMU_BUFFER_PAUSED); + buf->pause = PAUSE_PAUSED; + } else if (buf->pause != PAUSE_PAUSED) { + // Don't signal PAUSED until the next read to ensure at least 1 silent + // frame is included in the fade out. + buf->pause = PAUSE_FADING_COMPLETE; } - buf->fade.offset = 0; return req; } - } else if (buf->pause == PAUSE_FADING_OUT || buf->pause == PAUSE_PAUSED) { - if (buf->pause == PAUSE_FADING_OUT) { - // If unpaused during a fade out, resume immediately to not break the stream. - buf->pause = PAUSE_PLAYING; + } else if (buf->pause == PAUSE_FADING || buf->pause == PAUSE_FADING_COMPLETE) { + // If unpaused during a fade out, resume immediately to not break the stream. + buf->pause = PAUSE_PLAYING; + // Though, we will still jump back (possible pop) unless we are ignoring sync. + if (buf->ignore_desync) { + ret = al_ring_buffer_discard(&buf->rb, buf->fade.offset); + base_pts += camu_audio_format_bytes_to_sec(fmt, ret); } #else // No fade and clock paused. al_memset(data, 0, req); @@ -302,32 +302,38 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif #endif } - if (al_atomic_load(s32)(&buf->unpause, AL_ATOMIC_ACQUIRE) > 0) { - // If pause != PLAYING here this will likely cause unexpected behavior. + if (al_atomic_load(u32)(&buf->volume.set, AL_ATOMIC_ACQUIRE) > 0) { + buf->volume.user = al_atomic_load(f32)(&buf->volume.queued, AL_ATOMIC_RELAXED); +#ifdef CAMU_AUDIO_BUFFER_FADE + if (buf->fade.volume == -1.f) { + buf->fade.volume = buf->volume.user; + } +#endif + al_atomic_sub(u32)(&buf->volume.set, 1, AL_ATOMIC_RELEASE); + } + + if (!buf->ignore_desync && al_atomic_load(u32)(&buf->unpause, AL_ATOMIC_ACQUIRE) > 0) { + // Queuing multiple resyncs before resuming the stream will cause pops! + al_log_debug("audio_buffer", "Forcing resync."); buf->pause = PAUSE_PAUSED; - al_atomic_sub(s32)(&buf->unpause, 1, AL_ATOMIC_RELEASE); + al_atomic_sub(u32)(&buf->unpause, 1, AL_ATOMIC_RELEASE); } - struct camu_audio_format *fmt = &buf->fmt.req; - ptrdiff_t ret, signal = req; ptrdiff_t have = al_ring_buffer_occupied(&buf->rb); #ifdef CAMU_AUDIO_BUFFER_FADE - // Cut off fade if it's reaching too far. - if (have < buf->fade.offset) have = 0; - else have -= buf->fade.offset; + if (buf->pause == PAUSE_FADING) { + // Cut off fade if it's reaching too far. + if (have < buf->fade.offset) have = 0; + else have -= buf->fade.offset; + } #endif - // @NOTE: We are not safe to increment buf->pts from a different thread. - // We would need to accumulate the difference and do an atomic add instead - // of the load/store we do here. - f64 base_pts = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); // Unpause and possibly attempt syncing to the clock. if (UNLIKELY(buf->pause == PAUSE_PAUSED)) { if (!buf->ignore_desync) { pts -= base_pts; if (pts > 0.0) { // Skip. - ret = (ptrdiff_t)camu_audio_format_sec_to_bytes(fmt, pts); - ret = MIN(ret, have); + ret = MIN((ptrdiff_t)camu_audio_format_sec_to_bytes(fmt, pts), have); al_log_info("audio_buffer", "Skipping %fs of audio (%zd bytes).", pts, ret); ret = al_ring_buffer_discard(&buf->rb, ret); have -= ret; @@ -335,14 +341,13 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif // Could go on to underrun. } else if (pts < 0.0) { // Delay. pts = -pts; - ret = (ptrdiff_t)camu_audio_format_sec_to_bytes(fmt, pts); - ret = MIN(ret, req); + ret = MIN((ptrdiff_t)camu_audio_format_sec_to_bytes(fmt, pts), req); al_log_info("audio_buffer", "Delaying audio by %fs.", pts); al_memset(data, 0, ret); data += ret; req -= ret; // We can continue to delay. - if (req == 0) return signal; + if (req == 0) goto out; } } buf->pause = PAUSE_PLAYING; @@ -388,7 +393,7 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif if (LIKELY(req > 0)) { #ifdef CAMU_AUDIO_BUFFER_FADE - if (buf->pause == PAUSE_FADING_OUT) { + if (buf->pause == PAUSE_FADING) { // Peeking into the ring buffer won't consume data. So, when resumed we // will fade in from the same position that the fade out started. ret = al_ring_buffer_peek(&buf->rb, data, buf->fade.offset, req); @@ -403,7 +408,7 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif } f32 step = 0.f; - if (buf->pause == PAUSE_FADING_OUT || buf->fade.volume > buf->volume.user) { + if (buf->pause == PAUSE_FADING || buf->fade.volume > buf->volume.user) { step = -FADE_STEP(fmt); } else if (buf->fade.volume < buf->volume.user) { step = FADE_STEP(fmt); @@ -411,10 +416,11 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif if (step != 0.f || buf->fade.volume != 1.f) { if (buf->volume.user > 1.f) { - // Only adjust the step if it's an increase to avoid drawn-out + // Only adjust the step if it's an increase to avoid drawn out // fades if the volume is low. step *= buf->volume.user; } + step *= MAX(buf->fade.volume, FADE_MIN(fmt)); buf->fade.volume = apply_volume(data, req, fmt, buf->fade.volume, buf->volume.user, step); } #else @@ -424,11 +430,9 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif #endif } - al_atomic_store(f64)(&buf->pts, base_pts, AL_ATOMIC_RELEASE); - // Check if we should request to uncork. #ifdef CAMU_AUDIO_BUFFER_FADE - if (flow == FLOWING && buf->pause != PAUSE_FADING_OUT) { + if (flow == FLOWING && buf->pause != PAUSE_FADING) { #else if (flow == FLOWING) { #endif @@ -438,6 +442,11 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif } } +out: + // We are not safe to increment buf->pts from a different thread. + // We would need to accumulate the difference and do an atomic add here. + al_atomic_store(f64)(&buf->pts, base_pts, AL_ATOMIC_RELEASE); + // To signal EOF, return less then req. return signal; } diff --git a/src/buffer/audio.h b/src/buffer/audio.h index 901becf..ef907bd 100644 --- a/src/buffer/audio.h +++ b/src/buffer/audio.h @@ -21,7 +21,7 @@ struct camu_audio_buffer { atomic(bool) no_video; u8 pause; - atomic(s32) unpause; + atomic(u32) unpause; struct camu_resampler_format fmt; struct camu_resampler *resampler; @@ -39,8 +39,8 @@ struct camu_audio_buffer { struct { f32 user; + atomic(u32) set; atomic(f32) queued; - atomic(s32) set; } volume; #ifdef CAMU_AUDIO_BUFFER_FADE diff --git a/src/buffer/frame_queue.h b/src/buffer/frame_queue.h index e40971d..fdb49b5 100644 --- a/src/buffer/frame_queue.h +++ b/src/buffer/frame_queue.h @@ -17,7 +17,7 @@ enum { struct camu_frame_queue { struct camu_video_buffer *buf; #ifdef CAMU_HAVE_FFMPEG - bool (*configure_subtitles)(struct camu_frame_queue *, s32, s32, AVCodecParameters *); + bool (*configure_subtitles)(struct camu_frame_queue *, u32, u32, AVCodecParameters *); #endif void (*push)(struct camu_frame_queue *, struct camu_codec_frame *, f64); #ifdef CAMU_HAVE_FFMPEG diff --git a/src/buffer/video.c b/src/buffer/video.c index 41aa9ed..2fc5773 100644 --- a/src/buffer/video.c +++ b/src/buffer/video.c @@ -4,14 +4,16 @@ #include "common.h" #include "common_internal.h" -#ifdef CAMU_VIDEO_BUFFER_FORCE_SCALER #ifdef CAMU_HAVE_FFMPEG -#include "../codec/ffmpeg/scaler.h" +#include "../codec/ffmpeg/common.h" #endif + +#ifdef CAMU_VIDEO_BUFFER_FORCE_SCALER +#include "../codec/ffmpeg/scaler.h" #endif #define BUFFER_MARK_LOW ((1.0 / 30.0) * 6) -#define BUFFER_MARK_BUFFERED ((1.0 / 30.0) * 5) +#define BUFFER_MARK_BUFFERED ((1.0 / 30.0) * 5) // Must be >1. #define BUFFER_MARK_HIGH ((1.0 / 30.0) * 12) #define BUFFER_MARK_RESET (BUFFER_MARK_HIGH * 2.0) @@ -27,6 +29,7 @@ bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *cl buf->avg_frame_duration = 0.0; buf->queue = NULL; buf->buffered = false; + buf->buffered_with_one_frame = false; buf->weighted_read = true; al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED); #ifdef CAMU_SCREEN_THREADED @@ -53,7 +56,7 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_code const char *format_name = camu_pixel_format_name(fmt->format); if (!format_name) format_name = "unknown"; - al_log_info("video_buffer", "Stream: %s (%dx%d) %s.", + al_log_info("video_buffer", "Stream: %s (%ux%u) %s.", format_name, fmt->width, fmt->height, "IMAGE"); break; @@ -63,20 +66,20 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_code AVCodecParameters *codecpar = stream->av.stream->codecpar; AVRational frame_rate = stream->av.stream->avg_frame_rate; - fmt->width = codecpar->width; - fmt->height = codecpar->height; + fmt->width = (u32)codecpar->width; + fmt->height = (u32)codecpar->height; fmt->format = codecpar->format; camu_video_format_copy(&buf->fmt.in, fmt); buf->single_frame = stream->duration == 0 || frame_rate.den == 0; - buf->avg_frame_duration = buf->single_frame ? 0.0 : av_q2d(av_inv_q(frame_rate)); + buf->avg_frame_duration = frame_rate.den > 0 ? av_q2d(av_inv_q(frame_rate)) : 0.0; - const char *format_name = av_get_pix_fmt_name(buf->fmt.in.format); + const char *format_name = av_get_pix_fmt_name(fmt->format); if (buf->single_frame) { - al_log_info("video_buffer", "Stream: %s (%dx%d) %s.", + al_log_info("video_buffer", "Stream: %s (%ux%u) %s.", format_name, fmt->width, fmt->height, "IMAGE"); } else { - al_log_info("video_buffer", "Stream: %s (%dx%d) %s %.3ffps.", + al_log_info("video_buffer", "Stream: %s (%ux%u) %s %.3ffps.", format_name, fmt->width, fmt->height, "VIDEO", av_q2d(frame_rate)); } @@ -86,8 +89,8 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_code } #ifdef CAMU_VIDEO_BUFFER_FORCE_SCALER #ifdef CAMU_HAVE_FFMPEG - if (fmt->width > 0 && fmt->height > 0 && fmt->format != CAMU_PIXEL_FORMAT_RGBA && - fmt->format != CAMU_PIXEL_FORMAT_RGB) { + if (fmt->width > 0 && fmt->height > 0 && + (fmt->format != CAMU_PIXEL_FORMAT_RGBA && fmt->format != CAMU_PIXEL_FORMAT_RGB)) { buf->fmt.scaler_needed = true; buf->fmt.req.width = fmt->width; buf->fmt.req.height = fmt->height; @@ -95,7 +98,7 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_code buf->scaler = camu_ff_scaler_create(); if (buf->scaler->init(buf->scaler, &buf->fmt)) { const char *format_name = av_get_pix_fmt_name(buf->fmt.req.format); - al_log_info("video_buffer", "Scaling to: %s (%dx%d).", + al_log_info("video_buffer", "Scaling to: %s (%ux%u).", format_name, buf->fmt.req.width, buf->fmt.req.height); } else { // Scaler will be freed in video_buffer_free(). @@ -134,21 +137,19 @@ bool camu_video_buffer_is_single_frame(struct camu_video_buffer *buf) static void after_push_internal(struct camu_video_buffer *buf) { - f64 have = buf->queue->count(buf->queue) * buf->avg_frame_duration; + s32 count = buf->queue->count(buf->queue); + f64 have = count * buf->avg_frame_duration; if (!buf->buffered && (buf->single_frame || have >= BUFFER_MARK_BUFFERED)) { - // Preserve order of: flush -> callback -> set flow, for single frames. + // Preserve order of: set flow -> flush -> callback, for single frames. if (buf->single_frame) { + al_atomic_store(u8)(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED); buf->queue->flush(buf->queue); } - + buf->buffered = true; + buf->buffered_with_one_frame = count == 1; al_log_debug("video_buffer", "Buffered (mark: %.2fs).", have); buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED); - buf->buffered = true; - - if (buf->single_frame) { - al_atomic_store(u8)(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED); - } } if (have >= BUFFER_MARK_RESET) { @@ -164,12 +165,12 @@ static bool push_av_frame_internal(struct camu_video_buffer *buf, AVFrame *frame { AVStream *stream = buf->stream->av.stream; f64 pts = frame->best_effort_timestamp * av_q2d(stream->time_base); - f64 duration = frame->duration * av_q2d(stream->time_base); - f64 base = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); - if (!buf->single_frame && frame_is_late(buf->clock, base, pts, duration)) { + f64 duration = camu_ff_frame_duration(frame) * av_q2d(stream->time_base); + f64 base_pts = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); + if (!buf->single_frame && frame_is_late(buf->clock, base_pts, pts, duration)) { return false; } - if (base == -1.0) al_atomic_store(f64)(&buf->pts, pts, AL_ATOMIC_RELEASE); + if (base_pts == -1.0) al_atomic_store(f64)(&buf->pts, pts, AL_ATOMIC_RELEASE); #ifdef CAMU_VIDEO_BUFFER_FORCE_SCALER if (buf->fmt.scaler_needed) { if (!buf->scaler->scale(buf->scaler, (const u8 **)frame->data, frame->linesize)) { @@ -193,8 +194,8 @@ void camu_video_buffer_push(struct camu_video_buffer *buf, struct camu_codec_fra } switch (frame->mode) { case CAMU_NORMAL: { - f64 base = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); - if (base == -1.0) { + f64 base_pts = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); + if (base_pts == -1.0) { al_atomic_store(f64)(&buf->pts, 0.0, AL_ATOMIC_RELEASE); } buf->queue->push(buf->queue, frame, 0.0); @@ -225,14 +226,16 @@ void camu_video_buffer_push_subtitle(struct camu_video_buffer *buf, AVPacket *pk void camu_video_buffer_flush(struct camu_video_buffer *buf) { al_log_debug("video_buffer", "Flush requested."); + al_atomic_store(u8)(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED); buf->queue->flush(buf->queue); if (!buf->buffered) { - f64 have = buf->queue->count(buf->queue) * buf->avg_frame_duration; + s32 count = buf->queue->count(buf->queue); + f64 have = count * buf->avg_frame_duration; + buf->buffered = true; + buf->buffered_with_one_frame = count == 1; al_log_debug("video_buffer", "Buffered (mark: %.2fs).", have); buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED); - buf->buffered = true; } - al_atomic_store(u8)(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED); } // Not thread-safe, must be called while the buffer is not being read from or written to. @@ -247,29 +250,33 @@ void camu_video_buffer_reset(struct camu_video_buffer *buf) bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out, bool *weighted) { - f64 base = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); + f64 base_pts = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); if (!buf->single_frame) { f64 pts = camu_clock_get_pts(buf->clock, buf->latency, !buf->weighted_read); - if (pts > base) { - base = pts; - al_atomic_store(f64)(&buf->pts, base, AL_ATOMIC_RELEASE); + if (pts > base_pts) { + base_pts = pts; + al_atomic_store(f64)(&buf->pts, base_pts, AL_ATOMIC_RELEASE); } } u8 flow = al_atomic_load(u8)(&buf->flow, AL_ATOMIC_ACQUIRE); if (flow == ERRORED) return false; - u8 ret = buf->queue->read(buf->queue, base, out); + u8 ret = buf->queue->read(buf->queue, base_pts, out); if (ret == CAMU_QUEUE_ERR) { buf->callback(buf->userdata, CAMU_BUFFER_ERRORED); al_atomic_store(u8)(&buf->flow, ERRORED, AL_ATOMIC_RELEASE); return false; } - if (flow == FLUSHED && (ret == CAMU_QUEUE_EOF || (buf->single_frame && ret == CAMU_QUEUE_OK))) { + // If a buffer only ever has 1 frame, libplacebo will never return EOF. + // In our code that 1 frame buffer might be a video, in which case that behavior is erroneous. + bool eof = ret == CAMU_QUEUE_EOF || (buf->buffered_with_one_frame && ret == CAMU_QUEUE_OK); + if (flow == FLUSHED && eof) { al_log_debug("video_buffer", "Flushed."); buf->callback(buf->userdata, CAMU_BUFFER_EOF); al_atomic_store(u8)(&buf->flow, SIGNALED, AL_ATOMIC_RELEASE); } else if (flow == FLOWING) { + // Never attempt to uncork if flow = FLUSHED. f64 have = buf->queue->count(buf->queue) * buf->avg_frame_duration; if (have <= BUFFER_MARK_LOW) { buf->callback(buf->userdata, CAMU_BUFFER_UNCORK); diff --git a/src/buffer/video.h b/src/buffer/video.h index 33991a2..0814251 100644 --- a/src/buffer/video.h +++ b/src/buffer/video.h @@ -29,6 +29,8 @@ struct camu_video_buffer { struct camu_frame_queue *queue; bool buffered; + // We need to manually trigger EOF in read(). + bool buffered_with_one_frame; // Flush the renderer on this read and don't allow it to set the clock. bool weighted_read; |