diff options
| author | 2025-02-04 21:05:18 -0500 | |
|---|---|---|
| committer | 2025-02-04 21:05:18 -0500 | |
| commit | 0060d4f3df9b264e4a4adb77da67336709b41b6b (patch) | |
| tree | a59d4e2f218cec89af56bdb2036d0a2ecd488349 /src | |
| 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')
46 files changed, 642 insertions, 466 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; diff --git a/src/codec/codec.h b/src/codec/codec.h index af824d7..e8d2b6d 100644 --- a/src/codec/codec.h +++ b/src/codec/codec.h @@ -120,8 +120,8 @@ struct camu_audio_format { }; struct camu_video_format { - s32 width; - s32 height; + u32 width; + u32 height; s32 format; }; @@ -158,8 +158,8 @@ struct camu_codec_frame { s32 format; f64 pts; struct { - s32 width; - s32 height; + u32 width; + u32 height; } video; struct { u32 sample_count; @@ -177,7 +177,7 @@ struct camu_demuxer { bool (*seek)(struct camu_demuxer *, u64); void (*free)(struct camu_demuxer **); array(struct camu_codec_stream) streams; - s32 subscribed; + u32 subscribed; }; struct camu_renderer; diff --git a/src/codec/ffmpeg/common.c b/src/codec/ffmpeg/common.c index 95bf247..c5fe7cd 100644 --- a/src/codec/ffmpeg/common.c +++ b/src/codec/ffmpeg/common.c @@ -4,6 +4,15 @@ #include "common.h" +s64 camu_ff_frame_duration(AVFrame *frame) +{ +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 30, 100) + return frame->duration; +#else + return frame->pkt_duration; +#endif +} + void camu_ff_set_log_callback(void (*callback)(void *, s32, const char *, va_list)) { av_log_set_callback(callback); @@ -15,6 +24,7 @@ static size_t pos = 0; // If a line takes more than 2 steps to print, make sure we stay within AL_LOG_MESSAGE_SIZE. #define CHUNK_SIZE (AL_LOG_MESSAGE_SIZE / 2u) +// @TODO: thread-saftey. static void av_log_callback(void *userdata, s32 level, const char *fmt, va_list args) { (void)userdata; diff --git a/src/codec/ffmpeg/common.h b/src/codec/ffmpeg/common.h index 229ffb0..885dd4d 100644 --- a/src/codec/ffmpeg/common.h +++ b/src/codec/ffmpeg/common.h @@ -2,6 +2,11 @@ #include <al/types.h> #include <al/log.h> +#include <libavutil/frame.h> + +void camu_ff_common_init(void); + +s64 camu_ff_frame_duration(AVFrame *frame); // userdata, level, fmt, args void camu_ff_set_log_callback(void (*callback)(void *, int, const char *, va_list)); diff --git a/src/codec/ffmpeg/decoder.c b/src/codec/ffmpeg/decoder.c index d3c9f41..dd0cc0b 100644 --- a/src/codec/ffmpeg/decoder.c +++ b/src/codec/ffmpeg/decoder.c @@ -326,9 +326,8 @@ static s32 receive_frames(struct camu_ff_decoder *av) continue; } if (avframe->best_effort_timestamp == AV_NOPTS_VALUE) { - // This trips under what I think are normal conditions, - // more testing is needed. - al_assert(avframe->duration == 0); + // This trips under what I think are normal conditions, more testing is needed. + //al_assert(avframe->duration == 0); avframe->best_effort_timestamp = 0; } av->callback(av->userdata, frame); diff --git a/src/codec/ffmpeg/packet_ext.c b/src/codec/ffmpeg/packet_ext.c index 2ff425e..f3704f2 100644 --- a/src/codec/ffmpeg/packet_ext.c +++ b/src/codec/ffmpeg/packet_ext.c @@ -16,7 +16,9 @@ void nn_packet_write_av_codec_parameters(struct nn_packet *packet, AVCodecParame NNWT_PACKET_WRITE_TYPE(packet, s32, codecpar->width); NNWT_PACKET_WRITE_TYPE(packet, s32, codecpar->height); NNWT_PACKET_WRITE_TYPE(packet, AVRational, codecpar->sample_aspect_ratio); +#if LIBAVCODEC_VERSION_INT >= 60 NNWT_PACKET_WRITE_TYPE(packet, AVRational, codecpar->framerate); +#endif NNWT_PACKET_WRITE_TYPE(packet, enum AVFieldOrder, codecpar->field_order); NNWT_PACKET_WRITE_TYPE(packet, enum AVColorRange, codecpar->color_range); NNWT_PACKET_WRITE_TYPE(packet, enum AVColorPrimaries, codecpar->color_primaries); @@ -43,7 +45,7 @@ void nn_packet_write_av_dictionary(struct nn_packet *packet, AVDictionary *dict) s32 count = av_dict_count(dict); NNWT_PACKET_WRITE_TYPE(packet, s32, count); const AVDictionaryEntry *entry = NULL; - while ((entry = av_dict_iterate(dict, entry))) { + while ((entry = av_dict_get(dict, "", entry, AV_DICT_IGNORE_SUFFIX))) { size_t len = al_strlen(entry->key); al_assert(len <= UINT32_MAX); NNWT_PACKET_WRITE_TYPE(packet, u32, len); @@ -112,7 +114,9 @@ void nn_packet_read_av_codec_parameters(struct nn_packet *packet, AVCodecParamet NNWT_PACKET_READ_TYPE(packet, s32, codecpar->width); NNWT_PACKET_READ_TYPE(packet, s32, codecpar->height); NNWT_PACKET_READ_TYPE(packet, AVRational, codecpar->sample_aspect_ratio); +#if LIBAVCODEC_VERSION_INT >= 60 NNWT_PACKET_READ_TYPE(packet, AVRational, codecpar->framerate); +#endif NNWT_PACKET_READ_TYPE(packet, enum AVFieldOrder, codecpar->field_order); NNWT_PACKET_READ_TYPE(packet, enum AVColorRange, codecpar->color_range); NNWT_PACKET_READ_TYPE(packet, enum AVColorPrimaries, codecpar->color_primaries); diff --git a/src/codec/ffmpeg/resampler.c b/src/codec/ffmpeg/resampler.c index 4f5a2ab..d2c8f62 100644 --- a/src/codec/ffmpeg/resampler.c +++ b/src/codec/ffmpeg/resampler.c @@ -19,7 +19,7 @@ static bool ff_resampler_init(struct camu_resampler *resamp, struct camu_resampl &fmt->req.channel_layout, fmt->req.format, fmt->req.sample_rate, &fmt->in.channel_layout, fmt->in.format, fmt->in.sample_rate, 0, NULL); if (ret != 0) { - al_log_error("lav_resampler", "Failed to configure resampler context."); + al_log_error("ff_resampler", "Failed to configure resampler context."); return false; } @@ -30,7 +30,7 @@ static bool ff_resampler_init(struct camu_resampler *resamp, struct camu_resampl if (swr_init(av->resample_context) != 0) { swr_free(&av->resample_context); - al_log_error("lav_resampler", "Failed to init resampler context."); + al_log_error("ff_resampler", "Failed to init resampler context."); return false; } @@ -60,7 +60,7 @@ static s32 ff_resampler_convert(struct camu_resampler *resamp, const u8 **in_dat s32 resample_count = swr_convert(av->resample_context, av->data, out_resample_count, in_data, in_samples); if (resample_count < 0) { - al_log_error("lav_resampler", "Failed to convert samples (%d).", resample_count); + al_log_error("ff_resampler", "Failed to convert samples (%d).", resample_count); } return resample_count; } @@ -72,7 +72,7 @@ static s32 ff_resampler_flush(struct camu_resampler *resamp) av->sample_count, NULL, 0); if (swr_init(av->resample_context) != 0) { swr_free(&av->resample_context); - al_log_error("lav_resampler", "Failed to re-init resampler context after flush."); + al_log_error("ff_resampler", "Failed to re-init resampler context after flush."); return -1; } return resample_count; diff --git a/src/codec/ffmpeg/scaler.c b/src/codec/ffmpeg/scaler.c index bc8d41b..a76c0a5 100644 --- a/src/codec/ffmpeg/scaler.c +++ b/src/codec/ffmpeg/scaler.c @@ -63,9 +63,11 @@ static bool ff_scaler_scale(struct camu_scaler *scale, const u8 **in_slice, s32 { struct camu_ff_scaler *av = (struct camu_ff_scaler *)scale; AVFrame *frame = av->frame.av.frame; + struct camu_scaler_format *fmt = &av->fmt; + s32 slice_height = (s32)fmt->in.height; s32 ret = sws_scale(av->scaler_context, in_slice, in_strides, 0, - av->fmt.in.height, frame->data, frame->linesize); - if (ret != av->fmt.req.height) { + slice_height, frame->data, frame->linesize); + if (ret != (s32)fmt->req.height) { al_log_error("ff_scaler", "Failed to scale frame (%s).", av_err2str(ret)); return false; } diff --git a/src/codec/stb_image/client.c b/src/codec/stb_image/client.c index 3742def..250876f 100644 --- a/src/codec/stb_image/client.c +++ b/src/codec/stb_image/client.c @@ -22,15 +22,16 @@ static s32 stbi_decoder_push(struct camu_decoder *dec, struct camu_codec_packet u8 *data = nn_buffer_get_ptr(packet->buffer, 0); u8 *pixels = stbi_load_from_memory(data, packet->buffer->size, &w, &h, &channels, 0); if (!pixels) return CAMU_ERR_ERROR; + struct camu_video_format *fmt = &stb->stream->video.fmt; - al_assert(w == fmt->width && h == fmt->height); + al_assert(w > 0 && h > 0 && (u32)w == fmt->width && (u32)h == fmt->height); struct camu_codec_frame *frame = al_alloc_object(struct camu_codec_frame); frame->mode = CAMU_NORMAL; frame->flags = 0; frame->data = pixels; - frame->video.width = w; - frame->video.height = h; + frame->video.width = (u32)w; + frame->video.height = (u32)h; frame->format = camu_pixel_format_from_channels(channels); al_assert(frame->format == fmt->format); frame->pts = 0.0; diff --git a/src/codec/stb_image/server.c b/src/codec/stb_image/server.c index 3642a64..79327d0 100644 --- a/src/codec/stb_image/server.c +++ b/src/codec/stb_image/server.c @@ -28,8 +28,8 @@ static bool stbi_demuxer_init(struct camu_demuxer *demux, struct cch_handle *han stream.mode = CAMU_NORMAL; stream.type = CAMU_STREAM_VIDEO; struct camu_video_format *fmt = &stream.video.fmt; - fmt->width = w; - fmt->height = h; + fmt->width = (u32)w; + fmt->height = (u32)h; fmt->format = camu_pixel_format_from_channels(channels); al_array_push(stb->demux.streams, stream); diff --git a/src/fruits/cmsrv/cmsrv.c b/src/fruits/cmsrv/cmsrv.c index 0149246..be620a7 100644 --- a/src/fruits/cmsrv/cmsrv.c +++ b/src/fruits/cmsrv/cmsrv.c @@ -63,6 +63,8 @@ static u8 server_line_callback(void *userdata, str *line) #elif defined NAUNET_HAS_CURL if (camu_is_url(line, 0)) { nn_packet_write_u8(packet, CAMU_RESOURCE_HTTP); +#else + if (0) { #endif #if CACHE_HAVE_CDIO } else if (al_str_cmp(line, &al_str_c("cdda://"), 0, 7) == 0) { diff --git a/src/fruits/cmv/cmv.c b/src/fruits/cmv/cmv.c index ac6f6e8..ebc2236 100644 --- a/src/fruits/cmv/cmv.c +++ b/src/fruits/cmv/cmv.c @@ -139,6 +139,8 @@ s32 main(s32 argc, char *argv[]) #elif defined NAUNET_HAS_CURL if (camu_is_url(&arg, 0)) { nn_packet_write_u8(packet, CAMU_RESOURCE_HTTP); +#else + if (0) { #endif #if CACHE_HAVE_CDIO } else if (al_str_cmp(&arg, &al_str_c("cdda://"), 0, 7) == 0) { diff --git a/src/liana/client.c b/src/liana/client.c index 1da27c1..44cba88 100644 --- a/src/liana/client.c +++ b/src/liana/client.c @@ -56,8 +56,8 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe #endif } else if (type == CAMU_STREAM_VIDEO) { struct camu_video_format *fmt = &track->stream.video.fmt; - fmt->width = nn_packet_read_s32(packet); - fmt->height = nn_packet_read_s32(packet); + fmt->width = nn_packet_read_u32(packet); + fmt->height = nn_packet_read_u32(packet); fmt->format = nn_packet_read_s32(packet); } break; @@ -139,6 +139,7 @@ skip: client->callback(client->userdata, LIANA_CLIENT_CONFIGURE, track->client->stream, track); lia_vcr_add_track(&client->vcr, track); } + client->callback(client->userdata, LIANA_CLIENT_CONFIGURE_COMPLETE, NULL, NULL); } static void info_packet_callback(void *userdata, struct nn_packet_stream *stream, struct nn_packet *packet) @@ -154,7 +155,7 @@ static void info_packet_callback(void *userdata, struct nn_packet_stream *stream } stream->packet_callback = data_packet_callback; struct nn_packet *rpacket = nn_packet_create(); - nn_packet_write_s32(rpacket, client->mask); + nn_packet_write_u32(rpacket, client->mask); nn_packet_stream_send_packet(stream, rpacket); lia_vcr_start(&client->vcr); } @@ -191,7 +192,7 @@ static bool connection_callback(void *userdata, struct nn_packet_stream *stream) struct nn_packet *packet = nn_packet_create(); nn_packet_write_u32(packet, client->node_id); nn_packet_write_u32(packet, client->connection_id); - nn_packet_write_s32(packet, client->mask); + nn_packet_write_u32(packet, client->mask); nn_packet_write_u64(packet, client->pos); if (client->mask == 0) { stream->packet_callback = info_packet_callback; diff --git a/src/liana/client.h b/src/liana/client.h index 9fe676b..5d5fc90 100644 --- a/src/liana/client.h +++ b/src/liana/client.h @@ -6,10 +6,22 @@ #include "vcr.h" +enum { + LIANA_CLIENT_CONFIGURE = 0, + LIANA_CLIENT_CONFIGURE_COMPLETE, + LIANA_CLIENT_DATA, + LIANA_CLIENT_SUBTITLE, + LIANA_CLIENT_REMOVE_BUFFERS, + LIANA_CLIENT_RESUME_AT, + LIANA_CLIENT_RECONNECTED, + LIANA_CLIENT_EOF, + LIANA_CLIENT_CLOSED +}; + struct lia_client { struct nn_event_loop *loop; u32 node_id; - s32 mask; + u32 mask; u64 pos; u64 at; u8 reconnect; diff --git a/src/liana/handler.h b/src/liana/handler.h index 9f93e5c..1fa77a0 100644 --- a/src/liana/handler.h +++ b/src/liana/handler.h @@ -5,10 +5,16 @@ #include "../codec/codec.h" #include "../cache/handle.h" +enum { + LIANA_PACKET_DATA = 0, + LIANA_PACKET_EOF, + LIANA_PACKET_ERROR +}; + struct lia_server_handler { bool (*init)(struct lia_server_handler *, struct cch_handle *); void (*write_info)(struct lia_server_handler *, struct nn_packet *); - void (*subscribe)(struct lia_server_handler *, s32); + void (*subscribe)(struct lia_server_handler *, u32); u64 (*get_duration)(struct lia_server_handler *); bool (*seek)(struct lia_server_handler *, u64); void (*step)(struct lia_server_handler *); @@ -17,36 +23,6 @@ struct lia_server_handler { s32 status; }; -enum { - LIANA_PACKET_DATA = 0, - LIANA_PACKET_EOF, - LIANA_PACKET_ERROR -}; - -enum { - LIANA_CLIENT_CONFIGURE = 0, - LIANA_CLIENT_DATA, - LIANA_CLIENT_SUBTITLE, - LIANA_CLIENT_REMOVE_BUFFERS, - LIANA_CLIENT_RESUME_AT, - LIANA_CLIENT_RECONNECTED, - LIANA_CLIENT_EOF, - LIANA_CLIENT_CLOSED -}; - -struct lia_resume_req { - u64 start; - u64 offset; -}; - -struct lia_seek_req { - u64 base; - u64 ts; - u64 delay; - bool paused; - u64 paused_at; -}; - struct lia_client_handler { bool (*init)(struct lia_client_handler *, struct camu_renderer *, struct camu_codec_stream *); bool (*handle_packet)(struct lia_client_handler *, struct nn_packet *); diff --git a/src/liana/handlers/cdio.h b/src/liana/handlers/cdio.h index 4c40986..3b0e991 100644 --- a/src/liana/handlers/cdio.h +++ b/src/liana/handlers/cdio.h @@ -2,6 +2,7 @@ #include "../handler.h" +#ifdef LIANA_SERVER struct lia_cdio_server { struct lia_server_handler handler; struct cch_handle *handle; @@ -10,9 +11,13 @@ struct lia_cdio_server { f64 pts; }; +struct lia_server_handler *lia_cdio_server_create(); +#endif + +#ifdef LIANA_CLIENT struct lia_cdio_client { struct lia_client_handler handler; }; -struct lia_server_handler *lia_cdio_server_create(); struct lia_client_handler *lia_cdio_client_create(void); +#endif diff --git a/src/liana/handlers/cdio_client.c b/src/liana/handlers/cdio_client.c index 665858f..6d61e59 100644 --- a/src/liana/handlers/cdio_client.c +++ b/src/liana/handlers/cdio_client.c @@ -1,3 +1,5 @@ +#include "../client.h" + #include "cdio.h" static bool cdio_client_init(struct lia_client_handler *handler, struct camu_renderer *renderer, diff --git a/src/liana/handlers/cdio_server.c b/src/liana/handlers/cdio_server.c index 01a9265..6ea1422 100644 --- a/src/liana/handlers/cdio_server.c +++ b/src/liana/handlers/cdio_server.c @@ -39,7 +39,7 @@ static void cdio_server_write_info(struct lia_server_handler *handler, struct nn nn_packet_write_s32(packet, cdio->fmt.channel_count); } -static void cdio_server_subscribe(struct lia_server_handler *handler, s32 mask) +static void cdio_server_subscribe(struct lia_server_handler *handler, u32 mask) { (void)handler; (void)mask; diff --git a/src/liana/handlers/codec.h b/src/liana/handlers/codec.h index 29d513b..696cbb8 100644 --- a/src/liana/handlers/codec.h +++ b/src/liana/handlers/codec.h @@ -5,6 +5,7 @@ #include "../server.h" #include "../handler.h" +#ifdef LIANA_SERVER struct lia_codec_server { struct lia_server_handler handler; struct camu_demuxer *demux; @@ -14,10 +15,14 @@ struct lia_codec_server { struct camu_codec_packet packet; }; +struct lia_server_handler *lia_codec_server_create(void); +#endif + +#ifdef LIANA_CLIENT struct lia_codec_client { struct lia_client_handler handler; struct camu_decoder *dec; }; -struct lia_server_handler *lia_codec_server_create(void); struct lia_client_handler *lia_codec_client_create(void); +#endif diff --git a/src/liana/handlers/codec_client.c b/src/liana/handlers/codec_client.c index 0aef69f..36e475e 100644 --- a/src/liana/handlers/codec_client.c +++ b/src/liana/handlers/codec_client.c @@ -1,5 +1,4 @@ #include "../../codec/codec.h" -#include "../../server/common.h" #ifdef CAMU_HAVE_FFMPEG #include "../../codec/ffmpeg/decoder.h" @@ -11,6 +10,7 @@ #include "../../codec/wuffs/decoder.h" #include "../vcr.h" +#include "../client.h" #include "codec.h" @@ -87,13 +87,13 @@ static bool codec_client_handle_packet(struct lia_client_handler *handler, struc switch (mode) { case CAMU_NORMAL: { if (codec->dec) { -#ifdef CAMU_DIRECT_MODE - success = push_packet(codec, (struct nn_buffer *)packet->opaque); -#else - struct nn_buffer buffer; - nn_packet_read_buffer(packet, &buffer); - success = push_packet(codec, &buffer); -#endif + if (packet->opaque) { + success = push_packet(codec, (struct nn_buffer *)packet->opaque); + } else { + struct nn_buffer buffer; + nn_packet_read_buffer(packet, &buffer); + success = push_packet(codec, &buffer); + } } else { success = true; } diff --git a/src/liana/handlers/codec_server.c b/src/liana/handlers/codec_server.c index dcbfe17..7868a42 100644 --- a/src/liana/handlers/codec_server.c +++ b/src/liana/handlers/codec_server.c @@ -63,8 +63,8 @@ static void codec_server_write_info(struct lia_server_handler *handler, struct n switch (stream->mode) { case CAMU_NORMAL: { struct camu_video_format *fmt = &stream->video.fmt; - nn_packet_write_s32(packet, fmt->width); - nn_packet_write_s32(packet, fmt->height); + nn_packet_write_u32(packet, fmt->width); + nn_packet_write_u32(packet, fmt->height); nn_packet_write_s32(packet, fmt->format); break; } @@ -78,7 +78,7 @@ static void codec_server_write_info(struct lia_server_handler *handler, struct n } } -static void codec_server_subscribe(struct lia_server_handler *handler, s32 mask) +static void codec_server_subscribe(struct lia_server_handler *handler, u32 mask) { struct lia_codec_server *codec = (struct lia_codec_server *)handler; codec->demux->subscribed = mask; diff --git a/src/liana/handlers/dvd.h b/src/liana/handlers/dvd.h index 4b71ec8..dd5ac0a 100644 --- a/src/liana/handlers/dvd.h +++ b/src/liana/handlers/dvd.h @@ -1,9 +1,10 @@ #pragma once -#include <dvdnav/dvdnav.h> - #include "../handler.h" +#ifdef LIANA_SERVER +#include <dvdnav/dvdnav.h> + struct lia_dvd_server { struct lia_server_handler handler; struct cch_handle *handle; @@ -11,9 +12,13 @@ struct lia_dvd_server { struct nn_buffer buffer; }; +struct lia_server_handler *lia_dvd_server_create(void); +#endif + +#ifdef LIANA_CLIENT struct lia_dvd_client { struct lia_client_handler handler; }; -struct lia_server_handler *lia_dvd_server_create(void); struct lia_client_handler *lia_dvd_client_create(void); +#endif diff --git a/src/liana/handlers/dvd_server.c b/src/liana/handlers/dvd_server.c index afb2c07..f564325 100644 --- a/src/liana/handlers/dvd_server.c +++ b/src/liana/handlers/dvd_server.c @@ -53,7 +53,7 @@ static void dvd_server_write_info(struct lia_server_handler *handler, struct nn_ (void)packet; } -static void dvd_server_subscribe(struct lia_server_handler *handler, s32 mask) +static void dvd_server_subscribe(struct lia_server_handler *handler, u32 mask) { (void)handler; (void)mask; diff --git a/src/liana/server.c b/src/liana/server.c index 6459a0e..24c4bfc 100644 --- a/src/liana/server.c +++ b/src/liana/server.c @@ -152,11 +152,9 @@ static void data_connection_closed_callback(void *userdata, struct nn_packet_str } } -static void subscribe_packet_callback(void *userdata, struct nn_packet_stream *stream, struct nn_packet *packet) +static void start_connection_handler(struct lia_node_connection *conn, u32 mask) { - struct lia_node_connection *conn = (struct lia_node_connection *)userdata; - s32 mask = nn_packet_read_s32(packet); - nn_packet_stream_return_packet(stream, packet); + struct nn_packet_stream *stream = conn->stream; conn->handler->subscribe(conn->handler, mask); stream->packet_callback = discard_packet_callback; stream->packet_sent_callback = data_packet_sent_callback; @@ -165,6 +163,14 @@ static void subscribe_packet_callback(void *userdata, struct nn_packet_stream *s nn_thread_create(&conn->thread, handler_thread, conn); } +static void subscribe_packet_callback(void *userdata, struct nn_packet_stream *stream, struct nn_packet *packet) +{ + struct lia_node_connection *conn = (struct lia_node_connection *)userdata; + u32 mask = nn_packet_read_u32(packet); + nn_packet_stream_return_packet(stream, packet); + start_connection_handler(conn, mask); +} + static void subscribe_packet_sent_callback(void *userdata, struct nn_packet *packet) { (void)userdata; @@ -183,7 +189,7 @@ static void handle_connection(struct lia_node_connection *conn, struct nn_packet { struct nn_packet_stream *stream = conn->stream; - s32 mask = nn_packet_read_s32(packet); + u32 mask = nn_packet_read_u32(packet); u64 seek_pos = nn_packet_read_u64(packet); al_assert(!conn->ref); @@ -204,12 +210,8 @@ static void handle_connection(struct lia_node_connection *conn, struct nn_packet stream->connection_closed_callback = subscribe_connection_closed_callback; nn_packet_stream_send_packet(stream, rpacket); } else { + start_connection_handler(conn, mask); conn->handler->subscribe(conn->handler, mask); - stream->packet_callback = discard_packet_callback; - stream->packet_sent_callback = data_packet_sent_callback; - stream->packets_sent_callback = data_packets_sent_callback; - stream->connection_closed_callback = data_connection_closed_callback; - nn_thread_create(&conn->thread, handler_thread, conn); } } diff --git a/src/libsink/common.h b/src/libsink/common.h index 0bb3ded..254e246 100644 --- a/src/libsink/common.h +++ b/src/libsink/common.h @@ -1,7 +1,5 @@ #pragma once -#define CAMU_SINK_LOCAL - enum { CAMU_SINK_SET = 0, CAMU_SINK_PAUSE, diff --git a/src/libsink/sink.c b/src/libsink/sink.c index 03dd379..716990e 100644 --- a/src/libsink/sink.c +++ b/src/libsink/sink.c @@ -2,21 +2,21 @@ #include <nnwt/multiplex.h> #include "../server/common.h" - -#include "../liana/list.h" -#include "../liana/handler.h" - #include "../buffer/common.h" +#include "../liana/list.h" #include "sink.h" #include "common.h" +#define CAMU_SINK_LOCAL //#define CAMU_SINK_ONESHOT //#define CAMU_SINK_TRACE #ifndef CAMU_SINK_NO_VIDEO +#ifdef CAMU_RENDERER_LIBPLACEBO #include "../render/renderer_libplacebo.h" #endif +#endif // Requested state of the sinks outputs. enum { @@ -91,7 +91,7 @@ enum { #define VIDEO_IS_SINGLE_FRAME(entry) camu_video_buffer_is_single_frame(&(entry)->video.buf) #endif -#if defined CAMU_SCREEN_THREADED && defined CAMU_MIXER_THREADED_START_STOP +#if defined CAMU_SCREEN_THREADED && defined CAMU_MIXER_THREADED #define BLOCKING_SLEEP(delay) nn_thread_sleep(delay) #else #define BLOCKING_SLEEP(delay) nn_event_loop_sleep(sink->loop, delay) @@ -151,7 +151,9 @@ static void remove_entry_audio_buffer(struct camu_sink *sink, struct camu_sink_e { al_assert(!entry->ended && entry->audio.state != BUFFER_ENDED); al_assert(entry->audio.state != BUFFER_INIT); + if (entry->audio.state == BUFFER_ADDED) { + entry->audio.state = BUFFER_SET_OR_BUFFERED; #ifdef CAMU_MIXER_THREADED_START_STOP sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); #else @@ -161,7 +163,6 @@ static void remove_entry_audio_buffer(struct camu_sink *sink, struct camu_sink_e .opaque = entry }); #endif - entry->audio.state = BUFFER_SET_OR_BUFFERED; } else if (entry->audio.state == BUFFER_SET_OR_BUFFERED) { entry->audio.state = BUFFER_CONFIGURED; } else if (entry->audio.state == BUFFER_QUEUED) { @@ -175,9 +176,10 @@ static void remove_entry_video_buffer(struct camu_sink *sink, struct camu_sink_e // Don't assert !entry->ended here because of single frame handling. al_assert(entry->video.state != BUFFER_ENDED); al_assert(entry->video.state != BUFFER_INIT); + if (entry->video.state == BUFFER_ADDED) { - sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); entry->video.state = BUFFER_SET_OR_BUFFERED; + sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); } else if (entry->video.state == BUFFER_SET_OR_BUFFERED) { entry->video.state = BUFFER_CONFIGURED; } else if (entry->video.state == BUFFER_QUEUED) { @@ -235,7 +237,7 @@ static void add_or_queue_entry(struct camu_sink_entry *entry) static void sink_local_pause(struct camu_sink *sink, struct camu_sink_entry *entry) { if (camu_clock_is_paused(&entry->clock)) { - entry->audio.buffer_paused = false; + entry->buffers_paused = false; camu_clock_resume(&entry->clock, 0); if (!AUDIO_EMPTY(entry) && sink->audio.state == SINK_PAUSED) { sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL); @@ -248,7 +250,7 @@ static void sink_local_pause(struct camu_sink *sink, struct camu_sink_entry *ent } #endif } else { - entry->audio.buffer_paused = true; + entry->buffers_paused = true; camu_clock_pause(&entry->clock, 0); #ifndef CAMU_SINK_NO_VIDEO if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry) && sink->video.state == SINK_PLAYING) { @@ -598,90 +600,102 @@ static void maybe_cleanup_old_entries(struct camu_sink *sink) void add_audio_if_set_and_buffered(struct camu_sink_entry *entry) { al_assert(!entry->ended); - al_assert(entry->audio.state != BUFFER_ADDED); + al_assert(entry->audio.state != BUFFER_INIT && + (entry->audio.state != BUFFER_QUEUED) && + (entry->audio.state != BUFFER_ADDED)); + if (entry->audio.state == BUFFER_ENDED) { al_log_warn("sink", "Tried to add an ended audio buffer."); return; } + if (entry->audio.state == BUFFER_CONFIGURED) { entry->audio.state = BUFFER_SET_OR_BUFFERED; } else if (entry->audio.state == BUFFER_SET_OR_BUFFERED) { entry->audio.state = BUFFER_ADDED; - bool ignore_video = true; + #ifndef CAMU_SINK_NO_VIDEO - ignore_video = VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry); - if (ignore_video) { + if (VIDEO_EMPTY(entry)) { struct camu_sink *sink = entry->sink; sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, CAMU_SINK_VIDEO, NULL); } #endif - camu_audio_buffer_set_no_video(&entry->audio.buf, ignore_video); -#ifdef CAMU_SINK_LOCAL - // If we're local we don't have to worry about syncing audio-only entries. - camu_audio_buffer_set_ignore_desync(&entry->audio.buf, ignore_video); -#else - // Force resync. - camu_audio_buffer_unpause(&entry->audio.buf); -#endif if (VIDEO_ADDED_OR_EMPTY(entry)) { - add_entry_audio_buffer(entry); - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = entry->audio.buffer_paused ? STOP : START, - .value.i = CAMU_SINK_AUDIO - }); #ifndef CAMU_SINK_NO_VIDEO - if (!ignore_video) { - // Single frames are unconditionally added in add_video_if_set_and_buffered(). + bool single_frame = VIDEO_IS_SINGLE_FRAME(entry); + + // Single frames are unconditionally added in add_video_if_set_and_buffered(). + if (!VIDEO_EMPTY(entry) && !single_frame) { add_entry_video_buffer(entry); } #endif + add_entry_audio_buffer(entry); + + // This entry could be in previous. This is well defined but confusing. maybe_remove_previous(entry->sink); + #ifndef CAMU_SINK_NO_VIDEO - if (VIDEO_EMPTY(entry)) { - // This must come after maybe_remove_previous(). + // This must come after maybe_remove_previous(). + if (!single_frame) { queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = STOP, + .op = VIDEO_EMPTY(entry) || entry->buffers_paused ? STOP : START, .value.i = CAMU_SINK_VIDEO }); } #endif + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = entry->buffers_paused ? STOP : START, + .value.i = CAMU_SINK_AUDIO + }); } - } } #ifndef CAMU_SINK_NO_VIDEO void add_video_if_set_and_buffered(struct camu_sink_entry *entry) { - al_assert(entry->video.state != BUFFER_ADDED); + al_assert(entry->video.state != BUFFER_INIT && + (entry->video.state != BUFFER_QUEUED) && + (entry->video.state != BUFFER_ADDED)); + if (entry->video.state == BUFFER_ENDED) { al_log_warn("sink", "Tried to add an ended video buffer."); return; } + if (entry->video.state == BUFFER_CONFIGURED) { entry->video.state = BUFFER_SET_OR_BUFFERED; } else if (entry->video.state == BUFFER_SET_OR_BUFFERED) { entry->video.state = BUFFER_ADDED; + bool single_frame = VIDEO_IS_SINGLE_FRAME(entry); + if (AUDIO_ADDED_OR_EMPTY(entry)) { - if (entry->audio.state == BUFFER_ADDED) { + add_entry_video_buffer(entry); + if (!AUDIO_EMPTY(entry)) { add_entry_audio_buffer(entry); - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = entry->audio.buffer_paused ? STOP : START, - .value.i = CAMU_SINK_AUDIO - }); } - add_entry_video_buffer(entry); + + // This entry could be in previous. maybe_remove_previous(entry->sink); + + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = single_frame || entry->buffers_paused ? STOP : START, + .value.i = CAMU_SINK_VIDEO + }); + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = AUDIO_EMPTY(entry) || entry->buffers_paused ? STOP : START, + .value.i = CAMU_SINK_AUDIO + }); } else if (single_frame) { add_entry_video_buffer(entry); + // Stopping video here is needed if skipping from a video to an image. + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_VIDEO + }); } - // Stopping video here is needed if skipping from a video to an image. - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = single_frame ? STOP : START, - .value.i = CAMU_SINK_VIDEO - }); } } #endif @@ -714,14 +728,30 @@ static void switch_to(struct camu_sink *sink, struct camu_sink_entry *target) if (!target->ended) { add_or_queue_entry(target); + } else { #ifndef CAMU_SINK_NO_VIDEO - } else if (VIDEO_IS_SINGLE_FRAME(target)) { - add_video_if_set_and_buffered(target); + if (VIDEO_IS_SINGLE_FRAME(target)) { + add_video_if_set_and_buffered(target); + } else { + // @TODO: current-less + sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, CAMU_SINK_VIDEO, NULL); + queue_cmd(sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_VIDEO + }); + } #endif + queue_cmd(sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_AUDIO + }); } sink->current = target; sink->current->audio.ignore_paused = false; + if (!sink->current->buffers_paused) { + camu_audio_buffer_unpause(&sink->current->audio.buf); + } } #ifndef CAMU_SINK_LOCAL @@ -811,7 +841,7 @@ static void audio_buffer_callback(void *userdata, u8 op) remove_entry_audio_buffer(sink, entry); } // This assert likely doesn't matter due to the handling of the ENDED state. - //al_assert(entry->audio.state == BUFFER_SET_OR_BUFFERED); + al_assert(entry->audio.state == BUFFER_SET_OR_BUFFERED); entry->audio.state = BUFFER_ENDED; bool end_entry = VIDEO_ENDED_OR_EMPTY(entry); #ifndef CAMU_SINK_NO_VIDEO @@ -859,7 +889,7 @@ static void video_buffer_callback(void *userdata, u8 op) if (entry->video.state == BUFFER_ADDED) { remove_entry_video_buffer(sink, entry); } - //al_assert(entry->video.state == BUFFER_SET_OR_BUFFERED); + al_assert(entry->video.state == BUFFER_SET_OR_BUFFERED); entry->video.state = BUFFER_ENDED; if (AUDIO_ENDED_OR_EMPTY(entry)) { swapped = end_entry_and_advance_queue(sink, entry); @@ -881,17 +911,6 @@ static void video_buffer_callback(void *userdata, u8 op) } #endif -static void maybe_unset_current(struct camu_sink *sink) -{ - if (sink->current) { - if (!sink->current->ended) { - remove_entry_buffers(sink, sink->current); - } - // @TODO: A current-less state is not properly handled. - sink->current = NULL; - } -} - static void clock_callback(void *userdata, u8 op) { struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata; @@ -909,11 +928,12 @@ static void clock_callback(void *userdata, u8 op) } } -static void evaluate_and_set_latency(struct camu_sink *sink, struct camu_sink_entry *entry) +static void evaluate_and_set_buffer_params(struct camu_sink *sink, struct camu_sink_entry *entry) { #ifdef CAMU_SINK_LOCAL #ifndef CAMU_SINK_NO_VIDEO - if (!AUDIO_EMPTY(entry) && !VIDEO_EMPTY(entry)) { + bool ignore_video = VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry); + if (!AUDIO_EMPTY(entry) && !ignore_video) { f64 audio = camu_mixer_get_latency(sink->audio.mixer); s32 frames = audio / entry->video.buf.avg_frame_duration; frames -= sink->video.renderer->get_latency(sink->video.renderer); @@ -922,7 +942,11 @@ static void evaluate_and_set_latency(struct camu_sink *sink, struct camu_sink_en #else (void)sink; (void)entry; + bool ignore_video = true; #endif + // If we're local we don't have to worry about syncing audio-only entries. + camu_audio_buffer_set_ignore_desync(&entry->audio.buf, ignore_video); + camu_audio_buffer_set_no_video(&entry->audio.buf, ignore_video); #else // To sync clients with differing audio latencies our only option is to factor the mixer // latency directly into the audio buffer. @@ -933,8 +957,12 @@ static void evaluate_and_set_latency(struct camu_sink *sink, struct camu_sink_en frames += sink->video.renderer->get_latency(sink->video.renderer); camu_video_buffer_set_latency(&entry->video.buf, frames); } + bool ignore_video = VIDEO_EMPTY(entry) || VIDEO_IS_SINGLE_FRAME(entry); +#else + bool ignore_video = true; #endif camu_audio_buffer_set_latency(&entry->audio.buf, audio); + camu_audio_buffer_set_no_video(&entry->audio.buf, ignore_video); #endif } @@ -957,7 +985,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str struct camu_sink *sink = entry->sink; switch (op) { case LIANA_CLIENT_CONFIGURE: { - // No data can be sent until all selected streams are configured. + // No data will be sent until all selected streams are configured. switch (stream->type) { case CAMU_STREAM_AUDIO: entry->audio.track = (struct lia_vcr_track *)opaque; @@ -974,9 +1002,6 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } else { al_assert(false); } - if (VIDEO_ADDED_OR_EMPTY(entry)) { - evaluate_and_set_latency(sink, entry); - } nn_mutex_unlock(&sink->mutex); break; #ifndef CAMU_SINK_NO_VIDEO @@ -995,31 +1020,37 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } else { al_assert(false); } - if (AUDIO_ADDED_OR_EMPTY(entry)) { - evaluate_and_set_latency(sink, entry); - } nn_mutex_unlock(&sink->mutex); break; case CAMU_STREAM_SUBTITLE: #ifdef CAMU_HAVE_FFMPEG if (!camu_video_buffer_configure_subtitles(&entry->video.buf, stream->av.stream->codecpar)) { - // @TODO: + al_log_warn("sink", "Video buffer failed to configure subtitles."); } #endif break; case CAMU_STREAM_ATTACHMENT: { #ifdef CAMU_HAVE_FFMPEG +#ifdef CAMU_RENDERER_LIBPLACEBO struct camu_renderer_lp *lp = (struct camu_renderer_lp *)sink->video.renderer; AVDictionaryEntry *entry = av_dict_get(stream->av.stream->metadata, "filename", NULL, 0); AVCodecParameters *codecpar = stream->av.stream->codecpar; ass_add_font(lp->ass, entry->value, (const char *)codecpar->extradata, codecpar->extradata_size); #endif +#endif break; } #endif } break; } + case LIANA_CLIENT_CONFIGURE_COMPLETE: { + // All present buffers were configured. + nn_mutex_lock(&sink->mutex); + evaluate_and_set_buffer_params(sink, entry); + nn_mutex_unlock(&sink->mutex); + break; + } case LIANA_CLIENT_DATA: { struct camu_codec_frame *frame = (struct camu_codec_frame *)opaque; switch (stream->type) { @@ -1098,7 +1129,9 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } #endif - // Resolve any queued REMOVE_BUFFER requests before waiting. + // Resolve any queued REMOVE_BUFFER requests before blocking. + // This is why we are safe to block the event loop thread even if + // MIXER_THREADED_START_STOP is not set. run_queue_by_opaque(sink, entry); nn_mutex_unlock(&sink->mutex); @@ -1112,7 +1145,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str ) { BLOCKING_SLEEP(NNWT_TS_FROM_USEC(2000)); } // Reset possible ENDED state here in case the entry ended at - // some pointer after unlocking to block above. + // some point after unlocking to block above. nn_mutex_lock(&sink->mutex); if (reconnect) { @@ -1205,19 +1238,33 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str nn_mutex_lock(&sink->mutex); if (entry == sink->target) { - maybe_unset_current(sink); + // @TODO: current-less + // current could be paused and targeting this entry. + // That could cause an error in the logic of set_command_callback() + // where we check if target (prev_target) is set. sink->target = NULL; } else if (entry == sink->current) { if (sink->reconnecting) { al_assert(sink->reconnecting == sink->current); sink->reconnecting = NULL; } - sink->current = NULL; if (sink->target) { switch_to(sink, sink->target); sink->target = NULL; + } else { + // @TODO: current-less + sink->current = NULL; + sink->callback(sink->userdata, CAMU_SINK_REFRESH_VIDEO, CAMU_SINK_VIDEO, NULL); + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_VIDEO + }); + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_AUDIO + }); } - // If current was never fully added we need to do this here. + // If current was never fully added we need to call this here. maybe_remove_previous(sink); } else if (entry == sink->queued) { sink->queued = NULL; @@ -1249,6 +1296,7 @@ static struct camu_sink_entry *create_entry(struct camu_sink *sink, u32 id) entry->ended = false; camu_clock_init(&entry->clock, clock_callback, entry); + entry->buffers_paused = false; entry->audio.state = BUFFER_INIT; entry->audio.ignore_paused = false; @@ -1293,7 +1341,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn, u8 op = nn_packet_read_u8(packet); if (op == LIANA_SINK_UNSET) { - maybe_unset_current(sink); + // @TODO: current-less goto out; } @@ -1327,7 +1375,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn, camu_clock_set(&entry->clock, seek_pos / 1000000.0); // ended here does not map to entry->ended, we use it as a hint. if (!ended) { - entry->audio.buffer_paused = pause == LIANA_PAUSE_NONE || pause == LIANA_PAUSE_PAUSE; + entry->buffers_paused = pause == LIANA_PAUSE_NONE || pause == LIANA_PAUSE_PAUSE; } else { camu_clock_resume(&entry->clock, 0); } @@ -1363,7 +1411,7 @@ static bool set_command_callback(void *userdata, struct nn_rpc_connection *conn, if (!entry->ended && camu_clock_is_paused(&entry->clock)) { // This will resume user-paused entries, but whatever. - entry->audio.buffer_paused = false; + entry->buffers_paused = false; entry->audio.ignore_paused = false; camu_clock_resume(&entry->clock, 0); } @@ -1473,17 +1521,31 @@ static bool pause_command_callback(void *userdata, struct nn_rpc_connection *con #else switch (pause) { case LIANA_PAUSE_PAUSE: - entry->audio.buffer_paused = true; + entry->buffers_paused = true; camu_clock_pause(&entry->clock, at); + if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) { + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = STOP, + .value.i = CAMU_SINK_VIDEO + }); + } break; case LIANA_PAUSE_RESUME: - entry->audio.buffer_paused = false; + entry->buffers_paused = false; camu_clock_resume(&entry->clock, at); - camu_audio_buffer_unpause(&entry->audio.buf); - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = START, - .value.i = CAMU_SINK_AUDIO - }); + if (!AUDIO_EMPTY(entry)) { + camu_audio_buffer_unpause(&entry->audio.buf); + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = START, + .value.i = CAMU_SINK_AUDIO + }); + } + if (!VIDEO_EMPTY(entry) && !VIDEO_IS_SINGLE_FRAME(entry)) { + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = START, + .value.i = CAMU_SINK_VIDEO + }); + } break; } #endif diff --git a/src/libsink/sink.h b/src/libsink/sink.h index 6845c6d..9bb408d 100644 --- a/src/libsink/sink.h +++ b/src/libsink/sink.h @@ -40,16 +40,15 @@ enum { struct camu_sink_entry { u32 id; + struct lia_client client; s32 sequence; u16 lru; bool ended; u32 reset_id; struct camu_clock clock; - struct lia_client client; + bool buffers_paused; struct { u8 state; - // Don't start the audio output for this entry. - bool buffer_paused; // Don't stop the audio output when this entry is paused. bool ignore_paused; struct camu_audio_buffer buf; diff --git a/src/mixer/audio_miniaudio.c b/src/mixer/audio_miniaudio.c index ba093f4..13f4b8f 100644 --- a/src/mixer/audio_miniaudio.c +++ b/src/mixer/audio_miniaudio.c @@ -75,9 +75,6 @@ static bool audio_miniaudio_init(struct camu_audio *audio, str *name) ma_log_register_callback(&ma->log, ma_log_callback_init(miniaudio_log_callback, NULL)); ma->context_config = ma_context_config_init(); ma->context_config.pLog = &ma->log; -#ifdef MA_ENABLE_ALSA - ma->context_config.alsa.useVerboseDeviceEnumeration = MA_TRUE; -#endif #ifdef MA_ENABLE_PULSEAUDIO char *c_str = al_str_to_c_str(name); ma->context_config.pulse.pApplicationName = c_str; @@ -85,6 +82,9 @@ static bool audio_miniaudio_init(struct camu_audio *audio, str *name) #else (void)name; #endif +#ifdef MA_ENABLE_ALSA + ma->context_config.alsa.useVerboseDeviceEnumeration = MA_TRUE; +#endif s32 ret = ma_context_init(backends, ARRAY_SIZE(backends), &ma->context_config, &ma->context); if (ret != MA_SUCCESS) { al_log_error("audio_miniaudio", "Failed to initialize context (%d).", ret); @@ -177,9 +177,18 @@ static bool audio_miniaudio_configure_stream(struct camu_audio *audio, void *opa ma->config.noPreSilencedOutputBuffer = MA_TRUE; ma->config.noFixedSizedCallback = MA_TRUE; ma->config.noClip = MA_TRUE; + ma->config.playback.shareMode = ma_share_mode_shared; +#ifdef MA_ENABLE_PULSEAUDIO + ma->config.pulse.pStreamNamePlayback = ma->context_config.pulse.pApplicationName; +#endif +#ifdef MA_ENABLE_ALSA + ma->config.alsa.noAutoFormat = MA_TRUE; + ma->config.alsa.noAutoChannels = MA_TRUE; + ma->config.alsa.noAutoResample = MA_TRUE; +#endif #ifdef MA_ENABLE_WASAPI - ma->config.wasapi.noAutoConvertSRC = MA_FALSE; - ma->config.wasapi.noDefaultQualitySRC = MA_FALSE; + ma->config.wasapi.noAutoConvertSRC = MA_TRUE; + ma->config.wasapi.noDefaultQualitySRC = MA_TRUE; #endif #ifdef MA_ENABLE_AAUDIO ma->config.aaudio.usage = ma_aaudio_usage_media; diff --git a/src/render/meson.build b/src/render/meson.build index 668c3d4..75cde7c 100644 --- a/src/render/meson.build +++ b/src/render/meson.build @@ -59,46 +59,49 @@ elif get_option('renderer') == 'libplacebo' render_args += ['-DCAMU_RENDERER_LIBPLACEBO'] endif -libass_opts = [] -if not is_windows - libass_opts += ['fontconfig=enabled'] - # libass(e46aede):meson.build:126 - fontconfig = dependency('fontconfig', - version: '>=2.10.92', +libass = dependency('libass', required: false, allow_fallback: false) +if not libass.found() + libass_opts = [] + if not is_windows + libass_opts += ['fontconfig=enabled'] + # libass(e46aede):meson.build:126 + fontconfig = dependency('fontconfig', + version: '>=2.10.92', + default_options: [ + 'doc=disabled', + 'nls=disabled', + 'tests=disabled', + 'tools=disabled', + 'cache-build=disabled' + ]) + endif + # libass(e46aede):meson.build:90 + fribidi = dependency('fribidi', + version: '>=0.19.1', + default_options: [ + 'docs=false', + 'tests=false', + 'bin=false' + ]) + # libass(e46aede):meson.build:84 + # fontconfig(72b9a48):meson.build:28 + freetype = dependency('freetype2', + version: '>=9.17.3', + default_options: [ + 'harfbuzz=disabled' + ]) + # freetype2(42608f7):meson.build:346 + harfbuz = dependency('harfbuzz', + version: '>=2.0.0', default_options: [ - 'doc=disabled', - 'nls=disabled', + 'freetype=disabled', 'tests=disabled', - 'tools=disabled', - 'cache-build=disabled' + 'introspection=disabled', + 'docs=disabled', + 'utilities=disabled' ]) + libass = dependency('libass', default_options: libass_opts) endif -# libass(e46aede):meson.build:90 -fribidi = dependency('fribidi', - version: '>=0.19.1', - default_options: [ - 'docs=false', - 'tests=false', - 'bin=false' - ]) -# libass(e46aede):meson.build:84 -# fontconfig(72b9a48):meson.build:28 -freetype = dependency('freetype2', - version: '>=9.17.3', - default_options: [ - 'harfbuzz=disabled' - ]) -# freetype2(42608f7):meson.build:346 -harfbuz = dependency('harfbuzz', - version: '>=2.0.0', - default_options: [ - 'freetype=disabled', - 'tests=disabled', - 'introspection=disabled', - 'docs=disabled', - 'utilities=disabled' - ]) -libass = dependency('libass', default_options: libass_opts) render_deps += [libass] render = declare_dependency(sources: render_src, dependencies: render_deps, compile_args: render_args) diff --git a/src/render/queue_libplacebo.c b/src/render/queue_libplacebo.c index 22df3dc..165fc4b 100644 --- a/src/render/queue_libplacebo.c +++ b/src/render/queue_libplacebo.c @@ -3,6 +3,7 @@ #ifdef CAMU_HAVE_FFMPEG #define PL_LIBAV_IMPLEMENTATION 1 #include <libplacebo/utils/libav.h> +#include "../codec/ffmpeg/common.h" #endif #include "../buffer/video.h" @@ -10,7 +11,7 @@ #include "queue_libplacebo.h" #ifdef CAMU_HAVE_FFMPEG -static bool queue_lp_configure_subtitiles(struct camu_frame_queue *queue, s32 width, s32 height, AVCodecParameters *codecpar) +static bool queue_lp_configure_subtitiles(struct camu_frame_queue *queue, u32 width, u32 height, AVCodecParameters *codecpar) { struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue; if (!lq->ass) return false; @@ -86,7 +87,7 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src out_frame->planes[0] = plane; out_frame->repr = pl_color_repr_unknown; out_frame->color = pl_color_space_monitor; - out_frame->crop = (pl_rect2df){ 0, 0, frame->video.width, frame->video.height }; + out_frame->crop = (pl_rect2df){ 0.f, 0.f, frame->video.width, frame->video.height }; //out_frame->profile = camu_codec_frame_discard(frame); @@ -168,7 +169,7 @@ static struct camu_overlay_lp *create_subtitle_overlay(pl_gpu gpu, ASS_Image *as // https://github.com/mpv-player/mpv/blob/70aaba71d6e3071a732069a1d222d1eb4293faf2/sub/ass_mp.c#L195 current = (struct pl_overlay *)&overlay->overlays[overlay->num++]; struct pl_overlay_part *part = (struct pl_overlay_part *)¤t->parts[0]; - part->src = (pl_rect2df){ 0, 0, ass_frame->w, ass_frame->h }; + part->src = (pl_rect2df){ 0.f, 0.f, ass_frame->w, ass_frame->h }; part->dst = (pl_rect2df){ ass_frame->dst_x, ass_frame->dst_y, @@ -280,7 +281,7 @@ static void queue_lp_push_av_frame(struct camu_frame_queue *queue, AVFrame *fram frame->opaque = lq; pl_queue_push(lq->queue, &(struct pl_source_frame){ .pts = pts, - .duration = frame->duration * av_q2d(stream->time_base), + .duration = camu_ff_frame_duration(frame) * av_q2d(stream->time_base), .map = map_av_frame, .unmap = unmap_av_frame, .discard = discard_av_frame, diff --git a/src/render/queue_tiger.c b/src/render/queue_tiger.c index d21236d..df072d2 100644 --- a/src/render/queue_tiger.c +++ b/src/render/queue_tiger.c @@ -5,7 +5,7 @@ #define GL_UNPACK_ROW_LENGTH 0x0CF2 -static void upload_texture(struct camu_frame_queue_tiger *tq, u8 *data, s32 width, s32 height, s32 format, s32 linesize) +static void upload_texture(struct camu_frame_queue_tiger *tq, u8 *data, u32 width, u32 height, s32 format, s32 linesize) { glGenTextures(1, &tq->texture_id); glActiveTexture(GL_TEXTURE0); @@ -13,7 +13,7 @@ static void upload_texture(struct camu_frame_queue_tiger *tq, u8 *data, s32 widt //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); //if (frame->flags & CAMU_FRAME_DEVICE_ALLOCATED) { // glBindBuffer(GL_PIXEL_UNPACK_BUFFER, tq->tr->texture_buffer); //} @@ -31,7 +31,7 @@ static void upload_texture(struct camu_frame_queue_tiger *tq, u8 *data, s32 widt default: al_assert(false); } - glGenerateMipmap(GL_TEXTURE_2D); + //glGenerateMipmap(GL_TEXTURE_2D); } static void queue_tiger_push(struct camu_frame_queue *queue, struct camu_codec_frame *frame, f64 pts) diff --git a/src/render/renderer.h b/src/render/renderer.h index c78e1c1..101dda0 100644 --- a/src/render/renderer.h +++ b/src/render/renderer.h @@ -19,7 +19,7 @@ struct camu_frame_queue; struct camu_screen; struct camu_renderer { bool (*create_renderer)( - struct camu_renderer *, s32 *, s32 *, + struct camu_renderer *, u32 *, u32 *, #if defined CAMU_RENDERER_VULKAN VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL (*get_vk_proc_address)(VkInstance, const char *), VkResult (*vk_create_surface)(void *, VkInstance, VkSurfaceKHR *), @@ -39,13 +39,11 @@ struct camu_renderer { void *priv); struct camu_frame_queue *(*create_queue)(struct camu_renderer *); u32 (*get_latency)(struct camu_renderer *); - void (*resize)(struct camu_renderer *, s32 *, s32 *, bool); + void (*resize)(struct camu_renderer *, u32 *, u32 *); void (*render)(struct camu_renderer *, struct camu_screen *, bool); #ifdef CAMU_HAVE_FFMPEG s32 (*get_buffer2)(struct AVCodecContext *s, AVFrame *frame, s32 flags); void *opaque; #endif - void *(*get_device_ptr)(struct camu_renderer *, size_t); - void (*commit_device_ptr)(struct camu_renderer *); void (*free)(struct camu_renderer **); }; diff --git a/src/render/renderer_libplacebo.c b/src/render/renderer_libplacebo.c index 0ff0326..2c632ee 100644 --- a/src/render/renderer_libplacebo.c +++ b/src/render/renderer_libplacebo.c @@ -13,7 +13,17 @@ static f32 clear_color[4] = { 0.f, 0.f, 0.f, 1.f }; -static void renderer_lp_resize(struct camu_renderer *renderer, s32 *width, s32 *height, bool double_swap) +static inline void pl_swapchain_resize_compat(pl_swapchain swapchain, u32 *width, u32 *height) +{ + s32 req_width = (s32)*width; + s32 req_height = (s32)*height; + pl_swapchain_resize(swapchain, &req_width, &req_height); + al_assert(req_width > 0 && req_height > 0); + *width = (u32)req_width; + *height = (u32)req_height; +} + +static void renderer_lp_resize(struct camu_renderer *renderer, u32 *width, u32 *height) { struct camu_renderer_lp *lr = (struct camu_renderer_lp *)renderer; if (lr->swapchain) { @@ -21,8 +31,7 @@ static void renderer_lp_resize(struct camu_renderer *renderer, s32 *width, s32 * pl_swapchain_submit_frame(lr->swapchain); lr->have_frame = false; } - pl_swapchain_resize(lr->swapchain, width, height); - lr->double_swap = double_swap; + pl_swapchain_resize_compat(lr->swapchain, width, height); } } @@ -36,7 +45,7 @@ static void log_callback(void *userdata, enum pl_log_level level, const char *me } } -static bool renderer_lp_create_renderer(struct camu_renderer *renderer, s32 *width, s32 *height, +static bool renderer_lp_create_renderer(struct camu_renderer *renderer, u32 *width, u32 *height, #if defined CAMU_RENDERER_VULKAN VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL (*get_vk_proc_address)(VkInstance, const char *), VkResult (*vk_create_surface)(void *, VkInstance, VkSurfaceKHR *), @@ -144,9 +153,7 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, s32 *wid lr->renderer = pl_renderer_create(lr->logger, lr->gpu); - pl_swapchain_resize(lr->swapchain, width, height); - lr->double_swap = false; - + pl_swapchain_resize_compat(lr->swapchain, width, height); lr->have_frame = false; al_memset(&lr->params, 0, sizeof(struct pl_render_params)); @@ -220,11 +227,6 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree } #endif - if (lr->double_swap) { - pl_swapchain_swap_buffers(lr->swapchain); - lr->double_swap = false; - } - if (!lr->have_frame) { struct pl_swapchain_frame frame; pl_swapchain_start_frame(lr->swapchain, &frame); @@ -250,7 +252,10 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree do_gpu_finish |= weighted; // Terrible hack. Let's us distinguish single frames with the same dimensions. // Tied to a libplacebo patch to consider info_priv in the hash. - lr->params.info_priv = video->buf; + // Add in last_pts to account for buffer resets. This is a completely + // broken "hash" of the f64 pts value but hopefully works for now. + // https://www.virtualdub.org/blog2/entry_259.html + lr->params.info_priv = video->buf + (intptr_t)*((u64 *)&video->buf->last_pts); target->crop = mix.frames[0]->crop; target->crop.x1 *= video->view.zoom / video->view.stretch; target->crop.y1 *= video->view.zoom * video->view.stretch; @@ -281,25 +286,29 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree if (!scr->videos.count && !force) { // Don't spin too hard on a potential error state. - nn_thread_sleep(NNWT_TS_FROM_USEC(26)); + //al_log_warn("render_libplacebo", "Spinning in renderer."); + nn_thread_sleep(NNWT_TS_FROM_USEC(128)); return; } pl_swapchain_submit_frame(lr->swapchain); - pl_swapchain_swap_buffers(lr->swapchain); lr->have_frame = false; + pl_swapchain_swap_buffers(lr->swapchain); + if (do_gpu_finish) { // Block until render completes. pl_gpu_finish(lr->gpu); } - f64 tick = nn_get_tick(); - f64 frame_time = tick - lr->last_render_tick; - if (frame_time > (1.0 / 24.0)) { - al_log_info("render_libplacebo", "FRAME_TIME: %fs", frame_time); + if (!force) { + f64 tick = nn_get_tick(); + f64 frame_time = tick - lr->last_render_tick; + if (frame_time > (1.0 / 24.0)) { + al_log_info("render_libplacebo", "FRAME_TIME: %fs", frame_time); + } + lr->last_render_tick = tick; } - lr->last_render_tick = tick; } void renderer_lp_free(struct camu_renderer **renderer) diff --git a/src/render/renderer_libplacebo.h b/src/render/renderer_libplacebo.h index a4bd40e..38407bb 100644 --- a/src/render/renderer_libplacebo.h +++ b/src/render/renderer_libplacebo.h @@ -26,7 +26,6 @@ struct camu_renderer_lp { pl_gpu gpu; pl_log logger; pl_swapchain swapchain; - bool double_swap; struct pl_frame target; bool have_frame; pl_renderer renderer; diff --git a/src/render/renderer_tiger.c b/src/render/renderer_tiger.c index 74db594..ab6e85b 100644 --- a/src/render/renderer_tiger.c +++ b/src/render/renderer_tiger.c @@ -19,7 +19,7 @@ static const GLfloat default_vertices[] = { 1.f, 1.f, 0.f, 1.f, 1.f }; -static f32 clear_color[4] = { 0.0f, 0.0f, 0.0f, 1.f }; +static GLfloat clear_color[4] = { 0.f, 0.f, 0.f, 1.f }; static const GLchar *default_vs = \ "#version 100\n" \ @@ -86,7 +86,7 @@ static bool compile_shaders(struct camu_renderer_tiger *tr) } #endif -static void renderer_tiger_resize(struct camu_renderer *renderer, s32 *width, s32 *height) +static void renderer_tiger_resize(struct camu_renderer *renderer, u32 *width, u32 *height) { struct camu_renderer_tiger *tr = (struct camu_renderer_tiger *)renderer; (void)tr; @@ -94,12 +94,16 @@ static void renderer_tiger_resize(struct camu_renderer *renderer, s32 *width, s3 (void)height; } -static bool renderer_tiger_create_renderer(struct camu_renderer *renderer, s32 *width, s32 *height, +static bool renderer_tiger_create_renderer(struct camu_renderer *renderer, u32 *width, u32 *height, #if defined CAMU_RENDERER_VULKAN VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL (*get_vk_proc_address)(VkInstance, const char *), void (*vk_create_surface)(void *, VkInstance, VkSurfaceKHR *), const char *const *(*vk_get_extensions)(u32 *), #elif defined CAMU_RENDERER_OPENGL +#ifdef STELA_USE_EGL + EGLDisplay display, + EGLContext context, +#endif void (*get_gl_proc_address(char const *procname))(void), bool (*gl_load_loader)(void *), bool (*gl_make_current)(void *), @@ -109,7 +113,11 @@ static bool renderer_tiger_create_renderer(struct camu_renderer *renderer, s32 * void *priv) { struct camu_renderer_tiger *tr = (struct camu_renderer_tiger *)renderer; + (void)width; + (void)height; #ifdef CAMU_RENDERER_OPENGL + (void)display; + (void)context; (void)get_gl_proc_address; tr->load_loader = gl_load_loader; @@ -120,9 +128,9 @@ static bool renderer_tiger_create_renderer(struct camu_renderer *renderer, s32 * tr->make_current(tr->priv); - if (!tr->load_loader(tr->priv)) return false; - - tr->r.resize(&tr->r, width, height); + if (!tr->load_loader(tr->priv)) { + return false; + } clear_color[0] = ((global_color_palette.colors[0] >> 16) & 0xff) / 255.f; clear_color[1] = ((global_color_palette.colors[0] >> 8) & 0xff) / 255.f; @@ -145,7 +153,7 @@ static bool renderer_tiger_create_renderer(struct camu_renderer *renderer, s32 * glVertexAttribPointer(pos, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void *)0); if (stl_gl_check_error()) { al_log_error("render_tiger", "glVertexAttribPointer returned error, use GLES context."); - al_assert(false); + al_assert_and_return(false); } glEnableVertexAttribArray(pos); GLint texcoord = glGetAttribLocation(tr->program, "a_TexCoord"); @@ -216,11 +224,6 @@ static void set_uniform(struct camu_renderer_tiger *tr, const GLchar *name, #endif } -#ifdef CAMU_RENDERER_OPENGL -static mat4x4 model = { 0 }; -static GLuint texture0 = 0; -#endif - static void renderer_tiger_render(struct camu_renderer *renderer, struct camu_screen *scr, bool force) { #ifdef CAMU_RENDERER_OPENGL @@ -241,19 +244,23 @@ static void renderer_tiger_render(struct camu_renderer *renderer, struct camu_sc } #endif - if (scr->videos.size > 0) { + if (scr->videos.count > 0) { struct camu_screen_video *video = &al_array_last(scr->videos); GLuint texture_id; - if (camu_video_buffer_read(video->buf, &texture_id)) { + bool weighted; + if (camu_video_buffer_read(video->buf, &texture_id, &weighted)) { glBindTexture(GL_TEXTURE_2D, texture_id); + mat4x4 model; mat4x4_identity(model); mat4x4_ortho(model, 0.f, (f32)scr->width, (f32)scr->height, 0.f, -1.f, 1.f); mat4x4_translate_in_place(model, (f32)video->view.x_offset, (f32)video->view.y_offset, 0.f); mat4x4_scale_aniso(model, model, (f32)video->view.zoom, (f32)video->view.zoom, 1.f); mat4x4_scale_aniso(model, model, (f32)video->view.width, (f32)video->view.height, 1.f); set_uniform(tr, "g_ModelViewProjectionMatrix", TYPE_MAT4, model); + + GLuint texture0 = 0; set_uniform(tr, "g_Texture0", TYPE_SAMPLER2D, &texture0); glViewport(0, 0, scr->width, scr->height); @@ -267,6 +274,7 @@ static void renderer_tiger_render(struct camu_renderer *renderer, struct camu_sc tr->release_current(tr->priv); } +/* static void *renderer_tiger_get_device_ptr(struct camu_renderer *renderer, size_t length) { (void)renderer; @@ -289,6 +297,7 @@ static void renderer_tiger_commit_device_ptr(struct camu_renderer *renderer) //glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); #endif } +*/ static void renderer_tiger_free(struct camu_renderer **renderer) { @@ -307,8 +316,6 @@ struct camu_renderer *camu_renderer_tiger_create(void) tr->r.get_latency = renderer_tiger_get_latency; tr->r.resize = renderer_tiger_resize; tr->r.render = renderer_tiger_render; - tr->r.get_device_ptr = renderer_tiger_get_device_ptr; - tr->r.commit_device_ptr = renderer_tiger_commit_device_ptr; tr->r.free = renderer_tiger_free; return (struct camu_renderer *)tr; } diff --git a/src/screen/screen.c b/src/screen/screen.c index b4f65c2..cb49188 100644 --- a/src/screen/screen.c +++ b/src/screen/screen.c @@ -5,6 +5,8 @@ #include "view.h" #include "screen.h" +#define SCREEN_INVALID_TS ((u64)-1) + static struct camu_view *get_view_from_mouse_pos(struct camu_screen *scr) { struct camu_view *view = NULL; @@ -27,24 +29,20 @@ static void should_close_callback(void *userdata) scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, NULL); } -static void resize_callback(void *userdata, s32 width, s32 height) +static void resize_callback(void *userdata, u32 width, u32 height) { struct camu_screen *scr = (struct camu_screen *)userdata; - bool fullscreen = scr->window->fullscreen; if (scr->renderer) { - // Checking if window->fullscreen changed here relies on the order of resize events. - // If it's possible for a different resize to be buffered before the resize - // of the fullscreen toggle, this may not work as expected. - scr->renderer->resize(scr->renderer, &width, &height, fullscreen != scr->last_fullscreen); + scr->renderer->resize(scr->renderer, &width, &height); } - scr->width = width; - scr->height = height; - scr->last_fullscreen = fullscreen; + al_assert(width > 0 && height > 0); + scr->width = (u32)width; + scr->height = (u32)height; struct camu_screen_video *video; al_array_foreach_ptr(scr->videos, i, video) { camu_view_calculate(&video->view, scr->width, scr->height); } - scr->force_refresh = true; + al_atomic_store(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED); } static void refresh_callback(void *userdata) @@ -68,26 +66,25 @@ static bool pointer_pos_callback(void *userdata, f64 x, f64 y) struct camu_screen *scr = (struct camu_screen *)userdata; struct camu_view *view = get_view_from_mouse_pos(scr); bool queue_refresh = false; - if (view) { - if (scr->flags & CAMU_SCREEN_DRAGGING) { - if (!(scr->flags & CAMU_SCREEN_MODIFIER) && - (scr->flags & CAMU_SCREEN_ZOOM_PAN_SIMPLE)) { - f64 dx = x - scr->last_mouse_x; - f64 dy = y - scr->last_mouse_y; - if (camu_view_pan_simple(view, scr->width, scr->height, dx, dy)) { - view->mode = CAMU_VIEW_DETACHED; - queue_refresh = true; - } - if (fabs(dx) + fabs(dy) > 3.0) scr->last_click_ts = 0; - } else { - u64 now = nn_get_timestamp(); - if (!scr->last_click_ts) { - scr->last_click_ts = now; - } - if (now - scr->last_seek_ts > 100000) { - scr->last_seek_ts = now; - seek_to_percent_at_pointer(scr); - } + if (scr->flags & CAMU_SCREEN_DRAGGING) { + if (scr->flags & CAMU_SCREEN_MOD_CONTROL) { + u64 now = nn_get_timestamp(); + if (scr->last_seek_ts == SCREEN_INVALID_TS) { + scr->last_seek_ts = now; + } + if (now - scr->last_seek_ts > 100000) { + scr->last_seek_ts = now; + seek_to_percent_at_pointer(scr); + } + } else if (view && scr->flags & CAMU_SCREEN_ZOOM_PAN_SIMPLE) { + f64 dx = x - scr->last_mouse_x; + f64 dy = y - scr->last_mouse_y; + if (camu_view_pan_simple(view, scr->width, scr->height, dx, dy)) { + view->mode = CAMU_VIEW_DETACHED; + queue_refresh = true; + } + if (fabs(dx) + fabs(dy) > 3.0) { + scr->last_click_ts = SCREEN_INVALID_TS; } } } @@ -105,14 +102,17 @@ static bool mouse_button_callback(void *userdata, u8 state, u8 button) case STELA_BUTTON_PRESSED: scr->flags |= CAMU_SCREEN_DRAGGING; scr->last_click_ts = nn_get_timestamp(); + //scr->callback(scr->userdata, CAMU_SCREEN_TOGGLE_PAUSE, NULL); break; case STELA_BUTTON_RELEASED: scr->flags &= ~CAMU_SCREEN_DRAGGING; - if (scr->last_click_ts && nn_get_timestamp() - scr->last_click_ts <= 300000) { - if (scr->flags & CAMU_SCREEN_MODIFIER) { + //scr->callback(scr->userdata, CAMU_SCREEN_TOGGLE_PAUSE, NULL); + if (scr->last_click_ts != SCREEN_INVALID_TS && + (nn_get_timestamp() - scr->last_click_ts <= 300000)) { + if (scr->flags & CAMU_SCREEN_MOD_CONTROL) { seek_to_percent_at_pointer(scr); } else { - if (scr->last_mouse_x >= scr->width / 2.f) { + if (scr->last_mouse_x >= scr->width / 2.0) { scr->callback(scr->userdata, CAMU_SCREEN_NEXT, NULL); } else { scr->callback(scr->userdata, CAMU_SCREEN_PREVIOUS, NULL); @@ -125,10 +125,10 @@ static bool mouse_button_callback(void *userdata, u8 state, u8 button) case STELA_MOUSE2: switch (state) { case STELA_BUTTON_PRESSED: - scr->flags |= CAMU_SCREEN_MODIFIER; + scr->flags |= CAMU_SCREEN_MOD_CONTROL; break; case STELA_BUTTON_RELEASED: - scr->flags &= ~CAMU_SCREEN_MODIFIER; + scr->flags &= ~CAMU_SCREEN_MOD_CONTROL; break; } break; @@ -157,7 +157,7 @@ static bool scroll_callback(void *userdata, f64 y) { struct camu_screen *scr = (struct camu_screen *)userdata; y = -y / SCROLL_MULTIPLIER; - if (!(scr->flags & CAMU_SCREEN_MODIFIER)) { + if (!(scr->flags & CAMU_SCREEN_MOD_CONTROL)) { struct camu_view *view = get_view_from_mouse_pos(scr); if (view) { if (scr->flags & CAMU_SCREEN_ZOOM_PAN_SIMPLE) { @@ -180,8 +180,11 @@ static bool key_callback(void *userdata, u8 state, u8 button) case STELA_BUTTON_PRESSED: //al_printf("%x\n", button); switch (button) { + case 0x2a: // shift + scr->flags |= CAMU_SCREEN_MOD_SHIFT; + break; case 0x1d: // control - scr->flags |= CAMU_SCREEN_MODIFIER; + scr->flags |= CAMU_SCREEN_MOD_CONTROL; break; case 0x10: // q scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, NULL); @@ -215,9 +218,23 @@ static bool key_callback(void *userdata, u8 state, u8 button) case 0x13: { // r struct camu_view *view = get_view_from_mouse_pos(scr); if (view) { - if (!(scr->flags & CAMU_SCREEN_MODIFIER)) { - view->mode = CAMU_DEFAULT_VIEW; - } else { + if (scr->flags & CAMU_SCREEN_MOD_SHIFT) { + switch (view->rotation) { + case CAMU_VIEW_ROTATION_0: + case CAMU_VIEW_ROTATION_360: + view->rotation = CAMU_VIEW_ROTATION_90; + break; + case CAMU_VIEW_ROTATION_270: + view->rotation = CAMU_VIEW_ROTATION_0; + break; + case CAMU_VIEW_ROTATION_180: + view->rotation = CAMU_VIEW_ROTATION_270; + break; + case CAMU_VIEW_ROTATION_90: + view->rotation = CAMU_VIEW_ROTATION_180; + break; + } + } else if (scr->flags & CAMU_SCREEN_MOD_CONTROL) { switch (view->rotation) { case CAMU_VIEW_ROTATION_0: case CAMU_VIEW_ROTATION_360: @@ -233,6 +250,8 @@ static bool key_callback(void *userdata, u8 state, u8 button) view->rotation = CAMU_VIEW_ROTATION_0; break; } + } else { + view->mode = CAMU_DEFAULT_VIEW; } camu_view_calculate(view, scr->width, scr->height); return true; @@ -261,8 +280,11 @@ static bool key_callback(void *userdata, u8 state, u8 button) break; case STELA_BUTTON_RELEASED: switch (button) { + case 0x2a: // shift + scr->flags &= ~CAMU_SCREEN_MOD_SHIFT; + break; case 0x1d: // control - scr->flags &= ~CAMU_SCREEN_MODIFIER; + scr->flags &= ~CAMU_SCREEN_MOD_CONTROL; break; } default: @@ -279,6 +301,10 @@ static void key_immediate_callback(void *userdata, u8 state, u8 button) switch (button) { case 0x21: // f scr->window->toggle_fullscreen(scr->window); + // Least intrusive workaround I can think of for the egl resizing issue. + // Will only work for fullscreening, things like toggling floating state + // on sway are still broken. + al_atomic_store(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED); break; } break; @@ -301,10 +327,12 @@ bool camu_screen_init(struct camu_screen *scr, void *context) scr->window->should_close_callback = should_close_callback; scr->window->userdata = scr; scr->renderer = NULL; - scr->force_refresh = false; + al_atomic_store(u32)(&scr->force_refresh, 0, AL_ATOMIC_RELAXED); scr->flags = CAMU_SCREEN_ZOOM_PAN_SIMPLE; + scr->last_click_ts = SCREEN_INVALID_TS; scr->last_mouse_x = 0.0; scr->last_mouse_y = 0.0; + scr->last_seek_ts = SCREEN_INVALID_TS; al_array_init(scr->videos); #ifdef CAMU_SCREEN_THREADED al_array_init(scr->add_queue); @@ -334,7 +362,6 @@ bool camu_screen_create_window(struct camu_screen *scr, const char *name) } scr->width = scr->window->width; scr->height = scr->window->height; - scr->last_fullscreen = false; #ifdef STELA_EVENT_BUFFER nn_thread_create(&scr->thread, event_thread, scr); #endif @@ -365,9 +392,9 @@ bool camu_screen_create_renderer(struct camu_screen *scr, struct camu_renderer * static void add_buffer_internal(struct camu_screen *scr, struct camu_video_buffer *buf) { - struct camu_screen_video *rvideo; - al_array_foreach_ptr(scr->videos, i, rvideo) { - al_assert(rvideo->buf != buf); + struct camu_screen_video *added; + al_array_foreach_ptr(scr->videos, i, added) { + al_assert(added->buf != buf); } struct camu_screen_video video = { .buf = buf @@ -383,17 +410,17 @@ static void add_buffer_internal(struct camu_screen *scr, struct camu_video_buffe #endif } else { video.view.mode = CAMU_DEFAULT_VIEW; + video.view.rotation = CAMU_VIEW_ROTATION_0; video.view.width = fmt->width; video.view.height = fmt->height; - video.view.stretch = 1.0; - video.view.rotation = CAMU_VIEW_ROTATION_0; video.view.zindex = 0; + video.view.stretch = 1.0; } camu_view_calculate(&video.view, scr->width, scr->height); #ifdef CAMU_SCREEN_THREADED al_atomic_store(u8)(&buf->ref, 1, AL_ATOMIC_RELAXED); #endif - + //al_atomic_store(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED); al_array_push(scr->videos, video); } @@ -428,7 +455,6 @@ static void run_queue_internal(struct camu_screen *scr) add_buffer_internal(scr, buf); } scr->add_queue.count = 0; - //scr->force_refresh = scr->videos.count == 0; } #endif @@ -436,15 +462,15 @@ void camu_screen_add_buffer(struct camu_screen *scr, struct camu_video_buffer *b { #ifdef CAMU_SCREEN_THREADED nn_mutex_lock(&scr->mutex); - struct camu_video_buffer *rbuf; - al_array_foreach(scr->add_queue, i, rbuf) { - if (rbuf == buf) { + struct camu_video_buffer *queued; + al_array_foreach(scr->add_queue, i, queued) { + if (queued == buf) { nn_mutex_unlock(&scr->mutex); return; } } - al_array_foreach_rev(scr->rem_queue, i, rbuf) { - if (rbuf == buf) { + al_array_foreach_rev(scr->rem_queue, i, queued) { + if (queued == buf) { al_array_remove_at(scr->rem_queue, i); bool queue_empty = scr->add_queue.count + scr->rem_queue.count == 0; if (queue_empty) { @@ -467,15 +493,15 @@ void camu_screen_remove_buffer(struct camu_screen *scr, struct camu_video_buffer { #ifdef CAMU_SCREEN_THREADED nn_mutex_lock(&scr->mutex); - struct camu_video_buffer *rbuf; - al_array_foreach(scr->rem_queue, i, rbuf) { - if (rbuf == buf) { + struct camu_video_buffer *queued; + al_array_foreach(scr->rem_queue, i, queued) { + if (queued == buf) { nn_mutex_unlock(&scr->mutex); return; } } - al_array_foreach_rev(scr->add_queue, i, rbuf) { - if (rbuf == buf) { + al_array_foreach_rev(scr->add_queue, i, queued) { + if (queued == buf) { al_array_remove_at(scr->add_queue, i); bool queue_empty = scr->add_queue.count + scr->rem_queue.count == 0; if (queue_empty) { @@ -517,12 +543,13 @@ void camu_screen_clear(struct camu_screen *scr) void camu_screen_set_state(struct camu_screen *scr, s32 state) { + // @TODO: What to do if a buffer is queued for removal and we pause here. al_atomic_store(s32)(&scr->state, state, AL_ATOMIC_RELAXED); } void camu_screen_force_refresh(struct camu_screen *scr) { - scr->force_refresh = true; + al_atomic_store(u32)(&scr->force_refresh, 1, AL_ATOMIC_RELAXED); camu_screen_wake(scr); } @@ -552,9 +579,12 @@ bool camu_screen_tick(struct camu_screen *scr, bool *force) al_assert(state != CAMU_SCREEN_STOPPED); bool paused = state == CAMU_SCREEN_PAUSED; bool do_render = camu_screen_poll(scr, paused) || !paused; - do_render |= scr->force_refresh; - *force = scr->force_refresh; - scr->force_refresh = false; + u32 force_refresh = al_atomic_load(u32)(&scr->force_refresh, AL_ATOMIC_ACQUIRE); + do_render |= force_refresh > 0; + *force = force_refresh > 0; + if (force_refresh > 0) { + al_atomic_store(u32)(&scr->force_refresh, 0, AL_ATOMIC_RELEASE); + } return do_render; } diff --git a/src/screen/screen.h b/src/screen/screen.h index e0d1fc3..0b8ed33 100644 --- a/src/screen/screen.h +++ b/src/screen/screen.h @@ -18,9 +18,10 @@ #define CAMU_SCREEN_HEIGHT 700 enum { - CAMU_SCREEN_MODIFIER = 1, - CAMU_SCREEN_DRAGGING = 1 << 1, - CAMU_SCREEN_ZOOM_PAN_SIMPLE = 1 << 2 + CAMU_SCREEN_MOD_SHIFT = 1, + CAMU_SCREEN_MOD_CONTROL = 1 << 1, + CAMU_SCREEN_DRAGGING = 1 << 2, + CAMU_SCREEN_ZOOM_PAN_SIMPLE = 1 << 3 }; enum { @@ -55,11 +56,10 @@ struct camu_screen { struct nn_thread thread; #endif struct camu_renderer *renderer; - bool force_refresh; + atomic(u32) force_refresh; u32 flags; - s32 width; - s32 height; - bool last_fullscreen; + u32 width; + u32 height; u64 last_click_ts; f64 last_mouse_y; f64 last_mouse_x; diff --git a/src/screen/view.c b/src/screen/view.c index 0ca5f4c..b5fade2 100644 --- a/src/screen/view.c +++ b/src/screen/view.c @@ -15,34 +15,33 @@ #define VIEW_ON_ITS_SIDE(view) \ (view->rotation % CAMU_VIEW_ROTATION_180 == CAMU_VIEW_ROTATION_90) -void camu_view_calculate(struct camu_view *view, s32 window_width, s32 window_height) +void camu_view_calculate(struct camu_view *view, u32 window_width, u32 window_height) { - f64 width, height; + u32 width, height; if (VIEW_ON_ITS_SIDE(view)) { - width = (f64)view->height; - height = (f64)view->width; - view->stretch = height / width; + width = view->height; + height = view->width; + view->stretch = height / (f64)width; } else { - width = (f64)view->width; - height = (f64)view->height; + width = view->width; + height = view->height; view->stretch = 1.0; } - f64 frame_ratio = width / height; + f64 frame_ratio = width / (f64)height; f64 ratio = window_width / (f64)window_height; - view->ratio = ratio / frame_ratio; switch (view->mode) { case CAMU_VIEW_AUTOFIT: if (frame_ratio > ratio) { - view->zoom = window_width / width; + view->zoom = window_width / (f64)width; } else { - view->zoom = window_height / height; + view->zoom = window_height / (f64)height; } break; case CAMU_VIEW_ZOOM: if (frame_ratio < ratio) { - view->zoom = window_width / width; + view->zoom = window_width / (f64)width; } else { - view->zoom = window_height / height; + view->zoom = window_height / (f64)height; } break; default: @@ -52,7 +51,7 @@ void camu_view_calculate(struct camu_view *view, s32 window_width, s32 window_he view->y_offset = (window_height - (height * view->zoom)) / 2.0; } -bool camu_view_pan_simple(struct camu_view *view, s32 window_width, s32 window_height, f64 dx, f64 dy) +bool camu_view_pan_simple(struct camu_view *view, u32 window_width, u32 window_height, f64 dx, f64 dy) { f64 nx = view->x_offset + dx; f64 ny = view->y_offset + dy; @@ -68,7 +67,7 @@ bool camu_view_pan_simple(struct camu_view *view, s32 window_width, s32 window_h return view_changed; } -bool camu_view_zoom_simple(struct camu_view *view, s32 window_width, s32 window_height, f64 x, f64 y, f64 v) +bool camu_view_zoom_simple(struct camu_view *view, u32 window_width, u32 window_height, f64 x, f64 y, f64 v) { x -= view->x_offset; y -= view->y_offset; diff --git a/src/screen/view.h b/src/screen/view.h index 8cd3d28..30b6f64 100644 --- a/src/screen/view.h +++ b/src/screen/view.h @@ -21,17 +21,16 @@ enum { struct camu_view { u8 mode; - s32 width; - s32 height; - f64 ratio; + u8 rotation; + u32 width; + u32 height; + s32 zindex; f64 zoom; f64 x_offset; f64 y_offset; f64 stretch; - s32 rotation; - s32 zindex; }; -void camu_view_calculate(struct camu_view *view, s32 window_width, s32 window_height); -bool camu_view_pan_simple(struct camu_view *view, s32 window_width, s32 window_height, f64 dx, f64 dy); -bool camu_view_zoom_simple(struct camu_view *view, s32 window_width, s32 window_height, f64 x, f64 y, f64 v); +void camu_view_calculate(struct camu_view *view, u32 window_width, u32 window_height); +bool camu_view_pan_simple(struct camu_view *view, u32 window_width, u32 window_height, f64 dx, f64 dy); +bool camu_view_zoom_simple(struct camu_view *view, u32 window_width, u32 window_height, f64 x, f64 y, f64 v); diff --git a/src/server/db.c b/src/server/db.c index e2ad808..3d44e11 100644 --- a/src/server/db.c +++ b/src/server/db.c @@ -1,10 +1,11 @@ #include <al/log.h> #include <nnwt/file.h> -#include <jansson.h> #include "server.h" +#if 0 // This is still nothing. +#include <jansson.h> static void open_user(struct camu_server *server, struct nn_dir *camu_db, struct nn_dir_entry *dir) { @@ -33,6 +34,13 @@ static void open_user(struct camu_server *server, struct nn_dir *camu_db, struct out: al_str_free(&path); } +#endif +static void open_user(struct camu_server *server, struct nn_dir *camu_db, struct nn_dir_entry *dir) +{ + (void)server; + (void)camu_db; + (void)dir; +} bool camu_db_open(struct camu_server *server, str *path) { diff --git a/src/server/server.c b/src/server/server.c index fcacf1d..e22cf88 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -268,7 +268,6 @@ static void send_clients_entry_seeked(struct camu_server *server, struct lia_lis static void list_callback(void *userdata, u8 op, struct lia_list_entry *entry, void *opaque) { struct camu_server *server = (struct camu_server *)userdata; - (void)server; struct camu_resource *resource = (struct camu_resource *)entry->opaque; switch (op) { case LIANA_LOAD_ENTRY: @@ -295,6 +294,9 @@ static void list_callback(void *userdata, u8 op, struct lia_list_entry *entry, v lia_node_close(resource->node); break; case LIANA_LIST_META: + if (server->meta_callback) { + server->meta_callback(server->userdata, *(u8 *)opaque, entry); + } switch (*(u8 *)opaque) { case LIANA_META_CURRENT_CHANGED: al_log_info("server", "Now playing: %ls.", entry->name.data); diff --git a/src/server/server.h b/src/server/server.h index 210f8a4..db7d30c 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -45,6 +45,8 @@ struct camu_server { struct camu_portal_bridge bridge; struct camu_post_cache cache; #endif + void (*meta_callback)(void *, u8, struct lia_list_entry *entry); // @TEMP + void *userdata; }; void camu_server_init(struct camu_server *server, struct nn_event_loop *loop); |