#pragma once #include #include #include #ifdef CAMU_HAVE_FFMPEG #include #include #endif #include "../cache/handle.h" #include "codecs.h" enum { CAMU_LANG_UNKNOWN = 0, CAMU_LANG_ENGLISH, CAMU_LANG_JAPANESE }; #ifdef CAMU_HAVE_FFMPEG enum { CAMU_OK = 0, CAMU_ERR_ERROR = 1, CAMU_ERR_EOF = AVERROR_EOF, CAMU_ERR_AGAIN = AVERROR(EAGAIN) }; enum { CAMU_SEEK_SIZE = AVSEEK_SIZE }; // Stream types must be >= 0 and < 7 because we use them as shifts for a u8 mask and 7 // is reserved for STREAM_UNKNOWN which is different than AVMEDIA_TYPE_UNKNOWN(-1). enum { CAMU_STREAM_VIDEO = AVMEDIA_TYPE_VIDEO, // 0 CAMU_STREAM_AUDIO = AVMEDIA_TYPE_AUDIO, // 1 CAMU_STREAM_SUBTITLE = AVMEDIA_TYPE_SUBTITLE, CAMU_STREAM_ATTACHMENT = AVMEDIA_TYPE_ATTACHMENT, CAMU_STREAM_UNKNOWN = 7 }; AL_STATIC_ASSERT(stream_type_count, AVMEDIA_TYPE_NB, <, 7); enum { CAMU_SAMPLE_FORMAT_NONE = AV_SAMPLE_FMT_NONE, // -1 CAMU_SAMPLE_FORMAT_U8 = AV_SAMPLE_FMT_U8, CAMU_SAMPLE_FORMAT_S16 = AV_SAMPLE_FMT_S16, CAMU_SAMPLE_FORMAT_S32 = AV_SAMPLE_FMT_S32, CAMU_SAMPLE_FORMAT_FLT = AV_SAMPLE_FMT_FLT, CAMU_SAMPLE_FORMAT_U8P = AV_SAMPLE_FMT_U8P, CAMU_SAMPLE_FORMAT_S16P = AV_SAMPLE_FMT_S16P, CAMU_SAMPLE_FORMAT_S32P = AV_SAMPLE_FMT_S32P, CAMU_SAMPLE_FORMAT_FLTP = AV_SAMPLE_FMT_FLTP }; enum { CAMU_PIXEL_FORMAT_NONE = AV_PIX_FMT_NONE, // -1 CAMU_PIXEL_FORMAT_RGBA = AV_PIX_FMT_RGBA, CAMU_PIXEL_FORMAT_RGB24 = AV_PIX_FMT_RGB24, CAMU_PIXEL_FORMAT_RGB32 = AV_PIX_FMT_RGB32, CAMU_PIXEL_FORMAT_GREYA = AV_PIX_FMT_GRAY8A, CAMU_PIXEL_FORMAT_GREY = AV_PIX_FMT_GRAY8, CAMU_PIXEL_FORMAT_PAL8 = AV_PIX_FMT_PAL8 }; enum { CAMU_CODEC_FALLBACK = -1, CAMU_CODEC_NONE = AV_CODEC_ID_NONE, CAMU_CODEC_PNG = AV_CODEC_ID_PNG, CAMU_CODEC_JPEG = AV_CODEC_ID_MJPEG }; #define CAMU_PLANAR_DATA_POINTERS AV_NUM_DATA_POINTERS #else enum { CAMU_OK = 0, CAMU_ERR_ERROR = 1, CAMU_ERR_EOF = -1, CAMU_ERR_AGAIN = -2 }; enum { CAMU_SEEK_SIZE = 0x10000 }; // Using values from FFmpeg for less confusing errors. enum { CAMU_STREAM_VIDEO = 0, CAMU_STREAM_AUDIO = 1, CAMU_STREAM_SUBTITLE = 3, CAMU_STREAM_ATTACHMENT = 4, CAMU_STREAM_UNKNOWN = 7 }; enum { CAMU_SAMPLE_FORMAT_NONE = -1, CAMU_SAMPLE_FORMAT_U8, CAMU_SAMPLE_FORMAT_S16, CAMU_SAMPLE_FORMAT_S32, CAMU_SAMPLE_FORMAT_FLT, CAMU_SAMPLE_FORMAT_U8P, CAMU_SAMPLE_FORMAT_S16P, CAMU_SAMPLE_FORMAT_S32P, CAMU_SAMPLE_FORMAT_FLTP }; enum { CAMU_PIXEL_FORMAT_NONE = -1, CAMU_PIXEL_FORMAT_RGBA, CAMU_PIXEL_FORMAT_RGB24, CAMU_PIXEL_FORMAT_RGB32, CAMU_PIXEL_FORMAT_GREYA, CAMU_PIXEL_FORMAT_GREY, CAMU_PIXEL_FORMAT_PAL8 }; enum { CAMU_CODEC_FALLBACK = -1, CAMU_CODEC_NONE, CAMU_CODEC_PNG, CAMU_CODEC_JPEG }; #define CAMU_PLANAR_DATA_POINTERS 8 #endif enum { CAMU_FRAME_DEVICE_ALLOCATED = 1 }; enum { CAMU_NORMAL = 0, #ifdef CAMU_HAVE_FFMPEG CAMU_FFMPEG_COMPAT #endif }; struct camu_audio_format { s32 format; s32 sample_rate; #ifdef CAMU_HAVE_FFMPEG #ifndef CAMU_OLD_FFMPEG AVChannelLayout channel_layout; #else u64 channel_layout; #endif #endif s32 channel_count; }; struct camu_video_format { u32 width; u32 height; s32 format; }; struct camu_codec_stream { u8 mode; u8 type; s32 index; u64 duration; struct camu_codec_info *codec_info; struct { struct camu_video_format fmt; } video; struct { struct camu_audio_format fmt; } audio; #ifdef CAMU_HAVE_FFMPEG struct { AVStream *stream; AVFormatContext *format_context; } av; #endif }; struct camu_codec_packet { u8 mode; struct nn_buffer *buffer; #ifdef CAMU_HAVE_FFMPEG struct { AVPacket *pkt; } av; #endif }; struct camu_codec_frame { u8 mode; u8 flags; u8 *data; s32 format; f64 pts; struct { u32 width; u32 height; } video; struct { u32 sample_count; } audio; #ifdef CAMU_HAVE_FFMPEG struct { AVFrame *frame; } av; #endif }; struct camu_demuxer { u8 mode; bool (*init)(struct camu_demuxer *, struct cch_handle *); s32 (*get_packet)(struct camu_demuxer *, struct camu_codec_packet *); u64 (*get_duration)(struct camu_demuxer *); bool (*seek)(struct camu_demuxer *, u64, bool); void (*free)(struct camu_demuxer **); array(struct camu_codec_stream) streams; u32 subscribed; }; struct camu_renderer; struct camu_decoder { u8 mode; bool (*init)(struct camu_decoder *, struct camu_renderer *, struct camu_codec_stream *, void (*callback)(void *, struct camu_codec_frame *), void *); s32 (*push)(struct camu_decoder *, struct camu_codec_packet *); #ifdef CAMU_HAVE_FFMPEG s32 (*push_av_packet)(struct camu_decoder *, AVPacket *); #endif s32 (*process)(struct camu_decoder *); void (*flush)(struct camu_decoder *); void (*free)(struct camu_decoder **); }; struct camu_resampler_format { bool resampler_needed; struct camu_audio_format in; struct camu_audio_format req; }; struct camu_resampler { bool (*init)(struct camu_resampler *, struct camu_resampler_format *); s32 (*convert)(struct camu_resampler *, const u8 **, s32); s32 (*flush)(struct camu_resampler *); u8 **(*get_data)(struct camu_resampler *); void (*free)(struct camu_resampler **); }; struct camu_scaler_format { bool scaler_needed; struct camu_video_format in; struct camu_video_format req; }; struct camu_scaler { bool (*init)(struct camu_scaler *, struct camu_scaler_format *); bool (*scale)(struct camu_scaler *, const u8 **in_slice, s32 *in_strides); struct camu_codec_frame *(*get_frame)(struct camu_scaler *); void (*free)(struct camu_scaler **); }; static inline bool camu_resampler_format_matches(struct camu_resampler_format *fmt) { return fmt->in.format == fmt->req.format && fmt->in.sample_rate == fmt->req.sample_rate #ifdef CAMU_HAVE_FFMPEG #ifndef CAMU_OLD_FFMPEG && av_channel_layout_compare(&fmt->in.channel_layout, &fmt->req.channel_layout) == 0 #else && fmt->in.channel_layout == fmt->req.channel_layout #endif #endif && fmt->in.channel_count == fmt->req.channel_count; } #ifdef CAMU_HAVE_FFMPEG #ifndef CAMU_OLD_FFMPEG #define AV_CODECPAR_CHANNEL_LAYOUT(codecpar) ((codecpar)->ch_layout) #define AV_CODECPAR_CHANNELS(codecpar) ((codecpar)->ch_layout.nb_channels) static inline void camu_default_channel_layout(AVChannelLayout *ch_layout, s32 channels) { av_channel_layout_default(ch_layout, channels); } static inline void camu_copy_channel_layout(AVChannelLayout *dest, AVChannelLayout *src) { av_channel_layout_copy(dest, src); } #else #define AV_CODECPAR_CHANNEL_LAYOUT(codecpar) ((codecpar)->channel_layout) #define AV_CODECPAR_CHANNELS(codecpar) ((codecpar)->channels) static inline void camu_default_channel_layout(u64 *channel_layout, s32 channels) { *channel_layout = av_get_default_channel_layout(channels); } static inline void camu_copy_channel_layout(u64 *dest, u64 *src) { *dest = *src; } #endif #endif static inline void camu_audio_format_copy(struct camu_audio_format *dest, struct camu_audio_format *src) { dest->format = src->format; dest->sample_rate = src->sample_rate; #ifdef CAMU_HAVE_FFMPEG #ifndef CAMU_OLD_FFMPEG av_channel_layout_copy(&dest->channel_layout, &src->channel_layout); #else dest->channel_layout = src->channel_layout; #endif #endif dest->channel_count = src->channel_count; } static inline const char *camu_audio_format_name(s32 format) { #ifdef CAMU_HAVE_FFMPEG return av_get_sample_fmt_name((enum AVSampleFormat)format); #else (void)format; return "(undefined)"; #endif } static inline size_t camu_audio_format_bytes_per_sample(struct camu_audio_format *fmt) { #ifdef CAMU_HAVE_FFMPEG return (size_t)av_get_bytes_per_sample((enum AVSampleFormat)fmt->format); #else switch (fmt->format) { case CAMU_SAMPLE_FORMAT_U8: case CAMU_SAMPLE_FORMAT_U8P: return 1; case CAMU_SAMPLE_FORMAT_S16: case CAMU_SAMPLE_FORMAT_S16P: return 2; case CAMU_SAMPLE_FORMAT_S32: case CAMU_SAMPLE_FORMAT_S32P: case CAMU_SAMPLE_FORMAT_FLT: case CAMU_SAMPLE_FORMAT_FLTP: return 4; default: al_assert_and_return(0); } #endif } static inline size_t camu_audio_format_samples_to_bytes(struct camu_audio_format *fmt, size_t samples) { return samples * camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count; } static inline f64 camu_audio_format_samples_to_sec(struct camu_audio_format *fmt, size_t samples) { return samples / (f64)fmt->sample_rate; } static inline size_t camu_audio_format_usec_to_bytes(struct camu_audio_format *fmt, u64 usec) { // Keep order of operations for precision. size_t samples = usec * (fmt->sample_rate / 1000000.0); return samples * camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count; } static inline size_t camu_audio_format_sec_to_bytes(struct camu_audio_format *fmt, f64 sec) { size_t samples = sec * fmt->sample_rate; return samples * camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count; } static inline u64 camu_audio_format_bytes_to_usec(struct camu_audio_format *fmt, size_t bytes) { size_t bps = camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count; return ((bytes / bps) * 1000000 + (fmt->sample_rate >> 1)) / fmt->sample_rate; } static inline f64 camu_audio_format_bytes_to_sec(struct camu_audio_format *fmt, size_t bytes) { size_t samples = bytes / (camu_audio_format_bytes_per_sample(fmt) * fmt->channel_count); return samples / (f64)fmt->sample_rate; } static inline void camu_video_format_copy(struct camu_video_format *dest, struct camu_video_format *src) { dest->width = src->width; dest->height = src->height; dest->format = src->format; } static inline const char *camu_pixel_format_name(s32 format) { #ifdef CAMU_HAVE_FFMPEG return av_get_pix_fmt_name((enum AVPixelFormat)format); #else switch (format) { case CAMU_PIXEL_FORMAT_RGBA: return "rgba"; case CAMU_PIXEL_FORMAT_RGB24: return "rgb"; case CAMU_PIXEL_FORMAT_GREYA: return "grey + alpha"; case CAMU_PIXEL_FORMAT_GREY: return "grey"; default: return "(unknown)"; } #endif } static inline s32 camu_pixel_format_from_channels(s32 channels) { switch (channels) { case 4: return CAMU_PIXEL_FORMAT_RGBA; case 3: return CAMU_PIXEL_FORMAT_RGB24; case 2: return CAMU_PIXEL_FORMAT_GREYA; case 1: return CAMU_PIXEL_FORMAT_GREY; default: al_assert_and_return(CAMU_PIXEL_FORMAT_NONE); } } static inline void camu_codec_frame_discard(struct camu_codec_frame *frame) { switch (frame->mode) { #ifdef CAMU_HAVE_FFMPEG case CAMU_FFMPEG_COMPAT: av_frame_free(&frame->av.frame); break; #endif case CAMU_NORMAL: if (frame->data) al_free(frame->data); break; } al_free(frame); } static inline s32 camu_guess_format(str *short_name, str *ext, str *mime_type) { if ((ext && al_str_eq(ext, &al_str_c(".png"))) || (short_name && al_str_eq(short_name, &al_str_c("png"))) || (mime_type && al_str_eq(mime_type, &al_str_c("image/png")))) { return CAMU_CODEC_PNG; } else if ((ext && (al_str_eq(ext, &al_str_c(".jpeg")) || al_str_eq(ext, &al_str_c(".jpg")) || al_str_eq(ext, &al_str_c(".jpe")) || al_str_eq(ext, &al_str_c(".jfif")))) || (short_name && al_str_eq(short_name, &al_str_c("jpeg"))) || (mime_type && al_str_eq(mime_type, &al_str_c("image/jpeg")))) { return CAMU_CODEC_JPEG; } #ifdef CAMU_HAVE_FFMPEG char *c_short_name = short_name ? al_str_to_c_str(short_name) : NULL; char *c_ext = ext ? al_str_to_c_str(ext) : NULL; char *c_mime_type = mime_type ? al_str_to_c_str(mime_type) : NULL; // Empty short_name to attempt avoiding image2 probing. const AVOutputFormat *fmt = av_guess_format(c_short_name ? c_short_name : "", c_ext, c_mime_type); if (c_short_name) al_free(c_short_name); if (c_ext) al_free(c_ext); if (c_mime_type) al_free(c_mime_type); // In FFmpeg AV_CODEC_ID_MJPEG also means "image2". if (fmt) return (fmt->audio_codec != AV_CODEC_ID_NONE) ? fmt->audio_codec : fmt->video_codec; #endif return -1; }