Merge ffmpeg support

This commit is contained in:
Alibek Omarov
2025-06-15 13:15:17 +05:00
22 changed files with 974 additions and 1033 deletions

View File

@@ -70,8 +70,10 @@ jobs:
targetos: apple
targetarch: amd64
env:
SDL_VERSION: 2.32.0
SDL_VERSION: 2.32.8
FFMPEG_VERSION: 7.1
GH_CPU_ARCH: ${{ matrix.targetarch }}
GH_CPU_OS: ${{ matrix.targetos }}
GH_CROSSCOMPILING: ${{ matrix.cross }}
steps:
- name: Checkout

View File

@@ -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 */

View File

@@ -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

View File

@@ -157,6 +157,23 @@ 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
AVI_PAUSE, // no argument, pauses playback
AVI_RESUME, // no argument, resumes playback
};
struct movie_state_s;
struct ref_viewpass_s;
typedef struct render_api_s
@@ -193,16 +210,16 @@ 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_Reserved0)( void ); // for potential interface expansion without broken compatibility
void (*AVI_Reserved1)( void );
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, ... );
// 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 );

View File

@@ -18,12 +18,11 @@ 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 );
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 );
@@ -34,4 +33,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

View File

@@ -0,0 +1,761 @@
/*
avi_ffmpreg.c - playing AVI files (ffmpeg backend)
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
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"
#include "common.h"
#include "client.h"
static qboolean avi_initialized;
static poolhandle_t avi_mempool;
#if XASH_AVI == AVI_FFMPEG
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
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;
byte paused : 1;
};
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;
case AVI_PAUSE:
Avi->paused = true;
break;
case AVI_RESUME:
Avi->paused = false;
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_HaveAudioTrack( const movie_state_t *Avi )
{
return Avi ? Avi->active && Avi->audio_ctx : false;
}
// just let it compile, bruh!
byte *AVI_GetVideoFrame( movie_state_t *Avi, int target )
{
return Avi->dst;
}
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_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 )
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;
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
{
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 (video)", 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 ));
}
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;
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 );
}

View File

@@ -1,90 +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 "build.h"
#if !XASH_WIN32
#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 )
{
return false;
}
void AVI_Shutdown( void )
{
;
}
#endif // WIN32

View File

@@ -1,732 +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 "build.h"
#if XASH_WIN32
#include "common.h"
#include "client.h"
#include <vfw.h> // 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 },
};
dll_info_t msvfw_dll = { "msvfw32.dll", msvfw_funcs, ARRAYSIZE( 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 },
};
dll_info_t msacm_dll = { "msacm32.dll", msacm_funcs, ARRAYSIZE( 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 },
};
dll_info_t avifile_dll = { "avifil32.dll", avifile_funcs, ARRAYSIZE( 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 // _WIN32

View File

@@ -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

View File

@@ -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 )

View File

@@ -233,7 +233,7 @@ static intptr_t pfnRenderGetParm( int parm, int arg )
return CL_RenderGetParm( parm, arg, true );
}
static void pfnAVI_StreamSound( void *avi, int entnum, float fvol, float attn, float synctime )
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
}
@@ -263,16 +263,16 @@ 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,
NULL,
NULL,
AVI_FreeVideo,
AVI_IsActive,
pfnAVI_StreamSound,
AVI_Think,
AVI_SetParm,
NULL, // GL_Bind,
NULL, // GL_SelectTexture,
NULL, // GL_LoadTexMatrixExt,

View File

@@ -24,11 +24,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 +83,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 +152,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 +167,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;
}
@@ -219,45 +200,28 @@ 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 );
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;
}
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
@@ -270,7 +234,6 @@ void SCR_StopCinematic( void )
AVI_CloseVideo( cin_state );
S_StopStreaming();
cin_time = 0.0f;
cls.state = ca_disconnected;
cls.signon = 0;

View File

@@ -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;
@@ -1193,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
@@ -1610,7 +1594,6 @@ void SND_UpdateSound( void )
}
S_StreamBackgroundTrack ();
S_StreamSoundTrack ();
// mix some sound
S_UpdateChannels ();

View File

@@ -242,7 +242,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
{
@@ -287,66 +287,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
}
}

View File

@@ -177,7 +177,8 @@ typedef int sound_t;
#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];
extern qboolean snd_ambient;
@@ -243,8 +244,8 @@ 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 );
void S_UpdateFrame( struct ref_viewpass_s *rvp );
void S_StopAllSounds( qboolean ambient );
@@ -264,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 );

View File

@@ -803,8 +803,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 );

View File

@@ -14,6 +14,21 @@ int main(int argc, char **argv)
{
backtrace(0, 0);
backtrace_symbols(0, 0);
}'''
FFMPEG_CHECK_FRAGMENT='''
#include <libswresample/swresample.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/avutil.h>
int main(int argc, char **argv)
{
swresample_version();
avformat_version();
avcodec_version();
swscale_version();
avutil_version();
return 0;
}
'''
@@ -42,6 +57,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 find_sdl(conf):
@@ -126,6 +144,21 @@ def configure(conf):
conf.define('ENGINE_DLL', 1)
conf.define_cond('XASH_ENGINE_TESTS', conf.options.ENGINE_TESTS)
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_STATIC_LIBS', conf.env.STATIC_LINKING)
conf.define_cond('XASH_CUSTOM_SWAP', conf.options.CUSTOM_SWAP)
conf.define_cond('XASH_NO_ASYNC_NS_RESOLVE', not have_async_resolve)
@@ -202,6 +235,13 @@ def build(bld):
source += bld.path.ant_glob(['platform/sdl%d/*.c' % bld.env.XASH_SDL])
defines += ['XASH_SDL=%d' % bld.env.XASH_SDL]
if bld.get_define('HAVE_FFMPEG'):
libs.append('SWRESAMPLE')
libs.append('AVCODEC')
libs.append('AVFORMAT')
libs.append('AVUTIL')
libs.append('SWSCALE')
# add client files
if not bld.env.DEDICATED:
source += bld.path.ant_glob('client/**/*.c')

View File

@@ -24,7 +24,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]}
@@ -59,14 +59,18 @@ 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
./waf configure $AMD64 $ENABLE_TESTS --enable-lto --enable-bundled-deps -s SDL2_linux --enable-stbtt --enable-utils --enable-tui --enable-dedicated || die_configure
./waf configure $WAF_EXTRA_ARGS --enable-lto --enable-bundled-deps -s SDL2_linux --enable-stbtt --enable-utils --enable-tui --enable-dedicated || die_configure
./waf build || die_configure
}
@@ -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
}

View File

@@ -5,27 +5,39 @@
# Build engine
cd $BUILDDIR
WAF_EXTRA_ARGS=""
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
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%
# NOTE: --enable-msvcdeps only used for CI builds, enabling it non-English versions of MSVC causes useless console spam
./waf.bat configure -s "SDL2_VC" -T release --enable-utils --enable-tests --enable-lto --enable-msvcdeps --enable-tui $AMD64 || die_configure
./waf.bat configure -s "SDL2_VC" -T release --enable-utils --enable-tests --enable-lto --enable-msvcdeps --enable-tui $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 SDL2_VC/lib/x86/SDL2.pdb .
cp -v SDL2_VC/lib/x86/SDL2.dll . # Install SDL2
cp -v SDL2_VC/lib/x86/SDL2.pdb .
elif [ "$ARCH" = "amd64" ]; then
cp SDL2_VC/lib/x64/SDL2.dll .
cp SDL2_VC/lib/x64/SDL2.pdb .
cp -v SDL2_VC/lib/x64/SDL2.dll .
cp -v SDL2_VC/lib/x64/SDL2.pdb .
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"

View File

@@ -85,6 +85,10 @@ 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

View File

@@ -1,9 +1,24 @@
#!/bin/bash
curl -L https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/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
if [ "$GH_CPU_ARCH" = "i386" ]; then
rustup target add i686-pc-windows-msvc
fi
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

View File

@@ -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