Also support dumping JMD and SDMP over TCP/IP
[lsnes.git] / src / video / sdmp.cpp
blobe97173dfe3eb325fef91a3abbd52619ba27bef8c
1 #include "lsnes.hpp"
2 #include <snes/snes.hpp>
3 #include "core/advdumper.hpp"
4 #include "core/dispatch.hpp"
5 #include "library/serialization.hpp"
6 #include "video/tcp.hpp"
8 #include <iomanip>
9 #include <cassert>
10 #include <cstring>
11 #include <sstream>
12 #include <zlib.h>
13 #include <sstream>
14 #include <fstream>
15 #include <stdexcept>
17 #define CUTOFF 2100000000
18 #define SDUMP_FLAG_HIRES 1
19 #define SDUMP_FLAG_INTERLACED 2
20 #define SDUMP_FLAG_OVERSCAN 4
21 #define SDUMP_FLAG_PAL 8
23 namespace
25 void deleter_fn(void* f)
27 delete reinterpret_cast<std::ofstream*>(f);
30 class sdmp_avsnoop : public information_dispatch
32 public:
33 sdmp_avsnoop(const std::string& prefix, const std::string& mode) throw(std::bad_alloc,
34 std::runtime_error)
35 : information_dispatch("dump-sdmp")
37 enable_send_sound();
38 oprefix = prefix;
39 sdump_ss = (mode != "ms");
40 ssize = 0;
41 next_seq = 0;
42 dumped_pic = false;
43 if(mode == "tcp") {
44 out = &(socket_address(prefix).connect());
45 deleter = socket_address::deleter();
46 } else {
47 out = NULL;
48 deleter = deleter_fn;
52 ~sdmp_avsnoop() throw()
54 try {
55 if(out)
56 deleter(out);
57 } catch(...) {
61 void on_raw_frame(const uint32_t* raw, bool hires, bool interlaced, bool overscan, unsigned region)
63 unsigned flags = 0;
64 flags |= (hires ? SDUMP_FLAG_HIRES : 0);
65 flags |= (interlaced ? SDUMP_FLAG_INTERLACED : 0);
66 flags |= (overscan ? SDUMP_FLAG_OVERSCAN : 0);
67 flags |= (region == VIDEO_REGION_PAL ? SDUMP_FLAG_PAL : 0);
68 unsigned char tbuffer[2049];
69 if(!out || (ssize > CUTOFF && !sdump_ss)) {
70 std::cerr << "Starting new segment" << std::endl;
71 if(out)
72 deleter(out);
73 std::ostringstream str;
74 if(sdump_ss)
75 str << oprefix;
76 else
77 str << oprefix << "_" << std::setw(4) << std::setfill('0') << (next_seq++)
78 << ".sdmp";
79 std::string str2 = str.str();
80 out = new std::ofstream(str2.c_str(), std::ios::out | std::ios::binary);
81 if(!*out)
82 throw std::runtime_error("Failed to open '" + str2 + "'");
83 write32ube(tbuffer, 0x53444D50U);
84 write32ube(tbuffer + 4, SNES::system.cpu_frequency());
85 write32ube(tbuffer + 8, SNES::system.apu_frequency());
86 out->write(reinterpret_cast<char*>(tbuffer), 12);
87 if(!*out)
88 throw std::runtime_error("Failed to write header to '" + str2 + "'");
89 ssize = 12;
91 dumped_pic = true;
92 tbuffer[0] = flags;
93 for(unsigned i = 0; i < 512; i++) {
94 for(unsigned j = 0; j < 512; j++)
95 write32ube(tbuffer + (4 * j + 1), raw[512 * i + j]);
96 out->write(reinterpret_cast<char*>(tbuffer + (i ? 1 : 0)), i ? 2048 : 2049);
98 if(!*out)
99 throw std::runtime_error("Failed to write frame");
100 ssize += 1048577;
103 void on_sample(short l, short r)
105 if(!out || !dumped_pic)
106 return;
107 unsigned char pkt[5];
108 pkt[0] = 16;
109 write16sbe(pkt + 1, l);
110 write16sbe(pkt + 3, r);
111 out->write(reinterpret_cast<char*>(pkt), 5);
112 if(!*out)
113 throw std::runtime_error("Failed to write sample");
114 ssize += 5;
117 void on_dump_end()
119 deleter(out);
120 out = NULL;
123 bool get_dumper_flag() throw()
125 return true;
127 private:
128 std::string oprefix;
129 bool sdump_ss;
130 bool dumped_pic;
131 uint64_t ssize;
132 uint64_t next_seq;
133 void (*deleter)(void* f);
134 std::ostream* out;
137 sdmp_avsnoop* vid_dumper;
139 class adv_sdmp_dumper : public adv_dumper
141 public:
142 adv_sdmp_dumper() : adv_dumper("INTERNAL-SDMP") { information_dispatch::do_dumper_update(); }
143 ~adv_sdmp_dumper() throw();
144 std::set<std::string> list_submodes() throw(std::bad_alloc)
146 std::set<std::string> x;
147 x.insert("ss");
148 x.insert("ms");
149 x.insert("tcp");
150 return x;
153 unsigned mode_details(const std::string& mode) throw()
155 if(mode == "ss")
156 return target_type_file;
157 if(mode == "ms")
158 return target_type_prefix;
159 if(mode == "tcp")
160 return target_type_special;
161 return target_type_mask;
164 std::string name() throw(std::bad_alloc)
166 return "SDMP";
169 std::string modename(const std::string& mode) throw(std::bad_alloc)
171 if(mode == "ss")
172 return "Single-Segment";
173 if(mode == "ms")
174 return "Multi-Segment";
175 if(mode == "tcp")
176 return "over TCP/IP";
177 return "What?";
180 bool busy()
182 return (vid_dumper != NULL);
185 void start(const std::string& mode, const std::string& prefix) throw(std::bad_alloc,
186 std::runtime_error)
188 if(prefix == "")
189 throw std::runtime_error("Expected target");
190 if(vid_dumper)
191 throw std::runtime_error("SDMP Dump already in progress");
192 try {
193 vid_dumper = new sdmp_avsnoop(prefix, mode);
194 } catch(std::bad_alloc& e) {
195 throw;
196 } catch(std::exception& e) {
197 std::ostringstream x;
198 x << "Error starting SDMP dump: " << e.what();
199 throw std::runtime_error(x.str());
201 messages << "Dumping SDMP (" << mode << ") to " << prefix << std::endl;
202 information_dispatch::do_dumper_update();
205 void end() throw()
207 if(!vid_dumper)
208 throw std::runtime_error("No SDMP video dump in progress");
209 try {
210 vid_dumper->on_dump_end();
211 messages << "SDMP Dump finished" << std::endl;
212 } catch(std::bad_alloc& e) {
213 throw;
214 } catch(std::exception& e) {
215 messages << "Error ending SDMP dump: " << e.what() << std::endl;
217 delete vid_dumper;
218 vid_dumper = NULL;
219 information_dispatch::do_dumper_update();
221 } adv;
223 adv_sdmp_dumper::~adv_sdmp_dumper() throw()