Switch internally to 16-bit graphics instead of 32-bit
[lsnes.git] / avidump / avidump.cpp
blobf0aa13e8c62f14a03b758143960dadb64af5b7c0
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 uint16_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 uint16_t* data, uint16_t width, uint16_t height, uint32_t fps_n,
206 uint32_t fps_d) 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 rwidth = (width + 3) / 4 * 4;
223 uint16_t rheight = (height + 3) / 4 * 4;
224 bool this_is_keyframe;
225 if(rwidth != pwidth || rheight != pheight || fps_n != pfps_n || fps_d != pfps_d || !avi_open) {
226 std::cerr << "Starting segment # " << current_segment << ": " << width << "x" << height << "."
227 << std::endl;
228 fixup_avi_header_and_close();
229 pwidth = rwidth;
230 pheight = rheight;
231 pfps_n = fps_n;
232 pfps_d = fps_d;
233 pframe.resize(2 * static_cast<size_t>(rwidth) * rheight);
234 tframe.resize(2 * static_cast<size_t>(rwidth) * rheight);
235 cframe.resize(compressBound(tframe.size()) + 13);
236 memset(&tframe[0], 0, tframe.size());
237 memset(&pframe[0], 0, pframe.size());
238 open_and_write_avi_header(rwidth, rheight, fps_n, fps_d);
241 this_is_keyframe = (segment_frames == 0 || segment_frames - segment_last_keyframe >= keyframe_interval);
242 uint8_t _magic[2] = {2, 1};
243 uint16_t magic = *reinterpret_cast<uint16_t*>(_magic);
245 if(this_is_keyframe) {
246 for(size_t i = 0; i < height; i++)
247 memcpy(&tframe[2 * i * rwidth], data + (i * width), 2 * width);
248 if(magic != 258)
249 for(size_t i = 0; i < tframe.size(); i += 2) {
250 tframe[i] ^= tframe[i + 1];
251 tframe[i + 1] ^= tframe[i];
252 tframe[i] ^= tframe[i + 1];
254 segment_last_keyframe = segment_frames;
255 } else {
256 for(size_t i = 0; i < height; i++)
257 memcpy(&tframe[2 * i * rwidth], data + (i * width), 2 * width);
258 if(magic != 258)
259 for(size_t i = 0; i < tframe.size(); i += 2) {
260 tframe[i] ^= tframe[i + 1];
261 tframe[i + 1] ^= tframe[i];
262 tframe[i] ^= tframe[i + 1];
264 for(size_t i = 0; i < tframe.size(); i++)
265 tframe[i] -= pframe[i];
267 uLongf l = cframe.size() - 10;
268 if(compress2(&cframe[10], &l, &tframe[0], tframe.size(), compression_level) != Z_OK)
269 throw std::runtime_error("Error compressing frame");
270 //Pad the frame.
271 while((l % 4) != 2)
272 l++;
273 cframe[0] = '0';
274 cframe[1] = '0';
275 cframe[2] = 'd';
276 cframe[3] = 'b'; //strictly speaking, this is wrong, but FCEUX does this when dumping.
277 cframe[4] = (l + 2);
278 cframe[5] = (l + 2) >> 8;
279 cframe[6] = (l + 2) >> 16;
280 cframe[7] = (l + 2) >> 24;
281 cframe[8] = (this_is_keyframe ? 0x3 : 0x2) | (compression_level << 4);
282 cframe[9] = 4;
283 avi_stream.write(reinterpret_cast<char*>(&cframe[0]), l + 10);
284 if(!avi_stream)
285 throw std::runtime_error("Error writing video frame");
286 //Flags is 0x10 for keyframes, because those frames are always keyframes, chunks, independent and take time.
287 //For non-keyframes, flags are 0x00 (chunk, not a keyframe).
288 segment_chunks.push_back(avi_frame(this_is_keyframe ? 0x10 : 0x00, 0x30306462, segment_movi_ptr + 4, l + 2));
289 segment_movi_ptr += (l + 10);
290 total_data += (l + 10);
291 segment_frames++;
292 total_frames++;
294 if((segment_frames % 1200) == 0)
295 print_summary(std::cerr);
297 for(size_t i = 0; i < height; i++)
298 memcpy(&pframe[2 * i * rwidth], data + (i * width), 2 * width);
299 if(magic != 258)
300 for(size_t i = 0; i < pframe.size(); i += 2) {
301 pframe[i] ^= pframe[i + 1];
302 pframe[i + 1] ^= pframe[i];
303 pframe[i] ^= pframe[i + 1];
307 void avidumper::on_end() throw(std::bad_alloc, std::runtime_error)
309 if(capture_error)
310 throw std::runtime_error("Video capture thread crashed: " + capture_error_str);
311 frame_mutex.lock();
312 sigquit = true;
313 frame_cond.notify_all();
314 frame_mutex.unlock();
315 frame_thread->join();
317 flush_audio_to(audio_put_ptr);
318 fixup_avi_header_and_close();
321 namespace
323 struct str
325 str(const char* _s)
327 s = _s;
329 const char* s;
332 struct u16
334 u16(uint16_t _v)
336 v = _v;
338 uint16_t v;
341 struct u32
343 u32(uint32_t _v)
345 v = _v;
347 uint32_t v;
350 struct ptr
352 ptr(uint32_t& _p) : p(_p)
355 uint32_t& p;
358 void append(std::vector<uint8_t>& v)
362 template<typename... rest>
363 void append(std::vector<uint8_t>& v, struct str s, rest... _rest)
365 const char* str = s.s;
366 while(*str)
367 v.push_back(*(str++));
368 append(v, _rest...);
371 template<typename... rest>
372 void append(std::vector<uint8_t>& v, struct u16 u, rest... _rest)
374 uint16_t val = u.v;
375 v.push_back(val);
376 v.push_back(val >> 8);
377 append(v, _rest...);
380 template<typename... rest>
381 void append(std::vector<uint8_t>& v, struct u32 u, rest... _rest)
383 uint32_t val = u.v;
384 v.push_back(val);
385 v.push_back(val >> 8);
386 v.push_back(val >> 16);
387 v.push_back(val >> 24);
388 append(v, _rest...);
391 template<typename... rest>
392 void append(std::vector<uint8_t>& v, ptr u, rest... _rest)
394 u.p = v.size();
395 append(v, _rest...);
398 void fix_write(std::ostream& str, uint32_t off, uint32_t val)
400 str.seekp(off, std::ios::beg);
401 char x[4];
402 x[0] = val;
403 x[1] = val >> 8;
404 x[2] = val >> 16;
405 x[3] = val >> 24;
406 str.write(x, 4);
407 if(!str)
408 throw std::runtime_error("Can't fixup AVI header");
412 void avidumper::flush_audio_to(unsigned commit_to)
414 if(!avi_open)
415 return;
417 //Count the number of samples to actually write.
418 unsigned samples_to_write = 0;
419 unsigned aptr = audio_get_ptr;
420 unsigned idx = 8;
421 while(aptr != commit_to) {
422 samples_to_write++;
423 if((aptr += 2) == AVIDUMPER_AUDIO_BUFFER)
424 aptr = 0;
427 std::vector<uint8_t> buf;
428 buf.resize(8 + 4 * samples_to_write);
429 buf[0] = '0';
430 buf[1] = '1';
431 buf[2] = 'w';
432 buf[3] = 'b';
433 buf[4] = (4 * samples_to_write);
434 buf[5] = (4 * samples_to_write) >> 8;
435 buf[6] = (4 * samples_to_write) >> 16;
436 buf[7] = (4 * samples_to_write) >> 24;
438 while(audio_get_ptr != commit_to) {
439 //Write sample.
440 buf[idx] = static_cast<unsigned short>(audio_buffer[audio_get_ptr]);
441 buf[idx + 1] = static_cast<unsigned short>(audio_buffer[audio_get_ptr]) >> 8;
442 buf[idx + 2] = static_cast<unsigned short>(audio_buffer[audio_get_ptr + 1]);
443 buf[idx + 3] = static_cast<unsigned short>(audio_buffer[audio_get_ptr + 1]) >> 8;
444 idx += 4;
445 segment_samples++;
446 total_samples++;
447 if((audio_get_ptr += 2) == AVIDUMPER_AUDIO_BUFFER)
448 audio_get_ptr = 0;
450 assert(idx == 8 + 4 * samples_to_write);
451 avi_stream.write(reinterpret_cast<char*>(&buf[0]), idx);
452 if(!avi_stream)
453 throw std::runtime_error("Error writing audio frame");
454 //Flags is 0x10, because sound frames are always keyframes, chunks, independent and take time.
455 segment_chunks.push_back(avi_frame(0x10, 0x30317762, segment_movi_ptr + 4, 4 * samples_to_write));
456 segment_movi_ptr += idx;
457 total_data += idx;
460 void avidumper::open_and_write_avi_header(uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
462 std::string fstr;
464 std::ostringstream str;
465 str << prefix << "_" << std::setw(9) << std::setfill('0') << (current_segment++) << ".avi";
466 fstr = str.str();
468 avi_stream.clear();
469 avi_stream.open(fstr.c_str(), std::ios::out | std::ios::binary);
470 if(!avi_stream)
471 throw std::runtime_error("Can't open output AVI file");
472 std::vector<uint8_t> aviheader;
473 uint32_t usecs_per_frame = static_cast<uint32_t>(1000000ULL * fps_d / fps_n);
475 /* AVI main chunk header. */
476 /* The tentative AVI header size of 336 doesn't include the video data, so we need to fix it up later. */
477 append(aviheader, str("RIFF"), ptr(fixup_avi_size), u32(336), str("AVI "));
478 /* Header list header. Has 312 bytes of data. */
479 append(aviheader, str("LIST"), u32(312), str("hdrl"));
480 /* Main AVI header. */
481 append(aviheader, str("avih"), u32(56), /* 56 byte header of type avih. */
482 u32(usecs_per_frame), /* usecs per frame. */
483 u32(1000000), /* Max transfer rate... Give some random value. */
484 u32(0), /* Padding granularity (no padding). */
485 u32(2064), /* Flags... Has index, trust chunk types */
486 ptr(fixup_avi_frames), u32(0), /* Frame count... To be fixed later. */
487 u32(0), /* Initial frames... We don't have any. */
488 u32(2), /* 2 streams (video + audio). */
489 u32(1000000), /* Suggested buffer size... Give some random value. */
490 u32(width), u32(height), /* Size of image. */
491 u32(0), u32(0), u32(0), u32(0)); /* Reserved. */
492 /* Stream list header For stream #1, 124 bytes of data. */
493 append(aviheader, str("LIST"), u32(124), str("strl"));
494 /* Stream header for stream #1 (video). */
495 append(aviheader, str("strh"), u32(64), /* 64 byte header of type strh */
496 str("vids"), u32(0), /* Video data??? */
497 u32(0), /* Some flags that are all clear. */
498 u16(0), u16(0), /* Priority and language... Doesn't matter. */
499 u32(0), /* Initial frames... We don't have any. */
500 u32(fps_d), u32(fps_n), /* Frame rate is fps_n / fps_d. */
501 u32(0), /* Starting time... It starts at t=0. */
502 ptr(fixup_avi_length), u32(0), /* Video length (to be fixed later). */
503 u32(1000000), /* Suggested buffer size... Just give some random value. */
504 u32(9999), /* Quality... Doesn't matter. */
505 u32(2), /* Video sample size... 16bpp. */
506 u32(0), u32(0), /* Bounding box upper left. */
507 u32(width), u32(height)); /* Bounding box lower right. */
508 /* BITMAPINFO header for the video stream. */
509 append(aviheader, str("strf"), u32(40), /* 40 byte header of type strf. */
510 u32(40), /* BITMAPINFOHEADER is 40 bytes. */
511 u32(width), u32(height), /* Image size. */
512 u16(1), u16(16), /* 1 plane, 16 bits (RGB16). */
513 str("CSCD"), /* Compressed with Camstudio codec. */
514 u32(2 * width * height), /* Image size. */
515 u32(4000), u32(4000), /* Resolution... Give some random values. */
516 u32(0), u32(0)); /* Colors used values (0 => All colors used). */
517 /* Stream list header For stream #2, 104 bytes of data. */
518 append(aviheader, str("LIST"), u32(104), str("strl"));
519 /* Stream header for stream #2. */
520 append(aviheader, str("strh"), u32(64), /* 64 byte header of type strh */
521 str("auds"), u32(0), /* audio data??? */
522 u32(0), /* Flags... None set. */
523 u16(0), u16(0), /* Priority and language... Doesn't matter. */
524 u32(0), /* Initial frames... None. */
525 u32(1), u32(audio_sampling_rate), /* Audio sampling rate. */
526 u32(0), /* Starts at t=0s. */
527 ptr(fixup_avi_a_length), u32(0), /* Audio length (to be fixed later). */
528 u32(4096), /* Suggested buffer size... Some random value. */
529 u32(5), /* Audio quality... Some random value. */
530 u32(4), /* Sample size (16bit Stereo PCM). */
531 u32(0), u32(0), u32(0), u32(0)); /* Bounding box, not sane for audio data. */
532 /* WAVEFORMAT header for the audio stream. */
533 append(aviheader, str("strf"), u32(20), /* 20 byte header of type strf. */
534 u16(1), /* PCM. */
535 u16(2), /* Stereo. */
536 u32(audio_sampling_rate), /* Audio Sampling rate. */
537 u32(4 * audio_sampling_rate), /* Audio transfer rate (4 times sampling rate). */
538 u16(4), /* Sample size. */
539 u16(16), /* Bits per sample. */
540 u16(0), /* Extension size... We don't have extension. */
541 u16(0)); /* Dummy. */
542 /* MOVI list header. 4 bytes without movie data. */
543 append(aviheader, str("LIST"), ptr(fixup_movi_size), u32(4), str("movi"));
544 avi_stream.write(reinterpret_cast<char*>(&aviheader[0]), aviheader.size());
545 if(!avi_stream)
546 throw std::runtime_error("Can't write AVI header");
547 total_data += aviheader.size();
548 avi_open = true;
549 segment_movi_ptr = 0;
550 segment_frames = 0;
551 segment_samples = 0;
552 segment_last_keyframe = 0;
553 segment_chunks.clear();
556 void avidumper::fixup_avi_header_and_close()
558 if(!avi_open)
559 return;
560 print_summary(std::cerr);
561 uint8_t buf[16];
562 buf[0] = 'i';
563 buf[1] = 'd';
564 buf[2] = 'x';
565 buf[3] = '1';
566 buf[4] = (16 * segment_chunks.size());
567 buf[5] = (16 * segment_chunks.size()) >> 8;
568 buf[6] = (16 * segment_chunks.size()) >> 16;
569 buf[7] = (16 * segment_chunks.size()) >> 24;
570 avi_stream.write(reinterpret_cast<char*>(buf), 8);
571 if(!avi_stream)
572 throw std::runtime_error("Error writing index header");
573 for(auto i = segment_chunks.begin(); i != segment_chunks.end(); ++i) {
574 i->write(buf);
575 avi_stream.write(reinterpret_cast<char*>(buf), 16);
576 if(!avi_stream)
577 throw std::runtime_error("Error writing index entry");
579 total_data += (8 + 16 * segment_chunks.size());
581 fix_write(avi_stream, fixup_avi_size, 344 + segment_movi_ptr + 16 * segment_chunks.size());
582 fix_write(avi_stream, fixup_avi_frames, segment_frames);
583 fix_write(avi_stream, fixup_avi_length, segment_frames);
584 fix_write(avi_stream, fixup_avi_a_length, segment_samples);
585 fix_write(avi_stream, fixup_movi_size, 4 + segment_movi_ptr);
586 segment_chunks.clear();
587 avi_stream.close();
588 avi_open = false;
591 avidumper::~avidumper() throw()
593 delete frame_thread;
596 void avidumper::wait_idle() throw()
598 umutex_class _frame_mutex(frame_mutex);
599 while(1) {
600 if(!mt_data)
601 return;
602 frame_cond.wait(_frame_mutex);