1
0
mirror of https://git.code.sf.net/p/fuse-emulator/fuse synced 2026-01-28 14:20:54 +03:00
Files
fuse/movie.c
2016-10-24 11:45:18 +11:00

490 lines
13 KiB
C

/* movie.c: Routines for creating 'movie' with border
Copyright (c) 2006-2015 Gergely Szasz
Copyright (c) 2015 Stuart Brady
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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
Author contact information:
E-mail: philip-fuse@shadowmagic.org.uk
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <libspectrum.h>
#ifdef HAVE_ZLIB_H
#define ZLIB_CONST
#include <zlib.h>
#endif
#include "display.h"
#include "fuse.h"
#include "machine.h"
#include "movie.h"
#include "movie_tables.h"
#include "options.h"
#include "peripherals/scld.h"
#include "screenshot.h"
#include "settings.h"
#include "sound.h"
#include "ui/ui.h"
#undef MOVIE_DEBUG_PRINT
/*
FMF - Fuse Movie File
Fuse Movie File:
File Header:
off len data description
0 4 "FMF_" Magic header
4 2 "V1" Version
6 1 <e|E> Endianness (e - little / E- big)
7 1 <U|Z> Compression ( U - uncompressed / Z - zlib compressed )
8 1 # Frame rate ( 1:# )
9 1 <$|R|C|X> Screen type
10 1 <A|B|C|D|E> timing code
11 1 <P|U|A> Sound encoding
12 2 Freq Sound freq in Hz
14 1 <S|M> Sound stereo / mono
15 1 "\n" padding (<new line>)
e.g. FMF_V1eZ\001$AU\000\175M -> little endian compressed normal screen, 48k timing, u-Law mono 32000Hz sound
Data
Frame data header
off len data description
0 1 <$|S|N|X> Data chunk type
$ -> screen area (slice)
off len data description
0 1 x X coord (0-39)
1 2 y Y coord (0-239)
3 1 w width (1-40)
4 2 h height (1-240)
6 ? runlength encoded data bytes
bitmap1; attrib1 ZX$/TX$/HiCol ($/X/C)
bitmap1; bitmap2; attrib HiR (R)
runlength encoding:
abcdefghbb#gg# -> two identical byte plus len code a #+2 len
S -> sound chunk
off len data description
0 1 <P|U|A> sound encoding type P-> 16bit signed PCM, U -> u-Law, A -> A-Law
1 2 Freq sound freq in Hz
3 1 <S|M> channels S-> stereo, M-> mono
4 2 length-1 length in frames (0-65535) -> 1-65536 sound frame
N -> New Frame
off len data description
0 1 # frame rate 1:# (1:1 -> ~50/s, 1:2 -> ~25/a ...)
1 1 <$|R|C|X> screen type $ - standard screen,
R - HiRes,
C - HiColor,
X - standard screen on Timex (double size)
2 1 <A|B|C|D> frame timing code A - 16, 48, TC2048, TC2068, Scorpion, SE
B - 128, +2, +2A, +3, +3E
C - TS2068
D - Pentagon
E - 48 NTSC
In a frame there are no, one or several screen rectangle, changed from
the previouse frame.
X -> End of recording
off len data description
There is no any data... It mark the end of the last frame. So we can
concat several FMF file without any problem...
*/
int movie_recording = 0;
static int movie_paused = 0;
static int frame_no, slice_no;
static FILE *of = NULL; /* out file */
static int fmf_screen;
static libspectrum_byte head[8];
static int freq = 0;
static char stereo = 'M';
static char format = '?';
static int framesiz = 4;
static libspectrum_byte sbuff[ 4096 ];
#ifdef HAVE_ZLIB_H
#define ZBUF_SIZE 8192
static int fmf_compr = -1;
static z_stream zstream;
static unsigned char zbuf_o[ ZBUF_SIZE ];
#endif /* HAVE_ZLIB_H */
static unsigned char alaw_table[2048 + 1] = { ALAW_ENC_TAB };
void movie_start_frame( void );
void movie_init_sound( int f, int s );
static char
get_timing( void )
{
switch( machine_current->machine ) {
case LIBSPECTRUM_MACHINE_16:
case LIBSPECTRUM_MACHINE_48:
case LIBSPECTRUM_MACHINE_TC2048:
case LIBSPECTRUM_MACHINE_TC2068:
case LIBSPECTRUM_MACHINE_SCORP:
case LIBSPECTRUM_MACHINE_SE:
return 'A';
case LIBSPECTRUM_MACHINE_128:
case LIBSPECTRUM_MACHINE_PLUS2:
case LIBSPECTRUM_MACHINE_PLUS2A:
case LIBSPECTRUM_MACHINE_PLUS3:
case LIBSPECTRUM_MACHINE_PLUS3E:
return 'B';
case LIBSPECTRUM_MACHINE_TS2068:
return 'C';
case LIBSPECTRUM_MACHINE_PENT:
case LIBSPECTRUM_MACHINE_PENT512:
case LIBSPECTRUM_MACHINE_PENT1024:
return 'D';
case LIBSPECTRUM_MACHINE_48_NTSC:
return 'E';
case LIBSPECTRUM_MACHINE_UNKNOWN:
default:
return '?';
}
}
static char
get_screentype( void )
{
if( machine_current->timex ) { /* ALTDFILE and default */
if( scld_last_dec.name.hires )
return 'R'; /* HIRES screen */
else if( scld_last_dec.name.b1 )
return 'C'; /* HICOLOR screen */
else
return 'X'; /* STANDARD screen on timex machine */
}
return '$'; /* STANDARD screen */
}
#ifdef HAVE_ZLIB_H
static void
fwrite_compr( const void *b, size_t n, size_t m, FILE *f )
{
if( fmf_compr == 0 ) {
fwrite( b, n, m, f );
} else {
zstream.avail_in = n * m;
zstream.next_in = b;
zstream.avail_out = ZBUF_SIZE;
zstream.next_out = zbuf_o;
do {
deflate( &zstream, Z_NO_FLUSH );
while( zstream.avail_out != ZBUF_SIZE ) {
fwrite( zbuf_o, ZBUF_SIZE - zstream.avail_out, 1, of );
zstream.avail_out = ZBUF_SIZE;
zstream.next_out = zbuf_o;
deflate( &zstream, Z_NO_FLUSH );
}
} while ( zstream.avail_in != 0 );
}
}
#else /* HAVE_ZLIB_H */
#define fwrite_compr fwrite
#endif /* HAVE_ZLIB_H */
static void
movie_compress_area( int x, int y, int w, int h, int s )
{
libspectrum_dword *dpoint, *dline;
libspectrum_byte d, d1, *b;
libspectrum_byte buff[ 960 ];
int w0, h0, l;
dline = &display_last_screen[x + 40 * y];
b = buff; l = -1;
d1 = ( ( *dline >> s ) & 0xff ) + 1; /* *d1 != dpoint :-) */
for( h0 = h; h0 > 0; h0--, dline += 40 ) {
dpoint = dline;
for( w0 = w; w0 > 0; w0--, dpoint++) {
d = ( *dpoint >> s ) & 0xff; /* bitmask1 */
if( d != d1 ) {
if( l > -1 ) { /* save running length 0-255 */
*b++ = l;
l = -1; /* reset l */
}
*b++ = d1 = d;
} else if( l >= 0 ) {
if( l == 255 ) { /* close run, and may start a new? */
*b++ = l; *b++ = d; l = -1;
} else {
l++;
}
} else {
*b++ = d;
l++;
}
/* d1 = d; */
}
if( b - buff > 960 - 128 ) { /* worst case 40*1.5 per line */
fwrite_compr( buff, b - buff, 1, of );
b = buff;
}
}
if( l > -1 ) { /* save running length 0-255 */
*b++ = l;
}
if( b != buff ) { /* dump remain */
fwrite_compr( buff, b - buff, 1, of );
}
}
/* Fetch pixel (x, y). On a Timex this will be a point on a 640x480 canvas,
on a Sinclair/Amstrad/Russian clone this will be a point on a 320x240
canvas */
/* x: 0 - 39; y: 0 - 239 */
/* abcdefghijkl... cc# where # mean cc + # c char*/
void
movie_add_area( int x, int y, int w, int h )
{
if( movie_paused ) {
movie_start_frame();
return;
}
head[0] = '$'; /* RLE compressed data... */
head[1] = x;
head[2] = y & 0xff;
head[3] = y >> 8;
head[4] = w;
head[5] = h & 0xff;
head[6] = h >> 8;
fwrite_compr( head, 7, 1, of );
movie_compress_area( x, y, w, h, 0 ); /* Bitmap1 */
movie_compress_area( x, y, w, h, 8 ); /* Attrib/B2 */
if( fmf_screen == 'R' ) {
movie_compress_area( x, y, w, h, 16 ); /* HiRes attrib */
}
slice_no++;
}
static void
movie_start_fmf( const char *name )
{
if( ( of = fopen(name, "wb") ) == NULL ) { /* trunc old file ? or append ? */
ui_error( UI_ERROR_ERROR, "error opening movie file '%s': %s", name,
strerror( errno ) );
return;
}
#ifdef WORDS_BIGENDIAN
fwrite( "FMF_V1E", 7, 1, of ); /* write magic header Fuse Movie File */
#else /* WORDS_BIGENDIAN */
fwrite( "FMF_V1e", 7, 1, of ); /* write magic header Fuse Movie File */
#endif /* WORDS_BIGENDIAN */
#ifdef HAVE_ZLIB_H
if( option_enumerate_movie_movie_compr() == 0 ) {
fmf_compr = 0;
fwrite( "U", 1, 1, of ); /* not compressed */
} else {
fmf_compr = Z_DEFAULT_COMPRESSION;
fwrite( "Z", 1, 1, of ); /* compressed */
}
if( fmf_compr != 0 ) {
zstream.zalloc = Z_NULL;
zstream.zfree = Z_NULL;
zstream.opaque = Z_NULL;
zstream.avail_in = 0;
zstream.next_in = Z_NULL;
deflateInit( &zstream, fmf_compr );
}
#else /* HAVE_ZLIB_H */
fwrite( "U", 1, 1, of ); /* cannot be compressed */
#endif /* HAVE_ZLIB_H */
movie_init_sound( settings_current.sound_freq,
sound_stereo_ay != SOUND_STEREO_AY_NONE );
head[0] = settings_current.frame_rate;
head[1] = get_screentype();
head[2] = get_timing();
head[3] = format; /* sound format */
head[4] = freq & 0xff;
head[5] = freq >> 8;
head[6] = stereo;
head[7] = '\n'; /* padding */
fwrite( head, 8, 1, of ); /* write initial params */
movie_add_area( 0, 0, 40, 240 );
}
void
movie_start( const char *name ) /* some init, open file (name)*/
{
frame_no = slice_no = 0;
if( name == NULL || *name == '\0' )
name = "fuse.fmf"; /* fuse movie file */
movie_start_fmf( name );
movie_recording = 1;
ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_RECORDING, 1 );
ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_PAUSE, 1 );
}
void
movie_stop( void )
{
if( !movie_paused && !movie_recording ) return;
fwrite_compr( "X", 1, 1, of ); /* End of Recording! */
#ifdef HAVE_ZLIB_H
{
if( fmf_compr != 0 ) { /* close zlib */
zstream.avail_in = 0;
do {
zstream.avail_out = ZBUF_SIZE;
zstream.next_out = zbuf_o;
deflate( &zstream, Z_SYNC_FLUSH );
if( zstream.avail_out != ZBUF_SIZE )
fwrite( zbuf_o, ZBUF_SIZE - zstream.avail_out, 1, of );
} while ( zstream.avail_out != ZBUF_SIZE );
deflateEnd( &zstream );
fmf_compr = -1;
}
}
#endif /* HAVE_ZLIB_H */
format = '?';
if( of ) {
fclose( of );
of = NULL;
}
#ifdef MOVIE_DEBUG_PRINT
fprintf( stderr, "Debug movie: saved %d.%d frame(.slice)\n", frame_no, slice_no );
#endif /* MOVIE_DEBUG_PRINT */
movie_recording = 0;
movie_paused = 0;
ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_RECORDING, 0 );
}
void
movie_pause( void )
{
if( !movie_paused && !movie_recording ) return;
if( movie_recording ) {
movie_recording = 0;
movie_paused = 1;
ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_PAUSE, 0 );
} else {
movie_recording = 1;
movie_paused = 1;
ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_PAUSE, 1 );
}
}
void
movie_init_sound( int f, int s )
{
/* initialise sound format */
format = option_enumerate_movie_movie_compr() == 2 ? 'A' : 'P';
freq = f;
stereo = ( s ? 'S' : 'M' );
framesiz = ( stereo == 'S' ? 2 : 1 ) * ( format == 'P' ? 2 : 1 );
}
static inline void
write_alaw( libspectrum_signed_word *buff, int len )
{
int i = 0;
while( len-- ) {
if( *buff >= 0)
sbuff[i++] = alaw_table[*buff >> 4];
else
sbuff[i++] = 0x7f & alaw_table [- *buff >> 4];
buff++;
if( i == 4096 ) {
i = 0;
fwrite_compr( sbuff, 4096, 1, of ); /* write frame */
}
}
if( i )
fwrite_compr( sbuff, i, 1, of ); /* write remaind */
}
static void
add_sound( libspectrum_signed_word *buff, int len )
{
head[0] = 'S'; /* sound frame */
head[1] = format; /* sound format */
head[2] = freq & 0xff;
head[3] = freq >> 8;
head[4] = stereo;
len--; /*len - 1*/
head[5] = len & 0xff;
head[6] = len >> 8;
len++; /* len :-) */
fwrite_compr( head, 7, 1, of ); /* Sound frame */
if( format == 'P' )
fwrite_compr( buff, len * framesiz , 1, of ); /* write frame */
else if( format == 'A' )
write_alaw( buff, len * framesiz );
}
void
movie_add_sound( libspectrum_signed_word *buff, int len )
{
while( len ) {
if( stereo == 'S' ) {
add_sound( buff, len > 131072 ? 65536 : len >> 1 );
buff += len > 131072 ? 131072 : len;
len -= len > 131072 ? 131072 : len;
} else {
add_sound( buff, len > 65536 ? 65536 : len );
buff += len > 65536 ? 65536 : len;
len -= len > 65536 ? 65536 : len;
}
}
}
void
movie_start_frame( void )
{
/* $ - ZX$, T - TX$, C - HiCol, R - HiRes */
head[0] = 'N';
head[1] = settings_current.frame_rate;
head[2] = get_screentype();
head[3] = get_timing();
fwrite_compr( head, 4, 1, of ); /* New frame! */
frame_no++;
if( movie_paused ) {
movie_paused = 0;
movie_add_area( 0, 0, 40, 240 );
}
}
void
movie_init( void )
{
/* start movie recording if user requested... */
if( settings_current.movie_start )
movie_start( settings_current.movie_start );
}