warzone2100/tools/rpl2avi/rpl2avi.c

884 lines
26 KiB
C

/*
Copyright (C) 2006 Angus Lees <gus@inodes.org>
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 2 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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* Convert ARMovie/Eidos' Replay (.RPL) file to uncompressed AVI
* GPL - Angus Lees <gus@inodes.org> April 2006
*/
/* There seems to be a bug somewhere in the streamer adpcm decode code
(at least under wine). It throws a whole bunch of '-1's into the
decoded audio stream for long files, which sounds like lots of
static. Work around this by doing the decoding ourselves. */
#define USE_MY_CODE 1
#define WIN32_LEAN_AND_MEAN /* heh */
#include <windows.h>
#include <vfw.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include "streamer.h"
#define STREAMER_AUDIOOUT_BITSIZE 16 /* streamer always outputs SLE16 */
#ifdef __WINE__
/* This throws a stub warning on wine at present (Wine 0.9.9) */
#define AVIFileExit() do {} while (0)
#endif
#ifdef __WINE__
/* HACK HACK HACK: We need to increase cbIdxRecords to work around an
* index size limitation in current (0.9.12) wine versions */
#define MAX_AVISTREAMS 8
typedef struct _IAVIFileImpl IAVIFileImpl;
typedef struct _EXTRACHUNKS {
LPVOID lp;
DWORD cb;
} EXTRACHUNKS, *LPEXTRACHUNKS;
typedef struct _IPersistFileImpl {
/* IUnknown stuff */
const void *lpVtbl;
/* IPersistFile stuff */
void *paf;
} IPersistFileImpl;
struct _IAVIFileImpl {
/* IUnknown stuff */
const void *lpVtbl;
LONG ref;
/* IAVIFile stuff... */
IPersistFileImpl iPersistFile;
AVIFILEINFOW fInfo;
void *ppStreams[MAX_AVISTREAMS];
EXTRACHUNKS fileextra;
DWORD dwMoviChunkPos; /* some stuff for saving ... */
DWORD dwIdxChunkPos;
DWORD dwNextFramePos;
DWORD dwInitialFrames;
MMCKINFO ckLastRecord;
AVIINDEXENTRY *idxRecords; /* won't be updated while loading */
DWORD nIdxRecords; /* current fill level */
DWORD cbIdxRecords; /* size of idxRecords */
/* IPersistFile stuff ... */
HMMIO hmmio;
LPWSTR szFileName;
UINT uMode;
BOOL fDirty;
};
#endif
static const char *avierror(HRESULT err) {
switch (err) {
case AVIERR_OK: return "OK";
case AVIERR_UNSUPPORTED: return "UNSUPPORTED";
case AVIERR_BADFORMAT: return "BADFORMAT";
case AVIERR_MEMORY: return "MEMORY";
case AVIERR_INTERNAL: return "INTERNAL";
case AVIERR_BADFLAGS: return "BADFLAGS";
case AVIERR_BADPARAM: return "BADPARAM";
case AVIERR_BADSIZE: return "BADSIZE";
case AVIERR_BADHANDLE: return "BADHANDLE";
case AVIERR_FILEREAD: return "FILEREAD";
case AVIERR_FILEWRITE: return "FILEWRITE";
case AVIERR_FILEOPEN: return "FILEOPEN";
case AVIERR_COMPRESSOR: return "COMPRESSOR";
case AVIERR_NOCOMPRESSOR: return "NOCOMPRESSOR";
case AVIERR_READONLY: return "READONLY";
case AVIERR_NODATA: return "NODATA";
case AVIERR_BUFFERTOOSMALL: return "BUFFERTOOSMALL";
case AVIERR_CANTCOMPRESS: return "CANTCOMPRESS";
case AVIERR_USERABORT: return "USERABORT";
case AVIERR_ERROR: return "ERROR";
default: return "Unknown?";
}
}
static const char *winstr_error(LONG err) {
switch (err) {
case STREAMER_OK: return "OK";
case STREAMER_NO_SOUND_DRIVER: return "STREAMER_NO_SOUND_DRIVER";
case STREAMER_NO_SOUND_IN_RPL: return "STREAMER_NO_SOUND_IN_RPL";
case STREAMER_BAD_ALLOC_SOUND_HANDLE: return "STREAMER_BAD_ALLOC_SOUND_HANDLE";
case STREAMER_BAD_ALLOC_DBUFFERS: return "STREAMER_BAD_ALLOC_DBUFFERS";
case STREAMER_BAD_ALLOC_SAMPLE_HANDLE: return "STREAMER_BAD_ALLOC_SAMPLE_HANDLE";
case STREAMER_BAD_ALLOC_TIMER: return "STREAMER_BAD_ALLOC_TIMER";
case STREAMER_BAD_ALLOC_TIMER_HANDLE: return "STREAMER_BAD_ALLOC_TIMER_HANDLE";
case STREAMER_BAD_ALLOC_VIDEO_BUFFER: return "STREAMER_BAD_ALLOC_VIDEO_BUFFER";
case STREAMER_BAD_ALLOC_GLCW_LUT: return "STREAMER_BAD_ALLOC_GLCW_LUT";
case STREAMER_BAD_ALLOC_LSCW_LUT: return "STREAMER_BAD_ALLOC_LSCW_LUT";
case STREAMER_BAD_ALLOC_GSCW_LUT: return "STREAMER_BAD_ALLOC_GSCW_LUT";
case STREAMER_BAD_ALLOC_VIDEO_HANDLE: return "STREAMER_BAD_ALLOC_VIDEO_HANDLE";
case STREAMER_VESA_ERROR: return "STREAMER_VESA_ERROR";
case STREAMER_BAD_ALLOC_SB_OFFSETS: return "STREAMER_BAD_ALLOC_SB_OFFSETS";
case STREAMER_BAD_ALLOC_MOVIE_HANDLE: return "STREAMER_BAD_ALLOC_MOVIE_HANDLE";
case STREAMER_BAD_ALLOC_MOVIE_INDEX: return "STREAMER_BAD_ALLOC_MOVIE_INDEX";
case STREAMER_FILE_OPEN_ERROR: return "STREAMER_FILE_OPEN_ERROR";
case STREAMER_BAD_PLAYBACK_STATE: return "STREAMER_BAD_PLAYBACK_STATE";
case STREAMER_FILE_OPEN_ERROR_HIGH: return "STREAMER_FILE_OPEN_ERROR_HIGH";
case STREAMER_BAD_MOVIE_HEADER: return "STREAMER_BAD_MOVIE_HEADER";
case STREAMER_CHUNK_OUT_FILE_ERROR: return "STREAMER_CHUNK_OUT_FILE_ERROR";
case STREAMER_BAD_ALLOC_OPTIONS: return "STREAMER_BAD_ALLOC_OPTIONS";
case STREAMER_BAD_ALIGN_SEEK: return "STREAMER_BAD_ALIGN_SEEK";
case STREAMER_PRELOAD_KEY_PRESS: return "STREAMER_PRELOAD_KEY_PRESS";
case STREAMER_FILE_READ_ERROR: return "STREAMER_FILE_READ_ERROR";
case STREAMER_BAD_ALLOC_SCREEN_BUFFER: return "STREAMER_BAD_ALLOC_SCREEN_BUFFER";
case STREAMER_BAD_SOUND_CALLBACK: return "STREAMER_BAD_SOUND_CALLBACK";
case STREAMER_BADTHREADCREATE: return "STREAMER_BADTHREADCREATE";
case STREAMER_BADDATARATE: return "STREAMER_BADDATARATE";
case STREAMER_BADMOVIEHANDLE: return "STREAMER_BADMOVIEHANDLE";
case STREAMER_NOEFFECTS: return "STREAMER_NOEFFECTS";
case STREAMER_FINISHEDVIDEO: return "STREAMER_FINISHEDVIDEO";
case STREAMER_FINISHEDAUDIO: return "STREAMER_FINISHEDAUDIO";
case STREAMER_BAD_ALLOC_SOUND_BUFFER: return "STREAMER_BAD_ALLOC_SOUND_BUFFER";
case STREAMER_NOTINGRAPHICSMODE: return "STREAMER_NOTINGRAPHICSMODE";
case STREAMER_BAD_MOVIEHANDLE: return "STREAMER_BAD_MOVIEHANDLE";
case STREAMER_BAD_VIDEOHANDLE: return "STREAMER_BAD_VIDEOHANDLE";
case STREAMER_BAD_SOUNDHANDLE: return "STREAMER_BAD_SOUNDHANDLE";
case STREAMER_BADSOUNDBUFFERNO: return "STREAMER_BADSOUNDBUFFERNO";
case STREAMER_NODATADATASTREAM: return "STREAMER_NODATADATASTREAM";
case STREAMER_BADMOVIEFORMAT: return "STREAMER_BADMOVIEFORMAT";
case STREAMER_130CODECOPENERROR: return "STREAMER_130CODECOPENERROR";
default: return "Unknown?";
}
}
static signed char index_adjust[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };
static short step_size[89] = {
7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};
struct adpcm_state {
int pred_val, step_idx;
};
/* This code is "borrowed" from the ALSA library
http://www.alsa-project.org */
static unsigned short
adpcm_decode_sample(struct adpcm_state *state, char code) {
short pred_diff; /* Predicted difference to next sample */
short step; /* holds previous step_size value */
char sign;
int i;
/* Separate sign and magnitude */
sign = code & 0x8;
code &= 0x7;
/*
* Computes pred_diff = (code + 0.5) * step / 4,
* but see comment in adpcm_coder.
*/
step = step_size[state->step_idx];
/* Compute difference and new predicted value */
pred_diff = step >> 3;
for (i = 0x4; i; i >>= 1, step >>= 1) {
if (code & i) {
pred_diff += step;
}
}
state->pred_val += (sign) ? -pred_diff : pred_diff;
/* Clamp output value */
if (state->pred_val > 32767) {
state->pred_val = 32767;
} else if (state->pred_val < -32768) {
state->pred_val = -32768;
}
/* Find new step_size index value */
state->step_idx += index_adjust[(int) code];
if (state->step_idx < 0) {
state->step_idx = 0;
} else if (state->step_idx > 88) {
state->step_idx = 88;
}
return state->pred_val;
}
void adpcm_init(struct adpcm_state *state) {
state->pred_val = 0;
state->step_idx = 0;
}
void adpcm_decode(struct adpcm_state *state,
unsigned char* input, unsigned int input_size,
unsigned short* output) {
unsigned int i;
for (i = 0; i < input_size; ++i) {
unsigned char two_samples = input[i];
*(output++) = adpcm_decode_sample(state, two_samples >> 4);
*(output++) = adpcm_decode_sample(state, two_samples & 15);
}
}
static long last_updated;
static PAVISTREAM audio;
static size_t audiobuffersize;
static size_t audiosamplesize;
static void sound_callback(LPSOUNDHANDLE shandle) {
HRESULT res;
long state;
/* printf("sound_callback called!\n"); */
state = Streamer_GetSoundDecodeMode(shandle);
#ifndef USE_MY_CODE
res = AVIStreamWrite(audio, -1, audiobuffersize / audiosamplesize,
Streamer_GetSoundBuffer(shandle, state),
audiobuffersize, 0, 0, 0);
if (FAILED(res)) {
fprintf(stderr, "AVIStreamWrite(audio) returned %s\n", avierror(res));
return;
}
#endif
Streamer_SetSoundDecodeMode(shandle, SSDM_IDLE);
last_updated = state;
}
#ifdef __WINE__
static char *convert_path(const char *path) {
WCHAR *dospath;
char *newpath;
dospath = wine_get_dos_file_name(path);
if (!dospath) {
fprintf(stderr, "Wine can't get a DOS path for %s\n", path);
exit(1);
}
newpath = malloc(MAX_PATH); /* leak */
newpath[0] = '\0';
WideCharToMultiByte(CP_UNIXCP, 0, dospath, -1, newpath, MAX_PATH, NULL, NULL);
return newpath;
}
#else
#define convert_path(p) (p)
#endif
struct RPL {
FILE* f;
int loop;
size_t nchunks, current_chunk;
int width, height, bpp;
float fps;
int frames_per_chunk;
int even_chunk_size, odd_chunk_size;
int channels;
long chunk_catalog_off, sprite_off, key_frame_off;
struct rpl_chunk {
unsigned long offset;
size_t video_size, audio_size;
} *chunks;
int sound_format, sound_bits, audio_freq;
unsigned char *audio_buffer;
unsigned short *audio_decode_buffer;
} rpl;
static char *downcase_string(char *str)
{
size_t i;
for (i = 0; str[i]; i++) {
if (isupper(str[i]))
str[i] = tolower(str[i]);
}
return str;
}
static char *readline(FILE *f, char *linebuf, size_t len) {
char c;
size_t i = 0;
for (i = 0; i < len-1; i++) {
if (fread(&c, 1, 1, f) != 1) {
perror("Error reading from sequence file");
break;
}
if (c == '\n' || !isprint(c))
break;
linebuf[i] = c;
}
linebuf[i] = '\0';
return linebuf;
}
static int readint(FILE *f, char *linebuf, size_t len, char **rest) {
int num, numread;
readline(f, linebuf, len);
if (sscanf(linebuf, "%u %n", &num, &numread) < 1) {
if (rest) *rest = "";
num = 0;
}
else if (rest)
*rest = downcase_string(&linebuf[numread]);
return num;
}
static float readfloat(FILE *f, char *linebuf, size_t len, char **rest) {
float num;
int numread;
readline(f, linebuf, len);
if (sscanf(linebuf, "%f %n", &num, &numread) < 1) {
if (rest) *rest = "";
num = 0.0;
} else if (rest)
*rest = downcase_string(&linebuf[numread]);
return num;
}
static void init_rpl(const char* path) {
char buf[80], *comment;
int tmp;
size_t i, len = sizeof(buf);
size_t max_audio_size = 0;
FILE* f = fopen(path, "rb");
rpl.f = f;
if (!f) {
perror("Unable to open rpl file");
exit(1);
}
rpl.chunks = NULL;
rpl.current_chunk = 0;
rpl.loop = 0;
rpl.audio_buffer = NULL;
rpl.audio_decode_buffer = NULL;
if (strcmp(readline(f, buf, len), "ARMovie") != 0)
fprintf(stderr, "Missing RPL magic number\n");
readline(f, buf, len);
/* fprintf(stderr, "Header filename: %s\n", buf); */
readline(f, buf, len);
/* fprintf(stderr, "Copyright: %s\n", buf); */
readline(f, buf, len);
/* fprintf(stderr, "Author: %s\n", buf); */
tmp = readint(f, buf, len, NULL);
switch (tmp) {
case 130:
/* init_dec130(&rpl->dec130_state);
rpl->decode_video = rpl_dec130_decode; */
break;
case 13: /* mpeg? */
case 18: /* h263? */
default:
fprintf(stderr, "Unknown video format %i\n", tmp);
break;
}
rpl.width = readint(f, buf, len, NULL);
rpl.height = readint(f, buf, len, NULL);
rpl.bpp = readint(f, buf, len, &comment);
#if 0
/* Eidos RPL files lie about this part of the 'standard' */
if (!strstr(comment, "yuv"))
debug(LOG_VIDEO, "%s: Header doesn't declare this as YUV."
" We only support YUV at present..\n", m->filename);
#endif
rpl.fps = readfloat(f, buf, len, NULL);
rpl.sound_format = readint(f, buf, len, &comment);
rpl.audio_freq = readint(f, buf, len, NULL);
rpl.channels = readint(f, buf, len, NULL);
rpl.sound_bits = readint(f, buf, len, NULL);
#if 0
switch (sound_format) {
case 1: /* PCM */
if (sound_bits == 8)
m->audio_format =
rpl->channels == 1 ? AL_FORMAT_MONO8 : AL_FORMAT_STEREO8;
else if (sound_bits == 16)
m->audio_format =
rpl->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
else
break;
rpl->decode_audio = rpl_passthru_audio;
break;
case 101:
if (al_supports_adpcm) {
debug(LOG_VIDEO, "Using native OpenAL adpcm support (channels=%d)\n",
rpl->channels);
m->audio_format = rpl->channels == 1
? AL_FORMAT_IMA_ADPCM_MONO16_EXT
: AL_FORMAT_IMA_ADPCM_STEREO16_EXT;
rpl->decode_audio = rpl_passthru_audio;
}
else {
m->audio_format = rpl->channels == 1
? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
rpl->decode_audio = rpl_adpcm_decode;
}
break;
}
if (sound_format && !rpl->decode_audio)
debug(LOG_VIDEO, "Unknown RPL sound format %i (bits=%d, channels=%d)\n",
sound_format, sound_bits, rpl->channels);
#endif
rpl.frames_per_chunk = readint(f, buf, len, NULL);
rpl.nchunks = readint(f, buf, len, &comment);
if (strstr(comment, "forever")) rpl.loop = 1;
rpl.even_chunk_size = readint(f, buf, len, NULL);
rpl.odd_chunk_size = readint(f, buf, len, NULL);
rpl.chunk_catalog_off = readint(f, buf, len, NULL);
rpl.sprite_off = readint(f, buf, len, NULL);
rpl.key_frame_off = readint(f, buf, len, NULL);
rpl.chunks = calloc(rpl.nchunks+1, sizeof(struct rpl_chunk));
fseek(f, rpl.chunk_catalog_off, SEEK_SET);
for (i = 0; i < rpl.nchunks+1; i++) {
struct rpl_chunk *c = &(rpl.chunks[i]);
readline(f, buf, len);
if (sscanf(buf, "%lu , %zu ; %zu",
&c->offset, &c->video_size, &c->audio_size) != 3)
fprintf(stderr, "Bad catalog line: %s\n", buf);
if (c->audio_size > max_audio_size)
max_audio_size = c->audio_size;
}
rpl.audio_buffer = malloc(max_audio_size);
rpl.audio_decode_buffer = malloc(4*max_audio_size);
}
int main(int argc, char **argv) {
char *inputfilename, *outputfilename, *unixinfile, *unixoutfile,
*metafilename = NULL;
LONG ret;
LPMOVIEHANDLE mhandle;
LPVIDEOHANDLE vhandle;
LPSOUNDHANDLE shandle = NULL;
LONG buffer_pixel_width, buffer_pixel_height; /* what are these for?? */
BYTE ap, ac, rp, rc, gp, gc, bp, bc;
LPBYTE videobuffer, flipbuffer, soundbuffer;
BOOL audio_finished;
PAVIFILE avi;
PAVISTREAM video;
AVISTREAMINFO info;
BITMAPINFOHEADER bi;
HRESULT res;
int pixelsize;
size_t rowsize;
struct adpcm_state adpcm_state;
int arg;
if (argc >= 3 && strcmp(argv[1], "--meta")==0) {
metafilename = argv[2];
arg = 3;
}
else
arg = 1;
if (argc - arg != 2) {
fprintf(stderr, "Usage: %s [--meta FILE] <in.rpl> <out.avi>\n", argv[0]);
exit(1);
}
unixinfile = argv[arg];
unixoutfile = argv[arg+1];
inputfilename = convert_path(unixinfile);
outputfilename = convert_path(unixoutfile);
ret = Streamer_InitMovie(&mhandle, NULL, 0, inputfilename,
STREAMER_BUFFERSIZE, SIM_NONE);
if (ret != STREAMER_OK) {
fprintf(stderr, "Streamer_InitMovie(%s) returned %s\n",
inputfilename, winstr_error(ret));
exit(1);
}
printf("Name: %s\n", Movie_GetName(mhandle));
printf("Author: %s\n", Movie_GetAuthor(mhandle));
printf("Copyright: %s\n", Movie_GetCopyright(mhandle));
if (metafilename) {
FILE *f = fopen(metafilename, "w");
if (!f) {
perror(metafilename);
exit(1);
}
fprintf(f, "--title='%s'\n", Movie_GetName(mhandle));
fprintf(f, "--artist='%s'\n", Movie_GetAuthor(mhandle));
fprintf(f, "--copyright='%s'\n", Movie_GetCopyright(mhandle));
fclose(f);
}
AVIFileInit();
res = AVIFileOpen(&avi, outputfilename, OF_WRITE|OF_CREATE, 0);
if (FAILED(res)) {
AVIFileExit();
fprintf(stderr, "Error writing %s: %s\n", outputfilename, avierror(res));
exit(1);
}
ret = Streamer_InitVideo(&vhandle, mhandle,
Movie_GetXSize(mhandle), Movie_GetYSize(mhandle),
0, 0, 0, 0, 0, 0,
DFLAG_INVIEWPORT,
&buffer_pixel_width, &buffer_pixel_height);
if (ret != STREAMER_OK) {
fprintf(stderr, "Streamer_InitVideo() returned %s\n", winstr_error(ret));
exit(1);
}
Streamer_SetVideoPitch(vhandle,
Movie_GetXSize(mhandle), Movie_GetYSize(mhandle));
switch (Movie_GetColourDepth(mhandle)) {
default:
fprintf(stderr, "Don't cope with native depth %ld yet, using 24bit..\n",
Movie_GetColourDepth(mhandle));
/* fall through */
case 32: /* top byte unused, so really 24bit.. */
case 24:
/* BGR */
pixelsize = 32;
ap = 24;
ac = 0;
rp = 16;
rc = 8;
gp = 8;
gc = 8;
bp = 0;
bc = 8;
break;
case 16:
/* 555 RGB */
pixelsize = 16;
ap = 15;
ac = 1;
rp = 10;
rc = 5;
gp = 5;
gc = 5;
bp = 0;
bc = 5;
break;
}
printf("Found %ldx%ldx%ldbpp %gfps video stream\n",
Movie_GetXSize(mhandle), Movie_GetYSize(mhandle),
Movie_GetColourDepth(mhandle), Movie_GetFrameRate(mhandle));
ret = Streamer_SetPixelFormat(vhandle, Movie_GetColourDepth(mhandle),
ap, ac, rp, rc, gp, gc, bp, bc);
if (ret != STREAMER_OK) {
fprintf(stderr, "Streamer_SetPixelFormat returned %s\n", winstr_error(ret));
exit(1);
}
memset(&info, 0, sizeof(info));
info.fccType = streamtypeVIDEO;
info.dwScale = 10000;
info.dwRate = Movie_GetFrameRate(mhandle) * 10000;
info.dwSuggestedBufferSize =
Movie_GetXSize(mhandle) * Movie_GetYSize(mhandle) * pixelsize / 8;
SetRect(&info.rcFrame, 0, 0,
Movie_GetXSize(mhandle), Movie_GetYSize(mhandle));
res = AVIFileCreateStream(avi, &video, &info);
if (FAILED(res)) {
fprintf(stderr, "AVIFileCreateStream(video) returned %s\n", avierror(res));
exit(1);
}
memset(&bi, 0, sizeof(bi));
bi.biSize = sizeof(bi);
bi.biWidth = Movie_GetXSize(mhandle);
bi.biHeight = Movie_GetYSize(mhandle);
bi.biBitCount = pixelsize;
bi.biSizeImage = bi.biWidth * bi.biHeight * bi.biBitCount / 8;
bi.biPlanes = 1;
bi.biCompression = BI_RGB;
res = AVIStreamSetFormat(video, 0, &bi, bi.biSize);
if (FAILED(res)) {
fprintf(stderr, "AVIStreamSetFormat(video) returned %s\n", avierror(res));
exit(1);
}
if (Movie_GetSoundChannels(mhandle) &&
Movie_GetSoundPrecision(mhandle) &&
Movie_GetSoundRate(mhandle)) {
long compression;
BOOL pcm;
long precision = Movie_GetSoundPrecision(mhandle);
long channels = Movie_GetSoundChannels(mhandle);
WAVEFORMATEX wf;
if (precision == 4) {
compression = 4;
pcm = FALSE;
} else {
compression = 1;
pcm = TRUE;
}
printf("Found %ldbit %ldHz %s audio stream\n",
precision, Movie_GetSoundRate(mhandle),
channels == 1 ? "mono" : "stereo");
audiosamplesize = channels * STREAMER_AUDIOOUT_BITSIZE / 8;
/* Should be one video frame's worth of (uncompressed) audio, so
* we get a nicely interleaved AVI file.. */
audiobuffersize = Movie_GetSoundRate(mhandle) * audiosamplesize /
Movie_GetFrameRate(mhandle);
ret = Streamer_InitSound(sound_callback, &shandle,
audiobuffersize,
compression, pcm, 4096, channels);
if (ret != STREAMER_OK) {
fprintf(stderr, "Streamer_InitSound() returned %s\n", winstr_error(ret));
exit(1);
}
memset(&info, 0, sizeof(info));
info.fccType = streamtypeAUDIO;
info.dwSampleSize = audiosamplesize;
info.dwScale = 1;
info.dwRate = Movie_GetSoundRate(mhandle);
info.dwQuality = -1;
res = AVIFileCreateStream(avi, &audio, &info);
if (FAILED(res)) {
fprintf(stderr, "AVIFileCreateStream(audio) returned %s\n", avierror(res));
exit(1);
}
memset(&wf, 0, sizeof(wf));
wf.wFormatTag = WAVE_FORMAT_PCM;
wf.nChannels = channels;
wf.nSamplesPerSec = Movie_GetSoundRate(mhandle);
wf.wBitsPerSample = STREAMER_AUDIOOUT_BITSIZE;
wf.nBlockAlign = audiosamplesize;
wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
wf.cbSize = 0;
#if USE_MY_CODE
init_rpl(unixinfile);
adpcm_init(&adpcm_state);
#endif
AVIStreamSetFormat(audio, 0, &wf, sizeof(WAVEFORMATEX));
soundbuffer = malloc(audiobuffersize * 2);
if (!soundbuffer) {
fprintf(stderr, "Malloc failed, out of memory\n");
exit(1);
}
Streamer_SetSoundBuffer(shandle, 0, soundbuffer);
Streamer_SetSoundBuffer(shandle, 1, soundbuffer + audiobuffersize);
}
if (shandle)
Streamer_SetSoundDecodeMode(shandle, SSDM_IDLE);
last_updated = SSDM_SECONDBUFFER;
videobuffer = malloc(Movie_GetXSize(mhandle) * Movie_GetYSize(mhandle) *
pixelsize / 8);
flipbuffer = malloc(Movie_GetXSize(mhandle) * Movie_GetYSize(mhandle) *
pixelsize / 8);
if (!videobuffer || !flipbuffer) {
fprintf(stderr, "Out of memory allocating video buffer\n");
exit(1);
}
/* used during image copy below */
rowsize = pixelsize * Movie_GetXSize(mhandle) / 8;
#ifdef __WINE__
/* HACK HACK HACK - work around 1024 index entry limit in wine 0.9.12.
* See http://bugs.winehq.com/show_bug.cgi?id=5137 */
if (Movie_GetTotalFrames(mhandle) > 1024)
((IAVIFileImpl*)avi)->cbIdxRecords =
Movie_GetTotalFrames(mhandle) * sizeof(AVIINDEXENTRY);
#endif
/* kicks the file into interleaved mode */
AVIFileEndRecord(avi);
/* This prefills audio buffer, etc */
ret = Streamer_InitStreaming(mhandle, vhandle, shandle);
if (ret != STREAMER_OK) {
fprintf(stderr, "Streamer_InitStreaming returned %s\n", winstr_error(ret));
exit(1);
}
audio_finished = (shandle == NULL);
/* main loop */
while (1) {
LONG i;
ret = Streamer_Stream(mhandle, vhandle, shandle, NULL,
1, /* framestoplay */
videobuffer, NULL,
NULL, 0);
if (ret == STREAMER_BADDATARATE) {
/* couldn't read enough in one go */
continue;
}
else if (ret == STREAMER_FINISHEDVIDEO) {
break;
} else if (ret == STREAMER_FINISHEDAUDIO) {
audio_finished = 1;
} else if (ret != STREAMER_OK) {
fprintf(stderr, "Streamer_Stream returned %s\n", winstr_error(ret));
exit(1);
}
/* Streamer calls the audio callback during the *next* _Stream()
* call, so we need to write the rec marker here, between the call
* to _Stream() and writing out the "next" video frame */
AVIFileEndRecord(avi);
printf("[%ld] ", Movie_GetCurrentFrame(mhandle));
/* Flip video frame vertically for AVI file.
* NB: Can't flip videobuffer in-place, since Streamer seems to
* only make incremental changes to buffer, rather than redrawing
* completely each time */
for (i = 0; i < Movie_GetYSize(mhandle); i++) {
memcpy(flipbuffer + (Movie_GetYSize(mhandle) - i - 1) * rowsize,
videobuffer + i * rowsize,
rowsize);
}
res = AVIStreamWrite(video, -1, 1, flipbuffer,
Movie_GetXSize(mhandle) * Movie_GetYSize(mhandle) *
pixelsize / 8,
AVIIF_KEYFRAME, 0, 0);
if (FAILED(res))
fprintf(stderr, "AVIStreamWrite(video) returned %s\n", avierror(res));
if (!audio_finished) {
#ifdef USE_MY_CODE
HRESULT res;
struct rpl_chunk *c = &rpl.chunks[Movie_GetCurrentFrame(mhandle)-1];
fseek(rpl.f, c->offset + c->video_size, SEEK_SET);
fread(rpl.audio_buffer, c->audio_size, 1, rpl.f);
if (rpl.sound_format == 101 && rpl.sound_bits == 4) { /* adpcm */
adpcm_decode(&adpcm_state, rpl.audio_buffer, c->audio_size,
rpl.audio_decode_buffer);
size_t outbuflen = c->audio_size * 4;
res = AVIStreamWrite(audio, -1, outbuflen/audiosamplesize,
rpl.audio_decode_buffer, outbuflen,
0, 0, 0);
} else if (rpl.sound_format == 1 && rpl.sound_bits == 16) { /* sle16 */
res = AVIStreamWrite(audio, -1, c->audio_size/audiosamplesize,
rpl.audio_buffer, c->audio_size,
0, 0, 0);
} else {
fprintf(stderr, "Unknown sound codec (fmt=%d bits=%d)\n",
rpl.sound_format, rpl.sound_bits);
exit(1);
}
if (FAILED(res)) {
fprintf(stderr, "AVIStreamWrite(audio) returned %s\n", avierror(res));
exit(1);
}
#endif
if (Streamer_GetSoundDecodeMode(shandle) == SSDM_IDLE) {
/* No need for double buffering, since we're synchronously
* writing out the entire buffer to the AVI file. Left here,
* since it shows the general mechanism Streamer expects to
* deal with. */
LONG mode = last_updated == SSDM_FIRSTBUFFER ?
SSDM_SECONDBUFFER : SSDM_FIRSTBUFFER;
Streamer_SetSoundDecodeMode(shandle, mode);
}
}
}
AVIFileEndRecord(avi);
printf("Done.\n");
AVIStreamRelease(video);
if (shandle) AVIStreamRelease(audio);
AVIFileRelease(avi);
Streamer_ShutDownSound(&shandle);
Streamer_ShutDownVideo(&vhandle);
Streamer_ShutDownMovie(&mhandle);
AVIFileExit();
return 0;
}