diff options
| author | 2024-11-17 15:26:17 -0500 | |
|---|---|---|
| committer | 2024-11-17 15:26:17 -0500 | |
| commit | 588b6d8bfa1b2efdebf8c424c86f6069cceafa41 (patch) | |
| tree | 545308813e9ae19d3293b78c7cb39555a64c0abe /src | |
| parent | 58d38d767442c83e31ae6dbaea7b1fc8ccb00633 (diff) | |
| download | camu-588b6d8bfa1b2efdebf8c424c86f6069cceafa41.tar.gz camu-588b6d8bfa1b2efdebf8c424c86f6069cceafa41.tar.bz2 camu-588b6d8bfa1b2efdebf8c424c86f6069cceafa41.zip | |
Rough subtitles implementation, improve buffers
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src')
37 files changed, 722 insertions, 280 deletions
diff --git a/src/buffer/audio.c b/src/buffer/audio.c index 3abae42..5058154 100644 --- a/src/buffer/audio.c +++ b/src/buffer/audio.c @@ -9,9 +9,9 @@ #include "common_internal.h" #include "volume.h" -#define BUFFER_SIZE (9 * 1000000L) -#define BUFFER_MARK_MIN (4.3 * 1000000L) // Must be a most half of the buffer size. -#define BUFFER_MARK_BUFFERED (4.0 * 1000000L) +#define BUFFER_SIZE 9.0 +#define BUFFER_MARK_MIN 4.3 // Must be a most half of the buffer size. +#define BUFFER_MARK_BUFFERED 4.0 #ifdef CAMU_AUDIO_BUFFER_FADE #define FADE_STEP(fmt) (1.f / (fmt)->sample_rate) @@ -27,7 +27,7 @@ enum { static void reset_buffer_state(struct camu_audio_buffer *buf) { - buf->pts = -1.0; + 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); @@ -40,11 +40,9 @@ static void reset_buffer_state(struct camu_audio_buffer *buf) al_atomic_store(size_t)(&buf->uncork_at, 0, AL_ATOMIC_RELAXED); } -bool camu_audio_buffer_init(struct camu_audio_buffer *buf, struct camu_clock *clock, - struct camu_mixer *mixer) +bool camu_audio_buffer_init(struct camu_audio_buffer *buf, struct camu_clock *clock) { buf->clock = clock; - buf->mixer = mixer; reset_buffer_state(buf); buf->ignore_desync = false; buf->latency = 0.0; @@ -54,10 +52,11 @@ bool camu_audio_buffer_init(struct camu_audio_buffer *buf, struct camu_clock *cl return true; } -bool camu_audio_buffer_configure(struct camu_audio_buffer *buf, struct camu_codec_stream *stream) +bool camu_audio_buffer_configure(struct camu_audio_buffer *buf, struct camu_codec_stream *stream, + struct camu_mixer *mixer) { camu_audio_format_copy(&buf->fmt.in, &stream->audio.fmt); - camu_mixer_pick_format(buf->mixer, &buf->fmt); + camu_mixer_pick_format(mixer, &buf->fmt); buf->fmt.resampler_needed = !camu_resampler_format_matches(&buf->fmt); if (buf->fmt.resampler_needed) { #ifdef CAMU_HAVE_FFMPEG @@ -76,12 +75,12 @@ bool camu_audio_buffer_configure(struct camu_audio_buffer *buf, struct camu_code 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); - buf->size = camu_audio_format_usec_to_bytes(&buf->fmt.req, BUFFER_SIZE); + buf->size = camu_audio_format_sec_to_bytes(&buf->fmt.req, BUFFER_SIZE); buf->data = (u8 *)al_malloc(buf->size); al_ring_buffer_init(&buf->rb, buf->data, buf->size); - buf->mark.min = camu_audio_format_usec_to_bytes(&buf->fmt.req, BUFFER_MARK_MIN); - buf->mark.buffered = camu_audio_format_usec_to_bytes(&buf->fmt.req, BUFFER_MARK_BUFFERED); + buf->mark.min = camu_audio_format_sec_to_bytes(&buf->fmt.req, BUFFER_MARK_MIN); + buf->mark.buffered = camu_audio_format_sec_to_bytes(&buf->fmt.req, BUFFER_MARK_BUFFERED); camu_peak_buffer_init(&buf->peak, 1024 * 16); buf->stream = stream; @@ -144,6 +143,16 @@ static bool push_internal(struct camu_audio_buffer *buf, u8 *data, s32 sample_co return true; } +static bool frame_is_late(struct camu_audio_buffer *buf, f64 pts, f64 duration) +{ + f64 base = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); + if (base == -1.0) { + base = camu_clock_get_base_pts(buf->clock); + al_atomic_store(f64)(&buf->pts, base, AL_ATOMIC_RELEASE); + } + return pts + duration < base; +} + #ifdef CAMU_HAVE_FFMPEG static void push_av_frame_internal(struct camu_audio_buffer *buf, AVFrame *frame) { @@ -151,8 +160,7 @@ static void push_av_frame_internal(struct camu_audio_buffer *buf, AVFrame *frame AVStream *stream = buf->stream->av.stream; f64 pts = frame->best_effort_timestamp * av_q2d(stream->time_base); f64 duration = camu_audio_format_samples_to_sec(&buf->fmt.in, sample_count); - if (pts + duration >= camu_clock_get_base_pts(buf->clock)) { - if (buf->pts == -1.0) buf->pts = pts; + if (!frame_is_late(buf, pts, duration)) { u8 **data = frame->data; if (buf->fmt.resampler_needed) { sample_count = buf->resamp->convert(buf->resamp, (const u8 **)data, sample_count); @@ -173,9 +181,8 @@ void camu_audio_buffer_push(struct camu_audio_buffer *buf, struct camu_codec_fra s32 sample_count = frame->audio.sample_count; f64 pts = frame->pts; f64 duration = camu_audio_format_samples_to_sec(&buf->fmt.in, sample_count); - if (pts + duration >= camu_clock_get_base_pts(buf->clock)) { - if (buf->pts == -1.0) buf->pts = pts; - u8 *store[AV_NUM_DATA_POINTERS] = { 0 }; + if (!frame_is_late(buf, pts, duration)) { + u8 *store[CAMU_NUM_DATA_POINTERS] = { 0 }; store[0] = frame->data; u8 **data = store; if (buf->fmt.resampler_needed) { @@ -201,17 +208,6 @@ void camu_audio_buffer_unpause(struct camu_audio_buffer *buf) al_atomic_add(s32)(&buf->unpause, 1, AL_ATOMIC_RELAXED); } -// Not thread-safe, must be called while the buffer is not being read from or written to. -void camu_audio_buffer_reset(struct camu_audio_buffer *buf) -{ - reset_buffer_state(buf); - al_ring_buffer_reset(&buf->rb); - camu_peak_buffer_flush(&buf->peak); - if (buf->fmt.resampler_needed) { - buf->resamp->flush(buf->resamp); - } -} - // flush() always comes from the same thread as push(). void camu_audio_buffer_flush(struct camu_audio_buffer *buf) { @@ -233,18 +229,29 @@ void camu_audio_buffer_flush(struct camu_audio_buffer *buf) al_log_debug("audio_buffer", "Flush requested."); } +// Not thread-safe, must be called while the buffer is not being read from or written to. +void camu_audio_buffer_reset(struct camu_audio_buffer *buf) +{ + reset_buffer_state(buf); + al_ring_buffer_reset(&buf->rb); + camu_peak_buffer_flush(&buf->peak); + if (buf->fmt.resampler_needed) { + buf->resamp->flush(buf->resamp); + } +} + +static void increment_pts(struct camu_audio_buffer *buf, f64 amount, f64 *base) +{ + *base += amount; + al_atomic_store(f64)(&buf->pts, *base, AL_ATOMIC_RELEASE); +} + #define NOT_PAUSED(pause) (pause != PAUSE_PAUSED) // PAUSE_PAUSED signifies that the last read was silence. Meaning we can skip // around in the buffer without worrying about pops. size_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, size_t req) { - u8 flow = al_atomic_load(u8)(&buf->flow, AL_ATOMIC_ACQUIRE); - if (flow == SIGNALED || camu_clock_is_ended(buf->clock)) { - buf->callback(buf->userdata, CAMU_BUFFER_EOF); - return 0; - } - 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 @@ -287,6 +294,7 @@ size_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, size_t re al_atomic_sub(s32)(&buf->unpause, 1, AL_ATOMIC_RELEASE); } + f64 base = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); f64 pts = camu_clock_get_pts(buf->clock, buf->latency); size_t ret, signal = req; size_t have = al_ring_buffer_occupied(&buf->rb); @@ -296,7 +304,7 @@ size_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, size_t re else have -= buf->fade.offset; if (buf->pause != PAUSE_FADING && buf->pause != PAUSE_PLAYING) { #endif - pts -= buf->pts; + pts -= base; // Attempt syncing to the clock. // For this to work the mixer must report a reasonably accurate value for latency. if (UNLIKELY(!buf->ignore_desync && buf->pause == PAUSE_PAUSED)) { @@ -306,7 +314,7 @@ size_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, size_t re al_log_info("audio_buffer", "Skipping %fs of audio (%zu bytes).", pts, ret); ret = al_ring_buffer_discard(&buf->rb, ret); have -= ret; - buf->pts += camu_audio_format_bytes_to_sec(&buf->fmt.req, ret); + increment_pts(buf, camu_audio_format_bytes_to_sec(&buf->fmt.req, ret), &base); // Could go on to underrun. } else if (pts < 0.0) { // Delay. pts = -pts; @@ -325,6 +333,7 @@ size_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, size_t re } #endif + u8 flow = al_atomic_load(u8)(&buf->flow, AL_ATOMIC_ACQUIRE); if (have < req) { // We don't have enough data to fulfill our request. if (flow == FLUSHED) { // Stream is flushed. // Check peak buffer for any remaining data. @@ -375,7 +384,7 @@ size_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, size_t re // Read as much as we determined we can. ret = al_ring_buffer_read(&buf->rb, data, req); al_assert(ret == req); - buf->pts += camu_audio_format_bytes_to_sec(&buf->fmt.req, ret); + increment_pts(buf, camu_audio_format_bytes_to_sec(&buf->fmt.req, ret), &base); #ifdef CAMU_AUDIO_BUFFER_FADE } f32 step = 0.f; @@ -386,6 +395,7 @@ size_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, size_t re } if (step != 0.f || buf->fade.volume != 1.f) { if (buf->volume.user > 1.f) { + // Only adjust step if it's an increase. step *= buf->volume.user; } buf->fade.volume = apply_volume(data, req, &buf->fmt.req, buf->fade.volume, buf->volume.user, step); diff --git a/src/buffer/audio.h b/src/buffer/audio.h index 1d49448..b4d7594 100644 --- a/src/buffer/audio.h +++ b/src/buffer/audio.h @@ -13,9 +13,8 @@ struct camu_audio_buffer { struct camu_codec_stream *stream; - struct camu_mixer *mixer; - f64 pts; + atomic(f64) pts; u8 pause; atomic(s32) unpause; struct camu_clock *clock; @@ -57,13 +56,14 @@ struct camu_audio_buffer { void *userdata; }; -bool camu_audio_buffer_init(struct camu_audio_buffer *buf, struct camu_clock *clock, struct camu_mixer *mixer); -bool camu_audio_buffer_configure(struct camu_audio_buffer *buf, struct camu_codec_stream *stream); +bool camu_audio_buffer_init(struct camu_audio_buffer *buf, struct camu_clock *clock); +bool camu_audio_buffer_configure(struct camu_audio_buffer *buf, struct camu_codec_stream *stream, + struct camu_mixer *mixer); void camu_audio_buffer_set_volume(struct camu_audio_buffer *buf, f32 volume); void camu_audio_buffer_set_latency(struct camu_audio_buffer *buf, f64 latency); void camu_audio_buffer_push(struct camu_audio_buffer *buf, struct camu_codec_frame *frame); void camu_audio_buffer_unpause(struct camu_audio_buffer *buf); -void camu_audio_buffer_reset(struct camu_audio_buffer *buf); void camu_audio_buffer_flush(struct camu_audio_buffer *buf); +void camu_audio_buffer_reset(struct camu_audio_buffer *buf); size_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, size_t req); void camu_audio_buffer_free(struct camu_audio_buffer *buf); diff --git a/src/buffer/frame_queue.h b/src/buffer/frame_queue.h index 0175668..f670ded 100644 --- a/src/buffer/frame_queue.h +++ b/src/buffer/frame_queue.h @@ -5,6 +5,7 @@ #endif #include "../codec/codec.h" +#include "../buffer/video.h" enum { CAMU_QUEUE_OK = 0, @@ -14,9 +15,12 @@ enum { }; struct camu_frame_queue { + struct camu_video_buffer *buf; void (*push)(struct camu_frame_queue *, struct camu_codec_frame *, f64); #ifdef CAMU_HAVE_FFMPEG void (*push_av_frame)(struct camu_frame_queue *, AVFrame *, f64); + void (*configure_subtitles)(struct camu_frame_queue *, s32, s32, AVCodecParameters *); + void (*push_subtitle)(struct camu_frame_queue *, AVPacket *); #endif void (*flush)(struct camu_frame_queue *); s32 (*count)(struct camu_frame_queue *); diff --git a/src/buffer/video.c b/src/buffer/video.c index a8b35c6..b528141 100644 --- a/src/buffer/video.c +++ b/src/buffer/video.c @@ -15,14 +15,15 @@ #define BUFFER_MARK_HIGH ((1.0 / 30.0) * 12) #define BUFFER_MARK_RESET (BUFFER_MARK_HIGH * 2.0) -bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *clock, struct camu_renderer *renderer) +bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *clock) { buf->clock = clock; buf->latency = 0.0; - buf->pts = -1.0; + al_atomic_store(f64)(&buf->pts, -1.0, AL_ATOMIC_RELAXED); // Defaulting single_frame to true can simplify non-configured buffers in sink. buf->single_frame = true; - buf->queue = renderer->create_queue(renderer); + buf->avg_frame_duration = 0.0; + buf->queue = NULL; buf->buffered = false; al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED); #ifdef CAMU_SCREEN_THREADED @@ -32,27 +33,23 @@ bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *cl return true; } -bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_codec_stream *stream) +bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_codec_stream *stream, + struct camu_renderer *renderer) { buf->stream = stream; + buf->queue = renderer->create_queue(renderer); + buf->queue->buf = buf; struct camu_video_format *fmt = &buf->stream->video.fmt; switch (stream->mode) { case CAMU_NORMAL: { camu_video_format_copy(&buf->fmt.in, fmt); - - buf->single_frame = true; - buf->avg_frame_duration = 0.0; - const char *format_name = camu_pixel_format_name(fmt->format); - al_log_info("video_buffer", "Stream: %s (%dx%d) %s.", - format_name, fmt->width, fmt->height, "IMAGE"); - + al_log_info("video_buffer", "Stream: %s (%dx%d) %s.", format_name, fmt->width, fmt->height, "IMAGE"); break; } #ifdef CAMU_HAVE_FFMPEG case CAMU_FFMPEG_COMPAT: { AVCodecParameters *codecpar = stream->av.stream->codecpar; - s64 duration = stream->av.stream->duration; AVRational frame_rate = stream->av.stream->avg_frame_rate; fmt->width = codecpar->width; @@ -60,7 +57,7 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_code fmt->format = codecpar->format; camu_video_format_copy(&buf->fmt.in, fmt); - buf->single_frame = duration == 0 || frame_rate.den == 0; + buf->single_frame = stream->duration == 0 || frame_rate.den == 0; if (buf->single_frame) frame_rate = (AVRational){ 0, 1 }; buf->avg_frame_duration = buf->single_frame ? 0.0 : av_q2d(av_inv_q(frame_rate)); @@ -129,25 +126,33 @@ static void after_push_internal(struct camu_video_buffer *buf) static bool push_av_frame_internal(struct camu_video_buffer *buf, AVFrame *frame) { f64 pts = 0.0; + f64 base = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); if (!buf->single_frame) { AVStream *stream = buf->stream->av.stream; pts = frame->best_effort_timestamp * av_q2d(stream->time_base); - if (pts + buf->avg_frame_duration < camu_clock_get_base_pts(buf->clock)) { + // If buf->pts = -1.0, this is the first frame pushed either on + // a new stream or after a reset(). Meaning, we can safely set buf->pts + // from this thread. + if (base == -1.0) { + base = camu_clock_get_base_pts(buf->clock); + al_atomic_store(f64)(&buf->pts, base, AL_ATOMIC_RELEASE); + } + if (pts + buf->avg_frame_duration < base) { av_frame_free(&frame); return false; } + } else if (base == -1.0) { + al_atomic_store(f64)(&buf->pts, 0.0, AL_ATOMIC_RELEASE); } - if (buf->pts == -1.0) buf->pts = pts; #ifdef CAMU_VIDEO_BUFFER_FORCE_SCALER if (buf->fmt.scaler_needed) { if (!buf->scale->scale(buf->scale, (const u8 **)frame->data, frame->linesize)) { return false; } av_frame_free(&frame); - frame = av_frame_clone(buf->scale->get_frame(buf->scale)); + frame = av_frame_clone(buf->scale->get_frame(buf->scale)->av.frame); } #endif - if (frame) frame->opaque = buf; buf->queue->push_av_frame(buf->queue, frame, pts); return true; } @@ -161,9 +166,14 @@ void camu_video_buffer_push(struct camu_video_buffer *buf, struct camu_codec_fra return; } switch (frame->mode) { - case CAMU_NORMAL: + case CAMU_NORMAL: { + f64 base = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); + if (base == -1.0) { + al_atomic_store(f64)(&buf->pts, 0.0, AL_ATOMIC_RELEASE); + } buf->queue->push(buf->queue, frame, 0.0); break; + } #ifdef CAMU_HAVE_FFMPEG case CAMU_FFMPEG_COMPAT: { bool pushed = push_av_frame_internal(buf, frame->av.frame); @@ -176,15 +186,19 @@ void camu_video_buffer_push(struct camu_video_buffer *buf, struct camu_codec_fra after_push_internal(buf); } -// Not thread-safe, must be called while the buffer is not being read from or written to. -void camu_video_buffer_reset(struct camu_video_buffer *buf) +#ifdef CAMU_HAVE_FFMPEG +void camu_video_buffer_configure_subtitles(struct camu_video_buffer *buf, AVCodecParameters *codecpar) { - buf->pts = -1.0; - buf->queue->reset(buf->queue); - buf->buffered = false; - al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED); + struct camu_video_format *fmt = &buf->stream->video.fmt; + buf->queue->configure_subtitles(buf->queue, fmt->width, fmt->height, codecpar); } +void camu_video_buffer_push_subtitle(struct camu_video_buffer *buf, AVPacket *pkt) +{ + buf->queue->push_subtitle(buf->queue, pkt); +} +#endif + // flush() always comes from the same thread as push(). void camu_video_buffer_flush(struct camu_video_buffer *buf) { @@ -198,18 +212,28 @@ void camu_video_buffer_flush(struct camu_video_buffer *buf) 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. +void camu_video_buffer_reset(struct camu_video_buffer *buf) +{ + al_atomic_store(f64)(&buf->pts, -1.0, AL_ATOMIC_RELAXED); + if (buf->queue) buf->queue->reset(buf->queue); + buf->buffered = false; + al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED); +} + bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out) { - u8 flow = al_atomic_load(u8)(&buf->flow, AL_ATOMIC_ACQUIRE); + f64 base = 0.0; if (!buf->single_frame) { - if (flow == SIGNALED || camu_clock_is_ended(buf->clock)) { - buf->callback(buf->userdata, CAMU_BUFFER_EOF); - return false; - } + base = al_atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); f64 pts = camu_clock_get_pts(buf->clock, buf->latency); - if (pts > buf->pts) buf->pts = pts; + if (pts > base) { + base = pts; + al_atomic_store(f64)(&buf->pts, base, AL_ATOMIC_RELEASE); + } } - u8 ret = buf->queue->read(buf->queue, buf->pts, out); + u8 ret = buf->queue->read(buf->queue, base, out); + u8 flow = al_atomic_load(u8)(&buf->flow, AL_ATOMIC_ACQUIRE); if (flow == FLUSHED && (ret == CAMU_QUEUE_EOF || (buf->single_frame && ret == CAMU_QUEUE_OK))) { buf->callback(buf->userdata, CAMU_BUFFER_EOF); al_atomic_store(u8)(&buf->flow, SIGNALED, AL_ATOMIC_RELEASE); @@ -228,5 +252,5 @@ void camu_video_buffer_free(struct camu_video_buffer *buf) #ifdef CAMU_VIDEO_BUFFER_FORCE_SCALER if (buf->fmt.scaler_needed) buf->scale->free(&buf->scale); #endif - buf->queue->free(&buf->queue); + if (buf->queue) buf->queue->free(&buf->queue); } diff --git a/src/buffer/video.h b/src/buffer/video.h index 0b48361..fd7ec9f 100644 --- a/src/buffer/video.h +++ b/src/buffer/video.h @@ -6,7 +6,6 @@ #include "../codec/codec.h" #include "../render/renderer.h" -#include "../screen/screen.h" #include "../screen/view.h" #include "clock.h" @@ -17,7 +16,7 @@ struct camu_video_buffer { struct camu_clock *clock; f64 latency; - f64 pts; + atomic(f64) pts; bool single_frame; f64 avg_frame_duration; @@ -42,12 +41,17 @@ struct camu_video_buffer { void *userdata; }; -bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *clock, struct camu_renderer *renderer); -bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_codec_stream *stream); +bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *clock); +bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_codec_stream *stream, + struct camu_renderer *renderer); void camu_video_buffer_set_latency(struct camu_video_buffer *buf, s32 frames); bool camu_video_buffer_is_single_frame(struct camu_video_buffer *buf); void camu_video_buffer_push(struct camu_video_buffer *buf, struct camu_codec_frame *frame); -void camu_video_buffer_reset(struct camu_video_buffer *buf); +#ifdef CAMU_HAVE_FFMPEG +void camu_video_buffer_configure_subtitles(struct camu_video_buffer *buf, AVCodecParameters *codecpar); +void camu_video_buffer_push_subtitle(struct camu_video_buffer *buf, AVPacket *pkt); +#endif void camu_video_buffer_flush(struct camu_video_buffer *buf); +void camu_video_buffer_reset(struct camu_video_buffer *buf); bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out); void camu_video_buffer_free(struct camu_video_buffer *buf); diff --git a/src/codec/codec.h b/src/codec/codec.h index 6fd42e9..4b93673 100644 --- a/src/codec/codec.h +++ b/src/codec/codec.h @@ -29,7 +29,8 @@ enum { CAMU_STREAM_UNKNOWN = AVMEDIA_TYPE_UNKNOWN, CAMU_STREAM_AUDIO = AVMEDIA_TYPE_AUDIO, CAMU_STREAM_VIDEO = AVMEDIA_TYPE_VIDEO, - CAMU_STREAM_SUBTITLE = AVMEDIA_TYPE_SUBTITLE + CAMU_STREAM_SUBTITLE = AVMEDIA_TYPE_SUBTITLE, + CAMU_STREAM_ATTACHMENT = AVMEDIA_TYPE_ATTACHMENT }; enum { CAMU_SAMPLE_FORMAT_NONE = AV_SAMPLE_FMT_NONE, @@ -52,6 +53,7 @@ enum { CAMU_PIXEL_FORMAT_GREYA = AV_PIX_FMT_GRAY8A, CAMU_PIXEL_FORMAT_GREY = AV_PIX_FMT_GRAY8 }; +#define CAMU_NUM_DATA_POINTERS AV_NUM_DATA_POINTERS #else enum { CAMU_OK = 0, @@ -66,7 +68,8 @@ enum { CAMU_STREAM_UNKNOWN = 0, CAMU_STREAM_AUDIO, CAMU_STREAM_VIDEO, - CAMU_STREAM_SUBTITLE + CAMU_STREAM_SUBTITLE, + CAMU_STREAM_ATTACHMENT }; enum { CAMU_SAMPLE_FORMAT_NONE = -1, @@ -89,6 +92,7 @@ enum { CAMU_PIXEL_FORMAT_GREYA, CAMU_PIXEL_FORMAT_GREY }; +#define CAMU_NUM_DATA_POINTERS 8 #endif enum { @@ -204,7 +208,7 @@ struct camu_scaler_format { struct camu_scaler { bool (*init)(struct camu_scaler *, struct camu_scaler_format *); bool (*scale)(struct camu_scaler *, const u8 **in_slice, s32 *in_strides); - AVFrame *(*get_frame)(struct camu_scaler *); + struct camu_codec_frame *(*get_frame)(struct camu_scaler *); void (*free)(struct camu_scaler **); }; @@ -276,7 +280,7 @@ static inline f64 camu_audio_format_samples_to_sec(struct camu_audio_format *fmt return samples / (f64)fmt->sample_rate; } -static inline size_t camu_audio_format_usec_to_bytes(struct camu_audio_format *fmt, size_t usec) +static inline size_t camu_audio_format_usec_to_bytes(struct camu_audio_format *fmt, u64 usec) { // Keep order of operations for precision. size_t samples = usec * (fmt->sample_rate / 1000000.0); @@ -289,6 +293,12 @@ static inline size_t camu_audio_format_sec_to_bytes(struct camu_audio_format *fm return samples * camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count; } +static inline u64 camu_audio_format_bytes_to_usec(struct camu_audio_format *fmt, size_t bytes) +{ + size_t samples = bytes / (camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count); + return samples / (fmt->sample_rate / 1000000.0); +} + static inline f64 camu_audio_format_bytes_to_sec(struct camu_audio_format *fmt, size_t bytes) { size_t samples = bytes / (camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count); diff --git a/src/codec/ffmpeg/decoder.c b/src/codec/ffmpeg/decoder.c index d3ab3b2..0fb60ac 100644 --- a/src/codec/ffmpeg/decoder.c +++ b/src/codec/ffmpeg/decoder.c @@ -37,10 +37,11 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend } s32 cpus = av_cpu_count(); - if (cpus > 4) cpus = 2; + if (cpus >= 8) cpus = 4; av->codec_context->thread_count = cpus / 2; // FF_THREAD_FRAME or FF_THREAD_SLICE. - av->codec_context->thread_type = FF_THREAD_SLICE; + //av->codec_context->thread_type = FF_THREAD_SLICE; + av->codec_context->thread_type = FF_THREAD_FRAME; al_log_debug("ff_decoder", "Using %i threads for decoder.", cpus); #if !defined CAMU_SINK_NO_VIDEO && defined CAMU_RENDERER_VULKAN @@ -61,8 +62,8 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend s64 kbps = codecpar->bit_rate > 0 ? codecpar->bit_rate / 1000L : 0L; al_log_info("ff_decoder", "Codec: %s (%s) %ldkbps.", codec->name, long_name, kbps); - av->time_base = stream->av.stream->time_base; - av->duration = stream->av.stream->duration; + //av->duration = stream->duration; + //av->time_base = stream->av.stream->time_base; //av->last_pts = 0; //av->last_duration = 0; //av->seek_pos = -1; diff --git a/src/codec/ffmpeg/decoder.h b/src/codec/ffmpeg/decoder.h index 88bbd4c..2e35bb1 100644 --- a/src/codec/ffmpeg/decoder.h +++ b/src/codec/ffmpeg/decoder.h @@ -8,8 +8,8 @@ struct camu_ff_decoder { struct camu_decoder dec; AVCodecContext *codec_context; - AVRational time_base; - u64 duration; + //AVRational time_base; + //u64 duration; //s64 last_pts; //s64 last_duration; //s64 seek_pos; diff --git a/src/codec/ffmpeg/demuxer.c b/src/codec/ffmpeg/demuxer.c index 8ea645b..24c1ba6 100644 --- a/src/codec/ffmpeg/demuxer.c +++ b/src/codec/ffmpeg/demuxer.c @@ -77,28 +77,31 @@ static bool ff_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handl for (u32 i = 0; i < av->format_context->nb_streams; i++) { AVStream *stream = av->format_context->streams[i]; enum AVMediaType type = stream->codecpar->codec_type; - if (type != AVMEDIA_TYPE_AUDIO && type != AVMEDIA_TYPE_VIDEO) type = AVMEDIA_TYPE_UNKNOWN; - // Try to detect attached images. - if (type == AVMEDIA_TYPE_VIDEO && GUESS_STREAM_IS_IMAGE(stream)) { - al_log_info("ff_demuxer", "Guessing that stream #%u is an image.", i); - stream->duration = 0; - } else if (stream->duration < 0) { - al_log_warn("ff_demuxer", "Stream #%u has an invalid duration (%ld).", i, stream->duration); - if (av->format_context->duration < 0) { - stream->duration = 0; + u64 duration = 0; + if (type == AVMEDIA_TYPE_AUDIO || type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_SUBTITLE) { + // Try to detect attached images. + if (type == AVMEDIA_TYPE_VIDEO && GUESS_STREAM_IS_IMAGE(stream)) { + al_log_info("ff_demuxer", "Guessing that stream #%u is an image.", i); } else { - stream->duration = av_rescale_q(av->format_context->duration, AV_TIME_BASE_Q, stream->time_base); + if (stream->duration < 0) { + al_log_warn("ff_demuxer", "Stream #%u has an invalid duration (%ld).", i, stream->duration); + stream->duration = av->format_context->duration < 0 ? 0 : + av_rescale_q(av->format_context->duration, AV_TIME_BASE_Q, stream->time_base); + al_log_info("ff_demuxer", "Defaulting stream #%u to a duration of %.3fs.", + i, stream->duration * av_q2d(stream->time_base)); + al_assert(stream->duration >= 0); + } + duration = (u64)av_rescale_q(stream->duration, stream->time_base, AV_TIME_BASE_Q); + if (duration > av->duration) av->duration = duration; } - al_log_info("ff_demuxer", "Setting stream #%u to a duration of %.3fs.", i, stream->duration * av_q2d(stream->time_base)); + if (stream->start_time == AV_NOPTS_VALUE) stream->start_time = 0; + } else if (type != AVMEDIA_TYPE_ATTACHMENT) { + continue; } - if (stream->start_time == AV_NOPTS_VALUE) stream->start_time = 0; - s64 duration = av_rescale_q(stream->duration, stream->time_base, AV_TIME_BASE_Q); - al_assert(duration >= 0); - if ((u64)duration > av->duration) av->duration = (u64)duration; al_array_push(av->demux.streams, ((struct camu_codec_stream){ .mode = CAMU_FFMPEG_COMPAT, .type = type, - .duration = (u64)duration, + .duration = duration, .av.stream = stream })); } diff --git a/src/codec/ffmpeg/packet_ext.c b/src/codec/ffmpeg/packet_ext.c index c0e400d..16f5748 100644 --- a/src/codec/ffmpeg/packet_ext.c +++ b/src/codec/ffmpeg/packet_ext.c @@ -38,6 +38,23 @@ void aki_packet_write_av_codec_id(struct aki_packet *packet, enum AVCodecID code AKI_PACKET_WRITE_TYPE(packet, enum AVCodecID, codec_id); } +void aki_packet_write_av_dictionary(struct aki_packet *packet, AVDictionary *dict) +{ + s32 count = av_dict_count(dict); + AKI_PACKET_WRITE_TYPE(packet, s32, count); + const AVDictionaryEntry *entry = NULL; + while ((entry = av_dict_iterate(dict, entry))) { + size_t len = al_strlen(entry->key); + al_assert(len <= UINT32_MAX); + AKI_PACKET_WRITE_TYPE(packet, u32, len); + AKI_PACKET_WRITE_DATA(packet, entry->key, len); + len = al_strlen(entry->value); + al_assert(len <= UINT32_MAX); + AKI_PACKET_WRITE_TYPE(packet, u32, len); + AKI_PACKET_WRITE_DATA(packet, entry->value, len); + } +} + void aki_packet_write_av_stream(struct aki_packet *packet, AVStream *stream) { AKI_PACKET_WRITE_TYPE(packet, s32, stream->index); @@ -46,6 +63,7 @@ void aki_packet_write_av_stream(struct aki_packet *packet, AVStream *stream) AKI_PACKET_WRITE_TYPE(packet, s64, stream->duration); AKI_PACKET_WRITE_TYPE(packet, s64, stream->start_time); AKI_PACKET_WRITE_TYPE(packet, s64, stream->nb_frames); + aki_packet_write_av_dictionary(packet, stream->metadata); if (stream->avg_frame_rate.den == 0) { // r_frame_rate.den == 0 handled on the client. AKI_PACKET_WRITE_TYPE(packet, AVRational, stream->r_frame_rate); @@ -118,6 +136,25 @@ enum AVCodecID aki_packet_read_av_codec_id(struct aki_packet *packet) return id; } +void aki_packet_read_av_dictionary(struct aki_packet *packet, AVDictionary **dict) +{ + s32 count; + AKI_PACKET_READ_TYPE(packet, s32, count); + for (s32 i = 0; i < count; i++) { + char *key, *value; + u32 len; + AKI_PACKET_READ_TYPE(packet, u32, len); + AKI_PACKET_READ_DATA(packet, len, key); + // Key and value MUST be allocated with av_malloc functions + // just like all FFmpeg structures. + key = av_strndup(key, len); + AKI_PACKET_READ_TYPE(packet, u32, len); + AKI_PACKET_READ_DATA(packet, len, value); + value = av_strndup(value, len); + av_dict_set(dict, key, value, AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL); + } +} + AVStream *aki_packet_read_av_stream(AVFormatContext *format_context, const AVCodec *codec, struct aki_packet *packet) { AVStream *stream = avformat_new_stream(format_context, codec); @@ -127,6 +164,8 @@ AVStream *aki_packet_read_av_stream(AVFormatContext *format_context, const AVCod AKI_PACKET_READ_TYPE(packet, s64, stream->duration); AKI_PACKET_READ_TYPE(packet, s64, stream->start_time); AKI_PACKET_READ_TYPE(packet, s64, stream->nb_frames); + stream->metadata = NULL; + aki_packet_read_av_dictionary(packet, &stream->metadata); AKI_PACKET_READ_TYPE(packet, AVRational, stream->avg_frame_rate); return stream; } diff --git a/src/codec/ffmpeg/packet_ext.h b/src/codec/ffmpeg/packet_ext.h index 36b6ed5..26a1392 100644 --- a/src/codec/ffmpeg/packet_ext.h +++ b/src/codec/ffmpeg/packet_ext.h @@ -6,6 +6,7 @@ #include <libavcodec/packet.h> #include <libavcodec/codec_par.h> #include <libavformat/avformat.h> +#include <libavutil/dict.h> void aki_packet_write_av_codec_parameters(struct aki_packet *packet, AVCodecParameters *codecpar); void aki_packet_write_av_codec_id(struct aki_packet *packet, enum AVCodecID codec_id); diff --git a/src/codec/ffmpeg/scaler.c b/src/codec/ffmpeg/scaler.c index d233268..0089dd1 100644 --- a/src/codec/ffmpeg/scaler.c +++ b/src/codec/ffmpeg/scaler.c @@ -49,11 +49,12 @@ static bool ff_scaler_init(struct camu_scaler *scale, struct camu_scaler_format return false; } - av->frame = alloc_picture(fmt->req.format, fmt->req.width, fmt->req.height); - if (!av->frame) { + AVFrame *frame = alloc_picture(fmt->req.format, fmt->req.width, fmt->req.height); + if (!frame) { al_log_error("ff_scaler", "Failed to allocate frame."); return false; } + av->frame.av.frame = frame; return true; } @@ -61,8 +62,9 @@ static bool ff_scaler_init(struct camu_scaler *scale, struct camu_scaler_format static bool ff_scaler_scale(struct camu_scaler *scale, const u8 **in_slice, s32 *in_strides) { struct camu_ff_scaler *av = (struct camu_ff_scaler *)scale; - s32 ret = sws_scale(av->scaler_context, in_slice, in_strides, 0, av->fmt.in.height, - av->frame->data, av->frame->linesize); + AVFrame *frame = av->frame.av.frame; + 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) { al_log_error("ff_scaler", "Failed to scale frame (%s).", av_err2str(ret)); return false; @@ -70,17 +72,17 @@ static bool ff_scaler_scale(struct camu_scaler *scale, const u8 **in_slice, s32 return true; } -static AVFrame *ff_scaler_get_frame(struct camu_scaler *scale) +static struct camu_codec_frame *ff_scaler_get_frame(struct camu_scaler *scale) { struct camu_ff_scaler *av = (struct camu_ff_scaler *)scale; - return av->frame; + return &av->frame; } static void ff_scaler_free(struct camu_scaler **scale) { struct camu_ff_scaler *av = (struct camu_ff_scaler *)*scale; - av_freep(&av->frame->data[0]); - av_frame_free(&av->frame); + av_freep(&av->frame.av.frame->data[0]); + av_frame_free(&av->frame.av.frame); sws_freeContext(av->scaler_context); al_free(av); *scale = NULL; diff --git a/src/codec/ffmpeg/scaler.h b/src/codec/ffmpeg/scaler.h index a4effb3..f01f3fd 100644 --- a/src/codec/ffmpeg/scaler.h +++ b/src/codec/ffmpeg/scaler.h @@ -9,7 +9,7 @@ struct camu_ff_scaler { struct camu_scaler scale; struct SwsContext *scaler_context; struct camu_scaler_format fmt; - AVFrame *frame; + struct camu_codec_frame frame; }; struct camu_scaler *camu_ff_scaler_create(void); diff --git a/src/fruits/cmsrv/cmsrv.c b/src/fruits/cmsrv/cmsrv.c index c6f4546..511484d 100644 --- a/src/fruits/cmsrv/cmsrv.c +++ b/src/fruits/cmsrv/cmsrv.c @@ -127,7 +127,9 @@ s32 wmain(s32 argc, wchar_t **argv) al_set_print(log_callback, &s); +#ifdef CAMU_HAVE_FFMPEG camu_ff_set_default_log_callback(); +#endif aki_event_loop_init(&s.loop); @@ -162,6 +164,10 @@ s32 wmain(s32 argc, wchar_t **argv) cmsrv_ui_close(&s.ui); +#ifdef CAMU_HAVE_FFMPEG + camu_ff_free_default_log_callback(); +#endif + aki_common_close(); return EXIT_SUCCESS; diff --git a/src/fruits/cmsrv/ui.c b/src/fruits/cmsrv/ui.c index 1a52641..e10db5e 100644 --- a/src/fruits/cmsrv/ui.c +++ b/src/fruits/cmsrv/ui.c @@ -1,8 +1,9 @@ #include <al/lib.h> +#include <al/log.h> #include "ui.h" -#define LOG_RATIO 1.3 +#define LOG_RATIO 2.1 static s32 resize_cb(struct ncplane *p) { @@ -99,7 +100,10 @@ static void layout_lists(struct cmsrv_ui *ui, struct ncplane *parent) static void erase_lists(struct cmsrv_ui *ui) { struct ncplane *n = ui->lists.n; - if (n) ncplane_erase(n); + if (n) { + ncplane_erase(n); + notcurses_render(ui->nc); + } } static void putnwstr_maxwidth_yx(struct ncplane *n, u32 y, u32 x, u32 maxwidth, wstr *w) @@ -118,7 +122,7 @@ static void render_lists(struct cmsrv_ui *ui) u32 max_width = ncplane_dim_x(n); u32 max_height = ncplane_dim_y(n); u32 current_line = 0; - s32 entries_per_list = 10; + s32 entries_per_list = max_height; struct lia_list *list; al_array_foreach(ui->server->lists, i, list) { diff --git a/src/fruits/cmv/cmv.c b/src/fruits/cmv/cmv.c index 96b6f75..0fe33ad 100644 --- a/src/fruits/cmv/cmv.c +++ b/src/fruits/cmv/cmv.c @@ -61,7 +61,9 @@ s32 wmain(s32 argc, wchar_t **argv) signal(SIGINT, sigint_handler); +#ifdef CAMU_HAVE_FFMPEG camu_ff_set_default_log_callback(); +#endif aki_event_loop_init(&c.loop); @@ -132,7 +134,9 @@ s32 wmain(s32 argc, wchar_t **argv) out: aki_event_loop_destroy(&c.loop); +#ifdef CAMU_HAVE_FFMPEG camu_ff_free_default_log_callback(); +#endif stl_global_close(); aki_common_close(); diff --git a/src/fruits/ctv/ctv.c b/src/fruits/ctv/ctv.c index 75936eb..5f4a819 100644 --- a/src/fruits/ctv/ctv.c +++ b/src/fruits/ctv/ctv.c @@ -49,6 +49,11 @@ static void onSurfaceCreated(GLFMDisplay *display, s32 width, s32 height) struct ctv *c = (struct ctv *)glfmGetUserData(display); (void)width; (void)height; + GLFMRenderingAPI api = glfmGetRenderingAPI(display); + al_log_info("window_glfm", "Created GLES context of version %s.", + api == GLFMRenderingAPIOpenGLES32 ? "3.2" : + api == GLFMRenderingAPIOpenGLES31 ? "3.1" : + api == GLFMRenderingAPIOpenGLES3 ? "3.0" : "2.0"); if (!c->created) { c->scr.callback = screen_callback; c->scr.userdata = c; diff --git a/src/fruits/droid/ctv/gradle/libs.versions.toml b/src/fruits/droid/ctv/gradle/libs.versions.toml index 0b1b89c..c474f95 100644 --- a/src/fruits/droid/ctv/gradle/libs.versions.toml +++ b/src/fruits/droid/ctv/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.7.1" +agp = "8.7.2" [libraries] diff --git a/src/liana/client.c b/src/liana/client.c index 527376a..0edc262 100644 --- a/src/liana/client.c +++ b/src/liana/client.c @@ -22,14 +22,15 @@ static void parse_info_packet(struct lia_client *client, struct aki_packet *pack u32 count = aki_packet_read_u32(packet); for (u32 i = 0; i < count; i++) { u8 mode = aki_packet_read_u8(packet); + u8 type = aki_packet_read_u8(packet); + u64 duration = aki_packet_read_u64(packet); + s32 index = aki_packet_read_s32(packet); + al_assert(index < 32); struct lia_vcr_track *track = NULL; switch (mode) { case CAMU_NORMAL: { - client->mask |= 1 << 0; + client->mask |= 1 << index; track = al_alloc_object(struct lia_vcr_track); - track->stream.mode = mode; - u8 type = aki_packet_read_u8(packet); - track->stream.type = type; if (type == CAMU_STREAM_AUDIO) { struct camu_audio_format *fmt = &track->stream.audio.fmt; fmt->format = aki_packet_read_s32(packet); @@ -44,49 +45,63 @@ static void parse_info_packet(struct lia_client *client, struct aki_packet *pack fmt->height = aki_packet_read_s32(packet); fmt->format = aki_packet_read_s32(packet); } - track->index = 0; break; } #ifdef CAMU_HAVE_FFMPEG case CAMU_FFMPEG_COMPAT: { - const AVCodec *codec = avcodec_find_decoder(aki_packet_read_av_codec_id(packet)); - AVFormatContext *format_context = avformat_alloc_context(); - AVStream *stream = aki_packet_read_av_stream(format_context, codec, packet); - s32 index = stream->index; - al_assert(index < 32); - switch (stream->codecpar->codec_type) { - case AVMEDIA_TYPE_AUDIO: + switch (type) { + case CAMU_STREAM_AUDIO: client->mask |= 1 << index; break; - case AVMEDIA_TYPE_VIDEO: + case CAMU_STREAM_VIDEO: client->mask |= 1 << index; - //continue; break; - case AVMEDIA_TYPE_SUBTITLE: + case CAMU_STREAM_SUBTITLE: + client->mask |= 1 << index; + break; + case CAMU_STREAM_ATTACHMENT: + // Assume we have all the data we need in the AVStream object. + break; default: continue; } + enum AVCodecID codec_id = aki_packet_read_av_codec_id(packet); + const AVCodec *codec = avcodec_find_decoder(codec_id); + AVFormatContext *format_context = avformat_alloc_context(); + AVStream *stream = aki_packet_read_av_stream(format_context, codec, packet); + if (type == CAMU_STREAM_SUBTITLE && codec_id != AV_CODEC_ID_ASS) { + client->mask &= ~(1 << index); + continue; + } + if (type == CAMU_STREAM_ATTACHMENT) { + struct camu_codec_stream attachment; + attachment.type = CAMU_STREAM_ATTACHMENT; + attachment.av.stream = stream; + client->callback(client->userdata, LIANA_CLIENT_CONFIGURE, &attachment, track); + avformat_free_context(format_context); + continue; + } track = al_alloc_object(struct lia_vcr_track); - track->stream.mode = CAMU_FFMPEG_COMPAT; - track->stream.type = stream->codecpar->codec_type; track->stream.av.format_context = format_context; track->stream.av.stream = stream; - if (track->stream.type == CAMU_STREAM_AUDIO) { + if (type == CAMU_STREAM_AUDIO) { struct camu_audio_format *fmt = &track->stream.audio.fmt; fmt->format = stream->codecpar->format; fmt->sample_rate = stream->codecpar->sample_rate; av_channel_layout_copy(&fmt->channel_layout, &stream->codecpar->ch_layout); fmt->channel_count = stream->codecpar->ch_layout.nb_channels; } - track->index = index; break; } #endif } + track->index = index; + track->stream.mode = mode; + track->stream.type = type; + track->stream.duration = duration; track->client = lia_handler_by_name(&liana)->create_client_handler(); track->client->callback = client->callback; track->client->userdata = client->userdata; - track->stream.mode = mode; if (!track->client->init(track->client, client->renderer, &track->stream)) { track->client->free(&track->client); #ifdef CAMU_HAVE_FFMPEG @@ -107,8 +122,12 @@ static void info_packet_callback(void *userdata, struct aki_packet_stream *strea struct lia_client *client = (struct lia_client *)userdata; client->connection_id = aki_packet_read_u16(packet); parse_info_packet(client, packet); - //al_assert(client->mask != 0); aki_packet_free(packet); + if (client->mask == 0 || lia_vcr_is_empty(&client->vcr)) { + client->reconnect = false; + aki_packet_stream_disconnect(&client->data); + return; + } stream->packet_callback = data_packet_callback; struct aki_packet *rpacket = aki_packet_create(); aki_packet_write_s32(rpacket, client->mask); @@ -124,6 +143,7 @@ static void packet_sent_callback(void *userdata, struct aki_packet *packet) static void connection_callback(void *userdata, struct aki_packet_stream *stream) { struct lia_client *client = (struct lia_client *)userdata; + lia_vcr_start(&client->vcr, client->loop); stream->packet_sent_callback = packet_sent_callback; struct aki_packet *packet = aki_packet_create(); aki_packet_write_u16(packet, client->id); @@ -173,8 +193,6 @@ void lia_client_connect(struct lia_client *client, struct aki_event_loop *loop, client->mask = 0; client->reconnect = false; lia_vcr_init(&client->vcr, &client->data); - // TODO: Starting and stopping of vcr could be more clear. - lia_vcr_start(&client->vcr, client->loop); al_str_clone(&client->addr, addr); client->port = port; if (!aki_packet_stream_init(&client->data, type, connection_callback, connection_closed_callback, client)) { diff --git a/src/liana/handler.h b/src/liana/handler.h index f994069..84aa1de 100644 --- a/src/liana/handler.h +++ b/src/liana/handler.h @@ -26,6 +26,7 @@ enum { enum { LIANA_CLIENT_CONFIGURE = 0, LIANA_CLIENT_DATA, + LIANA_CLIENT_SUBTITLE, LIANA_CLIENT_REMOVE_BUFFERS, LIANA_CLIENT_RESUME_AT, LIANA_CLIENT_EOF, diff --git a/src/liana/handlers/cdio_server.c b/src/liana/handlers/cdio_server.c index d271829..641ed92 100644 --- a/src/liana/handlers/cdio_server.c +++ b/src/liana/handlers/cdio_server.c @@ -32,6 +32,8 @@ static void cdio_server_write_info(struct lia_server_handler *handler, struct ak aki_packet_write_u32(packet, 1); aki_packet_write_u8(packet, CAMU_NORMAL); aki_packet_write_u8(packet, CAMU_STREAM_AUDIO); + aki_packet_write_u64(packet, cdio->handler.get_duration(&cdio->handler)); + aki_packet_write_s32(packet, 0); aki_packet_write_s32(packet, cdio->fmt.format); aki_packet_write_s32(packet, cdio->fmt.sample_rate); aki_packet_write_s32(packet, cdio->fmt.channel_count); @@ -48,9 +50,9 @@ static u64 cdio_server_get_duration(struct lia_server_handler *handler) struct lia_cdio_server *cdio = (struct lia_cdio_server *)handler; struct cch_chapter *first = &al_array_at(cdio->handle->entry->chapters, 0); struct cch_chapter *last = &al_array_last(cdio->handle->entry->chapters); - f64 seconds = camu_audio_format_bytes_to_sec(&cdio->fmt, (last->end - first->start) * CDIO_CD_FRAMESIZE_RAW); - al_log_info("cdio", "Length: %.2fs.", seconds); - return (u64)(seconds * 1000000.0); + u64 length = camu_audio_format_bytes_to_usec(&cdio->fmt, (last->end - first->start) * CDIO_CD_FRAMESIZE_RAW); + al_log_info("cdio", "Length: %.2fs.", length / 1000000.0); + return length; } static bool cdio_server_seek(struct lia_server_handler *handler, u64 pos) diff --git a/src/liana/handlers/codec_client.c b/src/liana/handlers/codec_client.c index d420c59..7c57d03 100644 --- a/src/liana/handlers/codec_client.c +++ b/src/liana/handlers/codec_client.c @@ -21,13 +21,15 @@ static bool codec_client_init(struct lia_client_handler *handler, struct camu_re struct camu_codec_stream *stream) { struct lia_codec_client *codec = (struct lia_codec_client *)handler; - codec->dec = camu_ff_decoder_create(); - //codec->dec = camu_stbi_decoder_create(); - //codec->dec = camu_spng_decoder_create(); - //codec->dec = camu_wuffs_decoder_create(); codec->handler.stream = stream; - if (!codec->dec->init(codec->dec, renderer, stream, data_callback, codec)) { - return false; + if (stream->type == CAMU_STREAM_AUDIO || stream->type == CAMU_STREAM_VIDEO) { + codec->dec = camu_ff_decoder_create(); + //codec->dec = camu_stbi_decoder_create(); + //codec->dec = camu_spng_decoder_create(); + //codec->dec = camu_wuffs_decoder_create(); + if (!codec->dec->init(codec->dec, renderer, stream, data_callback, codec)) { + return false; + } } return true; } @@ -38,8 +40,6 @@ static bool push_av_packet(struct lia_codec_client *codec, AVPacket *pkt) struct camu_codec_packet packet; packet.av.pkt = pkt; s32 ret = codec->dec->push(codec->dec, &packet); - av_packet_unref(pkt); - av_packet_free(&pkt); return ret == CAMU_OK; } #endif @@ -56,7 +56,7 @@ static bool codec_client_handle_packet(struct lia_client_handler *handler, struc { struct lia_codec_client *codec = (struct lia_codec_client *)handler; if (!packet) { - struct lia_codec_client *codec = (struct lia_codec_client *)handler; + if (!codec->dec) return true; s32 ret = codec->dec->push(codec->dec, NULL); // Flush returns success. ret = codec->dec->process(codec->dec); @@ -68,6 +68,7 @@ static bool codec_client_handle_packet(struct lia_client_handler *handler, struc u8 type = aki_packet_read_u8(packet); switch (type) { case CAMU_NORMAL: { + if (!codec->dec) return true; struct aki_buffer buffer; aki_packet_read_buffer(packet, &buffer); success = push_packet(codec, &buffer); @@ -75,12 +76,20 @@ static bool codec_client_handle_packet(struct lia_client_handler *handler, struc } #ifdef CAMU_HAVE_FFMPEG case CAMU_FFMPEG_COMPAT: { - success = push_av_packet(codec, aki_packet_read_av_packet(packet)); + AVPacket *pkt = aki_packet_read_av_packet(packet); + if (!codec->dec) { + codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_SUBTITLE, codec->handler.stream, pkt); + return true; + } else { + success = push_av_packet(codec, pkt); + } + av_packet_unref(pkt); + av_packet_free(&pkt); break; } #endif } - // Forcing in EOF on errors is not necessary but should be a better experience client-side. + // Forcing in EOF on an error is not necessary but should be a better experience client-side. if (!success) { codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_EOF, codec->handler.stream, NULL); return false; @@ -96,13 +105,13 @@ static bool codec_client_handle_packet(struct lia_client_handler *handler, struc static void codec_client_flush(struct lia_client_handler *handler) { struct lia_codec_client *codec = (struct lia_codec_client *)handler; - codec->dec->flush(codec->dec); + if (codec->dec) codec->dec->flush(codec->dec); } static void codec_client_free(struct lia_client_handler **handler) { struct lia_codec_client *codec = (struct lia_codec_client *)*handler; - codec->dec->free(&codec->dec); + if (codec->dec) codec->dec->free(&codec->dec); al_free(codec); *handler = NULL; } diff --git a/src/liana/handlers/codec_server.c b/src/liana/handlers/codec_server.c index 58da2e6..94b18df 100644 --- a/src/liana/handlers/codec_server.c +++ b/src/liana/handlers/codec_server.c @@ -42,9 +42,11 @@ static void codec_server_write_info(struct lia_server_handler *handler, struct a struct camu_codec_stream *stream; al_array_foreach_ptr(codec->demux->streams, i, stream) { aki_packet_write_u8(packet, stream->mode); + aki_packet_write_u8(packet, stream->type); + aki_packet_write_u64(packet, stream->duration); + aki_packet_write_s32(packet, i); switch (stream->mode) { case CAMU_NORMAL: { - aki_packet_write_u8(packet, stream->type); struct camu_video_format *fmt = &stream->video.fmt; aki_packet_write_s32(packet, fmt->width); aki_packet_write_s32(packet, fmt->height); diff --git a/src/liana/list.c b/src/liana/list.c index 65efc9d..09720b6 100644 --- a/src/liana/list.c +++ b/src/liana/list.c @@ -151,7 +151,7 @@ static bool handle_add(struct lia_list *list, struct lia_list_entry *entry) sink->set = list->current; sink->callback(sink->userdata, LIANA_SINK_SET, entry, list->current, &time); } - // meta playing + al_log_info("list", "Now playing: %ls.\n", AL_WSTR_PRINTF(&entry->name)); } else { /* if (list->queued == -1) { @@ -309,7 +309,7 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index) sink->callback(sink->userdata, LIANA_SINK_SET, target, index, &time); } - // meta playing + al_log_info("list", "Now playing: %ls.\n", AL_WSTR_PRINTF(&target->name)); return true; } @@ -403,7 +403,7 @@ static void handle_end(struct lia_list *list, s32 sequence) al_array_foreach(list->sinks, i, sink) { sink->queued = -1; } - // meta playing + al_log_info("list", "Now playing: %ls.\n", AL_WSTR_PRINTF(¤t->name)); } else if (next < size) { struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); cmd->op = SKIPTO; diff --git a/src/liana/vcr.c b/src/liana/vcr.c index 213d5cb..c9e7975 100644 --- a/src/liana/vcr.c +++ b/src/liana/vcr.c @@ -3,9 +3,18 @@ #include "vcr.h" #include "handler.h" -#define VCR_BUFFER_INIT 256 -#define VCR_BUFFER_BUFFERED (VCR_BUFFER_INIT - 16) -#define VCR_BUFFER_LOW (VCR_BUFFER_INIT - 64) +#define VCR_BUFFER_BUFFERED MB(24) + +enum { + VCR_EXPAND_UNTOUCHED = 0, + VCR_EXPAND_GROWN, + VCR_EXPAND_COMPLETE +}; + +// Only track the buffered state of audio and video streams as we don't +// expect any other type of stream to ever call cork(). +#define TRACK_IGNORE_BUFFERED(track) \ + (!(track->stream.type == CAMU_STREAM_AUDIO || track->stream.type == CAMU_STREAM_VIDEO)) static void signal_callback(void *userdata) { @@ -13,14 +22,22 @@ static void signal_callback(void *userdata) aki_packet_stream_cork(vcr->data, false); } +static void reset_metrics(struct lia_vcr *vcr) +{ + vcr->metric.current_frame = 0; + vcr->metric.last_report_ts = 0; +} + void lia_vcr_init(struct lia_vcr *vcr, struct aki_packet_stream *data) { al_array_init(vcr->tracks); - al_atomic_store(s32)(&vcr->count, 0, AL_ATOMIC_RELAXED); - vcr->mark.low = VCR_BUFFER_LOW; + al_atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED); vcr->mark.buffered = VCR_BUFFER_BUFFERED; + vcr->mark.low = 0; + vcr->expand = VCR_EXPAND_UNTOUCHED; vcr->data = data; aki_signal_init(&vcr->signal, signal_callback, vcr); + reset_metrics(vcr); } void lia_vcr_start(struct lia_vcr *vcr, struct aki_event_loop *loop) @@ -45,9 +62,10 @@ static aki_thread_result AKI_THREADCALL vcr_track_thread(void *userdata) // We were signaled to close, exit thread. goto out; } + u32 size = aki_packet_get_size(packet); u8 buffered = al_atomic_load(u8)(&track->buffered, AL_ATOMIC_RELAXED); - s32 count = al_atomic_sub(s32)(&vcr->count, 1, AL_ATOMIC_RELAXED); - if (count <= vcr->mark.low && buffered) { + u64 buffer = al_atomic_sub(u64)(&vcr->count, size, AL_ATOMIC_RELAXED); + if (buffered && buffer <= vcr->mark.low) { aki_signal_send(&vcr->signal); } } @@ -90,12 +108,17 @@ void lia_vcr_add_track(struct lia_vcr *vcr, struct lia_vcr_track *track) track->vcr = vcr; aki_cond_init(&track->cond); aki_mutex_init(&track->mutex); - al_atomic_store(u8)(&track->buffered, 0, AL_ATOMIC_RELAXED); - aki_packet_cache_init(&track->cache, VCR_BUFFER_INIT); + al_atomic_store(u8)(&track->buffered, TRACK_IGNORE_BUFFERED(track), AL_ATOMIC_RELAXED); + aki_packet_cache_init(&track->cache, 256); al_array_push(vcr->tracks, track); al_atomic_store(s32)(&track->state, LIANA_STREAM_RUNNING, AL_ATOMIC_RELAXED); } +bool lia_vcr_is_empty(struct lia_vcr *vcr) +{ + return vcr->tracks.size == 0; +} + static struct lia_vcr_track *get_track_from_index(struct lia_vcr *vcr, s32 index) { struct lia_vcr_track *track; @@ -105,7 +128,7 @@ static struct lia_vcr_track *get_track_from_index(struct lia_vcr *vcr, s32 index return NULL; } -static void cork_if_buffered(struct lia_vcr *vcr) +static void cork_if_buffered(struct lia_vcr *vcr, u64 buffer) { u8 buffered = 1; struct lia_vcr_track *track; @@ -113,6 +136,15 @@ static void cork_if_buffered(struct lia_vcr *vcr) buffered &= al_atomic_load(u8)(&track->buffered, AL_ATOMIC_RELAXED); } if (buffered) { + if (vcr->expand == VCR_EXPAND_UNTOUCHED) { + vcr->mark.buffered = buffer * 2; + vcr->expand = VCR_EXPAND_GROWN; + al_log_info("vcr", "Expanded buffer to size %.2fMB.", vcr->mark.buffered / (f32)MB(1)); + return; + } else if (vcr->expand == VCR_EXPAND_GROWN) { + vcr->mark.low = vcr->mark.buffered - MB(2); + vcr->expand = VCR_EXPAND_COMPLETE; + } aki_packet_stream_cork(vcr->data, true); al_array_foreach(vcr->tracks, i, track) { aki_packet_cache_flush(&track->cache); @@ -120,7 +152,24 @@ static void cork_if_buffered(struct lia_vcr *vcr) } } -bool lia_vcr_push_packet(struct lia_vcr *vcr, struct aki_packet *packet) +static void update_metrics(struct lia_vcr *vcr, u32 size) +{ + vcr->metric.current_frame += size; + u64 now = aki_get_timestamp(); + if (!vcr->metric.last_report_ts) { + vcr->metric.last_report_ts = now; + return; + } + u64 diff; + if ((diff = now - vcr->metric.last_report_ts) > 1000000Lu) { + f32 kbps = (vcr->metric.current_frame / 125.f) / (diff / 1000000.f); + al_log_info("vcr", "Receiving packets at %.2fkbps.", kbps); + vcr->metric.current_frame = 0; + vcr->metric.last_report_ts = now; + } +} + +void lia_vcr_push_packet(struct lia_vcr *vcr, struct aki_packet *packet) { struct lia_vcr_track *track; u8 op = aki_packet_read_u8(packet); @@ -130,34 +179,37 @@ bool lia_vcr_push_packet(struct lia_vcr *vcr, struct aki_packet *packet) if (!track) { al_log_warn("liana", "Received data from errored or unknown track."); aki_packet_free(packet); - return false; + return; } if (!track->running) { aki_thread_create(&track->thread, vcr_track_thread, track); track->running = true; } - s32 count; + u32 size = aki_packet_get_size(packet); if (!aki_packet_cache_send_packet(&track->cache, packet)) { aki_packet_free(packet); - } else if ((count = al_atomic_add(s32)(&vcr->count, 1, AL_ATOMIC_RELAXED)) > vcr->mark.buffered) { - vcr->mark.buffered = count; - cork_if_buffered(vcr); + return; + } + u64 buffer; + if ((buffer = al_atomic_add(u64)(&vcr->count, size, AL_ATOMIC_RELAXED)) >= vcr->mark.buffered) { + cork_if_buffered(vcr, buffer); } + update_metrics(vcr, size); break; case LIANA_PACKET_EOF: al_array_foreach(vcr->tracks, i, track) { aki_packet_cache_send_packet(&track->cache, NULL); } aki_packet_free(packet); + aki_signal_stop(&vcr->signal); break; case LIANA_PACKET_ERROR: al_log_warn("liana", "Unhandled error packet."); aki_packet_free(packet); break; default: - al_assert_and_return(false); + al_assert(false); } - return true; } void lia_vcr_cork(struct lia_vcr_track *track) @@ -206,11 +258,15 @@ void lia_vcr_flush(struct lia_vcr *vcr) vcr_track_close_internal(track); track->client->flush(track->client); aki_packet_cache_enable(&track->cache); - al_atomic_store(u8)(&track->buffered, 0, AL_ATOMIC_RELAXED); + al_atomic_store(u8)(&track->buffered, TRACK_IGNORE_BUFFERED(track), AL_ATOMIC_RELAXED); al_atomic_store(s32)(&track->state, LIANA_STREAM_RUNNING, AL_ATOMIC_RELAXED); } - aki_signal_send(&vcr->signal); - al_atomic_store(s32)(&vcr->count, 0, AL_ATOMIC_RELAXED); + aki_signal_stop(&vcr->signal); + al_atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED); + if (vcr->expand == VCR_EXPAND_COMPLETE) { + vcr->mark.low = 0; + vcr->expand = VCR_EXPAND_GROWN; + } } void lia_vcr_close_all(struct lia_vcr *vcr) diff --git a/src/liana/vcr.h b/src/liana/vcr.h index f471c4e..fc7fb32 100644 --- a/src/liana/vcr.h +++ b/src/liana/vcr.h @@ -29,16 +29,22 @@ struct lia_vcr_track { struct lia_vcr { array(struct lia_vcr_track *) tracks; - atomic(s32) count; - struct { s32 low, buffered; } mark; + atomic(u64) count; + struct { u64 buffered, low; } mark; + u8 expand; struct aki_packet_stream *data; struct aki_signal signal; + struct { + u64 current_frame; + u64 last_report_ts; + } metric; }; void lia_vcr_init(struct lia_vcr *vcr, struct aki_packet_stream *data); void lia_vcr_start(struct lia_vcr *vcr, struct aki_event_loop *loop); void lia_vcr_add_track(struct lia_vcr *vcr, struct lia_vcr_track *track); -bool lia_vcr_push_packet(struct lia_vcr *vcr, struct aki_packet *packet); +bool lia_vcr_is_empty(struct lia_vcr *vcr); +void lia_vcr_push_packet(struct lia_vcr *vcr, struct aki_packet *packet); void lia_vcr_cork(struct lia_vcr_track *track); void lia_vcr_uncork(struct lia_vcr_track *track); void lia_vcr_flush(struct lia_vcr *vcr); diff --git a/src/libsink/common.h b/src/libsink/common.h index 0bb3ded..708b1bb 100644 --- a/src/libsink/common.h +++ b/src/libsink/common.h @@ -1,6 +1,6 @@ #pragma once -#define CAMU_SINK_LOCAL +//#define CAMU_SINK_LOCAL enum { CAMU_SINK_SET = 0, diff --git a/src/libsink/sink.c b/src/libsink/sink.c index 538515a..2caa973 100644 --- a/src/libsink/sink.c +++ b/src/libsink/sink.c @@ -10,6 +10,8 @@ #include "sink.h" #include "common.h" +#include "../render/renderer_libplacebo.h" + enum { SINK_EMPTY = 0, SINK_PAUSED, @@ -36,7 +38,7 @@ enum { CLOSE }; -#define ENTRY_MAX_AGE 7 +#define ENTRY_MAX_AGE 5 #define BUFFER_EMPTY(buf) ((buf)->state == BUFFER_INIT || (buf)->state == BUFFER_QUEUED) @@ -47,6 +49,13 @@ enum { #endif #define AUDIO_READY_OR_EMPTY(entry) (BUFFER_EMPTY(&(entry)->audio) || (entry)->audio.state == BUFFER_ADDED) +#ifdef CAMU_SINK_NO_VIDEO +#define VIDEO_REMOVED_OR_EMPTY(entry) true +#else +#define VIDEO_REMOVED_OR_EMPTY(entry) (BUFFER_EMPTY(&(entry)->video) || (entry)->video.state != BUFFER_ADDED) +#endif +#define AUDIO_REMOVED_OR_EMPTY(entry) (BUFFER_EMPTY(&(entry)->audio) || (entry)->audio.state != BUFFER_ADDED) + #if defined CAMU_SCREEN_THREADED && defined CAMU_MIXER_THREADED #define BLOCKING_SLEEP(delay) aki_thread_sleep(delay) #else @@ -379,6 +388,36 @@ void add_video_if_set_and_buffered(struct camu_sink_entry *entry) } #endif +static void switch_to(struct camu_sink *sink, struct camu_sink_entry *entry) +{ + if (sink->current) { + if (camu_clock_is_ended(&entry->clock)) { + remove_entry_buffers(sink, sink->current); + } else { + maybe_add_to_previous(sink, sink->current, entry); + } + } + set_or_queue_entry(entry); + sink->current = entry; +} + +static bool end_entry_and_advance_queue(struct camu_sink *sink, struct camu_sink_entry *entry) +{ + al_log_info("sink", "Entry ended."); + camu_clock_end(&entry->clock); + if (sink->target) { + switch_to(sink, sink->target); + sink->target = NULL; + } + if (sink->queued) { + set_or_queue_entry(sink->queued); + sink->current = sink->queued; + sink->queued = NULL; + return true; + } + return false; +} + static void audio_buffer_callback(void *userdata, u8 op) { struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata; @@ -386,9 +425,7 @@ static void audio_buffer_callback(void *userdata, u8 op) switch (op) { case CAMU_BUFFER_BUFFERED: aki_mutex_lock(&sink->mutex); - if (!entry->ended) { - add_audio_if_set_and_buffered(entry); - } + add_audio_if_set_and_buffered(entry); aki_mutex_unlock(&sink->mutex); break; case CAMU_BUFFER_CORK: @@ -410,20 +447,23 @@ static void audio_buffer_callback(void *userdata, u8 op) case CAMU_BUFFER_EOF: { lia_vcr_cork(entry->audio.track); aki_mutex_lock(&sink->mutex); + al_log_info("sink", "Audio EOF."); remove_entry_audio_buffer(sink, entry); - camu_clock_end(&entry->clock); - // EOF on the audio buffer unconditionally - // advances the queue. - if (sink->queued) { - set_or_queue_entry(sink->queued); - sink->current = sink->queued; - sink->queued = NULL; + bool run_queue = VIDEO_REMOVED_OR_EMPTY(entry); +#ifndef CAMU_SINK_NO_VIDEO + bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf); + run_queue = run_queue || single_frame; +#endif + if (run_queue) { + end_entry_and_advance_queue(sink, entry); } aki_mutex_unlock(&sink->mutex); - queue_cmd(sink, (struct camu_sink_cmd){ - .op = END, - .value.i = entry->sequence - }); + if (run_queue) { + queue_cmd(sink, (struct camu_sink_cmd){ + .op = END, + .value.i = entry->sequence + }); + } break; } } @@ -436,11 +476,8 @@ static void video_buffer_callback(void *userdata, u8 op) struct camu_sink *sink = entry->sink; switch (op) { case CAMU_BUFFER_BUFFERED: { - bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf); aki_mutex_lock(&sink->mutex); - if (!entry->ended || single_frame) { - add_video_if_set_and_buffered(entry); - } + add_video_if_set_and_buffered(entry); aki_mutex_unlock(&sink->mutex); break; } @@ -455,19 +492,13 @@ static void video_buffer_callback(void *userdata, u8 op) bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf); bool swapped = false; aki_mutex_lock(&sink->mutex); + al_log_info("sink", "Video EOF."); if (!single_frame) { remove_entry_video_buffer(sink, entry); } - // If this entry has no audio, advance the queue. - bool run_queue = !single_frame && BUFFER_EMPTY(&entry->audio); + bool run_queue = !single_frame && AUDIO_REMOVED_OR_EMPTY(entry); if (run_queue) { - camu_clock_end(&entry->clock); - if (sink->queued) { - set_or_queue_entry(sink->queued); - sink->current = sink->queued; - sink->queued = NULL; - swapped = true; - } + swapped = end_entry_and_advance_queue(sink, entry); } aki_mutex_unlock(&sink->mutex); if (!swapped) { @@ -528,7 +559,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str switch (stream->type) { case CAMU_STREAM_AUDIO: entry->audio.track = (struct lia_vcr_track *)opaque; - if (!camu_audio_buffer_configure(&entry->audio.buf, stream)) { + if (!camu_audio_buffer_configure(&entry->audio.buf, stream, sink->audio.mixer)) { lia_client_disconnect(&entry->client); } aki_mutex_lock(&sink->mutex); @@ -545,7 +576,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str #ifndef CAMU_SINK_NO_VIDEO case CAMU_STREAM_VIDEO: entry->video.track = (struct lia_vcr_track *)opaque; - if (!camu_video_buffer_configure(&entry->video.buf, stream)) { + if (!camu_video_buffer_configure(&entry->video.buf, stream, sink->video.renderer)) { lia_client_disconnect(&entry->client); } aki_mutex_lock(&sink->mutex); @@ -559,6 +590,20 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } aki_mutex_unlock(&sink->mutex); break; + case CAMU_STREAM_SUBTITLE: +#ifdef CAMU_HAVE_FFMPEG + camu_video_buffer_configure_subtitles(&entry->video.buf, stream->av.stream->codecpar); +#endif + break; + case CAMU_STREAM_ATTACHMENT: { +#ifdef CAMU_HAVE_FFMPEG + 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 + break; + } #endif } break; @@ -580,6 +625,18 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } break; } + case LIANA_CLIENT_SUBTITLE: { + switch (stream->type) { + case CAMU_STREAM_SUBTITLE: { +#ifdef CAMU_HAVE_FFMPEG + AVPacket *pkt = (AVPacket *)opaque; + camu_video_buffer_push_subtitle(&entry->video.buf, pkt); +#endif + break; + } + } + break; + } case LIANA_CLIENT_REMOVE_BUFFERS: { aki_mutex_lock(&sink->mutex); if (entry->audio.state == BUFFER_ADDED) { @@ -588,7 +645,8 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } #ifndef CAMU_SINK_NO_VIDEO bool reconnect = *(bool *)opaque; - bool keep_video = reconnect && camu_video_buffer_is_single_frame(&entry->video.buf); + bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf); + bool keep_video = reconnect && single_frame; if (!keep_video && 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; @@ -599,7 +657,7 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str while (keep_video ? entry_audio_buffer_held(entry) : entry_buffers_held(entry)) { BLOCKING_SLEEP(AKI_TS_FROM_USEC(2000)); } - if (!keep_video) camu_video_buffer_reset(&entry->video.buf); + if (reconnect && !single_frame) camu_video_buffer_reset(&entry->video.buf); #else while (entry_buffers_held(entry)) { BLOCKING_SLEEP(AKI_TS_FROM_USEC(2000)); @@ -634,8 +692,21 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str } case LIANA_CLIENT_CLOSED: { aki_mutex_lock(&sink->mutex); - if (entry == sink->current) sink->current = NULL; - if (entry == sink->queued) sink->queued = NULL; + if (entry == sink->target) { + if (sink->current) { + remove_entry_buffers(sink, sink->current); + sink->current = NULL; + } + sink->target = NULL; + } else if (entry == sink->current) { + sink->current = NULL; + if (sink->target) { + switch_to(sink, sink->target); + sink->target = NULL; + } + } else if (entry == sink->queued) { + sink->queued = NULL; + } lia_client_free(&entry->client); camu_audio_buffer_free(&entry->audio.buf); #ifndef CAMU_SINK_NO_VIDEO @@ -647,13 +718,13 @@ static void client_callback(void *userdata, u8 op, struct camu_codec_stream *str if (rentry == entry) { al_array_remove_at(sink->entries, i); removed = true; - al_log_debug("sink", "Entry closed by disconnect."); + al_log_info("sink", "Entry closed by disconnect."); break; } } al_free(entry); if (!removed) { - al_log_debug("sink", "Entry closed by cleanup."); + al_log_info("sink", "Entry closed by cleanup."); } aki_mutex_unlock(&sink->mutex); break; @@ -743,20 +814,6 @@ static void maybe_cleanup_old_entries(struct camu_sink *sink) } } -static void switch_to(struct camu_sink *sink, struct camu_sink_entry *entry) -{ - if (entry->ended || camu_clock_is_ended(&entry->clock)) { - if (sink->current) { - remove_entry_buffers(sink, sink->current); - } - } else { - if (sink->current) { - maybe_add_to_previous(sink, sink->current, entry); - } - } - set_or_queue_entry(entry); - sink->current = entry; -} static void clock_callback(void *userdata, u8 op) { @@ -790,13 +847,13 @@ static struct camu_sink_entry *get_entry_from_id(struct camu_sink *sink, u16 id, entry->audio.state = BUFFER_INIT; entry->audio.armed = false; - camu_audio_buffer_init(&entry->audio.buf, &entry->clock, sink->audio.mixer); + camu_audio_buffer_init(&entry->audio.buf, &entry->clock); entry->audio.buf.callback = audio_buffer_callback; entry->audio.buf.userdata = entry; #ifndef CAMU_SINK_NO_VIDEO entry->video.state = BUFFER_INIT; - camu_video_buffer_init(&entry->video.buf, &entry->clock, sink->video.renderer); + camu_video_buffer_init(&entry->video.buf, &entry->clock); entry->video.buf.callback = video_buffer_callback; entry->video.buf.userdata = entry; #endif @@ -842,6 +899,7 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn u64 seek_pos = aki_packet_read_u64(packet); u8 pause = aki_packet_read_u8(packet); bool ended = aki_packet_read_bool(packet); + (void)ended; bool created; struct camu_sink_entry *entry = get_entry_from_id(sink, node_id, &created); @@ -851,9 +909,6 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn if (created) { camu_clock_set(&entry->clock, seek_pos / 1000000.0); - if (ended) { - camu_clock_end(&entry->clock); - } struct camu_renderer *renderer = NULL; #ifndef CAMU_SINK_NO_VIDEO renderer = sink->video.renderer; @@ -861,8 +916,6 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn lia_client_connect(&entry->client, sink->loop, sink->type, &addr, port, node_id, seek_pos, renderer); } - entry->ended = ended; - if (op == LIANA_SINK_BUFFER) { goto out; } else if (op == LIANA_SINK_BUFFER_AND_QUEUE) { @@ -890,27 +943,25 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn al_log_warn("sink", "Ignoring target on NONE."); sink->target = NULL; } - if (sink->current) { - maybe_add_to_previous(sink, sink->current, entry); + if (entry == sink->current) { + break; } - set_or_queue_entry(entry); - sink->current = entry; + switch_to(sink, entry); break; case LIANA_PAUSE_RESUME: if (sink->target) { al_log_warn("sink", "Ignoring target on RESUME."); sink->target = NULL; } + camu_clock_resume(&entry->clock, at); if (entry == sink->current) { camu_audio_buffer_unpause(&entry->audio.buf); - } else if (sink->current) { - maybe_add_to_previous(sink, sink->current, entry); + break; } - camu_clock_resume(&entry->clock, at); - set_or_queue_entry(entry); - sink->current = entry; + switch_to(sink, entry); break; case LIANA_PAUSE_PAUSE: { + // For target to be set that must mean that current is set and armed to pause. struct camu_sink_entry *prev_target = sink->target; if (prev_target) { if (prev_target == entry) { @@ -919,9 +970,8 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn sink->target = entry; } camu_clock_pause(&prev_target->clock, at); - } else if (sink->current) { - if (camu_clock_is_ended(&sink->current->clock)) { - // Server thought we weren't done, be we are. + } else { + if (!sink->current || camu_clock_is_ended(&sink->current->clock)) { switch_to(sink, entry); } else { sink->target = entry; @@ -941,8 +991,8 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn camu_audio_buffer_unpause(&entry->audio.buf); } camu_clock_pause(&prev_target->clock, at); - } else if (sink->current) { - if (camu_clock_is_ended(&sink->current->clock)) { + } else { + if (!sink->current || camu_clock_is_ended(&sink->current->clock)) { switch_to(sink, entry); } else { sink->target = entry; @@ -1024,7 +1074,6 @@ static bool seek_command_callback(void *userdata, struct aki_rpc_connection *con if (!current) goto out; if (current->sequence == sequence) { - current->ended = false; #ifdef CAMU_SINK_LOCAL (void)at; lia_client_seek(¤t->client, pos, 0); diff --git a/src/libsink/sink.h b/src/libsink/sink.h index 5feadf5..18ef134 100644 --- a/src/libsink/sink.h +++ b/src/libsink/sink.h @@ -42,7 +42,6 @@ struct camu_sink_entry { u16 lru; struct camu_clock clock; struct lia_client client; - bool ended; struct { u8 state; bool armed; diff --git a/src/mixer/audio_miniaudio.c b/src/mixer/audio_miniaudio.c index 4cb67de..beda5f1 100644 --- a/src/mixer/audio_miniaudio.c +++ b/src/mixer/audio_miniaudio.c @@ -75,10 +75,16 @@ 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; ma->context_config.pulse.tryAutoSpawn = MA_TRUE; +#else + (void)name; +#endif s32 ret = ma_context_init(backends, AL_ARRAY_SIZE(backends), &ma->context_config, &ma->context); if (ret != MA_SUCCESS) { al_log_error("audio_miniaudio", "Failed to initialize context (%d).", ret); @@ -171,8 +177,15 @@ 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; +#ifdef MA_ENABLE_WASAPI ma->config.wasapi.noAutoConvertSRC = MA_FALSE; ma->config.wasapi.noDefaultQualitySRC = MA_FALSE; +#endif +#ifdef MA_ENABLE_AAUDIO + ma->config.aaudio.usage = ma_aaudio_usage_media; + ma->config.aaudio.contentType = ma_aaudio_content_type_music; + ma->config.aaudio.allowedCapturePolicy = ma_aaudio_allow_capture_by_all; +#endif s32 ret = ma_device_init(&ma->context, &ma->config, &ma->device); if (ret != MA_SUCCESS) { al_log_error("audio_miniaudio", "Failed to initialize device (%d).", ret); @@ -214,7 +227,9 @@ static void audio_miniaudio_free(struct camu_audio **audio) struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)*audio; ma_device_uninit(&ma->device); ma_context_uninit(&ma->context); +#ifdef MA_ENABLE_PULSEAUDIO al_free((void *)ma->context_config.pulse.pApplicationName); +#endif al_free(ma); *audio = NULL; } diff --git a/src/mixer/audio_null.c b/src/mixer/audio_null.c index ac2846a..a6eeea3 100644 --- a/src/mixer/audio_null.c +++ b/src/mixer/audio_null.c @@ -14,7 +14,9 @@ static void audio_null_pick_format(struct camu_audio *audio, struct camu_resampl (void)na; fmt->req.format = fmt->in.format; fmt->req.channel_count = fmt->in.channel_count; +#ifdef CAMU_HAVE_FFMPEG av_channel_layout_default(&fmt->req.channel_layout, fmt->in.channel_count); +#endif fmt->req.sample_rate = fmt->in.sample_rate; } diff --git a/src/render/meson.build b/src/render/meson.build index 74d323e..c2bcfd7 100644 --- a/src/render/meson.build +++ b/src/render/meson.build @@ -59,4 +59,11 @@ elif get_option('renderer') == 'libplacebo' render_args += ['-DCAMU_RENDERER_LIBPLACEBO'] endif +libass_opts = [] +if not is_windows + libass_opts += ['fontconfig=enabled'] +endif +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 bd5ed7b..a95afc4 100644 --- a/src/render/queue_libplacebo.c +++ b/src/render/queue_libplacebo.c @@ -101,12 +101,84 @@ static void queue_lp_push(struct camu_frame_queue *queue, struct camu_codec_fram } #ifdef CAMU_HAVE_FFMPEG +static struct camu_overlay_lp *create_subtitle_overlay(pl_gpu gpu, ASS_Image *ass_frame) +{ + struct camu_overlay_lp *overlay = al_alloc_object(struct camu_overlay_lp); + for (; ass_frame; ass_frame = ass_frame->next) { + pl_tex tex = pl_tex_create(gpu, pl_tex_params( + .w = ass_frame->w, + .h = ass_frame->h, + .format = pl_find_named_fmt(gpu, "r8"), + .sampleable = true, + .host_writable = true + )); + + bool ok = pl_tex_upload(gpu, pl_tex_transfer_params( + .tex = tex, + .row_pitch = ass_frame->stride, + .ptr = ass_frame->bitmap + )); + if (!ok) { + al_log_error("frame_queue_libplacebo", "Failed to upload subtitle bitmap."); + continue; + } + + struct pl_overlay *current = NULL; + if (overlay->num >= overlay->alloc) { + s32 old_alloc = overlay->alloc; + overlay->alloc = old_alloc ? al_next_power_of_two(overlay->alloc + 1) : 8; + if (!overlay->overlays) { + overlay->overlays = al_malloc(sizeof(struct pl_overlay) * overlay->alloc); + } else { + overlay->overlays = al_realloc(overlay->overlays, sizeof(struct pl_overlay) * overlay->alloc); + } + for (s32 i = old_alloc; i < overlay->alloc; i++) { + current = (struct pl_overlay *)&overlay->overlays[i]; + current->parts = al_malloc(sizeof(struct pl_overlay_part) * 1); + current->num_parts = 0; + } + } + + // https://github.com/mpv-player/mpv/blob/70aaba71d6e3071a732069a1d222d1eb4293faf2/video/out/vo_gpu_next.c#L289 + // 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->dst = (pl_rect2df){ + ass_frame->dst_x, + ass_frame->dst_y, + ass_frame->dst_x + ass_frame->w, + ass_frame->dst_y + ass_frame->h + }; + u32 c = ass_frame->color; + part->color[0] = (c >> 24) / 255.0; + part->color[1] = ((c >> 16) & 0xFF) / 255.0; + part->color[2] = ((c >> 8) & 0xFF) / 255.0; + part->color[3] = 1.0 - (c & 0xFF) / 255.0; + + current->tex = tex; + //pl_color_space_from_avframe(¤t->color, frame); + current->color = *pl_color_space( + .primaries = PL_COLOR_PRIM_BT_709, + .transfer = PL_COLOR_TRC_SRGB + ); + current->mode = PL_OVERLAY_MONOCHROME; + current->repr = pl_color_repr_unknown; + //current->repr.sys = pl_system_from_av(frame->colorspace); + //current->repr.levels = pl_levels_from_av(frame->color_range); + current->repr.alpha = PL_ALPHA_INDEPENDENT; + current->coords = PL_OVERLAY_COORDS_SRC_FRAME; + current->num_parts = 1; + } + return overlay; +} + static bool map_av_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src, struct pl_frame *out_frame) { AVFrame *frame = src->frame_data; - struct camu_video_buffer *buf = frame->opaque; - AVStream *stream = buf->stream->av.stream; + struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)frame->opaque; + AVStream *stream = lq->q.buf->stream->av.stream; bool ok = pl_map_avframe_ex(gpu, out_frame, pl_avframe_params( .frame = frame, @@ -114,6 +186,27 @@ static bool map_av_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame * .map_dovi = false )); + s64 now = av_rescale_q(frame->best_effort_timestamp, stream->time_base, (AVRational){ 1, 1000 }); + s32 change; + ASS_Image *ass_frame = ass_render_frame(lq->ass_renderer, lq->ass_track, now, &change); + if (ass_frame) { + struct camu_overlay_lp *overlay = NULL; + if (lq->overlays.size > 0) { + overlay = al_array_last(lq->overlays); + } + if (!overlay || change > 0) { + overlay = create_subtitle_overlay(gpu, ass_frame); + overlay->lq = lq; + al_array_push(lq->overlays, overlay); + } + overlay->ref++; + out_frame->overlays = overlay->overlays; + out_frame->num_overlays = overlay->num; + ((struct pl_source_frame *)src)->frame_data = overlay; + } else { + ((struct pl_source_frame *)src)->frame_data = NULL; + } + av_frame_free(&frame); if (!ok) { @@ -128,7 +221,27 @@ static bool map_av_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame * static void unmap_av_frame(pl_gpu gpu, struct pl_frame *frame, const struct pl_source_frame *src) { - (void)src; + if (src->frame_data) { + struct camu_overlay_lp *overlay = (struct camu_overlay_lp *)src->frame_data; + struct camu_frame_queue_lp *lq = overlay->lq; + if (!--overlay->ref) { + for (s32 i = 0; i < overlay->alloc; i++) { + struct pl_overlay *current = &overlay->overlays[i]; + if (current->num_parts > 0) { + pl_tex_destroy(gpu, ¤t->tex); + } + al_free((struct pl_overlay_part *)current->parts); + } + struct camu_overlay_lp *roverlay; + al_array_foreach(lq->overlays, i, roverlay) { + if (roverlay == overlay) { + al_array_remove_at(lq->overlays, i); + break; + } + } + al_free(overlay); + } + } pl_unmap_avframe(gpu, frame); } @@ -142,10 +255,10 @@ static void discard_av_frame(const struct pl_source_frame *src) static void queue_lp_push_av_frame(struct camu_frame_queue *queue, AVFrame *frame, f64 pts) { struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue; - struct camu_video_buffer *buf = frame->opaque; + frame->opaque = lq; pl_queue_push(lq->queue, &(struct pl_source_frame){ .pts = pts, - .duration = buf->avg_frame_duration, + .duration = lq->q.buf->avg_frame_duration, .map = map_av_frame, .unmap = unmap_av_frame, .discard = discard_av_frame, @@ -153,6 +266,22 @@ static void queue_lp_push_av_frame(struct camu_frame_queue *queue, AVFrame *fram .first_field = pl_field_from_avframe(frame) }); } + +static void queue_lp_configure_subtitiles(struct camu_frame_queue *queue, s32 width, s32 height, AVCodecParameters *codecpar) +{ + struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue; + ass_set_frame_size(lq->ass_renderer, width, height); + ass_set_storage_size(lq->ass_renderer, width, height); + ass_set_fonts(lq->ass_renderer, NULL, NULL, ASS_FONTPROVIDER_AUTODETECT, NULL, 0); + ass_process_codec_private(lq->ass_track, (const char *)codecpar->extradata, + codecpar->extradata_size); +} + +static void queue_lp_push_subtitle(struct camu_frame_queue *queue, AVPacket *pkt) +{ + struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue; + ass_process_chunk(lq->ass_track, (const char *)pkt->data, pkt->size, pkt->pts, pkt->duration); +} #endif static void queue_lp_flush(struct camu_frame_queue *queue) @@ -170,10 +299,11 @@ static s32 queue_lp_count(struct camu_frame_queue *queue) static u8 queue_lp_read(struct camu_frame_queue *queue, f64 pts, void *out) { struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue; + struct pl_frame_mix *mix = (struct pl_frame_mix *)out; lq->params.pts = pts; - switch (pl_queue_update(lq->queue, (struct pl_frame_mix *)out, &lq->params)) { + switch (pl_queue_update(lq->queue, mix, &lq->params)) { case PL_QUEUE_OK: - return CAMU_QUEUE_OK; + break; case PL_QUEUE_MORE: return CAMU_QUEUE_MORE; case PL_QUEUE_EOF: @@ -182,6 +312,7 @@ static u8 queue_lp_read(struct camu_frame_queue *queue, f64 pts, void *out) default: return CAMU_QUEUE_ERR; } + return CAMU_QUEUE_OK; } static void queue_lp_reset(struct camu_frame_queue *queue) @@ -194,6 +325,8 @@ static void queue_lp_reset(struct camu_frame_queue *queue) static void queue_lp_free(struct camu_frame_queue **queue) { struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)*queue; + ass_free_track(lq->ass_track); + ass_renderer_done(lq->ass_renderer); pl_queue_destroy(&lq->queue); al_free(lq); *queue = NULL; @@ -202,9 +335,12 @@ static void queue_lp_free(struct camu_frame_queue **queue) struct camu_frame_queue *camu_frame_queue_lp_create(void) { struct camu_frame_queue_lp *lq = al_alloc_object(struct camu_frame_queue_lp); + al_array_init(lq->overlays); lq->q.push = queue_lp_push; #ifdef CAMU_HAVE_FFMPEG lq->q.push_av_frame = queue_lp_push_av_frame; + lq->q.configure_subtitles = queue_lp_configure_subtitiles; + lq->q.push_subtitle = queue_lp_push_subtitle; #endif lq->q.flush = queue_lp_flush; lq->q.count = queue_lp_count; diff --git a/src/render/queue_libplacebo.h b/src/render/queue_libplacebo.h index 829ef87..3b403e5 100644 --- a/src/render/queue_libplacebo.h +++ b/src/render/queue_libplacebo.h @@ -4,11 +4,23 @@ #include <libplacebo/utils/frame_queue.h> #include <libplacebo/utils/upload.h> +#include <libass/ass.h> + +struct camu_overlay_lp { + struct pl_overlay *overlays; + s32 alloc; + s32 num; + s16 ref; + struct camu_frame_queue_lp *lq; +}; struct camu_frame_queue_lp { struct camu_frame_queue q; pl_queue queue; struct pl_queue_params params; + ASS_Renderer *ass_renderer; + ASS_Track *ass_track; + array(struct camu_overlay_lp *) overlays; }; struct camu_frame_queue *camu_frame_queue_lp_create(void); diff --git a/src/render/renderer.h b/src/render/renderer.h index dcd74c1..b304fdf 100644 --- a/src/render/renderer.h +++ b/src/render/renderer.h @@ -12,8 +12,8 @@ #define CAMU_RENDERER_VULKAN #endif -struct camu_screen; struct camu_frame_queue; +struct camu_screen; struct camu_renderer { bool (*create_renderer)( struct camu_renderer *, s32 *, s32 *, diff --git a/src/render/renderer_libplacebo.c b/src/render/renderer_libplacebo.c index ae341f3..480da0b 100644 --- a/src/render/renderer_libplacebo.c +++ b/src/render/renderer_libplacebo.c @@ -161,6 +161,8 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, s32 *wid lr->r.opaque = &lr->gpu; #endif + lr->ass = ass_library_init(); + return true; err: lr->r.free((struct camu_renderer **)&lr); @@ -177,6 +179,8 @@ static struct camu_frame_queue *renderer_lp_create_queue(struct camu_renderer *r lq->params.interpolation_threshold = 0.01f; lq->params.radius = pl_frame_mix_radius(&lr->params); lq->params.timeout = 0uL; // vsync + lq->ass_renderer = ass_renderer_init(lr->ass); + lq->ass_track = ass_new_track(lr->ass); return queue; } @@ -208,19 +212,22 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree while (scr->videos.size > 0) { bool any_eof = false; al_array_foreach_ptr(scr->videos, i, video) { - if (camu_video_buffer_read(video->buf, &mix) && mix.frames) { - target.crop = mix.frames[0]->crop; - target.crop.x1 *= video->view.zoom / video->view.stretch; - target.crop.y1 *= video->view.zoom * video->view.stretch; - target.crop.x0 += video->view.x_offset; - target.crop.y0 += video->view.y_offset; - target.crop.x1 += video->view.x_offset; - target.crop.y1 += video->view.y_offset; - target.rotation = video->view.rotation; - //lr->params.color_adjustment = pl_color_adjustment( - // .saturation = 0.0 - //); - pl_render_image_mix(lr->renderer, &mix, &target, &lr->params); + if (camu_video_buffer_read(video->buf, &mix)) { + // QUEUE_MORE doesn't return a frame obviously but don't treat it like an EOF. + if (mix.frames) { + target.crop = mix.frames[0]->crop; + target.crop.x1 *= video->view.zoom / video->view.stretch; + target.crop.y1 *= video->view.zoom * video->view.stretch; + target.crop.x0 += video->view.x_offset; + target.crop.y0 += video->view.y_offset; + target.crop.x1 += video->view.x_offset; + target.crop.y1 += video->view.y_offset; + target.rotation = video->view.rotation; + //lr->params.color_adjustment = pl_color_adjustment( + // .saturation = 0.0 + //); + pl_render_image_mix(lr->renderer, &mix, &target, &lr->params); + } } else { any_eof = true; } @@ -250,6 +257,7 @@ void renderer_lp_free(struct camu_renderer **renderer) if (lr->gl) pl_opengl_destroy(&lr->gl); #endif if (lr->logger) pl_log_destroy(&lr->logger); + if (lr->ass) ass_library_done(lr->ass); al_free(lr); *renderer = NULL; } diff --git a/src/render/renderer_libplacebo.h b/src/render/renderer_libplacebo.h index 80f4688..5b206c6 100644 --- a/src/render/renderer_libplacebo.h +++ b/src/render/renderer_libplacebo.h @@ -4,6 +4,7 @@ #include <libplacebo/renderer.h> #include <libplacebo/shaders/lut.h> #include <libplacebo/colorspace.h> +#include <libass/ass.h> #include "renderer.h" @@ -27,6 +28,8 @@ struct camu_renderer_lp { pl_swapchain swapchain; pl_renderer renderer; struct pl_render_params params; + ASS_Library *ass; + ASS_Renderer *ass_renderer; }; struct camu_renderer *camu_renderer_lp_create(void); |