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