1 //
2 // AVI.c
3 // CoolCV
4 //
5 // Created by Oscar Toledo on 02/11/15.
6 // Copyright (c) 2015 Oscar Toledo. All rights reserved.
7 //
8 // With portions of my private video converter to AVI
9 //
10 // Modified by J. Zbiciak for jzIntv:
11 // -- Variable audio rates
12 // -- 8bpp palette-based encoding mode (was 32bpp)
13 // -- Border color and border region
14 // -- Selectable compression
15 // -- Slight code and data restructuring
16
17 // ZMBV specs from http://wiki.multimedia.cx/?title=DosBox_Capture_Codec
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <stdint.h>
22 #include "config.h"
23 #include "plat/plat_lib.h"
24 #include "zlib/zlib.h"
25 #include "gfx/palette.h"
26 #include "avi/avi.h"
27
28 #define APP_TITLE "jzIntv"
29
30 #define AVI_AUDIO_BUF (65536)
31 #define AVI_VIDEO_BUF_BYTES (1 << 20) /* 1MB should be waaay plenty */
32 #define AVI_VIDEO_BUF_FRAMES (4096) /* waaaaay plenty */
33
34 #define INPUT_DIM_X (320)
35 #define INPUT_DIM_Y (192)
36
37 #define AVI_BORD_X_SZ (16)
38 #define AVI_BORD_Y_SZ (16)
39 #define AVI_DIM_X (INPUT_DIM_X + 2*AVI_BORD_X_SZ)
40 #define AVI_DIM_Y (INPUT_DIM_Y + 2*AVI_BORD_Y_SZ)
41 #define AVI_BPP (8)
42 #define AVI_FRAME_PIX (AVI_DIM_X * AVI_DIM_Y)
43 #define AVI_FRAME_BYTES (AVI_DIM_X * AVI_DIM_Y * AVI_BPP / 8)
44
45 #define AVI_VIDEO_ENCODE_BYTES (AVI_FRAME_BYTES + 4096)
46 #define AVI_VIDEO_WRAP_THRESH (AVI_VIDEO_BUF_BYTES - AVI_VIDEO_ENCODE_BYTES)
47
48 #define BLOCK_WIDTH (16)
49 #define BLOCK_HEIGHT (16)
50
51 #define NUM_X_BLOCK (AVI_DIM_X / BLOCK_WIDTH)
52 #define NUM_Y_BLOCK (AVI_DIM_Y / BLOCK_HEIGHT)
53
54 #if AVI_BPP != 8
55 #error "Code needs more work to support BPP != 8"
56 #endif
57
58 #if AVI_DIM_X % BLOCK_WIDTH || AVI_DIM_Y % BLOCK_HEIGHT
59 #error "Image dimensions must be a multiple of block size"
60 #endif
61
62 #define STRUCT_ALIGN (64)
63
64 static double avi_time_scale = 1.0;
65 static double audio_time_scale = 1.0;
66 static double audio_time_scale_ratio = 1.0;
67
68
69 /* Locations to be patched */
70 typedef struct avi_container_t
71 {
72 long pos[6];
73 long base_movie_size;
74 } avi_container_t;
75
76 /* RIFF state */
77 typedef struct avi_riff_t
78 {
79 FILE *file;
80 int *index;
81 int *base_index;
82 int index_size;
83 int nesting_level;
84 long nesting[512];
85 } avi_riff_t;
86
87 /* Audio buffering */
88 typedef struct avi_audio_t
89 {
90 uint16_t buffer[AVI_AUDIO_BUF];
91 uint16_t *buffer_read;
92 uint16_t *buffer_write;
93 int buffer_total_read;
94 int buffer_total_write;
95 double time_remainder;
96 } avi_audio_t ALIGN(STRUCT_ALIGN);
97
98 typedef struct
99 {
100 uint8_t f[AVI_DIM_Y][AVI_DIM_X];
101 } avi_frame_t ALIGN(STRUCT_ALIGN);
102
103 /* Video buffering */
104 typedef struct avi_video_t
105 {
106 avi_frame_t frame[2];
107 uint8_t toggle;
108 uint8_t palette[256][3];
109 uint8_t encode_buf[AVI_VIDEO_BUF_BYTES];
110 uint32_t encode_buf_wr, encode_buf_rd;
111 uint32_t encode_len[AVI_VIDEO_BUF_FRAMES];
112 uint32_t encode_len_wr, encode_len_rd;
113 uint32_t advance_frames_remaining;
114 double time_remainder;
115 } avi_video_t ALIGN(STRUCT_ALIGN);
116
117 typedef struct avi_pvt_t
118 {
119 avi_container_t container; /* Locations to be patched */
120 avi_info_t info; /* Information and statistics */
121 avi_riff_t riff; /* RIFF state */
122 avi_audio_t audio; /* Audio buffering */
123 avi_video_t video; /* Video buffering */
124 z_stream zstream; /* ZLib state */
125 } avi_pvt_t ALIGN(STRUCT_ALIGN);
126
127
128 /*
129 ** Write a 32 bits code
130 */
riff_write_code(avi_riff_t * const riff,char * code)131 LOCAL INLINE void riff_write_code(avi_riff_t *const riff, char *code)
132 {
133 fwrite(code, 1, 4, riff->file);
134 }
135
136 /*
137 ** Write a 16 bits integer
138 */
riff_write_int_16(avi_riff_t * const riff,int value)139 LOCAL INLINE void riff_write_int_16(avi_riff_t *const riff, int value)
140 {
141 char buf[2];
142
143 buf[0] = value;
144 buf[1] = value >> 8;
145 fwrite(buf, 1, 2, riff->file);
146 }
147
148 /*
149 ** Write a 32 bits integer
150 */
riff_write_int_32(avi_riff_t * const riff,int value)151 LOCAL INLINE void riff_write_int_32(avi_riff_t *const riff, int value)
152 {
153 char buf[4];
154
155 buf[0] = value;
156 buf[1] = value >> 8;
157 buf[2] = value >> 16;
158 buf[3] = value >> 24;
159 fwrite(buf, 1, 4, riff->file);
160 }
161
162 #if 0
163 /*
164 ** Writes a text string
165 */
166 LOCAL INLINE void riff_write_string(avi_riff_t *const riff, char *text)
167 {
168 fwrite(text, 1, strlen(text) + 1, riff->file);
169 }
170 #endif
171
172 /*
173 ** Starts a RIFF element
174 */
riff_start_element(avi_riff_t * const riff,char * element)175 LOCAL INLINE void riff_start_element(avi_riff_t *const riff, char *element)
176 {
177 riff_write_code(riff, element);
178 riff_write_int_32(riff, 0);
179 riff->nesting[riff->nesting_level++] = ftell(riff->file);
180 }
181
182 /*
183 ** Closes a RIFF element
184 */
riff_close_element(avi_riff_t * const riff)185 LOCAL INLINE int riff_close_element(avi_riff_t *const riff)
186 {
187 long c;
188 long a;
189
190 c = riff->nesting[--riff->nesting_level];
191 a = ftell(riff->file);
192 if (a & 1)
193 fwrite("\0", 1, 1, riff->file);
194 fseek(riff->file, c - 4, SEEK_SET);
195 riff_write_int_32(riff, (int) (a - c));
196 if (a & 1)
197 a++;
198 fseek(riff->file, a, SEEK_SET);
199 return (int) (a - c);
200 }
201
202 /*
203 ** Starts a RIFF structure
204 */
riff_start_structure(avi_riff_t * const riff,char * list,char * title)205 LOCAL INLINE void riff_start_structure(avi_riff_t *const riff,
206 char *list, char *title)
207 {
208 riff_start_element(riff, list);
209 riff_write_code(riff, title);
210 }
211
212 /*
213 ** Terminate a RIFF structure
214 */
riff_close_structure(avi_riff_t * const riff)215 LOCAL INLINE int riff_close_structure(avi_riff_t *const riff)
216 {
217 return riff_close_element(riff);
218 }
219
220 // Check in following order:
221 // -- Zero offset
222 // -- 1 STIC tile in four cardinal directions (N/S/E/W)
223 // -- Clockwise spiral from center
224 // Vectors constrained to even pixels due to pixel-doubled nature of Inty.
225 static const int movement[][2] =
226 {
227 { 0, 0 }, { -16, 0 }, { 16, 0 }, { 0, 16 }, { 0, -16 },
228 { 2, 0 }, { 2, 2 }, { 0, 2 }, { -2, 2 }, { -2, 0 },
229 { -2, -2 }, { 0, -2 }, { 2, -2 }, { 4, -2 }, { 4, 0 },
230 { 4, 2 }, { 4, 4 }, { 2, 4 }, { 0, 4 }, { -2, 4 },
231 { -4, 4 }, { -4, 2 }, { -4, 0 }, { -4, -2 }, { -4, -4 },
232 { -2, -4 }, { 0, -4 }, { 2, -4 }, { 4, -4 }, { 6, -4 },
233 { 6, -2 }, { 6, 0 }, { 6, 2 }, { 6, 4 }, { 6, 6 },
234 { 4, 6 }, { 2, 6 }, { 0, 6 }, { -2, 6 }, { -4, 6 },
235 { -6, 6 }, { -6, 4 }, { -6, 2 }, { -6, 0 }, { -6, -2 },
236 { -6, -4 }, { -6, -6 }, { -4, -6 }, { -2, -6 }, { 0, -6 },
237 { 2, -6 }, { 4, -6 }, { 6, -6 }, { 8, -6 }, { 8, -4 },
238 { 8, -2 }, { 8, 0 }, { 8, 2 }, { 8, 4 }, { 8, 6 },
239 { 8, 8 }, { 6, 8 }, { 4, 8 }, { 2, 8 }, { 0, 8 },
240 { -2, 8 }, { -4, 8 }, { -6, 8 }, { -8, 8 }, { -8, 6 },
241 { -8, 4 }, { -8, 2 }, { -8, 0 }, { -8, -2 }, { -8, -4 },
242 { -8, -6 }, { -8, -8 }, { -6, -8 }, { -4, -8 }, { -2, -8 },
243 { 0, -8 }, { 2, -8 }, { 4, -8 }, { 6, -8 }, { 8, -8 },
244 };
245
avi_zalloc(void * opaque,uInt items,uInt size)246 LOCAL void *avi_zalloc( void *opaque, uInt items, uInt size)
247 {
248 UNUSED(opaque);
249 return (void *)malloc(items * size);
250 }
251
avi_zfree(void * opaque,void * to_free)252 LOCAL void avi_zfree( void *opaque, void *to_free )
253 {
254 UNUSED(opaque);
255 free(to_free);
256 }
257
258 #define NUM_MOVEMENT ((int)(sizeof(movement)/sizeof(movement[0])))
259
avi_pvt_align(void * pvt_alloc)260 LOCAL INLINE avi_pvt_t *avi_pvt_align(void *pvt_alloc)
261 {
262 intptr_t pvt_i = (intptr_t)pvt_alloc;
263 pvt_i += (STRUCT_ALIGN - pvt_i) & (STRUCT_ALIGN - 1);
264 return (avi_pvt_t *)pvt_i;
265 }
266
267 /*
268 ** Start video recording
269 */
avi_start_video(avi_writer_t * const avi,FILE * const avi_file,const int fps,const int audio_rate,const int compress,const uint64_t start_time)270 int avi_start_video
271 (
272 avi_writer_t *const avi,
273 FILE *const avi_file,
274 const int fps,
275 const int audio_rate,
276 const int compress,
277 const uint64_t start_time
278 )
279 {
280 // For now, don't try to handle silent AVIs.
281 if (!audio_rate)
282 return 0;
283
284 int stereo = 0;
285 int bits = 16;
286 double real_fps = fps == 60 ? 59.92274 : 50.08012; // STIC_FRAMCLKS/MHz
287
288 if (!avi->pvt_alloc)
289 {
290 /* Allocate a fresh avi_pvt_t for all of our stuff */
291 avi->pvt_alloc = (void*)calloc(sizeof(avi_pvt_t) + STRUCT_ALIGN, 1);
292 if (!avi->pvt_alloc)
293 return -1;
294
295 avi->pvt = avi_pvt_align(avi->pvt_alloc);
296 } else
297 {
298 memset((void*)avi->pvt, 0, sizeof(avi_pvt_t));
299 }
300
301 avi_pvt_t *const pvt = avi->pvt;
302 avi_container_t *const container = &(pvt->container);
303 avi_info_t *const info = &(pvt->info);
304 avi_riff_t *const riff = &(pvt->riff);
305 avi_audio_t *const audio = &(pvt->audio);
306 avi_video_t *const video = &(pvt->video);
307 z_stream *const zstream = &(pvt->zstream);
308
309 info->start_time = start_time;
310 info->active = 1;
311 info->frames_per_sec = real_fps * 1000000;
312 info->usec_per_frame = 1000000 / real_fps;
313 info->audio_rate = audio_rate;
314 info->key_frames = 32; // Must be power-of-2
315 info->advance_audio_frames = 0;
316 info->total_frames = 0;
317 info->max_size_frame = 0;
318 info->max_size_audio = 0;
319 info->total_audio = 0;
320 info->compress = compress;
321
322 audio->buffer_read = audio->buffer;
323 audio->buffer_write = audio->buffer;
324 audio->buffer_total_read = 0;
325 audio->buffer_total_write = 0;
326 audio->time_remainder = 0;
327
328 video->toggle = 0;
329 video->encode_buf_rd = 0;
330 video->encode_buf_wr = 0;
331 video->encode_len_rd = 0;
332 video->encode_len_wr = 0;
333 video->advance_frames_remaining = info->advance_audio_frames;
334 video->time_remainder = 0;
335
336 riff->file = avi_file;
337 riff->nesting_level = 0;
338 riff->index_size = 1048576;
339 riff->index = CALLOC(int, riff->index_size * 4);
340 riff->base_index = riff->index;
341 if (!riff->index)
342 return -1;
343
344 riff_start_structure(riff, "RIFF", "AVI ");
345 riff_start_structure(riff, "LIST", "hdrl");
346 riff_start_element(riff, "avih");
347 riff_write_int_32(riff, info->usec_per_frame); /* Microsecs per frame */
348 riff_write_int_32(riff, 300000); /* Maximum bytes per second */
349 riff_write_int_32(riff, 0); /* Reserved */
350 riff_write_int_32(riff, 0x0110); /* Flags */
351 /* bit 4 = Includes idx1 */
352 /* bit 5 = Use idx1 for everything */
353 /* bit 8 = Video and audio intermixed */
354 /* bit 16 = Optimized for realtime capture */
355 /* bit 17 = Copyrighted data */
356 container->pos[4] = ftell(riff->file);
357 riff_write_int_32(riff, 0); /* Total frames */
358 riff_write_int_32(riff, info->advance_audio_frames);
359 /* Sound frames in advance */
360 riff_write_int_32(riff, 2); /* Total streams */
361 container->pos[0] = ftell(riff->file);
362 riff_write_int_32(riff, 0); /* Suggested buffer size */
363 riff_write_int_32(riff, AVI_DIM_X); /* X size */
364 riff_write_int_32(riff, AVI_DIM_Y); /* Y size */
365 riff_write_int_32(riff, 0); /* Scale */
366 riff_write_int_32(riff, 0); /* Speed */
367 riff_write_int_32(riff, 0); /* Start */
368 riff_write_int_32(riff, 0); /* Length */
369 riff_close_element(riff);
370 /* Video stream */
371 riff_start_structure(riff,"LIST","strl"); /* Stream Leader */
372 riff_start_element(riff, "strh"); /* Stream Header */
373 riff_write_code(riff, "vids"); /* Video */
374 riff_write_code(riff, "ZMBV"); /* Codec */
375 riff_write_int_32(riff, 0); /* Flags */
376 riff_write_int_32(riff, 0); /* Reserved */
377 riff_write_int_32(riff, 0); /* Frames in advance */
378 riff_write_int_32(riff, 1000000); /* Scale */
379 riff_write_int_32(riff, info->frames_per_sec); /* Speed */
380 riff_write_int_32(riff, 0); /* Start of clip */
381 container->pos[5] = ftell(riff->file);
382 riff_write_int_32(riff, 0); /* Length of clip in frames */
383 container->pos[1] = ftell(riff->file);
384 riff_write_int_32(riff, 0); /* Suggested buffer site */
385 riff_write_int_32(riff, 8000); /* Quality (by 100) */
386 riff_write_int_32(riff, 0); /* Sample size */
387 riff_write_int_32(riff, 0); /* Reserved */
388 riff_write_int_32(riff, 0); /* Reserved */
389 riff_close_element(riff);
390 riff_start_element(riff, "strf"); /* Stream Format (idem. BMP) */
391 riff_write_int_32(riff, 40); /* Header size (incl itself) */
392 riff_write_int_32(riff, AVI_DIM_X); /* X size */
393 riff_write_int_32(riff, AVI_DIM_Y); /* Y size */
394 riff_write_int_16(riff, 1); /* Number of planes */
395 riff_write_int_16(riff, AVI_BPP); /* Bits per pixel */
396 riff_write_code(riff, "ZMBV"); /* Codec */
397 riff_write_int_32(riff, AVI_FRAME_BYTES); /* Image size (in bytes) */
398 riff_write_int_32(riff, 0); /* X pixels per meter */
399 riff_write_int_32(riff, 0); /* Y pixels per meter */
400 riff_write_int_32(riff, 0); /* Used colors */
401 riff_write_int_32(riff, 0); /* Important colors */
402 riff_close_element(riff);
403 riff_close_structure(riff);
404 /* Audio stream */
405 riff_start_structure(riff, "LIST", "strl"); /* Stream Leader */
406 riff_start_element(riff, "strh"); /* Stream Header */
407 riff_write_code(riff, "auds"); /* Audio */
408 riff_write_code(riff, "\0\0\0\0"); /* Codec (no compression) */
409 riff_write_int_32(riff, 0); /* Flags */
410 riff_write_int_32(riff, 0); /* Reserved */
411 riff_write_int_32(riff, info->advance_audio_frames);
412 /* Frames in advance */
413 riff_write_int_32(riff, 1); /* Scale */
414 riff_write_int_32(riff, audio_rate); /* Audio sample rate */
415 riff_write_int_32(riff, 0); /* Start of clip */
416 container->pos[2] = ftell(riff->file);
417 riff_write_int_32(riff, 0); /* Length of clip */
418 container->pos[3] = ftell(riff->file);
419 riff_write_int_32(riff, 0); /* Suggested buffer size */
420 riff_write_int_32(riff, 8000); /* Quality (by 100) */
421 riff_write_int_32(riff, (stereo + 1) * (bits / 8)); /* Sample size */
422 riff_write_int_32(riff, 0); /* Reserved */
423 riff_write_int_32(riff, 0); /* Reserved */
424 riff_close_element(riff);
425 riff_start_element(riff, "strf"); /* Stream Format (idem. WAV) */
426 riff_write_int_16(riff, 1); /* Format = PCM */
427 riff_write_int_16(riff, stereo + 1); /* Number of channels */
428 riff_write_int_32(riff, audio_rate); /* Audio sample rate */
429 riff_write_int_32(riff,
430 audio_rate * (stereo + 1) * (bits / 8)); /* Frequency x Chans x Bytes */
431 riff_write_int_16(riff, (stereo + 1) * (bits / 8)); /* Bytes per sample */
432 riff_write_int_16(riff, bits); /* Bits per individual sample */
433 riff_close_element(riff);
434 riff_close_structure(riff);
435
436 riff_start_structure(riff, "LIST", "info");
437
438 riff_start_element(riff, "ISFT");
439 fwrite(APP_TITLE, 1, sizeof(APP_TITLE) - 1, riff->file);
440 riff_close_element(riff);
441
442 riff_close_structure(riff);
443
444 riff_close_structure(riff);
445 container->base_movie_size = ftell(riff->file) + 8;
446 riff_start_structure(riff, "LIST", "movi");
447
448 if (info->compress)
449 {
450 int ret;
451
452 // These are needed because jzIntv's embedded zlib is Z_SOLO
453 zstream->zalloc = avi_zalloc;
454 zstream->zfree = avi_zfree;
455 ret = deflateInit(zstream, 6);
456 if (ret != Z_OK) {
457 jzp_printf("deflateInit returned %d; disabling compression\n", ret);
458 info->compress = 0;
459 }
460 }
461 return 0;
462 }
463
464 // Avoid cumulative rounding error...
465 //
466 // For now, just be lazy and use multiplies and divides. This *can* be
467 // done completely with adds/subs and 32-bit arithmetic using the same
468 // error-bounding approach Bresenham line drawing uses. I'm just too lazy
469 // at the moment.
next_audio_frame_size(const avi_info_t * info)470 LOCAL int next_audio_frame_size(const avi_info_t *info)
471 {
472 const uint64_t curr = (int64_t)info->total_frames * info->usec_per_frame;
473 const uint64_t next = curr + info->usec_per_frame;
474 const uint32_t curr_samples = (curr * info->audio_rate) / 1000000;
475 const uint32_t next_samples = (next * info->audio_rate) / 1000000;
476
477 return next_samples - curr_samples;
478 }
479
480 /*
481 ** Grow AVI index
482 */
grow_base_riff(avi_riff_t * const riff)483 LOCAL int grow_base_riff(avi_riff_t *const riff)
484 {
485 int *new_pointer;
486
487 if (riff->base_index == riff->index + riff->index_size * 4) {
488 new_pointer = (int *)realloc((void *)riff->index,
489 riff->index_size * 2 * 4 * sizeof(int));
490 if (new_pointer == NULL)
491 return 0;
492 riff->base_index = (riff->base_index - riff->index) + new_pointer;
493 riff->index = new_pointer;
494 riff->index_size *= 2;
495 }
496 return 1;
497 }
498
499 LOCAL void avi_end_video_internal
500 (
501 const avi_writer_t *const avi,
502 const int leave_active
503 );
504
505 /*
506 ** Write out the interleaved A/V stream.
507 */
avi_write_interleaved_stream(const avi_writer_t * const avi,const int flush)508 LOCAL int avi_write_interleaved_stream
509 (
510 const avi_writer_t *const avi,
511 const int flush
512 )
513 {
514 avi_pvt_t *const pvt = avi->pvt;
515 avi_info_t *const info = &(pvt->info);
516 avi_riff_t *const riff = &(pvt->riff);
517 avi_audio_t *const audio = &(pvt->audio);
518 avi_video_t *const video = &(pvt->video);
519 avi_container_t *const container = &(pvt->container);
520
521 if (!riff->file)
522 return 0;
523
524 long v0;
525 long v1;
526 long v2;
527 const int audio_frame_size = next_audio_frame_size(info);
528 const int audio_avail = audio->buffer_total_write -
529 audio->buffer_total_read;
530 const int video_avail = video->encode_len_wr - video->encode_len_rd +
531 video->advance_frames_remaining;
532 uint32_t encoded_video_size = 0;
533 int key_frame = 0;
534
535 if (!video_avail || audio_avail < audio_frame_size)
536 return 0;
537
538 v0 = ftell(riff->file);
539 riff_start_structure(riff, "LIST", "rec ");
540 v1 = ftell(riff->file);
541 if (!video->advance_frames_remaining)
542 {
543 riff_start_element(riff, "00dc");
544 uint8_t *const encoded_video = video->encode_buf + video->encode_buf_rd;
545 encoded_video_size =
546 video->encode_len[video->encode_len_rd++ % AVI_VIDEO_BUF_FRAMES];
547
548 key_frame = (*encoded_video == 0x01);
549
550 fwrite(encoded_video, 1, encoded_video_size, riff->file);
551 riff_close_element(riff);
552 if (info->max_size_frame < encoded_video_size)
553 info->max_size_frame = encoded_video_size;
554
555 video->encode_buf_rd += encoded_video_size;
556 if (video->encode_buf_rd >= AVI_VIDEO_WRAP_THRESH)
557 video->encode_buf_rd = 0;
558 }
559 v2 = ftell(riff->file);
560
561 const uint32_t encoded_audio_size = audio_frame_size * sizeof(int16_t);
562 if (video->advance_frames_remaining || !flush)
563 {
564 riff_start_element(riff, "01wb");
565 uint32_t audio_remain = audio_frame_size;
566 if (audio->buffer_read + audio_remain > audio->buffer + AVI_AUDIO_BUF)
567 {
568 const uint32_t c = (audio->buffer + AVI_AUDIO_BUF) -
569 audio->buffer_read;
570 if (c)
571 fwrite(audio->buffer_read, 1, c * sizeof(int16_t), riff->file);
572 audio->buffer_read = audio->buffer;
573 audio->buffer_total_read += c;
574 audio_remain -= c;
575 }
576 fwrite(audio->buffer_read, sizeof(int16_t), audio_remain, riff->file);
577 audio->buffer_read += audio_remain;
578 audio->buffer_total_read += audio_remain;
579 riff_close_element(riff);
580 if (info->max_size_audio < encoded_audio_size)
581 info->max_size_audio = encoded_audio_size;
582 info->total_audio += audio_frame_size;
583 }
584 const uint32_t container_size = riff_close_structure(riff);
585
586 if (grow_base_riff(riff)) {
587 *riff->base_index++ = 0x20636572;
588 *riff->base_index++ = 0x00000001;
589 *riff->base_index++ = (int) (v0 - container->base_movie_size);
590 *riff->base_index++ = (int) container_size;
591 }
592 if (!video->advance_frames_remaining) {
593 if (grow_base_riff(riff)) {
594 *riff->base_index++ = 0x63643030;
595 *riff->base_index++ = (key_frame ? 0x10 : 0x00) | (0x02);
596 *riff->base_index++ = (int) (v1 - container->base_movie_size);
597 *riff->base_index++ = (int) encoded_video_size;
598 }
599 }
600 if (info->total_frames < info->advance_audio_frames || !flush) {
601 if (grow_base_riff(riff)) {
602 *riff->base_index++ = 0x62773130;
603 *riff->base_index++ = 0;
604 *riff->base_index++ = (int) (v2 - container->base_movie_size);
605 *riff->base_index++ = (int) encoded_audio_size;
606 }
607 }
608 info->total_frames++;
609 if (video->advance_frames_remaining)
610 video->advance_frames_remaining--;
611
612 /* Safeguard, stop AVI file before reaching 2 GB */
613 long size_thresh = (ftell(riff->file)
614 + audio_frame_size * info->advance_audio_frames
615 + (riff->base_index - riff->index) * 4);
616 if (size_thresh > 0x7fff0000l)
617 {
618 jzp_printf("Closing AVI near to 2GB\n");
619 avi_end_video_internal(avi,1);
620 return 0;
621 }
622 return 1;
623 }
624
625
626 /*
627 ** Record a frame of audio
628 */
avi_record_audio_internal(const avi_writer_t * const avi,const int16_t * const audio_data,const int num_samples)629 static void avi_record_audio_internal
630 (
631 const avi_writer_t *const avi,
632 const int16_t *const audio_data,
633 const int num_samples
634 )
635 {
636 // Push out as much interleaved audio/video as we can
637 while (avi_write_interleaved_stream(avi, 0))
638 ;
639
640 avi_audio_t *const audio = &(avi->pvt->audio);
641 size_t wrap = 0;
642
643 if (audio->buffer_write + num_samples > audio->buffer + AVI_AUDIO_BUF) {
644 wrap = (audio->buffer + AVI_AUDIO_BUF) - audio->buffer_write;
645 if (wrap)
646 memcpy(audio->buffer_write, audio_data, wrap * sizeof(int16_t));
647 audio->buffer_write = audio->buffer;
648 }
649
650 memcpy(audio->buffer_write, audio_data + wrap,
651 (num_samples - wrap) * sizeof(int16_t));
652
653 audio->buffer_write += num_samples - wrap;
654 audio->buffer_total_write += num_samples;
655 }
656
657 // Note: Audio comes in pre-time-scaled according to whatever the --ratecontrol
658 // setting is. The audio_time_scale_ratio is the ratio between avi_time_scale
659 // and the ratecontrol setting.
avi_record_audio(const avi_writer_t * const avi,const int16_t * const audio_data,const int num_samples,const int silent)660 void avi_record_audio
661 (
662 const avi_writer_t *const avi,
663 const int16_t *const audio_data,
664 const int num_samples,
665 const int silent
666 )
667 {
668 if (!avi->pvt || !avi->pvt->riff.file)
669 return;
670
671 if (num_samples >= AVI_AUDIO_BUF)
672 return;
673
674 if (audio_time_scale_ratio == 1.0)
675 {
676 avi_record_audio_internal(avi, audio_data, num_samples);
677 return;
678 }
679
680 avi_audio_t *audio = &(avi->pvt->audio);
681
682 // This could behave oddly if num_samples differs wildly call-to-call.
683 // Ordinarily, though, it won't, because the snd subsystem mixes in
684 // audio_buf sized quanta.
685 audio->time_remainder -= num_samples;
686
687 #if 1
688 // If we're scaling to faster than real-time, allow non-silent segments
689 // to borrow into silent segments by a few extra buffers.
690 if (audio_time_scale_ratio > 1.0 && silent &&
691 audio->time_remainder >= -5.0 * num_samples)
692 return;
693 #endif
694
695 while (audio->time_remainder < 0.0)
696 {
697 audio->time_remainder += audio_time_scale_ratio * num_samples;
698 avi_record_audio_internal(avi, audio_data, num_samples);
699 if (audio_time_scale_ratio > 1.0)
700 break;
701 }
702 }
703
704
705 // Compute block difference, decimated by 4x in each direction.
block_diff_decim4(const avi_frame_t * const RESTRICT prev,const avi_frame_t * const RESTRICT curr,const int px,const int py,const int cx,const int cy)706 LOCAL INLINE int block_diff_decim4(const avi_frame_t *const RESTRICT prev,
707 const avi_frame_t *const RESTRICT curr,
708 const int px, const int py,
709 const int cx, const int cy)
710 {
711 int x, y, dif = 0;
712
713 assert(px >= 0); assert(px <= AVI_DIM_X - BLOCK_WIDTH);
714 assert(py >= 0); assert(py <= AVI_DIM_Y - BLOCK_HEIGHT);
715 assert(cx >= 0); assert(cx <= AVI_DIM_X - BLOCK_WIDTH);
716 assert(cy >= 0); assert(cy <= AVI_DIM_Y - BLOCK_HEIGHT);
717
718 for (y = 0; y < BLOCK_HEIGHT; y += 4)
719 for (x = 0; x < BLOCK_WIDTH; x += 4)
720 dif += prev->f[py + y][px + x] != curr->f[cy + y][cx + x];
721
722 return dif << 4;
723 }
724
725 // Compute block difference across all pixels.
block_diff(const avi_frame_t * const RESTRICT prev,const avi_frame_t * const RESTRICT curr,const int px,const int py,const int cx,const int cy)726 LOCAL INLINE int block_diff(const avi_frame_t *const RESTRICT prev,
727 const avi_frame_t *const RESTRICT curr,
728 const int px, const int py,
729 const int cx, const int cy)
730 {
731 int x, y, dif = 0;
732
733 assert(px >= 0); assert(px <= AVI_DIM_X - BLOCK_WIDTH);
734 assert(py >= 0); assert(py <= AVI_DIM_Y - BLOCK_HEIGHT);
735 assert(cx >= 0); assert(cx <= AVI_DIM_X - BLOCK_WIDTH);
736 assert(cy >= 0); assert(cy <= AVI_DIM_Y - BLOCK_HEIGHT);
737
738 for (y = 0; y < BLOCK_HEIGHT; y++)
739 for (x = 0; x < BLOCK_WIDTH; x++)
740 dif += prev->f[py + y][px + x] != curr->f[cy + y][cx + x];
741
742 return dif;
743 }
744
745 // Search for the best match for the current block in the previous image.
motion_search(const avi_frame_t * const RESTRICT prev,const avi_frame_t * const RESTRICT curr,const int x,const int y,int * const RESTRICT move_x,int * const RESTRICT move_y)746 LOCAL INLINE int motion_search(const avi_frame_t *const RESTRICT prev,
747 const avi_frame_t *const RESTRICT curr,
748 const int x, const int y,
749 int *const RESTRICT move_x,
750 int *const RESTRICT move_y)
751 {
752 int best_dif = INT_MAX;
753 int best_explore = 0;
754 int explore, ex, ey, dif;
755
756 for (explore = 0; explore < NUM_MOVEMENT; explore++)
757 {
758 ex = x + movement[explore][0];
759 ey = y + movement[explore][1];
760
761 /* First see if we're out of bounds */
762 if (ex < 0 || ex > (AVI_DIM_X - BLOCK_WIDTH )) continue;
763 if (ey < 0 || ey > (AVI_DIM_Y - BLOCK_HEIGHT)) continue;
764
765 /* Next see if this is even worth it. */
766 dif = block_diff_decim4(prev, curr, ex, ey, x, y);
767
768 /* If it's a possible improvement, do a finer-grain search */
769 if (dif < 64)
770 dif = block_diff(prev, curr, ex, ey, x, y);
771
772 if (dif < best_dif)
773 {
774 best_dif = dif;
775 best_explore = explore;
776
777 /* If it's a small enough difference, stop now. */
778 if (dif < 4)
779 break;
780 }
781 }
782
783 *move_x = movement[best_explore][0];
784 *move_y = movement[best_explore][1];
785 return best_dif;
786 }
787
send_block_delta(const avi_frame_t * const RESTRICT prev,const avi_frame_t * const RESTRICT curr,const int px,const int py,const int cx,const int cy,uint8_t * const RESTRICT out)788 LOCAL INLINE uint8_t *send_block_delta(const avi_frame_t *const RESTRICT prev,
789 const avi_frame_t *const RESTRICT curr,
790 const int px, const int py,
791 const int cx, const int cy,
792 uint8_t *const RESTRICT out)
793 {
794 int x, y, cnt = 0;
795
796 for (y = 0; y < BLOCK_HEIGHT; y++)
797 for (x = 0; x < BLOCK_WIDTH; x++)
798 out[cnt++] = prev->f[py + y][px + x] ^ curr->f[cy + y][cx + x];
799
800 return out + cnt;
801 }
802
send_frame(const uint8_t * const palette,void * const data,const int data_size,uint8_t * const output,const int output_avail,const int compress,z_stream * const zstream)803 LOCAL INLINE uint8_t *send_frame
804 (
805 const uint8_t *const palette,
806 void *const data, const int data_size,
807 uint8_t *const output, const int output_avail,
808 const int compress, z_stream *const zstream
809 )
810 {
811 assert(compress || output != data || !palette);
812
813 if (!compress)
814 {
815 int offset = 0;
816 if (palette)
817 {
818 memcpy((void *)output, palette, 256 * 3);
819 offset = 256 * 3;
820 }
821 if ((void *)output != data)
822 memcpy((void *)(output + offset), data, data_size);
823 return output + offset + data_size;
824 }
825
826 if (palette) // XXX: palette != NULL means intraframe
827 deflateReset(zstream);
828 zstream->total_in = 0;
829 zstream->total_out = 0;
830 zstream->next_out = output;
831 zstream->avail_out = output_avail;
832
833 if (palette)
834 {
835 zstream->next_in = palette;
836 zstream->avail_in = 256 * 3;
837 deflate(zstream, Z_SYNC_FLUSH);
838 assert(zstream->avail_in == 0);
839 assert(zstream->avail_out != 0);
840 }
841
842 zstream->next_in = (const z_Bytef*)data;
843 zstream->avail_in = data_size;
844 deflate(zstream, Z_SYNC_FLUSH);
845 assert(zstream->avail_in == 0);
846 assert(zstream->avail_out != 0);
847 return output + zstream->total_out;
848 }
849
850 /*
851 ** Set the palette for 8bpp mode.
852 ** Note, code doesn't support dynamic palettes. Call once when opening video.
853 */
avi_set_palette(const avi_writer_t * const avi,const palette_t * const palette,const int length,const int offset)854 void avi_set_palette
855 (
856 const avi_writer_t *const avi,
857 const palette_t *const palette,
858 const int length,
859 const int offset
860 )
861 {
862 if (avi->pvt)
863 {
864 memcpy((void*)&avi->pvt->video.palette[offset],
865 (const void *)&palette->color[0][0], length * 3);
866 }
867 }
868
869 // Only encode video frame into video frame circular buffer
avi_record_video_internal(const avi_writer_t * const avi,const uint8_t * const image,const uint8_t border)870 LOCAL void avi_record_video_internal
871 (
872 const avi_writer_t *const avi,
873 const uint8_t *const image,
874 const uint8_t border
875 )
876 {
877 avi_pvt_t *const pvt = avi->pvt;
878 avi_info_t *const info = &(pvt->info);
879 avi_video_t *const video = &(pvt->video);
880 z_stream *const zstream = &(pvt->zstream);
881
882 unsigned char *base_output;
883 unsigned char *output;
884 unsigned char *motion_vecs;
885 unsigned char *block_deltas;
886 static unsigned char buffer_codec[AVI_FRAME_BYTES + 4096];
887 int x, y;
888 int block;
889 const int rel_frame = info->total_frames - info->advance_audio_frames;
890 const int key_frame = (rel_frame & (info->key_frames - 1)) == 0;
891
892 avi_frame_t *const RESTRICT curr = &(video->frame[ video->toggle]);
893 const avi_frame_t *const RESTRICT prev = &(video->frame[!video->toggle]);
894
895 if (rel_frame < 0)
896 return;
897
898 // Push out as much interleaved audio/video as we can
899 while (avi_write_interleaved_stream(avi, 0))
900 ;
901
902 // Enough room for a full encoded frame?
903 if (video->encode_buf_wr < AVI_VIDEO_WRAP_THRESH)
904 {
905 output = base_output = video->encode_buf + video->encode_buf_wr;
906 } else
907 {
908 video->encode_buf_wr = 0;
909 output = base_output = video->encode_buf;
910 }
911
912 // Did we overrun circular buffer?
913 if (video->encode_buf_wr < video->encode_buf_rd &&
914 video->encode_buf_wr + AVI_VIDEO_ENCODE_BYTES > video->encode_buf_rd)
915 {
916 jzp_printf("Video overrun! Closing AVI.");
917 avi_end_video_internal(avi,1);
918 }
919
920 video->toggle = !video->toggle;
921
922 #if AVI_BORD_X_SZ > 0 || AVI_BORD_Y_SZ > 0
923 if (border != curr->f[0][0])
924 memset((void*)&(curr->f), border, sizeof(avi_frame_t));
925 #endif
926
927 // NOTE: This pixel-doubles horizontally for the Intellivision.
928 // If we want to be more efficient, we would teach the whole encoder
929 // to work in 160x192 and then just double pixels when sending blocks.
930 for (y = 0; y < INPUT_DIM_Y; y++)
931 {
932 const uint8_t *RESTRICT vid_row_i = image + y * (INPUT_DIM_X/2);
933 uint8_t *RESTRICT vid_row_o =
934 &(curr->f[y + AVI_BORD_Y_SZ][AVI_BORD_X_SZ]);
935
936 for (x = 0; x < INPUT_DIM_X/2; x++)
937 {
938 uint8_t p = *vid_row_i++;
939 *vid_row_o++ = p;
940 *vid_row_o++ = p;
941 }
942 }
943
944 if (key_frame)
945 {
946 *output++ = 0x01; /* Key frame */
947 *output++ = 0x00; /* Hi version */
948 *output++ = 0x01; /* Lo version */
949 /* Compression (0x00 = Uncompressed, 0x01 = ZLIB) */
950 *output++ = info->compress;
951 *output++ = 0x04; /* 4 = 8 bpp */
952 *output++ = BLOCK_WIDTH; /* Block width */
953 *output++ = BLOCK_HEIGHT; /* Block height */
954
955 const uint32_t remain = AVI_VIDEO_ENCODE_BYTES - (output - base_output);
956 output = send_frame(AVI_BPP == 8 ? &video->palette[0][0] : NULL,
957 (void *)curr->f, AVI_FRAME_BYTES, output, remain,
958 info->compress, zstream);
959 } else
960 {
961 *output++ = 0x00; /* Non-key frame (+2 = Delta palette) */
962 motion_vecs = buffer_codec;
963 block_deltas = motion_vecs + NUM_X_BLOCK * NUM_Y_BLOCK * 2;
964 block = 0;
965 for (y = 0; y < AVI_DIM_Y; y += BLOCK_HEIGHT)
966 {
967 for (x = 0; x < AVI_DIM_X; x += BLOCK_WIDTH)
968 {
969 int move_x = 0, move_y = 0;
970 int best_dif = motion_search(prev, curr, x, y,
971 &move_x, &move_y);
972 if (best_dif == 0)
973 {
974 motion_vecs[block++] = move_x * 2;
975 motion_vecs[block++] = move_y * 2;
976 } else
977 {
978 motion_vecs[block++] = move_x * 2 + 1;
979 motion_vecs[block++] = move_y * 2;
980 block_deltas = send_block_delta(prev, curr,
981 x + move_x, y + move_y,
982 x, y, block_deltas);
983 }
984 }
985 }
986 assert(block == NUM_X_BLOCK * NUM_Y_BLOCK * 2);
987 assert(block % 4 == 0);
988
989 const uint32_t remain = AVI_VIDEO_ENCODE_BYTES - (output - base_output);
990 output = send_frame(NULL, (void *)buffer_codec,
991 block_deltas - buffer_codec,
992 output, remain, info->compress, zstream);
993 }
994
995 const uint32_t length = output - base_output;
996
997 video->encode_len[video->encode_len_wr++ % AVI_VIDEO_BUF_FRAMES] = length;
998 video->encode_buf_wr += length;
999 }
1000
avi_record_video(const avi_writer_t * const avi,const uint8_t * const image,const uint8_t border)1001 void avi_record_video
1002 (
1003 const avi_writer_t *const avi,
1004 const uint8_t *const image,
1005 const uint8_t border
1006 )
1007 {
1008 if (!avi->pvt || !avi->pvt->info.active)
1009 return;
1010
1011 if (avi_time_scale == 1.0)
1012 {
1013 avi_record_video_internal(avi, image, border);
1014 return;
1015 }
1016
1017 avi_video_t *video = &(avi->pvt->video);
1018
1019 video->time_remainder -= 1.0;
1020
1021 while (video->time_remainder < 0.0)
1022 {
1023 video->time_remainder += avi_time_scale;
1024 avi_record_video_internal(avi, image, border);
1025 }
1026 }
1027
1028 /*
1029 ** End of video recording
1030 */
avi_end_video_internal(const avi_writer_t * const avi,const int leave_active)1031 LOCAL void avi_end_video_internal
1032 (
1033 const avi_writer_t *const avi,
1034 const int leave_active
1035 )
1036 {
1037 avi_pvt_t *const pvt = avi->pvt;
1038 avi_info_t *const info = &(pvt->info);
1039 avi_riff_t *const riff = &(pvt->riff);
1040 avi_container_t *const container = &(pvt->container);
1041 z_stream *const zstream = &(pvt->zstream);
1042 size_t c;
1043 size_t d;
1044
1045 if (!riff->file)
1046 return;
1047
1048 // Push out as much interleaved audio/video as we can
1049 while (avi_write_interleaved_stream(avi, 0))
1050 ;
1051
1052 fseek(riff->file, container->pos[4], SEEK_SET);
1053 riff_write_int_32(riff, info->total_frames);
1054 fseek(riff->file, container->pos[5], SEEK_SET);
1055 riff_write_int_32(riff, info->total_frames);
1056 fseek(riff->file, 0, SEEK_END);
1057
1058 // Flush the rest
1059 while (avi_write_interleaved_stream(avi, 1))
1060 ;
1061
1062 riff_close_structure(riff);
1063 riff_start_element(riff, "idx1");
1064 c = riff->base_index - riff->index;
1065 for (d = 0; d < c; d++)
1066 riff_write_int_32(riff, riff->index[d]);
1067 riff_close_element(riff);
1068 riff_close_structure(riff);
1069 fseek(riff->file, container->pos[0], SEEK_SET);
1070 riff_write_int_32(riff,
1071 (info->max_size_frame + 20 + 20 +
1072 info->max_size_audio + 2047) & ~0x07ff);
1073 fseek(riff->file, container->pos[1], SEEK_SET);
1074 riff_write_int_32(riff, info->max_size_frame);
1075 fseek(riff->file, container->pos[2], SEEK_SET);
1076 riff_write_int_32(riff, info->total_audio);
1077 fseek(riff->file, container->pos[3], SEEK_SET);
1078 riff_write_int_32(riff, info->max_size_audio);
1079 free(riff->index);
1080 fclose(riff->file);
1081 riff->file = NULL;
1082
1083 if (info->compress)
1084 {
1085 deflateReset(zstream);
1086 int ret = deflateEnd(zstream);
1087 if (ret != Z_OK)
1088 printf("\nWarning: deflateEnd returned %d: %s\n", ret,
1089 zstream->msg ? zstream->msg :"");
1090 }
1091
1092 if (!leave_active)
1093 info->active = 0;
1094 }
1095
avi_end_video(const avi_writer_t * const avi)1096 void avi_end_video( const avi_writer_t *const avi )
1097 {
1098 if (avi->pvt && avi->pvt->info.active)
1099 avi_end_video_internal( avi, 0 );
1100 }
1101
avi_is_active(const avi_writer_t * const avi)1102 int avi_is_active( const avi_writer_t *const avi )
1103 {
1104 return avi->pvt && avi->pvt->info.active;
1105 }
1106
avi_start_time(const avi_writer_t * const avi)1107 uint64_t avi_start_time( const avi_writer_t *const avi )
1108 {
1109 return avi->pvt ? avi->pvt->info.start_time : 0;
1110 }
1111
avi_info(const avi_writer_t * const avi)1112 const avi_info_t *avi_info( const avi_writer_t *const avi )
1113 {
1114 return avi->pvt ? &avi->pvt->info : (avi_info_t*)NULL;
1115 }
1116
avi_set_time_scale(const double avi_time_scale_,const double incoming_audio_time_scale)1117 void avi_set_time_scale( const double avi_time_scale_,
1118 const double incoming_audio_time_scale )
1119 {
1120 avi_time_scale = avi_time_scale_ > 0.01 ? avi_time_scale_ : 1.0;
1121 audio_time_scale = incoming_audio_time_scale;
1122 audio_time_scale_ratio = avi_time_scale / incoming_audio_time_scale;
1123 printf("AVI: %5.3f %5.3f %5.3f\n", avi_time_scale, audio_time_scale,
1124 audio_time_scale_ratio);
1125 }
1126