1 #include "core/advdumper.hpp"
2 #include "core/dispatch.hpp"
3 #include "core/settings.hpp"
4 #include "library/serialization.hpp"
5 #include "video/tcp.hpp"
14 #define INBUF_PIXELS 4096
15 #define OUTBUF_ADVANCE 4096
20 numeric_setting
clevel("jmd-compression", 0, 9, 7);
22 void deleter_fn(void* f
)
24 delete reinterpret_cast<std::ofstream
*>(f
);
27 class jmd_avsnoop
: public information_dispatch
30 jmd_avsnoop(const std::string
& filename
, bool tcp_flag
) throw(std::bad_alloc
)
31 : information_dispatch("dump-jmd")
36 jmd
= &(socket_address(filename
).connect());
37 deleter
= socket_address::deleter();
39 jmd
= new std::ofstream(filename
.c_str(), std::ios::out
| std::ios::binary
);
43 throw std::runtime_error("Can't open output JMD file.");
45 //Write the segment tables.
47 //Stream #1 is PCM audio.
48 //Stream #2 is Gameinfo.
52 -1, -1, 0x4A, 0x50, 0x43, 0x52, 0x52, 0x4D, 0x55, 0x4C, 0x54, 0x49, 0x44, 0x55, 0x4D,
56 /* Video channel header. */
57 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 'v', 'i',
58 /* Audio channel header. */
59 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 'a', 'u',
60 /* Gameinfo channel header. */
61 0x00, 0x02, 0x00, 0x05, 0x00, 0x02, 'g', 'i',
62 /* Dummy channel header. */
63 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 'd', 'u'
65 jmd
->write(header
, sizeof(header
));
67 throw std::runtime_error("Can't write JMD header and segment table");
68 have_dumped_frame
= false;
74 soundrate
= get_sound_rate();
76 on_gameinfo(get_gameinfo());
77 } catch(std::exception
& e
) {
78 messages
<< "Can't write gameinfo: " << e
.what() << std::endl
;
82 ~jmd_avsnoop() throw()
90 void on_frame(struct lcscreen
& _frame
, uint32_t fps_n
, uint32_t fps_d
)
92 render_video_hud(dscr
, _frame
, 1, 1, 0, 8, 16, 0, 0, 0, 0, NULL
);
94 f
.ts
= get_next_video_ts(fps_n
, fps_d
);
96 //We'll compress the frame here.
97 f
.data
= compress_frame(dscr
.memory
, dscr
.width
, dscr
.height
);
100 have_dumped_frame
= true;
103 void on_sample(short l
, short r
)
105 uint64_t ts
= get_next_audio_ts();
106 if(have_dumped_frame
) {
111 samples
.push_back(s
);
112 flush_buffers(false);
121 if(last_written_ts
> maxtc
) {
126 char dummypacket
[8] = {0x00, 0x03};
127 write32ube(dummypacket
+ 2, maxtc
- last_written_ts
);
128 last_written_ts
= maxtc
;
129 jmd
->write(dummypacket
, sizeof(dummypacket
));
131 throw std::runtime_error("Can't write JMD ending dummy packet");
136 void on_gameinfo(const struct gameinfo_struct
& gi
)
139 for(size_t i
= 0; i
< gi
.get_author_count(); i
++) {
141 authstr
= authstr
+ ", ";
142 authstr
= authstr
+ gi
.get_author_short(i
);
147 bool get_dumper_flag() throw()
152 uint64_t get_next_video_ts(uint32_t fps_n
, uint32_t fps_d
)
154 uint64_t ret
= video_w
;
155 video_w
+= (1000000000ULL * fps_d
) / fps_n
;
156 video_n
+= (1000000000ULL * fps_d
) % fps_n
;
157 if(video_n
>= fps_n
) {
161 maxtc
= (ret
> maxtc
) ? ret
: maxtc
;
165 uint64_t get_next_audio_ts()
167 uint64_t ret
= audio_w
;
168 audio_w
+= (1000000000ULL * soundrate
.second
) / soundrate
.first
;
169 audio_n
+= (1000000000ULL * soundrate
.second
) % soundrate
.first
;
170 if(audio_n
>= soundrate
.first
) {
171 audio_n
-= soundrate
.first
;
174 maxtc
= (ret
> maxtc
) ? ret
: maxtc
;
180 bool have_dumped_frame
;
186 std::pair
<uint32_t, uint32_t> soundrate
;
190 std::vector
<char> data
;
199 std::deque
<frame_buffer
> frames
;
200 std::deque
<sample_buffer
> samples
;
202 std::vector
<char> compress_frame(uint32_t* memory
, uint32_t width
, uint32_t height
)
204 std::vector
<char> ret
;
206 memset(&stream
, 0, sizeof(stream
));
207 if(deflateInit(&stream
, complevel
) != Z_OK
)
208 throw std::runtime_error("Can't initialize zlib stream");
212 write16ube(&ret
[0], width
);
213 write16ube(&ret
[2], height
);
214 uint8_t input_buffer
[4 * INBUF_PIXELS
];
216 size_t pixels
= static_cast<size_t>(width
) * height
;
217 bool input_clear
= true;
218 bool flushed
= false;
223 for(unsigned i
= 0; i
< INBUF_PIXELS
&& pixel
< pixels
; i
++, pixel
++)
224 write32ule(input_buffer
+ (4 * i
), memory
[pixel
]);
228 //Now the input data to compress is in input_buffer, bsize elements.
229 stream
.next_in
= reinterpret_cast<uint8_t*>(input_buffer
);
230 stream
.avail_in
= 4 * bsize
;
232 if(!stream
.avail_out
) {
234 usize
+= (OUTBUF_ADVANCE
- stream
.avail_out
);
236 ret
.resize(usize
+ OUTBUF_ADVANCE
);
237 stream
.next_out
= reinterpret_cast<uint8_t*>(&ret
[usize
]);
238 stream
.avail_out
= OUTBUF_ADVANCE
;
240 int r
= deflate(&stream
, (ptr
== pixels
) ? Z_FINISH
: 0);
241 if(r
== Z_STREAM_END
)
244 throw std::runtime_error("Can't deflate data");
248 usize
+= (OUTBUF_ADVANCE
- stream
.avail_out
);
255 void flush_buffers(bool force
)
257 while(!frames
.empty() || !samples
.empty()) {
258 if(frames
.empty() || samples
.empty()) {
261 else if(!frames
.empty()) {
262 frame_buffer
& f
= frames
.front();
265 } else if(!samples
.empty()) {
266 sample_buffer
& s
= samples
.front();
272 frame_buffer
& f
= frames
.front();
273 sample_buffer
& s
= samples
.front();
284 void flush_frame(frame_buffer
& f
)
286 //Channel 0, minor 1.
287 char videopacketh
[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
288 write32ube(videopacketh
+ 2, f
.ts
- last_written_ts
);
289 last_written_ts
= f
.ts
;
291 uint64_t datasize
= f
.data
.size(); //Possibly upcast to avoid warnings.
292 for(unsigned shift
= 63; shift
> 0; shift
-= 7)
293 if(datasize
>= (1ULL << shift
))
294 videopacketh
[7 + lneed
++] = 0x80 | ((datasize
>> shift
) & 0x7F);
295 videopacketh
[7 + lneed
++] = (datasize
& 0x7F);
297 jmd
->write(videopacketh
, 7 + lneed
);
299 throw std::runtime_error("Can't write JMD video packet header");
301 jmd
->write(&f
.data
[0], datasize
);
303 throw std::runtime_error("Can't write JMD video packet body");
306 void flush_sample(sample_buffer
& s
)
308 //Channel 1, minor 1, payload 4.
309 char soundpacket
[12] = {0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04};
310 write32ube(soundpacket
+ 2, s
.ts
- last_written_ts
);
311 last_written_ts
= s
.ts
;
312 write16sbe(soundpacket
+ 8, s
.l
);
313 write16sbe(soundpacket
+ 10, s
.r
);
314 jmd
->write(soundpacket
, sizeof(soundpacket
));
316 throw std::runtime_error("Can't write JMD sound packet");
320 void (*deleter
)(void* f
);
321 uint64_t last_written_ts
;
325 jmd_avsnoop
* vid_dumper
;
327 class adv_jmd_dumper
: public adv_dumper
330 adv_jmd_dumper() : adv_dumper("INTERNAL-JMD") {information_dispatch::do_dumper_update(); }
331 ~adv_jmd_dumper() throw();
332 std::set
<std::string
> list_submodes() throw(std::bad_alloc
)
334 std::set
<std::string
> x
;
340 unsigned mode_details(const std::string
& mode
) throw()
342 return (mode
== "tcp") ? target_type_special
: target_type_file
;
345 std::string
name() throw(std::bad_alloc
)
350 std::string
modename(const std::string
& mode
) throw(std::bad_alloc
)
352 return (mode
== "tcp") ? "over TCP/IP" : "to file";
357 return (vid_dumper
!= NULL
);
360 void start(const std::string
& mode
, const std::string
& prefix
) throw(std::bad_alloc
,
364 throw std::runtime_error("Expected target");
366 throw std::runtime_error("JMD dumping already in progress");
368 vid_dumper
= new jmd_avsnoop(prefix
, mode
== "tcp");
369 } catch(std::bad_alloc
& e
) {
371 } catch(std::exception
& e
) {
372 std::ostringstream x
;
373 x
<< "Error starting JMD dump: " << e
.what();
374 throw std::runtime_error(x
.str());
376 messages
<< "Dumping to " << prefix
<< " at level " << clevel
<< std::endl
;
377 information_dispatch::do_dumper_update();
383 throw std::runtime_error("No JMD video dump in progress");
385 vid_dumper
->on_dump_end();
386 messages
<< "JMD Dump finished" << std::endl
;
387 } catch(std::bad_alloc
& e
) {
389 } catch(std::exception
& e
) {
390 messages
<< "Error ending JMD dump: " << e
.what() << std::endl
;
394 information_dispatch::do_dumper_update();
398 adv_jmd_dumper::~adv_jmd_dumper() throw()