lsnes rr2-β24
[lsnes.git] / src / video / pipedec.cpp
blob2e2c68c24eac24d25a9f13347dbc6a371dd9e555
1 #include "core/advdumper.hpp"
2 #include "core/dispatch.hpp"
3 #include "core/instance.hpp"
4 #include "core/moviedata.hpp"
5 #include "core/random.hpp"
6 #include "core/messages.hpp"
7 #include "core/rom.hpp"
8 #include "video/sox.hpp"
9 #include "library/serialization.hpp"
10 #include "library/string.hpp"
11 #include "library/hex.hpp"
12 #include <fcntl.h>
14 #include <iomanip>
15 #include <cassert>
16 #include <cstring>
17 #include <cerrno>
18 #include <cstring>
19 #include <sstream>
20 #include <fstream>
21 #include <zlib.h>
23 namespace
25 std::string get_pipedec_command(const std::string& type)
27 auto r = regex("(.*)[\\/][^\\/]+", get_config_path());
28 if(!r)
29 throw std::runtime_error("Can't read pipedec config file");
30 std::ifstream cfg(r[1] + "/pipedec/.pipedec");
31 if(!cfg)
32 throw std::runtime_error("Can't read pipedec config file");
33 std::string line;
34 while(std::getline(cfg, line)) {
35 if(line.length() >= type.length() && line.substr(0, type.length()) == type)
36 return line.substr(type.length());
38 throw std::runtime_error("No command found for " + type);
41 std::string substitute_cmd(std::string cmd, uint32_t w, uint32_t h, uint32_t fps_n, uint32_t fps_d,
42 uint32_t& segid)
44 bool perc_flag = false;
45 std::string out;
46 for(size_t i = 0; i < cmd.length(); i++) {
47 char ch = cmd[i];
48 if(perc_flag) {
49 switch(ch) {
50 case 'w':
51 out = out + (stringfmt() << w).str();
52 break;
53 case 'h':
54 out = out + (stringfmt() << h).str();
55 break;
56 case 'n':
57 out = out + (stringfmt() << fps_n).str();
58 break;
59 case 'd':
60 out = out + (stringfmt() << fps_d).str();
61 break;
62 case 'u':
63 out = out + (stringfmt() << time(NULL)).str();
64 break;
65 case 'i':
66 out = out + (stringfmt() << segid++).str();
67 break;
68 case '%':
69 out = out + "%";
70 break;
72 perc_flag = false;
73 } else if(ch == '%')
74 perc_flag = true;
75 else
76 out.append(1, ch);
78 return out;
81 class pipedec_dump_obj : public dumper_base
83 public:
84 pipedec_dump_obj(master_dumper& _mdumper, dumper_factory_base& _fbase, const std::string& mode,
85 const std::string& prefix)
86 : dumper_base(_mdumper, _fbase), mdumper(_mdumper)
88 bool _upsidedown = (mode[0] != 'v');
89 bool _swap = (mode[2] == 'R');
90 bool _bits32 = (mode[3] == '3');
92 if(prefix == "")
93 throw std::runtime_error("Expected filename");
94 try {
95 cmd = get_pipedec_command("!" + mode + ":");
96 video = NULL;
97 auto r = mdumper.get_rate();
98 audio = new sox_dumper(prefix, static_cast<double>(r.first) / r.second, 2);
99 if(!audio)
100 throw std::runtime_error("Can't open output file");
101 have_dumped_frame = false;
102 upsidedown = _upsidedown;
103 bits32 = _bits32;
104 swap = _swap;
106 last_width = 0;
107 last_height = 0;
108 last_fps_n = 0;
109 last_fps_d = 0;
110 segid = hex::from<uint32_t>(get_random_hexstring(8));
111 mdumper.add_dumper(*this);
112 } catch(std::bad_alloc& e) {
113 throw;
114 } catch(std::exception& e) {
115 std::ostringstream x;
116 x << "Error starting PIPEDEC dump: " << e.what();
117 throw std::runtime_error(x.str());
119 messages << "Dumping to " << prefix << std::endl;
121 ~pipedec_dump_obj() throw()
123 mdumper.drop_dumper(*this);
124 delete audio;
125 if(video)
126 pclose(video);
127 messages << "PIPEDEC Dump finished" << std::endl;
129 void on_frame(struct framebuffer::raw& _frame, uint32_t fps_n, uint32_t fps_d)
131 if(!render_video_hud(dscr, _frame, fps_n, fps_d, 1, 1, 0, 0, 0, 0, NULL))
132 return;
133 size_t w = dscr.get_width();
134 size_t h = dscr.get_height();
135 uint32_t stride = dscr.get_stride();
137 if(!video || last_width != w || last_height != h || last_fps_n != fps_n ||
138 last_fps_d != fps_d) {
139 //Segment change.
140 tmp.resize(4 * stride + 16);
141 std::string rcmd = substitute_cmd(cmd, w, h, fps_n, fps_d, segid);
142 if(video)
143 pclose(video);
144 video = popen(rcmd.c_str(), "w");
145 #if defined(_WIN32) || defined(_WIN64)
146 if(video)
147 setmode(fileno(video), O_BINARY);
148 #endif
149 if(!video) {
150 int err = errno;
151 messages << "Error starting a segment (" << err << ")" << std::endl;
152 return;
154 last_width = w;
155 last_height = h;
156 last_fps_n = fps_n;
157 last_fps_d = fps_d;
159 if(!video)
160 return;
161 uint32_t alignment = (16 - reinterpret_cast<size_t>(&tmp[0])) % 16;
162 char* data2 = &tmp[alignment];
163 for(size_t i = 0; i < h; i++) {
164 size_t ri = upsidedown ? (h - i - 1) : i;
165 char* data = reinterpret_cast<char*>(dscr.rowptr(ri));
166 if(bits32)
167 if(swap)
168 framebuffer::copy_swap4(reinterpret_cast<uint8_t*>(data2),
169 reinterpret_cast<uint32_t*>(data), stride);
170 else
171 memcpy(data2, data, 4 * stride);
172 else
173 if(swap)
174 framebuffer::copy_drop4s(reinterpret_cast<uint8_t*>(data2),
175 reinterpret_cast<uint32_t*>(data), stride);
176 else
177 framebuffer::copy_drop4(reinterpret_cast<uint8_t*>(data2),
178 reinterpret_cast<uint32_t*>(data), stride);
180 if(fwrite(data2, bits32 ? 4 : 3, w, video) < w)
181 messages << "Video write error" << std::endl;
183 have_dumped_frame = true;
186 void on_sample(short l, short r)
188 if(have_dumped_frame && audio)
189 audio->sample(l, r);
191 void on_rate_change(uint32_t n, uint32_t d)
193 messages << "Pipedec: Changing sound rate mid-dump not supported." << std::endl;
195 void on_gameinfo_change(const master_dumper::gameinfo& gi)
197 //Do nothing.
199 void on_end()
201 delete this;
203 private:
204 master_dumper& mdumper;
205 FILE* video;
206 sox_dumper* audio;
207 bool have_dumped_frame;
208 struct framebuffer::fb<false> dscr;
209 bool upsidedown;
210 bool bits32;
211 bool swap;
212 std::string cmd;
213 std::vector<char> tmp;
214 uint32_t last_fps_d;
215 uint32_t last_fps_n;
216 uint32_t last_height;
217 uint32_t last_width;
218 uint32_t segid;
221 class adv_pipedec_dumper : public dumper_factory_base
223 public:
224 adv_pipedec_dumper() : dumper_factory_base("INTERNAL-PIPEDEC")
226 ctor_notify();
228 ~adv_pipedec_dumper() throw();
229 std::set<std::string> list_submodes() throw(std::bad_alloc)
231 std::set<std::string> x;
232 x.insert("RGB24");
233 x.insert("vGB24");
234 x.insert("RGB32");
235 x.insert("vGB32");
236 x.insert("BGR24");
237 x.insert("vGR24");
238 x.insert("BGR32");
239 x.insert("vGR32");
240 return x;
242 unsigned mode_details(const std::string& mode) throw()
244 return target_type_file;
246 std::string mode_extension(const std::string& mode) throw()
248 return "sox";
250 std::string name() throw(std::bad_alloc)
252 return "PIPEDEC";
254 std::string modename(const std::string& mode) throw(std::bad_alloc)
256 return "!" + mode;
258 pipedec_dump_obj* start(master_dumper& _mdumper, const std::string& mode, const std::string& prefix)
259 throw(std::bad_alloc, std::runtime_error)
261 return new pipedec_dump_obj(_mdumper, *this, mode, prefix);
263 } adv;
265 adv_pipedec_dumper::~adv_pipedec_dumper() throw()