Also support dumping JMD and SDMP over TCP/IP

This commit is contained in:
Ilari Liusvaara 2012-04-06 15:42:01 +03:00
parent 1974362569
commit d6d749581f
5 changed files with 144 additions and 68 deletions

View file

@ -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);

View file

@ -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) {

View file

@ -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;

View file

@ -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();
}

View file

@ -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