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