summaryrefslogtreecommitdiff
path: root/src/buffer
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2025-02-04 21:05:18 -0500
committerAndrew Opalach <andrew@akon.city> 2025-02-04 21:05:18 -0500
commit0060d4f3df9b264e4a4adb77da67336709b41b6b (patch)
treea59d4e2f218cec89af56bdb2036d0a2ecd488349 /src/buffer
parentcdd1f574312722ab305cba6771d9bcc5da0b0c38 (diff)
downloadcamu-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.c147
-rw-r--r--src/buffer/audio.h4
-rw-r--r--src/buffer/frame_queue.h2
-rw-r--r--src/buffer/video.c79
-rw-r--r--src/buffer/video.h2
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;