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