Move render_video_hud and killed_audio_length to master dumper class
[lsnes.git] / src / video / jmd.cpp
blob800d3cade40819ac24ab0c17461c123bfae61853
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 if(prefix == "")
40 throw std::runtime_error("Expected target");
41 try {
42 complevel = clevel(*CORE().settings);
43 if(mode == "tcp") {
44 jmd = &(socket_address(prefix).connect());
45 deleter = socket_address::deleter();
46 } else {
47 jmd = new std::ofstream(prefix.c_str(), std::ios::out | std::ios::binary);
48 deleter = deleter_fn;
50 if(!*jmd)
51 throw std::runtime_error("Can't open output JMD file.");
52 last_written_ts = 0;
53 //Write the segment tables.
54 //Stream #0 is video.
55 //Stream #1 is PCM audio.
56 //Stream #2 is Gameinfo.
57 //Stream #3 is Dummy.
58 char header[] = {
59 /* Magic */
60 -1, -1, 0x4A, 0x50, 0x43, 0x52, 0x52, 0x4D, 0x55, 0x4C, 0x54, 0x49, 0x44,
61 0x55, 0x4D, 0x50,
62 /* Channel count. */
63 0x00, 0x04,
64 /* Video channel header. */
65 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 'v', 'i',
66 /* Audio channel header. */
67 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 'a', 'u',
68 /* Gameinfo channel header. */
69 0x00, 0x02, 0x00, 0x05, 0x00, 0x02, 'g', 'i',
70 /* Dummy channel header. */
71 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 'd', 'u'
73 jmd->write(header, sizeof(header));
74 if(!*jmd)
75 throw std::runtime_error("Can't write JMD header and segment table");
76 have_dumped_frame = false;
77 audio_w = 0;
78 audio_n = 0;
79 video_w = 0;
80 video_n = 0;
81 maxtc = 0;
82 soundrate = mdumper.get_rate();
83 akill = 0;
84 akillfrac = 0;
85 mdumper.add_dumper(*this);
86 } catch(std::bad_alloc& e) {
87 throw;
88 } catch(std::exception& e) {
89 std::ostringstream x;
90 x << "Error starting JMD dump: " << e.what();
91 throw std::runtime_error(x.str());
93 messages << "Dumping to " << prefix << " at level " << clevel(*CORE().settings) << std::endl;
95 ~jmd_dump_obj() throw()
97 mdumper.drop_dumper(*this);
98 try {
99 char dummypacket[8] = {0x00, 0x03};
100 if(!jmd)
101 goto out;
102 flush_buffers(true);
103 if(last_written_ts > maxtc) {
104 deleter(jmd);
105 jmd = NULL;
106 return;
108 serialization::u32b(dummypacket + 2, maxtc - last_written_ts);
109 last_written_ts = maxtc;
110 jmd->write(dummypacket, sizeof(dummypacket));
111 if(!*jmd)
112 throw std::runtime_error("Can't write JMD ending dummy packet");
113 deleter(jmd);
114 jmd = NULL;
115 out:
116 messages << "JMD Dump finished" << std::endl;
117 } catch(std::bad_alloc& e) {
118 throw;
119 } catch(std::exception& e) {
120 messages << "Error ending JMD dump: " << e.what() << std::endl;
124 void on_frame(struct framebuffer::raw& _frame, uint32_t fps_n, uint32_t fps_d)
126 if(!mdumper.render_video_hud(dscr, _frame, 1, 1, 0, 0, 0, 0, NULL)) {
127 akill += mdumper.killed_audio_length(fps_n, fps_d, akillfrac);
128 return;
130 frame_buffer f;
131 f.ts = get_next_video_ts(fps_n, fps_d);
132 //We'll compress the frame here.
133 f.data = compress_frame(dscr.rowptr(0), dscr.get_stride(), dscr.get_width(),
134 dscr.get_height());
135 frames.push_back(f);
136 flush_buffers(false);
137 have_dumped_frame = true;
140 void on_sample(short l, short r)
142 if(akill) {
143 akill--;
144 return;
146 uint64_t ts = get_next_audio_ts();
147 if(have_dumped_frame) {
148 sample_buffer s;
149 s.ts = ts;
150 s.l = l;
151 s.r = r;
152 samples.push_back(s);
153 flush_buffers(false);
156 void on_rate_change(uint32_t n, uint32_t d)
158 soundrate = std::make_pair(n, d);
159 audio_n = 0;
161 void on_gameinfo_change(const master_dumper::gameinfo& gi)
163 //TODO: Dump the gameinfo.
165 void on_end()
167 delete this;
169 private:
170 uint64_t get_next_video_ts(uint32_t fps_n, uint32_t fps_d)
172 uint64_t ret = video_w;
173 video_w += (1000000000ULL * fps_d) / fps_n;
174 video_n += (1000000000ULL * fps_d) % fps_n;
175 if(video_n >= fps_n) {
176 video_n -= fps_n;
177 video_w++;
179 maxtc = (ret > maxtc) ? ret : maxtc;
180 return ret;
183 uint64_t get_next_audio_ts()
185 uint64_t ret = audio_w;
186 audio_w += (1000000000ULL * soundrate.second) / soundrate.first;
187 audio_n += (1000000000ULL * soundrate.second) % soundrate.first;
188 if(audio_n >= soundrate.first) {
189 audio_n -= soundrate.first;
190 audio_w++;
192 maxtc = (ret > maxtc) ? ret : maxtc;
193 return ret;
196 framebuffer::fb<false> dscr;
197 unsigned dcounter;
198 bool have_dumped_frame;
199 uint64_t audio_w;
200 uint64_t audio_n;
201 uint64_t video_w;
202 uint64_t video_n;
203 uint64_t maxtc;
204 std::pair<uint32_t, uint32_t> soundrate;
205 uint64_t akill;
206 double akillfrac;
207 struct frame_buffer
209 uint64_t ts;
210 std::vector<char> data;
212 struct sample_buffer
214 uint64_t ts;
215 short l;
216 short r;
219 std::deque<frame_buffer> frames;
220 std::deque<sample_buffer> samples;
222 void compact_buffer(uint8_t* buf, size_t p, size_t s, size_t w, size_t& c)
224 size_t x = p % s;
225 size_t y = p / s;
226 size_t sptr = 0;
227 size_t dptr = 0;
228 size_t left = c;
229 while(left > 0) {
230 if(x < w) {
231 //Something to copy.
232 size_t px = min(w - x, left);
233 memmove(buf + dptr, buf + sptr, 4 * px);
234 x += px;
235 sptr += 4 * px;
236 dptr += 4 * px;
237 left -= px;
238 } else {
239 //In postgap.
240 size_t px = min(s - x, left);
241 x += px;
242 sptr += 4 * px;
243 left -= px;
244 if(x == s) {
245 x = 0;
246 y++;
250 c = dptr / 4;
253 std::vector<char> compress_frame(uint32_t* memory, uint32_t stride, uint32_t width, uint32_t height)
255 std::vector<char> ret;
256 z_stream stream;
257 memset(&stream, 0, sizeof(stream));
258 if(deflateInit(&stream, complevel) != Z_OK)
259 throw std::runtime_error("Can't initialize zlib stream");
261 size_t usize = 4;
262 ret.resize(4);
263 serialization::u16b(&ret[0], width);
264 serialization::u16b(&ret[2], height);
265 uint8_t input_buffer[4 * INBUF_PIXELS] __attribute__((aligned(16)));
266 size_t ptr = 0;
267 size_t pixels = static_cast<size_t>(stride) * height;
268 bool input_clear = true;
269 bool flushed = false;
270 size_t bsize = 0;
271 while(1) {
272 if(input_clear) {
273 size_t csize;
274 size_t pixel = ptr;
275 size_t pcount = min(static_cast<size_t>(INBUF_PIXELS), pixels - pixel);
276 framebuffer::copy_swap4(input_buffer, memory + pixel, pcount);
277 csize = pcount;
278 compact_buffer(input_buffer, pixel, stride, width, csize);
279 pixel += pcount;
280 bsize = csize;
281 ptr = pixel;
282 input_clear = false;
283 //Now the input data to compress is in input_buffer, bsize elements.
284 stream.next_in = reinterpret_cast<uint8_t*>(input_buffer);
285 stream.avail_in = 4 * bsize;
287 if(!stream.avail_out) {
288 if(flushed)
289 usize += (OUTBUF_ADVANCE - stream.avail_out);
290 flushed = true;
291 ret.resize(usize + OUTBUF_ADVANCE);
292 stream.next_out = reinterpret_cast<uint8_t*>(&ret[usize]);
293 stream.avail_out = OUTBUF_ADVANCE;
295 int r = deflate(&stream, (ptr == pixels) ? Z_FINISH : 0);
296 if(r == Z_STREAM_END)
297 break;
298 if(r != Z_OK)
299 throw std::runtime_error("Can't deflate data");
300 if(!stream.avail_in)
301 input_clear = true;
303 usize += (OUTBUF_ADVANCE - stream.avail_out);
304 deflateEnd(&stream);
306 ret.resize(usize);
307 return ret;
310 void flush_buffers(bool force)
312 while(!frames.empty() || !samples.empty()) {
313 if(frames.empty() || samples.empty()) {
314 if(!force)
315 return;
316 else if(!frames.empty()) {
317 frame_buffer& f = frames.front();
318 flush_frame(f);
319 frames.pop_front();
320 } else if(!samples.empty()) {
321 sample_buffer& s = samples.front();
322 flush_sample(s);
323 samples.pop_front();
325 continue;
327 frame_buffer& f = frames.front();
328 sample_buffer& s = samples.front();
329 if(f.ts <= s.ts) {
330 flush_frame(f);
331 frames.pop_front();
332 } else {
333 flush_sample(s);
334 samples.pop_front();
339 void flush_frame(frame_buffer& f)
341 //Channel 0, minor 1.
342 char videopacketh[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
343 serialization::u32b(videopacketh + 2, f.ts - last_written_ts);
344 last_written_ts = f.ts;
345 unsigned lneed = 0;
346 uint64_t datasize = f.data.size(); //Possibly upcast to avoid warnings.
347 for(unsigned shift = 63; shift > 0; shift -= 7)
348 if(datasize >= (1ULL << shift))
349 videopacketh[7 + lneed++] = 0x80 | ((datasize >> shift) & 0x7F);
350 videopacketh[7 + lneed++] = (datasize & 0x7F);
352 jmd->write(videopacketh, 7 + lneed);
353 if(!*jmd)
354 throw std::runtime_error("Can't write JMD video packet header");
355 if(datasize > 0)
356 jmd->write(&f.data[0], datasize);
357 if(!*jmd)
358 throw std::runtime_error("Can't write JMD video packet body");
361 void flush_sample(sample_buffer& s)
363 //Channel 1, minor 1, payload 4.
364 char soundpacket[12] = {0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04};
365 serialization::u32b(soundpacket + 2, s.ts - last_written_ts);
366 last_written_ts = s.ts;
367 serialization::s16b(soundpacket + 8, s.l);
368 serialization::s16b(soundpacket + 10, s.r);
369 jmd->write(soundpacket, sizeof(soundpacket));
370 if(!*jmd)
371 throw std::runtime_error("Can't write JMD sound packet");
374 std::ostream* jmd;
375 void (*deleter)(void* f);
376 uint64_t last_written_ts;
377 unsigned complevel;
378 master_dumper& mdumper;
381 class adv_jmd_dumper : public dumper_factory_base
383 public:
384 adv_jmd_dumper() : dumper_factory_base("INTERNAL-JMD")
386 ctor_notify();
388 ~adv_jmd_dumper() throw();
389 std::set<std::string> list_submodes() throw(std::bad_alloc)
391 std::set<std::string> x;
392 x.insert("file");
393 x.insert("tcp");
394 return x;
396 unsigned mode_details(const std::string& mode) throw()
398 return (mode == "tcp") ? target_type_special : target_type_file;
400 std::string mode_extension(const std::string& mode) throw()
402 return "jmd"; //Ignored if tcp mode.
404 std::string name() throw(std::bad_alloc)
406 return "JMD";
408 std::string modename(const std::string& mode) throw(std::bad_alloc)
410 return (mode == "tcp") ? "over TCP/IP" : "to file";
412 jmd_dump_obj* start(master_dumper& _mdumper, const std::string& mode, const std::string& prefix)
413 throw(std::bad_alloc, std::runtime_error)
415 return new jmd_dump_obj(_mdumper, *this, mode, prefix);
417 } adv;
419 adv_jmd_dumper::~adv_jmd_dumper() throw()