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 static const 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  char dummy[AVI_HEADER_SIZE];
29  memset(dummy, 0, sizeof(dummy));
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?)
45  FileOperations::unlink(filename);
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  unsigned char avi_header[AVI_HEADER_SIZE];
53  memset(&avi_header, 0, sizeof(avi_header));
54  unsigned header_pos = 0;
55 
56 #define AVIOUT4(_S_) memcpy(&avi_header[header_pos],_S_,4);header_pos+=4;
57 #define AVIOUTw(_S_) Endian::write_UA_L16(&avi_header[header_pos], _S_);header_pos+=2;
58 #define AVIOUTd(_S_) Endian::write_UA_L32(&avi_header[header_pos], _S_);header_pos+=4;
59 #define AVIOUTs(_S_) memcpy(&avi_header[header_pos],_S_,strlen(_S_)+1);header_pos+=(strlen(_S_)+1 + 1) & ~1;
60 
61  bool hasAudio = audiorate != 0;
62 
63  // write avi header
64  AVIOUT4("RIFF"); // Riff header
65  AVIOUTd(AVI_HEADER_SIZE + written - 8 + unsigned(index.size() * sizeof(Endian::L32)));
66  AVIOUT4("AVI ");
67  AVIOUT4("LIST"); // List header
68  unsigned main_list = header_pos;
69  AVIOUTd(0); // size of list, filled in later
70  AVIOUT4("hdrl");
71 
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 
89  // Video stream list
90  AVIOUT4("LIST");
91  AVIOUTd(4 + 8 + 56 + 8 + 40); // Size of the list
92  AVIOUT4("strl");
93  // video stream header
94  AVIOUT4("strh");
95  AVIOUTd(56); // # of bytes to follow
96  AVIOUT4("vids"); // Type
97  AVIOUT4(ZMBVEncoder::CODEC_4CC); // Handler
98  AVIOUTd(0); // Flags
99  AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage
100  AVIOUTd(0); // InitialFrames
101  AVIOUTd(1000000); // Scale
102  AVIOUTd(unsigned(1000000 * fps)); // Rate: Rate/Scale == samples/second
103  AVIOUTd(0); // Start
104  AVIOUTd(frames); // Length
105  AVIOUTd(0); // SuggestedBufferSize
106  AVIOUTd(unsigned(~0)); // Quality
107  AVIOUTd(0); // SampleSize
108  AVIOUTd(0); // Frame
109  AVIOUTd(0); // Frame
110  // The video stream format
111  AVIOUT4("strf");
112  AVIOUTd(40); // # of bytes to follow
113  AVIOUTd(40); // Size
114  AVIOUTd(width); // Width
115  AVIOUTd(height); // Height
116  AVIOUTd(0);
117  AVIOUT4(ZMBVEncoder::CODEC_4CC); // Compression
118  AVIOUTd(width * height * 4); // SizeImage (in bytes?)
119  AVIOUTd(0); // XPelsPerMeter
120  AVIOUTd(0); // YPelsPerMeter
121  AVIOUTd(0); // ClrUsed: Number of colors used
122  AVIOUTd(0); // ClrImportant: Number of colors important
123 
124  if (hasAudio) {
125  // 1 fragment is 1 (for mono) or 2 (for stereo) samples
126  unsigned bitsPerSample = 16;
127  unsigned bytesPerSample = bitsPerSample / 8;
128  unsigned bytesPerFragment = bytesPerSample * channels;
129  unsigned bytesPerSecond = audiorate * bytesPerFragment;
130  unsigned fragments = audiowritten / channels;
131 
132  // Audio stream list
133  AVIOUT4("LIST");
134  AVIOUTd(4 + 8 + 56 + 8 + 16);// Length of list in bytes
135  AVIOUT4("strl");
136  // The audio stream header
137  AVIOUT4("strh");
138  AVIOUTd(56); // # of bytes to follow
139  AVIOUT4("auds");
140  AVIOUTd(0); // Format (Optionally)
141  AVIOUTd(0); // Flags
142  AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage
143  AVIOUTd(0); // InitialFrames
144  // Rate/Scale should be number of samples per second!
145  AVIOUTd(bytesPerFragment); // Scale
146  AVIOUTd(bytesPerSecond); // Rate, actual rate is scale/rate
147  AVIOUTd(0); // Start
148  AVIOUTd(fragments); // Length
149  AVIOUTd(0); // SuggestedBufferSize
150  AVIOUTd(unsigned(~0)); // Quality
151  AVIOUTd(bytesPerFragment); // SampleSize (should be the same as BlockAlign)
152  AVIOUTd(0); // Frame
153  AVIOUTd(0); // Frame
154  // The audio stream format
155  AVIOUT4("strf");
156  AVIOUTd(16); // # of bytes to follow
157  AVIOUTw(1); // Format, WAVE_ZMBV_FORMAT_PCM
158  AVIOUTw(channels); // Number of channels
159  AVIOUTd(audiorate); // SamplesPerSec
160  AVIOUTd(bytesPerSecond); // AvgBytesPerSec
161  AVIOUTw(bytesPerFragment); // BlockAlign: for PCM: nChannels * BitsPerSaple / 8
162  AVIOUTw(bitsPerSample); // BitsPerSample
163  }
164 
165  std::string versionStr = Version::full();
166 
167  // The standard snprintf() function does always zero-terminate the
168  // output it writes. Though windows doesn't have a snprintf() function,
169  // instead we #define snprintf to _snprintf and the latter does NOT
170  // properly zero-terminate. See also
171  // http://stackoverflow.com/questions/7706936/is-snprintf-always-null-terminating
172  //
173  // A buffer size of 11 characters is large enough till the year 9999.
174  // But the compiler doesn't understand calendars and warns that the
175  // snprintf output could be truncated (e.g. because the year is
176  // -2147483647). To silence this warning (and also to work around the
177  // windows _snprintf stuff) we add some extra buffer space.
178  static constexpr size_t size = (4 + 1 + 2 + 1 + 2 + 1) + 22;
179  char dateStr[size];
180  time_t t = time(nullptr);
181  struct tm *tm = localtime(&t);
182  snprintf(dateStr, sizeof(dateStr), "%04d-%02d-%02d", 1900 + tm->tm_year,
183  tm->tm_mon + 1, tm->tm_mday);
184 
185  AVIOUT4("LIST");
186  AVIOUTd(4 // list type
187  + (4 + 4 + ((versionStr.size() + 1 + 1) & ~1)) // 1st chunk
188  + (4 + 4 + ((strlen(dateStr ) + 1 + 1) & ~1)) // 2nd chunk
189  ); // size of the list
190  AVIOUT4("INFO");
191  AVIOUT4("ISFT");
192  AVIOUTd(unsigned(versionStr.size()) + 1); // # of bytes to follow
193  AVIOUTs(versionStr.c_str());
194  AVIOUT4("ICRD");
195  AVIOUTd(unsigned(strlen(dateStr)) + 1); // # of bytes to follow
196  AVIOUTs(dateStr);
197  // TODO: add artist (IART), comments (ICMT), name (INAM), etc.
198  // use a loop over chunks (type + string) to create the above bytes in
199  // a much nicer way
200 
201  // Finish stream list, i.e. put number of bytes in the list to proper pos
202  int nmain = header_pos - main_list - 4;
203  int njunk = AVI_HEADER_SIZE - 8 - 12 - header_pos;
204  assert(njunk > 0); // increase AVI_HEADER_SIZE if this occurs
205  AVIOUT4("JUNK");
206  AVIOUTd(njunk);
207  // Fix the size of the main list
208  header_pos = main_list;
209  AVIOUTd(nmain);
210  header_pos = AVI_HEADER_SIZE - 12;
211 
212  AVIOUT4("LIST");
213  AVIOUTd(written + 4); // Length of list in bytes
214  AVIOUT4("movi");
215 
216  try {
217  // First add the index table to the end
218  unsigned idxSize = unsigned(index.size()) * sizeof(Endian::L32);
219  index[0] = ('i' << 0) | ('d' << 8) | ('x' << 16) | ('1' << 24);
220  index[1] = idxSize - 8;
221  file.write(&index[0], idxSize);
222  file.seek(0);
223  file.write(&avi_header, AVI_HEADER_SIZE);
224  } catch (MSXException&) {
225  // can't throw from destructor
226  }
227 }
228 
229 void AviWriter::addAviChunk(const char* tag, unsigned size, void* data, unsigned flags)
230 {
231  struct {
232  char t[4];
233  Endian::L32 s;
234  } chunk;
235  memcpy(chunk.t, tag, sizeof(chunk.t));
236  chunk.s = size;
237  file.write(&chunk, sizeof(chunk));
238 
239  unsigned writesize = (size + 1) & ~1;
240  file.write(data, writesize);
241  unsigned pos = written + 4;
242  written += writesize + 8;
243 
244  size_t idxSize = index.size();
245  index.resize(idxSize + 4);
246  memcpy(&index[idxSize], tag, sizeof(Endian::L32));
247  index[idxSize + 1] = flags;
248  index[idxSize + 2] = pos;
249  index[idxSize + 3] = size;
250 }
251 
252 void AviWriter::addFrame(FrameSource* frame, unsigned samples, int16_t* sampleData)
253 {
254  bool keyFrame = (frames++ % 300 == 0);
255  void* buffer;
256  unsigned size;
257  codec.compressFrame(keyFrame, frame, buffer, size);
258  addAviChunk("00dc", size, buffer, keyFrame ? 0x10 : 0x0);
259 
260  if (samples) {
261  assert((samples % channels) == 0);
262  assert(audiorate != 0);
263  if (OPENMSX_BIGENDIAN) {
264  // See comment in WavWriter::write()
265  //VLA(Endian::L16, buf, samples); // doesn't work in clang
266  std::vector<Endian::L16> buf(sampleData, sampleData + samples);
267  addAviChunk("01wb", samples * sizeof(int16_t), buf.data(), 0);
268  } else {
269  addAviChunk("01wb", samples * sizeof(int16_t), sampleData, 0);
270  }
271  audiowritten += samples;
272  }
273 }
274 
275 } // namespace openmsx
#define AVIOUTd(_S_)
int unlink(const std::string &path)
Call unlink() in a platform-independent manner.
AviWriter(const Filename &filename, unsigned width, unsigned height, unsigned bpp, unsigned channels, unsigned freq)
Definition: AviWriter.cc:17
#define AVIOUTw(_S_)
Interface for getting lines from a video frame.
Definition: FrameSource.hh:14
std::string getURL() const
Returns the URL of this file object.
Definition: File.cc:128
static const char * CODEC_4CC
Definition: ZMBVEncoder.hh:20
#define AVIOUTs(_S_)
#define AVIOUT4(_S_)
constexpr auto data(C &c) -> decltype(c.data())
Definition: span.hh:69
constexpr size_t strlen(const char *s) noexcept
Definition: cstd.hh:135
EndianT< uint32_t, ConvLittle< openmsx::OPENMSX_BIGENDIAN > > L32
Definition: endian.hh:113
This class represents a filename.
Definition: Filename.hh:17
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
void write(const void *buffer, size_t num)
Write to file.
Definition: File.cc:88
void close()
Close the current file.
Definition: File.cc:78
static std::string full()
Definition: Version.cc:8
void seek(size_t pos)
Move read/write pointer to the specified position.
Definition: File.cc:108
void addFrame(FrameSource *frame, unsigned samples, int16_t *sampleData)
Definition: AviWriter.cc:252
constexpr auto size(const C &c) -> decltype(c.size())
Definition: span.hh:62
TclObject t