summaryrefslogtreecommitdiff
path: root/src/codec
diff options
context:
space:
mode:
Diffstat (limited to 'src/codec')
-rw-r--r--src/codec/codec.h115
-rw-r--r--src/codec/common.h29
-rw-r--r--src/codec/libav/avio.c13
-rw-r--r--src/codec/libav/avio.h10
-rw-r--r--src/codec/libav/common.c6
-rw-r--r--src/codec/libav/common.h6
-rw-r--r--src/codec/libav/decoder.c165
-rw-r--r--src/codec/libav/decoder.h20
-rw-r--r--src/codec/libav/demuxer.c184
-rw-r--r--src/codec/libav/demuxer.h21
-rw-r--r--src/codec/libav/encoder.c186
-rw-r--r--src/codec/libav/encoder.h26
-rw-r--r--src/codec/libav/packet_ext.c168
-rw-r--r--src/codec/libav/packet_ext.h19
-rw-r--r--src/codec/libav/resampler.c163
-rw-r--r--src/codec/libav/resampler.h40
-rw-r--r--src/codec/libav/scaler.c79
-rw-r--r--src/codec/libav/scaler.h24
-rw-r--r--src/codec/meson.build67
-rw-r--r--src/codec/spng/decoder.h11
-rw-r--r--src/codec/spng/demuxer.h12
-rw-r--r--src/codec/spng/impl.c166
-rw-r--r--src/codec/stb_image/decoder.h11
-rw-r--r--src/codec/stb_image/demuxer.h12
-rw-r--r--src/codec/stb_image/impl.c155
-rw-r--r--src/codec/wuffs/decoder.h13
-rw-r--r--src/codec/wuffs/demuxer.h12
-rw-r--r--src/codec/wuffs/impl.c213
28 files changed, 1946 insertions, 0 deletions
diff --git a/src/codec/codec.h b/src/codec/codec.h
new file mode 100644
index 0000000..d30a648
--- /dev/null
+++ b/src/codec/codec.h
@@ -0,0 +1,115 @@
+#pragma once
+
+#include <al/types.h>
+#include <al/array.h>
+#include <aki/common.h>
+
+#include "../cache/handle.h"
+
+#ifdef HAVE_FFMPEG
+#include <libavutil/pixdesc.h>
+#include <libavformat/avformat.h>
+#endif
+
+#ifdef HAVE_FFMPEG
+enum {
+ CAMU_OK = 0,
+ CAMU_ERR_EOF = AVERROR_EOF,
+ CAMU_ERR_AGAIN = AVERROR(EAGAIN)
+};
+enum {
+ CAMU_SEEK_SIZE = AVSEEK_SIZE
+};
+#else
+enum {
+ CAMU_OK = 0,
+ CAMU_ERR_EOF = -1,
+ CAMU_ERR_AGAIN = -2
+};
+enum {
+ CAMU_SEEK_SIZE = 0x10000
+};
+#endif
+
+enum {
+ CAMU_PIXEL_FMT_RGB = 0,
+ CAMU_PIXEL_FMT_RGBA
+};
+
+enum {
+ CAMU_FRAME_DEVICE_ALLOCATED = 1
+};
+
+enum {
+ CAMU_NORMAL = 0,
+#ifdef HAVE_FFMPEG
+ CAMU_FFMPEG_COMPAT
+#endif
+};
+
+struct camu_icc_profile {
+ u8 *data;
+ size_t size;
+};
+
+struct camu_stream {
+ u8 type;
+ s64 duration;
+ struct {
+ u8 fmt;
+ s32 width;
+ s32 height;
+ struct camu_icc_profile icc;
+ } video;
+ //struct {
+ //} audio;
+#ifdef HAVE_FFMPEG
+ struct {
+ AVStream *stream;
+ AVFormatContext *format_context;
+ } av;
+#endif
+};
+
+struct camu_packet {
+ u8 type;
+ struct aki_buffer *buffer;
+#ifdef HAVE_FFMPEG
+ struct { AVPacket *pkt; } av;
+#endif
+};
+
+struct camu_frame {
+ u8 type;
+ u8 *data;
+ u8 flags;
+ u8 fmt;
+ s32 width;
+ s32 height;
+ f64 pts;
+#ifdef HAVE_FFMPEG
+ struct { AVFrame *frame; } av;
+#endif
+ void *opaque;
+};
+
+struct camu_demuxer {
+ bool (*init)(struct camu_demuxer *, struct cch_handle *);
+ s32 (*get_packet)(struct camu_demuxer *, struct camu_packet *);
+ s64 (*get_duration)(struct camu_demuxer *);
+ bool (*seek)(struct camu_demuxer *, s64);
+ void (*free)(struct camu_demuxer **);
+ u8 type;
+ array(struct camu_stream) streams;
+};
+
+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 *);
+ s32 (*process)(struct camu_decoder *);
+ void (*flush)(struct camu_decoder *);
+ void (*free)(struct camu_decoder **);
+ struct camu_stream *stream;
+};
diff --git a/src/codec/common.h b/src/codec/common.h
new file mode 100644
index 0000000..f3db182
--- /dev/null
+++ b/src/codec/common.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <al/lib.h>
+
+static inline void *camu_page_alloc(size_t size)
+{
+#ifdef HAVE_POSIX_MEMALIGN
+ void *ptr = NULL;
+ al_posix_memalign(&ptr, al_page_size, size);
+ return ptr;
+#else
+ return al_malloc(size);
+#endif
+}
+
+static inline void *camu_page_realloc(void *ptr, size_t old_size, size_t size)
+{
+#ifdef 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/libav/avio.c b/src/codec/libav/avio.c
new file mode 100644
index 0000000..97f66da
--- /dev/null
+++ b/src/codec/libav/avio.c
@@ -0,0 +1,13 @@
+#include "avio.h"
+
+s32 camu_avio_read(void *data, u8 *buf, s32 buf_size)
+{
+ struct cch_handle *handle = (struct cch_handle *)data;
+ return cch_handle_read(handle, buf, buf_size);
+}
+
+s64 camu_avio_seek(void *data, s64 offset, s32 whence)
+{
+ struct cch_handle *handle = (struct cch_handle *)data;
+ return (s64)cch_handle_seek(handle, offset, whence);
+}
diff --git a/src/codec/libav/avio.h b/src/codec/libav/avio.h
new file mode 100644
index 0000000..09df4ec
--- /dev/null
+++ b/src/codec/libav/avio.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <libavutil/error.h>
+#include <libavformat/avio.h>
+
+#include "../../codec/codec.h"
+#include "../../cache/handle.h"
+
+s32 camu_avio_read(void *data, u8 *buf, s32 buf_size);
+s64 camu_avio_seek(void *data, s64 offset, s32 whence);
diff --git a/src/codec/libav/common.c b/src/codec/libav/common.c
new file mode 100644
index 0000000..8ce6667
--- /dev/null
+++ b/src/codec/libav/common.c
@@ -0,0 +1,6 @@
+#include "common.h"
+
+void camu_lav_set_log_callback(void (*callback)(void *, int, const char *, va_list))
+{
+ av_log_set_callback(callback);
+}
diff --git a/src/codec/libav/common.h b/src/codec/libav/common.h
new file mode 100644
index 0000000..854e4c7
--- /dev/null
+++ b/src/codec/libav/common.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <al/types.h>
+#include <libavutil/log.h>
+
+void camu_lav_set_log_callback(void (*callback)(void *, int, const char *, va_list));
diff --git a/src/codec/libav/decoder.c b/src/codec/libav/decoder.c
new file mode 100644
index 0000000..ebdcb03
--- /dev/null
+++ b/src/codec/libav/decoder.c
@@ -0,0 +1,165 @@
+#include <libavutil/cpu.h>
+#include <al/lib.h>
+#include <al/log.h>
+
+//#include "../../render/renderer.h"
+
+#include "decoder.h"
+
+static void close_internal(struct camu_lav_decoder *av)
+{
+ if (av->codec_context) {
+ avcodec_close(av->codec_context);
+ avcodec_free_context(&av->codec_context);
+ }
+}
+
+static bool lav_decoder_init(struct camu_decoder *dec, struct camu_renderer *renderer, struct camu_stream *stream)
+{
+ struct camu_lav_decoder *av = (struct camu_lav_decoder *)dec;
+
+ AVCodecParameters *codecpar = stream->av.stream->codecpar;
+ const AVCodec *codec = avcodec_find_decoder(codecpar->codec_id);
+
+ if (!codec) {
+ al_log_error("lav_decoder", "Failed to find decoder.");
+ goto err;
+ }
+
+ av->codec_context = avcodec_alloc_context3(codec);
+
+ if (!av->codec_context) {
+ al_log_error("lav_decoder", "Failed to alloc codec context.");
+ goto err;
+ }
+
+ if (avcodec_parameters_to_context(av->codec_context, codecpar) < 0) {
+ al_log_error("lav_decoder", "Failed to copy codec parameters.");
+ goto err;
+ }
+
+ s32 cpus = av_cpu_count();
+ cpus = (cpus > 16) ? 4 : cpus / 4;
+ if (cpus == 0) cpus = 1;
+ av->codec_context->thread_count = cpus;
+ al_log_debug("lav_decoder", "Using %i threads for decoder.", cpus);
+
+ (void)renderer;
+ /*
+ if (renderer && renderer->get_buffer2 && codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+ av->codec_context->get_buffer2 = renderer->get_buffer2;
+ av->codec_context->opaque = renderer->opaque;
+ }
+ */
+
+ if (avcodec_open2(av->codec_context, codec, NULL) < 0) {
+ al_log_error("lav_decoder", "Failed to open codec (%s).", codec->name);
+ goto err;
+ }
+
+ al_log_info("lav_decoder", "Codec: %s (%s) %ldkbps.", codec->name, codec->long_name, codecpar->bit_rate / 1000);
+
+ av->time_base = stream->av.stream->time_base;
+ av->last_pts = 0;
+ av->last_duration = 0;
+ //av->seek_pos = -1;
+
+ return true;
+err:
+ close_internal(av);
+
+ return false;
+}
+
+static void lav_decoder_set_callback(struct camu_decoder *dec, void (*callback)(void *, struct camu_frame *), void *userdata)
+{
+ struct camu_lav_decoder *av = (struct camu_lav_decoder *)dec;
+ av->callback = callback;
+ av->userdata = userdata;
+}
+
+static s32 send_packet(struct camu_lav_decoder *av, AVPacket *pkt)
+{
+ /*
+ if (pkt && av->seek_pos >= 0 && pkt->pts + pkt->duration < av->seek_pos) {
+ av->codec_context->skip_frame = AVDISCARD_NONKEY;
+ } else {
+ av->codec_context->skip_frame = AVDISCARD_NONE;
+ }
+ */
+
+ s32 ret = avcodec_send_packet(av->codec_context, pkt);
+ if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
+ al_log_error("lav_decoder", "Error sending packet to decoder: (%s).", av_err2str(ret));
+ }
+
+ return ret;
+}
+
+static s32 lav_decoder_push(struct camu_decoder *dec, struct camu_packet *packet)
+{
+ struct camu_lav_decoder *av = (struct camu_lav_decoder *)dec;
+ return send_packet(av, (packet) ? packet->av.pkt : NULL);
+}
+
+static void lav_decoder_flush(struct camu_decoder *dec)
+{
+ struct camu_lav_decoder *av = (struct camu_lav_decoder *)dec;
+ avcodec_flush_buffers(av->codec_context);
+}
+
+static s32 receive_frames(struct camu_lav_decoder *av)
+{
+ s32 ret;
+ do {
+ struct camu_frame *frame = al_alloc_object(struct camu_frame);
+ frame->type = 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);
+ if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
+ al_log_error("lav_decoder", "Error receiving packet from decoder: (%s).", av_err2str(ret));
+ continue;
+ }
+ // 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;
+ // }
+ // av->last_pts = frame->av.frame->best_effort_timestamp;
+ // av->last_duration = av_get_audio_frame_duration(av->codec_context, frame->av.frame->linesize[0])
+ // / (av->codec_context->sample_rate * av->time_base.num / (f64)av->time_base.den);
+ //}
+ av->callback(av->userdata, frame);
+ } while (1);
+ return ret;
+}
+
+static s32 lav_decoder_process(struct camu_decoder *dec)
+{
+ struct camu_lav_decoder *av = (struct camu_lav_decoder *)dec;
+ return receive_frames(av);
+}
+
+static void lav_decoder_free(struct camu_decoder **dec)
+{
+ struct camu_lav_decoder *av = (struct camu_lav_decoder *)*dec;
+ close_internal(av);
+ al_free(av);
+ *dec = NULL;
+}
+
+struct camu_decoder *camu_lav_decoder_create(void)
+{
+ struct camu_lav_decoder *av = al_alloc_object(struct camu_lav_decoder);
+ av->dec.init = lav_decoder_init;
+ av->dec.set_callback = lav_decoder_set_callback;
+ av->dec.push = lav_decoder_push;
+ av->dec.process = lav_decoder_process;
+ av->dec.flush = lav_decoder_flush;
+ av->dec.free = lav_decoder_free;
+ return (struct camu_decoder *)av;
+}
diff --git a/src/codec/libav/decoder.h b/src/codec/libav/decoder.h
new file mode 100644
index 0000000..44105ce
--- /dev/null
+++ b/src/codec/libav/decoder.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <al/types.h>
+
+#include <libavcodec/avcodec.h>
+
+#include "../codec.h"
+
+struct camu_lav_decoder {
+ struct camu_decoder dec;
+ AVCodecContext *codec_context;
+ AVRational time_base;
+ s64 last_pts;
+ s64 last_duration;
+ //s64 seek_pos;
+ void (*callback)(void *, struct camu_frame *);
+ void *userdata;
+};
+
+struct camu_decoder *camu_lav_decoder_create(void);
diff --git a/src/codec/libav/demuxer.c b/src/codec/libav/demuxer.c
new file mode 100644
index 0000000..f7ff35c
--- /dev/null
+++ b/src/codec/libav/demuxer.c
@@ -0,0 +1,184 @@
+#include <al/log.h>
+
+#include "../common.h"
+
+#include "demuxer.h"
+
+#define BUF_SIZE 16384
+
+static void close_internal(struct camu_lav_demuxer *av)
+{
+ if (av->io_context) {
+ av_free(av->io_context->buffer);
+ avio_context_free(&av->io_context);
+ }
+ if (av->format_context) {
+ avformat_flush(av->format_context);
+ avformat_close_input(&av->format_context);
+ avformat_free_context(av->format_context);
+ }
+}
+
+static bool lav_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handle)
+{
+ struct camu_lav_demuxer *av = (struct camu_lav_demuxer *)demux;
+
+ av->format_context = avformat_alloc_context();
+ if (!av->format_context) {
+ al_log_error("lav_demuxer", "Failed to create format context.");
+ 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);
+ if (!av->io_context) {
+ al_log_error("lav_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
+ // disables long seeks (Not tested in a long time).
+ av->io_context->seekable &= ~AVIO_SEEKABLE_NORMAL;
+ } else {
+ av->io_context->seekable |= AVIO_SEEKABLE_NORMAL;
+ }
+
+ 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) {
+ al_log_error("lav_demuxer", "Failed to open input.");
+ goto err;
+ }
+
+ if (avformat_find_stream_info(av->format_context, 0) < 0) {
+ al_log_error("lav_demux", "Failed to find stream info.");
+ goto err;
+ }
+
+ if (!av->format_context->nb_streams) {
+ al_log_error("lav_demux", "No streams found.");
+ goto err;
+ }
+
+ av->duration = 0;
+
+ bool audio_selected = false;
+ bool video_selected = false;
+
+ 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)) {
+ continue;
+ }
+ if (type == AVMEDIA_TYPE_AUDIO) {
+ if (audio_selected) continue;
+ else audio_selected = true;
+ }
+ if (type == AVMEDIA_TYPE_VIDEO) {
+ if (video_selected) continue;
+ else video_selected = true;
+ }
+ // Try to detect attached images.
+ if (type == AVMEDIA_TYPE_VIDEO && stream->duration >= 0 && stream->nb_frames <= 1 &&
+ (stream->avg_frame_rate.den == 0 && stream->r_frame_rate.den > 0)) {
+ stream->duration = 0;
+ al_log_info("lav_demux", "Assuming stream #%i is an image.", i);
+ } else if (stream->duration < 0) {
+ if (av->format_context->duration < 0) {
+ stream->duration = 0;
+ } else {
+ // This could be completely wrong. In that case, it's still probably
+ // better than 0 (assuming the stream is not an image).
+ stream->duration = av_rescale_q(av->format_context->duration,
+ AV_TIME_BASE_Q, stream->time_base);
+ }
+ al_log_info("lav_demux", "Setting stream #%i to a duration of %.3fs.",
+ i, stream->duration * av_q2d(stream->time_base));
+ }
+ if (stream->start_time < 0) {
+ stream->start_time = 0;
+ }
+ s64 duration = av_rescale_q(stream->duration, stream->time_base, AV_TIME_BASE_Q);
+ if (duration > av->duration) {
+ av->duration = duration;
+ }
+ al_array_push(av->demux.streams, ((struct camu_stream){
+ .type = CAMU_FFMPEG_COMPAT,
+ .av.stream = stream
+ }));
+ }
+
+ av_format_inject_global_side_data(av->format_context);
+
+ //av_dump_format(av->format_context, 0, "", 0);
+
+ av->eof = false;
+
+ return true;
+err:
+ close_internal(av);
+
+ return false;
+}
+
+static s32 lav_demuxer_get_packet(struct camu_demuxer *demux, struct camu_packet *packet)
+{
+ struct camu_lav_demuxer *av = (struct camu_lav_demuxer *)demux;
+ packet->type = CAMU_FFMPEG_COMPAT;
+ s32 ret = 0;
+ do {
+ ret = av_read_frame(av->format_context, packet->av.pkt);
+ if (ret < 0 && ret != AVERROR_EOF) {
+ al_log_error("lav_demuxer", "Failed to read frame (%s).", av_err2str(ret));
+ }
+ break;
+ } while (1);
+ if (ret == AVERROR_EOF) av->eof = true;
+ return ret;
+}
+
+static s64 lav_demuxer_get_duration(struct camu_demuxer *demux)
+{
+ struct camu_lav_demuxer *av = (struct camu_lav_demuxer *)demux;
+ return av->duration;
+}
+
+static bool lav_demuxer_seek(struct camu_demuxer *demux, s64 pos)
+{
+ struct camu_lav_demuxer *av = (struct camu_lav_demuxer *)demux;
+ s64 ts = av_rescale_q(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) {
+ return true;
+ }
+ return false;
+}
+
+static void lav_demuxer_free(struct camu_demuxer **demux)
+{
+ struct camu_lav_demuxer *av = (struct camu_lav_demuxer *)*demux;
+ close_internal(av);
+ al_array_free(av->demux.streams);
+ al_free(av);
+ *demux = NULL;
+}
+
+struct camu_demuxer *camu_lav_demuxer_create(void)
+{
+ struct camu_lav_demuxer *av = al_alloc_object(struct camu_lav_demuxer);
+ av->demux.type = CAMU_FFMPEG_COMPAT;
+ al_array_init(av->demux.streams);
+ av->demux.init = lav_demuxer_init;
+ av->demux.get_packet = lav_demuxer_get_packet;
+ av->demux.get_duration = lav_demuxer_get_duration;
+ av->demux.seek = lav_demuxer_seek;
+ av->demux.free = lav_demuxer_free;
+ return (struct camu_demuxer *)av;
+}
diff --git a/src/codec/libav/demuxer.h b/src/codec/libav/demuxer.h
new file mode 100644
index 0000000..a9d8c5c
--- /dev/null
+++ b/src/codec/libav/demuxer.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <al/types.h>
+#include <al/lib.h>
+#include <al/array.h>
+
+#include <libavformat/avformat.h>
+
+#include "../codec.h"
+
+#include "avio.h"
+
+struct camu_lav_demuxer {
+ struct camu_demuxer demux;
+ AVIOContext *io_context;
+ AVFormatContext *format_context;
+ s64 duration;
+ bool eof;
+};
+
+struct camu_demuxer *camu_lav_demuxer_create(void);
diff --git a/src/codec/libav/encoder.c b/src/codec/libav/encoder.c
new file mode 100644
index 0000000..4f3fa80
--- /dev/null
+++ b/src/codec/libav/encoder.c
@@ -0,0 +1,186 @@
+#include <libavcodec/avcodec.h>
+#include <libavcodec/codec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/audio_fifo.h>
+#include <libavutil/channel_layout.h>
+#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 CHANNELS 2
+
+bool camu_lav_encoder_init(struct camu_lav_encoder *enc, char *encoder_name, char *ext)
+{
+ al_memset(enc, 0, sizeof(struct camu_lav_encoder));
+
+ const AVCodec *codec = avcodec_find_encoder_by_name(encoder_name);
+
+ if (!codec) {
+ al_log_error("lav_encoder", "Failed to find codec with name: %s.", encoder_name);
+ goto err;
+ }
+
+ enc->codec_context = avcodec_alloc_context3(codec);
+
+ if (!enc->codec_context) {
+ al_log_error("lav_encoder", "Failed to alloc codec context.");
+ goto err;
+ }
+
+ enc->codec_context->sample_rate = 48000;
+ 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->time_base = (AVRational){ 1, enc->codec_context->sample_rate };
+ enc->codec_context->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
+
+ enc->output_format = av_guess_format(ext, NULL, NULL);
+ if (enc->output_format && enc->output_format->flags & AVFMT_GLOBALHEADER) {
+ enc->codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
+ }
+
+ AVDictionary *opts = NULL;
+ av_dict_set(&opts, "b", "256k", 0);
+ av_dict_set(&opts, "vbr", "off", 0);
+ av_dict_set(&opts, "compression_level", "10", 0);
+ av_dict_set(&opts, "frame_duration", "20", 0);
+ av_dict_set(&opts, "application", "audio", 0);
+
+ if (avcodec_open2(enc->codec_context, codec, &opts) < 0) {
+ al_log_error("lav_encoder", "Failed to open codec.");
+ av_dict_free(&opts);
+ goto err;
+ }
+
+ av_dict_free(&opts);
+
+ 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("lav_encoder", "Failed to alloc fifo.");
+ goto err;
+ }
+
+ 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->pts = 0;
+
+ return true;
+err:
+ camu_lav_encoder_close(enc);
+ return false;
+}
+
+void camu_lav_encoder_push(struct camu_lav_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);
+ }
+ }
+ av_audio_fifo_write(enc->audio_fifo, (void **)data, sample_count);
+}
+
+static void free_frame_data(struct camu_lav_encoder *enc)
+{
+ if (enc->frame && enc->frame->nb_samples > 0) av_freep(&enc->frame->data[0]);
+}
+
+static void alloc_frame_data(struct camu_lav_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;
+}
+
+static bool receive_packets(struct camu_lav_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("lav_encoder", "Error receiving packet from encoder: (%s).", av_err2str(ret));
+ return false;
+ }
+
+ return true;
+}
+
+static s32 send_frame(struct camu_lav_encoder *enc, AVFrame *in_frame)
+{
+ s32 ret = avcodec_send_frame(enc->codec_context, in_frame);
+ if (ret < 0 && ret != AVERROR_EOF) {
+ al_log_error("lav_encoder", "Error sending frame to encoder: (%s).", av_err2str(ret));
+ }
+ return ret;
+}
+
+bool camu_lav_encoder_process(struct camu_lav_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;
+ }
+
+ if (samples_available < frame_size) frame_size = samples_available;
+
+ alloc_frame_data(enc, frame_size);
+ av_audio_fifo_read(enc->audio_fifo, (void **)enc->frame->data, frame_size);
+
+ s32 ret;
+ do {
+ ret = send_frame(enc, enc->frame);
+ // TODO: Check ret < 0 ?
+ if (!receive_packets(enc)) return false;
+ } while (ret == AVERROR(EAGAIN));
+
+ enc->frame->pts += frame_size;
+
+ // ret < 0 means encoder erroed while sending frame.
+ return ret == 0;
+}
+
+void camu_lav_encoder_close(struct camu_lav_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/libav/encoder.h b/src/codec/libav/encoder.h
new file mode 100644
index 0000000..b5a707a
--- /dev/null
+++ b/src/codec/libav/encoder.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/audio_fifo.h>
+#include <libavutil/rational.h>
+
+#include "resampler.h"
+
+struct camu_lav_encoder {
+ AVCodecContext *codec_context;
+ const AVOutputFormat *output_format;
+
+ struct camu_lav_resample_fmt fmt;
+
+ AVFrame *frame;
+ AVAudioFifo *audio_fifo;
+
+ void (*callback)(void *, AVPacket *);
+ void *userdata;
+};
+
+bool camu_lav_encoder_init(struct camu_lav_encoder *enc, char *encoder_name, char *ext);
+void camu_lav_encoder_push(struct camu_lav_encoder *enc, u8 **data, s32 sample_count);
+bool camu_lav_encoder_process(struct camu_lav_encoder *enc, bool flush);
+void camu_lav_encoder_close(struct camu_lav_encoder *enc);
diff --git a/src/codec/libav/packet_ext.c b/src/codec/libav/packet_ext.c
new file mode 100644
index 0000000..8fe4c39
--- /dev/null
+++ b/src/codec/libav/packet_ext.c
@@ -0,0 +1,168 @@
+#include "packet_ext.h"
+
+void aki_packet_write_av_codec_parameters(struct aki_packet *packet, AVCodecParameters *codecpar)
+{
+ AKI_PACKET_WRITE_TYPE(packet, enum AVMediaType, codecpar->codec_type);
+ AKI_PACKET_WRITE_TYPE(packet, enum AVCodecID, codecpar->codec_id);
+ AKI_PACKET_WRITE_TYPE(packet, u32, codecpar->codec_tag);
+ AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->extradata_size);
+ AKI_PACKET_WRITE_DATA(packet, codecpar->extradata, codecpar->extradata_size);
+ AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->format);
+ AKI_PACKET_WRITE_TYPE(packet, s64, codecpar->bit_rate);
+ AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->bits_per_coded_sample);
+ AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->bits_per_raw_sample);
+ AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->profile);
+ AKI_PACKET_WRITE_TYPE(packet, s32, codecpar->level);
+ 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, 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);
+ AKI_PACKET_WRITE_TYPE(packet, enum AVColorTransferCharacteristic, codecpar->color_trc);
+ 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, 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)
+{
+ AKI_PACKET_WRITE_TYPE(packet, enum AVCodecID, codec_id);
+}
+
+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, 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);
+ } else {
+ aki_packet_write_av_rational(packet, stream->avg_frame_rate);
+ }
+}
+
+void aki_packet_write_av_packet(struct aki_packet *packet, AVPacket *pkt)
+{
+ AKI_PACKET_WRITE_TYPE(packet, s64, pkt->pts);
+ AKI_PACKET_WRITE_TYPE(packet, s64, pkt->dts);
+ AKI_PACKET_WRITE_TYPE(packet, s32, pkt->size);
+ AKI_PACKET_WRITE_DATA(packet, pkt->data, pkt->size);
+ AKI_PACKET_WRITE_TYPE(packet, s32, pkt->stream_index);
+ 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);
+ 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);
+ }
+ AKI_PACKET_WRITE_TYPE(packet, s64, pkt->duration);
+ AKI_PACKET_WRITE_TYPE(packet, s64, pkt->pos);
+ AKI_PACKET_WRITE_TYPE(packet, AVRational, pkt->time_base);
+}
+
+void aki_packet_read_av_codec_parameters(struct aki_packet *packet, AVCodecParameters *codecpar)
+{
+ AKI_PACKET_READ_TYPE(packet, enum AVMediaType, codecpar->codec_type);
+ AKI_PACKET_READ_TYPE(packet, enum AVCodecID, codecpar->codec_id);
+ AKI_PACKET_READ_TYPE(packet, u32, codecpar->codec_tag);
+ AKI_PACKET_READ_TYPE(packet, s32, codecpar->extradata_size);
+ codecpar->extradata = av_malloc(codecpar->extradata_size);
+ u8 *extradata;
+ AKI_PACKET_READ_DATA(packet, codecpar->extradata_size, extradata);
+ al_memcpy(codecpar->extradata, extradata, codecpar->extradata_size);
+ AKI_PACKET_READ_TYPE(packet, s32, codecpar->format);
+ AKI_PACKET_READ_TYPE(packet, s64, codecpar->bit_rate);
+ AKI_PACKET_READ_TYPE(packet, s32, codecpar->bits_per_coded_sample);
+ AKI_PACKET_READ_TYPE(packet, s32, codecpar->bits_per_raw_sample);
+ AKI_PACKET_READ_TYPE(packet, s32, codecpar->profile);
+ AKI_PACKET_READ_TYPE(packet, s32, codecpar->level);
+ 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, 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);
+ AKI_PACKET_READ_TYPE(packet, enum AVColorTransferCharacteristic, codecpar->color_trc);
+ 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, 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)
+{
+ enum AVCodecID id;
+ AKI_PACKET_READ_TYPE(packet, enum AVCodecID, id);
+ return id;
+}
+
+AVStream *aki_packet_read_av_stream(AVFormatContext *format_context, const AVCodec *codec, struct aki_packet *packet)
+{
+ 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, 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);
+ return stream;
+}
+
+AVPacket *aki_packet_read_av_packet(struct aki_packet *packet)
+{
+ AVPacket *pkt = av_packet_alloc();
+ AKI_PACKET_READ_TYPE(packet, s64, pkt->pts);
+ AKI_PACKET_READ_TYPE(packet, s64, pkt->dts);
+ AKI_PACKET_READ_TYPE(packet, s32, pkt->size);
+ AKI_PACKET_READ_DATA(packet, pkt->size, pkt->data);
+ AKI_PACKET_READ_TYPE(packet, s32, pkt->stream_index);
+ AKI_PACKET_READ_TYPE(packet, s32, pkt->flags);
+ s32 side_data_elems;
+ 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);
+ 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);
+ al_memcpy(side_data, data, size);
+ }
+ AKI_PACKET_READ_TYPE(packet, s64, pkt->duration);
+ AKI_PACKET_READ_TYPE(packet, s64, pkt->pos);
+ AKI_PACKET_READ_TYPE(packet, AVRational, pkt->time_base);
+ return pkt;
+}
diff --git a/src/codec/libav/packet_ext.h b/src/codec/libav/packet_ext.h
new file mode 100644
index 0000000..e2f7cd5
--- /dev/null
+++ b/src/codec/libav/packet_ext.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <aki/common.h>
+
+#include <libavcodec/packet.h>
+#include <libavcodec/codec_par.h>
+#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/libav/resampler.c b/src/codec/libav/resampler.c
new file mode 100644
index 0000000..3dcb6d4
--- /dev/null
+++ b/src/codec/libav/resampler.c
@@ -0,0 +1,163 @@
+#include <libavutil/channel_layout.h>
+#include <libavutil/frame.h>
+#include <libavutil/opt.h>
+#include <libavutil/samplefmt.h>
+#include <libswresample/swresample.h>
+#include <al/lib.h>
+#include <al/log.h>
+
+#include "resampler.h"
+
+static bool in_req_match(struct camu_lav_resample_fmt *fmt)
+{
+ return (av_channel_layout_compare(&fmt->in_channel_layout, &fmt->req_channel_layout) == 0) &&
+ (fmt->in_format == fmt->req_format) && (fmt->in_sample_rate == fmt->req_sample_rate);
+}
+
+bool camu_lav_resampler_init(struct camu_lav_resampler *resamp, struct camu_lav_resample_fmt *fmt)
+{
+ al_memset(resamp, 0, sizeof(struct camu_lav_resampler));
+
+ fmt->resampler_needed = !in_req_match(fmt);
+
+ camu_lav_resample_fmt_copy(&resamp->fmt, fmt);
+
+ if (!fmt->resampler_needed) {
+ return true;
+ }
+
+ resamp->resample_context = swr_alloc();
+
+ SwrContext *ctx = resamp->resample_context;
+
+ if (!ctx) {
+ al_log_error("lav_resampler", "Failed to create resampler context.");
+ return false;
+ }
+
+ av_opt_set_chlayout(ctx, "in_chlayout", &fmt->in_channel_layout, 0);
+ av_opt_set_chlayout(ctx, "out_chlayout", &fmt->req_channel_layout, 0);
+ av_opt_set_int(ctx, "in_sample_rate", fmt->in_sample_rate, 0);
+ av_opt_set_int(ctx, "out_sample_rate", fmt->req_sample_rate, 0);
+ av_opt_set_sample_fmt(ctx, "in_sample_fmt", fmt->in_format, 0);
+ av_opt_set_sample_fmt(ctx, "out_sample_fmt", fmt->req_format, 0);
+
+ // Slower but more accurate resampler.
+ av_opt_set_int(ctx, "resampler", SWR_ENGINE_SOXR, 0);
+
+ if (swr_init(ctx) != 0) {
+ swr_free(&ctx);
+ al_log_error("lav_resampler", "Failed to open resampler context.");
+ return false;
+ }
+
+ return true;
+}
+
+s32 camu_lav_resampler_sample_count(struct camu_lav_resampler *resamp, s32 in_samples)
+{
+ al_assert(resamp->fmt.resampler_needed);
+ return swr_get_out_samples(resamp->resample_context, in_samples);
+}
+
+static inline void free_sample_data(struct camu_lav_resampler *resamp)
+{
+ if (resamp->data[0]) av_freep(&resamp->data[0]);
+}
+
+static inline void alloc_sample_data(struct camu_lav_resampler *resamp, 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, 0);
+ resamp->sample_count = sample_count;
+}
+
+s32 camu_lav_resampler_convert(struct camu_lav_resampler *resamp, const u8 **in_data, s32 in_samples)
+{
+ al_assert(resamp->fmt.resampler_needed);
+ s32 max_resample_count = in_samples ?
+ camu_lav_resampler_sample_count(resamp, in_samples) : resamp->sample_count;
+ alloc_sample_data(resamp, max_resample_count);
+ s32 resample_count = swr_convert(resamp->resample_context, resamp->data,
+ max_resample_count, in_data, in_samples);
+ if (resample_count < 0) {
+ al_log_error("lav_resampler", "Failed to convert samples (%i).", resample_count);
+ }
+ return resample_count;
+}
+
+u8 **camu_lav_resampler_get_data(struct camu_lav_resampler *resamp)
+{
+ return (u8 **)resamp->data;
+}
+
+void camu_lav_resampler_close(struct camu_lav_resampler *resamp)
+{
+ al_assert(resamp->fmt.resampler_needed);
+ if (resamp->resample_context) {
+ swr_close(resamp->resample_context);
+ swr_free(&resamp->resample_context);
+ }
+ free_sample_data(resamp);
+}
+
+void camu_lav_resample_fmt_copy(struct camu_lav_resample_fmt *dest, struct camu_lav_resample_fmt *src)
+{
+ 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;
+ dest->resampler_needed = src->resampler_needed;
+}
+
+size_t camu_lav_resample_fmt_bytes_per_sample(struct camu_lav_resample_fmt *fmt)
+{
+ return (size_t)av_get_bytes_per_sample(fmt->req_format);
+}
+
+size_t camu_lav_resample_fmt_samples_to_bytes(struct camu_lav_resample_fmt *fmt, size_t samples)
+{
+ s32 size, nb_channels = fmt->req_channel_layout.nb_channels;
+ if (av_samples_get_buffer_size(&size, nb_channels, (s32)samples, fmt->req_format, 1) < 0) {
+ return 0;
+ }
+ return (size_t)size;
+}
+
+size_t camu_lav_resample_fmt_samples_to_usec(struct camu_lav_resample_fmt *fmt, size_t samples)
+{
+ return samples / (fmt->req_sample_rate / 1000000.0);
+}
+
+f64 camu_lav_resample_fmt_samples_to_sec(struct camu_lav_resample_fmt *fmt, size_t samples)
+{
+ return samples / (f64)fmt->req_sample_rate;
+}
+
+size_t camu_lav_resample_fmt_usec_to_bytes(struct camu_lav_resample_fmt *fmt, size_t usec)
+{
+ size_t samples = usec * (fmt->req_sample_rate / 1000000.0);
+ return samples * (camu_lav_resample_fmt_bytes_per_sample(fmt) * fmt->req_channel_layout.nb_channels);
+}
+
+size_t camu_lav_resample_fmt_sec_to_bytes(struct camu_lav_resample_fmt *fmt, f64 sec)
+{
+ size_t samples = sec * fmt->req_sample_rate;
+ return samples * (camu_lav_resample_fmt_bytes_per_sample(fmt) * fmt->req_channel_layout.nb_channels);
+}
+
+size_t camu_lav_resample_fmt_bytes_to_usec(struct camu_lav_resample_fmt *fmt, size_t bytes)
+{
+ size_t samples = bytes / (camu_lav_resample_fmt_bytes_per_sample(fmt) * fmt->req_channel_layout.nb_channels);
+ return samples / (fmt->req_sample_rate / 1000000.0);
+}
+
+f64 camu_lav_resample_fmt_bytes_to_sec(struct camu_lav_resample_fmt *fmt, size_t bytes)
+{
+ size_t samples = bytes / (camu_lav_resample_fmt_bytes_per_sample(fmt) * fmt->req_channel_layout.nb_channels);
+ return samples / ((f64)fmt->req_sample_rate);
+}
diff --git a/src/codec/libav/resampler.h b/src/codec/libav/resampler.h
new file mode 100644
index 0000000..703d9a7
--- /dev/null
+++ b/src/codec/libav/resampler.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <libswresample/swresample.h>
+#include <libavutil/opt.h>
+#include <al/types.h>
+
+struct camu_lav_resample_fmt {
+ s32 in_channel_count;
+ s32 req_channel_count;
+ AVChannelLayout in_channel_layout;
+ AVChannelLayout req_channel_layout;
+ s32 in_sample_rate;
+ s32 req_sample_rate;
+ enum AVSampleFormat in_format;
+ enum AVSampleFormat req_format;
+ bool resampler_needed;
+};
+
+struct camu_lav_resampler {
+ SwrContext *resample_context;
+ struct camu_lav_resample_fmt fmt;
+ u8 *data[AV_NUM_DATA_POINTERS];
+ s32 sample_count;
+};
+
+bool camu_lav_resampler_init(struct camu_lav_resampler *resamp, struct camu_lav_resample_fmt *fmt);
+s32 camu_lav_resampler_sample_count(struct camu_lav_resampler *resamp, s32 in_samples);
+s32 camu_lav_resampler_convert(struct camu_lav_resampler *resamp, const u8 **in_data, s32 in_samples);
+u8 **camu_lav_resampler_get_data(struct camu_lav_resampler *resamp);
+void camu_lav_resampler_close(struct camu_lav_resampler *resamp);
+
+void camu_lav_resample_fmt_copy(struct camu_lav_resample_fmt *dest, struct camu_lav_resample_fmt *src);
+size_t camu_lav_resample_fmt_bytes_per_sample(struct camu_lav_resample_fmt *fmt);
+size_t camu_lav_resample_fmt_samples_to_bytes(struct camu_lav_resample_fmt *fmt, size_t samples);
+size_t camu_lav_resample_fmt_samples_to_usec(struct camu_lav_resample_fmt *fmt, size_t samples);
+f64 camu_lav_resample_fmt_samples_to_sec(struct camu_lav_resample_fmt *fmt, size_t samples);
+size_t camu_lav_resample_fmt_usec_to_bytes(struct camu_lav_resample_fmt *fmt, size_t usec);
+size_t camu_lav_resample_fmt_sec_to_bytes(struct camu_lav_resample_fmt *fmt, f64 sec);
+size_t camu_lav_resample_fmt_bytes_to_usec(struct camu_lav_resample_fmt *fmt, size_t bytes);
+f64 camu_lav_resample_fmt_bytes_to_sec(struct camu_lav_resample_fmt *fmt, size_t bytes);
diff --git a/src/codec/libav/scaler.c b/src/codec/libav/scaler.c
new file mode 100644
index 0000000..6146c69
--- /dev/null
+++ b/src/codec/libav/scaler.c
@@ -0,0 +1,79 @@
+#include <al/lib.h>
+#include <al/log.h>
+#include <libavutil/imgutils.h>
+#include <libavcodec/avcodec.h>
+
+#include "scaler.h"
+
+static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, s32 width, s32 height)
+{
+ AVFrame *frame = av_frame_alloc();
+ if (!frame) return NULL;
+ s32 size = av_image_get_buffer_size(pix_fmt, width, height, 1);
+ u8 *buffer = (u8 *)av_malloc(size);
+ if (!buffer) {
+ av_free(frame);
+ return NULL;
+ }
+ s32 ret = av_image_fill_arrays(frame->data, frame->linesize, buffer, pix_fmt, width, height, 1);
+ if (ret < 0) {
+ al_log_error("lav_scaler", "Failed to allocate picture buffer (%s).", av_err2str(ret));
+ av_free(frame);
+ return NULL;
+ }
+ frame->width = width;
+ frame->height = height;
+ frame->format = pix_fmt;
+ return frame;
+}
+
+bool camu_lav_scaler_init(struct camu_lav_scaler *scale, struct camu_lav_scale_fmt *fmt)
+{
+ al_memset(scale, 0, sizeof(struct camu_lav_scaler));
+
+ /*
+ if (fmt->in_width == fmt->req_width && fmt->in_height == fmt->req_height &&
+ fmt->in_format == fmt->req_format) {
+ fmt->scaler_needed = false;
+ return true;
+ }
+ */
+
+ fmt->scaler_needed = true;
+
+ scale->scaler_context = sws_getContext(fmt->in_width, fmt->in_height, fmt->in_format,
+ fmt->req_width, fmt->req_height, fmt->req_format,
+ SWS_BITEXACT, NULL, NULL, NULL);
+
+ if (!scale->scaler_context) {
+ al_log_error("lav_scaler", "Failed to create scaler context.");
+ return false;
+ }
+
+ scale->frame = alloc_picture(fmt->req_format, fmt->req_width, fmt->req_height);
+
+ if (!scale->frame) {
+ al_log_error("lav_scaler", "Failed to allocate frame.");
+ return false;
+ }
+
+ scale->fmt = *fmt;
+
+ return true;
+}
+
+bool camu_lav_scaler_scale(struct camu_lav_scaler *scale, const u8 **in_slice, s32 *in_strides)
+{
+ s32 ret = sws_scale(scale->scaler_context, in_slice, in_strides, 0,
+ scale->fmt.in_height, scale->frame->data, scale->frame->linesize);
+ if (ret != scale->fmt.req_height) {
+ al_log_error("lav_scaler", "Failed to scale frame (%s).", av_err2str(ret));
+ return false;
+ }
+ return true;
+}
+
+void camu_lav_scaler_close(struct camu_lav_scaler *scale)
+{
+ sws_freeContext(scale->scaler_context);
+}
diff --git a/src/codec/libav/scaler.h b/src/codec/libav/scaler.h
new file mode 100644
index 0000000..4d54085
--- /dev/null
+++ b/src/codec/libav/scaler.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <libswscale/swscale.h>
+#include <al/types.h>
+
+struct camu_lav_scale_fmt {
+ s32 in_width;
+ s32 in_height;
+ enum AVPixelFormat in_format;
+ s32 req_width;
+ s32 req_height;
+ enum AVPixelFormat req_format;
+ bool scaler_needed;
+};
+
+struct camu_lav_scaler {
+ struct SwsContext *scaler_context;
+ struct camu_lav_scale_fmt fmt;
+ AVFrame *frame;
+};
+
+bool camu_lav_scaler_init(struct camu_lav_scaler *scale, struct camu_lav_scale_fmt *fmt);
+bool camu_lav_scaler_scale(struct camu_lav_scaler *scale, const u8 **in_slice, s32 *in_strides);
+void camu_lav_scaler_close(struct camu_lav_scaler *scale);
diff --git a/src/codec/meson.build b/src/codec/meson.build
new file mode 100644
index 0000000..e1fd5f7
--- /dev/null
+++ b/src/codec/meson.build
@@ -0,0 +1,67 @@
+av_src = [
+ 'libav/common.c',
+ 'libav/demuxer.c',
+ 'libav/decoder.c',
+ 'libav/resampler.c',
+ 'libav/scaler.c',
+ 'libav/encoder.c',
+ 'libav/packet_ext.c',
+ 'libav/avio.c'
+]
+av_inc = []
+av_deps = []
+
+#libdir = meson.source_root() + '/build-mingw/subprojects/ffmpeg-6.0/dist/msys64/clang64lib'
+#incdir = meson.source_root() + '/build-mingw/subprojects/ffmpeg-6.0/dist/msys64/clang64include'
+#av_inc += [include_directories(incdir)]
+#libavutil = compiler.find_library('avutil', dirs: libdir)
+#libavformat = compiler.find_library('avformat', dirs: libdir)
+#libavcodec = compiler.find_library('avcodec', dirs: libdir)
+#libswresample = compiler.find_library('swresample', dirs: libdir)
+#libswscale = compiler.find_library('swscale', dirs: libdir)
+
+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())
+ av_proj = subproject('ffmpeg', required: false)
+ if av_proj.found()
+ libavutil = av_proj.get_variable('avutil')
+ libavformat = av_proj.get_variable('avformat')
+ libavcodec = av_proj.get_variable('avcodec')
+ libswresample = av_proj.get_variable('swresample')
+ libswscale = av_proj.get_variable('swscale')
+ zlib = compiler.find_library('z', required: false)
+ if zlib.found()
+ av_deps += [zlib]
+ endif
+ soxr = compiler.find_library('soxr', required: false)
+ if soxr.found()
+ av_deps += [soxr]
+ endif
+ if av_proj.get_variable('is_msvc')
+ av_deps += [compiler.find_library('Bcrypt', required: true)]
+ endif
+ av_inc += [av_proj.get_variable('inc')]
+ endif
+endif
+
+if libavutil.found() and libavformat.found() and libavcodec.found() and libswresample.found() and libswscale.found()
+ av_deps += [libavutil, libavformat, libavcodec, libswresample, libswscale]
+ av = declare_dependency(sources: av_src, dependencies: av_deps, include_directories: av_inc)
+ add_project_arguments('-DHAVE_FFMPEG', language: ['c', 'cpp'])
+endif
+
+stb_image = subproject('stb_image').get_variable('stb_image')
+#stb_image_decoder = declare_dependency(sources: ['stb_image/impl.c'],
+# dependencies: stb_image)
+#
+wuffs = subproject('wuffs').get_variable('wuffs')
+wuffs_decoder = declare_dependency(sources: ['wuffs/impl.c'],
+ dependencies: wuffs)
+
+#spng = dependency('spng')
+#spng_decoder = declare_dependency(sources: ['spng/impl.c'],
+# dependencies: spng)
diff --git a/src/codec/spng/decoder.h b/src/codec/spng/decoder.h
new file mode 100644
index 0000000..fee30c6
--- /dev/null
+++ b/src/codec/spng/decoder.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "../codec.h"
+
+struct camu_spng_decoder {
+ struct camu_decoder dec;
+ void (*callback)(void *, struct camu_frame *);
+ void (*userdata);
+};
+
+struct camu_decoder *camu_spng_decoder_create(void);
diff --git a/src/codec/spng/demuxer.h b/src/codec/spng/demuxer.h
new file mode 100644
index 0000000..1947bb7
--- /dev/null
+++ b/src/codec/spng/demuxer.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "../codec.h"
+
+struct camu_spng_demuxer {
+ struct camu_demuxer demux;
+ struct cch_handle *handle;
+ struct aki_buffer buffer;
+ bool eof;
+};
+
+struct camu_demuxer *camu_spng_demuxer_create(void);
diff --git a/src/codec/spng/impl.c b/src/codec/spng/impl.c
new file mode 100644
index 0000000..d090bdc
--- /dev/null
+++ b/src/codec/spng/impl.c
@@ -0,0 +1,166 @@
+#include <al/lib.h>
+#include <aki/common.h>
+#include <spng.h>
+
+#include "../../cache/entry.h"
+
+#include "../common.h"
+
+#include "demuxer.h"
+#include "decoder.h"
+
+static bool spng_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handle)
+{
+ struct camu_spng_demuxer *spng = (struct camu_spng_demuxer *)demux;
+
+ spng->handle = handle;
+ aki_buffer_init(&spng->buffer);
+ spng->eof = false;
+
+ cch_handle_seek(spng->handle, 0, SEEK_SET);
+ off_t size = cch_entry_get_size(spng->handle->entry);
+ aki_buffer_ensure_space(&spng->buffer, size);
+ cch_handle_read(spng->handle, aki_buffer_get_ptr(&spng->buffer, 0), size);
+ spng->buffer.size = size;
+
+ spng_ctx *ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32);
+ spng_set_png_buffer(ctx, aki_buffer_get_ptr(&spng->buffer, 0), spng->buffer.size);
+ spng_set_option(ctx, SPNG_CHUNK_COUNT_LIMIT, 6250);
+ spng_set_crc_action(ctx, SPNG_CRC_DISCARD, SPNG_CRC_DISCARD);
+
+ struct spng_ihdr ihdr;
+ spng_get_ihdr(ctx, &ihdr);
+
+ struct camu_stream stream;
+ stream.type = CAMU_NORMAL;
+ stream.video.width = ihdr.width;
+ stream.video.height = ihdr.height;
+
+ al_array_push(spng->demux.streams, stream);
+
+ spng_ctx_free(ctx);
+
+ return true;
+}
+
+static s32 spng_demuxer_get_packet(struct camu_demuxer *demux, struct camu_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;
+}
+
+static s64 spng_demuxer_get_duration(struct camu_demuxer *demux)
+{
+ (void)demux;
+ return 0;
+}
+
+static bool spng_demuxer_seek(struct camu_demuxer *demux, s64 pos)
+{
+ (void)demux;
+ (void)pos;
+ return true;
+}
+
+static void spng_demuxer_free(struct camu_demuxer **demux)
+{
+ struct camu_spng_demuxer *spng = (struct camu_spng_demuxer *)*demux;
+ al_free(spng);
+ *demux = NULL;
+}
+
+static bool spng_decoder_init(struct camu_decoder *dec, struct camu_renderer *renderer, struct camu_stream *stream)
+{
+ 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->callback = callback;
+ spng->userdata = userdata;
+}
+
+static s32 spng_decoder_push(struct camu_decoder *dec, struct camu_packet *packet)
+{
+ if (!packet) return CAMU_OK;
+
+ struct camu_spng_decoder *spng = (struct camu_spng_decoder *)dec;
+
+ 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);
+ spng_set_option(ctx, SPNG_CHUNK_COUNT_LIMIT, 6250);
+ spng_set_crc_action(ctx, SPNG_CRC_DISCARD, SPNG_CRC_DISCARD);
+
+ 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;
+
+ 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);
+
+ spng->callback(spng->userdata, frame);
+
+ return CAMU_OK;
+}
+
+static void spng_decoder_flush(struct camu_decoder *dec)
+{
+ (void)dec;
+}
+
+static s32 spng_decoder_process(struct camu_decoder *dec)
+{
+ (void)dec;
+ return CAMU_ERR_EOF;
+}
+
+static void spng_decoder_free(struct camu_decoder **dec)
+{
+ struct camu_spng_decoder *spng = (struct camu_spng_decoder *)*dec;
+ al_free(spng);
+ *dec = NULL;
+}
+
+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;
+ al_array_init(spng->demux.streams);
+ spng->demux.init = spng_demuxer_init;
+ spng->demux.get_packet = spng_demuxer_get_packet;
+ spng->demux.get_duration = spng_demuxer_get_duration;
+ spng->demux.seek = spng_demuxer_seek;
+ spng->demux.free = spng_demuxer_free;
+ return (struct camu_demuxer *)spng;
+}
+
+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;
+ spng->dec.free = spng_decoder_free;
+ return (struct camu_decoder *)spng;
+}
diff --git a/src/codec/stb_image/decoder.h b/src/codec/stb_image/decoder.h
new file mode 100644
index 0000000..7083a09
--- /dev/null
+++ b/src/codec/stb_image/decoder.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "../codec.h"
+
+struct camu_stbi_decoder {
+ struct camu_decoder dec;
+ void (*callback)(void *, struct camu_frame *);
+ void (*userdata);
+};
+
+struct camu_decoder *camu_stbi_decoder_create(void);
diff --git a/src/codec/stb_image/demuxer.h b/src/codec/stb_image/demuxer.h
new file mode 100644
index 0000000..52f9256
--- /dev/null
+++ b/src/codec/stb_image/demuxer.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "../codec.h"
+
+struct camu_stbi_demuxer {
+ struct camu_demuxer demux;
+ struct cch_handle *handle;
+ struct aki_buffer buffer;
+ bool eof;
+};
+
+struct camu_demuxer *camu_stbi_demuxer_create(void);
diff --git a/src/codec/stb_image/impl.c b/src/codec/stb_image/impl.c
new file mode 100644
index 0000000..953cca8
--- /dev/null
+++ b/src/codec/stb_image/impl.c
@@ -0,0 +1,155 @@
+#include <al/lib.h>
+#include <aki/common.h>
+#include "../common.h"
+#define STB_IMAGE_IMPLEMENTATION
+#define STBI_NO_STDIO
+#define STBI_NO_FAILURE_STRINGS
+#define STB_IMAGE_STATIC
+#define STBI_MALLOC camu_page_alloc
+#define STBI_FREE al_free
+#define STBI_REALLOC al_realloc
+#include <stb_image.h>
+
+#include "../../cache/entry.h"
+
+#include "demuxer.h"
+#include "decoder.h"
+
+static bool stbi_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handle)
+{
+ struct camu_stbi_demuxer *stb = (struct camu_stbi_demuxer *)demux;
+
+ stb->handle = handle;
+ aki_buffer_init(&stb->buffer);
+ stb->eof = false;
+
+ cch_handle_seek(stb->handle, 0, SEEK_SET);
+ off_t size = cch_entry_get_size(stb->handle->entry);
+ 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);
+
+ struct camu_stream stream = { 0 };
+ stream.type = CAMU_NORMAL;
+ stream.video.width = w;
+ stream.video.height = h;
+
+ al_array_push(stb->demux.streams, stream);
+
+ return true;
+}
+
+static s32 stbi_demuxer_get_packet(struct camu_demuxer *demux, struct camu_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;
+}
+
+static s64 stbi_demuxer_get_duration(struct camu_demuxer *demux)
+{
+ (void)demux;
+ return 0;
+}
+
+static bool stbi_demuxer_seek(struct camu_demuxer *demux, s64 pos)
+{
+ (void)demux;
+ (void)pos;
+ return true;
+}
+
+static void stbi_demuxer_free(struct camu_demuxer **demux)
+{
+ struct camu_stbi_demuxer *stb = (struct camu_stbi_demuxer *)*demux;
+ 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)
+{
+ struct camu_stbi_decoder *stb = (struct camu_stbi_decoder *)dec;
+ stb->callback = callback;
+ stb->userdata = userdata;
+}
+
+static s32 stbi_decoder_push(struct camu_decoder *dec, struct camu_packet *packet)
+{
+ if (!packet) return CAMU_OK;
+
+ struct camu_stbi_decoder *stb = (struct camu_stbi_decoder *)dec;
+
+ s32 w, h, channels;
+ u8 *pixels = stbi_load_from_memory(aki_buffer_get_ptr(packet->buffer, 0),
+ packet->buffer->size, &w, &h, &channels, 4);
+
+ struct camu_frame *frame = al_alloc_object(struct camu_frame);
+ frame->type = CAMU_NORMAL;
+ frame->data = pixels;
+ frame->width = stb->dec.stream->video.width;
+ frame->height = stb->dec.stream->video.height;
+ frame->fmt = CAMU_PIXEL_FMT_RGBA;
+ frame->pts = 0.0;
+ frame->flags = 0;
+
+ stb->callback(stb->userdata, frame);
+
+ return CAMU_OK;
+}
+
+static void stbi_decoder_flush(struct camu_decoder *dec)
+{
+ (void)dec;
+}
+
+static s32 stbi_decoder_process(struct camu_decoder *dec)
+{
+ (void)dec;
+ return CAMU_ERR_EOF;
+}
+
+static void stbi_decoder_free(struct camu_decoder **dec)
+{
+ struct camu_stbi_decoder *stb = (struct camu_stbi_decoder *)*dec;
+ al_free(stb);
+ *dec = NULL;
+}
+
+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;
+ al_array_init(stb->demux.streams);
+ stb->demux.init = stbi_demuxer_init;
+ stb->demux.get_packet = stbi_demuxer_get_packet;
+ stb->demux.get_duration = stbi_demuxer_get_duration;
+ stb->demux.seek = stbi_demuxer_seek;
+ stb->demux.free = stbi_demuxer_free;
+ return (struct camu_demuxer *)stb;
+}
+
+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;
+ stb->dec.free = stbi_decoder_free;
+ return (struct camu_decoder *)stb;
+}
diff --git a/src/codec/wuffs/decoder.h b/src/codec/wuffs/decoder.h
new file mode 100644
index 0000000..71f0290
--- /dev/null
+++ b/src/codec/wuffs/decoder.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "../common.h"
+#include "../codec.h"
+
+struct camu_wuffs_decoder {
+ struct camu_decoder dec;
+ u8 file_type;
+ void (*callback)(void *, struct camu_frame *);
+ void (*userdata);
+};
+
+struct camu_decoder *camu_wuffs_decoder_create(void);
diff --git a/src/codec/wuffs/demuxer.h b/src/codec/wuffs/demuxer.h
new file mode 100644
index 0000000..6e9e391
--- /dev/null
+++ b/src/codec/wuffs/demuxer.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "../codec.h"
+
+struct camu_wuffs_demuxer {
+ struct camu_demuxer demux;
+ struct cch_handle *handle;
+ struct aki_buffer buffer;
+ bool eof;
+};
+
+struct camu_demuxer *camu_wuffs_demuxer_create(void);
diff --git a/src/codec/wuffs/impl.c b/src/codec/wuffs/impl.c
new file mode 100644
index 0000000..a1e58e4
--- /dev/null
+++ b/src/codec/wuffs/impl.c
@@ -0,0 +1,213 @@
+#define WUFFS_IMPLEMENTATION
+#define WUFFS_CONFIG__STATIC_FUNCTIONS
+#define WUFFS_CONFIG__MODULES
+#define WUFFS_CONFIG__MODULE__BASE
+#define WUFFS_CONFIG__MODULE__ADLER32
+#define WUFFS_CONFIG__MODULE__DEFLATE
+#define WUFFS_CONFIG__MODULE__ZLIB
+#define WUFFS_CONFIG__MODULE__CRC32
+#define WUFFS_CONFIG__MODULE__PNG
+#define WUFFS_CONFIG__MODULE__JPEG
+#define WUFFS_CONFIG__MODULE__IMAGE
+#include <wuffs-unsupported-snapshot.c>
+
+#include <aki/common.h>
+
+#include "../../cache/entry.h"
+
+#include "demuxer.h"
+#include "decoder.h"
+
+static bool wuffs_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handle)
+{
+ struct camu_wuffs_demuxer *wfs = (struct camu_wuffs_demuxer *)demux;
+
+ wfs->handle = handle;
+ aki_buffer_init(&wfs->buffer);
+ wfs->eof = false;
+
+ cch_handle_seek(wfs->handle, 0, SEEK_SET);
+ off_t size = cch_entry_get_size(wfs->handle->entry);
+ aki_buffer_ensure_space(&wfs->buffer, size);
+ cch_handle_read(wfs->handle, aki_buffer_get_ptr(&wfs->buffer, 0), size);
+ wfs->buffer.size = size;
+
+ 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;
+
+ 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};
+ 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};
+ 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_stream stream;
+ stream.type = CAMU_NORMAL;
+ stream.video.width = wuffs_base__pixel_config__width(&ic.pixcfg);
+ stream.video.height = wuffs_base__pixel_config__height(&ic.pixcfg);
+
+ al_array_push(wfs->demux.streams, stream);
+
+ return true;
+}
+
+static s32 wuffs_demuxer_get_packet(struct camu_demuxer *demux, struct camu_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;
+}
+
+static s64 wuffs_demuxer_get_duration(struct camu_demuxer *demux)
+{
+ (void)demux;
+ return 0;
+}
+
+static bool wuffs_demuxer_seek(struct camu_demuxer *demux, s64 pos)
+{
+ (void)demux;
+ (void)pos;
+ return true;
+}
+
+static void wuffs_demuxer_free(struct camu_demuxer **demux)
+{
+ struct camu_wuffs_demuxer *wfs = (struct camu_wuffs_demuxer *)*demux;
+ al_free(wfs);
+ *demux = NULL;
+}
+
+static bool wuffs_decoder_init(struct camu_decoder *dec, struct camu_renderer *renderer, struct camu_stream *stream)
+{
+ 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->callback = callback;
+ wfs->userdata = userdata;
+}
+
+static s32 wuffs_decoder_push(struct camu_decoder *dec, struct camu_packet *packet)
+{
+ if (!packet) return CAMU_OK;
+
+ struct camu_wuffs_decoder *wfs = (struct camu_wuffs_decoder *)dec;
+
+ 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;
+
+ 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};
+ 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};
+ 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;
+
+ 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};
+ 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;
+ 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;
+
+ wfs->callback(wfs->userdata, frame);
+
+ return CAMU_OK;
+}
+
+static void wuffs_decoder_flush(struct camu_decoder *dec)
+{
+ (void)dec;
+}
+
+static s32 wuffs_decoder_process(struct camu_decoder *dec)
+{
+ (void)dec;
+ return CAMU_ERR_EOF;
+}
+
+static void wuffs_decoder_free(struct camu_decoder **dec)
+{
+ struct camu_wuffs_decoder *wfs = (struct camu_wuffs_decoder *)*dec;
+ al_free(wfs);
+ *dec = NULL;
+}
+
+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;
+ al_array_init(wfs->demux.streams);
+ wfs->demux.init = wuffs_demuxer_init;
+ wfs->demux.get_packet = wuffs_demuxer_get_packet;
+ wfs->demux.get_duration = wuffs_demuxer_get_duration;
+ wfs->demux.seek = wuffs_demuxer_seek;
+ wfs->demux.free = wuffs_demuxer_free;
+ return (struct camu_demuxer *)wfs;
+}
+
+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;
+ wfs->dec.free = wuffs_decoder_free;
+ return (struct camu_decoder *)wfs;
+}