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"
9 #include "library/minmax.hpp"
10 #include "library/workthread.hpp"
11 #include "core/messages.hpp"
12 #include "core/instance.hpp"
13 #include "core/settings.hpp"
14 #include "core/moviedata.hpp"
15 #include "core/moviefile.hpp"
16 #include "core/rom.hpp"
24 #ifdef WITH_SECRET_RABBIT_CODE
25 #include <samplerate.h>
27 #define RESAMPLE_BUFFER 1024
33 uint32_t rates
[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000,
34 128000, 176400, 192000};
36 uint32_t topowerof2(uint32_t base
)
38 if((base
& (base
- 1)) == 0)
39 return base
; //Already power of two.
48 uint32_t get_rate(uint32_t n
, uint32_t d
, unsigned mode
)
51 auto best
= std::make_pair(1e99
, static_cast<size_t>(0));
52 for(size_t i
= 0; i
< sizeof(rates
) / sizeof(rates
[0]); i
++)
53 best
= ::min(best
, std::make_pair(fabs(log(static_cast<double>(d
) * rates
[i
] / n
)),
55 return rates
[best
.second
];
56 } else if(mode
== 1) {
57 return static_cast<uint32_t>(n
/ d
);
58 } else if(mode
== 2) {
59 return static_cast<uint32_t>((n
+ d
- 1) / d
);
60 } else if(mode
== 3) {
68 return static_cast<uint32_t>(n
/ x
);
69 } else if(mode
== 4) {
70 uint32_t base
= static_cast<uint32_t>((n
+ d
- 1) / d
);
71 //Handle large values specially.
72 if(base
> 0xFA000000U
) return 0xFFFFFFFFU
;
73 if(base
> 0xBB800000U
) return 0xFA000000U
;
74 if(base
> 0xAC440000U
) return 0xBB800000U
;
75 uint32_t base_A
= topowerof2((base
+ 7999) / 8000);
76 uint32_t base_B
= topowerof2((base
+ 11024) / 11025);
77 uint32_t base_C
= topowerof2((base
+ 11999) / 12000);
78 return min(base_A
* 8000, min(base_B
* 11025, base_C
* 12000));
84 settingvar::supervariable
<settingvar::model_bool
<settingvar::yes_no
>> dump_large(lsnes_setgrp
, "avi-large",
85 "AVI‣Large dump", false);
86 settingvar::supervariable
<settingvar::model_int
<0, 32>> fixed_xfact(lsnes_setgrp
, "avi-xfactor",
87 "AVI‣Fixed X factor", 0);
88 settingvar::supervariable
<settingvar::model_int
<0, 32>> fixed_yfact(lsnes_setgrp
, "avi-yfactor",
89 "AVI‣Fixed Y factor", 0);
90 settingvar::supervariable
<settingvar::model_int
<0, 8191>> dtb(lsnes_setgrp
, "avi-top-border",
91 "AVI‣Top padding", 0);
92 settingvar::supervariable
<settingvar::model_int
<0, 8191>> dbb(lsnes_setgrp
, "avi-bottom-border",
93 "AVI‣Bottom padding", 0);
94 settingvar::supervariable
<settingvar::model_int
<0, 8191>> dlb(lsnes_setgrp
, "avi-left-border",
95 "AVI‣Left padding", 0);
96 settingvar::supervariable
<settingvar::model_int
<0, 8191>> drb(lsnes_setgrp
, "avi-right-border",
97 "AVI‣Right padding", 0);
98 settingvar::supervariable
<settingvar::model_int
<0, 999999999>> max_frames_per_segment(lsnes_setgrp
,
99 "avi-maxframes", "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::supervariable
<settingvar::model_enumerated
<&soundrates
>> soundrate_setting(lsnes_setgrp
,
104 "avi-soundrate", "AVI‣Sound mode", 5);
106 settingvar::enumeration soundrates
{"nearest-common", "round-down", "round-up", "multiply"};
107 settingvar::supervariable
<settingvar::model_enumerated
<&soundrates
>> soundrate_setting(lsnes_setgrp
,
108 "avi-soundrate", "AVI‣Sound mode", 2);
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
);
129 struct avi_video_codec
* vcodec
;
130 struct avi_audio_codec
* acodec
;
131 uint32_t sample_rate
;
132 uint16_t audio_chans
;
138 struct resample_worker
: public workthread
140 resample_worker(avi_worker
* _worker
, double _ratio
, uint32_t _nch
);
143 void sendblock(short* block
, size_t frames
);
145 void set_ratio(double _ratio
);
147 std::vector
<short> buffers
;
148 std::vector
<float> buffers2
;
149 std::vector
<float> buffers3
;
150 std::vector
<short> buffers4
;
158 struct avi_worker
: public workthread
160 avi_worker(const struct avi_info
& info
);
163 void queue_video(uint32_t* _frame
, uint32_t stride
, uint32_t width
, uint32_t height
, uint32_t fps_n
,
165 void queue_audio(int16_t* data
, size_t samples
);
169 uint32_t frame_width
;
170 uint32_t frame_stride
;
171 uint32_t frame_height
;
172 uint32_t frame_fps_n
;
173 uint32_t frame_fps_d
;
175 uint32_t max_segframes
;
177 avi_video_codec
* ivcodec
;
180 #define WORKFLAG_QUEUE_FRAME 1
181 #define WORKFLAG_FLUSH 2
182 #define WORKFLAG_END 4
184 avi_worker::avi_worker(const struct avi_info
& info
)
185 : aviout(info
.prefix
, *info
.vcodec
, *info
.acodec
, info
.sample_rate
, info
.audio_chans
)
187 ivcodec
= info
.vcodec
;
189 max_segframes
= info
.max_frames
;
193 avi_worker::~avi_worker()
197 void avi_worker::queue_video(uint32_t* _frame
, uint32_t stride
, uint32_t width
, uint32_t height
,
198 uint32_t fps_n
, uint32_t fps_d
)
204 frame_stride
= stride
;
205 frame_height
= height
;
209 set_workflag(WORKFLAG_QUEUE_FRAME
);
212 void avi_worker::queue_audio(int16_t* data
, size_t samples
)
215 aviout
.audio_queue().push(data
, samples
);
216 set_workflag(WORKFLAG_FLUSH
);
219 void avi_worker::entry()
223 uint32_t work
= clear_workflag(~workthread::quit_request
);
224 //Flush the queue first in order to provode backpressure.
225 if(work
& WORKFLAG_FLUSH
) {
226 clear_workflag(WORKFLAG_FLUSH
);
229 //Then add frames if any.
230 if(work
& WORKFLAG_QUEUE_FRAME
) {
232 f
.stride
= frame_stride
;
233 f
.odata
= new uint32_t[f
.stride
* frame_height
+ 16];
235 while(reinterpret_cast<size_t>(f
.data
) % 16)
237 f
.width
= frame_width
;
238 f
.height
= frame_height
;
239 f
.fps_n
= frame_fps_n
;
240 f
.fps_d
= frame_fps_d
;
241 f
.force_break
= (segframes
== max_segframes
&& max_segframes
> 0);
244 auto wc
= get_wait_count();
245 ivcodec
->send_performance_counters(wc
.first
, wc
.second
);
246 framebuffer::copy_swap4(reinterpret_cast<uint8_t*>(f
.data
), frame
,
247 f
.stride
* frame_height
);
249 clear_workflag(WORKFLAG_QUEUE_FRAME
);
251 aviout
.video_queue().push_back(f
);
253 set_workflag(WORKFLAG_FLUSH
);
255 //End the streaam if that is flagged.
256 if(work
& WORKFLAG_END
) {
260 clear_workflag(WORKFLAG_END
| WORKFLAG_FLUSH
| WORKFLAG_QUEUE_FRAME
);
262 //If signaled to quit and no more work, do so.
263 if(work
== workthread::quit_request
) {
272 resample_worker::resample_worker(avi_worker
* _worker
, double _ratio
, uint32_t _nch
)
277 buffers
.resize(RESAMPLE_BUFFER
* nch
);
278 buffers2
.resize(RESAMPLE_BUFFER
* nch
);
279 buffers3
.resize((RESAMPLE_BUFFER
* nch
* ratio
) + 128 * nch
);
280 buffers4
.resize((RESAMPLE_BUFFER
* nch
* ratio
) + 128 * nch
);
282 #ifdef WITH_SECRET_RABBIT_CODE
284 resampler
= src_new(SRC_SINC_BEST_QUALITY
, nch
, &errc
);
286 throw std::runtime_error(std::string("Error initing libsamplerate: ") +
289 throw std::runtime_error("HQ sample rate conversion not available");
294 resample_worker::~resample_worker()
296 #ifdef WITH_SECRET_RABBIT_CODE
297 src_delete((SRC_STATE
*)resampler
);
301 void resample_worker::set_ratio(double _ratio
)
304 buffers3
.resize((RESAMPLE_BUFFER
* nch
* ratio
) + 128 * nch
);
305 buffers4
.resize((RESAMPLE_BUFFER
* nch
* ratio
) + 128 * nch
);
308 void resample_worker::sendend()
311 set_workflag(WORKFLAG_END
);
315 void resample_worker::sendblock(short* block
, size_t frames
)
320 if(bufused
+ frames
< RESAMPLE_BUFFER
) {
321 memcpy(&buffers
[bufused
* nch
], block
, 2 * nch
* frames
);
323 block
+= (frames
* nch
);
325 } else if(bufused
< RESAMPLE_BUFFER
) {
326 size_t processable
= RESAMPLE_BUFFER
- bufused
;
327 memcpy(&buffers
[bufused
* nch
], block
, 2 * nch
* processable
);
328 block
+= (processable
* nch
);
329 frames
-= processable
;
330 bufused
= RESAMPLE_BUFFER
;
333 set_workflag(WORKFLAG_QUEUE_FRAME
);
338 class avi_dumper_obj
: public dumper_base
341 avi_dumper_obj(master_dumper
& _mdumper
, dumper_factory_base
& _fbase
, const std::string
& mode
,
342 const std::string
& prefix
)
343 : dumper_base(_mdumper
, _fbase
), mdumper(_mdumper
)
346 avi_video_codec_type
* vcodec
;
347 avi_audio_codec_type
* acodec
;
350 throw std::runtime_error("Expected prefix");
351 struct avi_info info
;
352 info
.audio_chans
= 2;
353 info
.sample_rate
= 32000;
354 info
.max_frames
= max_frames_per_segment(*core
.settings
);
355 info
.prefix
= prefix
;
356 rpair(vcodec
, acodec
) = find_codecs(mode
);
357 info
.vcodec
= vcodec
->get_instance();
358 info
.acodec
= acodec
->get_instance();
360 unsigned srate_setting
= soundrate_setting(*core
.settings
);
361 chans
= info
.audio_chans
= 2;
362 soundrate
= mdumper
.get_rate();
363 audio_record_rate
= info
.sample_rate
= get_rate(soundrate
.first
, soundrate
.second
,
365 worker
= new avi_worker(info
);
366 soxdumper
= new sox_dumper(info
.prefix
+ ".sox",
367 static_cast<double>(soundrate
.first
) / soundrate
.second
, 2);
369 have_dumped_frame
= false;
371 if(srate_setting
== 4 || srate_setting
== 5) {
372 double ratio
= 1.0 * audio_record_rate
* soundrate
.second
/ soundrate
.first
;
374 sbuffer
.resize(RESAMPLE_BUFFER
* chans
);
375 resampler_w
= new resample_worker(worker
, ratio
, chans
);
377 mdumper
.add_dumper(*this);
378 } catch(std::bad_alloc
& e
) {
380 } catch(std::exception
& e
) {
381 std::ostringstream x
;
382 x
<< "Error starting AVI dump: " << e
.what();
383 throw std::runtime_error(x
.str());
385 messages
<< "Dumping AVI (" << vcodec
->get_hname() << " / " << acodec
->get_hname()
386 << ") to " << prefix
<< std::endl
;
388 ~avi_dumper_obj() throw()
392 resampler_w
->sendend();
393 worker
->request_quit();
395 mdumper
.drop_dumper(*this);
400 messages
<< "AVI Dump finished" << std::endl
;
402 void on_frame(struct framebuffer::raw
& _frame
, uint32_t fps_n
, uint32_t fps_d
)
405 uint32_t hscl
= 1, vscl
= 1;
406 unsigned fxfact
= fixed_xfact(*core
.settings
);
407 unsigned fyfact
= fixed_yfact(*core
.settings
);
408 if(fxfact
!= 0 && fyfact
!= 0) {
411 } else if(dump_large(*core
.settings
)) {
412 rpair(hscl
, vscl
) = core
.rom
->get_scale_factors(_frame
.get_width(),
413 _frame
.get_height());
415 if(!render_video_hud(dscr
, _frame
, fps_n
, fps_d
, hscl
, vscl
, dlb(*core
.settings
),
416 dtb(*core
.settings
), drb(*core
.settings
), dbb(*core
.settings
),
417 [this]() -> void { this->worker
->wait_busy(); }))
419 worker
->queue_video(dscr
.rowptr(0), dscr
.get_stride(), dscr
.get_width(), dscr
.get_height(),
421 have_dumped_frame
= true;
423 void on_sample(short l
, short r
)
426 if(!have_dumped_frame
)
428 sbuffer
[sbuffer_fill
++] = l
;
429 sbuffer
[sbuffer_fill
++] = r
;
430 if(sbuffer_fill
== sbuffer
.size()) {
431 resampler_w
->sendblock(&sbuffer
[0], sbuffer_fill
/ chans
);
434 soxdumper
->sample(l
, r
);
440 dcounter
+= soundrate
.first
;
441 while(dcounter
< soundrate
.second
* audio_record_rate
+ soundrate
.first
) {
442 if(have_dumped_frame
)
443 worker
->queue_audio(x
, 2);
444 dcounter
+= soundrate
.first
;
446 dcounter
-= (soundrate
.second
* audio_record_rate
+ soundrate
.first
);
447 if(have_dumped_frame
)
448 soxdumper
->sample(l
, r
);
450 void on_rate_change(uint32_t n
, uint32_t d
)
452 messages
<< "Warning: Changing AVI sound rate mid-dump is not supported!" << std::endl
;
453 //Try to do it anyway.
454 soundrate
= mdumper
.get_rate();
456 double ratio
= 1.0 * audio_record_rate
* soundrate
.second
/ soundrate
.first
;
458 resampler_w
->set_ratio(ratio
);
460 void on_gameinfo_change(const master_dumper::gameinfo
& gi
)
469 resample_worker
* resampler_w
;
471 master_dumper
& mdumper
;
472 sox_dumper
* soxdumper
;
473 framebuffer::fb
<false> dscr
;
475 bool have_dumped_frame
;
476 std::pair
<uint32_t, uint32_t> soundrate
;
477 uint32_t audio_record_rate
;
478 std::vector
<short> sbuffer
;
483 class adv_avi_dumper
: public dumper_factory_base
486 adv_avi_dumper() : dumper_factory_base("INTERNAL-AVI")
490 ~adv_avi_dumper() throw();
491 std::set
<std::string
> list_submodes() throw(std::bad_alloc
)
493 std::set
<std::string
> x
;
494 for(auto v
= avi_video_codec_type::find_next(NULL
); v
; v
= avi_video_codec_type::find_next(v
))
495 for(auto a
= avi_audio_codec_type::find_next(NULL
); a
;
496 a
= avi_audio_codec_type::find_next(a
))
497 x
.insert(v
->get_iname() + std::string("/") + a
->get_iname());
500 unsigned mode_details(const std::string
& mode
) throw()
502 return target_type_prefix
;
504 std::string
mode_extension(const std::string
& mode
) throw()
506 return ""; //Not interesting
508 std::string
name() throw(std::bad_alloc
)
510 return "AVI (internal)";
512 std::string
modename(const std::string
& mode
) throw(std::bad_alloc
)
514 avi_video_codec_type
* vcodec
;
515 avi_audio_codec_type
* acodec
;
516 rpair(vcodec
, acodec
) = find_codecs(mode
);
517 return vcodec
->get_hname() + std::string(" / ") + acodec
->get_hname();
519 avi_dumper_obj
* start(master_dumper
& _mdumper
, const std::string
& mode
, const std::string
& prefix
)
520 throw(std::bad_alloc
, std::runtime_error
)
522 return new avi_dumper_obj(_mdumper
, *this, mode
, prefix
);
526 adv_avi_dumper::~adv_avi_dumper() throw()
530 void resample_worker::entry()
534 uint32_t work
= clear_workflag(~workthread::quit_request
);
535 if(work
& (WORKFLAG_QUEUE_FRAME
| WORKFLAG_END
)) {
536 #ifdef WITH_SECRET_RABBIT_CODE
539 src_short_to_float_array(&buffers
[0], &buffers2
[0], bufused
* nch
);
540 block
.data_in
= &buffers2
[0];
541 block
.data_out
= &buffers3
[0];
542 block
.input_frames
= bufused
;
543 block
.input_frames_used
= 0;
544 block
.output_frames
= buffers3
.size() / nch
;
545 block
.output_frames_gen
= 0;
546 block
.end_of_input
= (work
& WORKFLAG_END
) ? 1 : 0;
547 block
.src_ratio
= ratio
;
548 int errc
= src_process((SRC_STATE
*)resampler
, &block
);
550 throw std::runtime_error(std::string("Error using libsamplerate: ") +
552 src_float_to_short_array(&buffers3
[0], &buffers4
[0], block
.output_frames_gen
* nch
);
553 worker
->queue_audio(&buffers4
[0], block
.output_frames_gen
* nch
);
554 if((size_t)block
.input_frames_used
< bufused
)
555 memmove(&buffers
[0], &buffers
[block
.output_frames_gen
* nch
], (bufused
-
556 block
.input_frames_used
) * nch
);
557 bufused
-= block
.input_frames_used
;
558 if(block
.output_frames_gen
> 0 && work
& WORKFLAG_END
)
559 goto again
; //Try again to get all the samples.
561 clear_workflag(WORKFLAG_END
| WORKFLAG_FLUSH
| WORKFLAG_QUEUE_FRAME
);
563 if(work
& WORKFLAG_END
)
566 if(work
== workthread::quit_request
)