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