1 /* movie.c: Routines for creating 'movie' with border
2 Copyright (c) 2006-2011 Gergely Szasz
3
4 $Id: movie.c 4775 2012-11-26 23:03:36Z sbaldovi $
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 Author contact information:
21
22 E-mail: philip-fuse@shadowmagic.org.uk
23
24 */
25
26 #include <config.h>
27
28 #include <errno.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <sys/types.h>
33 #include <unistd.h>
34
35 #include <libspectrum.h>
36 #ifdef HAVE_ZLIB_H
37 #include <zlib.h>
38 #endif
39
40 #include "display.h"
41 #include "fuse.h"
42 #include "machine.h"
43 #include "movie_tables.h"
44 #include "options.h"
45 #include "peripherals/scld.h"
46 #include "screenshot.h"
47 #include "settings.h"
48 #include "sound.h"
49 #include "ui/ui.h"
50
51 #undef MOVIE_DEBUG_PRINT
52
53 /*
54 FMF - Fuse Movie File
55
56 Fuse Movie File:
57 File Header:
58 off len data description
59 0 4 "FMF_" Magic header
60 4 2 "V1" Version
61 6 1 <e|E> Endianness (e - little / E- big)
62 7 1 <U|Z> Compression ( U - uncompressed / Z - zlib compressed )
63 8 1 # Frame rate ( 1:# )
64 9 1 <$|R|C|X> Screen type
65 10 1 <A|B|C|D|E> timing code
66 11 1 <P|U|A> Sound encoding
67 12 2 Freq Sound freq in Hz
68 14 1 <S|M> Sound stereo / mono
69 15 1 "\n" padding (<new line>)
70
71 e.g. FMF_V1eZ\001$AU\000\175M -> little endian compressed normal screen, 48k timing, u-Law mono 32000Hz sound
72 Data
73 Frame data header
74 off len data description
75 0 1 <$|S|N|X> Data chunk type
76
77 $ -> screen area (slice)
78 off len data description
79 0 1 x X coord (0-39)
80 1 2 y Y coord (0-239)
81 3 1 w width (1-40)
82 4 2 h height (1-240)
83 6 ? runlength encoded data bytes
84 bitmap1; attrib1 ZX$/TX$/HiCol ($/X/C)
85 bitmap1; bitmap2; attrib HiR (R)
86 runlength encoding:
87 abcdefghbb#gg# -> two identical byte plus len code a #+2 len
88
89 S -> sound chunk
90 off len data description
91 0 1 <P|U|A> sound encoding type P-> 16bit signed PCM, U -> u-Law, A -> A-Law
92 1 2 Freq sound freq in Hz
93 3 1 <S|M> channels S-> stereo, M-> mono
94 4 2 length-1 length in frames (0-65535) -> 1-65536 sound frame
95
96 N -> New Frame
97 off len data description
98 0 1 # frame rate 1:# (1:1 -> ~50/s, 1:2 -> ~25/a ...)
99 1 1 <$|R|C|X> screen type $ - standard screen,
100 R - HiRes,
101 C - HiColor,
102 X - standard screen on Timex (double size)
103 2 1 <A|B|C|D> frame timing code A - 16, 48, TC2048, TC2068, Scorpion, SE
104 B - 128, +2, +2A, +3, +3E
105 C - TS2068
106 D - Pentagon
107 E - 48 NTSC
108 In a frame there are no, one or several screen rectangle, changed from
109 the previouse frame.
110
111 X -> End of recording
112 off len data description
113 There is no any data... It mark the end of the last frame. So we can
114 concat several FMF file without any problem...
115 */
116
117 int movie_recording = 0;
118 static int movie_paused = 0;
119
120 static int frame_no, slice_no;
121
122 static FILE *of = NULL; /* out file */
123 static int fmf_screen;
124 static libspectrum_byte head[8];
125 static int freq = 0;
126 static char stereo = 'M';
127 static char format = '?';
128 static int framesiz = 4;
129
130 static libspectrum_byte sbuff[ 4096 ];
131 #ifdef HAVE_ZLIB_H
132 #define ZBUF_SIZE 8192
133 static int fmf_compr = -1;
134 static z_stream zstream;
135 static unsigned char zbuf_o[ ZBUF_SIZE ];
136 #endif /* HAVE_ZLIB_H */
137
138 static unsigned char alaw_table[2048 + 1] = { ALAW_ENC_TAB };
139
140 void movie_start_frame( void );
141 void movie_init_sound( int f, int s );
142
143 static char
get_timing()144 get_timing()
145 {
146 switch( machine_current->machine ) {
147 case LIBSPECTRUM_MACHINE_16:
148 case LIBSPECTRUM_MACHINE_48:
149 case LIBSPECTRUM_MACHINE_TC2048:
150 case LIBSPECTRUM_MACHINE_TC2068:
151 case LIBSPECTRUM_MACHINE_SCORP:
152 case LIBSPECTRUM_MACHINE_SE:
153 return 'A';
154 break;
155 case LIBSPECTRUM_MACHINE_128:
156 case LIBSPECTRUM_MACHINE_PLUS2:
157 case LIBSPECTRUM_MACHINE_PLUS2A:
158 case LIBSPECTRUM_MACHINE_PLUS3:
159 case LIBSPECTRUM_MACHINE_PLUS3E:
160 return 'B';
161 break;
162 case LIBSPECTRUM_MACHINE_TS2068:
163 return 'C';
164 break;
165 case LIBSPECTRUM_MACHINE_PENT:
166 case LIBSPECTRUM_MACHINE_PENT512:
167 case LIBSPECTRUM_MACHINE_PENT1024:
168 return 'D';
169 break;
170 case LIBSPECTRUM_MACHINE_48_NTSC:
171 return 'E';
172 break;
173 case LIBSPECTRUM_MACHINE_UNKNOWN:
174 default:
175 return '?';
176 }
177 }
178
179 static char
get_screentype()180 get_screentype()
181 {
182 if( machine_current->timex ) { /* ALTDFILE and default */
183 if( scld_last_dec.name.hires )
184 return 'R'; /* HIRES screen */
185 else if( scld_last_dec.name.b1 )
186 return 'C'; /* HICOLOR screen */
187 else
188 return 'X'; /* STANDARD screen on timex machine */
189 }
190 return '$'; /* STANDARD screen */
191 }
192
193 #ifdef HAVE_ZLIB_H
194 static void
fwrite_compr(void * b,size_t n,size_t m,FILE * f)195 fwrite_compr( void *b, size_t n, size_t m, FILE *f )
196 {
197 if( fmf_compr == 0 ) {
198 fwrite( b, n, m, f );
199 } else {
200 zstream.avail_in = n * m;
201 zstream.next_in = b;
202 zstream.avail_out = ZBUF_SIZE;
203 zstream.next_out = zbuf_o;
204 do {
205 deflate( &zstream, Z_NO_FLUSH );
206 while( zstream.avail_out != ZBUF_SIZE ) {
207 fwrite( zbuf_o, ZBUF_SIZE - zstream.avail_out, 1, of );
208 zstream.avail_out = ZBUF_SIZE;
209 zstream.next_out = zbuf_o;
210 deflate( &zstream, Z_NO_FLUSH );
211 }
212 } while ( zstream.avail_in != 0 );
213 }
214 }
215 #else /* HAVE_ZLIB_H */
216 #define fwrite_compr fwrite
217 #endif /* HAVE_ZLIB_H */
218
219 static void
movie_compress_area(int x,int y,int w,int h,int s)220 movie_compress_area( int x, int y, int w, int h, int s )
221 {
222 libspectrum_dword *dpoint, *dline;
223 libspectrum_byte d, d1, *b;
224 libspectrum_byte buff[ 960 ];
225 int w0, h0, l;
226
227 dline = &display_last_screen[x + 40 * y];
228 b = buff; l = -1;
229 d1 = ( ( *dline >> s ) & 0xff ) + 1; /* *d1 != dpoint :-) */
230
231 for( h0 = h; h0 > 0; h0--, dline += 40 ) {
232 dpoint = dline;
233 for( w0 = w; w0 > 0; w0--, dpoint++) {
234 d = ( *dpoint >> s ) & 0xff; /* bitmask1 */
235 if( d != d1 ) {
236 if( l > -1 ) { /* save running length 0-255 */
237 *b++ = l;
238 l = -1; /* reset l */
239 }
240 *b++ = d1 = d;
241 } else if( l >= 0 ) {
242 if( l == 255 ) { /* close run, and may start a new? */
243 *b++ = l; *b++ = d; l = -1;
244 } else {
245 l++;
246 }
247 } else {
248 *b++ = d;
249 l++;
250 }
251 /* d1 = d; */
252 }
253 if( b - buff > 960 - 128 ) { /* worst case 40*1.5 per line */
254 fwrite_compr( buff, b - buff, 1, of );
255 b = buff;
256 }
257 }
258 if( l > -1 ) { /* save running length 0-255 */
259 *b++ = l;
260 }
261 if( b != buff ) { /* dump remain */
262 fwrite_compr( buff, b - buff, 1, of );
263 }
264 }
265
266 /* Fetch pixel (x, y). On a Timex this will be a point on a 640x480 canvas,
267 on a Sinclair/Amstrad/Russian clone this will be a point on a 320x240
268 canvas */
269
270 /* x: 0 - 39; y: 0 - 239 */
271
272 /* abcdefghijkl... cc# where # mean cc + # c char*/
273
274 void
movie_add_area(int x,int y,int w,int h)275 movie_add_area( int x, int y, int w, int h )
276 {
277 if( movie_paused ) {
278 movie_start_frame();
279 return;
280 }
281 head[0] = '$'; /* RLE compressed data... */
282 head[1] = x;
283 head[2] = y & 0xff;
284 head[3] = y >> 8;
285 head[4] = w;
286 head[5] = h & 0xff;
287 head[6] = h >> 8;
288 fwrite_compr( head, 7, 1, of );
289 movie_compress_area( x, y, w, h, 0 ); /* Bitmap1 */
290 movie_compress_area( x, y, w, h, 8 ); /* Attrib/B2 */
291 if( fmf_screen == 'R' ) {
292 movie_compress_area( x, y, w, h, 16 ); /* HiRes attrib */
293 }
294 slice_no++;
295 }
296
297 static void
movie_start_fmf(const char * name)298 movie_start_fmf( const char *name )
299 {
300 if( ( of = fopen(name, "wb") ) == NULL ) { /* trunc old file ? or append ? */
301 ui_error( UI_ERROR_ERROR, "error opening movie file '%s': %s", name,
302 strerror( errno ) );
303 return;
304 }
305 #ifdef WORDS_BIGENDIAN
306 fwrite( "FMF_V1E", 7, 1, of ); /* write magic header Fuse Movie File */
307 #else /* WORDS_BIGENDIAN */
308 fwrite( "FMF_V1e", 7, 1, of ); /* write magic header Fuse Movie File */
309 #endif /* WORDS_BIGENDIAN */
310 #ifdef HAVE_ZLIB_H
311 if( option_enumerate_movie_movie_compr() == 0 ) {
312 fmf_compr = 0;
313 fwrite( "U", 1, 1, of ); /* not compressed */
314 } else {
315 fmf_compr = Z_DEFAULT_COMPRESSION;
316 fwrite( "Z", 1, 1, of ); /* compressed */
317 }
318 if( fmf_compr != 0 ) {
319 zstream.zalloc = Z_NULL;
320 zstream.zfree = Z_NULL;
321 zstream.opaque = Z_NULL;
322 zstream.avail_in = 0;
323 zstream.next_in = Z_NULL;
324 deflateInit( &zstream, fmf_compr );
325 }
326 #else /* HAVE_ZLIB_H */
327 fwrite( "U", 1, 1, of ); /* cannot be compressed */
328 #endif /* HAVE_ZLIB_H */
329 movie_init_sound( settings_current.sound_freq,
330 sound_stereo_ay != SOUND_STEREO_AY_NONE );
331 head[0] = settings_current.frame_rate;
332 head[1] = get_screentype();
333 head[2] = get_timing();
334 head[3] = format; /* sound format */
335 head[4] = freq & 0xff;
336 head[5] = freq >> 8;
337 head[6] = stereo;
338 head[7] = '\n'; /* padding */
339 fwrite( head, 8, 1, of ); /* write initial params */
340 movie_add_area( 0, 0, 40, 240 );
341 }
342
343 void
movie_start(const char * name)344 movie_start( const char *name ) /* some init, open file (name)*/
345 {
346 frame_no = slice_no = 0;
347 if( name == NULL || *name == '\0' )
348 name = "fuse.fmf"; /* fuse movie file */
349
350 movie_start_fmf( name );
351 movie_recording = 1;
352 ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_RECORDING, 1 );
353 ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_PAUSE, 1 );
354 }
355
356 void
movie_stop(void)357 movie_stop( void )
358 {
359 if( !movie_paused && !movie_recording ) return;
360
361 fwrite_compr( "X", 1, 1, of ); /* End of Recording! */
362 #ifdef HAVE_ZLIB_H
363 {
364 if( fmf_compr != 0 ) { /* close zlib */
365 zstream.avail_in = 0;
366 do {
367 zstream.avail_out = ZBUF_SIZE;
368 zstream.next_out = zbuf_o;
369 deflate( &zstream, Z_SYNC_FLUSH );
370 if( zstream.avail_out != ZBUF_SIZE )
371 fwrite( zbuf_o, ZBUF_SIZE - zstream.avail_out, 1, of );
372 } while ( zstream.avail_out != ZBUF_SIZE );
373 deflateEnd( &zstream );
374 fmf_compr = -1;
375 }
376 }
377 #endif /* HAVE_ZLIB_H */
378 format = '?';
379 if( of ) {
380 fclose( of );
381 of = NULL;
382 }
383 #ifdef MOVIE_DEBUG_PRINT
384 fprintf( stderr, "Debug movie: saved %d.%d frame(.slice)\n", frame_no, slice_no );
385 #endif /* MOVIE_DEBUG_PRINT */
386 movie_recording = 0;
387 movie_paused = 0;
388 ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_RECORDING, 0 );
389 }
390
391 void
movie_pause(void)392 movie_pause( void )
393 {
394 if( !movie_paused && !movie_recording ) return;
395
396 if( movie_recording ) {
397 movie_recording = 0;
398 movie_paused = 1;
399 ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_PAUSE, 0 );
400 } else {
401 movie_recording = 1;
402 movie_paused = 1;
403 ui_menu_activate( UI_MENU_ITEM_FILE_MOVIE_PAUSE, 1 );
404 }
405 }
406
407 void
movie_init_sound(int f,int s)408 movie_init_sound( int f, int s )
409 {
410 /* initialise sound format */
411 format = option_enumerate_movie_movie_compr() == 2 ? 'A' : 'P';
412 freq = f;
413 stereo = ( s ? 'S' : 'M' );
414 framesiz = ( stereo == 'S' ? 2 : 1 ) * ( format == 'P' ? 2 : 1 );
415 }
416
417 static inline void
write_alaw(libspectrum_signed_word * buff,int len)418 write_alaw( libspectrum_signed_word *buff, int len )
419 {
420 int i = 0;
421 while( len-- ) {
422 if( *buff >= 0)
423 sbuff[i++] = alaw_table[*buff >> 4];
424 else
425 sbuff[i++] = 0x7f & alaw_table [- *buff >> 4];
426 buff++;
427 if( i == 4096 ) {
428 i = 0;
429 fwrite_compr( sbuff, 4096, 1, of ); /* write frame */
430 }
431 }
432 if( i )
433 fwrite_compr( sbuff, i, 1, of ); /* write remaind */
434 }
435
436 static void
add_sound(libspectrum_signed_word * buff,int len)437 add_sound( libspectrum_signed_word *buff, int len )
438 {
439 head[0] = 'S'; /* sound frame */
440 head[1] = format; /* sound format */
441 head[2] = freq & 0xff;
442 head[3] = freq >> 8;
443 head[4] = stereo;
444 len--; /*len - 1*/
445 head[5] = len & 0xff;
446 head[6] = len >> 8;
447 len++; /* len :-) */
448 fwrite_compr( head, 7, 1, of ); /* Sound frame */
449 if( format == 'P' )
450 fwrite_compr( buff, len * framesiz , 1, of ); /* write frame */
451 else if( format == 'A' )
452 write_alaw( buff, len * framesiz );
453 }
454
455 void
movie_add_sound(libspectrum_signed_word * buff,int len)456 movie_add_sound( libspectrum_signed_word *buff, int len )
457 {
458 while( len ) {
459 if( stereo == 'S' ) {
460 add_sound( buff, len > 131072 ? 65536 : len >> 1 );
461 buff += len > 131072 ? 131072 : len;
462 len -= len > 131072 ? 131072 : len;
463 } else {
464 add_sound( buff, len > 65536 ? 65536 : len );
465 buff += len > 65536 ? 65536 : len;
466 len -= len > 65536 ? 65536 : len;
467 }
468 }
469 }
470
471 void
movie_start_frame(void)472 movie_start_frame( void )
473 {
474 /* $ - ZX$, T - TX$, C - HiCol, R - HiRes */
475 head[0] = 'N';
476 head[1] = settings_current.frame_rate;
477 head[2] = get_screentype();
478 head[3] = get_timing();
479 fwrite_compr( head, 4, 1, of ); /* New frame! */
480 frame_no++;
481 if( movie_paused ) {
482 movie_paused = 0;
483 movie_add_area( 0, 0, 40, 240 );
484 }
485 }
486
487 void
movie_init()488 movie_init()
489 {
490 /* start movie recording if user requested... */
491 if( settings_current.movie_start )
492 movie_start( settings_current.movie_start );
493 }
494