AviWriter.cc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // Code based on DOSBox-0.65
  2. #include "AviWriter.hh"
  3. #include "FileOperations.hh"
  4. #include "MSXException.hh"
  5. #include "build-info.hh"
  6. #include "Version.hh"
  7. #include "cstdiop.hh" // for snprintf
  8. #include <cassert>
  9. #include <cstring>
  10. #include <ctime>
  11. namespace openmsx {
  12. constexpr unsigned AVI_HEADER_SIZE = 500;
  13. AviWriter::AviWriter(const Filename& filename, unsigned width_,
  14. unsigned height_, unsigned bpp, unsigned channels_,
  15. unsigned freq_)
  16. : file(filename, "wb")
  17. , codec(width_, height_, bpp)
  18. , fps(0.0f) // will be filled in later
  19. , width(width_)
  20. , height(height_)
  21. , channels(channels_)
  22. , audiorate(freq_)
  23. {
  24. uint8_t dummy[AVI_HEADER_SIZE] = {};
  25. file.write(dummy, sizeof(dummy));
  26. index.resize(2);
  27. frames = 0;
  28. written = 0;
  29. audiowritten = 0;
  30. }
  31. AviWriter::~AviWriter()
  32. {
  33. if (written == 0) {
  34. // no data written yet (a recording less than one video frame)
  35. std::string filename = file.getURL();
  36. file.close(); // close file (needed for windows?)
  37. FileOperations::unlink(filename);
  38. return;
  39. }
  40. assert(fps != 0.0f); // a decent fps should have been set
  41. // Possible cleanup: use structs for the different headers, that
  42. // also allows to use the aligned versions of the Endian routines.
  43. uint8_t avi_header[AVI_HEADER_SIZE] = {};
  44. unsigned header_pos = 0;
  45. auto AVIOUT4 = [&](const char (&s)[5]) { // expect a string-literal of 4 chars (+ zero terminator)
  46. assert(s[4] == '\0');
  47. memcpy(&avi_header[header_pos], s, 4);
  48. header_pos += 4;
  49. };
  50. auto AVIOUTw = [&](uint16_t w) {
  51. Endian::write_UA_L16(&avi_header[header_pos], w);
  52. header_pos += sizeof(w);
  53. };
  54. auto AVIOUTd = [&](uint32_t d) {
  55. Endian::write_UA_L32(&avi_header[header_pos], d);
  56. header_pos += sizeof(d);
  57. };
  58. auto AVIOUTs = [&](const char* s) {
  59. auto len1 = strlen(s) + 1; // +1 for zero-terminator
  60. memcpy(&avi_header[header_pos], s, len1);
  61. header_pos += (len1 + 1) & ~1; // round-up to even
  62. };
  63. bool hasAudio = audiorate != 0;
  64. // write avi header
  65. AVIOUT4("RIFF"); // Riff header
  66. AVIOUTd(AVI_HEADER_SIZE + written - 8 + unsigned(index.size() * sizeof(Endian::L32)));
  67. AVIOUT4("AVI ");
  68. AVIOUT4("LIST"); // List header
  69. unsigned main_list = header_pos;
  70. AVIOUTd(0); // size of list, filled in later
  71. AVIOUT4("hdrl");
  72. AVIOUT4("avih");
  73. AVIOUTd(56); // # of bytes to follow
  74. AVIOUTd(unsigned(1000000 / fps)); // Microseconds per frame
  75. AVIOUTd(0);
  76. AVIOUTd(0); // PaddingGranularity (whatever that might be)
  77. AVIOUTd(0x110); // Flags,0x10 has index, 0x100 interleaved
  78. AVIOUTd(frames); // TotalFrames
  79. AVIOUTd(0); // InitialFrames
  80. AVIOUTd(hasAudio? 2 : 1); // Stream count
  81. AVIOUTd(0); // SuggestedBufferSize
  82. AVIOUTd(width); // Width
  83. AVIOUTd(height); // Height
  84. AVIOUTd(0); // TimeScale: Unit used to measure time
  85. AVIOUTd(0); // DataRate: Data rate of playback
  86. AVIOUTd(0); // StartTime: Starting time of AVI data
  87. AVIOUTd(0); // DataLength: Size of AVI data chunk
  88. // Video stream list
  89. AVIOUT4("LIST");
  90. AVIOUTd(4 + 8 + 56 + 8 + 40); // Size of the list
  91. AVIOUT4("strl");
  92. // video stream header
  93. AVIOUT4("strh");
  94. AVIOUTd(56); // # of bytes to follow
  95. AVIOUT4("vids"); // Type
  96. AVIOUT4(ZMBVEncoder::CODEC_4CC); // Handler
  97. AVIOUTd(0); // Flags
  98. AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage
  99. AVIOUTd(0); // InitialFrames
  100. AVIOUTd(1000000); // Scale
  101. AVIOUTd(unsigned(1000000 * fps)); // Rate: Rate/Scale == samples/second
  102. AVIOUTd(0); // Start
  103. AVIOUTd(frames); // Length
  104. AVIOUTd(0); // SuggestedBufferSize
  105. AVIOUTd(unsigned(~0)); // Quality
  106. AVIOUTd(0); // SampleSize
  107. AVIOUTd(0); // Frame
  108. AVIOUTd(0); // Frame
  109. // The video stream format
  110. AVIOUT4("strf");
  111. AVIOUTd(40); // # of bytes to follow
  112. AVIOUTd(40); // Size
  113. AVIOUTd(width); // Width
  114. AVIOUTd(height); // Height
  115. AVIOUTd(0);
  116. AVIOUT4(ZMBVEncoder::CODEC_4CC); // Compression
  117. AVIOUTd(width * height * 4); // SizeImage (in bytes?)
  118. AVIOUTd(0); // XPelsPerMeter
  119. AVIOUTd(0); // YPelsPerMeter
  120. AVIOUTd(0); // ClrUsed: Number of colors used
  121. AVIOUTd(0); // ClrImportant: Number of colors important
  122. if (hasAudio) {
  123. // 1 fragment is 1 (for mono) or 2 (for stereo) samples
  124. unsigned bitsPerSample = 16;
  125. unsigned bytesPerSample = bitsPerSample / 8;
  126. unsigned bytesPerFragment = bytesPerSample * channels;
  127. unsigned bytesPerSecond = audiorate * bytesPerFragment;
  128. unsigned fragments = audiowritten / channels;
  129. // Audio stream list
  130. AVIOUT4("LIST");
  131. AVIOUTd(4 + 8 + 56 + 8 + 16);// Length of list in bytes
  132. AVIOUT4("strl");
  133. // The audio stream header
  134. AVIOUT4("strh");
  135. AVIOUTd(56); // # of bytes to follow
  136. AVIOUT4("auds");
  137. AVIOUTd(0); // Format (Optionally)
  138. AVIOUTd(0); // Flags
  139. AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage
  140. AVIOUTd(0); // InitialFrames
  141. // Rate/Scale should be number of samples per second!
  142. AVIOUTd(bytesPerFragment); // Scale
  143. AVIOUTd(bytesPerSecond); // Rate, actual rate is scale/rate
  144. AVIOUTd(0); // Start
  145. AVIOUTd(fragments); // Length
  146. AVIOUTd(0); // SuggestedBufferSize
  147. AVIOUTd(unsigned(~0)); // Quality
  148. AVIOUTd(bytesPerFragment); // SampleSize (should be the same as BlockAlign)
  149. AVIOUTd(0); // Frame
  150. AVIOUTd(0); // Frame
  151. // The audio stream format
  152. AVIOUT4("strf");
  153. AVIOUTd(16); // # of bytes to follow
  154. AVIOUTw(1); // Format, WAVE_ZMBV_FORMAT_PCM
  155. AVIOUTw(channels); // Number of channels
  156. AVIOUTd(audiorate); // SamplesPerSec
  157. AVIOUTd(bytesPerSecond); // AvgBytesPerSec
  158. AVIOUTw(bytesPerFragment); // BlockAlign: for PCM: nChannels * BitsPerSaple / 8
  159. AVIOUTw(bitsPerSample); // BitsPerSample
  160. }
  161. std::string versionStr = Version::full();
  162. // The standard snprintf() function does always zero-terminate the
  163. // output it writes. Though windows doesn't have a snprintf() function,
  164. // instead we #define snprintf to _snprintf and the latter does NOT
  165. // properly zero-terminate. See also
  166. // http://stackoverflow.com/questions/7706936/is-snprintf-always-null-terminating
  167. //
  168. // A buffer size of 11 characters is large enough till the year 9999.
  169. // But the compiler doesn't understand calendars and warns that the
  170. // snprintf output could be truncated (e.g. because the year is
  171. // -2147483647). To silence this warning (and also to work around the
  172. // windows _snprintf stuff) we add some extra buffer space.
  173. constexpr size_t size = (4 + 1 + 2 + 1 + 2 + 1) + 22;
  174. char dateStr[size];
  175. time_t t = time(nullptr);
  176. struct tm *tm = localtime(&t);
  177. snprintf(dateStr, sizeof(dateStr), "%04d-%02d-%02d", 1900 + tm->tm_year,
  178. tm->tm_mon + 1, tm->tm_mday);
  179. AVIOUT4("LIST");
  180. AVIOUTd(4 // list type
  181. + (4 + 4 + ((versionStr.size() + 1 + 1) & ~1)) // 1st chunk
  182. + (4 + 4 + ((strlen(dateStr ) + 1 + 1) & ~1)) // 2nd chunk
  183. ); // size of the list
  184. AVIOUT4("INFO");
  185. AVIOUT4("ISFT");
  186. AVIOUTd(unsigned(versionStr.size()) + 1); // # of bytes to follow
  187. AVIOUTs(versionStr.c_str());
  188. AVIOUT4("ICRD");
  189. AVIOUTd(unsigned(strlen(dateStr)) + 1); // # of bytes to follow
  190. AVIOUTs(dateStr);
  191. // TODO: add artist (IART), comments (ICMT), name (INAM), etc.
  192. // use a loop over chunks (type + string) to create the above bytes in
  193. // a much nicer way
  194. // Finish stream list, i.e. put number of bytes in the list to proper pos
  195. int nmain = header_pos - main_list - 4;
  196. int njunk = AVI_HEADER_SIZE - 8 - 12 - header_pos;
  197. assert(njunk > 0); // increase AVI_HEADER_SIZE if this occurs
  198. AVIOUT4("JUNK");
  199. AVIOUTd(njunk);
  200. // Fix the size of the main list
  201. header_pos = main_list;
  202. AVIOUTd(nmain);
  203. header_pos = AVI_HEADER_SIZE - 12;
  204. AVIOUT4("LIST");
  205. AVIOUTd(written + 4); // Length of list in bytes
  206. AVIOUT4("movi");
  207. try {
  208. // First add the index table to the end
  209. unsigned idxSize = unsigned(index.size()) * sizeof(Endian::L32);
  210. index[0] = ('i' << 0) | ('d' << 8) | ('x' << 16) | ('1' << 24);
  211. index[1] = idxSize - 8;
  212. file.write(&index[0], idxSize);
  213. file.seek(0);
  214. file.write(avi_header, AVI_HEADER_SIZE);
  215. } catch (MSXException&) {
  216. // can't throw from destructor
  217. }
  218. }
  219. void AviWriter::addAviChunk(const char* tag, unsigned size, void* data, unsigned flags)
  220. {
  221. struct {
  222. char t[4];
  223. Endian::L32 s;
  224. } chunk;
  225. memcpy(chunk.t, tag, sizeof(chunk.t));
  226. chunk.s = size;
  227. file.write(&chunk, sizeof(chunk));
  228. unsigned writesize = (size + 1) & ~1;
  229. file.write(data, writesize);
  230. unsigned pos = written + 4;
  231. written += writesize + 8;
  232. size_t idxSize = index.size();
  233. index.resize(idxSize + 4);
  234. memcpy(&index[idxSize], tag, sizeof(Endian::L32));
  235. index[idxSize + 1] = flags;
  236. index[idxSize + 2] = pos;
  237. index[idxSize + 3] = size;
  238. }
  239. void AviWriter::addFrame(FrameSource* frame, unsigned samples, int16_t* sampleData)
  240. {
  241. bool keyFrame = (frames++ % 300 == 0);
  242. void* buffer;
  243. unsigned size;
  244. codec.compressFrame(keyFrame, frame, buffer, size);
  245. addAviChunk("00dc", size, buffer, keyFrame ? 0x10 : 0x0);
  246. if (samples) {
  247. assert((samples % channels) == 0);
  248. assert(audiorate != 0);
  249. if (OPENMSX_BIGENDIAN) {
  250. // See comment in WavWriter::write()
  251. //VLA(Endian::L16, buf, samples); // doesn't work in clang
  252. std::vector<Endian::L16> buf(sampleData, sampleData + samples);
  253. addAviChunk("01wb", samples * sizeof(int16_t), buf.data(), 0);
  254. } else {
  255. addAviChunk("01wb", samples * sizeof(int16_t), sampleData, 0);
  256. }
  257. audiowritten += samples;
  258. }
  259. }
  260. } // namespace openmsx