lsnes rr2-β24
[lsnes.git] / src / video / jmd.cpp
blob6359695d62fde643fa73dea4274e4f145f1bbc1d
1 #include "core/advdumper.hpp"
2 #include "core/dispatch.hpp"
3 #include "core/instance.hpp"
4 #include "core/moviedata.hpp"
5 #include "core/settings.hpp"
6 #include "core/messages.hpp"
7 #include "library/serialization.hpp"
8 #include "library/minmax.hpp"
9 #include "video/tcp.hpp"
11 #include <iomanip>
12 #include <cassert>
13 #include <cstring>
14 #include <sstream>
15 #include <fstream>
16 #include <deque>
17 #include <zlib.h>
18 #define INBUF_PIXELS 3072
19 #define OUTBUF_ADVANCE 4096
22 namespace
24 settingvar::supervariable<settingvar::model_int<0,9>> clevel(lsnes_setgrp, "jmd-compression",
25 "JMD‣Compression", 7);
27 void deleter_fn(void* f)
29 delete reinterpret_cast<std::ofstream*>(f);
32 class jmd_dump_obj : public dumper_base
34 public:
35 jmd_dump_obj(master_dumper& _mdumper, dumper_factory_base& _fbase, const std::string& mode,
36 const std::string& prefix)
37 : dumper_base(_mdumper, _fbase), mdumper(_mdumper)
39 auto& core = CORE();
40 if(prefix == "")
41 throw std::runtime_error("Expected target");
42 try {
43 complevel = clevel(*core.settings);
44 if(mode == "tcp") {
45 jmd = &(socket_address(prefix).connect());
46 deleter = socket_address::deleter();
47 } else {
48 jmd = new std::ofstream(prefix.c_str(), std::ios::out | std::ios::binary);
49 deleter = deleter_fn;
51 if(!*jmd)
52 throw std::runtime_error("Can't open output JMD file.");
53 last_written_ts = 0;
54 //Write the segment tables.
55 //Stream #0 is video.
56 //Stream #1 is PCM audio.
57 //Stream #2 is Gameinfo.
58 //Stream #3 is Dummy.
59 char header[] = {
60 /* Magic */
61 -1, -1, 0x4A, 0x50, 0x43, 0x52, 0x52, 0x4D, 0x55, 0x4C, 0x54, 0x49, 0x44,
62 0x55, 0x4D, 0x50,
63 /* Channel count. */
64 0x00, 0x04,
65 /* Video channel header. */
66 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 'v', 'i',
67 /* Audio channel header. */
68 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 'a', 'u',
69 /* Gameinfo channel header. */
70 0x00, 0x02, 0x00, 0x05, 0x00, 0x02, 'g', 'i',
71 /* Dummy channel header. */
72 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 'd', 'u'
74 jmd->write(header, sizeof(header));
75 if(!*jmd)
76 throw std::runtime_error("Can't write JMD header and segment table");
77 have_dumped_frame = false;
78 audio_w = 0;
79 audio_n = 0;
80 video_w = 0;
81 video_n = 0;
82 maxtc = 0;
83 soundrate = mdumper.get_rate();
84 mdumper.add_dumper(*this);
85 } catch(std::bad_alloc& e) {
86 throw;
87 } catch(std::exception& e) {
88 std::ostringstream x;
89 x << "Error starting JMD dump: " << e.what();
90 throw std::runtime_error(x.str());
92 messages << "Dumping to " << prefix << " at level " << clevel(*core.settings) << std::endl;
94 ~jmd_dump_obj() throw()
96 mdumper.drop_dumper(*this);
97 try {
98 char dummypacket[8] = {0x00, 0x03};
99 if(!jmd)
100 goto out;
101 flush_buffers(true);
102 if(last_written_ts > maxtc) {
103 deleter(jmd);
104 jmd = NULL;
105 return;
107 serialization::u32b(dummypacket + 2, maxtc - last_written_ts);
108 last_written_ts = maxtc;
109 jmd->write(dummypacket, sizeof(dummypacket));
110 if(!*jmd)
111 throw std::runtime_error("Can't write JMD ending dummy packet");
112 deleter(jmd);
113 jmd = NULL;
114 out:
115 messages << "JMD Dump finished" << std::endl;
116 } catch(std::bad_alloc& e) {
117 throw;
118 } catch(std::exception& e) {
119 messages << "Error ending JMD dump: " << e.what() << std::endl;
123 void on_frame(struct framebuffer::raw& _frame, uint32_t fps_n, uint32_t fps_d)
125 if(!render_video_hud(dscr, _frame, fps_n, fps_d, 1, 1, 0, 0, 0, 0, NULL))
126 return;
127 frame_buffer f;
128 f.ts = get_next_video_ts(fps_n, fps_d);
129 //We'll compress the frame here.
130 f.data = compress_frame(dscr.rowptr(0), dscr.get_stride(), dscr.get_width(),
131 dscr.get_height());
132 frames.push_back(f);
133 flush_buffers(false);
134 have_dumped_frame = true;
137 void on_sample(short l, short r)
139 uint64_t ts = get_next_audio_ts();
140 if(have_dumped_frame) {
141 sample_buffer s;
142 s.ts = ts;
143 s.l = l;
144 s.r = r;
145 samples.push_back(s);
146 flush_buffers(false);
149 void on_rate_change(uint32_t n, uint32_t d)
151 soundrate = std::make_pair(n, d);
152 audio_n = 0;
154 void on_gameinfo_change(const master_dumper::gameinfo& gi)
156 //TODO: Dump the gameinfo.
158 void on_end()
160 delete this;
162 private:
163 uint64_t get_next_video_ts(uint32_t fps_n, uint32_t fps_d)
165 uint64_t ret = video_w;
166 video_w += (1000000000ULL * fps_d) / fps_n;
167 video_n += (1000000000ULL * fps_d) % fps_n;
168 if(video_n >= fps_n) {
169 video_n -= fps_n;
170 video_w++;
172 maxtc = (ret > maxtc) ? ret : maxtc;
173 return ret;
176 uint64_t get_next_audio_ts()
178 uint64_t ret = audio_w;
179 audio_w += (1000000000ULL * soundrate.second) / soundrate.first;
180 audio_n += (1000000000ULL * soundrate.second) % soundrate.first;
181 if(audio_n >= soundrate.first) {
182 audio_n -= soundrate.first;
183 audio_w++;
185 maxtc = (ret > maxtc) ? ret : maxtc;
186 return ret;
189 framebuffer::fb<false> dscr;
190 unsigned dcounter;
191 bool have_dumped_frame;
192 uint64_t audio_w;
193 uint64_t audio_n;
194 uint64_t video_w;
195 uint64_t video_n;
196 uint64_t maxtc;
197 std::pair<uint32_t, uint32_t> soundrate;
198 struct frame_buffer
200 uint64_t ts;
201 std::vector<char> data;
203 struct sample_buffer
205 uint64_t ts;
206 short l;
207 short r;
210 std::deque<frame_buffer> frames;
211 std::deque<sample_buffer> samples;
213 void compact_buffer(uint8_t* buf, size_t p, size_t s, size_t w, size_t& c)
215 size_t x = p % s;
216 size_t y = p / s;
217 size_t sptr = 0;
218 size_t dptr = 0;
219 size_t left = c;
220 while(left > 0) {
221 if(x < w) {
222 //Something to copy.
223 size_t px = min(w - x, left);
224 memmove(buf + dptr, buf + sptr, 4 * px);
225 x += px;
226 sptr += 4 * px;
227 dptr += 4 * px;
228 left -= px;
229 } else {
230 //In postgap.
231 size_t px = min(s - x, left);
232 x += px;
233 sptr += 4 * px;
234 left -= px;
235 if(x == s) {
236 x = 0;
237 y++;
241 c = dptr / 4;
244 std::vector<char> compress_frame(uint32_t* memory, uint32_t stride, uint32_t width, uint32_t height)
246 std::vector<char> ret;
247 z_stream stream;
248 memset(&stream, 0, sizeof(stream));
249 if(deflateInit(&stream, complevel) != Z_OK)
250 throw std::runtime_error("Can't initialize zlib stream");
252 size_t usize = 4;
253 ret.resize(4);
254 serialization::u16b(&ret[0], width);
255 serialization::u16b(&ret[2], height);
256 uint8_t input_buffer[4 * INBUF_PIXELS] __attribute__((aligned(16)));
257 size_t ptr = 0;
258 size_t pixels = static_cast<size_t>(stride) * height;
259 bool input_clear = true;
260 bool flushed = false;
261 size_t bsize = 0;
262 while(1) {
263 if(input_clear) {
264 size_t csize;
265 size_t pixel = ptr;
266 size_t pcount = min(static_cast<size_t>(INBUF_PIXELS), pixels - pixel);
267 framebuffer::copy_swap4(input_buffer, memory + pixel, pcount);
268 csize = pcount;
269 compact_buffer(input_buffer, pixel, stride, width, csize);
270 pixel += pcount;
271 bsize = csize;
272 ptr = pixel;
273 input_clear = false;
274 //Now the input data to compress is in input_buffer, bsize elements.
275 stream.next_in = reinterpret_cast<uint8_t*>(input_buffer);
276 stream.avail_in = 4 * bsize;
278 if(!stream.avail_out) {
279 if(flushed)
280 usize += (OUTBUF_ADVANCE - stream.avail_out);
281 flushed = true;
282 ret.resize(usize + OUTBUF_ADVANCE);
283 stream.next_out = reinterpret_cast<uint8_t*>(&ret[usize]);
284 stream.avail_out = OUTBUF_ADVANCE;
286 int r = deflate(&stream, (ptr == pixels) ? Z_FINISH : 0);
287 if(r == Z_STREAM_END)
288 break;
289 if(r != Z_OK)
290 throw std::runtime_error("Can't deflate data");
291 if(!stream.avail_in)
292 input_clear = true;
294 usize += (OUTBUF_ADVANCE - stream.avail_out);
295 deflateEnd(&stream);
297 ret.resize(usize);
298 return ret;
301 void flush_buffers(bool force)
303 while(!frames.empty() || !samples.empty()) {
304 if(frames.empty() || samples.empty()) {
305 if(!force)
306 return;
307 else if(!frames.empty()) {
308 frame_buffer& f = frames.front();
309 flush_frame(f);
310 frames.pop_front();
311 } else if(!samples.empty()) {
312 sample_buffer& s = samples.front();
313 flush_sample(s);
314 samples.pop_front();
316 continue;
318 frame_buffer& f = frames.front();
319 sample_buffer& s = samples.front();
320 if(f.ts <= s.ts) {
321 flush_frame(f);
322 frames.pop_front();
323 } else {
324 flush_sample(s);
325 samples.pop_front();
330 void flush_frame(frame_buffer& f)
332 //Channel 0, minor 1.
333 char videopacketh[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
334 serialization::u32b(videopacketh + 2, f.ts - last_written_ts);
335 last_written_ts = f.ts;
336 unsigned lneed = 0;
337 uint64_t datasize = f.data.size(); //Possibly upcast to avoid warnings.
338 for(unsigned shift = 63; shift > 0; shift -= 7)
339 if(datasize >= (1ULL << shift))
340 videopacketh[7 + lneed++] = 0x80 | ((datasize >> shift) & 0x7F);
341 videopacketh[7 + lneed++] = (datasize & 0x7F);
343 jmd->write(videopacketh, 7 + lneed);
344 if(!*jmd)
345 throw std::runtime_error("Can't write JMD video packet header");
346 if(datasize > 0)
347 jmd->write(&f.data[0], datasize);
348 if(!*jmd)
349 throw std::runtime_error("Can't write JMD video packet body");
352 void flush_sample(sample_buffer& s)
354 //Channel 1, minor 1, payload 4.
355 char soundpacket[12] = {0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04};
356 serialization::u32b(soundpacket + 2, s.ts - last_written_ts);
357 last_written_ts = s.ts;
358 serialization::s16b(soundpacket + 8, s.l);
359 serialization::s16b(soundpacket + 10, s.r);
360 jmd->write(soundpacket, sizeof(soundpacket));
361 if(!*jmd)
362 throw std::runtime_error("Can't write JMD sound packet");
365 std::ostream* jmd;
366 void (*deleter)(void* f);
367 uint64_t last_written_ts;
368 unsigned complevel;
369 master_dumper& mdumper;
372 class adv_jmd_dumper : public dumper_factory_base
374 public:
375 adv_jmd_dumper() : dumper_factory_base("INTERNAL-JMD")
377 ctor_notify();
379 ~adv_jmd_dumper() throw();
380 std::set<std::string> list_submodes() throw(std::bad_alloc)
382 std::set<std::string> x;
383 x.insert("file");
384 x.insert("tcp");
385 return x;
387 unsigned mode_details(const std::string& mode) throw()
389 return (mode == "tcp") ? target_type_special : target_type_file;
391 std::string mode_extension(const std::string& mode) throw()
393 return "jmd"; //Ignored if tcp mode.
395 std::string name() throw(std::bad_alloc)
397 return "JMD";
399 std::string modename(const std::string& mode) throw(std::bad_alloc)
401 return (mode == "tcp") ? "over TCP/IP" : "to file";
403 jmd_dump_obj* start(master_dumper& _mdumper, const std::string& mode, const std::string& prefix)
404 throw(std::bad_alloc, std::runtime_error)
406 return new jmd_dump_obj(_mdumper, *this, mode, prefix);
408 } adv;
410 adv_jmd_dumper::~adv_jmd_dumper() throw()