mirror of
https://github.com/mupen64plus/mupen64plus-oldsvn.git
synced 2025-04-02 10:52:35 -04:00
466 lines
9.6 KiB
C
466 lines
9.6 KiB
C
#ifdef VCR_SUPPORT
|
|
|
|
#include "vcr.h"
|
|
#include "vcr_compress.h"
|
|
#include "vcr_resample.h"
|
|
|
|
#include "plugin.h"
|
|
#include "rom.h"
|
|
#include "savestates.h"
|
|
#include "../memory/memory.h"
|
|
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <zlib.h>
|
|
|
|
enum ETask
|
|
{
|
|
Idle = 0,
|
|
StartRecording,
|
|
Recording,
|
|
StartPlayback,
|
|
Playback
|
|
};
|
|
|
|
static const char *m_taskName[] =
|
|
{
|
|
"Idle",
|
|
"StartRecording",
|
|
"Recording",
|
|
"StartPlayback",
|
|
"Playback"
|
|
};
|
|
|
|
static char m_filename[PATH_MAX];
|
|
static gzFile m_file = 0;
|
|
static int m_task = Idle;
|
|
static int m_count; // number of recorded samples
|
|
static int m_pos; // playback position
|
|
static int m_capture = 0; // capture movie
|
|
static int m_audioFreq = 33000; //0x30018;
|
|
static int m_audioBitrate = 16; // 16 bits
|
|
static float m_videoFrame = 0;
|
|
static float m_audioFrame = 0;
|
|
|
|
|
|
static int fpsByCountrycode()
|
|
{
|
|
switch(ROM_HEADER->Country_code&0xFF)
|
|
{
|
|
case 0x44:
|
|
case 0x46:
|
|
case 0x49:
|
|
case 0x50:
|
|
case 0x53:
|
|
case 0x55:
|
|
case 0x58:
|
|
case 0x59:
|
|
return 25;
|
|
break;
|
|
|
|
case 0x37:
|
|
case 0x41:
|
|
case 0x45:
|
|
case 0x4a:
|
|
return 30;
|
|
break;
|
|
}
|
|
|
|
printf( "[VCR]: Warning - unknown country code, using 30 FPS for video.\n" );
|
|
return 30;
|
|
}
|
|
|
|
|
|
void
|
|
VCR_getKeys( int Control, BUTTONS *Keys )
|
|
{
|
|
if (m_task != Playback) // && m_task != StartPlayback)
|
|
getKeys( Control, Keys );
|
|
|
|
if (m_task == Idle)
|
|
return;
|
|
|
|
if (m_task == StartRecording)
|
|
{
|
|
// wait until state is saved, then record
|
|
if ((savestates_job & SAVESTATE) == 0)
|
|
{
|
|
printf( "[VCR]: Starting recording...\n" );
|
|
m_task = Recording;
|
|
m_count = 0;
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
|
|
if (m_task == Recording)
|
|
{
|
|
int cont = Control;
|
|
gzwrite( m_file, &cont, sizeof (int) );
|
|
gzwrite( m_file, Keys, sizeof (BUTTONS) );
|
|
m_count++;
|
|
return;
|
|
}
|
|
|
|
if (m_task == StartPlayback)
|
|
{
|
|
// wait until state is loaded, then playback
|
|
if ((savestates_job & LOADSTATE) == 0)
|
|
{
|
|
printf( "[VCR]: Starting playback...\n" );
|
|
m_task = Playback;
|
|
m_pos = 0;
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
|
|
if (m_task == Playback)
|
|
{
|
|
int cont;
|
|
gzread( m_file, &cont, sizeof (int) );
|
|
if (cont == -1) // end
|
|
{
|
|
if (m_capture != 0)
|
|
VCR_stopCapture();
|
|
else
|
|
VCR_stopPlayback();
|
|
return;
|
|
}
|
|
if (cont != Control)
|
|
{
|
|
printf( "[VCR]: Warning - controller num from file doesn't match requested number\n" );
|
|
// ...
|
|
}
|
|
gzread( m_file, Keys, sizeof (BUTTONS) );
|
|
m_pos++;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
VCR_updateScreen()
|
|
{
|
|
void *image;
|
|
int width, height;
|
|
static int frame = 0;
|
|
float desync;
|
|
|
|
if (m_capture == 0 || readScreen == 0 || m_task != Playback)
|
|
{
|
|
updateScreen();
|
|
return;
|
|
}
|
|
|
|
frame ^= 1;
|
|
if (!frame)
|
|
return;
|
|
|
|
updateScreen();
|
|
readScreen( &image, &width, &height );
|
|
if (image == 0)
|
|
{
|
|
fprintf( stderr, "[VCR]: Couldn't read screen (out of memory?)\n" );
|
|
return;
|
|
}
|
|
|
|
VCRComp_addVideoFrame( image );
|
|
free( image );
|
|
|
|
m_videoFrame += 1.0;
|
|
desync = m_videoFrame - m_audioFrame;
|
|
if (desync >= 5.0)
|
|
{
|
|
int len;
|
|
char *buf;
|
|
printf( "[VCR]: A/V desyncronization detected: %+f frames\n", desync );
|
|
len = (44100/(float)fpsByCountrycode()) * desync;
|
|
len <<= 2;
|
|
buf = malloc( len );
|
|
memset( buf, 0, len );
|
|
VCRComp_addAudioData( (char *)buf, len );
|
|
free( buf );
|
|
m_audioFrame += ((len/4)/44100.0)*fpsByCountrycode();
|
|
}
|
|
else if (desync <= -5.0)
|
|
{
|
|
printf( "[VCR]: A/V desyncronization detected: %+f frames\n", desync );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
VCR_aiDacrateChanged( int SystemType )
|
|
{
|
|
aiDacrateChanged( SystemType );
|
|
|
|
m_audioBitrate = ai_register.ai_bitrate+1;
|
|
switch (SystemType)
|
|
{
|
|
case SYSTEM_NTSC:
|
|
m_audioFreq = 48681812 / (ai_register.ai_dacrate + 1);
|
|
break;
|
|
case SYSTEM_PAL:
|
|
m_audioFreq = 49656530 / (ai_register.ai_dacrate + 1);
|
|
break;
|
|
case SYSTEM_MPAL:
|
|
m_audioFreq = 48628316 / (ai_register.ai_dacrate + 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void VCR_aiLenChanged()
|
|
{
|
|
short *p = (short *)((char*)rdram + (ai_register.ai_dram_addr & 0xFFFFFF));
|
|
unsigned int len = ai_register.ai_len;
|
|
int ret;
|
|
short *buf;
|
|
|
|
aiLenChanged();
|
|
if (m_capture == 0)
|
|
return;
|
|
|
|
// hack - mupen64 updates bitrate after calling aiDacrateChanged
|
|
m_audioBitrate = ai_register.ai_bitrate+1;
|
|
|
|
ret = VCR_resample( &buf, 44100, p, m_audioFreq, m_audioBitrate, len );
|
|
if (ret > 0)
|
|
{
|
|
VCRComp_addAudioData( (char *)buf, ret );
|
|
free( buf );
|
|
m_audioFrame += ((ret/4)/44100.0)*fpsByCountrycode();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
VCR_startRecord( const char *filename )
|
|
{
|
|
char buf[PATH_MAX];
|
|
|
|
if (m_task != Idle)
|
|
{
|
|
fprintf( stderr, "[VCR]: Cannot start recording, current task is \"%s\"\n", m_taskName[m_task] );
|
|
return -1;
|
|
}
|
|
|
|
strncpy( m_filename, filename, PATH_MAX );
|
|
|
|
// open record file
|
|
strcpy( buf, m_filename );
|
|
strncat( buf, ".rec", PATH_MAX );
|
|
m_file = gzopen( buf, "wb" );
|
|
if (m_file == 0)
|
|
{
|
|
fprintf( stderr, "[VCR]: Cannot start recording, could not open file '%s': %s\n", filename, strerror( errno ) );
|
|
return -1;
|
|
}
|
|
|
|
// save state
|
|
printf( "[VCR]: Saving state...\n" );
|
|
strcpy( buf, m_filename );
|
|
strncat( buf, ".st", PATH_MAX );
|
|
savestates_select_filename( buf );
|
|
savestates_job |= SAVESTATE;
|
|
m_task = StartRecording;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
VCR_stopRecord()
|
|
{
|
|
if (m_task == StartRecording)
|
|
{
|
|
char buf[PATH_MAX];
|
|
|
|
m_task = Idle;
|
|
if (m_file)
|
|
{
|
|
gzclose( m_file );
|
|
m_file = 0;
|
|
}
|
|
printf( "[VCR]: Removing files (nothing recorded)\n" );
|
|
|
|
strcpy( buf, m_filename );
|
|
strncat( m_filename, ".st", PATH_MAX );
|
|
if (unlink( buf ) < 0)
|
|
fprintf( stderr, "[VCR]: Couldn't remove save state: %s\n", strerror( errno ) );
|
|
|
|
strcpy( buf, m_filename );
|
|
strncat( m_filename, ".rec", PATH_MAX );
|
|
if (unlink( buf ) < 0)
|
|
fprintf( stderr, "[VCR]: Couldn't remove recorded file: %s\n", strerror( errno ) );
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (m_task == Recording)
|
|
{
|
|
int end = -1;
|
|
|
|
m_task = Idle;
|
|
gzwrite( m_file, &end, sizeof (int) );
|
|
gzwrite( m_file, &m_count, sizeof (int) );
|
|
gzclose( m_file );
|
|
|
|
printf( "[VCR]: Record stopped. Recorded %ld input samples\n", m_count );
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
VCR_startPlayback( const char *filename )
|
|
{
|
|
char buf[PATH_MAX];
|
|
|
|
if (m_task != Idle)
|
|
{
|
|
fprintf( stderr, "[VCR]: Cannot start playback, current task is \"%s\"\n", m_taskName[m_task] );
|
|
return -1;
|
|
}
|
|
|
|
strncpy( m_filename, filename, PATH_MAX );
|
|
char *p = strrchr( m_filename, '.' );
|
|
if (p)
|
|
{
|
|
if (!strcasecmp( p, ".rec" ) || !strcasecmp( p, ".st" ))
|
|
*p = '\0';
|
|
}
|
|
|
|
// open record file
|
|
strcpy( buf, m_filename );
|
|
strncat( buf, ".rec", PATH_MAX );
|
|
m_file = gzopen( buf, "rb" );
|
|
if (m_file == 0)
|
|
{
|
|
fprintf( stderr, "[VCR]: Cannot start playback, could not open .rec file '%s': %s\n", filename, strerror( errno ) );
|
|
return -1;
|
|
}
|
|
m_pos = 0;
|
|
|
|
// load state
|
|
printf( "[VCR]: Loading state...\n" );
|
|
strcpy( buf, m_filename );
|
|
strncat( buf, ".st", PATH_MAX );
|
|
savestates_select_filename( buf );
|
|
savestates_job |= LOADSTATE;
|
|
m_task = StartPlayback;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
VCR_stopPlayback()
|
|
{
|
|
if (m_task == StartPlayback)
|
|
{
|
|
m_task = Idle;
|
|
if (m_file)
|
|
{
|
|
gzclose( m_file );
|
|
m_file = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (m_task == Playback)
|
|
{
|
|
m_task = Idle;
|
|
if (m_file)
|
|
{
|
|
gzclose( m_file );
|
|
m_file = 0;
|
|
}
|
|
printf( "[VCR]: Playback stopped (%ld samples played)\n", m_pos );
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
#ifdef __WIN32__
|
|
void init_readScreen();
|
|
#endif
|
|
|
|
int
|
|
VCR_startCapture( const char *recFilename, const char *aviFilename )
|
|
{
|
|
#ifdef __WIN32__
|
|
init_readScreen();
|
|
#endif
|
|
if (readScreen == 0)
|
|
{
|
|
printf( "[VCR]: You need a video plugin which supports ReadScreen() to record movies!\n" );
|
|
return -1;
|
|
}
|
|
|
|
m_videoFrame = 0.0;
|
|
m_audioFrame = 0.0;
|
|
void *dest;
|
|
int width, height;
|
|
readScreen( &dest, &width, &height );
|
|
if (dest)
|
|
free( dest );
|
|
VCRComp_startFile( aviFilename, width, height, fpsByCountrycode() );
|
|
m_capture = 1;
|
|
if (VCR_startPlayback( recFilename ) < 0)
|
|
{
|
|
fprintf( stderr, "[VCR]: Cannot start capture, could not start playback!\n" );
|
|
return -1;
|
|
}
|
|
printf( "[VCR]: Starting capture...\n" );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
VCR_stopCapture()
|
|
{
|
|
m_capture = 0;
|
|
#ifndef __WIN32__
|
|
usleep( 100000 );
|
|
#endif
|
|
VCR_stopPlayback();
|
|
VCRComp_finishFile();
|
|
printf( "[VCR]: Capture finished.\n" );
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
VCR_coreStopped()
|
|
{
|
|
switch (m_task)
|
|
{
|
|
case StartRecording:
|
|
case Recording:
|
|
VCR_stopRecord();
|
|
break;
|
|
case StartPlayback:
|
|
case Playback:
|
|
if (m_capture != 0)
|
|
VCR_stopCapture();
|
|
else
|
|
VCR_stopPlayback();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
#endif // VCR_SUPPORT
|