mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-10 22:22:10 +08:00
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.
This commit is contained in:
+17
-2
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <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;
|
||||
};
|
||||
|
||||
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
|
||||
@@ -79,6 +79,8 @@ movie_state_t *AVI_GetState( int num )
|
||||
|
||||
qboolean AVI_Initailize( void )
|
||||
{
|
||||
Con_Printf( "AVI: Not supported\n" );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+12
-27
@@ -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 )
|
||||
|
||||
@@ -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,
|
||||
|
||||
+21
-32
@@ -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;
|
||||
|
||||
@@ -8,6 +8,23 @@ import os
|
||||
|
||||
top = '.'
|
||||
|
||||
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;
|
||||
}
|
||||
'''
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user