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"
18 #define INBUF_PIXELS 3072
19 #define OUTBUF_ADVANCE 4096
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
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
)
40 throw std::runtime_error("Expected target");
42 complevel
= clevel(*CORE().settings
);
44 jmd
= &(socket_address(prefix
).connect());
45 deleter
= socket_address::deleter();
47 jmd
= new std::ofstream(prefix
.c_str(), std::ios::out
| std::ios::binary
);
51 throw std::runtime_error("Can't open output JMD file.");
53 //Write the segment tables.
55 //Stream #1 is PCM audio.
56 //Stream #2 is Gameinfo.
60 -1, -1, 0x4A, 0x50, 0x43, 0x52, 0x52, 0x4D, 0x55, 0x4C, 0x54, 0x49, 0x44,
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
));
75 throw std::runtime_error("Can't write JMD header and segment table");
76 have_dumped_frame
= false;
82 soundrate
= mdumper
.get_rate();
85 mdumper
.add_dumper(*this);
86 } catch(std::bad_alloc
& e
) {
88 } catch(std::exception
& e
) {
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);
99 char dummypacket
[8] = {0x00, 0x03};
103 if(last_written_ts
> maxtc
) {
108 serialization::u32b(dummypacket
+ 2, maxtc
- last_written_ts
);
109 last_written_ts
= maxtc
;
110 jmd
->write(dummypacket
, sizeof(dummypacket
));
112 throw std::runtime_error("Can't write JMD ending dummy packet");
116 messages
<< "JMD Dump finished" << std::endl
;
117 } catch(std::bad_alloc
& e
) {
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
);
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(),
136 flush_buffers(false);
137 have_dumped_frame
= true;
140 void on_sample(short l
, short r
)
146 uint64_t ts
= get_next_audio_ts();
147 if(have_dumped_frame
) {
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
);
161 void on_gameinfo_change(const master_dumper::gameinfo
& gi
)
163 //TODO: Dump the gameinfo.
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
) {
179 maxtc
= (ret
> maxtc
) ? ret
: maxtc
;
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
;
192 maxtc
= (ret
> maxtc
) ? ret
: maxtc
;
196 framebuffer::fb
<false> dscr
;
198 bool have_dumped_frame
;
204 std::pair
<uint32_t, uint32_t> soundrate
;
210 std::vector
<char> data
;
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
)
232 size_t px
= min(w
- x
, left
);
233 memmove(buf
+ dptr
, buf
+ sptr
, 4 * px
);
240 size_t px
= min(s
- x
, left
);
253 std::vector
<char> compress_frame(uint32_t* memory
, uint32_t stride
, uint32_t width
, uint32_t height
)
255 std::vector
<char> ret
;
257 memset(&stream
, 0, sizeof(stream
));
258 if(deflateInit(&stream
, complevel
) != Z_OK
)
259 throw std::runtime_error("Can't initialize zlib stream");
263 serialization::u16b(&ret
[0], width
);
264 serialization::u16b(&ret
[2], height
);
265 uint8_t input_buffer
[4 * INBUF_PIXELS
] __attribute__((aligned(16)));
267 size_t pixels
= static_cast<size_t>(stride
) * height
;
268 bool input_clear
= true;
269 bool flushed
= false;
275 size_t pcount
= min(static_cast<size_t>(INBUF_PIXELS
), pixels
- pixel
);
276 framebuffer::copy_swap4(input_buffer
, memory
+ pixel
, pcount
);
278 compact_buffer(input_buffer
, pixel
, stride
, width
, csize
);
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
) {
289 usize
+= (OUTBUF_ADVANCE
- stream
.avail_out
);
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
)
299 throw std::runtime_error("Can't deflate data");
303 usize
+= (OUTBUF_ADVANCE
- stream
.avail_out
);
310 void flush_buffers(bool force
)
312 while(!frames
.empty() || !samples
.empty()) {
313 if(frames
.empty() || samples
.empty()) {
316 else if(!frames
.empty()) {
317 frame_buffer
& f
= frames
.front();
320 } else if(!samples
.empty()) {
321 sample_buffer
& s
= samples
.front();
327 frame_buffer
& f
= frames
.front();
328 sample_buffer
& s
= samples
.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
;
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
);
354 throw std::runtime_error("Can't write JMD video packet header");
356 jmd
->write(&f
.data
[0], datasize
);
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
));
371 throw std::runtime_error("Can't write JMD sound packet");
375 void (*deleter
)(void* f
);
376 uint64_t last_written_ts
;
378 master_dumper
& mdumper
;
381 class adv_jmd_dumper
: public dumper_factory_base
384 adv_jmd_dumper() : dumper_factory_base("INTERNAL-JMD")
388 ~adv_jmd_dumper() throw();
389 std::set
<std::string
> list_submodes() throw(std::bad_alloc
)
391 std::set
<std::string
> 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
)
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
);
419 adv_jmd_dumper::~adv_jmd_dumper() throw()