mirror of
https://github.com/Q3Rally-Team/q3rally.git
synced 2024-12-15 23:01:24 +00:00
999 lines
25 KiB
C
999 lines
25 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 2008 Stefan Langer <raute@users.sourceforge.net>
|
|
|
|
This file is part of Spearmint Source Code.
|
|
|
|
Spearmint Source Code 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.
|
|
|
|
Spearmint Source Code 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Spearmint Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, Spearmint Source Code is also subject to certain additional terms.
|
|
You should have received a copy of these additional terms immediately following
|
|
the terms and conditions of the GNU General Public License. If not, please
|
|
request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional
|
|
terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc.,
|
|
Suite 120, Rockville, Maryland 20850 USA.
|
|
===========================================================================
|
|
*/
|
|
|
|
/*
|
|
|
|
This is a "ogm"-decoder to use a "better"(smaller files,higher resolutions) Cinematic-Format than roq
|
|
|
|
In this code "ogm" is only: ogg wrapper, vorbis audio, xvid video (or theora video)
|
|
(ogm(Ogg Media) in general is ogg wrapper with all kind of audio/video/subtitle/...)
|
|
|
|
... infos used for this src:
|
|
xvid:
|
|
* examples/xvid_decraw.c
|
|
* xvid.h
|
|
ogg/vobis:
|
|
* decoder_example.c (libvorbis src)
|
|
* libogg Documentation ( http://www.xiph.org/ogg/doc/libogg/ )
|
|
* VLC ogg demux ( http://trac.videolan.org/vlc/browser/trunk/modules/demux/ogg.c )
|
|
theora:
|
|
* theora doxygen docs (1.0beta1)
|
|
*/
|
|
|
|
#if defined(USE_CODEC_VORBIS) && (defined(USE_CIN_XVID) || defined(USE_CIN_THEORA))
|
|
|
|
#include <ogg/ogg.h>
|
|
#include <vorbis/codec.h>
|
|
|
|
#ifdef USE_CIN_XVID
|
|
#include <xvid.h>
|
|
#endif
|
|
#ifdef USE_CIN_THEORA
|
|
#include <theora/theora.h>
|
|
#endif
|
|
|
|
#include "client.h"
|
|
#include "snd_local.h"
|
|
|
|
#define OGG_BUFFER_SIZE 8*1024 //4096
|
|
|
|
typedef struct
|
|
{
|
|
fileHandle_t ogmFile;
|
|
|
|
ogg_sync_state oy; /* sync and verify incoming physical bitstream */
|
|
//ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */
|
|
ogg_stream_state os_audio;
|
|
ogg_stream_state os_video;
|
|
|
|
vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
|
|
vorbis_info vi; /* struct that stores all the static vorbis bitstream settings */
|
|
vorbis_comment vc; /* struct that stores all the bitstream user comments */
|
|
|
|
qboolean videoStreamIsXvid; //FIXME: atm there isn't realy a check for this (all "video" streams are handelt as xvid, because xvid support more than one "subtype")
|
|
#ifdef USE_CIN_XVID
|
|
xvid_dec_stats_t xvid_dec_stats;
|
|
void *xvid_dec_handle;
|
|
#endif
|
|
qboolean videoStreamIsTheora;
|
|
#ifdef USE_CIN_THEORA
|
|
theora_info th_info; // dump_video.c(example decoder): ti
|
|
theora_comment th_comment; // dump_video.c(example decoder): tc
|
|
theora_state th_state; // dump_video.c(example decoder): td
|
|
|
|
yuv_buffer th_yuvbuffer;
|
|
#endif
|
|
|
|
unsigned char *outputBuffer;
|
|
int outputWidht;
|
|
int outputHeight;
|
|
int outputBufferSize; // in Pixel (so "real Bytesize" = outputBufferSize*4)
|
|
int VFrameCount; // output video-stream
|
|
ogg_int64_t Vtime_unit;
|
|
int currentTime; // input from Run-function
|
|
} cin_ogm_t;
|
|
|
|
static cin_ogm_t g_ogm;
|
|
|
|
int nextNeededVFrame(void);
|
|
|
|
|
|
/* ####################### #######################
|
|
|
|
XVID
|
|
|
|
*/
|
|
#ifdef USE_CIN_XVID
|
|
|
|
#define BPP 4
|
|
|
|
static int init_xvid(void)
|
|
{
|
|
int ret;
|
|
|
|
xvid_gbl_init_t xvid_gbl_init;
|
|
xvid_dec_create_t xvid_dec_create;
|
|
|
|
/* Reset the structure with zeros */
|
|
memset(&xvid_gbl_init, 0, sizeof(xvid_gbl_init_t));
|
|
memset(&xvid_dec_create, 0, sizeof(xvid_dec_create_t));
|
|
|
|
/* Version */
|
|
xvid_gbl_init.version = XVID_VERSION;
|
|
|
|
xvid_gbl_init.cpu_flags = 0;
|
|
xvid_gbl_init.debug = 0;
|
|
|
|
xvid_global(NULL, 0, &xvid_gbl_init, NULL);
|
|
|
|
/* Version */
|
|
xvid_dec_create.version = XVID_VERSION;
|
|
|
|
/*
|
|
* Image dimensions -- set to 0, xvidcore will resize when ever it is
|
|
* needed
|
|
*/
|
|
xvid_dec_create.width = 0;
|
|
xvid_dec_create.height = 0;
|
|
|
|
ret = xvid_decore(NULL, XVID_DEC_CREATE, &xvid_dec_create, NULL);
|
|
|
|
g_ogm.xvid_dec_handle = xvid_dec_create.handle;
|
|
|
|
return (ret);
|
|
}
|
|
|
|
static int dec_xvid(unsigned char *input, int input_size)
|
|
{
|
|
int ret;
|
|
|
|
xvid_dec_frame_t xvid_dec_frame;
|
|
|
|
/* Reset all structures */
|
|
memset(&xvid_dec_frame, 0, sizeof(xvid_dec_frame_t));
|
|
memset(&g_ogm.xvid_dec_stats, 0, sizeof(xvid_dec_stats_t));
|
|
|
|
/* Set version */
|
|
xvid_dec_frame.version = XVID_VERSION;
|
|
g_ogm.xvid_dec_stats.version = XVID_VERSION;
|
|
|
|
/* No general flags to set */
|
|
xvid_dec_frame.general = XVID_LOWDELAY; //0;
|
|
|
|
/* Input stream */
|
|
xvid_dec_frame.bitstream = input;
|
|
xvid_dec_frame.length = input_size;
|
|
|
|
/* Output frame structure */
|
|
xvid_dec_frame.output.plane[0] = g_ogm.outputBuffer;
|
|
xvid_dec_frame.output.stride[0] = g_ogm.outputWidht * BPP;
|
|
if(g_ogm.outputBuffer == NULL)
|
|
xvid_dec_frame.output.csp = XVID_CSP_NULL;
|
|
else
|
|
xvid_dec_frame.output.csp = XVID_CSP_RGBA; // example was with XVID_CSP_I420
|
|
|
|
ret = xvid_decore(g_ogm.xvid_dec_handle, XVID_DEC_DECODE, &xvid_dec_frame, &g_ogm.xvid_dec_stats);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
static int shutdown_xvid(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
if(g_ogm.xvid_dec_handle)
|
|
ret = xvid_decore(g_ogm.xvid_dec_handle, XVID_DEC_DESTROY, NULL, NULL);
|
|
|
|
return (ret);
|
|
}
|
|
#endif
|
|
|
|
/* ####################### #######################
|
|
|
|
OGG/OGM
|
|
... also calls to vorbis/theora-libs
|
|
|
|
*/
|
|
|
|
/*
|
|
loadBlockToSync
|
|
|
|
return:
|
|
!0 -> no data transferred
|
|
*/
|
|
static int loadBlockToSync(void)
|
|
{
|
|
int r = -1;
|
|
char *buffer;
|
|
int bytes;
|
|
|
|
if(g_ogm.ogmFile)
|
|
{
|
|
buffer = ogg_sync_buffer(&g_ogm.oy, OGG_BUFFER_SIZE);
|
|
bytes = FS_Read(buffer, OGG_BUFFER_SIZE, g_ogm.ogmFile);
|
|
ogg_sync_wrote(&g_ogm.oy, bytes);
|
|
|
|
r = (bytes == 0);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
loadPagesToStreams
|
|
|
|
return:
|
|
!0 -> no data transferred (or not for all Streams)
|
|
*/
|
|
static int loadPagesToStreams(void)
|
|
{
|
|
int r = -1;
|
|
int AudioPages = 0;
|
|
int VideoPages = 0;
|
|
ogg_stream_state *osptr = NULL;
|
|
ogg_page og;
|
|
|
|
while(!AudioPages || !VideoPages)
|
|
{
|
|
if(ogg_sync_pageout(&g_ogm.oy, &og) != 1)
|
|
break;
|
|
|
|
if(g_ogm.os_audio.serialno == ogg_page_serialno(&og))
|
|
{
|
|
osptr = &g_ogm.os_audio;
|
|
++AudioPages;
|
|
}
|
|
if(g_ogm.os_video.serialno == ogg_page_serialno(&og))
|
|
{
|
|
osptr = &g_ogm.os_video;
|
|
++VideoPages;
|
|
}
|
|
|
|
if(osptr != NULL)
|
|
{
|
|
ogg_stream_pagein(osptr, &og);
|
|
}
|
|
}
|
|
|
|
if(AudioPages && VideoPages)
|
|
r = 0;
|
|
|
|
return r;
|
|
}
|
|
|
|
#define SIZEOF_RAWBUFF 4*1024
|
|
static byte rawBuffer[SIZEOF_RAWBUFF];
|
|
|
|
#define MIN_AUDIO_PRELOAD 400 // in ms
|
|
#define MAX_AUDIO_PRELOAD 500 // in ms
|
|
|
|
|
|
/*
|
|
|
|
return: audio wants more packets
|
|
*/
|
|
static qboolean loadAudio(void)
|
|
{
|
|
qboolean anyDataTransferred = qtrue;
|
|
float **pcm;
|
|
float *right, *left;
|
|
int samples, samplesNeeded;
|
|
int i;
|
|
short *ptr;
|
|
ogg_packet op;
|
|
vorbis_block vb;
|
|
|
|
memset(&op, 0, sizeof(op));
|
|
memset(&vb, 0, sizeof(vb));
|
|
vorbis_block_init(&g_ogm.vd, &vb);
|
|
|
|
while(anyDataTransferred && g_ogm.currentTime + MAX_AUDIO_PRELOAD > (int)(g_ogm.vd.granulepos * 1000 / g_ogm.vi.rate))
|
|
{
|
|
anyDataTransferred = qfalse;
|
|
|
|
if((samples = vorbis_synthesis_pcmout(&g_ogm.vd, &pcm)) > 0)
|
|
{
|
|
// vorbis -> raw
|
|
ptr = (short *)rawBuffer;
|
|
samplesNeeded = (SIZEOF_RAWBUFF) / (2 * 2); // (width*channel)
|
|
if(samples < samplesNeeded)
|
|
samplesNeeded = samples;
|
|
|
|
left = pcm[0];
|
|
right = (g_ogm.vi.channels > 1) ? pcm[1] : pcm[0];
|
|
for(i = 0; i < samplesNeeded; ++i)
|
|
{
|
|
ptr[0] = (left[i] >= -1.0f &&
|
|
left[i] <= 1.0f) ? left[i] * 32767.f : 32767 * ((left[i] > 0.0f) - (left[i] < 0.0f));
|
|
ptr[1] = (right[i] >= -1.0f &&
|
|
right[i] <= 1.0f) ? right[i] * 32767.f : 32767 * ((right[i] > 0.0f) - (right[i] < 0.0f));
|
|
ptr += 2; //numChans;
|
|
}
|
|
|
|
if(i > 0)
|
|
{
|
|
// tell libvorbis how many samples we actually consumed
|
|
vorbis_synthesis_read(&g_ogm.vd, i);
|
|
|
|
// S_RawSamples(ssize, 22050, 2, 2, (byte *)sbuf, 1.0f, -1);
|
|
S_RawSamples(0, i, g_ogm.vi.rate, 2, 2, rawBuffer, 1.0f, -1);
|
|
|
|
anyDataTransferred = qtrue;
|
|
}
|
|
}
|
|
|
|
if(!anyDataTransferred)
|
|
{
|
|
// op -> vorbis
|
|
if(ogg_stream_packetout(&g_ogm.os_audio, &op))
|
|
{
|
|
if(vorbis_synthesis(&vb, &op) == 0)
|
|
vorbis_synthesis_blockin(&g_ogm.vd, &vb);
|
|
anyDataTransferred = qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
vorbis_block_clear(&vb);
|
|
|
|
if(g_ogm.currentTime + MIN_AUDIO_PRELOAD > (int)(g_ogm.vd.granulepos * 1000 / g_ogm.vi.rate))
|
|
return qtrue;
|
|
else
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
|
|
return: 1 -> loaded a new Frame ( g_ogm.outputBuffer points to the actual frame )
|
|
0 -> no new Frame
|
|
<0 -> error
|
|
*/
|
|
#ifdef USE_CIN_XVID
|
|
static int loadVideoFrameXvid(void)
|
|
{
|
|
int r = 0;
|
|
ogg_packet op;
|
|
int used_bytes = 0;
|
|
|
|
memset(&op, 0, sizeof(op));
|
|
|
|
while(!r && (ogg_stream_packetout(&g_ogm.os_video, &op)))
|
|
{
|
|
used_bytes = dec_xvid(op.packet, op.bytes);
|
|
if(g_ogm.xvid_dec_stats.type == XVID_TYPE_VOL)
|
|
{
|
|
if(g_ogm.outputWidht != g_ogm.xvid_dec_stats.data.vol.width ||
|
|
g_ogm.outputHeight != g_ogm.xvid_dec_stats.data.vol.height)
|
|
{
|
|
g_ogm.outputWidht = g_ogm.xvid_dec_stats.data.vol.width;
|
|
g_ogm.outputHeight = g_ogm.xvid_dec_stats.data.vol.height;
|
|
Com_DPrintf("[XVID]new resolution %dx%d\n", g_ogm.outputWidht, g_ogm.outputHeight);
|
|
}
|
|
|
|
if(g_ogm.outputBufferSize < g_ogm.xvid_dec_stats.data.vol.width * g_ogm.xvid_dec_stats.data.vol.height)
|
|
{
|
|
|
|
g_ogm.outputBufferSize = g_ogm.xvid_dec_stats.data.vol.width * g_ogm.xvid_dec_stats.data.vol.height;
|
|
|
|
/* Free old output buffer */
|
|
if(g_ogm.outputBuffer)
|
|
free(g_ogm.outputBuffer);
|
|
|
|
/* Allocate the new buffer */
|
|
g_ogm.outputBuffer = (unsigned char *)malloc(g_ogm.outputBufferSize * 4); //FIXME? should the 4 stay for BPP?
|
|
if(g_ogm.outputBuffer == NULL)
|
|
{
|
|
g_ogm.outputBufferSize = 0;
|
|
r = -2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// use the rest of this packet
|
|
used_bytes += dec_xvid(op.packet + used_bytes, op.bytes - used_bytes);
|
|
}
|
|
|
|
// we got a real output frame ...
|
|
if(g_ogm.xvid_dec_stats.type > 0)
|
|
{
|
|
r = 1;
|
|
|
|
++g_ogm.VFrameCount;
|
|
// Com_Printf("frame infos: %d %d %d\n", xvid_dec_stats.data.vop.general, xvid_dec_stats.data.vop.time_base, xvid_dec_stats.data.vop.time_increment);
|
|
// Com_Printf("frame info time: %d (Frame# %d, %d)\n", xvid_dec_stats.data.vop.time_base, VFrameCount, (int)(VFrameCount*Vtime_unit/10000000));
|
|
}
|
|
|
|
// if((op.bytes-used_bytes)>0)
|
|
// Com_Printf("unused: %d(firstChar: %X)\n",(op.bytes-used_bytes),(int)(op.packet[used_bytes]));
|
|
}
|
|
|
|
return r;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
|
|
return: 1 -> loaded a new Frame ( g_ogm.outputBuffer points to the actual frame )
|
|
0 -> no new Frame
|
|
<0 -> error
|
|
*/
|
|
#ifdef USE_CIN_THEORA
|
|
/*
|
|
how many >> are needed to make y==x (shifting y>>i)
|
|
return: -1 -> no match
|
|
>=0 -> number of shifts
|
|
*/
|
|
static int findSizeShift(int x, int y)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; (y >> i); ++i)
|
|
if(x == (y >> i))
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int loadVideoFrameTheora(void)
|
|
{
|
|
int r = 0;
|
|
ogg_packet op;
|
|
|
|
memset(&op, 0, sizeof(op));
|
|
|
|
while(!r && (ogg_stream_packetout(&g_ogm.os_video, &op)))
|
|
{
|
|
ogg_int64_t th_frame;
|
|
|
|
theora_decode_packetin(&g_ogm.th_state, &op);
|
|
|
|
th_frame = theora_granule_frame(&g_ogm.th_state, g_ogm.th_state.granulepos);
|
|
|
|
if((g_ogm.VFrameCount < th_frame && th_frame >= nextNeededVFrame()) || !g_ogm.outputBuffer)
|
|
{
|
|
// int i,j;
|
|
int yWShift, uvWShift;
|
|
int yHShift, uvHShift;
|
|
|
|
if(theora_decode_YUVout(&g_ogm.th_state, &g_ogm.th_yuvbuffer))
|
|
continue;
|
|
|
|
if(g_ogm.outputWidht != g_ogm.th_info.width || g_ogm.outputHeight != g_ogm.th_info.height)
|
|
{
|
|
g_ogm.outputWidht = g_ogm.th_info.width;
|
|
g_ogm.outputHeight = g_ogm.th_info.height;
|
|
Com_DPrintf("[Theora(ogg)]new resolution %dx%d\n", g_ogm.outputWidht, g_ogm.outputHeight);
|
|
}
|
|
|
|
if(g_ogm.outputBufferSize < g_ogm.th_info.width * g_ogm.th_info.height)
|
|
{
|
|
|
|
g_ogm.outputBufferSize = g_ogm.th_info.width * g_ogm.th_info.height;
|
|
|
|
/* Free old output buffer */
|
|
if(g_ogm.outputBuffer)
|
|
free(g_ogm.outputBuffer);
|
|
|
|
/* Allocate the new buffer */
|
|
g_ogm.outputBuffer = (unsigned char *)malloc(g_ogm.outputBufferSize * 4);
|
|
if(g_ogm.outputBuffer == NULL)
|
|
{
|
|
g_ogm.outputBufferSize = 0;
|
|
r = -2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
yWShift = findSizeShift(g_ogm.th_yuvbuffer.y_width, g_ogm.th_info.width);
|
|
uvWShift = findSizeShift(g_ogm.th_yuvbuffer.uv_width, g_ogm.th_info.width);
|
|
yHShift = findSizeShift(g_ogm.th_yuvbuffer.y_height, g_ogm.th_info.height);
|
|
uvHShift = findSizeShift(g_ogm.th_yuvbuffer.uv_height, g_ogm.th_info.height);
|
|
|
|
if(yWShift < 0 || uvWShift < 0 || yHShift < 0 || uvHShift < 0)
|
|
{
|
|
Com_Printf("[Theora] unexpected resolution in a yuv-Frame\n");
|
|
r = -1;
|
|
}
|
|
else
|
|
{
|
|
|
|
Frame_yuv_to_rgb24(g_ogm.th_yuvbuffer.y, g_ogm.th_yuvbuffer.u, g_ogm.th_yuvbuffer.v,
|
|
g_ogm.th_info.width, g_ogm.th_info.height, g_ogm.th_yuvbuffer.y_stride,
|
|
g_ogm.th_yuvbuffer.uv_stride, yWShift, uvWShift, yHShift, uvHShift,
|
|
(unsigned int *)g_ogm.outputBuffer);
|
|
|
|
/* unsigned char* pixelPtr = g_ogm.outputBuffer;
|
|
unsigned int* pixPtr;
|
|
pixPtr = (unsigned int*)g_ogm.outputBuffer;
|
|
|
|
//TODO: use one yuv->rgb funktion for the hole frame (the big amout of stack movement(yuv->rgb calls) couldn't be good ;) )
|
|
for(j=0;j<g_ogm.th_info.height;++j) {
|
|
for(i=0;i<g_ogm.th_info.width;++i) {
|
|
#if 1
|
|
// simple grayscale-output ^^
|
|
pixelPtr[0] =
|
|
pixelPtr[1] =
|
|
pixelPtr[2] = g_ogm.th_yuvbuffer.y[i+j*g_ogm.th_yuvbuffer.y_stride];
|
|
pixelPtr+=4;
|
|
|
|
#else
|
|
// using RoQ yuv->rgb code
|
|
*pixPtr++ = yuv_to_rgb24( g_ogm.th_yuvbuffer.y[(i>>yWShift)+(j>>yHShift)*g_ogm.th_yuvbuffer.y_stride],
|
|
g_ogm.th_yuvbuffer.u[(i>>uvWShift)+(j>>uvHShift)*g_ogm.th_yuvbuffer.uv_stride],
|
|
g_ogm.th_yuvbuffer.v[(i>>uvWShift)+(j>>uvHShift)*g_ogm.th_yuvbuffer.uv_stride]);
|
|
#endif
|
|
}
|
|
}
|
|
*/
|
|
|
|
r = 1;
|
|
g_ogm.VFrameCount = th_frame;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
return r;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
|
|
return: 1 -> loaded a new Frame ( g_ogm.outputBuffer points to the actual frame )
|
|
0 -> no new Frame
|
|
<0 -> error
|
|
*/
|
|
static int loadVideoFrame(void)
|
|
{
|
|
#ifdef USE_CIN_XVID
|
|
if(g_ogm.videoStreamIsXvid)
|
|
return loadVideoFrameXvid();
|
|
#endif
|
|
#ifdef USE_CIN_THEORA
|
|
if(g_ogm.videoStreamIsTheora)
|
|
return loadVideoFrameTheora();
|
|
#endif
|
|
|
|
// if we come to this point, there will be no codec that use the stream content ...
|
|
if(g_ogm.os_video.serialno)
|
|
{
|
|
ogg_packet op;
|
|
|
|
while(ogg_stream_packetout(&g_ogm.os_video, &op));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
|
|
return: qtrue => noDataTransferred
|
|
*/
|
|
static qboolean loadFrame(void)
|
|
{
|
|
qboolean anyDataTransferred = qtrue;
|
|
qboolean needVOutputData = qtrue;
|
|
|
|
// qboolean audioSDone = qfalse;
|
|
// qboolean videoSDone = qfalse;
|
|
qboolean audioWantsMoreData = qfalse;
|
|
int status;
|
|
|
|
while(anyDataTransferred && (needVOutputData || audioWantsMoreData))
|
|
{
|
|
anyDataTransferred = qfalse;
|
|
|
|
// xvid -> "gl" ? videoDone : needPacket
|
|
// vorbis -> raw sound ? audioDone : needPacket
|
|
// anyDataTransferred = videoDone && audioDone;
|
|
// needVOutputData = videoDone && audioDone;
|
|
// if needPacket
|
|
{
|
|
// videoStream -> xvid ? videoStreamDone : needPage
|
|
// audioSteam -> vorbis ? audioStreamDone : needPage
|
|
// anyDataTransferred = audioStreamDone && audioStreamDone;
|
|
|
|
if(needVOutputData && (status = loadVideoFrame()))
|
|
{
|
|
needVOutputData = qfalse;
|
|
if(status > 0)
|
|
anyDataTransferred = qtrue;
|
|
else
|
|
anyDataTransferred = qfalse; // error (we don't need any videodata and we had no transferred)
|
|
}
|
|
|
|
// if needPage
|
|
if(needVOutputData || audioWantsMoreData)
|
|
{
|
|
// try to transfer Pages to the audio- and video-Stream
|
|
if(loadPagesToStreams())
|
|
{
|
|
// try to load a datablock from file
|
|
anyDataTransferred |= !loadBlockToSync();
|
|
}
|
|
else
|
|
anyDataTransferred = qtrue; // successful loadPagesToStreams()
|
|
}
|
|
|
|
// load all Audio after loading new pages ...
|
|
if(g_ogm.VFrameCount > 1) // wait some videoframes (it's better to have some delay, than a lagy sound)
|
|
audioWantsMoreData = loadAudio();
|
|
}
|
|
}
|
|
|
|
// ogg_packet_clear(&op);
|
|
|
|
return !anyDataTransferred;
|
|
}
|
|
|
|
//from VLC ogg.c ( http://trac.videolan.org/vlc/browser/trunk/modules/demux/ogg.c )
|
|
typedef struct
|
|
{
|
|
char streamtype[8];
|
|
char subtype[4];
|
|
|
|
ogg_int32_t size; /* size of the structure */
|
|
|
|
ogg_int64_t time_unit; /* in reference time */// in 10^-7 seconds (dT between frames)
|
|
ogg_int64_t samples_per_unit;
|
|
ogg_int32_t default_len; /* in media time */
|
|
|
|
ogg_int32_t buffersize;
|
|
ogg_int16_t bits_per_sample;
|
|
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
ogg_int32_t width;
|
|
ogg_int32_t height;
|
|
} stream_header_video;
|
|
|
|
struct
|
|
{
|
|
ogg_int16_t channels;
|
|
ogg_int16_t blockalign;
|
|
ogg_int32_t avgbytespersec;
|
|
} stream_header_audio;
|
|
} sh;
|
|
} stream_header_t;
|
|
|
|
qboolean isPowerOf2(int x)
|
|
{
|
|
int bitsSet = 0;
|
|
int i;
|
|
|
|
for(i = 0; i < sizeof(int) * 8; ++i)
|
|
if(x & (1 << i))
|
|
++bitsSet;
|
|
|
|
return (bitsSet <= 1);
|
|
}
|
|
|
|
/*
|
|
|
|
return: 0 -> no problem
|
|
*/
|
|
//TODO: vorbis/theora-header&init in sub-functions
|
|
//TODO: "clean" error-returns ...
|
|
int Cin_OGM_Init(const char *filename)
|
|
{
|
|
int status;
|
|
ogg_page og;
|
|
ogg_packet op;
|
|
int i;
|
|
|
|
if(g_ogm.ogmFile)
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: it seams there was already a ogm running, it will be killed to start %s\n", filename);
|
|
Cin_OGM_Shutdown();
|
|
}
|
|
|
|
memset(&g_ogm, 0, sizeof(cin_ogm_t));
|
|
|
|
FS_FOpenFileRead(filename, &g_ogm.ogmFile, qtrue);
|
|
if(!g_ogm.ogmFile)
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: Can't open ogm-file for reading (%s)\n", filename);
|
|
return -1;
|
|
}
|
|
|
|
ogg_sync_init(&g_ogm.oy); /* Now we can read pages */
|
|
|
|
//FIXME? can serialno be 0 in ogg? (better way to check inited?)
|
|
//TODO: support for more than one audio stream? / detect files with one stream(or without correct ones)
|
|
while(!g_ogm.os_audio.serialno || !g_ogm.os_video.serialno)
|
|
{
|
|
if(ogg_sync_pageout(&g_ogm.oy, &og) == 1)
|
|
{
|
|
if(strstr((char *)(og.body + 1), "vorbis"))
|
|
{
|
|
//FIXME? better way to find audio stream
|
|
if(g_ogm.os_audio.serialno)
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: more than one audio stream, in ogm-file(%s) ... we will stay at the first one\n", filename);
|
|
}
|
|
else
|
|
{
|
|
ogg_stream_init(&g_ogm.os_audio, ogg_page_serialno(&og));
|
|
ogg_stream_pagein(&g_ogm.os_audio, &og);
|
|
}
|
|
}
|
|
#ifdef USE_CIN_THEORA
|
|
if(strstr((char *)(og.body + 1), "theora"))
|
|
{
|
|
if(g_ogm.os_video.serialno)
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: more than one video stream, in ogm-file(%s) ... we will stay at the first one\n", filename);
|
|
}
|
|
else
|
|
{
|
|
g_ogm.videoStreamIsTheora = qtrue;
|
|
ogg_stream_init(&g_ogm.os_video, ogg_page_serialno(&og));
|
|
ogg_stream_pagein(&g_ogm.os_video, &og);
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_CIN_XVID
|
|
if(strstr((char *)(og.body + 1), "video"))
|
|
{ //FIXME? better way to find video stream
|
|
if(g_ogm.os_video.serialno)
|
|
{
|
|
Com_Printf("more than one video stream, in ogm-file(%s) ... we will stay at the first one\n", filename);
|
|
}
|
|
else
|
|
{
|
|
stream_header_t *sh;
|
|
|
|
g_ogm.videoStreamIsXvid = qtrue;
|
|
|
|
sh = (stream_header_t *) (og.body + 1);
|
|
//TODO: one solution for checking xvid and theora
|
|
if(!isPowerOf2(sh->sh.stream_header_video.width))
|
|
{
|
|
Com_Printf("VideoWidth of the ogm-file isn't a power of 2 value (%s)\n", filename);
|
|
|
|
return -5;
|
|
}
|
|
if(!isPowerOf2(sh->sh.stream_header_video.height))
|
|
{
|
|
Com_Printf("VideoHeight of the ogm-file isn't a power of 2 value (%s)\n", filename);
|
|
|
|
return -6;
|
|
}
|
|
|
|
g_ogm.Vtime_unit = sh->time_unit;
|
|
|
|
ogg_stream_init(&g_ogm.os_video, ogg_page_serialno(&og));
|
|
ogg_stream_pagein(&g_ogm.os_video, &og);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else if(loadBlockToSync())
|
|
break;
|
|
}
|
|
|
|
if(g_ogm.videoStreamIsXvid && g_ogm.videoStreamIsTheora)
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: Found \"video\"- and \"theora\"-stream ,ogm-file (%s)\n", filename);
|
|
return -2;
|
|
}
|
|
|
|
#if 1
|
|
if(!g_ogm.os_audio.serialno)
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: Haven't found a audio(vorbis) stream in ogm-file (%s)\n", filename);
|
|
return -2;
|
|
}
|
|
#endif
|
|
if(!g_ogm.os_video.serialno)
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: Haven't found a video stream in ogm-file (%s)\n", filename);
|
|
return -3;
|
|
}
|
|
|
|
//load vorbis header
|
|
vorbis_info_init(&g_ogm.vi);
|
|
vorbis_comment_init(&g_ogm.vc);
|
|
i = 0;
|
|
while(i < 3)
|
|
{
|
|
status = ogg_stream_packetout(&g_ogm.os_audio, &op);
|
|
if(status < 0)
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: Corrupt ogg packet while loading vorbis-headers, ogm-file(%s)\n", filename);
|
|
return -8;
|
|
}
|
|
if(status > 0)
|
|
{
|
|
status = vorbis_synthesis_headerin(&g_ogm.vi, &g_ogm.vc, &op);
|
|
if(i == 0 && status < 0)
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: This Ogg bitstream does not contain Vorbis audio data, ogm-file(%s)\n", filename);
|
|
return -9;
|
|
}
|
|
++i;
|
|
}
|
|
else if(loadPagesToStreams())
|
|
{
|
|
if(loadBlockToSync())
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: Couldn't find all vorbis headers before end of ogm-file (%s)\n", filename);
|
|
return -10;
|
|
}
|
|
}
|
|
}
|
|
|
|
vorbis_synthesis_init(&g_ogm.vd, &g_ogm.vi);
|
|
|
|
#ifdef USE_CIN_XVID
|
|
status = init_xvid();
|
|
if(status)
|
|
{
|
|
Com_Printf("[Xvid]Decore INIT problem, return value %d(ogm-file: %s)\n", status, filename);
|
|
|
|
return -4;
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_CIN_THEORA
|
|
if(g_ogm.videoStreamIsTheora)
|
|
{
|
|
ROQ_GenYUVTables();
|
|
|
|
theora_info_init(&g_ogm.th_info);
|
|
theora_comment_init(&g_ogm.th_comment);
|
|
|
|
i = 0;
|
|
while(i < 3)
|
|
{
|
|
status = ogg_stream_packetout(&g_ogm.os_video, &op);
|
|
if(status < 0)
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: Corrupt ogg packet while loading theora-headers, ogm-file(%s)\n", filename);
|
|
return -8;
|
|
}
|
|
if(status > 0)
|
|
{
|
|
status = theora_decode_header(&g_ogm.th_info, &g_ogm.th_comment, &op);
|
|
if(i == 0 && status != 0)
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: This Ogg bitstream does not contain theora data, ogm-file(%s)\n", filename);
|
|
return -9;
|
|
}
|
|
++i;
|
|
}
|
|
else if(loadPagesToStreams())
|
|
{
|
|
if(loadBlockToSync())
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: Couldn't find all theora headers before end of ogm-file (%s)\n", filename);
|
|
return -10;
|
|
}
|
|
}
|
|
}
|
|
|
|
theora_decode_init(&g_ogm.th_state, &g_ogm.th_info);
|
|
|
|
if(!isPowerOf2(g_ogm.th_info.width))
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: VideoWidth of the ogm-file isn't a power of 2 value (%s)\n", filename);
|
|
return -5;
|
|
}
|
|
if(!isPowerOf2(g_ogm.th_info.height))
|
|
{
|
|
Com_Printf(S_COLOR_YELLOW "WARNING: VideoHeight of the ogm-file isn't a power of 2 value (%s)\n", filename);
|
|
return -6;
|
|
}
|
|
|
|
g_ogm.Vtime_unit = ((ogg_int64_t) g_ogm.th_info.fps_denominator * 1000 * 10000 / g_ogm.th_info.fps_numerator);
|
|
}
|
|
#endif
|
|
|
|
Com_DPrintf("OGM-Init done (%s)\n", filename);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int nextNeededVFrame(void)
|
|
{
|
|
return (int)(g_ogm.currentTime * (ogg_int64_t) 10000 / g_ogm.Vtime_unit);
|
|
}
|
|
|
|
/*
|
|
|
|
time ~> time in ms to which the movie should run
|
|
return: 0 => nothing special
|
|
1 => eof
|
|
*/
|
|
int Cin_OGM_Run(int time)
|
|
{
|
|
|
|
g_ogm.currentTime = time;
|
|
|
|
while(!g_ogm.VFrameCount || time + 20 >= (int)(g_ogm.VFrameCount * g_ogm.Vtime_unit / 10000))
|
|
{
|
|
if(loadFrame())
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
Gives a Pointer to the current Output-Buffer
|
|
and the Resolution
|
|
*/
|
|
unsigned char *Cin_OGM_GetOutput(int *outWidth, int *outHeight)
|
|
{
|
|
if(outWidth != NULL)
|
|
*outWidth = g_ogm.outputWidht;
|
|
if(outHeight != NULL)
|
|
*outHeight = g_ogm.outputHeight;
|
|
|
|
return g_ogm.outputBuffer;
|
|
}
|
|
|
|
void Cin_OGM_Shutdown()
|
|
{
|
|
#ifdef USE_CIN_XVID
|
|
int status;
|
|
|
|
status = shutdown_xvid();
|
|
if(status)
|
|
Com_Printf("[Xvid]Decore RELEASE problem, return value %d\n", status);
|
|
#endif
|
|
|
|
#ifdef USE_CIN_THEORA
|
|
theora_clear(&g_ogm.th_state);
|
|
theora_comment_clear(&g_ogm.th_comment);
|
|
theora_info_clear(&g_ogm.th_info);
|
|
#endif
|
|
|
|
if(g_ogm.outputBuffer)
|
|
free(g_ogm.outputBuffer);
|
|
g_ogm.outputBuffer = NULL;
|
|
|
|
vorbis_dsp_clear(&g_ogm.vd);
|
|
vorbis_comment_clear(&g_ogm.vc);
|
|
vorbis_info_clear(&g_ogm.vi); /* must be called last (comment from vorbis example code) */
|
|
|
|
ogg_stream_clear(&g_ogm.os_audio);
|
|
ogg_stream_clear(&g_ogm.os_video);
|
|
|
|
ogg_sync_clear(&g_ogm.oy);
|
|
|
|
FS_FCloseFile(g_ogm.ogmFile);
|
|
g_ogm.ogmFile = 0;
|
|
}
|
|
|
|
#else
|
|
int Cin_OGM_Init(const char *filename)
|
|
{
|
|
return 1;
|
|
}
|
|
int Cin_OGM_Run(int time)
|
|
{
|
|
return 1;
|
|
}
|
|
unsigned char *Cin_OGM_GetOutput(int *outWidth, int *outHeight)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void Cin_OGM_Shutdown(void)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|