cin: Add ogv video support

This commit is contained in:
Denis Pauk 2023-10-27 19:43:41 +03:00
parent 5c49886d7b
commit 258d041b5d
6 changed files with 657 additions and 11 deletions

View file

@ -48,6 +48,9 @@ WITH_CURL:=yes
# installed
WITH_OPENAL:=yes
# Use avcodec for decode ogv videos
WITH_AVCODEC:=yes
# Sets an RPATH to $ORIGIN/lib. It can be used to
# inject custom libraries, e.g. a patches libSDL.so
# or libopenal.so. Not supported on Windows.
@ -442,6 +445,11 @@ ifeq ($(WITH_CURL),yes)
release/yquake2.exe : CFLAGS += -DUSE_CURL
endif
ifeq ($(WITH_AVCODEC),yes)
release/yquake2.exe : CFLAGS += -DAVMEDIADECODE
release/yquake2.exe : LDLIBS += -lavformat -lavcodec -lswscale -lswresample -lavutil
endif
ifeq ($(WITH_OPENAL),yes)
release/yquake2.exe : CFLAGS += -DUSE_OPENAL -DDEFAULT_OPENAL_DRIVER='"openal32.dll"'
endif
@ -471,6 +479,11 @@ ifeq ($(WITH_CURL),yes)
release/quake2 : CFLAGS += -DUSE_CURL
endif
ifeq ($(WITH_AVCODEC),yes)
release/quake2 : CFLAGS += -DAVMEDIADECODE
release/quake2 : LDLIBS += -lavformat -lavcodec -lswscale -lswresample -lavutil
endif
ifeq ($(WITH_OPENAL),yes)
ifeq ($(YQ2_OSTYPE), OpenBSD)
release/quake2 : CFLAGS += -DUSE_OPENAL -DDEFAULT_OPENAL_DRIVER='"libopenal.so"'

View file

@ -299,7 +299,8 @@ The build dependencies can be installed with:
* On Arch Linux based distributions: `pacman -S base-devel mesa openal
curl sdl2`
* On Debian based distributions: `apt install build-essential
libgl1-mesa-dev libsdl2-dev libopenal-dev libcurl4-openssl-dev`
libgl1-mesa-dev libsdl2-dev libopenal-dev libcurl4-openssl-dev
libavformat-dev libvulkan-dev`
* On FreeBSD: `pkg install gmake libGL sdl2 openal-soft curl`
* On NetBSD: `pkgin install gmake SDL2 openal-soft curl`
* On OpenBSD: `pkg_add gmake sdl2 openal curl`

View file

@ -0,0 +1,509 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <stdint.h>
typedef struct cinavdecode
{
AVFormatContext *demuxer;
AVCodecContext **dec_ctx;
AVFrame *dec_frame;
AVPacket *packet;
struct SwsContext *swsctx_image;
SwrContext *swr_audio;
int eof;
/* video */
float fps;
int width, height;
uint8_t *video_res;
long video_buffsize;
long video_pos;
long video_frame_size;
double video_timestamp;
/* audio */
int channels, rate;
uint8_t *audio_res;
long audio_buffsize;
long audio_pos;
long audio_frame_size;
double audio_timestamp;
} cinavdecode_t;
static void
cinavdecode_close(cinavdecode_t *state)
{
if (!state)
{
return;
}
if (state->video_res)
{
free(state->video_res);
}
if (state->audio_res)
{
free(state->audio_res);
}
if (state->packet)
{
av_packet_free(&state->packet);
//av_packet_unref(state->packet);
}
if (state->dec_frame)
{
av_frame_free(&state->dec_frame);
}
if (state->swsctx_image)
{
sws_freeContext(state->swsctx_image);
}
if (state->swr_audio)
{
swr_free(&state->swr_audio);
}
if (state->demuxer)
{
int i;
for (i = 0; i < state->demuxer->nb_streams; i++)
{
if (state->dec_ctx)
{
avcodec_free_context(&state->dec_ctx[i]);
}
}
avformat_close_input(&state->demuxer);
}
av_free(state);
}
/*
* Rewrite to use callback?
*/
static cinavdecode_t *
cinavdecode_open(const char *name)
{
cinavdecode_t *state = NULL;
int ret, i, count;
state = av_calloc(1, sizeof(cinavdecode_t));
memset(state, 0, sizeof(cinavdecode_t));
ret = avformat_open_input(&state->demuxer, name, NULL, NULL);
if (ret < 0)
{
/* can't open file */
cinavdecode_close(state);
return NULL;
}
if ((ret = avformat_find_stream_info(state->demuxer, NULL)) < 0)
{
/* Can't get steam info */
cinavdecode_close(state);
return NULL;
}
state->dec_ctx = av_calloc(state->demuxer->nb_streams, sizeof(AVCodecContext *));
if (!state->dec_ctx)
{
/* can't allocate streams */
cinavdecode_close(state);
return NULL;
}
for (i = 0; i < state->demuxer->nb_streams; i++)
{
AVStream *stream = state->demuxer->streams[i];
const AVCodec *dec = avcodec_find_decoder(stream->codecpar->codec_id);
AVCodecContext *codec_ctx;
if (!dec)
{
/* Has no decoder */
cinavdecode_close(state);
return NULL;
}
codec_ctx = avcodec_alloc_context3(dec);
if (!codec_ctx)
{
/* Failed alloc context */
cinavdecode_close(state);
return NULL;
}
ret = avcodec_parameters_to_context(codec_ctx, stream->codecpar);
if (ret < 0) {
/* Set parameters to contex */
cinavdecode_close(state);
return NULL;
}
/* Inform the decoder about the timebase for the packet timestamps.
* This is highly recommended, but not mandatory. */
codec_ctx->pkt_timebase = stream->time_base;
/* Reencode video & audio and remux subtitles etc. */
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO ||
codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO)
{
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
{
codec_ctx->framerate = av_guess_frame_rate(
state->demuxer, stream, NULL);
state->fps = (float)codec_ctx->framerate.num / codec_ctx->framerate.den;
state->width = codec_ctx->width;
state->height = codec_ctx->height;
printf("frame per second: %f\n", state->fps);
printf("format: %d size: %dx%d\n",
codec_ctx->pix_fmt, state->width, state->height);
state->swsctx_image = sws_getContext(
codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
codec_ctx->width, codec_ctx->height, AV_PIX_FMT_RGBA, 0, 0, 0, 0);
}
else
{
state->channels = codec_ctx->ch_layout.nb_channels;
state->rate = codec_ctx->sample_rate;
printf("channels: %d, rate; %d\n", state->channels, state->rate);
printf("Audio format: ");
switch (codec_ctx->sample_fmt)
{
case AV_SAMPLE_FMT_NONE: printf("None\n"); break;
case AV_SAMPLE_FMT_U8: printf("unsigned 8 bits\n"); break;
case AV_SAMPLE_FMT_S16: printf("signed 16 bits\n"); break;
case AV_SAMPLE_FMT_S32: printf("signed 32 bits\n"); break;
case AV_SAMPLE_FMT_FLT: printf("float\n"); break;
case AV_SAMPLE_FMT_DBL: printf("double\n"); break;
case AV_SAMPLE_FMT_U8P: printf("unsigned 8 bits, planar\n"); break;
case AV_SAMPLE_FMT_S16P: printf("signed 16 bits, planar\n"); break;
case AV_SAMPLE_FMT_S32P: printf("signed 32 bits, planar\n"); break;
case AV_SAMPLE_FMT_FLTP: printf("float, planar\n"); break;
case AV_SAMPLE_FMT_DBLP: printf("double, planar\n"); break;
case AV_SAMPLE_FMT_S64: printf("signed 64 bits\n"); break;
case AV_SAMPLE_FMT_S64P: printf("signed 64 bits, planar\n"); break;
default: printf("unknow\n"); break;
}
printf("rate: %d\n", codec_ctx->sample_rate);
ret = swr_alloc_set_opts2(&state->swr_audio,
&codec_ctx->ch_layout, AV_SAMPLE_FMT_S16, codec_ctx->sample_rate,
&codec_ctx->ch_layout, codec_ctx->sample_fmt, codec_ctx->sample_rate,
0, NULL);
if (ret < 0)
{
/* Can't open format sound convert */
cinavdecode_close(state);
return NULL;
}
ret = swr_init(state->swr_audio);
if (ret < 0)
{
/* Can't open format sound convert */
cinavdecode_close(state);
return NULL;
}
}
/* Open decoder */
ret = avcodec_open2(codec_ctx, dec, NULL);
if (ret < 0)
{
/* Can't open decoder */
cinavdecode_close(state);
return NULL;
}
state->dec_ctx[i] = codec_ctx;
}
}
av_dump_format(state->demuxer, 0, name, 0);
state->dec_frame = av_frame_alloc();
if (!state->dec_frame)
{
/* Can't allocate frame */
cinavdecode_close(state);
return NULL;
}
state->packet = av_packet_alloc();
if (!state->packet)
{
/* Can't allocate frame */
cinavdecode_close(state);
return NULL;
}
if (state->fps < 1 || state->rate < 5500)
{
/* too rare update */
cinavdecode_close(state);
return NULL;
}
/* Fix here if audio not in sync */
count = state->rate * state->channels / state->fps;
/* round up to channels and width */
count = (count + (state->channels) - 1) & (~(state->channels) - 1);
state->audio_frame_size = count * 2;
/* buffer 1 second of sound */
state->audio_buffsize = state->audio_frame_size * (state->fps + 1);
state->audio_res = malloc(state->audio_buffsize);
/* buffer half second of video */
state->video_frame_size = 4 * state->width * state->height;
state->video_buffsize = state->video_frame_size * (state->fps / 2 + 1);
state->video_res = malloc(state->video_buffsize);
return state;
}
static int
next_frame_ready(const cinavdecode_t *state)
{
if (state->video_pos < state->video_frame_size)
{
/* need more frames */
return 0;
}
if (state->audio_pos < state->audio_frame_size)
{
/* need more audio */
return 0;
}
return 1;
}
/*
* Caller should alloacate buffer enough for single frame
*/
static int
cinavdecode_next_frame(cinavdecode_t *state, uint8_t *video, uint8_t *audio)
{
/* read all packets */
while (!next_frame_ready(state) && !state->eof)
{
int stream_index, ret;
if ((ret = av_read_frame(state->demuxer, state->packet)) < 0)
{
state->eof = 1;
break;
}
stream_index = state->packet->stream_index;
/* send the packet with the compressed data to the decoder */
ret = avcodec_send_packet(state->dec_ctx[stream_index], state->packet);
if (ret < 0)
{
state->eof = 1;
/* can't send packet for decode */
break;
}
/* read all the output frames (in general there may be any number of them */
while (ret >= 0)
{
ret = avcodec_receive_frame(state->dec_ctx[stream_index], state->dec_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
/* End of package */
break;
}
else if (ret < 0)
{
state->eof = 1;
/* Decode error */
break;
}
if (state->dec_ctx[stream_index]->codec_type == AVMEDIA_TYPE_AUDIO)
{
uint8_t *out[1];
int ret, required_space;
state->audio_timestamp = (
(double)state->dec_frame->pts *
state->dec_ctx[stream_index]->pkt_timebase.num /
state->dec_ctx[stream_index]->pkt_timebase.den
);
required_space = state->dec_frame->nb_samples * 2 * state->channels;
if ((state->audio_pos + required_space) > state->audio_buffsize)
{
uint8_t *new_buffer;
/* add current frame size */
state->audio_buffsize += required_space;
/* double buffer */
state->audio_buffsize *= 2;
/* realloacate buffer */
new_buffer = realloc(state->audio_res, state->audio_buffsize);
if (!new_buffer)
{
/* Memalloc error */
state->eof = 1;
break;
}
state->audio_res = new_buffer;
printf("new audio buffer %.2f:%.2f\n",
(float)state->audio_pos / state->audio_frame_size,
(float)state->audio_buffsize / state->audio_frame_size);
}
out[0] = state->audio_res + state->audio_pos;
ret = swr_convert(state->swr_audio, out, state->dec_frame->nb_samples,
(const uint8_t **)state->dec_frame->data, state->dec_frame->nb_samples);
if (ret < 0)
{
/* Decode error */
state->eof = 1;
break;
}
/*
printf("Audio %.2f: samples: %d, channels %d --> %d\n",
(float)state->audio_pos / state->audio_frame_size,
state->dec_frame->nb_samples,
state->dec_ctx[stream_index]->ch_layout.nb_channels, ret);
*/
/* move current pos */
state->audio_pos += required_space;
}
else if (state->dec_ctx[stream_index]->codec_type == AVMEDIA_TYPE_VIDEO)
{
uint8_t *out[1];
int linesize[1], ret, required_space;
state->video_timestamp = (
(double)state->dec_frame->pts *
state->dec_ctx[stream_index]->pkt_timebase.num /
state->dec_ctx[stream_index]->pkt_timebase.den
);
required_space = state->video_frame_size;
if ((state->video_pos + required_space) > state->video_buffsize)
{
uint8_t *new_buffer;
/* add current frame size */
state->video_buffsize += required_space;
/* double buffer */
state->video_buffsize *= 2;
/* realloacate buffer */
new_buffer = realloc(state->video_res, state->video_buffsize);
if (!new_buffer)
{
/* Memalloc error */
state->eof = 1;
break;
}
state->video_res = new_buffer;
printf("new video buffer %.2f:%.2f\n",
(float)state->video_pos / state->video_frame_size,
(float)state->video_buffsize / state->video_frame_size);
}
/* RGB stride */
linesize[0] = 4 * state->dec_frame->width;
/* RGB24 have one plane */
out[0] = state->video_res + state->video_pos;
ret = sws_scale(state->swsctx_image,
(const uint8_t * const*)state->dec_frame->data,
state->dec_frame->linesize, 0,
state->dec_frame->height, out, linesize);
if (ret < 0)
{
/* Decode error */
state->eof = 1;
break;
}
/*
printf("video %.2f: %p, stride: %d, width: %d, height: %d, format: %d\n",
(float)state->video_pos / state->video_frame_size,
state->dec_frame->data[0], state->dec_frame->linesize[0],
state->dec_frame->width, state->dec_frame->height,
state->dec_frame->format);
*/
state->video_pos += required_space;
}
}
av_packet_unref(state->packet);
/* use 2.5 second of audio buffering and 1.5 video buffer for async in
* media stream */
if ((state->audio_pos / state->audio_frame_size) > (state->fps * 2.5)||
(state->video_pos / state->video_frame_size) > (state->fps * 1.5))
{
/* flush buffer if more than 1.5 second async in video stream */
break;
}
};
/*printf("Audio %.2f (%.2f): Video %.2f (%.2f)\n",
(float)state->audio_pos / state->audio_frame_size,
state->audio_timestamp,
(float)state->video_pos / state->video_frame_size,
state->video_timestamp);*/
memcpy(audio, state->audio_res, state->audio_frame_size);
if (state->audio_pos >= state->audio_frame_size)
{
memmove(state->audio_res, state->audio_res + state->audio_frame_size,
state->audio_pos - state->audio_frame_size);
}
state->audio_pos -= state->audio_frame_size;
if (state->audio_pos < 0)
{
state->audio_pos = 0;
}
memcpy(video, state->video_res, state->video_frame_size);
if (state->video_pos >= state->video_frame_size)
{
memmove(state->video_res, state->video_res + state->video_frame_size,
state->video_pos - state->video_frame_size);
}
state->video_pos -= state->video_frame_size;
if (state->video_pos < 0)
{
state->video_pos = 0;
}
if (!state->eof || state->video_pos || state->audio_pos)
{
return 1;
}
else
{
/* no frames any more */
return -1;
}
}

View file

@ -31,6 +31,10 @@
#include "input/header/input.h"
#include "cinema/smacker.h"
#ifdef AVMEDIADECODE
#include "cinema/avdecode.h"
#endif
#define PL_MPEG_IMPLEMENTATION
#include "cinema/pl_mpeg.h"
@ -62,6 +66,7 @@ typedef struct
typedef enum
{
video_cin,
video_av,
video_smk,
video_mpg
} cinema_t;
@ -101,6 +106,11 @@ typedef struct
/* mpg video */
plm_t *plm_video;
#ifdef AVMEDIADECODE
/* ffmpeg avideo */
cinavdecode_t *av_video;
#endif
} cinematics_t;
cinematics_t cin;
@ -209,6 +219,12 @@ SCR_StopCinematic(void)
{
cl.cinematictime = 0; /* done */
if (cin.av_video)
{
cinavdecode_close(cin.av_video);
cin.av_video = NULL;
}
if (cin.smk_video)
{
smk_close(cin.smk_video);
@ -627,6 +643,74 @@ SCR_ReadNextFrame(void)
return pic;
}
#ifdef AVMEDIADECODE
static byte *
SCR_ReadNextAVFrame(void)
{
size_t count, i;
byte *buffer;
count = cin.height * cin.width * cin.color_bits / 8;
buffer = Z_Malloc(count);
if (cinavdecode_next_frame(cin.av_video, buffer, cin.audio_buf) < 0)
{
Z_Free(buffer);
return NULL;
}
/* force untransparent image show */
for (i=0; i < count; i += 4)
{
buffer[i + 3] = 255;
}
if (cin.s_channels > 0)
{
S_RawSamples(cin.av_video->audio_frame_size / (cin.s_width * cin.s_channels),
cin.s_rate, cin.s_width, cin.s_channels,
cin.audio_buf, Cvar_VariableValue("s_volume"));
}
cl.cinematicframe++;
return buffer;
}
static qboolean
SCR_LoadAVcodec(const char *arg, const char *dot)
{
char name[MAX_OSPATH], *path = NULL;
while (1)
{
path = FS_NextPath(path);
if (!path)
{
printf("can't decode or open %s\n", path);
break;
}
Com_sprintf(name, sizeof(name), "%s/video/%s", path, arg);
cin.av_video = cinavdecode_open(name);
if (cin.av_video)
{
break;
}
}
if (!cin.av_video)
{
/* Can't open file */
return false;
}
return true;
}
#endif
void
SCR_RunCinematic(void)
{
@ -669,7 +753,6 @@ SCR_RunCinematic(void)
}
cin.pic = cin.pic_pending;
cin.pic_pending = NULL;
switch (cin.video_type)
{
case video_cin:
@ -681,6 +764,13 @@ SCR_RunCinematic(void)
case video_mpg:
cin.pic_pending = SCR_ReadNextMPGFrame();
break;
#ifdef AVMEDIADECODE
case video_av:
cin.pic_pending = SCR_ReadNextAVFrame();
break;
#endif
default:
cin.pic_pending = NULL;
}
if (!cin.pic_pending)
@ -1046,6 +1136,39 @@ SCR_PlayCinematic(char *arg)
return;
}
#ifdef AVMEDIADECODE
if (dot && !strcmp(dot, ".ogv"))
{
if (!SCR_LoadAVcodec(arg, dot))
{
cin.av_video = NULL;
cl.cinematictime = 0; /* done */
return;
}
SCR_EndLoadingPlaque();
cin.color_bits = 32;
cls.state = ca_active;
cin.s_rate = cin.av_video->rate;
cin.s_width = 2;
cin.s_channels = cin.av_video->channels;
cin.audio_buf = Z_Malloc(cin.av_video->audio_frame_size);
cin.width = cin.av_video->width;
cin.height = cin.av_video->height;
cin.fps = cin.av_video->fps;
cl.cinematicframe = 0;
cin.pic = SCR_ReadNextAVFrame();
cl.cinematictime = Sys_Milliseconds();
cin.video_type = video_av;
return;
}
#endif
Com_sprintf(name, sizeof(name), "video/%s", arg);
FS_FOpenFile(name, &cl.cinematic_file, false);
@ -1082,4 +1205,3 @@ SCR_PlayCinematic(char *arg)
cin.video_type = video_cin;
}

View file

@ -1250,7 +1250,7 @@ Mod_LoadFileWithoutExt(const char *namewe, void **buffer, const char* ext)
!strcmp(ext, "md2") ||
!strcmp(ext, "mdl"))
{
int filesize, len;
int filesize;
/* Check Heretic2 model */
Q_strlcpy(newname, namewe, sizeof(newname));

View file

@ -545,6 +545,7 @@ SV_Map(qboolean attractloop, char *levelstring, qboolean loadgame, qboolean isau
}
if ((l > 4) && (!strcmp(level + l - 4, ".cin") ||
!strcmp(level + l - 4, ".ogv") ||
!strcmp(level + l - 4, ".mpg") ||
!strcmp(level + l - 4, ".smk")))
{