summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/buffer/audio.c427
-rw-r--r--src/buffer/audio.h36
-rw-r--r--src/buffer/clock.c151
-rw-r--r--src/buffer/clock.h56
-rw-r--r--src/buffer/frame_queue.h2
-rw-r--r--src/buffer/peak_buffer.c12
-rw-r--r--src/buffer/peak_buffer.h5
-rw-r--r--src/buffer/video.c74
-rw-r--r--src/buffer/video.h29
-rw-r--r--src/buffer/volume.h36
-rw-r--r--src/cache/backing.h13
-rw-r--r--src/cache/backings/file.c27
-rw-r--r--src/cache/backings/file.h4
-rw-r--r--src/cache/backings/file_common.c1
-rw-r--r--src/cache/backings/file_mapped.c39
-rw-r--r--src/cache/backings/memory.c27
-rw-r--r--src/cache/entry.c15
-rw-r--r--src/cache/entry.h9
-rw-r--r--src/cache/handle.c37
-rw-r--r--src/cache/handle.h2
-rw-r--r--src/cache/handler.h13
-rw-r--r--src/cache/handlers/cdio.c206
-rw-r--r--src/cache/handlers/cdio.h21
-rw-r--r--src/cache/handlers/file.c8
-rw-r--r--src/cache/handlers/file.h1
-rw-r--r--src/cache/handlers/http.c153
-rw-r--r--src/cache/handlers/http.h14
-rw-r--r--src/cache/meson.build2
-rw-r--r--src/cache/range.h23
-rw-r--r--src/cache/threaded_waits.c126
-rw-r--r--src/cache/threaded_waits.h15
-rw-r--r--src/cache/wait.h10
-rw-r--r--src/codec/codec.h260
-rw-r--r--src/codec/common.h12
-rw-r--r--src/codec/ffmpeg/common.c32
-rw-r--r--src/codec/ffmpeg/common.h6
-rw-r--r--src/codec/ffmpeg/decoder.c60
-rw-r--r--src/codec/ffmpeg/decoder.h8
-rw-r--r--src/codec/ffmpeg/demuxer.c57
-rw-r--r--src/codec/ffmpeg/demuxer.h1
-rw-r--r--src/codec/ffmpeg/encoder.c126
-rw-r--r--src/codec/ffmpeg/encoder.h7
-rw-r--r--src/codec/ffmpeg/packet_ext.c38
-rw-r--r--src/codec/ffmpeg/packet_ext.h2
-rw-r--r--src/codec/ffmpeg/resampler.c173
-rw-r--r--src/codec/ffmpeg/resampler.h34
-rw-r--r--src/codec/meson.build126
-rw-r--r--src/codec/spng/decoder.h3
-rw-r--r--src/codec/spng/impl.c59
-rw-r--r--src/codec/stb_image/decoder.h3
-rw-r--r--src/codec/stb_image/impl.c75
-rw-r--r--src/codec/wuffs/decoder.h3
-rw-r--r--src/codec/wuffs/impl.c110
-rw-r--r--src/fruits/cmc/cmc.c128
-rw-r--r--src/fruits/cmc/cmc.h27
-rw-r--r--src/fruits/cmc/cmc.py17
-rw-r--r--src/fruits/cmc/meson.build15
-rw-r--r--src/fruits/cmc/tui/pager.c88
-rw-r--r--src/fruits/cmc/tui/pager.h33
-rw-r--r--src/fruits/cmc/tui/pane_lists.c93
-rw-r--r--src/fruits/cmc/tui/pane_lists.h20
-rw-r--r--src/fruits/cmc/tui/pane_search.c335
-rw-r--r--src/fruits/cmc/tui/pane_search.h29
-rw-r--r--src/fruits/cmc/tui/resources.c85
-rw-r--r--src/fruits/cmc/tui/resources.h31
-rw-r--r--src/fruits/cmc/tui/ui.c144
-rw-r--r--src/fruits/cmc/tui/ui.h40
-rw-r--r--src/fruits/cmc/tui/view.h24
-rw-r--r--src/fruits/cmc/tui/view_twitter.c370
-rw-r--r--src/fruits/cmc/tui/view_twitter.h35
-rw-r--r--src/fruits/cmsrv/cmsrv.c196
-rw-r--r--src/fruits/cmsrv/meson.build19
-rw-r--r--src/fruits/cmsrv/ui.c210
-rw-r--r--src/fruits/cmsrv/ui.h44
-rw-r--r--src/fruits/cmv/cmv.c151
-rw-r--r--src/fruits/cmv/meson.build25
-rw-r--r--src/fruits/sink/meson.build15
-rw-r--r--src/fruits/sink/sink.c208
-rw-r--r--src/liana/client.c199
-rw-r--r--src/liana/client.h31
-rw-r--r--src/liana/common.h1
-rw-r--r--src/liana/handler.h58
-rw-r--r--src/liana/handlers.c37
-rw-r--r--src/liana/handlers.h13
-rw-r--r--src/liana/handlers/cdio.h18
-rw-r--r--src/liana/handlers/cdio_client.c49
-rw-r--r--src/liana/handlers/cdio_server.c106
-rw-r--r--src/liana/handlers/codec.h19
-rw-r--r--src/liana/handlers/codec_client.c116
-rw-r--r--src/liana/handlers/codec_server.c143
-rw-r--r--src/liana/handlers/dvd.h14
-rw-r--r--src/liana/handlers/dvd_server.c (renamed from src/shrub/handlers/dvd_server.c)12
-rw-r--r--src/liana/list.c413
-rw-r--r--src/liana/list.h107
-rw-r--r--src/liana/list_cmp.h53
-rw-r--r--src/liana/meson.build36
-rw-r--r--src/liana/server.c345
-rw-r--r--src/liana/server.h42
-rw-r--r--src/liana/vcr.c232
-rw-r--r--src/liana/vcr.h46
-rw-r--r--src/libclient/client.c239
-rw-r--r--src/libclient/client.h60
-rw-r--r--src/libclient/meson.build4
-rw-r--r--src/libclient/resource_client.c87
-rw-r--r--src/libclient/resource_client.h29
-rw-r--r--src/libsink/common.h8
-rw-r--r--src/libsink/meson.build2
-rw-r--r--src/libsink/sink.c791
-rw-r--r--src/libsink/sink.h32
-rw-r--r--src/list/list.c257
-rw-r--r--src/list/list.h57
-rw-r--r--src/list/meson.build5
-rw-r--r--src/mixer/audio.h4
-rw-r--r--src/mixer/audio_miniaudio.c70
-rw-r--r--src/mixer/audio_miniaudio.h6
-rw-r--r--src/mixer/audio_null.c67
-rw-r--r--src/mixer/audio_null.h7
-rw-r--r--src/mixer/meson.build45
-rw-r--r--src/mixer/mixer.c109
-rw-r--r--src/mixer/mixer.h16
-rwxr-xr-xsrc/portal/cpy/build.sh2
-rw-r--r--src/portal/cpy/portal.pyx8
-rw-r--r--src/portal/cpy/post.pxd6
-rw-r--r--src/portal/meson.build4
-rw-r--r--src/portal/patches/snscrape_auth.diff2
-rw-r--r--src/portal/py/base.py25
-rw-r--r--src/portal/py/modules/__init__.py16
-rw-r--r--src/portal/py/modules/common.py2
-rw-r--r--src/portal/py/modules/fanbox.py32
-rw-r--r--src/portal/py/modules/instagram.py10
-rw-r--r--src/portal/py/modules/patreon.py104
-rw-r--r--src/portal/py/modules/pixiv_app.py12
-rw-r--r--src/portal/py/modules/pixiv_web.py25
-rw-r--r--src/portal/py/modules/twitter.py6
-rw-r--r--src/portal/py/modules/youtube.py118
-rw-r--r--src/portal/py/post.py116
-rw-r--r--src/portal/py/test.py1
-rw-r--r--src/portal/py/tests/__init__.py0
-rw-r--r--src/portal/py/tests/archive_query.py (renamed from src/portal/tests/archive_query.py)21
-rw-r--r--src/portal/py/tests/logger.py (renamed from src/portal/tests/logger.py)0
-rw-r--r--src/portal/py/tests/old_twitter_api.py (renamed from src/portal/tests/old_twitter_api.py)0
-rw-r--r--src/portal/py/tests/rewrite_post.py (renamed from src/portal/tests/rewrite_post.py)0
-rw-r--r--src/portal/py/tests/test.py103
-rw-r--r--src/portal/py/tests/unescape_json.py (renamed from src/portal/tests/unescape_json.py)0
-rw-r--r--src/portal/requirements.txt4
-rwxr-xr-xsrc/portal/scripts/create_vendor.sh16
-rwxr-xr-xsrc/portal/scripts/run_py.sh3
-rw-r--r--src/portal/src/packet_ext.c8
-rw-r--r--src/portal/src/post.c4
-rw-r--r--src/portal/src/post.h13
-rw-r--r--src/portal/src/search.c32
-rw-r--r--src/portal/src/search.h16
-rw-r--r--src/portal/tests/test.py51
-rw-r--r--src/render/meson.build102
-rw-r--r--src/render/queue_libplacebo.c66
-rw-r--r--src/render/queue_tiger.c35
-rw-r--r--src/render/queue_tiger.h4
-rw-r--r--src/render/renderer.h9
-rw-r--r--src/render/renderer_libplacebo.c55
-rw-r--r--src/render/renderer_tiger.c38
-rw-r--r--src/render/renderer_tiger.h2
-rw-r--r--src/screen/meson.build10
-rw-r--r--src/screen/screen.c228
-rw-r--r--src/screen/screen.h16
-rw-r--r--src/server/common.c10
-rw-r--r--src/server/common.h47
-rw-r--r--src/server/db.c31
-rw-r--r--src/server/list.c13
-rw-r--r--src/server/list.h11
-rw-r--r--src/server/local_compat.c213
-rw-r--r--src/server/local_compat.h87
-rw-r--r--src/server/meson.build10
-rw-r--r--src/server/server.c433
-rw-r--r--src/server/server.h61
-rw-r--r--src/server/user.c0
-rw-r--r--src/server/user.h8
-rw-r--r--src/shrub/client.c283
-rw-r--r--src/shrub/client.h33
-rw-r--r--src/shrub/common.h26
-rw-r--r--src/shrub/handler.h59
-rw-r--r--src/shrub/handlers/cdio.h17
-rw-r--r--src/shrub/handlers/cdio_server.c21
-rw-r--r--src/shrub/handlers/codec.h20
-rw-r--r--src/shrub/handlers/codec_client.c102
-rw-r--r--src/shrub/handlers/codec_server.c140
-rw-r--r--src/shrub/handlers/dvd.h14
-rw-r--r--src/shrub/meson.build29
-rw-r--r--src/shrub/server.c401
-rw-r--r--src/shrub/server.h62
-rw-r--r--src/shrub/vcr.c177
-rw-r--r--src/shrub/vcr.h45
-rw-r--r--src/sink/desktop.c187
-rw-r--r--src/sink/desktop.h26
-rw-r--r--src/sink/meson.build3
-rw-r--r--src/util/color_palette.c15
-rw-r--r--src/util/meson.build3
196 files changed, 6873 insertions, 6195 deletions
diff --git a/src/buffer/audio.c b/src/buffer/audio.c
index 644be42..1bf42bf 100644
--- a/src/buffer/audio.c
+++ b/src/buffer/audio.c
@@ -1,169 +1,139 @@
#include <al/log.h>
+#ifdef CAMU_HAVE_FFMPEG
+#include "../codec/ffmpeg/resampler.h"
+#endif
+
#include "audio.h"
#include "common.h"
#include "common_internal.h"
+#include "volume.h"
-#define BUFFER_USEC (12 * 1000000L)
-#define BUFFER_WATERMARK_LOW (4 * 1000000L) // Must be a most half of the buffer size.
-#define BUFFER_WATERMARK_HIGH (3 * 1000000L)
+#define BUFFER_USEC (3 * 1000000L)
+#define BUFFER_MARK_MIN (1.1 * 1000000L) // Must be a most half of the buffer size.
+#define BUFFER_MARK_BUFFERED (1.0 * 1000000L)
-#define DESYNC_PTS 0.022
+#define LARGE_DESYNC_PTS 0.322
#ifdef CAMU_AUDIO_BUFFER_FADE
-#define FADE 1.0
-#define FADE_LENGTH(rate) (rate * 0.4)
-#define FADE_STEP(rate) (FADE / FADE_LENGTH(rate))
+#define FADE_STEP(fmt) (1.f / (fmt)->sample_rate)
#endif
enum {
- PAUSE_PRE = 0,
- PAUSE_PAUSED,
+ PAUSE_PAUSED = 0,
#ifdef CAMU_AUDIO_BUFFER_FADE
PAUSE_FADING,
#endif
PAUSE_PLAYING
};
-static bool setup_optimal_resampler(struct camu_audio_buffer *buf, struct camu_ff_resampler *resamp,
- struct camu_ff_resample_fmt *fmt)
+static void reset_buffer_state(struct camu_audio_buffer *buf)
{
- /*
- switch ((enum AVSampleFormat)fmt->in_format) {
- case AV_SAMPLE_FMT_S16P:
- fmt->req_format = AV_SAMPLE_FMT_S16;
- break;
- case AV_SAMPLE_FMT_S32P:
- fmt->req_format = AV_SAMPLE_FMT_S32;
- break;
- case AV_SAMPLE_FMT_DBL:
- case AV_SAMPLE_FMT_DBLP:
- case AV_SAMPLE_FMT_FLTP:
- fmt->req_format = AV_SAMPLE_FMT_FLT;
- break;
- case AV_SAMPLE_FMT_S16:
- case AV_SAMPLE_FMT_S32:
- case AV_SAMPLE_FMT_FLT:
- fmt->req_format = fmt->in_format;
- break;
- default:
- al_log_warn("audio_buffer", "Unhandled sample format.");
- break;
- }
-
- fmt->req_format = fmt->in_format;
- fmt->req_channel_count = fmt->in_channel_count;
- fmt->req_sample_rate = fmt->in_sample_rate;
- */
-
- buf->fade_rate = buf->fmt.req_sample_rate / 10;
- buf->bytes_per_sample = (s32)camu_ff_resample_fmt_bytes_per_sample(fmt);
-
- return camu_ff_resampler_init(resamp, fmt);
+ buf->pts = -1.0;
+ buf->pause = PAUSE_PAUSED;
+ buf->volume = 1.f;
+#ifdef CAMU_AUDIO_BUFFER_FADE
+ buf->fade_offset = 0;
+#endif
+ buf->buffered = false;
+ al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED);
+ 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)
{
- buf->mixer = mixer;
buf->clock = clock;
+ buf->mixer = mixer;
+ reset_buffer_state(buf);
buf->ignore_desync = false;
- al_atomic_size_t_store(&buf->continue_mark, 0, AL_ATOMIC_RELAXED);
- buf->buffered = false;
- al_atomic_u8_store(&buf->flow, FLOWING, AL_ATOMIC_RELAXED);
- buf->pts = -1.0;
- buf->pause = PAUSE_PRE;
-#ifdef CAMU_AUDIO_BUFFER_FADE
- buf->fade_period = 0;
- buf->fade_offset = 0;
-#endif
+ buf->latency = 0.0;
#ifdef CAMU_MIXER_THREADED
- al_atomic_bool_store(&buf->ref, false, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&buf->ref, 0, AL_ATOMIC_RELAXED);
#endif
return true;
}
-bool camu_audio_buffer_configure(struct camu_audio_buffer *buf, struct camu_stream *stream)
+bool camu_audio_buffer_configure(struct camu_audio_buffer *buf, struct camu_codec_stream *stream)
{
- buf->fmt.in_format = stream->av.stream->codecpar->format;
- av_channel_layout_copy(&buf->fmt.in_channel_layout, &stream->av.stream->codecpar->ch_layout);
- buf->fmt.in_channel_count = buf->fmt.in_channel_layout.nb_channels;
- buf->fmt.in_sample_rate = stream->av.stream->codecpar->sample_rate;
-
+ camu_audio_format_copy(&buf->fmt.in, &stream->audio.fmt);
camu_mixer_pick_format(buf->mixer, &buf->fmt);
- if (!setup_optimal_resampler(buf, &buf->resamp, &buf->fmt)) {
+ buf->fmt.resampler_needed = !camu_resampler_format_matches(&buf->fmt);
+ if (buf->fmt.resampler_needed) {
+#ifdef CAMU_HAVE_FFMPEG
+ buf->resamp = camu_ff_resampler_create();
+ if (!buf->resamp->init(buf->resamp, &buf->fmt)) {
+ return false;
+ }
+#else
return false;
+#endif
}
- const char *in_format_name = av_get_sample_fmt_name(buf->fmt.in_format);
- const char *req_format_name = av_get_sample_fmt_name(buf->fmt.req_format);
+ 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_layout.nb_channels, buf->fmt.in_sample_rate,
- req_format_name, buf->fmt.req_channel_layout.nb_channels, buf->fmt.req_sample_rate);
+ 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_ff_resample_fmt_usec_to_bytes(&buf->fmt, BUFFER_USEC);
+ buf->size = camu_audio_format_usec_to_bytes(&buf->fmt.req, BUFFER_USEC);
buf->data = (u8 *)al_malloc(buf->size);
al_ring_buffer_init(&buf->rb, buf->data, buf->size);
- buf->watermark.low = camu_ff_resample_fmt_usec_to_bytes(&buf->fmt, BUFFER_WATERMARK_LOW);
- buf->watermark.high = camu_ff_resample_fmt_usec_to_bytes(&buf->fmt, BUFFER_WATERMARK_HIGH);
- camu_peak_buffer_init(&buf->peak);
+ 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);
+ camu_peak_buffer_init(&buf->peak, 1024 * 16);
buf->stream = stream;
return true;
}
+void camu_audio_buffer_set_latency(struct camu_audio_buffer *buf, f64 latency)
+{
+ buf->latency = latency;
+}
+
#ifdef _DEBUG_
-#define BUFFER_OCCUPIED_DEBUG(buf) \
- camu_ff_resample_fmt_bytes_to_sec(&buf->fmt, al_ring_buffer_occupied(&buf->rb))
+#define OCCUPIED_SECONDS_DEBUG(buf) \
+ camu_audio_format_bytes_to_sec(&buf->fmt.req, al_ring_buffer_occupied(&buf->rb))
#else
-#define BUFFER_OCCUPIED_DEBUG(buf) 0
+#define OCCUPIED_SECONDS_DEBUG(buf) 0
#endif
-static bool push_internal(struct camu_audio_buffer *buf, u8 **data, s32 sample_count, f64 pts)
+static bool push_internal(struct camu_audio_buffer *buf, u8 *data, s32 sample_count)
{
- if (buf->fmt.resampler_needed) {
- sample_count = camu_ff_resampler_convert(&buf->resamp, (const u8 **)data, sample_count);
- data = camu_ff_resampler_get_data(&buf->resamp);
- }
- if (sample_count <= 0) return false;
-
- f64 duration = camu_ff_resample_fmt_samples_to_sec(&buf->fmt, sample_count);
- if (pts + duration < camu_clock_get_base_pts(buf->clock)) {
- return false;
- }
- if (buf->pts == -1.0) buf->pts = pts;
-
- size_t size, have = camu_ff_resample_fmt_samples_to_bytes(&buf->fmt, (size_t)sample_count);
size_t space = al_ring_buffer_space(&buf->rb);
- if (!buf->buffered && buf->size - space > buf->watermark.high) {
+ if (!buf->buffered && buf->size - space > buf->mark.buffered) {
+ al_log_debug("audio_buffer", "Buffered (mark: %.2fs).", OCCUPIED_SECONDS_DEBUG(buf));
buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED);
buf->buffered = true;
- al_log_debug("audio_buffer", "Buffered (watermark: %.2fs).", BUFFER_OCCUPIED_DEBUG(buf));
}
- size_t peak = buf->peak.buf.size;
- if (space < (have + peak)) {
- camu_peak_buffer_push(&buf->peak, data[0], have);
- if (peak >= buf->watermark.low) {
- // Discard the peak buffer. If this happens the writer of this buffer
- // is taking _way_ too long to stop.
- camu_peak_buffer_flush(&buf->peak, &size);
+
+ size_t have = camu_audio_format_samples_to_bytes(&buf->fmt.req, (size_t)sample_count);
+ size_t peak = camu_peak_buffer_size(&buf->peak);
+ if (space < have + peak) {
+ al_assert(have < buf->mark.min);
+ camu_peak_buffer_push(&buf->peak, data, have);
+ 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.");
+ camu_peak_buffer_flush(&buf->peak);
+ peak = 0;
}
- // Continuing based on an outdated continue mark value is safe as long as the
- // peak buffer is smaller than the low watermark and the low watermark is
- // less than or equal to half the buffer size.
- al_atomic_size_t_store(&buf->continue_mark, buf->watermark.low + peak, AL_ATOMIC_RELAXED);
+ al_assert(peak <= buf->mark.min);
+ // We adjust the min mark by the peak buffer size just for consistency.
+ al_atomic_store(size_t)(&buf->uncork_at, buf->mark.min - peak, AL_ATOMIC_RELAXED);
buf->callback(buf->userdata, CAMU_BUFFER_CORK);
return false;
}
if (peak > 0) {
- u8 *ptr = camu_peak_buffer_flush(&buf->peak, &size);
- al_ring_buffer_write(&buf->rb, ptr, size);
+ al_ring_buffer_write(&buf->rb, camu_peak_buffer_flush(&buf->peak), peak);
}
- al_ring_buffer_write(&buf->rb, data[0], have);
+ al_ring_buffer_write(&buf->rb, data, have);
return true;
}
@@ -171,23 +141,40 @@ static bool push_internal(struct camu_audio_buffer *buf, u8 **data, s32 sample_c
#ifdef CAMU_HAVE_FFMPEG
static void push_av_frame_internal(struct camu_audio_buffer *buf, AVFrame *frame)
{
- // It doesn't matter if there's a race here with flush(), as long
- // as any pushed frame is from the same stream.
- // A reset() must finish before any data is pushed.
- if (al_atomic_u8_load(&buf->flow, AL_ATOMIC_RELAXED) == FLOWING) {
- AVStream *stream = buf->stream->av.stream;
- f64 pts = (frame->best_effort_timestamp - stream->start_time) * av_q2d(stream->time_base);
- push_internal(buf, frame->data, frame->nb_samples, pts);
+ s32 sample_count = frame->nb_samples;
+ 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, frame->nb_samples);
+ if (pts + duration >= camu_clock_get_base_pts(buf->clock)) {
+ if (buf->pts == -1.0) buf->pts = pts;
+ u8 **data = frame->data;
+ if (buf->fmt.resampler_needed) {
+ sample_count = buf->resamp->convert(buf->resamp, (const u8 **)data, sample_count);
+ data = buf->resamp->get_data(buf->resamp);
+ }
+ if (sample_count > 0) push_internal(buf, data[0], sample_count);
}
av_frame_free(&frame);
}
#endif
-void camu_audio_buffer_push(struct camu_audio_buffer *buf, struct camu_frame *frame)
+// A reset() must finish before any data is pushed.
+void camu_audio_buffer_push(struct camu_audio_buffer *buf, struct camu_codec_frame *frame)
{
- switch (frame->type) {
- case CAMU_NORMAL:
+ al_assert(al_atomic_load(u8)(&buf->flow, AL_ATOMIC_RELAXED) == FLOWING);
+ switch (frame->mode) {
+ case CAMU_NORMAL: {
+ u8 *store[AV_NUM_DATA_POINTERS] = { 0 };
+ store[0] = frame->data;
+ u8 **data = store;
+ s32 sample_count = frame->audio.sample_count;
+ if (buf->fmt.resampler_needed) {
+ sample_count = buf->resamp->convert(buf->resamp, (const u8 **)store, sample_count);
+ data = buf->resamp->get_data(buf->resamp);
+ }
+ push_internal(buf, data[0], sample_count);
break;
+ }
#ifdef CAMU_HAVE_FFMPEG
case CAMU_FFMPEG_COMPAT: {
push_av_frame_internal(buf, frame->av.frame);
@@ -198,92 +185,56 @@ void camu_audio_buffer_push(struct camu_audio_buffer *buf, struct camu_frame *fr
al_free(frame);
}
+// Not thread-safe, must be called while the buffer is not being read from or written to.
void camu_audio_buffer_unpause(struct camu_audio_buffer *buf)
{
- buf->pause = PAUSE_PRE;
+ buf->pause = PAUSE_PAUSED;
}
-// Not thread-safe, must be called while the buffer is not being read from or written to.
+// Not thread-safe.
void camu_audio_buffer_reset(struct camu_audio_buffer *buf)
{
- al_atomic_size_t_store(&buf->continue_mark, 0, AL_ATOMIC_RELAXED);
- buf->buffered = false;
- al_atomic_u8_store(&buf->flow, FLOWING, AL_ATOMIC_RELAXED);
- buf->pts = -1.0;
- buf->pause = PAUSE_PRE;
+ 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)
{
+ if (buf->fmt.resampler_needed) {
+ s32 sample_count = buf->resamp->flush(buf->resamp);
+ if (sample_count > 0) {
+ u8 **data = buf->resamp->get_data(buf->resamp);
+ if (!push_internal(buf, data[0], sample_count)) {
+ al_log_debug("audio_buffer", "Buffer filled by resampler flush.");
+ }
+ }
+ }
if (!buf->buffered) {
+ al_log_debug("audio_buffer", "Buffered (mark: %.2fs).", OCCUPIED_SECONDS_DEBUG(buf));
buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED);
buf->buffered = true;
- al_log_debug("audio_buffer", "Buffered (watermark: %.2fs).", BUFFER_OCCUPIED_DEBUG(buf));
}
- al_atomic_u8_store(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED);
al_log_debug("audio_buffer", "Flush requested.");
}
-#ifdef CAMU_AUDIO_BUFFER_FADE
-#define MAX_BYTES 8
-#define TYPED_CAST(f, type) (f64)(*((type *)f))
-#define TYPED_CLAMP(f, type, min, max, ret) *((type *)ret) = (type)AL_CLAMP(f, min, max)
+#define NOT_PAUSED(pause) (pause != PAUSE_PAUSED)
-// This is obviously bad for optimization, should just make a function for each type.
-static void handle_fade(struct camu_audio_buffer *buf, u8 *data, size_t size, bool out)
-{
- u8 bytes[MAX_BYTES];
- u8 *ptr = bytes + (MAX_BYTES - buf->bytes_per_sample);
- f64 value;
- u32 sample_count = size / buf->bytes_per_sample;
- u32 channel_count = 2;
- for (u32 i = 0; i < sample_count; i++) {
- al_memcpy(ptr, data, buf->bytes_per_sample);
- switch (buf->bytes_per_sample) {
- case 2:
- value = TYPED_CAST(ptr, s16);
- break;
- case 4:
- value = TYPED_CAST(ptr, s32);
- break;
- }
- value *= (buf->volume);
- switch (buf->bytes_per_sample) {
- case 2:
- TYPED_CLAMP(value, s16, INT16_MIN, INT16_MAX, ptr);
- break;
- case 4:
- TYPED_CLAMP(value, s32, INT32_MIN, INT32_MAX, ptr);
- break;
- }
- al_memcpy(data, ptr, buf->bytes_per_sample);
- if ((i + 1) % channel_count == 0) {
- if (out) buf->volume = AL_CLAMP((buf->volume - FADE_STEP(buf->fade_rate)), 0.0, 1.0);
- else buf->volume = AL_CLAMP((buf->volume + FADE_STEP(buf->fade_rate)), 0.0, 1.0);
- if (buf->fade_period > 0) buf->fade_period--;
- }
- data += buf->bytes_per_sample;
- }
-}
-#endif
-
-#define NOT_PAUSED(pause) (pause != PAUSE_PAUSED && pause != PAUSE_PRE)
-
-// PAUSE_PAUSED means the last read was silence, meaning it's safe to skip.
-// The sole purpose of PAUSE_PRE is to avoid a fade in at the start of the stream.
+// 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)
{
if (camu_clock_is_paused(buf->clock)) {
#ifdef CAMU_AUDIO_BUFFER_FADE
if (buf->pause == PAUSE_PLAYING) {
- // Fade out. Signified by >0 fade_period and pause = PAUSE_FADING.
- buf->volume = FADE;
- buf->fade_period = FADE_LENGTH(buf->fade_rate);
- buf->fade_offset = 0;
buf->pause = PAUSE_FADING;
- } else if (buf->fade_period == 0) { // Fade out done.
+ buf->fade_offset = 0;
+ } else if (buf->volume == 0.f) { // Fade out done.
al_memset(data, 0, req);
if (NOT_PAUSED(buf->pause)) {
buf->pause = PAUSE_PAUSED;
@@ -292,12 +243,6 @@ size_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, size_t re
return req;
}
} else if (buf->pause == PAUSE_FADING || buf->pause == PAUSE_PAUSED) {
- // Fade in. Signified by >0 fade_period and pause = PAUSE_PLAYING.
- buf->volume = 1.0 - FADE;
- buf->fade_period = FADE_LENGTH(buf->fade_rate);
- buf->fade_offset = 0;
- // We don't want to consider sync if reversing an active
- // fade but we do if pause = PAUSE_PAUSED.
if (buf->pause == PAUSE_FADING) buf->pause = PAUSE_PLAYING;
#else
al_memset(data, 0, req);
@@ -308,66 +253,87 @@ size_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, size_t re
return req;
#endif
}
- f64 pts = camu_clock_get_pts(buf->clock, camu_mixer_get_latency(buf->mixer));
+ u8 flow = al_atomic_load(u8)(&buf->flow, AL_ATOMIC_ACQUIRE);
+ if (flow == SIGNALED) {
+ buf->callback(buf->userdata, CAMU_BUFFER_EOF);
+ return 0;
+ }
+ f64 pts = camu_clock_get_pts(buf->clock, buf->latency);
size_t ret, signal = req;
- size_t size = al_ring_buffer_occupied(&buf->rb);
- // Cut off fade if it's reaching too far.
- if (size < buf->fade_offset) size = 0;
- else size -= buf->fade_offset;
+ size_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 && buf->pause != PAUSE_PLAYING) {
#endif
pts -= buf->pts;
- // Possibly 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_PRE || buf->pause == PAUSE_PAUSED))) {
- if (UNLIKELY(fabs(pts) >= 0.322 && buf->pause != PAUSE_PRE)) {
+ // 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)) {
+ if (UNLIKELY(fabs(pts) >= LARGE_DESYNC_PTS)) {
al_log_warn("audio_buffer", "Abnormally large audio desync of %.5fs", pts);
}
- if (pts > 0.0) {
- ret = camu_ff_resample_fmt_sec_to_bytes(&buf->fmt, pts);
- ret = AL_MIN(ret, size);
- al_log_debug("audio_buffer", "Skipping %.5fs of audio (%zu bytes).", pts, ret);
+ if (pts > 0.0) { // Skip.
+ ret = camu_audio_format_sec_to_bytes(&buf->fmt.req, pts);
+ ret = AL_MIN(ret, have);
+ al_log_debug("audio_buffer", "Skipping %fs of audio (%zu bytes).", pts, ret);
ret = al_ring_buffer_discard(&buf->rb, ret);
- size -= ret;
- buf->pts += camu_ff_resample_fmt_bytes_to_sec(&buf->fmt, ret);
+ have -= ret;
+ buf->pts += camu_audio_format_bytes_to_sec(&buf->fmt.req, ret);
// Could go on to underrun.
- } else if (pts < 0.0) {
+ } else if (pts < 0.0) { // Delay.
pts = -pts;
- ret = camu_ff_resample_fmt_sec_to_bytes(&buf->fmt, pts);
+ ret = camu_audio_format_sec_to_bytes(&buf->fmt.req, pts);
ret = AL_MIN(ret, req);
- al_log_debug("audio_buffer", "Delaying audio by %.5fs (%zu bytes).", pts, ret);
+ al_log_debug("audio_buffer", "Delaying audio by %fs (%zu bytes).", pts, ret);
al_memset(data, 0, ret);
data += ret;
+ req -= ret;
// We can continue to delay.
- if ((req -= ret) == 0) return signal;
+ if (req == 0) return signal;
}
}
if (buf->pause != PAUSE_PLAYING) buf->pause = PAUSE_PLAYING;
#ifdef CAMU_AUDIO_BUFFER_FADE
}
#endif
- u8 flow = al_atomic_u8_load(&buf->flow, AL_ATOMIC_RELAXED);
- if (size < req) { // We don't have enough data to fulfill our request.
- if (flow == FLUSHED) {
- // If flow = FLUSHED, we know we've recieved all the data
- // this stream wants to send. Signal that we are done.
- signal = size;
- buf->callback(buf->userdata, CAMU_BUFFER_EOF);
- al_atomic_u8_store(&buf->flow, SIGNALED, AL_ATOMIC_RELAXED);
- al_log_debug("audio_buffer", "Flushed (signal: %zu).", signal);
+ 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.
+ ret = camu_peak_buffer_size(&buf->peak);
+ if (ret > 0) {
+ // This doesn't break single reader, single writer because
+ // pushing data after a flush is not allowed.
+ al_ring_buffer_write(&buf->rb, camu_peak_buffer_flush(&buf->peak), ret);
+ have += ret;
+ }
+ // Only signal EOF if what, if any, data from the peak buffer
+ // still isn't enough for this request.
+ if (have < req) {
+ buf->callback(buf->userdata, CAMU_BUFFER_EOF);
+ flow = SIGNALED;
+ al_atomic_store(u8)(&buf->flow, SIGNALED, AL_ATOMIC_RELEASE);
+ signal = have;
+ al_log_debug("audio_buffer", "Flushed (signal: %zu).", signal);
+ }
} else {
- // Put silence into the remainder of the request buffer.
- al_memset(data + size, 0, req - size);
+ // Silence remainder of the request.
+ al_memset(data + have, 0, req - have);
if (flow == FLOWING) {
- // Our stream hit an underrun because data wasn't coming in fast
- // enough. It's also possible to underrun if this function takes too
- // long which isn't checked here. That is unlikely though.
- al_log_warn("audio_buffer", "Underrun (req: %zu, have: %zu).", req, size);
+ // We hit an underrun because data wasn't coming in fast enough.
+ // An underrun can also happen in the audio output if read() (this function)
+ // takes too long. That isn't checked here.
+ al_log_warn("audio_buffer", "Underrun (req: %zu, have: %zu).", req, have);
+ // If we have data by the next read(), try to skip ahead to maintain sync.
+ // This might exacerbate the underrun issue but an underrun is already
+ // unexpected behavior, trying to stay in sync comes first.
+ buf->pause = PAUSE_PAUSED;
}
- // If flow = SIGNALED, we are just waiting to be removed or reset.
+ // If flow = SIGNALED, we are waiting to be removed or reset.
}
- req = size;
+ // The amount of available data could've increased in the flow = FLUSHED case.
+ req = AL_MIN(req, have);
}
if (req > 0) {
#ifdef CAMU_AUDIO_BUFFER_FADE
@@ -378,29 +344,38 @@ size_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, size_t re
buf->fade_offset += ret;
} else {
#endif
- // Read as much as we can from the ring buffer.
+ // Read as much as we determined we can.
ret = al_ring_buffer_read(&buf->rb, data, req);
- buf->pts += camu_ff_resample_fmt_bytes_to_sec(&buf->fmt, ret);
- if (flow == FLOWING) {
- ret = al_atomic_size_t_load(&buf->continue_mark, AL_ATOMIC_RELAXED);
- // Request to uncork if we are below the mark.
- if (ret && ((buf->size - size) - req) >= ret) {
- buf->callback(buf->userdata, CAMU_BUFFER_UNCORK);
- }
- }
+ al_assert(ret == req);
+ buf->pts += camu_audio_format_bytes_to_sec(&buf->fmt.req, ret);
#ifdef CAMU_AUDIO_BUFFER_FADE
}
- if (buf->fade_period > 0) handle_fade(buf, data, req, buf->pause == PAUSE_FADING);
+ f32 step = 0.f;
+ if (buf->pause == PAUSE_FADING) {
+ step = -FADE_STEP(&buf->fmt.req);
+ } else if (buf->volume < 1.f) {
+ step = FADE_STEP(&buf->fmt.req);
+ }
+ if (step != 0.f || buf->volume != 1.f) {
+ buf->volume = apply_volume(data, req, &buf->fmt.req, buf->volume, step);
+ }
#endif
}
- // Never return less than req, unless we hit the end of the stream.
+ // Check if we should request to uncork.
+ if (flow == FLOWING) {
+ ret = al_atomic_load(size_t)(&buf->uncork_at, AL_ATOMIC_RELAXED);
+ if (ret && have - req <= ret) {
+ buf->callback(buf->userdata, CAMU_BUFFER_UNCORK);
+ }
+ }
+ // To signal EOF, return less then req.
return signal;
}
void camu_audio_buffer_free(struct camu_audio_buffer *buf)
{
if (buf->fmt.resampler_needed) {
- camu_ff_resampler_close(&buf->resamp);
+ buf->resamp->free(&buf->resamp);
}
if (buf->data) al_free(buf->data);
camu_peak_buffer_free(&buf->peak);
diff --git a/src/buffer/audio.h b/src/buffer/audio.h
index c4ba142..3adccc7 100644
--- a/src/buffer/audio.h
+++ b/src/buffer/audio.h
@@ -4,45 +4,44 @@
#include <al/ring_buffer.h>
#include "../codec/codec.h"
-#include "../codec/ffmpeg/resampler.h"
#include "../mixer/mixer.h"
#include "clock.h"
#include "peak_buffer.h"
-//#define CAMU_AUDIO_BUFFER_FADE
+#define CAMU_AUDIO_BUFFER_FADE
struct camu_audio_buffer {
- struct camu_stream *stream;
+ struct camu_codec_stream *stream;
struct camu_mixer *mixer;
f64 pts;
u8 pause;
- bool ignore_desync;
struct camu_clock *clock;
+ bool ignore_desync;
+ f64 latency;
- f64 volume;
- s32 fade_period;
- size_t fade_offset;
- s32 fade_rate;
-
- s32 bytes_per_sample;
- struct camu_ff_resampler resamp;
- struct camu_ff_resample_fmt fmt;
+ struct camu_resampler_format fmt;
+ struct camu_resampler *resamp;
u8 *data;
size_t size;
struct al_ring_buffer rb;
- struct { size_t low, high; } watermark;
+ struct { size_t min, buffered; } mark;
bool buffered;
+ atomic(u8) flow;
+
struct camu_peak_buffer peak;
- atomic_size_t continue_mark;
+ atomic(size_t) uncork_at;
- atomic_u8 flow;
+ f32 volume;
+#ifdef CAMU_AUDIO_BUFFER_FADE
+ size_t fade_offset;
+#endif
#ifdef CAMU_MIXER_THREADED
- atomic_bool ref;
+ atomic(u8) ref;
#endif
void (*callback)(void *, u8);
@@ -50,8 +49,9 @@ struct camu_audio_buffer {
};
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_stream *stream);
-void camu_audio_buffer_push(struct camu_audio_buffer *buf, struct camu_frame *frame);
+bool camu_audio_buffer_configure(struct camu_audio_buffer *buf, struct camu_codec_stream *stream);
+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);
diff --git a/src/buffer/clock.c b/src/buffer/clock.c
index d26e4a8..c4a07a6 100644
--- a/src/buffer/clock.c
+++ b/src/buffer/clock.c
@@ -1,116 +1,117 @@
#include <al/lib.h>
+#include <aki/thread.h>
#include "clock.h"
-void camu_clock_set(struct camu_clock *clock, struct shrb_seek_req *req)
-{
- al_atomic_f64_store(&clock->tick, 0.0, AL_ATOMIC_RELAXED);
- if (req->paused) {
- clock->pause_at = req->paused_at / 1000000.0;
- al_atomic_f64_store(&clock->base, clock->pause_at, AL_ATOMIC_RELAXED);
- al_atomic_u64_store(&clock->start, 0Lu, AL_ATOMIC_RELAXED);
- } else {
- clock->pause_at = -1.0;
- al_atomic_f64_store(&clock->base, req->base / 1000000.0, AL_ATOMIC_RELAXED);
- al_atomic_u64_store(&clock->start, req->ts + req->delay, AL_ATOMIC_RELAXED);
- clock->ts = req->ts;
- }
- clock->offset = 0L;
- clock->delay = req->delay;
-}
+#define RUNNING 0.0
+#define PAUSED -DBL_MAX
+#define ENDED DBL_MAX
-f64 calc_tick_internal(struct camu_clock *clock)
+void camu_clock_init(struct camu_clock *clock, void (*callback)(void *, u8), void *userdata)
{
- s64 diff = clock->delay == 0Lu ? 0L
- : ((al_atomic_u64_load(&clock->start, AL_ATOMIC_ACQUIRE) + clock->offset)
- - aki_get_timestamp());
- f64 tick = aki_get_tick() - al_atomic_f64_load(&clock->base, AL_ATOMIC_RELAXED);
- //al_printf("DIFFERENCE: %f\n", diff / 1000000.0);
- tick += diff / 1000000.0;
- al_atomic_f64_store(&clock->tick, tick, AL_ATOMIC_RELAXED);
- al_atomic_u64_store(&clock->start, 0Lu, AL_ATOMIC_RELEASE);
- return tick;
+ clock->callback = callback;
+ clock->userdata = userdata;
}
-bool camu_clock_is_late(struct camu_clock *clock)
+static f64 calc_tick_offset(f64 tick, u64 now, u64 target)
{
- s64 diff = (s64)(al_atomic_u64_load(&clock->start, AL_ATOMIC_RELAXED)) - aki_get_timestamp();
- return clock->delay > 0L && diff < 0L;
+ if (now > target) return tick - (now - target) / 1000000.0;
+ else return tick + (target - now) / 1000000.0;
}
-void camu_clock_ready(struct camu_clock *clock)
+void camu_clock_set(struct camu_clock *clock, f64 base)
{
- if (clock->pause_at != -1.0) {
- clock->pause_at = -1.0;
- return;
- }
- camu_clock_resume(clock);
+ clock->base = base;
+ clock->offset = 0.0;
+ al_atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED);
+ al_atomic_store(f64)(&clock->pause, PAUSED, AL_ATOMIC_RELAXED);
+ clock->paused_at = 0.0;
}
-void camu_clock_offset(struct camu_clock *clock, s64 offset)
+void camu_clock_offset(struct camu_clock *clock, f64 amount)
{
- clock->offset += offset;
- //al_printf("OFFSET: %f\n", clock->offset / 1000000.0);
+ clock->offset += amount;
}
-void camu_clock_resume(struct camu_clock *clock)
+void camu_clock_pause(struct camu_clock *clock, u64 target)
{
- f64 tick = al_atomic_f64_load(&clock->tick, AL_ATOMIC_ACQUIRE);
- if (tick != 0.0) return;
- u64 start = al_atomic_u64_load(&clock->start, AL_ATOMIC_RELAXED);
- if (start != 0Lu) {
- // No longer report paused, calculate tick on first call to get_pts().
- al_atomic_f64_store(&clock->tick, -1.0, AL_ATOMIC_RELEASE);
+ f64 pause = al_atomic_load(f64)(&clock->pause, AL_ATOMIC_ACQUIRE);
+ if (pause == ENDED) return;
+ al_assert(clock->paused_at == -1.0);
+ f64 tick = aki_get_tick();
+ if (target > 0) {
+ tick = calc_tick_offset(tick, aki_get_timestamp(), target);
+ al_atomic_store(f64)(&clock->pause, tick, AL_ATOMIC_RELEASE);
} else {
- tick = al_atomic_f64_load(&clock->base, AL_ATOMIC_RELAXED);
- al_atomic_f64_store(&clock->tick, aki_get_tick() - tick, AL_ATOMIC_RELEASE);
+ al_atomic_store(f64)(&clock->pause, PAUSED, AL_ATOMIC_RELEASE);
}
- clock->pause_at = -1.0;
+ clock->paused_at = tick;
}
-void camu_clock_arm_resume(struct camu_clock *clock, u64 ts)
+void camu_clock_resume(struct camu_clock *clock, u64 target)
{
- al_atomic_u64_store(&clock->start, ts, AL_ATOMIC_RELAXED);
- al_atomic_f64_store(&clock->tick, -1.0, AL_ATOMIC_RELAXED);
- clock->pause_at = -1.0;
+ f64 pause = al_atomic_load(f64)(&clock->pause, AL_ATOMIC_ACQUIRE);
+ if (pause == ENDED) return;
+ al_assert(clock->paused_at != -1.0);
+ f64 tick = aki_get_tick();
+ //if (pause != PAUSED && tick < pause) {
+ // tick = pause;
+ //} else if (target > 0) {
+ if (target > 0) {
+ tick = calc_tick_offset(tick, aki_get_timestamp(), target);
+ al_printf("%f\n", tick);
+ }
+ if (clock->paused_at == 0.0) {
+ if (target == 0) {
+ // target = 0 can never be synced.
+ al_atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED);
+ } else {
+ al_atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED);
+ }
+ } else {
+ clock->offset += tick - clock->paused_at;
+ }
+ al_atomic_store(f64)(&clock->pause, RUNNING, AL_ATOMIC_RELEASE);
+ clock->paused_at = -1.0;
}
-void camu_clock_pause(struct camu_clock *clock, f64 base)
+bool camu_clock_is_paused(struct camu_clock *clock)
{
- f64 tick = al_atomic_f64_load(&clock->tick, AL_ATOMIC_ACQUIRE);
- if (tick == 0.0) return;
- if (base == -1.0) base = aki_get_tick() - tick;
- al_atomic_f64_store(&clock->tick, 0.0, AL_ATOMIC_RELEASE);
- clock->pause_at = base;
- al_atomic_f64_store(&clock->base, clock->pause_at, AL_ATOMIC_RELAXED);
+ return al_atomic_load(f64)(&clock->pause, AL_ATOMIC_RELAXED) == PAUSED;
}
-void camu_clock_arm_pause(struct camu_clock *clock, f64 pts)
+void camu_clock_end(struct camu_clock *clock)
{
- clock->pause_at = pts;
+ al_atomic_store(f64)(&clock->pause, ENDED, AL_ATOMIC_RELAXED);
}
-bool camu_clock_is_paused(struct camu_clock *clock)
+bool camu_clock_is_ended(struct camu_clock *clock)
{
- f64 tick = al_atomic_f64_load(&clock->tick, AL_ATOMIC_RELAXED);
- if (tick == 0.0) return true;
- if (clock->pause_at >= 0.0 && aki_get_tick() - tick >= clock->pause_at) {
- al_atomic_f64_store(&clock->base, clock->pause_at, AL_ATOMIC_RELAXED);
- al_atomic_f64_store(&clock->tick, 0.0, AL_ATOMIC_RELAXED);
- return true;
- }
- return false;
+ return al_atomic_load(f64)(&clock->pause, AL_ATOMIC_RELAXED) == ENDED;
}
f64 camu_clock_get_base_pts(struct camu_clock *clock)
{
- return al_atomic_f64_load(&clock->base, AL_ATOMIC_RELAXED);
+ return clock->base;
}
f64 camu_clock_get_pts(struct camu_clock *clock, f64 offset)
{
- f64 tick = al_atomic_f64_load(&clock->tick, AL_ATOMIC_RELAXED);
- if (tick == -1.0) tick = calc_tick_internal(clock);
- f64 pts = (aki_get_tick() - tick) + offset;
- return pts;
+ f64 pause = al_atomic_load(f64)(&clock->pause, AL_ATOMIC_ACQUIRE);
+ if (pause == PAUSED || pause == ENDED) return -1.0;
+ f64 current = aki_get_tick();
+ f64 tick = al_atomic_load(f64)(&clock->tick, AL_ATOMIC_RELAXED);
+ if (tick == -1.0) {
+ tick = al_atomic_compare_and_swap(f64)(&clock->tick, -1.0, current);
+ if (tick == -1.0) tick = current;
+ }
+ // pause > 0.0 means running or pause armed.
+ if (pause > 0.0 && current > pause) {
+ current = pause;
+ pause = al_atomic_compare_and_swap(f64)(&clock->pause, pause, PAUSED);
+ if (pause != PAUSED) {
+ clock->callback(clock->userdata, CAMU_CLOCK_PAUSED);
+ }
+ }
+ return clock->base + (current - (tick + clock->offset)) + offset;
}
diff --git a/src/buffer/clock.h b/src/buffer/clock.h
index 6f505be..17edb0d 100644
--- a/src/buffer/clock.h
+++ b/src/buffer/clock.h
@@ -1,27 +1,51 @@
#pragma once
-#include <aki/thread.h>
+#include <al/types.h>
#include <al/atomic.h>
-#include "../shrub/handler.h"
+// Synced:
+// () = other clients only
+// Pause:
+// - User input -> start fade and request pause from server
+// - Get response -> (start fade) arm pause or reseek if late
+// Resume:
+// - User input -> request resume from server
+// - Get response -> arm resume, apply offset to queued
+// Next/Prev:
+// - User input -> start fade request next from server
+// - Get response -> (start fade) arm pause, arm resume on next, then swap on pause
+//
+// Local:
+// Pause:
+// - User input -> start fade and pause immediately
+// Reumse:
+// - User input -> resume and fade in
+// Next/Prev:
+// - User input -> swap
+
+enum {
+ CAMU_CLOCK_PAUSED = 0
+};
struct camu_clock {
- atomic_f64 base, tick;
- atomic_u64 start;
- u64 ts;
- s64 offset;
- u64 delay;
- f64 pause_at;
+ f64 base;
+ f64 offset;
+ atomic(f64) tick;
+ atomic(f64) pause;
+ f64 paused_at;
+ void (*callback)(void *, u8);
+ void *userdata;
};
-void camu_clock_set(struct camu_clock *clock, struct shrb_seek_req *req);
-bool camu_clock_is_late(struct camu_clock *clock);
-void camu_clock_ready(struct camu_clock *clock);
-void camu_clock_offset(struct camu_clock *clock, s64 offset);
-void camu_clock_resume(struct camu_clock *clock);
-void camu_clock_arm_resume(struct camu_clock *clock, u64 ts);
-void camu_clock_pause(struct camu_clock *clock, f64 base);
-void camu_clock_arm_pause(struct camu_clock *clock, f64 pts);
+void camu_clock_init(struct camu_clock *clock, void (*callback)(void *, u8), void *userdata);
+void camu_clock_set(struct camu_clock *clock, f64 base);
+void camu_clock_offset(struct camu_clock *clock, f64 amount);
+
+void camu_clock_pause(struct camu_clock *clock, u64 ts);
+void camu_clock_resume(struct camu_clock *clock, u64 ts);
bool camu_clock_is_paused(struct camu_clock *clock);
+void camu_clock_end(struct camu_clock *clock);
+bool camu_clock_is_ended(struct camu_clock *clock);
+
f64 camu_clock_get_base_pts(struct camu_clock *clock);
f64 camu_clock_get_pts(struct camu_clock *clock, f64 offset);
diff --git a/src/buffer/frame_queue.h b/src/buffer/frame_queue.h
index 512b7f2..0175668 100644
--- a/src/buffer/frame_queue.h
+++ b/src/buffer/frame_queue.h
@@ -14,7 +14,7 @@ enum {
};
struct camu_frame_queue {
- void (*push)(struct camu_frame_queue *, struct camu_frame *, f64);
+ 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);
#endif
diff --git a/src/buffer/peak_buffer.c b/src/buffer/peak_buffer.c
index 2856a78..005e77f 100644
--- a/src/buffer/peak_buffer.c
+++ b/src/buffer/peak_buffer.c
@@ -2,10 +2,10 @@
// https://github.com/MusicPlayerDaemon/MPD/blob/c71e586c530e1d066efde7c2ca40c363f74100c7/src/util/PeakBuffer.cxx
-void camu_peak_buffer_init(struct camu_peak_buffer *buf)
+void camu_peak_buffer_init(struct camu_peak_buffer *buf, size_t size)
{
aki_buffer_init(&buf->buf);
- aki_buffer_ensure_space(&buf->buf, 128 * 1024);
+ aki_buffer_ensure_space(&buf->buf, size);
}
void camu_peak_buffer_push(struct camu_peak_buffer *buf, u8 *data, size_t size)
@@ -13,9 +13,13 @@ void camu_peak_buffer_push(struct camu_peak_buffer *buf, u8 *data, size_t size)
aki_buffer_append(&buf->buf, data, size);
}
-u8 *camu_peak_buffer_flush(struct camu_peak_buffer *buf, size_t *size)
+size_t camu_peak_buffer_size(struct camu_peak_buffer *buf)
+{
+ return aki_buffer_get_size(&buf->buf);
+}
+
+u8 *camu_peak_buffer_flush(struct camu_peak_buffer *buf)
{
- *size = aki_buffer_get_size(&buf->buf);
aki_buffer_set_size(&buf->buf, 0);
return aki_buffer_get_ptr(&buf->buf, 0);
}
diff --git a/src/buffer/peak_buffer.h b/src/buffer/peak_buffer.h
index b8d9934..c518ade 100644
--- a/src/buffer/peak_buffer.h
+++ b/src/buffer/peak_buffer.h
@@ -7,7 +7,8 @@ struct camu_peak_buffer {
struct aki_buffer buf;
};
-void camu_peak_buffer_init(struct camu_peak_buffer *buf);
+void camu_peak_buffer_init(struct camu_peak_buffer *buf, size_t size);
void camu_peak_buffer_push(struct camu_peak_buffer *buf, u8 *data, size_t size);
-u8 *camu_peak_buffer_flush(struct camu_peak_buffer *buf, size_t *size);
+size_t camu_peak_buffer_size(struct camu_peak_buffer *buf);
+u8 *camu_peak_buffer_flush(struct camu_peak_buffer *buf);
void camu_peak_buffer_free(struct camu_peak_buffer *buf);
diff --git a/src/buffer/video.c b/src/buffer/video.c
index 3b9be20..b2381b2 100644
--- a/src/buffer/video.c
+++ b/src/buffer/video.c
@@ -4,35 +4,39 @@
#include "common.h"
#include "common_internal.h"
-//#define CAMU_VIDEO_BUFFER_FORCE_SCALER
-
#define BUFFER_WATERMARK_LOW 8 // frames.
-#define BUFFER_WATERMARK_BUFFERED 8
-#define BUFFER_WATERMARK_HIGH 14
+#define BUFFER_WATERMARK_BUFFERED 5
+#define BUFFER_WATERMARK_HIGH 12
#define BUFFER_WATERMARK_RESET BUFFER_WATERMARK_HIGH + 10.
-bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *clock, f64 offset,
- struct camu_renderer *renderer)
+bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *clock, struct camu_renderer *renderer)
{
buf->clock = clock;
- buf->offset = offset;
+ buf->latency = 0.0;
+ buf->pts = -1.0;
+ // Defaulting single_frame to true should simplify non-configured buffers in sink.
+ buf->single_frame = true;
buf->queue = renderer->create_queue(renderer);
buf->buffered = false;
- al_atomic_u8_store(&buf->flow, FLOWING, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED);
#ifdef CAMU_SCREEN_THREADED
- al_atomic_bool_store(&buf->ref, false, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&buf->ref, 0, AL_ATOMIC_RELAXED);
#endif
buf->view.mode = CAMU_VIEW_NONE;
return true;
}
-bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_stream *stream)
+bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_codec_stream *stream)
{
buf->stream = stream;
- switch (stream->type) {
+ switch (stream->mode) {
case CAMU_NORMAL: {
+ s32 width = buf->stream->video.width;
+ s32 height = buf->stream->video.height;
buf->single_frame = true;
buf->avg_frame_duration = 0.0;
+ const char *format_name = camu_pixel_format_name(buf->stream->video.format);
+ al_log_info("video_buffer", "Stream: %s (%dx%d) %s.", format_name, width, height, "IMAGE");
break;
}
#ifdef CAMU_HAVE_FFMPEG
@@ -44,8 +48,8 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_stre
buf->fmt.in_format = stream->av.stream->codecpar->format;
buf->single_frame = stream->av.stream->duration == 0 ||
stream->av.stream->avg_frame_rate.den == 0;
- AVRational frame_rate = buf->single_frame ? (AVRational){ 0, 1 } :
- stream->av.stream->avg_frame_rate;
+ AVRational frame_rate = buf->single_frame ? ((AVRational){ 0, 1 })
+ : stream->av.stream->avg_frame_rate;
buf->avg_frame_duration = av_q2d(av_inv_q(frame_rate));
const char *format_name = av_get_pix_fmt_name(buf->fmt.in_format);
al_log_info("video_buffer", "Stream: %s (%dx%d) %s %.2ffps.", format_name,
@@ -56,7 +60,7 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_stre
buf->fmt.req_format = AV_PIX_FMT_RGBA;
if (camu_ff_scaler_init(&buf->scale, &buf->fmt) && buf->fmt.scaler_needed) {
format_name = av_get_pix_fmt_name(buf->fmt.req_format);
- al_log_info("video_buffer", "Scaling stream to: %s (%dx%d)", format_name,
+ al_log_info("video_buffer", "Scaling to: %s (%dx%d)", format_name,
buf->fmt.req_width, buf->fmt.req_height);
} else {
return false;
@@ -69,6 +73,11 @@ bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_stre
return true;
}
+void camu_video_buffer_set_latency(struct camu_video_buffer *buf, f64 latency)
+{
+ buf->latency = latency;
+}
+
bool camu_video_buffer_is_single_frame(struct camu_video_buffer *buf)
{
return buf->single_frame;
@@ -78,12 +87,12 @@ static void after_push_internal(struct camu_video_buffer *buf)
{
s32 count = buf->queue->count(buf->queue);
if (!buf->buffered && (buf->single_frame || count >= BUFFER_WATERMARK_BUFFERED)) {
+ al_log_debug("video_buffer", "Buffered (watermark: %d frames).", count);
// Preserve order of: flush -> callback -> set flow, for single frames.
if (buf->single_frame) buf->queue->flush(buf->queue);
buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED);
buf->buffered = true;
- al_log_debug("video_buffer", "Buffered (watermark: %d frames).", count);
- if (buf->single_frame) al_atomic_u8_store(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED);
+ if (buf->single_frame) al_atomic_store(u8)(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED);
}
if (count >= BUFFER_WATERMARK_RESET) buf->queue->reset(buf->queue);
else if (count >= BUFFER_WATERMARK_HIGH) buf->callback(buf->userdata, CAMU_BUFFER_CORK);
@@ -95,12 +104,13 @@ static void push_av_frame_internal(struct camu_video_buffer *buf, AVFrame *frame
f64 pts = 0.0;
if (!buf->single_frame) {
AVStream *stream = buf->stream->av.stream;
- pts = (frame->best_effort_timestamp - stream->start_time) * av_q2d(stream->time_base);
+ pts = frame->best_effort_timestamp * av_q2d(stream->time_base);
if (pts + buf->avg_frame_duration < camu_clock_get_base_pts(buf->clock)) {
av_frame_free(&frame);
return;
}
}
+ if (buf->pts == -1.0) buf->pts = pts;
#ifdef CAMU_VIDEO_BUFFER_FORCE_SCALER
if (buf->fmt.scaler_needed) {
if (!camu_ff_scaler_scale(&buf->scale, (const u8 **)frame->data, frame->linesize)) {
@@ -115,14 +125,14 @@ static void push_av_frame_internal(struct camu_video_buffer *buf, AVFrame *frame
}
#endif
-void camu_video_buffer_push(struct camu_video_buffer *buf, struct camu_frame *frame)
+void camu_video_buffer_push(struct camu_video_buffer *buf, struct camu_codec_frame *frame)
{
// A single frame will be sent again after a seek, discard it here (for now).
if (buf->single_frame && buf->buffered) {
- camu_frame_discard(frame);
+ camu_codec_frame_discard(frame);
return;
}
- switch (frame->type) {
+ switch (frame->mode) {
case CAMU_NORMAL:
buf->queue->push(buf->queue, frame, 0.0);
break;
@@ -141,7 +151,7 @@ void camu_video_buffer_reset(struct camu_video_buffer *buf)
{
buf->queue->reset(buf->queue);
buf->buffered = false;
- al_atomic_u8_store(&buf->flow, FLOWING, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&buf->flow, FLOWING, AL_ATOMIC_RELAXED);
}
// flush() always comes from the same thread as push().
@@ -149,26 +159,28 @@ void camu_video_buffer_flush(struct camu_video_buffer *buf)
{
buf->queue->flush(buf->queue);
if (!buf->buffered) {
+ al_log_debug("video_buffer", "Buffered (watermark: %d frames).", buf->queue->count(buf->queue));
buf->callback(buf->userdata, CAMU_BUFFER_BUFFERED);
buf->buffered = true;
- al_log_debug("video_buffer", "Buffered (watermark: %d frames).", buf->queue->count(buf->queue));
}
- al_atomic_u8_store(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&buf->flow, FLUSHED, AL_ATOMIC_RELAXED);
}
bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out)
{
- bool paused = buf->single_frame || camu_clock_is_paused(buf->clock);
- f64 base = camu_clock_get_base_pts(buf->clock);
- if (!paused) {
- f64 pts = camu_clock_get_pts(buf->clock, buf->offset);
- if (pts > base) base = pts;
+ if (!buf->single_frame) {
+ f64 pts = camu_clock_get_pts(buf->clock, buf->latency);
+ if (pts > buf->pts) buf->pts = pts;
+ }
+ u8 flow = al_atomic_load(u8)(&buf->flow, AL_ATOMIC_RELAXED);
+ if (flow == SIGNALED && !buf->single_frame) {
+ buf->callback(buf->userdata, CAMU_BUFFER_EOF);
+ return false;
}
- u8 flow = al_atomic_u8_load(&buf->flow, AL_ATOMIC_RELAXED);
- u8 ret = buf->queue->read(buf->queue, base, out);
+ u8 ret = buf->queue->read(buf->queue, buf->pts, out);
if (flow == FLUSHED && (ret == CAMU_QUEUE_EOF || (buf->single_frame && ret == CAMU_QUEUE_OK))) {
buf->callback(buf->userdata, CAMU_BUFFER_EOF);
- al_atomic_u8_store(&buf->flow, SIGNALED, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&buf->flow, SIGNALED, AL_ATOMIC_RELAXED);
al_log_debug("video_buffer", "Flushed.");
} else if (flow == FLOWING && buf->queue->count(buf->queue) <= BUFFER_WATERMARK_LOW) {
buf->callback(buf->userdata, CAMU_BUFFER_UNCORK);
diff --git a/src/buffer/video.h b/src/buffer/video.h
index 5e18b33..0e8bfff 100644
--- a/src/buffer/video.h
+++ b/src/buffer/video.h
@@ -1,36 +1,43 @@
#pragma once
+//#define CAMU_VIDEO_BUFFER_FORCE_SCALER
+
#include <al/types.h>
#include "../codec/codec.h"
-#include "../codec/ffmpeg/scaler.h"
#include "../render/renderer.h"
#include "../screen/screen.h"
#include "../screen/view.h"
+#ifdef CAMU_VIDEO_BUFFER_FORCE_SCALER
+#endif
+#include "../codec/ffmpeg/scaler.h"
#include "clock.h"
#include "frame_queue.h"
struct camu_video_buffer {
- struct camu_stream *stream;
+ struct camu_codec_stream *stream;
struct camu_clock *clock;
- f32 start_time;
- f32 avg_frame_duration;
- f64 offset;
+ f64 latency;
+ f64 pts;
bool single_frame;
+ f32 avg_frame_duration;
+#ifdef CAMU_VIDEO_BUFFER_FORCE_SCALER
struct camu_ff_scaler scale;
+#endif
+ // TODO: Terrible
struct camu_ff_scale_fmt fmt;
struct camu_frame_queue *queue;
bool buffered;
- atomic_u8 flow;
+ atomic(u8) flow;
#ifdef CAMU_SCREEN_THREADED
- atomic_bool ref;
+ atomic(u8) ref;
#endif
struct camu_view view;
@@ -38,11 +45,11 @@ struct camu_video_buffer {
void *userdata;
};
-bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *clock, f64 offset,
- struct camu_renderer *renderer);
-bool camu_video_buffer_configure(struct camu_video_buffer *buf, struct camu_stream *stream);
+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);
+void camu_video_buffer_set_latency(struct camu_video_buffer *buf, f64 latency);
bool camu_video_buffer_is_single_frame(struct camu_video_buffer *buf);
-void camu_video_buffer_push(struct camu_video_buffer *buf, struct camu_frame *frame);
+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);
void camu_video_buffer_flush(struct camu_video_buffer *buf);
bool camu_video_buffer_read(struct camu_video_buffer *buf, void *out);
diff --git a/src/buffer/volume.h b/src/buffer/volume.h
new file mode 100644
index 0000000..f785eb9
--- /dev/null
+++ b/src/buffer/volume.h
@@ -0,0 +1,36 @@
+#include "../codec/codec.h"
+
+AL_UNUSED_FUNCTION_PUSH
+
+static inline f32 apply_audio_f32(f32 *data, size_t sample_count, s32 channel_count, f32 volume, f32 step)
+{
+ for (u32 i = 0; i < sample_count; i++) {
+ if ((i + 1) % channel_count == 0) {
+ volume = AL_CLAMP(volume + step, 0.f, 1.f);
+ }
+ data[i] *= volume;
+ }
+ return volume;
+}
+
+static inline f32 apply_volume(u8 *data, size_t size, struct camu_audio_format *fmt, f32 volume, f32 step)
+{
+ size_t sample_count;
+ switch (camu_audio_format_bytes_per_sample(fmt)) {
+ case 4:
+ sample_count = size / 4;
+ if (fmt->format == CAMU_SAMPLE_FORMAT_S32) {
+ } else if (fmt->format == CAMU_SAMPLE_FORMAT_FLT) {
+ return apply_audio_f32((f32 *)data, sample_count, fmt->channel_count, volume, step);
+ }
+ break;
+ case 2:
+ sample_count = size / 2;
+ if (fmt->format == CAMU_SAMPLE_FORMAT_S16) {
+ }
+ break;
+ }
+ return volume;
+}
+
+AL_UNUSED_FUNCTION_POP
diff --git a/src/cache/backing.h b/src/cache/backing.h
index 24aff44..d9d8bad 100644
--- a/src/cache/backing.h
+++ b/src/cache/backing.h
@@ -1,19 +1,26 @@
#pragma once
#include <al/types.h>
+#include <al/array.h>
enum {
- CCH_BACKING_READ = 0,
- CCH_BACKING_MAPPED
+ CACHE_BACKING_READ = 0,
+ CACHE_BACKING_MAPPED
+};
+
+struct cch_range {
+ off_t start;
+ off_t end;
};
struct cch_backing {
u8 mode;
+ array(struct cch_range) available;
+ void (*lock)(struct cch_backing *);
void (*write)(struct cch_backing *, u8 *, off_t, size_t *);
void (*read)(struct cch_backing *, u8 *, off_t, size_t *);
u8 *(*get_ptr)(struct cch_backing *, off_t, size_t *);
void (*unlock)(struct cch_backing *);
- void *(*get_mmap)(struct cch_backing *);
off_t (*get_size_estimate)(struct cch_backing *);
void (*resize)(struct cch_backing *, size_t);
void (*free)(struct cch_backing **);
diff --git a/src/cache/backings/file.c b/src/cache/backings/file.c
index 4c5f344..153b1e4 100644
--- a/src/cache/backings/file.c
+++ b/src/cache/backings/file.c
@@ -1,6 +1,14 @@
+#include "../range.h"
+
#include "file.h"
#include "file_common.c"
+static void file_backing_lock(struct cch_backing *backing)
+{
+ struct cch_backing_file *file = (struct cch_backing_file *)backing;
+ aki_mutex_lock(&file->mutex);
+}
+
static void file_backing_write(struct cch_backing *backing, u8 *buf, off_t index, size_t *size)
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
@@ -10,10 +18,11 @@ static void file_backing_write(struct cch_backing *backing, u8 *buf, off_t index
al_assert(file->u.pointer == index);
}
if (file->size >= 0 && index + ((off_t)*size) >= file->size) {
- *size = file->size - index;
+ *size = AL_MAX(file->size - index, 0);
}
aki_file_write(&file->file, buf, *size);
file->u.pointer += *size;
+ cch_backing_fill_range(&file->backing, index, *size);
aki_mutex_unlock(&file->mutex);
}
@@ -26,13 +35,19 @@ static void file_backing_read(struct cch_backing *backing, u8 *buf, off_t index,
al_assert(file->u.pointer == index);
}
if (file->size >= 0 && index + ((off_t)*size) >= file->size) {
- *size = file->size - index;
+ *size = AL_MAX(file->size - index, 0);
}
aki_file_read(&file->file, buf, *size);
file->u.pointer += *size;
aki_mutex_unlock(&file->mutex);
}
+static void file_backing_unlock(struct cch_backing *backing)
+{
+ struct cch_backing_file *file = (struct cch_backing_file *)backing;
+ aki_mutex_unlock(&file->mutex);
+}
+
static void file_backing_resize(struct cch_backing *backing, size_t size)
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
@@ -49,6 +64,7 @@ static void file_backing_free(struct cch_backing **backing)
{
struct cch_backing_file *file = (struct cch_backing_file *)*backing;
aki_file_close(&file->file);
+ al_array_free(file->backing.available);
al_free(file);
*backing = NULL;
}
@@ -56,15 +72,16 @@ static void file_backing_free(struct cch_backing **backing)
struct cch_backing *cch_backing_file_create(str *path, size_t size)
{
struct cch_backing_file *file = al_alloc_object(struct cch_backing_file);
- file->backing.mode = CCH_BACKING_READ;
+ file->backing.mode = CACHE_BACKING_READ;
+ file->backing.lock = file_backing_lock;
file->backing.write = file_backing_write;
file->backing.read = file_backing_read;
file->backing.get_ptr = NULL;
- file->backing.unlock = NULL;
- file->backing.get_mmap = NULL;
+ file->backing.unlock = file_backing_unlock;
file->backing.get_size_estimate = file_backing_get_size_estimate;
file->backing.resize = file_backing_resize;
file->backing.free = file_backing_free;
+ al_array_init(file->backing.available);
if (!file_open_internal(file, path, size)) {
al_free(file);
return NULL;
diff --git a/src/cache/backings/file.h b/src/cache/backings/file.h
index 9794bbb..3f8437b 100644
--- a/src/cache/backings/file.h
+++ b/src/cache/backings/file.h
@@ -13,8 +13,8 @@ struct cch_backing_file {
off_t size;
off_t filesize;
union {
- void *map; // CCH_BACKING_MAPPED
- off_t pointer; // CCH_BACKING_READ
+ void *map; // CACHE_BACKING_MAPPED
+ off_t pointer; // CACHE_BACKING_READ
} u;
struct aki_mutex mutex;
};
diff --git a/src/cache/backings/file_common.c b/src/cache/backings/file_common.c
index 232580a..35322fd 100644
--- a/src/cache/backings/file_common.c
+++ b/src/cache/backings/file_common.c
@@ -14,6 +14,7 @@ static bool file_open_internal(struct cch_backing_file *file, str *path, size_t
file->filesize = aki_file_get_filesize(&file->file);
if (!size) {
file->size = file->filesize;
+ cch_backing_fill_range(&file->backing, 0, file->size);
} else {
file->size = -1;
if (file->filesize < (off_t)size) {
diff --git a/src/cache/backings/file_mapped.c b/src/cache/backings/file_mapped.c
index 4277c21..6c6fc94 100644
--- a/src/cache/backings/file_mapped.c
+++ b/src/cache/backings/file_mapped.c
@@ -1,14 +1,23 @@
+#include "../range.h"
+
#include "file.h"
#include "file_common.c"
+static void file_backing_lock(struct cch_backing *backing)
+{
+ struct cch_backing_file *file = (struct cch_backing_file *)backing;
+ aki_mutex_lock(&file->mutex);
+}
+
static void file_backing_write(struct cch_backing *backing, u8 *buf, off_t index, size_t *size)
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
aki_mutex_lock(&file->mutex);
- if (file->size >= 0 && index + ((off_t)*size) >= file->size) {
- *size = file->size - index;
+ if (file->size >= 0 && index + (off_t)*size >= file->size) {
+ *size = AL_MAX(file->size - index, 0);
}
al_memcpy(file->u.map + index, buf, *size);
+ cch_backing_fill_range(&file->backing, index, *size);
aki_mutex_unlock(&file->mutex);
}
@@ -16,8 +25,8 @@ static u8 *file_backing_get_ptr(struct cch_backing *backing, off_t index, size_t
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
aki_mutex_lock(&file->mutex);
- if (file->size >= 0 && index + ((off_t)*size) >= file->size) {
- *size = file->size - index;
+ if (file->size >= 0 && index + (off_t)*size >= file->size) {
+ *size = AL_MAX(file->size - index, 0);
}
return (u8 *)(file->u.map + index);
}
@@ -28,24 +37,15 @@ static void file_backing_unlock(struct cch_backing *backing)
aki_mutex_unlock(&file->mutex);
}
-static void *file_backing_get_mmap(struct cch_backing *backing)
-{
- struct cch_backing_file *file = (struct cch_backing_file *)backing;
- aki_mutex_lock(&file->mutex);
- void *map = file->u.map;
- aki_mutex_unlock(&file->mutex);
- return map;
-}
-
static void file_backing_resize(struct cch_backing *backing, size_t size)
{
struct cch_backing_file *file = (struct cch_backing_file *)backing;
aki_mutex_lock(&file->mutex);
file->size = size;
if ((off_t)size > file->filesize) {
- // TODO: Unmap, truncate, map?
- file->u.map = aki_file_mremap(&file->file, size, file->u.map);
+ aki_file_munmap(&file->file, file->u.map);
aki_file_truncate(&file->file, size);
+ file->u.map = aki_file_mmap(&file->file);
file->filesize = size;
}
aki_mutex_unlock(&file->mutex);
@@ -56,6 +56,7 @@ static void file_backing_free(struct cch_backing **backing)
struct cch_backing_file *file = (struct cch_backing_file *)*backing;
aki_file_munmap(&file->file, file->u.map);
aki_file_close(&file->file);
+ al_array_free(file->backing.available);
al_free(file);
*backing = NULL;
}
@@ -63,20 +64,20 @@ static void file_backing_free(struct cch_backing **backing)
struct cch_backing *cch_backing_file_create(str *path, size_t size)
{
struct cch_backing_file *file = al_alloc_object(struct cch_backing_file);
- file->backing.mode = CCH_BACKING_MAPPED;
+ file->backing.mode = CACHE_BACKING_MAPPED;
+ file->backing.lock = file_backing_lock;
file->backing.write = file_backing_write;
file->backing.read = NULL;
- file->backing.get_mmap = file_backing_get_mmap;
file->backing.get_ptr = file_backing_get_ptr;
file->backing.unlock = file_backing_unlock;
file->backing.get_size_estimate = file_backing_get_size_estimate;
file->backing.resize = file_backing_resize;
file->backing.free = file_backing_free;
- if (!file_open_internal(file, path, size)) {
+ al_array_init(file->backing.available);
+ if (!file_open_internal(file, path, size) || !(file->u.map = aki_file_mmap(&file->file))) {
al_free(file);
return NULL;
}
- file->u.map = aki_file_mmap(&file->file);
aki_mutex_init(&file->mutex);
return (struct cch_backing *)file;
}
diff --git a/src/cache/backings/memory.c b/src/cache/backings/memory.c
index 194d66c..1dca8f9 100644
--- a/src/cache/backings/memory.c
+++ b/src/cache/backings/memory.c
@@ -1,22 +1,31 @@
+#include "../range.h"
+
#include "memory.h"
static void ensure_alloced(struct cch_backing_memory *mem, size_t size)
{
if (size > mem->alloc) {
- mem->data = al_realloc(mem->data, size);
- mem->alloc = size;
+ mem->alloc = al_next_power_of_two(size);
+ mem->data = al_realloc(mem->data, mem->alloc);
}
}
+static void memory_backing_lock(struct cch_backing *backing)
+{
+ struct cch_backing_memory *mem = (struct cch_backing_memory *)backing;
+ aki_mutex_lock(&mem->mutex);
+}
+
static void memory_backing_write(struct cch_backing *backing, u8 *buf, off_t index, size_t *size)
{
struct cch_backing_memory *mem = (struct cch_backing_memory *)backing;
aki_mutex_lock(&mem->mutex);
- if (mem->size >= 0 && index + ((off_t)*size) >= mem->size) {
- *size = mem->size - index;
+ if (mem->size >= 0 && index + (off_t)*size >= mem->size) {
+ *size = AL_MAX(mem->size - index, 0);
}
ensure_alloced(mem, index + *size);
al_memcpy(mem->data + index, buf, *size);
+ cch_backing_fill_range(&mem->backing, index, *size);
aki_mutex_unlock(&mem->mutex);
}
@@ -24,8 +33,8 @@ static u8 *memory_backing_get_ptr(struct cch_backing *backing, off_t index, size
{
struct cch_backing_memory *mem = (struct cch_backing_memory *)backing;
aki_mutex_lock(&mem->mutex);
- if (mem->size >= 0 && index + ((off_t)*size) >= mem->size) {
- *size = mem->size - index;
+ if (mem->size >= 0 && index + (off_t)*size >= mem->size) {
+ *size = AL_MAX(mem->size - index, 0);
}
return (u8 *)(mem->data + index);
}
@@ -59,6 +68,7 @@ static void memory_backing_free(struct cch_backing **backing)
struct cch_backing_memory *mem = (struct cch_backing_memory *)*backing;
al_free(mem->data);
aki_mutex_destroy(&mem->mutex);
+ al_array_free(mem->backing.available);
al_free(mem);
*backing = NULL;
}
@@ -66,9 +76,9 @@ static void memory_backing_free(struct cch_backing **backing)
struct cch_backing *cch_backing_memory_create(size_t size)
{
struct cch_backing_memory *mem = al_alloc_object(struct cch_backing_memory);
- mem->backing.mode = CCH_BACKING_MAPPED;
+ mem->backing.mode = CACHE_BACKING_MAPPED;
+ mem->backing.lock = memory_backing_lock;
mem->backing.write = memory_backing_write;
- mem->backing.get_mmap = NULL;
mem->backing.get_ptr = memory_backing_get_ptr;
mem->backing.unlock = memory_backing_unlock;
mem->backing.get_size_estimate = memory_backing_get_size_estimate;
@@ -78,5 +88,6 @@ struct cch_backing *cch_backing_memory_create(size_t size)
mem->alloc = size;
mem->size = -1;
aki_mutex_init(&mem->mutex);
+ al_array_init(mem->backing.available);
return (struct cch_backing *)mem;
}
diff --git a/src/cache/entry.c b/src/cache/entry.c
index 04e8f92..8068397 100644
--- a/src/cache/entry.c
+++ b/src/cache/entry.c
@@ -16,13 +16,26 @@ bool cch_entry_get_handle(struct cch_entry *entry, struct cch_handle *handle)
return true;
}
+str *cch_entry_get_liana(struct cch_entry *entry)
+{
+ return &entry->handler->liana;
+}
+
void cch_entry_set_size(struct cch_entry *entry, off_t filesize)
{
- return entry->backing->resize(entry->backing, filesize);
+ if (filesize == 0) {
+ // The backing will keep size = -1 internally.
+ entry->unknown_size = true;
+ return;
+ }
+ entry->backing->resize(entry->backing, filesize);
}
off_t cch_entry_get_size(struct cch_entry *entry)
{
+ if (entry->unknown_size) {
+ return 0;
+ }
return entry->backing->get_size_estimate(entry->backing);
}
diff --git a/src/cache/entry.h b/src/cache/entry.h
index 305fb1f..3196809 100644
--- a/src/cache/entry.h
+++ b/src/cache/entry.h
@@ -6,15 +6,24 @@
#include "backing.h"
#include "handler.h"
+struct cch_chapter {
+ off_t start;
+ off_t end;
+};
+
struct cch_entry {
u32 hash;
s32 ref_count;
+ bool unknown_size;
+ array(struct cch_chapter) chapters;
+ struct cch_chapter *chapter;
struct cch_backing *backing;
struct cch_handler *handler;
struct aki_mutex mutex;
};
bool cch_entry_get_handle(struct cch_entry *entry, struct cch_handle *handle);
+str *cch_entry_get_liana(struct cch_entry *entry);
void cch_entry_set_size(struct cch_entry *entry, off_t filesize);
off_t cch_entry_get_size(struct cch_entry *entry);
void cch_entry_return_handle(struct cch_entry *entry, struct cch_handle *handle);
diff --git a/src/cache/handle.c b/src/cache/handle.c
index 9dfaf70..5bc868c 100644
--- a/src/cache/handle.c
+++ b/src/cache/handle.c
@@ -6,26 +6,32 @@
#include "handle.h"
#include "entry.h"
+#include "threaded_waits.h"
-static bool wait_for_size(struct cch_handle *handle, struct cch_handler *handler)
+static off_t wait_for_size(struct cch_handle *handle, struct cch_handler *handler)
{
- handle->wait.id = al_rand_u16();
handle->wait.end = -1;
- return handler->wait_for_range(handler, &handle->wait);
+ if (!handler->wait_for_range(handler, &handle->wait)) {
+ return -1;
+ }
+ return cch_entry_get_size(handler->entry);
}
s32 cch_handle_read(struct cch_handle *handle, u8 *buf, s32 size)
{
struct cch_handler *handler = handle->entry->handler;
- wait_for_size(handle, handler);
+ // filesize < 0: Size is yet to be evaluated and we can wait on it.
+ // filesize = 0: Size is explicitly unknown.
off_t filesize = cch_entry_get_size(handle->entry);
- if (filesize >= 0) {
+ if (size < 0) {
+ filesize = wait_for_size(handle, handler);
+ }
+ if (filesize > 0) {
if (handle->pointer >= filesize) return CAMU_ERR_EOF;
if (handle->pointer + size >= filesize) {
size = filesize - handle->pointer;
}
}
- handle->wait.id = al_rand_u16();
handle->wait.start = handle->pointer;
handle->wait.end = handle->pointer + size;
if (!handler->wait_for_range(handler, &handle->wait)) {
@@ -33,25 +39,27 @@ s32 cch_handle_read(struct cch_handle *handle, u8 *buf, s32 size)
}
struct cch_backing *backing = handle->entry->backing;
size_t available = (size_t)size;
- if (backing->mode == CCH_BACKING_MAPPED) {
+ if (backing->mode == CACHE_BACKING_MAPPED) {
u8 *ptr = backing->get_ptr(backing, handle->pointer, &available);
if (ptr && available > 0) {
al_memcpy(buf, ptr, available);
}
backing->unlock(backing);
- } else if (backing->mode == CCH_BACKING_READ) {
+ } else if (backing->mode == CACHE_BACKING_READ) {
backing->read(backing, buf, handle->pointer, &available);
}
handle->pointer += available;
- return (available > 0) ? (s32)available : CAMU_ERR_EOF;
+ return available > 0 ? (s32)available : CAMU_ERR_EOF;
}
off_t cch_handle_seek(struct cch_handle *handle, off_t offset, s32 whence)
{
struct cch_handler *handler = handle->entry->handler;
- wait_for_size(handle, handler);
off_t filesize = cch_entry_get_size(handle->entry);
- if (filesize < 0) return -1;
+ if (filesize < 0) {
+ filesize = wait_for_size(handle, handler);
+ }
+ if (filesize <= 0) return -1;
if (whence == CAMU_SEEK_SIZE) {
al_log_debug("cache_handle", "seek_size");
return filesize;
@@ -91,10 +99,5 @@ bool cch_handle_can_seek(struct cch_handle *handle)
void cch_handle_disable(struct cch_handle *handle)
{
- aki_mutex_lock(&handle->wait.mutex);
- handle->wait.disabled = true;
- if (aki_cond_is_waiting(&handle->wait.cond)) {
- aki_cond_signal(&handle->wait.cond);
- }
- aki_mutex_unlock(&handle->wait.mutex);
+ cch_threaded_wait_disable(&handle->wait);
}
diff --git a/src/cache/handle.h b/src/cache/handle.h
index 55223aa..ae936f3 100644
--- a/src/cache/handle.h
+++ b/src/cache/handle.h
@@ -3,7 +3,7 @@
#include <al/types.h>
#include <aki/thread.h>
-#include "handler.h"
+#include "wait.h"
struct cch_entry;
struct cch_handle {
diff --git a/src/cache/handler.h b/src/cache/handler.h
index 5a2861e..9dd1896 100644
--- a/src/cache/handler.h
+++ b/src/cache/handler.h
@@ -2,15 +2,10 @@
#include <al/types.h>
#include <al/array.h>
+#include <al/str.h>
#include <aki/thread.h>
-struct cch_handler_wait {
- u16 id;
- off_t start, end;
- struct aki_cond cond;
- struct aki_mutex mutex;
- bool disabled;
-};
+#include "handle.h"
struct cch_handler {
bool (*can_seek)(struct cch_handler *);
@@ -18,6 +13,8 @@ struct cch_handler {
bool (*wait_for_range)(struct cch_handler *, struct cch_handler_wait *);
void (*free)(struct cch_handler **);
struct cch_entry *entry;
- struct cch_backing *backing;
+ str liana;
+ bool disabled;
+ struct aki_mutex mutex;
array(struct cch_handler_wait *) waits;
};
diff --git a/src/cache/handlers/cdio.c b/src/cache/handlers/cdio.c
new file mode 100644
index 0000000..23f87b6
--- /dev/null
+++ b/src/cache/handlers/cdio.c
@@ -0,0 +1,206 @@
+#include <al/log.h>
+
+#include "../backings/file.h"
+#include "../backings/memory.h"
+#include "../threaded_waits.h"
+#include "../range.h"
+
+#include "cdio.h"
+
+// 1 sector at a time to minimize skips
+#define SECTORS_PER_STEP 1
+#define BYTES_PER_STEP (SECTORS_PER_STEP * CDIO_CD_FRAMESIZE_RAW)
+
+static void cdio_log_messages(struct cch_handler_cdio *cdio)
+{
+ char *error_messages = cdio_cddap_errors(cdio->drive);
+ char *messages = cdio_cddap_messages(cdio->drive);
+ if (error_messages) {
+ al_log_error("cdio", "\n%s", error_messages);
+ cdio_cddap_free_messages(error_messages);
+ }
+ if (messages) {
+ al_log_info("cdio", "\n%s", messages);
+ cdio_cddap_free_messages(messages);
+ }
+}
+
+static bool handler_cdio_can_seek(struct cch_handler *handler)
+{
+ (void)handler;
+ return true;
+}
+
+static aki_thread_result AKI_THREADCALL cd_read_thread(void *userdata)
+{
+ struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)userdata;
+ while (1) {
+ if (!al_atomic_load(s32)(&cdio->running, AL_ATOMIC_RELAXED)) {
+ // We don't care about how complete the disc read was.
+ return 0;
+ }
+ // cdio_read() here can block for a very long time so we buffer each read
+ // before writing it to the backing.
+ long ret = cdio_cddap_read(cdio->drive, aki_buffer_get_ptr(&cdio->buffer, 0), cdio->sector, SECTORS_PER_STEP);
+ cdio_log_messages(cdio);
+ al_assert(ret <= SECTORS_PER_STEP);
+ off_t pointer = cdio->sector * CDIO_CD_FRAMESIZE_RAW;
+ size_t size;
+ u8 *ptr = cdio->backing->get_ptr(cdio->backing, pointer, &size);
+ if (size < BYTES_PER_STEP) {
+ al_log_error("cdio", "Tried to write over expected size during normal operation.");
+ cdio->backing->unlock(cdio->backing);
+ break;
+ }
+ if (ret == SECTORS_PER_STEP) {
+ al_memcpy(ptr, aki_buffer_get_ptr(&cdio->buffer, 0), BYTES_PER_STEP);
+ } else if (ret == -10) {
+ al_log_error("cdio", "I/O error, skipping sector.");
+ al_memset(ptr, 0, BYTES_PER_STEP);
+ ret = SECTORS_PER_STEP;
+ } else if (ret < 0) {
+ al_log_error("cdio", "Fatal error (%d).", ret);
+ cdio->backing->unlock(cdio->backing);
+ break;
+ }
+ cch_backing_fill_range(cdio->backing, pointer, BYTES_PER_STEP);
+ cdio->sector += ret;
+ cdio->backing->unlock(cdio->backing);
+ cch_threaded_waits_evaluate(&cdio->handler, cdio->backing);
+ if (ret < SECTORS_PER_STEP || cdio->sector > cdio->end) {
+ al_log_debug("cdio", "Normal CD EOF.");
+ break;
+ }
+ }
+ if (cdio->sector <= cdio->end) {
+ al_log_warn("cdio", "Disc read cut short (sectors: %zd, end: %zd).", cdio->sector, cdio->end);
+ // We didn't read the full disc, disable waiters.
+ cch_threaded_waits_disable_all(&cdio->handler);
+ }
+ return 0;
+}
+
+static void handler_cdio_maybe_spawn_worker(struct cch_handler *handler, size_t index)
+{
+ struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)handler;
+ if (cdio->start == (lsn_t)index) return;
+ if (al_atomic_load(s32)(&cdio->running, AL_ATOMIC_ACQUIRE)) {
+ al_atomic_store(s32)(&cdio->running, 0, AL_ATOMIC_RELEASE);
+ aki_thread_join(&cdio->thread);
+ }
+ cdio->start = index;
+ cdio->sector = cdio->start;
+ al_atomic_store(s32)(&cdio->running, 1, AL_ATOMIC_RELEASE);
+ aki_thread_create(&cdio->thread, cd_read_thread, cdio);
+}
+
+static bool handler_cdio_wait_for_range(struct cch_handler *handler, struct cch_handler_wait *wait)
+{
+ struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)handler;
+ return cch_threaded_wait_for_range(&cdio->handler, cdio->backing, wait);
+}
+
+static void handler_cdio_free(struct cch_handler **handler)
+{
+ struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)*handler;
+ cch_threaded_waits_disable_all(&cdio->handler);
+ if (al_atomic_load(s32)(&cdio->running, AL_ATOMIC_RELAXED)) {
+ al_atomic_store(s32)(&cdio->running, 0, AL_ATOMIC_RELAXED);
+ aki_thread_join(&cdio->thread);
+ }
+ if (cdio->drive) {
+ cdio_log_messages(cdio);
+ cdio_cddap_close(cdio->drive);
+ }
+ cch_threaded_waits_close(&cdio->handler);
+ al_str_free(&cdio->handler.liana);
+ al_free(cdio);
+ *handler = NULL;
+}
+
+static bool open_cd_drive(struct cch_handler_cdio *cdio)
+{
+ cdrom_drive_t *drive = NULL;
+ char **cd_drives;
+ CdIo_t *cdio_handle = NULL;
+
+ cd_drives = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false);
+ if (cd_drives) {
+ // Use first drive.
+ cdio_handle = cdio_open(*cd_drives, DRIVER_UNKNOWN);
+ drive = cdio_cddap_identify_cdio(cdio_handle, CDDA_MESSAGE_LOGIT, NULL);
+ } else {
+ al_log_error("cdio", "No compatible disc found.");
+ return false;
+ }
+ cdio_free_device_list(cd_drives);
+
+ cdio_cddap_verbose_set(drive, CDDA_MESSAGE_LOGIT, CDDA_MESSAGE_LOGIT);
+
+ s32 ret = cdio_cddap_open(drive);
+ if (ret != 0) {
+ al_log_error("cdio", "Unable to open disc (%d).", ret);
+ return false;
+ }
+
+ ret = cdio_cddap_speed_set(drive, 4);
+ if (ret != 0) {
+ al_log_warn("cdio", "Failed to set drive speed (%d).", ret);
+ }
+
+ // Not sure what this is based on, but it can seemingly just be wrong.
+ track_t tracks = cdio_cddap_tracks(drive);
+ al_log_info("cdio", "CD with %d tracks loaded.", tracks);
+ al_array_reserve(cdio->handler.entry->chapters, tracks);
+
+ lsn_t start = cdio_cddap_disc_firstsector(drive);
+ lsn_t end = cdio_cddap_disc_lastsector(drive);
+ lsn_t offset = start;
+ s32 track = CDIO_INVALID_TRACK;
+ while (offset < end) {
+ track = cdio_cddap_sector_gettrack(drive, offset);
+ al_assert(track != CDIO_INVALID_TRACK);
+ lsn_t track_start = cdio_cddap_track_firstsector(drive, track);
+ lsn_t track_end = cdio_cddap_track_lastsector(drive, track);
+ al_array_push(cdio->handler.entry->chapters, ((struct cch_chapter){ track_start, track_end }));
+ offset = track_end + 1;
+ }
+ // end is a valid index so add one framesize.
+ cch_entry_set_size(cdio->handler.entry, ((end - start) + 1) * CDIO_CD_FRAMESIZE_RAW);
+ cdio->start = -1;
+ cdio->end = end;
+
+ cdio->drive = drive;
+
+ cdio_log_messages(cdio);
+
+ return true;
+}
+
+struct cch_entry *cch_handler_cdio_create(void)
+{
+ struct cch_backing *backing = cch_backing_file_create(al_str_c("/tmp/camu_cd_data"), 4096);
+ if (!backing) return NULL;
+ struct cch_handler_cdio *cdio = al_alloc_object(struct cch_handler_cdio);
+ cdio->backing = backing;
+ struct cch_entry *entry = al_alloc_object(struct cch_entry);
+ entry->backing = backing;
+ entry->handler = (struct cch_handler *)cdio;
+ entry->ref_count = 0;
+ entry->unknown_size = false;
+ entry->hash = 0;
+ aki_mutex_init(&entry->mutex);
+ entry->handler->can_seek = handler_cdio_can_seek;
+ entry->handler->maybe_spawn_worker = handler_cdio_maybe_spawn_worker;
+ entry->handler->wait_for_range = handler_cdio_wait_for_range;
+ entry->handler->free = handler_cdio_free;
+ entry->handler->entry = entry;
+ al_str_from(&entry->handler->liana, "cdio");
+ if (!open_cd_drive(cdio)) return NULL;
+ al_atomic_store(s32)(&cdio->running, 0, AL_ATOMIC_RELAXED);
+ aki_buffer_init(&cdio->buffer);
+ aki_buffer_ensure_space(&cdio->buffer, BYTES_PER_STEP);
+ aki_buffer_set_size(&cdio->buffer, BYTES_PER_STEP);
+ cch_threaded_waits_init(&cdio->handler);
+ return entry;
+}
diff --git a/src/cache/handlers/cdio.h b/src/cache/handlers/cdio.h
new file mode 100644
index 0000000..953c988
--- /dev/null
+++ b/src/cache/handlers/cdio.h
@@ -0,0 +1,21 @@
+#include <al/atomic.h>
+#include <aki/common.h>
+#include <cdio/paranoia/cdda.h>
+#include <cdio/cd_types.h>
+
+#include "../handler.h"
+#include "../entry.h"
+
+struct cch_handler_cdio {
+ struct cch_handler handler;
+ struct cch_backing *backing;
+ cdrom_drive_t *drive;
+ lsn_t start;
+ lsn_t end;
+ lsn_t sector;
+ atomic(s32) running;
+ struct aki_thread thread;
+ struct aki_buffer buffer;
+};
+
+struct cch_entry *cch_handler_cdio_create(void);
diff --git a/src/cache/handlers/file.c b/src/cache/handlers/file.c
index ec6a146..03e2814 100644
--- a/src/cache/handlers/file.c
+++ b/src/cache/handlers/file.c
@@ -25,6 +25,7 @@ static bool handler_file_wait_for_range(struct cch_handler *handler, struct cch_
static void handler_file_free(struct cch_handler **handler)
{
struct cch_handler_file *file = (struct cch_handler_file *)*handler;
+ al_str_free(&file->handler.liana);
al_free(file);
*handler = NULL;
}
@@ -33,17 +34,20 @@ struct cch_entry *cch_handler_file_create(str *path)
{
struct cch_backing *backing = cch_backing_file_create(path, 0);
if (!backing) return NULL;
+ struct cch_handler_file *file = al_alloc_object(struct cch_handler_file);
+ file->backing = backing;
struct cch_entry *entry = al_alloc_object(struct cch_entry);
entry->backing = backing;
+ entry->handler = (struct cch_handler *)file;
entry->ref_count = 0;
+ entry->unknown_size = false;
entry->hash = 0;
- entry->handler = (struct cch_handler *)al_alloc_object(struct cch_handler_file);
aki_mutex_init(&entry->mutex);
entry->handler->can_seek = handler_file_can_seek;
entry->handler->maybe_spawn_worker = handler_file_maybe_spawn_worker;
entry->handler->wait_for_range = handler_file_wait_for_range;
entry->handler->free = handler_file_free;
entry->handler->entry = entry;
- entry->handler->backing = entry->backing;
+ al_str_from(&entry->handler->liana, "codec");
return entry;
}
diff --git a/src/cache/handlers/file.h b/src/cache/handlers/file.h
index 52e5c94..85e1c7f 100644
--- a/src/cache/handlers/file.h
+++ b/src/cache/handlers/file.h
@@ -7,6 +7,7 @@
struct cch_handler_file {
struct cch_handler handler;
+ struct cch_backing *backing;
};
struct cch_entry *cch_handler_file_create(str *path);
diff --git a/src/cache/handlers/http.c b/src/cache/handlers/http.c
index 789b247..71ae186 100644
--- a/src/cache/handlers/http.c
+++ b/src/cache/handlers/http.c
@@ -1,8 +1,10 @@
#include <al/random.h>
#include <al/log.h>
-#include "../backings/file.h"
#include "../backings/memory.h"
+#include "../backings/file.h"
+#include "../threaded_waits.h"
+#include "../wait.h"
#include "http.h"
@@ -14,21 +16,6 @@ static bool handler_http_can_seek(struct cch_handler *handler)
return false;
}
-static void disable_waiters_internal(struct cch_handler_http *http)
-{
- aki_mutex_lock(&http->mutex);
- // Don't allow any further waits.
- http->disabled = true;
- struct cch_handler_wait *wait;
- al_array_foreach(http->handler.waits, i, wait) {
- aki_mutex_lock(&wait->mutex);
- wait->disabled = true;
- aki_cond_signal(&wait->cond);
- aki_mutex_unlock(&wait->mutex);
- }
- aki_mutex_unlock(&http->mutex);
-}
-
static size_t http_callback(void *userdata, u8 op, u8 *buf, s64 int0)
{
struct cch_handler_http *http = (struct cch_handler_http *)userdata;
@@ -39,8 +26,7 @@ static size_t http_callback(void *userdata, u8 op, u8 *buf, s64 int0)
break;
}
case AKI_HTTP_ERROR: {
- al_log_debug("cache_handler_http", "Error.");
- disable_waiters_internal(http);
+ cch_threaded_waits_disable_all(&http->handler);
break;
}
case AKI_HTTP_RESPONSE_CODE: {
@@ -54,37 +40,18 @@ static size_t http_callback(void *userdata, u8 op, u8 *buf, s64 int0)
case AKI_HTTP_CONTENT_LENGTH: {
off_t content_length = (off_t)int0;
al_log_debug("cache_handler_http", "Content-Length: %lld.", content_length);
- aki_mutex_lock(&http->mutex);
cch_entry_set_size(http->handler.entry, content_length);
- struct cch_handler_wait *wait;
- al_array_foreach(http->handler.waits, i, wait) {
- if (wait->end < 0) {
- aki_mutex_lock(&wait->mutex);
- aki_cond_signal(&wait->cond);
- al_array_remove_at_iter(http->handler.waits, i);
- aki_mutex_unlock(&wait->mutex);
- }
- }
- aki_mutex_unlock(&http->mutex);
+ cch_threaded_waits_signal_any(&http->handler);
break;
}
case AKI_HTTP_WRITE: {
- http->handler.backing->write(http->handler.backing, buf, http->pointer, &ret);
- aki_mutex_lock(&http->mutex);
- http->pointer += ret;
- struct cch_handler_wait *wait;
- al_array_foreach(http->handler.waits, i, wait) {
- aki_mutex_lock(&wait->mutex);
- // Assume that if we get data before a length, we won't get a length.
- // So, signal the waiter (wait_for_range returns false).
- if (wait->end < 0) wait->disabled = true;
- if (wait->disabled || http->pointer >= wait->end) {
- aki_cond_signal(&wait->cond);
- al_array_remove_at_iter(http->handler.waits, i);
- }
- aki_mutex_unlock(&wait->mutex);
+ // If we got data before a Content-Length, assume the size is unknown.
+ if (cch_entry_get_size(http->handler.entry) < 0) {
+ cch_entry_set_size(http->handler.entry, 0);
}
- aki_mutex_unlock(&http->mutex);
+ http->backing->write(http->backing, buf, http->pointer, &ret);
+ http->pointer += ret;
+ cch_threaded_waits_evaluate(&http->handler, http->backing);
break;
}
default:
@@ -93,123 +60,63 @@ static size_t http_callback(void *userdata, u8 op, u8 *buf, s64 int0)
return ret;
}
-static void signal_callback(void *userdata)
-{
- struct cch_handler_http *http = (struct cch_handler_http *)userdata;
- u32 size;
- s32 index;
- do {
- camu_queue_try_pop(http->queue, size, index);
- if (size == 0) break;
- if (index == -1) {
- aki_event_loop_break(&http->loop);
- break;
- }
- al_array_push(http->requests, (struct aki_http){0});
- struct aki_http *request = &al_array_last(http->requests);
- aki_http_init(request);
- aki_http_set_url(request, &http->url);
- aki_http_set_user_agent(request, USER_AGENT);
- aki_http_request_stream(request, AKI_HTTP_GET, &http->loop, http_callback, http);
- } while (1);
-}
-
static void handler_http_maybe_spawn_worker(struct cch_handler *handler, size_t index)
{
struct cch_handler_http *http = (struct cch_handler_http *)handler;
- camu_queue_push(http->queue, index);
- aki_signal_send(&http->signal);
+ (void)index;
+ al_array_push(http->requests, (struct aki_http){ 0 });
+ struct aki_http *request = &al_array_last(http->requests);
+ aki_http_init(request);
+ aki_http_set_url(request, &http->url);
+ aki_http_set_user_agent(request, USER_AGENT);
+ aki_http_request_stream(request, AKI_HTTP_GET, http->loop, http_callback, http);
}
static bool handler_http_wait_for_range(struct cch_handler *handler, struct cch_handler_wait *wait)
{
struct cch_handler_http *http = (struct cch_handler_http *)handler;
- aki_mutex_lock(&http->mutex);
- bool canceled = http->disabled;
- if (!canceled && (cch_entry_get_size(http->handler.entry) < 0 || http->pointer < wait->end)) {
- aki_mutex_lock(&wait->mutex);
- if (!wait->disabled) {
- al_array_push(http->handler.waits, wait);
- aki_mutex_unlock(&http->mutex);
- aki_cond_wait(&wait->cond, &wait->mutex);
- } else {
- aki_mutex_unlock(&wait->mutex);
- aki_mutex_unlock(&http->mutex);
- return false;
- }
- aki_mutex_unlock(&wait->mutex);
- aki_mutex_lock(&http->mutex);
- if (wait->disabled) {
- u16 id = wait->id;
- al_array_foreach(http->handler.waits, i, wait) {
- if (wait->id == id) {
- al_array_remove_at_iter(http->handler.waits, i);
- break;
- }
- }
- canceled = true;
- }
- }
- aki_mutex_unlock(&http->mutex);
- return !canceled;
+ return cch_threaded_wait_for_range(&http->handler, http->backing, wait);
}
static void handler_http_free(struct cch_handler **handler)
{
struct cch_handler_http *http = (struct cch_handler_http *)*handler;
- disable_waiters_internal(http);
- camu_queue_push(http->queue, -1);
- aki_signal_send(&http->signal);
- aki_thread_join(&http->thread);
- aki_mutex_destroy(&http->mutex);
+ cch_threaded_waits_disable_all(&http->handler);
struct aki_http *request;
al_array_foreach_ptr(http->requests, i, request) {
aki_http_close(request);
}
+ cch_threaded_waits_close(&http->handler);
al_array_free(http->requests);
- camu_queue_free(http->queue);
al_str_free(&http->url);
- al_array_free(http->handler.waits);
+ al_str_free(&http->handler.liana);
al_free(http);
*handler = NULL;
}
-static aki_thread_result AKI_THREADCALL event_loop_thread(void *userdata)
-{
- struct cch_handler_http *http = (struct cch_handler_http *)userdata;
- aki_event_loop_run(&http->loop);
- aki_event_loop_destroy(&http->loop);
- return 0;
-}
-
-struct cch_entry *cch_handler_http_create(str *url)
+struct cch_entry *cch_handler_http_create(str *url, struct aki_event_loop *loop)
{
- //struct cch_backing *backing = cch_backing_file_create(al_str_c("/tmp/camu_http_data"), 1024 * 128);
struct cch_backing *backing = cch_backing_memory_create(1024 * 128);
if (!backing) return NULL;
+ struct cch_handler_http *http = al_alloc_object(struct cch_handler_http);
+ http->backing = backing;
struct cch_entry *entry = al_alloc_object(struct cch_entry);
entry->backing = backing;
+ entry->handler = (struct cch_handler *)http;
entry->ref_count = 0;
+ entry->unknown_size = false;
entry->hash = 0;
- entry->handler = (struct cch_handler *)al_alloc_object(struct cch_handler_http);
aki_mutex_init(&entry->mutex);
entry->handler->can_seek = handler_http_can_seek;
entry->handler->maybe_spawn_worker = handler_http_maybe_spawn_worker;
entry->handler->wait_for_range = handler_http_wait_for_range;
entry->handler->free = handler_http_free;
entry->handler->entry = entry;
- entry->handler->backing = entry->backing;
- al_array_init(entry->handler->waits);
- struct cch_handler_http *http = (struct cch_handler_http *)entry->handler;
+ al_str_from(&entry->handler->liana, "codec");
+ http->loop = loop;
al_str_clone(&http->url, url);
http->pointer = 0;
- http->disabled = false;
al_array_init(http->requests);
- camu_queue_init(http->queue);
- aki_event_loop_init(&http->loop);
- aki_signal_init(&http->signal, signal_callback, http);
- aki_signal_start(&http->signal, &http->loop);
- aki_mutex_init(&http->mutex);
- aki_thread_create(&http->thread, event_loop_thread, http);
+ cch_threaded_waits_init(&http->handler);
return entry;
}
diff --git a/src/cache/handlers/http.h b/src/cache/handlers/http.h
index e8fb2e0..5f9df86 100644
--- a/src/cache/handlers/http.h
+++ b/src/cache/handlers/http.h
@@ -4,22 +4,16 @@
#include <aki/thread.h>
#include <aki/signal.h>
-#include "../../util/queue.h"
-
#include "../handler.h"
#include "../entry.h"
struct cch_handler_http {
struct cch_handler handler;
- off_t pointer;
+ struct cch_backing *backing;
str url;
- bool disabled;
+ off_t pointer;
array(struct aki_http) requests;
- queue(s32) queue;
- struct aki_event_loop loop;
- struct aki_signal signal;
- struct aki_mutex mutex;
- struct aki_thread thread;
+ struct aki_event_loop *loop;
};
-struct cch_entry *cch_handler_http_create(str *url);
+struct cch_entry *cch_handler_http_create(str *url, struct aki_event_loop *loop);
diff --git a/src/cache/meson.build b/src/cache/meson.build
index 099b2a6..8684ccd 100644
--- a/src/cache/meson.build
+++ b/src/cache/meson.build
@@ -1,7 +1,9 @@
cache_src = [
'entry.c',
'handle.c',
+ 'threaded_waits.c',
'handlers/file.c',
+ 'handlers/cdio.c',
'backings/memory.c'
]
diff --git a/src/cache/range.h b/src/cache/range.h
new file mode 100644
index 0000000..598b655
--- /dev/null
+++ b/src/cache/range.h
@@ -0,0 +1,23 @@
+#include <al/types.h>
+
+#include "backing.h"
+
+AL_UNUSED_FUNCTION_PUSH
+
+// TODO: cch_backing_remove_range.
+//
+// TODO: report overlap, don't unlock in backing_write() and handle fill in handler
+
+static void cch_backing_fill_range(struct cch_backing *backing, off_t index, off_t size)
+{
+ struct cch_range *range;
+ al_array_foreach_ptr(backing->available, i, range) {
+ if (index >= range->start && index <= range->end) {
+ range->end = AL_MAX(index + size, range->end);
+ return;
+ }
+ }
+ al_array_push(backing->available, ((struct cch_range){ index, index + size }));
+}
+
+AL_UNUSED_FUNCTION_POP
diff --git a/src/cache/threaded_waits.c b/src/cache/threaded_waits.c
new file mode 100644
index 0000000..a93fe85
--- /dev/null
+++ b/src/cache/threaded_waits.c
@@ -0,0 +1,126 @@
+#include "threaded_waits.h"
+#include "entry.h"
+
+void cch_threaded_waits_init(struct cch_handler *handler)
+{
+ handler->disabled = false;
+ aki_mutex_init(&handler->mutex);
+ al_array_init(handler->waits);
+}
+
+static bool wait_range_satisfied(struct cch_backing *backing, struct cch_handler_wait *wait)
+{
+ if (wait->end < 0) return true;
+ struct cch_range *range;
+ al_array_foreach_ptr(backing->available, i, range) {
+ if (wait->start >= range->start && range->end >= wait->end) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool cch_threaded_wait_for_range(struct cch_handler *handler, struct cch_backing *backing,
+ struct cch_handler_wait *wait)
+{
+ aki_mutex_lock(&handler->mutex);
+ bool canceled = handler->disabled;
+ off_t size = cch_entry_get_size(handler->entry);
+ backing->lock(backing);
+ bool satisfied = size >= 0 && wait_range_satisfied(backing, wait);
+ backing->unlock(backing);
+ if (!canceled && !satisfied) {
+ aki_mutex_lock(&wait->mutex);
+ if (!wait->disabled) {
+ al_array_push(handler->waits, wait);
+ aki_mutex_unlock(&handler->mutex);
+ aki_cond_wait(&wait->cond, &wait->mutex);
+ } else {
+ aki_mutex_unlock(&wait->mutex);
+ aki_mutex_unlock(&handler->mutex);
+ return false;
+ }
+ aki_mutex_unlock(&wait->mutex);
+ aki_mutex_lock(&handler->mutex);
+ if (wait->disabled) {
+ struct cch_handler_wait *rwait;
+ al_array_foreach(handler->waits, i, rwait) {
+ if (rwait == wait) {
+ al_array_remove_at(handler->waits, i);
+ break;
+ }
+ }
+ canceled = true;
+ }
+ }
+ aki_mutex_unlock(&handler->mutex);
+ return !canceled;
+}
+
+void cch_threaded_waits_signal_any(struct cch_handler *handler)
+{
+ aki_mutex_lock(&handler->mutex);
+ struct cch_handler_wait *wait;
+ al_array_foreach_rev(handler->waits, i, wait) {
+ if (wait->end < 0) {
+ al_array_remove_at(handler->waits, i);
+ aki_mutex_lock(&wait->mutex);
+ aki_cond_signal(&wait->cond);
+ aki_mutex_unlock(&wait->mutex);
+ }
+ }
+ aki_mutex_unlock(&handler->mutex);
+}
+
+void cch_threaded_wait_disable(struct cch_handler_wait *wait)
+{
+ aki_mutex_lock(&wait->mutex);
+ wait->disabled = true;
+ // cond_is_waiting() could be false if the handler is disabled.
+ if (aki_cond_is_waiting(&wait->cond)) {
+ aki_cond_signal(&wait->cond);
+ }
+ aki_mutex_unlock(&wait->mutex);
+}
+
+void cch_threaded_waits_disable_all(struct cch_handler *handler)
+{
+ aki_mutex_lock(&handler->mutex);
+ if (handler->disabled) {
+ aki_mutex_unlock(&handler->mutex);
+ return;
+ }
+ // Disallow any further waits.
+ handler->disabled = true;
+ struct cch_handler_wait *wait;
+ al_array_foreach(handler->waits, i, wait) {
+ aki_mutex_lock(&wait->mutex);
+ wait->disabled = true;
+ aki_cond_signal(&wait->cond);
+ aki_mutex_unlock(&wait->mutex);
+ }
+ aki_mutex_unlock(&handler->mutex);
+}
+
+void cch_threaded_waits_evaluate(struct cch_handler *handler, struct cch_backing *backing)
+{
+ aki_mutex_lock(&handler->mutex);
+ backing->lock(backing);
+ struct cch_handler_wait *wait;
+ al_array_foreach_rev(handler->waits, i, wait) {
+ aki_mutex_lock(&wait->mutex);
+ if (wait_range_satisfied(backing, wait)) {
+ aki_cond_signal(&wait->cond);
+ al_array_remove_at(handler->waits, i);
+ }
+ aki_mutex_unlock(&wait->mutex);
+ }
+ backing->unlock(backing);
+ aki_mutex_unlock(&handler->mutex);
+}
+
+void cch_threaded_waits_close(struct cch_handler *handler)
+{
+ aki_mutex_destroy(&handler->mutex);
+ al_array_free(handler->waits);
+}
diff --git a/src/cache/threaded_waits.h b/src/cache/threaded_waits.h
new file mode 100644
index 0000000..edbee03
--- /dev/null
+++ b/src/cache/threaded_waits.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <al/types.h>
+
+#include "handler.h"
+#include "backing.h"
+
+void cch_threaded_waits_init(struct cch_handler *handler);
+bool cch_threaded_wait_for_range(struct cch_handler *handler, struct cch_backing *backing,
+ struct cch_handler_wait *wait);
+void cch_threaded_waits_signal_any(struct cch_handler *handler);
+void cch_threaded_wait_disable(struct cch_handler_wait *wait);
+void cch_threaded_waits_disable_all(struct cch_handler *handler);
+void cch_threaded_waits_evaluate(struct cch_handler *handler, struct cch_backing *backing);
+void cch_threaded_waits_close(struct cch_handler *handler);
diff --git a/src/cache/wait.h b/src/cache/wait.h
new file mode 100644
index 0000000..8654362
--- /dev/null
+++ b/src/cache/wait.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <aki/thread.h>
+
+struct cch_handler_wait {
+ off_t start, end;
+ struct aki_cond cond;
+ struct aki_mutex mutex;
+ bool disabled;
+};
diff --git a/src/codec/codec.h b/src/codec/codec.h
index fd847b8..9528522 100644
--- a/src/codec/codec.h
+++ b/src/codec/codec.h
@@ -3,38 +3,94 @@
#include <al/types.h>
#include <al/array.h>
#include <aki/common.h>
-
-#include "../cache/handle.h"
-
#ifdef CAMU_HAVE_FFMPEG
#include <libavutil/pixdesc.h>
#include <libavformat/avformat.h>
#endif
+#include "../cache/handle.h"
+
+#ifndef CAMU_SINK_NO_VIDEO
+#include "../render/renderer.h"
+#else
+struct camu_renderer;
+#endif
+
#ifdef CAMU_HAVE_FFMPEG
enum {
CAMU_OK = 0,
+ CAMU_ERR_ERROR = 1,
CAMU_ERR_EOF = AVERROR_EOF,
CAMU_ERR_AGAIN = AVERROR(EAGAIN)
};
enum {
CAMU_SEEK_SIZE = AVSEEK_SIZE
};
+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
+};
+enum {
+ CAMU_SAMPLE_FORMAT_NONE = AV_SAMPLE_FMT_NONE,
+ CAMU_SAMPLE_FORMAT_U8 = AV_SAMPLE_FMT_U8,
+ CAMU_SAMPLE_FORMAT_S16 = AV_SAMPLE_FMT_S16,
+ CAMU_SAMPLE_FORMAT_S32 = AV_SAMPLE_FMT_S32,
+ CAMU_SAMPLE_FORMAT_FLT = AV_SAMPLE_FMT_FLT,
+ CAMU_SAMPLE_FORMAT_DBL = AV_SAMPLE_FMT_DBL,
+ CAMU_SAMPLE_FORMAT_U8P = AV_SAMPLE_FMT_U8P,
+ CAMU_SAMPLE_FORMAT_S16P = AV_SAMPLE_FMT_S16P,
+ CAMU_SAMPLE_FORMAT_S32P = AV_SAMPLE_FMT_S32P,
+ CAMU_SAMPLE_FORMAT_FLTP = AV_SAMPLE_FMT_FLTP,
+ CAMU_SAMPLE_FORMAT_DBLP = AV_SAMPLE_FMT_DBLP,
+ CAMU_SAMPLE_FORMAT_S64 = AV_SAMPLE_FMT_S64,
+ CAMU_SAMPLE_FORMAT_S64P = AV_SAMPLE_FMT_S64P
+};
+enum {
+ CAMU_PIXEL_FORMAT_RGBA = AV_PIX_FMT_RGBA,
+ CAMU_PIXEL_FORMAT_RGB = AV_PIX_FMT_RGB8,
+ CAMU_PIXEL_FORMAT_GREYA = AV_PIX_FMT_GRAY8A,
+ CAMU_PIXEL_FORMAT_GREY = AV_PIX_FMT_GRAY8
+};
#else
enum {
CAMU_OK = 0,
+ CAMU_ERR_ERROR = 1,
CAMU_ERR_EOF = -1,
CAMU_ERR_AGAIN = -2
};
enum {
CAMU_SEEK_SIZE = 0x10000
};
-#endif
-
enum {
- CAMU_PIXEL_FMT_RGB = 0,
- CAMU_PIXEL_FMT_RGBA
+ CAMU_STREAM_UNKNOWN = 0,
+ CAMU_STREAM_AUDIO,
+ CAMU_STREAM_VIDEO,
+ CAMU_STREAM_SUBTITLE
};
+enum {
+ CAMU_SAMPLE_FORMAT_NONE = -1,
+ CAMU_SAMPLE_FORMAT_U8,
+ CAMU_SAMPLE_FORMAT_S16,
+ CAMU_SAMPLE_FORMAT_S32,
+ CAMU_SAMPLE_FORMAT_FLT,
+ CAMU_SAMPLE_FORMAT_DBL,
+ CAMU_SAMPLE_FORMAT_U8P,
+ CAMU_SAMPLE_FORMAT_S16P,
+ CAMU_SAMPLE_FORMAT_S32P,
+ CAMU_SAMPLE_FORMAT_FLTP,
+ CAMU_SAMPLE_FORMAT_DBLP,
+ CAMU_SAMPLE_FORMAT_S64,
+ CAMU_SAMPLE_FORMAT_S64P
+};
+enum {
+ CAMU_PIXEL_FORMAT_RGBA = 0,
+ CAMU_PIXEL_FORMAT_RGB,
+ CAMU_PIXEL_FORMAT_GREYA,
+ CAMU_PIXEL_FORMAT_GREY
+};
+#endif
enum {
CAMU_FRAME_DEVICE_ALLOCATED = 1
@@ -47,21 +103,27 @@ enum {
#endif
};
-struct camu_icc_profile {
- u8 *data;
- size_t size;
+struct camu_audio_format {
+ s32 format;
+ s32 sample_rate;
+#ifdef CAMU_HAVE_FFMPEG
+ AVChannelLayout channel_layout;
+#endif
+ s32 channel_count;
};
-struct camu_stream {
+struct camu_codec_stream {
+ u8 mode;
u8 type;
- s64 duration;
+ u64 duration;
struct {
- u8 fmt;
s32 width;
s32 height;
- struct camu_icc_profile icc;
+ s32 format;
} video;
- //struct { } audio;
+ struct {
+ struct camu_audio_format fmt;
+ } audio;
#ifdef CAMU_HAVE_FFMPEG
struct {
AVStream *stream;
@@ -70,56 +132,182 @@ struct camu_stream {
#endif
};
-struct camu_packet {
- u8 type;
+struct camu_codec_packet {
+ u8 mode;
struct aki_buffer *buffer;
#ifdef CAMU_HAVE_FFMPEG
struct { AVPacket *pkt; } av;
#endif
};
-struct camu_frame {
- u8 type;
- u8 *data;
+struct camu_codec_frame {
+ u8 mode;
u8 flags;
- u8 fmt;
- s32 width;
- s32 height;
+ u8 *data;
+ s32 format;
f64 pts;
+ struct {
+ s32 width;
+ s32 height;
+ } video;
+ struct {
+ u32 sample_count;
+ } audio;
#ifdef CAMU_HAVE_FFMPEG
struct { AVFrame *frame; } av;
#endif
- void *opaque;
};
struct camu_demuxer {
- u8 type;
+ u8 mode;
bool (*init)(struct camu_demuxer *, struct cch_handle *);
- s32 (*get_packet)(struct camu_demuxer *, struct camu_packet *);
+ s32 (*get_packet)(struct camu_demuxer *, struct camu_codec_packet *);
u64 (*get_duration)(struct camu_demuxer *);
bool (*seek)(struct camu_demuxer *, u64);
void (*free)(struct camu_demuxer **);
- array(struct camu_stream) streams;
- array(s32) subscribed;
+ array(struct camu_codec_stream) streams;
+ s32 subscribed;
};
-struct camu_renderer;
struct camu_decoder {
- bool (*init)(struct camu_decoder *, struct camu_renderer *, struct camu_stream *);
- void (*set_callback)(struct camu_decoder *, void (*callback)(void *, struct camu_frame *), void *);
- s32 (*push)(struct camu_decoder *, struct camu_packet *);
+ bool (*init)(struct camu_decoder *, struct camu_renderer *, struct camu_codec_stream *,
+ void (*callback)(void *, struct camu_codec_frame *), void *);
+ s32 (*push)(struct camu_decoder *, struct camu_codec_packet *);
s32 (*process)(struct camu_decoder *);
void (*flush)(struct camu_decoder *);
void (*free)(struct camu_decoder **);
- struct camu_stream *stream;
};
-static inline void camu_frame_discard(struct camu_frame *frame)
+struct camu_resampler_format {
+ bool resampler_needed;
+ struct camu_audio_format in;
+ struct camu_audio_format req;
+};
+
+struct camu_resampler {
+ bool (*init)(struct camu_resampler *, struct camu_resampler_format *);
+ s32 (*convert)(struct camu_resampler *, const u8 **, s32);
+ s32 (*flush)(struct camu_resampler *);
+ u8 **(*get_data)(struct camu_resampler *);
+ void (*free)(struct camu_resampler **);
+};
+
+AL_UNUSED_FUNCTION_PUSH
+
+static inline bool camu_resampler_format_matches(struct camu_resampler_format *fmt)
{
+ return fmt->in.format == fmt->req.format
+ && fmt->in.sample_rate == fmt->req.sample_rate
#ifdef CAMU_HAVE_FFMPEG
- if (frame->type == CAMU_FFMPEG_COMPAT) {
- av_frame_free(&frame->av.frame);
+ && av_channel_layout_compare(&fmt->in.channel_layout, &fmt->req.channel_layout) == 0
+#endif
+ && fmt->in.channel_count == fmt->req.channel_count;
+}
+
+static inline void camu_audio_format_copy(struct camu_audio_format *dest, struct camu_audio_format *src)
+{
+ dest->format = src->format;
+ dest->sample_rate = src->sample_rate;
+#ifdef CAMU_HAVE_FFMPEG
+ av_channel_layout_copy(&dest->channel_layout, &src->channel_layout);
+#endif
+ dest->channel_count = src->channel_count;
+}
+
+static const char *camu_audio_format_name(s32 format)
+{
+#ifdef CAMU_HAVE_FFMPEG
+ return av_get_sample_fmt_name(format);
+#else
+ (void)format;
+ return "(undefined)";
+#endif
+}
+
+static inline s32 camu_audio_format_bytes_per_sample(struct camu_audio_format *fmt)
+{
+#if CAMU_HAVE_FFMPEG
+ return av_get_bytes_per_sample(fmt->format);
+#else
+ switch (fmt->format) {
+ case CAMU_SAMPLE_FORMAT_U8:
+ case CAMU_SAMPLE_FORMAT_U8P: return 1;
+ case CAMU_SAMPLE_FORMAT_S16:
+ case CAMU_SAMPLE_FORMAT_S16P: return 2;
+ case CAMU_SAMPLE_FORMAT_S32:
+ case CAMU_SAMPLE_FORMAT_S32P:
+ case CAMU_SAMPLE_FORMAT_FLT:
+ case CAMU_SAMPLE_FORMAT_FLTP: return 4;
+ case CAMU_SAMPLE_FORMAT_S64:
+ case CAMU_SAMPLE_FORMAT_S64P:
+ case CAMU_SAMPLE_FORMAT_DBL:
+ case CAMU_SAMPLE_FORMAT_DBLP: return 8;
+ default:
+ al_assert(false);
+ return -1;
}
#endif
+}
+
+static inline size_t camu_audio_format_samples_to_bytes(struct camu_audio_format *fmt, size_t samples)
+{
+ s32 bytes_per_sample = camu_audio_format_bytes_per_sample(fmt);
+ return samples * bytes_per_sample * fmt->channel_count;
+}
+
+static inline f64 camu_audio_format_samples_to_sec(struct camu_audio_format *fmt, size_t samples)
+{
+ return samples / (f64)fmt->sample_rate;
+}
+
+static inline size_t camu_audio_format_usec_to_bytes(struct camu_audio_format *fmt, size_t usec)
+{
+ // Keep order of operations for precision.
+ size_t samples = usec * (fmt->sample_rate / 1000000.0);
+ return samples * camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count;
+}
+
+static inline size_t camu_audio_format_sec_to_bytes(struct camu_audio_format *fmt, f64 sec)
+{
+ size_t samples = sec * fmt->sample_rate;
+ return samples * camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count;
+}
+
+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);
+ return samples / (f64)fmt->sample_rate;
+}
+
+static const char *camu_pixel_format_name(s32 format)
+{
+ switch (format) {
+ case CAMU_PIXEL_FORMAT_RGBA: return "rgba";
+ case CAMU_PIXEL_FORMAT_RGB: return "rgb";
+ case CAMU_PIXEL_FORMAT_GREYA: return "grey + alpha";
+ case CAMU_PIXEL_FORMAT_GREY: return "grey";
+ }
+ return "(unknown)";
+}
+
+static inline s32 camu_pixel_format_from_channels(s32 channels)
+{
+ switch (channels) {
+ case 4: return CAMU_PIXEL_FORMAT_RGBA;
+ case 3: return CAMU_PIXEL_FORMAT_RGB;
+ case 2: return CAMU_PIXEL_FORMAT_GREYA;
+ case 1: return CAMU_PIXEL_FORMAT_GREY;
+ default: al_assert(false); return CAMU_PIXEL_FORMAT_GREY;
+ }
+}
+
+static inline void camu_codec_frame_discard(struct camu_codec_frame *frame)
+{
+#ifdef CAMU_HAVE_FFMPEG
+ if (frame->mode == CAMU_FFMPEG_COMPAT) av_frame_free(&frame->av.frame);
+#endif
+ if (frame->mode == CAMU_NORMAL && frame->data) al_free(frame->data);
al_free(frame);
}
+
+AL_UNUSED_FUNCTION_POP
diff --git a/src/codec/common.h b/src/codec/common.h
index adf503d..98665d7 100644
--- a/src/codec/common.h
+++ b/src/codec/common.h
@@ -13,17 +13,7 @@ static inline void *camu_page_alloc(size_t size)
#endif
}
-static inline void *camu_page_realloc(void *ptr, size_t old_size, size_t size)
+static inline void *camu_page_realloc(void *ptr, size_t size)
{
-#ifdef AL_HAVE_POSIX_MEMALIGN
- void *old_ptr = ptr;
- al_posix_memalign(&ptr, al_page_size, size);
- if (old_ptr) {
- al_memcpy(ptr, old_ptr, old_size);
- al_free(old_ptr);
- }
- return ptr;
-#else
return al_realloc(ptr, size);
-#endif
}
diff --git a/src/codec/ffmpeg/common.c b/src/codec/ffmpeg/common.c
index 57caff7..b7a232e 100644
--- a/src/codec/ffmpeg/common.c
+++ b/src/codec/ffmpeg/common.c
@@ -1,6 +1,38 @@
+#include <al/lib.h>
+#include <al/log.h>
+#include <libavutil/log.h>
+
#include "common.h"
void camu_ff_set_log_callback(void (*callback)(void *, int, const char *, va_list))
{
av_log_set_callback(callback);
}
+
+static char *av_log_buf = NULL;
+static s32 av_log_pos = 0;
+
+static void av_log_callback(void *userdata, int level, const char *fmt, va_list args)
+{
+ (void)userdata;
+ al_assert(av_log_buf);
+ if (level < AV_LOG_DEBUG) {
+ av_log_pos += al_vsnprintf(&av_log_buf[av_log_pos], 512, fmt, args);
+ if (av_log_buf[av_log_pos - 1] == '\n' || av_log_pos >= 512) {
+ al_log_info("ff_log", av_log_buf);
+ av_log_pos = 0;
+ }
+ }
+}
+
+void camu_ff_set_default_log_callback()
+{
+ av_log_buf = (char *)al_malloc(1024);
+ camu_ff_set_log_callback(av_log_callback);
+}
+
+
+void camu_ff_free_default_log_callback()
+{
+ al_free(av_log_buf);
+}
diff --git a/src/codec/ffmpeg/common.h b/src/codec/ffmpeg/common.h
index ded7b35..229ffb0 100644
--- a/src/codec/ffmpeg/common.h
+++ b/src/codec/ffmpeg/common.h
@@ -1,6 +1,10 @@
#pragma once
#include <al/types.h>
-#include <libavutil/log.h>
+#include <al/log.h>
+// userdata, level, fmt, args
void camu_ff_set_log_callback(void (*callback)(void *, int, const char *, va_list));
+
+void camu_ff_set_default_log_callback();
+void camu_ff_free_default_log_callback();
diff --git a/src/codec/ffmpeg/decoder.c b/src/codec/ffmpeg/decoder.c
index 7071bea..d231b2f 100644
--- a/src/codec/ffmpeg/decoder.c
+++ b/src/codec/ffmpeg/decoder.c
@@ -1,8 +1,6 @@
-#include <libavutil/cpu.h>
#include <al/lib.h>
#include <al/log.h>
-
-//#include "../../render/renderer.h"
+#include <libavutil/cpu.h>
#include "decoder.h"
@@ -11,7 +9,8 @@ static void close_internal(struct camu_ff_decoder *av)
if (av->codec_context) avcodec_free_context(&av->codec_context);
}
-static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *renderer, struct camu_stream *stream)
+static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *renderer, struct camu_codec_stream *stream,
+ void (*callback)(void *, struct camu_codec_frame *), void *userdata)
{
struct camu_ff_decoder *av = (struct camu_ff_decoder *)dec;
@@ -37,49 +36,45 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend
/*
s32 cpus = av_cpu_count();
- cpus = (cpus > 16) ? 4 : cpus / 4;
- if (cpus == 0) cpus = 1;
+ if (cpus > 4) cpus = 2;
av->codec_context->thread_count = cpus;
+ av->codec_context->thread_type = FF_THREAD_SLICE;
al_log_debug("ff_decoder", "Using %i threads for decoder.", cpus);
*/
- (void)renderer;
- /*
- if (renderer && renderer->get_buffer2 && codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+#ifndef CAMU_SINK_NO_VIDEO
+ if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO && renderer && renderer->get_buffer2) {
av->codec_context->get_buffer2 = renderer->get_buffer2;
av->codec_context->opaque = renderer->opaque;
}
- */
+#else
+ (void)renderer;
+#endif
if (avcodec_open2(av->codec_context, codec, NULL) < 0) {
al_log_error("ff_decoder", "Failed to open codec (%s).", codec->name);
goto err;
}
- if (codec->long_name) {
- al_log_info("ff_decoder", "Codec: %s (%s) %ldkbps.", codec->name, codec->long_name, codecpar->bit_rate / 1000);
- } else {
- al_log_info("ff_decoder", "Codec: %s %ldkbps.", codec->name, codecpar->bit_rate / 1000);
- }
+ const char *long_name = codec->long_name ? codec->long_name : codec->name;
+ 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->last_pts = 0;
- av->last_duration = 0;
+ av->duration = stream->av.stream->duration;
+ //av->last_pts = 0;
+ //av->last_duration = 0;
//av->seek_pos = -1;
+ av->callback = callback;
+ av->userdata = userdata;
+
return true;
err:
close_internal(av);
return false;
}
-static void ff_decoder_set_callback(struct camu_decoder *dec, void (*callback)(void *, struct camu_frame *), void *userdata)
-{
- struct camu_ff_decoder *av = (struct camu_ff_decoder *)dec;
- av->callback = callback;
- av->userdata = userdata;
-}
-
static s32 send_packet(struct camu_ff_decoder *av, AVPacket *pkt)
{
/*
@@ -92,16 +87,16 @@ static s32 send_packet(struct camu_ff_decoder *av, AVPacket *pkt)
s32 ret = avcodec_send_packet(av->codec_context, pkt);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
- al_log_error("ff_decoder", "Error sending packet to decoder: (%s).", av_err2str(ret));
+ al_log_error("ff_decoder", "Error sending packet to the decoder: (%s).", av_err2str(ret));
}
return ret;
}
-static s32 ff_decoder_push(struct camu_decoder *dec, struct camu_packet *packet)
+static s32 ff_decoder_push(struct camu_decoder *dec, struct camu_codec_packet *packet)
{
struct camu_ff_decoder *av = (struct camu_ff_decoder *)dec;
- return send_packet(av, (packet) ? packet->av.pkt : NULL);
+ return send_packet(av, packet ? packet->av.pkt : NULL);
}
static void ff_decoder_flush(struct camu_decoder *dec)
@@ -114,19 +109,19 @@ static s32 receive_frames(struct camu_ff_decoder *av)
{
s32 ret;
do {
- struct camu_frame *frame = al_alloc_object(struct camu_frame);
- frame->type = CAMU_FFMPEG_COMPAT;
+ struct camu_codec_frame *frame = al_alloc_object(struct camu_codec_frame);
+ frame->mode = CAMU_FFMPEG_COMPAT;
frame->av.frame = av_frame_alloc();
ret = avcodec_receive_frame(av->codec_context, frame->av.frame);
if (ret < 0) {
av_frame_free(&frame->av.frame);
al_free(frame);
+ // Checking for EAGAIN should prevent an infinite loop.
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
- al_log_error("ff_decoder", "Error receiving packet from decoder: (%s).", av_err2str(ret));
+ al_log_error("ff_decoder", "Error receiving packet from the decoder: (%s).", av_err2str(ret));
continue;
}
- // Track pts and duration of the previous frame so we can handle multiple frames
- // in a single packet.
+ // Track pts and duration of the previous frame so we can handle multiple frames in a single packet.
//if (av->codec_context->codec_type == AVMEDIA_TYPE_AUDIO) {
// if (frame->av.frame->best_effort_timestamp < 0) {
// frame->av.frame->best_effort_timestamp = av->last_pts + av->last_duration;
@@ -158,7 +153,6 @@ struct camu_decoder *camu_ff_decoder_create(void)
{
struct camu_ff_decoder *av = al_alloc_object(struct camu_ff_decoder);
av->dec.init = ff_decoder_init;
- av->dec.set_callback = ff_decoder_set_callback;
av->dec.push = ff_decoder_push;
av->dec.process = ff_decoder_process;
av->dec.flush = ff_decoder_flush;
diff --git a/src/codec/ffmpeg/decoder.h b/src/codec/ffmpeg/decoder.h
index 45cca00..88bbd4c 100644
--- a/src/codec/ffmpeg/decoder.h
+++ b/src/codec/ffmpeg/decoder.h
@@ -1,7 +1,6 @@
#pragma once
#include <al/types.h>
-
#include <libavcodec/avcodec.h>
#include "../codec.h"
@@ -10,10 +9,11 @@ struct camu_ff_decoder {
struct camu_decoder dec;
AVCodecContext *codec_context;
AVRational time_base;
- s64 last_pts;
- s64 last_duration;
+ u64 duration;
+ //s64 last_pts;
+ //s64 last_duration;
//s64 seek_pos;
- void (*callback)(void *, struct camu_frame *);
+ void (*callback)(void *, struct camu_codec_frame *);
void *userdata;
};
diff --git a/src/codec/ffmpeg/demuxer.c b/src/codec/ffmpeg/demuxer.c
index 9b26596..7ff0dca 100644
--- a/src/codec/ffmpeg/demuxer.c
+++ b/src/codec/ffmpeg/demuxer.c
@@ -3,7 +3,7 @@
#include "demuxer.h"
#include "avio.h"
-#define BUF_SIZE 16384
+#define DEMUX_BUF_SIZE (4096 * 4)
static void close_internal(struct camu_ff_demuxer *av)
{
@@ -28,16 +28,16 @@ static bool ff_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handl
goto err;
}
- u8 *buf = (u8 *)av_malloc(BUF_SIZE);
- av->io_context = avio_alloc_context(buf, BUF_SIZE, 0, handle, camu_avio_read, NULL, camu_avio_seek);
+ u8 *buf = (u8 *)av_malloc(DEMUX_BUF_SIZE);
+ av->io_context = avio_alloc_context(buf, DEMUX_BUF_SIZE, 0, handle, camu_avio_read, NULL, camu_avio_seek);
if (!av->io_context) {
al_log_error("ff_demuxer", "Failed to create custom io context.");
goto err;
}
if (!cch_handle_can_seek(handle)) {
- // This should always be unset if we know we cannot seek.
- // But, should also never be unset if we *do* want to seek because it
+ // AVIO_SEEKABLE_NORMAL should always be unset if we know we cannot seek.
+ // However, it should also never be unset if we *do* want to seek because it
// disables long seeks (Not tested in a long time).
av->io_context->seekable &= ~AVIO_SEEKABLE_NORMAL;
} else {
@@ -47,12 +47,19 @@ static bool ff_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handl
av->format_context->pb = av->io_context;
av->format_context->flags |= AVFMT_FLAG_CUSTOM_IO;
- if (avformat_open_input(&av->format_context, "", 0, 0) < 0) {
+ AVDictionary *opts = NULL;
+ av_dict_set(&opts, "analyzeduration", "10000000", 0);
+ av_dict_set(&opts, "probesize", "20M", 0);
+
+ if (avformat_open_input(&av->format_context, "file:", NULL, &opts) < 0) {
al_log_error("ff_demuxer", "Failed to open input.");
+ av_dict_free(&opts);
goto err;
}
- if (avformat_find_stream_info(av->format_context, 0) < 0) {
+ av_dict_free(&opts);
+
+ if (avformat_find_stream_info(av->format_context, NULL) < 0) {
al_log_error("ff_demuxer", "Failed to find stream info.");
goto err;
}
@@ -70,6 +77,7 @@ 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);
@@ -83,12 +91,14 @@ static bool ff_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handl
}
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 < 0) stream->start_time = 0;
+ 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_stream){
- .type = CAMU_FFMPEG_COMPAT,
+ al_array_push(av->demux.streams, ((struct camu_codec_stream){
+ .mode = CAMU_FFMPEG_COMPAT,
+ .type = type,
+ .duration = (u64)duration,
.av.stream = stream
}));
}
@@ -106,30 +116,15 @@ err:
return false;
}
-/*
-static AVStream *av_stream_at_index(struct camu_ff_demuxer *av, s32 index)
-{
- struct camu_stream *stream;
- al_array_foreach_ptr(av->demux.streams, i, stream) {
- if (stream->av.stream->index == index) return stream->av.stream;
- }
- return NULL;
-}
-*/
-
static bool subscribed_to_index(struct camu_demuxer *demux, s32 index)
{
- s32 subscribed;
- al_array_foreach(demux->subscribed, i, subscribed) {
- if (subscribed == index) return true;
- }
- return false;
+ return ((demux->subscribed >> index) & 1);
}
-static s32 ff_demuxer_get_packet(struct camu_demuxer *demux, struct camu_packet *packet)
+static s32 ff_demuxer_get_packet(struct camu_demuxer *demux, struct camu_codec_packet *packet)
{
struct camu_ff_demuxer *av = (struct camu_ff_demuxer *)demux;
- packet->type = CAMU_FFMPEG_COMPAT;
+ packet->mode = CAMU_FFMPEG_COMPAT;
s32 ret = 0;
do {
ret = av_read_frame(av->format_context, packet->av.pkt);
@@ -160,12 +155,13 @@ static bool ff_demuxer_seek(struct camu_demuxer *demux, u64 pos)
{
struct camu_ff_demuxer *av = (struct camu_ff_demuxer *)demux;
if (pos > av->duration) pos = av->duration;
+ al_assert(pos <= INT64_MAX);
s64 ts = av_rescale_q((s64)pos, (AVRational){ 1, 1000000 }, AV_TIME_BASE_Q);
if (av->eof) {
avformat_flush(av->format_context);
av->eof = false;
}
- if (avformat_seek_file(av->format_context, -1, INT64_MIN, ts, ts, AVSEEK_FLAG_BACKWARD) < 0) {
+ if (avformat_seek_file(av->format_context, -1, INT64_MIN, ts, ts, 0) < 0) {
return false;
}
av->seeked = true;
@@ -184,9 +180,8 @@ static void ff_demuxer_free(struct camu_demuxer **demux)
struct camu_demuxer *camu_ff_demuxer_create(void)
{
struct camu_ff_demuxer *av = al_alloc_object(struct camu_ff_demuxer);
- av->demux.type = CAMU_FFMPEG_COMPAT;
+ av->demux.mode = CAMU_FFMPEG_COMPAT;
al_array_init(av->demux.streams);
- al_array_init(av->demux.subscribed);
av->demux.init = ff_demuxer_init;
av->demux.get_packet = ff_demuxer_get_packet;
av->demux.get_duration = ff_demuxer_get_duration;
diff --git a/src/codec/ffmpeg/demuxer.h b/src/codec/ffmpeg/demuxer.h
index 8327534..68ad03e 100644
--- a/src/codec/ffmpeg/demuxer.h
+++ b/src/codec/ffmpeg/demuxer.h
@@ -3,7 +3,6 @@
#include <al/types.h>
#include <al/lib.h>
#include <al/array.h>
-
#include <libavformat/avformat.h>
#include "../codec.h"
diff --git a/src/codec/ffmpeg/encoder.c b/src/codec/ffmpeg/encoder.c
index 4617baf..83a8b55 100644
--- a/src/codec/ffmpeg/encoder.c
+++ b/src/codec/ffmpeg/encoder.c
@@ -1,3 +1,5 @@
+#include <al/lib.h>
+#include <al/log.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/codec.h>
#include <libavformat/avformat.h>
@@ -6,13 +8,12 @@
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/samplefmt.h>
-#include <al/lib.h>
-#include <al/log.h>
#include "encoder.h"
#define BITRATE 256000
-#define FRAME_SIZE 20
+#define FRAME_DURATION 20
+#define SAMPLES_PER_FRAME (48000 / 1000.0 * FRAME_DURATION)
#define CHANNELS 2
static void free_frame_data(struct camu_ff_encoder *enc)
@@ -20,15 +21,6 @@ static void free_frame_data(struct camu_ff_encoder *enc)
if (enc->frame && enc->frame->nb_samples > 0) av_freep(&enc->frame->data[0]);
}
-static void alloc_frame_data(struct camu_ff_encoder *enc, s32 frame_size)
-{
- if (enc->frame->nb_samples >= frame_size) return;
- free_frame_data(enc);
- s32 nb_channels = enc->frame->ch_layout.nb_channels;
- av_samples_alloc(enc->frame->data, NULL, nb_channels, frame_size, enc->frame->format, 0);
- enc->frame->nb_samples = frame_size;
-}
-
bool camu_ff_encoder_init(struct camu_ff_encoder *enc, char *encoder_name, char *ext)
{
al_memset(enc, 0, sizeof(struct camu_ff_encoder));
@@ -51,7 +43,7 @@ bool camu_ff_encoder_init(struct camu_ff_encoder *enc, char *encoder_name, char
av_channel_layout_default(&enc->codec_context->ch_layout, CHANNELS);
enc->codec_context->sample_fmt = AV_SAMPLE_FMT_FLT;
enc->codec_context->bit_rate = BITRATE;
- enc->codec_context->frame_size = FRAME_SIZE;
+ enc->codec_context->frame_size = SAMPLES_PER_FRAME;
enc->codec_context->time_base = (AVRational){ 1, enc->codec_context->sample_rate };
enc->codec_context->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
@@ -77,23 +69,17 @@ bool camu_ff_encoder_init(struct camu_ff_encoder *enc, char *encoder_name, char
AVCodecContext *out_codec = enc->codec_context;
- av_channel_layout_copy(&enc->fmt.req_channel_layout, &out_codec->ch_layout);
- enc->fmt.req_sample_rate = out_codec->sample_rate;
- enc->fmt.req_format = out_codec->sample_fmt;
-
- enc->audio_fifo = av_audio_fifo_alloc(enc->fmt.req_format,
- enc->fmt.req_channel_layout.nb_channels, 1);
-
- if (!enc->audio_fifo) {
- al_log_error("ff_encoder", "Failed to alloc fifo.");
- goto err;
- }
+ enc->fmt.req.format = out_codec->sample_fmt;
+ enc->fmt.req.sample_rate = out_codec->sample_rate;
+ av_channel_layout_copy(&enc->fmt.req.channel_layout, &out_codec->ch_layout);
+ enc->fmt.req.channel_count = out_codec->ch_layout.nb_channels;
enc->frame = av_frame_alloc();
- enc->frame->nb_samples = 0;
- enc->frame->sample_rate = enc->fmt.req_sample_rate;
- av_channel_layout_copy(&enc->frame->ch_layout, &enc->fmt.req_channel_layout);
- enc->frame->format = enc->fmt.req_format;
+ enc->frame->format = enc->fmt.req.format;
+ enc->frame->sample_rate = enc->fmt.req.sample_rate;
+ av_channel_layout_copy(&enc->frame->ch_layout, &enc->fmt.req.channel_layout);
+ av_samples_alloc(enc->frame->data, NULL, enc->frame->ch_layout.nb_channels, SAMPLES_PER_FRAME, enc->frame->format, 1);
+ enc->frame->nb_samples = SAMPLES_PER_FRAME;
enc->frame->pts = 0;
return true;
@@ -104,93 +90,31 @@ err:
void camu_ff_encoder_push(struct camu_ff_encoder *enc, u8 **data, s32 sample_count)
{
- s32 alloc_size = av_audio_fifo_size(enc->audio_fifo) + sample_count;
- if (av_audio_fifo_space(enc->audio_fifo) < alloc_size) {
- if (av_audio_fifo_realloc(enc->audio_fifo, alloc_size) < 0) {
- al_assert(false);
- }
+ al_assert(sample_count == SAMPLES_PER_FRAME * CHANNELS);
+ enc->frame->data[0] = data[0];
+ s32 ret = avcodec_send_frame(enc->codec_context, enc->frame);
+ if (ret < 0 && ret != AVERROR_EOF) {
+ al_log_error("ff_encoder", "Error sending frame to the encoder: (%s).", av_err2str(ret));
+ return;
}
- av_audio_fifo_write(enc->audio_fifo, (void **)data, sample_count);
-}
-
-static bool receive_packets(struct camu_ff_encoder *enc)
-{
- s32 ret;
AVPacket pkt = { 0 };
-
- do {
- ret = avcodec_receive_packet(enc->codec_context, &pkt);
- if (ret < 0) break;
- enc->callback(enc->userdata, &pkt);
- } while (1);
-
- if (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
- al_log_error("ff_encoder", "Error receiving packet from encoder: (%s).", av_err2str(ret));
- return false;
- }
-
- return true;
-}
-
-static s32 send_frame(struct camu_ff_encoder *enc, AVFrame *in_frame)
-{
- s32 ret = avcodec_send_frame(enc->codec_context, in_frame);
+ ret = avcodec_receive_packet(enc->codec_context, &pkt);
if (ret < 0 && ret != AVERROR_EOF) {
- al_log_error("ff_encoder", "Error sending frame to encoder: (%s).", av_err2str(ret));
+ al_log_error("ff_encoder", "Error receiving packet from the encoder: (%s).", av_err2str(ret));
+ return;
}
- return ret;
-}
-
-static s32 read_frame(struct camu_ff_encoder *enc, s32 frame_size)
-{
- alloc_frame_data(enc, frame_size);
- return av_audio_fifo_read(enc->audio_fifo, (void **)enc->frame->data, frame_size);
-}
-
-bool camu_ff_encoder_process(struct camu_ff_encoder *enc, bool flush)
-{
- s32 samples_available = av_audio_fifo_size(enc->audio_fifo);
- if (samples_available == 0 && flush) {
- // Flush encoder.
- send_frame(enc, NULL);
- receive_packets(enc);
- // This should guarantee that there are not packets left.
- return false;
- }
-
- s32 frame_size = enc->codec_context->frame_size;
- if (samples_available == 0 || (samples_available < frame_size && !flush)) {
- return false;
- }
-
- s32 ret;
- do {
- if (samples_available < frame_size) frame_size = samples_available;
- read_frame(enc, frame_size);
- samples_available -= frame_size;
- do {
- ret = send_frame(enc, enc->frame);
- if (ret < 0) break;
- if (!receive_packets(enc)) return false;
- } while (ret == AVERROR(EAGAIN));
- if (ret < 0) break;
- enc->frame->pts += frame_size;
- } while (samples_available > 0);
-
- // ret < 0 means encoder errored while sending frame.
- return ret == 0;
+ enc->callback(enc->userdata, &pkt);
+ enc->frame->pts += SAMPLES_PER_FRAME;
}
void camu_ff_encoder_reset(struct camu_ff_encoder *enc)
{
avcodec_flush_buffers(enc->codec_context);
- av_audio_fifo_reset(enc->audio_fifo);
}
void camu_ff_encoder_close(struct camu_ff_encoder *enc)
{
if (enc->codec_context) avcodec_free_context(&enc->codec_context);
- if (enc->audio_fifo) av_audio_fifo_free(enc->audio_fifo);
free_frame_data(enc);
if (enc->frame) av_frame_free(&enc->frame);
}
diff --git a/src/codec/ffmpeg/encoder.h b/src/codec/ffmpeg/encoder.h
index 9bfe8ea..c0d953c 100644
--- a/src/codec/ffmpeg/encoder.h
+++ b/src/codec/ffmpeg/encoder.h
@@ -5,16 +5,14 @@
#include <libavutil/audio_fifo.h>
#include <libavutil/rational.h>
-#include "resampler.h"
+#include "../codec.h"
struct camu_ff_encoder {
AVCodecContext *codec_context;
const AVOutputFormat *output_format;
- struct camu_ff_resample_fmt fmt;
-
+ struct camu_resampler_format fmt;
AVFrame *frame;
- AVAudioFifo *audio_fifo;
void (*callback)(void *, AVPacket *);
void *userdata;
@@ -22,6 +20,5 @@ struct camu_ff_encoder {
bool camu_ff_encoder_init(struct camu_ff_encoder *enc, char *encoder_name, char *ext);
void camu_ff_encoder_push(struct camu_ff_encoder *enc, u8 **data, s32 sample_count);
-bool camu_ff_encoder_process(struct camu_ff_encoder *enc, bool flush);
void camu_ff_encoder_reset(struct camu_ff_encoder *enc);
void camu_ff_encoder_close(struct camu_ff_encoder *enc);
diff --git a/src/codec/ffmpeg/packet_ext.c b/src/codec/ffmpeg/packet_ext.c
index 8fe4c39..c0e400d 100644
--- a/src/codec/ffmpeg/packet_ext.c
+++ b/src/codec/ffmpeg/packet_ext.c
@@ -16,6 +16,7 @@ void aki_packet_write_av_codec_parameters(struct aki_packet *packet, AVCodecPara
AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->width);
AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->height);
AKI_PACKET_WRITE_TYPE(packet, AVRational, codecpar->sample_aspect_ratio);
+ AKI_PACKET_WRITE_TYPE(packet, AVRational, codecpar->framerate);
AKI_PACKET_WRITE_TYPE(packet, enum AVFieldOrder, codecpar->field_order);
AKI_PACKET_WRITE_TYPE(packet, enum AVColorRange, codecpar->color_range);
AKI_PACKET_WRITE_TYPE(packet, enum AVColorPrimaries, codecpar->color_primaries);
@@ -23,18 +24,13 @@ void aki_packet_write_av_codec_parameters(struct aki_packet *packet, AVCodecPara
AKI_PACKET_WRITE_TYPE(packet, enum AVColorSpace, codecpar->color_space);
AKI_PACKET_WRITE_TYPE(packet, enum AVChromaLocation, codecpar->chroma_location);
AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->video_delay);
+ AKI_PACKET_WRITE_TYPE(packet, AVChannelLayout, codecpar->ch_layout);
AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->sample_rate);
AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->block_align);
AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->frame_size);
AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->initial_padding);
AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->trailing_padding);
AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->seek_preroll);
- AKI_PACKET_WRITE_TYPE(packet, AVChannelLayout, codecpar->ch_layout);
-}
-
-void aki_packet_write_av_rational(struct aki_packet *packet, AVRational rational)
-{
- AKI_PACKET_WRITE_TYPE(packet, AVRational, rational);
}
void aki_packet_write_av_codec_id(struct aki_packet *packet, enum AVCodecID codec_id)
@@ -46,15 +42,15 @@ void aki_packet_write_av_stream(struct aki_packet *packet, AVStream *stream)
{
AKI_PACKET_WRITE_TYPE(packet, s32, stream->index);
aki_packet_write_av_codec_parameters(packet, stream->codecpar);
- aki_packet_write_av_rational(packet, stream->time_base);
+ AKI_PACKET_WRITE_TYPE(packet, AVRational, stream->time_base);
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);
if (stream->avg_frame_rate.den == 0) {
// r_frame_rate.den == 0 handled on the client.
- aki_packet_write_av_rational(packet, stream->r_frame_rate);
+ AKI_PACKET_WRITE_TYPE(packet, AVRational, stream->r_frame_rate);
} else {
- aki_packet_write_av_rational(packet, stream->avg_frame_rate);
+ AKI_PACKET_WRITE_TYPE(packet, AVRational, stream->avg_frame_rate);
}
}
@@ -68,7 +64,9 @@ void aki_packet_write_av_packet(struct aki_packet *packet, AVPacket *pkt)
AKI_PACKET_WRITE_TYPE(packet, s32, pkt->flags);
AKI_PACKET_WRITE_TYPE(packet, s32, pkt->side_data_elems);
for (s32 i = 0; i < pkt->side_data_elems; i++) {
- AKI_PACKET_WRITE_TYPE(packet, size_t, pkt->side_data[i].size);
+ // Possibly upcast size_t.
+ s64 size = (s64)pkt->side_data[i].size;
+ AKI_PACKET_WRITE_TYPE(packet, s64, size);
AKI_PACKET_WRITE_DATA(packet, pkt->side_data[i].data, pkt->side_data[i].size);
AKI_PACKET_WRITE_TYPE(packet, enum AVPacketSideDataType, pkt->side_data[i].type);
}
@@ -96,6 +94,7 @@ void aki_packet_read_av_codec_parameters(struct aki_packet *packet, AVCodecParam
AKI_PACKET_READ_TYPE(packet, s32, codecpar->width);
AKI_PACKET_READ_TYPE(packet, s32, codecpar->height);
AKI_PACKET_READ_TYPE(packet, AVRational, codecpar->sample_aspect_ratio);
+ AKI_PACKET_READ_TYPE(packet, AVRational, codecpar->framerate);
AKI_PACKET_READ_TYPE(packet, enum AVFieldOrder, codecpar->field_order);
AKI_PACKET_READ_TYPE(packet, enum AVColorRange, codecpar->color_range);
AKI_PACKET_READ_TYPE(packet, enum AVColorPrimaries, codecpar->color_primaries);
@@ -103,20 +102,13 @@ void aki_packet_read_av_codec_parameters(struct aki_packet *packet, AVCodecParam
AKI_PACKET_READ_TYPE(packet, enum AVColorSpace, codecpar->color_space);
AKI_PACKET_READ_TYPE(packet, enum AVChromaLocation, codecpar->chroma_location);
AKI_PACKET_READ_TYPE(packet, s32, codecpar->video_delay);
+ AKI_PACKET_READ_TYPE(packet, AVChannelLayout, codecpar->ch_layout);
AKI_PACKET_READ_TYPE(packet, s32, codecpar->sample_rate);
AKI_PACKET_READ_TYPE(packet, s32, codecpar->block_align);
AKI_PACKET_READ_TYPE(packet, s32, codecpar->frame_size);
AKI_PACKET_READ_TYPE(packet, s32, codecpar->initial_padding);
AKI_PACKET_READ_TYPE(packet, s32, codecpar->trailing_padding);
AKI_PACKET_READ_TYPE(packet, s32, codecpar->seek_preroll);
- AKI_PACKET_READ_TYPE(packet, AVChannelLayout, codecpar->ch_layout);
-}
-
-AVRational aki_packet_read_av_rational(struct aki_packet *packet)
-{
- AVRational r;
- AKI_PACKET_READ_TYPE(packet, AVRational, r);
- return r;
}
enum AVCodecID aki_packet_read_av_codec_id(struct aki_packet *packet)
@@ -131,11 +123,11 @@ AVStream *aki_packet_read_av_stream(AVFormatContext *format_context, const AVCod
AVStream *stream = avformat_new_stream(format_context, codec);
AKI_PACKET_READ_TYPE(packet, s32, stream->index);
aki_packet_read_av_codec_parameters(packet, stream->codecpar);
- stream->time_base = aki_packet_read_av_rational(packet);
+ AKI_PACKET_READ_TYPE(packet, AVRational, stream->time_base);
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->avg_frame_rate = aki_packet_read_av_rational(packet);
+ AKI_PACKET_READ_TYPE(packet, AVRational, stream->avg_frame_rate);
return stream;
}
@@ -152,10 +144,12 @@ AVPacket *aki_packet_read_av_packet(struct aki_packet *packet)
AKI_PACKET_READ_TYPE(packet, s32, side_data_elems);
pkt->side_data_elems = 0;
for (s32 i = 0; i < side_data_elems; i++) {
- size_t size;
u8 *data;
enum AVPacketSideDataType type;
- AKI_PACKET_READ_TYPE(packet, s32, size);
+ s64 storage_for_size;
+ AKI_PACKET_READ_TYPE(packet, s64, storage_for_size);
+ al_assert((sizeof(size_t) > 4 || storage_for_size <= INT32_MAX));
+ size_t size = (size_t)storage_for_size;
AKI_PACKET_READ_DATA(packet, size, data);
AKI_PACKET_READ_TYPE(packet, enum AVPacketSideDataType, type);
u8 *side_data = av_packet_new_side_data(pkt, type, size);
diff --git a/src/codec/ffmpeg/packet_ext.h b/src/codec/ffmpeg/packet_ext.h
index ff6e337..36b6ed5 100644
--- a/src/codec/ffmpeg/packet_ext.h
+++ b/src/codec/ffmpeg/packet_ext.h
@@ -8,13 +8,11 @@
#include <libavformat/avformat.h>
void aki_packet_write_av_codec_parameters(struct aki_packet *packet, AVCodecParameters *codecpar);
-void aki_packet_write_av_rational(struct aki_packet *packet, AVRational rational);
void aki_packet_write_av_codec_id(struct aki_packet *packet, enum AVCodecID codec_id);
void aki_packet_write_av_stream(struct aki_packet *packet, AVStream *stream);
void aki_packet_write_av_packet(struct aki_packet *packet, AVPacket *pkt);
void aki_packet_read_av_codec_parameters(struct aki_packet *packet, AVCodecParameters *codecpar);
-AVRational aki_packet_read_av_rational(struct aki_packet *packet);
enum AVCodecID aki_packet_read_av_codec_id(struct aki_packet *packet);
AVStream *aki_packet_read_av_stream(AVFormatContext *format_context, const AVCodec *codec, struct aki_packet *packet);
AVPacket *aki_packet_read_av_packet(struct aki_packet *packet);
diff --git a/src/codec/ffmpeg/resampler.c b/src/codec/ffmpeg/resampler.c
index d4b281a..00418e6 100644
--- a/src/codec/ffmpeg/resampler.c
+++ b/src/codec/ffmpeg/resampler.c
@@ -1,161 +1,108 @@
+#include <al/lib.h>
+#include <al/log.h>
#include <libavutil/channel_layout.h>
#include <libavutil/frame.h>
-#include <libavutil/opt.h>
#include <libavutil/samplefmt.h>
+#include <libavutil/opt.h>
#include <libswresample/swresample.h>
-#include <al/lib.h>
-#include <al/log.h>
#include "resampler.h"
-static bool in_req_matches(struct camu_ff_resample_fmt *fmt)
+static bool ff_resampler_init(struct camu_resampler *resamp, struct camu_resampler_format *fmt)
{
- return (fmt->in_format == fmt->req_format) &&
- (av_channel_layout_compare(&fmt->in_channel_layout, &fmt->req_channel_layout) == 0) &&
- (fmt->in_sample_rate == fmt->req_sample_rate);
-}
-
-bool camu_ff_resampler_init(struct camu_ff_resampler *resamp, struct camu_ff_resample_fmt *fmt)
-{
- al_memset(resamp, 0, sizeof(struct camu_ff_resampler));
-
- fmt->resampler_needed = !in_req_matches(fmt);
- if (!fmt->resampler_needed) return true;
+ struct camu_ff_resampler *av = (struct camu_ff_resampler *)resamp;
+ al_assert(fmt->resampler_needed);
- camu_ff_resample_fmt_copy(&resamp->fmt, fmt);
-
- SwrContext *ctx = NULL;
- s32 ret = swr_alloc_set_opts2(&ctx,
- &fmt->req_channel_layout,
- fmt->req_format,
- fmt->req_sample_rate,
- &fmt->in_channel_layout,
- fmt->in_format,
- fmt->in_sample_rate,
- 0,
- NULL);
+ camu_audio_format_copy(&av->fmt, &fmt->req);
+ s32 ret = swr_alloc_set_opts2(&av->resample_context,
+ &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.");
return false;
}
+#ifndef _WIN32
// Slower but more accurate resampler.
- //av_opt_set_int(ctx, "resampler", SWR_ENGINE_SOXR, 0);
+ av_opt_set_int(av->resample_context, "resampler", SWR_ENGINE_SOXR, 0);
+#endif
- if (swr_init(ctx) != 0) {
- swr_free(&ctx);
- al_log_error("lav_resampler", "Failed to open resampler context.");
+ if (swr_init(av->resample_context) != 0) {
+ swr_free(&av->resample_context);
+ al_log_error("lav_resampler", "Failed to init resampler context.");
return false;
}
- resamp->resample_context = ctx;
-
return true;
}
-s32 camu_ff_resampler_sample_count(struct camu_ff_resampler *resamp, s32 in_samples)
-{
- return swr_get_out_samples(resamp->resample_context, in_samples);
- //return av_rescale_rnd(swr_get_delay(resamp->resample_context, resamp->fmt.req_sample_rate) +
- // in_samples, resamp->fmt.in_sample_rate, resamp->fmt.req_sample_rate, AV_ROUND_UP);
-}
-
-static inline void free_sample_data(struct camu_ff_resampler *resamp)
+static inline void free_sample_data(struct camu_ff_resampler *av)
{
- if (resamp->data[0]) av_freep(&resamp->data[0]);
+ if (av->data[0]) av_freep(&av->data[0]);
}
-static inline void alloc_sample_data(struct camu_ff_resampler *resamp, s32 sample_count)
+static inline void alloc_sample_data(struct camu_ff_resampler *av, s32 sample_count)
{
- if (resamp->sample_count >= sample_count) return;
- free_sample_data(resamp);
- s32 nb_channels = resamp->fmt.req_channel_layout.nb_channels;
- av_samples_alloc(resamp->data, NULL, nb_channels, sample_count, resamp->fmt.req_format, 32);
- resamp->sample_count = sample_count;
+ if (av->sample_count >= sample_count) return;
+ free_sample_data(av);
+ s32 nb_channels = av->fmt.channel_layout.nb_channels;
+ av_samples_alloc(av->data, NULL, nb_channels, sample_count, av->fmt.format, 32);
+ av->sample_count = sample_count;
}
-s32 camu_ff_resampler_convert(struct camu_ff_resampler *resamp, const u8 **in_data, s32 in_samples)
+static s32 ff_resampler_convert(struct camu_resampler *resamp, const u8 **in_data, s32 in_samples)
{
+ struct camu_ff_resampler *av = (struct camu_ff_resampler *)resamp;
al_assert(in_samples > 0);
- s32 max_resample_count = camu_ff_resampler_sample_count(resamp, in_samples);
- alloc_sample_data(resamp, max_resample_count);
- s32 resample_count = swr_convert(resamp->resample_context, resamp->data,
- max_resample_count, in_data, in_samples);
+ s32 out_resample_count = swr_get_out_samples(av->resample_context, in_samples);
+ alloc_sample_data(av, out_resample_count);
+ 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 (%i).", resample_count);
+ al_log_error("lav_resampler", "Failed to convert samples (%d).", resample_count);
}
return resample_count;
}
-u8 **camu_ff_resampler_get_data(struct camu_ff_resampler *resamp)
+static s32 ff_resampler_flush(struct camu_resampler *resamp)
{
- return (u8 **)resamp->data;
-}
-
-void camu_ff_resampler_close(struct camu_ff_resampler *resamp)
-{
- if (resamp->resample_context) {
- swr_close(resamp->resample_context);
- swr_free(&resamp->resample_context);
+ struct camu_ff_resampler *av = (struct camu_ff_resampler *)resamp;
+ s32 resample_count = swr_convert(av->resample_context, av->data,
+ 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.");
+ return -1;
}
- free_sample_data(resamp);
-}
-
-void camu_ff_resample_fmt_copy(struct camu_ff_resample_fmt *dest, struct camu_ff_resample_fmt *src)
-{
- dest->resampler_needed = src->resampler_needed;
- dest->in_format = src->in_format;
- av_channel_layout_copy(&dest->in_channel_layout, &src->in_channel_layout);
- dest->in_sample_rate = src->in_sample_rate;
- dest->req_format = src->req_format;
- av_channel_layout_copy(&dest->req_channel_layout, &src->req_channel_layout);
- dest->req_sample_rate = src->req_sample_rate;
-}
-
-size_t camu_ff_resample_fmt_bytes_per_sample(struct camu_ff_resample_fmt *fmt)
-{
- return (size_t)av_get_bytes_per_sample(fmt->req_format);
-}
-
-size_t camu_ff_resample_fmt_samples_to_bytes(struct camu_ff_resample_fmt *fmt, size_t samples)
-{
- s32 size;
- s32 ret = av_samples_get_buffer_size(&size, fmt->req_channel_layout.nb_channels, (s32)samples, fmt->req_format, 1);
- al_assert(ret >= 0);
- return (size_t)size;
-}
-
-size_t camu_ff_resample_fmt_samples_to_usec(struct camu_ff_resample_fmt *fmt, size_t samples)
-{
- return samples / (fmt->req_sample_rate / 1000000.0);
-}
-
-f64 camu_ff_resample_fmt_samples_to_sec(struct camu_ff_resample_fmt *fmt, size_t samples)
-{
- return samples / (f64)fmt->req_sample_rate;
-}
-
-size_t camu_ff_resample_fmt_usec_to_bytes(struct camu_ff_resample_fmt *fmt, size_t usec)
-{
- size_t samples = usec * (fmt->req_sample_rate / 1000000.0);
- return samples * (camu_ff_resample_fmt_bytes_per_sample(fmt) * fmt->req_channel_layout.nb_channels);
+ return resample_count;
}
-size_t camu_ff_resample_fmt_sec_to_bytes(struct camu_ff_resample_fmt *fmt, f64 sec)
+static u8 **ff_resampler_get_data(struct camu_resampler *resamp)
{
- size_t samples = sec * fmt->req_sample_rate;
- return samples * (camu_ff_resample_fmt_bytes_per_sample(fmt) * fmt->req_channel_layout.nb_channels);
+ struct camu_ff_resampler *av = (struct camu_ff_resampler *)resamp;
+ return (u8 **)av->data;
}
-size_t camu_ff_resample_fmt_bytes_to_usec(struct camu_ff_resample_fmt *fmt, size_t bytes)
+static void ff_resampler_free(struct camu_resampler **resamp)
{
- size_t samples = bytes / (camu_ff_resample_fmt_bytes_per_sample(fmt) * fmt->req_channel_layout.nb_channels);
- return samples / (fmt->req_sample_rate / 1000000.0);
+ struct camu_ff_resampler *av = (struct camu_ff_resampler *)*resamp;
+ if (av->resample_context) {
+ swr_close(av->resample_context);
+ swr_free(&av->resample_context);
+ }
+ free_sample_data(av);
+ al_free(av);
+ *resamp = NULL;
}
-f64 camu_ff_resample_fmt_bytes_to_sec(struct camu_ff_resample_fmt *fmt, size_t bytes)
+struct camu_resampler *camu_ff_resampler_create(void)
{
- size_t samples = bytes / (camu_ff_resample_fmt_bytes_per_sample(fmt) * fmt->req_channel_layout.nb_channels);
- return samples / ((f64)fmt->req_sample_rate);
+ struct camu_ff_resampler *av = al_alloc_object(struct camu_ff_resampler);
+ av->resamp.init = ff_resampler_init;
+ av->resamp.convert = ff_resampler_convert;
+ av->resamp.flush = ff_resampler_flush;
+ av->resamp.get_data = ff_resampler_get_data;
+ av->resamp.free = ff_resampler_free;
+ return (struct camu_resampler *)av;
}
diff --git a/src/codec/ffmpeg/resampler.h b/src/codec/ffmpeg/resampler.h
index 4f7b07d..35e1158 100644
--- a/src/codec/ffmpeg/resampler.h
+++ b/src/codec/ffmpeg/resampler.h
@@ -1,40 +1,16 @@
#pragma once
-#include <libswresample/swresample.h>
-#include <libavutil/opt.h>
#include <al/types.h>
+#include <libswresample/swresample.h>
-struct camu_ff_resample_fmt {
- bool resampler_needed;
- enum AVSampleFormat in_format;
- enum AVSampleFormat req_format;
- s32 in_channel_count;
- s32 req_channel_count;
- AVChannelLayout in_channel_layout;
- AVChannelLayout req_channel_layout;
- s32 in_sample_rate;
- s32 req_sample_rate;
-};
+#include "../codec.h"
struct camu_ff_resampler {
+ struct camu_resampler resamp;
SwrContext *resample_context;
- struct camu_ff_resample_fmt fmt;
+ struct camu_audio_format fmt;
u8 *data[AV_NUM_DATA_POINTERS];
s32 sample_count;
};
-bool camu_ff_resampler_init(struct camu_ff_resampler *resamp, struct camu_ff_resample_fmt *fmt);
-s32 camu_ff_resampler_sample_count(struct camu_ff_resampler *resamp, s32 in_samples);
-s32 camu_ff_resampler_convert(struct camu_ff_resampler *resamp, const u8 **in_data, s32 in_samples);
-u8 **camu_ff_resampler_get_data(struct camu_ff_resampler *resamp);
-void camu_ff_resampler_close(struct camu_ff_resampler *resamp);
-
-void camu_ff_resample_fmt_copy(struct camu_ff_resample_fmt *dest, struct camu_ff_resample_fmt *src);
-size_t camu_ff_resample_fmt_bytes_per_sample(struct camu_ff_resample_fmt *fmt);
-size_t camu_ff_resample_fmt_samples_to_bytes(struct camu_ff_resample_fmt *fmt, size_t samples);
-size_t camu_ff_resample_fmt_samples_to_usec(struct camu_ff_resample_fmt *fmt, size_t samples);
-f64 camu_ff_resample_fmt_samples_to_sec(struct camu_ff_resample_fmt *fmt, size_t samples);
-size_t camu_ff_resample_fmt_usec_to_bytes(struct camu_ff_resample_fmt *fmt, size_t usec);
-size_t camu_ff_resample_fmt_sec_to_bytes(struct camu_ff_resample_fmt *fmt, f64 sec);
-size_t camu_ff_resample_fmt_bytes_to_usec(struct camu_ff_resample_fmt *fmt, size_t bytes);
-f64 camu_ff_resample_fmt_bytes_to_sec(struct camu_ff_resample_fmt *fmt, size_t bytes);
+struct camu_resampler *camu_ff_resampler_create(void);
diff --git a/src/codec/meson.build b/src/codec/meson.build
index f3b604f..7a01040 100644
--- a/src/codec/meson.build
+++ b/src/codec/meson.build
@@ -1,71 +1,77 @@
-ffmpeg_src = [
- 'ffmpeg/common.c',
- 'ffmpeg/packet_ext.c'
-]
-ffmpeg_server_src = [
- ffmpeg_src,
- 'ffmpeg/demuxer.c',
- 'ffmpeg/encoder.c',
- 'ffmpeg/avio.c'
-]
-ffmpeg_client_src = [
- ffmpeg_src,
- 'ffmpeg/decoder.c',
- 'ffmpeg/resampler.c'
-]
-ffmpeg_inc = []
-ffmpeg_args = []
-ffmpeg_server_deps = []
-ffmpeg_client_deps = []
-ffmpeg_client_use_scaler = false
+codec_deps = []
-if ffmpeg_client_use_scaler
- ffmpeg_client_src += ['ffmpeg/scaler.c']
+if get_option('codecs').contains('ffmpeg')
+ ffmpeg_src = [
+ 'ffmpeg/common.c',
+ 'ffmpeg/packet_ext.c',
+ 'ffmpeg/decoder.c',
+ 'ffmpeg/resampler.c',
+ 'ffmpeg/scaler.c',
+ 'ffmpeg/encoder.c',
+ ]
+ ffmpeg_inc = []
+ ffmpeg_deps = []
+ ffmpeg_args = []
+if not get_option('sink-only')
+ ffmpeg_src += [
+ 'ffmpeg/demuxer.c',
+ 'ffmpeg/avio.c'
+ ]
endif
-libavutil = dependency('libavutil', required: false)
-libavformat = dependency('libavformat', required: false)
-libavcodec = dependency('libavcodec', required: false)
-libswresample = dependency('libswresample', required: false)
-libswscale = dependency('libswscale', required: false)
-if not (libavutil.found() and libavformat.found() and libavcodec.found() and libswresample.found() and libswscale.found())
- ffmpeg_proj = subproject('ffmpeg', required: false)
- if ffmpeg_proj.found()
- libavutil = ffmpeg_proj.get_variable('avutil')
- libavformat = ffmpeg_proj.get_variable('avformat')
- libavcodec = ffmpeg_proj.get_variable('avcodec')
- libswresample = ffmpeg_proj.get_variable('swresample')
- libswscale = ffmpeg_proj.get_variable('swscale')
- # Only used to set include dirs for ffmpeg.
- ffmpeg_inc_dep = ffmpeg_proj.get_variable('ffmpeg_inc_dep')
- ffmpeg_server_deps += [ffmpeg_inc_dep]
- ffmpeg_client_deps += [ffmpeg_inc_dep, dependency('zlib')]
- soxr = compiler.find_library('soxr', required: false)
- if soxr.found()
- ffmpeg_client_deps += [soxr]
+ libavutil = dependency('libavutil', required: false)
+ libavformat = dependency('libavformat', required: false)
+ libavcodec = dependency('libavcodec', required: false)
+ libswresample = dependency('libswresample', required: false)
+ libswscale = dependency('libswscale', required: false)
+ if not (libavutil.found() and libavformat.found() and libavcodec.found() and libswresample.found() and libswscale.found())
+ ffmpeg_proj = subproject('ffmpeg', required: false)
+ if ffmpeg_proj.found()
+ libavutil = ffmpeg_proj.get_variable('avutil')
+ libavformat = ffmpeg_proj.get_variable('avformat')
+ libavcodec = ffmpeg_proj.get_variable('avcodec')
+ libswresample = ffmpeg_proj.get_variable('swresample')
+ libswscale = ffmpeg_proj.get_variable('swscale')
+ # Only used to set include dirs for ffmpeg.
+ ffmpeg_inc_dep = ffmpeg_proj.get_variable('ffmpeg_inc_dep')
+ # Extra static dependencies.
+ zlib = dependency('zlib')
+ bcrypt = compiler.find_library('bcrypt')
+ ffmpeg_deps += [ffmpeg_inc_dep, zlib, bcrypt]
+ soxr = compiler.find_library('soxr', required: false)
+ if soxr.found()
+ ffmpeg_deps += [soxr]
+ endif
endif
- ffmpeg_client_deps += [compiler.find_library('bcrypt')]
endif
-endif
-if libavutil.found() and libavformat.found() and libavcodec.found() and libswresample.found() and libswscale.found()
- ffmpeg_args += ['-DCAMU_HAVE_FFMPEG']
- ffmpeg_server_deps += [libavutil, libavformat, libavcodec]
- ffmpeg_client_deps += [libavutil, libavformat, libavcodec, libswresample]
- if ffmpeg_client_use_scaler
- ffmpeg_client_deps += [libswscale]
+ if libavutil.found() and libavformat.found() and libavcodec.found() and libswresample.found() and libswscale.found()
+ ffmpeg_deps += [libavutil, libavformat, libavcodec, libswresample, libswscale]
+ ffmpeg_args += ['-DCAMU_HAVE_FFMPEG']
+ ffmpeg_codec = declare_dependency(sources: ffmpeg_src, dependencies: ffmpeg_deps,
+ include_directories: ffmpeg_inc, compile_args: ffmpeg_args)
+ codec_deps += [ffmpeg_codec]
endif
- ffmpeg_server = declare_dependency(sources: ffmpeg_server_src,
- dependencies: ffmpeg_server_deps, include_directories: ffmpeg_inc, compile_args: ffmpeg_args)
- ffmpeg_client = declare_dependency(sources: ffmpeg_client_src,
- dependencies: ffmpeg_client_deps, include_directories: ffmpeg_inc, compile_args: ffmpeg_args)
endif
-#stb_image = declare_dependency(sources: ['stb_image/impl.c'],
-# dependencies: subproject('stb').get_variable('stb'))
+if get_option('codecs').contains('stb_image')
+ stb = subproject('stb').get_variable('stb')
+ stb_image_codec = declare_dependency(sources: ['stb_image/impl.c'], dependencies: stb)
+ codec_deps += [stb_image_codec]
+endif
+
+if get_option('codecs').contains('spng')
+ spng = dependency('spng', required: false)
+ if spng.found()
+ spng_codec = declare_dependency(sources: ['spng/impl.c'], dependencies: spng)
+ codec_deps += [spng_codec]
+ endif
+endif
-#wuffs = declare_dependency(sources: ['wuffs/impl.c'],
-# dependencies: subproject('wuffs').get_variable('wuffs'))
+if get_option('codecs').contains('wuffs')
+ wuffs = subproject('wuffs').get_variable('wuffs')
+ wuffs_codec = declare_dependency(sources: ['wuffs/impl.c'], dependencies: wuffs)
+ codec_deps += [wuffs_codec]
+endif
-#spng = declare_dependency(sources: ['spng/impl.c'],
-# dependencies: dependency('spng'))
+codecs = declare_dependency(dependencies: [stela, codec_deps])
diff --git a/src/codec/spng/decoder.h b/src/codec/spng/decoder.h
index fee30c6..c027fce 100644
--- a/src/codec/spng/decoder.h
+++ b/src/codec/spng/decoder.h
@@ -4,7 +4,8 @@
struct camu_spng_decoder {
struct camu_decoder dec;
- void (*callback)(void *, struct camu_frame *);
+ struct camu_codec_stream *stream;
+ void (*callback)(void *, struct camu_codec_frame *);
void (*userdata);
};
diff --git a/src/codec/spng/impl.c b/src/codec/spng/impl.c
index 9089e99..198d514 100644
--- a/src/codec/spng/impl.c
+++ b/src/codec/spng/impl.c
@@ -31,10 +31,12 @@ static bool spng_demuxer_init(struct camu_demuxer *demux, struct cch_handle *han
struct spng_ihdr ihdr;
spng_get_ihdr(ctx, &ihdr);
- struct camu_stream stream;
- stream.type = CAMU_NORMAL;
+ struct camu_codec_stream stream;
+ stream.mode = CAMU_NORMAL;
+ stream.type = CAMU_STREAM_VIDEO;
stream.video.width = ihdr.width;
stream.video.height = ihdr.height;
+ stream.video.format = CAMU_PIXEL_FORMAT_RGBA;
al_array_push(spng->demux.streams, stream);
@@ -43,14 +45,16 @@ static bool spng_demuxer_init(struct camu_demuxer *demux, struct cch_handle *han
return true;
}
-static s32 spng_demuxer_get_packet(struct camu_demuxer *demux, struct camu_packet *packet)
+static s32 spng_demuxer_get_packet(struct camu_demuxer *demux, struct camu_codec_packet *packet)
{
struct camu_spng_demuxer *spng = (struct camu_spng_demuxer *)demux;
- if (spng->eof) return CAMU_ERR_EOF;
- packet->type = CAMU_NORMAL;
- packet->buffer = &spng->buffer;
- spng->eof = true;
- return CAMU_OK;
+ if (!spng->eof) {
+ packet->mode = CAMU_NORMAL;
+ packet->buffer = &spng->buffer;
+ spng->eof = true;
+ return CAMU_OK;
+ }
+ return CAMU_ERR_EOF;
}
static u64 spng_demuxer_get_duration(struct camu_demuxer *demux)
@@ -69,31 +73,27 @@ static bool spng_demuxer_seek(struct camu_demuxer *demux, u64 pos)
static void spng_demuxer_free(struct camu_demuxer **demux)
{
struct camu_spng_demuxer *spng = (struct camu_spng_demuxer *)*demux;
+ aki_buffer_free(&spng->buffer);
+ al_array_free(spng->demux.streams);
al_free(spng);
*demux = NULL;
}
-static bool spng_decoder_init(struct camu_decoder *dec, struct camu_renderer *renderer, struct camu_stream *stream)
+static bool spng_decoder_init(struct camu_decoder *dec, struct camu_renderer *renderer, struct camu_codec_stream *stream,
+ void (*callback)(void *, struct camu_codec_frame *), void *userdata)
{
struct camu_spng_decoder *spng = (struct camu_spng_decoder *)dec;
- (void)spng;
- (void)stream;
(void)renderer;
- return true;
-}
-
-static void spng_decoder_set_callback(struct camu_decoder *dec, void (*callback)(void *, struct camu_frame *), void *userdata)
-{
- struct camu_spng_decoder *spng = (struct camu_spng_decoder *)dec;
+ spng->stream = stream;
spng->callback = callback;
spng->userdata = userdata;
+ return true;
}
-static s32 spng_decoder_push(struct camu_decoder *dec, struct camu_packet *packet)
+static s32 spng_decoder_push(struct camu_decoder *dec, struct camu_codec_packet *packet)
{
- if (!packet) return CAMU_OK;
-
struct camu_spng_decoder *spng = (struct camu_spng_decoder *)dec;
+ if (!packet) return CAMU_OK;
spng_ctx *ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32);
spng_set_png_buffer(ctx, aki_buffer_get_ptr(packet->buffer, 0), packet->buffer->size);
@@ -103,19 +103,17 @@ static s32 spng_decoder_push(struct camu_decoder *dec, struct camu_packet *packe
size_t out_size;
spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size);
- struct camu_frame *frame = al_alloc_object(struct camu_frame);
- frame->type = CAMU_NORMAL;
- frame->width = spng->dec.stream->video.width;
- frame->height = spng->dec.stream->video.height;
- frame->fmt = CAMU_PIXEL_FMT_RGBA;
- frame->pts = 0.0;
- frame->flags = 0;
-
+ struct camu_codec_frame *frame = al_alloc_object(struct camu_codec_frame);
+ frame->mode = CAMU_NORMAL;
frame->data = camu_page_alloc(out_size);
spng_decode_chunks(ctx);
spng_decode_image(ctx, frame->data, out_size, SPNG_FMT_RGBA8, 0);
-
spng_ctx_free(ctx);
+ frame->flags = 0;
+ frame->format = CAMU_PIXEL_FORMAT_RGBA;
+ frame->width = spng->stream->video.width;
+ frame->height = spng->stream->video.height;
+ frame->pts = 0.0;
spng->callback(spng->userdata, frame);
@@ -143,7 +141,7 @@ static void spng_decoder_free(struct camu_decoder **dec)
struct camu_demuxer *camu_spng_demuxer_create(void)
{
struct camu_spng_demuxer *spng = al_alloc_object(struct camu_spng_demuxer);
- spng->demux.type = CAMU_NORMAL;
+ spng->demux.mode = CAMU_NORMAL;
al_array_init(spng->demux.streams);
spng->demux.init = spng_demuxer_init;
spng->demux.get_packet = spng_demuxer_get_packet;
@@ -157,7 +155,6 @@ struct camu_decoder *camu_spng_decoder_create(void)
{
struct camu_spng_decoder *spng = al_alloc_object(struct camu_spng_decoder);
spng->dec.init = spng_decoder_init;
- spng->dec.set_callback = spng_decoder_set_callback;
spng->dec.push = spng_decoder_push;
spng->dec.process = spng_decoder_process;
spng->dec.flush = spng_decoder_flush;
diff --git a/src/codec/stb_image/decoder.h b/src/codec/stb_image/decoder.h
index 7083a09..af50799 100644
--- a/src/codec/stb_image/decoder.h
+++ b/src/codec/stb_image/decoder.h
@@ -4,7 +4,8 @@
struct camu_stbi_decoder {
struct camu_decoder dec;
- void (*callback)(void *, struct camu_frame *);
+ struct camu_codec_stream *stream;
+ void (*callback)(void *, struct camu_codec_frame *);
void (*userdata);
};
diff --git a/src/codec/stb_image/impl.c b/src/codec/stb_image/impl.c
index 6058ff2..98d9e2c 100644
--- a/src/codec/stb_image/impl.c
+++ b/src/codec/stb_image/impl.c
@@ -7,8 +7,10 @@
#define STBI_NO_FAILURE_STRINGS
#define STBI_MALLOC camu_page_alloc
#define STBI_FREE al_free
-#define STBI_REALLOC al_realloc
+#define STBI_REALLOC camu_page_realloc
+AL_UNUSED_FUNCTION_PUSH
#include <stb_image.h>
+AL_UNUSED_FUNCTION_POP
#include "../../cache/entry.h"
@@ -28,27 +30,37 @@ static bool stbi_demuxer_init(struct camu_demuxer *demux, struct cch_handle *han
aki_buffer_ensure_space(&stb->buffer, size);
cch_handle_read(stb->handle, aki_buffer_get_ptr(&stb->buffer, 0), size);
stb->buffer.size = size;
+
s32 w, h, channels;
- stbi_info_from_memory(aki_buffer_get_ptr(&stb->buffer, 0), stb->buffer.size, &w, &h, &channels);
+ u8 *data = aki_buffer_get_ptr(&stb->buffer, 0);
+ if (!stbi_info_from_memory(data, stb->buffer.size, &w, &h, &channels)) {
+ goto err;
+ }
- struct camu_stream stream = { 0 };
- stream.type = CAMU_NORMAL;
+ struct camu_codec_stream stream = { 0 };
+ stream.mode = CAMU_NORMAL;
+ stream.type = CAMU_STREAM_VIDEO;
stream.video.width = w;
stream.video.height = h;
+ stream.video.format = camu_pixel_format_from_channels(channels);
al_array_push(stb->demux.streams, stream);
return true;
+err:
+ return false;
}
-static s32 stbi_demuxer_get_packet(struct camu_demuxer *demux, struct camu_packet *packet)
+static s32 stbi_demuxer_get_packet(struct camu_demuxer *demux, struct camu_codec_packet *packet)
{
struct camu_stbi_demuxer *stb = (struct camu_stbi_demuxer *)demux;
- if (stb->eof) return CAMU_ERR_EOF;
- packet->type = CAMU_NORMAL;
- packet->buffer = &stb->buffer;
- stb->eof = true;
- return CAMU_OK;
+ if (!stb->eof) {
+ packet->mode = CAMU_NORMAL;
+ packet->buffer = &stb->buffer;
+ stb->eof = true;
+ return CAMU_OK;
+ }
+ return CAMU_ERR_EOF;
}
static u64 stbi_demuxer_get_duration(struct camu_demuxer *demux)
@@ -67,44 +79,44 @@ static bool stbi_demuxer_seek(struct camu_demuxer *demux, u64 pos)
static void stbi_demuxer_free(struct camu_demuxer **demux)
{
struct camu_stbi_demuxer *stb = (struct camu_stbi_demuxer *)*demux;
+ aki_buffer_free(&stb->buffer);
+ al_array_free(stb->demux.streams);
al_free(stb);
*demux = NULL;
}
-static bool stbi_decoder_init(struct camu_decoder *dec, struct camu_renderer *renderer, struct camu_stream *stream)
-{
- struct camu_stbi_decoder *stb = (struct camu_stbi_decoder *)dec;
- (void)stb;
- (void)stream;
- (void)renderer;
- return true;
-}
-static void stbi_decoder_set_callback(struct camu_decoder *dec, void (*callback)(void *, struct camu_frame *), void *userdata)
+static bool stbi_decoder_init(struct camu_decoder *dec, struct camu_renderer *renderer, struct camu_codec_stream *stream,
+ void (*callback)(void *, struct camu_codec_frame *), void *userdata)
{
struct camu_stbi_decoder *stb = (struct camu_stbi_decoder *)dec;
+ (void)renderer;
+ stb->stream = stream;
stb->callback = callback;
stb->userdata = userdata;
+ return true;
}
-static s32 stbi_decoder_push(struct camu_decoder *dec, struct camu_packet *packet)
+static s32 stbi_decoder_push(struct camu_decoder *dec, struct camu_codec_packet *packet)
{
- if (!packet) return CAMU_OK;
-
struct camu_stbi_decoder *stb = (struct camu_stbi_decoder *)dec;
+ if (!packet) return CAMU_OK;
s32 w, h, channels;
- u8 *pixels = stbi_load_from_memory(aki_buffer_get_ptr(packet->buffer, 0),
- packet->buffer->size, &w, &h, &channels, 4);
+ u8 *data = aki_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;
+ al_assert(w == stb->stream->video.width && h == stb->stream->video.height);
- struct camu_frame *frame = al_alloc_object(struct camu_frame);
- frame->type = CAMU_NORMAL;
+ struct camu_codec_frame *frame = al_alloc_object(struct camu_codec_frame);
+ frame->mode = CAMU_NORMAL;
+ frame->flags = 0;
frame->data = pixels;
- frame->width = stb->dec.stream->video.width;
- frame->height = stb->dec.stream->video.height;
- frame->fmt = CAMU_PIXEL_FMT_RGBA;
+ frame->video.width = w;
+ frame->video.height = h;
+ frame->format = camu_pixel_format_from_channels(channels);
+ al_assert(frame->format == stb->stream->video.format);
frame->pts = 0.0;
- frame->flags = 0;
stb->callback(stb->userdata, frame);
@@ -132,7 +144,7 @@ static void stbi_decoder_free(struct camu_decoder **dec)
struct camu_demuxer *camu_stbi_demuxer_create(void)
{
struct camu_stbi_demuxer *stb = al_alloc_object(struct camu_stbi_demuxer);
- stb->demux.type = CAMU_NORMAL;
+ stb->demux.mode = CAMU_NORMAL;
al_array_init(stb->demux.streams);
stb->demux.init = stbi_demuxer_init;
stb->demux.get_packet = stbi_demuxer_get_packet;
@@ -146,7 +158,6 @@ struct camu_decoder *camu_stbi_decoder_create(void)
{
struct camu_stbi_decoder *stb = al_alloc_object(struct camu_stbi_decoder);
stb->dec.init = stbi_decoder_init;
- stb->dec.set_callback = stbi_decoder_set_callback;
stb->dec.push = stbi_decoder_push;
stb->dec.process = stbi_decoder_process;
stb->dec.flush = stbi_decoder_flush;
diff --git a/src/codec/wuffs/decoder.h b/src/codec/wuffs/decoder.h
index 71f0290..bd79236 100644
--- a/src/codec/wuffs/decoder.h
+++ b/src/codec/wuffs/decoder.h
@@ -5,8 +5,9 @@
struct camu_wuffs_decoder {
struct camu_decoder dec;
+ struct camu_codec_stream *stream;
u8 file_type;
- void (*callback)(void *, struct camu_frame *);
+ void (*callback)(void *, struct camu_codec_frame *);
void (*userdata);
};
diff --git a/src/codec/wuffs/impl.c b/src/codec/wuffs/impl.c
index 26e9011..4d9ea7e 100644
--- a/src/codec/wuffs/impl.c
+++ b/src/codec/wuffs/impl.c
@@ -1,3 +1,4 @@
+#include <aki/common.h>
#define WUFFS_IMPLEMENTATION
#define WUFFS_CONFIG__STATIC_FUNCTIONS
#define WUFFS_CONFIG__MODULES
@@ -11,8 +12,6 @@
#define WUFFS_CONFIG__MODULE__IMAGE
#include <wuffs-unsupported-snapshot.c>
-#include <aki/common.h>
-
#include "../../cache/entry.h"
#include "demuxer.h"
@@ -32,44 +31,47 @@ static bool wuffs_demuxer_init(struct camu_demuxer *demux, struct cch_handle *ha
cch_handle_read(wfs->handle, aki_buffer_get_ptr(&wfs->buffer, 0), size);
wfs->buffer.size = size;
- wuffs_png__decoder wf_dec = {0};
+ wuffs_png__decoder wf_dec = { 0 };
wuffs_base__status status = wuffs_png__decoder__initialize(&wf_dec, sizeof(wf_dec), WUFFS_VERSION, 0);
- (void)status;
- //if (!wuffs_base__status__is_ok(&status)) goto err;
+ if (!wuffs_base__status__is_ok(&status)) goto err;
wuffs_png__decoder__set_quirk(&wf_dec, WUFFS_BASE__QUIRK_IGNORE_CHECKSUM, true);
- //wuffs_png__decoder__set_report_metadata(&wf_dec, WUFFS_BASE__FOURCC__ICCP, true);
-
- wuffs_base__io_buffer src = {0};
+ wuffs_base__io_buffer src = { 0 };
src.data.ptr = aki_buffer_get_ptr(&wfs->buffer, 0);
src.data.len = wfs->buffer.size;
src.meta.wi = wfs->buffer.size;
src.meta.closed = true;
- wuffs_base__image_config ic = {0};
+ wuffs_base__image_config ic = { 0 };
status = wuffs_png__decoder__decode_image_config(&wf_dec, &ic, &src);
- //if (!wuffs_base__status__is_ok(&status)) goto err;
- //if (!wuffs_base__image_config__is_valid(&ic)) goto err;
+ if (!wuffs_base__status__is_ok(&status)) goto err;
+ if (!wuffs_base__image_config__is_valid(&ic)) goto err;
- struct camu_stream stream;
- stream.type = CAMU_NORMAL;
+ struct camu_codec_stream stream;
+ stream.mode = CAMU_NORMAL;
+ stream.type = CAMU_STREAM_VIDEO;
stream.video.width = wuffs_base__pixel_config__width(&ic.pixcfg);
stream.video.height = wuffs_base__pixel_config__height(&ic.pixcfg);
+ stream.video.format = CAMU_PIXEL_FORMAT_RGBA;
al_array_push(wfs->demux.streams, stream);
return true;
+err:
+ return false;
}
-static s32 wuffs_demuxer_get_packet(struct camu_demuxer *demux, struct camu_packet *packet)
+static s32 wuffs_demuxer_get_packet(struct camu_demuxer *demux, struct camu_codec_packet *packet)
{
struct camu_wuffs_demuxer *wfs = (struct camu_wuffs_demuxer *)demux;
- if (wfs->eof) return CAMU_ERR_EOF;
- packet->type = CAMU_NORMAL;
- packet->buffer = &wfs->buffer;
- wfs->eof = true;
- return CAMU_OK;
+ if (!wfs->eof) {
+ packet->mode = CAMU_NORMAL;
+ packet->buffer = &wfs->buffer;
+ wfs->eof = true;
+ return CAMU_OK;
+ }
+ return CAMU_ERR_EOF;
}
static u64 wuffs_demuxer_get_duration(struct camu_demuxer *demux)
@@ -88,85 +90,74 @@ static bool wuffs_demuxer_seek(struct camu_demuxer *demux, u64 pos)
static void wuffs_demuxer_free(struct camu_demuxer **demux)
{
struct camu_wuffs_demuxer *wfs = (struct camu_wuffs_demuxer *)*demux;
+ aki_buffer_free(&wfs->buffer);
+ al_array_free(wfs->demux.streams);
al_free(wfs);
*demux = NULL;
}
-static bool wuffs_decoder_init(struct camu_decoder *dec, struct camu_renderer *renderer, struct camu_stream *stream)
+static bool wuffs_decoder_init(struct camu_decoder *dec, struct camu_renderer *renderer, struct camu_codec_stream *stream,
+ void (*callback)(void *, struct camu_codec_frame *), void *userdata)
{
struct camu_wuffs_decoder *wfs = (struct camu_wuffs_decoder *)dec;
- (void)wfs;
- (void)stream;
(void)renderer;
- return true;
-}
-
-static void wuffs_decoder_set_callback(struct camu_decoder *dec, void (*callback)(void *, struct camu_frame *), void *userdata)
-{
- struct camu_wuffs_decoder *wfs = (struct camu_wuffs_decoder *)dec;
+ wfs->stream = stream;
wfs->callback = callback;
wfs->userdata = userdata;
+ return true;
}
-static s32 wuffs_decoder_push(struct camu_decoder *dec, struct camu_packet *packet)
+static s32 wuffs_decoder_push(struct camu_decoder *dec, struct camu_codec_packet *packet)
{
+ struct camu_wuffs_decoder *wfs = (struct camu_wuffs_decoder *)dec;
if (!packet) return CAMU_OK;
- struct camu_wuffs_decoder *wfs = (struct camu_wuffs_decoder *)dec;
+ struct camu_codec_frame *frame = al_alloc_object(struct camu_codec_frame);
+ frame->mode = CAMU_NORMAL;
+ frame->data = NULL;
+ frame->flags = 0;
+ frame->width = wfs->stream->video.width;
+ frame->height = wfs->stream->video.height;
+ frame->format = CAMU_PIXEL_FMT_RGBA;
- wuffs_png__decoder wf_dec = {0};
+ wuffs_png__decoder wf_dec = { 0 };
wuffs_base__status status = wuffs_png__decoder__initialize(&wf_dec, sizeof(wf_dec), WUFFS_VERSION, 0);
- //if (!wuffs_base__status__is_ok(&status)) goto err;
+ if (!wuffs_base__status__is_ok(&status)) goto err;
wuffs_png__decoder__set_quirk(&wf_dec, WUFFS_BASE__QUIRK_IGNORE_CHECKSUM, true);
- //wuffs_png__decoder__set_report_metadata(&wf_dec, WUFFS_BASE__FOURCC__ICCP, true);
-
- wuffs_base__io_buffer src = {0};
+ wuffs_base__io_buffer src = { 0 };
src.data.ptr = aki_buffer_get_ptr(packet->buffer, 0);
src.data.len = packet->buffer->size;
src.meta.wi = packet->buffer->size;
src.meta.closed = true;
- //wf_dec.private_impl.p_decode_iccp[0] = 1;
-
- wuffs_base__image_config ic = {0};
+ wuffs_base__image_config ic = { 0 };
status = wuffs_png__decoder__decode_image_config(&wf_dec, &ic, &src);
- //if (!wuffs_base__status__is_ok(&status)) goto err;
- //if (!wuffs_base__image_config__is_valid(&ic)) goto err;
-
- struct camu_frame *frame = al_alloc_object(struct camu_frame);
- frame->type = CAMU_NORMAL;
- frame->width = wfs->dec.stream->video.width;
- frame->height = wfs->dec.stream->video.height;
- frame->fmt = CAMU_PIXEL_FMT_RGBA;
+ if (!wuffs_base__status__is_ok(&status)) goto err;
+ if (!wuffs_base__image_config__is_valid(&ic)) goto err;
u32 pixsub = WUFFS_BASE__PIXEL_SUBSAMPLING__NONE;
u32 pixfmt = WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL;
wuffs_base__pixel_config__set(&ic.pixcfg, pixfmt, pixsub, frame->width, frame->height);
- wuffs_base__pixel_buffer pb = {0};
+ wuffs_base__pixel_buffer pb = { 0 };
size_t dst_size = frame->width * frame->height * 4;
size_t wrk_size = wuffs_png__decoder__workbuf_len(&wf_dec).max_incl;
- /*
- void *device_ptr = dec->renderer->get_device_ptr(dec->renderer, dst_size + wrk_size);
- wuffs_base__slice_u8 wrk = wuffs_base__make_slice_u8(device_ptr + dst_size, wrk_size);
- status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ic.pixcfg,
- wuffs_base__make_slice_u8(device_ptr, dst_size));
- */
frame->data = camu_page_alloc(dst_size + wrk_size);
- status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ic.pixcfg,
- wuffs_base__make_slice_u8(frame->data, dst_size));
- //if (!wuffs_base__status__is_ok(&status)) goto err;
+ status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ic.pixcfg, wuffs_base__make_slice_u8(frame->data, dst_size));
+ if (!wuffs_base__status__is_ok(&status)) goto err;
wuffs_base__slice_u8 wrk = wuffs_base__make_slice_u8(frame->data + dst_size, wrk_size);
status = wuffs_png__decoder__decode_frame(&wf_dec, &pb, &src, 0, wrk, NULL);
- (void)status;
- //if (!wuffs_base__status__is_ok(&status)) goto err;
+ if (!wuffs_base__status__is_ok(&status)) goto err;
wfs->callback(wfs->userdata, frame);
return CAMU_OK;
+err:
+ camu_codec_frame_discard(frame);
+ return CAMU_ERR_ERROR;
}
static void wuffs_decoder_flush(struct camu_decoder *dec)
@@ -190,7 +181,7 @@ static void wuffs_decoder_free(struct camu_decoder **dec)
struct camu_demuxer *camu_wuffs_demuxer_create(void)
{
struct camu_wuffs_demuxer *wfs = al_alloc_object(struct camu_wuffs_demuxer);
- wfs->demux.type = CAMU_NORMAL;
+ wfs->demux.mode = CAMU_NORMAL;
al_array_init(wfs->demux.streams);
wfs->demux.init = wuffs_demuxer_init;
wfs->demux.get_packet = wuffs_demuxer_get_packet;
@@ -204,7 +195,6 @@ struct camu_decoder *camu_wuffs_decoder_create(void)
{
struct camu_wuffs_decoder *wfs = al_alloc_object(struct camu_wuffs_decoder);
wfs->dec.init = wuffs_decoder_init;
- wfs->dec.set_callback = wuffs_decoder_set_callback;
wfs->dec.push = wuffs_decoder_push;
wfs->dec.process = wuffs_decoder_process;
wfs->dec.flush = wuffs_decoder_flush;
diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c
deleted file mode 100644
index 5bbdb0b..0000000
--- a/src/fruits/cmc/cmc.c
+++ /dev/null
@@ -1,128 +0,0 @@
-#include <al/lib.h>
-#include <al/log.h>
-#include <al/wstr.h>
-#include <al/random.h>
-#include <aki/common.h>
-#include <aki/event_loop.h>
-
-#include "../../server/common.h"
-
-#include "cmc.h"
-
-static void client_callback(void *userdata, u8 op, void *opaque)
-{
- struct cmc *c = (struct cmc *)userdata;
- switch (op) {
- case CAMU_CLIENT_STATE_UPDATED: {
- switch (c->action) {
- case CMC_ACTION_OPEN: {
- cmc_ui_open(&c->ui, c);
- break;
- }
- case CMC_ACTION_LIST: {
- switch (c->type) {
- case CMC_LIST: {
- struct camu_list *list;
- al_array_foreach(c->client.state.lists, i, list) {
- al_printf("%.*s:\n", AL_STR_PRINTF(&list->name));
- str *unique_id;
- al_array_foreach_ptr(list->entries, j, unique_id) {
- if ((s32)j == list->current) {
- al_printf(" > %.*s\n", AL_STR_PRINTF(unique_id));
- } else {
- al_printf(" %.*s\n", AL_STR_PRINTF(unique_id));
- }
- }
- }
- aki_event_loop_break(&c->loop);
- break;
- }
- case CMC_SEARCH: {
- struct camu_search *search;
- al_array_foreach(c->client.state.searches, i, search) {
- al_printf("%u %x %.*s\n", i, search->id, AL_STR_PRINTF(&search->query));
- }
- aki_event_loop_break(&c->loop);
- break;
- }
- }
- break;
- }
- }
- break;
- }
- case CAMU_CLIENT_SEARCH_CREATED: {
- aki_event_loop_break(&c->loop);
- break;
- }
- case CAMU_CLIENT_RESULTS: {
- cmc_ui_handle_results(&c->ui.search, (struct camu_result_list *)opaque);
- break;
- }
- case CAMU_CLIENT_END_OF_RESULTS: {
- cmc_ui_handle_end_of_results(&c->ui.search, *((s32 *)opaque));
- break;
- }
- case CAMU_CLIENT_DISCONNECTED:
- aki_event_loop_break(&c->loop);
- break;
- }
-}
-
-static struct cmc c = { 0 };
-
-s32 main(s32 argc, char *argv[])
-{
- if (argc < 2 || !aki_common_init()) return EXIT_FAILURE;
-
- str module;
- str query;
-
- str *cmd = al_str_cr(argv[1]);
- if (al_str_eq(cmd, al_str_c("search"))) {
- c.type = CMC_SEARCH;
- if (argc == 2) {
- c.action = CMC_ACTION_LIST;
- } else {
- c.action = CMC_ACTION_CREATE;
- al_str_from(&module, "twitter");
- al_str_from(&query, argv[2]);
- }
- } else if (al_str_eq(cmd, al_str_c("open"))) {
- c.action = CMC_ACTION_OPEN;
- } else if (al_str_eq(cmd, al_str_c("list"))) {
- c.type = CMC_LIST;
- if (argc == 2) {
- c.action = CMC_ACTION_LIST;
- } else {
- c.action = CMC_ACTION_CREATE;
- }
- } else {
- return EXIT_FAILURE;
- }
-
- aki_event_loop_init(&c.loop);
-
- camu_client_init(&c.client, &c.loop, client_callback, &c);
- if (!camu_client_login(&c.client, al_str_c("andrew"), CAMU_SERVER_IP, CAMU_PORT)) {
- goto err;
- }
-
- if (c.action == CMC_ACTION_CREATE) {
- if (c.type == CMC_SEARCH) {
- camu_client_create_search(&c.client, &module, &query);
- }
- }
-
- aki_event_loop_run(&c.loop);
-
- if (c.action == CMC_ACTION_OPEN && c.ui.nc) notcurses_stop(c.ui.nc);
-
- camu_client_close(&c.client);
-
- aki_common_close();
-
- return EXIT_SUCCESS;
-err:
- return EXIT_FAILURE;
-}
diff --git a/src/fruits/cmc/cmc.h b/src/fruits/cmc/cmc.h
deleted file mode 100644
index 625d256..0000000
--- a/src/fruits/cmc/cmc.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#pragma once
-
-#include <al/types.h>
-#include <aki/event_loop.h>
-
-#include "../../libclient/client.h"
-
-#include "tui/ui.h"
-
-enum {
- CMC_LIST = 0,
- CMC_SEARCH
-};
-
-enum {
- CMC_ACTION_OPEN = 0,
- CMC_ACTION_LIST,
- CMC_ACTION_CREATE,
-};
-
-struct cmc {
- u8 type;
- u8 action;
- struct aki_event_loop loop;
- struct camu_client client;
- struct cmc_ui ui;
-};
diff --git a/src/fruits/cmc/cmc.py b/src/fruits/cmc/cmc.py
new file mode 100644
index 0000000..5c0a5b6
--- /dev/null
+++ b/src/fruits/cmc/cmc.py
@@ -0,0 +1,17 @@
+import cffi
+import locale
+from notcurses import notcurses
+
+ffi = cffi.FFI()
+
+print(ffi.string(notcurses.lib.notcurses_version()))
+
+class CMC:
+ def __init__(self):
+ pass
+
+c = CMC()
+
+locale.setlocale(locale.LC_ALL, "")
+nc = notcurses.Notcurses()
+nc.render()
diff --git a/src/fruits/cmc/meson.build b/src/fruits/cmc/meson.build
deleted file mode 100644
index 57ded9c..0000000
--- a/src/fruits/cmc/meson.build
+++ /dev/null
@@ -1,15 +0,0 @@
-cmc_src = [
- 'cmc.c',
- 'tui/ui.c',
- 'tui/resources.c',
- 'tui/pager.c',
- 'tui/view_twitter.c',
- 'tui/pane_lists.c',
- 'tui/pane_search.c'
-]
-cmc_deps = [common_deps, libclient, subproject('stb').get_variable('stb')]
-
-notcurses = dependency('notcurses')
-cmc_deps += [notcurses]
-
-executable('cmc', cmc_src, dependencies: cmc_deps)
diff --git a/src/fruits/cmc/tui/pager.c b/src/fruits/cmc/tui/pager.c
deleted file mode 100644
index a672315..0000000
--- a/src/fruits/cmc/tui/pager.c
+++ /dev/null
@@ -1,88 +0,0 @@
-#include "pager.h"
-
-void cmc_pager_init(struct cmc_pager *pager, u32 page)
-{
- pager->find_anchor = PAGE_NONE;
- pager->anchor = 0;
- pager->redraw = true;
- pager->selected = 0;
- al_array_init(pager->prev);
- pager->max_page_num = page;
- pager->min_page_num = page;
- pager->end_of_page = -1;
- pager->pending_requests = 0;
- al_array_init(pager->pages);
-}
-
-static bool any_pending_requests(struct cmc_view_page *page)
-{
- struct cmc_view *view;
- al_array_foreach(page->views, j, view) {
- if (view->pending_requests > 0) {
- return true;
- }
- }
- return false;
-}
-
-bool cmc_pager_maybe_process_pages(struct cmc_pager *pager, void (*func)(void *, struct cmc_view_page *), void *userdata)
-{
- bool any_page_processed = false;
- struct cmc_view_page *page;
- al_array_foreach(pager->pages, i, page) {
- if (page->processed) continue;
- if (!any_pending_requests(page)) {
- page->processed = true;
- func(userdata, page);
- any_page_processed = true;
- }
- }
- return any_page_processed;
-}
-
-struct cmc_view_page *cmc_get_page_from_num(struct cmc_pager *pager, u32 num)
-{
- struct cmc_view_page *page;
- al_array_foreach(pager->pages, i, page) {
- if (page->num == num) return page;
- }
- return NULL;
-}
-
-struct cmc_view_page *cmc_pager_page_containing_index(struct cmc_pager *pager, u32 index, u32 *offset)
-{
- u32 count = 0, size = 0;
- struct cmc_view_page *page = NULL;
- for (u32 i = pager->min_page_num; i <= pager->max_page_num; i++) {
- if (!(page = cmc_get_page_from_num(pager, i))) continue;
- size = page->views.size;
- count += size;
- if (count > index) {
- if (offset) *offset = size - (count - index);
- return page;
- }
- }
- return NULL;
-}
-
-bool cmc_pager_iterate_to_height(struct cmc_pager *pager, u32 height, u32 anchor, u32 *count,
- void (*func)(void *, struct cmc_view *, u32), void *userdata)
-{
- u32 y = 0, offset;
- struct cmc_view_page *page = cmc_pager_page_containing_index(pager, anchor, &offset);
- if (!page) return false;
- struct cmc_view *view;
- do {
- // If the page is not processed, return out of space as to not display a discontinuity.
- if (!page->processed) return true;
- for (; offset < page->views.size; offset++) {
- view = al_array_at(page->views, offset);
- // If this post would draw past the height, return out of space.
- if (y + view->rows > height) return true;
- if (func) func(userdata, view, y);
- (*count)++;
- y += view->rows + 1;
- }
- } while ((offset = 0, (page = cmc_get_page_from_num(pager, page->num + 1))));
- return false;
-}
diff --git a/src/fruits/cmc/tui/pager.h b/src/fruits/cmc/tui/pager.h
deleted file mode 100644
index 6aeeb9c..0000000
--- a/src/fruits/cmc/tui/pager.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#pragma once
-
-#include <al/types.h>
-#include <al/array.h>
-
-#include "view.h"
-
-enum {
- PAGE_NONE = 0,
- PAGE_UP,
- PAGE_DOWN
-};
-
-struct cmc_pager {
- u8 find_anchor;
- s32 anchor;
- bool redraw;
- u32 count;
- s32 selected;
- array(struct cmc_view *) prev;
- u32 max_page_num;
- u32 min_page_num;
- s32 end_of_page;
- u32 pending_requests;
- array(struct cmc_view_page *) pages;
-};
-
-void cmc_pager_init(struct cmc_pager *pager, u32 page);
-bool cmc_pager_maybe_process_pages(struct cmc_pager *pager, void (*func)(void *, struct cmc_view_page *), void *userdata);
-struct cmc_view_page *cmc_get_page_from_num(struct cmc_pager *pager, u32 num);
-struct cmc_view_page *cmc_pager_page_containing_index(struct cmc_pager *pager, u32 index, u32 *offset);
-bool cmc_pager_iterate_to_height(struct cmc_pager *pager, u32 height, u32 anchor, u32 *count,
- void (*func)(void *, struct cmc_view *, u32), void *userdata);
diff --git a/src/fruits/cmc/tui/pane_lists.c b/src/fruits/cmc/tui/pane_lists.c
deleted file mode 100644
index 6367e57..0000000
--- a/src/fruits/cmc/tui/pane_lists.c
+++ /dev/null
@@ -1,93 +0,0 @@
-#include "../cmc.h"
-
-#include "pane_lists.h"
-#include "ui.h"
-
-void cmc_pane_lists_init(struct cmc_pane_lists *pane, struct cmc_ui *ui)
-{
- pane->offset = 0;
- pane->selected = 0;
- pane->list = al_array_at(ui->c->client.state.lists, 0);
- pane->ui = ui;
-}
-
-bool cmc_pane_lists_handle_input(struct cmc_pane_lists *pane, struct ncinput *input)
-{
- if (!(input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN)) return false;
- s32 rows = pane->ui->rows - 1;
- s32 size = (s32)pane->list->entries.size;
- if (input->id == 'j') {
- if (pane->selected + 1 < size) {
- pane->selected++;
- if (pane->selected - pane->offset >= rows) {
- pane->offset += rows;
- }
- return true;
- }
- } else if (input->id == 'k') {
- if (pane->selected - 1 >= 0) {
- pane->selected--;
- if (pane->selected - pane->offset < 0) {
- pane->offset -= rows;
- }
- return true;
- }
- } else if (input->id == 'n') {
- if (pane->offset + rows < size) {
- pane->offset += rows;
- pane->selected += rows;
- if (pane->selected >= size) {
- pane->selected = size - 1;
- }
- return true;
- }
- } else if (input->id == 'p') {
- if (pane->offset - rows >= 0) {
- pane->offset -= rows;
- pane->selected -= rows;
- return true;
- }
- } else if (input->id == NCKEY_RETURN) {
- camu_client_skipto(&pane->ui->c->client, pane->selected);
- }
- return false;
-}
-
-bool cmc_pane_lists_handle_input_overlay(struct cmc_pane_lists *pane, struct ncinput *input)
-{
- (void)pane;
- (void)input;
- return false;
-}
-
-void cmc_pane_lists_draw_overlay(struct cmc_pane_lists *pane)
-{
- (void)pane;
-}
-
-void cmc_pane_lists_refresh(struct cmc_pane_lists *pane)
-{
- struct cmc_ui *ui = pane->ui;
-
- ncplane_cursor_move_yx(ui->p, 0, 0);
- ncplane_erase(ui->p);
- notcurses_refresh(ui->nc, NULL, NULL);
- notcurses_render(ui->nc);
-
- str *unique_id;
- u32 rows = AL_MIN(pane->list->entries.size - pane->offset, ui->rows - 1);
- for (u32 i = pane->offset; i < pane->offset + rows; i++) {
- unique_id = &al_array_at(pane->list->entries, i);
- if ((s32)i == pane->selected) {
- ncplane_set_fg_palindex(ui->p, 4);
- } else {
- ncplane_set_fg_default(ui->p);
- }
- u32 cols = AL_MIN(unique_id->len, ui->cols);
- for (u32 j = 0; j < cols; j++) {
- ncplane_putchar_yx(ui->p, i - pane->offset, j + 1, al_str_at(unique_id, j));
- }
- }
-
- notcurses_render(ui->nc);
-}
diff --git a/src/fruits/cmc/tui/pane_lists.h b/src/fruits/cmc/tui/pane_lists.h
deleted file mode 100644
index 323eb52..0000000
--- a/src/fruits/cmc/tui/pane_lists.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-
-#include <al/types.h>
-#include <notcurses/notcurses.h>
-
-#include "../../libclient/client.h"
-
-struct cmc_ui;
-struct cmc_pane_lists {
- s32 offset;
- s32 selected;
- struct camu_list *list;
- struct cmc_ui *ui;
-};
-
-void cmc_pane_lists_init(struct cmc_pane_lists *pane, struct cmc_ui *ui);
-bool cmc_pane_lists_handle_input(struct cmc_pane_lists *pane, struct ncinput *input);
-bool cmc_pane_lists_handle_input_overlay(struct cmc_pane_lists *pane, struct ncinput *input);
-void cmc_pane_lists_draw_overlay(struct cmc_pane_lists *pane);
-void cmc_pane_lists_refresh(struct cmc_pane_lists *pane);
diff --git a/src/fruits/cmc/tui/pane_search.c b/src/fruits/cmc/tui/pane_search.c
deleted file mode 100644
index 002a4d1..0000000
--- a/src/fruits/cmc/tui/pane_search.c
+++ /dev/null
@@ -1,335 +0,0 @@
-#include <al/lib.h>
-#include <notcurses/nckeys.h>
-
-#include "../cmc.h"
-
-#include "pane_search.h"
-#include "view_twitter.h"
-#include "ui.h"
-
-static void request_page_internal(struct cmc *c, struct cmc_search *search, u32 num)
-{
- search->pager.pending_requests++;
- camu_client_get_page(&c->client, search->search, num);
-}
-
-void cmc_pane_search_init(struct cmc_pane_search *pane, struct cmc_ui *ui)
-{
- pane->ui = ui;
- al_array_init(pane->searches);
- pane->search = NULL;
- struct camu_search *s;
- al_array_foreach(pane->ui->c->client.state.searches, i, s) {
- struct cmc_search *search = al_alloc_object(struct cmc_search);
- search->search = s;
- cmc_pager_init(&search->pager, s->page);
- al_array_push(pane->searches, search);
- }
- if (pane->searches.size > 0) {
- pane->search = al_array_at(pane->searches, 0);
- }
-}
-
-static struct cmc_view *get_view_by_visual_index(struct cmc_pager *pager, s32 index)
-{
- u32 offset;
- struct cmc_view_page *page = cmc_pager_page_containing_index(pager, pager->anchor + index, &offset);
- if (!page || !page->processed) return NULL;
- return al_array_at(page->views, offset);
-}
-
-// Given an index of a post, request the page to the offset of the page containing that post.
-static bool request_page_at_page_offset(struct cmc_pane_search *pane, struct cmc_pager *pager, u32 index, s32 offset)
-{
- struct cmc_view_page *page = cmc_pager_page_containing_index(pager, index, NULL);
- if (page) {
- s32 req = (s32)page->num + offset;
- if (req < 0) return false;
- request_page_internal(pane->ui->c, pane->search, req);
- }
- return true;
-}
-
-static void add_selected_from_index(struct cmc *c, struct cmc_pager *pager, u32 index)
-{
- struct cmc_view *view = get_view_by_visual_index(pager, pager->selected);
- if (view && view->post->media.size > index) {
- camu_client_add(&c->client, &view->post->unique_id, index, true);
- }
-}
-
-#define ON_LAST_PAGE(pager) \
- (pager->end_of_page >= 0 && pager->anchor + pager->count > (u32)pager->end_of_page)
-
-bool cmc_pane_search_handle_input(struct cmc_pane_search *pane, struct ncinput *input)
-{
- struct cmc_pager *pager = &pane->search->pager;
- if (!(input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN)) return false;
- if (input->id == 'j') {
- if (pager->selected + 1 >= (s32)pager->count) {
- if (pager->pending_requests > 0 || pager->find_anchor != PAGE_NONE) return false;
- if (ON_LAST_PAGE(pager)) return false;
- if (pager->count == 0) pager->count = 1;
- u32 index = pager->anchor + pager->count;
- pager->find_anchor = PAGE_DOWN;
- struct cmc_view_page *page = cmc_pager_page_containing_index(pager, index, NULL);
- if (!page) {
- request_page_at_page_offset(pane, pager, index - 1, 1);
- return false;
- }
- } else {
- pager->selected++;
- }
- return true;
- } else if (input->id == 'k') {
- if (pager->selected - 1 < 0) {
- if (pager->pending_requests > 0 || pager->find_anchor != PAGE_NONE) return false;
- if (pager->anchor - 1 < 0 && !request_page_at_page_offset(pane, pager, 0, -1)) {
- // The reason to do this here instead of letting it resolve in
- // refresh_pane_pager() is so we can return early if
- // we are at the top of the page.
- return false;
- }
- pager->anchor--;
- pager->find_anchor = PAGE_UP;
- // Wait for the requested previous page to come in.
- if (pager->anchor < 0) return false;
- } else {
- pager->selected--;
- }
- return true;
- } else if (input->id == NCKEY_RETURN || input->id == '1') {
- add_selected_from_index(pane->ui->c, pager, 0);
- } else if (input->id == '2') {
- add_selected_from_index(pane->ui->c, pager, 1);
- } else if (input->id == '3') {
- add_selected_from_index(pane->ui->c, pager, 2);
- } else if (input->id == '4') {
- add_selected_from_index(pane->ui->c, pager, 3);
- }
- return false;
-}
-
-bool cmc_pane_search_handle_input_overlay(struct cmc_pane_search *pane, struct ncinput *input)
-{
- if (!(input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN)) return false;
- struct cmc_ui *ui = pane->ui;
- struct cmc_pager *pager = &pane->search->pager;
- if (input->id == NCKEY_TAB) {
- pager->redraw = true;
- } else if (input->id == 'j') {
- if (ui->overlay.selected + 1 < (s32)pane->searches.size) {
- ui->overlay.selected++;
- return true;
- }
- } else if (input->id == 'k') {
- if (ui->overlay.selected - 1 >= 0) {
- ui->overlay.selected--;
- return true;
- }
- } else if (input->id == NCKEY_RETURN) {
- cmc_ui_toggle_overlay(ui);
- struct cmc_view *view;
- al_array_foreach(pager->prev, j, view) {
- ncplane_erase(view->container);
- ncplane_reparent_family(view->container, view->container);
- }
- pager->prev.size = 0;
- pane->search = al_array_at(pane->searches, ui->overlay.selected);
- pager = &pane->search->pager;
- pager->redraw = true;
- return true;
- }
- return false;
-}
-
-void cmc_pane_search_maybe_draw_overlay(struct cmc_pane_search *pane)
-{
- struct cmc_ui *ui = pane->ui;
-
- if (!ui->overlay.active) return;
-
- ncplane_cursor_move_yx(ui->overlay.p, 0, 0);
- ncplane_erase(ui->overlay.p);
-
- u64 color = 0;
- u32 background = 0;
- ncchannel_set_rgb8(&background, 255, 255, 255);
- ncchannel_set_alpha(&background, 255);
- ncchannels_set_fchannel(&color, background);
-
- ncchannel_set_rgb8(&background, 124, 12, 53);
- ncchannels_set_bchannel(&color, background);
- ncchannels_set_fchannel(&color, background);
- ncplane_gradient(ui->overlay.p, 0, 0, ui->overlay.rows, ui->overlay.cols, "", 0, color, color, color, color);
-
- ncplane_set_bg_rgb8(ui->overlay.p, 124, 12, 53);
-
- struct cmc_search *search;
- al_array_foreach(ui->search.searches, i, search) {
- if ((s32)i == ui->overlay.selected) {
- ncplane_set_fg_palindex(ui->overlay.p, 4);
- } else {
- ncplane_set_fg_default(ui->overlay.p);
- }
- str *query = &search->search->query;
- for (u32 j = 0; j < query->len; j++) {
- ncplane_putchar_yx(ui->overlay.p, i, j + 1, al_str_at(query, j));
- }
- }
-}
-
-static void draw_selection_line(struct cmc_view *view, u32 y, u32 rows)
-{
- ncplane_move_yx(view->container, y, 0);
- wchar_t box = L'█';
- u32 k = ((struct cmc_view_twitter *)view)->repost ? 1 : 0;
- for (; k < rows - 1; k++) {
- ncplane_cursor_move_yx(view->container, k, 0);
- ncplane_putwc(view->container, box);
- }
-}
-
-void cmc_pane_search_refresh(struct cmc_pane_search *pane)
-{
- struct cmc_pager *pager = &pane->search->pager;
- if (pager->pending_requests > 0) return;
-
- u32 page = pane->search->search->page;
- if (!cmc_get_page_from_num(pager, page)) {
- request_page_internal(pane->ui->c, pane->search, page);
- return;
- }
-
- if (pager->find_anchor == PAGE_UP) {
- u32 count;
- // If find_anchor = PAGE_UP, search->anchor is index of the requested view.
- s32 anchor = pager->anchor;
- while (anchor >= 0) {
- count = 0;
- cmc_pager_iterate_to_height(pager, pane->ui->rows, anchor, &count, NULL, NULL);
- // TODO: Handle count == 0.
- // Is the requested view still visible?
- if ((s32)(anchor + count) - 1 < pager->anchor) break;
- anchor--;
- // Load more posts if needed.
- if (anchor < 0 && request_page_at_page_offset(pane, pager, 0, -1)) return;
- }
- anchor++; // We want the anchor before the last attempt above.
- pager->selected = abs(pager->anchor - anchor);
- pager->anchor = anchor;
- pager->redraw = true;
- pager->find_anchor = PAGE_NONE;
- } else if (pager->find_anchor == PAGE_DOWN) {
- // When moving down we already know how many views are being displayed
- // so just move the anchor 1 past that.
- pager->selected = 0;
- pager->anchor += pager->count;
- pager->redraw = true;
- pager->find_anchor = PAGE_NONE;
- }
-
- cmc_pane_search_maybe_draw_overlay(pane);
-
- struct cmc_view *view;
- bool out_of_space = true;
- if (pager->redraw) {
- al_array_foreach(pager->prev, j, view) {
- ncplane_erase(view->container);
- ncplane_reparent_family(view->container, view->container);
- }
- pager->prev.size = 0;
- pager->count = 0;
- out_of_space = cmc_pager_iterate_to_height(pager, pane->ui->rows, pager->anchor, &pager->count, cmc_draw_tweet_func, pane->ui);
- } else {
- al_array_foreach(pager->prev, j, view) {
- ncplane_erase(view->container);
- }
- }
- if ((view = get_view_by_visual_index(pager, pager->selected))) {
- draw_selection_line(view, view->y, view->rows);
- }
- if (pager->redraw) {
- pager->redraw = false;
- notcurses_refresh(pane->ui->nc, NULL, NULL);
- }
- notcurses_render(pane->ui->nc);
- if (pager->count > 0 && !out_of_space && pager->end_of_page == -1) {
- request_page_at_page_offset(pane, pager, (pager->anchor + pager->count) - 1, 1);
- }
-}
-
-static struct cmc_search *get_search_from_id(struct cmc_pane_search *pane, s32 id)
-{
- struct cmc_search *search;
- al_array_foreach(pane->searches, i, search) {
- if (search->search->id == id) return search;
- }
- return NULL;
-}
-
-static void process_page_func(void *userdata, struct cmc_view_page *page)
-{
- struct cmc_ui *ui = (struct cmc_ui *)userdata;
- struct cmc_view *view;
- al_array_foreach(page->views, i, view) {
- view->rows = cmc_layout_tweet(ui, (struct cmc_view_twitter *)view, ui->cols);
- cmc_draw_tweet((struct cmc_view_twitter *)view, view->container, view->rows, ui->cols);
- }
-}
-
-void cmc_ui_resources_callback(void *userdata)
-{
- struct cmc_pane_search *pane = (struct cmc_pane_search *)userdata;
- struct cmc_pager *pager = &pane->search->pager;
- if (cmc_pager_maybe_process_pages(pager, process_page_func, pane->ui)) {
- pager->redraw = true;
- cmc_pane_search_refresh(pane);
- }
-}
-
-void cmc_ui_handle_results(struct cmc_pane_search *pane, struct camu_result_list *results)
-{
- struct cmc_search *search = get_search_from_id(pane, results->search->id);
- if (!search) return;
- struct cmc_pager *pager = &search->pager;
- pager->pending_requests--;
-
- struct cmc_view_page *page = al_alloc_object(struct cmc_view_page);
- al_array_push(pager->pages, page);
- page->num = results->num;
- page->processed = false;
- if (page->num > pager->max_page_num) pager->max_page_num = page->num;
- if (page->num < pager->min_page_num) pager->min_page_num = page->num;
- if (pager->find_anchor == PAGE_UP) {
- pager->anchor += results->unique_ids.size;
- if (pager->end_of_page >= 0) {
- pager->end_of_page += results->unique_ids.size;
- }
- }
-
- str *unique_id;
- struct cmc_view_twitter *view;
- al_array_foreach_ptr(results->unique_ids, i, unique_id) {
- view = cmc_create_view_twitter(pane->ui, unique_id);
- al_array_push(page->views, &view->v);
- }
-
- if (cmc_pager_maybe_process_pages(pager, process_page_func, pane->ui)) {
- pager->redraw = true;
- cmc_pane_search_refresh(pane);
- }
-}
-
-void cmc_ui_handle_end_of_results(struct cmc_pane_search *pane, s32 id)
-{
- struct cmc_search *search = get_search_from_id(pane, id);
- if (!search) return;
- struct cmc_pager *pager = &search->pager;
- pager->pending_requests--;
- pager->end_of_page = (pager->anchor + pager->count) - 1;
- if (cmc_pager_maybe_process_pages(pager, process_page_func, pane->ui)) {
- pager->redraw = true;
- cmc_pane_search_refresh(pane);
- }
-}
diff --git a/src/fruits/cmc/tui/pane_search.h b/src/fruits/cmc/tui/pane_search.h
deleted file mode 100644
index b7c5e2c..0000000
--- a/src/fruits/cmc/tui/pane_search.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-
-#include <notcurses/notcurses.h>
-
-#include "../../libclient/client.h"
-
-#include "pager.h"
-
-struct cmc_search {
- struct camu_search *search;
- struct cmc_pager pager;
-};
-
-struct cmc_ui;
-struct cmc_pane_search {
- array(struct cmc_search *) searches;
- struct cmc_search *search;
- struct cmc_ui *ui;
-};
-
-void cmc_pane_search_init(struct cmc_pane_search *pane, struct cmc_ui *ui);
-bool cmc_pane_search_handle_input(struct cmc_pane_search *pane, struct ncinput *input);
-bool cmc_pane_search_handle_input_overlay(struct cmc_pane_search *pane, struct ncinput *input);
-void cmc_pane_search_draw_overlay(struct cmc_pane_search *pane);
-void cmc_pane_search_refresh(struct cmc_pane_search *pane);
-
-void cmc_ui_resources_callback(void *userdata);
-void cmc_ui_handle_results(struct cmc_pane_search *pane, struct camu_result_list *results);
-void cmc_ui_handle_end_of_results(struct cmc_pane_search *pane, s32 id);
diff --git a/src/fruits/cmc/tui/resources.c b/src/fruits/cmc/tui/resources.c
deleted file mode 100644
index 19513bf..0000000
--- a/src/fruits/cmc/tui/resources.c
+++ /dev/null
@@ -1,85 +0,0 @@
-#include <al/log.h>
-#include <al/random.h>
-#define STB_IMAGE_IMPLEMENTATION
-#define STB_IMAGE_STATIC
-#define STBI_NO_STDIO
-#define STBI_NO_FAILURE_STRINGS
-#define STBI_MALLOC al_malloc
-#define STBI_FREE al_free
-#define STBI_REALLOC al_realloc
-#include <stb_image.h>
-
-#include "resources.h"
-#include "view.h"
-
-static void resource_client_callback(void *userdata, u16 id, struct aki_buffer *buffer)
-{
- struct cmc_resources *resources = (struct cmc_resources *)userdata;
- struct cmc_view_resource *resource;
- al_array_foreach(resources->cache, i, resource) {
- if (resource->id == id) {
- s32 w, h, channels;
- u8 *pixels = stbi_load_from_memory(aki_buffer_get_ptr(buffer, 0), buffer->size, &w, &h, &channels, 0);
- if (channels == 3) {
- resource->v = ncvisual_from_rgb_packed(pixels, h, w * channels, w, 255);
- } else if (channels == 4) {
- resource->v = ncvisual_from_rgba(pixels, h, w * channels, w);
- } else {
- al_log_warn("cmc", "Image has unsupported channel count.");
- }
- stbi_image_free(pixels);
- ncvisual_geom(resources->nc, resource->v, &resource->vopts, &resource->geom);
- struct cmc_view *view;
- al_array_foreach(resource->refs, j, view) {
- view->pending_requests--;
- }
- al_array_free(resource->refs);
- break;
- }
- }
- resources->callback(resources->userdata);
-}
-
-bool cmc_resources_connect(struct cmc_resources *resources, struct notcurses *nc,
- struct aki_event_loop *loop, str *addr, s32 port)
-{
- resources->nc = nc;
- resources->pending_requests = 0;
- camu_resource_client_init(&resources->client, loop, resource_client_callback, resources);
- return camu_resource_client_connect(&resources->client, addr, port);
-}
-
-void cmc_resources_set_callback(struct cmc_resources *resources, void (*callback)(void *), void *userdata)
-{
- resources->callback = callback;
- resources->userdata = userdata;
-}
-
-static struct cmc_view_resource *create_resource(struct cmc_resources *resources, str *unique_id, u32 index)
-{
- struct cmc_view_resource *resource = al_alloc_object(struct cmc_view_resource);
- al_array_push(resources->cache, resource);
- resource->vopts.blitter = NCBLIT_PIXEL;
- resource->vopts.scaling = NCSCALE_SCALE;
- resource->id = al_rand_u16();
- al_str_clone(&resource->unique_id, unique_id);
- resource->index = index;
- al_array_init(resource->refs);
- camu_resource_request(&resources->client, unique_id, index, resource->id);
- return resource;
-}
-
-struct cmc_view_resource *cmc_get_resource_from_cache(struct cmc_resources *resources, struct cmc_view *view,
- str *unique_id, u32 index)
-{
- struct cmc_view_resource *resource;
- al_array_foreach(resources->cache, i, resource) {
- if (al_str_eq(&resource->unique_id, unique_id) && resource->index == index) {
- return resource;
- }
- }
- resource = create_resource(resources, unique_id, index);
- view->pending_requests++;
- al_array_push(resource->refs, view);
- return resource;
-}
diff --git a/src/fruits/cmc/tui/resources.h b/src/fruits/cmc/tui/resources.h
deleted file mode 100644
index f12d861..0000000
--- a/src/fruits/cmc/tui/resources.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-
-#include <notcurses/notcurses.h>
-
-#include "../../libclient/resource_client.h"
-
-struct cmc_view;
-struct cmc_view_resource {
- u16 id;
- str unique_id;
- u32 index;
- array(struct cmc_view *) refs;
- struct ncvisual *v;
- struct ncvgeom geom;
- struct ncvisual_options vopts;
-};
-
-struct cmc_resources {
- struct camu_resource_client client;
- u32 pending_requests;
- struct notcurses *nc;
- array(struct cmc_view_resource *) cache;
- void (*callback)(void *);
- void *userdata;
-};
-
-bool cmc_resources_connect(struct cmc_resources *resources, struct notcurses *nc,
- struct aki_event_loop *loop, str *addr, s32 port);
-void cmc_resources_set_callback(struct cmc_resources *resources, void (*callback)(void *), void *userdata);
-struct cmc_view_resource *cmc_get_resource_from_cache(struct cmc_resources *resources, struct cmc_view *view,
- str *unique_id, u32 index);
diff --git a/src/fruits/cmc/tui/ui.c b/src/fruits/cmc/tui/ui.c
deleted file mode 100644
index 2c406bd..0000000
--- a/src/fruits/cmc/tui/ui.c
+++ /dev/null
@@ -1,144 +0,0 @@
-#include "../../server/common.h"
-
-#include "../cmc.h"
-
-#include "ui.h"
-
-/*
-struct nccell ul, ur; nccell_init(&ul); nccell_init(&ur);
-struct nccell ll, lr; nccell_init(&ll); nccell_init(&lr);
-struct nccell hl, vl; nccell_init(&hl); nccell_init(&vl);
-//nccell_set_channels(&hl, color);
-//nccell_set_channels(&vl, color);
-
-ncplane_box(tui->overlay.p, &ul, &ur, &ll, &lr, &hl, &vl, tui->overlay.rows - 1, tui->overlay.cols - 1, 0);
-
-nccell_release(tui->overlay.p, &ul); nccell_release(tui->overlay.p, &ur);
-nccell_release(tui->overlay.p, &ll); nccell_release(tui->overlay.p, &lr);
-nccell_release(tui->overlay.p, &hl); nccell_release(tui->overlay.p, &vl);
-*/
-
-//ncplane_double_box(tui->overlay.p, NCSTYLE_NONE, color, tui->overlay.rows - 1, tui->overlay.cols - 1, 0);
-//ncplane_rounded_box(tui->overlay.p, NCSTYLE_NONE, color, tui->overlay.rows - 1, tui->overlay.cols - 1, 0);
-
-void cmc_ui_toggle_overlay(struct cmc_ui *ui)
-{
- if (ui->overlay.active) {
- ncplane_reparent_family(ui->overlay.p, ui->overlay.p);
- } else {
- ncplane_reparent_family(ui->overlay.p, ui->p);
- }
- ui->overlay.active = !ui->overlay.active;
-}
-
-static void refresh_internal(struct cmc_ui *ui)
-{
- switch (ui->pane) {
- case CMC_PANE_LIBRARY:
- break;
- case CMC_PANE_LISTS:
- cmc_pane_lists_refresh(&ui->lists);
- break;
- case CMC_PANE_BROWSE:
- break;
- case CMC_PANE_SEARCH:
- cmc_pane_search_refresh(&ui->search);
- break;
- }
-}
-
-static void input_poll_callback(void *userdata, s32 revents)
-{
- struct cmc_ui *ui = (struct cmc_ui *)userdata;
- (void)revents;
- struct ncinput input;
- u32 ret;
- bool do_refresh = false;
- do {
- ret = notcurses_get_nblock(ui->nc, &input);
- if (ret == (u32)-1 || ret == 0) break;
- if (input.evtype == NCTYPE_PRESS || input.evtype == NCTYPE_UNKNOWN) {
- if (input.id == 'q') {
- aki_event_loop_break(&ui->c->loop);
- break;
- } else if (input.id == NCKEY_TAB) {
- cmc_ui_toggle_overlay(ui);
- do_refresh = true;
- }
- switch (ui->pane) {
- case CMC_PANE_LIBRARY:
- break;
- case CMC_PANE_LISTS:
- if (ui->overlay.active) {
- do_refresh |= cmc_pane_lists_handle_input_overlay(&ui->lists, &input);
- } else {
- do_refresh |= cmc_pane_lists_handle_input(&ui->lists, &input);
- }
- break;
- case CMC_PANE_BROWSE:
- break;
- case CMC_PANE_SEARCH:
- if (ui->overlay.active) {
- do_refresh |= cmc_pane_search_handle_input_overlay(&ui->search, &input);
- } else {
- do_refresh |= cmc_pane_search_handle_input(&ui->search, &input);
- }
- break;
- }
- }
- } while (1);
- if (do_refresh) refresh_internal(ui);
-}
-
-static s32 resize_cb(struct ncplane *p)
-{
- struct cmc_ui *ui = (struct cmc_ui *)ncplane_userptr(p);
- notcurses_stddim_yx(ui->nc, &ui->rows, &ui->cols);
- ui->cols -= 4;
- ui->rows -= 1;
- struct ncplane_options nopts = { 0 };
- nopts.cols = ui->cols;
- nopts.rows = ui->rows;
- nopts.x = 3;
- nopts.y = 1;
- nopts.flags = 0;
- ui->p = ncplane_create(p, &nopts);
- ui->pane = CMC_PANE_SEARCH;
- ui->overlay.cols = nopts.cols = 35;
- ui->overlay.rows = nopts.rows = 20;
- nopts.x = (ui->cols / 2) - (ui->overlay.cols / 2);
- nopts.y = (ui->rows / 2) - (ui->overlay.rows / 2);
- ui->overlay.p = ncpile_create(ui->nc, &nopts);
- return 0;
-}
-
-bool cmc_ui_open(struct cmc_ui *ui, struct cmc *c)
-{
- if (!(ui->nc = notcurses_init(NULL, stdin))) return false;
-
- ui->c = c;
-
- cmc_pane_lists_init(&ui->lists, ui);
- cmc_pane_search_init(&ui->search, ui);
-
- //if (!cmc_resources_connect(&ui->resources, ui->nc, &ui->c->loop, CAMU_SERVER_IP, CAMU_RESOURCE_PORT)) {
- // return false;
- //}
- cmc_resources_set_callback(&ui->resources, cmc_ui_resources_callback, &ui->search);
-
- aki_poll_init(&ui->input, input_poll_callback, ui);
- aki_poll_set(&ui->input, notcurses_inputready_fd(ui->nc), AKI_POLL_READ);
- aki_poll_start(&ui->input, &c->loop);
-
- ui->overlay.active = false;
- ui->overlay.selected = 0;
-
- struct ncplane *stdplane = notcurses_stdplane(ui->nc);
- ncplane_set_resizecb(stdplane, resize_cb);
- ncplane_set_userptr(stdplane, ui);
- resize_cb(stdplane);
-
- refresh_internal(ui);
-
- return true;
-}
diff --git a/src/fruits/cmc/tui/ui.h b/src/fruits/cmc/tui/ui.h
deleted file mode 100644
index 76ea353..0000000
--- a/src/fruits/cmc/tui/ui.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-
-#include <aki/event_loop.h>
-#include <notcurses/notcurses.h>
-
-#include "resources.h"
-#include "pane_lists.h"
-#include "pane_search.h"
-
-enum {
- CMC_PANE_LIBRARY = 0,
- CMC_PANE_LISTS,
- CMC_PANE_BROWSE,
- CMC_PANE_SEARCH
-};
-
-struct cmc;
-struct cmc_ui {
- struct notcurses *nc;
- u32 cols;
- u32 rows;
- struct ncplane *p;
- u8 pane;
- struct {
- struct ncplane *p;
- u32 cols;
- u32 rows;
- bool active;
- s32 selected;
- } overlay;
- struct aki_poll input;
- struct cmc_resources resources;
- struct cmc_pane_lists lists;
- struct cmc_pane_search search;
- struct cmc *c;
-};
-
-bool cmc_ui_open(struct cmc_ui *ui, struct cmc *c);
-void cmc_ui_toggle_overlay(struct cmc_ui *ui);
-void cmc_ui_maybe_draw_overlay(struct cmc_ui *ui);
diff --git a/src/fruits/cmc/tui/view.h b/src/fruits/cmc/tui/view.h
deleted file mode 100644
index 3c521c3..0000000
--- a/src/fruits/cmc/tui/view.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma once
-
-#include <al/types.h>
-#include <sho/post.h>
-
-enum {
- CMC_VIEW_NONE = 0,
- CMC_VIEW_SINGLE,
- CMC_VIEW_PARTITION
-};
-
-struct cmc_view {
- struct sho_post *post;
- struct ncplane *container;
- u32 pending_requests;
- u32 rows;
- u32 y;
-};
-
-struct cmc_view_page {
- u32 num;
- bool processed;
- array(struct cmc_view *) views;
-};
diff --git a/src/fruits/cmc/tui/view_twitter.c b/src/fruits/cmc/tui/view_twitter.c
deleted file mode 100644
index b1070a4..0000000
--- a/src/fruits/cmc/tui/view_twitter.c
+++ /dev/null
@@ -1,370 +0,0 @@
-#include "../cmc.h"
-
-#include "view_twitter.h"
-
-#define TEXT_OFFSET 10
-
-#define PROFILE_SIZE 5
-#define PREVIEW_OFFSET_WIDTH 26
-
-static u32 count_wrapped_lines(wstr *w, u32 width)
-{
- u32 counter = 0, wraps = 1;
- for (u32 i = 0; i < w->len; i++) {
- wchar_t wc = al_wstr_at(w, i);
- s32 wcwidth = al_wchar_width(wc);
- bool newline = wc == L'\n';
- if (newline || counter + wcwidth > width) {
- wraps++;
- counter = 0;
- }
- if (newline) continue;
- counter += wcwidth;
- }
- return wraps;
-}
-
-u32 cmc_layout_tweet(struct cmc_ui *ui, struct cmc_view_twitter *view, u32 width)
-{
- struct sho_post *post = view->v.post;
- struct sho_post *repost = view->repost;
-
- u32 rows = 1;
-
- view->text.width = width - TEXT_OFFSET;
- view->text.height = 1 + count_wrapped_lines(&post->text, view->text.width);
- view->text.y = repost ? 1 : 0;
- view->text.height += view->text.y;
- rows += view->text.height;
-
- if (view->preview.mode == CMC_VIEW_NONE) {
- return rows;
- }
-
- view->preview.width = view->text.width - 1;
- view->preview.x = TEXT_OFFSET;
- u32 partition_width = view->preview.width / 2;
-
- struct ncplane_options opts = { 0 };
- opts.cols = view->preview.width;
- opts.rows = ncplane_dim_y(notcurses_stdplane(ui->nc)) / 3;
- opts.x = 0;
- opts.y = 0;
- struct ncplane *inter = ncplane_create(notcurses_stdplane(ui->nc), &opts);
-
- u32 left = 0, right = 0;
- struct cmc_view_resource *resource;
- switch (view->preview.mode) {
- case CMC_VIEW_SINGLE: {
- resource = view->preview.resources[0];
- resource->vopts.n = inter;
- ncvisual_geom(ui->nc, resource->v, &resource->vopts, &resource->geom);
- // rcellx == width case very untested.
- left += resource->geom.rcelly + (s32)((resource->geom.rcellx != view->preview.width) ? 1 : -1);
- break;
- }
- case CMC_VIEW_PARTITION: {
- ncplane_resize_simple(inter, opts.rows, partition_width);
-
- resource = view->preview.resources[0];
- resource->vopts.n = inter;
- ncvisual_geom(ui->nc, resource->v, &resource->vopts, &resource->geom);
- left += resource->geom.rcelly + 1;
-
- resource = view->preview.resources[1];
- resource->vopts.n = inter;
- ncvisual_geom(ui->nc, resource->v, &resource->vopts, &resource->geom);
- right += resource->geom.rcelly + 1;
-
- if (view->preview.resources[2]) {
- resource = view->preview.resources[2];
- resource->vopts.n = inter;
- ncvisual_geom(ui->nc, resource->v, &resource->vopts, &resource->geom);
- left += resource->geom.rcelly + 1;
- }
-
- if (view->preview.resources[3]) {
- resource = view->preview.resources[3];
- resource->vopts.n = inter;
- ncvisual_geom(ui->nc, resource->v, &resource->vopts, &resource->geom);
- right += resource->geom.rcelly + 1;
- }
-
- break;
- }
- }
-
- ncplane_destroy(inter);
-
- view->preview.height = AL_MAX(right, left);
-
- return rows + view->preview.height;
-}
-
-static void date_to_relative_date(struct sho_post *post, char *out)
-{
- struct sho_post_date *created = NULL;
- struct sho_post_date *date;
- al_array_foreach_ptr(post->dates, i, date) {
- if (date->type == SHOKI_DATE_CREATED) {
- created = date;
- break;
- }
- }
- if (!created) return;
- f64 seconds = (aki_get_timestamp() / 1000000.0 - created->timestamp);
- s64 minutes = seconds / 60L;
- s64 hours = minutes / 60L;
- s64 days = hours / 24L;
- if (days > 0) {
- al_sprintf(out, "%lud", days);
- } else if (hours > 0) {
- al_sprintf(out, "%luh", hours);
- } else if (minutes > 0) {
- al_sprintf(out, "%lum", minutes);
- } else {
- al_sprintf(out, "%lus", (s64)seconds);
- }
-}
-
-static void number_to_shorthand(s64 number, char *out)
-{
- if (number >= 1000000000) {
- al_sprintf(out, "%.1fB", number / 1000000000.f);
- } else if (number >= 1000000) {
- al_sprintf(out, "%.1fM", number / 1000000.f);
- } else if (number >= 1000) {
- al_sprintf(out, "%.1fK", number / 1000.f);
- } else {
- al_sprintf(out, "%ld", number);
- }
-}
-
-static void draw_tweet_resources(struct cmc_ui *ui, struct cmc_view_twitter *view)
-{
- u32 partition_width = view->preview.width / 2;
- struct cmc_view_resource *resource;
- switch (view->preview.mode) {
- case CMC_VIEW_NONE:
- break;
- case CMC_VIEW_SINGLE: {
- resource = view->preview.resources[0];
- ncplane_resize_simple(view->preview.p[0], resource->geom.rcelly + 1, view->preview.width);
- ncplane_move_yx(view->preview.p[0], view->text.height, view->preview.x);
- resource->vopts.n = view->preview.p[0];
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
- break;
- }
- case CMC_VIEW_PARTITION: {
- resource = view->preview.resources[0];
- resource->vopts.n = view->preview.p[0];
- ncplane_resize_simple(view->preview.p[0], resource->geom.rcelly + 1, partition_width);
- ncplane_move_yx(view->preview.p[0], view->text.height, view->preview.x);
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
-
- resource = view->preview.resources[1];
- ncplane_resize_simple(view->preview.p[1], resource->geom.rcelly + 1, partition_width);
- ncplane_move_yx(view->preview.p[1], view->text.height, (view->preview.x + view->preview.resources[0]->geom.rcellx) + 3);
- resource->vopts.n = view->preview.p[1];
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
-
- if (view->preview.resources[2]) {
- resource = view->preview.resources[2];
- ncplane_resize_simple(view->preview.p[2], resource->geom.rcelly + 1, partition_width);
- ncplane_move_yx(view->preview.p[2], view->preview.resources[0]->geom.rcelly + view->text.height + 2, view->preview.x);
- resource->vopts.n = view->preview.p[2];
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
- }
-
- if (view->preview.resources[3]) {
- resource = view->preview.resources[3];
- ncplane_resize_simple(view->preview.p[3], resource->geom.rcelly + 1, partition_width);
- ncplane_move_yx(view->preview.p[3], view->preview.resources[1]->geom.rcelly + view->text.height + 2, (view->preview.x + view->preview.resources[2]->geom.rcellx) + 3);
- resource->vopts.n = view->preview.p[3];
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
- }
-
- break;
- }
- }
-
- if (view->profile.resource && view->profile.resource->v) {
- struct cmc_view_resource *resource = view->profile.resource;
- ncplane_resize_simple(view->profile.p, (PROFILE_SIZE + 1) / 2, PROFILE_SIZE);
- ncplane_move_yx(view->profile.p, view->text.y, PROFILE_SIZE - 2);
- resource->vopts.n = view->profile.p;
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
- }
-}
-
-void cmc_draw_tweet_func(void *userdata, struct cmc_view *view, u32 y)
-{
- struct cmc_ui *ui = (struct cmc_ui *)userdata;
- struct cmc_search *search = ui->search.search;
- ncplane_reparent_family(view->container, ui->p);
- ncplane_move_yx(view->container, y, 0);
- draw_tweet_resources(ui, (struct cmc_view_twitter *)view);
- view->y = y;
- al_array_push(search->pager.prev, view);
-}
-
-static char format_buf[32];
-
-void cmc_draw_tweet(struct cmc_view_twitter *view, struct ncplane *container, u32 rows, u32 width)
-{
- struct sho_post *post = view->v.post;
- struct sho_post *repost = view->repost;
-
- ncplane_resize_simple(container, rows, width);
-
- ncplane_resize_simple(view->text.p, view->text.height, width);
- ncplane_move_yx(view->text.p, 0, 0);
-
- s32 offset = TEXT_OFFSET;
-
- if (repost) {
- const wchar_t *retweet = L"⮤⮧";
- ncplane_set_styles(view->text.p, NCSTYLE_ITALIC);
- ncplane_putwstr_yx(view->text.p, 0, offset - 3, retweet);
- ncplane_set_styles(view->text.p, NCSTYLE_NONE);
- ncplane_putwstr_yx(view->text.p, 0, offset, &al_wstr_at(&repost->author.display_name, 0));
- }
-
- ncplane_set_styles(view->text.p, NCSTYLE_BOLD);
-
- ncplane_putwstr_yx(view->text.p, view->text.y, offset, &al_wstr_at(&post->author.display_name, 0));
- offset += al_wstr_width(&post->author.display_name) + 1;
-
- ncplane_set_styles(view->text.p, NCSTYLE_NONE);
- ncplane_putwstr_yx(view->text.p, view->text.y, offset, &al_wstr_at(&post->author.username, 0));
- offset += al_wstr_width(&post->author.username) + 1;
-
- ncplane_putwstr_yx(view->text.p, view->text.y, offset, L"∙");
- offset += 2;
-
- date_to_relative_date(post, format_buf);
- ncplane_putstr_yx(view->text.p, view->text.y, offset, format_buf);
-
- u32 text_y = view->text.y + 1, text_x = TEXT_OFFSET;
- for (u32 i = 0; i < post->text.len; i++) {
- wchar_t wc = al_wstr_at(&post->text, i);
- s32 wcwidth = al_wchar_width(wc);
- bool newline = wc == L'\n';
- if (newline || text_x + wcwidth > width) {
- text_y++;
- text_x = TEXT_OFFSET;
- }
- if (newline) continue;
- ncplane_putwc_yx(view->text.p, text_y, text_x, wc);
- text_x += wcwidth;
- }
-
- ncplane_resize_simple(view->stats.p, 1, view->text.width);
- ncplane_move_yx(view->stats.p, rows - 1, TEXT_OFFSET);
-
- u32 part = 10;
- offset = 0;
-
- if (post->comments.set) {
- const wchar_t *comment = L"🗩";
- ncplane_set_styles(view->stats.p, NCSTYLE_ITALIC);
- ncplane_putwstr_yx(view->stats.p, 0, offset, comment);
- ncplane_set_styles(view->stats.p, NCSTYLE_NONE);
- number_to_shorthand(post->comments.i, format_buf);
- ncplane_putstr_yx(view->stats.p, 0, offset + 3, format_buf);
- }
- offset += part;
-
- if (post->reposts.set) {
- const wchar_t *retweet = L"⮤⮧";
- ncplane_set_styles(view->stats.p, NCSTYLE_ITALIC);
- ncplane_putwstr_yx(view->stats.p, 0, offset, retweet);
- ncplane_set_styles(view->stats.p, NCSTYLE_NONE);
- number_to_shorthand(post->reposts.i, format_buf);
- ncplane_putstr_yx(view->stats.p, 0, offset + 3, format_buf);
- }
- offset += part;
-
- if (post->likes.set) {
- //const wchar_t *like = L"❤";
- const wchar_t *like = L"♥";
- ncplane_set_styles(view->stats.p, NCSTYLE_ITALIC);
- ncplane_putwstr_yx(view->stats.p, 0, offset, like);
- ncplane_set_styles(view->stats.p, NCSTYLE_NONE);
- number_to_shorthand(post->likes.i, format_buf);
- ncplane_putstr_yx(view->stats.p, 0, offset + 2, format_buf);
- }
-}
-
-static void get_post_and_maybe_repost(struct cmc_ui *ui, str *unique_id, struct sho_post **post, struct sho_post **repost)
-{
- struct sho_post_cache *cache = &ui->c->client.cache;
- *post = sho_post_cache_get(cache, unique_id);
- *repost = NULL;
- if ((*post)->type == SHOKI_POST_REPOST) {
- *repost = *post;
- *post = sho_post_cache_get(cache, &(*post)->post.unique_id);
- }
-}
-
-static void strip_link_from_tweet(struct sho_post *post)
-{
- wstr *check = al_wstr_c(L"https://t.co/");
- s32 index = al_wstr_rfind(&post->text, L':');
- if (index < 5) return;
- index -= 5; // [h]ttps:// (if matched).
- if (al_wstr_cmp(&post->text, check, index, check->len) == 0) {
- al_wstr_at(&post->text, index) = L'\0';
- post->text.len = index;
- }
-}
-
-struct cmc_view_twitter *cmc_create_view_twitter(struct cmc_ui *ui, str *unique_id)
-{
- struct cmc_view_twitter *view = al_alloc_object(struct cmc_view_twitter);
- get_post_and_maybe_repost(ui, unique_id, &view->v.post, &view->repost);
-
- if (view->v.post->media.size > 0) {
- strip_link_from_tweet(view->v.post);
- }
-
- view->v.rows = 0;
- view->v.y = 0;
- view->v.pending_requests = 0;
-
- struct ncplane_options default_options = { 0 };
- default_options.cols = 1;
- default_options.rows = 1;
- default_options.x = 0;
- default_options.y = 0;
-
- view->v.container = ncpile_create(ui->nc, &default_options);
- view->text.p = ncplane_create(view->v.container, &default_options);
- view->profile.p = ncplane_create(view->v.container, &default_options);
- for (u32 i = 0; i < 4; i++) {
- view->preview.p[i] = ncplane_create(view->v.container, &default_options);
- }
- view->stats.p = ncplane_create(view->v.container, &default_options);
-
- struct sho_post *post = view->v.post;
- view->profile.resource = cmc_get_resource_from_cache(&ui->resources, &view->v, &post->author.unique_id, 0);
- struct sho_post_media *media;
- al_array_foreach_ptr(post->media, i, media) {
- (void)media;
- view->preview.resources[i] = cmc_get_resource_from_cache(&ui->resources, &view->v, &post->unique_id, i);
- }
-
- switch (post->media.size) {
- case 0:
- view->preview.mode = CMC_VIEW_NONE;
- break;
- case 1:
- view->preview.mode = CMC_VIEW_SINGLE;
- break;
- default:
- view->preview.mode = CMC_VIEW_PARTITION;
- break;
- }
-
- return view;
-}
diff --git a/src/fruits/cmc/tui/view_twitter.h b/src/fruits/cmc/tui/view_twitter.h
deleted file mode 100644
index 9792826..0000000
--- a/src/fruits/cmc/tui/view_twitter.h
+++ /dev/null
@@ -1,35 +0,0 @@
-#pragma once
-
-#include "ui.h"
-#include "view.h"
-
-struct cmc_view_twitter {
- struct cmc_view v;
- struct sho_post *repost;
- struct {
- u32 width;
- u32 height;
- u32 y;
- struct ncplane *p;
- } text;
- struct {
- struct ncplane *p;
- struct cmc_view_resource *resource;
- } profile;
- struct {
- u8 mode;
- u32 width;
- u32 height;
- u32 x;
- struct ncplane *p[4];
- struct cmc_view_resource *resources[4];
- } preview;
- struct {
- struct ncplane *p;
- } stats;
-};
-
-struct cmc_view_twitter *cmc_create_view_twitter(struct cmc_ui *ui, str *unique_id);
-void cmc_draw_tweet(struct cmc_view_twitter *view, struct ncplane *container, u32 rows, u32 width);
-u32 cmc_layout_tweet(struct cmc_ui *ui, struct cmc_view_twitter *view, u32 width);
-void cmc_draw_tweet_func(void *userdata, struct cmc_view *view, u32 y);
diff --git a/src/fruits/cmsrv/cmsrv.c b/src/fruits/cmsrv/cmsrv.c
new file mode 100644
index 0000000..08f67dd
--- /dev/null
+++ b/src/fruits/cmsrv/cmsrv.c
@@ -0,0 +1,196 @@
+#define CAMU_LOCAL_SOCKET
+
+#include <al/log.h>
+#include <aki/common.h>
+#ifdef CAMU_LOCAL_SOCKET
+#include <aki/line_processor.h>
+#endif
+#include <aki/timer.h>
+
+#include "../../server/server.h"
+#include "../../server/list.h"
+#include "../../server/common.h"
+#include "../../cache/handlers/cdio.h"
+#include "../../codec/ffmpeg/common.h"
+#ifdef CAMU_LOCAL_SOCKET
+#include "../../server/local_compat.h"
+#endif
+
+#include "ui.h"
+
+struct cmsrv {
+ struct aki_event_loop loop;
+ struct aki_signal quit_signal;
+ struct camu_server server;
+#ifdef CAMU_LOCAL_SOCKET
+ struct {
+ struct aki_socket sock;
+ struct aki_line_processor cli;
+ struct camu_local_compat compat;
+ } local;
+#endif
+ struct cmsrv_ui ui;
+ struct aki_poll input_poll;
+ struct aki_timer render_timer;
+};
+
+#ifdef CAMU_LOCAL_SOCKET
+static u8 server_line_callback(void *userdata, str *line)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ struct camu_list *list = al_array_at(s->server.lists, 0);
+ if (al_str_eq(line, al_str_c(";PAUSE"))) {
+ lia_list_toggle_pause(&list->impl, LIANA_SEQUENCE_ANY, -1.0);
+ } else if (al_str_eq(line, al_str_c(";NEXT"))) {
+ lia_list_skip(&list->impl, LIANA_SEQUENCE_ANY, 1);
+ } else if (al_str_eq(line, al_str_c(";PREV"))) {
+ lia_list_skip(&list->impl, LIANA_SEQUENCE_ANY, -1);
+ } else if (al_str_eq(line, al_str_c(";SHUFFLE"))) {
+ lia_list_shuffle(&list->impl);
+ } else if (al_str_eq(line, al_str_c(";SORT"))) {
+ lia_list_sort(&list->impl);
+ } else if (al_str_eq(line, al_str_c(";REVERSE"))) {
+ lia_list_reverse(&list->impl);
+ } else if (al_str_eq(line, al_str_c(";CLEAR"))) {
+ lia_list_clear(&list->impl);
+ } else {
+ struct aki_packet *packet = aki_packet_create();
+ aki_packet_write_str(packet, line);
+ camu_server_local_add(&s->server, packet);
+ }
+ return AKI_LINE_PROCESSOR_CONTINUE;
+}
+#endif
+
+static void meta_callback(void *userdata, u8 op, struct lia_list_entry *entry)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ (void)s;
+ switch (op) {
+ case LIANA_META_PLAYING: {
+ struct camu_server_resource *resource = (struct camu_server_resource *)entry->opaque;
+#ifdef CAMU_HAVE_PORTAL
+ if (resource->post) {
+ al_log_info("server", "now playing: %.*ls.", AL_WSTR_PRINTF(&resource->post->title));
+ } else {
+#endif
+ al_log_info("server", "now playing: %.*s.", AL_STR_PRINTF(&resource->unique_id));
+#ifdef CAMU_HAVE_PORTAL
+ }
+#endif
+ break;
+ }
+ }
+}
+
+static void render_timer_callback(void *userdata, struct aki_timer *timer)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ (void)timer;
+ cmsrv_ui_render(&s->ui);
+ aki_timer_again(&s->render_timer);
+}
+
+static void input_poll_callback(void *userdata, s32 revents)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ (void)revents;
+ struct ncinput input;
+ do {
+ if (!cmsrv_ui_read_input(&s->ui, &input)) break;
+ if (input.evtype == NCTYPE_PRESS || input.evtype == NCTYPE_UNKNOWN) {
+ switch (input.id) {
+ case 'q':
+ aki_event_loop_break_one(&s->loop);
+ break;
+ case '1':
+ cmsrv_ui_set_pane(&s->ui, CMSRV_UI_LISTS);
+ break;
+ case '2':
+ cmsrv_ui_set_pane(&s->ui, CMSRV_UI_RESOURCES);
+ break;
+ case '3':
+ cmsrv_ui_set_pane(&s->ui, CMSRV_UI_NODES);
+ break;
+ }
+ }
+ } while (1);
+}
+
+static void quit_signal_callback(void *userdata)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ aki_event_loop_break_one(&s->loop);
+}
+
+static s32 log_callback(void *userdata, char *message)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ cmsrv_ui_push_message(&s->ui, message);
+ return al_strlen(message);
+}
+
+static struct cmsrv s = { 0 };
+
+static void sigint_handler(s32 signum)
+{
+ (void)signum;
+ aki_signal_send(&s.quit_signal);
+}
+
+#ifndef _WIN32
+s32 main(s32 argc, char *argv[])
+#else
+s32 wmain(s32 argc, wchar_t **argv)
+#endif
+{
+ (void)argc;
+ (void)argv;
+ if (!aki_common_init() || !cmsrv_ui_init(&s.ui, &s.server)) return EXIT_FAILURE;
+
+ signal(SIGINT, sigint_handler);
+
+ al_set_print(log_callback, &s);
+
+ camu_ff_set_default_log_callback();
+
+ aki_event_loop_init(&s.loop);
+
+ aki_signal_init(&s.quit_signal, quit_signal_callback, &s);
+ aki_signal_start(&s.quit_signal, &s.loop);
+
+ if (!camu_server_init(&s.server, CAMU_LOCAL_TYPE, &s.loop)) return EXIT_FAILURE;
+ camu_server_listen(&s.server, CAMU_LOCAL_ADDR, CAMU_PORT);
+ struct camu_list *list = al_array_at(s.server.lists, 0);
+ list->impl.callback = meta_callback;
+ list->impl.userdata = &s;
+
+#ifdef CAMU_LOCAL_SOCKET
+ s.local.sock.type = AKI_SOCKET_UNIX;
+ aki_socket_init(&s.local.sock);
+ aki_socket_set_blocking(&s.local.sock, false);
+ s.local.cli.callback = server_line_callback;
+ s.local.cli.userdata = &s;
+ aki_line_processor_init(&s.local.cli, al_str_c("\n"));
+ aki_line_processor_open_socket(&s.local.cli, &s.local.sock);
+ if (aki_socket_bind(&s.local.sock, CAMU_UNIX_LOCAL, 0) && aki_socket_listen(&s.local.sock)) {
+ aki_line_processor_run(&s.local.cli, &s.loop);
+ }
+#endif
+
+ aki_poll_init(&s.input_poll, input_poll_callback, &s);
+ aki_poll_set(&s.input_poll, cmsrv_ui_get_input_fd(&s.ui), AKI_POLL_READ);
+ aki_poll_start(&s.input_poll, &s.loop);
+
+ aki_timer_init(&s.render_timer, &s.loop, render_timer_callback, &s);
+ aki_timer_set_repeat(&s.render_timer, AKI_TS_FROM_USEC(100000));
+ aki_timer_again(&s.render_timer);
+
+ aki_event_loop_run(&s.loop);
+
+ cmsrv_ui_close(&s.ui);
+
+ aki_common_close();
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/fruits/cmsrv/meson.build b/src/fruits/cmsrv/meson.build
new file mode 100644
index 0000000..ee8eb91
--- /dev/null
+++ b/src/fruits/cmsrv/meson.build
@@ -0,0 +1,19 @@
+cmsrv_src = ['cmsrv.c']
+cmsrv_deps = [server]
+cmsrv_args = []
+
+if get_option('portal').enabled()
+ cmsrv_deps += [portal]
+endif
+
+use_tui = true
+if use_tui
+ cmsrv_src += ['ui.c']
+ cmsrv_deps += [dependency('notcurses-core')]
+endif
+
+if is_windows and meson.is_cross_build()
+ cmsrv_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows']
+endif
+
+executable('cmsrv', cmsrv_src, dependencies: cmsrv_deps, link_args: cmsrv_args)
diff --git a/src/fruits/cmsrv/ui.c b/src/fruits/cmsrv/ui.c
new file mode 100644
index 0000000..48f50ca
--- /dev/null
+++ b/src/fruits/cmsrv/ui.c
@@ -0,0 +1,210 @@
+#include <al/lib.h>
+
+#include "../../server/list.h"
+
+#include "ui.h"
+
+#define LOG_RATIO 1.3
+
+static s32 resize_cb(struct ncplane *p)
+{
+ struct cmsrv_ui *ui = (struct cmsrv_ui *)ncplane_userptr(p);
+ ui->pending_layout = true;
+ return 0;
+}
+
+bool cmsrv_ui_init(struct cmsrv_ui *ui, struct camu_server *server)
+{
+ if (!(ui->nc = notcurses_core_init(NULL, stdin))) {
+ return false;
+ }
+ ui->server = server;
+ notcurses_stddim_yx(ui->nc, &ui->term_rows, &ui->term_cols);
+ struct ncplane *stdplane = notcurses_stdplane(ui->nc);
+ ncplane_set_resizecb(stdplane, resize_cb);
+ ncplane_set_userptr(stdplane, ui);
+ ui->pending_layout = true;
+ al_array_init(ui->log.messages);
+ return true;
+}
+
+s32 cmsrv_ui_get_input_fd(struct cmsrv_ui *ui)
+{
+ return notcurses_inputready_fd(ui->nc);
+}
+
+bool cmsrv_ui_read_input(struct cmsrv_ui *ui, struct ncinput *input)
+{
+ al_memset(input, 0, sizeof(struct ncinput));
+ u32 ret = notcurses_get_nblock(ui->nc, input);
+ return !(ret == (u32)-1 || ret == 0);
+}
+
+void cmsrv_ui_set_pane(struct cmsrv_ui *ui, u8 pane)
+{
+ ui->pane = pane;
+}
+
+void cmsrv_ui_push_message(struct cmsrv_ui *ui, char *message)
+{
+ al_array_push(ui->log.messages, message);
+}
+
+static void layout_log(struct cmsrv_ui *ui, struct ncplane *parent)
+{
+ struct ncplane_options nopts = {
+ .rows = ui->term_rows / LOG_RATIO,
+ .cols = ui->term_cols,
+ .x = 0
+ };
+ nopts.y = ui->term_rows - nopts.rows;
+ ui->log.p = ncplane_create(parent, &nopts);
+}
+
+static void erase_log(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->log.p;
+ ncplane_erase(p);
+}
+
+static void render_log(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->log.p;
+ ncplane_erase(p);
+
+ u32 max_width = ncplane_dim_x(p) - 2;
+ u32 max_height = ncplane_dim_y(p) - 2;
+
+ u64 c = 0;
+ ncchannels_set_fg_default(&c);
+ ncplane_rounded_box(p, NCSTYLE_NONE, c, max_height + 1, max_width + 1, 0);
+
+ u32 size = ui->log.messages.size;
+ u32 index = size > max_height ? size - max_height : 0;
+ for (u32 i = index; i < size; i++) {
+ ncplane_putnstr_yx(p, (i - index) + 1, 1, max_width, al_array_at(ui->log.messages, i));
+ }
+}
+
+static void layout_lists(struct cmsrv_ui *ui, struct ncplane *parent)
+{
+ struct ncplane_options nopts = {
+ .rows = ui->term_rows - (ui->term_rows / LOG_RATIO),
+ .cols = ui->term_cols,
+ .x = 0,
+ .y = 0
+ };
+ ui->lists.p = ncplane_create(parent, &nopts);
+}
+
+static void erase_lists(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->lists.p;
+ ncplane_erase(p);
+}
+
+static void render_lists(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->lists.p;
+
+ u32 max_width = ncplane_dim_x(p);
+ u32 max_height = ncplane_dim_y(p);
+ u32 current_line = 0;
+ s32 entries_per_list = 10;
+
+ struct camu_list *list;
+ al_array_foreach(ui->server->lists, i, list) {
+ if (current_line++ >= max_height) break;
+ char *c_str = al_str_to_c_str(&list->name);
+ ncplane_putnstr_yx(p, i, 0, max_width, c_str);
+ al_free(c_str);
+ struct lia_list *impl = &list->impl;
+ s32 current = (s32)impl->current;
+ s32 index = AL_MAX(current - (entries_per_list / 2), 0);
+ s32 size = (s32)impl->entries.size;
+ s32 end = AL_MIN(index + entries_per_list, size);
+ for (s32 j = index; j < end; j++) {
+ struct lia_list_entry *entry = al_array_at(impl->entries, j);
+ c_str = al_str_to_c_str(&entry->name);
+ u32 y = i + (j - index) + 1;
+ if (j == current) {
+ ncplane_putchar_yx(p, y, 1, '>');
+ ncplane_putnstr_yx(p, y, 3, max_width - 3, c_str);
+ } else {
+ ncplane_putnstr_yx(p, y, 1, max_width - 1, c_str);
+ }
+ al_free(c_str);
+ if (++current_line >= max_height) break;
+ }
+ }
+}
+
+static void layout_nodes(struct cmsrv_ui *ui, struct ncplane *parent)
+{
+ struct ncplane_options nopts = {
+ .rows = ui->term_rows - (ui->term_rows / LOG_RATIO),
+ .cols = ui->term_cols,
+ .x = 0,
+ .y = 0
+ };
+ ui->nodes.p = ncplane_create(parent, &nopts);
+}
+
+static void erase_nodes(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->nodes.p;
+ ncplane_erase(p);
+}
+
+static void render_nodes(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->lists.p;
+
+ u32 max_width = ncplane_dim_x(p);
+ u32 max_height = ncplane_dim_y(p);
+ (void)max_height;
+
+ char strbuf[16];
+
+ struct lia_node *node;
+ al_array_foreach(ui->server->data.server.nodes, i, node) {
+ al_snprintf(strbuf, sizeof(strbuf), "%hu", node->id);
+ ncplane_putnstr_yx(p, i, 0, max_width, strbuf);
+ struct lia_node_connection *conn;
+ al_array_foreach(node->connections, j, conn) {
+ al_snprintf(strbuf, sizeof(strbuf), "%u", conn->pool.ready.size);
+ ncplane_putnstr_yx(p, i + j + 1, 2, max_width, strbuf);
+ }
+ }
+}
+
+void cmsrv_ui_render(struct cmsrv_ui *ui)
+{
+ if (ui->pending_layout) {
+ ui->pending_layout = false;
+ struct ncplane *stdplane = notcurses_stdplane(ui->nc);
+ layout_log(ui, stdplane);
+ layout_lists(ui, stdplane);
+ layout_nodes(ui, stdplane);
+ }
+ erase_log(ui);
+ erase_lists(ui);
+ erase_nodes(ui);
+ render_log(ui);
+ switch (ui->pane) {
+ case CMSRV_UI_LISTS:
+ render_lists(ui);
+ break;
+ case CMSRV_UI_RESOURCES:
+ break;
+ case CMSRV_UI_NODES:
+ render_nodes(ui);
+ break;
+ }
+ notcurses_render(ui->nc);
+}
+
+void cmsrv_ui_close(struct cmsrv_ui *ui)
+{
+ notcurses_stop(ui->nc);
+}
diff --git a/src/fruits/cmsrv/ui.h b/src/fruits/cmsrv/ui.h
new file mode 100644
index 0000000..08ad956
--- /dev/null
+++ b/src/fruits/cmsrv/ui.h
@@ -0,0 +1,44 @@
+#include <notcurses/notcurses.h>
+#include <al/types.h>
+#include <al/array.h>
+
+#include "../../server/server.h"
+
+enum {
+ CMSRV_UI_LISTS = 0,
+ CMSRV_UI_RESOURCES,
+ CMSRV_UI_NODES
+};
+
+struct cmsrv_ui {
+ struct notcurses *nc;
+ u32 term_cols;
+ u32 term_rows;
+ bool pending_layout;
+ u8 pane;
+ struct {
+ struct ncplane *p;
+ array(char *) messages;
+ } log;
+ struct {
+ struct ncplane *p;
+ } lists;
+ struct {
+ } resources;
+ struct {
+ struct ncplane *p;
+ } nodes;
+ struct camu_server *server;
+};
+
+bool cmsrv_ui_init(struct cmsrv_ui *ui, struct camu_server *server);
+
+s32 cmsrv_ui_get_input_fd(struct cmsrv_ui *ui);
+bool cmsrv_ui_read_input(struct cmsrv_ui *ui, struct ncinput *input);
+
+void cmsrv_ui_set_pane(struct cmsrv_ui *ui, u8 pane);
+
+void cmsrv_ui_push_message(struct cmsrv_ui *ui, char *message);
+void cmsrv_ui_render(struct cmsrv_ui *ui);
+
+void cmsrv_ui_close(struct cmsrv_ui *ui);
diff --git a/src/fruits/cmv/cmv.c b/src/fruits/cmv/cmv.c
new file mode 100644
index 0000000..8eaa8b7
--- /dev/null
+++ b/src/fruits/cmv/cmv.c
@@ -0,0 +1,151 @@
+#include <al/log.h>
+#include <aki/event_loop.h>
+#include <aki/thread.h>
+
+#include "../../sink/desktop.h"
+#include "../../server/common.h"
+#include "../../codec/ffmpeg/common.h"
+#ifndef CAMU_SINK_ONLY
+#include "../../server/server.h"
+#include "../../server/list.h"
+#else
+#include "../../server/common.c"
+#endif
+
+struct cmv {
+ struct aki_event_loop loop;
+ struct camu_desktop desktop;
+#ifndef CAMU_SINK_ONLY
+ struct camu_server server;
+#endif
+};
+
+#ifndef CAMU_SINK_ONLY
+static void meta_callback(void *userdata, u8 op, struct lia_list_entry *entry)
+{
+ struct cmv *c = (struct cmv *)userdata;
+ (void)c;
+ switch (op) {
+ case LIANA_META_PLAYING: {
+ struct camu_server_resource *resource = (struct camu_server_resource *)entry->opaque;
+ al_log_info("server", "now playing: %.*s.", AL_STR_PRINTF(&resource->unique_id));
+ break;
+ }
+ }
+}
+
+static void exit_callback(void *userdata, struct camu_desktop *desktop)
+{
+ struct cmv *c = (struct cmv *)userdata;
+ (void)desktop;
+ camu_server_close(&c->server);
+}
+#endif
+
+static aki_thread_result AKI_THREADCALL event_loop_thread(void *userdata)
+{
+ struct cmv *c = (struct cmv *)userdata;
+ aki_event_loop_run(&c->loop);
+ return 0;
+}
+
+static struct cmv c = { 0 };
+
+static void sigint_handler(int signum)
+{
+ (void)signum;
+ c.desktop.should_quit = 1;
+}
+
+#define failure() do { \
+ goto out; \
+} while (0)
+
+#ifndef _WIN32
+s32 main(s32 argc, char *argv[])
+#else
+s32 wmain(s32 argc, wchar_t **argv)
+#endif
+{
+ if (!aki_common_init() || !stl_global_init(false)) {
+ return EXIT_FAILURE;
+ }
+
+ s32 ret = EXIT_FAILURE;
+
+ signal(SIGINT, sigint_handler);
+
+ camu_ff_set_default_log_callback();
+
+ aki_event_loop_init(&c.loop);
+
+ u8 type;
+ str *addr;
+#ifndef CAMU_SINK_ONLY
+ bool local = argc > 1;
+ if (local) {
+ type = AKI_SOCKET_UNIX; addr = CAMU_UNIX_LOCAL;
+ if (!camu_server_init(&c.server, type, &c.loop)) failure();
+ if (!camu_server_listen(&c.server, addr, CAMU_PORT)) failure();
+ struct camu_list *list = al_array_at(c.server.lists, 0);
+ list->impl.callback = meta_callback;
+ list->impl.userdata = &c;
+ } else {
+ type = CAMU_LOCAL_TYPE;
+ addr = CAMU_LOCAL_ADDR;
+ }
+#else
+ type = AKI_SOCKET_TCP; addr = CAMU_SERVER_IP;
+#endif
+
+#ifndef CAMU_SINK_ONLY
+ if (local) {
+ for (s32 i = 1; i < argc; i++) {
+#ifndef _WIN32
+ str arg = *al_str_cr(argv[i]);
+#else
+ str arg;
+ al_wstr_to_str(al_wstr_cr(argv[i]), &arg);
+#endif
+ struct aki_packet *packet = aki_packet_create();
+ aki_packet_write_str(packet, &arg);
+ camu_server_local_add(&c.server, packet);
+ }
+ }
+#else
+ (void)argc;
+ (void)argv;
+#endif
+
+ if (!camu_desktop_init(&c.desktop, "cmv")) failure();
+#ifndef CAMU_SINK_ONLY
+ if (local) {
+ c.desktop.exit_callback = exit_callback;
+ c.desktop.userdata = &c;
+ }
+#endif
+ if (!camu_desktop_connect(&c.desktop, type, &c.loop, addr, CAMU_PORT)) failure();
+
+ struct aki_thread thread0;
+ aki_thread_create(&thread0, event_loop_thread, &c);
+
+ while (camu_desktop_tick(&c.desktop)) {}
+
+ camu_desktop_stop(&c.desktop);
+ aki_thread_join(&thread0);
+ camu_desktop_free(&c.desktop);
+#ifndef CAMU_SINK_ONLY
+ camu_server_free(&c.server);
+#endif
+
+ ret = EXIT_SUCCESS;
+out:
+ aki_event_loop_destroy(&c.loop);
+
+ camu_ff_free_default_log_callback();
+
+ stl_global_close();
+ aki_common_close();
+
+ return ret;
+}
diff --git a/src/fruits/cmv/meson.build b/src/fruits/cmv/meson.build
new file mode 100644
index 0000000..ba571c3
--- /dev/null
+++ b/src/fruits/cmv/meson.build
@@ -0,0 +1,25 @@
+cmv_src = ['cmv.c']
+cmv_deps = [desktop]
+cmv_args = []
+if get_option('sink-only')
+ cmv_args += ['-DCAMU_SINK_ONLY']
+else
+ cmv_deps += [server]
+endif
+
+if get_option('portal').enabled()
+ cmv_deps += [portal]
+endif
+
+use_tui = false
+if use_tui
+ cmv_src += []
+ cmv_deps += [dependency('notcurses')]
+endif
+
+cmv_link_args = []
+if is_windows and meson.is_cross_build()
+ cmv_link_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows']
+endif
+
+executable('cmv', cmv_src, dependencies: cmv_deps, c_args: cmv_args, link_args: cmv_link_args)
diff --git a/src/fruits/sink/meson.build b/src/fruits/sink/meson.build
deleted file mode 100644
index 1dd9140..0000000
--- a/src/fruits/sink/meson.build
+++ /dev/null
@@ -1,15 +0,0 @@
-sink_src = ['sink.c']
-sink_deps = [common_deps, buffer, render, screen, mixer, libsink]
-sink_args = []
-
-use_tui = false
-if use_tui
- sink_src += []
- sink_deps += [dependency('notcurses')]
-endif
-
-if is_windows and meson.is_cross_build()
- sink_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows']
-endif
-
-executable('sink', sink_src, dependencies: sink_deps, link_args: sink_args)
diff --git a/src/fruits/sink/sink.c b/src/fruits/sink/sink.c
deleted file mode 100644
index 2b3d451..0000000
--- a/src/fruits/sink/sink.c
+++ /dev/null
@@ -1,208 +0,0 @@
-#include <al/log.h>
-#include <aki/event_loop.h>
-
-#include "../../mixer/audio_miniaudio.h"
-
-#include "../../screen/screen.h"
-#include "../../render/renderer_libplacebo.h"
-//#include "../../render/renderer_tiger.h"
-
-#include "../../libsink/sink.h"
-#include "../../server/common.h"
-
-struct cmv {
- s32 quit;
- struct camu_screen scr;
- struct camu_renderer *renderer;
- struct camu_mixer mixer;
- struct aki_event_loop loop;
- struct camu_sink sink;
-};
-
-static u8 sink_callback(void *userdata, u8 op, u8 type, void *opaque)
-{
- struct cmv *c = (struct cmv *)userdata;
- switch (op) {
- case CAMU_SINK_ADD_BUFFER:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque;
- camu_mixer_add_buffer(&c->mixer, buf);
- al_log_debug("cmv", "Audio buffer added.");
- break;
- }
- case CAMU_SINK_VIDEO: {
- struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque;
- camu_screen_add_buffer(&c->scr, buf);
- al_log_debug("cmv", "Video buffer added.");
- break;
- }
- }
- break;
- case CAMU_SINK_REMOVE_BUFFER:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque;
- camu_mixer_remove_buffer(&c->mixer, buf);
- al_log_debug("cmv", "Audio buffer removed.");
- break;
- }
- case CAMU_SINK_VIDEO: {
- struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque;
- camu_screen_remove_buffer(&c->scr, buf);
- al_log_debug("cmv", "Video buffer removed.");
- break;
- }
- }
- break;
- case CAMU_SINK_SWAP_BUFFER:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- break;
- }
- }
- break;
- case CAMU_SINK_SET_BUFFERED: {
- break;
- }
- case CAMU_SINK_START:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- camu_mixer_resume(&c->mixer);
- al_log_debug("cmv", "Audio started.");
- break;
- }
- case CAMU_SINK_VIDEO: {
- camu_screen_set_state(&c->scr, CAMU_SCREEN_PLAYING);
- camu_screen_wake(&c->scr);
- al_log_debug("cmv", "Video started.");
- break;
- }
- }
- break;
- case CAMU_SINK_STOP:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- camu_mixer_pause(&c->mixer);
- al_log_debug("cmv", "Audio stopped.");
- break;
- }
- case CAMU_SINK_VIDEO: {
- camu_screen_set_state(&c->scr, CAMU_SCREEN_PAUSED);
- al_log_debug("cmv", "Video stopped.");
- break;
- }
- }
- break;
- case CAMU_SINK_EXIT:
- camu_sink_close(&c->sink);
- break;
- }
- return CAMU_SINK_OK;
-}
-
-static void screen_callback(void *userdata, u8 op, f64 float0)
-{
- struct cmv *c = (struct cmv *)userdata;
- switch (op) {
- case CAMU_SCREEN_NEXT:
- camu_sink_skip(&c->sink, 1);
- break;
- case CAMU_SCREEN_PREVIOUS:
- camu_sink_skip(&c->sink, -1);
- break;
- case CAMU_SCREEN_TOGGLE_PAUSE:
- camu_sink_toggle_pause(&c->sink);
- break;
- case CAMU_SCREEN_SEEK:
- camu_sink_seek(&c->sink, float0);
- break;
- case CAMU_SCREEN_RESEEK:
- camu_sink_reseek(&c->sink);
- break;
- case CAMU_SCREEN_CLOSE:
- c->quit = 1;
- break;
- }
-}
-
-static aki_thread_result AKI_THREADCALL event_loop_thread(void *userdata)
-{
- struct cmv *c = (struct cmv *)userdata;
- aki_event_loop_init(&c->loop);
- camu_sink_init(&c->sink, &c->loop, &c->mixer, c->renderer);
- c->sink.callback = sink_callback;
- c->sink.userdata = c;
- if (camu_sink_connect(&c->sink, CAMU_SERVER_IP, CAMU_PORT)) {
- aki_event_loop_run(&c->loop);
- }
- aki_event_loop_destroy(&c->loop);
- return 0;
-}
-
-static struct cmv c = { 0 };
-
-static void sigint_handler(s32 signum)
-{
- (void)signum;
- c.quit = 1;
-}
-
-#ifndef _WIN32
-s32 main(s32 argc, char *argv[])
-#else
-s32 wmain(s32 argc, wchar_t **argv)
-#endif
-{
- (void)argc;
- (void)argv;
- if (!aki_common_init()) return EXIT_FAILURE;
-
- signal(SIGINT, sigint_handler);
-
- c.scr.callback = screen_callback;
- c.scr.userdata = &c;
- if (!camu_screen_init(&c.scr) || !camu_screen_create_window(&c.scr, "cmv")) {
- goto err;
- }
- c.renderer = camu_renderer_lp_create();
- //c.renderer = camu_renderer_tiger_create();
- if (!camu_screen_create_renderer(&c.scr, c.renderer)) {
- goto err;
- }
- c.renderer->render(c.renderer, &c.scr);
-
- if (!camu_mixer_init(&c.mixer, (struct camu_audio *)&audio_plugin_miniaudio)) {
- goto err;
- }
-
- struct aki_thread thread0;
- aki_thread_create(&thread0, event_loop_thread, &c);
-
- c.quit = 0;
-
- while (!c.quit) {
- if (camu_screen_tick(&c.scr)) {
- c.renderer->render(c.renderer, &c.scr);
- }
- }
-
- camu_screen_set_state(&c.scr, CAMU_SCREEN_CLOSED);
-
- camu_sink_stop(&c.sink);
-
- aki_thread_join(&thread0);
-
- camu_sink_free(&c.sink);
-
- camu_mixer_close(&c.mixer);
- c.renderer->free(&c.renderer);
- camu_screen_close(&c.scr);
-
- aki_common_close();
-
- return EXIT_SUCCESS;
-err:
- aki_common_close();
- return EXIT_FAILURE;
-}
diff --git a/src/liana/client.c b/src/liana/client.c
new file mode 100644
index 0000000..d556453
--- /dev/null
+++ b/src/liana/client.c
@@ -0,0 +1,199 @@
+#include "../server/common.h"
+#ifdef CAMU_HAVE_FFMPEG
+#include "../codec/ffmpeg/packet_ext.h"
+#endif
+
+#include "client.h"
+#include "handlers.h"
+
+static void data_packet_callback(void *userdata, struct aki_packet_stream *stream, struct aki_packet *packet)
+{
+ struct lia_client *client = (struct lia_client *)userdata;
+ (void)stream;
+ lia_vcr_push_packet(&client->vcr, packet);
+}
+
+static void parse_info_packet(struct lia_client *client, struct aki_packet *packet)
+{
+ str liana;
+ aki_packet_read_str(packet, &liana);
+ client->duration = aki_packet_read_u64(packet);
+ u32 count = aki_packet_read_u32(packet);
+ for (u32 i = 0; i < count; i++) {
+ u8 mode = aki_packet_read_u8(packet);
+ struct lia_vcr_track *track = NULL;
+ switch (mode) {
+ case CAMU_NORMAL: {
+ client->mask |= 1 << 0;
+ 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);
+ fmt->sample_rate = aki_packet_read_s32(packet);
+ fmt->channel_count = aki_packet_read_s32(packet);
+#ifdef CAMU_HAVE_FFMPEG
+ av_channel_layout_default(&fmt->channel_layout, fmt->channel_count);
+#endif
+ } else if (type == CAMU_STREAM_VIDEO) {
+ track->stream.video.width = aki_packet_read_s32(packet);
+ track->stream.video.height = aki_packet_read_s32(packet);
+ track->stream.video.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:
+ client->mask |= 1 << index;
+ break;
+ case AVMEDIA_TYPE_VIDEO:
+ client->mask |= 1 << index;
+ break;
+ case AVMEDIA_TYPE_SUBTITLE:
+ default:
+ 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) {
+ 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->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)) {
+ client->callback(client->userdata, LIANA_CLIENT_CONFIGURE, track->client->stream, track);
+ }
+ lia_vcr_add_track(&client->vcr, track);
+ }
+}
+
+static void info_packet_callback(void *userdata, struct aki_packet_stream *stream, struct aki_packet *packet)
+{
+ 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);
+ stream->packet_callback = data_packet_callback;
+ struct aki_packet *rpacket = aki_packet_create();
+ aki_packet_write_s32(rpacket, client->mask);
+ aki_packet_stream_send_packet(stream, rpacket);
+}
+
+static void packet_sent_callback(void *userdata, struct aki_packet *packet)
+{
+ (void)userdata;
+ aki_packet_free(packet);
+}
+
+static void connection_callback(void *userdata, struct aki_packet_stream *stream)
+{
+ struct lia_client *client = (struct lia_client *)userdata;
+ stream->packet_sent_callback = packet_sent_callback;
+ struct aki_packet *packet = aki_packet_create();
+ aki_packet_write_u16(packet, client->id);
+ aki_packet_write_u16(packet, 0);
+ aki_packet_write_s32(packet, client->mask);
+ aki_packet_write_u64(packet, client->pos);
+ if (client->mask == 0) {
+ stream->packet_callback = info_packet_callback;
+ } else {
+ stream->packet_callback = data_packet_callback;
+ }
+ aki_packet_stream_send_packet(stream, packet);
+}
+
+static void connection_closed_callback(void *userdata, struct aki_packet_stream *stream)
+{
+ struct lia_client *client = (struct lia_client *)userdata;
+ if (client->reconnect) {
+ lia_vcr_flush(&client->vcr);
+ } else {
+ lia_vcr_close_all(&client->vcr);
+ }
+ // REMOVE_BUFFERS can possibly run the event loop while waiting.
+ // This should be accounted for in the client code here to not cause
+ // any unexpected behavior.
+ client->callback(client->userdata, LIANA_CLIENT_REMOVE_BUFFERS, NULL, &client->reconnect);
+ if (client->reconnect) {
+ client->reconnect = false;
+ aki_packet_stream_reconnect(stream, &client->addr, client->port);
+ } else {
+ client->callback(client->userdata, LIANA_CLIENT_CLOSED, NULL, NULL);
+ }
+}
+
+void lia_client_connect(struct lia_client *client, struct aki_event_loop *loop, u8 type,
+ str *addr, u16 port, u16 id, u64 pos, struct camu_renderer *renderer)
+{
+ client->loop = loop;
+ client->id = id;
+ client->pos = pos;
+ 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)) {
+ connection_closed_callback(client, &client->data);
+ }
+ aki_packet_stream_set_multiplex(&client->data, CAMU_MULTIPLEX_LIANA);
+ client->renderer = renderer;
+ aki_packet_stream_connect(&client->data, client->loop, &client->addr, client->port);
+}
+
+void lia_client_set_renderer(struct lia_client *client, struct camu_renderer *renderer)
+{
+ client->renderer = renderer;
+}
+
+void lia_client_seek(struct lia_client *client, u64 pos)
+{
+ client->reconnect = true;
+ client->pos = pos;
+ aki_packet_stream_disconnect(&client->data);
+}
+
+void lia_client_reseek(struct lia_client *client)
+{
+ (void)client;
+}
+
+void lia_client_disconnect(struct lia_client *client)
+{
+ client->reconnect = false;
+ aki_packet_stream_disconnect(&client->data);
+}
+
+void lia_client_free(struct lia_client *client)
+{
+ lia_vcr_free(&client->vcr);
+ aki_packet_stream_free(&client->data);
+ al_str_free(&client->addr);
+}
diff --git a/src/liana/client.h b/src/liana/client.h
new file mode 100644
index 0000000..5c16cd8
--- /dev/null
+++ b/src/liana/client.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <aki/packet_stream.h>
+
+#include "../codec/codec.h"
+
+#include "vcr.h"
+
+struct lia_client {
+ struct aki_event_loop *loop;
+ u16 id;
+ s32 mask;
+ u64 pos;
+ bool reconnect;
+ str addr;
+ u16 port;
+ u16 connection_id;
+ struct aki_packet_stream data;
+ u64 duration;
+ struct lia_vcr vcr;
+ struct camu_renderer *renderer;
+ void (*callback)(void *, u8, struct camu_codec_stream *stream, void *);
+ void *userdata;
+};
+
+void lia_client_connect(struct lia_client *client, struct aki_event_loop *loop, u8 type,
+ str *addr, u16 port, u16 id, u64 pos, struct camu_renderer *renderer);
+void lia_client_seek(struct lia_client *client, u64 pos);
+void lia_client_reseek(struct lia_client *client);
+void lia_client_disconnect(struct lia_client *client);
+void lia_client_free(struct lia_client *client);
diff --git a/src/liana/common.h b/src/liana/common.h
new file mode 100644
index 0000000..6f70f09
--- /dev/null
+++ b/src/liana/common.h
@@ -0,0 +1 @@
+#pragma once
diff --git a/src/liana/handler.h b/src/liana/handler.h
new file mode 100644
index 0000000..6c23ac5
--- /dev/null
+++ b/src/liana/handler.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <aki/packet_pool.h>
+
+#include "../codec/codec.h"
+#include "../cache/handle.h"
+
+struct lia_server_handler {
+ bool (*init)(struct lia_server_handler *, struct cch_handle *);
+ void (*write_info)(struct lia_server_handler *, struct aki_packet *);
+ void (*subscribe)(struct lia_server_handler *, s32);
+ u64 (*get_duration)(struct lia_server_handler *);
+ bool (*seek)(struct lia_server_handler *, u64);
+ void (*step)(struct lia_server_handler *);
+ void (*write_packet)(struct lia_server_handler *, struct aki_packet *);
+ void (*free)(struct lia_server_handler **);
+ s32 status;
+};
+
+enum {
+ LIANA_PACKET_DATA = 0,
+ LIANA_PACKET_EOF,
+ LIANA_PACKET_ERROR
+};
+
+enum {
+ LIANA_CLIENT_CONFIGURE = 0,
+ LIANA_CLIENT_SET,
+ LIANA_CLIENT_PAUSE,
+ LIANA_CLIENT_RESUME,
+ LIANA_CLIENT_DATA,
+ LIANA_CLIENT_REMOVE_BUFFERS,
+ 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 aki_packet *);
+ void (*flush)(struct lia_client_handler *);
+ void (*free)(struct lia_client_handler **);
+ struct camu_codec_stream *stream;
+ void (*callback)(void *, u8, struct camu_codec_stream *stream, void *);
+ void *userdata;
+};
diff --git a/src/liana/handlers.c b/src/liana/handlers.c
new file mode 100644
index 0000000..e67250a
--- /dev/null
+++ b/src/liana/handlers.c
@@ -0,0 +1,37 @@
+#include "handlers.h"
+
+#include "handlers/codec.h"
+#include "handlers/cdio.h"
+
+struct lia_handler_entry liana_handlers[] = {
+ {
+ .name = al_str_c("codec"),
+#ifdef LIANA_SERVER
+ .create_server_handler = lia_codec_server_create,
+#endif
+#ifdef LIANA_CLIENT
+ .create_client_handler = lia_codec_client_create
+#endif
+ },
+#ifdef LIANA_HAVE_CDIO
+ {
+ .name = al_str_c("cdio"),
+#ifdef LIANA_SERVER
+ .create_server_handler = lia_cdio_server_create,
+#endif
+#ifdef LIANA_CLIENT
+ .create_client_handler = lia_cdio_client_create
+#endif
+ }
+#endif
+};
+
+struct lia_handler_entry *lia_handler_by_name(str *name)
+{
+ for (u32 i = 0; i < AL_ARRAY_SIZE(liana_handlers); i++) {
+ if (al_str_eq(liana_handlers[i].name, name)) {
+ return &liana_handlers[i];
+ }
+ }
+ return NULL;
+}
diff --git a/src/liana/handlers.h b/src/liana/handlers.h
new file mode 100644
index 0000000..0193f18
--- /dev/null
+++ b/src/liana/handlers.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "handler.h"
+
+struct lia_handler_entry {
+ str *name;
+ struct lia_server_handler *(*create_server_handler)(void);
+ struct lia_client_handler *(*create_client_handler)(void);
+};
+
+extern struct lia_handler_entry liana_handlers[];
+
+struct lia_handler_entry *lia_handler_by_name(str *name);
diff --git a/src/liana/handlers/cdio.h b/src/liana/handlers/cdio.h
new file mode 100644
index 0000000..2727616
--- /dev/null
+++ b/src/liana/handlers/cdio.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "../handler.h"
+
+struct lia_cdio_server {
+ struct lia_server_handler handler;
+ struct cch_handle *handle;
+ struct camu_audio_format fmt;
+ f64 pts;
+ struct aki_buffer buffer;
+};
+
+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);
diff --git a/src/liana/handlers/cdio_client.c b/src/liana/handlers/cdio_client.c
new file mode 100644
index 0000000..93b1a27
--- /dev/null
+++ b/src/liana/handlers/cdio_client.c
@@ -0,0 +1,49 @@
+#include "cdio.h"
+
+static bool cdio_client_init(struct lia_client_handler *handler, struct camu_renderer *renderer,
+ struct camu_codec_stream *stream)
+{
+ struct lia_cdio_client *cdio = (struct lia_cdio_client *)handler;
+ (void)renderer;
+ cdio->handler.stream = stream;
+ return true;
+}
+
+static bool cdio_client_handle_packet(struct lia_client_handler *handler, struct aki_packet *packet)
+{
+ struct lia_cdio_client *cdio = (struct lia_cdio_client *)handler;
+ if (!packet) {
+ cdio->handler.callback(cdio->handler.userdata, LIANA_CLIENT_EOF, cdio->handler.stream, NULL);
+ return true;
+ }
+ struct camu_codec_frame *frame = al_alloc_object(struct camu_codec_frame);
+ frame->mode = CAMU_NORMAL;
+ frame->pts = aki_packet_read_f64(packet);
+ frame->audio.sample_count = aki_packet_read_s32(packet);
+ struct aki_buffer buffer;
+ aki_packet_read_buffer(packet, &buffer);
+ frame->data = al_malloc(buffer.size);
+ al_memcpy(frame->data, aki_buffer_get_ptr(&buffer, 0), buffer.size);
+ cdio->handler.callback(cdio->handler.userdata, LIANA_CLIENT_DATA, cdio->handler.stream, frame);
+ return true;
+}
+
+static void cdio_client_flush(struct lia_client_handler *handler)
+{
+ (void)handler;
+}
+
+static void cdio_client_free(struct lia_client_handler **handler)
+{
+ (void)handler;
+}
+
+struct lia_client_handler *lia_cdio_client_create(void)
+{
+ struct lia_cdio_client *cdio = al_alloc_object(struct lia_cdio_client);
+ cdio->handler.init = cdio_client_init;
+ cdio->handler.handle_packet = cdio_client_handle_packet;
+ cdio->handler.flush = cdio_client_flush;
+ cdio->handler.free = cdio_client_free;
+ return (struct lia_client_handler *)cdio;
+}
diff --git a/src/liana/handlers/cdio_server.c b/src/liana/handlers/cdio_server.c
new file mode 100644
index 0000000..1eaf6f3
--- /dev/null
+++ b/src/liana/handlers/cdio_server.c
@@ -0,0 +1,106 @@
+#include <al/log.h>
+#include <cdio/paranoia/cdda.h>
+
+#include "../../cache/entry.h"
+#include "../../cache/handlers/cdio.h"
+
+#include "cdio.h"
+
+#define SECTORS_PER_PACKET 8
+
+static bool cdio_server_init(struct lia_server_handler *handler, struct cch_handle *handle)
+{
+ struct lia_cdio_server *cdio = (struct lia_cdio_server *)handler;
+ cdio->handle = handle;
+
+ cdio->fmt.format = CAMU_SAMPLE_FORMAT_S16;
+ cdio->fmt.sample_rate = 44100;
+ cdio->fmt.channel_count = 2;
+ cdio->pts = 0.0;
+ aki_buffer_init(&cdio->buffer);
+
+ if (handle->entry->chapter) {
+ cch_handle_seek(handle, handle->entry->chapter->start * CDIO_CD_FRAMESIZE_RAW, SEEK_SET);
+ }
+
+ return true;
+}
+
+static void cdio_server_write_info(struct lia_server_handler *handler, struct aki_packet *packet)
+{
+ struct lia_cdio_server *cdio = (struct lia_cdio_server *)handler;
+ aki_packet_write_u64(packet, 0);
+ aki_packet_write_u32(packet, 1);
+ aki_packet_write_u8(packet, CAMU_NORMAL);
+ aki_packet_write_u8(packet, CAMU_STREAM_AUDIO);
+ 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);
+}
+
+static void cdio_server_subscribe(struct lia_server_handler *handler, s32 mask)
+{
+ (void)handler;
+ (void)mask;
+}
+
+static u64 cdio_server_get_duration(struct lia_server_handler *handler)
+{
+ (void)handler;
+ return 0;
+}
+
+static bool cdio_server_seek(struct lia_server_handler *handler, u64 pos)
+{
+ struct lia_cdio_server *cdio = (struct lia_cdio_server *)handler;
+ return cch_handle_seek(cdio->handle, pos, SEEK_SET);
+}
+
+static void cdio_server_step(struct lia_server_handler *handler)
+{
+ struct lia_cdio_server *cdio = (struct lia_cdio_server *)handler;
+ (void)cdio;
+}
+
+static void cdio_server_write_packet(struct lia_server_handler *handler, struct aki_packet *packet)
+{
+ struct lia_cdio_server *cdio = (struct lia_cdio_server *)handler;
+ off_t size = CDIO_CD_FRAMESIZE_RAW * SECTORS_PER_PACKET;
+ aki_buffer_ensure_space(&cdio->buffer, size);
+ s32 ret = cch_handle_read(cdio->handle, aki_buffer_get_ptr(&cdio->buffer, 0), size);
+ if (ret == CAMU_ERR_EOF) {
+ cdio->handler.status = CAMU_ERR_EOF;
+ aki_packet_write_u8(packet, LIANA_PACKET_EOF);
+ return;
+ }
+ cdio->handler.status = CAMU_OK;
+ aki_packet_write_u8(packet, LIANA_PACKET_DATA);
+ aki_packet_write_s32(packet, 0);
+ aki_packet_write_f64(packet, cdio->pts);
+ s32 sample_count = ret / (camu_audio_format_bytes_per_sample(&cdio->fmt) * cdio->fmt.channel_count);
+ cdio->pts += camu_audio_format_samples_to_sec(&cdio->fmt, sample_count);
+ aki_packet_write_s32(packet, sample_count);
+ aki_buffer_set_size(&cdio->buffer, ret);
+ aki_packet_write_buffer(packet, &cdio->buffer);
+}
+
+static void cdio_server_free(struct lia_server_handler **handler)
+{
+ struct lia_cdio_server *cdio = (struct lia_cdio_server *)*handler;
+ al_free(cdio);
+ *handler = NULL;
+}
+
+struct lia_server_handler *lia_cdio_server_create()
+{
+ struct lia_cdio_server *cdio = al_alloc_object(struct lia_cdio_server);
+ cdio->handler.init = cdio_server_init;
+ cdio->handler.write_info = cdio_server_write_info;
+ cdio->handler.subscribe = cdio_server_subscribe;
+ cdio->handler.get_duration = cdio_server_get_duration;
+ cdio->handler.seek = cdio_server_seek;
+ cdio->handler.step = cdio_server_step;
+ cdio->handler.write_packet = cdio_server_write_packet;
+ cdio->handler.free = cdio_server_free;
+ return (struct lia_server_handler *)cdio;
+}
diff --git a/src/liana/handlers/codec.h b/src/liana/handlers/codec.h
new file mode 100644
index 0000000..8c5528f
--- /dev/null
+++ b/src/liana/handlers/codec.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "../../codec/codec.h"
+
+#include "../handler.h"
+
+struct lia_codec_server {
+ struct lia_server_handler handler;
+ struct camu_demuxer *demux;
+ struct camu_codec_packet packet;
+};
+
+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);
diff --git a/src/liana/handlers/codec_client.c b/src/liana/handlers/codec_client.c
new file mode 100644
index 0000000..050cc6f
--- /dev/null
+++ b/src/liana/handlers/codec_client.c
@@ -0,0 +1,116 @@
+#include "../../codec/codec.h"
+#include "../../codec/ffmpeg/decoder.h"
+#include "../../codec/ffmpeg/packet_ext.h"
+#include "../../codec/stb_image/decoder.h"
+#include "../../codec/spng/decoder.h"
+#include "../../codec/wuffs/decoder.h"
+
+#include "../vcr.h"
+
+#include "codec.h"
+
+static void data_callback(void *userdata, struct camu_codec_frame *frame)
+{
+ struct lia_codec_client *codec = (struct lia_codec_client *)userdata;
+ codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_DATA, codec->handler.stream, frame);
+}
+
+static bool codec_client_init(struct lia_client_handler *handler, struct camu_renderer *renderer,
+ 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;
+ }
+ return true;
+}
+
+#ifdef CAMU_HAVE_FFMPEG
+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
+
+static bool push_packet(struct lia_codec_client *codec, struct aki_buffer *buffer)
+{
+ struct camu_codec_packet packet;
+ packet.buffer = buffer;
+ s32 ret = codec->dec->push(codec->dec, &packet);
+ return ret == CAMU_OK;
+}
+
+static bool codec_client_handle_packet(struct lia_client_handler *handler, struct aki_packet *packet)
+{
+ struct lia_codec_client *codec = (struct lia_codec_client *)handler;
+ if (!packet) {
+ struct lia_codec_client *codec = (struct lia_codec_client *)handler;
+ s32 ret = codec->dec->push(codec->dec, NULL);
+ // Flush returns success.
+ ret = codec->dec->process(codec->dec);
+ // process() could still error.
+ codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_EOF, codec->handler.stream, NULL);
+ return ret == CAMU_ERR_EOF;
+ }
+ bool success = false;
+ u8 type = aki_packet_read_u8(packet);
+ switch (type) {
+ case CAMU_NORMAL: {
+ struct aki_buffer buffer;
+ aki_packet_read_buffer(packet, &buffer);
+ success = push_packet(codec, &buffer);
+ break;
+ }
+#ifdef CAMU_HAVE_FFMPEG
+ case CAMU_FFMPEG_COMPAT: {
+ success = push_av_packet(codec, aki_packet_read_av_packet(packet));
+ break;
+ }
+#endif
+ }
+ // Forcing in EOF on errors 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;
+ }
+ s32 ret = codec->dec->process(codec->dec);
+ if (!(ret == CAMU_ERR_AGAIN || ret == CAMU_ERR_EOF)) {
+ codec->handler.callback(codec->handler.userdata, LIANA_CLIENT_EOF, codec->handler.stream, NULL);
+ return false;
+ }
+ return true;
+}
+
+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);
+}
+
+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);
+ al_free(codec);
+ *handler = NULL;
+}
+
+struct lia_client_handler *lia_codec_client_create(void)
+{
+ struct lia_codec_client *codec = al_alloc_object(struct lia_codec_client);
+ codec->handler.init = codec_client_init;
+ codec->handler.handle_packet = codec_client_handle_packet;
+ codec->handler.flush = codec_client_flush;
+ codec->handler.free = codec_client_free;
+ return (struct lia_client_handler *)codec;
+}
diff --git a/src/liana/handlers/codec_server.c b/src/liana/handlers/codec_server.c
new file mode 100644
index 0000000..07fafdf
--- /dev/null
+++ b/src/liana/handlers/codec_server.c
@@ -0,0 +1,143 @@
+#include "../../codec/codec.h"
+#include "../../codec/ffmpeg/demuxer.h"
+#include "../../codec/ffmpeg/packet_ext.h"
+#include "../../codec/stb_image/demuxer.h"
+#include "../../codec/spng/demuxer.h"
+#include "../../codec/wuffs/demuxer.h"
+
+#include "../handler.h"
+
+#include "codec.h"
+
+static bool codec_server_init(struct lia_server_handler *handler, struct cch_handle *handle)
+{
+ struct lia_codec_server *codec = (struct lia_codec_server *)handler;
+ codec->demux = camu_ff_demuxer_create();
+ //codec->demux = camu_stbi_demuxer_create();
+ //codec->demux = camu_spng_demuxer_create();
+ //codec->demux = camu_wuffs_demuxer_create();
+ if (!codec->demux->init(codec->demux, handle)) {
+ return false;
+ }
+ switch (codec->demux->mode) {
+ case CAMU_NORMAL:
+ break;
+#ifdef CAMU_HAVE_FFMPEG
+ case CAMU_FFMPEG_COMPAT:
+ codec->packet.av.pkt = av_packet_alloc();
+ break;
+#endif
+ }
+ return true;
+}
+
+static void codec_server_write_info(struct lia_server_handler *handler, struct aki_packet *packet)
+{
+ struct lia_codec_server *codec = (struct lia_codec_server *)handler;
+ u64 duration = codec->demux->get_duration(codec->demux);
+ aki_packet_write_u64(packet, duration);
+ aki_packet_write_u32(packet, codec->demux->streams.size);
+ struct camu_codec_stream *stream;
+ al_array_foreach_ptr(codec->demux->streams, i, stream) {
+ aki_packet_write_u8(packet, stream->mode);
+ switch (stream->mode) {
+ case CAMU_NORMAL:
+ aki_packet_write_u8(packet, stream->type);
+ aki_packet_write_s32(packet, stream->video.width);
+ aki_packet_write_s32(packet, stream->video.height);
+ aki_packet_write_s32(packet, stream->video.format);
+ break;
+#ifdef CAMU_HAVE_FFMPEG
+ case CAMU_FFMPEG_COMPAT:
+ aki_packet_write_av_codec_id(packet, stream->av.stream->codecpar->codec_id);
+ aki_packet_write_av_stream(packet, stream->av.stream);
+ break;
+#endif
+ }
+ }
+}
+
+static void codec_server_subscribe(struct lia_server_handler *handler, s32 mask)
+{
+ struct lia_codec_server *codec = (struct lia_codec_server *)handler;
+ codec->demux->subscribed = mask;
+}
+
+static u64 codec_server_get_duration(struct lia_server_handler *handler)
+{
+ struct lia_codec_server *codec = (struct lia_codec_server *)handler;
+ return codec->demux->get_duration(codec->demux);
+}
+
+static bool codec_server_seek(struct lia_server_handler *handler, u64 pos)
+{
+ struct lia_codec_server *codec = (struct lia_codec_server *)handler;
+ return codec->demux->seek(codec->demux, pos);
+}
+
+static void codec_server_step(struct lia_server_handler *handler)
+{
+ struct lia_codec_server *codec = (struct lia_codec_server *)handler;
+ codec->handler.status = codec->demux->get_packet(codec->demux, &codec->packet);
+}
+
+static void codec_server_write_packet(struct lia_server_handler *handler, struct aki_packet *packet)
+{
+ struct lia_codec_server *codec = (struct lia_codec_server *)handler;
+ if (codec->handler.status == CAMU_OK) {
+ aki_packet_write_u8(packet, LIANA_PACKET_DATA);
+ switch (codec->packet.mode) {
+ case CAMU_NORMAL: {
+ aki_packet_write_s32(packet, 0);
+ aki_packet_write_u8(packet, codec->packet.mode);
+ aki_packet_write_buffer(packet, codec->packet.buffer);
+ break;
+ }
+#ifdef CAMU_HAVE_FFMPEG
+ case CAMU_FFMPEG_COMPAT: {
+ AVPacket *pkt = codec->packet.av.pkt;
+ aki_packet_write_s32(packet, pkt->stream_index);
+ aki_packet_write_u8(packet, codec->packet.mode);
+ aki_packet_write_av_packet(packet, pkt);
+ av_packet_unref(pkt);
+ break;
+ }
+#endif
+ }
+ } else if (codec->handler.status == CAMU_ERR_EOF) {
+ aki_packet_write_u8(packet, LIANA_PACKET_EOF);
+ } else {
+ aki_packet_write_u8(packet, LIANA_PACKET_ERROR);
+ }
+}
+
+static void codec_server_free(struct lia_server_handler **handler)
+{
+ struct lia_codec_server *codec = (struct lia_codec_server *)*handler;
+ switch (codec->demux->mode) {
+ case CAMU_NORMAL:
+ break;
+#ifdef CAMU_HAVE_FFMPEG
+ case CAMU_FFMPEG_COMPAT:
+ av_packet_free(&codec->packet.av.pkt);
+ break;
+#endif
+ }
+ codec->demux->free(&codec->demux);
+ al_free(codec);
+ *handler = NULL;
+}
+
+struct lia_server_handler *lia_codec_server_create(void)
+{
+ struct lia_codec_server *codec = al_alloc_object(struct lia_codec_server);
+ codec->handler.init = codec_server_init;
+ codec->handler.write_info = codec_server_write_info;
+ codec->handler.subscribe = codec_server_subscribe;
+ codec->handler.get_duration = codec_server_get_duration;
+ codec->handler.seek = codec_server_seek;
+ codec->handler.step = codec_server_step;
+ codec->handler.write_packet = codec_server_write_packet;
+ codec->handler.free = codec_server_free;
+ return (struct lia_server_handler *)codec;
+}
diff --git a/src/liana/handlers/dvd.h b/src/liana/handlers/dvd.h
new file mode 100644
index 0000000..788c30e
--- /dev/null
+++ b/src/liana/handlers/dvd.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "../handler.h"
+
+struct lia_dvd_server {
+ struct lia_server_handler handler;
+};
+
+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);
diff --git a/src/shrub/handlers/dvd_server.c b/src/liana/handlers/dvd_server.c
index ea3a9d9..a487de4 100644
--- a/src/shrub/handlers/dvd_server.c
+++ b/src/liana/handlers/dvd_server.c
@@ -1,21 +1,21 @@
#include "dvd.h"
-static bool dvd_server_init(struct bmu_server_handler *handler, struct cch_handle *handle,
- struct aki_packet_pool *pool)
+static bool dvd_server_init(struct lia_server_handler *handler, struct cch_handle *handle)
{
- struct bmu_dvd_server *dvd = (struct bmu_dvd_server *)handler;
+ struct lia_dvd_server *dvd = (struct lia_dvd_server *)handler;
return true;
}
-struct bmu_server_handler *bmu_dvd_server_create(void)
+struct lia_server_handler *lia_dvd_server_create(void)
{
- struct bmu_dvd_server *dvd = al_alloc_object(struct bmu_dvd_server);
+ struct lia_dvd_server *dvd = al_alloc_object(struct lia_dvd_server);
dvd->handler.init = dvd_server_init;
// dvd->handler.write_info = dvd_server_write_info;
// dvd->handler.subscribe = dvd_server_subscribe;
// dvd->handler.get_duration = dvd_server_get_duration;
// dvd->handler.seek = dvd_server_seek;
// dvd->handler.step = dvd_server_step;
+// dvd->handler.write_packet = dvd_server_write_packet;
// dvd->handler.free = dvd_server_free;
- return (struct bmu_server_handler *)dvd;
+ return (struct lia_server_handler *)dvd;
}
diff --git a/src/liana/list.c b/src/liana/list.c
new file mode 100644
index 0000000..37a144f
--- /dev/null
+++ b/src/liana/list.c
@@ -0,0 +1,413 @@
+#include <aki/thread.h>
+#include <al/random.h>
+#include <al/lib.h>
+#include <al/log.h>
+
+#include "../libsink/common.h"
+
+#include "list.h"
+#include "list_cmp.h"
+
+void lia_list_init(struct lia_list *list)
+{
+ list->current = -1;
+ list->previous = -1;
+ list->queued = -1;
+ list->idle = true;
+ al_array_init(list->entries);
+ al_array_init(list->sinks);
+}
+
+/*
+static void buffer_ahead(struct lia_list *list)
+{
+ s32 size = (s32)list->entries.size;
+ if (list->queued >= 0 && list->queued + 1 < size) {
+ s32 ahead = list->queued + 1;
+ for (s32 i = ahead; i < AL_MIN(ahead + LIANA_BUFFER_AHEAD, size); i++) {
+ struct lia_list_entry *buffered = al_array_at(list->entries, i);
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ sink->callback(sink->userdata, CAMU_SINK_BUFFER, buffered, i, LIANA_TIMESTAMP_INVALID);
+ }
+ }
+ }
+}
+*/
+
+static void unset_all(struct lia_list *list)
+{
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ sink->set = -1;
+ sink->queued = -1;
+ }
+}
+
+void lia_list_add_sink(struct lia_list *list, void (*callback)(void *, u8, struct lia_list_entry *, s32, struct lia_timing *), void *userdata)
+{
+ struct lia_list_sink *sink = al_alloc_object(struct lia_list_sink);
+ sink->callback = callback;
+ sink->userdata = userdata;
+ al_array_push(list->sinks, sink);
+ if (list->current >= 0) {
+ sink->set = list->current;
+ struct lia_list_entry *current = al_array_at(list->entries, list->current);
+ u64 now = aki_get_timestamp();
+ u8 pause;
+ u64 at = LIANA_TIMESTAMP_INVALID;
+ u64 seek_pos = current->offset;
+ if (current->paused_at == LIANA_TIMESTAMP_INVALID) {
+ now += LIANA_BASE_DELAY;
+ if (now > current->start) {
+ at = now;
+ seek_pos += now - current->start;
+ } else {
+ at = current->start;
+ }
+ pause = LIANA_PAUSE_RESUME;
+ } else {
+ pause = LIANA_PAUSE_NONE;
+ }
+ struct lia_timing time = {
+ .at = at,
+ .seek_pos = seek_pos,
+ .pause = pause
+ };
+ sink->callback(sink->userdata, CAMU_SINK_SET, current, list->current, &time);
+ } else {
+ sink->set = -1;
+ }
+ sink->queued = -1;
+}
+
+void lia_list_unset(struct lia_list *list)
+{
+ unset_all(list);
+ list->current = list->entries.size - 1;
+ list->previous = -1;
+ list->queued = -1;
+ list->idle = true;
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ sink->callback(sink->userdata, CAMU_SINK_CLEAR, NULL, -1, NULL);
+ }
+}
+
+void lia_list_remove_sink(struct lia_list *list, void *userdata)
+{
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ if (sink->userdata == userdata) {
+ al_array_remove_at(list->sinks, i);
+ al_free(sink);
+ break;
+ }
+ }
+}
+
+void lia_list_add(struct lia_list *list, void *opaque, u64 duration, str *name)
+{
+ struct lia_list_entry *entry = al_alloc_object(struct lia_list_entry);
+ entry->opaque = opaque;
+ entry->paused_at = LIANA_TIMESTAMP_INVALID;
+ entry->held = false;
+ entry->offset = 0;
+ entry->duration = duration;
+ al_str_clone(&entry->name, name);
+ al_array_push(list->entries, entry);
+ if (list->idle) {
+ list->current++;
+ list->idle = false;
+ entry->start = aki_get_timestamp() + LIANA_BASE_DELAY;
+ struct lia_timing time = {
+ .at = entry->start,
+ .seek_pos = entry->offset,
+ .pause = LIANA_PAUSE_RESUME
+ };
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ al_assert(sink->set == -1);
+ sink->set = list->current;
+ sink->callback(sink->userdata, CAMU_SINK_SET, entry, list->current, &time);
+ }
+ if (list->callback) list->callback(list->userdata, LIANA_META_PLAYING, entry);
+ } else {
+ /*
+ if (list->queued == -1) {
+ struct lia_list_entry *current = al_array_at(list->entries, list->current);
+ list->queued = list->current + 1;
+ entry->start = current->start + (current->duration - current->offset);
+ struct lia_timing time = {
+ .at = entry->start,
+ .seek_pos = entry->offset,
+ .pause = LIANA_PAUSE_RESUME
+ };
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ sink->queued = list->queued;
+ sink->callback(sink->userdata, CAMU_SINK_BUFFER_AND_QUEUE, entry, list->queued, &time);
+ }
+ } else {
+ */
+ entry->start = LIANA_TIMESTAMP_INVALID;
+ //}
+ if (list->callback) list->callback(list->userdata, LIANA_META_QUEUED, entry);
+ }
+}
+
+static struct lia_list_entry *get_entry_from_sequence(struct lia_list *list, s32 sequence)
+{
+ s32 size = (s32)list->entries.size;
+ if (sequence < 0 || sequence >= size) return NULL;
+ return al_array_at(list->entries, sequence);
+}
+
+static bool assume_done(struct lia_list_entry *entry, u64 at)
+{
+ if (entry->duration == 0) return true;
+ if (entry->paused_at == LIANA_TIMESTAMP_INVALID && entry->start != LIANA_TIMESTAMP_INVALID) {
+ if (entry->offset > entry->duration) {
+ return true;
+ }
+ if (at < entry->start) return false;
+ if (at - entry->start >= entry->duration - entry->offset) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void lia_list_skipto(struct lia_list *list, s32 sequence, s32 index)
+{
+ if (sequence == LIANA_SEQUENCE_ANY) sequence = list->current;
+ if (index == list->current) return;
+
+ struct lia_list_entry *current = get_entry_from_sequence(list, sequence);
+ struct lia_list_entry *target = get_entry_from_sequence(list, index);
+ if (!current || !target) return;
+
+ u64 now = aki_get_timestamp();
+ u64 at = now + LIANA_BASE_DELAY;
+ u8 pause;
+
+ // TODO:
+ // - Think about what is means for an entry to be done. never put into a pause state?
+ // - clock_end()??
+ // - Sink needs to handle case where entry gets queued but the list already expects it to be playing
+ // - It's possible to know if sink->current is done during a set command, synchronously.
+ // So, check that when queueing an entry.
+ // - Can clock be ended during a queue command in any other case?
+ // - In the simplest case of our only operation being skip, how could client's become desynced?
+ // - Then with toggle pause
+ // - Is it safe to assert paused state on the client.
+ // - Do queued
+ // - Do seek
+
+ //if (current->start != LIANA_TIMESTAMP_INVALID && current->start > ts - LIANA_BASE_PING) {
+
+ // This should only happen if `start` has never been set.
+ if (target->start == LIANA_TIMESTAMP_INVALID && target->paused_at == LIANA_TIMESTAMP_INVALID) {
+ al_log_info("dd", "Hello");
+ target->start = at;
+ }
+
+ if (target->held) {
+ target->paused_at = LIANA_TIMESTAMP_INVALID;
+ target->held = false;
+ }
+
+ al_assert(!current->held);
+
+ bool done = assume_done(current, now);
+ if (done || current->paused_at == LIANA_TIMESTAMP_INVALID) {
+ if (!done) {
+ current->paused_at = at;
+ current->offset += current->paused_at - current->start;
+ current->start = LIANA_TIMESTAMP_INVALID;
+ current->held = true;
+ }
+ if (assume_done(target, now) || target->paused_at != LIANA_TIMESTAMP_INVALID) {
+ pause = !done ? LIANA_PAUSE_PAUSE : LIANA_PAUSE_NONE;
+ } else { //if (target->paused_at == LIANA_TIMESTAMP_INVALID) {
+ target->start = at;
+ pause = !done ? LIANA_PAUSE_BOTH : LIANA_PAUSE_RESUME;
+ }
+ al_log_info("dd", "%d: not paused, done: %d, pause: %d", sequence, done, pause);
+ } else {
+ if (assume_done(target, now) || target->paused_at != LIANA_TIMESTAMP_INVALID) {
+ pause = LIANA_PAUSE_NONE;
+ } else { //if (target->paused_at == LIANA_TIMESTAMP_INVALID) {
+ target->start = at;
+ pause = LIANA_PAUSE_RESUME;
+ }
+ al_log_info("dd", "%d: paused, pause: %d", sequence, pause);
+ }
+
+ list->current = index;
+ list->previous = sequence;
+ list->idle = false;
+
+ struct lia_timing time = {
+ .at = at,
+ .seek_pos = target->offset,
+ .pause = pause
+ };
+
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ sink->set = index;
+ sink->callback(sink->userdata, CAMU_SINK_SET, target, index, &time);
+ }
+
+ list->callback(list->userdata, LIANA_META_PLAYING, target);
+}
+
+void lia_list_skip(struct lia_list *list, s32 sequence, s32 n)
+{
+ if (sequence == LIANA_SEQUENCE_ANY) sequence = list->current;
+ lia_list_skipto(list, sequence, sequence + n);
+}
+
+void lia_list_toggle_pause(struct lia_list *list, s32 sequence, f64 pts)
+{
+ if (sequence == LIANA_SEQUENCE_ANY) sequence = list->current;
+ struct lia_list_entry *current = get_entry_from_sequence(list, sequence);
+ if (!current) return;
+
+ u64 now = aki_get_timestamp();
+ u8 pause = current->paused_at == LIANA_TIMESTAMP_INVALID ? LIANA_PAUSE_PAUSE : LIANA_PAUSE_RESUME;
+ u64 at;
+ switch (pause) {
+ case LIANA_PAUSE_PAUSE:
+ al_assert(pts != -1.0);
+ current->paused_at = now + LIANA_PAUSE_DELAY;
+ current->offset += current->paused_at - current->start;
+ current->start = LIANA_TIMESTAMP_INVALID;
+ at = current->paused_at;
+ break;
+ case LIANA_PAUSE_RESUME:
+ current->paused_at = LIANA_TIMESTAMP_INVALID;
+ current->start = now + LIANA_PAUSE_DELAY;
+ at = current->start;
+ break;
+ }
+
+ struct lia_timing time = {
+ .at = at,
+ .seek_pos = LIANA_TIMESTAMP_INVALID,
+ .pause = pause
+ };
+
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ sink->callback(sink->userdata, CAMU_SINK_PAUSE, current, sequence, &time);
+ }
+}
+
+void lia_list_seek(struct lia_list *list, s32 sequence, f64 percent)
+{
+ if (sequence == LIANA_SEQUENCE_ANY) sequence = list->current;
+ list->idle = false;
+ struct lia_list_entry *current = get_entry_from_sequence(list, sequence);
+ u64 pos = (u64)(current->duration * percent);
+ u64 now = aki_get_timestamp();
+ current->offset = pos;
+ current->start = now + LIANA_BASE_DELAY;
+ struct lia_timing time = {
+ .at = current->start,
+ .seek_pos = current->offset,
+ .pause = LIANA_PAUSE_NONE
+ };
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ sink->callback(sink->userdata, CAMU_SINK_SEEK, current, sequence, &time);
+ }
+}
+
+void lia_list_end(struct lia_list *list, s32 sequence)
+{
+ al_assert(sequence != LIANA_SEQUENCE_ANY);
+ if (sequence != list->current) return;
+ s32 size = (s32)list->entries.size;
+ s32 next = sequence + 1;
+ list->previous = sequence;
+ struct lia_list_entry *current = al_array_at(list->entries, list->current);
+ //current->offset = current->duration;
+ if (list->queued >= 0) {
+ list->current = list->queued;
+ list->queued = -1;
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ sink->queued = -1;
+ }
+ if (list->callback) {
+ list->callback(list->userdata, LIANA_META_PLAYING, al_array_at(list->entries, list->current));
+ }
+ } else if (next < size) {
+ lia_list_skipto(list, sequence, next);
+ } else {
+ list->idle = true;
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ sink->set = -1;
+ }
+ }
+}
+
+void lia_list_reverse(struct lia_list *list)
+{
+ u32 size = list->entries.size;
+ for (u32 i = 0; i < size; i++) {
+ u32 tail = size - (i + 1);
+ if (tail <= i) break;
+ AL_SWAP(al_array_at(list->entries, i), al_array_at(list->entries, tail), struct lia_list_entry *);
+ }
+ unset_all(list);
+}
+
+void lia_list_sort(struct lia_list *list)
+{
+ al_array_sort(list->entries, struct lia_list_entry *, camu_db_compare);
+ unset_all(list);
+}
+
+void lia_list_shuffle(struct lia_list *list)
+{
+ u32 size = list->entries.size;
+ if (size == 0) return;
+ for (u32 i = 0; i < size - 1; i++) {
+ u32 j = i + al_rand() / (AL_RAND_MAX / (size - i) + 1);
+ AL_SWAP(al_array_at(list->entries, i), al_array_at(list->entries, j), struct lia_list_entry *);
+ }
+ unset_all(list);
+}
+
+void lia_list_clear(struct lia_list *list)
+{
+ struct lia_list_entry *entry;
+ al_array_foreach(list->entries, i, entry) {
+ al_str_free(&entry->name);
+ al_free(entry);
+ }
+ list->entries.size = 0;
+ unset_all(list);
+ list->current = -1;
+ list->previous = -1;
+ list->idle = true;
+}
+
+void lia_list_free(struct lia_list *list)
+{
+ struct lia_list_entry *entry;
+ al_array_foreach(list->entries, i, entry) {
+ al_str_free(&entry->name);
+ al_free(entry);
+ }
+ al_array_free(list->entries);
+ struct lia_list_sink *sink;
+ al_array_foreach(list->sinks, i, sink) {
+ al_free(sink);
+ }
+ al_array_free(list->sinks);
+}
diff --git a/src/liana/list.h b/src/liana/list.h
new file mode 100644
index 0000000..8adea17
--- /dev/null
+++ b/src/liana/list.h
@@ -0,0 +1,107 @@
+#pragma once
+
+#include <al/types.h>
+#include <al/str.h>
+#include <al/array.h>
+
+#define LIANA_SEQUENCE_ANY -1
+#define LIANA_TIMESTAMP_INVALID UINT64_MAX
+
+#define LIANA_BASE_PING 200000Lu // 100ms
+#define LIANA_BASE_DELAY 450000Lu // 250ms
+#define LIANA_PAUSE_DELAY LIANA_BASE_PING
+#define LIANA_DELAY_IGNORE 0Lu
+
+#define LIANA_BUFFER_AHEAD 2
+
+enum {
+ LIANA_ENTRY_DURATION = 0,
+ LIANA_ENTRY_ID,
+ LIANA_ENTRY_SKIP
+};
+
+enum {
+ LIANA_META_PLAYING = 0,
+ LIANA_META_QUEUED
+};
+
+// Pause:
+// - Set `paused_at` to now() + PAUSE_DELAY.
+// - Increment `offset` by how long the entry will have been
+// playing when paused at the requested timestamp (`paused_at` - `start`).
+// Resume:
+// - Unset `paused_at`
+// - Set `start` to now() + PAUSE_DELAY.
+// Next/Prev:
+// Current Playing, Target Playing:
+// - Set `current->held_at` to now() + PAUSE_DELAY.
+// - Increment `current->offset` by the same logic as pause.
+// -
+// Current Playing, Target Paused:
+// Current Paused, Target Playing:
+// Current Paused, Target Paused:
+// Seek:
+
+// NOTE: keep global per list max time until all sinks _should_ by buffered
+// possibly use that instead of LIANA_PAUSE_DELAY.
+
+enum {
+ LIANA_PAUSE_NONE = 0,
+ LIANA_PAUSE_RESUME,
+ LIANA_PAUSE_PAUSE,
+ LIANA_PAUSE_BOTH
+};
+
+struct lia_timing {
+ u64 at;
+ u64 seek_pos;
+ u8 pause;
+};
+
+struct lia_list_entry {
+ void *opaque;
+ u64 start;
+ u64 paused_at;
+ bool held;
+ u64 offset;
+ u64 duration;
+ str name;
+};
+
+struct lia_list_sink {
+ s32 set;
+ s32 queued;
+ void (*callback)(void *, u8, struct lia_list_entry *, s32, struct lia_timing *);
+ void *userdata;
+};
+
+struct lia_list {
+ s32 current;
+ s32 previous;
+ s32 queued;
+ bool idle;
+ array(struct lia_list_entry *) entries;
+ array(struct lia_list_sink *) sinks;
+ void (*callback)(void *, u8, struct lia_list_entry *);
+ void *userdata;
+};
+
+void lia_list_init(struct lia_list *list);
+
+void lia_list_add_sink(struct lia_list *list, void (*callback)(void *, u8, struct lia_list_entry *, s32, struct lia_timing *), void *userdata);
+void lia_list_remove_sink(struct lia_list *list, void *userdata);
+
+void lia_list_add(struct lia_list *list, void *opaque, u64 duration, str *name);
+void lia_list_unset(struct lia_list *list);
+void lia_list_skipto(struct lia_list *list, s32 sequence, s32 i);
+void lia_list_skip(struct lia_list *list, s32 sequence, s32 n);
+void lia_list_toggle_pause(struct lia_list *list, s32 sequence, f64 pts);
+void lia_list_seek(struct lia_list *list, s32 sequence, f64 percent);
+void lia_list_end(struct lia_list *list, s32 sequence);
+
+void lia_list_reverse(struct lia_list *list);
+void lia_list_sort(struct lia_list *list);
+void lia_list_shuffle(struct lia_list *list);
+void lia_list_clear(struct lia_list *list);
+
+void lia_list_free(struct lia_list *list);
diff --git a/src/liana/list_cmp.h b/src/liana/list_cmp.h
new file mode 100644
index 0000000..1f77b46
--- /dev/null
+++ b/src/liana/list_cmp.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <al/str.h>
+
+#include "list.h"
+
+static void camu_db_num_from_path(str *path, s64 *id, s64 *index)
+{
+ s32 index0 = al_str_rfind(path, '/');
+ if (index0 < 0) return;
+
+ str s = *al_str_substr(path, index0 + 1, path->len);
+ index0 = al_str_find(&s, '_');
+ if (index0 < 0) return;
+ s = *al_str_substr(&s, index0 + 1, s.len);
+ index0 = al_str_find(&s, '_');
+ if (index0 < 0) return;
+ s = *al_str_substr(&s, index0 + 1, s.len);
+ index0 = al_str_find(&s, '_');
+ if (index0 < 0) return;
+ s = *al_str_substr(&s, 0, index0);
+
+ s64 num = al_str_to_long(&s, 10);
+ if (num == INT64_MIN || num == INT64_MAX) return;
+ *id = num;
+
+ index0 = al_str_rfind(path, '.');
+ s32 index1 = al_str_rfind(path, 'a');
+ if (index0 < 0 || index1 < 0) return;
+ s = *al_str_substr(path, index1 + 1, index0);
+
+ num = al_str_to_long(&s, 10);
+ if (num == INT64_MIN || num == INT64_MAX) return;
+ *index = num;
+}
+
+static s32 camu_db_compare(const void *a, const void *b)
+{
+ struct lia_list_entry *aa = *((struct lia_list_entry **)a);
+ struct lia_list_entry *bb = *((struct lia_list_entry **)b);
+ s64 anum = -1, aindex = -1;
+ s64 bnum = -1, bindex = -1;
+ camu_db_num_from_path(&aa->name, &anum, &aindex);
+ camu_db_num_from_path(&bb->name, &bnum, &bindex);
+ if (anum == bnum) {
+ if (aindex > bindex) return 1;
+ else if (aindex < bindex) return -1;
+ } else {
+ if (anum > bnum) return -1;
+ else if (anum < bnum) return 1;
+ }
+ return 0;
+}
diff --git a/src/liana/meson.build b/src/liana/meson.build
new file mode 100644
index 0000000..0859e99
--- /dev/null
+++ b/src/liana/meson.build
@@ -0,0 +1,36 @@
+liana_server_src = [
+ 'list.c',
+ 'server.c',
+ 'handlers/codec_server.c',
+ 'handlers.c'
+]
+liana_client_src = [
+ 'client.c',
+ 'vcr.c',
+ 'handlers/codec_client.c',
+ 'handlers.c'
+]
+liana_deps = [codecs]
+liana_args = []
+
+libcdio_paranoia = dependency('libcdio_paranoia', required: false, allow_fallback: true)
+libcdio_cdda = dependency('libcdio_cdda', required: false, allow_fallback: true)
+if libcdio_paranoia.found() and libcdio_cdda.found()
+ liana_server_src += ['handlers/cdio_server.c']
+ liana_client_src += ['handlers/cdio_client.c']
+ liana_deps += [libcdio_paranoia, libcdio_cdda]
+ liana_args += ['-DLIANA_HAVE_CDIO']
+endif
+
+#libdvdcss = dependency('libdvdcss', required: false, allow_fallback: true)
+#libdvdread = dependency('libdvdread', required: false, allow_fallback: true)
+#libdvdnav = dependency('libdvdnav', required: false, allow_fallback: true)
+#if libdvdcss.found() and libdvdread.found() and libdvdnav.found()
+# liana_server_src += ['handlers/dvd_server.c']
+# liana_server_deps += [libdvdcss]
+#endif
+
+liana_server = declare_dependency(sources: liana_server_src, dependencies: liana_deps,
+ compile_args: [liana_args, '-DLIANA_SERVER'])
+liana_client = declare_dependency(sources: liana_client_src, dependencies: liana_deps,
+ compile_args: [liana_args, '-DLIANA_CLIENT'])
diff --git a/src/liana/server.c b/src/liana/server.c
new file mode 100644
index 0000000..1fc96fb
--- /dev/null
+++ b/src/liana/server.c
@@ -0,0 +1,345 @@
+#include <al/random.h>
+
+#include "server.h"
+#include "handler.h"
+#include "handlers.h"
+#include "list.h"
+
+bool lia_server_init(struct lia_server *server, struct aki_event_loop *loop)
+{
+ server->loop = loop;
+ al_array_init(server->nodes);
+ al_array_init(server->zombies);
+ return true;
+}
+
+static void remove_zombie(struct lia_server *server, struct aki_packet_stream *stream)
+{
+ struct aki_packet_stream *zombie;
+ al_array_foreach(server->zombies, i, zombie) {
+ if (zombie == stream) {
+ al_array_remove_at(server->zombies, i);
+ break;
+ }
+ }
+}
+
+static void data_packet_sent_callback(void *userdata, struct aki_packet *packet)
+{
+ struct lia_node_connection *conn = (struct lia_node_connection *)userdata;
+ aki_packet_pool_return(&conn->pool, packet);
+}
+
+static u8 packet_pool_callback(void *userdata, struct aki_packet *packet)
+{
+ struct lia_node_connection *conn = (struct lia_node_connection *)userdata;
+ if (!aki_packet_stream_send_packet(conn->stream, packet)) {
+ return AKI_PACKET_POOL_RETURN;
+ }
+ return AKI_PACKET_POOL_KEEP;
+}
+
+static aki_thread_result AKI_THREADCALL handler_thread(void *userdata)
+{
+ struct lia_node_connection *conn = (struct lia_node_connection *)userdata;
+ do {
+ struct aki_packet *packet = aki_packet_pool_get(&conn->pool);
+ if (!packet) break;
+ conn->handler->step(conn->handler);
+ conn->handler->write_packet(conn->handler, packet);
+ aki_packet_pool_submit(&conn->pool, packet);
+ if (conn->handler->status != CAMU_OK) break;
+ } while (1);
+ aki_packet_pool_flush(&conn->pool);
+ return 0;
+}
+
+static void discard_packet_callback(void *userdata, struct aki_packet_stream *stream, struct aki_packet *packet)
+{
+ (void)userdata;
+ (void)stream;
+ aki_packet_free(packet);
+ // We should never be here. Although, we also shouldn't assert because
+ // any erroneous connection can bring us here.
+ al_assert(false);
+}
+
+static void close_connection_internal(struct lia_node_connection *conn)
+{
+ al_assert(conn->handler);
+ conn->handler->free(&conn->handler);
+ cch_entry_return_handle(conn->node->entry, &conn->handle);
+ aki_packet_stream_free(conn->stream);
+ al_free(conn->stream);
+ conn->stream = NULL;
+}
+
+static void data_connection_closed_callback(void *userdata, struct aki_packet_stream *stream)
+{
+ struct lia_node_connection *conn = (struct lia_node_connection *)userdata;
+ (void)stream;
+ aki_packet_pool_disable(&conn->pool);
+ cch_handle_disable(&conn->handle);
+ // This is joining handler_thread(), we will never be here if init_thread() blocks or fails.
+ aki_thread_join(&conn->thread);
+ close_connection_internal(conn);
+ aki_packet_pool_free(&conn->pool);
+}
+
+static void subscribe_packet_callback(void *userdata, struct aki_packet_stream *stream, struct aki_packet *packet)
+{
+ struct lia_node_connection *conn = (struct lia_node_connection *)userdata;
+ s32 mask = aki_packet_read_s32(packet);
+ conn->handler->subscribe(conn->handler, mask);
+ stream->packet_callback = discard_packet_callback;
+ stream->packet_sent_callback = data_packet_sent_callback;
+ stream->connection_closed_callback = data_connection_closed_callback;
+ aki_thread_create(&conn->thread, handler_thread, conn);
+ aki_packet_free(packet);
+}
+
+static void subscribe_packet_sent_callback(void *userdata, struct aki_packet *packet)
+{
+ (void)userdata;
+ aki_packet_free(packet);
+}
+
+static void subscribe_connection_closed_callback(void *userdata, struct aki_packet_stream *stream)
+{
+ struct lia_node_connection *conn = (struct lia_node_connection *)userdata;
+ (void)stream;
+ close_connection_internal(conn);
+ aki_packet_pool_free(&conn->pool);
+}
+
+static void handle_connection(struct lia_node_connection *conn, struct aki_packet *packet)
+{
+ struct aki_packet_stream *stream = conn->stream;
+
+ s32 mask = aki_packet_read_s32(packet);
+ u64 seek_pos = aki_packet_read_u64(packet);
+
+ // Besides being wasteful, seeking to 0 on a new stream can skip data.
+ if (seek_pos > 0) conn->handler->seek(conn->handler, seek_pos);
+
+ if (mask == 0) {
+ struct aki_packet *rpacket = aki_packet_create();
+ aki_packet_write_u16(rpacket, conn->id);
+ aki_packet_write_str(rpacket, cch_entry_get_liana(conn->node->entry));
+ conn->handler->write_info(conn->handler, rpacket);
+ stream->packet_callback = subscribe_packet_callback;
+ stream->packet_sent_callback = subscribe_packet_sent_callback;
+ stream->connection_closed_callback = subscribe_connection_closed_callback;
+ aki_packet_stream_send_packet(stream, rpacket);
+ } else {
+ conn->handler->subscribe(conn->handler, mask);
+ stream->packet_callback = discard_packet_callback;
+ stream->packet_sent_callback = data_packet_sent_callback;
+ stream->connection_closed_callback = data_connection_closed_callback;
+ aki_thread_create(&conn->thread, handler_thread, conn);
+ }
+}
+
+static void connection_closed_callback(void *userdata, struct aki_packet_stream *stream)
+{
+ struct lia_server *server = (struct lia_server *)userdata;
+ // This connection might no longer be a zombie, but that's fine.
+ remove_zombie(server, stream);
+ aki_packet_stream_free(stream);
+ al_free(stream);
+}
+
+static void signal_callback(void *userdata)
+{
+ struct lia_node_connection *conn = (struct lia_node_connection *)userdata;
+ struct lia_node *node = conn->node;
+ struct lia_server *server = node->server;
+ aki_signal_stop(&conn->signal);
+ aki_thread_join(&conn->thread);
+ struct aki_packet *packet = conn->packet;
+ if (!packet) {
+ // Connection was closed before init was done.
+ close_connection_internal(conn);
+ return;
+ }
+ if (!conn->errored) {
+ conn->id = al_inc_u16();
+ aki_packet_pool_init(&conn->pool, 48, server->loop, packet_pool_callback, conn);
+ al_array_push(node->connections, conn);
+ handle_connection(conn, packet);
+ } else {
+ conn->handler->free(&conn->handler);
+ cch_entry_return_handle(node->entry, &conn->handle);
+ struct aki_packet_stream *stream = conn->stream;
+ al_free(conn);
+ conn = NULL;
+ // This connection is now nothing but a packet stream.
+ stream->userdata = server;
+ stream->connection_closed_callback = connection_closed_callback;
+ aki_packet_stream_disconnect(stream);
+ }
+ aki_packet_free(packet);
+}
+
+static aki_thread_result AKI_THREADCALL init_thread(void *userdata)
+{
+ struct lia_node_connection *conn = (struct lia_node_connection *)userdata;
+ if (!conn->handler->init(conn->handler, &conn->handle)) {
+ conn->errored = true;
+ }
+ aki_signal_send(&conn->signal);
+ return 0;
+}
+
+static struct lia_node_connection *get_connection_from_id(struct lia_node *node, u16 id)
+{
+ struct lia_node_connection *conn = NULL;
+ al_array_foreach(node->connections, i, conn) {
+ if (conn->id == id) return conn;
+ }
+ return NULL;
+}
+
+static struct lia_node *get_node_from_id(struct lia_server *server, u16 id)
+{
+ struct lia_node *node;
+ al_array_foreach(server->nodes, i, node) {
+ if (node->id == id) return node;
+ }
+ return NULL;
+}
+
+static void pre_init_connection_closed_callback(void *userdata, struct aki_packet_stream *stream)
+{
+ struct lia_node_connection *conn = (struct lia_node_connection *)userdata;
+ (void)stream;
+ aki_packet_free(conn->packet);
+ // Checked in signal_callback and will signal to cleanup the connection.
+ conn->packet = NULL;
+}
+
+static void packet_callback(void *userdata, struct aki_packet_stream *stream, struct aki_packet *packet)
+{
+ struct lia_server *server = (struct lia_server *)userdata;
+
+ // We got a packet, this connection is no longer a zombie.
+ remove_zombie(server, stream);
+
+ u16 node_id = aki_packet_read_u16(packet);
+ u16 connection_id = aki_packet_read_u16(packet);
+
+ struct lia_node *node = get_node_from_id(server, node_id);
+ struct lia_node_connection *conn = NULL;
+ if (connection_id == 0) {
+ conn = al_alloc_object(struct lia_node_connection);
+ stream->userdata = conn;
+ conn->node = node;
+ conn->stream = stream;
+ conn->packet = packet;
+ aki_signal_init(&conn->signal, signal_callback, conn);
+ aki_signal_start(&conn->signal, server->loop);
+ cch_entry_get_handle(node->entry, &conn->handle);
+ conn->handler = lia_handler_by_name(cch_entry_get_liana(node->entry))->create_server_handler();
+ conn->errored = false;
+ stream->packet_callback = discard_packet_callback;
+ stream->connection_closed_callback = pre_init_connection_closed_callback;
+ aki_thread_create(&conn->thread, init_thread, conn);
+ } else {
+ // This is completely unused and connections never get removed from node->connection.
+ if ((conn = get_connection_from_id(node, connection_id))) {
+ handle_connection(conn, packet);
+ } else {
+ aki_packet_stream_disconnect(stream);
+ }
+ aki_packet_free(packet);
+ }
+}
+
+static void packet_sent_callback(void *userdata, struct aki_packet *packet)
+{
+ (void)userdata;
+ aki_packet_free(packet);
+}
+
+static void connection_callback(void *userdata, struct aki_packet_stream *stream)
+{
+ struct lia_server *server = (struct lia_server *)userdata;
+ stream->packet_callback = packet_callback;
+ stream->packet_sent_callback = packet_sent_callback;
+ al_array_push(server->zombies, stream);
+}
+
+void lia_server_add_socket(struct lia_server *server, struct aki_socket *sock)
+{
+ struct aki_packet_stream *stream = al_alloc_object(struct aki_packet_stream);
+ stream->connection_callback = connection_callback;
+ stream->connection_closed_callback = connection_closed_callback;
+ stream->userdata = server;
+ aki_packet_stream_from_socket(stream, server->loop, sock);
+}
+
+struct lia_node *lia_server_create_node(struct lia_server *server, struct cch_entry *entry)
+{
+ struct lia_node *node = al_alloc_object(struct lia_node);
+ node->id = al_inc_u16();
+ node->entry = entry;
+ al_array_init(node->connections);
+ node->server = server;
+ al_array_push(server->nodes, node);
+ return node;
+}
+
+u64 lia_node_get_duration(struct lia_node *node)
+{
+ (void)node;
+#if 0
+ return 0;
+#else
+ struct cch_handle handle;
+ cch_entry_get_handle(node->entry, &handle);
+ struct lia_server_handler *handler = lia_handler_by_name(cch_entry_get_liana(node->entry))->create_server_handler();
+ if (!handler->init(handler, &handle)) {
+ return LIANA_TIMESTAMP_INVALID;
+ }
+ u64 duration = handler->get_duration(handler);
+ handler->free(&handler);
+ cch_entry_return_handle(node->entry, &handle);
+ return duration;
+#endif
+}
+
+void lia_server_close(struct lia_server *server)
+{
+ struct lia_node *node;
+ al_array_foreach(server->nodes, i, node) {
+ struct lia_node_connection *connection;
+ al_array_foreach(node->connections, j, connection) {
+ if (connection->stream) {
+ aki_packet_stream_disconnect(connection->stream);
+ }
+ }
+ }
+ struct aki_packet_stream *zombie;
+ al_array_foreach_rev(server->zombies, i, zombie) {
+ aki_packet_stream_disconnect(zombie);
+ }
+}
+
+void lia_server_free(struct lia_server *server)
+{
+ struct lia_node *node;
+ al_array_foreach(server->nodes, i, node) {
+ struct lia_node_connection *connection;
+ al_array_foreach(node->connections, j, connection) {
+ al_free(connection);
+ }
+ al_array_free(node->connections);
+ al_free(node);
+ }
+ al_array_free(server->nodes);
+ struct aki_packet_stream *zombie;
+ al_array_foreach(server->zombies, i, zombie) {
+ al_free(zombie);
+ }
+ al_array_free(server->zombies);
+}
diff --git a/src/liana/server.h b/src/liana/server.h
new file mode 100644
index 0000000..3c4c294
--- /dev/null
+++ b/src/liana/server.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <aki/event_loop.h>
+#include <aki/packet_stream.h>
+#include <aki/packet_pool.h>
+#include <aki/socket.h>
+#include <aki/signal.h>
+
+#include "../cache/entry.h"
+
+struct lia_node_connection {
+ u16 id;
+ struct aki_packet *packet;
+ struct aki_packet_stream *stream;
+ struct lia_server_handler *handler;
+ bool errored;
+ struct cch_handle handle;
+ struct aki_thread thread;
+ struct aki_signal signal;
+ struct aki_packet_pool pool;
+ struct lia_node *node;
+};
+
+struct lia_node {
+ u16 id;
+ struct cch_entry *entry;
+ array(struct lia_node_connection *) connections;
+ struct lia_server *server;
+};
+
+struct lia_server {
+ struct aki_event_loop *loop;
+ array(struct lia_node *) nodes;
+ array(struct aki_packet_stream *) zombies;
+};
+
+bool lia_server_init(struct lia_server *server, struct aki_event_loop *loop);
+void lia_server_add_socket(struct lia_server *server, struct aki_socket *sock);
+struct lia_node *lia_server_create_node(struct lia_server *server, struct cch_entry *entry);
+u64 lia_node_get_duration(struct lia_node *node);
+void lia_server_close(struct lia_server *server);
+void lia_server_free(struct lia_server *server);
diff --git a/src/liana/vcr.c b/src/liana/vcr.c
new file mode 100644
index 0000000..fad8b78
--- /dev/null
+++ b/src/liana/vcr.c
@@ -0,0 +1,232 @@
+#include <al/log.h>
+
+#include "vcr.h"
+#include "handler.h"
+
+#define VCR_BUFFER_INIT 64
+#define VCR_BUFFER_BUFFERED (VCR_BUFFER_INIT - 8)
+#define VCR_BUFFER_LOW (VCR_BUFFER_INIT - 16)
+
+static void signal_callback(void *userdata)
+{
+ struct lia_vcr *vcr = (struct lia_vcr *)userdata;
+ aki_packet_stream_cork(vcr->data, false);
+}
+
+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;
+ vcr->mark.buffered = VCR_BUFFER_BUFFERED;
+ vcr->data = data;
+ aki_signal_init(&vcr->signal, signal_callback, vcr);
+}
+
+void lia_vcr_start(struct lia_vcr *vcr, struct aki_event_loop *loop)
+{
+ aki_signal_start(&vcr->signal, loop);
+}
+
+static aki_thread_result AKI_THREADCALL vcr_track_thread(void *userdata)
+{
+ struct lia_vcr_track *track = (struct lia_vcr_track *)userdata;
+ struct lia_vcr *vcr = track->vcr;
+ u32 packets;
+ struct aki_packet *packet = NULL;
+ while (aki_packet_cache_wait(&track->cache, &packets)) {
+ s32 state = 0;
+ for (u32 i = 0; i < packets; i++) {
+ packet = aki_packet_cache_pop(&track->cache);
+ state = 0;
+ if (packet) {
+ state = al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED);
+ if (state == LIANA_STREAM_CLOSED) {
+ // We were signaled to close, exit thread.
+ goto out;
+ }
+ 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) {
+ aki_signal_send(&vcr->signal);
+ }
+ }
+ if (!track->client->handle_packet(track->client, packet)) {
+ // Handler error, exit.
+ goto out;
+ }
+ if (packet) {
+ aki_packet_free(packet);
+ packet = NULL;
+ if (state == LIANA_STREAM_STOPPED) {
+ // Unlock here to accumulate packets while waiting.
+ aki_packet_cache_unlock(&track->cache);
+ aki_mutex_lock(&track->mutex);
+ aki_cond_wait(&track->cond, &track->mutex);
+ aki_mutex_unlock(&track->mutex);
+ break;
+ }
+ } else {
+ // NULL packet, exit thread.
+ goto out;
+ }
+ }
+ if (state != LIANA_STREAM_STOPPED) {
+ // If state = STOPPED, we already unlocked.
+ aki_packet_cache_unlock(&track->cache);
+ }
+ }
+ // packet_cache_wait() returned false.
+ return 0;
+out:
+ // packet_cache_wait() returned true and we are jumping out of the loop.
+ if (packet) aki_packet_free(packet);
+ aki_packet_cache_unlock(&track->cache);
+ return 0;
+}
+
+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 - 1);
+ al_array_push(vcr->tracks, track);
+ al_atomic_store(s32)(&track->state, LIANA_STREAM_RUNNING, AL_ATOMIC_RELAXED);
+}
+
+static struct lia_vcr_track *get_track_from_index(struct lia_vcr *vcr, s32 index)
+{
+ struct lia_vcr_track *track;
+ al_array_foreach(vcr->tracks, i, track) {
+ if (track->index == index) return track;
+ }
+ return NULL;
+}
+
+static void cork_if_buffered(struct lia_vcr *vcr)
+{
+ u8 buffered = 1;
+ struct lia_vcr_track *track;
+ al_array_foreach(vcr->tracks, i, track) {
+ buffered &= al_atomic_load(u8)(&track->buffered, AL_ATOMIC_RELAXED);
+ }
+ if (buffered) aki_packet_stream_cork(vcr->data, true);
+}
+
+bool lia_vcr_push_packet(struct lia_vcr *vcr, struct aki_packet *packet)
+{
+ struct lia_vcr_track *track;
+ s32 count;
+ u8 op = aki_packet_read_u8(packet);
+ switch (op) {
+ case LIANA_PACKET_DATA:
+ track = get_track_from_index(vcr, aki_packet_read_s32(packet));
+ al_assert(track);
+ if (!track->running) {
+ aki_thread_create(&track->thread, vcr_track_thread, track);
+ track->running = true;
+ }
+ 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;
+ vcr->mark.low = vcr->mark.buffered - 16;
+ cork_if_buffered(vcr);
+ }
+ break;
+ case LIANA_PACKET_EOF:
+ al_array_foreach(vcr->tracks, i, track) {
+ aki_packet_cache_send_packet(&track->cache, NULL);
+ }
+ aki_packet_free(packet);
+ break;
+ case LIANA_PACKET_ERROR:
+ al_log_warn("liana", "Unhandled error packet.");
+ aki_packet_free(packet);
+ break;
+ default:
+ al_assert_and_return(false);
+ }
+ return true;
+}
+
+void lia_vcr_cork(struct lia_vcr_track *track)
+{
+ al_atomic_store(s32)(&track->state, LIANA_STREAM_STOPPED, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&track->buffered, 1, AL_ATOMIC_RELAXED);
+}
+
+void lia_vcr_uncork(struct lia_vcr_track *track)
+{
+ if (al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED) != LIANA_STREAM_STOPPED) {
+ // This can be reached during normal operation. Whether or not that should
+ // be allowed is up for consideration.
+ return;
+ }
+ al_atomic_store(s32)(&track->state, LIANA_STREAM_RUNNING, AL_ATOMIC_RELAXED);
+ aki_mutex_lock(&track->mutex);
+ if (aki_cond_is_waiting(&track->cond)) {
+ aki_cond_signal(&track->cond);
+ }
+ aki_mutex_unlock(&track->mutex);
+}
+
+static void vcr_track_close_internal(struct lia_vcr_track *track)
+{
+ al_atomic_store(s32)(&track->state, LIANA_STREAM_CLOSED, AL_ATOMIC_RELAXED);
+ aki_packet_cache_disable(&track->cache);
+ aki_mutex_lock(&track->mutex);
+ if (aki_cond_is_waiting(&track->cond)) {
+ aki_cond_signal(&track->cond);
+ }
+ aki_mutex_unlock(&track->mutex);
+ if (track->running) {
+ aki_thread_join(&track->thread);
+ track->running = false;
+ }
+ struct aki_packet *packet;
+ while ((packet = aki_packet_cache_pop(&track->cache))) {
+ aki_packet_free(packet);
+ }
+}
+
+void lia_vcr_flush(struct lia_vcr *vcr)
+{
+ struct lia_vcr_track *track;
+ al_array_foreach(vcr->tracks, i, track) {
+ vcr_track_close_internal(track);
+ track->client->flush(track->client);
+ aki_packet_cache_enable(&track->cache);
+ track->running = false;
+ 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);
+}
+
+void lia_vcr_close_all(struct lia_vcr *vcr)
+{
+ struct lia_vcr_track *track;
+ al_array_foreach(vcr->tracks, i, track) {
+ vcr_track_close_internal(track);
+ }
+ aki_signal_stop(&vcr->signal);
+}
+
+void lia_vcr_free(struct lia_vcr *vcr)
+{
+ struct lia_vcr_track *track;
+ al_array_foreach(vcr->tracks, i, track) {
+ aki_packet_cache_free(&track->cache);
+ track->client->free(&track->client);
+#if CAMU_HAVE_FFMPEG
+ if (track->stream.mode == CAMU_FFMPEG_COMPAT) {
+ avformat_free_context(track->stream.av.format_context);
+ }
+#endif
+ al_free(track);
+ }
+ al_array_free(vcr->tracks);
+}
diff --git a/src/liana/vcr.h b/src/liana/vcr.h
new file mode 100644
index 0000000..f471c4e
--- /dev/null
+++ b/src/liana/vcr.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <al/atomic.h>
+#include <aki/packet_cache.h>
+#include <aki/packet_stream.h>
+#include <aki/signal.h>
+
+#include "../codec/codec.h"
+
+enum {
+ LIANA_STREAM_RUNNING = 0,
+ LIANA_STREAM_STOPPED,
+ LIANA_STREAM_CLOSED
+};
+
+struct lia_vcr_track {
+ s32 index;
+ struct camu_codec_stream stream;
+ struct lia_client_handler *client;
+ atomic(s32) state;
+ atomic(u8) buffered;
+ struct aki_packet_cache cache;
+ struct aki_cond cond;
+ struct aki_mutex mutex;
+ bool running;
+ struct aki_thread thread;
+ struct lia_vcr *vcr;
+};
+
+struct lia_vcr {
+ array(struct lia_vcr_track *) tracks;
+ atomic(s32) count;
+ struct { s32 low, buffered; } mark;
+ struct aki_packet_stream *data;
+ struct aki_signal signal;
+};
+
+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);
+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);
+void lia_vcr_close_all(struct lia_vcr *vcr);
+void lia_vcr_free(struct lia_vcr *vcr);
diff --git a/src/libclient/client.c b/src/libclient/client.c
index cd0bdf5..58264e0 100644
--- a/src/libclient/client.c
+++ b/src/libclient/client.c
@@ -1,247 +1,58 @@
-#include <al/log.h>
+#include "client.h"
#include "../server/common.h"
-#include "client.h"
-
-static void identifed_callback(void *userdata, struct aki_packet *packet)
-{
- struct camu_client *client = (struct camu_client *)userdata;
- client->callback(client->userdata, CAMU_CLIENT_CONNECTED, NULL);
- aki_packet_free(packet);
-}
+static struct aki_rpc_command commands[] = { };
static void connection_callback(void *userdata, struct aki_rpc_connection *conn)
{
struct camu_client *client = (struct camu_client *)userdata;
client->conn = conn;
- struct aki_packet *packet = aki_rpc_get_packet(&client->client, CAMU_SRV_IDENTIFY);
+ struct aki_packet *packet = aki_rpc_get_packet(&client->client, CAMU_SERVER_IDENTIFY);
aki_packet_write_u8(packet, CAMU_CLIENT);
aki_packet_write_str(packet, &client->username);
- aki_rpc_connection_command(client->conn, packet, identifed_callback, client);
+ aki_rpc_connection_command(client->conn, packet, NULL, NULL);
}
static void connection_closed_callback(void *userdata, struct aki_rpc_connection *conn)
{
struct camu_client *client = (struct camu_client *)userdata;
- (void)conn;
- client->callback(client->userdata, CAMU_CLIENT_DISCONNECTED, NULL);
+ al_assert(client->conn == conn);
+ client->conn = NULL;
}
-static u8 packet_pool_callback(void *userdata, struct aki_packet *packet)
-{
- struct camu_client *client = (struct camu_client *)userdata;
- aki_rpc_connection_command(client->conn, packet, packet->userdata, client);
- return AKI_PACKET_POOL_NOP;
-}
-
-static bool update_state_command_callback(void *userdata, struct aki_rpc_connection *conn,
- struct aki_packet *packet, struct aki_packet *rpacket)
-{
- struct camu_client *client = (struct camu_client *)userdata;
- (void)conn;
- (void)rpacket;
-
- str s;
- u32 size = aki_packet_read_u32(packet);
- for (u32 i = 0; i < size; i++) {
- struct camu_list *list = al_alloc_object(struct camu_list);
- aki_packet_read_str(packet, &s);
- al_str_clone(&list->name, &s);
- al_array_init(list->entries);
- u32 entries = aki_packet_read_u32(packet);
- for (u32 j = 0; j < entries; j++) {
- aki_packet_read_str(packet, &s);
- str unique_id;
- al_str_clone(&unique_id, &s);
- al_array_push(list->entries, unique_id);
- }
- list->current = aki_packet_read_s32(packet);
- al_array_push(client->state.lists, list);
- }
-
- size = aki_packet_read_u32(packet);
- for (u32 i = 0; i < size; i++) {
- struct camu_search *search = al_alloc_object(struct camu_search);
- search->id = aki_packet_read_s32(packet);
- aki_packet_read_str(packet, &s);
- al_str_clone(&search->module, &s);
- aki_packet_read_str(packet, &s);
- al_str_clone(&search->query, &s);
- search->page = aki_packet_read_u32(packet);
- al_array_push(client->state.searches, search);
- }
-
- client->callback(client->userdata, CAMU_CLIENT_STATE_UPDATED, NULL);
-
- return false;
-}
-
-static struct aki_rpc_command commands[] = {
- { .op = CAMU_CONN_STATE, .callback = update_state_command_callback, .userdata = NULL }
-};
-
-bool camu_client_init(struct camu_client *client, struct aki_event_loop *loop,
- void (*callback)(void *, u8, void *), void *userdata)
+bool camu_client_login(struct camu_client *client, struct aki_event_loop *loop,
+ u8 type, str *addr, u16 port, str *username)
{
client->loop = loop;
- client->callback = callback;
- client->userdata = userdata;
- client->username = AL_STR_EMPTY;
- al_array_init(client->state.lists);
- aki_packet_pool_init_ex(&client->pool, 0, client->loop,
- AKI_PACKET_POOL_MODE_PASSTHROUGH, packet_pool_callback, client);
- if (!aki_rpc_init(&client->client, AKI_SOCKET_TCP, connection_callback,
- connection_closed_callback, client)) {
- return false;
- }
+ al_str_clone(&client->username, username);
+ aki_rpc_init(&client->client, client->loop, connection_callback, connection_closed_callback, client);
for (u32 i = 0; i < AL_ARRAY_SIZE(commands); i++) {
commands[i].userdata = client;
aki_rpc_add_command(&client->client, &commands[i]);
}
- sho_post_cache_init(&client->cache);
- return true;
-}
-
-bool camu_client_login(struct camu_client *client, str *username, str *addr, s32 port)
-{
- al_str_clone(&client->username, username);
- if (!aki_rpc_connect(&client->client, client->loop, addr, port)) {
+ if (!aki_rpc_prepare_client(&client->client, type, CAMU_MULTIPLEX_RPC)) {
return false;
}
- while (!client->conn) {
- aki_event_loop_run_once(client->loop);
- }
- // TODO: Timeout.
+ aki_rpc_connect(&client->client, addr, port);
return true;
}
-static void create_search_callback(void *userdata, struct aki_packet *packet)
-{
- struct camu_client *client = (struct camu_client *)userdata;
- s32 id = aki_packet_read_s32(packet);
- client->callback(client->userdata, CAMU_CLIENT_SEARCH_CREATED, &id);
-}
-
-void camu_client_create_search(struct camu_client *client, str *module, str *query)
-{
- /*
- struct aki_packet *packet = aki_rpc_get_packet(&client->client, TREE_CMD_CREATE_SEARCH);
- aki_packet_write_str(packet, module);
- aki_packet_write_str(packet, query);
- packet->userdata = create_search_callback;
- aki_packet_pool_submit(&client->pool, packet);
- */
-}
-
-static struct camu_search *get_search_from_id(struct camu_client *client, s32 id)
-{
- struct camu_search *search;
- al_array_foreach(client->state.searches, i, search) {
- if (search->id == id) return search;
- }
- return NULL;
-}
-
-static void results_callback(void *userdata, struct aki_packet *packet)
-{
- struct camu_client *client = (struct camu_client *)userdata;
-
- // We still get the requested id even if there was an error.
- s32 id = aki_packet_read_s32(packet);
-
- s32 ok = aki_packet_read_s32(packet);
- if (ok != 0) {
- client->callback(client->userdata, CAMU_CLIENT_END_OF_RESULTS, &id);
- return;
- }
-
- struct camu_search *search = get_search_from_id(client, id);
- if (!search) return;
-
- u32 page_num = aki_packet_read_u32(packet);
- struct camu_result_list *results;
- al_array_foreach(search->lists, i, results) {
- if (results->num == page_num) {
- al_log_warn("client", "Requested page was already loaded.");
- return;
- }
- }
-
- results = al_alloc_object(struct camu_result_list);
- al_array_push(search->lists, results);
- results->num = page_num;
- al_array_init(results->unique_ids);
- results->search = search;
-
- u32 size = aki_packet_read_u32(packet);
- for (u32 i = 0; i < size; i++) {
- struct sho_post post;
- aki_packet_read_sho_post(packet, &post);
- sho_post_cache_push(&client->cache, &post);
- }
-
- str s;
- str unique_id;
- size = aki_packet_read_u32(packet);
- for (u32 i = 0; i < size; i++) {
- aki_packet_read_str(packet, &s);
- al_str_clone(&unique_id, &s);
- al_array_push(results->unique_ids, unique_id);
- }
-
- client->callback(client->userdata, CAMU_CLIENT_RESULTS, results);
-}
-
-void camu_client_get_page(struct camu_client *client, struct camu_search *search, u32 num)
-{
- /*
- struct aki_packet *packet = aki_rpc_get_packet(&client->client, TREE_CMD_GET_PAGE);
- aki_packet_write_s32(packet, search->id);
- aki_packet_write_u32(packet, num);
- packet->userdata = results_callback;
- aki_packet_pool_submit(&client->pool, packet);
- */
-}
-
-void camu_client_add(struct camu_client *client, str *unique_id, u32 index, bool set)
-{
- /*
- struct aki_packet *packet = aki_rpc_get_packet(&client->client, TREE_CMD_LIST_ACTION);
- aki_packet_write_u8(packet, TREE_LIST_ADD);
- aki_packet_write_str(packet, unique_id);
- aki_packet_write_u32(packet, index);
- aki_packet_write_u8(packet, set);
- packet->userdata = NULL;
- aki_packet_pool_submit(&client->pool, packet);
- */
-}
-
-void camu_client_skip(struct camu_client *client, s32 n)
-{
- /*
- struct aki_packet *packet = aki_rpc_get_packet(&client->client, TREE_CMD_LIST_ACTION);
- aki_packet_write_u8(packet, TREE_LIST_SKIP);
- aki_packet_write_s32(packet, n);
- packet->userdata = NULL;
- aki_packet_pool_submit(&client->pool, packet);
- */
-}
-
-void camu_client_skipto(struct camu_client *client, s32 i)
+void camu_client_create_list(struct camu_client *client, str *name,
+ void (*callback)(void *, struct aki_packet *), void *userdata)
{
- /*
- struct aki_packet *packet = aki_rpc_get_packet(&client->client, TREE_CMD_LIST_ACTION);
- aki_packet_write_u8(packet, TREE_LIST_SKIPTO);
- aki_packet_write_s32(packet, i);
- packet->userdata = NULL;
- aki_packet_pool_submit(&client->pool, packet);
- */
+ struct aki_packet *packet = aki_rpc_get_packet(&client->client, CAMU_SERVER_CLIENT_COMMAND);
+ aki_packet_write_u8(packet, CAMU_CLIENT_CREATE_LIST);
+ aki_packet_write_str(packet, name);
+ aki_rpc_connection_command(client->conn, packet, callback, userdata);
}
-void camu_client_close(struct camu_client *client)
+void camu_client_enable_sink(struct camu_client *client, str *list, str *sink,
+ void (*callback)(void *, struct aki_packet *), void *userdata)
{
- aki_packet_pool_free(&client->pool);
- al_str_free(&client->username);
- al_array_free(client->state.lists);
+ struct aki_packet *packet = aki_rpc_get_packet(&client->client, CAMU_SERVER_CLIENT_COMMAND);
+ aki_packet_write_u8(packet, CAMU_CLIENT_ENABLE_SINK);
+ aki_packet_write_str(packet, list);
+ aki_packet_write_str(packet, sink);
+ aki_rpc_connection_command(client->conn, packet, callback, userdata);
}
diff --git a/src/libclient/client.h b/src/libclient/client.h
index 98276f2..a56be26 100644
--- a/src/libclient/client.h
+++ b/src/libclient/client.h
@@ -1,62 +1,18 @@
#pragma once
#include <aki/rpc2.h>
-#include <aki/thread.h>
-#include <aki/packet_pool.h>
-#include <sho/post.h>
-#include <sho/post_cache.h>
-
-enum {
- CAMU_CLIENT_CONNECTED = 0,
- CAMU_CLIENT_STATE_UPDATED,
- CAMU_CLIENT_SEARCH_CREATED,
- CAMU_CLIENT_RESULTS,
- CAMU_CLIENT_END_OF_RESULTS,
- CAMU_CLIENT_DISCONNECTED
-};
-
-struct camu_list {
- str name;
- s32 current;
- array(str) entries;
-};
-
-struct camu_result_list {
- u32 num;
- array(str) unique_ids;
- struct camu_search *search;
-};
-
-struct camu_search {
- s32 id;
- str module;
- str query;
- u32 page;
- array(struct camu_result_list *) lists;
-};
struct camu_client {
struct aki_event_loop *loop;
- struct aki_rpc client;
str username;
- struct {
- array(struct camu_list *) lists;
- array(struct camu_search *) searches;
- } state;
- struct sho_post_cache cache;
+ struct aki_rpc client;
struct aki_rpc_connection *conn;
- struct aki_packet_pool pool;
- void (*callback)(void *, u8, void *);
- void *userdata;
};
-bool camu_client_init(struct camu_client *client, struct aki_event_loop *loop,
- void (*callback)(void *, u8, void *), void *userdata);
-bool camu_client_login(struct camu_client *client, str *username, str *addr, s32 port);
-void camu_client_create_search(struct camu_client *client, str *module, str *query);
-void camu_client_create_browse(struct camu_client *client);
-void camu_client_get_page(struct camu_client *client, struct camu_search *search, u32 num);
-void camu_client_add(struct camu_client *client, str *unique_id, u32 index, bool set);
-void camu_client_skip(struct camu_client *client, s32 n);
-void camu_client_skipto(struct camu_client *client, s32 i);
-void camu_client_close(struct camu_client *client);
+bool camu_client_login(struct camu_client *client, struct aki_event_loop *loop,
+ u8 type, str *addr, u16 port, str *username);
+
+void camu_client_create_list(struct camu_client *client, str *name,
+ void (*callback)(void *, struct aki_packet *), void *userdata);
+void camu_client_enable_sink(struct camu_client *client, str *list, str *sink,
+ void (*callback)(void *, struct aki_packet *), void *userdata);
diff --git a/src/libclient/meson.build b/src/libclient/meson.build
index dff4014..197f0ff 100644
--- a/src/libclient/meson.build
+++ b/src/libclient/meson.build
@@ -1,3 +1,3 @@
-libclient_src = ['client.c', 'resource_client.c']
-libclient_deps = [portal]
+libclient_src = ['client.c']
+libclient_deps = [common_deps]
libclient = declare_dependency(sources: libclient_src, dependencies: libclient_deps)
diff --git a/src/libclient/resource_client.c b/src/libclient/resource_client.c
deleted file mode 100644
index a016fa6..0000000
--- a/src/libclient/resource_client.c
+++ /dev/null
@@ -1,87 +0,0 @@
-#include <al/random.h>
-
-#include "resource_client.h"
-
-static void connection_callback(void *userdata, struct aki_packet_stream *stream)
-{
- struct camu_resource_client *client = (struct camu_resource_client *)userdata;
- (void)stream;
- client->connected = true;
-}
-
-static void connection_closed_callback(void *userdata, struct aki_packet_stream *stream)
-{
- struct camu_resource_client *client = (struct camu_resource_client *)userdata;
- (void)stream;
- client->connected = true;
-}
-
-static void packet_callback(void *userdata, struct aki_packet_stream *stream,
- struct aki_packet *packet)
-{
- struct camu_resource_client *client = (struct camu_resource_client *)userdata;
- (void)stream;
- u16 id = aki_packet_read_u16(packet);
- struct aki_buffer buffer;
- aki_buffer_init(&buffer);
- aki_packet_read_buffer(packet, &buffer);
- client->callback(client->userdata, id, &buffer);
- aki_packet_free(packet);
-}
-
-static void packet_sent_callback(void *userdata, struct aki_packet *packet)
-{
- (void)userdata;
- aki_packet_free(packet);
-}
-
-static void signal_callback(void *userdata)
-{
- struct camu_resource_client *client = (struct camu_resource_client *)userdata;
- u32 size;
- struct camu_resource_request request;
- do {
- camu_queue_try_pop(client->queue, size, request);
- if (size == 0) break;
- struct aki_packet *packet = aki_packet_create();
- aki_packet_write_u16(packet, request.id);
- aki_packet_write_str(packet, &request.unique_id);
- aki_packet_write_u32(packet, request.index);
- aki_packet_stream_send_packet(&client->client, packet);
- al_str_free(&request.unique_id);
- } while (1);
-}
-
-void camu_resource_client_init(struct camu_resource_client *client,
- struct aki_event_loop *loop, void (*callback)(void *, u16, struct aki_buffer *), void *userdata)
-{
- client->loop = loop;
- client->callback = callback;
- client->userdata = userdata;
- camu_queue_init(client->queue);
- aki_signal_init(&client->signal, signal_callback, client);
- aki_packet_stream_init(&client->client, AKI_SOCKET_TCP, connection_callback, connection_closed_callback,
- packet_callback, packet_sent_callback, client);
-}
-
-bool camu_resource_client_connect(struct camu_resource_client *client, str *addr, s32 port)
-{
- aki_signal_start(&client->signal, client->loop);
- aki_packet_stream_connect(&client->client, client->loop, addr, port);
- client->connected = false;
- while (!client->connected) {
- aki_event_loop_run_once(client->loop);
- }
- // TODO: Timeout.
- return true;
-}
-
-void camu_resource_request(struct camu_resource_client *client, str *unique_id, u32 index, u16 id)
-{
- struct camu_resource_request request;
- request.id = id;
- al_str_clone(&request.unique_id, unique_id);
- request.index = index;
- camu_queue_push(client->queue, request);
- aki_signal_send(&client->signal);
-}
diff --git a/src/libclient/resource_client.h b/src/libclient/resource_client.h
deleted file mode 100644
index 755e661..0000000
--- a/src/libclient/resource_client.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-
-#include <aki/packet_stream.h>
-#include <aki/thread.h>
-#include <aki/signal.h>
-
-#include "../util/queue.h"
-
-struct camu_resource_request {
- u16 id;
- str unique_id;
- u32 index;
-};
-
-struct camu_resource_client {
- struct aki_event_loop *loop;
- struct aki_packet_stream client;
- bool connected;
- struct aki_thread thread;
- struct aki_signal signal;
- queue(struct camu_resource_request) queue;
- void (*callback)(void *, u16, struct aki_buffer *);
- void *userdata;
-};
-
-void camu_resource_client_init(struct camu_resource_client *client,
- struct aki_event_loop *loop, void (*callback)(void *, u16, struct aki_buffer *), void *userdata);
-bool camu_resource_client_connect(struct camu_resource_client *client, str *addr, s32 port);
-void camu_resource_request(struct camu_resource_client *client, str *unique_id, u32 index, u16 id);
diff --git a/src/libsink/common.h b/src/libsink/common.h
index a5ad25c..deae228 100644
--- a/src/libsink/common.h
+++ b/src/libsink/common.h
@@ -1,6 +1,12 @@
#pragma once
+#define CAMU_SINK_LOCAL 0
+
enum {
CAMU_SINK_SET = 0,
- CAMU_SINK_QUEUE
+ CAMU_SINK_BUFFER,
+ CAMU_SINK_BUFFER_AND_QUEUE,
+ CAMU_SINK_CLEAR,
+ CAMU_SINK_PAUSE,
+ CAMU_SINK_SEEK
};
diff --git a/src/libsink/meson.build b/src/libsink/meson.build
index 83cfdd7..adc06d2 100644
--- a/src/libsink/meson.build
+++ b/src/libsink/meson.build
@@ -1,3 +1,3 @@
libsink_src = ['sink.c']
-libsink_deps = [shrub_client]
+libsink_deps = [liana_client]
libsink = declare_dependency(sources: libsink_src, dependencies: libsink_deps)
diff --git a/src/libsink/sink.c b/src/libsink/sink.c
index c6727ab..859b717 100644
--- a/src/libsink/sink.c
+++ b/src/libsink/sink.c
@@ -2,13 +2,11 @@
#include "../server/common.h"
-#include "../shrub/common.h"
-#include "../shrub/handler.h"
+#include "../liana/list.h"
+#include "../liana/handler.h"
#include "../buffer/common.h"
-#include "../list/list.h"
-
#include "sink.h"
#include "common.h"
@@ -23,7 +21,8 @@ enum {
BUFFER_QUEUED,
BUFFER_CONFIGURED,
BUFFER_SET_OR_BUFFERED,
- BUFFER_ADDED
+ BUFFER_ADDED,
+ BUFFER_REMOVED
};
enum {
@@ -32,21 +31,13 @@ enum {
TOGGLE_PAUSE,
SEEK,
SKIP,
- FINISHED,
+ SHUFFLE,
+ END,
RESEEK,
- SET_BUFFERED, // Currently set entry is buffered.
CLOSE
};
-#define ENTRY_AUDIO_BUFFER_HELD(entry) \
- (al_atomic_bool_load(&(entry)->audio.buf.ref, AL_ATOMIC_RELAXED))
-#ifdef CAMU_SINK_NO_VIDEO
-#define ENTRY_BUFFERS_HELD(entry) ENTRY_AUDIO_BUFFER_HELD(entry)
-#else
-#define ENTRY_VIDEO_BUFFER_HELD(entry) \
- (al_atomic_bool_load(&(entry)->video.buf.ref, AL_ATOMIC_RELAXED))
-#define ENTRY_BUFFERS_HELD(entry) (ENTRY_AUDIO_BUFFER_HELD(entry) || ENTRY_VIDEO_BUFFER_HELD(entry))
-#endif
+#define ENTRY_MAX_AGE 7
#ifdef CAMU_SINK_NO_VIDEO
#define ENTRY_VIDEO_READY_OR_EMPTY(entry) true
@@ -57,18 +48,67 @@ enum {
#define ENTRY_AUDIO_READY_OR_EMPTY(entry) \
(entry->audio.state == BUFFER_INIT || entry->audio.state == BUFFER_QUEUED || entry->audio.state == BUFFER_ADDED)
+#if defined CAMU_SCREEN_THREADED && defined CAMU_MIXER_THREADED
+#define BLOCKING_SLEEP(delay) aki_thread_sleep(delay)
+#else
+#define BLOCKING_SLEEP(delay) aki_event_loop_sleep(sink->loop, delay)
+#endif
+
+static bool entry_audio_buffer_held(struct camu_sink_entry *entry)
+{
+#ifdef CAMU_MIXER_THREADED
+ return al_atomic_load(u8)(&(entry)->audio.buf.ref, AL_ATOMIC_RELAXED) == 1;
+#else
+ (void)entry;
+ return false;
+#endif
+}
+
+#ifndef CAMU_SINK_NO_VIDEO
+static bool entry_video_buffer_held(struct camu_sink_entry *entry)
+{
+#ifdef CAMU_SCREEN_THREADED
+ return al_atomic_load(u8)(&(entry)->video.buf.ref, AL_ATOMIC_RELAXED) == 1;
+#else
+ (void)entry;
+ return false;
+#endif
+}
+#endif
+
+static bool entry_buffers_held(struct camu_sink_entry *entry)
+{
+#ifndef CAMU_SINK_NO_VIDEO
+ return entry_audio_buffer_held(entry) || entry_video_buffer_held(entry);
+#else
+ return entry_audio_buffer_held(entry);
+#endif
+}
+
+static void remove_entry_audio_buffer(struct camu_sink *sink, struct camu_sink_entry *entry)
+{
+ if (entry->audio.state == BUFFER_ADDED) {
+ sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
+ entry->audio.state = BUFFER_SET_OR_BUFFERED;
+ }
+}
+
+#ifndef CAMU_SINK_NO_VIDEO
+static void remove_entry_video_buffer(struct camu_sink *sink, struct camu_sink_entry *entry)
+{
+ 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;
+ }
+}
+#endif
+
static void remove_entry_buffers(struct camu_sink *sink, struct camu_sink_entry *entry)
{
#ifndef CAMU_SINK_NO_VIDEO
- 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;
- }
+ remove_entry_video_buffer(sink, entry);
#endif
- if (entry->audio.state == BUFFER_ADDED) {
- sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
- entry->audio.state = BUFFER_SET_OR_BUFFERED;
- }
+ remove_entry_audio_buffer(sink, entry);
}
static void add_audio_if_set_and_buffered(struct camu_sink_entry *entry);
@@ -76,21 +116,57 @@ static void add_audio_if_set_and_buffered(struct camu_sink_entry *entry);
static void add_video_if_set_and_buffered(struct camu_sink_entry *entry);
#endif
-static void swap_buffers_internal(struct camu_sink *sink)
+static void set_or_queue_entry(struct camu_sink_entry *entry)
{
- remove_entry_buffers(sink, sink->current);
- sink->current = sink->queued;
- sink->queued = NULL;
- add_audio_if_set_and_buffered(sink->current);
+ if (entry->audio.state == BUFFER_INIT) {
+ entry->audio.state = BUFFER_QUEUED;
+ } else {
+ add_audio_if_set_and_buffered(entry);
+ }
#ifndef CAMU_SINK_NO_VIDEO
- add_video_if_set_and_buffered(sink->current);
+ if (entry->video.state == BUFFER_INIT) {
+ entry->video.state = BUFFER_QUEUED;
+ } else {
+ add_video_if_set_and_buffered(entry);
+ }
#endif
}
+#if CAMU_SINK_LOCAL
+static void sink_local_pause(struct camu_sink *sink, struct camu_sink_entry *entry)
+{
+ if (camu_clock_is_paused(&entry->clock)) {
+ camu_clock_resume(&entry->clock, 0);
+ bool no_audio = entry->audio.state == BUFFER_INIT || entry->audio.state == BUFFER_QUEUED;
+ if (!no_audio && sink->audio.state == SINK_PAUSED) {
+ sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL);
+ sink->audio.state = SINK_PLAYING;
+ }
+#ifndef CAMU_SINK_NO_VIDEO
+ bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf);
+ if (!single_frame && sink->video.state == SINK_PAUSED) {
+ sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_VIDEO, NULL);
+ sink->video.state = SINK_PLAYING;
+ }
+#endif
+ } else {
+ camu_clock_pause(&entry->clock, 0);
+#ifndef CAMU_SINK_NO_VIDEO
+ bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf);
+ if (!single_frame && sink->video.state == SINK_PLAYING) {
+ sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_VIDEO, NULL);
+ sink->video.state = SINK_PAUSED;
+ }
+#endif
+ }
+}
+#endif
+
static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
{
switch (cmd->op) {
case START: {
+ aki_mutex_lock(&sink->mutex);
switch (cmd->value.i) {
case CAMU_SINK_AUDIO:
if (sink->audio.state == SINK_PAUSED) {
@@ -107,9 +183,11 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
break;
#endif
}
+ aki_mutex_unlock(&sink->mutex);
break;
}
case STOP: {
+ aki_mutex_lock(&sink->mutex);
switch (cmd->value.i) {
case CAMU_SINK_AUDIO:
if (sink->audio.state == SINK_PLAYING) {
@@ -126,90 +204,69 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd)
break;
#endif
}
+ aki_mutex_unlock(&sink->mutex);
break;
}
case SKIP: {
+ if (!sink->conn) return;
struct aki_packet *packet = aki_rpc_get_packet(&sink->client, CAMU_SERVER_LIST_ACTION);
- aki_packet_write_u8(packet, CAMU_SKIP);
- s32 sequence = sink->current ? sink->current->sequence : CAMU_SEQUENCE_INVALID;
+ aki_packet_write_str(packet, al_str_c("default"));
+ aki_packet_write_u8(packet, CAMU_LIST_SKIP);
+ //s32 sequence = sink->current ? sink->current->sequence : LIANA_SEQUENCE_ANY;
+ s32 sequence = LIANA_SEQUENCE_ANY;
aki_packet_write_s32(packet, sequence);
aki_packet_write_s32(packet, cmd->value.i);
aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
+ case SHUFFLE: {
+ if (!sink->conn) return;
+ struct aki_packet *packet = aki_rpc_get_packet(&sink->client, CAMU_SERVER_LIST_ACTION);
+ aki_packet_write_str(packet, al_str_c("default"));
+ aki_packet_write_u8(packet, CAMU_LIST_SHUFFLE);
+ aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
+ break;
+ }
case TOGGLE_PAUSE: {
- struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque;
-#ifdef CAMU_SINK_LOCAL_PAUSE
- if (camu_clock_is_paused(&entry->clock)) {
- camu_clock_resume(&entry->clock);
- if (sink->audio.state == SINK_PAUSED) {
- sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL);
- sink->audio.state = SINK_PLAYING;
- }
-#ifndef CAMU_SINK_NO_VIDEO
- if (sink->video.state == SINK_PAUSED) {
- sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_VIDEO, NULL);
- sink->video.state = SINK_PLAYING;
- }
-#endif
- } else {
- camu_clock_pause(&entry->clock, -1.0);
-#ifndef CAMU_SINK_NO_VIDEO
- if (sink->video.state == SINK_PLAYING) {
- sink->callback(sink->userdata, CAMU_SINK_STOP, CAMU_SINK_VIDEO, NULL);
- sink->video.state = SINK_PAUSED;
- }
-#endif
- }
+#if CAMU_SINK_LOCAL
+ sink_local_pause(sink, (struct camu_sink_entry *)cmd->opaque);
#else
- f64 base = camu_clock_get_base_pts(&entry->clock);
- if (!camu_clock_is_paused(&entry->clock)) {
- f64 pts = camu_clock_get_pts(&entry->clock, 0.0);
- if (pts > base) base = pts;
- }
+ if (!sink->conn) return;
struct aki_packet *packet = aki_rpc_get_packet(&sink->client, CAMU_SERVER_LIST_ACTION);
- aki_packet_write_u8(packet, CAMU_TOGGLE_PAUSE);
- s32 sequence = sink->current ? sink->current->sequence : CAMU_SEQUENCE_INVALID;
+ aki_packet_write_str(packet, al_str_c("default"));
+ aki_packet_write_u8(packet, CAMU_LIST_TOGGLE_PAUSE);
+ s32 sequence = sink->current ? sink->current->sequence : LIANA_SEQUENCE_ANY;
aki_packet_write_s32(packet, sequence);
- aki_packet_write_u64(packet, base * 1000000.0);
+ aki_packet_write_f64(packet, cmd->value.f);
aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
#endif
break;
}
case SEEK: {
+ if (!sink->conn) return;
struct aki_packet *packet = aki_rpc_get_packet(&sink->client, CAMU_SERVER_LIST_ACTION);
- aki_packet_write_u8(packet, CAMU_SEEK);
- s32 sequence = sink->current ? sink->current->sequence : CAMU_SEQUENCE_INVALID;
+ aki_packet_write_str(packet, al_str_c("default"));
+ aki_packet_write_u8(packet, CAMU_LIST_SEEK);
+ s32 sequence = sink->current ? sink->current->sequence : LIANA_SEQUENCE_ANY;
aki_packet_write_s32(packet, sequence);
aki_packet_write_f64(packet, cmd->value.f);
aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
- case FINISHED: {
+ case END: {
+ if (!sink->conn) return;
struct aki_packet *packet = aki_rpc_get_packet(&sink->client, CAMU_SERVER_LIST_ACTION);
- aki_packet_write_u8(packet, CAMU_FINISHED);
+ aki_packet_write_str(packet, al_str_c("default"));
+ aki_packet_write_u8(packet, CAMU_LIST_END);
aki_packet_write_s32(packet, cmd->value.i);
aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
break;
}
case RESEEK: {
struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque;
- shrb_client_reseek(&entry->client);
+ lia_client_reseek(&entry->client);
break;
}
-// case SET_BUFFERED: {
-// switch (cmd->value.i) {
-// case CAMU_SINK_AUDIO:
-// sink->callback(sink->userdata, CAMU_SINK_SET_BUFFERED, CAMU_SINK_AUDIO, NULL);
-// break;
-//#ifndef CAMU_SINK_NO_VIDEO
-// case CAMU_SINK_VIDEO:
-// sink->callback(sink->userdata, CAMU_SINK_SET_BUFFERED, CAMU_SINK_VIDEO, NULL);
-// break;
-//#endif
-// }
-// break;
-// }
case CLOSE: {
aki_signal_stop(&sink->signal);
sink->callback(sink->userdata, CAMU_SINK_EXIT, 0, NULL);
@@ -250,20 +307,15 @@ void add_audio_if_set_and_buffered(struct camu_sink_entry *entry)
u8 state = entry->audio.state;
if (state == BUFFER_SET_OR_BUFFERED) {
bool can_resume = ENTRY_VIDEO_READY_OR_EMPTY(entry);
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = START,
- .value.i = CAMU_SINK_AUDIO
- });
camu_audio_buffer_unpause(&entry->audio.buf);
entry->sink->callback(entry->sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
if (can_resume) {
- camu_clock_ready(&entry->clock);
maybe_remove_previous(entry->sink);
}
- //queue_cmd(entry->sink, (struct camu_sink_cmd){
- // .op = SET_BUFFERED,
- // .value.i = CAMU_SINK_AUDIO
- //});
+ queue_cmd(entry->sink, (struct camu_sink_cmd){
+ .op = START,
+ .value.i = CAMU_SINK_AUDIO
+ });
state = BUFFER_ADDED;
} else if (state == BUFFER_CONFIGURED) {
state = BUFFER_SET_OR_BUFFERED;
@@ -277,19 +329,15 @@ void add_video_if_set_and_buffered(struct camu_sink_entry *entry)
u8 state = entry->video.state;
if (state == BUFFER_SET_OR_BUFFERED) {
bool can_resume = ENTRY_AUDIO_READY_OR_EMPTY(entry);
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = START,
- .value.i = CAMU_SINK_VIDEO
- });
entry->sink->callback(entry->sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf);
if (can_resume) {
- camu_clock_ready(&entry->clock);
maybe_remove_previous(entry->sink);
}
- //queue_cmd(entry->sink, (struct camu_sink_cmd){
- // .op = SET_BUFFERED,
- // .value.i = CAMU_SINK_VIDEO
- //});
+ bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf);
+ queue_cmd(entry->sink, (struct camu_sink_cmd){
+ .op = single_frame ? STOP : START,
+ .value.i = CAMU_SINK_VIDEO
+ });
state = BUFFER_ADDED;
} else if (state == BUFFER_CONFIGURED) {
state = BUFFER_SET_OR_BUFFERED;
@@ -309,10 +357,10 @@ static void audio_buffer_callback(void *userdata, u8 op)
aki_mutex_unlock(&sink->mutex);
break;
case CAMU_BUFFER_CORK:
- shrb_vcr_stream_cork(entry->audio.stream);
+ lia_vcr_cork(entry->audio.track);
break;
case CAMU_BUFFER_UNCORK:
- shrb_vcr_stream_uncork(entry->audio.stream);
+ lia_vcr_uncork(entry->audio.track);
break;
case CAMU_BUFFER_PAUSED:
aki_mutex_lock(&sink->mutex);
@@ -325,28 +373,18 @@ static void audio_buffer_callback(void *userdata, u8 op)
aki_mutex_unlock(&sink->mutex);
break;
case CAMU_BUFFER_EOF: {
- bool swapped = false;
+ lia_vcr_cork(entry->audio.track);
aki_mutex_lock(&sink->mutex);
+ camu_clock_end(&entry->clock);
+ remove_entry_audio_buffer(sink, entry);
if (sink->queued) {
- al_log_info("sink", "Swapping audio buffers (gapless).");
- swap_buffers_internal(sink);
- swapped = true;
+ set_or_queue_entry(sink->queued);
+ sink->current = sink->queued;
+ sink->queued = NULL;
}
aki_mutex_unlock(&sink->mutex);
- if (!swapped) {
- queue_cmd(entry->sink, (struct camu_sink_cmd){
- .op = STOP,
- .value.i = CAMU_SINK_AUDIO
- });
-#ifndef CAMU_SINK_NO_VIDEO
- //queue_cmd(entry->sink, (struct camu_sink_cmd){
- // .op = SET_BUFFERED,
- // .value.i = CAMU_SINK_VIDEO
- //});
-#endif
- }
queue_cmd(sink, (struct camu_sink_cmd){
- .op = FINISHED,
+ .op = END,
.value.i = entry->sequence
});
break;
@@ -366,22 +404,39 @@ static void video_buffer_callback(void *userdata, u8 op)
aki_mutex_unlock(&sink->mutex);
break;
case CAMU_BUFFER_CORK:
- shrb_vcr_stream_cork(entry->video.stream);
+ lia_vcr_cork(entry->video.track);
break;
case CAMU_BUFFER_UNCORK:
- shrb_vcr_stream_uncork(entry->video.stream);
+ lia_vcr_uncork(entry->video.track);
break;
case CAMU_BUFFER_EOF: {
- queue_cmd(sink, (struct camu_sink_cmd){
- .op = STOP,
- .value.i = CAMU_SINK_VIDEO
- });
+ lia_vcr_cork(entry->video.track);
+ bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf);
+ bool swapped = false;
aki_mutex_lock(&sink->mutex);
- bool no_audio = entry->audio.state == BUFFER_INIT;
+ if (!single_frame) {
+ remove_entry_video_buffer(sink, entry);
+ }
+ bool run_queue = !single_frame && (entry->audio.state == BUFFER_INIT || entry->audio.state == BUFFER_QUEUED);
+ 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;
+ }
+ }
aki_mutex_unlock(&sink->mutex);
- if (no_audio) {
+ if (!swapped) {
queue_cmd(sink, (struct camu_sink_cmd){
- .op = FINISHED,
+ .op = STOP,
+ .value.i = CAMU_SINK_VIDEO
+ });
+ }
+ if (run_queue) {
+ queue_cmd(sink, (struct camu_sink_cmd){
+ .op = END,
.value.i = entry->sequence
});
}
@@ -391,124 +446,114 @@ static void video_buffer_callback(void *userdata, u8 op)
}
#endif
-static void client_callback(void *userdata, u8 op, struct shrb_vcr_stream *stream, void *opaque)
+static void evaluate_latency(struct camu_sink *sink, struct camu_sink_entry *entry)
+{
+ // Entry has both audio and video configured.
+#ifndef CAMU_SINK_NO_VIDEO
+ if (entry->audio.state != BUFFER_INIT && entry->audio.state != BUFFER_QUEUED &&
+ entry->video.state != BUFFER_INIT && entry->video.state != BUFFER_QUEUED) {
+ camu_video_buffer_set_latency(&entry->video.buf, -camu_mixer_get_latency(sink->audio.mixer));
+ }
+#else
+ (void)sink;
+ (void)entry;
+#endif
+}
+
+static void client_callback(void *userdata, u8 op, struct camu_codec_stream *stream, void *opaque)
{
struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata;
struct camu_sink *sink = entry->sink;
switch (op) {
- case SHRUB_CLIENT_CONFIGURE: {
+ case LIANA_CLIENT_CONFIGURE: {
// No data can be sent until all selected streams are configured.
switch (stream->type) {
- case SHRUB_STREAM_AUDIO:
- entry->audio.stream = stream;
- camu_audio_buffer_configure(&entry->audio.buf, &stream->stream);
- aki_mutex_lock(&entry->sink->mutex);
+ case CAMU_STREAM_AUDIO:
+ entry->audio.track = (struct lia_vcr_track *)opaque;
+ if (!camu_audio_buffer_configure(&entry->audio.buf, stream)) {
+ lia_client_disconnect(&entry->client);
+ }
+ aki_mutex_lock(&sink->mutex);
if (entry->audio.state == BUFFER_QUEUED) {
entry->audio.state = BUFFER_SET_OR_BUFFERED;
} else {
entry->audio.state = BUFFER_CONFIGURED;
}
- aki_mutex_unlock(&entry->sink->mutex);
+ evaluate_latency(sink, entry);
+ aki_mutex_unlock(&sink->mutex);
break;
#ifndef CAMU_SINK_NO_VIDEO
- case SHRUB_STREAM_VIDEO:
- entry->video.stream = stream;
- camu_video_buffer_configure(&entry->video.buf, &stream->stream);
- aki_mutex_lock(&entry->sink->mutex);
+ case CAMU_STREAM_VIDEO:
+ entry->video.track = (struct lia_vcr_track *)opaque;
+ if (!camu_video_buffer_configure(&entry->video.buf, stream)) {
+ lia_client_disconnect(&entry->client);
+ }
+ aki_mutex_lock(&sink->mutex);
if (entry->video.state == BUFFER_QUEUED) {
entry->video.state = BUFFER_SET_OR_BUFFERED;
} else {
entry->video.state = BUFFER_CONFIGURED;
}
- aki_mutex_unlock(&entry->sink->mutex);
+ evaluate_latency(sink, entry);
+ aki_mutex_unlock(&sink->mutex);
break;
#endif
}
break;
}
- case SHRUB_CLIENT_SET: {
- struct shrb_seek_req *req = (struct shrb_seek_req *)opaque;
- req->delay = SHRUB_DELAY_IGNORE;
- camu_clock_set(&entry->clock, req);
- if (req->paused) req->base = req->paused_at;
- break;
- }
- case SHRUB_CLIENT_PAUSE: {
- f64 pts = *(f64 *)opaque;
- aki_mutex_lock(&entry->sink->mutex);
- camu_clock_arm_pause(&entry->clock, pts);
- entry->audio.armed = false;
- aki_mutex_unlock(&entry->sink->mutex);
- break;
- }
- case SHRUB_CLIENT_RESUME: {
- aki_mutex_lock(&entry->sink->mutex);
- struct shrb_resume_req *req = (struct shrb_resume_req *)opaque;
- if (sink->queued) {
- camu_clock_offset(&sink->queued->clock, req->offset);
- }
- camu_clock_arm_resume(&entry->clock, req->start);
- if (sink->audio.state == SINK_PAUSED) {
- sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL);
- sink->audio.state = SINK_PLAYING;
- } else {
- entry->audio.armed = true;
- }
- aki_mutex_unlock(&entry->sink->mutex);
- break;
- }
- case SHRUB_CLIENT_DATA: {
- struct camu_frame *frame = (struct camu_frame *)opaque;
+ case LIANA_CLIENT_DATA: {
+ struct camu_codec_frame *frame = (struct camu_codec_frame *)opaque;
switch (stream->type) {
- case SHRUB_STREAM_AUDIO:
+ case CAMU_STREAM_AUDIO:
camu_audio_buffer_push(&entry->audio.buf, frame);
break;
#ifndef CAMU_SINK_NO_VIDEO
- case SHRUB_STREAM_VIDEO:
+ case CAMU_STREAM_VIDEO:
camu_video_buffer_push(&entry->video.buf, frame);
break;
#endif
default:
- camu_frame_discard(frame);
+ camu_codec_frame_discard(frame);
break;
}
break;
}
- case SHRUB_CLIENT_REMOVE_BUFFERS: {
- aki_mutex_lock(&entry->sink->mutex);
+ case LIANA_CLIENT_REMOVE_BUFFERS: {
+ aki_mutex_lock(&sink->mutex);
if (entry->audio.state == BUFFER_ADDED) {
- entry->sink->callback(entry->sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
+ sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf);
entry->audio.state = BUFFER_SET_OR_BUFFERED;
}
#ifndef CAMU_SINK_NO_VIDEO
- bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf);
- if (entry->video.state == BUFFER_ADDED && !single_frame) {
- entry->sink->callback(entry->sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf);
+ bool reconnect = *(bool *)opaque;
+ bool keep_video = reconnect && camu_video_buffer_is_single_frame(&entry->video.buf);
+ 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;
}
#endif
- aki_mutex_unlock(&entry->sink->mutex);
-#ifndef CAMU_SINK_NO_VIDEO
- if (single_frame) {
- while (ENTRY_AUDIO_BUFFER_HELD(entry)) { aki_thread_sleep(AKI_TS_FROM_USEC(200)); }
- } else {
-#endif
- while (ENTRY_BUFFERS_HELD(entry)) { aki_thread_sleep(AKI_TS_FROM_USEC(200)); }
+ aki_mutex_unlock(&sink->mutex);
#ifndef CAMU_SINK_NO_VIDEO
- camu_video_buffer_reset(&entry->video.buf);
+ 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);
+#else
+ while (entry_buffers_held(entry)) {
+ BLOCKING_SLEEP(AKI_TS_FROM_USEC(2000));
}
#endif
camu_audio_buffer_reset(&entry->audio.buf);
break;
}
- case SHRUB_CLIENT_EOF: {
+ case LIANA_CLIENT_EOF: {
switch (stream->type) {
- case SHRUB_STREAM_AUDIO: {
+ case CAMU_STREAM_AUDIO: {
camu_audio_buffer_flush(&entry->audio.buf);
break;
}
#ifndef CAMU_SINK_NO_VIDEO
- case SHRUB_STREAM_VIDEO: {
+ case CAMU_STREAM_VIDEO: {
bool single_frame = camu_video_buffer_is_single_frame(&entry->video.buf);
if (!single_frame) {
camu_video_buffer_flush(&entry->video.buf);
@@ -519,19 +564,47 @@ static void client_callback(void *userdata, u8 op, struct shrb_vcr_stream *strea
}
break;
}
- case SHRUB_CLIENT_CLOSED: {
- shrb_client_free(&entry->client);
+ case LIANA_CLIENT_CLOSED: {
+ aki_mutex_lock(&sink->mutex);
+ if (entry == sink->current) sink->current = NULL;
+ if (entry == sink->queued) sink->queued = NULL;
+ lia_client_free(&entry->client);
camu_audio_buffer_free(&entry->audio.buf);
#ifndef CAMU_SINK_NO_VIDEO
camu_video_buffer_free(&entry->video.buf);
#endif
+ bool removed = false;
+ struct camu_sink_entry *rentry;
+ al_array_foreach(sink->entries, i, rentry) {
+ if (rentry == entry) {
+ al_array_remove_at(sink->entries, i);
+ removed = true;
+ al_log_debug("sink", "Entry closed by disconnect.");
+ break;
+ }
+ }
al_free(entry);
- al_log_debug("sink", "Entry closed.");
+ if (!removed) {
+ al_log_debug("sink", "Entry closed by cleanup.");
+ }
+ aki_mutex_unlock(&sink->mutex);
break;
}
}
}
+static void mixer_callback(void *userdata, u8 op)
+{
+ struct camu_sink *sink = (struct camu_sink *)userdata;
+ if (op == CAMU_MIXER_EMPTY) {
+ al_log_debug("sink", "Mixer empty.");
+ queue_cmd(sink, (struct camu_sink_cmd){
+ .op = STOP,
+ .value.i = CAMU_SINK_AUDIO
+ });
+ }
+}
+
bool camu_sink_init(struct camu_sink *sink, struct aki_event_loop *loop,
struct camu_mixer *mixer
#ifndef CAMU_SINK_NO_VIDEO
@@ -548,6 +621,9 @@ bool camu_sink_init(struct camu_sink *sink, struct aki_event_loop *loop,
sink->current = NULL;
al_array_init(sink->previous);
al_array_init(sink->entries);
+ sink->lru = 0;
+ mixer->callback = mixer_callback;
+ mixer->userdata = sink;
sink->audio.mixer = mixer;
sink->audio.state = SINK_PAUSED;
#ifndef CAMU_SINK_NO_VIDEO
@@ -557,25 +633,82 @@ bool camu_sink_init(struct camu_sink *sink, struct aki_event_loop *loop,
return true;
}
-static struct camu_sink_entry *entry_from_node_id(struct camu_sink *sink, u16 node_id)
+static s32 lru_compare(const void *a, const void *b)
{
+ struct camu_sink_entry *aa = *((struct camu_sink_entry **)a);
+ struct camu_sink_entry *bb = *((struct camu_sink_entry **)b);
+ if (aa->lru > bb->lru) return -1;
+ else if (aa->lru < bb->lru) return 1;
+ return 0;
+}
+
+static void maybe_cleanup_old_entries(struct camu_sink *sink)
+{
+ al_array_sort(sink->entries, struct camu_sink_entry *, lru_compare);
+ // We check size <= MAX_AGE in the loop because sink->lru
+ // is not indicative of the amount of entries we have loaded.
+ // There are various reasons for this but the most obvious is
+ // that it's incremented for buffer and queue operations.
+ //
+ // Handle sink->lru wrapping.
+ // 0 65532 65533 65534 65535
+ // 0 1 65533 65534 65535
+ // 0 1 2 65534 65535
+ // 0 1 2 3 65535
+ // 0 1 2 3 4
struct camu_sink_entry *entry;
- al_array_foreach(sink->entries, i, entry) {
- if (entry->client.node_id == node_id) return entry;
+ al_array_foreach_rev(sink->entries, i, entry) {
+ if (sink->entries.size <= ENTRY_MAX_AGE) return;
+ if (entry->lru > sink->lru && (UINT16_MAX - (entry->lru - 1)) + sink->lru >= ENTRY_MAX_AGE) {
+ al_array_remove_at(sink->entries, i);
+ lia_client_disconnect(&entry->client);
+ }
+ }
+ if (sink->lru >= ENTRY_MAX_AGE) {
+ al_array_foreach_rev(sink->entries, i, entry) {
+ if (sink->entries.size <= ENTRY_MAX_AGE) return;
+ if (sink->lru - entry->lru >= ENTRY_MAX_AGE) {
+ al_array_remove_at(sink->entries, i);
+ lia_client_disconnect(&entry->client);
+ }
+ }
+ }
+}
+
+static void clock_callback(void *userdata, u8 op)
+{
+ struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata;
+ struct camu_sink *sink = entry->sink;
+ if (op == CAMU_CLOCK_PAUSED) {
+ aki_mutex_lock(&sink->mutex);
+ if (sink->target) {
+ if (sink->current) {
+ al_array_push(sink->previous, sink->current);
+ }
+ set_or_queue_entry(sink->target);
+ sink->current = sink->target;
+ sink->target = NULL;
+ }
+ aki_mutex_unlock(&sink->mutex);
}
- return NULL;
}
-static struct camu_sink_entry *ensure_entry_buffered_internal(struct camu_sink *sink,
- str *addr, s32 port, u32 node_id)
+static struct camu_sink_entry *get_entry_from_id(struct camu_sink *sink, u16 id, bool *created)
{
- struct camu_sink_entry *entry = entry_from_node_id(sink, node_id);
- if (entry) return entry;
+ struct camu_sink_entry *entry;
+ al_array_foreach(sink->entries, i, entry) {
+ if (entry->id == id) {
+ *created = false;
+ return entry;
+ }
+ }
entry = al_alloc_object(struct camu_sink_entry);
- al_array_push(sink->entries, entry);
+ entry->id = id;
entry->sink = sink;
+ camu_clock_init(&entry->clock, clock_callback, entry);
+
entry->audio.state = BUFFER_INIT;
entry->audio.armed = false;
camu_audio_buffer_init(&entry->audio.buf, &entry->clock, sink->audio.mixer);
@@ -584,15 +717,17 @@ static struct camu_sink_entry *ensure_entry_buffered_internal(struct camu_sink *
#ifndef CAMU_SINK_NO_VIDEO
entry->video.state = BUFFER_INIT;
- camu_video_buffer_init(&entry->video.buf, &entry->clock, 0.0, sink->video.renderer);
+ camu_video_buffer_init(&entry->video.buf, &entry->clock, sink->video.renderer);
entry->video.buf.callback = video_buffer_callback;
entry->video.buf.userdata = entry;
#endif
entry->client.callback = client_callback;
entry->client.userdata = entry;
- // Assume we can error out at any point after connect is called (even from within connect() itself).
- shrb_client_connect(&entry->client, sink->loop, addr, port, node_id);
+
+ al_array_push(sink->entries, entry);
+
+ *created = true;
return entry;
}
@@ -604,100 +739,237 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn
(void)conn;
(void)rpacket;
+ aki_mutex_lock(&sink->mutex);
+
+ u8 op = aki_packet_read_u8(packet);
+#if CAMU_SINK_LOCAL
+ if (op == CAMU_SINK_CLEAR) {
+ if (sink->current) {
+ remove_entry_buffers(sink, sink->current);
+ sink->current = NULL;
+ }
+ goto out;
+ }
+#else
+ if (op == CAMU_SINK_CLEAR) { al_assert(false); } // Unimplemented.
+#endif
+
str addr;
aki_packet_read_str(packet, &addr);
- s32 port = aki_packet_read_s32(packet);
+ u16 port = aki_packet_read_u16(packet);
u16 node_id = aki_packet_read_u16(packet);
s32 sequence = aki_packet_read_s32(packet);
- u64 start = aki_packet_read_u64(packet);
- u8 paused = aki_packet_read_u8(packet);
- u64 paused_at = aki_packet_read_u64(packet);
+ u64 at = aki_packet_read_u64(packet);
+ u64 seek_pos = aki_packet_read_u64(packet);
+ u8 pause = aki_packet_read_u8(packet);
- aki_mutex_lock(&sink->mutex);
- struct camu_sink_entry *entry = ensure_entry_buffered_internal(sink, &addr, port, node_id);
+ bool created;
+ struct camu_sink_entry *entry = get_entry_from_id(sink, node_id, &created);
entry->sequence = sequence;
- if (entry == sink->current) goto out;
- if (!paused) {
- // This will only have an effect if the clock is already set.
- camu_clock_arm_resume(&entry->clock, start + SHRUB_BASE_DELAY);
+ sink->lru = al_u16_inc_wrap(sink->lru);
+ entry->lru = sink->lru;
+
+ if (created) {
+ camu_clock_set(&entry->clock, seek_pos / 1000000.0);
+ struct camu_renderer *renderer = NULL;
+#ifndef CAMU_SINK_NO_VIDEO
+ renderer = sink->video.renderer;
+#endif
+ lia_client_connect(&entry->client, sink->loop, sink->type, &addr, port, node_id, seek_pos, renderer);
+ }
+
+ if (op == CAMU_SINK_BUFFER) {
+ goto out;
+ } else if (op == CAMU_SINK_BUFFER_AND_QUEUE) {
+ sink->queued = entry;
+ goto out;
}
+
+#if CAMU_SINK_LOCAL
+ (void)at;
+ (void)pause;
if (sink->current) {
- camu_clock_pause(&sink->current->clock, paused_at / 1000000.0);
+ if (!camu_clock_is_paused(&sink->current->clock)) {
+ camu_clock_pause(&sink->current->clock, 0);
+ }
al_array_push(sink->previous, sink->current);
}
+ set_or_queue_entry(entry);
sink->current = entry;
- if (sink->queued) sink->queued = NULL;
- if (entry->audio.state == BUFFER_INIT) {
- entry->audio.state = BUFFER_QUEUED;
- } else {
- add_audio_if_set_and_buffered(entry);
+ // This will resume a user paused stream.
+ camu_clock_resume(&entry->clock, 0);
+#else
+ switch (pause) {
+ case LIANA_PAUSE_NONE:
+ if (sink->current) {
+ al_array_push(sink->previous, sink->current);
+ }
+ set_or_queue_entry(entry);
+ sink->current = entry;
+ break;
+ case LIANA_PAUSE_RESUME:
+ camu_clock_resume(&entry->clock, at);
+ if (sink->current) {
+ al_array_push(sink->previous, sink->current);
+ }
+ set_or_queue_entry(entry);
+ sink->current = entry;
+ break;
+ case LIANA_PAUSE_PAUSE:
+ if (sink->current) {
+ if (camu_clock_is_ended(&sink->current->clock)) {
+ // Server thought we weren't done, be we are.
+ al_array_push(sink->previous, sink->current);
+ set_or_queue_entry(entry);
+ sink->current = entry;
+ } else {
+ al_printf("ay\n");
+ sink->target = entry;
+ camu_clock_pause(&sink->current->clock, at);
+ }
+ }
+ break;
+ case LIANA_PAUSE_BOTH: {
+ camu_clock_resume(&entry->clock, at);
+ struct camu_sink_entry *prev_target = sink->target;
+ if (prev_target) {
+ if (prev_target == entry) {
+ sink->target = NULL;
+ } else {
+ sink->target = entry;
+ }
+ camu_clock_pause(&prev_target->clock, at);
+ } else if (sink->current) {
+ if (camu_clock_is_ended(&sink->current->clock)) {
+ al_array_push(sink->previous, sink->current);
+ set_or_queue_entry(entry);
+ sink->current = entry;
+ } else {
+ sink->target = entry;
+ camu_clock_pause(&sink->current->clock, at);
+ }
+ }
+ break;
}
-#ifndef CAMU_SINK_NO_VIDEO
- if (entry->video.state == BUFFER_INIT) {
- entry->video.state = BUFFER_QUEUED;
- } else {
- add_video_if_set_and_buffered(entry);
}
#endif
+
out:
aki_mutex_unlock(&sink->mutex);
+ if (op != CAMU_SINK_BUFFER) {
+ maybe_cleanup_old_entries(sink);
+ }
+
aki_packet_free(packet);
return false;
}
-static bool queue_command_callback(void *userdata, struct aki_rpc_connection *conn,
+static bool pause_command_callback(void *userdata, struct aki_rpc_connection *conn,
struct aki_packet *packet, struct aki_packet *rpacket)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
(void)conn;
(void)rpacket;
- str addr;
- aki_packet_read_str(packet, &addr);
- s32 port = aki_packet_read_s32(packet);
- u16 node_id = aki_packet_read_u16(packet);
+ aki_mutex_lock(&sink->mutex);
+ struct camu_sink_entry *current = sink->current;
+ if (!current) goto out;
+#if CAMU_SINK_LOCAL
+ sink_local_pause(sink, current);
+#else
s32 sequence = aki_packet_read_s32(packet);
+ u64 at = aki_packet_read_u64(packet);
+ u8 pause = aki_packet_read_u8(packet);
+ if (current->sequence == sequence) {
+ if (pause == LIANA_PAUSE_PAUSE) {
+ camu_clock_pause(&sink->current->clock, at);
+ current->audio.armed = false;
+ } else {
+ camu_clock_resume(&sink->current->clock, at);
+ if (sink->audio.state == SINK_PAUSED) {
+ sink->callback(sink->userdata, CAMU_SINK_START, CAMU_SINK_AUDIO, NULL);
+ sink->audio.state = SINK_PLAYING;
+ } else {
+ current->audio.armed = true;
+ }
+ }
+ }
+#endif
+out:
+ aki_mutex_unlock(&sink->mutex);
+
+ aki_packet_free(packet);
+ return false;
+}
+
+static bool seek_command_callback(void *userdata, struct aki_rpc_connection *conn,
+ struct aki_packet *packet, struct aki_packet *rpacket)
+{
+ struct camu_sink *sink = (struct camu_sink *)userdata;
+ (void)conn;
+ (void)rpacket;
+
+ s32 sequence = aki_packet_read_s32(packet);
+ u64 at = aki_packet_read_u64(packet);
+ u64 pos = aki_packet_read_u64(packet);
aki_mutex_lock(&sink->mutex);
- sink->queued = ensure_entry_buffered_internal(sink, &addr, port, node_id);
- sink->queued->sequence = sequence;
+ struct camu_sink_entry *current = sink->current;
aki_mutex_unlock(&sink->mutex);
+ if (current->sequence == sequence) {
+ // TODO: Thread-safety here.
+ lia_client_seek(&current->client, pos);
+ camu_clock_set(&current->clock, pos / 1000000.0);
+ camu_clock_resume(&current->clock, at);
+ }
aki_packet_free(packet);
-
return false;
}
static struct aki_rpc_command commands[] = {
{ .op = CAMU_SINK_SET, .callback = set_command_callback, .userdata = NULL },
- { .op = CAMU_SINK_QUEUE, .callback = queue_command_callback, .userdata = NULL }
+ { .op = CAMU_SINK_PAUSE, .callback = pause_command_callback, .userdata = NULL },
+ { .op = CAMU_SINK_SEEK, .callback = seek_command_callback, .userdata = NULL }
};
+static void idd_callback(void *userdata, struct aki_packet *packet)
+{
+ struct camu_sink *sink = (struct camu_sink *)userdata;
+ (void)sink;
+ aki_packet_free(packet);
+}
+
static void connection_callback(void *userdata, struct aki_rpc_connection *conn)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
sink->conn = conn;
struct aki_packet *packet = aki_rpc_get_packet(&sink->client, CAMU_SERVER_IDENTIFY);
aki_packet_write_u8(packet, CAMU_SINK);
- aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
+ aki_packet_write_str(packet, &sink->name);
+ aki_rpc_connection_command(sink->conn, packet, idd_callback, sink);
}
static void connection_closed_callback(void *userdata, struct aki_rpc_connection *conn)
{
struct camu_sink *sink = (struct camu_sink *)userdata;
- (void)sink;
- (void)conn;
+ if (sink->conn) {
+ al_assert(sink->conn == conn);
+ sink->conn = NULL;
+ }
}
-bool camu_sink_connect(struct camu_sink *sink, str *addr, s32 port)
+bool camu_sink_connect(struct camu_sink *sink, u8 type, str *addr, u16 port, str *name)
{
+ al_str_clone(&sink->name, name);
+ sink->type = type;
aki_rpc_init(&sink->client, sink->loop, connection_callback, connection_closed_callback, sink);
for (u32 i = 0; i < AL_ARRAY_SIZE(commands); i++) {
commands[i].userdata = sink;
al_assert(sink->callback);
aki_rpc_add_command(&sink->client, &commands[i]);
}
- if (!aki_rpc_prepare_client(&sink->client, AKI_SOCKET_TCP, CAMU_MULTIPLEX_RPC)) {
+ if (!aki_rpc_prepare_client(&sink->client, sink->type, CAMU_MULTIPLEX_RPC)) {
return false;
}
aki_rpc_connect(&sink->client, addr, port);
@@ -715,14 +987,6 @@ void camu_sink_return_current(struct camu_sink *sink)
aki_mutex_unlock(&sink->mutex);
}
-void camu_sink_add(struct camu_sink *sink, str *line)
-{
- struct aki_packet *packet = aki_rpc_get_packet(&sink->client, CAMU_SERVER_LIST_ACTION);
- aki_packet_write_u8(packet, CAMU_ADD);
- aki_packet_write_str(packet, line);
- aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
-}
-
void camu_sink_skip(struct camu_sink *sink, s32 n)
{
queue_cmd(sink, (struct camu_sink_cmd){
@@ -731,14 +995,26 @@ void camu_sink_skip(struct camu_sink *sink, s32 n)
});
}
+void camu_sink_shuffle(struct camu_sink *sink)
+{
+ queue_cmd(sink, (struct camu_sink_cmd){
+ .op = SHUFFLE
+ });
+}
+
void camu_sink_toggle_pause(struct camu_sink *sink)
{
aki_mutex_lock(&sink->mutex);
struct camu_sink_entry *current = sink->current;
aki_mutex_unlock(&sink->mutex);
+ f64 pts = -1.0;
+ if (!camu_clock_is_paused(&current->clock)) {
+ pts = camu_clock_get_pts(&current->clock, 0.0);
+ }
if (current) {
queue_cmd(sink, (struct camu_sink_cmd){
.op = TOGGLE_PAUSE,
+ .value.f = pts,
.opaque = current
});
}
@@ -788,19 +1064,20 @@ void camu_sink_stop(struct camu_sink *sink)
void camu_sink_close(struct camu_sink *sink)
{
- // TODO: Make sure no commands can come in after this.
- aki_rpc_conn_disconnect(sink->client.conn);
- aki_mutex_lock(&sink->mutex);
+ if (sink->conn) aki_rpc_conn_disconnect(sink->conn);
struct camu_sink_entry *entry;
- al_array_foreach(sink->entries, i, entry) {
- shrb_client_close(&entry->client);
+ al_array_foreach_rev(sink->entries, i, entry) {
+ al_array_remove_at(sink->entries, i);
+ lia_client_disconnect(&entry->client);
}
- sink->entries.size = 0;
- aki_mutex_unlock(&sink->mutex);
}
void camu_sink_free(struct camu_sink *sink)
{
+ struct camu_sink_entry *entry;
+ al_array_foreach(sink->entries, i, entry) {
+ al_free(entry);
+ }
al_array_free(sink->entries);
aki_rpc_free(&sink->client);
camu_queue_free(sink->queue);
diff --git a/src/libsink/sink.h b/src/libsink/sink.h
index b789c9a..b9249cd 100644
--- a/src/libsink/sink.h
+++ b/src/libsink/sink.h
@@ -1,7 +1,5 @@
#pragma once
-//#define CAMU_SINK_LOCAL_PAUSE
-
#include <al/str.h>
#include <al/array.h>
#include <aki/rpc2.h>
@@ -15,7 +13,7 @@
#include "../buffer/video.h"
#endif
-#include "../shrub/client.h"
+#include "../liana/client.h"
enum {
CAMU_SINK_AUDIO = 0,
@@ -28,27 +26,32 @@ enum {
CAMU_SINK_ADD_BUFFER = 0,
CAMU_SINK_REMOVE_BUFFER,
CAMU_SINK_SWAP_BUFFER,
- CAMU_SINK_SET_BUFFERED,
CAMU_SINK_START,
CAMU_SINK_STOP,
CAMU_SINK_EXIT
};
+enum {
+ CAMU_SINK_OK = 0
+};
+
struct camu_sink_entry {
+ u16 id;
s32 sequence;
+ u16 lru;
struct camu_clock clock;
- struct shrb_client client;
+ struct lia_client client;
struct {
u8 state;
bool armed;
struct camu_audio_buffer buf;
- struct shrb_vcr_stream *stream;
+ struct lia_vcr_track *track;
} audio;
#ifndef CAMU_SINK_NO_VIDEO
struct {
u8 state;
struct camu_video_buffer buf;
- struct shrb_vcr_stream *stream;
+ struct lia_vcr_track *track;
} video;
#endif
struct camu_sink *sink;
@@ -60,22 +63,21 @@ struct camu_sink_cmd {
void *opaque;
};
-enum {
- CAMU_SINK_OK = 0,
- CAMU_SINK_BUFFERS_SWAPPED = 1
-};
-
struct camu_sink {
struct aki_event_loop *loop;
+ str name;
+ u8 type;
struct aki_rpc client;
struct aki_rpc_connection *conn;
struct aki_signal signal;
queue(struct camu_sink_cmd) queue;
struct aki_mutex mutex;
- struct camu_sink_entry *queued;
struct camu_sink_entry *current;
+ struct camu_sink_entry *queued;
+ struct camu_sink_entry *target;
array(struct camu_sink_entry *) previous;
array(struct camu_sink_entry *) entries;
+ u16 lru;
struct {
u8 state;
struct camu_mixer *mixer;
@@ -96,11 +98,11 @@ bool camu_sink_init(struct camu_sink *sink, struct aki_event_loop *loop,
, struct camu_renderer *renderer
#endif
);
-bool camu_sink_connect(struct camu_sink *sink, str *addr, s32 port);
+bool camu_sink_connect(struct camu_sink *sink, u8 type, str *addr, u16 port, str *name);
struct camu_sink_entry *camu_sink_get_current(struct camu_sink *sink);
void camu_sink_return_current(struct camu_sink *sink);
-void camu_sink_add(struct camu_sink *sink, str *line);
void camu_sink_skip(struct camu_sink *sink, s32 n);
+void camu_sink_shuffle(struct camu_sink *sink);
void camu_sink_toggle_pause(struct camu_sink *sink);
void camu_sink_seek(struct camu_sink *sink, f64 pos);
void camu_sink_reseek(struct camu_sink *sink);
diff --git a/src/list/list.c b/src/list/list.c
deleted file mode 100644
index d5be3dc..0000000
--- a/src/list/list.c
+++ /dev/null
@@ -1,257 +0,0 @@
-#include <al/random.h>
-#include <aki/thread.h>
-
-#include "../libsink/common.h"
-#include "../shrub/handler.h"
-
-#include "list.h"
-
-void camu_list_init(struct camu_list *list, str *name)
-{
- al_str_clone(&list->name, name);
- list->current = -1;
- list->prev = -1;
- list->idle = true;
- list->sequence = 0;
- al_array_init(list->entries);
- al_array_init(list->sinks);
-}
-
-static void camu_list_pump(struct camu_list *list)
-{
- s32 size = (s32)list->entries.size;
- if (list->current == -1 || size <= list->current) return;
- struct camu_list_entry *entry = al_array_at(list->entries, list->current);
- struct camu_list_entry *upcoming = NULL;
- if (list->current + 1 < size) {
- upcoming = al_array_at(list->entries, list->current + 1);
- }
- struct camu_list_entry *prev = NULL;
- if (list->prev >= 0) {
- prev = al_array_at(list->entries, list->prev);
- list->prev = -1;
- }
- struct camu_list_sink *sink;
- al_array_foreach(list->sinks, i, sink) {
- if (sink->set != list->current) {
- void *prev_opaque = NULL;
- if (prev) prev_opaque = prev->opaque;
- sink->callback(sink->userdata, CAMU_SINK_SET, entry->opaque, prev_opaque, entry->sequence);
- sink->set = list->current;
- }
- if (upcoming) {
- sink->callback(sink->userdata, CAMU_SINK_QUEUE, upcoming->opaque, entry->opaque, upcoming->sequence);
- }
- }
-}
-
-void camu_list_add_sink(struct camu_list *list, void (*callback)(void *, u8, void *, void *, s32),
- void *userdata)
-{
- struct camu_list_sink *sink = al_alloc_object(struct camu_list_sink);
- sink->set = -1;
- sink->callback = callback;
- sink->userdata = userdata;
- al_array_push(list->sinks, sink);
- camu_list_pump(list);
-}
-
-void camu_list_remove_sink(struct camu_list *list, void *userdata)
-{
- struct camu_list_sink *sink;
- al_array_foreach(list->sinks, i, sink) {
- if (sink->userdata == userdata) {
- al_array_remove_at_iter(list->sinks, i);
- break;
- }
- }
-}
-
-void camu_list_add(struct camu_list *list, void *opaque, void (*callback)(void *, u8, void *),
- u64 duration, bool set)
-{
- s32 size = (s32)list->entries.size;
- struct camu_list_entry *entry = al_alloc_object(struct camu_list_entry);
- entry->sequence = list->sequence++;
- entry->opaque = opaque;
- entry->callback = callback;
- entry->duration = duration;
- entry->start = aki_get_timestamp();
- entry->paused = false;
- if (!list->idle) {
- struct camu_list_entry *prev = al_array_at(list->entries, size - 1);
- u64 end = prev->start + prev->duration;
- if (end > entry->start) {
- entry->start += end - entry->start;
- }
- }
- al_array_push(list->entries, entry);
- if (set) list->current = size;
- else if (list->idle) {
- list->current++;
- list->idle = false;
- }
- camu_list_pump(list);
- entry->callback(entry->opaque, CAMU_LIST_IMPULSE, &entry->start);
-}
-
-static s32 index_of_entry_from_sequence(struct camu_list *list, s32 sequence)
-{
- if (sequence != CAMU_SEQUENCE_INVALID) {
- struct camu_list_entry *entry;
- al_array_foreach(list->entries, i, entry) {
- if (entry->sequence == sequence) return i;
- }
- }
- return CAMU_SEQUENCE_INVALID;
-}
-
-void camu_list_skip(struct camu_list *list, s32 sequence, s32 n)
-{
- s32 size = (s32)list->entries.size;
- s32 index = index_of_entry_from_sequence(list, sequence);
- if (index == CAMU_SEQUENCE_INVALID) index = list->current;
- if (index + n < 0 || index + n >= size || index + n == list->current) return;
- list->prev = list->current;
- struct camu_list_entry *prev = al_array_at(list->entries, list->prev);
- prev->callback(prev->opaque, CAMU_LIST_PAUSE, NULL);
- list->current = index + n;
- struct camu_list_entry *entry = al_array_at(list->entries, list->current);
- entry->start = aki_get_timestamp();
- entry->callback(entry->opaque, CAMU_LIST_IMPULSE, &entry->start);
- camu_list_pump(list);
-}
-
-void camu_list_skipto(struct camu_list *list, s32 i)
-{
- s32 size = (s32)list->entries.size;
- if (i < 0 || i >= size) return;
- list->current = i;
- camu_list_pump(list);
-}
-
-void camu_list_toggle_pause(struct camu_list *list, s32 sequence, u64 pos)
-{
- s32 index = index_of_entry_from_sequence(list, sequence);
- if (index == CAMU_SEQUENCE_INVALID) return;
- struct camu_list_entry *entry = al_array_at(list->entries, index);
- if (entry->paused) {
- u64 ts = aki_get_timestamp();
- struct shrb_resume_req req = {
- .start = ts,
- .offset = ts - entry->start
- };
- entry->start = ts;
- entry->callback(entry->opaque, CAMU_LIST_USER_RESUME, &req);
- entry->paused = false;
- } else {
- entry->start = aki_get_timestamp();
- entry->callback(entry->opaque, CAMU_LIST_USER_PAUSE, &pos);
- entry->paused = true;
- }
-}
-
-void camu_list_seek(struct camu_list *list, s32 sequence, f64 percent)
-{
- s32 index = index_of_entry_from_sequence(list, sequence);
- if (index == CAMU_SEQUENCE_INVALID) return;
- list->idle = false;
- struct camu_list_entry *entry = al_array_at(list->entries, index);
- entry->start = aki_get_timestamp();
- entry->callback(entry->opaque, CAMU_LIST_IMPULSE, &entry->start);
- u64 pos = percent * entry->duration;
- entry->callback(entry->opaque, CAMU_LIST_SEEK, &pos);
-}
-
-void camu_list_finished(struct camu_list *list, s32 sequence)
-{
- s32 size = (s32)list->entries.size;
- s32 index = index_of_entry_from_sequence(list, sequence);
- if (index == CAMU_SEQUENCE_INVALID) return;
- if (index + 1 >= size) {
- list->idle = true;
- return;
- }
- list->current = index + 1;
- camu_list_pump(list);
-}
-
-void camu_list_shuffle(struct camu_list *list)
-{
- u32 size = list->entries.size;
- if (size == 0) return;
- for (u32 i = 0; i < size - 1; i++) {
- u32 j = i + al_rand() / (AL_RAND_MAX / (size - i) + 1);
- struct camu_list_entry *tmp = al_array_at(list->entries, j);
- al_array_at(list->entries, j) = al_array_at(list->entries, i);
- al_array_at(list->entries, i) = tmp;
- }
- struct camu_list_sink *sink;
- al_array_foreach(list->sinks, i, sink) {
- sink->set = -1;
- }
- camu_list_pump(list);
-}
-
-static s64 camu_db_num_from_path(str *path)
-{
- s32 slash = al_str_rfind(path, '/');
- if (slash < 0) return -1;
- str s = *al_str_substr(path, slash + 1, path->len);
- s32 underscore = al_str_find(&s, '_');
- if (underscore < 0) return -1;
- s = *al_str_substr(&s, underscore + 1, s.len);
- underscore = al_str_find(&s, '_');
- if (underscore < 0) return -1;
- s = *al_str_substr(&s, underscore + 1, s.len);
- underscore = al_str_find(&s, '_');
- if (underscore < 0) return -1;
- s = *al_str_substr(&s, 0, underscore);
- s64 num = al_str_to_long(&s, 10);
- if (num == INT64_MIN || num == INT64_MAX) {
- return -1;
- }
- return num;
-}
-
-static s32 camu_db_compare(void *a, void *b)
-{
- struct camu_list_entry *aa = *((struct camu_list_entry **)a);
- struct camu_list_entry *bb = *((struct camu_list_entry **)b);
- str *astr = NULL, *bstr = NULL;
- aa->callback(aa->opaque, CAMU_LIST_ENTRY_ID, &astr);
- bb->callback(bb->opaque, CAMU_LIST_ENTRY_ID, &bstr);
- s64 anum = camu_db_num_from_path(astr);
- s64 bnum = camu_db_num_from_path(bstr);
- if (anum > bnum) {
- return -1;
- } else if (anum < bnum) {
- return 1;
- }
- return 0;
-}
-
-void camu_list_sort(struct camu_list *list)
-{
- al_array_sort(list->entries, struct camu_list_entry *, camu_db_compare);
- struct camu_list_sink *sink;
- al_array_foreach(list->sinks, i, sink) {
- sink->set = -1;
- }
- camu_list_pump(list);
-}
-
-void camu_list_reverse(struct camu_list *list)
-{
- u32 size = list->entries.size;
- for (u32 i = 0; i < size; i++) {
- u32 tail = size - (i + 1);
- if (tail <= i) break;
- AL_SWAP(al_array_at(list->entries, i), al_array_at(list->entries, tail), struct camu_list_entry *);
- }
- struct camu_list_sink *sink;
- al_array_foreach(list->sinks, i, sink) {
- sink->set = -1;
- }
- camu_list_pump(list);
-}
diff --git a/src/list/list.h b/src/list/list.h
deleted file mode 100644
index 4873265..0000000
--- a/src/list/list.h
+++ /dev/null
@@ -1,57 +0,0 @@
-#pragma once
-
-#include <al/str.h>
-#include <al/array.h>
-
-#define CAMU_SEQUENCE_INVALID INT32_MAX
-
-// TODO: Improve naming between this and server LIST_ACTION's
-enum {
- CAMU_LIST_ENTRY_ID = 0,
- CAMU_LIST_IMPULSE,
- CAMU_LIST_USER_PAUSE,
- CAMU_LIST_USER_RESUME,
- CAMU_LIST_PAUSE, // Pause at current position.
- CAMU_LIST_SEEK
-};
-
-struct camu_list_entry {
- s32 sequence;
- void *opaque;
- void (*callback)(void *, u8, void *);
- u64 duration;
- u64 start;
- bool paused;
-};
-
-struct camu_list_sink {
- s32 set;
- bool (*action)(void *, u8, void *);
- void (*callback)(void *, u8, void *, void *, s32);
- void *userdata;
-};
-
-struct camu_list {
- str name;
- s32 current;
- s32 prev;
- bool idle;
- s32 sequence; // Holds the next entry sequence.
- array(struct camu_list_entry *) entries;
- array(struct camu_list_sink *) sinks;
-};
-
-void camu_list_init(struct camu_list *list, str *name);
-void camu_list_add_sink(struct camu_list *list, void (*callback)(void *, u8, void *, void *, s32),
- void *userdata);
-void camu_list_remove_sink(struct camu_list *list, void *userdata);
-void camu_list_add(struct camu_list *list, void *opaque, void (*callback)(void *, u8, void *),
- u64 duration, bool set);
-void camu_list_skip(struct camu_list *list, s32 sequence, s32 n);
-void camu_list_skipto(struct camu_list *list, s32 i);
-void camu_list_toggle_pause(struct camu_list *list, s32 sequence, u64 pos);
-void camu_list_seek(struct camu_list *list, s32 sequence, f64 percent);
-void camu_list_finished(struct camu_list *list, s32 sequence);
-void camu_list_shuffle(struct camu_list *list);
-void camu_list_sort(struct camu_list *list);
-void camu_list_reverse(struct camu_list *list);
diff --git a/src/list/meson.build b/src/list/meson.build
deleted file mode 100644
index c61ac39..0000000
--- a/src/list/meson.build
+++ /dev/null
@@ -1,5 +0,0 @@
-list_src = [
- 'list.c'
-]
-list_deps = [common_deps]
-list = declare_dependency(sources: list_src, dependencies: list_deps)
diff --git a/src/mixer/audio.h b/src/mixer/audio.h
index 6624896..6e52f1a 100644
--- a/src/mixer/audio.h
+++ b/src/mixer/audio.h
@@ -2,12 +2,12 @@
#include <al/str.h>
-#include "../codec/ffmpeg/resampler.h"
+#include "../codec/codec.h"
struct camu_audio {
bool (*init)(struct camu_audio *, str *);
- void (*pick_format)(struct camu_audio *, struct camu_ff_resample_fmt *);
+ void (*pick_format)(struct camu_audio *, struct camu_resampler_format *);
bool (*configure_stream)(struct camu_audio *, void *);
u64 (*get_latency)(struct camu_audio *);
diff --git a/src/mixer/audio_miniaudio.c b/src/mixer/audio_miniaudio.c
index 26c01b2..fb4db23 100644
--- a/src/mixer/audio_miniaudio.c
+++ b/src/mixer/audio_miniaudio.c
@@ -1,13 +1,26 @@
#include <al/log.h>
-
+#if defined(__clang__) || defined(__GNUC__)
+_Pragma("GCC diagnostic push") \
+_Pragma("GCC diagnostic ignored \"-Wunused-function\"")
+_Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"")
+_Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"")
+#elif _MSC_VER
+#pragma warning(push, 0)
+#endif
#define MINIAUDIO_IMPLEMENTATION
#include "audio_miniaudio.h"
+#if defined(__clang__) || defined(__GNUC__)
+_Pragma("GCC diagnostic pop")
+#elif _MSC_VER
+#pragma warning(pop)
+#endif
static ma_backend backends[] = {
#ifndef _WIN32
ma_backend_pulseaudio,
#else
ma_backend_wasapi
+// ma_backend_dsound
#endif
};
@@ -84,32 +97,34 @@ static bool audio_miniaudio_init(struct camu_audio *audio, str *name)
return true;
}
-static enum AVSampleFormat av_sample_fmt_from_miniaudio(s32 fmt)
+static s32 camu_sample_format_from_miniaudio(s32 fmt)
{
switch (fmt) {
case ma_format_u8:
- return AV_SAMPLE_FMT_U8;
+ return CAMU_SAMPLE_FORMAT_U8;
case ma_format_s16:
- return AV_SAMPLE_FMT_S16;
+ return CAMU_SAMPLE_FORMAT_S16;
case ma_format_s24:
case ma_format_s32:
- return AV_SAMPLE_FMT_S32;
+ return CAMU_SAMPLE_FORMAT_S32;
case ma_format_f32:
- return AV_SAMPLE_FMT_FLT;
+ return CAMU_SAMPLE_FORMAT_FLT;
case ma_format_unknown:
default:
- al_assert(false);
- return AV_SAMPLE_FMT_NONE;
+ al_assert_and_return(false);
}
}
-static void audio_miniaudio_pick_format(struct camu_audio *audio, struct camu_ff_resample_fmt *fmt)
+static void audio_miniaudio_pick_format(struct camu_audio *audio, struct camu_resampler_format *fmt)
{
struct camu_audio_miniaudio *ma = (struct camu_audio_miniaudio *)audio;
- fmt->req_format = av_sample_fmt_from_miniaudio(ma->device.playback.internalFormat);
- fmt->req_channel_count = ma->device.playback.internalChannels;
- av_channel_layout_default(&fmt->req_channel_layout, fmt->req_channel_count);
- fmt->req_sample_rate = ma->device.playback.internalSampleRate;
+ fmt->req.format = camu_sample_format_from_miniaudio(ma->device.playback.format);
+ fmt->req.sample_rate = ma->device.playback.internalSampleRate;
+ u32 channels = ma->device.playback.channels;
+#ifdef CAMU_HAVE_FFMPEG
+ av_channel_layout_default(&fmt->req.channel_layout, channels);
+#endif
+ fmt->req.channel_count = channels;
}
static void data_callback(ma_device *device, void *output, const void *input, u32 frame_count)
@@ -134,8 +149,9 @@ static bool audio_miniaudio_configure_stream(struct camu_audio *audio, void *opa
ma->config.performanceProfile = ma_performance_profile_conservative;
ma->config.periods = DEFAULT_PERIODS;
ma->config.periodSizeInMilliseconds = DEFAULT_PERIOD_SIZE_IN_MILLISECONDS;
- ma->config.playback.format = ma_format_unknown;
- ma->config.playback.channels = 0;
+ //ma->config.playback.format = ma_format_unknown;
+ ma->config.playback.format = ma_format_f32;
+ ma->config.playback.channels = 2;
ma->config.sampleRate = 0;
ma->config.dataCallback = data_callback;
ma->config.pUserData = ma;
@@ -149,8 +165,8 @@ static bool audio_miniaudio_configure_stream(struct camu_audio *audio, void *opa
al_log_error("audio_miniaudio", "Failed to initialize device (%d).", ret);
return false;
}
- enum AVSampleFormat avfmt = av_sample_fmt_from_miniaudio(ma->device.playback.internalFormat);
- const char *format_name = av_get_sample_fmt_name(avfmt);
+ s32 format = camu_sample_format_from_miniaudio(ma->device.playback.internalFormat);
+ const char *format_name = camu_audio_format_name(format);
al_log_info("audio_miniaudio", "Output: %s (%dch) %dHz.", format_name,
ma->device.playback.internalChannels, ma->device.playback.internalSampleRate);
return true;
@@ -187,11 +203,14 @@ static void audio_miniaudio_free(struct camu_audio **audio)
ma_device_uninit(&ma->device);
ma_context_uninit(&ma->context);
al_free((void *)ma->context_config.pulse.pApplicationName);
+ al_free(ma);
*audio = NULL;
}
-struct camu_audio_miniaudio audio_plugin_miniaudio = {
- .a = {
+struct camu_audio *camu_audio_miniaudio_create(void)
+{
+ struct camu_audio_miniaudio *ma = al_alloc_object(struct camu_audio_miniaudio);
+ ma->a = (struct camu_audio){
.init = audio_miniaudio_init,
.pick_format = audio_miniaudio_pick_format,
.configure_stream = audio_miniaudio_configure_stream,
@@ -201,13 +220,6 @@ struct camu_audio_miniaudio audio_plugin_miniaudio = {
.free = audio_miniaudio_free,
.data_callback = NULL,
.userdata = NULL
- },
- .log = { },
- .context_config = { 0 },
- .context = { },
- .infos = NULL,
- .info_count = 0,
- .selected_device = NULL,
- .config = { 0 },
- .device = { 0 }
-};
+ };
+ return (struct camu_audio *)ma;
+}
diff --git a/src/mixer/audio_miniaudio.h b/src/mixer/audio_miniaudio.h
index 3d7f010..d93c69c 100644
--- a/src/mixer/audio_miniaudio.h
+++ b/src/mixer/audio_miniaudio.h
@@ -1,5 +1,9 @@
#pragma once
+// Include <aki/thread.h> here to ensure OS related headers
+// get included the way we want.
+// Specifically, on Windows we want to include <winsock2.h> before <windows.h>.
+#include <aki/thread.h>
#include <miniaudio.h>
#include "audio.h"
@@ -16,4 +20,4 @@ struct camu_audio_miniaudio {
ma_device device;
};
-extern struct camu_audio_miniaudio audio_plugin_miniaudio;
+struct camu_audio *camu_audio_miniaudio_create(void);
diff --git a/src/mixer/audio_null.c b/src/mixer/audio_null.c
new file mode 100644
index 0000000..ac2846a
--- /dev/null
+++ b/src/mixer/audio_null.c
@@ -0,0 +1,67 @@
+#include "audio_null.h"
+
+static bool audio_null_init(struct camu_audio *audio, str *name)
+{
+ struct camu_audio_null *na = (struct camu_audio_null *)audio;
+ (void)na;
+ (void)name;
+ return true;
+}
+
+static void audio_null_pick_format(struct camu_audio *audio, struct camu_resampler_format *fmt)
+{
+ struct camu_audio_null *na = (struct camu_audio_null *)audio;
+ (void)na;
+ fmt->req.format = fmt->in.format;
+ fmt->req.channel_count = fmt->in.channel_count;
+ av_channel_layout_default(&fmt->req.channel_layout, fmt->in.channel_count);
+ fmt->req.sample_rate = fmt->in.sample_rate;
+}
+
+static bool audio_null_configure_stream(struct camu_audio *audio, void *opaque)
+{
+ struct camu_audio_null *na = (struct camu_audio_null *)audio;
+ (void)na;
+ (void)opaque;
+ return true;
+}
+
+static u64 audio_null_get_latency(struct camu_audio *audio)
+{
+ struct camu_audio_null *na = (struct camu_audio_null *)audio;
+ (void)na;
+ return 0;
+}
+
+static void audio_null_start(struct camu_audio *audio)
+{
+ struct camu_audio_null *na = (struct camu_audio_null *)audio;
+ (void)na;
+}
+
+static void audio_null_stop(struct camu_audio *audio)
+{
+ struct camu_audio_null *na = (struct camu_audio_null *)audio;
+ (void)na;
+}
+
+static void audio_null_free(struct camu_audio **audio)
+{
+ struct camu_audio_null *na = (struct camu_audio_null *)*audio;
+ (void)na;
+ *audio = NULL;
+}
+
+struct camu_audio_null audio_plugin_null = {
+ .a = {
+ .init = audio_null_init,
+ .pick_format = audio_null_pick_format,
+ .configure_stream = audio_null_configure_stream,
+ .get_latency = audio_null_get_latency,
+ .start = audio_null_start,
+ .stop = audio_null_stop,
+ .free = audio_null_free,
+ .data_callback = NULL,
+ .userdata = NULL
+ }
+};
diff --git a/src/mixer/audio_null.h b/src/mixer/audio_null.h
new file mode 100644
index 0000000..b69cded
--- /dev/null
+++ b/src/mixer/audio_null.h
@@ -0,0 +1,7 @@
+#include "audio.h"
+
+struct camu_audio_null {
+ struct camu_audio a;
+};
+
+extern struct camu_audio_null audio_plugin_null;
diff --git a/src/mixer/meson.build b/src/mixer/meson.build
index 71c003b..ea9356c 100644
--- a/src/mixer/meson.build
+++ b/src/mixer/meson.build
@@ -1,27 +1,32 @@
mixer_src = ['mixer.c']
mixer_deps = [common_deps]
+mixer_args = []
-miniaudio = subproject('miniaudio').get_variable('miniaudio')
-mixer_src += ['audio_miniaudio.c']
-mixer_deps += [miniaudio]
-
-miniaudio_args = [
- '-DMA_ENABLE_ONLY_SPECIFIC_BACKENDS',
- '-DMA_NO_DECODING',
- '-DMA_NO_ENCODING',
- '-DMA_NO_RESOURCE_MANAGER',
- '-DMA_NO_NODE_GRAPH',
- '-DMA_NO_ENGINE',
- '-DMA_NO_GENERATION',
-]
-
-if not is_windows
- libpulse = dependency('libpulse')
- mixer_deps += [libpulse]
- miniaudio_args += ['-DMA_ENABLE_PULSEAUDIO', '-DMA_NO_RUNTIME_LINKING']
+use_miniaudio = true
+if use_miniaudio
+ miniaudio = subproject('miniaudio').get_variable('miniaudio')
+ mixer_src += ['audio_miniaudio.c']
+ mixer_deps += [miniaudio]
+ mixer_args += [
+ '-DMA_ENABLE_ONLY_SPECIFIC_BACKENDS',
+ '-DMA_NO_DECODING',
+ '-DMA_NO_ENCODING',
+ '-DMA_NO_RESOURCE_MANAGER',
+ '-DMA_NO_NODE_GRAPH',
+ '-DMA_NO_ENGINE',
+ '-DMA_NO_GENERATION',
+ ]
+ if not is_windows
+ libpulse = dependency('libpulse')
+ mixer_deps += [libpulse]
+ mixer_args += ['-DMA_ENABLE_PULSEAUDIO', '-DMA_NO_RUNTIME_LINKING']
+ else
+ mixer_args += ['-DMA_ENABLE_WASAPI']
+ #mixer_args += ['-DMA_ENABLE_DSOUND']
+ endif
else
- miniaudio_args += ['-DMA_ENABLE_WASAPI']
+ mixer_src += ['audio_null.c']
endif
mixer = declare_dependency(sources: mixer_src, dependencies: mixer_deps,
- compile_args: miniaudio_args)
+ compile_args: mixer_args)
diff --git a/src/mixer/mixer.c b/src/mixer/mixer.c
index 5caee1b..987bc8b 100644
--- a/src/mixer/mixer.c
+++ b/src/mixer/mixer.c
@@ -3,29 +3,43 @@
#include "mixer.h"
#include "audio.h"
+// Should an initial read inside of start() be marked as silence or not.
+#define MIXER_WANT_INITIAL_SILENCE 1
+
+// Number of silent frames to append before signaling MIXER_EMPTY.
+#define MIXER_TRAILING_SILENCE 100
+
static s32 data_callback(void *userdata, u8 *data, s32 frame_count, bool *silence)
{
struct camu_mixer *mixer = (struct camu_mixer *)userdata;
- size_t req = camu_ff_resample_fmt_samples_to_bytes(&mixer->fmt, (size_t)frame_count);
+ size_t req = camu_audio_format_samples_to_bytes(&mixer->fmt.req, (size_t)frame_count);
if (UNLIKELY(mixer->paused)) {
al_memset(data, 0, req);
- *silence = true;
+ *silence = MIXER_WANT_INITIAL_SILENCE;
} else {
#ifdef CAMU_MIXER_THREADED
- if (al_atomic_bool_load(&mixer->queued, AL_ATOMIC_RELAXED)) {
+ if (al_atomic_load(u8)(&mixer->queued, AL_ATOMIC_RELAXED)) {
camu_mixer_run_queue(mixer);
}
#endif
+ if (UNLIKELY(mixer->empty_after > 0)) {
+ if (--mixer->empty_after == 0) {
+ mixer->callback(mixer->userdata, CAMU_MIXER_EMPTY);
+ }
+ *silence = false;
+ } else {
+ *silence = true;
+ }
do {
- if (mixer->buffers.size == 0) {
+ if (al_array_is_empty(mixer->buffers)) {
al_memset(data, 0, req);
- *silence = true;
} else {
- struct camu_audio_buffer *buf = al_array_last(mixer->buffers);
size_t signal;
+ struct camu_audio_buffer *buf = al_array_last(mixer->buffers);
+ *silence = false;
if ((signal = camu_audio_buffer_read(buf, data, req)) < req) {
#ifdef CAMU_MIXER_THREADED
- if (al_atomic_bool_load(&mixer->queued, AL_ATOMIC_RELAXED)) {
+ if (al_atomic_load(u8)(&mixer->queued, AL_ATOMIC_RELAXED)) {
camu_mixer_run_queue(mixer);
}
#endif
@@ -33,7 +47,6 @@ static s32 data_callback(void *userdata, u8 *data, s32 frame_count, bool *silenc
req = req - signal;
continue;
}
- *silence = false;
}
break;
} while (1);
@@ -46,23 +59,20 @@ bool camu_mixer_init(struct camu_mixer *mixer, struct camu_audio *audio)
mixer->audio = audio;
mixer->audio->data_callback = data_callback;
mixer->audio->userdata = mixer;
- mixer->audio->init(mixer->audio, al_str_c("camu"));
- if (!mixer->audio->configure_stream(mixer->audio, NULL)) {
- return false;
- }
- camu_mixer_pick_format(mixer, &mixer->fmt);
+ mixer->audio->init(mixer->audio, al_str_c("cmv"));
mixer->paused = true;
+ mixer->empty_after = 0;
al_array_init(mixer->buffers);
#ifdef CAMU_MIXER_THREADED
al_array_init(mixer->add_queue);
al_array_init(mixer->rem_queue);
- al_atomic_bool_store(&mixer->queued, false, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&mixer->queued, 0, AL_ATOMIC_RELAXED);
aki_mutex_init(&mixer->mutex);
#endif
return true;
}
-void camu_mixer_pick_format(struct camu_mixer *mixer, struct camu_ff_resample_fmt *fmt)
+void camu_mixer_pick_format(struct camu_mixer *mixer, struct camu_resampler_format *fmt)
{
mixer->audio->pick_format(mixer->audio, fmt);
}
@@ -75,7 +85,7 @@ f64 camu_mixer_get_latency(struct camu_mixer *mixer)
static void add_buffer_internal(struct camu_mixer *mixer, struct camu_audio_buffer *buf)
{
#ifdef CAMU_MIXER_THREADED
- al_atomic_bool_store(&buf->ref, true, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&buf->ref, 1, AL_ATOMIC_RELAXED);
#endif
al_array_push(mixer->buffers, buf);
}
@@ -86,26 +96,14 @@ static void remove_buffer_internal(struct camu_mixer *mixer, struct camu_audio_b
al_array_foreach(mixer->buffers, i, cbuf) {
if (cbuf == buf) {
#ifdef CAMU_MIXER_THREADED
- al_atomic_bool_store(&buf->ref, false, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&buf->ref, 0, AL_ATOMIC_RELAXED);
#endif
- al_array_remove_at_iter(mixer->buffers, i);
+ al_array_remove_at(mixer->buffers, i);
break;
}
}
}
-void camu_mixer_add_buffer(struct camu_mixer *mixer, struct camu_audio_buffer *buf)
-{
-#ifdef CAMU_MIXER_THREADED
- aki_mutex_lock(&mixer->mutex);
- al_array_push(mixer->add_queue, buf);
- al_atomic_bool_store(&mixer->queued, true, AL_ATOMIC_RELAXED);
- aki_mutex_unlock(&mixer->mutex);
-#else
- add_buffer_internal(mixer, buf);
-#endif
-}
-
#ifdef CAMU_MIXER_THREADED
static void run_queue_internal(struct camu_mixer *mixer)
{
@@ -119,22 +117,43 @@ static void run_queue_internal(struct camu_mixer *mixer)
remove_buffer_internal(mixer, buf);
}
mixer->rem_queue.size = 0;
- al_atomic_bool_store(&mixer->queued, false, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&mixer->queued, 0, AL_ATOMIC_RELAXED);
+ if (al_array_is_empty(mixer->buffers)) {
+ mixer->empty_after = MIXER_TRAILING_SILENCE;
+ } else {
+ mixer->empty_after = 0;
+ }
}
#endif
+void camu_mixer_add_buffer(struct camu_mixer *mixer, struct camu_audio_buffer *buf)
+{
+#ifdef CAMU_MIXER_THREADED
+ aki_mutex_lock(&mixer->mutex);
+ al_array_push(mixer->add_queue, buf);
+ al_atomic_store(u8)(&mixer->queued, 1, AL_ATOMIC_RELAXED);
+ aki_mutex_unlock(&mixer->mutex);
+#else
+ add_buffer_internal(mixer, buf);
+ mixer->empty_after = 0;
+#endif
+}
+
void camu_mixer_remove_buffer(struct camu_mixer *mixer, struct camu_audio_buffer *buf)
{
#ifdef CAMU_MIXER_THREADED
aki_mutex_lock(&mixer->mutex);
al_array_push(mixer->rem_queue, buf);
- al_atomic_bool_store(&mixer->queued, true, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&mixer->queued, 1, AL_ATOMIC_RELAXED);
if (mixer->paused) {
run_queue_internal(mixer);
}
aki_mutex_unlock(&mixer->mutex);
#else
remove_buffer_internal(mixer, buf);
+ if (al_array_is_empty(mixer->buffers)) {
+ mixer->empty_after = MIXER_TRAILING_SILENCE;
+ }
#endif
}
@@ -167,17 +186,11 @@ void camu_mixer_resume(struct camu_mixer *mixer)
aki_mutex_lock(&mixer->mutex);
#endif
if (mixer->paused) {
-//#ifdef CAMU_MIXER_THREADED
-// bool queued = al_atomic_bool_load(&mixer->queued, AL_ATOMIC_RELAXED);
-// // Disable queue when starting audio in case start() directly calls data_callback().
-// // Locking here is necessary to 100% know the state of the audio backend at
-// // any time. This allows us to act on the value of paused, like in remove_buffer().
-// if (queued) al_atomic_bool_store(&mixer->queued, false, AL_ATOMIC_RELAXED);
-//#endif
+ // start() can internally call data_callback once before returning.
+ // In that call mixer->paused will still be true. So, we have a special case to
+ // immediately return silence and avoid any possible locking.
+ // Outputs can treat that silence as part of the stream with MIXER_WANT_INITIAL_SILENCE.
mixer->audio->start(mixer->audio);
-//#ifdef CAMU_MIXER_THREADED
-// if (queued) al_atomic_bool_store(&mixer->queued, true, AL_ATOMIC_RELAXED);
-//#endif
mixer->paused = false;
}
#ifdef CAMU_MIXER_THREADED
@@ -185,6 +198,18 @@ void camu_mixer_resume(struct camu_mixer *mixer)
#endif
}
+void camu_mixer_clear(struct camu_mixer *mixer)
+{
+#ifdef CAMU_MIXER_THREADED
+ run_queue_internal(mixer);
+ struct camu_audio_buffer *buf;
+ al_array_foreach(mixer->buffers, i, buf) {
+ al_atomic_store(u8)(&buf->ref, 0, AL_ATOMIC_RELAXED);
+ }
+#endif
+ mixer->buffers.size = 0;
+}
+
void camu_mixer_close(struct camu_mixer *mixer)
{
al_array_free(mixer->buffers);
diff --git a/src/mixer/mixer.h b/src/mixer/mixer.h
index 113db62..729657f 100644
--- a/src/mixer/mixer.h
+++ b/src/mixer/mixer.h
@@ -9,23 +9,30 @@
#include <aki/thread.h>
#endif
-#include "../codec/ffmpeg/resampler.h"
+#include "../codec/codec.h"
+
+enum {
+ CAMU_MIXER_EMPTY = 0
+};
struct camu_mixer {
struct camu_audio *audio;
+ struct camu_resampler_format fmt;
bool paused;
- struct camu_ff_resample_fmt fmt;
+ u8 empty_after;
array(struct camu_audio_buffer *) buffers;
#ifdef CAMU_MIXER_THREADED
array(struct camu_audio_buffer *) add_queue;
array(struct camu_audio_buffer *) rem_queue;
- atomic_bool queued;
+ atomic(u8) queued;
struct aki_mutex mutex;
#endif
+ void (*callback)(void *, u8);
+ void *userdata;
};
bool camu_mixer_init(struct camu_mixer *mixer, struct camu_audio *audio);
-void camu_mixer_pick_format(struct camu_mixer *mixer, struct camu_ff_resample_fmt *fmt);
+void camu_mixer_pick_format(struct camu_mixer *mixer, struct camu_resampler_format *fmt);
f64 camu_mixer_get_latency(struct camu_mixer *mixer);
void camu_mixer_add_buffer(struct camu_mixer *mixer, struct camu_audio_buffer *buf);
void camu_mixer_remove_buffer(struct camu_mixer *mixer, struct camu_audio_buffer *buf);
@@ -34,4 +41,5 @@ void camu_mixer_run_queue(struct camu_mixer *mixer);
#endif
void camu_mixer_pause(struct camu_mixer *mixer);
void camu_mixer_resume(struct camu_mixer *mixer);
+void camu_mixer_clear(struct camu_mixer *mixer);
void camu_mixer_close(struct camu_mixer *mixer);
diff --git a/src/portal/cpy/build.sh b/src/portal/cpy/build.sh
index 45a47f5..15ff4f5 100755
--- a/src/portal/cpy/build.sh
+++ b/src/portal/cpy/build.sh
@@ -1,3 +1,3 @@
#! /usr/bin/env sh
export PYTHONDONTWRITEBYTECODE=1
-python3.12 setup.py build_ext -i
+python3 setup.py build_ext -i
diff --git a/src/portal/cpy/portal.pyx b/src/portal/cpy/portal.pyx
index 3ddde80..ec33ac6 100644
--- a/src/portal/cpy/portal.pyx
+++ b/src/portal/cpy/portal.pyx
@@ -24,7 +24,7 @@ def encode_str(str):
def decode_str(str):
return str.decode('UTF-8')
-class LogOutputHook(object):
+class OutputHook(object):
def __init__(self):
self.fd = sys.__stdout__
@@ -35,7 +35,7 @@ class LogOutputHook(object):
if len(msg) > 0 and msg != '\n':
log_info(encode_str(msg))
-hook = LogOutputHook()
+hook = OutputHook()
sys.stdout = hook
sys.stderr = hook
@@ -147,8 +147,8 @@ cdef public int add_post_internal(bridge.camu_search *search, int id, unsigned i
bridge.camu_search_add_post(search, num, &cpost)
return 0
py_str_to_str(&cpost.url, encode_str(py_post.url))
- for type, date in py_post.dates.items():
- post.camu_post_add_date(&cpost, type.value, date)
+ for date in py_post.dates:
+ post.camu_post_add_date(&cpost, date.type, date.range[0], date.range[1], date.meta)
py_str_to_str(&cpost.author.unique_id, encode_str(py_post.author.unique_id))
py_str_to_wstr(&cpost.author.username, encode_str(py_post.author.username))
py_str_to_wstr(&cpost.author.display_name, encode_str(py_post.author.display_name))
diff --git a/src/portal/cpy/post.pxd b/src/portal/cpy/post.pxd
index 3f66508..aae9839 100644
--- a/src/portal/cpy/post.pxd
+++ b/src/portal/cpy/post.pxd
@@ -7,7 +7,9 @@ cdef extern from "../src/post.h":
cdef struct camu_post_date:
u8 type
- f32 timestamp
+ f32 range_start
+ f32 range_end
+ u32 meta
ctypedef struct camu_dates_array:
u32 size
@@ -57,5 +59,5 @@ cdef extern from "../src/post.h":
camu_post_ref in_reply_to
void camu_post_reset(camu_post *post)
- void camu_post_add_date(camu_post *post, u8 type, f32 timestamp)
+ void camu_post_add_date(camu_post *post, u8 type, f32 range_start, f32 range_end, u32 meta)
void camu_post_add_media(camu_post *post, u8 type, str *key, str *url, str *ext, str *thumbnail_url, str *thumbnail_ext)
diff --git a/src/portal/meson.build b/src/portal/meson.build
index 0df4366..c3bbeb4 100644
--- a/src/portal/meson.build
+++ b/src/portal/meson.build
@@ -5,8 +5,10 @@ portal_src = [
'src/packet_ext.c'
]
portal_deps = []
+portal_args = ['-DCAMU_HAVE_PORTAL']
python3_embed = import('python').find_installation('python3.12').dependency(embed: true)
portal_deps += [python3_embed]
-portal = declare_dependency(sources: portal_src, dependencies: portal_deps)
+portal = declare_dependency(sources: portal_src, dependencies: portal_deps,
+ compile_args: portal_args)
diff --git a/src/portal/patches/snscrape_auth.diff b/src/portal/patches/snscrape_auth.diff
index 67962a9..efdc6e0 100644
--- a/src/portal/patches/snscrape_auth.diff
+++ b/src/portal/patches/snscrape_auth.diff
@@ -121,7 +121,7 @@ index d1719a8..43d9076 100644
r._snscrapeObj = obj
if apiType is _TwitterAPIType.GRAPHQL and 'errors' in obj:
msg = 'Twitter responded with an error: ' + ', '.join(f'{e["name"]}: {e["message"]}' for e in obj['errors'])
-+ blockFor = 30.0
++ blockFor = 10.0
+ _logger.warn(f'Got errors waiting for {blockFor} seconds')
+ time.sleep(blockFor)
+ return False, msg
diff --git a/src/portal/py/base.py b/src/portal/py/base.py
index cc883af..7869521 100644
--- a/src/portal/py/base.py
+++ b/src/portal/py/base.py
@@ -1,21 +1,24 @@
import log
import time
+import json
import httpx
+import binascii
import threading
from enum import Enum
+from blake3 import blake3
from collections import OrderedDict
from typing import Optional, Self, Any
from post import Post, User
-USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; rv:122.0) Gecko/20100101 Firefox/122.0'
+type ParsedJson = Any
+
+USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; rv:131.0) Gecko/20100101 Firefox/131.0'
class Method(Enum):
HEAD = "HEAD"
GET = "GET"
POST = "POST"
-type ParsedJson = Any
-
class Search():
def __init__(self):
self.pages: dict[int, list[str]] = {}
@@ -59,8 +62,9 @@ class Module():
def __init__(self):
self.headers: dict[str, str] = {}
self.cookies: dict[str, str] = {}
+ self.mutex: threading.Lock = threading.Lock()
+ self.raw_responses: dict[str, str] = {}
self.unique_id_map: OrderedDict = OrderedDict()
- self.map_mutex: threading.Lock = threading.Lock()
self.session: httpx.Client = httpx.Client(follow_redirects=True, timeout=20.0, http2=True)
def init(self) -> bool:
@@ -84,6 +88,7 @@ class Module():
response = None
continue
if response.status_code == 404 or response.status_code == 403: # Shortcut 404, 403
+ log.warn(f'Returning None, {response.status_code}')
return None
if int(response.status_code / 100) != 2: # Non-200 response
log.error(f'Non-200 status code ({response.status_code}), retrying in 5 seconds...')
@@ -101,8 +106,16 @@ class Module():
break
return response
+ def add_raw_response(self, data: ParsedJson) -> str:
+ with self.mutex:
+ dump = json.dumps(data)
+ hash = blake3(dump.encode('utf-8')).digest()
+ hash_str = binascii.hexlify(hash).decode('ascii')
+ self.raw_responses[hash_str] = dump
+ return hash_str
+
def add_to_map(self, unique_id: str, item: Post | User):
- with self.map_mutex:
+ with self.mutex:
self.unique_id_map[unique_id] = item
if len(self.unique_id_map) > 10240:
self.unique_id_map.popitem(last=False)
@@ -111,7 +124,7 @@ class Module():
return None
def get_item(self, unique_id: str) -> Optional[Post | User]:
- with self.map_mutex:
+ with self.mutex:
return self.unique_id_map.get(unique_id, None)
def get_download(self, unique_id: str, key: str) -> Optional[dict[str, Any]]:
diff --git a/src/portal/py/modules/__init__.py b/src/portal/py/modules/__init__.py
index 994b25a..1a9e9ca 100644
--- a/src/portal/py/modules/__init__.py
+++ b/src/portal/py/modules/__init__.py
@@ -1,11 +1,11 @@
import config
from modules.youtube import YoutubeModule
-from modules.twitter import TwitterScrapeModule
-from modules.pixiv_app import PixivAppModule
-from modules.pixiv_web import PixivWebModule
+#from modules.twitter import TwitterScrapeModule
+#from modules.pixiv_app import PixivAppModule
+#from modules.pixiv_web import PixivWebModule
from modules.fanbox import FanboxModule
-from modules.instagram import InstagramModule
+#from modules.instagram import InstagramModule
from modules.patreon import PatreonModule
ALL_MODULES = {
@@ -13,12 +13,12 @@ ALL_MODULES = {
# 'twitter': (TwitterModule(
# config.TWITTER_ACCESS_TOKEN, config.TWITTER_ACCESS_TOKEN_SECRET,
# config.TWITTER_CONSUMER_TOKEN, config.TWITTER_CONSUMER_TOKEN_SECRET), []),
- 'twitter': (TwitterScrapeModule(config.TWITTER_COOKIES_PATH), []),
- 'pixiv_web': (PixivWebModule(config.PIXIV_SESSID, config.PIXIV_USERID), []),
+# 'twitter': (TwitterScrapeModule(config.TWITTER_COOKIES_PATH), []),
+# 'pixiv_web': (PixivWebModule(config.PIXIV_SESSID, config.PIXIV_USERID), []),
# 'pixiv_app': (PixivAppModule(config.PIXIV_REFRESH_TOKEN), []),
# 'fanbox': (FanboxModule(config.FANBOX_SESSID), []),
# 'instagram': (InstagramModule(
# config.INSTAGRAM_USER_AGENT, config.INSTAGRAM_SETTINGS_PATH,
-# config.INSTAGRAM_SESSION_ID), [])
- 'patreon': (PatreonModule(config.PATREON_SESSION_ID, config.PATREON_UUID, config.PATREON_USER_ID), [])
+# config.INSTAGRAM_SESSION_ID), []),
+# 'patreon': (PatreonModule(config.PATREON_SESSION_ID, config.PATREON_UUID, config.PATREON_USER_ID), [])
}
diff --git a/src/portal/py/modules/common.py b/src/portal/py/modules/common.py
index 62c3d5d..3b87884 100644
--- a/src/portal/py/modules/common.py
+++ b/src/portal/py/modules/common.py
@@ -1,6 +1,6 @@
from datetime import datetime, timezone
-def parse_pixiv_date(date_str: str) -> datetime:
+def reformat_pixiv_date(date_str: str) -> datetime:
rindex = date_str.rfind(':')
date_str = date_str[:rindex] + date_str[rindex + 1:]
return datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S%z")
diff --git a/src/portal/py/modules/fanbox.py b/src/portal/py/modules/fanbox.py
index 4323d6b..bf55b9d 100644
--- a/src/portal/py/modules/fanbox.py
+++ b/src/portal/py/modules/fanbox.py
@@ -3,9 +3,9 @@ import httpx
from typing import Optional, Any
from json.decoder import JSONDecodeError
from base import USER_AGENT, Search, Module, Method, ParsedJson
-from post import MediaUrl, PostType, DateType, Post, User, Image, File, Tag
+from post import Url, PostType, DateType, Post, User, Image, File, Tag
from query_parser import QueryParser
-from modules.common import parse_pixiv_date, get_current_utc_time
+from modules.common import reformat_pixiv_date, get_current_utc_time
BASE_URL = "https://api.fanbox.cc"
@@ -23,8 +23,8 @@ class FanboxBase(Search):
return None
return obj['body']
- def create_media_url(self, url: str) -> MediaUrl:
- return MediaUrl(url, url.split('.')[-1])
+ def create_media_url(self, url: str) -> Url:
+ return Url(url, url.split('.')[-1])
def create_post(self, data: ParsedJson) -> Optional[Post]:
kwargs = {}
@@ -33,12 +33,12 @@ class FanboxBase(Search):
kwargs['unique_id'] = unique_id
kwargs['raw_responses'] = {}
kwargs['raw_responses']['api'] = json.dumps(data)
- publish_date = parse_pixiv_date(data['publishedDatetime']).timestamp()
+ publish_date = reformat_pixiv_date(data['publishedDatetime']).timestamp()
kwargs['dates'] = {
DateType.CREATED: publish_date,
DateType.RETRIEVED: get_current_utc_time().timestamp()
}
- update_date = parse_pixiv_date(data['updatedDatetime']).timestamp()
+ update_date = reformat_pixiv_date(data['updatedDatetime']).timestamp()
if update_date != publish_date:
kwargs['dates'][DateType.EDITED] = update_date
user = User(unique_id=f'fanbox:u:{data['user']['userId']}', username=data['user']['name'])
@@ -57,11 +57,11 @@ class FanboxBase(Search):
if data['type'] == 'image':
text = data['body']['text']
for i, value in enumerate(data['body']['images']):
- media[str(i)] = Image(url=MediaUrl(value['originalUrl'], value['extension']), thumbnail_url=self.create_media_url(value['thumbnailUrl']))
+ media[str(i)] = Image(url=Url(value['originalUrl'], value['extension']), thumbnail_url=self.create_media_url(value['thumbnailUrl']))
elif data['type'] == 'file':
text = data['body']['text']
for i, value in enumerate(data['body']['files']):
- media[str(i)] = File(url=MediaUrl(value['url'], value['extension']), name=value['name'])
+ media[str(i)] = File(url=Url(value['url'], value['extension']), name=value['name'])
elif data['type'] == 'article':
for block in data['body']['blocks']:
if block['type'] == 'p':
@@ -71,9 +71,9 @@ class FanboxBase(Search):
elif block['type'] == 'url_embed':
text += f'embed::{block['urlEmbedId']}[]' + '\n'
for key, value in data['body']['imageMap'].items():
- media[key] = Image(url=MediaUrl(value['originalUrl'], value['extension']), thumbnail_url=self.create_media_url(value['thumbnailUrl']))
+ media[key] = Image(url=Url(value['originalUrl'], value['extension']), thumbnail_url=self.create_media_url(value['thumbnailUrl']))
for key, value in data['body']['fileMap'].items():
- media[key] = File(url=MediaUrl(value['url'], value['extension']), name=value['name'])
+ media[key] = File(url=Url(value['url'], value['extension']), name=value['name'])
kwargs['text'] = text
kwargs['media'] = media
post = Post(**kwargs)
@@ -138,7 +138,7 @@ class FanboxUser(FanboxBase):
self.errored = True
return False
self.pages[num] = []
- for item in obj['items']:
+ for item in obj:
post = self.request_post(item['id'])
if post:
self.pages[num].append(post.unique_id)
@@ -169,12 +169,20 @@ class FanboxModule(Module):
self.parser.add_command('post', FanboxPost)
self.parser.add_command('user', FanboxUser)
self.parser.add_command('supporting', FanboxSupporting)
- self.headers['Accept-Encoding'] = 'gzip, deflate, br'
+ self.headers['Accept-Encoding'] = 'gzip, deflate, br, zstd'
self.headers['Accept-Language'] = 'en-US,en;q=0.5'
+ self.headers['Alt-Used'] = 'api.fanbox.cc'
self.headers['Origin'] = 'https://www.fanbox.cc'
self.headers['Referer'] = 'https://www.fanbox.cc/'
self.headers['User-Agent'] = USER_AGENT
self.cookies['FANBOXSESSID'] = sessid
+ self.cookies['privacy_policy_agreement'] = '7'
+ self.cookies['privacy_policy_notification'] = '0'
+ self.cookies['p_ab_id'] = '0'
+ self.cookies['p_ab_id_2'] = '6'
+ self.cookies['p_ab_d_id'] = '251960935'
+ self.cookies['cf_clearance'] = ''
+ self.cookies['__cf_bm'] = ''
def search(self, query: str, *extra_args: Any) -> Optional[Search]:
return self.parser.parse_query(query)
diff --git a/src/portal/py/modules/instagram.py b/src/portal/py/modules/instagram.py
index d107566..28f7def 100644
--- a/src/portal/py/modules/instagram.py
+++ b/src/portal/py/modules/instagram.py
@@ -1,10 +1,9 @@
import os
import log
-import email.utils
from typing import Optional, Any
from pathlib import Path
from base import USER_AGENT, Module, Search
-from post import MediaUrl, PostType, DateType, Post, User, Image, Video
+from post import Url, PostType, DateType, Post, User, Image, Video
from modules.common import get_current_utc_time
from instagrapi import Client
from instagrapi.exceptions import UserNotFound, LoginRequired, ChallengeRequired
@@ -19,13 +18,13 @@ class InstagramSearch(Search):
self.page: int = 0
self.cursor: Any = None
- def create_media_url(self, url: str) -> MediaUrl:
+ def create_media_url(self, url: str) -> Url:
question = url.find('?')
if question >= 0:
ext = url[:question].split('.')[-1]
else:
ext = url.split('.')[-1]
- return MediaUrl(url, ext)
+ return Url(url, ext)
def request_page(self, num: int) -> bool:
if num >= self.page + self.REACH_LIMIT:
@@ -34,6 +33,7 @@ class InstagramSearch(Search):
for i in range(self.page, num + 1):
try:
media, self.cursor = self.module.cl.user_medias_paginated_v1(self.pk, 0, end_cursor=self.cursor)
+ #media, self.cursor = self.module.cl.user_clips_paginated_v1(self.pk, amount=12, end_cursor=self.cursor)
except LoginRequired:
self.errored = True
return False
@@ -47,10 +47,12 @@ class InstagramSearch(Search):
kwargs = {}
kwargs['type'] = PostType.POST
unique_id = 'instagram:p:{}'.format(m.id)
+ #unique_id = 'instagram:r:{}'.format(m.id)
kwargs['unique_id'] = unique_id
kwargs['raw_responses'] = {}
kwargs['raw_responses']['tile'] = m.json()
kwargs['url'] = 'https://www.instagram.com/p/{}'.format(m.code)
+ #kwargs['url'] = 'https://www.instagram.com/reel/{}'.format(m.code)
kwargs['dates'] = {
DateType.CREATED: m.taken_at.timestamp(),
DateType.RETRIEVED: get_current_utc_time().timestamp()
diff --git a/src/portal/py/modules/patreon.py b/src/portal/py/modules/patreon.py
index 40c3255..f33ec18 100644
--- a/src/portal/py/modules/patreon.py
+++ b/src/portal/py/modules/patreon.py
@@ -1,9 +1,12 @@
-import json
+import log
import httpx
+import sys
from json import JSONDecodeError
from typing import Optional, Any
+from datetime import datetime
from base import USER_AGENT, Search, Module, Method, ParsedJson
-from post import MediaUrl, PostType, DateType, Post, User, Media, Image, Animation, Tag, TagType
+from modules.common import get_current_utc_time
+from post import Date, DateType, DateMeta, Post, User, Url, Media, File
from query_parser import QueryParser
BASE_URL = 'https://www.patreon.com/api'
@@ -26,7 +29,7 @@ class PatreonBase(Search):
return None
return obj
- def search_user(self, query: str) -> Optional[User]:
+ def search_user(self, query: str) -> tuple[Optional[User], Optional[str]]:
url = f'{BASE_URL}/search'
params = {
'q': query,
@@ -37,32 +40,76 @@ class PatreonBase(Search):
}
obj = self.check_api_response(self.module.do_request(Method.GET, url, params=params))
if not obj:
- return None
+ return None, None
for result in obj['data']:
+ username = result['attributes']['creator_name']
+ display_name = result['attributes']['name']
+ # Only accept exact matches.
+ if username.lower() != query.lower() and display_name.lower() != query.lower():
+ continue
prefix = result['id'].find('campaign_')
if prefix >= 0:
campaign = result['id'][prefix + len('campaign_'):]
unique_id = 'patreon:c:' + campaign
- user = User(unique_id=unique_id, username=result['attributes']['creator_name'], display_name=result['attributes']['name'])
- return user
- return None
+ user = User(unique_id=unique_id, username=username, display_name=display_name)
+ return user, campaign
+ return None, None
class PatreonUser(PatreonBase):
def __init__(self, userdata: Any, arg: str):
super().__init__(userdata)
self.username: str = arg
self.user: Optional[User] = None
- self.campaign: str
+ self.campaign: Optional[str] = None
+ self.cursor: Optional[str] = None
+ self.attachments: dict[str, Media] = {}
+
+ # post_type
+ # image_file: https://www.patreon.com/posts/emma-again-29391136
+ # text_only: https://www.patreon.com/posts/hello-update-29391184
+
+ def create_post(self, data: ParsedJson, hash: str) -> Optional[Post]:
+ kwargs = {}
+ unique_id = f'patreon:p:{data['id']}'
+ kwargs['unique_id'] = unique_id
+ kwargs['raw_responses'] = {}
+ kwargs['raw_responses']['api'] = hash
+ kwargs['url'] = data['attributes']['url']
+ published_at = datetime.strptime(data['attributes']['published_at'], "%Y-%m-%dT%H:%M:%S.%f%z").timestamp()
+ current_time = get_current_utc_time().timestamp()
+ kwargs['dates'] = [
+ Date(DateType.CREATED, (published_at, published_at), DateMeta.NONE),
+ Date(DateType.RETRIEVED, (current_time, current_time), DateMeta.NONE),
+ ]
+ kwargs['author'] = self.user
+ kwargs['title'] = data['attributes']['title']
+ kwargs['likes'] = data['attributes']['like_count']
+ kwargs['comments'] = data['attributes']['comment_count']
+ kwargs['text'] = data['attributes']['content']
+ post_type = data['attributes']['post_type']
+ media = {}
+ #if 'access_rules' in data['relationships']:
+ # if not (len(data['relationships']['access_rules']['data']) == 1 and data['relationships']['access_rules']['data'][0]['id'] == '9816126'):
+ # return None
+ if 'media' in data['relationships']:
+ for m in data['relationships']['media']['data']:
+ media[m['id']] = self.attachments[m['id']]
+ if 'attachments' in data['relationships']:
+ for a in data['relationships']['attachments']['data']:
+ media[a['id']] = self.attachments[a['id']]
+ kwargs['media'] = media
+ post = Post(**kwargs)
+ self.module.add_to_map(unique_id, post)
+ return post
def request_page(self, num: int) -> bool:
if num >= self.page + self.REACH_LIMIT:
return False
if not self.user:
- user = self.search_user(self.username)
- if not user:
+ self.user, self.campaign = self.search_user(self.username)
+ if not self.user:
self.errored = True
return False
- self.campaign = user.unique_id[len('patreon:c:'):]
request_satisfied = False
for i in range(self.page, num + 1):
url = f'{BASE_URL}/posts'
@@ -81,7 +128,7 @@ class PatreonUser(PatreonBase):
'filter[contains_exclusive_posts]': 'true',
'filter[is_draft]': 'false',
'filter[accessible_by_user_id]': self.module.user_id,
- 'sort': '-published_at',
+ 'sort': 'published_at',
'json-api-version': '1.0',
'json-api-use-default-includes': 'false'
}
@@ -91,8 +138,27 @@ class PatreonUser(PatreonBase):
if not obj:
self.errored = True
return False
- print(json.dumps(obj, indent=4))
- return True
+ hash = self.module.add_raw_response(obj)
+ self.cursor = obj['meta']['pagination']['cursors']['next']
+ self.pages[i] = []
+ for inc in obj['included']:
+ if inc['type'] == 'attachment':
+ self.attachments[inc['id']] = File(url=Url(inc['attributes']['url']), name=inc['attributes']['name'])
+ elif inc['type'] == 'media':
+ self.attachments[inc['id']] = Media(url=Url(inc['attributes']['download_url']), thumbnail_url=Url(inc['attributes']['image_urls']['thumbnail']))
+ else:
+ log.error(f'Unhandled inc type {inc['type']}.')
+ for entry in obj['data']:
+ if entry['type'] != 'post':
+ log.error(f'Unhandled entry type {entry['type']}.')
+ continue
+ post = self.create_post(entry, hash)
+ if post:
+ self.pages[i].append(post.unique_id)
+ self.page += 1
+ if i == num:
+ request_satisfied = True
+ return request_satisfied
class PatreonModule(Module):
def __init__(self, session_id: str, uuid: str, user_id: str):
@@ -100,7 +166,8 @@ class PatreonModule(Module):
self.user_id: str = user_id
self.parser: QueryParser = QueryParser(self, 'user')
self.parser.add_command('user', PatreonUser)
- self.headers['Accept-Encoding'] = 'gzip, deflate, br'
+ self.headers['User-Agent'] = USER_AGENT
+ self.headers['Accept-Encoding'] = 'gzip, deflate, br, zstd'
self.headers['Accept-Language'] = 'en-US,en;q=0.5'
self.headers['x-patreon-uuid'] = uuid
self.cookies['patreon_locale_code'] = 'en-US'
@@ -116,9 +183,4 @@ class PatreonModule(Module):
post = self.unique_id_map[unique_id]
if key not in post.media:
return None
- return {
- 'urls': [post.media[key].url],
- 'headers': {
- 'User-Agent': USER_AGENT
- }
- }
+ return { 'urls': [post.media[key].url], 'headers': self.headers, 'cookies': self.cookies }
diff --git a/src/portal/py/modules/pixiv_app.py b/src/portal/py/modules/pixiv_app.py
index c84030d..ef5886d 100644
--- a/src/portal/py/modules/pixiv_app.py
+++ b/src/portal/py/modules/pixiv_app.py
@@ -1,10 +1,10 @@
import json
from typing import Optional, Any
from base import USER_AGENT, Search, Module, ParsedJson
-from post import MediaUrl, PostType, DateType, User, Post, Image, Tag, TagType
+from post import Url, PostType, DateType, User, Post, Image, Tag, TagType
from query_parser import QueryParser
-from pixivpy3 import *
-from modules.common import get_current_utc_time, parse_pixiv_date
+from pixivpy3 import AppPixivAPI
+from modules.common import get_current_utc_time, reformat_pixiv_date
class PixivAppBase(Search):
def __init__(self, userdata: Any):
@@ -33,8 +33,8 @@ class PixivAppBase(Search):
self.page += 1
return request_satisfied
- def create_media_url(self, url: str) -> MediaUrl:
- return MediaUrl(url, url.split('.')[-1])
+ def create_media_url(self, url: str) -> Url:
+ return Url(url, url.split('.')[-1])
def create_tag(self, data: ParsedJson) -> Tag:
tag = Tag(type=TagType.GENERAL, name=data['name'])
@@ -58,7 +58,7 @@ class PixivAppBase(Search):
kwargs['raw_responses']['api'] = json.dumps(data)
kwargs['url'] = 'https://www.pixiv.net/en/artworks/{}'.format(data['id'])
kwargs['dates'] = {
- DateType.CREATED: parse_pixiv_date(data['create_date']).timestamp(),
+ DateType.CREATED: reformat_pixiv_date(data['create_date']).timestamp(),
DateType.RETRIEVED: get_current_utc_time().timestamp()
}
user = User(unique_id=f'pixiv:u:{data['user']['id']}', username=data['user']['account'], display_name=data['user']['name'])
diff --git a/src/portal/py/modules/pixiv_web.py b/src/portal/py/modules/pixiv_web.py
index be68b7e..7dbb8bb 100644
--- a/src/portal/py/modules/pixiv_web.py
+++ b/src/portal/py/modules/pixiv_web.py
@@ -5,9 +5,9 @@ import urllib.parse
from typing import Optional, Any
from json.decoder import JSONDecodeError
from base import USER_AGENT, Search, Module, Method, ParsedJson
-from post import MediaUrl, PostType, DateType, Post, User, Media, Image, Animation, Tag, TagType
+from post import Url, PostType, DateType, Post, User, Media, Image, Animation, Tag, TagType
from query_parser import QueryParser
-from modules.common import parse_pixiv_date, get_current_utc_time
+from modules.common import reformat_pixiv_date, get_current_utc_time
BASE_URL = 'https://www.pixiv.net/ajax'
LANG = 'en'
@@ -30,8 +30,8 @@ class PixivWebBase(Search):
return None
return obj['body']
- def create_media_url(self, url: str) -> MediaUrl:
- return MediaUrl(url, url.split('.')[-1])
+ def create_media_url(self, url: str) -> Url:
+ return Url(url, url.split('.')[-1])
def parse_pages(self, data: ParsedJson) -> dict[str, Media]:
media = {}
@@ -45,7 +45,7 @@ class PixivWebBase(Search):
animation.frames.append((frame['file'], frame['delay']))
return animation
- def seek_profile_picture_url(self, user_illusts: ParsedJson) -> Optional[MediaUrl]:
+ def seek_profile_picture_url(self, user_illusts: ParsedJson) -> Optional[Url]:
for illust in user_illusts.values():
if illust is None:
continue
@@ -71,14 +71,15 @@ class PixivWebBase(Search):
kwargs['raw_responses'] = {}
kwargs['raw_responses']['api'] = json.dumps(data)
kwargs['url'] = f'https://www.pixiv.net/{LANG}/artworks/{data['illustId']}'
- create_date = parse_pixiv_date(data['createDate']).timestamp()
+ create_date = reformat_pixiv_date(data['createDate']).timestamp()
+ current_date = get_current_utc_time().timestamp()
kwargs['dates'] = {
- DateType.CREATED: create_date,
- DateType.RETRIEVED: get_current_utc_time().timestamp()
+ DateType.CREATED: (create_date, create_date, 0),
+ DateType.RETRIEVED: (current_date, current_date, 0)
}
- upload_date = parse_pixiv_date(data['uploadDate']).timestamp()
+ upload_date = reformat_pixiv_date(data['uploadDate']).timestamp()
if upload_date != create_date:
- kwargs['dates'][DateType.EDITED] = upload_date
+ kwargs['dates'][DateType.EDITED] = (upload_date, upload_date, 0)
user = User(unique_id=f'pixiv:u:{data['userId']}', username=data['userAccount'], display_name=data['userName'])
profile_picture_url = self.seek_profile_picture_url(data['userIllusts'])
if profile_picture_url:
@@ -242,10 +243,10 @@ class PixivWebModule(Module):
self.parser.add_command('illust', PixivWebIllust)
self.parser.add_command('user', PixivWebUser)
self.parser.add_command('following', PixivWebFollowing)
- self.headers['Accept-Encoding'] = 'gzip, deflate, br' # add 'zstd' when httpx update
+ self.headers['User-Agent'] = USER_AGENT
+ self.headers['Accept-Encoding'] = 'gzip, deflate, br, zstd'
self.headers['Accept-Language'] = 'en-US,en;q=0.5'
self.headers['Referer'] = 'https://www.pixiv.net/'
- self.headers['User-Agent'] = USER_AGENT
self.headers['x-user-id'] = user_id
self.cookies['PHPSESSID'] = sessid
diff --git a/src/portal/py/modules/twitter.py b/src/portal/py/modules/twitter.py
index 2996d0e..386bc5b 100644
--- a/src/portal/py/modules/twitter.py
+++ b/src/portal/py/modules/twitter.py
@@ -3,7 +3,7 @@ import http.cookiejar
from typing import Optional, Any, Iterator
from datetime import timezone
from base import USER_AGENT, Search, Module
-from post import MediaUrl, PostType, DateType, PostRef, Post, User, Media, Image, Video
+from post import Url, PostType, DateType, PostRef, Post, User, Media, Image, Video
from query_parser import QueryParser
from modules.common import get_current_utc_time
@@ -26,7 +26,7 @@ class TwitterScrapeBase(Search):
self.iterator: Iterator[Tweet | TweetRef | Tombstone]
self.page: int = 0
- def create_media_url(self, url: str) -> MediaUrl:
+ def create_media_url(self, url: str) -> Url:
format = url.find('format=')
if format >= 0:
ext = url[format + 7:format + 10]
@@ -35,7 +35,7 @@ class TwitterScrapeBase(Search):
if question >= 0:
url = url[0:question]
ext = url.split('.')[-1]
- return MediaUrl(url, ext)
+ return Url(url, ext)
def parse_media(self, data: list[twitter.Medium]) -> dict[str, Media]:
media = {}
diff --git a/src/portal/py/modules/youtube.py b/src/portal/py/modules/youtube.py
index d14dfe8..f809ff6 100644
--- a/src/portal/py/modules/youtube.py
+++ b/src/portal/py/modules/youtube.py
@@ -1,7 +1,8 @@
import log
+import json
from typing import Optional, Any
from base import Search, Module, ParsedJson
-from post import MediaUrl, Post, PostType, Media, Video
+from post import Url, Post, PostType, Media, Video
from query_parser import QueryParser
from yt_dlp import YoutubeDL
@@ -25,65 +26,53 @@ ydl_opts = {
'quiet': False,
'logger': YDLLogger(),
'cachedir': False,
-# 'cookiefile': '',
+ 'socket_timeout': 10
+# 'cookiefile': ''
}
-ydl = YoutubeDL(ydl_opts)
-
-def get_playback_url(data, video=True):
- if 'entries' in data:
- if len(data['entries']) == 0:
- return None
- data = data['entries'][0]
+# https://github.com/yt-dlp/yt-dlp/issues/4103
- if 'formats' not in data:
- if 'url' in data:
- return data['url']
- return None
+ydl = YoutubeDL(ydl_opts)
- # Filter out hls temporarily.
- data['formats'] = list(filter(lambda f: not f['protocol'].startswith('m3u8'), data['formats']))
+def get_playback_url(data, video=True) -> Optional[str]:
+ data['formats'] = list(filter(
+ lambda f: not f['protocol'].startswith('m3u8'), data['formats']))
if len(data['formats']) == 0:
return None
- url = data['formats'][0]['url']
+ result = data['formats'][-1]['url']
- # audio_ext?
- has_audio = list(filter(lambda f: 'acodec' not in f or f['acodec'] != 'none', data['formats']))
+ has_audio = list(filter(lambda f:
+ (f['acodec'] and f['acodec'] != 'none') or
+ (f['audio_ext'] and f['audio_ext'] != 'none') or
+ (f['abr']), data['formats']))
if video:
- if len(has_audio) > 0:
- data['formats'] = has_audio
- try:
- data['formats'] = list(filter(lambda f: 'quality' in f, data['formats']))
- url = max(data['formats'], key=lambda f: f['quality'])['url']
- except:
- pass
+ data['formats'] = list(filter(lambda f:
+ (f['vcodec'] and f['vcodec'] != 'none') or
+ (f['video_ext'] and f['video_ext'] != 'none') or
+ (f['vbr']), has_audio))
else:
- if len(has_audio) == 0:
- return None
- data['formats'] = has_audio
- try:
- audio_only = list(filter(lambda f: f['vcodec'] == 'none', data['formats']))
- if len(audio_only) > 0:
- data['formats'] = audio_only
- else:
- data['formats'] = list(filter(lambda f: f['ext'] in ['mp4'], data['formats']))
- except:
- pass
- try:
- data['formats'] = list(filter(lambda f: 'abr' in f, data['formats']))
- url = max(data['formats'], key=lambda f: f['abr'])['url']
- except:
- pass
+ data['formats'] = list(filter(lambda f:
+ (f['vcodec'] and f['vcodec'] == 'none') and
+ (f['video_ext'] and f['video_ext'] == 'none') and
+ (not f['vbr']), has_audio))
+
+ if len(data['formats']) > 0:
+ selection = max(data['formats'], key=lambda f: 0 if 'quality' not in f else f['quality'])
+ log.info(json.dumps(selection, indent=4))
+ result = selection['url']
+ else:
+ log.warn('Defaulting in Youtube get_playback_url().')
- return url
+ return result
class YoutubeBase(Search):
def __init__(self, userdata: Any):
super().__init__()
self.module: YoutubeModule = userdata
+ self.guessed_index: int = 0
def get_info(self) -> Optional[ParsedJson]:
return None
@@ -92,11 +81,25 @@ class YoutubeBase(Search):
info = self.get_info()
if not info:
return False
- url = get_playback_url(info)
+ if 'entries' in info:
+ if len(info['entries']) == 0:
+ return False
+ if len(info['entries']) <= self.guessed_index:
+ self.guessed_index = 0
+ info = info['entries'][self.guessed_index]
+ if 'formats' not in info and 'url' in info:
+ self.link = info['url']
+ info = self.get_info()
+ if not info:
+ return False
+ if 'direct' in info and info['direct']:
+ url = info['url']
+ else:
+ url = get_playback_url(info, video=False)
if not url:
return False
- media: dict[str, Media] = { '0': Video(url=MediaUrl(url=url)) }
unique_id = f'youtube:v:{info['id']}'
+ media: dict[str, Media] = { '0': Video(url=Url(url)) }
self.module.add_to_map(unique_id, Post(type=PostType.POST, unique_id=unique_id, url='', title=info['title'], text='', media=media))
self.pages[num] = [unique_id]
return True
@@ -107,7 +110,13 @@ class YoutubeSearch(YoutubeBase):
self.query: str = arg
def get_info(self) -> Optional[ParsedJson]:
- return ydl.extract_info(f'ytsearch1:{self.query}', download=False)
+ ydl.params['extract_flat'] = False
+ try:
+ info = ydl.extract_info(f'ytsearch1:{self.query}', download=False)
+ except Exception as e:
+ log.error(repr(e))
+ return None
+ return info
class YoutubeLink(YoutubeBase):
def __init__(self, userdata: Any, arg: str):
@@ -115,7 +124,24 @@ class YoutubeLink(YoutubeBase):
self.link: str = arg
def get_info(self) -> Optional[ParsedJson]:
- return ydl.extract_info(self.link, download=False)
+ index = self.link.find('&index=')
+ if index >= 0:
+ sub = self.link[index + 7:]
+ end = sub.find('&')
+ if end >= 0:
+ sub = sub[:end]
+ try:
+ self.guessed_index = int(sub, 10) - 1
+ except ValueError:
+ pass
+ log.info(f'Guessed index: {self.guessed_index}')
+ ydl.params['extract_flat'] = 'in_playlist'
+ try:
+ info = ydl.extract_info(self.link, download=False)
+ except Exception as e:
+ log.error(repr(e))
+ return None
+ return info
class YoutubeModule(Module):
def __init__(self):
diff --git a/src/portal/py/post.py b/src/portal/py/post.py
index 8149e4c..76e82b9 100644
--- a/src/portal/py/post.py
+++ b/src/portal/py/post.py
@@ -1,22 +1,38 @@
import dataclasses
from dataclasses import field
from typing import Optional, Any
-from enum import Enum
+from enum import IntEnum, IntFlag
from json import JSONEncoder
-class PostType(int, Enum):
+'''
+List of Things:
+ - Social media posts
+ - Albums
+ - Loose files
+'''
+
+class PostType(IntEnum):
UNKNOWN = 0
POST = 1
REPOST = 2
PREVIEW = 3
TOMBSTONE = 4
-class DateType(int, Enum):
- CREATED = 0
- EDITED = 1
- RETRIEVED = 2
+class DateType(IntEnum):
+ UNKNOWN = 0
+ CREATED = 1
+ EDITED = 2
+ RETRIEVED = 3
+
+class DateMeta(IntFlag):
+ NONE = 0
+ ESTIMATE = 1
+ GUESS = 1 << 1
+ LOOSE_PRECISION = 1 << 2
+ EDITED_AT_UNKNOWN_TIME = 1 << 3
+ ASSUMED_TYPE = 1 << 4
-class MediaType(int, Enum):
+class MediaType(IntEnum):
UNKNOWN = 0
FILE = 1
AUDIO = 2
@@ -25,7 +41,7 @@ class MediaType(int, Enum):
VIDEO_SPLIT = 5
ANIMATION = 6
-class TagType(int, Enum):
+class TagType(IntEnum):
UNKNOWN = 0
GENERAL = 1
ARTIST = 2
@@ -37,15 +53,33 @@ class TagType(int, Enum):
def df(c: Any) -> Any:
return field(default_factory=lambda: c)
+def default_url_ext_guess(url):
+ # TODO: List of known extensions.
+ question = url.rfind('?')
+ if question >= 0:
+ guess = url[0:question].rsplit('.')[-1]
+ else:
+ guess = url.rsplit('.')[-1]
+ return guess
+
@dataclasses.dataclass
-class MediaUrl():
- url: str = ''
- ext: str = 'unknown'
+class Url():
+ url: str
+ ext: str
+
+ def __init__(self, url: str, ext: str = 'unknown'):
+ self.url = url
+ ext_guess = default_url_ext_guess(url)
+ if ext_guess:
+ self.ext = ext_guess
+ else:
+ self.ext = ext
@dataclasses.dataclass
class Media():
type: MediaType = MediaType.UNKNOWN
- url: MediaUrl = df(MediaUrl())
+ url: Url = df(Url(''))
+ thumbnail_url: Url = df(Url(''))
@dataclasses.dataclass
class File(Media):
@@ -59,24 +93,20 @@ class Audio(Media):
@dataclasses.dataclass
class Image(Media):
type: MediaType = MediaType.IMAGE
- thumbnail_url: MediaUrl = df(MediaUrl())
@dataclasses.dataclass
class Video(Media):
type: MediaType = MediaType.VIDEO
- thumbnail_url: MediaUrl = df(MediaUrl())
@dataclasses.dataclass
class VideoSplit(Media):
type: MediaType = MediaType.VIDEO_SPLIT
- audio_url: MediaUrl = df(MediaUrl())
- subtitle_url: MediaUrl = df(MediaUrl())
- thumbnail_url: MediaUrl = df(MediaUrl())
+ audio_url: Url = df(Url(''))
+ subtitle_url: Url = df(Url(''))
@dataclasses.dataclass
class Animation(Media):
type: MediaType = MediaType.ANIMATION
- thumbnail_url: MediaUrl = df(MediaUrl())
frames: list[tuple[str, int]] = df([])
@dataclasses.dataclass
@@ -85,31 +115,59 @@ class Tag():
name: str = ''
alts: dict[str, str] = df({})
+'''
+Date(DateType.CREATED, (1396411200.0, 1396756800.0), DateMeta.ESTIMATE)
+ - Post is estimated to have been created at some point between April 2nd and 6th 2014.
+
+Date(DateType.EDITED, (1396411200.0, 1550247862.0), DateMeta.EDITED_AT_UNKNOWN_TIME)
+ - We have no clue when the post was edited, but it had to have been between the time it was created and now.
+ - EDITED_AT_UNKNOWN_TIME should be used when either of CREATED and/or RETRIEVED are used as placeholders.
+ It can be combined with GUESS or ESTIMATE if, for example, there is some indication about the end of the range.
+
+Date(DateType.RETRIEVED, (1550247862.0, 1550247862.0), DateMeta.NONE)
+ - Time of processing this post. Will likely be slightly after it was actually retrived but that is negligible.
+ - RETRIEVED can still have a range and meta if applicable.
+
+Date(DateType.CREATED, (1483228800.0, 1546300800.0), DateMeta.LOOSE_PRECISION)
+ - Date is exactly between "01 Jan 2017 12:00:00 AM UTC" and "01 Jan 2019 12:00:00 AM UTC".
+ Combined with LOOSE_PRECISION, the application will assume a meaning of "between 2017 and 2019".
+
+Date(DateType.CREATED, (1145059200.0, 1145059200.0), DateMeta.ASSUMED_TYPE)
+ - The date of April 15th 2006 is associated with the resource. We are assuming that's the creation date.
+'''
+@dataclasses.dataclass
+class Date():
+ type: DateType = DateType.UNKNOWN
+ range: tuple[float, float] = (0.0, 0.0)
+ meta: DateMeta = DateMeta.NONE
+ note: str = ''
+
@dataclasses.dataclass
class User():
unique_id: str = ''
username: str = ''
display_name: str = ''
- profile_picture_url: MediaUrl = df(MediaUrl())
+ profile_picture_url: Url = df(Url(''))
@dataclasses.dataclass
class PostRef():
unique_id: str = ''
-# V4 Ideas:
-# - Date estimate and range
-# - Edited but unknown when
-# - Generally an estimate/guess
-# - Formated text/body
-
+'''
+V4:
+ - Improved date.
+ - Ends when all current modules are ported and/or completed.
+V5:
+ - Formatted body/text.
+'''
@dataclasses.dataclass
class Post():
- version: int = 3
+ version: int = 4
type: PostType = PostType.UNKNOWN
unique_id: str = ''
raw_responses: dict[str, str] = df({})
url: str = ''
- dates: dict[DateType, float] = df({})
+ dates: list[Date] = df([])
author: User = df(User())
title: str = ''
text: str = ''
@@ -128,8 +186,4 @@ class Post():
class PostEncoder(JSONEncoder):
def default(self, o):
- if isinstance(o, Enum):
- return o.value
- if dataclasses.is_dataclass(o):
- return dataclasses.asdict(o)
return o.__dict__
diff --git a/src/portal/py/test.py b/src/portal/py/test.py
new file mode 100644
index 0000000..c5687b1
--- /dev/null
+++ b/src/portal/py/test.py
@@ -0,0 +1 @@
+import tests.test
diff --git a/src/portal/py/tests/__init__.py b/src/portal/py/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/portal/py/tests/__init__.py
diff --git a/src/portal/tests/archive_query.py b/src/portal/py/tests/archive_query.py
index 9f69c1f..1097634 100644
--- a/src/portal/tests/archive_query.py
+++ b/src/portal/py/tests/archive_query.py
@@ -5,13 +5,13 @@ import json
import threading
import gzip
from datetime import datetime, timezone
-sys.path.append('../py')
+sys.path.append('../')
from base import Method
from post import PostEncoder, PostType, DateType, User
from modules import ALL_MODULES
from logger import Logger
-mode = 'instagram'
+mode = 'fanbox'
cmd = 'user'
module = ALL_MODULES[mode][0]
if not module.init():
@@ -66,8 +66,8 @@ class QueryDownloadThread(threading.Thread):
else:
with open(path, open_method) as f:
f.write(content)
- except:
- self.log.write(f'ERROR: Writing file failed ({path}).')
+ except Exception as e:
+ self.log.write(f'ERROR: Writing file failed: {path} ({repr(e)}).')
return False
return True
@@ -168,18 +168,19 @@ class QueryDownloadThread(threading.Thread):
with queue_cond:
queue_cond.notify()
+#RUNTIME_PATH = "./run"
RUNTIME_PATH = "/mnt/store/files/tmp/run"
-LOG_FILE = f'{RUNTIME_PATH}/archive.log'
-ARG_FILE = f'{RUNTIME_PATH}/completed_args.log'
+LOG_FILE = f'{RUNTIME_PATH}/archive3.log'
+ARG_FILE = f'{RUNTIME_PATH}/completed_args3.log'
def read_completed_args(path):
args = []
if os.path.isfile(path):
with open(path, 'r') as f:
- for l in f.read().splitlines():
- if l[0] == '-':
+ for line in f.read().splitlines():
+ if line[0] == '-':
continue
- s = l.split(':')
+ s = line.split(':')
if s[3] == 'c':
args.append(f'{s[0]}:{s[1]}:{s[2]}')
return args
@@ -203,7 +204,7 @@ def download_args():
os.mkdir(output_dir)
#args = module.search(f'following:{22781328}')
- args = ['byjoshuajamal']
+ args = ['anoh223']
threads = []
start = True
diff --git a/src/portal/tests/logger.py b/src/portal/py/tests/logger.py
index 80e22da..80e22da 100644
--- a/src/portal/tests/logger.py
+++ b/src/portal/py/tests/logger.py
diff --git a/src/portal/tests/old_twitter_api.py b/src/portal/py/tests/old_twitter_api.py
index 40d0346..40d0346 100644
--- a/src/portal/tests/old_twitter_api.py
+++ b/src/portal/py/tests/old_twitter_api.py
diff --git a/src/portal/tests/rewrite_post.py b/src/portal/py/tests/rewrite_post.py
index 3bb152b..3bb152b 100644
--- a/src/portal/tests/rewrite_post.py
+++ b/src/portal/py/tests/rewrite_post.py
diff --git a/src/portal/py/tests/test.py b/src/portal/py/tests/test.py
new file mode 100644
index 0000000..bee7265
--- /dev/null
+++ b/src/portal/py/tests/test.py
@@ -0,0 +1,103 @@
+import json
+import httpx
+import os.path
+from base import Method
+from typing import Optional, Any
+from post import DateType, DateMeta, Date, PostEncoder, Post, MediaType
+from modules import ALL_MODULES
+#from twitter.scraper import Scraper
+
+#scraper = Scraper(cookies = {
+# 'ct0': '5a3c98b7b50a74dc556fa497f497932e8958b5b2ef7ba74440ce3f341bb4753f663d3fe7633222d62a52d4ce121673234b927d97eeef806129d42bbfba7ab4d4cfbd2749c20fe6f8da480d0a1e6629e1',
+# 'auth_token': 'ea66b4c2ebd04e22de855dce6f1366221635bf2e'
+#})
+#
+#media = scraper.media([1557919548904419328], limit=10)
+#print(media)
+
+#output = '/mnt/store/files/ext/patreon_tmp/FPSBlyck'
+#
+#module = ALL_MODULES['patreon'][0]
+#module.init()
+#search = module.search('FPSBlyck')
+#page_num = 0
+#mark = False
+#while not mark:
+# page = search.get_page(page_num)
+# if not page:
+# break
+# page_num += 1
+# for unique_id in page:
+# if unique_id == 'patreon:p:29707872':
+# print('hit mark')
+# mark = True
+# post = module.get_item(unique_id)
+# print(json.dumps(post, indent=4, cls=PostEncoder))
+# for key, m in post.media.items():
+# if m.type == MediaType.FILE:
+# path = f'{output}/{post.unique_id.replace(':', '_')}_{m.name}'
+# if not os.path.isfile(path):
+# dl = module.get_download(post.unique_id, key)
+# retries = 3
+# r: Optional[httpx.Response] = None
+# while retries >= 0:
+# try:
+# r = httpx.get(dl['urls'][0].url, headers=dl['headers'], cookies=dl['cookies'], follow_redirects=True)
+# except:
+# retries -= 1
+# else:
+# break
+# if r:
+# with open(path, 'wb+') as f:
+# f.write(r.content)
+# else:
+# print('Download failed')
+# else:
+# print('Skipping file')
+
+ALL_MODULES['youtube'][0].init()
+search = ALL_MODULES['youtube'][0].search('yoyoyoy')
+page = search.get_page(0)
+print(page)
+#print(json.dumps(page, indent=4, cls=PostEncoder))
+
+#d = Date(DateType.EDITED, (0.0, 0.0), DateMeta.EDITED_AT_UNKNOWN_TIME)
+#d = Date(DateType.EDITED, (0.0, 0.0), DateMeta.NONE)
+#print(json.dumps(d, indent=4, cls=PostEncoder))
+#sys.exit(1)
+
+#module = ALL_MODULES['fanbox'][0]
+#module = ALL_MODULES['pixiv_web'][0]
+#module = ALL_MODULES['youtube'][0]
+#module = ALL_MODULES['twitter'][0]
+#module.init()
+
+#search = module.search('opium_00pium')
+#search = module.search('search:ddd')
+#page = search.get_page(0)
+#for unique_id in page:
+# post = module.get_item(unique_id)
+# print(json.dumps(post, indent=4, cls=PostEncoder))
+# break
+
+#post = module.get_item(page[0])
+#for key in post.media.keys():
+# download_params = module.get_download(post.unique_id, key)
+# for url in download_params['urls']:
+# #response = module.do_request(Method.GET, url.url)
+# r = httpx.get(url.url, headers=download_params['headers'])
+# with open(f'test_{key}.{url.ext}', 'wb+') as f:
+# f.write(r.content)
+
+#print(json.dumps(page, indent=4, cls=PostEncoder))
+#for i in range(0, len(post.media)):
+# download_params = module.get_download(post.unique_id, i)
+# r = httpx.get(download_params['url'], headers=download_params['headers'])
+# if r.status_code != 200:
+# print('error{}'.format(r.status_code))
+# else:
+# with open('test{}.png'.format(i), 'wb+') as f:
+# f.write(r.content)
+
+#print(json.dumps(page, indent=4, cls=PostEncoder))
+#print(page)
diff --git a/src/portal/tests/unescape_json.py b/src/portal/py/tests/unescape_json.py
index eaf5194..eaf5194 100644
--- a/src/portal/tests/unescape_json.py
+++ b/src/portal/py/tests/unescape_json.py
diff --git a/src/portal/requirements.txt b/src/portal/requirements.txt
index d241387..f4982dc 100644
--- a/src/portal/requirements.txt
+++ b/src/portal/requirements.txt
@@ -1,2 +1,4 @@
-httpx[http2,brotli]
+httpx[http2,brotli,zstd]
+blake3
pillow
+notcurses
diff --git a/src/portal/scripts/create_vendor.sh b/src/portal/scripts/create_vendor.sh
index f4d551e..6fdf9e0 100755
--- a/src/portal/scripts/create_vendor.sh
+++ b/src/portal/scripts/create_vendor.sh
@@ -4,7 +4,7 @@ python3 -m venv ./venv
source ./venv/bin/activate
cd vendor/
-rm -r vendor/
+rm -rf vendor/
pip3 install -r ../requirements.txt --upgrade --target=./vendor
@@ -12,7 +12,11 @@ cd yt-dlp/
pip3 install . --upgrade --target=../vendor
cd ../
-cd snscrape/
+#cd snscrape/
+#pip3 install . --upgrade --target=../vendor
+#cd ../
+
+cd twitter-api-client/
pip3 install . --upgrade --target=../vendor
cd ../
@@ -24,5 +28,13 @@ cd instagrapi/
pip3 install . --upgrade --target=../vendor
cd ../
+cd CyberDropDownloader/
+pip3 install . --upgrade --target=../vendor
+cd ../
+
+#cd gallery-dl/
+#pip3 install . --upgrade --target=../vendor
+#cd ../
+
cd ../
rm -r venv
diff --git a/src/portal/scripts/run_py.sh b/src/portal/scripts/run_py.sh
index ddbe869..4414f93 100755
--- a/src/portal/scripts/run_py.sh
+++ b/src/portal/scripts/run_py.sh
@@ -1,4 +1,5 @@
#! /usr/bin/env sh
+export CURL_CA_BUNDLE=/etc/ssl/certs/ca-bundle.crt
export PYTHONDONTWRITEBYTECODE=1
export PYTHONPATH=$HOME/c/camu/src/portal/vendor/vendor
-python3 $@
+$@
diff --git a/src/portal/src/packet_ext.c b/src/portal/src/packet_ext.c
index 580c307..e2909ef 100644
--- a/src/portal/src/packet_ext.c
+++ b/src/portal/src/packet_ext.c
@@ -16,7 +16,9 @@ void aki_packet_write_camu_post(struct aki_packet *packet, struct camu_post *pos
struct camu_post_date *date;
al_array_foreach_ptr(post->dates, i, date) {
AKI_PACKET_WRITE_TYPE(packet, u8, date->type);
- AKI_PACKET_WRITE_TYPE(packet, f32, date->timestamp);
+ AKI_PACKET_WRITE_TYPE(packet, f32, date->range_start);
+ AKI_PACKET_WRITE_TYPE(packet, f32, date->range_end);
+ AKI_PACKET_WRITE_TYPE(packet, u32, date->meta);
}
aki_packet_write_str(packet, &post->author.unique_id);
aki_packet_write_wstr(packet, &post->author.username);
@@ -64,7 +66,9 @@ void aki_packet_read_camu_post(struct aki_packet *packet, struct camu_post *post
for (u32 i = 0; i < dates_size; i++) {
struct camu_post_date date;
AKI_PACKET_READ_TYPE(packet, u8, date.type);
- AKI_PACKET_READ_TYPE(packet, f32, date.timestamp);
+ AKI_PACKET_READ_TYPE(packet, f32, date.range_start);
+ AKI_PACKET_READ_TYPE(packet, f32, date.range_end);
+ AKI_PACKET_READ_TYPE(packet, u32, date.meta);
al_array_push(post->dates, date);
}
aki_packet_read_str(packet, &post->author.unique_id);
diff --git a/src/portal/src/post.c b/src/portal/src/post.c
index c5eb4b5..4cb0bcc 100644
--- a/src/portal/src/post.c
+++ b/src/portal/src/post.c
@@ -56,9 +56,9 @@ void camu_post_add_media(struct camu_post *post, u8 type, str *key, str *url, st
}));
}
-void camu_post_add_date(struct camu_post *post, u8 type, f32 timestamp)
+void camu_post_add_date(struct camu_post *post, u8 type, f32 range_start, f32 range_end, u32 meta)
{
al_array_push(post->dates, ((struct camu_post_date){
- .type = type, .timestamp = timestamp
+ .type = type, .range_start = range_start, .range_end = range_end, .meta = meta
}));
}
diff --git a/src/portal/src/post.h b/src/portal/src/post.h
index 11fa9b6..a946968 100644
--- a/src/portal/src/post.h
+++ b/src/portal/src/post.h
@@ -20,6 +20,13 @@ enum {
};
enum {
+ CAMU_DATE_ESTIMATE = 1,
+ CAMU_DATE_GUESS = 1 << 1,
+ CAMU_DATE_EDITED_AT_UNKNOWN_TIME = 1 << 2,
+ CAMU_DATE_ASSUMED_TYPE = 1 << 3
+};
+
+enum {
CAMU_MEDIA_UNKNOWN = 0,
CAMU_MEDIA_FILE,
CAMU_MEDIA_AUDIO,
@@ -46,7 +53,9 @@ typedef struct {
struct camu_post_date {
u8 type;
- f32 timestamp;
+ f32 range_start;
+ f32 range_end;
+ u32 meta;
};
typedef array(struct camu_post_date) camu_dates_array;
@@ -98,5 +107,5 @@ void camu_post_reset(struct camu_post *post);
void camu_post_clone(struct camu_post *dest, struct camu_post *src);
// Python internal.
-void camu_post_add_date(struct camu_post *post, u8 type, f32 timestamp);
+void camu_post_add_date(struct camu_post *post, u8 type, f32 range_start, f32 range_end, u32 meta);
void camu_post_add_media(struct camu_post *post, u8 type, str *key, str *url, str *ext, str *thumbnail_url, str *thumbnail_ext);
diff --git a/src/portal/src/search.c b/src/portal/src/search.c
index 7334c41..bcb0643 100644
--- a/src/portal/src/search.c
+++ b/src/portal/src/search.c
@@ -38,7 +38,7 @@ static struct camu_result_page *page_at_index(struct camu_search *search, u32 nu
al_array_foreach_ptr(search->pages, i, page) {
if (page->num == num) return page;
}
- al_array_push(search->pages, (struct camu_result_page){0});
+ al_array_push(search->pages, (struct camu_result_page){ 0 });
page = &al_array_last(search->pages);
page->num = num;
al_array_init(page->posts);
@@ -47,10 +47,10 @@ static struct camu_result_page *page_at_index(struct camu_search *search, u32 nu
}
-void camu_portal_init(struct camu_portal *portal, struct camu_post_cache *cache)
+void camu_portal_init(struct camu_portal_bridge *bridge, struct camu_post_cache *cache)
{
- portal->cache = cache;
- al_array_init(portal->searches);
+ bridge->cache = cache;
+ al_array_init(bridge->searches);
}
static void camu_search_init_internal(struct camu_search *search)
@@ -59,7 +59,7 @@ static void camu_search_init_internal(struct camu_search *search)
al_array_init(search->pages);
}
-s32 camu_portal_create_search(struct camu_portal *portal, str *module, str *query)
+s32 camu_portal_create_search(struct camu_portal_bridge *bridge, str *module, str *query)
{
s32 id = portal_bridge_search(module, query);
if (id >= 0) {
@@ -68,31 +68,31 @@ s32 camu_portal_create_search(struct camu_portal *portal, str *module, str *quer
search->id = id;
al_str_clone(&search->module, module);
al_str_clone(&search->query, query);
- search->portal = portal;
- al_array_push(portal->searches, search);
+ search->bridge = bridge;
+ al_array_push(bridge->searches, search);
}
al_log_info("portal", "New search %x (%.*s).", id, AL_STR_PRINTF(query));
return id;
}
-struct camu_search *camu_portal_get_search(struct camu_portal *portal, s32 id)
+struct camu_search *camu_portal_get_search(struct camu_portal_bridge *bridge, s32 id)
{
struct camu_search *search;
- al_array_foreach(portal->searches, i, search) {
+ al_array_foreach(bridge->searches, i, search) {
if (search->id == id) return search;
}
return NULL;
}
-void camu_portal_discard_search(struct camu_portal *portal, s32 id)
+void camu_portal_discard_search(struct camu_portal_bridge *bridge, s32 id)
{
- (void)portal;
+ (void)bridge;
(void)id;
}
-void camu_portal_close(struct camu_portal *portal)
+void camu_portal_close(struct camu_portal_bridge *bridge)
{
- (void)portal;
+ (void)bridge;
}
bool camu_search_get_page(struct camu_search *search, u32 num)
@@ -105,11 +105,11 @@ bool camu_search_get_page(struct camu_search *search, u32 num)
if (portal_bridge_get_page(search, search->id, num) == -1) {
return false;
}
- struct camu_portal *portal = search->portal;
- if (portal->cache) {
+ struct camu_portal_bridge *bridge = search->bridge;
+ if (bridge->cache) {
struct camu_post *post;
al_array_foreach_ptr(al_array_at(search->pages, num).posts, i, post) {
- camu_post_cache_push(portal->cache, post);
+ camu_post_cache_push(bridge->cache, post);
}
}
out:
diff --git a/src/portal/src/search.h b/src/portal/src/search.h
index 4962170..1f43ded 100644
--- a/src/portal/src/search.h
+++ b/src/portal/src/search.h
@@ -15,22 +15,22 @@ struct camu_search {
str query;
u32 page;
array(struct camu_result_page) pages;
- struct camu_portal *portal;
+ struct camu_portal_bridge *bridge;
};
-struct camu_portal {
- struct camu_post_cache *cache;
+struct camu_portal_bridge {
array(struct camu_search *) searches;
+ struct camu_post_cache *cache;
};
bool camu_python_init(void);
void camu_python_close(void);
-void camu_portal_init(struct camu_portal *portal, struct camu_post_cache *cache);
-s32 camu_portal_create_search(struct camu_portal *portal, str *module_str, str *search_str);
-struct camu_search *camu_portal_get_search(struct camu_portal *portal, s32 id);
-void camu_portal_discard_search(struct camu_portal *portal, s32 id);
-void camu_portal_close(struct camu_portal *portal);
+void camu_portal_init(struct camu_portal_bridge *bridge, struct camu_post_cache *cache);
+s32 camu_portal_create_search(struct camu_portal_bridge *bridge, str *module_str, str *search_str);
+struct camu_search *camu_portal_get_search(struct camu_portal_bridge *bridge, s32 id);
+void camu_portal_discard_search(struct camu_portal_bridge *bridge, s32 id);
+void camu_portal_close(struct camu_portal_bridge *bridge);
bool camu_search_get_page(struct camu_search *search, u32 num);
diff --git a/src/portal/tests/test.py b/src/portal/tests/test.py
deleted file mode 100644
index a5139f6..0000000
--- a/src/portal/tests/test.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import json
-import httpx
-import sys
-sys.path.append('../py')
-from base import Method
-from post import DateType, PostEncoder, Media
-from modules import ALL_MODULES
-
-#ALL_MODULES['instagram'][0].init()
-#search = ALL_MODULES['instagram'][0].search('opium_00pium')
-#page = search.get_page(0)
-#print(page)
-#print(json.dumps(page, indent=4, cls=PostEncoder))
-
-d = Media()
-print(d)
-sys.exit(1)
-
-#module = ALL_MODULES['fanbox'][0]
-#module = ALL_MODULES['pixiv_web'][0]
-module = ALL_MODULES['instagram'][0]
-#module = ALL_MODULES['twitter'][0]
-module.init()
-
-search = module.search('opium_00pium')
-page = search.get_page(1)
-for unique_id in page:
- post = module.get_item(unique_id)
- print(json.dumps(post, indent=4, cls=PostEncoder))
-
-#post = module.get_item(page[0])
-#for key in post.media.keys():
-# download_params = module.get_download(post.unique_id, key)
-# for url in download_params['urls']:
-# #response = module.do_request(Method.GET, url.url)
-# r = httpx.get(url.url, headers=download_params['headers'])
-# with open(f'test_{key}.{url.ext}', 'wb+') as f:
-# f.write(r.content)
-
-#print(json.dumps(page, indent=4, cls=PostEncoder))
-#for i in range(0, len(post.media)):
-# download_params = module.get_download(post.unique_id, i)
-# r = httpx.get(download_params['url'], headers=download_params['headers'])
-# if r.status_code != 200:
-# print('error{}'.format(r.status_code))
-# else:
-# with open('test{}.png'.format(i), 'wb+') as f:
-# f.write(r.content)
-
-#print(json.dumps(page, indent=4, cls=PostEncoder))
-#print(page)
diff --git a/src/render/meson.build b/src/render/meson.build
index 20f68c3..b62a5a5 100644
--- a/src/render/meson.build
+++ b/src/render/meson.build
@@ -1,58 +1,62 @@
render_src = []
render_deps = [common_deps]
+render_args = []
-#linmath = subproject('linmath.h').get_variable('linmath')
-#libm = compiler.find_library('m', required: false)
-#if libm.found()
-# render_deps += [libm]
-#endif
-#render_src += ['renderer_tiger.c', 'queue_tiger.c']
-#render_deps += [linmath]
-
-libplacebo_opts = [
- 'warning_level=2',
- 'demos=false',
- 'tests=false',
- 'bench=false',
- 'fuzz=false',
- 'glslang=disabled',
- 'gl-proc-addr=disabled',
- 'vk-proc-addr=disabled',
- 'd3d11=disabled',
- 'lcms=disabled',
- 'dovi=disabled',
- 'libdovi=disabled'
-]
-
-if sink_use_vulkan
- shaderc_opts = cmake.subproject_options()
- shaderc_opts.add_cmake_defines({ 'CMAKE_BUILD_TYPE': is_debug ? 'Debug' : 'Release' })
- shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_INSTALL': false })
- shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_TESTS': true })
- shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_EXAMPLES': true })
- shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_COPYRIGHT_CHECK': true })
- shaderc_proj = cmake.subproject('shaderc', options: shaderc_opts)
- shaderc = shaderc_proj.dependency('shaderc')
- meson.override_dependency('shaderc', shaderc)
- render_deps += [
- shaderc_proj.dependency('shaderc_util'),
- shaderc_proj.dependency('glslang'),
- shaderc_proj.dependency('MachineIndependent'),
- shaderc_proj.dependency('OSDependent'),
- shaderc_proj.dependency('GenericCodeGen'),
- shaderc_proj.dependency('SPIRV'),
- shaderc_proj.dependency('SPIRV-Tools-opt'),
- shaderc_proj.dependency('SPIRV-Tools-static')
+if get_option('renderer') == 'tiger'
+ render_src += ['renderer_tiger.c', 'queue_tiger.c']
+ linmath_h = subproject('linmath.h').get_variable('linmath_h')
+ render_deps += [linmath_h]
+ libm = compiler.find_library('m', required: false)
+ if libm.found()
+ render_deps += [libm]
+ endif
+ render_args += ['-DCAMU_RENDERER_TIGER']
+elif get_option('renderer') == 'libplacebo'
+ libplacebo_opts = ['warning_level=2', 'demos=false', 'tests=false', 'bench=false', 'fuzz=false',
+ 'glslang=disabled',
+ 'gl-proc-addr=disabled',
+ 'vk-proc-addr=disabled',
+ 'd3d11=disabled',
+ 'lcms=disabled',
+ 'dovi=disabled',
+ 'libdovi=disabled'
]
- libplacebo_opts += ['shaderc=enabled', 'vulkan=enabled', 'opengl=disabled']
-else
- libplacebo_opts += ['shaderc=disabled', 'opengl=enabled', 'vulkan=disabled']
-endif
-libplacebo = dependency('libplacebo', default_options: libplacebo_opts)
-if libplacebo.found()
+ if sink_use_vulkan
+ shaderc = dependency('shaderc', required: false, allow_fallback: false)
+ if shaderc.found()
+ render_deps += [shaderc]
+ else
+ shaderc_opts = cmake.subproject_options()
+ reltype = is_minsize ? 'MinSizeRel' : 'Release'
+ shaderc_opts.add_cmake_defines({ 'CMAKE_BUILD_TYPE': is_debug ? 'Debug' : reltype })
+ shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_INSTALL': true })
+ shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_TESTS': true })
+ shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_EXAMPLES': true })
+ shaderc_opts.add_cmake_defines({ 'SHADERC_SKIP_COPYRIGHT_CHECK': true })
+ shaderc_proj = cmake.subproject('shaderc', options: shaderc_opts)
+ shaderc = shaderc_proj.dependency('shaderc')
+ meson.override_dependency('shaderc', shaderc)
+ render_deps += [
+ shaderc_proj.dependency('shaderc_util'),
+ shaderc_proj.dependency('glslang'),
+ shaderc_proj.dependency('MachineIndependent'),
+ shaderc_proj.dependency('OSDependent'),
+ shaderc_proj.dependency('GenericCodeGen'),
+ shaderc_proj.dependency('SPIRV'),
+ shaderc_proj.dependency('SPIRV-Tools-opt'),
+ shaderc_proj.dependency('SPIRV-Tools-static')
+ ]
+ endif
+ libplacebo_opts += ['shaderc=enabled', 'vulkan=enabled', 'opengl=disabled']
+ else
+ libplacebo_opts += ['shaderc=disabled', 'opengl=enabled', 'vulkan=disabled']
+ endif
+
+ libplacebo = dependency('libplacebo', default_options: libplacebo_opts)
render_src += ['renderer_libplacebo.c', 'queue_libplacebo.c']
render_deps += [libplacebo]
+ render_args += ['-DCAMU_RENDERER_LIBPLACEBO']
endif
-render = declare_dependency(sources: render_src, dependencies: render_deps)
+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 6316fe2..bd5ed7b 100644
--- a/src/render/queue_libplacebo.c
+++ b/src/render/queue_libplacebo.c
@@ -12,49 +12,65 @@
static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src,
struct pl_frame *out_frame)
{
- struct camu_frame *frame = src->frame_data;
+ struct camu_codec_frame *frame = src->frame_data;
- u64 masks[4] = { 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 };
- s32 pixel_stride = 4;
-
- /*
- switch (frame->fmt) {
- case CAMU_PIXEL_FMT_RGBA:
+ u64 masks[4] = { 0 };
+ s32 pixel_stride;
+ switch (frame->format) {
+ case CAMU_PIXEL_FORMAT_RGBA:
+ masks[0] = 0x000000ff;
+ masks[1] = 0x0000ff00;
+ masks[2] = 0x00ff0000;
+ masks[3] = 0xff000000;
+ pixel_stride = 4;
+ break;
+ case CAMU_PIXEL_FORMAT_RGB:
+ masks[0] = 0x000000ff;
+ masks[1] = 0x0000ff00;
+ masks[2] = 0x00ff0000;
+ pixel_stride = 3;
+ break;
+ case CAMU_PIXEL_FORMAT_GREYA:
+ masks[0] = 0x000000ff;
+ masks[1] = 0x0000ff00;
+ pixel_stride = 2;
+ break;
+ case CAMU_PIXEL_FORMAT_GREY:
+ masks[0] = 0x000000ff;
+ pixel_stride = 1;
break;
default:
al_log_error("frame_queue_libplacebo", "Unsuppored pixel format.");
- return false;
+ al_assert_and_return(false);
}
- */
struct pl_plane_data data = {
.type = PL_FMT_UNORM,
- .width = frame->width,
- .height = frame->height,
+ .width = frame->video.width,
+ .height = frame->video.height,
.pixel_stride = pixel_stride,
- .row_stride = frame->width * pixel_stride,
+ .row_stride = frame->video.width * pixel_stride,
.pixels = frame->data
};
-
pl_plane_data_from_mask(&data, masks);
struct pl_plane plane = { 0 };
bool ok = pl_upload_plane(gpu, &plane, tex, &data);
- al_free(frame);
+ out_frame->num_planes = 1;
+ 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->profile =
+
+ camu_codec_frame_discard(frame);
if (!ok) {
al_log_error("frame_queue_libplacebo", "Failed to upload texture.");
return false;
}
- out_frame->num_planes = 1;
- out_frame->planes[0] = plane;
- out_frame->repr = pl_color_repr_unknown;
- out_frame->color = pl_color_space_unknown;
- out_frame->crop = (pl_rect2df){ 0, 0, frame->width, frame->height };
- //out_frame->profile =
-
return true;
}
@@ -67,11 +83,11 @@ static void unmap_frame(pl_gpu gpu, struct pl_frame *frame, const struct pl_sour
static void discard_frame(const struct pl_source_frame *src)
{
- struct camu_frame *frame = src->frame_data;
- al_free(frame);
+ struct camu_codec_frame *frame = src->frame_data;
+ camu_codec_frame_discard(frame);
}
-static void queue_lp_push(struct camu_frame_queue *queue, struct camu_frame *frame, f64 pts)
+static void queue_lp_push(struct camu_frame_queue *queue, struct camu_codec_frame *frame, f64 pts)
{
struct camu_frame_queue_lp *lq = (struct camu_frame_queue_lp *)queue;
pl_queue_push(lq->queue, &(struct pl_source_frame){
@@ -80,7 +96,7 @@ static void queue_lp_push(struct camu_frame_queue *queue, struct camu_frame *fra
.map = map_frame,
.unmap = unmap_frame,
.discard = discard_frame,
- .frame_data = frame,
+ .frame_data = frame
});
}
diff --git a/src/render/queue_tiger.c b/src/render/queue_tiger.c
index 0e76502..b1c34ca 100644
--- a/src/render/queue_tiger.c
+++ b/src/render/queue_tiger.c
@@ -1,10 +1,9 @@
#include <al/lib.h>
+#include <stl/gl.h>
-#include "renderer_tiger.h"
#include "queue_tiger.h"
-static void upload_texture(struct camu_frame_queue_tiger *tq, s32 width, s32 height,
- u8 *data, s32 linesize)
+static void upload_texture(struct camu_frame_queue_tiger *tq, struct camu_codec_frame *frame)
{
glGenTextures(1, &tq->texture_id);
glActiveTexture(GL_TEXTURE0);
@@ -19,18 +18,28 @@ static void upload_texture(struct camu_frame_queue_tiger *tq, s32 width, s32 hei
//if (linesize) {
// glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize);
//}
- (void)linesize;
//glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
- //glGenerateMipmap(GL_TEXTURE_2D);
+ switch (frame->format) {
+ case CAMU_PIXEL_FORMAT_RGBA:
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, frame->width, frame->height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, frame->data);
+ break;
+ case CAMU_PIXEL_FORMAT_RGB:
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frame->width, frame->height, 0,
+ GL_RGB, GL_UNSIGNED_BYTE, frame->data);
+ break;
+ default:
+ al_assert(false);
+ }
+ glGenerateMipmap(GL_TEXTURE_2D);
}
-static void queue_tiger_push(struct camu_frame_queue *queue, struct camu_frame *frame, f64 pts)
+static void queue_tiger_push(struct camu_frame_queue *queue, struct camu_codec_frame *frame, f64 pts)
{
struct camu_frame_queue_tiger *tq = (struct camu_frame_queue_tiger *)queue;
(void)pts;
- upload_texture(tq, frame->width, frame->height, frame->data, 0);
+ tq->queued_frame = frame;
}
#ifdef CAMU_HAVE_FFMPEG
@@ -38,7 +47,6 @@ static void queue_tiger_push_av_frame(struct camu_frame_queue *queue, AVFrame *f
{
struct camu_frame_queue_tiger *tq = (struct camu_frame_queue_tiger *)queue;
(void)pts;
- upload_texture(tq, frame->width, frame->height, frame->data[0], frame->linesize[0]);
}
#endif
@@ -58,6 +66,13 @@ static u8 queue_tiger_read(struct camu_frame_queue *queue, f64 pts, void *out)
{
struct camu_frame_queue_tiger *tq = (struct camu_frame_queue_tiger *)queue;
(void)pts;
+ if (tq->queued_frame) {
+ struct camu_codec_frame *frame = tq->queued_frame;
+ upload_texture(tq, frame);
+ tq->queued_frame = NULL;
+ tq->uploaded = true;
+ }
+ if (!tq->uploaded) return CAMU_QUEUE_MORE;
*((GLuint *)out) = tq->texture_id;
return CAMU_QUEUE_OK;
}
@@ -79,6 +94,8 @@ static void queue_tiger_free(struct camu_frame_queue **queue)
struct camu_frame_queue *camu_frame_queue_tiger_create(void)
{
struct camu_frame_queue_tiger *tq = al_alloc_object(struct camu_frame_queue_tiger);
+ tq->queued_frame = NULL;
+ tq->uploaded = false;
tq->q.push = queue_tiger_push;
#ifdef CAMU_HAVE_FFMPEG
tq->q.push_av_frame = queue_tiger_push_av_frame;
diff --git a/src/render/queue_tiger.h b/src/render/queue_tiger.h
index db675df..a90d8c7 100644
--- a/src/render/queue_tiger.h
+++ b/src/render/queue_tiger.h
@@ -1,6 +1,6 @@
#pragma once
-#include <skh/gl.h>
+#include <stl/gl.h>
#include "../buffer/frame_queue.h"
@@ -8,6 +8,8 @@ struct camu_frame_queue_tiger {
struct camu_frame_queue q;
GLuint texture_id;
struct camu_renderer_tiger *tr;
+ struct camu_codec_frame *queued_frame;
+ bool uploaded;
};
struct camu_frame_queue *camu_frame_queue_tiger_create(void);
diff --git a/src/render/renderer.h b/src/render/renderer.h
index e60c3a5..5b1d342 100644
--- a/src/render/renderer.h
+++ b/src/render/renderer.h
@@ -1,20 +1,19 @@
#pragma once
#include <al/types.h>
-#include <skh/window.h>
+#include <stl/window.h>
#ifdef CAMU_HAVE_FFMPEG
#include <libavcodec/avcodec.h>
#endif
-#include "../buffer/frame_queue.h"
-
-#if defined SEKIHI_API_OPENGL
+#if defined STELA_API_OPENGL
#define CAMU_RENDERER_OPENGL
-#elif defined SEKIHI_API_VULKAN
+#elif defined STELA_API_VULKAN
#define CAMU_RENDERER_VULKAN
#endif
struct camu_screen;
+struct camu_frame_queue;
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 9cc0917..eeef565 100644
--- a/src/render/renderer_libplacebo.c
+++ b/src/render/renderer_libplacebo.c
@@ -130,18 +130,20 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, s32 *wid
lr->renderer = pl_renderer_create(lr->logger, lr->gpu);
- lr->r.resize(&lr->r, width, height);
+ pl_swapchain_resize(lr->swapchain, width, height);
al_memset(&lr->params, 0, sizeof(struct pl_render_params));
- //lr->params = pl_render_default_params;
- lr->params = pl_render_fast_params;
- lr->params.upscaler = &pl_filter_box;
+ //lr->params = pl_render_fast_params;
+ //lr->params.upscaler = &pl_filter_nearest;
+ lr->params = pl_render_default_params;
+ //lr->params = pl_render_high_quality_params;
+ lr->params.deband_params = NULL;
- // Clear manually so we can draw multiple images per frame.
- lr->params.skip_target_clearing = true;
- // Breaks zoom/pan.
+ // Don't let the cache treat different images of the same size as the same frame.
lr->params.skip_caching_single_frame = true;
- // Prioritize a more consistant image (should be configurable).
+ // Clear manually so we can draw multiple images per frame.
+ lr->params.border = PL_CLEAR_SKIP;
+ // Prioritize a more consistant image.
lr->params.correct_subpixel_offsets = true;
#if 0
@@ -159,8 +161,8 @@ static bool renderer_lp_create_renderer(struct camu_renderer *renderer, s32 *wid
#endif
#ifdef CAMU_HAVE_FFMPEG
- //lr->r.get_buffer2 = pl_get_buffer2;
- //lr->r.opaque = &lr->gpu;
+ lr->r.get_buffer2 = pl_get_buffer2;
+ lr->r.opaque = &lr->gpu;
#endif
return true;
@@ -187,32 +189,23 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
struct camu_renderer_lp *lr = (struct camu_renderer_lp *)renderer;
#ifdef CAMU_SCREEN_THREADED
- if (al_atomic_bool_load(&scr->queued, AL_ATOMIC_RELAXED)) {
+ if (al_atomic_load(u8)(&scr->queued, AL_ATOMIC_RELAXED)) {
camu_screen_run_queue(scr);
}
#endif
- bool have_frame = false;
struct pl_swapchain_frame frame;
struct pl_frame target;
struct pl_frame_mix mix;
-#if 1
pl_swapchain_start_frame(lr->swapchain, &frame);
pl_frame_from_swapchain(&target, &frame);
pl_frame_clear_rgba(lr->gpu, &target, clear_color);
- have_frame = true;
-#endif
- if (scr->videos.size > 0) {
- struct camu_screen_video *video = &al_array_last(scr->videos);
+ struct camu_screen_video *video;
+ while (scr->videos.size > 0) {
+ video = &al_array_last(scr->videos);
if (camu_video_buffer_read(video->buf, &mix) && mix.frames) {
- if (!have_frame) {
- pl_swapchain_start_frame(lr->swapchain, &frame);
- pl_frame_from_swapchain(&target, &frame);
- pl_frame_clear_rgba(lr->gpu, &target, clear_color);
- have_frame = true;
- }
target.crop = mix.frames[0]->crop;
target.crop.x1 *= video->view.zoom / video->view.stretch;
target.crop.y1 *= video->view.zoom * video->view.stretch;
@@ -221,14 +214,22 @@ static void renderer_lp_render(struct camu_renderer *renderer, struct camu_scree
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);
+ break;
+#ifdef CAMU_SCREEN_THREADED
+ } else {
+ if (al_atomic_load(u8)(&scr->queued, AL_ATOMIC_RELAXED)) {
+ camu_screen_run_queue(scr);
+ }
+#endif
}
}
- if (have_frame) {
- pl_swapchain_submit_frame(lr->swapchain);
- pl_swapchain_swap_buffers(lr->swapchain);
- }
+ pl_swapchain_submit_frame(lr->swapchain);
+ pl_swapchain_swap_buffers(lr->swapchain);
}
void renderer_lp_free(struct camu_renderer **renderer)
diff --git a/src/render/renderer_tiger.c b/src/render/renderer_tiger.c
index 049a3b6..7127891 100644
--- a/src/render/renderer_tiger.c
+++ b/src/render/renderer_tiger.c
@@ -90,9 +90,8 @@ static void renderer_tiger_resize(struct camu_renderer *renderer, s32 *width, s3
{
struct camu_renderer_tiger *tr = (struct camu_renderer_tiger *)renderer;
(void)tr;
-#ifdef CAMU_RENDERER_OPENGL
- glViewport(0, 0, *width, *height);
-#endif
+ (void)width;
+ (void)height;
}
static bool renderer_tiger_create_renderer(struct camu_renderer *renderer, s32 *width, s32 *height,
@@ -123,7 +122,7 @@ static bool renderer_tiger_create_renderer(struct camu_renderer *renderer, s32 *
if (!tr->load_loader(tr->priv)) return false;
- renderer_tiger_resize(renderer, width, height);
+ tr->r.resize(&tr->r, width, height);
clear_color[0] = ((global_color_palette.colors[0] >> 16) & 0xff) / 255.f;
clear_color[1] = ((global_color_palette.colors[0] >> 8) & 0xff) / 255.f;
@@ -144,6 +143,10 @@ static bool renderer_tiger_create_renderer(struct camu_renderer *renderer, s32 *
GLint pos = glGetAttribLocation(tr->program, "a_Position");
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);
+ }
glEnableVertexAttribArray(pos);
GLint texcoord = glGetAttribLocation(tr->program, "a_TexCoord");
glVertexAttribPointer(texcoord, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void *)12);
@@ -208,7 +211,7 @@ static void set_uniform(struct camu_renderer_tiger *tr, const GLchar *name,
}
#ifdef CAMU_RENDERER_OPENGL
-static mat4x4 model;
+static mat4x4 model = { 0 };
static GLuint texture0 = 0;
#endif
@@ -217,6 +220,8 @@ static void renderer_tiger_render(struct camu_renderer *renderer, struct camu_sc
#ifdef CAMU_RENDERER_OPENGL
struct camu_renderer_tiger *tr = (struct camu_renderer_tiger *)renderer;
+ tr->make_current(tr->priv);
+
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(tr->program);
@@ -233,21 +238,26 @@ static void renderer_tiger_render(struct camu_renderer *renderer, struct camu_sc
struct camu_screen_video *video = &al_array_last(scr->videos);
GLuint texture_id;
- camu_video_buffer_read(video->buf, &texture_id);
- glBindTexture(GL_TEXTURE_2D, texture_id);
+ if (camu_video_buffer_read(video->buf, &texture_id)) {
+ glBindTexture(GL_TEXTURE_2D, texture_id);
- 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);
- set_uniform(tr, "g_Texture0", TYPE_SAMPLER2D, &texture0);
+ 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);
+ set_uniform(tr, "g_Texture0", TYPE_SAMPLER2D, &texture0);
- glDrawArrays(GL_TRIANGLES, 0, 6);
+ glViewport(0, 0, scr->width, scr->height);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ }
}
tr->swap_buffers(tr->priv);
#endif
+
+ tr->release_current(tr->priv);
}
static void *renderer_tiger_get_device_ptr(struct camu_renderer *renderer, size_t length)
diff --git a/src/render/renderer_tiger.h b/src/render/renderer_tiger.h
index 2979d91..5891c20 100644
--- a/src/render/renderer_tiger.h
+++ b/src/render/renderer_tiger.h
@@ -1,6 +1,6 @@
#pragma once
-#include <skh/gl.h>
+#include <stl/gl.h>
#include "renderer.h"
diff --git a/src/screen/meson.build b/src/screen/meson.build
index 7f9738c..9c01566 100644
--- a/src/screen/meson.build
+++ b/src/screen/meson.build
@@ -1,10 +1,2 @@
screen_src = ['screen.c', 'view.c']
-sekihi = subproject('sekihi',
- default_options: [
- sink_use_vulkan ? 'api=vulkan' : 'api=opengl',
- 'window=glfw',
- 'poll=inline',
- 'event-buffer=false',
- 'pause=true'
- ]).get_variable('sekihi')
-screen = declare_dependency(sources: screen_src, dependencies: [sekihi])
+screen = declare_dependency(sources: screen_src, dependencies: [stela])
diff --git a/src/screen/screen.c b/src/screen/screen.c
index f92cd98..afc6b9b 100644
--- a/src/screen/screen.c
+++ b/src/screen/screen.c
@@ -1,5 +1,7 @@
-#include <aki/thread.h>
#include <al/log.h>
+#include <aki/thread.h>
+
+#include "../liana/list.h"
#include "view.h"
#include "screen.h"
@@ -23,7 +25,7 @@ static struct camu_view *get_view_from_mouse_pos(struct camu_screen *scr)
static void should_close_callback(void *userdata)
{
struct camu_screen *scr = (struct camu_screen *)userdata;
- scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, 0.0);
+ scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, NULL);
}
static void resize_callback(void *userdata, s32 width, s32 height)
@@ -62,8 +64,8 @@ static bool pointer_pos_callback(void *userdata, f64 x, f64 y)
view->mode = CAMU_VIEW_DETACHED;
queue_refresh = true;
}
+ if (fabs(dx) + fabs(dy) > 3.0) scr->last_click_ts = 0;
}
- scr->last_click_ts = 0;
}
}
scr->last_mouse_x = x;
@@ -75,51 +77,54 @@ static bool mouse_button_callback(void *userdata, u8 state, u8 button)
{
struct camu_screen *scr = (struct camu_screen *)userdata;
switch (button) {
- case SEKIHI_MOUSE1:
+ case STELA_MOUSE1:
switch (state) {
- case SEKIHI_BUTTON_PRESSED:
+ case STELA_BUTTON_PRESSED:
scr->flags |= CAMU_SCREEN_DRAGGING;
scr->last_click_ts = aki_get_timestamp();
break;
- case SEKIHI_BUTTON_RELEASED:
+ case STELA_BUTTON_RELEASED:
scr->flags &= ~CAMU_SCREEN_DRAGGING;
if (scr->last_click_ts && aki_get_timestamp() - scr->last_click_ts <= 300000) {
if (scr->flags & CAMU_SCREEN_MODIFIER) {
- scr->callback(scr->userdata, CAMU_SCREEN_SEEK, scr->last_mouse_x / scr->width);
+ f64 percent = scr->last_mouse_x / scr->width;
+ scr->callback(scr->userdata, CAMU_SCREEN_SEEK, &percent);
} else {
if (scr->last_mouse_x >= scr->width / 2.f) {
- scr->callback(scr->userdata, CAMU_SCREEN_NEXT, 0.0);
+ scr->callback(scr->userdata, CAMU_SCREEN_NEXT, NULL);
} else {
- scr->callback(scr->userdata, CAMU_SCREEN_PREVIOUS, 0.0);
+ scr->callback(scr->userdata, CAMU_SCREEN_PREVIOUS, NULL);
}
}
}
break;
}
break;
- case SEKIHI_MOUSE2:
+ case STELA_MOUSE2:
switch (state) {
- case SEKIHI_BUTTON_PRESSED:
+ case STELA_BUTTON_PRESSED:
scr->flags |= CAMU_SCREEN_MODIFIER;
break;
- case SEKIHI_BUTTON_RELEASED:
+ case STELA_BUTTON_RELEASED:
scr->flags &= ~CAMU_SCREEN_MODIFIER;
break;
}
break;
- case SEKIHI_MOUSE3:
- scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, 0.0);
+ case STELA_MOUSE3: {
+ f64 percent = scr->last_mouse_x / scr->width;
+ scr->callback(scr->userdata, CAMU_SCREEN_SEEK, &percent);
break;
+ }
default:
break;
}
return false;
}
-#if defined SEKIHI_WINDOW_WAYLAND
-#define SCROLL_MULTIPLIER 150.0
+#if defined STELA_WINDOW_WAYLAND
+#define SCROLL_MULTIPLIER 155.0
#else
-#define SCROLL_MULTIPLIER 25.0
+#define SCROLL_MULTIPLIER 13.0
#endif
static bool scroll_callback(void *userdata, f64 y)
@@ -127,11 +132,7 @@ static bool scroll_callback(void *userdata, f64 y)
struct camu_screen *scr = (struct camu_screen *)userdata;
struct camu_view *view = get_view_from_mouse_pos(scr);
if (view) {
- if (scr->flags & CAMU_SCREEN_MODIFIER) {
- // Unimplemented.
- view->rotation += y;
- return true;
- } else if (scr->flags & CAMU_SCREEN_ZOOM_PAN_SIMPLE) {
+ if (scr->flags & CAMU_SCREEN_ZOOM_PAN_SIMPLE) {
y = -y / SCROLL_MULTIPLIER;
if (camu_view_zoom_simple(view, scr->width, scr->height, scr->last_mouse_x, scr->last_mouse_y, y)) {
view->mode = CAMU_VIEW_DETACHED;
@@ -146,40 +147,35 @@ static bool key_callback(void *userdata, u8 state, u8 button)
{
struct camu_screen *scr = (struct camu_screen *)userdata;
switch (state) {
- case SEKIHI_BUTTON_PRESSED:
+ case STELA_BUTTON_PRESSED:
//al_printf("%x\n", button);
switch (button) {
- case 0x1d:
+ case 0x1d: // control
scr->flags |= CAMU_SCREEN_MODIFIER;
break;
- case 0x10:
- case 0x18:
- case 'q':
- scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, 0.0);
+ case 0x10: // q
+ scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, NULL);
+ break;
+ case 0x31: // n
+ case 0x20: // d
+ case 0x6a: // right arrow
+ scr->callback(scr->userdata, CAMU_SCREEN_NEXT, NULL);
break;
- case 0x6a:
- case 0x31:
- case 0x4d:
- case 'n':
- scr->callback(scr->userdata, CAMU_SCREEN_NEXT, 0.0);
+ case 0x30: // b
+ case 0x1e: // a
+ case 0x69: // left arrow
+ scr->callback(scr->userdata, CAMU_SCREEN_PREVIOUS, NULL);
break;
- case 0x69:
- case 0x30:
- case 0x4b:
- case 'b':
- scr->callback(scr->userdata, CAMU_SCREEN_PREVIOUS, 0.0);
+ case 0x39: // spacebar
+ scr->callback(scr->userdata, CAMU_SCREEN_TOGGLE_PAUSE, NULL);
break;
- case 0x41:
- case 0x39:
- scr->callback(scr->userdata, CAMU_SCREEN_TOGGLE_PAUSE, 0.0);
+ case 0x1f: // s
+ scr->callback(scr->userdata, CAMU_SCREEN_SHUFFLE, NULL);
break;
- case 0x1f:
- case 's':
- scr->callback(scr->userdata, CAMU_SCREEN_RESEEK, 0.0);
+ case 0x12: // e
+ scr->callback(scr->userdata, CAMU_SCREEN_RESEEK, NULL);
break;
- case 0x13:
- case 0x1b:
- case 'r': {
+ case 0x13: { // r
struct camu_view *view = get_view_from_mouse_pos(scr);
if (view) {
if (!(scr->flags & CAMU_SCREEN_MODIFIER)) {
@@ -206,7 +202,7 @@ static bool key_callback(void *userdata, u8 state, u8 button)
}
break;
}
- case 0xd: {
+ case 0xd: { // +
struct camu_view *view = get_view_from_mouse_pos(scr);
if (view) {
camu_view_zoom_simple(view, scr->width, scr->height, scr->width / 2.0, scr->height / 2.0, 0.05);
@@ -214,7 +210,7 @@ static bool key_callback(void *userdata, u8 state, u8 button)
}
break;
}
- case 0xc: {
+ case 0xc: { // -
struct camu_view *view = get_view_from_mouse_pos(scr);
if (view) {
camu_view_zoom_simple(view, scr->width, scr->height, scr->width / 2.0, scr->height / 2.0, -0.05);
@@ -226,9 +222,9 @@ static bool key_callback(void *userdata, u8 state, u8 button)
break;
}
break;
- case SEKIHI_BUTTON_RELEASED:
+ case STELA_BUTTON_RELEASED:
switch (button) {
- case 0x1d:
+ case 0x1d: // control
scr->flags &= ~CAMU_SCREEN_MODIFIER;
break;
}
@@ -242,10 +238,9 @@ static void key_immediate_callback(void *userdata, u8 state, u8 button)
{
struct camu_screen *scr = (struct camu_screen *)userdata;
switch (state) {
- case SEKIHI_BUTTON_PRESSED:
+ case STELA_BUTTON_PRESSED:
switch (button) {
- case 0x29:
- case 0x21:
+ case 0x21: // f
scr->window->toggle_fullscreen(scr->window);
break;
}
@@ -257,10 +252,10 @@ static void key_immediate_callback(void *userdata, u8 state, u8 button)
bool camu_screen_init(struct camu_screen *scr)
{
- al_atomic_s32_store(&scr->state, CAMU_SCREEN_PAUSED, AL_ATOMIC_RELAXED);
+ al_atomic_store(s32)(&scr->state, CAMU_SCREEN_PAUSED, AL_ATOMIC_RELAXED);
al_array_init(scr->videos);
scr->renderer = NULL;
- scr->window = skh_window_create();
+ scr->window = stl_window_create();
scr->window->pointer_pos_callback = pointer_pos_callback;
scr->window->scroll_callback = scroll_callback;
scr->window->mouse_button_callback = mouse_button_callback;
@@ -273,24 +268,37 @@ bool camu_screen_init(struct camu_screen *scr)
#ifdef CAMU_SCREEN_THREADED
al_array_init(scr->add_queue);
al_array_init(scr->rem_queue);
- al_atomic_bool_store(&scr->queued, false, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&scr->queued, 0, AL_ATOMIC_RELAXED);
aki_mutex_init(&scr->mutex);
#endif
- scr->last_mouse_x = 0;
- scr->last_mouse_y = 0;
+ scr->last_mouse_x = 0.0;
+ scr->last_mouse_y = 0.0;
scr->flags = CAMU_SCREEN_ZOOM_PAN_SIMPLE;
return true;
}
+#if defined STELA_EVENT_BUFFER
+static aki_thread_result AKI_THREADCALL event_thread(void *userdata)
+{
+ struct camu_screen *scr = (struct camu_screen *)userdata;
+ while (scr->window->poll(scr->window, true) && al_atomic_load(s32)(&scr->state, AL_ATOMIC_RELAXED) != CAMU_SCREEN_CLOSED ) {
+ scr->window->process_events(scr->window);
+ }
+ return 0;
+}
+#endif
+
bool camu_screen_create_window(struct camu_screen *scr, const char *name)
{
- s32 flags = SEKIHI_WINDOW_VSYNC;
- //flags |= SEKIHI_WINDOW_WALLPAPER;
- if (!scr->window->create_window(scr->window, CAMU_SCREEN_WIDTH, CAMU_SCREEN_HEIGHT, "DP-1", name, flags)) {
+ s32 flags = STELA_WINDOW_VSYNC;
+ if (!scr->window->create_window(scr->window, CAMU_SCREEN_WIDTH, CAMU_SCREEN_HEIGHT, "", name, flags)) {
return false;
}
scr->width = scr->window->width;
scr->height = scr->window->height;
+#if defined STELA_EVENT_BUFFER
+ aki_thread_create(&scr->thread, event_thread, scr);
+#endif
return true;
}
@@ -314,23 +322,30 @@ 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_stream *stream = buf->stream;
struct camu_screen_video video = {
- .buf = buf,
- .view = {
- .mode = CAMU_DEFAULT_VIEW,
- .width = stream->video.width,
- .height = stream->video.height,
- .stretch = 1.0,
- .rotation = CAMU_VIEW_ROTATION_0,
- .zindex = 0
- }
+ .buf = buf
};
-#ifdef CAMU_SCREEN_THREADED
- al_atomic_bool_store(&buf->ref, true, AL_ATOMIC_RELAXED);
+ struct camu_codec_stream *stream = buf->stream;
+ if (buf->view.mode != CAMU_VIEW_NONE) {
+ video.view = buf->view;
+#if 0
+ } else if (!al_array_is_empty(scr->videos)) {
+ video.view = al_array_last(scr->videos).view;
+ video.view.width = stream->video.width;
+ video.view.height = stream->video.height;
#endif
- if (buf->view.mode != CAMU_VIEW_NONE) video.view = buf->view;
+ } else {
+ video.view.mode = CAMU_DEFAULT_VIEW;
+ video.view.width = stream->video.width;
+ video.view.height = stream->video.height;
+ video.view.stretch = 1.0;
+ video.view.rotation = CAMU_VIEW_ROTATION_0;
+ video.view.zindex = 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_array_push(scr->videos, video);
}
@@ -339,11 +354,11 @@ static void remove_buffer_internal(struct camu_screen *scr, struct camu_video_bu
struct camu_screen_video *video;
al_array_foreach_ptr(scr->videos, i, video) {
if (video->buf == buf) {
+ buf->view = video->view;
#ifdef CAMU_SCREEN_THREADED
- al_atomic_bool_store(&buf->ref, false, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&buf->ref, 0, AL_ATOMIC_RELAXED);
#endif
- buf->view = video->view;
- al_array_remove_at_iter(scr->videos, i);
+ al_array_remove_at(scr->videos, i);
break;
}
}
@@ -355,7 +370,7 @@ void camu_screen_add_buffer(struct camu_screen *scr, struct camu_video_buffer *b
aki_mutex_lock(&scr->mutex);
al_array_push(scr->add_queue, buf);
al_array_reserve(scr->videos, scr->videos.size + scr->add_queue.size);
- al_atomic_bool_store(&scr->queued, true, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&scr->queued, 1, AL_ATOMIC_RELAXED);
aki_mutex_unlock(&scr->mutex);
#else
add_buffer_internal(scr, buf);
@@ -382,11 +397,20 @@ void camu_screen_remove_buffer(struct camu_screen *scr, struct camu_video_buffer
{
#ifdef CAMU_SCREEN_THREADED
aki_mutex_lock(&scr->mutex);
- al_array_push(scr->rem_queue, buf);
- al_atomic_bool_store(&scr->queued, true, AL_ATOMIC_RELAXED);
- if (al_atomic_s32_load(&scr->state, AL_ATOMIC_RELAXED) == CAMU_SCREEN_CLOSED) {
- run_queue_internal(scr);
+ struct camu_video_buffer *rbuf;
+ al_array_foreach(scr->add_queue, i, rbuf) {
+ if (rbuf == buf) {
+ al_array_remove_at(scr->add_queue, i);
+ bool queue_empty = scr->add_queue.size + scr->rem_queue.size == 0;
+ if (queue_empty) {
+ al_atomic_store(u8)(&scr->queued, 0, AL_ATOMIC_RELAXED);
+ }
+ aki_mutex_unlock(&scr->mutex);
+ return;
+ }
}
+ al_array_push(scr->rem_queue, buf);
+ al_atomic_store(u8)(&scr->queued, 1, AL_ATOMIC_RELAXED);
aki_mutex_unlock(&scr->mutex);
#else
remove_buffer_internal(scr, buf);
@@ -399,24 +423,30 @@ void camu_screen_run_queue(struct camu_screen *scr)
{
aki_mutex_lock(&scr->mutex);
run_queue_internal(scr);
- al_atomic_bool_store(&scr->queued, false, AL_ATOMIC_RELAXED);
+ al_atomic_store(u8)(&scr->queued, 0, AL_ATOMIC_RELAXED);
aki_mutex_unlock(&scr->mutex);
}
#endif
void camu_screen_set_state(struct camu_screen *scr, s32 state)
{
- al_atomic_s32_store(&scr->state, state, AL_ATOMIC_RELAXED);
+ al_atomic_store(s32)(&scr->state, state, AL_ATOMIC_RELAXED);
}
+#define CAMU_SCREEN_POLL_HZ 576
+
bool camu_screen_poll(struct camu_screen *scr, bool block)
{
+#if defined STELA_EVENT_BUFFER
+ if (block) {
+ aki_thread_sleep(AKI_TS_FROM_USEC(1000000/CAMU_SCREEN_POLL_HZ));
+ }
+ stl_window_read_events(scr->window);
+#else
scr->window->poll(scr->window, block);
scr->window->process_events(scr->window);
-#if defined SEKIHI_EVENT_BUFFER
- skh_window_read_events(scr->window);
#endif
-#if defined SEKIHI_PAUSE
+#if defined STELA_PAUSE
return scr->window->should_refresh(scr->window);
#else
return true;
@@ -425,15 +455,29 @@ bool camu_screen_poll(struct camu_screen *scr, bool block)
bool camu_screen_tick(struct camu_screen *scr)
{
- bool paused = al_atomic_s32_load(&scr->state, AL_ATOMIC_RELAXED) == CAMU_SCREEN_PAUSED;
+ bool paused = al_atomic_load(s32)(&scr->state, AL_ATOMIC_RELAXED) == CAMU_SCREEN_PAUSED;
return camu_screen_poll(scr, paused) || !paused;
}
void camu_screen_wake(struct camu_screen *scr)
{
-#if defined SEKIHI_POLL_INLINE && defined SEKIHI_PAUSE
+#if defined STELA_POLL_INLINE && defined STELA_PAUSE
scr->window->wake(scr->window);
+#else
+ (void)scr;
+#endif
+}
+
+void camu_screen_clear(struct camu_screen *scr)
+{
+#ifdef CAMU_SCREEN_THREADED
+ run_queue_internal(scr);
+ struct camu_screen_video *video;
+ al_array_foreach_ptr(scr->videos, i, video) {
+ al_atomic_store(u8)(&video->buf->ref, 0, AL_ATOMIC_RELAXED);
+ }
#endif
+ scr->videos.size = 0;
}
void camu_screen_close(struct camu_screen *scr)
@@ -444,5 +488,9 @@ void camu_screen_close(struct camu_screen *scr)
aki_mutex_destroy(&scr->mutex);
#endif
al_array_free(scr->videos);
+ al_atomic_store(s32)(&scr->state, CAMU_SCREEN_CLOSED, AL_ATOMIC_RELAXED);
+#if defined STELA_EVENT_BUFFER
+ aki_thread_join(&scr->thread);
+#endif
scr->window->free(&scr->window);
}
diff --git a/src/screen/screen.h b/src/screen/screen.h
index 67cd7ed..af4169e 100644
--- a/src/screen/screen.h
+++ b/src/screen/screen.h
@@ -7,11 +7,10 @@
#include <al/atomic.h>
#include <aki/thread.h>
#endif
-#include <skh/window.h>
+#include <stl/window.h>
#include "../buffer/video.h"
#include "../render/renderer.h"
-#include "../codec/codec.h"
#include "view.h"
@@ -33,6 +32,7 @@ enum {
enum {
CAMU_SCREEN_NEXT = 0,
CAMU_SCREEN_PREVIOUS,
+ CAMU_SCREEN_SHUFFLE,
CAMU_SCREEN_TOGGLE_PAUSE,
CAMU_SCREEN_SEEK,
CAMU_SCREEN_RESEEK,
@@ -45,9 +45,12 @@ struct camu_screen_video {
};
struct camu_screen {
- atomic_s32 state;
+ atomic(s32) state;
u32 flags;
- struct skh_window *window;
+ struct stl_window *window;
+#if defined STELA_EVENT_BUFFER
+ struct aki_thread thread;
+#endif
struct camu_renderer *renderer;
s32 width;
s32 height;
@@ -58,10 +61,10 @@ struct camu_screen {
#ifdef CAMU_SCREEN_THREADED
array(struct camu_video_buffer *) add_queue;
array(struct camu_video_buffer *) rem_queue;
- atomic_bool queued;
+ atomic(u8) queued;
struct aki_mutex mutex;
#endif
- void (*callback)(void *, u8, f64);
+ void (*callback)(void *, u8, void *);
void *userdata;
};
@@ -77,4 +80,5 @@ void camu_screen_set_state(struct camu_screen *scr, s32 state);
bool camu_screen_poll(struct camu_screen *scr, bool block);
bool camu_screen_tick(struct camu_screen *scr);
void camu_screen_wake(struct camu_screen *scr);
+void camu_screen_clear(struct camu_screen *scr);
void camu_screen_close(struct camu_screen *scr);
diff --git a/src/server/common.c b/src/server/common.c
new file mode 100644
index 0000000..765418c
--- /dev/null
+++ b/src/server/common.c
@@ -0,0 +1,10 @@
+#include "common.h"
+
+//const str *CAMU_DB_PATH = al_str_c("/mnt/store/files/camu_db");
+str *CAMU_DB_PATH = al_str_c("/home/andrew/c/camu/data/camu_db_test");
+
+str *CAMU_SERVER_IP = al_str_c("108.52.160.112");
+//str *CAMU_SERVER_IP = al_str_c("127.0.0.1");
+
+str *CAMU_UNIX_PATH = al_str_c("/tmp/camu_sock");
+str *CAMU_UNIX_LOCAL = al_str_c("/tmp/cmv_sock");
diff --git a/src/server/common.h b/src/server/common.h
index 26d0edf..c31efcf 100644
--- a/src/server/common.h
+++ b/src/server/common.h
@@ -2,16 +2,20 @@
#include <al/str.h>
-#define CAMU_DB_PATH al_str_c("/mnt/store/files/camu_db")
-//#define CAMU_DB_PATH al_str_c("/home/andrew/c/camu/data/camu_db_test")
+extern str *CAMU_DB_PATH;
#define CAMU_PORT 14356
#define CAMU_MULTIPLEX_RPC 0x53
-#define CAMU_MULTIPLEX_SHRUB 0x85
+#define CAMU_MULTIPLEX_LIANA 0x85
-//#define CAMU_SERVER_IP al_str_c("127.0.0.1")
-//#define CAMU_SERVER_IP al_str_c("192.168.1.192")
-#define CAMU_SERVER_IP al_str_c("108.52.160.112")
+extern str *CAMU_SERVER_IP;
+extern str *CAMU_UNIX_PATH;
+extern str *CAMU_UNIX_LOCAL;
+
+//#define CAMU_LOCAL_TYPE AKI_SOCKET_TCP
+//#define CAMU_LOCAL_ADDR CAMU_SERVER_IP
+#define CAMU_LOCAL_TYPE AKI_SOCKET_UNIX
+#define CAMU_LOCAL_ADDR CAMU_UNIX_PATH
enum {
CAMU_NODE = 0,
@@ -21,14 +25,33 @@ enum {
enum {
CAMU_SERVER_IDENTIFY = 0,
+ CAMU_SERVER_CLIENT_COMMAND,
CAMU_SERVER_LIST_ACTION
};
enum {
- CAMU_ADD = 0,
- CAMU_SKIP,
- CAMU_SKIPTO,
- CAMU_TOGGLE_PAUSE,
- CAMU_SEEK,
- CAMU_FINISHED
+ CAMU_CLIENT_CREATE_LIST = 0,
+ CAMU_CLIENT_ENABLE_SINK
+};
+
+enum {
+ CAMU_LIST_ADD = 0,
+ CAMU_LIST_SKIP,
+ CAMU_LIST_SKIPTO,
+ CAMU_LIST_SHUFFLE,
+ CAMU_LIST_TOGGLE_PAUSE,
+ CAMU_LIST_SEEK,
+ CAMU_LIST_UNSET,
+ CAMU_LIST_END
};
+
+AL_UNUSED_FUNCTION_PUSH
+
+static bool camu_is_url(str *s, u32 i)
+{
+ return al_str_cmp(s, al_str_c("https://"), i, 8) == 0
+ || al_str_cmp(s, al_str_c("http://"), i, 7) == 0
+ || al_str_cmp(s, al_str_c("cdda://"), i, 7) == 0;
+}
+
+AL_UNUSED_FUNCTION_POP
diff --git a/src/server/db.c b/src/server/db.c
index 5042f2d..5c5e8fd 100644
--- a/src/server/db.c
+++ b/src/server/db.c
@@ -1,9 +1,11 @@
#include <al/log.h>
+#include <aki/file.h>
#include <jansson.h>
+#include "list.h"
#include "server.h"
-static bool open_user(struct camu_server *srv, struct aki_dir_entry *dir)
+static bool open_user(struct camu_server *server, struct aki_dir_entry *dir)
{
struct aki_file file;
if (!aki_file_open(&file, &dir->path, 0)) {
@@ -20,16 +22,14 @@ static bool open_user(struct camu_server *srv, struct aki_dir_entry *dir)
}
struct camu_user *user = al_alloc_object(struct camu_user);
al_str_from(&user->name, json_string_value(json_object_get(root, "username")));
- al_array_init(user->lists);
- struct camu_list *default_list = al_alloc_object(struct camu_list);
- camu_list_init(default_list, al_str_c("default"));
- al_array_push(user->lists, default_list);
al_log_info("server", "Loaded user \"%.*s\"", AL_STR_PRINTF(&user->name));
- al_array_push(srv->users, user);
+ al_array_push(server->users, user);
+ json_decref(root);
+ al_str_free(&s);
return true;
}
-bool camu_db_open(struct camu_server *srv, str *path)
+bool camu_db_open(struct camu_server *server, str *path)
{
struct aki_dir camu_db;
if (!aki_dir_open(&camu_db, path)) {
@@ -43,8 +43,9 @@ bool camu_db_open(struct camu_server *srv, str *path)
struct aki_dir_entry user;
while (aki_dir_read(&users, &user)) {
if (user.type == AKI_ENTRY_FILE) {
- open_user(srv, &user);
+ open_user(server, &user);
}
+ aki_dir_entry_free(&user);
}
aki_dir_close(&users);
}
@@ -54,3 +55,17 @@ bool camu_db_open(struct camu_server *srv, str *path)
aki_dir_close(&camu_db);
return true;
}
+
+void camu_db_close(struct camu_server *server)
+{
+ struct camu_user *user;
+ al_array_foreach(server->users, i, user) {
+ al_str_free(&user->name);
+ }
+ al_array_free(server->users);
+ struct camu_list *list;
+ al_array_foreach(server->lists, i, list) {
+ camu_list_free(list);
+ }
+ al_array_free(server->lists);
+}
diff --git a/src/server/list.c b/src/server/list.c
new file mode 100644
index 0000000..8ff123c
--- /dev/null
+++ b/src/server/list.c
@@ -0,0 +1,13 @@
+#include "list.h"
+
+void camu_list_init(struct camu_list *list, str *name)
+{
+ al_str_clone(&list->name, name);
+ lia_list_init(&list->impl);
+}
+
+void camu_list_free(struct camu_list *list)
+{
+ lia_list_free(&list->impl);
+ al_str_free(&list->name);
+}
diff --git a/src/server/list.h b/src/server/list.h
new file mode 100644
index 0000000..f3e52e6
--- /dev/null
+++ b/src/server/list.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "../liana/list.h"
+
+struct camu_list {
+ str name;
+ struct lia_list impl;
+};
+
+void camu_list_init(struct camu_list *list, str *name);
+void camu_list_free(struct camu_list *list);
diff --git a/src/server/local_compat.c b/src/server/local_compat.c
new file mode 100644
index 0000000..9870ce5
--- /dev/null
+++ b/src/server/local_compat.c
@@ -0,0 +1,213 @@
+#include <al/log.h>
+
+#ifdef AKIYO_HAS_CURL
+#include "../cache/handlers/http.h"
+#endif
+#include "../cache/handlers/file.h"
+#include "../cache/handlers/cdio.h"
+
+#include "local_compat.h"
+#include "common.h"
+#include "server.h"
+
+#ifdef CAMU_HAVE_PORTAL
+static bool uri_for_local(str *local, bool search, struct camu_portal_bridge *bridge, str *uri, struct camu_post **selected)
+{
+ if (search || camu_is_url(local, 0)) {
+ str module;
+ str query;
+ al_str_from(&module, "");
+ al_str_from(&query, "");
+ if (al_str_cmp(local, al_str_c("https://twitter.com"), 0, 19) == 0 ||
+ al_str_cmp(local, al_str_c("https://x.com"), 0, 13) == 0) {
+ al_str_cat(&query, al_str_c("tweet:"));
+ al_str_cat(&query, local);
+ al_str_cat(&module, al_str_c("twitter"));
+ } else if (al_str_cmp(local, al_str_c("https://instagram.com"), 0, 21) == 0) {
+ s32 slash = al_str_rfind(local, '/');
+ if (slash >= 0) {
+ al_str_cat(&query, al_str_substr(local, slash + 1, local->len));
+ }
+ al_str_cat(&module, al_str_c("instagram"));
+ } else {
+ if (search) {
+ al_str_cat(&query, al_str_substr(local, 1, local->len));
+ } else {
+ al_str_cat(&query, al_str_c("link:"));
+ al_str_cat(&query, local);
+ }
+ al_str_cat(&module, al_str_c("youtube"));
+ }
+ s32 id = camu_portal_create_search(bridge, &module, &query);
+ al_str_free(&module);
+ al_str_free(&query);
+ if (id < 0) return false;
+ struct camu_search *search = camu_portal_get_search(bridge, id);
+ if (!search || !camu_search_get_page(search, 0)) {
+ al_log_info("local_compat", "Search failed.");
+ return false;
+ }
+ struct camu_result_page *page = &al_array_at(search->pages, 0);
+ struct camu_post *post;
+ al_array_foreach_ptr(page->posts, i, post) {
+ struct camu_post_media *media;
+ al_array_foreach_ptr(post->media, j, media) {
+ if (media->url.len > 0) {
+ al_str_clone(uri, &media->url);
+ *selected = post;
+ break;
+ }
+ }
+ if (*selected) break;
+ }
+ camu_portal_discard_search(bridge, id);
+ }
+ return *selected != NULL;
+}
+#endif
+
+static void worker_signal_callback(void *userdata)
+{
+ struct camu_local_compat *compat = (struct camu_local_compat *)userdata;
+ u32 size;
+ struct camu_server_resource *resource;
+ do {
+ camu_queue_try_pop(compat->pending, size, resource);
+ if (size == 0) break;
+ struct cch_handler *handler = resource->entry->handler;
+ if (al_str_eq(&handler->liana, al_str_c("codec"))) {
+ handler->maybe_spawn_worker(handler, 0);
+ }
+ } while (1);
+}
+
+static aki_thread_result AKI_THREADCALL queue_thread(void *userdata)
+{
+ struct camu_local_compat *compat = (struct camu_local_compat *)userdata;
+#ifdef CAMU_HAVE_PORTAL
+ bool have_python = false;
+#endif
+ u32 count;
+ while (aki_packet_cache_wait(&compat->queue, &count)) {
+ struct aki_packet *packet = aki_packet_cache_pop(&compat->queue);
+ if (!packet) {
+ aki_packet_cache_unlock(&compat->queue);
+ break;
+ }
+ str local;
+ aki_packet_read_str(packet, &local);
+ struct cch_entry *entry = NULL;
+ struct camu_post *post = NULL;
+ if (al_str_cmp(&local, al_str_c("cdda://"), 0, 7) == 0) {
+ entry = cch_handler_cdio_create();
+ struct cch_chapter *chapter = &al_array_at(entry->chapters, 0);
+ if (local.len > 7) {
+ s64 index = al_str_to_long(al_str_substr(&local, 7, local.len), 10);
+ if (index != INT64_MIN && index != INT64_MAX && index > 0 && index <= entry->chapters.size) {
+ chapter = &al_array_at(entry->chapters, index - 1);
+ }
+ }
+ entry->chapter = chapter;
+ entry->handler->maybe_spawn_worker(entry->handler, chapter->start);
+ } else {
+#ifdef CAMU_HAVE_PORTAL
+#ifndef AKIYO_HAS_CURL
+#error "Curl required to use portal"
+#endif
+ bool is_search = al_str_at(&local, 0) == ';';
+ if (is_search || camu_is_url(&local, 0)) {
+ if (!have_python) {
+ // Defer python init.
+ have_python = camu_python_init();
+ }
+ if (have_python) {
+ str uri;
+ if (uri_for_local(&local, is_search, &compat->bridge, &uri, &post)) {
+ entry = cch_handler_http_create(&uri, compat->server->loop);
+ }
+ }
+ } else {
+ entry = cch_handler_file_create(&local);
+ }
+#else
+#ifdef AKIYO_HAS_CURL
+ if (camu_is_url(&local, 0)) {
+ entry = cch_handler_http_create(&local, compat->server->loop);
+ } else {
+#endif
+ entry = cch_handler_file_create(&local);
+#ifdef AKIYO_HAS_CURL
+ }
+#endif
+#endif
+ }
+ aki_packet_cache_unlock(&compat->queue);
+ if (entry) {
+ struct camu_server_resource *resource = al_alloc_object(struct camu_server_resource);
+ al_str_clone(&resource->unique_id, &local);
+ resource->post = post;
+ resource->entry = entry;
+ resource->node = lia_server_create_node(&compat->server->data.server, entry);
+ camu_queue_push(compat->pending, resource);
+ aki_signal_send(&compat->worker_signal);
+ resource->duration = lia_node_get_duration(resource->node);
+ al_array_push(compat->server->data.resources, resource);
+ camu_queue_push(compat->results, resource);
+ aki_signal_send(&compat->result_signal);
+ } else {
+ al_log_info("local_compat", "No resource could be created for: %.*s.", AL_STR_PRINTF(&local));
+ }
+ aki_packet_free(packet);
+ }
+#ifdef CAMU_HAVE_PORTAL
+ if (have_python) camu_python_close();
+#endif
+ return 0;
+}
+
+static void result_signal_callback(void *userdata)
+{
+ struct camu_local_compat *compat = (struct camu_local_compat *)userdata;
+ u32 size;
+ struct camu_server_resource *resource;
+ do {
+ camu_queue_try_pop(compat->results, size, resource);
+ if (size == 0) break;
+ compat->callback(compat->userdata, resource);
+ } while (1);
+}
+
+void camu_local_compat_run(struct camu_local_compat *compat, struct camu_server *server)
+{
+ compat->server = server;
+#ifdef CAMU_HAVE_PORTAL
+ camu_post_cache_init(&compat->cache);
+ camu_portal_init(&compat->bridge, &compat->cache);
+#endif
+ // Size 0 to flush on the first packet.
+ aki_packet_cache_init(&compat->queue, 0);
+ aki_signal_init(&compat->worker_signal, worker_signal_callback, compat);
+ aki_signal_start(&compat->worker_signal, compat->server->loop);
+ camu_queue_init(compat->pending);
+ aki_signal_init(&compat->result_signal, result_signal_callback, compat);
+ aki_signal_start(&compat->result_signal, compat->server->loop);
+ camu_queue_init(compat->results);
+ aki_thread_create(&compat->thread, queue_thread, compat);
+}
+
+void camu_local_compat_send(struct camu_local_compat *compat, struct aki_packet *packet)
+{
+ aki_packet_cache_send_packet(&compat->queue, packet);
+}
+
+void camu_local_compat_stop(struct camu_local_compat *compat)
+{
+ aki_packet_cache_disable(&compat->queue);
+ aki_thread_join(&compat->thread);
+ aki_signal_stop(&compat->result_signal);
+ camu_queue_free(compat->results);
+ struct aki_packet *packet;
+ while ((packet = aki_packet_cache_pop(&compat->queue))) {
+ aki_packet_free(packet);
+ }
+}
diff --git a/src/server/local_compat.h b/src/server/local_compat.h
index cdb88ce..dcd2fc7 100644
--- a/src/server/local_compat.h
+++ b/src/server/local_compat.h
@@ -1,68 +1,31 @@
#pragma once
-#include "../portal/src/search.h"
+#include <al/str.h>
+#include <aki/packet_cache.h>
+#include <aki/signal.h>
-#ifdef AKIYO_HAS_CURL
-#include "../cache/handlers/http.h"
+#ifdef CAMU_HAVE_PORTAL
+#include "../portal/src/search.h"
#endif
-#include "../cache/handlers/file.h"
+#include "../util/queue.h"
-static struct cch_entry *entry_for_external_path(struct camu_portal *portal, str *path)
-{
- struct cch_entry *entry = NULL;
-#ifdef AKIYO_HAS_CURL
- bool is_search = al_str_at(path, 0) == ';';
- if (is_search || al_str_cmp(path, al_str_c("https://"), 0, 8) == 0 ||
- al_str_cmp(path, al_str_c("http://"), 0, 7) == 0) {
- str module;
- str query;
- al_str_from(&module, "");
- al_str_from(&query, "");
- if (al_str_cmp(path, al_str_c("https://twitter.com"), 0, 19) == 0 ||
- al_str_cmp(path, al_str_c("https://x.com"), 0, 13) == 0) {
- al_str_cat(&query, al_str_c("tweet:"));
- al_str_cat(&query, path);
- al_str_cat(&module, al_str_c("twitter"));
- } else if (al_str_cmp(path, al_str_c("https://instagram.com"), 0, 21) == 0) {
- s32 slash = al_str_rfind(path, '/');
- if (slash >= 0) {
- al_str_cat(&query, al_str_substr(path, slash + 1, path->len));
- }
- al_str_cat(&module, al_str_c("instagram"));
- } else {
- if (is_search) {
- al_str_cat(&query, al_str_substr(path, 1, path->len));
- } else {
- al_str_cat(&query, al_str_c("link:"));
- al_str_cat(&query, path);
- }
- al_str_cat(&module, al_str_c("youtube"));
- }
- s32 id = camu_portal_create_search(portal, &module, &query);
- al_str_free(&module);
- al_str_free(&query);
- if (id < 0) return NULL;
- struct camu_search *search = camu_portal_get_search(portal, id);
- if (!search || !camu_search_get_page(search, 0)) {
- return NULL;
- }
- struct camu_result_page *page = &al_array_at(search->pages, 0);
- struct camu_post *post;
- al_array_foreach_ptr(page->posts, i, post) {
- struct camu_post_media *media;
- al_array_foreach_ptr(post->media, j, media) {
- if (media->url.len > 0) {
- entry = cch_handler_http_create(&media->url);
- break;
- }
- }
- if (entry) break;
- }
- camu_portal_discard_search(portal, id);
- if (entry) entry->handler->maybe_spawn_worker(entry->handler, 0);
- } else // {
+struct camu_server_resource;
+struct camu_local_compat {
+ struct aki_thread thread;
+#ifdef CAMU_HAVE_PORTAL
+ struct camu_portal_bridge bridge;
+ struct camu_post_cache cache;
#endif
- entry = cch_handler_file_create(path);
- // }
- return entry;
-}
+ struct aki_packet_cache queue;
+ struct aki_signal worker_signal;
+ queue(struct camu_server_resource *) pending;
+ struct aki_signal result_signal;
+ queue(struct camu_server_resource *) results;
+ struct camu_server *server;
+ void (*callback)(void *, struct camu_server_resource *);
+ void *userdata;
+};
+
+void camu_local_compat_run(struct camu_local_compat *compat, struct camu_server *server);
+void camu_local_compat_send(struct camu_local_compat *compat, struct aki_packet *packet);
+void camu_local_compat_stop(struct camu_local_compat *compat);
diff --git a/src/server/meson.build b/src/server/meson.build
index d10b131..0617c2c 100644
--- a/src/server/meson.build
+++ b/src/server/meson.build
@@ -1,6 +1,10 @@
server_src = [
'server.c',
- 'db.c'
+ 'common.c',
+ 'list.c',
+ 'user.c',
+ 'db.c',
+ 'local_compat.c'
]
-server_deps = [common_deps, portal, cache, shrub_server, list]
-executable('server', sources: server_src, dependencies: server_deps)
+server_deps = [common_deps, cache, liana_server]
+server = declare_dependency(sources: server_src, dependencies: server_deps)
diff --git a/src/server/server.c b/src/server/server.c
index 4e11d3e..6c991eb 100644
--- a/src/server/server.c
+++ b/src/server/server.c
@@ -1,18 +1,17 @@
#include <al/log.h>
+#include <al/lib.h>
#include "../libsink/common.h"
#include "server.h"
#include "common.h"
+#include "list.h"
#include "db.h"
-#ifdef CAMU_LOCAL_SOCKET
-#include "local_compat.h"
-#endif
-static struct camu_user *get_user_by_username(struct camu_server *tree, str *username)
+static struct camu_user *get_user_by_username(struct camu_server *server, str *username)
{
struct camu_user *user;
- al_array_foreach(tree->users, i, user) {
+ al_array_foreach(server->users, i, user) {
if (al_str_eq(&user->name, username)) {
return user;
}
@@ -20,278 +19,303 @@ static struct camu_user *get_user_by_username(struct camu_server *tree, str *use
return NULL;
}
-static void list_callback(void *userdata, u8 op, void *opaque, void *prev_opaque, s32 sequence)
+static void list_sink_callback(void *userdata, u8 op, struct lia_list_entry *entry, s32 sequence, struct lia_timing *timing)
{
- struct camu_srv_sink *sink = (struct camu_srv_sink *)userdata;
- struct camu_srv_resource *resource = (struct camu_srv_resource *)opaque;
- struct camu_srv_resource *prev = (struct camu_srv_resource *)prev_opaque;
- struct aki_packet *packet = aki_rpc_get_packet(sink->conn->rpc, op);
- aki_packet_write_str(packet, CAMU_SERVER_IP);
- aki_packet_write_s32(packet, CAMU_PORT);
- aki_packet_write_u16(packet, resource->node->id);
- aki_packet_write_s32(packet, sequence);
- aki_packet_write_u64(packet, resource->node->start);
- aki_packet_write_u8(packet, resource->node->paused != SHRUB_NOT_PAUSED);
- if (prev) {
- aki_packet_write_u64(packet, prev->node->paused_at);
- } else {
- aki_packet_write_u64(packet, 0);
+ struct camu_server_sink *sink = (struct camu_server_sink *)userdata;
+ switch (op) {
+ case CAMU_SINK_CLEAR: {
+ struct aki_packet *packet = aki_rpc_get_packet(sink->conn->rpc, CAMU_SINK_SET);
+ aki_packet_write_u8(packet, op);
+ aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
+ break;
+ }
+ case CAMU_SINK_SET:
+ case CAMU_SINK_BUFFER:
+ case CAMU_SINK_BUFFER_AND_QUEUE: {
+ struct camu_server_resource *resource = (struct camu_server_resource *)entry->opaque;
+ struct aki_packet *packet = aki_rpc_get_packet(sink->conn->rpc, CAMU_SINK_SET);
+ aki_packet_write_u8(packet, op);
+ aki_packet_write_str(packet, &sink->server->addr);
+ aki_packet_write_u16(packet, CAMU_PORT);
+ aki_packet_write_u16(packet, resource->node->id);
+ aki_packet_write_s32(packet, sequence);
+ aki_packet_write_u64(packet, timing->at);
+ aki_packet_write_u64(packet, timing->seek_pos);
+ aki_packet_write_u8(packet, timing->pause);
+ aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
+ break;
+ }
+ case CAMU_SINK_PAUSE: {
+ struct aki_packet *packet = aki_rpc_get_packet(sink->conn->rpc, CAMU_SINK_PAUSE);
+ aki_packet_write_s32(packet, sequence);
+ aki_packet_write_u64(packet, timing->at);
+ aki_packet_write_u8(packet, timing->pause);
+ aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
+ break;
+ }
+ case CAMU_SINK_SEEK: {
+ struct aki_packet *packet = aki_rpc_get_packet(sink->conn->rpc, CAMU_SINK_SEEK);
+ aki_packet_write_s32(packet, sequence);
+ aki_packet_write_u64(packet, timing->at);
+ aki_packet_write_u64(packet, timing->seek_pos);
+ aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
+ break;
}
- aki_rpc_connection_command(sink->conn, packet, NULL, NULL);
- if (op == CAMU_SINK_SET) {
- al_printf("Now Playing: %.*s\n", AL_STR_PRINTF(&resource->unique_id));
}
}
static bool identify_command_callback(void *userdata, struct aki_rpc_connection *conn,
struct aki_packet *packet, struct aki_packet *rpacket)
{
- struct camu_server *srv = (struct camu_server *)userdata;
+ struct camu_server *server = (struct camu_server *)userdata;
(void)rpacket;
- switch (aki_packet_read_u8(packet)) {
+
+ u8 op = aki_packet_read_u8(packet);
+ switch (op) {
case CAMU_NODE: {
- struct camu_srv_node *node = al_alloc_object(struct camu_srv_node);
+ struct camu_server_node *node = al_alloc_object(struct camu_server_node);
node->conn = conn;
- al_array_push(srv->nodes, node);
+ al_array_push(server->nodes, node);
al_log_info("server", "New node.");
break;
}
case CAMU_CLIENT: {
+ struct camu_server_client *client = al_alloc_object(struct camu_server_client);
+ client->conn = conn;
str username;
aki_packet_read_str(packet, &username);
- struct camu_user *user = get_user_by_username(srv, &username);
- if (user) {
- struct camu_srv_client *client = al_alloc_object(struct camu_srv_client);
- client->conn = conn;
- client->user = user;
- al_array_push(srv->clients, client);
- //send_current_state(tree, user, conn);
- al_log_info("server", "User \"%.*s\" logged in.", AL_STR_PRINTF(&user->name));
- } else {
- al_log_info("server", "User \"%.*s\" not found.", AL_STR_PRINTF(&user->name));
+ struct camu_user *user = get_user_by_username(server, &username);
+ if (!user) {
+ user = al_alloc_object(struct camu_user);
+ al_str_clone(&user->name, &username);
+ al_array_push(server->users, user);
}
+ client->user = user;
+ al_array_push(server->clients, client);
+ al_log_info("server", "User \"%.*s\" logged in.", AL_STR_PRINTF(&user->name));
break;
}
case CAMU_SINK: {
- struct camu_srv_sink *sink = al_alloc_object(struct camu_srv_sink);
+ struct camu_server_sink *sink = al_alloc_object(struct camu_server_sink);
sink->conn = conn;
- al_array_push(srv->sinks, sink);
- struct camu_user *user = al_array_at(srv->users, 0);
- struct camu_list *list = al_array_at(user->lists, 0);
- camu_list_add_sink(list, list_callback, sink);
+ str name;
+ aki_packet_read_str(packet, &name);
+ al_str_clone(&sink->name, &name);
+ sink->server = server;
+ al_array_push(server->sinks, sink);
+ struct camu_list *list = al_array_at(server->lists, 0);
+ lia_list_add_sink(&list->impl, list_sink_callback, sink);
al_log_info("server", "New sink.");
break;
}
}
+
aki_packet_free(packet);
return true;
}
-static void list_entry_callback(void *entry, u8 op, void *opaque)
+static struct camu_server_client *get_client_by_connection(struct camu_server *server, struct aki_rpc_connection *conn)
{
- struct camu_srv_resource *resource = (struct camu_srv_resource *)entry;
- switch (op) {
- case CAMU_LIST_ENTRY_ID:
- *(str **)opaque = &resource->unique_id;
- break;
- case CAMU_LIST_IMPULSE:
- shrb_node_set_start(resource->node, *(u64 *)opaque);
- break;
- case CAMU_LIST_USER_PAUSE:
- shrb_node_user_pause(resource->node, *(u64 *)opaque);
- break;
- case CAMU_LIST_USER_RESUME: {
- struct shrb_resume_req *req = (struct shrb_resume_req *)opaque;
- shrb_node_user_resume(resource->node, req);
- break;
+ struct camu_server_client *client;
+ al_array_foreach(server->clients, i, client) {
+ if (client->conn == conn) return client;
}
- case CAMU_LIST_PAUSE:
- shrb_node_pause(resource->node);
- break;
- case CAMU_LIST_SEEK:
- shrb_node_seek(resource->node, *(u64 *)opaque);
- break;
+ return NULL;
+}
+
+static struct camu_list *get_list_from_name(struct camu_server *server, str *name)
+{
+ struct camu_list *list;
+ al_array_foreach(server->lists, i, list) {
+ if (al_str_eq(&list->name, name)) return list;
}
+ return NULL;
}
-#ifdef CAMU_LOCAL_SOCKET
-static void list_add_local(struct camu_server *srv, struct camu_list *list, str *line)
+static struct camu_server_sink *get_sink_from_name(struct camu_server *server, str *name)
{
- struct cch_entry *entry = entry_for_external_path(&srv->portal, line);
- if (entry) {
- struct shrb_node *node = shrb_server_create_node(&srv->resource, 0, entry);
- if (node) {
- struct camu_srv_resource *resource = al_alloc_object(struct camu_srv_resource);
- al_array_push(srv->resources, resource);
- al_str_clone(&resource->unique_id, line);
- resource->entry = entry;
- resource->node = node;
- camu_list_add(list, resource, list_entry_callback, resource->node->duration, false);
- }
+ struct camu_server_sink *sink;
+ al_array_foreach(server->sinks, i, sink) {
+ if (al_str_eq(&sink->name, name)) return sink;
+ }
+ return NULL;
+}
+
+static bool client_command_command_callback(void *userdata, struct aki_rpc_connection *conn,
+ struct aki_packet *packet, struct aki_packet *rpacket)
+{
+ struct camu_server *server = (struct camu_server *)userdata;
+ (void)rpacket;
+
+ struct camu_server_client *client = get_client_by_connection(server, conn);
+ if (!client) goto out;
+
+ u8 op = aki_packet_read_u8(packet);
+ switch (op) {
+ case CAMU_CLIENT_CREATE_LIST: {
+ str name;
+ aki_packet_read_str(packet, &name);
+ struct camu_list *list = al_alloc_object(struct camu_list);
+ camu_list_init(list, &name);
+ al_array_push(server->lists, list);
+ break;
+ }
+ case CAMU_CLIENT_ENABLE_SINK: {
+ str name;
+ aki_packet_read_str(packet, &name);
+ struct camu_list *list = get_list_from_name(server, &name);
+ aki_packet_read_str(packet, &name);
+ struct camu_server_sink *sink = get_sink_from_name(server, &name);
+ lia_list_add_sink(&list->impl, list_sink_callback, sink);
+ break;
+ }
}
+
+out:
+ aki_packet_free(packet);
+ return true;
}
-#endif
static bool list_action_command_callback(void *userdata, struct aki_rpc_connection *conn,
struct aki_packet *packet, struct aki_packet *rpacket)
{
- struct camu_server *srv = (struct camu_server *)userdata;
+ struct camu_server *server = (struct camu_server *)userdata;
(void)conn;
(void)rpacket;
- struct camu_user *user = al_array_at(srv->users, 0);
- struct camu_list *list = al_array_at(user->lists, 0);
+ str name;
+ aki_packet_read_str(packet, &name);
- switch (aki_packet_read_u8(packet)) {
- case CAMU_ADD: {
- str line;
- aki_packet_read_str(packet, &line);
-#ifdef CAMU_LOCAL_SOCKET
- list_add_local(srv, list, &line);
-#endif
- break;
+ struct camu_list *list = get_list_from_name(server, &name);
+ if (!list) goto out;
+
+ u8 op = aki_packet_read_u8(packet);
+ switch (op) {
+ case CAMU_LIST_ADD: {
+ camu_local_compat_send(&server->compat, packet);
+ return false;
}
- case CAMU_SKIP: {
+ case CAMU_LIST_SKIP: {
s32 sequence = aki_packet_read_s32(packet);
s32 n = aki_packet_read_s32(packet);
- camu_list_skip(list, sequence, n);
+ lia_list_skip(&list->impl, sequence, n);
break;
}
- case CAMU_SKIPTO: {
+ case CAMU_LIST_SKIPTO: {
+ s32 sequence = aki_packet_read_s32(packet);
s32 i = aki_packet_read_s32(packet);
- camu_list_skipto(list, i);
+ lia_list_skipto(&list->impl, sequence, i);
+ break;
+ }
+ case CAMU_LIST_SHUFFLE: {
+ lia_list_shuffle(&list->impl);
break;
}
- case CAMU_TOGGLE_PAUSE: {
+ case CAMU_LIST_TOGGLE_PAUSE: {
s32 sequence = aki_packet_read_s32(packet);
- u64 pos = aki_packet_read_u64(packet);
- camu_list_toggle_pause(list, sequence, pos);
+ f64 pts = aki_packet_read_f64(packet);
+ lia_list_toggle_pause(&list->impl, sequence, pts);
break;
}
- case CAMU_SEEK: {
+ case CAMU_LIST_SEEK: {
s32 sequence = aki_packet_read_s32(packet);
f64 percent = aki_packet_read_f64(packet);
- camu_list_seek(list, sequence, percent);
+ lia_list_seek(&list->impl, sequence, percent);
+ break;
+ }
+ case CAMU_LIST_UNSET: {
+ lia_list_unset(&list->impl);
break;
}
- case CAMU_FINISHED: {
+ case CAMU_LIST_END: {
s32 sequence = aki_packet_read_s32(packet);
- camu_list_finished(list, sequence);
+ lia_list_end(&list->impl, sequence);
break;
}
}
+out:
aki_packet_free(packet);
return false;
}
static struct aki_rpc_command commands[] = {
{ .op = CAMU_SERVER_IDENTIFY, .callback = identify_command_callback, .userdata = NULL },
+ { .op = CAMU_SERVER_CLIENT_COMMAND, .callback = client_command_command_callback, .userdata = NULL },
{ .op = CAMU_SERVER_LIST_ACTION, .callback = list_action_command_callback, .userdata = NULL }
};
static void connection_callback(void *userdata, struct aki_rpc_connection *conn)
{
+ // TODO: Cleanup zombie connections.
(void)userdata;
(void)conn;
}
-static void cleanup_node(struct camu_srv_node *node)
+static void cleanup_node(struct camu_server_node *node)
{
al_free(node);
}
-static void cleanup_client(struct camu_srv_client *client)
+static void cleanup_client(struct camu_server_client *client)
{
al_free(client);
}
-static void cleanup_sink(struct camu_srv_sink *sink)
+static void cleanup_sink(struct camu_server_sink *sink)
{
+ al_str_free(&sink->name);
al_free(sink);
}
static void connection_closed_callback(void *userdata, struct aki_rpc_connection *conn)
{
- struct camu_server *srv = (struct camu_server *)userdata;
+ struct camu_server *server = (struct camu_server *)userdata;
- struct camu_srv_node *node;
- al_array_foreach(srv->nodes, i, node) {
+ struct camu_server_node *node;
+ al_array_foreach(server->nodes, i, node) {
if (node->conn == conn) {
- al_log_info("server", "Node removed.");
cleanup_node(node);
- al_array_remove_at_iter(srv->nodes, i);
+ al_array_remove_at(server->nodes, i);
+ al_log_info("server", "Node removed.");
break;
}
}
- struct camu_srv_client *client;
- al_array_foreach(srv->clients, i, client) {
+ struct camu_server_client *client;
+ al_array_foreach(server->clients, i, client) {
if (client->conn == conn) {
- al_log_info("server", "User \"%.*s\" logged out.", AL_STR_PRINTF(&client->user->name));
cleanup_client(client);
- al_array_remove_at_iter(srv->clients, i);
+ al_array_remove_at(server->clients, i);
+ al_log_info("server", "User \"%.*s\" logged out.", AL_STR_PRINTF(&client->user->name));
break;
}
}
- struct camu_srv_sink *sink;
- al_array_foreach(srv->sinks, i, sink) {
+ struct camu_server_sink *sink;
+ al_array_foreach(server->sinks, i, sink) {
if (sink->conn == conn) {
- al_log_info("server", "Sink removed.");
- al_array_remove_at_iter(srv->sinks, i);
- struct camu_user *user = al_array_at(srv->users, 0);
- struct camu_list *list = al_array_at(user->lists, 0);
- camu_list_remove_sink(list, sink);
+ al_array_remove_at(server->sinks, i);
+ struct camu_list *list;
+ al_array_foreach(server->lists, j, list) {
+ lia_list_remove_sink(&list->impl, sink);
+ }
cleanup_sink(sink);
+ al_log_info("server", "Sink removed.");
break;
}
}
}
-
-#ifdef CAMU_LOCAL_SOCKET
-static u8 server_line_callback(void *userdata, str *line)
-{
- struct camu_server *srv = (struct camu_server *)userdata;
- struct camu_user *user = al_array_at(srv->users, 0);
- struct camu_list *list = al_array_at(user->lists, 0);
- if (al_str_eq(line, al_str_c(";NEXT"))) {
- camu_list_skip(list, CAMU_SEQUENCE_INVALID, 1);
- } else if (al_str_eq(line, al_str_c(";PREV"))) {
- camu_list_skip(list, CAMU_SEQUENCE_INVALID, -1);
- } else if (al_str_eq(line, al_str_c(";SHUFFLE"))) {
- camu_list_shuffle(list);
- } else if (al_str_eq(line, al_str_c(";SORT"))) {
- camu_list_sort(list);
- } else if (al_str_eq(line, al_str_c(";REVERSE"))) {
- camu_list_reverse(list);
- } else if (al_str_eq(line, al_str_c(";CLEAR"))) {
- } else {
- list_add_local(srv, list, line);
- }
- return AKI_LINE_PROCESSOR_CONTINUE;
-}
-#endif
-
-static void sigint_handler(s32 signum)
-{
- (void)signum;
- // explode.
- exit(EXIT_FAILURE);
-}
-
-static struct camu_server srv = { 0 };
-
-static bool multiplex_callback(void *userdata, u8 id, struct aki_socket *s)
+static bool multiplex_callback(void *userdata, u8 id, struct aki_socket *sock)
{
- struct camu_server *srv = (struct camu_server *)userdata;
+ struct camu_server *server = (struct camu_server *)userdata;
switch (id) {
case CAMU_MULTIPLEX_RPC:
- aki_rpc_add_socket(&srv->server, s);
+ aki_rpc_add_socket(&server->server, sock);
return true;
- case CAMU_MULTIPLEX_SHRUB:
- shrb_server_add_socket(&srv->resource, s);
+ case CAMU_MULTIPLEX_LIANA:
+ lia_server_add_socket(&server->data.server, sock);
return true;
default:
break;
@@ -299,56 +323,71 @@ static bool multiplex_callback(void *userdata, u8 id, struct aki_socket *s)
return false;
}
-s32 main(void)
+static void local_compat_callback(void *userdata, struct camu_server_resource *resource)
{
- aki_common_init();
-
- signal(SIGINT, sigint_handler);
-
- al_array_init(srv.nodes);
- al_array_init(srv.clients);
- al_array_init(srv.sinks);
- al_array_init(srv.users);
-
- if (!camu_db_open(&srv, CAMU_DB_PATH)) return EXIT_FAILURE;
-
- camu_post_cache_init(&srv.cache);
-
- bool py_init = camu_python_init();
+ struct camu_server *server = (struct camu_server *)userdata;
+ struct camu_list *list = al_array_last(server->lists);
+ if (resource) lia_list_add(&list->impl, resource, resource->duration, &resource->unique_id);
+}
- camu_portal_init(&srv.portal, &srv.cache);
+bool camu_server_init(struct camu_server *server, u8 type, struct aki_event_loop *loop)
+{
+ server->loop = loop;
+ server->addr = al_str_zero();
+ al_array_init(server->nodes);
+ al_array_init(server->clients);
+ al_array_init(server->sinks);
+ al_array_init(server->users);
+ al_array_init(server->lists);
- aki_event_loop_init(&srv.loop);
+ struct camu_list *list = al_alloc_object(struct camu_list);
+ camu_list_init(list, al_str_c("default"));
+ al_array_push(server->lists, list);
- aki_rpc_init(&srv.server, &srv.loop, connection_callback, connection_closed_callback, &srv);
+ aki_rpc_init(&server->server, server->loop, connection_callback, connection_closed_callback, server);
for (u32 i = 0; i < AL_ARRAY_SIZE(commands); i++) {
- commands[i].userdata = &srv;
- aki_rpc_add_command(&srv.server, &commands[i]);
+ commands[i].userdata = server;
+ aki_rpc_add_command(&server->server, &commands[i]);
}
- shrb_server_init(&srv.resource, &srv.loop);
+ lia_server_init(&server->data.server, server->loop);
- aki_multiplex_socket_init(&srv.multi, AKI_SOCKET_TCP, multiplex_callback, &srv);
- aki_multiplex_socket_listen(&srv.multi, &srv.loop, al_str_c("0.0.0.0"), CAMU_PORT);
+ server->compat.callback = local_compat_callback;
+ server->compat.userdata = server;
+ camu_local_compat_run(&server->compat, server);
-#ifdef CAMU_LOCAL_SOCKET
- srv.socket.type = AKI_SOCKET_UNIX;
- aki_socket_init(&srv.socket);
- aki_socket_set_blocking(&srv.socket, false);
- srv.pro.callback = server_line_callback;
- srv.pro.userdata = &srv;
- aki_line_processor_init(&srv.pro, al_str_c("\n"));
- aki_line_processor_open_socket(&srv.pro, &srv.socket);
- if (aki_socket_listen(&srv.socket, al_str_c("/tmp/camu_sock"), 0)) {
- aki_line_processor_run(&srv.pro, &srv.loop);
- }
-#endif
+ return aki_multiplex_socket_init(&server->multi, type, multiplex_callback, server);
+}
- aki_event_loop_run(&srv.loop);
+bool camu_server_listen(struct camu_server *server, str *addr, u16 port)
+{
+ al_str_clone(&server->addr, addr);
+ if (server->multi.sock.type == AKI_SOCKET_TCP) addr = NULL; // any
+ return aki_multiplex_socket_listen(&server->multi, server->loop, addr, port);
+}
- if (py_init) camu_python_close();
+void camu_server_close(struct camu_server *server)
+{
+ camu_local_compat_stop(&server->compat);
+ lia_server_close(&server->data.server);
+ aki_multiplex_socket_close(&server->multi);
+}
- aki_common_close();
+void camu_server_free(struct camu_server *server)
+{
+ // This needs to happen in flight.
+ // Too many things can block us before we get here.
+ struct camu_server_resource *resource;
+ al_array_foreach(server->data.resources, i, resource) {
+ cch_entry_free(&resource->entry);
+ }
+ al_array_free(server->data.resources);
+ lia_server_free(&server->data.server);
+ aki_rpc_free(&server->server);
+ al_str_free(&server->addr);
+}
- return EXIT_SUCCESS;
+void camu_server_local_add(struct camu_server *server, struct aki_packet *packet)
+{
+ camu_local_compat_send(&server->compat, packet);
}
diff --git a/src/server/server.h b/src/server/server.h
index 9565bf6..528f08a 100644
--- a/src/server/server.h
+++ b/src/server/server.h
@@ -1,56 +1,57 @@
#pragma once
-#define CAMU_LOCAL_SOCKET
-
#include <aki/multiplex.h>
#include <aki/rpc2.h>
-#ifdef CAMU_LOCAL_SOCKET
-#include <aki/line_processor.h>
-#endif
-#include "../portal/src/search.h"
#include "../cache/entry.h"
-#include "../shrub/server.h"
-#include "../list/list.h"
+#include "../liana/server.h"
-struct camu_srv_node {
- struct aki_rpc_connection *conn;
-};
+#include "user.h"
+#include "local_compat.h"
-struct camu_user {
- str name;
- array(struct camu_list *) lists;
+struct camu_server_node {
+ struct aki_rpc_connection *conn;
};
-struct camu_srv_client {
+struct camu_server_client {
struct aki_rpc_connection *conn;
struct camu_user *user;
};
-struct camu_srv_sink {
+struct camu_server_sink {
struct aki_rpc_connection *conn;
+ str name;
+ struct camu_server *server;
};
-struct camu_srv_resource {
+struct camu_server_resource {
str unique_id;
+ struct camu_post *post;
struct cch_entry *entry;
- struct shrb_node *node;
+ struct lia_node *node;
+ u64 duration;
};
struct camu_server {
- struct aki_event_loop loop;
+ struct aki_event_loop *loop;
+ str addr;
struct aki_multiplex_socket multi;
struct aki_rpc server;
- array(struct camu_srv_node *) nodes;
- array(struct camu_srv_client *) clients;
- array(struct camu_srv_sink *) sinks;
+ array(struct camu_server_node *) nodes;
+ array(struct camu_server_client *) clients;
+ array(struct camu_server_sink *) sinks;
array(struct camu_user *) users;
- struct shrb_server resource;
- array(struct camu_srv_resource *) resources;
- struct camu_portal portal;
- struct camu_post_cache cache;
-#ifdef CAMU_LOCAL_SOCKET
- struct aki_socket socket;
- struct aki_line_processor pro;
-#endif
+ array(struct camu_list *) lists;
+ struct camu_local_compat compat;
+ struct {
+ struct lia_server server;
+ array(struct camu_server_resource *) resources;
+ } data;
};
+
+bool camu_server_init(struct camu_server *server, u8 type, struct aki_event_loop *loop);
+bool camu_server_listen(struct camu_server *server, str *addr, u16 port);
+void camu_server_close(struct camu_server *server);
+void camu_server_free(struct camu_server *server);
+
+void camu_server_local_add(struct camu_server *server, struct aki_packet *packet);
diff --git a/src/server/user.c b/src/server/user.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/server/user.c
diff --git a/src/server/user.h b/src/server/user.h
new file mode 100644
index 0000000..9800c45
--- /dev/null
+++ b/src/server/user.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <al/str.h>
+#include <al/array.h>
+
+struct camu_user {
+ str name;
+};
diff --git a/src/shrub/client.c b/src/shrub/client.c
deleted file mode 100644
index abe2d23..0000000
--- a/src/shrub/client.c
+++ /dev/null
@@ -1,283 +0,0 @@
-#include <al/log.h>
-
-#include "../codec/ffmpeg/packet_ext.h"
-#include "../server/common.h"
-
-#include "handlers/codec.h"
-
-#include "client.h"
-#include "handler.h"
-#include "common.h"
-
-static void send_reconnect_packet(struct shrb_client *client)
-{
- struct aki_packet *packet = aki_packet_create();
- aki_packet_write_u8(packet, SHRUB_CONTROL_RECONNECT);
- aki_packet_write_u32(packet, client->level);
- aki_packet_stream_send_packet(&client->control, packet);
-}
-
-static void cleanup_connection_internal(struct shrb_client *client)
-{
- client->callback(client->userdata, SHRUB_CLIENT_CLOSED, NULL, NULL);
-}
-
-static void packet_sent_callback(void *userdata, struct aki_packet *packet)
-{
- (void)userdata;
- aki_packet_free(packet);
-}
-
-static void data_packet_callback(void *userdata, struct aki_packet_stream *stream,
- struct aki_packet *packet)
-{
- struct shrb_client *client = (struct shrb_client *)userdata;
- (void)stream;
- shrb_vcr_push_packet(&client->vcr, packet);
-}
-
-static void data_connection_callback(void *userdata, struct aki_packet_stream *stream)
-{
- struct shrb_client *client = (struct shrb_client *)userdata;
- (void)stream;
- client->data.packet_callback = data_packet_callback;
- client->data.packet_sent_callback = packet_sent_callback;
- struct aki_packet *packet = aki_packet_create();
- aki_packet_write_u8(packet, SHRUB_CONNECTION_DATA);
- aki_packet_write_u16(packet, client->connection_id);
- aki_packet_write_u16(packet, client->node_id);
- aki_packet_write_u64(packet, client->seek_pos);
- aki_packet_stream_send_packet(&client->data, packet);
-}
-
-static void data_connection_closed_callback(void *userdata, struct aki_packet_stream *stream)
-{
- struct shrb_client *client = (struct shrb_client *)userdata;
- (void)stream;
- if (client->closed) {
- if (!client->control.connected) cleanup_connection_internal(client);
- return;
- }
- client->callback(client->userdata, SHRUB_CLIENT_REMOVE_BUFFERS, NULL, NULL);
- shrb_vcr_flush(&client->vcr);
- send_reconnect_packet(client);
-}
-
-void connect_data(struct shrb_client *client)
-{
- if (!client->reconnect) client->reconnect = true;
- else aki_packet_stream_free(&client->data);
- aki_packet_stream_init(&client->data, AKI_SOCKET_TCP, data_connection_callback,
- data_connection_closed_callback, client);
- aki_packet_stream_set_multiplex(&client->data, CAMU_MULTIPLEX_SHRUB);
- aki_packet_stream_connect(&client->data, client->loop, &client->addr, client->port);
-}
-
-static void parse_info_packet(struct shrb_client *client, struct aki_packet *packet)
-{
- client->duration = aki_packet_read_u64(packet);
- u32 count = aki_packet_read_u32(packet);
- for (u32 i = 0; i < count; i++) {
- struct shrb_vcr_stream *stream = al_alloc_object(struct shrb_vcr_stream);
- stream->client = shrb_codec_client_create();
- stream->client->callback = client->callback;
- stream->client->userdata = client->userdata;
- stream->stream.type = aki_packet_read_u8(packet);
- switch (stream->stream.type) {
- case CAMU_NORMAL:
- stream->type = SHRUB_STREAM_VIDEO;
- stream->index = 0;
- stream->stream.video.width = aki_packet_read_s32(packet);
- stream->stream.video.height = aki_packet_read_s32(packet);
- break;
-#ifdef CAMU_HAVE_FFMPEG
- case CAMU_FFMPEG_COMPAT: {
- stream->stream.type = CAMU_FFMPEG_COMPAT;
- stream->stream.av.format_context = avformat_alloc_context();
- const AVCodec *codec = avcodec_find_decoder(aki_packet_read_av_codec_id(packet));
- stream->stream.av.stream = aki_packet_read_av_stream(stream->stream.av.format_context, codec, packet);
- stream->index = stream->stream.av.stream->index;
- switch (stream->stream.av.stream->codecpar->codec_type) {
- case AVMEDIA_TYPE_AUDIO:
- stream->type = SHRUB_STREAM_AUDIO;
- al_array_push(client->subscribed, stream->index);
- break;
- case AVMEDIA_TYPE_VIDEO:
- stream->type = SHRUB_STREAM_VIDEO;
- al_array_push(client->subscribed, stream->index);
- break;
- case AVMEDIA_TYPE_SUBTITLE:
- default:
- stream->type = SHRUB_STREAM_UNKNOWN;
- break;
- }
- break;
- }
-#endif
- }
- if (!stream->client->init(stream->client, NULL, stream)) {
- // TODO: Handle this.
- }
- shrb_vcr_add_stream(&client->vcr, stream);
- }
-}
-
-static void disconnect_data_internal(struct shrb_client *client)
-{
- client->corked = false;
- aki_packet_stream_disconnect(&client->data);
-}
-
-static void send_subscribe_packet(struct shrb_client *client)
-{
- struct aki_packet *packet = aki_packet_create();
- aki_packet_write_u8(packet, SHRUB_CONTROL_SUBSCRIBE);
- aki_packet_write_u32(packet, client->subscribed.size);
- s32 index;
- al_array_foreach(client->subscribed, i, index) {
- aki_packet_write_s32(packet, index);
- }
- aki_packet_stream_send_packet(&client->control, packet);
-}
-
-static void parse_start_packet(struct shrb_client *client, struct aki_packet *packet)
-{
- u64 ts = aki_packet_read_u64(packet);
- u64 delay = aki_packet_read_u64(packet);
- u64 seek_pos = aki_packet_read_u64(packet);
- u8 paused = aki_packet_read_u8(packet);
- u64 paused_at = aki_packet_read_u64(packet);
- struct shrb_seek_req req = {
- .base = seek_pos,
- .ts = ts,
- .delay = delay,
- .paused = paused,
- .paused_at = paused_at
- };
- client->callback(client->userdata, SHRUB_CLIENT_SET, NULL, &req);
- client->seek_pos = req.base;
-}
-
-static void control_packet_callback(void *userdata, struct aki_packet_stream *stream,
- struct aki_packet *packet)
-{
- struct shrb_client *client = (struct shrb_client *)userdata;
- (void)stream;
-
- switch (aki_packet_read_u8(packet)) {
- case SHRUB_CONTROL_INFO:
- client->connection_id = aki_packet_read_u16(packet);
- parse_info_packet(client, packet);
- send_subscribe_packet(client);
- break;
- case SHRUB_CONTROL_START:
- parse_start_packet(client, packet);
- connect_data(client);
- break;
- case SHRUB_CONTROL_PAUSE: {
- f64 pts = aki_packet_read_u64(packet) / 1000000.f;
- client->callback(client->userdata, SHRUB_CLIENT_PAUSE, NULL, &pts);
- break;
- }
- case SHRUB_CONTROL_RESUME: {
- u64 start = aki_packet_read_u64(packet);
- u64 offset = aki_packet_read_u64(packet);
- struct shrb_resume_req req = {
- .start = start,
- .offset = offset
- };
- client->callback(client->userdata, SHRUB_CLIENT_RESUME, NULL, &req);
- break;
- }
- case SHRUB_CONTROL_SEEK:
- client->level = 0;
- disconnect_data_internal(client);
- break;
- default:
- break;
- }
-
- aki_packet_free(packet);
-}
-
-static void control_connection_callback(void *userdata, struct aki_packet_stream *stream)
-{
- struct shrb_client *client = (struct shrb_client *)userdata;
- (void)stream;
- client->control.packet_callback = control_packet_callback;
- client->control.packet_sent_callback = packet_sent_callback;
- struct aki_packet *packet = aki_packet_create();
- aki_packet_write_u8(packet, SHRUB_CONNECTION_CONTROL);
- aki_packet_write_u16(packet, 0);
- aki_packet_write_u16(packet, client->node_id);
- aki_packet_stream_send_packet(&client->control, packet);
-}
-
-static void control_connection_closed_callback(void *userdata, struct aki_packet_stream *stream)
-{
- struct shrb_client *client = (struct shrb_client *)userdata;
- (void)stream;
- if (client->closed) {
- if (!client->data.connected) cleanup_connection_internal(client);
- return;
- }
- // TODO: Reconnect.
-}
-
-bool shrb_client_connect(struct shrb_client *client, struct aki_event_loop *loop, str *addr, s32 port, u16 node_id)
-{
- client->loop = loop;
- client->node_id = node_id;
- al_str_clone(&client->addr, addr);
- client->port = port;
- client->closed = false;
- client->reconnect = false;
- client->level = 0;
- client->corked = false;
- al_array_init(client->subscribed);
- shrb_vcr_init(&client->vcr, client->loop, &client->data);
- if (!aki_packet_stream_init(&client->control, AKI_SOCKET_TCP, control_connection_callback,
- control_connection_closed_callback, client)) {
- return false;
- }
- aki_packet_stream_set_nodelay(&client->control, 1);
- aki_packet_stream_set_multiplex(&client->control, CAMU_MULTIPLEX_SHRUB);
- aki_packet_stream_connect(&client->control, client->loop, &client->addr, client->port);
- return true;
-}
-
-void shrb_client_reseek(struct shrb_client *client)
-{
- client->level++;
- al_log_debug("bimu", "Attempting re-seek at level %i.", client->level);
- disconnect_data_internal(client);
-}
-
-void shrb_client_seek(struct shrb_client *client, f64 percent)
-{
- u64 pos = client->duration * percent;
- struct aki_packet *packet = aki_packet_create();
- aki_packet_write_u8(packet, SHRUB_CONTROL_SEEK);
- aki_packet_write_u64(packet, pos);
- aki_packet_stream_send_packet(&client->control, packet);
-}
-
-void shrb_client_close(struct shrb_client *client)
-{
- struct shrb_vcr_stream *stream;
- al_array_foreach(client->vcr.streams, i, stream) {
- shrb_vcr_stream_close(stream);
- }
- client->closed = true;
- aki_packet_stream_disconnect(&client->control);
- aki_packet_stream_disconnect(&client->data);
-}
-
-void shrb_client_free(struct shrb_client *client)
-{
- aki_packet_stream_free(&client->control);
- aki_packet_stream_free(&client->data);
- al_str_free(&client->addr);
- al_array_free(client->subscribed);
- shrb_vcr_free(&client->vcr);
-}
diff --git a/src/shrub/client.h b/src/shrub/client.h
deleted file mode 100644
index 14f7da2..0000000
--- a/src/shrub/client.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#pragma once
-
-#include <al/types.h>
-#include <aki/packet_stream.h>
-
-#include "vcr.h"
-
-struct shrb_client {
- struct aki_event_loop *loop;
- u16 node_id;
- u16 connection_id;
- str addr;
- s32 port;
- struct aki_packet_stream control;
- struct aki_packet_stream data;
- bool closed;
- bool reconnect;
- u32 level;
- bool corked;
- u64 duration;
- u64 seek_pos;
- array(s32) subscribed;
- struct shrb_vcr vcr;
- void (*callback)(void *, u8, struct shrb_vcr_stream *stream, void *);
- void *userdata;
-};
-
-bool shrb_client_connect(struct shrb_client *client, struct aki_event_loop *loop, str *addr, s32 port, u16 node_id);
-void shrb_client_toggle_pause(struct shrb_client *client, f64 pts);
-void shrb_client_reseek(struct shrb_client *client);
-void shrb_client_seek(struct shrb_client *client, f64 percent);
-void shrb_client_close(struct shrb_client *client);
-void shrb_client_free(struct shrb_client *client);
diff --git a/src/shrub/common.h b/src/shrub/common.h
deleted file mode 100644
index 882eb49..0000000
--- a/src/shrub/common.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-
-enum {
- SHRUB_STREAM_AUDIO = 0,
- SHRUB_STREAM_VIDEO,
- SHRUB_STREAM_UNKNOWN,
-};
-
-enum {
- SHRUB_CONNECTION_CONTROL = 0,
- SHRUB_CONNECTION_DATA
-};
-
-enum {
- SHRUB_CONTROL_INFO = 0,
- SHRUB_CONTROL_SUBSCRIBE,
- SHRUB_CONTROL_RECONNECT,
- SHRUB_CONTROL_START,
- SHRUB_CONTROL_PAUSE,
- SHRUB_CONTROL_RESUME,
- SHRUB_CONTROL_SEEK
-};
-
-#define SHRUB_BASE_DELAY 500000Lu
-#define SHRUB_PAUSE_DELAY 60000Lu
-#define SHRUB_DELAY_IGNORE 0Lu
diff --git a/src/shrub/handler.h b/src/shrub/handler.h
deleted file mode 100644
index 7242130..0000000
--- a/src/shrub/handler.h
+++ /dev/null
@@ -1,59 +0,0 @@
-#pragma once
-
-#include <aki/packet_pool.h>
-
-#include "../codec/codec.h"
-#include "../cache/handle.h"
-
-#include "vcr.h"
-
-struct shrb_server_handler {
- bool (*init)(struct shrb_server_handler *, struct cch_handle *, struct aki_packet_pool *);
- void (*write_info)(struct shrb_server_handler *, struct aki_packet *);
- void (*subscribe)(struct shrb_server_handler *, s32, bool);
- u64 (*get_duration)(struct shrb_server_handler *);
- bool (*seek)(struct shrb_server_handler *, u64);
- bool (*step)(struct shrb_server_handler *);
- void (*free)(struct shrb_server_handler **);
-};
-
-enum {
- SHRUB_PACKET_DATA = 0,
- SHRUB_PACKET_EOF,
- SHRUB_PACKET_ERROR
-};
-
-enum {
- SHRUB_CLIENT_CONFIGURE = 0,
- SHRUB_CLIENT_SET,
- SHRUB_CLIENT_PAUSE,
- SHRUB_CLIENT_RESUME,
- SHRUB_CLIENT_DATA,
- SHRUB_CLIENT_REMOVE_BUFFERS,
- SHRUB_CLIENT_EOF,
- SHRUB_CLIENT_CLOSED
-};
-
-struct shrb_resume_req {
- u64 start;
- u64 offset;
-};
-
-struct shrb_seek_req {
- u64 base;
- u64 ts;
- u64 delay;
- bool paused;
- u64 paused_at;
-};
-
-struct shrb_client_stream;
-struct shrb_client_handler {
- bool (*init)(struct shrb_client_handler *, struct camu_renderer *, struct shrb_vcr_stream *);
- void (*handle_packet)(struct shrb_client_handler *, struct aki_packet *);
- void (*flush)(struct shrb_client_handler *);
- void (*free)(struct shrb_client_handler **);
- struct shrb_vcr_stream *stream;
- void (*callback)(void *, u8, struct shrb_vcr_stream *stream, void *);
- void *userdata;
-};
diff --git a/src/shrub/handlers/cdio.h b/src/shrub/handlers/cdio.h
deleted file mode 100644
index 0f614af..0000000
--- a/src/shrub/handlers/cdio.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include <cdio/paranoia/cdda.h>
-#include <cdio/cd_types.h>
-
-#include "../handler.h"
-
-struct bmu_cdio_server {
- struct bmu_server_handler handler;
-};
-
-struct bmu_cdio_client {
- struct bmu_client_handler handler;
-};
-
-struct bmu_server_handler *bmu_cdio_server_create(void);
-struct bmu_client_handler *bmu_cdio_client_create(void);
diff --git a/src/shrub/handlers/cdio_server.c b/src/shrub/handlers/cdio_server.c
deleted file mode 100644
index 151b94f..0000000
--- a/src/shrub/handlers/cdio_server.c
+++ /dev/null
@@ -1,21 +0,0 @@
-#include "cdio.h"
-
-static bool cdio_server_init(struct bmu_server_handler *handler, struct cch_handle *handle,
- struct aki_packet_pool *pool)
-{
- struct bmu_cdio_server *cdio = (struct bmu_cdio_server *)handler;
- return true;
-}
-
-struct bmu_server_handler *bmu_cdio_server_create(void)
-{
- struct bmu_cdio_server *cdio = al_alloc_object(struct bmu_cdio_server);
- cdio->handler.init = cdio_server_init;
-// cdio->handler.write_info = cdio_server_write_info;
-// cdio->handler.subscribe = cdio_server_subscribe;
-// cdio->handler.get_duration = cdio_server_get_duration;
-// cdio->handler.seek = cdio_server_seek;
-// cdio->handler.step = cdio_server_step;
-// cdio->handler.free = cdio_server_free;
- return (struct bmu_server_handler *)cdio;
-}
diff --git a/src/shrub/handlers/codec.h b/src/shrub/handlers/codec.h
deleted file mode 100644
index f17b17d..0000000
--- a/src/shrub/handlers/codec.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-
-#include "../handler.h"
-
-#include "../../codec/codec.h"
-
-struct shrb_codec_server {
- struct shrb_server_handler handler;
- struct camu_demuxer *demux;
- struct camu_packet packet;
- struct aki_packet_pool *pool;
-};
-
-struct shrb_codec_client {
- struct shrb_client_handler handler;
- struct camu_decoder *dec;
-};
-
-struct shrb_server_handler *shrb_codec_server_create(void);
-struct shrb_client_handler *shrb_codec_client_create(void);
diff --git a/src/shrub/handlers/codec_client.c b/src/shrub/handlers/codec_client.c
deleted file mode 100644
index f55a740..0000000
--- a/src/shrub/handlers/codec_client.c
+++ /dev/null
@@ -1,102 +0,0 @@
-#include "../../codec/codec.h"
-#include "../../codec/ffmpeg/decoder.h"
-#include "../../codec/ffmpeg/packet_ext.h"
-
-#include "../vcr.h"
-
-#include "codec.h"
-
-static void data_callback(void *userdata, struct camu_frame *frame)
-{
- struct shrb_codec_client *codec = (struct shrb_codec_client *)userdata;
- codec->handler.callback(codec->handler.userdata, SHRUB_CLIENT_DATA, codec->handler.stream, frame);
-}
-
-static bool codec_client_init(struct shrb_client_handler *handler, struct camu_renderer *renderer,
- struct shrb_vcr_stream *stream)
-{
- struct shrb_codec_client *codec = (struct shrb_codec_client *)handler;
- codec->dec = camu_ff_decoder_create();
- codec->handler.stream = stream;
- if (!codec->dec->init(codec->dec, renderer, &stream->stream)) {
- return false;
- }
- codec->dec->stream = &stream->stream;
- codec->handler.callback(codec->handler.userdata, SHRUB_CLIENT_CONFIGURE, codec->handler.stream, NULL);
- codec->dec->set_callback(codec->dec, data_callback, codec);
- return true;
-}
-
-#ifdef CAMU_HAVE_FFMPEG
-static void push_av_packet(struct shrb_codec_client *codec, AVPacket *pkt)
-{
- struct camu_packet packet;
- packet.av.pkt = pkt;
- s32 ret = codec->dec->push(codec->dec, &packet);
- av_packet_unref(pkt);
- av_packet_free(&pkt);
- al_assert(ret == CAMU_OK);
-}
-#endif
-
-static void push_packet(struct shrb_codec_client *codec, struct aki_buffer *buffer)
-{
- struct camu_packet packet;
- packet.buffer = buffer;
- s32 ret = codec->dec->push(codec->dec, &packet);
- al_assert(ret == CAMU_OK);
-}
-
-static void codec_client_handle_packet(struct shrb_client_handler *handler, struct aki_packet *packet)
-{
- struct shrb_codec_client *codec = (struct shrb_codec_client *)handler;
- if (!packet) {
- struct shrb_codec_client *codec = (struct shrb_codec_client *)handler;
- s32 ret = codec->dec->push(codec->dec, NULL);
- // Flush returns success.
- ret = codec->dec->process(codec->dec);
- al_assert(ret == CAMU_ERR_EOF);
- codec->handler.callback(codec->handler.userdata, SHRUB_CLIENT_EOF, codec->handler.stream, NULL);
- return;
- }
- switch (aki_packet_read_u8(packet)) {
- case CAMU_NORMAL: {
- struct aki_buffer buffer;
- aki_packet_read_buffer(packet, &buffer);
- push_packet(codec, &buffer);
- break;
- }
-#ifdef CAMU_HAVE_FFMPEG
- case CAMU_FFMPEG_COMPAT: {
- push_av_packet(codec, aki_packet_read_av_packet(packet));
- break;
- }
-#endif
- }
- s32 ret = codec->dec->process(codec->dec);
- al_assert(ret == CAMU_ERR_AGAIN || ret == CAMU_ERR_EOF);
-}
-
-static void codec_client_flush(struct shrb_client_handler *handler)
-{
- struct shrb_codec_client *codec = (struct shrb_codec_client *)handler;
- codec->dec->flush(codec->dec);
-}
-
-static void codec_client_free(struct shrb_client_handler **handler)
-{
- struct shrb_codec_client *codec = (struct shrb_codec_client *)*handler;
- codec->dec->free(&codec->dec);
- al_free(codec);
- *handler = NULL;
-}
-
-struct shrb_client_handler *shrb_codec_client_create(void)
-{
- struct shrb_codec_client *codec = al_alloc_object(struct shrb_codec_client);
- codec->handler.init = codec_client_init;
- codec->handler.handle_packet = codec_client_handle_packet;
- codec->handler.flush = codec_client_flush;
- codec->handler.free = codec_client_free;
- return (struct shrb_client_handler *)codec;
-}
diff --git a/src/shrub/handlers/codec_server.c b/src/shrub/handlers/codec_server.c
deleted file mode 100644
index cab615a..0000000
--- a/src/shrub/handlers/codec_server.c
+++ /dev/null
@@ -1,140 +0,0 @@
-#include "../../codec/codec.h"
-#include "../../codec/ffmpeg/demuxer.h"
-#include "../../codec/ffmpeg/packet_ext.h"
-
-#include "../handler.h"
-
-#include "codec.h"
-
-static bool codec_server_init(struct shrb_server_handler *handler, struct cch_handle *handle,
- struct aki_packet_pool *pool)
-{
- struct shrb_codec_server *codec = (struct shrb_codec_server *)handler;
- codec->demux = camu_ff_demuxer_create();
- if (!codec->demux->init(codec->demux, handle)) {
- return false;
- }
- switch (codec->demux->type) {
- case CAMU_NORMAL:
- break;
-#ifdef CAMU_HAVE_FFMPEG
- case CAMU_FFMPEG_COMPAT:
- codec->packet.av.pkt = av_packet_alloc();
- break;
-#endif
- }
- codec->pool = pool;
- return true;
-}
-
-static void codec_server_write_info(struct shrb_server_handler *handler, struct aki_packet *packet)
-{
- struct shrb_codec_server *codec = (struct shrb_codec_server *)handler;
- aki_packet_write_u64(packet, codec->demux->get_duration(codec->demux));
- aki_packet_write_u32(packet, codec->demux->streams.size);
- struct camu_stream *stream;
- al_array_foreach_ptr(codec->demux->streams, i, stream) {
- aki_packet_write_u8(packet, stream->type);
- switch (stream->type) {
- case CAMU_NORMAL:
- aki_packet_write_s32(packet, stream->video.width);
- aki_packet_write_s32(packet, stream->video.height);
- break;
-#ifdef CAMU_HAVE_FFMPEG
- case CAMU_FFMPEG_COMPAT:
- aki_packet_write_av_codec_id(packet, stream->av.stream->codecpar->codec_id);
- aki_packet_write_av_stream(packet, stream->av.stream);
- break;
-#endif
- }
- }
-}
-
-static void codec_server_subscribe(struct shrb_server_handler *handler, s32 index, bool subscribe)
-{
- struct shrb_codec_server *codec = (struct shrb_codec_server *)handler;
- if (subscribe) {
- al_array_push(codec->demux->subscribed, index);
- }
-}
-
-static u64 codec_server_get_duration(struct shrb_server_handler *handler)
-{
- struct shrb_codec_server *codec = (struct shrb_codec_server *)handler;
- return codec->demux->get_duration(codec->demux);
-}
-
-static bool codec_server_seek(struct shrb_server_handler *handler, u64 pos)
-{
- struct shrb_codec_server *codec = (struct shrb_codec_server *)handler;
- return codec->demux->seek(codec->demux, pos);
-}
-
-static bool codec_server_step(struct shrb_server_handler *handler)
-{
- struct shrb_codec_server *codec = (struct shrb_codec_server *)handler;
- struct aki_packet *packet = aki_packet_pool_get(codec->pool);
- if (!packet) return false;
- s32 ret = codec->demux->get_packet(codec->demux, &codec->packet);
- if (ret == CAMU_OK) {
- aki_packet_write_u8(packet, 0);
- switch (codec->packet.type) {
- case CAMU_NORMAL: {
- aki_packet_write_s32(packet, SHRUB_PACKET_DATA);
- aki_packet_write_u8(packet, codec->packet.type);
- aki_packet_write_buffer(packet, codec->packet.buffer);
- break;
- }
-#ifdef CAMU_HAVE_FFMPEG
- case CAMU_FFMPEG_COMPAT: {
- AVPacket *pkt = codec->packet.av.pkt;
- aki_packet_write_s32(packet, pkt->stream_index);
- aki_packet_write_u8(packet, codec->packet.type);
- aki_packet_write_av_packet(packet, pkt);
- av_packet_unref(pkt);
- break;
- }
-#endif
- }
- aki_packet_pool_submit(codec->pool, packet);
- } else if (ret == CAMU_ERR_EOF) {
- aki_packet_write_u8(packet, SHRUB_PACKET_EOF);
- aki_packet_pool_submit(codec->pool, packet);
- return false;
- } else {
- aki_packet_write_u8(packet, SHRUB_PACKET_ERROR);
- aki_packet_pool_submit(codec->pool, packet);
- return false;
- }
- return true;
-}
-
-static void codec_server_free(struct shrb_server_handler **handler)
-{
- struct shrb_codec_server *codec = (struct shrb_codec_server *)*handler;
- switch (codec->demux->type) {
- case CAMU_NORMAL:
- break;
-#ifdef CAMU_HAVE_FFMPEG
- case CAMU_FFMPEG_COMPAT:
- av_packet_free(&codec->packet.av.pkt);
- break;
-#endif
- }
- codec->demux->free(&codec->demux);
- al_free(codec);
- *handler = NULL;
-}
-
-struct shrb_server_handler *shrb_codec_server_create(void)
-{
- struct shrb_codec_server *codec = al_alloc_object(struct shrb_codec_server);
- codec->handler.init = codec_server_init;
- codec->handler.write_info = codec_server_write_info;
- codec->handler.subscribe = codec_server_subscribe;
- codec->handler.get_duration = codec_server_get_duration;
- codec->handler.seek = codec_server_seek;
- codec->handler.step = codec_server_step;
- codec->handler.free = codec_server_free;
- return (struct shrb_server_handler *)codec;
-}
diff --git a/src/shrub/handlers/dvd.h b/src/shrub/handlers/dvd.h
deleted file mode 100644
index db9bfe9..0000000
--- a/src/shrub/handlers/dvd.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#pragma once
-
-#include "../handler.h"
-
-struct bmu_dvd_server {
- struct bmu_server_handler handler;
-};
-
-struct bmu_dvd_client {
- struct bmu_client_handler handler;
-};
-
-struct bmu_server_handler *bmu_dvd_server_create(void);
-struct bmu_client_handler *bmu_dvd_client_create(void);
diff --git a/src/shrub/meson.build b/src/shrub/meson.build
deleted file mode 100644
index a3fc77b..0000000
--- a/src/shrub/meson.build
+++ /dev/null
@@ -1,29 +0,0 @@
-shrub_server_src = [
- 'server.c',
- 'handlers/codec_server.c'
-]
-shrub_client_src = [
- 'client.c',
- 'vcr.c',
- 'handlers/codec_client.c'
-]
-shrub_server_deps = [ffmpeg_server]
-shrub_client_deps = [ffmpeg_client]
-
-#libcdio_paranoia = dependency('libcdio_paranoia', required: false, allow_fallback: true)
-#libcdio_cdda = dependency('libcdio_cdda', required: false, allow_fallback: true)
-#if libcdio_paranoia.found() and libcdio_cdda.found()
-# shrub_server_src += ['handlers/cdio_server.c']
-# shrub_server_deps += [libcdio_paranoia, libcdio_cdda]
-#endif
-#
-#libdvdcss = dependency('libdvdcss', required: false, allow_fallback: true)
-#libdvdread = dependency('libdvdread', required: false, allow_fallback: true)
-#libdvdnav = dependency('libdvdnav', required: false, allow_fallback: true)
-#if libdvdcss.found() and libdvdread.found() and libdvdnav.found()
-# shrub_server_src += ['handlers/dvd_server.c']
-# shrub_server_deps += [libdvdcss]
-#endif
-
-shrub_server = declare_dependency(sources: shrub_server_src, dependencies: shrub_server_deps)
-shrub_client = declare_dependency(sources: shrub_client_src, dependencies: shrub_client_deps)
diff --git a/src/shrub/server.c b/src/shrub/server.c
deleted file mode 100644
index 31d8787..0000000
--- a/src/shrub/server.c
+++ /dev/null
@@ -1,401 +0,0 @@
-#include <al/random.h>
-#include <aki/common.h>
-
-#include "handlers/codec.h"
-
-#include "server.h"
-#include "common.h"
-
-static void control_connection_closed_callback(void *userdata, struct aki_packet_stream *stream)
-{
- struct shrb_node_connection *connection = (struct shrb_node_connection *)userdata;
- (void)stream;
- struct shrb_node_connection *rconnection;
- al_array_foreach(connection->node->connections, i, rconnection) {
- if (rconnection == connection) {
- al_array_remove_at_iter(connection->node->connections, i);
- break;
- }
- }
- aki_packet_stream_free(connection->control);
- connection->control = NULL;
- //cch_entry_return_handle(connection->node->entry, &connection->handle);
- //connection->handler->free(&connection->handler);
- //aki_packet_pool_free(&connection->pool);
-}
-
-static void data_connection_closed_callback(void *userdata, struct aki_packet_stream *stream)
-{
- struct shrb_node_connection *connection = (struct shrb_node_connection *)userdata;
- (void)stream;
- connection->running = 0;
- aki_packet_pool_disable(&connection->pool);
- aki_thread_join(&connection->thread);
- aki_packet_pool_enable(&connection->pool);
- aki_packet_stream_free(connection->data);
- connection->data = NULL;
-}
-
-static void default_packet_sent_callback(void *userdata, struct aki_packet *packet)
-{
- (void)userdata;
- aki_packet_free(packet);
-}
-
-static void packet_sent_callback(void *userdata, struct aki_packet *packet)
-{
- struct shrb_node_connection *connection = (struct shrb_node_connection *)userdata;
- aki_packet_pool_return(&connection->pool, packet);
-}
-
-static void data_packet_callback(void *userdata, struct aki_packet_stream *stream, struct aki_packet *packet)
-{
- (void)userdata;
- (void)stream;
- aki_packet_free(packet);
-}
-
-static u8 packet_pool_callback(void *userdata, struct aki_packet *packet)
-{
- struct shrb_node_connection *connection = (struct shrb_node_connection *)userdata;
- if (connection->data->connected) {
- aki_packet_stream_send_packet(connection->data, packet);
- return AKI_PACKET_POOL_KEEP;
- }
- return AKI_PACKET_POOL_RETURN;
-}
-
-static void send_resume_packet(struct shrb_node_connection *connection, u64 start, u64 offset)
-{
- struct aki_packet *packet = aki_packet_create();
- aki_packet_write_u8(packet, SHRUB_CONTROL_RESUME);
- aki_packet_write_u64(packet, start);
- aki_packet_write_u64(packet, offset);
- aki_packet_stream_send_packet(connection->control, packet);
-}
-
-static void send_pause_packet(struct shrb_node_connection *connection, u64 seek_pos)
-{
- struct aki_packet *packet = aki_packet_create();
- aki_packet_write_u8(packet, SHRUB_CONTROL_PAUSE);
- aki_packet_write_u64(packet, seek_pos);
- aki_packet_stream_send_packet(connection->control, packet);
-}
-
-static struct shrb_node *get_node_by_id(struct shrb_server *server, u16 id)
-{
- struct shrb_node *node;
- al_array_foreach(server->nodes, i, node) {
- if (node->id == id) return node;
- }
- return NULL;
-}
-
-// Possible connection senarios:
-// - initial connection and start
-// - connected while playing
-// - connected while paused
-// - connected while queued
-// - connected while queued after a paused stream
-// Questions:
-// - can the server estimate the position of the stream? I think yes
-
-static void send_start_packet(struct shrb_node_connection *connection)
-{
- struct shrb_node *node = connection->node;
- struct aki_packet *packet = aki_packet_create();
- aki_packet_write_u8(packet, SHRUB_CONTROL_START);
- u64 seek_pos = node->seek_pos;
- u64 delay = SHRUB_BASE_DELAY * powl(3, connection->level);
- u64 start = node->start;
- bool paused = node->paused != SHRUB_NOT_PAUSED;
- if (!paused) {
- s64 diff = aki_get_timestamp() - start;
- if (diff > (s64)SHRUB_BASE_DELAY) {
- start += diff;
- seek_pos += diff;
- }
- }
- // Start is not used if the node is paused.
- aki_packet_write_u64(packet, start);
- seek_pos += delay - SHRUB_BASE_DELAY;
- aki_packet_write_u64(packet, delay);
- aki_packet_write_u64(packet, seek_pos);
- aki_packet_write_u8(packet, paused);
- aki_packet_write_u64(packet, node->paused_at);
- aki_packet_stream_send_packet(connection->control, packet);
-}
-
-static void send_seek_packet(struct shrb_node_connection *connection, u64 pos)
-{
- connection->seek_pos = pos;
- struct aki_packet *packet = aki_packet_create();
- aki_packet_write_u8(packet, SHRUB_CONTROL_SEEK);
- aki_packet_stream_send_packet(connection->control, packet);
-}
-
-static void control_packet_callback(void *userdata, struct aki_packet_stream *stream, struct aki_packet *packet)
-{
- struct shrb_node_connection *connection = (struct shrb_node_connection *)userdata;
- (void)stream;
-
- switch (aki_packet_read_u8(packet)) {
- case SHRUB_CONTROL_SUBSCRIBE: {
- u32 count = aki_packet_read_u32(packet);
- for (u32 i = 0; i < count; i++) {
- connection->handler->subscribe(connection->handler, aki_packet_read_s32(packet), true);
- }
- send_start_packet(connection);
- break;
- }
- case SHRUB_CONTROL_RECONNECT: {
- connection->level = aki_packet_read_u32(packet);
- send_start_packet(connection);
- break;
- }
- case SHRUB_CONTROL_SEEK: {
- u64 pos = aki_packet_read_u64(packet);
- connection->node->seek_pos = pos;
- connection->node->seeked = true;
- //send_seek_packet(connection->node);
- break;
- }
- }
- aki_packet_free(packet);
-}
-
-static struct shrb_node_connection *get_connection_by_id(struct shrb_node *node, u16 id)
-{
- struct shrb_node_connection *connection;
- al_array_foreach(node->connections, i, connection) {
- if (connection->id == id) return connection;
- }
- return NULL;
-}
-
-static bool send_control_info_packet(struct shrb_node_connection *connection)
-{
- connection->handler = shrb_codec_server_create();
- if (!connection->handler->init(connection->handler, &connection->handle, &connection->pool)) {
- return false;
- }
- struct aki_packet *packet = aki_packet_create();
- aki_packet_write_u8(packet, SHRUB_CONTROL_INFO);
- aki_packet_write_u16(packet, connection->id);
- connection->handler->write_info(connection->handler, packet);
- aki_packet_stream_send_packet(connection->control, packet);
- return true;
-}
-
-static aki_thread_result AKI_THREADCALL handler_thread(void *userdata)
-{
- struct shrb_node_connection *connection = (struct shrb_node_connection *)userdata;
- while (connection->running && connection->handler->step(connection->handler)) {}
- return 0;
-}
-
-static void maybe_start_handler(struct shrb_node_connection *connection)
-{
- al_assert(!connection->running);
- // Don't seek to 0 unless node->seeked is set.
- if (connection->seek_pos > 0 || connection->node->seeked) {
- connection->handler->seek(connection->handler, connection->seek_pos);
- }
- connection->running = 1;
- aki_thread_create(&connection->thread, handler_thread, connection);
-}
-
-static void identify_packet_callback(void *userdata, struct aki_packet_stream *stream, struct aki_packet *packet)
-{
- struct shrb_server *server = (struct shrb_server *)userdata;
-
- u8 type = aki_packet_read_u8(packet);
-
- u16 connection_id = aki_packet_read_u16(packet);
- u16 node_id = aki_packet_read_u16(packet);
-
- // Before this point:
- // - We get socket from accept
- // - Create packet_stream
- // - connection_closed_callback could have been called before this point
- // - the socket exists only to wait for an identify packet
-
- // Disconnecting a stream at any point before setting the new callbacks should
- // be valid and handled properly by the client.
- // Generalize erronious connection checking/handling.
- // - Node with id doesn't exist.
- // - Control connected more than once and/or after info is sent.
- // - Data connected before control.
- // - Data connected more than once.
-
- struct shrb_node *node = get_node_by_id(server, node_id);
- al_assert(node);
-
- struct shrb_node_connection *connection = get_connection_by_id(node, connection_id);
- if (!connection) {
- al_assert(type == SHRUB_CONNECTION_CONTROL);
- connection = al_alloc_object(struct shrb_node_connection);
- al_array_push(node->connections, connection);
- connection->id = node->connection_id++;
- connection->node = node;
- connection->control = NULL;
- connection->data = NULL;
- aki_packet_pool_init(&connection->pool, 16, server->loop, packet_pool_callback, connection);
- cch_entry_get_handle(connection->node->entry, &connection->handle);
- connection->level = 0;
- connection->running = 0;
- }
-
- switch (type) {
- case SHRUB_CONNECTION_CONTROL:
- aki_packet_stream_set_nodelay(stream, 1);
- connection->control = stream;
- stream->packet_callback = control_packet_callback;
- stream->packet_sent_callback = default_packet_sent_callback;
- stream->connection_closed_callback = control_connection_closed_callback;
- stream->userdata = connection;
- if (!send_control_info_packet(connection)) {
- aki_packet_stream_disconnect(stream);
- }
- break;
- case SHRUB_CONNECTION_DATA:
- // TODO: Account for control connection being in a reconnect state.
- // TODO: is it possible for data->data to be non-NULL during normal operation
- // (disconnect and reconnect very fast?).
- // - Call maybe_cleanup_data in this function?
- // ^^^^ Literally any connection could call itself data, this needs to be handled better.
- al_assert(!connection->data);
- connection->seek_pos = aki_packet_read_u64(packet);
- connection->data = stream;
- stream->packet_callback = data_packet_callback;
- stream->packet_sent_callback = packet_sent_callback;
- stream->connection_closed_callback = data_connection_closed_callback;
- stream->userdata = connection;
- maybe_start_handler(connection);
- break;
- }
-
- aki_packet_free(packet);
-}
-
-static void connection_callback(void *userdata, struct aki_packet_stream *stream)
-{
- struct shrb_server *server = (struct shrb_server *)userdata;
- stream->packet_callback = identify_packet_callback;
- stream->packet_sent_callback = default_packet_sent_callback;
- stream->userdata = server;
-}
-
-bool shrb_server_init(struct shrb_server *server, struct aki_event_loop *loop)
-{
- server->loop = loop;
- al_array_init(server->nodes);
- return true;
-}
-
-static void connection_closed_callback(void *userdata, struct aki_packet_stream *stream)
-{
- (void)userdata;
- (void)stream;
-}
-
-void shrb_server_add_socket(struct shrb_server *server, struct aki_socket *s)
-{
- struct aki_packet_stream *stream = al_alloc_object(struct aki_packet_stream);
- stream->connection_callback = connection_callback;
- stream->connection_closed_callback = connection_closed_callback;
- stream->userdata = server;
- aki_packet_stream_from_socket(stream, server->loop, s);
-}
-
-struct shrb_node *shrb_server_create_node(struct shrb_server *server, u16 prev, struct cch_entry *entry)
-{
- struct shrb_node *node = al_alloc_object(struct shrb_node);
- node->id = al_rand_u16();
- node->prev = prev;
- node->entry = entry;
- struct cch_handle handle;
- if (!cch_entry_get_handle(node->entry, &handle)) {
- al_free(node);
- return NULL;
- }
- struct shrb_server_handler *handler = shrb_codec_server_create();
- if (!handler->init(handler, &handle, NULL)) {
- al_free(node);
- return NULL;
- }
- node->duration = handler->get_duration(handler);
- handler->free(&handler);
- cch_entry_return_handle(node->entry, &handle);
- node->seeked = false;
- node->start = 0;
- node->paused = SHRUB_NOT_PAUSED;
- node->paused_at = 0;
- node->seek_pos = 0;
- node->connection_id = 1;
- al_array_init(node->connections);
- node->server = server;
- al_array_push(server->nodes, node);
- return node;
-}
-
-void shrb_node_set_start(struct shrb_node *node, u64 start)
-{
- if (node->paused == SHRUB_PAUSED) {
- node->paused = SHRUB_NOT_PAUSED;
- }
- node->start = start;
-}
-
-void shrb_node_user_pause(struct shrb_node *node, u64 pos)
-{
- al_assert(node->paused != SHRUB_USER_PAUSED);
- pos += SHRUB_PAUSE_DELAY;
- node->seek_pos = pos;
- node->paused_at = pos;
- struct shrb_node_connection *connection;
- al_array_foreach(node->connections, i, connection) {
- send_pause_packet(connection, node->seek_pos);
- }
- node->paused = SHRUB_USER_PAUSED;
-}
-
-void shrb_node_user_resume(struct shrb_node *node, struct shrb_resume_req *req)
-{
- al_assert(node->paused == SHRUB_USER_PAUSED);
- node->start = req->start + SHRUB_PAUSE_DELAY;
- struct shrb_node_connection *connection;
- al_array_foreach(node->connections, i, connection) {
- send_resume_packet(connection, node->start, req->offset);
- }
- node->start -= SHRUB_BASE_DELAY;
- node->paused = SHRUB_NOT_PAUSED;
-}
-
-void shrb_node_pause(struct shrb_node *node)
-{
- return;
- if (node->paused != SHRUB_NOT_PAUSED) return;
- u64 start = node->start;
- s64 pos = node->seek_pos + (aki_get_timestamp() - (start + SHRUB_BASE_DELAY));
- pos += SHRUB_PAUSE_DELAY;
- al_assert(pos >= 0);
- node->seek_pos = (u64)pos;
- node->paused_at = pos;
- node->paused = SHRUB_PAUSED;
-}
-
-void shrb_node_seek(struct shrb_node *node, u64 pos)
-{
- node->seek_pos = pos;
- node->seeked = true;
- struct shrb_node_connection *connection;
- al_array_foreach(node->connections, i, connection) {
- send_seek_packet(connection, node->seek_pos);
- }
-}
-
-void shrb_server_close(struct shrb_server *server)
-{
- (void)server;
-}
diff --git a/src/shrub/server.h b/src/shrub/server.h
deleted file mode 100644
index cb901c5..0000000
--- a/src/shrub/server.h
+++ /dev/null
@@ -1,62 +0,0 @@
-#pragma once
-
-#include <al/array.h>
-#include <aki/multiplex.h>
-#include <aki/packet_stream.h>
-#include <aki/packet_pool.h>
-
-#include "../cache/entry.h"
-#include "../cache/handle.h"
-
-#include "handler.h"
-
-struct shrb_node_connection {
- u16 id;
- struct shrb_node *node;
- struct aki_packet_stream *control;
- struct aki_packet_stream *data;
- struct aki_packet_pool pool;
- s32 running;
- u32 level;
- u64 seek_pos;
- struct cch_handle handle;
- struct shrb_server_handler *handler;
- struct aki_thread thread;
-};
-
-enum {
- SHRUB_NOT_PAUSED = 0,
- SHRUB_PAUSED,
- SHRUB_USER_PAUSED
-};
-
-struct shrb_node {
- u16 id;
- u16 prev;
- struct cch_entry *entry;
- u64 duration;
- bool seeked;
- u64 start; // Exact start of the stream minus BASE_DELAY.
- u8 paused;
- u64 paused_at;
- u64 seek_pos;
- u16 connection_id;
- array(struct shrb_node_connection *) connections;
- struct shrb_server *server;
-};
-
-struct shrb_server {
- struct aki_event_loop *loop;
- array(struct shrb_node *) nodes;
-};
-
-bool shrb_server_init(struct shrb_server *server, struct aki_event_loop *loop);
-void shrb_server_add_socket(struct shrb_server *server, struct aki_socket *s);
-struct shrb_node *shrb_server_create_node(struct shrb_server *server, u16 prev, struct cch_entry *entry);
-void shrb_node_set_start(struct shrb_node *node, u64 start);
-void shrb_node_user_pause(struct shrb_node *node, u64 pos);
-void shrb_node_user_resume(struct shrb_node *node, struct shrb_resume_req *req);
-void shrb_node_pause(struct shrb_node *node);
-void shrb_node_resume(struct shrb_node *node);
-void shrb_node_seek(struct shrb_node *node, u64 pos);
-void shrb_server_close(struct shrb_server *server);
diff --git a/src/shrub/vcr.c b/src/shrub/vcr.c
deleted file mode 100644
index 7f2b058..0000000
--- a/src/shrub/vcr.c
+++ /dev/null
@@ -1,177 +0,0 @@
-#include <al/log.h>
-
-#include "vcr.h"
-#include "handler.h"
-
-#define VCR_BUFFER_HIGH 1024
-#define VCR_BUFFER_BUFFERED (VCR_BUFFER_HIGH - 64)
-#define VCR_BUFFER_LOW (VCR_BUFFER_HIGH - 256)
-
-static void signal_callback(void *userdata)
-{
- struct shrb_vcr *vcr = (struct shrb_vcr *)userdata;
- aki_packet_stream_cork(vcr->data, false);
-}
-
-void shrb_vcr_init(struct shrb_vcr *vcr, struct aki_event_loop *loop, struct aki_packet_stream *data)
-{
- al_array_init(vcr->streams);
- al_atomic_u64_store(&vcr->count, 0, AL_ATOMIC_RELAXED);
- vcr->data = data;
- aki_signal_init(&vcr->signal, signal_callback, vcr);
- aki_signal_start(&vcr->signal, loop);
-}
-
-static aki_thread_result AKI_THREADCALL vcr_stream_thread(void *userdata)
-{
- struct shrb_vcr_stream *stream = (struct shrb_vcr_stream *)userdata;
- struct shrb_vcr *vcr = stream->vcr;
- while (aki_packet_cache_wait(&stream->cache)) {
- struct aki_packet *packet = aki_packet_cache_pop(&stream->cache);
- if (!packet) {
- stream->client->handle_packet(stream->client, NULL);
- break;
- }
- if (al_atomic_u64_sub(&vcr->count, 1, AL_ATOMIC_RELAXED) < VCR_BUFFER_LOW && stream->buffered) {
- aki_signal_send(&vcr->signal);
- }
- if (al_atomic_s32_load(&stream->state, AL_ATOMIC_RELAXED) == SHRUB_STREAM_STOPPED) {
- aki_mutex_lock(&stream->mutex);
- aki_cond_wait(&stream->cond, &stream->mutex);
- aki_mutex_unlock(&stream->mutex);
- }
- if (al_atomic_s32_load(&stream->state, AL_ATOMIC_RELAXED) == SHRUB_STREAM_CLOSED) {
- aki_packet_free(packet);
- break;
- }
- stream->client->handle_packet(stream->client, packet);
- aki_packet_free(packet);
- }
- return 0;
-}
-
-void shrb_vcr_add_stream(struct shrb_vcr *vcr, struct shrb_vcr_stream *stream)
-{
- stream->vcr = vcr;
- aki_cond_init(&stream->cond);
- aki_mutex_init(&stream->mutex);
- aki_packet_cache_init(&stream->cache, VCR_BUFFER_HIGH);
- stream->buffered = 0;
- al_array_push(vcr->streams, stream);
- al_atomic_s32_store(&stream->state, SHRUB_STREAM_RUNNING, AL_ATOMIC_RELAXED);
-}
-
-static struct shrb_vcr_stream *get_stream_from_index(struct shrb_vcr *vcr, s32 index)
-{
- struct shrb_vcr_stream *stream;
- al_array_foreach(vcr->streams, i, stream) {
- if (stream->index == index) return stream;
- }
- return NULL;
-}
-
-bool shrb_vcr_push_packet(struct shrb_vcr *vcr, struct aki_packet *packet)
-{
- struct shrb_vcr_stream *stream;
- switch (aki_packet_read_u8(packet)) {
- case SHRUB_PACKET_DATA:
- stream = get_stream_from_index(vcr, aki_packet_read_s32(packet));
- al_assert(stream);
- if (!stream->running) {
- aki_thread_create(&stream->thread, vcr_stream_thread, stream);
- stream->running = true;
- }
- if (!aki_packet_cache_send_packet(&stream->cache, packet)) {
- aki_packet_free(packet);
- } else if (al_atomic_u64_add(&vcr->count, 1, AL_ATOMIC_RELAXED) >= VCR_BUFFER_BUFFERED) {
- aki_packet_stream_cork(vcr->data, true);
- al_array_foreach(vcr->streams, i, stream) {
- stream->buffered = 1;
- }
- }
- break;
- case SHRUB_PACKET_EOF:
- al_array_foreach(vcr->streams, i, stream) {
- aki_packet_cache_send_packet(&stream->cache, NULL);
- }
- aki_packet_free(packet);
- break;
- case SHRUB_PACKET_ERROR:
- al_log_warn("bimu", "Unhandled error packet.");
- aki_packet_free(packet);
- break;
- default:
- al_assert(false);
- }
- return true;
-}
-
-static void vcr_stream_close_internal(struct shrb_vcr_stream *stream)
-{
- al_atomic_s32_store(&stream->state, SHRUB_STREAM_CLOSED, AL_ATOMIC_RELAXED);
- aki_packet_cache_disable(&stream->cache);
- aki_mutex_lock(&stream->mutex);
- if (aki_cond_is_waiting(&stream->cond)) {
- aki_cond_signal(&stream->cond);
- }
- aki_mutex_unlock(&stream->mutex);
- if (stream->running) {
- aki_thread_join(&stream->thread);
- }
- struct aki_packet *packet;
- while ((packet = aki_packet_cache_pop(&stream->cache))) {
- aki_packet_free(packet);
- }
-}
-
-void shrb_vcr_stream_close(struct shrb_vcr_stream *stream)
-{
- vcr_stream_close_internal(stream);
-}
-
-void shrb_vcr_flush(struct shrb_vcr *vcr)
-{
- struct shrb_vcr_stream *stream;
- al_array_foreach(vcr->streams, i, stream) {
- vcr_stream_close_internal(stream);
- stream->client->flush(stream->client);
- aki_packet_cache_enable(&stream->cache);
- stream->running = false;
- al_atomic_s32_store(&stream->state, SHRUB_STREAM_RUNNING, AL_ATOMIC_RELAXED);
- }
- al_atomic_u64_store(&vcr->count, 0, AL_ATOMIC_RELAXED);
-}
-
-void shrb_vcr_stream_cork(struct shrb_vcr_stream *stream)
-{
- al_atomic_s32_store(&stream->state, SHRUB_STREAM_STOPPED, AL_ATOMIC_RELAXED);
-}
-
-void shrb_vcr_stream_uncork(struct shrb_vcr_stream *stream)
-{
- if (al_atomic_s32_load(&stream->state, AL_ATOMIC_RELAXED) == SHRUB_STREAM_RUNNING) {
- // DEBUG: This will be reached during normal operation.
- return;
- }
- al_atomic_s32_store(&stream->state, SHRUB_STREAM_RUNNING, AL_ATOMIC_RELAXED);
- aki_mutex_lock(&stream->mutex);
- if (aki_cond_is_waiting(&stream->cond)) {
- aki_cond_signal(&stream->cond);
- }
- aki_mutex_unlock(&stream->mutex);
-}
-
-void shrb_vcr_free(struct shrb_vcr *vcr)
-{
- aki_signal_stop(&vcr->signal);
- struct shrb_vcr_stream *stream;
- al_array_foreach(vcr->streams, i, stream) {
- aki_packet_cache_free(&stream->cache);
- stream->client->free(&stream->client);
- if (stream->stream.type == CAMU_FFMPEG_COMPAT) {
- avformat_free_context(stream->stream.av.format_context);
- }
- al_free(stream);
- }
- al_array_free(vcr->streams);
-}
diff --git a/src/shrub/vcr.h b/src/shrub/vcr.h
deleted file mode 100644
index 4fae2c0..0000000
--- a/src/shrub/vcr.h
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include <aki/packet_cache.h>
-#include <al/atomic.h>
-#include <aki/packet_stream.h>
-#include <aki/signal.h>
-
-#include "../codec/codec.h"
-
-enum {
- SHRUB_STREAM_RUNNING = 0,
- SHRUB_STREAM_STOPPED,
- SHRUB_STREAM_CLOSED
-};
-
-struct shrb_vcr_stream {
- u8 type;
- s32 index;
- struct camu_stream stream;
- struct shrb_client_handler *client;
- atomic_s32 state;
- struct aki_packet_cache cache;
- s32 buffered;
- struct aki_cond cond;
- struct aki_mutex mutex;
- bool running;
- struct aki_thread thread;
- struct shrb_vcr *vcr;
-};
-
-struct shrb_vcr {
- array(struct shrb_vcr_stream *) streams;
- atomic_u64 count;
- struct aki_packet_stream *data;
- struct aki_signal signal;
-};
-
-void shrb_vcr_init(struct shrb_vcr *vcr, struct aki_event_loop *loop, struct aki_packet_stream *data);
-void shrb_vcr_add_stream(struct shrb_vcr *vcr, struct shrb_vcr_stream *stream);
-bool shrb_vcr_push_packet(struct shrb_vcr *vcr, struct aki_packet *packet);
-void shrb_vcr_stream_close(struct shrb_vcr_stream *stream);
-void shrb_vcr_flush(struct shrb_vcr *vcr);
-void shrb_vcr_stream_cork(struct shrb_vcr_stream *stream);
-void shrb_vcr_stream_uncork(struct shrb_vcr_stream *stream);
-void shrb_vcr_free(struct shrb_vcr *vcr);
diff --git a/src/sink/desktop.c b/src/sink/desktop.c
new file mode 100644
index 0000000..8e628fd
--- /dev/null
+++ b/src/sink/desktop.c
@@ -0,0 +1,187 @@
+#include <al/log.h>
+
+#include "../mixer/audio_miniaudio.h"
+//#include "../mixer/audio_null.h"
+#if defined CAMU_RENDERER_TIGER
+#include "../render/renderer_tiger.h"
+#elif defined CAMU_RENDERER_LIBPLACEBO
+#include "../render/renderer_libplacebo.h"
+#endif
+
+#include "desktop.h"
+
+static u8 sink_callback(void *userdata, u8 op, u8 type, void *opaque)
+{
+ struct camu_desktop *c = (struct camu_desktop *)userdata;
+ switch (op) {
+ case CAMU_SINK_ADD_BUFFER:
+ switch (type) {
+ case CAMU_SINK_AUDIO: {
+ struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque;
+ camu_mixer_add_buffer(&c->mixer, buf);
+ al_log_debug("camu_desktop", "Audio buffer added.");
+ break;
+ }
+ case CAMU_SINK_VIDEO: {
+ struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque;
+ camu_screen_add_buffer(&c->scr, buf);
+ al_log_debug("camu_desktop", "Video buffer added.");
+ break;
+ }
+ }
+ break;
+ case CAMU_SINK_REMOVE_BUFFER:
+ switch (type) {
+ case CAMU_SINK_AUDIO: {
+ struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque;
+ camu_mixer_remove_buffer(&c->mixer, buf);
+ al_log_debug("camu_desktop", "Audio buffer removed.");
+ break;
+ }
+ case CAMU_SINK_VIDEO: {
+ struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque;
+ camu_screen_remove_buffer(&c->scr, buf);
+ al_log_debug("camu_desktop", "Video buffer removed.");
+ break;
+ }
+ }
+ break;
+ case CAMU_SINK_SWAP_BUFFER:
+ switch (type) {
+ case CAMU_SINK_AUDIO: {
+ break;
+ }
+ }
+ break;
+ case CAMU_SINK_START:
+ switch (type) {
+ case CAMU_SINK_AUDIO: {
+ camu_mixer_resume(&c->mixer);
+ al_log_info("camu_desktop", "Audio started.");
+ break;
+ }
+ case CAMU_SINK_VIDEO: {
+ camu_screen_set_state(&c->scr, CAMU_SCREEN_PLAYING);
+ camu_screen_wake(&c->scr);
+ al_log_info("camu_desktop", "Video started.");
+ break;
+ }
+ }
+ break;
+ case CAMU_SINK_STOP:
+ switch (type) {
+ case CAMU_SINK_AUDIO: {
+ camu_mixer_pause(&c->mixer);
+ al_log_info("camu_desktop", "Audio stopped.");
+ break;
+ }
+ case CAMU_SINK_VIDEO: {
+ camu_screen_set_state(&c->scr, CAMU_SCREEN_PAUSED);
+ al_log_info("camu_desktop", "Video stopped.");
+ break;
+ }
+ }
+ break;
+ case CAMU_SINK_EXIT:
+ camu_sink_close(&c->sink);
+ if (c->exit_callback) {
+ c->exit_callback(c->userdata, c);
+ }
+ break;
+ }
+ return CAMU_SINK_OK;
+}
+
+static void screen_callback(void *userdata, u8 op, void *opaque)
+{
+ struct camu_desktop *c = (struct camu_desktop *)userdata;
+ switch (op) {
+ case CAMU_SCREEN_NEXT:
+ camu_sink_skip(&c->sink, 1);
+ break;
+ case CAMU_SCREEN_PREVIOUS:
+ camu_sink_skip(&c->sink, -1);
+ break;
+ case CAMU_SCREEN_SHUFFLE:
+ camu_sink_shuffle(&c->sink);
+ break;
+ case CAMU_SCREEN_TOGGLE_PAUSE:
+ camu_sink_toggle_pause(&c->sink);
+ break;
+ case CAMU_SCREEN_SEEK: {
+ f64 percent = *(f64 *)opaque;
+ camu_sink_seek(&c->sink, percent);
+ break;
+ }
+ case CAMU_SCREEN_RESEEK:
+ camu_sink_reseek(&c->sink);
+ break;
+ case CAMU_SCREEN_CLOSE:
+ c->should_quit = 1;
+ break;
+ }
+}
+
+bool camu_desktop_init(struct camu_desktop *c, const char *name)
+{
+ c->scr.callback = screen_callback;
+ c->scr.userdata = c;
+ if (!camu_screen_init(&c->scr) || !camu_screen_create_window(&c->scr, name)) {
+ return false;
+ }
+#if defined CAMU_RENDERER_TIGER
+ c->renderer = camu_renderer_tiger_create();
+#elif defined CAMU_RENDERER_LIBPLACEBO
+ c->renderer = camu_renderer_lp_create();
+#endif
+ if (!camu_screen_create_renderer(&c->scr, c->renderer)) {
+ return false;
+ }
+ c->renderer->render(c->renderer, &c->scr);
+ if (!camu_mixer_init(&c->mixer, camu_audio_miniaudio_create())) {
+ //if (!camu_mixer_init(&c->mixer, (struct camu_audio *)&audio_plugin_null)) {
+ return false;
+ }
+ if (!c->mixer.audio->configure_stream(c->mixer.audio, NULL)) {
+ return false;
+ }
+ camu_mixer_pick_format(&c->mixer, &c->mixer.fmt);
+ c->should_quit = 0;
+ c->exit_callback = NULL;
+ c->userdata = NULL;
+ return true;
+}
+
+bool camu_desktop_connect(struct camu_desktop *c, u8 type, struct aki_event_loop *loop, str *addr, u16 port)
+{
+ c->loop = loop;
+ camu_sink_init(&c->sink, c->loop, &c->mixer, c->renderer);
+ c->sink.callback = sink_callback;
+ c->sink.userdata = c;
+ return camu_sink_connect(&c->sink, type, addr, port, al_str_c("desktop"));
+}
+
+bool camu_desktop_tick(struct camu_desktop *c)
+{
+ if (!c->should_quit && camu_screen_tick(&c->scr)) {
+ c->renderer->render(c->renderer, &c->scr);
+ }
+ return !c->should_quit;
+}
+
+void camu_desktop_stop(struct camu_desktop *c)
+{
+ camu_mixer_pause(&c->mixer);
+ camu_mixer_clear(&c->mixer);
+ camu_screen_set_state(&c->scr, CAMU_SCREEN_CLOSED);
+ camu_screen_clear(&c->scr);
+ camu_sink_stop(&c->sink);
+}
+
+void camu_desktop_free(struct camu_desktop *c)
+{
+ camu_sink_free(&c->sink);
+ camu_mixer_close(&c->mixer);
+ c->renderer->free(&c->renderer);
+ camu_screen_close(&c->scr);
+}
diff --git a/src/sink/desktop.h b/src/sink/desktop.h
new file mode 100644
index 0000000..efc1c34
--- /dev/null
+++ b/src/sink/desktop.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <al/types.h>
+#include <al/str.h>
+#include <aki/event_loop.h>
+
+#include "../libsink/sink.h"
+
+#include "../screen/screen.h"
+
+struct camu_desktop {
+ struct aki_event_loop *loop;
+ s32 should_quit;
+ struct camu_screen scr;
+ struct camu_renderer *renderer;
+ struct camu_mixer mixer;
+ struct camu_sink sink;
+ void (*exit_callback)(void *, struct camu_desktop *);
+ void *userdata;
+};
+
+bool camu_desktop_init(struct camu_desktop *c, const char *name);
+bool camu_desktop_connect(struct camu_desktop *c, u8 type, struct aki_event_loop *loop, str *addr, u16 port);
+bool camu_desktop_tick(struct camu_desktop *c);
+void camu_desktop_stop(struct camu_desktop *c);
+void camu_desktop_free(struct camu_desktop *c);
diff --git a/src/sink/meson.build b/src/sink/meson.build
new file mode 100644
index 0000000..df80934
--- /dev/null
+++ b/src/sink/meson.build
@@ -0,0 +1,3 @@
+desktop_src = ['desktop.c']
+desktop_deps = [common_deps, buffer, render, screen, mixer, libsink]
+desktop = declare_dependency(sources: desktop_src, dependencies: desktop_deps)
diff --git a/src/util/color_palette.c b/src/util/color_palette.c
index 53c2c68..3bc7f3e 100644
--- a/src/util/color_palette.c
+++ b/src/util/color_palette.c
@@ -1,9 +1,8 @@
-#include <aki/file.h>
-#include <jansson.h>
-
#include "color_palette.h"
-struct camu_color_palette global_color_palette = {0};
+#ifdef AKIYO_HAS_JSON
+#include <aki/file.h>
+#include <jansson.h>
static char *special_colors[2] = {
"background",
@@ -28,9 +27,13 @@ static char *normal_colors[16] = {
"color14",
"color15"
};
+#endif
+
+struct camu_color_palette global_color_palette = { 0 };
bool camu_color_palette_init(str *path)
{
+#ifdef AKIYO_HAS_JSON
struct aki_file file;
if (!aki_file_open(&file, path, 0)) {
return false;
@@ -59,4 +62,8 @@ bool camu_color_palette_init(str *path)
global_color_palette.colors[i + 2] = (u32)al_str_to_long(al_str_w((char *)color, 1, 6), 16);
}
return true;
+#else
+ (void)path;
+ return false;
+#endif
}
diff --git a/src/util/meson.build b/src/util/meson.build
index eff0fe6..1d62f55 100644
--- a/src/util/meson.build
+++ b/src/util/meson.build
@@ -1,6 +1,7 @@
util_src = ['color_palette.c', 'blocking_ring_buffer.c']
blake3_opts = cmake.subproject_options()
-blake3_opts.add_cmake_defines({ 'CMAKE_BUILD_TYPE': is_debug ? 'Debug' : 'Release' })
+reltype = is_minsize ? 'MinSizeRel' : 'Release'
+blake3_opts.add_cmake_defines({ 'CMAKE_BUILD_TYPE': is_debug ? 'Debug' : reltype })
blake3 = cmake.subproject('BLAKE3').dependency('blake3')
util_deps = [blake3]
util = declare_dependency(sources: util_src, dependencies: util_deps)