Lua: Use multiarg for rest of gui-* stuff
[lsnes.git] / src / video / avi.cpp
blobb2e70e53985809fd1ff13c8d2649a34d06635dbf
1 #include "video/sox.hpp"
2 #include "video/avi/writer.hpp"
4 #include "video/avi/codec.hpp"
6 #include "core/advdumper.hpp"
7 #include "core/dispatch.hpp"
8 #include "lua/lua.hpp"
9 #include "library/minmax.hpp"
10 #include "library/workthread.hpp"
11 #include "core/misc.hpp"
12 #include "core/settings.hpp"
13 #include "core/moviedata.hpp"
14 #include "core/moviefile.hpp"
16 #include <iomanip>
17 #include <cassert>
18 #include <cstring>
19 #include <cmath>
20 #include <sstream>
21 #include <zlib.h>
22 #ifdef WITH_SECRET_RABBIT_CODE
23 #include <samplerate.h>
24 #endif
25 #define RESAMPLE_BUFFER 1024
27 namespace
29 class avi_avsnoop;
30 avi_avsnoop* vid_dumper;
31 uint64_t akill = 0;
32 double akillfrac = 0;
34 uint32_t rates[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000,
35 128000, 176400, 192000};
37 uint32_t topowerof2(uint32_t base)
39 if((base & (base - 1)) == 0)
40 return base; //Already power of two.
41 base |= (base >> 16);
42 base |= (base >> 8);
43 base |= (base >> 4);
44 base |= (base >> 2);
45 base |= (base >> 1);
46 return base + 1;
49 uint32_t get_rate(uint32_t n, uint32_t d, unsigned mode)
51 if(mode == 0) {
52 auto best = std::make_pair(1e99, static_cast<size_t>(0));
53 for(size_t i = 0; i < sizeof(rates) / sizeof(rates[0]); i++)
54 best = ::min(best, std::make_pair(fabs(log(static_cast<double>(d) * rates[i] / n)),
55 i));
56 return rates[best.second];
57 } else if(mode == 1) {
58 return static_cast<uint32_t>(n / d);
59 } else if(mode == 2) {
60 return static_cast<uint32_t>((n + d - 1) / d);
61 } else if(mode == 3) {
62 uint32_t x = n;
63 uint32_t y = d;
64 while(y) {
65 uint32_t t = x % d;
66 x = y;
67 y = t;
69 return static_cast<uint32_t>(n / x);
70 } else if(mode == 4) {
71 uint32_t base = static_cast<uint32_t>((n + d - 1) / d);
72 //Handle large values specially.
73 if(base > 0xFA000000U) return 0xFFFFFFFFU;
74 if(base > 0xBB800000U) return 0xFA000000U;
75 if(base > 0xAC440000U) return 0xBB800000U;
76 uint32_t base_A = topowerof2((base + 7999) / 8000);
77 uint32_t base_B = topowerof2((base + 11024) / 11025);
78 uint32_t base_C = topowerof2((base + 11999) / 12000);
79 return min(base_A * 8000, min(base_B * 11025, base_C * 12000));
80 } else if(mode == 5)
81 return 48000;
82 return 48000;
85 settingvar::variable<settingvar::model_bool<settingvar::yes_no>> dump_large(lsnes_vset, "avi-large",
86 "AVI‣Large dump", false);
87 settingvar::variable<settingvar::model_int<0, 32>> fixed_xfact(lsnes_vset, "avi-xfactor",
88 "AVI‣Fixed X factor", 0);
89 settingvar::variable<settingvar::model_int<0, 32>> fixed_yfact(lsnes_vset, "avi-yfactor",
90 "AVI‣Fixed Y factor", 0);
91 settingvar::variable<settingvar::model_int<0, 8191>> dtb(lsnes_vset, "avi-top-border", "AVI‣Top padding", 0);
92 settingvar::variable<settingvar::model_int<0, 8191>> dbb(lsnes_vset, "avi-bottom-border",
93 "AVI‣Bottom padding", 0);
94 settingvar::variable<settingvar::model_int<0, 8191>> dlb(lsnes_vset, "avi-left-border",
95 "AVI‣Left padding", 0);
96 settingvar::variable<settingvar::model_int<0, 8191>> drb(lsnes_vset, "avi-right-border", "AVI‣Right padding",
97 0);
98 settingvar::variable<settingvar::model_int<0, 999999999>> max_frames_per_segment(lsnes_vset, "avi-maxframes",
99 "AVI‣Max frames per segment", 0);
100 #ifdef WITH_SECRET_RABBIT_CODE
101 settingvar::enumeration soundrates {"nearest-common", "round-down", "round-up", "multiply",
102 "High quality 44.1kHz", "High quality 48kHz"};
103 settingvar::variable<settingvar::model_enumerated<&soundrates>> soundrate_setting(lsnes_vset, "avi-soundrate",
104 "AVI‣Sound mode", 5);
105 #else
106 settingvar::enumeration soundrates {"nearest-common", "round-down", "round-up", "multiply"};
107 settingvar::variable<settingvar::model_enumerated<&soundrates>> soundrate_setting(lsnes_vset, "avi-soundrate",
108 "AVI‣Sound mode", 2);
109 #endif
111 std::pair<avi_video_codec_type*, avi_audio_codec_type*> find_codecs(const std::string& mode)
113 avi_video_codec_type* v = NULL;
114 avi_audio_codec_type* a = NULL;
115 std::string _mode = mode;
116 size_t s = _mode.find_first_of("/");
117 if(s < _mode.length()) {
118 std::string vcodec = _mode.substr(0, s);
119 std::string acodec = _mode.substr(s + 1);
120 v = avi_video_codec_type::find(vcodec);
121 a = avi_audio_codec_type::find(acodec);
123 return std::make_pair(v, a);
126 struct avi_info
128 std::string prefix;
129 struct avi_video_codec* vcodec;
130 struct avi_audio_codec* acodec;
131 uint32_t sample_rate;
132 uint16_t audio_chans;
133 uint32_t max_frames;
136 struct resample_worker : public worker_thread
138 resample_worker(double _ratio, uint32_t _nch);
139 ~resample_worker();
140 void entry();
141 void sendblock(short* block, size_t frames);
142 void sendend();
143 private:
144 std::vector<short> buffers;
145 std::vector<float> buffers2;
146 std::vector<float> buffers3;
147 std::vector<short> buffers4;
148 size_t bufused;
149 double ratio;
150 uint32_t nch;
151 void* resampler;
154 struct avi_worker : public worker_thread
156 avi_worker(const struct avi_info& info);
157 ~avi_worker();
158 void entry();
159 void queue_video(uint32_t* _frame, uint32_t width, uint32_t height, uint32_t fps_n, uint32_t fps_d);
160 void queue_audio(int16_t* data, size_t samples);
161 private:
162 avi_writer aviout;
163 uint32_t* frame;
164 uint32_t frame_width;
165 uint32_t frame_height;
166 uint32_t frame_fps_n;
167 uint32_t frame_fps_d;
168 uint32_t segframes;
169 uint32_t max_segframes;
170 bool closed;
171 avi_video_codec* ivcodec;
174 #define WORKFLAG_QUEUE_FRAME 1
175 #define WORKFLAG_FLUSH 2
176 #define WORKFLAG_END 4
178 avi_worker::avi_worker(const struct avi_info& info)
179 : aviout(info.prefix, *info.vcodec, *info.acodec, info.sample_rate, info.audio_chans)
181 ivcodec = info.vcodec;
182 segframes = 0;
183 max_segframes = info.max_frames;
184 fire();
187 avi_worker::~avi_worker()
191 void avi_worker::queue_video(uint32_t* _frame, uint32_t width, uint32_t height, uint32_t fps_n, uint32_t fps_d)
193 rethrow();
194 wait_busy();
195 frame = _frame;
196 frame_width = width;
197 frame_height = height;
198 frame_fps_n = fps_n;
199 frame_fps_d = fps_d;
200 set_busy();
201 set_workflag(WORKFLAG_QUEUE_FRAME);
204 void avi_worker::queue_audio(int16_t* data, size_t samples)
206 rethrow();
207 aviout.audio_queue().push(data, samples);
208 set_workflag(WORKFLAG_FLUSH);
211 void avi_worker::entry()
213 while(1) {
214 wait_workflag();
215 uint32_t work = clear_workflag(~WORKFLAG_QUIT_REQUEST);
216 //Flush the queue first in order to provode backpressure.
217 if(work & WORKFLAG_FLUSH) {
218 clear_workflag(WORKFLAG_FLUSH);
219 aviout.flush();
221 //Then add frames if any.
222 if(work & WORKFLAG_QUEUE_FRAME) {
223 frame_object f;
224 f.data = new uint32_t[frame_width * frame_height];
225 f.width = frame_width;
226 f.height = frame_height;
227 f.fps_n = frame_fps_n;
228 f.fps_d = frame_fps_d;
229 f.force_break = (segframes == max_segframes && max_segframes > 0);
230 if(f.force_break)
231 segframes = 0;
232 auto wc = get_wait_count();
233 ivcodec->send_performance_counters(wc.first, wc.second);
234 memcpy(&f.data[0], frame, 4 * frame_width * frame_height);
235 frame = NULL;
236 clear_workflag(WORKFLAG_QUEUE_FRAME);
237 clear_busy();
238 aviout.video_queue().push_back(f);
239 segframes++;
240 set_workflag(WORKFLAG_FLUSH);
242 //End the streaam if that is flagged.
243 if(work & WORKFLAG_END) {
244 if(!closed)
245 aviout.close();
246 closed = true;
247 clear_workflag(WORKFLAG_END | WORKFLAG_FLUSH | WORKFLAG_QUEUE_FRAME);
249 //If signaled to quit and no more work, do so.
250 if(work == WORKFLAG_QUIT_REQUEST) {
251 if(!closed)
252 aviout.close();
253 closed = true;
254 break;
259 resample_worker::resample_worker(double _ratio, uint32_t _nch)
261 ratio = _ratio;
262 nch = _nch;
263 buffers.resize(RESAMPLE_BUFFER * nch);
264 buffers2.resize(RESAMPLE_BUFFER * nch);
265 buffers3.resize((RESAMPLE_BUFFER * nch * ratio) + 128 * nch);
266 buffers4.resize((RESAMPLE_BUFFER * nch * ratio) + 128 * nch);
267 bufused = 0;
268 #ifdef WITH_SECRET_RABBIT_CODE
269 int errc = 0;
270 resampler = src_new(SRC_SINC_BEST_QUALITY, nch, &errc);
271 if(errc)
272 throw std::runtime_error(std::string("Error initing libsamplerate: ") +
273 src_strerror(errc));
274 #else
275 throw std::runtime_error("HQ sample rate conversion not available");
276 #endif
277 fire();
280 resample_worker::~resample_worker()
282 #ifdef WITH_SECRET_RABBIT_CODE
283 src_delete((SRC_STATE*)resampler);
284 #endif
287 void resample_worker::sendend()
289 rethrow();
290 set_workflag(WORKFLAG_END);
291 request_quit();
294 void resample_worker::sendblock(short* block, size_t frames)
296 again:
297 rethrow();
298 wait_busy();
299 if(bufused + frames < RESAMPLE_BUFFER) {
300 memcpy(&buffers[bufused * nch], block, 2 * nch * frames);
301 bufused += frames;
302 block += (frames * nch);
303 frames = 0;
304 } else if(bufused < RESAMPLE_BUFFER) {
305 size_t processable = RESAMPLE_BUFFER - bufused;
306 memcpy(&buffers[bufused * nch], block, 2 * nch * processable);
307 block += (processable * nch);
308 frames -= processable;
309 bufused = RESAMPLE_BUFFER;
311 set_busy();
312 set_workflag(WORKFLAG_QUEUE_FRAME);
313 if(frames > 0)
314 goto again;
317 void waitfn();
319 class avi_avsnoop : public information_dispatch
321 public:
322 avi_avsnoop(avi_info& info) throw(std::bad_alloc, std::runtime_error)
323 : information_dispatch("dump-avi-int")
325 enable_send_sound();
326 chans = info.audio_chans = 2;
327 soundrate = get_sound_rate();
328 audio_record_rate = info.sample_rate = get_rate(soundrate.first, soundrate.second,
329 soundrate_setting);
330 worker = new avi_worker(info);
331 soxdumper = new sox_dumper(info.prefix + ".sox", static_cast<double>(soundrate.first) /
332 soundrate.second, 2);
333 dcounter = 0;
334 have_dumped_frame = false;
335 resampler_w = NULL;
336 if(soundrate_setting == 4 || soundrate_setting == 5) {
337 double ratio = 1.0 * audio_record_rate * soundrate.second / soundrate.first;
338 sbuffer_fill = 0;
339 sbuffer.resize(RESAMPLE_BUFFER * chans);
340 resampler_w = new resample_worker(ratio, chans);
344 ~avi_avsnoop() throw()
346 if(resampler_w)
347 delete resampler_w;
348 delete worker;
349 delete soxdumper;
352 void on_frame(struct framebuffer::raw& _frame, uint32_t fps_n, uint32_t fps_d)
354 uint32_t hscl = 1;
355 uint32_t vscl = 1;
356 auto scl = our_rom.rtype->get_scale_factors(_frame.get_width(), _frame.get_height());
357 if(fixed_xfact != 0 && fixed_yfact != 0) {
358 hscl = fixed_xfact;
359 vscl = fixed_yfact;
360 } else if(dump_large) {
361 hscl = scl.first;
362 vscl = scl.second;
364 if(!render_video_hud(dscr, _frame, hscl, vscl, 0, 8, 16, dlb, dtb, drb, dbb, waitfn)) {
365 akill += killed_audio_length(fps_n, fps_d, akillfrac);
366 return;
368 worker->queue_video(dscr.rowptr(0), dscr.get_width(), dscr.get_height(), fps_n, fps_d);
369 have_dumped_frame = true;
372 void on_sample(short l, short r)
374 if(akill) {
375 akill--;
376 return;
378 if(resampler_w) {
379 if(!have_dumped_frame)
380 return;
381 sbuffer[sbuffer_fill++] = l;
382 sbuffer[sbuffer_fill++] = r;
383 if(sbuffer_fill == sbuffer.size()) {
384 resampler_w->sendblock(&sbuffer[0], sbuffer_fill / chans);
385 sbuffer_fill = 0;
387 soxdumper->sample(l, r);
388 return;
390 short x[2];
391 x[0] = l;
392 x[1] = r;
393 dcounter += soundrate.first;
394 while(dcounter < soundrate.second * audio_record_rate + soundrate.first) {
395 if(have_dumped_frame)
396 worker->queue_audio(x, 2);
397 dcounter += soundrate.first;
399 dcounter -= (soundrate.second * audio_record_rate + soundrate.first);
400 if(have_dumped_frame)
401 soxdumper->sample(l, r);
404 void on_dump_end()
406 if(worker) {
407 if(resampler_w)
408 resampler_w->sendend();
409 worker->request_quit();
411 if(soxdumper)
412 soxdumper->close();
413 delete worker;
414 delete soxdumper;
415 worker = NULL;
416 soxdumper = NULL;
419 bool get_dumper_flag() throw()
421 return true;
423 avi_worker* worker;
424 resample_worker* resampler_w;
425 private:
426 sox_dumper* soxdumper;
427 framebuffer::fb<false> dscr;
428 unsigned dcounter;
429 bool have_dumped_frame;
430 std::pair<uint32_t, uint32_t> soundrate;
431 uint32_t audio_record_rate;
432 std::vector<short> sbuffer;
433 size_t sbuffer_fill;
434 uint32_t chans;
437 void waitfn()
439 vid_dumper->worker->wait_busy();
442 class adv_avi_dumper : public adv_dumper
444 public:
445 adv_avi_dumper() : adv_dumper("INTERNAL-AVI") {information_dispatch::do_dumper_update(); }
446 ~adv_avi_dumper() throw();
447 std::set<std::string> list_submodes() throw(std::bad_alloc)
449 std::set<std::string> x;
450 for(auto v = avi_video_codec_type::find_next(NULL); v; v = avi_video_codec_type::find_next(v))
451 for(auto a = avi_audio_codec_type::find_next(NULL); a;
452 a = avi_audio_codec_type::find_next(a))
453 x.insert(v->get_iname() + std::string("/") + a->get_iname());
454 return x;
457 unsigned mode_details(const std::string& mode) throw()
459 return target_type_prefix;
462 std::string mode_extension(const std::string& mode) throw()
464 return ""; //Not interesting
468 std::string name() throw(std::bad_alloc)
470 return "AVI (internal)";
473 std::string modename(const std::string& mode) throw(std::bad_alloc)
475 auto c = find_codecs(mode);
476 return c.first->get_hname() + std::string(" / ") + c.second->get_hname();
479 bool busy()
481 return (vid_dumper != NULL);
484 void start(const std::string& mode, const std::string& prefix) throw(std::bad_alloc,
485 std::runtime_error)
487 if(prefix == "")
488 throw std::runtime_error("Expected prefix");
489 if(vid_dumper)
490 throw std::runtime_error("AVI dumping already in progress");
491 struct avi_info info;
492 info.audio_chans = 2;
493 info.sample_rate = 32000;
494 info.max_frames = max_frames_per_segment;
495 info.prefix = prefix;
496 auto c = find_codecs(mode);
497 info.vcodec = c.first->get_instance();
498 info.acodec = c.second->get_instance();
499 try {
500 vid_dumper = new avi_avsnoop(info);
501 } catch(std::bad_alloc& e) {
502 throw;
503 } catch(std::exception& e) {
504 std::ostringstream x;
505 x << "Error starting AVI dump: " << e.what();
506 throw std::runtime_error(x.str());
508 messages << "Dumping AVI (" << c.first->get_hname() << " / " << c.second->get_hname()
509 << ") to " << prefix << std::endl;
510 information_dispatch::do_dumper_update();
511 akill = 0;
512 akillfrac = 0;
515 void end() throw()
517 if(!vid_dumper)
518 throw std::runtime_error("No AVI video dump in progress");
519 try {
520 vid_dumper->on_dump_end();
521 messages << "AVI Dump finished" << std::endl;
522 } catch(std::bad_alloc& e) {
523 throw;
524 } catch(std::exception& e) {
525 messages << "Error ending AVI dump: " << e.what() << std::endl;
527 delete vid_dumper;
528 vid_dumper = NULL;
529 information_dispatch::do_dumper_update();
531 } adv;
533 adv_avi_dumper::~adv_avi_dumper() throw()
537 void resample_worker::entry()
539 while(1) {
540 wait_workflag();
541 uint32_t work = clear_workflag(~WORKFLAG_QUIT_REQUEST);
542 if(work & (WORKFLAG_QUEUE_FRAME | WORKFLAG_END)) {
543 #ifdef WITH_SECRET_RABBIT_CODE
544 again:
545 SRC_DATA block;
546 src_short_to_float_array(&buffers[0], &buffers2[0], bufused * nch);
547 block.data_in = &buffers2[0];
548 block.data_out = &buffers3[0];
549 block.input_frames = bufused;
550 block.input_frames_used = 0;
551 block.output_frames = buffers3.size() / nch;
552 block.output_frames_gen = 0;
553 block.end_of_input = (work & WORKFLAG_END) ? 1 : 0;
554 block.src_ratio = ratio;
555 int errc = src_process((SRC_STATE*)resampler, &block);
556 if(errc)
557 throw std::runtime_error(std::string("Error using libsamplerate: ") +
558 src_strerror(errc));
559 src_float_to_short_array(&buffers3[0], &buffers4[0], block.output_frames_gen * nch);
560 vid_dumper->worker->queue_audio(&buffers4[0], block.output_frames_gen * nch);
561 if((size_t)block.input_frames_used < bufused)
562 memmove(&buffers[0], &buffers[block.output_frames_gen * nch], (bufused -
563 block.input_frames_used) * nch);
564 bufused -= block.input_frames_used;
565 if(block.output_frames_gen > 0 && work & WORKFLAG_END)
566 goto again; //Try again to get all the samples.
567 #endif
568 clear_workflag(WORKFLAG_END | WORKFLAG_FLUSH | WORKFLAG_QUEUE_FRAME);
569 clear_busy();
570 if(work & WORKFLAG_END)
571 return;
573 if(work == WORKFLAG_QUIT_REQUEST)
574 break;