Don't dump JMD at undefined compression level
[lsnes.git] / src / core / jmd-control.cpp
blob965beecc19cd961b5ae20247bdf6f0a75ddbd45d
1 #include "jmd.hpp"
3 #include "core/advdumper.hpp"
4 #include "core/command.hpp"
5 #include "core/dispatch.hpp"
6 #include "core/lua.hpp"
7 #include "core/misc.hpp"
8 #include "core/settings.hpp"
10 #include <iomanip>
11 #include <cassert>
12 #include <cstring>
13 #include <sstream>
14 #include <zlib.h>
16 namespace
18 numeric_setting clevel("jmd-compression", 0, 9, 7);
20 class jmd_avsnoop : public information_dispatch
22 public:
23 jmd_avsnoop(const std::string& filename, unsigned level) throw(std::bad_alloc)
24 : information_dispatch("dump-jmd")
26 enable_send_sound();
27 vid_dumper = new jmd_dumper(filename, level);
28 have_dumped_frame = false;
29 audio_w = 0;
30 audio_n = 0;
31 video_w = 0;
32 video_n = 0;
33 maxtc = 0;
34 soundrate = get_sound_rate();
35 try {
36 on_gameinfo(get_gameinfo());
37 } catch(std::exception& e) {
38 messages << "Can't write gameinfo: " << e.what() << std::endl;
42 ~jmd_avsnoop() throw()
44 delete vid_dumper;
47 void on_frame(struct lcscreen& _frame, uint32_t fps_n, uint32_t fps_d)
49 struct lua_render_context lrc;
50 render_queue rq;
51 lrc.left_gap = 0;
52 lrc.right_gap = 0;
53 lrc.bottom_gap = 0;
54 lrc.top_gap = 0;
55 lrc.queue = &rq;
56 lrc.width = _frame.width;
57 lrc.height = _frame.height;
58 lua_callback_do_video(&lrc);
59 dscr.set_palette(0, 8, 16);
60 dscr.reallocate(lrc.left_gap + _frame.width + lrc.right_gap, lrc.top_gap + _frame.height +
61 lrc.bottom_gap, false);
62 dscr.set_origin(lrc.left_gap, lrc.top_gap);
63 dscr.copy_from(_frame, 1, 1);
64 rq.run(dscr);
66 vid_dumper->video(get_next_video_ts(fps_n, fps_d), dscr.memory, dscr.width, dscr.height);
67 have_dumped_frame = true;
70 void on_sample(short l, short r)
72 uint64_t ts = get_next_audio_ts();
73 if(have_dumped_frame)
74 vid_dumper->audio(ts, l, r);
77 void on_dump_end()
79 vid_dumper->end(maxtc);
82 void on_gameinfo(const struct gameinfo_struct& gi)
84 std::string authstr;
85 for(size_t i = 0; i < gi.get_author_count(); i++) {
86 if(i != 0)
87 authstr = authstr + ", ";
88 authstr = authstr + gi.get_author_short(i);
90 vid_dumper->gameinfo(gi.gamename, authstr, 1000000000ULL * gi.length, gi.get_rerecords());
93 bool get_dumper_flag() throw()
95 return true;
97 private:
98 uint64_t get_next_video_ts(uint32_t fps_n, uint32_t fps_d)
100 uint64_t ret = video_w;
101 video_w += (1000000000ULL * fps_d) / fps_n;
102 video_n += (1000000000ULL * fps_d) % fps_n;
103 if(video_n >= fps_n) {
104 video_n -= fps_n;
105 video_w++;
107 maxtc = (ret > maxtc) ? ret : maxtc;
108 return ret;
111 uint64_t get_next_audio_ts()
113 uint64_t ret = audio_w;
114 audio_w += (1000000000ULL * soundrate.second) / soundrate.first;
115 audio_n += (1000000000ULL * soundrate.second) % soundrate.first;
116 if(audio_n >= soundrate.first) {
117 audio_n -= soundrate.first;
118 audio_w++;
120 maxtc = (ret > maxtc) ? ret : maxtc;
121 return ret;
124 jmd_dumper* vid_dumper;
125 screen dscr;
126 unsigned dcounter;
127 bool have_dumped_frame;
128 uint64_t audio_w;
129 uint64_t audio_n;
130 uint64_t video_w;
131 uint64_t video_n;
132 uint64_t maxtc;
133 std::pair<uint32_t, uint32_t> soundrate;
136 jmd_avsnoop* vid_dumper;
138 void startdump(std::string prefix)
140 if(prefix == "")
141 throw std::runtime_error("Expected filename");
142 if(vid_dumper)
143 throw std::runtime_error("JMD dumping already in progress");
144 unsigned long level2 = (unsigned long)clevel;
145 try {
146 vid_dumper = new jmd_avsnoop(prefix, level2);
147 } catch(std::bad_alloc& e) {
148 throw;
149 } catch(std::exception& e) {
150 std::ostringstream x;
151 x << "Error starting JMD dump: " << e.what();
152 throw std::runtime_error(x.str());
154 messages << "Dumping to " << prefix << " at level " << level2 << std::endl;
155 information_dispatch::do_dumper_update();
158 void enddump()
160 if(!vid_dumper)
161 throw std::runtime_error("No JMD video dump in progress");
162 try {
163 vid_dumper->on_dump_end();
164 messages << "JMD Dump finished" << std::endl;
165 } catch(std::bad_alloc& e) {
166 throw;
167 } catch(std::exception& e) {
168 messages << "Error ending JMD dump: " << e.what() << std::endl;
170 delete vid_dumper;
171 vid_dumper = NULL;
172 information_dispatch::do_dumper_update();
175 function_ptr_command<const std::string&> jmd_dump("dump-jmd", "Start JMD capture",
176 "Syntax: dump-jmd <file>\nStart JMD capture to <file>.\n",
177 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
178 tokensplitter t(args);
179 std::string prefix = t.tail();
180 startdump(prefix);
183 function_ptr_command<> end_avi("end-jmd", "End JMD capture",
184 "Syntax: end-jmd\nEnd a JMD capture.\n",
185 []() throw(std::bad_alloc, std::runtime_error) {
186 enddump();
189 class adv_jmd_dumper : public adv_dumper
191 public:
192 adv_jmd_dumper() : adv_dumper("INTERNAL-JMD") {information_dispatch::do_dumper_update(); }
193 ~adv_jmd_dumper() throw();
194 std::set<std::string> list_submodes() throw(std::bad_alloc)
196 std::set<std::string> x;
197 return x;
200 bool wants_prefix(const std::string& mode) throw()
202 return false;
205 std::string name() throw(std::bad_alloc)
207 return "JMD";
210 std::string modename(const std::string& mode) throw(std::bad_alloc)
212 return "";
215 bool busy()
217 return (vid_dumper != NULL);
220 void start(const std::string& mode, const std::string& targetname) throw(std::bad_alloc,
221 std::runtime_error)
223 startdump(targetname);
226 void end() throw()
228 enddump();
230 } adv;
232 adv_jmd_dumper::~adv_jmd_dumper() throw()