From 1884f0a3cecf6b55d515889552d436d4a33b22f4 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 7 May 2023 04:10:18 +0300 Subject: [PATCH 01/14] engine: client: avi: add new macro XASH_AVI and corresponding backend select macros (AVI_NULL and AVI_FFMPEG) --- common/backends.h | 4 ++++ common/defaults.h | 12 +++++++++++- engine/client/avi/avi.h | 2 +- engine/client/avi/avi_stub.c | 6 +++--- engine/client/avi/avi_win.c | 6 +++--- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/common/backends.h b/common/backends.h index 6fd51f19f6..52f7b7e8a4 100644 --- a/common/backends.h +++ b/common/backends.h @@ -50,4 +50,8 @@ GNU General Public License for more details. #define LIB_WIN32 2 #define LIB_STATIC 3 +// movies (XASH_AVI) +#define AVI_NULL 0 +#define AVI_FFMPEG 1 + #endif /* BACKENDS_H */ diff --git a/common/defaults.h b/common/defaults.h index 3267a950ca..e06fdc7635 100644 --- a/common/defaults.h +++ b/common/defaults.h @@ -78,7 +78,6 @@ SETUP BACKENDS DEFINITIONS // usually only 10-20 fds availiable #define XASH_REDUCE_FD #endif - #endif // XASH_DEDICATED // @@ -105,6 +104,17 @@ SETUP BACKENDS DEFINITIONS #endif // !XASH_WIN32 #endif +// +// determine movie playback backend +// +#ifndef XASH_AVI + #if HAVE_FFMPEG + #define XASH_AVI AVI_FFMPEG + #else + #define XASH_AVI AVI_NULL + #endif +#endif + #ifdef XASH_STATIC_LIBS #define XASH_LIB LIB_STATIC #define XASH_INTERNAL_GAMELIBS diff --git a/engine/client/avi/avi.h b/engine/client/avi/avi.h index 20fb045073..6bc1c7ad00 100644 --- a/engine/client/avi/avi.h +++ b/engine/client/avi/avi.h @@ -18,7 +18,7 @@ GNU General Public License for more details. // // avikit.c // -typedef struct movie_state_s movie_state_t; +typedef struct movie_state_s movie_state_t; int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ); byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame ); qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ); diff --git a/engine/client/avi/avi_stub.c b/engine/client/avi/avi_stub.c index 0c6504d2ee..a4ff7fb03a 100644 --- a/engine/client/avi/avi_stub.c +++ b/engine/client/avi/avi_stub.c @@ -13,8 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#include "build.h" -#if !XASH_WIN32 +#include "defaults.h" +#if XASH_AVI == AVI_NULL #include "common.h" int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) @@ -87,4 +87,4 @@ void AVI_Shutdown( void ) ; } -#endif // WIN32 +#endif // XASH_AVI == AVI_NULL diff --git a/engine/client/avi/avi_win.c b/engine/client/avi/avi_win.c index 128fe62ffd..853707125b 100644 --- a/engine/client/avi/avi_win.c +++ b/engine/client/avi/avi_win.c @@ -14,8 +14,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#include "build.h" -#if XASH_WIN32 +#include "defaults.h" +#if XASH_AVI == AVI_WIN32 #include "common.h" #include "client.h" #include // video for windows @@ -732,4 +732,4 @@ void AVI_Shutdown( void ) Sys_FreeLibrary( &msacm_dll ); avi_initialized = false; } -#endif // _WIN32 +#endif // XASH_AVI == AVI_WIN32 From 4275829bd5568ddc0384f967d9515c7a52fbde4f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 14 May 2023 08:11:16 +0300 Subject: [PATCH 02/14] engine: client: partially rework how AVI playback works in Xash. Add ffmpeg-based player. This intentionally will not build without --enable-ffmpeg for now. We add new functions to the RenderAPI called AVI_Think and AVI_SetParm. Game developer is supposed to call AVI_Think each frame and set video playback parameters through AVI_SetParm function. What's tested: * Intro cinematics. * Looping videos. * Playing back an hour long video. * API extension (PrimeXT's avi branch at https://github.com/a1batross/PrimeXT). What's broken: * There is no way to seek a video file, only rewind to the start. --- common/render_api.h | 19 +- engine/client/avi/avi.h | 3 + engine/client/avi/avi_ffmpeg.c | 692 +++++++++++++++++++++++++++++++++ engine/client/avi/avi_stub.c | 2 + engine/client/cl_cmds.c | 3 +- engine/client/cl_gameui.c | 39 +- engine/client/cl_render.c | 4 +- engine/client/cl_video.c | 53 +-- engine/wscript | 41 ++ 9 files changed, 792 insertions(+), 64 deletions(-) create mode 100644 engine/client/avi/avi_ffmpeg.c diff --git a/common/render_api.h b/common/render_api.h index 8ab2643ce5..4dd8896246 100644 --- a/common/render_api.h +++ b/common/render_api.h @@ -157,6 +157,21 @@ typedef struct decallist_s modelstate_t studio_state; // studio decals only } decallist_t; +enum movie_parms_e +{ + AVI_PARM_LAST = 0, // marker for SetParm to end parse parsing arguments + AVI_RENDER_TEXNUM, // (int) sets texture to draw into, if 0 will draw to screen + AVI_RENDER_X, // (int) when set to screen, sets position where to draw + AVI_RENDER_Y, + AVI_RENDER_W, // (int) sets texture or screen width + AVI_RENDER_H, // set to -1 to draw full screen + AVI_REWIND, // no argument, rewind playback to the beginning + AVI_ENTNUM, // (int) entity number, -1 for no spatialization + AVI_VOLUME, // (int) volume from 0 to 255 + AVI_ATTN, // (float) attenuation value +}; + +struct movie_state_s; struct ref_viewpass_s; typedef struct render_api_s @@ -201,8 +216,8 @@ typedef struct render_api_s void (*AVI_FreeVideo)( void *Avi ); int (*AVI_IsActive)( void *Avi ); void (*AVI_StreamSound)( void *Avi, int entnum, float fvol, float attn, float synctime ); - void (*AVI_Reserved0)( void ); // for potential interface expansion without broken compatibility - void (*AVI_Reserved1)( void ); + qboolean (*AVI_Think)( struct movie_state_s *Avi ); + qboolean (*AVI_SetParm)( struct movie_state_s *Avi, enum movie_parms_e parm, ... ); // glState related calls (must use this instead of normal gl-calls to prevent de-synchornize local states between engine and the client) void (*GL_Bind)( int tmu, unsigned int texnum ); diff --git a/engine/client/avi/avi.h b/engine/client/avi/avi.h index 6bc1c7ad00..9adc504abe 100644 --- a/engine/client/avi/avi.h +++ b/engine/client/avi/avi.h @@ -34,4 +34,7 @@ movie_state_t *AVI_GetState( int num ); qboolean AVI_Initailize( void ); void AVI_Shutdown( void ); +qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ); +qboolean AVI_Think( movie_state_t *Avi ); + #endif // AVI_H diff --git a/engine/client/avi/avi_ffmpeg.c b/engine/client/avi/avi_ffmpeg.c new file mode 100644 index 0000000000..e5ea932914 --- /dev/null +++ b/engine/client/avi/avi_ffmpeg.c @@ -0,0 +1,692 @@ +/* +avi_ffmpreg.c - playing AVI files (ffmpeg backend) +Copyright (C) 2023 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "defaults.h" +#if XASH_AVI == AVI_FFMPEG +#include "common.h" +#include "client.h" +#include +#include +#include +#include +#include + +struct movie_state_s +{ + // ffmpeg contexts + AVFormatContext *fmt_ctx; + AVCodecContext *video_ctx; + AVCodecContext *audio_ctx; + struct SwsContext *sws_ctx; + struct SwrContext *swr_ctx; + + AVPacket *pkt; + AVFrame *aframe; + AVFrame *vframe; + AVFrame *vframe_copy; + + int64_t first_time; + int64_t last_time; + + // video stream + byte *dst; + double duration; + int video_stream; + int xres; + int yres; + int dst_linesize; + enum AVPixelFormat pix_fmt; + + // rendering video parameters + int x, y, w, h; // passed to R_DrawStretchRaw + int texture; // passed to R_UploadStretchRaw + + // audio stream + int audio_stream; + int channels; + int rate; + enum AVSampleFormat s_fmt; + + byte *cached_audio; + size_t cached_audio_buf_len; // absolute size of cached_audio array + size_t cached_audio_len; // how many data in bytes we have in cached_audio array + size_t cached_audio_pos; // how far we've read into cached_audio array + + // rendering audio parameters + float attn; + int16_t entnum; // MAX_ENTITY_BITS is 13 + byte volume; + byte active : 1; + byte quiet : 1; +}; + +static qboolean avi_initialized; +static poolhandle_t avi_mempool; +static movie_state_t avi[2]; + +qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ) +{ + qboolean ret = true; + va_list va; + va_start( va, parm ); + + while( parm != AVI_PARM_LAST ) + { + float fval; + int val; + + switch( parm ) + { + case AVI_RENDER_TEXNUM: + Avi->texture = va_arg( va, int ); + break; + case AVI_RENDER_X: + Avi->x = va_arg( va, int ); + break; + case AVI_RENDER_Y: + Avi->y = va_arg( va, int ); + break; + case AVI_RENDER_W: + Avi->w = va_arg( va, int ); + break; + case AVI_RENDER_H: + Avi->h = va_arg( va, int ); + break; + case AVI_REWIND: + if( Avi->audio_ctx ) + avcodec_flush_buffers( Avi->audio_ctx ); + avcodec_flush_buffers( Avi->video_ctx ); + Avi->cached_audio_len = Avi->cached_audio_pos = 0; + Avi->last_time = -1; + Avi->first_time = 0; + av_seek_frame( Avi->fmt_ctx, -1, 0, AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD ); + break; + case AVI_ENTNUM: + val = va_arg( va, int ); + Avi->entnum = bound( 0, val, MAX_EDICTS ); + break; + case AVI_VOLUME: + val = va_arg( va, int ); + Avi->volume = bound( 0, val, 255 ); + break; + case AVI_ATTN: + fval = va_arg( va, double ); + Avi->attn = Q_max( 0.0f, fval ); + break; + default: + ret = false; + } + + parm = va_arg( va, enum movie_parms_e ); + } + + va_end( va ); + + return ret; +} + +static void AVI_SpewError( qboolean quiet, const char *fmt, ... ) FORMAT_CHECK( 2 ); +static void AVI_SpewError( qboolean quiet, const char *fmt, ... ) +{ + char buf[MAX_VA_STRING]; + va_list va; + + if( quiet ) + return; + + va_start( va, fmt ); + Q_vsnprintf( buf, sizeof( buf ), fmt, va ); + va_end( va ); + + Con_Printf( S_ERROR "%s", buf ); +} + +static void AVI_SpewAvError( qboolean quiet, const char *func, int numerr ) +{ + if( !quiet ) + Con_Printf( S_ERROR "%s: %s (%d)\n", func, av_err2str( numerr ), numerr ); +} + +static int AVI_OpenCodecContext( AVCodecContext **dst_dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type, qboolean quiet ) +{ + const AVCodec *dec; + AVCodecContext *dec_ctx; + AVStream *st; + int idx, ret; + + if(( ret = av_find_best_stream( fmt_ctx, type, -1, -1, NULL, 0 )) < 0 ) + { + AVI_SpewAvError( quiet, "av_find_best_stream", ret ); + return ret; + } + + idx = ret; + st = fmt_ctx->streams[idx]; + + if( !( dec = avcodec_find_decoder( st->codecpar->codec_id ))) + { + AVI_SpewError( quiet, S_ERROR "Failed to find %s codec\n", av_get_media_type_string( type )); + return AVERROR( EINVAL ); + } + + if( !( dec_ctx = avcodec_alloc_context3( dec ))) + { + AVI_SpewError( quiet, S_ERROR "Failed to allocate %s codec context", dec->name ); + return AVERROR( ENOMEM ); + } + + if(( ret = avcodec_parameters_to_context( dec_ctx, st->codecpar )) < 0 ) + { + AVI_SpewAvError( quiet, "avcodec_parameters_to_context", ret ); + avcodec_free_context( &dec_ctx ); + return ret; + } + + dec_ctx->pkt_timebase = st->time_base; + + if(( ret = avcodec_open2( dec_ctx, dec, NULL )) < 0 ) + { + AVI_SpewAvError( quiet, "avcodec_open2", ret ); + + avcodec_free_context( &dec_ctx ); + return ret; + } + + *dst_dec_ctx = dec_ctx; + return idx; // always positive +} + +int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) +{ + return 0; +} + +int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) +{ + return 0; +} + +qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ) +{ + if( !Avi->active ) + return false; + + if( xres ) + *xres = Avi->xres; + + if( yres ) + *yres = Avi->yres; + + if( duration ) + *duration = Avi->duration; + + return true; +} + +qboolean AVI_GetAudioInfo( movie_state_t *Avi, wavdata_t *snd_info ) +{ + if( !Avi->active || Avi->audio_stream < 0 ) + return false; + + snd_info->rate = Avi->rate; + snd_info->channels = Avi->channels; + snd_info->width = av_get_bytes_per_sample( Avi->s_fmt ); + snd_info->size = (size_t)snd_info->rate * snd_info->width * snd_info->channels; + snd_info->loopStart = 0; + + return true; +} + +// just let it compile, bruh! +byte *AVI_GetVideoFrame( movie_state_t *Avi, int target ) +{ + return Avi->dst; +} + +int AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, int offset, int length ) +{ + return 0; +} + +static void AVI_StreamAudio( movie_state_t *Avi ) +{ + int buffer_samples, file_samples, file_bytes; + rawchan_t *ch = NULL; + + // keep the same semantics, when S_RAW_SOUND_SOUNDTRACK doesn't play if S_SetStream wasn't enabled + qboolean disable_stream = Avi->entnum == S_RAW_SOUND_SOUNDTRACK ? !s_listener.streaming : false; + + if( !dma.initialized || disable_stream || s_listener.paused || !Avi->cached_audio ) + return; + + ch = S_FindRawChannel( Avi->entnum, true ); + + if( !ch ) + return; + + ch->master_vol = Avi->volume; + ch->dist_mult = (Avi->attn / SND_CLIP_DISTANCE); + + if( ch->s_rawend < soundtime ) + ch->s_rawend = soundtime; + + while( ch->s_rawend < soundtime + ch->max_samples ) + { + size_t copy; + + buffer_samples = ch->max_samples - (ch->s_rawend - soundtime); + + file_samples = buffer_samples * ((float)Avi->rate / SOUND_DMA_SPEED); + if( file_samples <= 1 ) return; // no more samples need + + file_bytes = file_samples * av_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels; + + if( file_bytes > ch->max_samples ) + { + file_bytes = ch->max_samples; + file_samples = file_bytes / ( av_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels ); + } + + copy = Q_min( file_bytes, Q_max( Avi->cached_audio_len - Avi->cached_audio_pos, 0 )); + + if( !copy ) + break; + + if( file_bytes > copy ) + { + file_bytes = copy; + file_samples = file_bytes / ( av_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels ); + } + + ch->s_rawend = S_RawSamplesStereo( ch->rawsamples, ch->s_rawend, ch->max_samples, file_samples, Avi->rate, av_get_bytes_per_sample( Avi->s_fmt ), Avi->channels, Avi->cached_audio + Avi->cached_audio_pos ); + Avi->cached_audio_pos += copy; + } +} + +static void AVI_HandleAudio( movie_state_t *Avi, const AVFrame *frame ) +{ + int samples = frame->nb_samples; + size_t len = samples * av_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels; + int outsamples; + uint8_t *ptr; + + // allocate data + if( !Avi->cached_audio ) + { + Avi->cached_audio_buf_len = len; + Avi->cached_audio_pos = 0; + Avi->cached_audio_len = 0; + Avi->cached_audio = Mem_Malloc( avi_mempool, len ); + } + else + { + if( Avi->cached_audio_pos ) + { + // Con_Printf( "%s: erasing old data of size %d\n", __func__, Avi->cached_audio_pos ); + Avi->cached_audio_len -= Avi->cached_audio_pos; + memmove( Avi->cached_audio, Avi->cached_audio + Avi->cached_audio_pos, Avi->cached_audio_len ); + Avi->cached_audio_pos = 0; + } + + if( len + Avi->cached_audio_len > Avi->cached_audio_buf_len ) + { + // Con_Printf( "%s: resizing old buffer of size %d to size %d\n", __func__, Avi->cached_audio_buf_len, len + Avi->cached_audio_buf_len ); + Avi->cached_audio_buf_len = len + Avi->cached_audio_len; + Avi->cached_audio = Mem_Realloc( avi_mempool, Avi->cached_audio, Avi->cached_audio_buf_len ); + } + } + + ptr = Avi->cached_audio + Avi->cached_audio_len; + outsamples = swr_convert( Avi->swr_ctx, &ptr, samples, (void *)frame->data, samples ); + Avi->cached_audio_len += outsamples * av_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels; + + // Con_Printf( "%s: got audio chunk of size %d samples\n", __func__, outsamples ); +} + +qboolean AVI_Think( movie_state_t *Avi ) +{ + qboolean decoded = false; + qboolean flushing = false; + qboolean redraw = false; + const double timebase = (double)Avi->video_ctx->pkt_timebase.den / Avi->video_ctx->pkt_timebase.num; + int64_t curtime = round( Platform_DoubleTime() * timebase ); + + if( !Avi->first_time ) // always remember at which timestamp we started playing + Avi->first_time = curtime; + + Con_NPrintf( 1, "cached_audio_buf_len = %zu", Avi->cached_audio_buf_len ); + + while( 1 ) // try to get multiple decoded frames to keep up when we're running at low fps + { + int res; + + AVI_StreamAudio( Avi ); // always flush audio buffers + + // recalc time so we always play last possible frame + curtime = round( Platform_DoubleTime() * timebase ); + + if( Avi->last_time > curtime ) + break; + + if(( res = av_read_frame( Avi->fmt_ctx, Avi->pkt )) >= 0 ) + { + if( Avi->pkt->stream_index == Avi->audio_stream ) + { + res = avcodec_send_packet( Avi->audio_ctx, Avi->pkt ); + if( res < 0 ) + AVI_SpewAvError( Avi->quiet, "avcodec_send_packet (audio)", res ); + } + else if( Avi->pkt->stream_index == Avi->video_stream ) + { + res = avcodec_send_packet( Avi->video_ctx, Avi->pkt ); + if( res < 0 ) + AVI_SpewAvError( Avi->quiet, "avcodec_send_packet (audio)", res ); + } + av_packet_unref( Avi->pkt ); + } + else + { + if( res != AVERROR_EOF ) + AVI_SpewAvError( Avi->quiet, "av_read_frame", res ); + + if( Avi->audio_ctx ) + avcodec_flush_buffers( Avi->audio_ctx ); + + avcodec_flush_buffers( Avi->video_ctx ); + flushing = true; + break; + } + + if( Avi->audio_ctx ) + { + while( avcodec_receive_frame( Avi->audio_ctx, Avi->aframe ) == 0 ) + { + AVI_HandleAudio( Avi, Avi->aframe ); + decoded = true; + } + } + + while( avcodec_receive_frame( Avi->video_ctx, Avi->vframe ) == 0 ) + { + Avi->last_time = Avi->first_time + Avi->vframe->best_effort_timestamp; + decoded = true; + + if( FBitSet( Avi->vframe->flags, AV_FRAME_FLAG_CORRUPT|AV_FRAME_FLAG_DISCARD )) + continue; + + if( Avi->vframe->decode_error_flags != 0 ) + continue; + + av_frame_unref( Avi->vframe_copy ); + if( av_frame_ref( Avi->vframe_copy, Avi->vframe ) == 0 ) + redraw = true; + } + } + + if( redraw ) + { + sws_scale( Avi->sws_ctx, (void*)Avi->vframe_copy->data, Avi->vframe_copy->linesize, 0, Avi->video_ctx->height, + &Avi->dst, &Avi->dst_linesize ); + av_frame_unref( Avi->vframe_copy ); + } + + if( Avi->texture == 0 ) + { + int w = Avi->w >= 0 ? Avi->w : refState.width; + int h = Avi->h >= 0 ? Avi->h : refState.height; + + ref.dllFuncs.R_DrawStretchRaw( Avi->x, Avi->y, w, h, Avi->xres, Avi->yres, Avi->dst, redraw ); + } + else if( redraw && Avi->texture > 0 ) + ref.dllFuncs.AVI_UploadRawFrame( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst ); + + if( flushing && !decoded ) + return false; // probably hit an EOF + + return true; +} + +void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) +{ + byte *dst[4]; + int dst_linesize[4]; + int ret; + + if( Avi->active ) + AVI_CloseVideo( Avi ); + + if( !filename || !avi_initialized ) + return; + + Avi->active = false; + Avi->quiet = quiet; + Avi->video_ctx = Avi->audio_ctx = NULL; + Avi->fmt_ctx = NULL; + + if(( ret = avformat_open_input( &Avi->fmt_ctx, filename, NULL, NULL )) < 0 ) + { + AVI_SpewAvError( quiet, "avformat_open_input", ret ); + return; + } + + if(( ret = avformat_find_stream_info( Avi->fmt_ctx, NULL )) < 0 ) + { + AVI_SpewAvError( quiet, "avformat_find_stream_info", ret ); + return; + } + + if( !( Avi->pkt = av_packet_alloc( ))) + { + AVI_SpewAvError( quiet, "av_packet_alloc", 0 ); + return; + } + + if( !( Avi->vframe = av_frame_alloc( ))) + { + AVI_SpewAvError( quiet, "av_frame_alloc (video)", 0 ); + return; + } + + if( !( Avi->vframe_copy = av_frame_alloc( ))) + { + AVI_SpewAvError( quiet, "av_frame_alloc (video)", 0 ); + return; + } + + + Avi->video_stream = AVI_OpenCodecContext( &Avi->video_ctx, Avi->fmt_ctx, AVMEDIA_TYPE_VIDEO, quiet ); + + if( Avi->video_stream < 0 ) + return; + + Avi->xres = Avi->video_ctx->width; + Avi->yres = Avi->video_ctx->height; + Avi->pix_fmt = Avi->video_ctx->pix_fmt; + Avi->duration = Avi->fmt_ctx->duration / (double)AV_TIME_BASE; + Avi->entnum = S_RAW_SOUND_SOUNDTRACK; + Avi->attn = ATTN_NONE; + Avi->volume = 255; + + if( !( Avi->sws_ctx = sws_getContext( Avi->xres, Avi->yres, Avi->pix_fmt, + Avi->xres, Avi->yres, AV_PIX_FMT_BGR0, SWS_POINT, NULL, NULL, NULL ))) + { + AVI_SpewAvError( quiet, "sws_getContext", 0 ); + return; + } + + if(( ret = av_image_alloc( dst, dst_linesize, Avi->xres, Avi->yres, AV_PIX_FMT_BGR0, 1 )) < 0 ) + { + AVI_SpewAvError( quiet, "av_image_alloc", ret ); + return; + } + + Avi->dst = dst[0]; + Avi->dst_linesize = dst_linesize[0]; + + if( load_audio ) + { + if( !( Avi->aframe = av_frame_alloc( ))) + { + AVI_SpewAvError( quiet, "av_frame_alloc (audio)", 0 ); + return; + } + + Avi->audio_stream = AVI_OpenCodecContext( &Avi->audio_ctx, Avi->fmt_ctx, AVMEDIA_TYPE_AUDIO, quiet ); + + // audio stream was requested but it wasn't found + if( Avi->audio_stream < 0 ) + return; + + Avi->channels = Q_min( Avi->audio_ctx->ch_layout.nb_channels, 2 ); + if( Avi->audio_ctx->sample_fmt == AV_SAMPLE_FMT_U8 || Avi->audio_ctx->sample_fmt == AV_SAMPLE_FMT_U8P ) + Avi->s_fmt = AV_SAMPLE_FMT_U8; + else Avi->s_fmt = AV_SAMPLE_FMT_S16; + Avi->rate = Avi->audio_ctx->sample_rate; + + if(( ret = swr_alloc_set_opts2( &Avi->swr_ctx, &Avi->audio_ctx->ch_layout, Avi->s_fmt, Avi->rate, + &Avi->audio_ctx->ch_layout, Avi->audio_ctx->sample_fmt, Avi->audio_ctx->sample_rate, 0, 0 )) < 0 ) + { + AVI_SpewAvError( quiet, "swr_alloc_set_opts2", ret ); + return; + } + + if(( ret = swr_init( Avi->swr_ctx )) < 0 ) + { + AVI_SpewAvError( quiet, "swr_init", ret ); + return; + } + } + + Avi->active = true; +} + +void AVI_CloseVideo( movie_state_t *Avi ) +{ + if( Avi->active ) + { + if( Avi->cached_audio ) + Mem_Free( Avi->cached_audio ); + + swr_free( &Avi->swr_ctx ); + avcodec_free_context( &Avi->audio_ctx ); + av_frame_free( &Avi->aframe ); + + av_free( Avi->dst ); + sws_freeContext( Avi->sws_ctx ); + avcodec_free_context( &Avi->video_ctx ); + av_frame_free( &Avi->vframe ); + av_frame_free( &Avi->vframe_copy ); + + av_packet_free( &Avi->pkt ); + + avformat_close_input( &Avi->fmt_ctx ); + } + + memset( Avi, 0, sizeof( *Avi )); +} + +movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ) +{ + movie_state_t *Avi; + string path; + const char *fullpath; + + // fast reject + if( !avi_initialized ) + return NULL; + + // open cinematic + Q_snprintf( path, sizeof( path ), "media/%s", filename ); + COM_DefaultExtension( path, ".avi", sizeof( path )); + fullpath = FS_GetDiskPath( path, false ); + + if( FS_FileExists( path, false ) && !fullpath ) + { + Con_Printf( "Couldn't load %s from packfile. Please extract it\n", path ); + return NULL; + } + + Avi = Mem_Calloc( avi_mempool, sizeof( movie_state_t )); + AVI_OpenVideo( Avi, fullpath, load_audio, false ); + + if( !AVI_IsActive( Avi )) + { + AVI_FreeVideo( Avi ); // something bad happens + return NULL; + } + + // all done + return Avi; +} + +void AVI_FreeVideo( movie_state_t *Avi ) +{ + if( !Avi ) + return; + + AVI_CloseVideo( Avi ); + + if( Mem_IsAllocatedExt( avi_mempool, Avi )) + Mem_Free( Avi ); +} + +qboolean AVI_IsActive( movie_state_t *Avi ) +{ + return Avi ? Avi->active : false; +} + +movie_state_t *AVI_GetState( int num ) +{ + return &avi[num]; +} + +qboolean AVI_Initailize( void ) +{ + uint ver; + + if( Sys_CheckParm( "-noavi" )) + { + Con_Printf( "AVI: Disabled\n" ); + return false; + } + + // print version we're compiled with and which version we're running with + ver = avutil_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVUTIL_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + ver = avformat_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVFORMAT_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + ver = avformat_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVCODEC_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + ver = swscale_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBSWSCALE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + ver = swresample_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBSWRESAMPLE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + avi_initialized = true; + avi_mempool = Mem_AllocPool( "AVI Zone" ); + return true; +} + +void AVI_Shutdown( void ) +{ + Mem_FreePool( &avi_mempool ); + avi_initialized = false; +} + +#endif // XASH_AVI == AVI_NULL diff --git a/engine/client/avi/avi_stub.c b/engine/client/avi/avi_stub.c index a4ff7fb03a..c911e3e440 100644 --- a/engine/client/avi/avi_stub.c +++ b/engine/client/avi/avi_stub.c @@ -79,6 +79,8 @@ movie_state_t *AVI_GetState( int num ) qboolean AVI_Initailize( void ) { + Con_Printf( "AVI: Not supported\n" ); + return false; } diff --git a/engine/client/cl_cmds.c b/engine/client/cl_cmds.c index 691574c2f6..18338fb5d7 100644 --- a/engine/client/cl_cmds.c +++ b/engine/client/cl_cmds.c @@ -42,7 +42,8 @@ void CL_PlayVideo_f( void ) switch( Cmd_Argc( )) { case 2: // simple user version - Q_snprintf( path, sizeof( path ), "media/%s.avi", Cmd_Argv( 1 )); + Q_snprintf( path, sizeof( path ), "media/%s", Cmd_Argv( 1 )); + COM_DefaultExtension( path, ".avi", sizeof( path )); SCR_PlayCinematic( path ); break; case 3: // sequenced cinematics used this diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index 3122e938ee..95abf99d56 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -302,14 +302,11 @@ void UI_ConnectionProgress_ParseServerInfo( const char *server ) static void GAME_EXPORT UI_DrawLogo( const char *filename, float x, float y, float width, float height ) { - static float cin_time; - static int last_frame = -1; - byte *cin_data = NULL; movie_state_t *cin_state; - int cin_frame; - qboolean redraw = false; - if( !gameui.drawLogo ) return; + if( !gameui.drawLogo ) + return; + cin_state = AVI_GetState( CIN_LOGO ); if( !AVI_IsActive( cin_state )) @@ -336,37 +333,25 @@ static void GAME_EXPORT UI_DrawLogo( const char *filename, float x, float y, flo gameui.drawLogo = false; return; } - - cin_time = 0.0f; - last_frame = -1; } if( width <= 0 || height <= 0 ) { // precache call, don't draw - cin_time = 0.0f; - last_frame = -1; return; } - // advances cinematic time (ignores maxfps and host_framerate settings) - cin_time += host.realframetime; - - // restarts the cinematic - if( cin_time > gameui.logo_length ) - cin_time = 0.0f; + AVI_SetParm( cin_state, + AVI_RENDER_TEXNUM, 0, + AVI_RENDER_X, (int)x, + AVI_RENDER_Y, (int)y, + AVI_RENDER_W, (int)width, + AVI_RENDER_H, (int)height, + AVI_PARM_LAST ); // read the next frame - cin_frame = AVI_GetVideoFrameNumber( cin_state, cin_time ); - - if( cin_frame != last_frame ) - { - cin_data = AVI_GetVideoFrame( cin_state, cin_frame ); - last_frame = cin_frame; - redraw = true; - } - - ref.dllFuncs.R_DrawStretchRaw( x, y, width, height, gameui.logo_xres, gameui.logo_yres, cin_data, redraw ); + if( !AVI_Think( cin_state )) + AVI_SetParm( cin_state, AVI_REWIND, AVI_PARM_LAST ); } static int GAME_EXPORT UI_GetLogoWidth( void ) diff --git a/engine/client/cl_render.c b/engine/client/cl_render.c index 400ed0fbec..87feac0e79 100644 --- a/engine/client/cl_render.c +++ b/engine/client/cl_render.c @@ -269,8 +269,8 @@ static render_api_t gRenderAPI = (void*)AVI_FreeVideo, (void*)AVI_IsActive, S_StreamAviSamples, - NULL, - NULL, + AVI_Think, + AVI_SetParm, NULL, // GL_Bind, NULL, // GL_SelectTexture, NULL, // GL_LoadTexMatrixExt, diff --git a/engine/client/cl_video.c b/engine/client/cl_video.c index d3ec40aa39..a23554d106 100644 --- a/engine/client/cl_video.c +++ b/engine/client/cl_video.c @@ -26,8 +26,6 @@ AVI PLAYING static int xres, yres; static float video_duration; -static float cin_time; -static int cin_frame; static wavdata_t cin_audio; static movie_state_t *cin_state; @@ -88,7 +86,17 @@ void SCR_CheckStartupVids( void ) char *pfile; string token; - if( Sys_CheckParm( "-nointro" ) || host_developer.value || cls.demonum != -1 || GameState->nextstate != STATE_RUNFRAME ) +#if 0 + if( host_developer.value ) + { + // don't run movies where we in developer-mode + cls.movienum = -1; + CL_CheckStartupDemos(); + return; + } +#endif + + if( Sys_CheckParm( "-nointro" ) || cls.demonum != -1 || GameState->nextstate != STATE_RUNFRAME ) { // don't run movies where we in developer-mode cls.movienum = -1; @@ -147,23 +155,9 @@ void SCR_RunCinematic( void ) Key_SetKeyDest( key_menu ); S_StopStreaming(); cls.movienum = -1; - cin_time = 0.0f; cls.signon = 0; return; } - - // advances cinematic time (ignores maxfps and host_framerate settings) - cin_time += host.realframetime; - - // stop the video after it finishes - if( cin_time > video_duration + 0.1f ) - { - SCR_NextMovie( ); - return; - } - - // read the next frame - cin_frame = AVI_GetVideoFrameNumber( cin_state, cin_time ); } /* @@ -176,21 +170,11 @@ should be skipped */ qboolean SCR_DrawCinematic( void ) { - static int last_frame = -1; - qboolean redraw = false; - byte *frame = NULL; - - if( !ref.initialized || cin_time <= 0.0f ) + if( !ref.initialized ) return false; - if( cin_frame != last_frame ) - { - frame = AVI_GetVideoFrame( cin_state, cin_frame ); - last_frame = cin_frame; - redraw = true; - } - - ref.dllFuncs.R_DrawStretchRaw( 0, 0, refState.width, refState.height, xres, yres, frame, redraw ); + if( !AVI_Think( cin_state )) + return SCR_NextMovie(); return true; } @@ -232,10 +216,16 @@ qboolean SCR_PlayCinematic( const char *arg ) S_StartStreaming(); } + AVI_SetParm( cin_state, + AVI_RENDER_X, 0, + AVI_RENDER_Y, 0, + AVI_RENDER_W, -1, + AVI_RENDER_H, -1, + AVI_PARM_LAST ); + UI_SetActiveMenu( false ); cls.state = ca_cinematic; Con_FastClose(); - cin_time = 0.0f; cls.signon = 0; return true; @@ -270,7 +260,6 @@ void SCR_StopCinematic( void ) AVI_CloseVideo( cin_state ); S_StopStreaming(); - cin_time = 0.0f; cls.state = ca_disconnected; cls.signon = 0; diff --git a/engine/wscript b/engine/wscript index 904e797ba2..f2384f81bc 100644 --- a/engine/wscript +++ b/engine/wscript @@ -8,6 +8,23 @@ import os top = '.' +FFMPEG_CHECK_FRAGMENT=''' +#include +#include +#include +#include +#include +int main(int argc, char **argv) +{ + swresample_version(); + avformat_version(); + avcodec_version(); + swscale_version(); + avutil_version(); + return 0; +} +''' + def options(opt): grp = opt.add_option_group('Engine options') @@ -35,6 +52,9 @@ def options(opt): grp.add_option('--enable-ffmpeg', action = 'store_true', dest = 'FFMPEG', default = False, help = '') # hidden option, does nothing + grp.add_option('--enable-ffmpeg', action = 'store_true', dest = 'FFMPEG', default = False, + help = 'enable ffmpeg based movie playback') + opt.load('sdl2') def configure(conf): @@ -125,6 +145,20 @@ def configure(conf): conf.define('ENGINE_DLL', 1) + if conf.options.FFMPEG: + # add ffmpeg libraries into single uselib package + conf.check_cfg(package='libswresample', uselib_store='SWRESAMPLE', args='--cflags --libs') + conf.check_cfg(package='libavformat', uselib_store='AVFORMAT', args='--cflags --libs') + conf.check_cfg(package='libavcodec', uselib_store='AVCODEC', args='--cflags --libs') + conf.check_cfg(package='libswscale', uselib_store='SWSCALE', args='--cflags --libs') + conf.check_cfg(package='libavutil', uselib_store='AVUTIL', args='--cflags --libs') + + # validate ffmpeg libs installation + conf.check_cc(fragment=FFMPEG_CHECK_FRAGMENT, use='SWRESAMPLE AVCODEC AVFORMAT AVUTIL SWSCALE', msg='Checking for ffmpeg sanity') + + conf.env.FFMPEG = True + conf.define('HAVE_FFMPEG', True) + conf.define_cond('XASH_ENGINE_TESTS', conf.env.ENGINE_TESTS) conf.define_cond('XASH_STATIC_LIBS', conf.env.STATIC_LINKING) conf.define_cond('XASH_CUSTOM_SWAP', conf.options.CUSTOM_SWAP) @@ -186,6 +220,13 @@ def build(bld): libs.append('SDL3' if bld.env.HAVE_SDL3 else 'SDL2') source += bld.path.ant_glob(['platform/sdl/*.c']) + if bld.get_define('HAVE_FFMPEG'): + libs.append('SWRESAMPLE') + libs.append('AVCODEC') + libs.append('AVFORMAT') + libs.append('AVUTIL') + libs.append('SWSCALE') + if bld.env.MAGX: libs.append('MAGX') is_cxx_link = True From 16fe6b1b1a17aca1a0094971264f81067562012b Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 8 Dec 2024 16:16:15 +0300 Subject: [PATCH 03/14] engine: client: avi: remove VFW-based player --- engine/client/avi/avi_win.c | 735 ------------------------------------ 1 file changed, 735 deletions(-) delete mode 100644 engine/client/avi/avi_win.c diff --git a/engine/client/avi/avi_win.c b/engine/client/avi/avi_win.c deleted file mode 100644 index 853707125b..0000000000 --- a/engine/client/avi/avi_win.c +++ /dev/null @@ -1,735 +0,0 @@ -/* -avi_win.c - playing AVI files (based on original AVIKit code, Win32 version) -Copyright (c) 2003-2004, Ruari O'Sullivan -Copyright (C) 2010 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#include "defaults.h" -#if XASH_AVI == AVI_WIN32 -#include "common.h" -#include "client.h" -#include // video for windows - -// msvfw32.dll exports -static HDRAWDIB (_stdcall *pDrawDibOpen)( void ); -static BOOL (_stdcall *pDrawDibClose)( HDRAWDIB hdd ); -static BOOL (_stdcall *pDrawDibDraw)( HDRAWDIB, HDC, int, int, int, int, LPBITMAPINFOHEADER, void*, int, int, int, int, uint ); - -static dllfunc_t msvfw_funcs[] = -{ -{ "DrawDibOpen", (void **) &pDrawDibOpen }, -{ "DrawDibDraw", (void **) &pDrawDibDraw }, -{ "DrawDibClose", (void **) &pDrawDibClose }, -{ NULL, NULL } -}; - -dll_info_t msvfw_dll = { "msvfw32.dll", msvfw_funcs, false }; - -// msacm32.dll exports -static MMRESULT (_stdcall *pacmStreamOpen)( LPHACMSTREAM, HACMDRIVER, LPWAVEFORMATEX, LPWAVEFORMATEX, LPWAVEFILTER, DWORD, DWORD, DWORD ); -static MMRESULT (_stdcall *pacmStreamPrepareHeader)( HACMSTREAM, LPACMSTREAMHEADER, DWORD ); -static MMRESULT (_stdcall *pacmStreamUnprepareHeader)( HACMSTREAM, LPACMSTREAMHEADER, DWORD ); -static MMRESULT (_stdcall *pacmStreamConvert)( HACMSTREAM, LPACMSTREAMHEADER, DWORD ); -static MMRESULT (_stdcall *pacmStreamSize)( HACMSTREAM, DWORD, LPDWORD, DWORD ); -static MMRESULT (_stdcall *pacmStreamClose)( HACMSTREAM, DWORD ); - -static dllfunc_t msacm_funcs[] = -{ -{ "acmStreamOpen", (void **) &pacmStreamOpen }, -{ "acmStreamPrepareHeader", (void **) &pacmStreamPrepareHeader }, -{ "acmStreamUnprepareHeader", (void **) &pacmStreamUnprepareHeader }, -{ "acmStreamConvert", (void **) &pacmStreamConvert }, -{ "acmStreamSize", (void **) &pacmStreamSize }, -{ "acmStreamClose", (void **) &pacmStreamClose }, -{ NULL, NULL } -}; - -dll_info_t msacm_dll = { "msacm32.dll", msacm_funcs, false }; - -// avifil32.dll exports -static int (_stdcall *pAVIStreamInfo)( PAVISTREAM pavi, AVISTREAMINFO *psi, LONG lSize ); -static int (_stdcall *pAVIStreamRead)( PAVISTREAM pavi, LONG lStart, LONG lSamples, void *lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples ); -static PGETFRAME (_stdcall *pAVIStreamGetFrameOpen)( PAVISTREAM pavi, LPBITMAPINFOHEADER lpbiWanted ); -static int (_stdcall *pAVIStreamTimeToSample)( PAVISTREAM pavi, LONG lTime ); -static void* (_stdcall *pAVIStreamGetFrame)( PGETFRAME pg, LONG lPos ); -static int (_stdcall *pAVIStreamGetFrameClose)( PGETFRAME pg ); -static dword (_stdcall *pAVIStreamRelease)( PAVISTREAM pavi ); -static int (_stdcall *pAVIFileOpenW)( PAVIFILE *ppfile, LPCWSTR szFile, UINT uMode, LPCLSID lpHandler ); -static int (_stdcall *pAVIFileGetStream)( PAVIFILE pfile, PAVISTREAM *ppavi, DWORD fccType, LONG lParam ); -static int (_stdcall *pAVIStreamReadFormat)( PAVISTREAM pavi, LONG lPos,LPVOID lpFormat, LONG *lpcbFormat ); -static int (_stdcall *pAVIStreamStart)( PAVISTREAM pavi ); -static dword (_stdcall *pAVIFileRelease)( PAVIFILE pfile ); -static void (_stdcall *pAVIFileInit)( void ); -static void (_stdcall *pAVIFileExit)( void ); - -static dllfunc_t avifile_funcs[] = -{ -{ "AVIFileExit", (void **) &pAVIFileExit }, -{ "AVIFileGetStream", (void **) &pAVIFileGetStream }, -{ "AVIFileInit", (void **) &pAVIFileInit }, -{ "AVIFileOpenW", (void **) &pAVIFileOpenW }, -{ "AVIFileRelease", (void **) &pAVIFileRelease }, -{ "AVIStreamGetFrame", (void **) &pAVIStreamGetFrame }, -{ "AVIStreamGetFrameClose", (void **) &pAVIStreamGetFrameClose }, -{ "AVIStreamGetFrameOpen", (void **) &pAVIStreamGetFrameOpen }, -{ "AVIStreamInfoA", (void **) &pAVIStreamInfo }, -{ "AVIStreamRead", (void **) &pAVIStreamRead }, -{ "AVIStreamReadFormat", (void **) &pAVIStreamReadFormat }, -{ "AVIStreamRelease", (void **) &pAVIStreamRelease }, -{ "AVIStreamStart", (void **) &pAVIStreamStart }, -{ "AVIStreamTimeToSample", (void **) &pAVIStreamTimeToSample }, -{ NULL, NULL } -}; - -dll_info_t avifile_dll = { "avifil32.dll", avifile_funcs, false }; - -typedef struct movie_state_s -{ - qboolean active; - qboolean quiet; // ignore error messages - - PAVIFILE pfile; // avi file pointer - PAVISTREAM video_stream; // video stream pointer - PGETFRAME video_getframe; // pointer to getframe object for video stream - int video_frames; // total frames - int video_xres; // video stream resolution - int video_yres; - float video_fps; // video stream fps - - PAVISTREAM audio_stream; // audio stream pointer - WAVEFORMAT *audio_header; // audio stream header - int audio_header_size; // WAVEFORMAT is returned for PCM data; WAVEFORMATEX for others - int audio_codec; // WAVE_FORMAT_PCM is oldstyle: anything else needs conversion - int audio_length; // in converted samples - int audio_bytes_per_sample; // guess. - - // compressed audio specific data - dword cpa_blockalign; // block size to read - HACMSTREAM cpa_conversion_stream; - ACMSTREAMHEADER cpa_conversion_header; - byte *cpa_srcbuffer; // maintained buffer for raw data - byte *cpa_dstbuffer; - - dword cpa_blocknum; // current block - dword cpa_blockpos; // read position in current block - dword cpa_blockoffset; // corresponding offset in bytes in the output stream - - // for additional unpack Ms-RLE codecs etc - HDC hDC; // compatible DC - HDRAWDIB hDD; // DrawDib handler - HBITMAP hBitmap; // for DIB conversions - byte *pframe_data; // converted framedata -} movie_state_t; - -static qboolean avi_initialized = false; -static movie_state_t avi[2]; - -// Converts a compressed audio stream into uncompressed PCM. -qboolean AVI_ACMConvertAudio( movie_state_t *Avi ) -{ - WAVEFORMATEX dest_header, *sh, *dh; - AVISTREAMINFO stream_info; - dword dest_length; - short bits; - - // WMA codecs, both versions - they simply don't work. - if( Avi->audio_header->wFormatTag == 0x160 || Avi->audio_header->wFormatTag == 0x161 ) - { - if( !Avi->quiet ) - Con_Reportf( S_ERROR "ACM does not support this audio codec.\n" ); - return false; - } - - // get audio stream info to work with - pAVIStreamInfo( Avi->audio_stream, &stream_info, sizeof( stream_info )); - - if( Avi->audio_header_size < sizeof( WAVEFORMATEX )) - { - if( !Avi->quiet ) - Con_Reportf( S_ERROR "ACM failed to open conversion stream.\n" ); - return false; - } - - sh = (WAVEFORMATEX *)Avi->audio_header; - bits = 16; // predict state - - // how much of this is actually required? - dest_header.wFormatTag = WAVE_FORMAT_PCM; // yay - dest_header.wBitsPerSample = bits; // 16bit - dest_header.nChannels = sh->nChannels; - dest_header.nSamplesPerSec = sh->nSamplesPerSec; // take straight from the source stream - dest_header.nAvgBytesPerSec = (bits >> 3) * sh->nChannels * sh->nSamplesPerSec; - dest_header.nBlockAlign = (bits >> 3) * sh->nChannels; - dest_header.cbSize = 0; // no more data. - - dh = &dest_header; - - // open the stream - if( pacmStreamOpen( &Avi->cpa_conversion_stream, NULL, sh, dh, NULL, 0, 0, 0 ) != MMSYSERR_NOERROR ) - { - // try with 8 bit destination instead - bits = 8; - - dest_header.wBitsPerSample = bits; // 8bit - dest_header.nAvgBytesPerSec = ( bits >> 3 ) * sh->nChannels * sh->nSamplesPerSec; - dest_header.nBlockAlign = ( bits >> 3 ) * sh->nChannels; // 1 sample at a time - - if( pacmStreamOpen( &Avi->cpa_conversion_stream, NULL, sh, dh, NULL, 0, 0, 0 ) != MMSYSERR_NOERROR ) - { - if( !Avi->quiet ) - Con_Reportf( S_ERROR "ACM failed to open conversion stream.\n" ); - return false; - } - } - - Avi->cpa_blockalign = sh->nBlockAlign; - dest_length = 0; - - // mp3 specific fix - if( sh->wFormatTag == 0x55 ) - { - LPMPEGLAYER3WAVEFORMAT k; - - k = (LPMPEGLAYER3WAVEFORMAT)sh; - Avi->cpa_blockalign = k->nBlockSize; - } - - // get the size of the output buffer for streaming the compressed audio - if( pacmStreamSize( Avi->cpa_conversion_stream, Avi->cpa_blockalign, &dest_length, ACM_STREAMSIZEF_SOURCE ) != MMSYSERR_NOERROR ) - { - if( !Avi->quiet ) - Con_Reportf( S_ERROR "Couldn't get ACM conversion stream size.\n" ); - pacmStreamClose( Avi->cpa_conversion_stream, 0 ); - return false; - } - - Avi->cpa_srcbuffer = (byte *)Mem_Malloc( cls.mempool, Avi->cpa_blockalign ); - Avi->cpa_dstbuffer = (byte *)Mem_Malloc( cls.mempool, dest_length ); // maintained buffer for raw data - - // prep the headers! - Avi->cpa_conversion_header.cbStruct = sizeof( ACMSTREAMHEADER ); - Avi->cpa_conversion_header.fdwStatus = 0; - Avi->cpa_conversion_header.dwUser = 0; // no user data - Avi->cpa_conversion_header.pbSrc = Avi->cpa_srcbuffer; // source buffer - Avi->cpa_conversion_header.cbSrcLength = Avi->cpa_blockalign; // source buffer size - Avi->cpa_conversion_header.cbSrcLengthUsed = 0; - Avi->cpa_conversion_header.dwSrcUser = 0; // no user data - Avi->cpa_conversion_header.pbDst = Avi->cpa_dstbuffer; // dest buffer - Avi->cpa_conversion_header.cbDstLength = dest_length; // dest buffer size - Avi->cpa_conversion_header.cbDstLengthUsed = 0; - Avi->cpa_conversion_header.dwDstUser = 0; // no user data - - if( pacmStreamPrepareHeader( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, 0 ) != MMSYSERR_NOERROR ) - { - if( !Avi->quiet ) - Con_Reportf( S_ERROR "couldn't prepare stream headers.\n" ); - pacmStreamClose( Avi->cpa_conversion_stream, 0 ); - return false; - } - - Avi->cpa_blocknum = 0; // start at 0. - Avi->cpa_blockpos = 0; - Avi->cpa_blockoffset = 0; - - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN|ACM_STREAMCONVERTF_START ); - - // convert first chunk twice. it often fails the first time. BLACK MAGIC. - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); - - Avi->audio_bytes_per_sample = (bits >> 3 ) * Avi->audio_header->nChannels; - - return true; -} - -qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ) -{ - if( !Avi->active ) - return false; - - if( xres != NULL ) - *xres = Avi->video_xres; - - if( yres != NULL ) - *yres = Avi->video_yres; - - if( duration != NULL ) - *duration = (float)Avi->video_frames / Avi->video_fps; - - return true; -} - -// returns a unique frame identifier -int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) -{ - if( !Avi->active ) - return 0; - - return (time * Avi->video_fps); -} - -int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) -{ - if( !Avi->active || !Avi->audio_stream ) - return 0; - - // UNDONE: what about compressed audio? - return pAVIStreamTimeToSample( Avi->audio_stream, time ) * Avi->audio_bytes_per_sample; -} - -// gets the raw frame data -byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame ) -{ - LPBITMAPINFOHEADER frame_info; - byte *frame_raw; - - if( !Avi->active ) return NULL; - - if( frame >= Avi->video_frames ) - frame = Avi->video_frames - 1; - - frame_info = (LPBITMAPINFOHEADER)pAVIStreamGetFrame( Avi->video_getframe, frame ); - frame_raw = (byte *)frame_info + frame_info->biSize + frame_info->biClrUsed * sizeof( RGBQUAD ); - pDrawDibDraw( Avi->hDD, Avi->hDC, 0, 0, Avi->video_xres, Avi->video_yres, frame_info, frame_raw, 0, 0, Avi->video_xres, Avi->video_yres, 0 ); - - return Avi->pframe_data; -} - -qboolean AVI_GetAudioInfo( movie_state_t *Avi, wavdata_t *snd_info ) -{ - if( !Avi->active || Avi->audio_stream == NULL || snd_info == NULL ) - { - return false; - } - - snd_info->rate = Avi->audio_header->nSamplesPerSec; - snd_info->channels = Avi->audio_header->nChannels; - - if( Avi->audio_codec == WAVE_FORMAT_PCM ) // uncompressed audio! - snd_info->width = ( Avi->audio_bytes_per_sample > Avi->audio_header->nChannels ) ? 2 : 1; - else snd_info->width = 2; // assume compressed audio is always 16 bit - - snd_info->size = snd_info->rate * snd_info->width * snd_info->channels; - snd_info->loopStart = 0; // using loopStart as streampos - - return true; -} - -// sync the current audio read to a specific offset -qboolean AVI_SeekPosition( movie_state_t *Avi, dword offset ) -{ - int breaker; - - if( offset < Avi->cpa_blockoffset ) // well, shit. we can't seek backwards... restart - { - if( Avi->cpa_blockoffset - offset < 500000 ) - return false; // don't bother if it's gonna catch up soon - - Avi->cpa_blocknum = 0; // start at 0, eh. - Avi->cpa_blockpos = 0; - Avi->cpa_blockoffset = 0; - - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN|ACM_STREAMCONVERTF_START ); - - // convert first chunk twice. it often fails the first time. BLACK MAGIC. - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); - } - - // now then: seek forwards to the required block - breaker = 30; // maximum zero blocks: anti-freeze protection - - while( Avi->cpa_blockoffset + Avi->cpa_conversion_header.cbDstLengthUsed < offset ) - { - Avi->cpa_blocknum++; - Avi->cpa_blockoffset += Avi->cpa_conversion_header.cbDstLengthUsed; - - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); - - if( Avi->cpa_conversion_header.cbDstLengthUsed == 0 ) - breaker--; - else breaker = 30; - - if( breaker <= 0 ) - return false; - - Avi->cpa_blockpos = 0; - } - - // seek to the right position inside the block - Avi->cpa_blockpos = offset - Avi->cpa_blockoffset; - - return true; -} - -// get a chunk of audio from the stream (in bytes) -int AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, int offset, int length ) -{ - int result = 0; - int i; - - // zero data past the end of the file - if( offset + length > Avi->audio_length ) - { - if( offset <= Avi->audio_length ) - { - int remaining_length = Avi->audio_length - offset; - - AVI_GetAudioChunk( Avi, audiodata, offset, remaining_length ); - - for( i = remaining_length; i < length; i++ ) - audiodata[i] = 0; - } - else - { - // we out of soundtrack, just zeroing buffer - for( i = 0; i < length; i++ ) - audiodata[i] = 0; - -// return length; - } - } - - // uncompressed audio! - if( Avi->audio_codec == WAVE_FORMAT_PCM ) - { - // very simple - read straight out - pAVIStreamRead( Avi->audio_stream, offset / Avi->audio_bytes_per_sample, length / Avi->audio_bytes_per_sample, audiodata, length, &result, NULL ); - return result; - } - else - { - // compressed audio! - result = 0; - - // seek to correct chunk and all that stuff - if( !AVI_SeekPosition( Avi, offset )) - return 0; // don't continue if we're waiting for the play pointer to catch up - - while( length > 0 ) - { - int blockread = Avi->cpa_conversion_header.cbDstLengthUsed - Avi->cpa_blockpos; - - if( blockread <= 0 ) // read next - { - Avi->cpa_blocknum++; - Avi->cpa_blockoffset += Avi->cpa_conversion_header.cbDstLengthUsed; - - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); - - Avi->cpa_blockpos = 0; - continue; - } - - if( blockread > length ) - blockread = length; - - // copy the data - memcpy( audiodata + result, (void *)( Avi->cpa_dstbuffer + Avi->cpa_blockpos ), blockread ); - - Avi->cpa_blockpos += blockread; - result += blockread; - length -= blockread; - } - - return result; - } -} - -void AVI_CloseVideo( movie_state_t *Avi ) -{ - if( Avi->active ) - { - pAVIStreamGetFrameClose( Avi->video_getframe ); - - if( Avi->audio_stream != NULL ) - { - pAVIStreamRelease( Avi->audio_stream ); - Mem_Free( Avi->audio_header ); - - if( Avi->audio_codec != WAVE_FORMAT_PCM ) - { - pacmStreamUnprepareHeader( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, 0 ); - pacmStreamClose( Avi->cpa_conversion_stream, 0 ); - Mem_Free( Avi->cpa_srcbuffer ); - Mem_Free( Avi->cpa_dstbuffer ); - } - } - - pAVIStreamRelease( Avi->video_stream ); - - DeleteObject( Avi->hBitmap ); - pDrawDibClose( Avi->hDD ); - DeleteDC( Avi->hDC ); - } - - memset( Avi, 0, sizeof( movie_state_t )); -} - -void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) -{ - BITMAPINFOHEADER bmih; - AVISTREAMINFO stream_info; - int opened_streams = 0; - LONG hr; - wchar_t pathBuffer[MAX_PATH]; - - // default state: non-working. - Avi->active = false; - Avi->quiet = quiet; - - // can't load Video For Windows :-( - if( !avi_initialized ) return; - - // convert to wide char - if( MultiByteToWideChar( CP_UTF8, 0, filename, -1, pathBuffer, ARRAYSIZE( pathBuffer )) <= 0 ) - { - Con_DPrintf( S_ERROR "filename buffer limit exceeded\n" ); - return; - } - - // load the AVI - hr = pAVIFileOpenW( &Avi->pfile, pathBuffer, OF_SHARE_DENY_WRITE, 0L ); - - if( hr != 0 ) // error opening AVI: - { - switch( hr ) - { - case AVIERR_BADFORMAT: - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "corrupt file or unknown format.\n" ); - break; - case AVIERR_MEMORY: - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "insufficient memory to open file.\n" ); - break; - case AVIERR_FILEREAD: - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "disk error reading file.\n" ); - break; - case AVIERR_FILEOPEN: - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "disk error opening file.\n" ); - break; - case REGDB_E_CLASSNOTREG: - default: - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "no handler found (or file not found).\n" ); - break; - } - return; - } - - Avi->video_stream = Avi->audio_stream = NULL; - - // open the streams until a stream is not available. - while( 1 ) - { - PAVISTREAM stream = NULL; - - if( pAVIFileGetStream( Avi->pfile, &stream, 0L, opened_streams++ ) != AVIERR_OK ) - break; - - if( stream == NULL ) - break; - - pAVIStreamInfo( stream, &stream_info, sizeof( stream_info )); - - if( stream_info.fccType == streamtypeVIDEO && Avi->video_stream == NULL ) - { - Avi->video_stream = stream; - Avi->video_frames = stream_info.dwLength; - Avi->video_xres = stream_info.rcFrame.right - stream_info.rcFrame.left; - Avi->video_yres = stream_info.rcFrame.bottom - stream_info.rcFrame.top; - Avi->video_fps = (float)stream_info.dwRate / (float)stream_info.dwScale; - } - else if( stream_info.fccType == streamtypeAUDIO && Avi->audio_stream == NULL && load_audio ) - { - int size; - - Avi->audio_stream = stream; - - // read the audio header - pAVIStreamReadFormat( Avi->audio_stream, pAVIStreamStart( Avi->audio_stream ), 0, &size ); - - Avi->audio_header = (WAVEFORMAT *)Mem_Malloc( cls.mempool, size ); - pAVIStreamReadFormat( Avi->audio_stream, pAVIStreamStart( Avi->audio_stream ), Avi->audio_header, &size ); - Avi->audio_header_size = size; - Avi->audio_codec = Avi->audio_header->wFormatTag; - - // length of converted audio in samples - Avi->audio_length = (int)((float)stream_info.dwLength / Avi->audio_header->nAvgBytesPerSec ); - Avi->audio_length *= Avi->audio_header->nSamplesPerSec; - - if( Avi->audio_codec != WAVE_FORMAT_PCM ) - { - if( !AVI_ACMConvertAudio( Avi )) - { - Mem_Free( Avi->audio_header ); - Avi->audio_stream = NULL; - continue; - } - } - else Avi->audio_bytes_per_sample = Avi->audio_header->nBlockAlign; - Avi->audio_length *= Avi->audio_bytes_per_sample; - } - else - { - pAVIStreamRelease( stream ); - } - } - - // display error message-stream not found. - if( Avi->video_stream == NULL ) - { - if( Avi->pfile ) // if file is open, close it - pAVIFileRelease( Avi->pfile ); - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "couldn't find a valid video stream.\n" ); - return; - } - - pAVIFileRelease( Avi->pfile ); // release the file - Avi->video_getframe = pAVIStreamGetFrameOpen( Avi->video_stream, NULL ); // open the frame getter - - if( Avi->video_getframe == NULL ) - { - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "error attempting to read video frames.\n" ); - return; // couldn't open frame getter. - } - - bmih.biSize = sizeof( BITMAPINFOHEADER ); - bmih.biPlanes = 1; - bmih.biBitCount = 32; - bmih.biCompression = BI_RGB; - bmih.biWidth = Avi->video_xres; - bmih.biHeight = -Avi->video_yres; // invert height to flip image upside down - - Avi->hDC = CreateCompatibleDC( 0 ); - Avi->hDD = pDrawDibOpen(); - Avi->hBitmap = CreateDIBSection( Avi->hDC, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&Avi->pframe_data), NULL, 0 ); - SelectObject( Avi->hDC, Avi->hBitmap ); - - Avi->active = true; // done -} - -qboolean AVI_IsActive( movie_state_t *Avi ) -{ - if( Avi != NULL ) - return Avi->active; - return false; -} - -movie_state_t *AVI_GetState( int num ) -{ - return &avi[num]; -} - -/* -============= -AVIKit user interface - -============= -*/ -movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ) -{ - movie_state_t *Avi; - string path; - const char *fullpath; - - // fast reject - if( !avi_initialized ) - return NULL; - - // open cinematic - Q_snprintf( path, sizeof( path ), "media/%s", filename ); - COM_DefaultExtension( path, ".avi", sizeof( path )); - fullpath = FS_GetDiskPath( path, false ); - - if( FS_FileExists( path, false ) && !fullpath ) - { - Con_Printf( "Couldn't load %s from packfile. Please extract it\n", path ); - return NULL; - } - - Avi = Mem_Malloc( cls.mempool, sizeof( movie_state_t )); - AVI_OpenVideo( Avi, fullpath, load_audio, false ); - - if( !AVI_IsActive( Avi )) - { - AVI_FreeVideo( Avi ); // something bad happens - return NULL; - } - - // all done - return Avi; -} - -void AVI_FreeVideo( movie_state_t *state ) -{ - if( !state ) return; - - if( Mem_IsAllocatedExt( cls.mempool, state )) - { - AVI_CloseVideo( state ); - Mem_Free( state ); - } -} - -qboolean AVI_Initailize( void ) -{ - if( Sys_CheckParm( "-noavi" )) - { - Con_Printf( "AVI: Disabled\n" ); - return false; - } - - if( !Sys_LoadLibrary( &avifile_dll )) - return false; - - if( !Sys_LoadLibrary( &msvfw_dll )) - { - Sys_FreeLibrary( &avifile_dll ); - return false; - } - - if( !Sys_LoadLibrary( &msacm_dll )) - { - Sys_FreeLibrary( &avifile_dll ); - Sys_FreeLibrary( &msvfw_dll ); - return false; - } - - avi_initialized = true; - pAVIFileInit(); - - return true; -} - -void AVI_Shutdown( void ) -{ - if( !avi_initialized ) return; - - pAVIFileExit(); - - Sys_FreeLibrary( &avifile_dll ); - Sys_FreeLibrary( &msvfw_dll ); - Sys_FreeLibrary( &msacm_dll ); - avi_initialized = false; -} -#endif // XASH_AVI == AVI_WIN32 From a3c33e0fa9c424e8bb613f1e04d9d06680da9551 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 9 Dec 2024 00:12:51 +0300 Subject: [PATCH 04/14] engine: client: sound: make S_RawSampleStereo public to other parts of engine --- engine/client/s_main.c | 4 +--- engine/client/sound.h | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/client/s_main.c b/engine/client/s_main.c index e1a6d52bf7..4e8d26cade 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -20,8 +20,6 @@ GNU General Public License for more details. #include "pm_local.h" #include "platform/platform.h" -#define SND_CLIP_DISTANCE 1000.0f - dma_t dma; poolhandle_t sndpool; static soundfade_t soundfade; @@ -1111,7 +1109,7 @@ rawchan_t *S_FindRawChannel( int entnum, qboolean create ) S_RawSamplesStereo =================== */ -static uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data ) +uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data ) { uint fracstep, samplefrac; uint src, dst; diff --git a/engine/client/sound.h b/engine/client/sound.h index 4a8819c5ba..01dc6c5e50 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -177,6 +177,7 @@ typedef struct #define MAX_CHANNELS (256 + MAX_DYNAMIC_CHANNELS) // Scourge Of Armagon has too many static sounds on hip2m4.bsp #define MAX_RAW_CHANNELS 48 #define MAX_RAW_SAMPLES 8192 +#define SND_CLIP_DISTANCE 1000.0f extern sound_t ambient_sfx[NUM_AMBIENTS]; extern qboolean snd_ambient; @@ -242,6 +243,7 @@ int S_GetCurrentStaticSounds( soundlist_t *pout, int size ); int S_GetCurrentDynamicSounds( soundlist_t *pout, int size ); sfx_t *S_GetSfxByHandle( sound_t handle ); rawchan_t *S_FindRawChannel( int entnum, qboolean create ); +uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data ); void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol ); void S_RawSamples( uint samples, uint rate, word width, word channels, const byte *data, int entnum ); void S_StopSound( int entnum, int channel, const char *soundname ); From eefb823ee2ef2533ef79cf7d71031601de268e2f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 9 Dec 2024 00:14:16 +0300 Subject: [PATCH 05/14] engine: client: remove legacy movie audio track streaming functions --- engine/client/avi/avi.h | 3 +- engine/client/avi/avi_ffmpeg.c | 18 +------- engine/client/cl_render.c | 7 ++- engine/client/cl_video.c | 28 +----------- engine/client/s_main.c | 81 ---------------------------------- engine/client/s_stream.c | 63 -------------------------- engine/client/sound.h | 1 - engine/common/common.h | 2 - 8 files changed, 10 insertions(+), 193 deletions(-) diff --git a/engine/client/avi/avi.h b/engine/client/avi/avi.h index 9adc504abe..ace6716750 100644 --- a/engine/client/avi/avi.h +++ b/engine/client/avi/avi.h @@ -22,8 +22,7 @@ typedef struct movie_state_s movie_state_t; int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ); byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame ); qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ); -qboolean AVI_GetAudioInfo( movie_state_t *Avi, wavdata_t *snd_info ); -int AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, int offset, int length ); +qboolean AVI_HaveAudioTrack( const movie_state_t *Avi ); void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ); movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ); int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ); diff --git a/engine/client/avi/avi_ffmpeg.c b/engine/client/avi/avi_ffmpeg.c index e5ea932914..b5c20a0d6a 100644 --- a/engine/client/avi/avi_ffmpeg.c +++ b/engine/client/avi/avi_ffmpeg.c @@ -235,18 +235,9 @@ qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *dura return true; } -qboolean AVI_GetAudioInfo( movie_state_t *Avi, wavdata_t *snd_info ) +qboolean AVI_HaveAudioTrack( const movie_state_t *Avi ) { - if( !Avi->active || Avi->audio_stream < 0 ) - return false; - - snd_info->rate = Avi->rate; - snd_info->channels = Avi->channels; - snd_info->width = av_get_bytes_per_sample( Avi->s_fmt ); - snd_info->size = (size_t)snd_info->rate * snd_info->width * snd_info->channels; - snd_info->loopStart = 0; - - return true; + return Avi ? Avi->active && Avi->audio_ctx : false; } // just let it compile, bruh! @@ -255,11 +246,6 @@ byte *AVI_GetVideoFrame( movie_state_t *Avi, int target ) return Avi->dst; } -int AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, int offset, int length ) -{ - return 0; -} - static void AVI_StreamAudio( movie_state_t *Avi ) { int buffer_samples, file_samples, file_bytes; diff --git a/engine/client/cl_render.c b/engine/client/cl_render.c index 87feac0e79..8ad4aded3b 100644 --- a/engine/client/cl_render.c +++ b/engine/client/cl_render.c @@ -236,6 +236,11 @@ static intptr_t pfnRenderGetParm( int parm, int arg ) return CL_RenderGetParm( parm, arg, true ); } +static void pfnAVI_StreamSound( movie_state_t *avi, int entnum, float fvol, float attn, float synctime ) +{ + return; // stub, use AVI_SetParm and AVI_Think to stream AVI sound +} + static render_api_t gRenderAPI = { pfnRenderGetParm, // GL_RenderGetParm, @@ -268,7 +273,7 @@ static render_api_t gRenderAPI = NULL, // R_UploadStretchRaw, (void*)AVI_FreeVideo, (void*)AVI_IsActive, - S_StreamAviSamples, + (void*)pfnAVI_StreamSound, AVI_Think, AVI_SetParm, NULL, // GL_Bind, diff --git a/engine/client/cl_video.c b/engine/client/cl_video.c index a23554d106..ed0bd86e04 100644 --- a/engine/client/cl_video.c +++ b/engine/client/cl_video.c @@ -24,9 +24,6 @@ AVI PLAYING ================================================================= */ -static int xres, yres; -static float video_duration; -static wavdata_t cin_audio; static movie_state_t *cin_state; /* @@ -203,13 +200,7 @@ qboolean SCR_PlayCinematic( const char *arg ) return false; } - if( !( AVI_GetVideoInfo( cin_state, &xres, &yres, &video_duration ))) // couldn't open this at all. - { - AVI_CloseVideo( cin_state ); - return false; - } - - if( AVI_GetAudioInfo( cin_state, &cin_audio )) + if( AVI_HaveAudioTrack( cin_state )) { // begin streaming S_StopAllSounds( true ); @@ -231,23 +222,6 @@ qboolean SCR_PlayCinematic( const char *arg ) return true; } -int SCR_GetAudioChunk( char *rawdata, int length ) -{ - int r; - - r = AVI_GetAudioChunk( cin_state, rawdata, cin_audio.loopStart, length ); - cin_audio.loopStart += r; // advance play position - - return r; -} - -wavdata_t *SCR_GetMovieInfo( void ) -{ - if( AVI_IsActive( cin_state )) - return &cin_audio; - return NULL; -} - /* ================== SCR_StopCinematic diff --git a/engine/client/s_main.c b/engine/client/s_main.c index 4e8d26cade..512cc6a254 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -1205,86 +1205,6 @@ void S_RawSamples( uint samples, uint rate, word width, word channels, const byt S_RawEntSamples( entnum, samples, rate, width, channels, data, snd_vol ); } -/* -=================== -S_PositionedRawSamples -=================== -*/ -void S_StreamAviSamples( void *Avi, int entnum, float fvol, float attn, float synctime ) -{ - int bufferSamples; - int fileSamples; - byte raw[MAX_RAW_SAMPLES]; - float duration = 0.0f; - int r, fileBytes; - rawchan_t *ch = NULL; - - if( !dma.initialized || s_listener.paused || !CL_IsInGame( )) - return; - - if( entnum < 0 || entnum >= GI->max_edicts ) - return; - - if( !( ch = S_FindRawChannel( entnum, true ))) - return; - - if( ch->sound_info.rate == 0 ) - { - if( !AVI_GetAudioInfo( Avi, &ch->sound_info )) - return; // no audiotrack - } - - ch->master_vol = bound( 0, fvol * 255, 255 ); - ch->dist_mult = (attn / SND_CLIP_DISTANCE); - - // see how many samples should be copied into the raw buffer - if( ch->s_rawend < soundtime ) - ch->s_rawend = soundtime; - - // position is changed, synchronization is lost etc - if( fabs( ch->oldtime - synctime ) > s_mixahead.value ) - ch->sound_info.loopStart = AVI_TimeToSoundPosition( Avi, synctime * 1000 ); - ch->oldtime = synctime; // keep actual time - - while( ch->s_rawend < soundtime + ch->max_samples ) - { - wavdata_t *info = &ch->sound_info; - - bufferSamples = ch->max_samples - (ch->s_rawend - soundtime); - - // decide how much data needs to be read from the file - fileSamples = bufferSamples * ((float)info->rate / SOUND_DMA_SPEED ); - if( fileSamples <= 1 ) return; // no more samples need - - // our max buffer size - fileBytes = fileSamples * ( info->width * info->channels ); - - if( fileBytes > sizeof( raw )) - { - fileBytes = sizeof( raw ); - fileSamples = fileBytes / ( info->width * info->channels ); - } - - // read audio stream - r = AVI_GetAudioChunk( Avi, raw, info->loopStart, fileBytes ); - info->loopStart += r; // advance play position - - if( r < fileBytes ) - { - fileBytes = r; - fileSamples = r / ( info->width * info->channels ); - } - - if( r > 0 ) - { - // add to raw buffer - ch->s_rawend = S_RawSamplesStereo( ch->rawsamples, ch->s_rawend, ch->max_samples, - fileSamples, info->rate, info->width, info->channels, raw ); - } - else break; // no more samples for this frame - } -} - /* =================== S_FreeIdleRawChannels @@ -1688,7 +1608,6 @@ void SND_UpdateSound( void ) } S_StreamBackgroundTrack (); - S_StreamSoundTrack (); // mix some sound S_UpdateChannels (); diff --git a/engine/client/s_stream.c b/engine/client/s_stream.c index 7f3e297315..555df323cd 100644 --- a/engine/client/s_stream.c +++ b/engine/client/s_stream.c @@ -286,66 +286,3 @@ void S_StopStreaming( void ) if( !dma.initialized ) return; s_listener.streaming = false; } - -/* -================= -S_StreamSoundTrack -================= -*/ -void S_StreamSoundTrack( void ) -{ - int bufferSamples; - int fileSamples; - byte raw[MAX_RAW_SAMPLES]; - int r, fileBytes; - rawchan_t *ch = NULL; - - if( !dma.initialized || !s_listener.streaming || s_listener.paused ) - return; - - ch = S_FindRawChannel( S_RAW_SOUND_SOUNDTRACK, true ); - - Assert( ch != NULL ); - - // see how many samples should be copied into the raw buffer - if( ch->s_rawend < soundtime ) - ch->s_rawend = soundtime; - - while( ch->s_rawend < soundtime + ch->max_samples ) - { - wavdata_t *info = SCR_GetMovieInfo(); - - if( !info ) break; // bad soundtrack? - - bufferSamples = ch->max_samples - (ch->s_rawend - soundtime); - - // decide how much data needs to be read from the file - fileSamples = bufferSamples * ((float)info->rate / SOUND_DMA_SPEED ); - if( fileSamples <= 1 ) return; // no more samples need - - // our max buffer size - fileBytes = fileSamples * ( info->width * info->channels ); - - if( fileBytes > sizeof( raw )) - { - fileBytes = sizeof( raw ); - fileSamples = fileBytes / ( info->width * info->channels ); - } - - // read audio stream - r = SCR_GetAudioChunk( raw, fileBytes ); - - if( r < fileBytes ) - { - fileBytes = r; - fileSamples = r / ( info->width * info->channels ); - } - - if( r > 0 ) - { - // add to raw buffer - S_RawSamples( fileSamples, info->rate, info->width, info->channels, raw, S_RAW_SOUND_SOUNDTRACK ); - } - else break; // no more samples for this frame - } -} diff --git a/engine/client/sound.h b/engine/client/sound.h index 01dc6c5e50..d0a6e812a3 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -265,7 +265,6 @@ void SND_ForceCloseMouth( int entnum ); // // s_stream.c // -void S_StreamSoundTrack( void ); void S_StreamBackgroundTrack( void ); void S_PrintBackgroundTrackState( void ); void S_FadeMusicVolume( float fadePercent ); diff --git a/engine/common/common.h b/engine/common/common.h index 7b1a404721..97d47fedf5 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -741,8 +741,6 @@ void SCR_Init( void ); void SCR_UpdateScreen( void ); void SCR_BeginLoadingPlaque( qboolean is_background ); void SCR_CheckStartupVids( void ); -int SCR_GetAudioChunk( char *rawdata, int length ); -wavdata_t *SCR_GetMovieInfo( void ); void SCR_Shutdown( void ); void Con_Print( const char *txt ); void Con_NPrintf( int idx, const char *fmt, ... ) FORMAT_CHECK( 2 ); From 43ba2e747b349f31052759283b48bf9a415e1881 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 9 Dec 2024 00:18:46 +0300 Subject: [PATCH 06/14] engine: avi: mention SDL3 and FTEQW developers which ffmpeg integrations in their projects helped me to understand the ffmpeg API --- engine/client/avi/avi_ffmpeg.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/client/avi/avi_ffmpeg.c b/engine/client/avi/avi_ffmpeg.c index b5c20a0d6a..132f0cf939 100644 --- a/engine/client/avi/avi_ffmpeg.c +++ b/engine/client/avi/avi_ffmpeg.c @@ -1,6 +1,8 @@ /* avi_ffmpreg.c - playing AVI files (ffmpeg backend) -Copyright (C) 2023 Alibek Omarov +Copyright (C) FTEQW developers (for plugins/avplug/avdecode.c) +Copyright (C) Sam Lantinga (for tests/testffmpeg.c) +Copyright (C) 2023-2024 Alibek Omarov This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From 2d1a885954921af53b7620ce1c29467277b66435 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 9 Dec 2024 23:46:36 +0300 Subject: [PATCH 07/14] common: render_api: use movie_state_s in AVI functions for easier type checking --- common/render_api.h | 14 +++++++------- engine/client/cl_render.c | 14 +++++++------- engine/client/client.h | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/common/render_api.h b/common/render_api.h index 4dd8896246..8e9f23d8ce 100644 --- a/common/render_api.h +++ b/common/render_api.h @@ -208,14 +208,14 @@ typedef struct render_api_s void (*R_EntityRemoveDecals)( struct model_s *mod ); // remove all the decals from specified entity (BSP only) // AVIkit support - void *(*AVI_LoadVideo)( const char *filename, qboolean load_audio ); - int (*AVI_GetVideoInfo)( void *Avi, int *xres, int *yres, float *duration ); // a1ba: changed longs to int - int (*AVI_GetVideoFrameNumber)( void *Avi, float time ); - byte *(*AVI_GetVideoFrame)( void *Avi, int frame ); + struct movie_state_s *(*AVI_LoadVideo)( const char *filename, qboolean load_audio ); + qboolean (*AVI_GetVideoInfo)( struct movie_state_s *Avi, int *xres, int *yres, float *duration ); // a1ba: changed longs to int + int (*AVI_GetVideoFrameNumber)( struct movie_state_s *Avi, float time ); + byte *(*AVI_GetVideoFrame)( struct movie_state_s *Avi, int frame ); void (*AVI_UploadRawFrame)( int texture, int cols, int rows, int width, int height, const byte *data ); - void (*AVI_FreeVideo)( void *Avi ); - int (*AVI_IsActive)( void *Avi ); - void (*AVI_StreamSound)( void *Avi, int entnum, float fvol, float attn, float synctime ); + void (*AVI_FreeVideo)( struct movie_state_s *Avi ); + qboolean (*AVI_IsActive)( struct movie_state_s *Avi ); + void (*AVI_StreamSound)( struct movie_state_s *Avi, int entnum, float fvol, float attn, float synctime ); qboolean (*AVI_Think)( struct movie_state_s *Avi ); qboolean (*AVI_SetParm)( struct movie_state_s *Avi, enum movie_parms_e parm, ... ); diff --git a/engine/client/cl_render.c b/engine/client/cl_render.c index 8ad4aded3b..121c4a7251 100644 --- a/engine/client/cl_render.c +++ b/engine/client/cl_render.c @@ -266,14 +266,14 @@ static render_api_t gRenderAPI = NULL, // DrawSingleDecal, NULL, // R_DecalSetupVerts, NULL, // R_EntityRemoveDecals, - (void*)AVI_LoadVideo, - (void*)AVI_GetVideoInfo, - (void*)AVI_GetVideoFrameNumber, - (void*)AVI_GetVideoFrame, + AVI_LoadVideo, + AVI_GetVideoInfo, + AVI_GetVideoFrameNumber, + AVI_GetVideoFrame, NULL, // R_UploadStretchRaw, - (void*)AVI_FreeVideo, - (void*)AVI_IsActive, - (void*)pfnAVI_StreamSound, + AVI_FreeVideo, + AVI_IsActive, + pfnAVI_StreamSound, AVI_Think, AVI_SetParm, NULL, // GL_Bind, diff --git a/engine/client/client.h b/engine/client/client.h index 476a8828d1..5c98761bc0 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -1115,7 +1115,7 @@ void Con_PageUp( int lines ); // // s_main.c // -void S_StreamAviSamples( void *Avi, int entnum, float fvol, float attn, float synctime ); +void S_StreamAviSamples( movie_state_t *Avi, int entnum, float fvol, float attn, float synctime ); void S_StartBackgroundTrack( const char *intro, const char *loop, int position, qboolean fullpath ); void S_StopBackgroundTrack( void ); void S_StreamSetPause( int pause ); From 1d78e400ff65d0dc50c4b9a25339829a3cf2ce62 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 11 Dec 2024 09:58:24 +0300 Subject: [PATCH 08/14] engine: sound: increase MAX_RAW_SAMPLES to 16384 to fit an audio track coming from the movies --- engine/client/sound.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/sound.h b/engine/client/sound.h index d0a6e812a3..0e14f07bbb 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -176,7 +176,7 @@ typedef struct #define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS) #define MAX_CHANNELS (256 + MAX_DYNAMIC_CHANNELS) // Scourge Of Armagon has too many static sounds on hip2m4.bsp #define MAX_RAW_CHANNELS 48 -#define MAX_RAW_SAMPLES 8192 +#define MAX_RAW_SAMPLES 16384 #define SND_CLIP_DISTANCE 1000.0f extern sound_t ambient_sfx[NUM_AMBIENTS]; From 4987080e1edfc932f9d25f1fa46c6ba0f9c8a03f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 13 Dec 2024 01:57:49 +0300 Subject: [PATCH 09/14] engine: client: remove unused S_RawSamples wrapper function --- engine/client/s_main.c | 14 -------------- engine/client/s_stream.c | 2 +- engine/client/sound.h | 1 - 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/engine/client/s_main.c b/engine/client/s_main.c index 512cc6a254..b97e038afb 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -1191,20 +1191,6 @@ void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word chan ch->leftvol = ch->rightvol = snd_vol; } -/* -=================== -S_RawSamples -=================== -*/ -void S_RawSamples( uint samples, uint rate, word width, word channels, const byte *data, int entnum ) -{ - int snd_vol = 128; - - if( entnum < 0 ) snd_vol = 256; // bg track or movie track - - S_RawEntSamples( entnum, samples, rate, width, channels, data, snd_vol ); -} - /* =================== S_FreeIdleRawChannels diff --git a/engine/client/s_stream.c b/engine/client/s_stream.c index 555df323cd..3c538b6b59 100644 --- a/engine/client/s_stream.c +++ b/engine/client/s_stream.c @@ -241,7 +241,7 @@ void S_StreamBackgroundTrack( void ) if( r > 0 ) { // add to raw buffer - S_RawSamples( fileSamples, info->rate, info->width, info->channels, raw, S_RAW_SOUND_BACKGROUNDTRACK ); + S_RawEntSamples( S_RAW_SOUND_BACKGROUNDTRACK, fileSamples, info->rate, info->width, info->channels, raw, 255 ); } else { diff --git a/engine/client/sound.h b/engine/client/sound.h index 0e14f07bbb..d6e965c7a8 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -245,7 +245,6 @@ sfx_t *S_GetSfxByHandle( sound_t handle ); rawchan_t *S_FindRawChannel( int entnum, qboolean create ); uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data ); void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol ); -void S_RawSamples( uint samples, uint rate, word width, word channels, const byte *data, int entnum ); void S_StopSound( int entnum, int channel, const char *soundname ); void S_UpdateFrame( struct ref_viewpass_s *rvp ); void S_StopAllSounds( qboolean ambient ); From c8bf7e5630fe923845541eadceb3b60ab0cb3347 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 13 Dec 2024 02:25:40 +0300 Subject: [PATCH 10/14] engine: avi: get rid of stubs file, temporarily put these stubs into avi_ffmpeg.c --- engine/client/avi/avi_ffmpeg.c | 175 +++++++++++++++++++++++---------- engine/client/avi/avi_stub.c | 92 ----------------- 2 files changed, 121 insertions(+), 146 deletions(-) diff --git a/engine/client/avi/avi_ffmpeg.c b/engine/client/avi/avi_ffmpeg.c index 132f0cf939..b8b6fc2e98 100644 --- a/engine/client/avi/avi_ffmpeg.c +++ b/engine/client/avi/avi_ffmpeg.c @@ -16,9 +16,13 @@ GNU General Public License for more details. */ #include "defaults.h" -#if XASH_AVI == AVI_FFMPEG #include "common.h" #include "client.h" + +static qboolean avi_initialized; +static poolhandle_t avi_mempool; + +#if XASH_AVI == AVI_FFMPEG #include #include #include @@ -74,10 +78,6 @@ struct movie_state_s byte quiet : 1; }; -static qboolean avi_initialized; -static poolhandle_t avi_mempool; -static movie_state_t avi[2]; - qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ) { qboolean ret = true; @@ -585,6 +585,122 @@ void AVI_CloseVideo( movie_state_t *Avi ) memset( Avi, 0, sizeof( *Avi )); } +static void AVI_PrintFFmpegVersion( void ) +{ + uint ver; + + // print version we're compiled with and which version we're running with + ver = avutil_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVUTIL_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + ver = avformat_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVFORMAT_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + ver = avformat_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVCODEC_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + ver = swscale_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBSWSCALE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + ver = swresample_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBSWRESAMPLE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); +} +#else +struct movie_state_s +{ + qboolean active; +}; + +int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) +{ + return 0; +} + +byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame ) +{ + return NULL; +} + +qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ) +{ + return false; +} + +qboolean AVI_HaveAudioTrack( const movie_state_t *Avi ) +{ + return false; +} + +void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) +{ + ; +} + +int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) +{ + return 0; +} + +void AVI_CloseVideo( movie_state_t *Avi ) +{ + ; +} + +qboolean AVI_Think( movie_state_t *Avi ) +{ + return false; +} + +qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ) +{ + return false; +} + +static void AVI_PrintFFmpegVersion( void ) +{ + +} +#endif // XASH_AVI == AVI_NULL + +static movie_state_t avi[2]; +movie_state_t *AVI_GetState( int num ) +{ + return &avi[num]; +} + +qboolean AVI_IsActive( movie_state_t *Avi ) +{ + return Avi ? Avi->active : false; +} + +qboolean AVI_Initailize( void ) +{ + if( XASH_AVI == AVI_NULL ) + { + Con_Printf( "AVI: Not supported\n" ); + return false; + } + + if( Sys_CheckParm( "-noavi" )) + { + Con_Printf( "AVI: Disabled\n" ); + return false; + } + + AVI_PrintFFmpegVersion(); + + avi_initialized = true; + avi_mempool = Mem_AllocPool( "AVI Zone" ); + + return false; +} + +void AVI_Shutdown( void ) +{ + Mem_FreePool( &avi_mempool ); + avi_initialized = XASH_AVI != AVI_NULL; +} + movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ) { movie_state_t *Avi; @@ -629,52 +745,3 @@ void AVI_FreeVideo( movie_state_t *Avi ) if( Mem_IsAllocatedExt( avi_mempool, Avi )) Mem_Free( Avi ); } - -qboolean AVI_IsActive( movie_state_t *Avi ) -{ - return Avi ? Avi->active : false; -} - -movie_state_t *AVI_GetState( int num ) -{ - return &avi[num]; -} - -qboolean AVI_Initailize( void ) -{ - uint ver; - - if( Sys_CheckParm( "-noavi" )) - { - Con_Printf( "AVI: Disabled\n" ); - return false; - } - - // print version we're compiled with and which version we're running with - ver = avutil_version(); - Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVUTIL_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); - - ver = avformat_version(); - Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVFORMAT_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); - - ver = avformat_version(); - Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVCODEC_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); - - ver = swscale_version(); - Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBSWSCALE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); - - ver = swresample_version(); - Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBSWRESAMPLE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); - - avi_initialized = true; - avi_mempool = Mem_AllocPool( "AVI Zone" ); - return true; -} - -void AVI_Shutdown( void ) -{ - Mem_FreePool( &avi_mempool ); - avi_initialized = false; -} - -#endif // XASH_AVI == AVI_NULL diff --git a/engine/client/avi/avi_stub.c b/engine/client/avi/avi_stub.c index c911e3e440..e69de29bb2 100644 --- a/engine/client/avi/avi_stub.c +++ b/engine/client/avi/avi_stub.c @@ -1,92 +0,0 @@ -/* -avi_stub.c - playing AVI files (stub) -Copyright (C) 2018 a1batross - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#include "defaults.h" -#if XASH_AVI == AVI_NULL -#include "common.h" - -int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) -{ - return 0; -} - -byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame ) -{ - return NULL; -} - -qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ) -{ - return false; -} - -qboolean AVI_GetAudioInfo( movie_state_t *Avi, wavdata_t *snd_info ) -{ - return false; -} - -int AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, int offset, int length ) -{ - return 0; -} - -void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) -{ - ; -} - -movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ) -{ - return NULL; -} - -int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) -{ - return 0; -} - -void AVI_CloseVideo( movie_state_t *Avi ) -{ - ; -} - -qboolean AVI_IsActive( movie_state_t *Avi ) -{ - return false; -} - -void AVI_FreeVideo( movie_state_t *Avi ) -{ - ; -} - -movie_state_t *AVI_GetState( int num ) -{ - return NULL; -} - -qboolean AVI_Initailize( void ) -{ - Con_Printf( "AVI: Not supported\n" ); - - return false; -} - -void AVI_Shutdown( void ) -{ - ; -} - -#endif // XASH_AVI == AVI_NULL From 8b4b0479c8b4da4faa1112f1847f33514c8259cb Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 13 Dec 2024 02:49:19 +0300 Subject: [PATCH 11/14] ci: enable ffmpeg by default for Windows and Linux x86 targets --- .github/workflows/c-cpp.yml | 2 ++ scripts/gha/build_linux.sh | 22 +++++++++++++++------- scripts/gha/build_win32.sh | 16 +++++++++++++--- scripts/gha/deps_linux.sh | 4 ++++ scripts/gha/deps_win32.sh | 17 ++++++++++++++++- scripts/lib.sh | 21 +++++++++++++++++++++ 6 files changed, 71 insertions(+), 11 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index e1b734fc68..8f6b0eb360 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -67,7 +67,9 @@ jobs: targetarch: amd64 env: SDL_VERSION: 2.30.9 + FFMPEG_VERSION: 7.1 GH_CPU_ARCH: ${{ matrix.targetarch }} + GH_CPU_OS: ${{ matrix.targetos }} GH_CROSSCOMPILING: ${{ matrix.cross }} steps: - name: Checkout diff --git a/scripts/gha/build_linux.sh b/scripts/gha/build_linux.sh index 3c14f06563..dc3d15fdd0 100755 --- a/scripts/gha/build_linux.sh +++ b/scripts/gha/build_linux.sh @@ -18,7 +18,7 @@ for i in arm64 armhf riscv64 ppc64el; do CROSS_COMPILE_CC[$i]=${ARCH_TRIPLET[$i]}-gcc CROSS_COMPILE_CXX[$i]=${ARCH_TRIPLET[$i]}-g++ done -export PKG_CONFIG_PATH=${ARCH_TRIPLET[$GH_CPU_ARCH]} +export PKG_CONFIG_PATH=$PWD/ffmpeg/lib/pkgconfig:${ARCH_TRIPLET[$GH_CPU_ARCH]} export CC=${CROSS_COMPILE_CC[$GH_CPU_ARCH]} export CXX=${CROSS_COMPILE_CXX[$GH_CPU_ARCH]} @@ -53,17 +53,21 @@ build_engine() cd "$BUILDDIR" || die if [ "$ARCH" = "amd64" ]; then # we need enabling 64-bit target only on Intel-compatible CPUs - AMD64="-8" + WAF_EXTRA_ARGS="-8" + fi + + if [ -d "ffmpeg" ]; then + WAF_EXTRA_ARGS+=" --enable-ffmpeg" fi if [ "$GH_CROSSCOMPILING" != "true" ]; then - ENABLE_TESTS="--enable-tests" + WAF_EXTRA_ARGS+=" --enable-tests" fi if [ "$1" = "dedicated" ]; then - ./waf configure $AMD64 $ENABLE_TESTS --enable-lto --enable-bundled-deps -d || die_configure + ./waf configure $WAF_EXTRA_ARGS --enable-lto --enable-bundled-deps -d || die_configure elif [ "$1" = "full" ]; then - ./waf configure $AMD64 $ENABLE_TESTS --enable-lto --enable-bundled-deps -s SDL2_linux --enable-stb --enable-utils || die_configure + ./waf configure $WAF_EXTRA_ARGS --enable-lto --enable-bundled-deps -s SDL2_linux --enable-stb --enable-utils || die_configure else die fi @@ -75,9 +79,13 @@ deploy_engine() { cd "$BUILDDIR" || die ./waf install --destdir="$APPDIR" || die - cp SDL2_linux/lib/libSDL2-2.0.so.0 "$APPDIR/" + cp -av SDL2_linux/lib/libSDL2-2.0.so.0 "$APPDIR/" if [ "$GH_CPU_ARCH" = "i386" ]; then - cp 3rdparty/vgui_support/vgui-dev/lib/vgui.so "$APPDIR/" + cp -av 3rdparty/vgui_support/vgui-dev/lib/vgui.so "$APPDIR/" + fi + + if [ -d "ffmpeg" ]; then + cp -av ffmpeg/lib/libav* ffmpeg/lib/libsw* "$APPDIR/" fi } diff --git a/scripts/gha/build_win32.sh b/scripts/gha/build_win32.sh index 3873dbd15e..e0778815c6 100755 --- a/scripts/gha/build_win32.sh +++ b/scripts/gha/build_win32.sh @@ -9,20 +9,30 @@ if [ "$ARCH" = "amd64" ]; then # we need enabling 64-bit target only on Intel-co AMD64="-8" fi +if [ -d "ffmpeg" ]; then + export PKGCONFIG="$PWD/pkgconf/bin/pkgconf.exe" + export PKG_CONFIG_PATH="$PWD/ffmpeg/lib/pkgconfig" + WAF_EXTRA_ARGS="--enable-ffmpeg" +fi + # NOTE: to build with other version use --msvc_version during configuration # NOTE: sometimes you may need to add WinSDK to %PATH% -./waf.bat configure -s "SDL2_VC" -T release --enable-utils --enable-tests --enable-lto $AMD64 || die_configure +./waf.bat configure -s "SDL2_VC" -T release --enable-utils --enable-tests --enable-lto $AMD64 $WAF_EXTRA_ARGS || die_configure ./waf.bat build || die ./waf.bat install --destdir=. || die if [ "$ARCH" = "i386" ]; then - cp SDL2_VC/lib/x86/SDL2.dll . # Install SDL2 + cp -v SDL2_VC/lib/x86/SDL2.dll . # Install SDL2 elif [ "$ARCH" = "amd64" ]; then - cp SDL2_VC/lib/x64/SDL2.dll . + cp -v SDL2_VC/lib/x64/SDL2.dll . else die fi +if [ -d "ffmpeg" ]; then + cp -v ffmpeg/bin/av* ffmpeg/bin/sw* . +fi + WINSDK_LATEST=$(ls -1 "C:/Program Files (x86)/Windows Kits/10/bin" | grep -E '^10' | sort -rV | head -n1) echo "Latest installed Windows SDK is $WINSDK_LATEST" diff --git a/scripts/gha/deps_linux.sh b/scripts/gha/deps_linux.sh index e933dae6b6..59efc0805d 100755 --- a/scripts/gha/deps_linux.sh +++ b/scripts/gha/deps_linux.sh @@ -70,5 +70,9 @@ if [ -n "${APPIMAGETOOL[$GH_CPU_ARCH]}" ]; then chmod +x appimagetool.AppImage fi +FFMPEG_ARCHIVE=$(get_ffmpeg_archive) +wget https://github.com/FWGS/FFmpeg-Builds/releases/download/latest/$FFMPEG_ARCHIVE.tar.xz -qO- | tar -xJf - +mv $FFMPEG_ARCHIVE ffmpeg + wget "https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/SDL2-$SDL_VERSION.tar.gz" -qO- | tar -xzf - mv "SDL2-$SDL_VERSION" SDL2_src diff --git a/scripts/gha/deps_win32.sh b/scripts/gha/deps_win32.sh index f73c47ea9f..29a9cccd4d 100755 --- a/scripts/gha/deps_win32.sh +++ b/scripts/gha/deps_win32.sh @@ -1,5 +1,20 @@ #!/bin/bash -curl http://libsdl.org/release/SDL2-devel-$SDL_VERSION-VC.zip -o SDL2.zip +. scripts/lib.sh + +curl -L http://libsdl.org/release/SDL2-devel-$SDL_VERSION-VC.zip -o SDL2.zip unzip -q SDL2.zip mv SDL2-$SDL_VERSION SDL2_VC + +curl -L https://github.com/FWGS/potential-meme/releases/download/prebuilts/mingw-w64-x86_64-pkgconf-1.2.3.0-1-any.pkg.tar.zst -o pkgconf.tar.zst +7z x pkgconf.tar.zst +7z x pkgconf.tar +rm pkgconf.tar* +mv mingw64 pkgconf + +FFMPEG_ARCHIVE=$(get_ffmpeg_archive) +curl -L https://github.com/FWGS/FFmpeg-Builds/releases/download/latest/$FFMPEG_ARCHIVE.zip -o ffmpeg.zip +if [ -f ffmpeg.zip ]; then + unzip -x ffmpeg.zip + mv $FFMPEG_ARCHIVE ffmpeg +fi diff --git a/scripts/lib.sh b/scripts/lib.sh index bc281338cf..cfe5ee1429 100644 --- a/scripts/lib.sh +++ b/scripts/lib.sh @@ -9,6 +9,27 @@ die_configure() die } +get_ffmpeg_archive() +{ + if [ "$GH_CPU_OS" == "win32" ]; then + A=win + else + A="$GH_CPU_OS" + fi + + if [ "$GH_CPU_ARCH" == "amd64" ]; then + B=64 + elif [ "$GH_CPU_ARCH" == "i386" ]; then + B=32 + else + B="$GH_CPU_ARCH" + fi + + FLAVOR=lgpl-shared-minimal + + echo "ffmpeg-n$FFMPEG_VERSION-latest-$A$B-$FLAVOR-$FFMPEG_VERSION" +} + if [ -n "$TRAVIS_BUILD_DIR" ]; then BUILDDIR=$TRAVIS_BUILD_DIR elif [ -n "$GITHUB_WORKSPACE" ]; then From 952ccb1247349970ca138672b45dadbc4e403a61 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 23 Jan 2025 18:55:48 +0300 Subject: [PATCH 12/14] engine: avi: very dumb pause --- common/render_api.h | 2 ++ engine/client/avi/avi_ffmpeg.c | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/common/render_api.h b/common/render_api.h index 8e9f23d8ce..0b7f672f25 100644 --- a/common/render_api.h +++ b/common/render_api.h @@ -169,6 +169,8 @@ enum movie_parms_e AVI_ENTNUM, // (int) entity number, -1 for no spatialization AVI_VOLUME, // (int) volume from 0 to 255 AVI_ATTN, // (float) attenuation value + AVI_PAUSE, // no argument, pauses playback + AVI_RESUME, // no argument, resumes playback }; struct movie_state_s; diff --git a/engine/client/avi/avi_ffmpeg.c b/engine/client/avi/avi_ffmpeg.c index b8b6fc2e98..4772494d0a 100644 --- a/engine/client/avi/avi_ffmpeg.c +++ b/engine/client/avi/avi_ffmpeg.c @@ -76,6 +76,7 @@ struct movie_state_s byte volume; byte active : 1; byte quiet : 1; + byte paused : 1; }; qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ) @@ -127,6 +128,12 @@ qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ) fval = va_arg( va, double ); Avi->attn = Q_max( 0.0f, fval ); break; + case AVI_PAUSE: + Avi->paused = true; + break; + case AVI_RESUME: + Avi->paused = false; + break; default: ret = false; } @@ -253,7 +260,7 @@ static void AVI_StreamAudio( movie_state_t *Avi ) int buffer_samples, file_samples, file_bytes; rawchan_t *ch = NULL; - // keep the same semantics, when S_RAW_SOUND_SOUNDTRACK doesn't play if S_SetStream wasn't enabled + // keep the same semantics, when S_RAW_SOUND_SOUNDTRACK doesn't play if S_StartStreaming wasn't enabled qboolean disable_stream = Avi->entnum == S_RAW_SOUND_SOUNDTRACK ? !s_listener.streaming : false; if( !dma.initialized || disable_stream || s_listener.paused || !Avi->cached_audio ) @@ -354,6 +361,13 @@ qboolean AVI_Think( movie_state_t *Avi ) if( !Avi->first_time ) // always remember at which timestamp we started playing Avi->first_time = curtime; + if( Avi->paused ) + { + // FIXME: there might be a better way to do this + Avi->last_time = curtime; + return true; + } + Con_NPrintf( 1, "cached_audio_buf_len = %zu", Avi->cached_audio_buf_len ); while( 1 ) // try to get multiple decoded frames to keep up when we're running at low fps @@ -380,7 +394,7 @@ qboolean AVI_Think( movie_state_t *Avi ) { res = avcodec_send_packet( Avi->video_ctx, Avi->pkt ); if( res < 0 ) - AVI_SpewAvError( Avi->quiet, "avcodec_send_packet (audio)", res ); + AVI_SpewAvError( Avi->quiet, "avcodec_send_packet (video)", res ); } av_packet_unref( Avi->pkt ); } From 32def35408e86d7ce003daba5a6445966fb9336f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 23 Jan 2025 18:56:44 +0300 Subject: [PATCH 13/14] engine: avi: remove stub file --- engine/client/avi/avi_stub.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 engine/client/avi/avi_stub.c diff --git a/engine/client/avi/avi_stub.c b/engine/client/avi/avi_stub.c deleted file mode 100644 index e69de29bb2..0000000000 From 004af02e99739c4d205e0aba4bc7de525aa647b4 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jan 2025 11:13:29 +0300 Subject: [PATCH 14/14] engine: avi: comment out debug messages --- engine/client/avi/avi_ffmpeg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/avi/avi_ffmpeg.c b/engine/client/avi/avi_ffmpeg.c index 4772494d0a..40a6932c75 100644 --- a/engine/client/avi/avi_ffmpeg.c +++ b/engine/client/avi/avi_ffmpeg.c @@ -368,7 +368,7 @@ qboolean AVI_Think( movie_state_t *Avi ) return true; } - Con_NPrintf( 1, "cached_audio_buf_len = %zu", Avi->cached_audio_buf_len ); + // Con_NPrintf( 1, "cached_audio_buf_len = %zu", Avi->cached_audio_buf_len ); while( 1 ) // try to get multiple decoded frames to keep up when we're running at low fps {