9 #define AVI_CUTOFF 2000000000
11 avi_frame::avi_frame(uint32_t _flags
, uint32_t _type
, uint32_t _offset
, uint32_t _size
)
19 void avi_frame::write(uint8_t* buf
)
21 //Yes, this is written big-endian!
34 buf
[10] = offset
>> 16;
35 buf
[11] = offset
>> 24;
45 struct dumper_thread_obj
47 int operator()(avidumper
* d
)
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());
60 int avidumper::encode_thread()
62 umutex_class
_frame_mutex(frame_mutex
);
65 _frame_mutex
.unlock();
66 on_frame_threaded(mt_data
, mt_width
, mt_height
, mt_fps_n
, mt_fps_d
);
70 frame_cond
.notify_all();
71 frame_cond
.wait(_frame_mutex
);
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
;
84 capture_error
= 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()
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
)
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
)
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
)
133 throw std::runtime_error("Video capture thread crashed: " + capture_error_str
);
136 audio_commit_ptr
= audio_put_ptr
;
142 frame_cond
.notify_all();
143 frame_mutex
.unlock();
145 on_frame_threaded(mt_data
, mt_width
, mt_height
, mt_fps_n
, mt_fps_d
);
152 std::string
fmtint(uint64_t val
, unsigned prec
)
154 std::ostringstream s2
;
155 s2
<< std::setw(prec
) << std::setfill(' ') << val
;
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
)
166 size_t p
= x
.find_first_of("e");
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
;
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.
212 unsigned commit_to
= audio_commit_ptr
;
213 frame_mutex
.unlock();
215 flush_audio_to(commit_to
);
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
<< "."
228 fixup_avi_header_and_close();
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
);
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
;
256 for(size_t i
= 0; i
< height
; i
++)
257 memcpy(&tframe
[2 * i
* rwidth
], data
+ (i
* width
), 2 * width
);
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");
276 cframe
[3] = 'b'; //strictly speaking, this is wrong, but FCEUX does this when dumping.
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);
283 avi_stream
.write(reinterpret_cast<char*>(&cframe
[0]), l
+ 10);
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);
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
);
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
)
310 throw std::runtime_error("Video capture thread crashed: " + capture_error_str
);
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();
352 ptr(uint32_t& _p
) : p(_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
;
367 v
.push_back(*(str
++));
371 template<typename
... rest
>
372 void append(std::vector
<uint8_t>& v
, struct u16 u
, rest
... _rest
)
376 v
.push_back(val
>> 8);
380 template<typename
... rest
>
381 void append(std::vector
<uint8_t>& v
, struct u32 u
, rest
... _rest
)
385 v
.push_back(val
>> 8);
386 v
.push_back(val
>> 16);
387 v
.push_back(val
>> 24);
391 template<typename
... rest
>
392 void append(std::vector
<uint8_t>& v
, ptr u
, rest
... _rest
)
398 void fix_write(std::ostream
& str
, uint32_t off
, uint32_t val
)
400 str
.seekp(off
, std::ios::beg
);
408 throw std::runtime_error("Can't fixup AVI header");
412 void avidumper::flush_audio_to(unsigned commit_to
)
417 //Count the number of samples to actually write.
418 unsigned samples_to_write
= 0;
419 unsigned aptr
= audio_get_ptr
;
421 while(aptr
!= commit_to
) {
423 if((aptr
+= 2) == AVIDUMPER_AUDIO_BUFFER
)
427 std::vector
<uint8_t> buf
;
428 buf
.resize(8 + 4 * samples_to_write
);
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
) {
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;
447 if((audio_get_ptr
+= 2) == AVIDUMPER_AUDIO_BUFFER
)
450 assert(idx
== 8 + 4 * samples_to_write
);
451 avi_stream
.write(reinterpret_cast<char*>(&buf
[0]), idx
);
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
;
460 void avidumper::open_and_write_avi_header(uint16_t width
, uint16_t height
, uint32_t fps_n
, uint32_t fps_d
)
464 std::ostringstream str
;
465 str
<< prefix
<< "_" << std::setw(9) << std::setfill('0') << (current_segment
++) << ".avi";
469 avi_stream
.open(fstr
.c_str(), std::ios::out
| std::ios::binary
);
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. */
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());
546 throw std::runtime_error("Can't write AVI header");
547 total_data
+= aviheader
.size();
549 segment_movi_ptr
= 0;
552 segment_last_keyframe
= 0;
553 segment_chunks
.clear();
556 void avidumper::fixup_avi_header_and_close()
560 print_summary(std::cerr
);
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);
572 throw std::runtime_error("Error writing index header");
573 for(auto i
= segment_chunks
.begin(); i
!= segment_chunks
.end(); ++i
) {
575 avi_stream
.write(reinterpret_cast<char*>(buf
), 16);
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();
591 avidumper::~avidumper() throw()
596 void avidumper::wait_idle() throw()
598 umutex_class
_frame_mutex(frame_mutex
);
602 frame_cond
.wait(_frame_mutex
);