diff options
| author | 2023-11-06 11:54:08 -0500 | |
|---|---|---|
| committer | 2023-11-06 11:54:08 -0500 | |
| commit | a48a68cfb04b2020737c0adfb0e7667451a22b5c (patch) | |
| tree | a92121897f84298c3798a4f2ba56de45cd100eab /src/codec/libav | |
| download | camu-a48a68cfb04b2020737c0adfb0e7667451a22b5c.tar.gz camu-a48a68cfb04b2020737c0adfb0e7667451a22b5c.tar.bz2 camu-a48a68cfb04b2020737c0adfb0e7667451a22b5c.zip | |
Add camu
- Subprojects temporarily omitted
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/codec/libav')
| -rw-r--r-- | src/codec/libav/avio.c | 13 | ||||
| -rw-r--r-- | src/codec/libav/avio.h | 10 | ||||
| -rw-r--r-- | src/codec/libav/common.c | 6 | ||||
| -rw-r--r-- | src/codec/libav/common.h | 6 | ||||
| -rw-r--r-- | src/codec/libav/decoder.c | 165 | ||||
| -rw-r--r-- | src/codec/libav/decoder.h | 20 | ||||
| -rw-r--r-- | src/codec/libav/demuxer.c | 184 | ||||
| -rw-r--r-- | src/codec/libav/demuxer.h | 21 | ||||
| -rw-r--r-- | src/codec/libav/encoder.c | 186 | ||||
| -rw-r--r-- | src/codec/libav/encoder.h | 26 | ||||
| -rw-r--r-- | src/codec/libav/packet_ext.c | 168 | ||||
| -rw-r--r-- | src/codec/libav/packet_ext.h | 19 | ||||
| -rw-r--r-- | src/codec/libav/resampler.c | 163 | ||||
| -rw-r--r-- | src/codec/libav/resampler.h | 40 | ||||
| -rw-r--r-- | src/codec/libav/scaler.c | 79 | ||||
| -rw-r--r-- | src/codec/libav/scaler.h | 24 |
16 files changed, 1130 insertions, 0 deletions
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); |