1 /******************************************************************************/
2 /* Mednafen - Multi-system Emulator */
3 /******************************************************************************/
4 /* qtrecord.cpp:
5 ** Copyright (C) 2010-2016 Mednafen Team
6 **
7 ** This program is free software; you can redistribute it and/or
8 ** modify it under the terms of the GNU General Public License
9 ** as published by the Free Software Foundation; either version 2
10 ** of the License, or (at your option) any later version.
11 **
12 ** This program is distributed in the hope that it will be useful,
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ** GNU General Public License for more details.
16 **
17 ** You should have received a copy of the GNU General Public License
18 ** along with this program; if not, write to the Free Software Foundation, Inc.,
19 ** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include <mednafen/mednafen.h>
23 #include <mednafen/Time.h>
24 #include "qtrecord.h"
25 #include <minilzo/minilzo.h>
26 #include "video/png.h"
27
28 #include <zlib.h>
29
30 namespace Mednafen
31 {
32
w16(uint16 val)33 void QTRecord::w16(uint16 val)
34 {
35 uint8 buf[2];
36
37 MDFN_en16msb(buf, val);
38
39 qtfile.write(buf, sizeof(buf));
40 }
41
w32(uint32 val)42 void QTRecord::w32(uint32 val)
43 {
44 uint8 buf[4];
45
46 MDFN_en32msb(buf, val);
47
48 qtfile.write(buf, sizeof(buf));
49 }
50
w32s(const char * str)51 void QTRecord::w32s(const char *str)
52 {
53 uint8 buf[4];
54
55 memset(buf, 0x20, sizeof(buf));
56
57 for(unsigned int i = 0; i < sizeof(buf); i++)
58 {
59 if(!str[i])
60 break;
61
62 buf[i] = str[i];
63 }
64
65 qtfile.write(buf, sizeof(buf));
66 }
67
w64s(const char * str)68 void QTRecord::w64s(const char *str)
69 {
70 uint8 buf[8];
71
72 memset(buf, 0x20, sizeof(buf));
73
74 for(unsigned int i = 0; i < sizeof(buf); i++)
75 {
76 if(!str[i])
77 break;
78
79 buf[i] = str[i];
80 }
81
82 qtfile.write(buf, sizeof(buf));
83 }
84
85
w64(uint64 val)86 void QTRecord::w64(uint64 val)
87 {
88 uint8 buf[8];
89
90 MDFN_en64msb(buf, val);
91
92 qtfile.write(buf, sizeof(buf));
93 }
94
95 // fixed_len doesn't include the leading 1-byte count
wps(const char * str,uint8 fixed_len)96 void QTRecord::wps(const char *str, uint8 fixed_len)
97 {
98 uint8 slen = std::min<size_t>(255, strlen(str));
99 uint8 count = (fixed_len ? fixed_len : slen);
100 char buf[1 + 255];
101
102 memset(buf, 0, sizeof(buf));
103 buf[0] = count;
104 memcpy(&buf[1], str, std::min<uint8>(slen, count));
105
106 qtfile.write(buf, 1 + count);
107 }
108
vardata_begin(void)109 void QTRecord::vardata_begin(void)
110 {
111 vardata_foffsets.push_back(qtfile.tell());
112
113 w32(0); // Overwritten in vardata_end()
114 }
115
vardata_end(void)116 void QTRecord::vardata_end(void)
117 {
118 uint64 cur_offset = qtfile.tell();
119 uint64 start_offset = vardata_foffsets.back();
120
121 vardata_foffsets.pop_back();
122
123 qtfile.seek(start_offset, SEEK_SET);
124 w32(cur_offset - start_offset);
125
126 //printf("%d\n", cur_offset - start_offset);
127
128 qtfile.seek(cur_offset, SEEK_SET);
129 }
130
atom_begin(uint32 type,bool small_atom)131 void QTRecord::atom_begin(uint32 type, bool small_atom)
132 {
133 //small_atom = true; // DEBUG, REMOVE ME
134
135 atom_foffsets.push_back(qtfile.tell());
136 atom_smalls.push_back(small_atom);
137
138 if(small_atom)
139 {
140 w32(0);
141 w32(type);
142 }
143 else
144 {
145 w32(0x00000001);
146 w32(type);
147 w64(0);
148 }
149 }
150
151
atom_begin(const char * type,bool small_atom)152 void QTRecord::atom_begin(const char *type, bool small_atom)
153 {
154 uint32 type_num = 0;
155
156 for(int i = 0; i < 4; i++)
157 {
158 if(!type[i])
159 break;
160
161 type_num |= (uint32)type[i] << ((3 - i) * 8);
162 }
163 atom_begin(type_num, small_atom);
164 }
165
atom_end(void)166 void QTRecord::atom_end(void)
167 {
168 uint64 cur_offset = qtfile.tell();
169 uint64 start_offset = atom_foffsets.back();
170 bool small_atom = atom_smalls.back();
171
172 atom_foffsets.pop_back();
173 atom_smalls.pop_back();
174
175 if(small_atom)
176 {
177 qtfile.seek(start_offset, SEEK_SET);
178 w32(cur_offset - start_offset);
179 }
180 else
181 {
182 qtfile.seek(start_offset + 8, SEEK_SET);
183
184 w64(cur_offset - start_offset);
185 }
186
187 qtfile.seek(cur_offset, SEEK_SET);
188 }
189
QTRecord(const std::string & path,const VideoSpec & spec)190 QTRecord::QTRecord(const std::string& path, const VideoSpec &spec) : qtfile(path, FileStream::MODE_WRITE_SAFE), resampler(NULL)
191 {
192 Finished = false;
193
194 SoundFramesWritten = 0;
195
196 SoundRate = spec.SoundRate;
197 SoundChan = spec.SoundChan;
198
199 // If we change the rate threshold for resampling here, remember to change the std::min in the informational printf in
200 // mednafen.cpp
201 if(SoundRate > 64000)
202 {
203 int err = 0;
204
205 ResampInRate = spec.SoundRate;
206 SoundRate = 64000;
207 if(!(resampler = speex_resampler_init(spec.SoundChan, spec.SoundRate, SoundRate, 5, &err)))
208 {
209 throw MDFN_Error(0, _("Error initializing audio resampler."));
210 }
211 //printf("%f ms\n", 1000.0 * speex_resampler_get_input_latency(resampler) / spec.SoundRate);
212 }
213 else
214 resampler = NULL;
215
216 ResampInBufferFramesInCount = 0;
217 ResampInBuffer.resize(0);
218 ResampOutBuffer.resize(0);
219
220 //ResampInBuffer.resize((uint32)((100 * spec.SoundRate + 999) / 1000) * SoundChan);
221 //ResampOutBuffer.resize((uint32)((100 * SoundRate + 999) / 1000) * SoundChan);
222
223 //printf("%u %u\n", ResampInBuffer.size(), ReampOutBuffer.size());
224
225 TimeIndex = 0;
226 if(SoundRate && SoundChan)
227 {
228 TimeScale = SoundRate;
229 }
230 else
231 {
232 TimeScale = 10000;
233 MC = spec.MasterClock;
234 MCAccum = 0;
235 }
236
237 QTVideoWidth = spec.VideoWidth;
238 QTVideoHeight = spec.VideoHeight;
239
240 A = 65536 * spec.AspectXAdjust;
241 D = 65536 * spec.AspectYAdjust;
242
243 VideoCodec = spec.VideoCodec;
244
245 if(VideoCodec == VCODEC_PNG)
246 RawVideoBuffer.resize((1 + QTVideoWidth * 3) * QTVideoHeight);
247 else
248 RawVideoBuffer.resize(QTVideoWidth * QTVideoHeight * 3);
249
250 if(VideoCodec == VCODEC_CSCD)
251 CompressedVideoBuffer.resize((RawVideoBuffer.size() * 110 + 99 ) / 100); // 1.10
252 else if(VideoCodec == VCODEC_PNG)
253 CompressedVideoBuffer.resize(compressBound(RawVideoBuffer.size()));
254
255 {
256 uint32 appley_time = Time::EpochTime() + 2082844800;
257
258 CreationTS = appley_time;
259 ModificationTS = appley_time;
260 }
261
262 Write_ftyp();
263
264 atom_begin("mdat", false);
265 }
266
267
WriteFrame(const MDFN_Surface * surface,const MDFN_Rect & DisplayRect,const int32 * LineWidths,const int16 * SoundBuf,const int32 SoundBufSize,const int64 MasterCycles)268 void QTRecord::WriteFrame(const MDFN_Surface *surface, const MDFN_Rect &DisplayRect, const int32 *LineWidths,
269 const int16 *SoundBuf, const int32 SoundBufSize, const int64 MasterCycles)
270 {
271 QTChunk qts;
272
273 memset(&qts, 0, sizeof(qts));
274
275 if(DisplayRect.h <= 0)
276 {
277 fprintf(stderr, "[BUG] qtrecord.cpp: DisplayRect.h <= 0\n");
278 return;
279 }
280
281
282 qts.video_foffset = qtfile.tell();
283
284 // Write video here
285 {
286 uint32 dest_y = 0;
287 int yscale_factor = QTVideoHeight / DisplayRect.h;
288
289 for(int y = DisplayRect.y; y < DisplayRect.y + DisplayRect.h; y++)
290 {
291 int x_start;
292 int width;
293 int xscale_factor;
294 uint32 dest_x;
295 uint32 *src_ptr;
296 uint8 *dest_line;
297
298 if(dest_y >= QTVideoHeight)
299 break;
300
301 if(VideoCodec == VCODEC_CSCD)
302 dest_line = &RawVideoBuffer[(QTVideoHeight - 1 - dest_y) * QTVideoWidth * 3];
303 else if(VideoCodec == VCODEC_PNG)
304 dest_line = &RawVideoBuffer[dest_y * (QTVideoWidth * 3 + 1)];
305 else
306 dest_line = &RawVideoBuffer[dest_y * QTVideoWidth * 3];
307
308 x_start = DisplayRect.x;
309
310 if(LineWidths[0] == ~0)
311 width = DisplayRect.w;
312 else
313 width = LineWidths[y];
314
315 xscale_factor = QTVideoWidth / width;
316
317 dest_x = 0;
318
319 src_ptr = surface->pixels + y * surface->pitchinpix + x_start;
320
321 if(VideoCodec == VCODEC_PNG)
322 {
323 *dest_line = 0;
324 dest_line++;
325 }
326
327 #if 0
328 while(dest_x < ((QTVideoWidth - (xscale_factor * width)) / 2))
329 {
330 dest_line[dest_x * 3 + 0] = 0;
331 dest_line[dest_x * 3 + 1] = 0;
332 dest_line[dest_x * 3 + 2] = 0;
333
334 dest_x++;
335 }
336 #endif
337
338 for(int x = x_start; x < x_start + width; x++)
339 {
340 for(int sub_x = 0; sub_x < xscale_factor; sub_x++)
341 {
342 if(dest_x < QTVideoWidth)
343 {
344 int r, g, b, a;
345
346 surface->DecodeColor(*src_ptr, r, g, b, a);
347
348 if(VideoCodec == VCODEC_CSCD)
349 {
350 dest_line[dest_x * 3 + 0] = b;
351 dest_line[dest_x * 3 + 1] = g;
352 dest_line[dest_x * 3 + 2] = r;
353 }
354 else
355 {
356 dest_line[dest_x * 3 + 0] = r;
357 dest_line[dest_x * 3 + 1] = g;
358 dest_line[dest_x * 3 + 2] = b;
359 }
360
361 dest_x++;
362 }
363 }
364 src_ptr++;
365 }
366
367 while(dest_x < QTVideoWidth)
368 {
369 dest_line[dest_x * 3 + 0] = 0;
370 dest_line[dest_x * 3 + 1] = 0;
371 dest_line[dest_x * 3 + 2] = 0;
372
373 dest_x++;
374 }
375
376 for(int sub_y = 1; sub_y < yscale_factor; sub_y++)
377 {
378 if((dest_y + sub_y) >= QTVideoHeight)
379 break;
380
381 if(VideoCodec == VCODEC_CSCD)
382 memcpy(&RawVideoBuffer[(QTVideoHeight - 1 - (dest_y + sub_y)) * QTVideoWidth * 3], dest_line, QTVideoWidth * 3);
383 else if(VideoCodec == VCODEC_PNG)
384 memcpy(&RawVideoBuffer[(dest_y + sub_y) * (QTVideoWidth * 3 + 1)], dest_line - 1, QTVideoWidth * 3 + 1);
385 else
386 memcpy(&RawVideoBuffer[(dest_y + sub_y) * QTVideoWidth * 3], dest_line, QTVideoWidth * 3);
387 }
388
389 dest_y += yscale_factor;
390 } // end for(int y = DisplayRect.y; y < DisplayRect.y + DisplayRect.h; y++)
391 }
392
393 if(VideoCodec == VCODEC_CSCD)
394 {
395 static uint8 workmem[LZO1X_1_MEM_COMPRESS];
396 lzo_uint dst_len = CompressedVideoBuffer.size();
397 uint8 tmp[2];
398
399 tmp[0] = (0 << 1) | 0x1;
400 tmp[1] = 0;
401
402 qtfile.write(tmp, 2);
403
404 lzo1x_1_compress(&RawVideoBuffer[0], RawVideoBuffer.size(), &CompressedVideoBuffer[0], &dst_len, workmem);
405
406 qtfile.write(&CompressedVideoBuffer[0], dst_len);
407 }
408 else if(VideoCodec == VCODEC_RAW)
409 qtfile.write(&RawVideoBuffer[0], RawVideoBuffer.size());
410 else if(VideoCodec == VCODEC_PNG)
411 {
412 //PNGWrite(qtfile, surface, DisplayRect, LineWidths);
413 static const uint8 png_sig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
414 uint8 IHDR[13];
415 uLongf compress_buffer_size;
416
417 qtfile.write(png_sig, sizeof(png_sig));
418
419 MDFN_en32msb(&IHDR[0], QTVideoWidth);
420 MDFN_en32msb(&IHDR[4], QTVideoHeight);
421
422 IHDR[8] = 8; // 8 bits per color component
423 IHDR[9] = 2; // Color type: RGB triplet(no alpha)
424 IHDR[10] = 0; // Compression: deflate
425 IHDR[11] = 0; // Basic adaptive filter set
426 IHDR[12] = 0; // No interlace
427
428
429 PNGWrite::WriteChunk(qtfile, 13, "IHDR", IHDR);
430
431 compress_buffer_size = CompressedVideoBuffer.size();
432
433 compress(&CompressedVideoBuffer[0], &compress_buffer_size, &RawVideoBuffer[0], RawVideoBuffer.size());
434
435 PNGWrite::WriteChunk(qtfile, compress_buffer_size, "IDAT", &CompressedVideoBuffer[0]);
436
437 PNGWrite::WriteChunk(qtfile, 0, "IEND", 0);
438 }
439
440
441
442 qts.video_byte_size = qtfile.tell() - qts.video_foffset;
443
444
445
446 qts.audio_foffset = qtfile.tell();
447
448 // Write audio here
449 //
450 //
451 int32 SoundBufROSize;
452
453 if(resampler)
454 {
455 spx_uint32_t in_len;
456 spx_uint32_t out_len;
457
458 if((ResampInBuffer.size() - (ResampInBufferFramesInCount * SoundChan)) < SoundBufSize * SoundChan)
459 ResampInBuffer.resize((ResampInBufferFramesInCount + SoundBufSize) * SoundChan);
460
461 for(unsigned i = 0; i < SoundBufSize * SoundChan; i++)
462 ResampInBuffer[ResampInBufferFramesInCount * SoundChan + i] = SoundBuf[i];
463
464 ResampInBufferFramesInCount += SoundBufSize;
465
466 {
467 // *2 for padding for potential precision issues related to the resampling ratio.
468 size_t needed_out_size = (uint64_t)ResampInBuffer.size() / SoundChan * 2 * SoundRate / ResampInRate * SoundChan;
469
470 //printf("Zoom: %u %u\n", (unsigned)ResampInBuffer.size(), (unsigned)needed_out_size);
471
472 if(ResampOutBuffer.size() < needed_out_size)
473 ResampOutBuffer.resize(needed_out_size);
474
475 // Very crude, but works because we're only downsampling. TODO make it more precise?
476 //if(ResampOutBuffer.size() < ResampInBuffer.size())
477 // ResampOutBuffer.resize(ResampInBuffer.size());
478 }
479
480 in_len = ResampInBufferFramesInCount;
481 out_len = ResampOutBuffer.size() / SoundChan;
482
483 speex_resampler_process_interleaved_int(resampler, (const spx_int16_t *)&ResampInBuffer[0], &in_len, (spx_int16_t *)&ResampOutBuffer[0], &out_len);
484
485 if((ResampInBufferFramesInCount - in_len) > 0) // Shouldn't happen, unless there's not enough room in the output buffer.
486 {
487 printf("%u\n", ResampInBufferFramesInCount - in_len);
488 memmove(&ResampInBuffer[0], &ResampInBuffer[in_len * SoundChan], (ResampInBufferFramesInCount - in_len) * sizeof(int16) * SoundChan);
489 }
490
491 ResampInBufferFramesInCount -= in_len;
492 SoundBufROSize = out_len;
493
494 for(unsigned i = 0; i < SoundBufROSize * SoundChan; i++)
495 MDFN_en16msb((uint8 *)&ResampOutBuffer[i], ResampOutBuffer[i]);
496 }
497 else
498 {
499 SoundBufROSize = SoundBufSize;
500
501 if(ResampOutBuffer.size() < SoundBufSize * SoundChan)
502 ResampOutBuffer.resize(SoundBufSize * SoundChan);
503
504 for(unsigned i = 0; i < SoundBufROSize * SoundChan; i++)
505 MDFN_en16msb((uint8 *)&ResampOutBuffer[i], SoundBuf[i]);
506 }
507
508 qtfile.write(&ResampOutBuffer[0], sizeof(int16) * SoundBufROSize * SoundChan);
509 //
510 //
511 //
512 qts.audio_byte_size = qtfile.tell() - qts.audio_foffset;
513
514 SoundFramesWritten += SoundBufROSize;
515
516 if(SoundRate && SoundChan)
517 {
518 qts.time_length = qts.audio_byte_size / SoundChan / sizeof(int16);
519 TimeIndex += SoundBufROSize;
520 }
521 else
522 {
523 uint64 tnt;
524
525 MCAccum += (uint64)MasterCycles * TimeScale;
526 tnt = MCAccum / (MC >> 32);
527 MCAccum %= (MC >> 32);
528
529 //printf("%u\n", tnt);
530
531 qts.time_length = tnt;
532 TimeIndex += tnt;
533 }
534
535 QTChunks.push_back(qts);
536 }
537
Write_ftyp(void)538 void QTRecord::Write_ftyp(void) // Leaf
539 {
540 atom_begin("ftyp");
541
542 w32s("qt "); // Major brand
543
544 w32(0x20070900); // Minor_Version
545
546 w32s("qt "); // Compatible brand
547
548 // Placeholders to get the mdat start at 0x20 for prettiness(and libquicktime does it, I don't know if it has another reason).
549 w32(0);
550 w32(0);
551 w32(0);
552
553 atom_end();
554 }
555
Write_mvhd(void)556 void QTRecord::Write_mvhd(void) // Leaf
557 {
558 atom_begin("mvhd");
559
560 w32(0); // Version/flags
561 w32(CreationTS); // Created Mac date
562 w32(ModificationTS); // Modified Mac date
563 w32(TimeScale); // Time scale
564
565 w32(TimeIndex); // Duration
566 w32(65536 * 1); // Preferred rate
567 w16(256 * 1); // Preferred volume
568
569 for(int i = 0; i < 5; i++)
570 w16(0); // Reserved(5 * 2 = 10)
571
572 w32(65536 * 1); // A
573 w32(0); // B
574 w32(0); // U
575 w32(0); // C
576 w32(65536 * 1); // D
577 w32(0); // V
578 w32(0); // X
579 w32(0); // Y
580 w32(1 << 30); // W
581
582 w32(0); // Preview time
583 w32(0); // Preview duration
584
585 w32(0); // Poster time
586
587 w32(0); // Selection time.
588 w32(TimeIndex); // Selection duration.
589
590 w32(0); // Current time
591
592 // Next track id
593 if(SoundRate && SoundChan)
594 w32(3);
595 else
596 w32(2);
597
598 atom_end();
599 }
600
Write_tkhd(void)601 void QTRecord::Write_tkhd(void) // Leaf
602 {
603 atom_begin("tkhd");
604
605 w32(0xF); // Version and flags
606
607 w32(CreationTS); // Created mac date
608 w32(ModificationTS); // Modified mac date
609
610 if(OnAudioTrack)
611 w32(2);
612 else
613 w32(1); // Track id
614
615 w32(0); // Reserved
616
617 w32(TimeIndex); // Duration
618
619 w64(0); // Reserved
620
621 w16(0); // Video layer.
622 w16(0); // Alternate/other
623 w16(256); // Track audio volume
624 w16(0); // Reserved
625
626 w32(A); // A
627 w32(0); // B
628 w32(0); // U
629 w32(0); // C
630 w32(D); // D
631 w32(0); // V
632 w32(0); // X
633 w32(0); // Y
634 w32(1 << 30); // W
635
636 w32(65536 * QTVideoWidth);
637 w32(65536 * QTVideoHeight);
638
639 atom_end();
640 }
641
642 // Sample description
Write_stsd(void)643 void QTRecord::Write_stsd(void) // Leaf
644 {
645 atom_begin("stsd");
646
647 w32(0); // Version and flags
648
649 w32(1); // Number of sample descriptions
650
651 if(OnAudioTrack) // Audio track
652 {
653 vardata_begin(); // w32(36)
654
655 w32s("twos"); // Data format
656
657 w32(0); // Reserved
658 w16(0); // Reserved
659
660 w16(1); // dref index?
661
662 w16(0); // Version
663 w16(0); // Revision level
664
665 w32s("MDFN"); // Vendor
666
667 w16(SoundChan); // Number of sound channels
668 w16(16); // Sample size
669 w16(0); // Audio compression ID
670 w16(0); // Audio packet rate
671 w32(SoundRate * 65536); // Audio sample rate
672
673 vardata_end();
674 }
675 else // Video track
676 {
677 vardata_begin(); // w32(86 + 12); // Description length (+12 for gama)
678
679 if(VideoCodec == VCODEC_CSCD)
680 w32s("CSCD"); // Data format
681 else if(VideoCodec == VCODEC_PNG)
682 w32s("png ");
683 else
684 w32s("raw "); // Data format
685
686 w32(0); // Reserved
687 w16(0); // Reserved
688
689 w16(1); // dref index?
690
691 w16(0); // Version
692 w16(0); // Revision level
693
694 w32s("MDFN"); // Vendor
695
696 w32(1024); // Video temporal quality
697 w32(1024); // Video spatial quality
698
699 w16(QTVideoWidth); // Width of source image
700 w16(QTVideoHeight); // Height of source image
701
702 w32(48 * 65536); // Horizontal PPI(FIXME)
703
704 w32(48 * 65536); // Vertical PPI(FIXME)
705
706 w32(0); // Data size must be set to 0
707
708 w16(1); // Frame count(per sample)
709
710 wps("Mednafen " MEDNAFEN_VERSION, 31); // Video encoder
711
712 w16(24); // Depth
713
714 w16(0xFFFF); // Color table ID
715
716 atom_begin("gama");
717 w32(65536 * 2.2);
718 atom_end();
719
720 vardata_end();
721 }
722
723 atom_end();
724 }
725
726 // Time-to-sample
Write_stts(void)727 void QTRecord::Write_stts(void) // Leaf
728 {
729 atom_begin("stts");
730
731 w32(0); // Version and flags
732
733 if(OnAudioTrack)
734 {
735 w32(1); // Number of entries
736
737 // Entry
738 w32(SoundFramesWritten);
739 w32(1);
740 }
741 else
742 {
743 w32(QTChunks.size()); // number of entries
744
745 for(uint32 i = 0; i < QTChunks.size(); i++)
746 {
747 w32(1);
748 w32(QTChunks[i].time_length);
749 }
750 }
751
752 atom_end();
753 }
754
755 // Sample-to-chunk
Write_stsc(void)756 void QTRecord::Write_stsc(void) // Leaf
757 {
758 atom_begin("stsc");
759
760 w32(0); // Version and flags
761
762 if(OnAudioTrack)
763 {
764 w32(QTChunks.size()); // Number of entries
765 for(uint32 i = 0; i < QTChunks.size(); i++)
766 {
767 w32(1 + i); // First chunk number using this entry
768 w32(QTChunks[i].audio_byte_size / SoundChan / sizeof(int16)); // Samples per chunk
769 w32(1); // Sample description ID(references stsd)
770 }
771 }
772 else
773 {
774 w32(1); // Number of entries
775 w32(1); // First chunk
776 w32(1); // Samples per chunk
777 w32(1); // Sample description ID(references data in stsd)
778 }
779
780 atom_end();
781 }
782
Write_stsz(void)783 void QTRecord::Write_stsz(void) // Leaf
784 {
785 atom_begin("stsz");
786
787 w32(0); // Version and flags
788
789 if(OnAudioTrack)
790 {
791 w32(1);
792 w32(SoundFramesWritten);
793 }
794 else
795 {
796 w32(0); // Sample size
797
798 w32(QTChunks.size()); // Number of entries
799
800 for(uint32 i = 0; i < QTChunks.size(); i++)
801 {
802 if(OnAudioTrack)
803 w32(QTChunks[i].audio_byte_size);
804 else
805 w32(QTChunks[i].video_byte_size);
806 }
807 }
808
809 atom_end();
810 }
811
812 // Chunk offset atom(64-bit style)
Write_co64(void)813 void QTRecord::Write_co64(void) // Leaf
814 {
815 atom_begin("co64");
816
817 w32(0); // Version and flags
818
819 w32(QTChunks.size()); // Number of entries
820
821 for(uint32 i = 0; i < QTChunks.size(); i++)
822 {
823 if(OnAudioTrack)
824 w64(QTChunks[i].audio_foffset);
825 else
826 w64(QTChunks[i].video_foffset);
827 }
828
829 atom_end();
830 }
831
Write_stco(void)832 void QTRecord::Write_stco(void) // Leaf
833 {
834 atom_begin("stco");
835
836 w32(0); // Version and flags
837
838 w32(QTChunks.size()); // Number of entries
839
840 for(uint32 i = 0; i < QTChunks.size(); i++)
841 {
842 if(OnAudioTrack)
843 w32(QTChunks[i].audio_foffset);
844 else
845 w32(QTChunks[i].video_foffset);
846 }
847
848 atom_end();
849 }
850
Write_stbl(void)851 void QTRecord::Write_stbl(void)
852 {
853 atom_begin("stbl");
854
855 Write_stsd();
856
857 Write_stts();
858
859 Write_stsc();
860
861 Write_stsz();
862
863 bool need64bit_offset = false;
864
865 if(OnAudioTrack && QTChunks.back().audio_foffset >= ((uint64)1 << 32))
866 need64bit_offset = true;
867
868 if(!OnAudioTrack && QTChunks.back().video_foffset >= ((uint64)1 << 32))
869 need64bit_offset = true;
870
871 if(need64bit_offset)
872 Write_co64();
873 else
874 Write_stco();
875
876 atom_end();
877 }
878
879 // Media header atom
Write_mdhd(void)880 void QTRecord::Write_mdhd(void) // Leaf
881 {
882 atom_begin("mdhd");
883
884 w32(0); // Version/flags
885 w32(CreationTS); // Creation date
886 w32(ModificationTS); // Modification date
887 w32(TimeScale); // Time scale
888 w32(TimeIndex); // Duration
889
890 w16(0); // Language
891 w16(0); // Quality
892
893 atom_end();
894 }
895
896 // Sound media information header
Write_smhd(void)897 void QTRecord::Write_smhd(void) // Leaf
898 {
899 atom_begin("smhd");
900 w32(0x1); // Version/flags
901 w16(0); // Balance
902 w16(0); // Reserved
903
904 atom_end();
905 }
906
907 // Video media information header
Write_vmhd(void)908 void QTRecord::Write_vmhd(void) // Leaf
909 {
910 atom_begin("vmhd");
911
912 w32(0x1); // Version/flags
913
914 w16(0); // Quickdraw graphics mode (Simple Copy, no dither)
915
916 // RGB values(unused I guess in simple copy?)
917 w16(0x8000);
918 w16(0x8000);
919 w16(0x8000);
920
921 atom_end();
922 }
923
Write_hdlr(const char * str,const char * comp_name)924 void QTRecord::Write_hdlr(const char *str, const char *comp_name) // Leaf
925 {
926 atom_begin("hdlr");
927
928 w32(0); // Version/flags
929
930 w64s(str);
931
932 w32(0); // Reserved
933
934 w32(0); // reserved
935
936 w32(0); // Reserved
937
938 wps(comp_name, 0);
939
940 atom_end();
941 }
942
Write_dinf(void)943 void QTRecord::Write_dinf(void)
944 {
945 atom_begin("dinf");
946
947 atom_begin("dref");
948
949 w32(0); // Version/flags
950 w32(1); // Number of references
951
952 atom_begin("alis");
953 w32(0x00000001); // Version/flags
954 atom_end();
955
956 atom_end();
957
958 atom_end();
959 }
960
Write_minf(void)961 void QTRecord::Write_minf(void)
962 {
963 atom_begin("minf");
964
965 if(OnAudioTrack)
966 Write_smhd();
967 else
968 Write_vmhd();
969
970 Write_hdlr("dhlralis", "Mednafen Alias Data Handler");
971
972 Write_dinf();
973
974 Write_stbl();
975
976 atom_end();
977 }
978
Write_mdia(void)979 void QTRecord::Write_mdia(void)
980 {
981 atom_begin("mdia");
982
983 Write_mdhd();
984
985 if(OnAudioTrack)
986 Write_hdlr("mhlrsoun", "Mednafen Sound Media Handler");
987 else
988 Write_hdlr("mhlrvide", "Mednafen Video Media Handler");
989
990 Write_minf();
991
992 atom_end();
993 }
994
Write_edts(void)995 void QTRecord::Write_edts(void)
996 {
997 atom_begin("edts");
998
999 atom_begin("elst");
1000 w32(0); // version/flags
1001 w32(1); // Number of edits
1002 w32(TimeIndex); // Duration
1003 w32(0); // start time
1004 w32(65536 * 1); // Rate
1005 atom_end();
1006
1007 atom_end();
1008 }
1009
Write_trak(void)1010 void QTRecord::Write_trak(void)
1011 {
1012 atom_begin("trak");
1013
1014 Write_tkhd();
1015
1016 Write_edts();
1017
1018 Write_mdia();
1019
1020 atom_end();
1021 }
1022
Write_udta(void)1023 void QTRecord::Write_udta(void)
1024 {
1025 atom_begin("udta");
1026
1027 atom_begin("@fmt");
1028
1029 qtfile.put_string("Computer-generated via an emulator.");
1030
1031 atom_end();
1032
1033 atom_begin("@swr");
1034
1035 qtfile.put_string("Mednafen " MEDNAFEN_VERSION);
1036
1037 atom_end();
1038
1039 atom_end();
1040 }
1041
Write_moov(void)1042 void QTRecord::Write_moov(void)
1043 {
1044 atom_begin("moov");
1045
1046 Write_mvhd();
1047
1048 OnAudioTrack = false;
1049 Write_trak();
1050
1051 if(SoundRate && SoundChan)
1052 {
1053 OnAudioTrack = true;
1054 Write_trak();
1055 }
1056
1057 Write_udta();
1058
1059 atom_end();
1060 }
1061
Finish(void)1062 void QTRecord::Finish(void)
1063 {
1064 if(Finished)
1065 return;
1066
1067 Finished = true;
1068
1069 atom_end();
1070
1071 Write_moov();
1072
1073 qtfile.close();
1074 }
1075
~QTRecord(void)1076 QTRecord::~QTRecord(void)
1077 {
1078 try
1079 {
1080 Finish();
1081 }
1082 catch(std::exception &e)
1083 {
1084 MDFND_OutputNotice(MDFN_NOTICE_ERROR, e.what());
1085 }
1086
1087 if(resampler)
1088 {
1089 speex_resampler_destroy(resampler);
1090 resampler = NULL;
1091 }
1092 }
1093
1094 }
1095