Remove errant tab from joystick message
[lsnes.git] / avidump / avidump.cpp
blobbbce1cde323514d46542f146a026fd6ae7a18893
1 #include "avidump.hpp"
2 #include <iomanip>
3 #include <cassert>
4 #include <cstring>
5 #include <sstream>
6 #include <zlib.h>
7 //#include "misc.hpp"
9 #define AVI_CUTOFF 2000000000
11 avi_frame::avi_frame(uint32_t _flags, uint32_t _type, uint32_t _offset, uint32_t _size)
13 flags = _flags;
14 type = _type;
15 offset = _offset;
16 size = _size;
19 void avi_frame::write(uint8_t* buf)
21 //Yes, this is written big-endian!
22 buf[0] = type >> 24;
23 buf[1] = type >> 16;
24 buf[2] = type >> 8;
25 buf[3] = type;
27 buf[4] = flags;
28 buf[5] = flags >> 8;
29 buf[6] = flags >> 16;
30 buf[7] = flags >> 24;
32 buf[8] = offset;
33 buf[9] = offset >> 8;
34 buf[10] = offset >> 16;
35 buf[11] = offset >> 24;
37 buf[12] = size;
38 buf[13] = size >> 8;
39 buf[14] = size >> 16;
40 buf[15] = size >> 24;
43 namespace
45 struct dumper_thread_obj
47 int operator()(avidumper* d)
49 try {
50 return d->encode_thread();
51 } catch(std::exception& e) {
52 std::cerr << "Encode thread threw: " << e.what() << std::endl;
53 d->set_capture_error(e.what());
55 return 1;
60 int avidumper::encode_thread()
62 umutex_class _frame_mutex(frame_mutex);
63 while(!sigquit) {
64 if(mt_data) {
65 _frame_mutex.unlock();
66 on_frame_threaded(mt_data, mt_width, mt_height, mt_fps_n, mt_fps_d);
67 _frame_mutex.lock();
69 mt_data = NULL;
70 frame_cond.notify_all();
71 frame_cond.wait(_frame_mutex);
73 return 0;
76 avidumper::avidumper(const std::string& _prefix, struct avi_info parameters)
78 compression_level = parameters.compression_level;
79 audio_sampling_rate = parameters.audio_sampling_rate;
80 keyframe_interval = parameters.keyframe_interval;
81 maxframes = parameters.max_frames_per_segment;
83 avi_open = false;
84 capture_error = false;
85 pwidth = 0xFFFF;
86 pheight = 0xFFFF;
87 pfps_n = 0xFFFFFFFFU;
88 pfps_d = 0xFFFFFFFFU;
89 current_segment = 0;
90 prefix = _prefix;
91 total_data = 0;
92 total_frames = 0;
93 total_samples = 0;
94 audio_put_ptr = 0;
95 audio_get_ptr = 0;
96 audio_commit_ptr = 0;
97 mt_data = NULL;
98 mt_width = 0;
99 mt_height = 0;
100 mt_fps_n = 0;
101 mt_fps_d = 0;
102 sigquit = false;
104 std::cerr << "Creating thread..." << std::endl;
105 dumper_thread_obj dto;
106 frame_thread = new thread_class(dto, this);
107 std::cerr << "Created thread..." << std::endl;
110 void avidumper::set_capture_error(const char* err) throw()
112 try {
113 capture_error = true;
114 capture_error_str = err;
115 } catch(std::bad_alloc& e) {
119 void avidumper::on_sample(short left, short right) throw(std::bad_alloc, std::runtime_error)
121 if(capture_error)
122 throw std::runtime_error("Video capture thread crashed: " + capture_error_str);
123 audio_buffer[audio_put_ptr++] = left;
124 audio_buffer[audio_put_ptr++] = right;
125 if(audio_put_ptr == AVIDUMPER_AUDIO_BUFFER)
126 audio_put_ptr = 0;
129 void avidumper::on_frame(const uint32_t* data, uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
130 throw(std::bad_alloc, std::runtime_error)
132 if(capture_error)
133 throw std::runtime_error("Video capture thread crashed: " + capture_error_str);
134 wait_idle();
135 frame_mutex.lock();
136 audio_commit_ptr = audio_put_ptr;
137 mt_data = data;
138 mt_width = width;
139 mt_height = height;
140 mt_fps_n = fps_n;
141 mt_fps_d = fps_d;
142 frame_cond.notify_all();
143 frame_mutex.unlock();
144 #ifdef NO_THREADS
145 on_frame_threaded(mt_data, mt_width, mt_height, mt_fps_n, mt_fps_d);
146 mt_data = NULL;
147 #endif
150 namespace
152 std::string fmtint(uint64_t val, unsigned prec)
154 std::ostringstream s2;
155 s2 << std::setw(prec) << std::setfill(' ') << val;
156 return s2.str();
159 std::string fmtdbl(double val, unsigned prec)
161 std::ostringstream s2;
162 s2 << std::setw(prec) << std::setfill(' ') << val;
163 std::string x = s2.str();
164 if(x.length() == prec)
165 return x;
166 size_t p = x.find_first_of("e");
167 if(p >= x.length())
168 return x.substr(0, prec);
169 return x.substr(0, p - (x.length() - prec)) + x.substr(p);
173 void avidumper::print_summary(std::ostream& str)
175 uint64_t local_segno = current_segment;
176 uint64_t local_vframes = segment_frames;
177 double local_vlength = segment_frames * static_cast<double>(pfps_d) / pfps_n;
178 uint64_t global_vframes = total_frames;
179 double global_vlength = total_frames * static_cast<double>(pfps_d) / pfps_n;
180 uint64_t local_aframes = segment_samples;
181 double local_alength = static_cast<double>(segment_samples) / audio_sampling_rate;
182 uint64_t global_aframes = total_samples;
183 double global_alength = static_cast<double>(total_samples) / audio_sampling_rate;
184 uint64_t local_size = segment_movi_ptr + 352 + 16 * segment_chunks.size();
185 uint64_t global_size = total_data + 8 + 16 * segment_chunks.size();
187 std::ostringstream s2;
189 s2 << "Quantity |This segment |All segments |" << std::endl;
190 s2 << "----------------+---------------------+---------------------+" << std::endl;
191 s2 << "Segment number | " << fmtint(local_segno, 10) << "| N/A|" << std::endl;
192 s2 << "Video stream |" << fmtint(local_vframes, 10) << "/" << fmtdbl(local_vlength, 10) << "|"
193 << fmtint(global_vframes, 10) << "/" << fmtdbl(global_vlength, 10) << "|" << std::endl;
194 s2 << "Audio stream |" << fmtint(local_aframes, 10) << "/" << fmtdbl(local_alength, 10) << "|"
195 << fmtint(global_aframes, 10) << "/" << fmtdbl(global_alength, 10) << "|" << std::endl;
196 s2 << "A/V desync | " << fmtdbl(local_alength - local_vlength, 10) << "| "
197 << fmtdbl(global_alength - global_vlength, 10) << "|" << std::endl;
198 s2 << "Size | " << fmtint(local_size, 10) << "| "
199 << fmtint(global_size, 10) << "|" << std::endl;
200 s2 << "----------------+---------------------+---------------------+" << std::endl;
202 str << s2.str();
205 void avidumper::on_frame_threaded(const uint32_t* data, uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
206 throw(std::bad_alloc, std::runtime_error)
208 //The AVI part of sound to write is [audio_get, audio_commit). We don't write part [audio_commit,audio_put)
209 //yet, as it is being concurrently written. Also grab lock to read the commit value. Also, if global frame
210 //counter is 0, don't write audio to avoid A/V desync.
211 frame_mutex.lock();
212 unsigned commit_to = audio_commit_ptr;
213 frame_mutex.unlock();
214 if(total_frames)
215 flush_audio_to(commit_to);
216 else
217 audio_get_ptr = commit_to;
219 if(segment_movi_ptr > AVI_CUTOFF - 16 * segment_chunks.size() || (maxframes && segment_frames > maxframes))
220 fixup_avi_header_and_close();
222 uint16_t rheight = (height + 3) / 4 * 4;
223 bool this_is_keyframe;
224 if(width != pwidth || height != pheight || fps_n != pfps_n || fps_d != pfps_d || !avi_open) {
225 std::cerr << "Starting segment # " << current_segment << ": " << width << "x" << height << "."
226 << std::endl;
227 fixup_avi_header_and_close();
228 pwidth = width;
229 pheight = height;
230 pfps_n = fps_n;
231 pfps_d = fps_d;
232 pframe.resize(4 * static_cast<size_t>(width) * height);
233 tframe.resize(4 * static_cast<size_t>(width) * rheight);
234 cframe.resize(compressBound(4 * static_cast<size_t>(width) * rheight) + 13);
235 memset(&tframe[0], 0, 4 * static_cast<size_t>(width) * rheight);
236 open_and_write_avi_header(width, rheight, fps_n, fps_d);
239 this_is_keyframe = (segment_frames == 0 || segment_frames - segment_last_keyframe >= keyframe_interval);
241 if(this_is_keyframe) {
242 memcpy(&tframe[0], data, 4 * static_cast<size_t>(width) * height);
243 segment_last_keyframe = segment_frames;
244 } else {
245 memcpy(&tframe[0], data, 4 * static_cast<size_t>(width) * height);
246 for(size_t i = 0; i < 4 * static_cast<size_t>(width) * height; i++)
247 tframe[i] -= pframe[i];
249 uLongf l = cframe.size() - 10;
250 if(compress2(&cframe[10], &l, &tframe[0], tframe.size(), compression_level) != Z_OK)
251 throw std::runtime_error("Error compressing frame");
252 //Pad the frame.
253 while((l % 4) != 2)
254 l++;
255 cframe[0] = '0';
256 cframe[1] = '0';
257 cframe[2] = 'd';
258 cframe[3] = 'b'; //strictly speaking, this is wrong, but FCEUX does this when dumping.
259 cframe[4] = (l + 2);
260 cframe[5] = (l + 2) >> 8;
261 cframe[6] = (l + 2) >> 16;
262 cframe[7] = (l + 2) >> 24;
263 cframe[8] = (this_is_keyframe ? 0x3 : 0x2) | (compression_level << 4);
264 cframe[9] = 12;
265 avi_stream.write(reinterpret_cast<char*>(&cframe[0]), l + 10);
266 if(!avi_stream)
267 throw std::runtime_error("Error writing video frame");
268 //Flags is 0x10 for keyframes, because those frames are always keyframes, chunks, independent and take time.
269 //For non-keyframes, flags are 0x00 (chunk, not a keyframe).
270 segment_chunks.push_back(avi_frame(this_is_keyframe ? 0x10 : 0x00, 0x30306462, segment_movi_ptr + 4, l + 2));
271 segment_movi_ptr += (l + 10);
272 total_data += (l + 10);
273 segment_frames++;
274 total_frames++;
276 if((segment_frames % 1200) == 0)
277 print_summary(std::cerr);
279 memcpy(&pframe[0], data, 4 * static_cast<size_t>(width) * height);
282 void avidumper::on_end() throw(std::bad_alloc, std::runtime_error)
284 if(capture_error)
285 throw std::runtime_error("Video capture thread crashed: " + capture_error_str);
286 frame_mutex.lock();
287 sigquit = true;
288 frame_cond.notify_all();
289 frame_mutex.unlock();
290 frame_thread->join();
292 flush_audio_to(audio_put_ptr);
293 fixup_avi_header_and_close();
296 namespace
298 struct str
300 str(const char* _s)
302 s = _s;
304 const char* s;
307 struct u16
309 u16(uint16_t _v)
311 v = _v;
313 uint16_t v;
316 struct u32
318 u32(uint32_t _v)
320 v = _v;
322 uint32_t v;
325 struct ptr
327 ptr(uint32_t& _p) : p(_p)
330 uint32_t& p;
333 void append(std::vector<uint8_t>& v)
337 template<typename... rest>
338 void append(std::vector<uint8_t>& v, struct str s, rest... _rest)
340 const char* str = s.s;
341 while(*str)
342 v.push_back(*(str++));
343 append(v, _rest...);
346 template<typename... rest>
347 void append(std::vector<uint8_t>& v, struct u16 u, rest... _rest)
349 uint16_t val = u.v;
350 v.push_back(val);
351 v.push_back(val >> 8);
352 append(v, _rest...);
355 template<typename... rest>
356 void append(std::vector<uint8_t>& v, struct u32 u, rest... _rest)
358 uint32_t val = u.v;
359 v.push_back(val);
360 v.push_back(val >> 8);
361 v.push_back(val >> 16);
362 v.push_back(val >> 24);
363 append(v, _rest...);
366 template<typename... rest>
367 void append(std::vector<uint8_t>& v, ptr u, rest... _rest)
369 u.p = v.size();
370 append(v, _rest...);
373 void fix_write(std::ostream& str, uint32_t off, uint32_t val)
375 str.seekp(off, std::ios::beg);
376 char x[4];
377 x[0] = val;
378 x[1] = val >> 8;
379 x[2] = val >> 16;
380 x[3] = val >> 24;
381 str.write(x, 4);
382 if(!str)
383 throw std::runtime_error("Can't fixup AVI header");
387 void avidumper::flush_audio_to(unsigned commit_to)
389 if(!avi_open)
390 return;
392 //Count the number of samples to actually write.
393 unsigned samples_to_write = 0;
394 unsigned aptr = audio_get_ptr;
395 unsigned idx = 8;
396 while(aptr != commit_to) {
397 samples_to_write++;
398 if((aptr += 2) == AVIDUMPER_AUDIO_BUFFER)
399 aptr = 0;
402 std::vector<uint8_t> buf;
403 buf.resize(8 + 4 * samples_to_write);
404 buf[0] = '0';
405 buf[1] = '1';
406 buf[2] = 'w';
407 buf[3] = 'b';
408 buf[4] = (4 * samples_to_write);
409 buf[5] = (4 * samples_to_write) >> 8;
410 buf[6] = (4 * samples_to_write) >> 16;
411 buf[7] = (4 * samples_to_write) >> 24;
413 while(audio_get_ptr != commit_to) {
414 //Write sample.
415 buf[idx] = static_cast<unsigned short>(audio_buffer[audio_get_ptr]);
416 buf[idx + 1] = static_cast<unsigned short>(audio_buffer[audio_get_ptr]) >> 8;
417 buf[idx + 2] = static_cast<unsigned short>(audio_buffer[audio_get_ptr + 1]);
418 buf[idx + 3] = static_cast<unsigned short>(audio_buffer[audio_get_ptr + 1]) >> 8;
419 idx += 4;
420 segment_samples++;
421 total_samples++;
422 if((audio_get_ptr += 2) == AVIDUMPER_AUDIO_BUFFER)
423 audio_get_ptr = 0;
425 assert(idx == 8 + 4 * samples_to_write);
426 avi_stream.write(reinterpret_cast<char*>(&buf[0]), idx);
427 if(!avi_stream)
428 throw std::runtime_error("Error writing audio frame");
429 //Flags is 0x10, because sound frames are always keyframes, chunks, independent and take time.
430 segment_chunks.push_back(avi_frame(0x10, 0x30317762, segment_movi_ptr + 4, 4 * samples_to_write));
431 segment_movi_ptr += idx;
432 total_data += idx;
435 void avidumper::open_and_write_avi_header(uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
437 std::string fstr;
439 std::ostringstream str;
440 str << prefix << "_" << std::setw(9) << std::setfill('0') << (current_segment++) << ".avi";
441 fstr = str.str();
443 avi_stream.clear();
444 avi_stream.open(fstr.c_str(), std::ios::out | std::ios::binary);
445 if(!avi_stream)
446 throw std::runtime_error("Can't open output AVI file");
447 std::vector<uint8_t> aviheader;
448 uint32_t usecs_per_frame = static_cast<uint32_t>(1000000ULL * fps_d / fps_n);
450 /* AVI main chunk header. */
451 /* The tentative AVI header size of 336 doesn't include the video data, so we need to fix it up later. */
452 append(aviheader, str("RIFF"), ptr(fixup_avi_size), u32(336), str("AVI "));
453 /* Header list header. Has 312 bytes of data. */
454 append(aviheader, str("LIST"), u32(312), str("hdrl"));
455 /* Main AVI header. */
456 append(aviheader, str("avih"), u32(56), /* 56 byte header of type avih. */
457 u32(usecs_per_frame), /* usecs per frame. */
458 u32(1000000), /* Max transfer rate... Give some random value. */
459 u32(0), /* Padding granularity (no padding). */
460 u32(2064), /* Flags... Has index, trust chunk types */
461 ptr(fixup_avi_frames), u32(0), /* Frame count... To be fixed later. */
462 u32(0), /* Initial frames... We don't have any. */
463 u32(2), /* 2 streams (video + audio). */
464 u32(1000000), /* Suggested buffer size... Give some random value. */
465 u32(width), u32(height), /* Size of image. */
466 u32(0), u32(0), u32(0), u32(0)); /* Reserved. */
467 /* Stream list header For stream #1, 124 bytes of data. */
468 append(aviheader, str("LIST"), u32(124), str("strl"));
469 /* Stream header for stream #1 (video). */
470 append(aviheader, str("strh"), u32(64), /* 64 byte header of type strh */
471 str("vids"), u32(0), /* Video data??? */
472 u32(0), /* Some flags that are all clear. */
473 u16(0), u16(0), /* Priority and language... Doesn't matter. */
474 u32(0), /* Initial frames... We don't have any. */
475 u32(fps_d), u32(fps_n), /* Frame rate is fps_n / fps_d. */
476 u32(0), /* Starting time... It starts at t=0. */
477 ptr(fixup_avi_length), u32(0), /* Video length (to be fixed later). */
478 u32(1000000), /* Suggested buffer size... Just give some random value. */
479 u32(9999), /* Quality... Doesn't matter. */
480 u32(4), /* Video sample size... 32bpp. */
481 u32(0), u32(0), /* Bounding box upper left. */
482 u32(width), u32(height)); /* Bounding box lower right. */
483 /* BITMAPINFO header for the video stream. */
484 append(aviheader, str("strf"), u32(40), /* 40 byte header of type strf. */
485 u32(40), /* BITMAPINFOHEADER is 40 bytes. */
486 u32(width), u32(height), /* Image size. */
487 u16(1), u16(32), /* 1 plane, 32 bits (RGB32). */
488 str("CSCD"), /* Compressed with Camstudio codec. */
489 u32(4 * width * height), /* Image size. */
490 u32(4000), u32(4000), /* Resolution... Give some random values. */
491 u32(0), u32(0)); /* Colors used values (0 => All colors used). */
492 /* Stream list header For stream #2, 104 bytes of data. */
493 append(aviheader, str("LIST"), u32(104), str("strl"));
494 /* Stream header for stream #2. */
495 append(aviheader, str("strh"), u32(64), /* 64 byte header of type strh */
496 str("auds"), u32(0), /* audio data??? */
497 u32(0), /* Flags... None set. */
498 u16(0), u16(0), /* Priority and language... Doesn't matter. */
499 u32(0), /* Initial frames... None. */
500 u32(1), u32(audio_sampling_rate), /* Audio sampling rate. */
501 u32(0), /* Starts at t=0s. */
502 ptr(fixup_avi_a_length), u32(0), /* Audio length (to be fixed later). */
503 u32(4096), /* Suggested buffer size... Some random value. */
504 u32(5), /* Audio quality... Some random value. */
505 u32(4), /* Sample size (16bit Stereo PCM). */
506 u32(0), u32(0), u32(0), u32(0)); /* Bounding box, not sane for audio data. */
507 /* WAVEFORMAT header for the audio stream. */
508 append(aviheader, str("strf"), u32(20), /* 20 byte header of type strf. */
509 u16(1), /* PCM. */
510 u16(2), /* Stereo. */
511 u32(audio_sampling_rate), /* Audio Sampling rate. */
512 u32(4 * audio_sampling_rate), /* Audio transfer rate (4 times sampling rate). */
513 u16(4), /* Sample size. */
514 u16(16), /* Bits per sample. */
515 u16(0), /* Extension size... We don't have extension. */
516 u16(0)); /* Dummy. */
517 /* MOVI list header. 4 bytes without movie data. */
518 append(aviheader, str("LIST"), ptr(fixup_movi_size), u32(4), str("movi"));
519 avi_stream.write(reinterpret_cast<char*>(&aviheader[0]), aviheader.size());
520 if(!avi_stream)
521 throw std::runtime_error("Can't write AVI header");
522 total_data += aviheader.size();
523 avi_open = true;
524 segment_movi_ptr = 0;
525 segment_frames = 0;
526 segment_samples = 0;
527 segment_last_keyframe = 0;
528 segment_chunks.clear();
531 void avidumper::fixup_avi_header_and_close()
533 if(!avi_open)
534 return;
535 print_summary(std::cerr);
536 uint8_t buf[16];
537 buf[0] = 'i';
538 buf[1] = 'd';
539 buf[2] = 'x';
540 buf[3] = '1';
541 buf[4] = (16 * segment_chunks.size());
542 buf[5] = (16 * segment_chunks.size()) >> 8;
543 buf[6] = (16 * segment_chunks.size()) >> 16;
544 buf[7] = (16 * segment_chunks.size()) >> 24;
545 avi_stream.write(reinterpret_cast<char*>(buf), 8);
546 if(!avi_stream)
547 throw std::runtime_error("Error writing index header");
548 for(auto i = segment_chunks.begin(); i != segment_chunks.end(); ++i) {
549 i->write(buf);
550 avi_stream.write(reinterpret_cast<char*>(buf), 16);
551 if(!avi_stream)
552 throw std::runtime_error("Error writing index entry");
554 total_data += (8 + 16 * segment_chunks.size());
556 fix_write(avi_stream, fixup_avi_size, 344 + segment_movi_ptr + 16 * segment_chunks.size());
557 fix_write(avi_stream, fixup_avi_frames, segment_frames);
558 fix_write(avi_stream, fixup_avi_length, segment_frames);
559 fix_write(avi_stream, fixup_avi_a_length, segment_samples);
560 fix_write(avi_stream, fixup_movi_size, 4 + segment_movi_ptr);
561 segment_chunks.clear();
562 avi_stream.close();
563 avi_open = false;
566 avidumper::~avidumper() throw()
568 delete frame_thread;
571 void avidumper::wait_idle() throw()
573 umutex_class _frame_mutex(frame_mutex);
574 while(1) {
575 if(!mt_data)
576 return;
577 frame_cond.wait(_frame_mutex);