Also support dumping JMD and SDMP over TCP/IP
This commit is contained in:
parent
1974362569
commit
d6d749581f
5 changed files with 144 additions and 68 deletions
|
@ -5,12 +5,15 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
typedef void (*deleter_fn_t)(void*);
|
||||
|
||||
class socket_address
|
||||
{
|
||||
public:
|
||||
socket_address(const std::string& spec);
|
||||
socket_address next();
|
||||
std::ostream& connect();
|
||||
static deleter_fn_t deleter();
|
||||
static bool supported();
|
||||
private:
|
||||
socket_address(int f, int st, int p);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "core/dispatch.hpp"
|
||||
#include "core/settings.hpp"
|
||||
#include "library/serialization.hpp"
|
||||
#include "video/tcp.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
#include <cassert>
|
||||
|
@ -18,16 +19,27 @@ namespace
|
|||
{
|
||||
numeric_setting clevel("jmd-compression", 0, 9, 7);
|
||||
|
||||
void deleter_fn(void* f)
|
||||
{
|
||||
delete reinterpret_cast<std::ofstream*>(f);
|
||||
}
|
||||
|
||||
class jmd_avsnoop : public information_dispatch
|
||||
{
|
||||
public:
|
||||
jmd_avsnoop(const std::string& filename) throw(std::bad_alloc)
|
||||
jmd_avsnoop(const std::string& filename, bool tcp_flag) throw(std::bad_alloc)
|
||||
: information_dispatch("dump-jmd")
|
||||
{
|
||||
enable_send_sound();
|
||||
complevel = clevel;
|
||||
jmd.open(filename.c_str(), std::ios::out | std::ios::binary);
|
||||
if(!jmd)
|
||||
if(tcp_flag) {
|
||||
jmd = &(socket_address(filename).connect());
|
||||
deleter = socket_address::deleter();
|
||||
} else {
|
||||
jmd = new std::ofstream(filename.c_str(), std::ios::out | std::ios::binary);
|
||||
deleter = deleter_fn;
|
||||
}
|
||||
if(!*jmd)
|
||||
throw std::runtime_error("Can't open output JMD file.");
|
||||
last_written_ts = 0;
|
||||
//Write the segment tables.
|
||||
|
@ -50,8 +62,8 @@ namespace
|
|||
/* Dummy channel header. */
|
||||
0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 'd', 'u'
|
||||
};
|
||||
jmd.write(header, sizeof(header));
|
||||
if(!jmd)
|
||||
jmd->write(header, sizeof(header));
|
||||
if(!*jmd)
|
||||
throw std::runtime_error("Can't write JMD header and segment table");
|
||||
have_dumped_frame = false;
|
||||
audio_w = 0;
|
||||
|
@ -103,18 +115,22 @@ namespace
|
|||
|
||||
void on_dump_end()
|
||||
{
|
||||
if(!jmd)
|
||||
return;
|
||||
flush_buffers(true);
|
||||
if(last_written_ts > maxtc) {
|
||||
jmd.close();
|
||||
deleter(jmd);
|
||||
jmd = NULL;
|
||||
return;
|
||||
}
|
||||
char dummypacket[8] = {0x00, 0x03};
|
||||
write32ube(dummypacket + 2, maxtc - last_written_ts);
|
||||
last_written_ts = maxtc;
|
||||
jmd.write(dummypacket, sizeof(dummypacket));
|
||||
if(!jmd)
|
||||
jmd->write(dummypacket, sizeof(dummypacket));
|
||||
if(!*jmd)
|
||||
throw std::runtime_error("Can't write JMD ending dummy packet");
|
||||
jmd.close();
|
||||
deleter(jmd);
|
||||
jmd = NULL;
|
||||
}
|
||||
|
||||
void on_gameinfo(const struct gameinfo_struct& gi)
|
||||
|
@ -278,12 +294,12 @@ namespace
|
|||
videopacketh[7 + lneed++] = 0x80 | ((datasize >> shift) & 0x7F);
|
||||
videopacketh[7 + lneed++] = (datasize & 0x7F);
|
||||
|
||||
jmd.write(videopacketh, 7 + lneed);
|
||||
if(!jmd)
|
||||
jmd->write(videopacketh, 7 + lneed);
|
||||
if(!*jmd)
|
||||
throw std::runtime_error("Can't write JMD video packet header");
|
||||
if(datasize > 0)
|
||||
jmd.write(&f.data[0], datasize);
|
||||
if(!jmd)
|
||||
jmd->write(&f.data[0], datasize);
|
||||
if(!*jmd)
|
||||
throw std::runtime_error("Can't write JMD video packet body");
|
||||
}
|
||||
|
||||
|
@ -295,12 +311,13 @@ namespace
|
|||
last_written_ts = s.ts;
|
||||
write16sbe(soundpacket + 8, s.l);
|
||||
write16sbe(soundpacket + 10, s.r);
|
||||
jmd.write(soundpacket, sizeof(soundpacket));
|
||||
if(!jmd)
|
||||
jmd->write(soundpacket, sizeof(soundpacket));
|
||||
if(!*jmd)
|
||||
throw std::runtime_error("Can't write JMD sound packet");
|
||||
}
|
||||
|
||||
std::ofstream jmd;
|
||||
std::ostream* jmd;
|
||||
void (*deleter)(void* f);
|
||||
uint64_t last_written_ts;
|
||||
unsigned complevel;
|
||||
};
|
||||
|
@ -315,12 +332,14 @@ namespace
|
|||
std::set<std::string> list_submodes() throw(std::bad_alloc)
|
||||
{
|
||||
std::set<std::string> x;
|
||||
x.insert("file");
|
||||
x.insert("tcp");
|
||||
return x;
|
||||
}
|
||||
|
||||
unsigned mode_details(const std::string& mode) throw()
|
||||
{
|
||||
return target_type_file;
|
||||
return (mode == "tcp") ? target_type_special : target_type_file;
|
||||
}
|
||||
|
||||
std::string name() throw(std::bad_alloc)
|
||||
|
@ -330,7 +349,7 @@ namespace
|
|||
|
||||
std::string modename(const std::string& mode) throw(std::bad_alloc)
|
||||
{
|
||||
return "";
|
||||
return (mode == "tcp") ? "over TCP/IP" : "to file";
|
||||
}
|
||||
|
||||
bool busy()
|
||||
|
@ -342,11 +361,11 @@ namespace
|
|||
std::runtime_error)
|
||||
{
|
||||
if(prefix == "")
|
||||
throw std::runtime_error("Expected filename");
|
||||
throw std::runtime_error("Expected target");
|
||||
if(vid_dumper)
|
||||
throw std::runtime_error("JMD dumping already in progress");
|
||||
try {
|
||||
vid_dumper = new jmd_avsnoop(prefix);
|
||||
vid_dumper = new jmd_avsnoop(prefix, mode == "tcp");
|
||||
} catch(std::bad_alloc& e) {
|
||||
throw;
|
||||
} catch(std::exception& e) {
|
||||
|
|
|
@ -28,6 +28,11 @@ namespace
|
|||
return h;
|
||||
}
|
||||
|
||||
void deleter_fn(void* f)
|
||||
{
|
||||
delete reinterpret_cast<std::ofstream*>(f);
|
||||
}
|
||||
|
||||
class raw_avsnoop : public information_dispatch
|
||||
{
|
||||
public:
|
||||
|
@ -38,18 +43,20 @@ namespace
|
|||
if(socket_mode) {
|
||||
socket_address videoaddr = socket_address(prefix);
|
||||
socket_address audioaddr = videoaddr.next();
|
||||
deleter = socket_address::deleter();
|
||||
video = audio = NULL;
|
||||
try {
|
||||
video = &videoaddr.connect();
|
||||
audio = &audioaddr.connect();
|
||||
} catch(...) {
|
||||
delete video;
|
||||
delete audio;
|
||||
deleter(video);
|
||||
deleter(audio);
|
||||
throw;
|
||||
}
|
||||
} else {
|
||||
video = new std::ofstream(prefix + ".video", std::ios::out | std::ios::binary);
|
||||
audio = new std::ofstream(prefix + ".audio", std::ios::out | std::ios::binary);
|
||||
deleter = deleter_fn;
|
||||
}
|
||||
if(!*video || !*audio)
|
||||
throw std::runtime_error("Can't open output files");
|
||||
|
@ -60,8 +67,10 @@ namespace
|
|||
|
||||
~raw_avsnoop() throw()
|
||||
{
|
||||
delete video;
|
||||
delete audio;
|
||||
if(video)
|
||||
deleter(video);
|
||||
if(audio)
|
||||
deleter(audio);
|
||||
}
|
||||
|
||||
void on_frame(struct lcscreen& _frame, uint32_t fps_n, uint32_t fps_d)
|
||||
|
@ -104,8 +113,8 @@ namespace
|
|||
|
||||
void on_dump_end()
|
||||
{
|
||||
delete video;
|
||||
delete audio;
|
||||
deleter(video);
|
||||
deleter(audio);
|
||||
video = NULL;
|
||||
audio = NULL;
|
||||
}
|
||||
|
@ -117,6 +126,7 @@ namespace
|
|||
private:
|
||||
std::ostream* audio;
|
||||
std::ostream* video;
|
||||
void (*deleter)(void* f);
|
||||
bool have_dumped_frame;
|
||||
struct screen<false> dscr;
|
||||
struct screen<true> dscr2;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "core/advdumper.hpp"
|
||||
#include "core/dispatch.hpp"
|
||||
#include "library/serialization.hpp"
|
||||
#include "video/tcp.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
#include <cassert>
|
||||
|
@ -21,25 +22,38 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
void deleter_fn(void* f)
|
||||
{
|
||||
delete reinterpret_cast<std::ofstream*>(f);
|
||||
}
|
||||
|
||||
class sdmp_avsnoop : public information_dispatch
|
||||
{
|
||||
public:
|
||||
sdmp_avsnoop(const std::string& prefix, bool ssflag) throw(std::bad_alloc)
|
||||
sdmp_avsnoop(const std::string& prefix, const std::string& mode) throw(std::bad_alloc,
|
||||
std::runtime_error)
|
||||
: information_dispatch("dump-sdmp")
|
||||
{
|
||||
enable_send_sound();
|
||||
oprefix = prefix;
|
||||
sdump_ss = ssflag;
|
||||
sdump_ss = (mode != "ms");
|
||||
ssize = 0;
|
||||
next_seq = 0;
|
||||
sdump_iopen = false;
|
||||
dumped_pic = false;
|
||||
if(mode == "tcp") {
|
||||
out = &(socket_address(prefix).connect());
|
||||
deleter = socket_address::deleter();
|
||||
} else {
|
||||
out = NULL;
|
||||
deleter = deleter_fn;
|
||||
}
|
||||
}
|
||||
|
||||
~sdmp_avsnoop() throw()
|
||||
{
|
||||
try {
|
||||
if(sdump_iopen)
|
||||
out.close();
|
||||
if(out)
|
||||
deleter(out);
|
||||
} catch(...) {
|
||||
}
|
||||
}
|
||||
|
@ -52,10 +66,10 @@ namespace
|
|||
flags |= (overscan ? SDUMP_FLAG_OVERSCAN : 0);
|
||||
flags |= (region == VIDEO_REGION_PAL ? SDUMP_FLAG_PAL : 0);
|
||||
unsigned char tbuffer[2049];
|
||||
if(!sdump_iopen || (ssize > CUTOFF && !sdump_ss)) {
|
||||
if(!out || (ssize > CUTOFF && !sdump_ss)) {
|
||||
std::cerr << "Starting new segment" << std::endl;
|
||||
if(sdump_iopen)
|
||||
out.close();
|
||||
if(out)
|
||||
deleter(out);
|
||||
std::ostringstream str;
|
||||
if(sdump_ss)
|
||||
str << oprefix;
|
||||
|
@ -63,47 +77,47 @@ namespace
|
|||
str << oprefix << "_" << std::setw(4) << std::setfill('0') << (next_seq++)
|
||||
<< ".sdmp";
|
||||
std::string str2 = str.str();
|
||||
out.open(str2.c_str(), std::ios::out | std::ios::binary);
|
||||
if(!out)
|
||||
out = new std::ofstream(str2.c_str(), std::ios::out | std::ios::binary);
|
||||
if(!*out)
|
||||
throw std::runtime_error("Failed to open '" + str2 + "'");
|
||||
sdump_iopen = true;
|
||||
write32ube(tbuffer, 0x53444D50U);
|
||||
write32ube(tbuffer + 4, SNES::system.cpu_frequency());
|
||||
write32ube(tbuffer + 8, SNES::system.apu_frequency());
|
||||
out.write(reinterpret_cast<char*>(tbuffer), 12);
|
||||
if(!out)
|
||||
out->write(reinterpret_cast<char*>(tbuffer), 12);
|
||||
if(!*out)
|
||||
throw std::runtime_error("Failed to write header to '" + str2 + "'");
|
||||
ssize = 12;
|
||||
}
|
||||
dumped_pic = true;
|
||||
tbuffer[0] = flags;
|
||||
for(unsigned i = 0; i < 512; i++) {
|
||||
for(unsigned j = 0; j < 512; j++)
|
||||
write32ube(tbuffer + (4 * j + 1), raw[512 * i + j]);
|
||||
out.write(reinterpret_cast<char*>(tbuffer + (i ? 1 : 0)), i ? 2048 : 2049);
|
||||
out->write(reinterpret_cast<char*>(tbuffer + (i ? 1 : 0)), i ? 2048 : 2049);
|
||||
}
|
||||
if(!out)
|
||||
if(!*out)
|
||||
throw std::runtime_error("Failed to write frame");
|
||||
ssize += 1048577;
|
||||
}
|
||||
|
||||
void on_sample(short l, short r)
|
||||
{
|
||||
if(!sdump_iopen)
|
||||
if(!out || !dumped_pic)
|
||||
return;
|
||||
unsigned char pkt[5];
|
||||
pkt[0] = 16;
|
||||
write16sbe(pkt + 1, l);
|
||||
write16sbe(pkt + 3, r);
|
||||
out.write(reinterpret_cast<char*>(pkt), 5);
|
||||
if(!out)
|
||||
out->write(reinterpret_cast<char*>(pkt), 5);
|
||||
if(!*out)
|
||||
throw std::runtime_error("Failed to write sample");
|
||||
ssize += 5;
|
||||
}
|
||||
|
||||
void on_dump_end()
|
||||
{
|
||||
if(sdump_iopen)
|
||||
out.close();
|
||||
deleter(out);
|
||||
out = NULL;
|
||||
}
|
||||
|
||||
bool get_dumper_flag() throw()
|
||||
|
@ -113,10 +127,11 @@ namespace
|
|||
private:
|
||||
std::string oprefix;
|
||||
bool sdump_ss;
|
||||
bool dumped_pic;
|
||||
uint64_t ssize;
|
||||
uint64_t next_seq;
|
||||
bool sdump_iopen;
|
||||
std::ofstream out;
|
||||
void (*deleter)(void* f);
|
||||
std::ostream* out;
|
||||
};
|
||||
|
||||
sdmp_avsnoop* vid_dumper;
|
||||
|
@ -131,12 +146,19 @@ namespace
|
|||
std::set<std::string> x;
|
||||
x.insert("ss");
|
||||
x.insert("ms");
|
||||
x.insert("tcp");
|
||||
return x;
|
||||
}
|
||||
|
||||
unsigned mode_details(const std::string& mode) throw()
|
||||
{
|
||||
return (mode != "ss") ? target_type_prefix : target_type_file;
|
||||
if(mode == "ss")
|
||||
return target_type_file;
|
||||
if(mode == "ms")
|
||||
return target_type_prefix;
|
||||
if(mode == "tcp")
|
||||
return target_type_special;
|
||||
return target_type_mask;
|
||||
}
|
||||
|
||||
std::string name() throw(std::bad_alloc)
|
||||
|
@ -146,7 +168,13 @@ namespace
|
|||
|
||||
std::string modename(const std::string& mode) throw(std::bad_alloc)
|
||||
{
|
||||
return (mode == "ss" ? "Single-Segment" : "Multi-Segment");
|
||||
if(mode == "ss")
|
||||
return "Single-Segment";
|
||||
if(mode == "ms")
|
||||
return "Multi-Segment";
|
||||
if(mode == "tcp")
|
||||
return "over TCP/IP";
|
||||
return "What?";
|
||||
}
|
||||
|
||||
bool busy()
|
||||
|
@ -157,16 +185,12 @@ namespace
|
|||
void start(const std::string& mode, const std::string& prefix) throw(std::bad_alloc,
|
||||
std::runtime_error)
|
||||
{
|
||||
if(prefix == "") {
|
||||
if(mode == "ss")
|
||||
throw std::runtime_error("Expected filename");
|
||||
else
|
||||
throw std::runtime_error("Expected prefix");
|
||||
}
|
||||
if(prefix == "")
|
||||
throw std::runtime_error("Expected target");
|
||||
if(vid_dumper)
|
||||
throw std::runtime_error("SDMP Dump already in progress");
|
||||
try {
|
||||
vid_dumper = new sdmp_avsnoop(prefix, mode == "ss");
|
||||
vid_dumper = new sdmp_avsnoop(prefix, mode);
|
||||
} catch(std::bad_alloc& e) {
|
||||
throw;
|
||||
} catch(std::exception& e) {
|
||||
|
@ -174,10 +198,7 @@ namespace
|
|||
x << "Error starting SDMP dump: " << e.what();
|
||||
throw std::runtime_error(x.str());
|
||||
}
|
||||
if(mode == "ss")
|
||||
messages << "Dumping SDMP (SS) to " << prefix << std::endl;
|
||||
else
|
||||
messages << "Dumping SDMP to " << prefix << std::endl;
|
||||
messages << "Dumping SDMP (" << mode << ") to " << prefix << std::endl;
|
||||
information_dispatch::do_dumper_update();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
#ifdef NO_TCP_SOCKETS
|
||||
|
||||
namespace
|
||||
{
|
||||
void deleter_fn(void* f)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
socket_address::socket_address(const std::string& spec)
|
||||
{
|
||||
throw std::runtime_error("TCP/IP support not compiled in");
|
||||
|
@ -21,11 +28,6 @@ bool socket_address::supported()
|
|||
return false;
|
||||
}
|
||||
|
||||
socket_address::socket_address(int f, int st, int p)
|
||||
{
|
||||
throw std::runtime_error("TCP/IP support not compiled in");
|
||||
}
|
||||
|
||||
#else
|
||||
#include <boost/iostreams/categories.hpp>
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
|
@ -59,6 +61,7 @@ namespace
|
|||
socket_output(int _fd)
|
||||
: fd(_fd)
|
||||
{
|
||||
broken = false;
|
||||
}
|
||||
|
||||
void close()
|
||||
|
@ -68,15 +71,23 @@ namespace
|
|||
|
||||
std::streamsize write(const char* s, std::streamsize n)
|
||||
{
|
||||
if(broken)
|
||||
return n;
|
||||
size_t w = n;
|
||||
while(n > 0) {
|
||||
ssize_t r = ::send(fd, s, n, 0);
|
||||
if(r >= 0) {
|
||||
s += r;
|
||||
n -= r;
|
||||
} else if(errno == EPIPE) {
|
||||
std::cerr << "The other end of socket went away" << std::endl;
|
||||
broken = true;
|
||||
n = 0;
|
||||
break;
|
||||
} else { //Error.
|
||||
int err = errno;
|
||||
std::cerr << "Socket write error: " << strerror(err) << std::endl;
|
||||
n = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +95,13 @@ namespace
|
|||
}
|
||||
protected:
|
||||
int fd;
|
||||
bool broken;
|
||||
};
|
||||
|
||||
void deleter_fn(void* f)
|
||||
{
|
||||
delete reinterpret_cast<boost::iostreams::stream<socket_output>*>(f);
|
||||
}
|
||||
}
|
||||
|
||||
socket_address::socket_address(const std::string& name)
|
||||
|
@ -218,11 +235,17 @@ bool socket_address::supported()
|
|||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
deleter_fn_t socket_address::deleter()
|
||||
{
|
||||
return deleter_fn;
|
||||
}
|
||||
|
||||
|
||||
socket_address::socket_address(int f, int st, int p)
|
||||
{
|
||||
family = f;
|
||||
socktype = st;
|
||||
protocol = p;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Reference in a new issue