Refactor TCP dumping

This is in preparation for having JMD dumper also support dump over
TCP/IP.

Also fix some bugs in dumping (especially over TCP/IP)
This commit is contained in:
Ilari Liusvaara 2012-04-06 13:35:12 +03:00
parent 33187dedde
commit b00ccb2731
5 changed files with 286 additions and 218 deletions

View file

@ -69,6 +69,8 @@ struct avi_video_codec
* Parameter fps_n: fps numerator.
* Parameter fps_d: fps denominator.
* Returns: Stream format.
*
* Note: The next frame emitted MUST be a keyframe.
*/
virtual format reset(uint32_t width, uint32_t height, uint32_t fps_n, uint32_t fps_d) = 0;
/**

23
include/video/tcp.hpp Normal file
View file

@ -0,0 +1,23 @@
#ifndef _tcp__hpp__included__
#define _tcp__hpp__included__
#include <iostream>
#include <string>
#include <vector>
class socket_address
{
public:
socket_address(const std::string& spec);
socket_address next();
std::ostream& connect();
static bool supported();
private:
socket_address(int f, int st, int p);
int family;
int socktype;
int protocol;
std::vector<char> memory;
};
#endif

View file

@ -96,10 +96,19 @@ void dumper_menu::on_select(wxCommandEvent& e)
if(id < wxid_range_low || id > wxid_range_high)
return;
for(auto i : menustructure) {
std::string error_str;
adv_dumper* t = existing_dumpers[i.first].instance;
if(i.second.end_wxid == id) {
//Execute end of dump operation.
runemufn([t]() { t->end(); });
runemufn([t, &error_str]() {
try {
t->end();
} catch(std::exception& e) {
error_str = e.what();
}});
if(error_str != "")
wxMessageBox(towxstring(error_str), _T("Error ending dump"), wxICON_EXCLAMATION | wxOK,
pwin);
return;
}
if(i.second.start_wxids.count(id)) {
@ -110,11 +119,19 @@ void dumper_menu::on_select(wxCommandEvent& e)
wxFileDialog* d = new wxFileDialog(pwin, towxstring(prefixed ? std::string("Choose prefix") :
std::string("Choose file")), wxT("."));
if(d->ShowModal() == wxID_OK)
prefix = tostdstring(d->GetPath());
prefix = tostdstring(d->GetFilename());
d->Destroy();
if(prefix == "")
return;
runemufn([t, mode, prefix]() { t->start(mode, prefix); });
runemufn([t, mode, prefix, &error_str]() {
try {
t->start(mode, prefix);
} catch(std::exception& e) {
error_str = e.what();
}});
if(error_str != "")
wxMessageBox(towxstring(error_str), _T("Error starting dump"), wxICON_EXCLAMATION |
wxOK, pwin);
return;
}
}

View file

@ -1,5 +1,6 @@
#include "core/advdumper.hpp"
#include "core/dispatch.hpp"
#include "video/tcp.hpp"
#include "library/serialization.hpp"
#include <iomanip>
@ -10,29 +11,6 @@
#include <sstream>
#include <fstream>
#include <zlib.h>
#ifndef NO_TCP_SOCKETS
#include <boost/iostreams/categories.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/iostreams/filter/symmetric.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <unistd.h>
#if defined(_WIN32) || defined(_WIN64)
//Why the fuck does windows have nonstandard socket API???
#define _WIN32_WINNT 0x0501
#include <winsock2.h>
#include <ws2tcpip.h>
struct sockaddr_un { int sun_family; char sun_path[108]; };
#else
#include <sys/socket.h>
#include <netdb.h>
#include <sys/un.h>
#endif
#include <sys/types.h>
#endif
#define IS_RGB(m) (((m) + ((m) >> 3)) & 2)
#define IS_64(m) (m % 5 < 2)
@ -42,194 +20,6 @@ struct sockaddr_un { int sun_family; char sun_path[108]; };
namespace
{
#ifndef NO_TCP_SOCKETS
//Increment port number by 1.
void mung_sockaddr(struct sockaddr* addr, socklen_t addrlen)
{
switch(addr->sa_family) {
case AF_INET: { //IPv4
struct sockaddr_in* _addr = (struct sockaddr_in*)addr;
_addr->sin_port = htons(htons(_addr->sin_port) + 1);
break;
}
case AF_INET6: { //IPv6
struct sockaddr_in6* _addr = (struct sockaddr_in6*)addr;
_addr->sin6_port = htons(htons(_addr->sin6_port) + 1);
break;
}
case AF_UNIX: { //Unix domain sockets.
struct sockaddr_un* _addr = (struct sockaddr_un*)addr;
const char* b1 = (char*)_addr;
const char* b2 = (char*)&_addr->sun_path;
size_t maxpath = addrlen - (b2 - b1);
for(size_t i = 0; i < maxpath; i++)
if(i && !_addr->sun_path[i]) {
maxpath = i;
break;
}
if(!maxpath)
throw std::runtime_error("Eh, empty unix domain socket path?");
_addr->sun_path[maxpath - 1]++;
break;
}
default:
throw std::runtime_error("This address family is not supported, sorry.");
}
}
int compat_connect(int fd, struct sockaddr* addr, socklen_t addrlen)
{
#if defined(_WIN32) || defined(_WIN64)
return connect(fd, addr, addrlen) ? -1 : 0;
#else
return connect(fd, addr, addrlen);
#endif
}
std::pair<int, int> establish_connections(struct addrinfo* i)
{
struct sockaddr* addr = i->ai_addr;
socklen_t addrlen = i->ai_addrlen;
int a = socket(i->ai_family, i->ai_socktype, i->ai_protocol);
if(a < 0) {
int err = errno;
throw std::runtime_error(std::string("socket: ") + strerror(err));
}
int b = socket(i->ai_family, i->ai_socktype, i->ai_protocol);
if(b < 0) {
int err = errno;
close(a);
throw std::runtime_error(std::string("socket: ") + strerror(err));
}
if(compat_connect(a, addr, addrlen) < 0) {
int err = errno;
close(a);
close(b);
throw std::runtime_error(std::string("connect (video): ") + strerror(err));
}
mung_sockaddr(addr, addrlen);
if(compat_connect(b, addr, addrlen) < 0) {
int err = errno;
close(a);
close(b);
throw std::runtime_error(std::string("connect (audio): ") + strerror(err));
}
std::cerr << "Routing video to socket " << a << std::endl;
std::cerr << "Routing audio to socket " << b << std::endl;
return std::make_pair(a, b);
}
std::pair<int, int> get_sockets(const std::string& name)
{
struct addrinfo hints;
struct addrinfo* ainfo;
bool real = false;
int r;
std::string node, service, tmp = name;
size_t s;
struct sockaddr_un uaddr;
if(name[0] == '/' || name[0] == '@') {
//Fake a unix-domain.
if(name.length() >= sizeof(sockaddr_un) - offsetof(sockaddr_un, sun_path) - 1)
throw std::runtime_error("Path too long for filesystem socket");
size_t namelen = offsetof(struct sockaddr_un, sun_path) + name.length();
uaddr.sun_family = AF_UNIX;
strcpy(uaddr.sun_path, name.c_str());
if(name[0] == '@')
uaddr.sun_path[0] = 0; //Mark as abstract namespace socket.
ainfo = &hints;
ainfo->ai_flags = 0;
ainfo->ai_family = AF_UNIX;
ainfo->ai_socktype = SOCK_STREAM;
ainfo->ai_protocol = 0;
ainfo->ai_addrlen = (name[0] == '@') ? namelen : sizeof(sockaddr_un),
ainfo->ai_addr = reinterpret_cast<sockaddr*>(&uaddr);
ainfo->ai_canonname = NULL;
ainfo->ai_next = NULL;
goto establish;
}
//Split into address and port.
s = tmp.find_last_of(":");
if(s >= tmp.length())
throw std::runtime_error("Port number has to be specified");
node = tmp.substr(0, s);
service = tmp.substr(s + 1);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
#ifdef AI_V4MAPPED
hints.ai_flags = AI_V4MAPPED;
#endif
#ifdef AI_ADDRCONFIG
hints.ai_flags = AI_ADDRCONFIG;
#endif
real = true;
r = getaddrinfo(node.c_str(), service.c_str(), &hints, &ainfo);
if(r < 0)
throw std::runtime_error(std::string("getaddrinfo: ") + gai_strerror(r));
establish:
auto x = establish_connections(ainfo);
if(real)
freeaddrinfo(ainfo);
return x;
}
class socket_output
{
public:
typedef char char_type;
typedef boost::iostreams::sink_tag category;
socket_output(int _fd)
: fd(_fd)
{
}
void close()
{
::close(fd);
}
std::streamsize write(const char* s, std::streamsize n)
{
size_t w = n;
while(n > 0) {
ssize_t r = ::send(fd, s, n, 0);
if(r >= 0) {
s += r;
n -= r;
} else { //Error.
int err = errno;
messages << "Socket write error: " << strerror(err) << std::endl;
break;
}
}
return w;
}
protected:
int fd;
};
bool tcp_dump_supported = true;
#else
std::pair<int, int> get_sockets(const std::string& name)
{
throw std::runtime_error("Dumping over TCP/IP not supported");
}
class socket_output
{
public:
typedef char char_type;
typedef boost::iostreams::sink_tag category;
socket_output(int _fd) {}
void close() {}
std::streamsize write(const char* s, std::streamsize n) { return n; }
};
bool tcp_dump_supported = false;
#endif
unsigned strhash(const std::string& str)
{
unsigned h = 0;
@ -246,9 +36,17 @@ establish:
{
enable_send_sound();
if(socket_mode) {
std::pair<int, int> socks = get_sockets(prefix);
video = new boost::iostreams::stream<socket_output>(socks.first);
audio = new boost::iostreams::stream<socket_output>(socks.second);
socket_address videoaddr = socket_address(prefix);
socket_address audioaddr = videoaddr.next();
video = audio = NULL;
try {
video = &videoaddr.connect();
audio = &audioaddr.connect();
} catch(...) {
delete video;
delete 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);
@ -336,7 +134,7 @@ establish:
std::set<std::string> list_submodes() throw(std::bad_alloc)
{
std::set<std::string> x;
for(size_t i = 0; i < (tcp_dump_supported ? 2 : 1); i++)
for(size_t i = 0; i < (socket_address::supported() ? 2 : 1); i++)
for(size_t j = 0; j < 2; j++)
for(size_t k = 0; k < 2; k++)
x.insert(std::string("") + (i ? "tcp" : "") + (j ? "bgr" : "rgb")

228
src/video/tcp.cpp Normal file
View file

@ -0,0 +1,228 @@
#include "video/tcp.hpp"
#ifdef NO_TCP_SOCKETS
socket_address::socket_address(const std::string& spec)
{
throw std::runtime_error("TCP/IP support not compiled in");
}
socket_address socket_address::next()
{
}
std::ostream& socket_address::connect()
{
throw std::runtime_error("TCP/IP support not compiled in");
}
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>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/iostreams/filter/symmetric.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <unistd.h>
#if defined(_WIN32) || defined(_WIN64)
//Why the fuck does windows have nonstandard socket API???
#define _WIN32_WINNT 0x0501
#include <winsock2.h>
#include <ws2tcpip.h>
struct sockaddr_un { int sun_family; char sun_path[108]; };
#else
#include <sys/socket.h>
#include <netdb.h>
#include <sys/un.h>
#endif
#include <sys/types.h>
namespace
{
class socket_output
{
public:
typedef char char_type;
typedef struct : public boost::iostreams::sink_tag, boost::iostreams::closable_tag {} category;
socket_output(int _fd)
: fd(_fd)
{
}
void close()
{
::close(fd);
}
std::streamsize write(const char* s, std::streamsize n)
{
size_t w = n;
while(n > 0) {
ssize_t r = ::send(fd, s, n, 0);
if(r >= 0) {
s += r;
n -= r;
} else { //Error.
int err = errno;
std::cerr << "Socket write error: " << strerror(err) << std::endl;
break;
}
}
return w;
}
protected:
int fd;
};
}
socket_address::socket_address(const std::string& name)
{
struct addrinfo hints;
struct addrinfo* ainfo;
bool real = false;
int r;
std::string node, service, tmp = name;
size_t s;
struct sockaddr_un uaddr;
if(name[0] == '/' || name[0] == '@') {
//Fake a unix-domain.
if(name.length() >= sizeof(sockaddr_un) - offsetof(sockaddr_un, sun_path) - 1)
throw std::runtime_error("Path too long for filesystem socket");
size_t namelen = offsetof(struct sockaddr_un, sun_path) + name.length();
uaddr.sun_family = AF_UNIX;
strcpy(uaddr.sun_path, name.c_str());
if(name[0] == '@')
uaddr.sun_path[0] = 0; //Mark as abstract namespace socket.
family = AF_UNIX;
socktype = SOCK_STREAM;
protocol = 0;
memory.resize((name[0] == '@') ? namelen : sizeof(sockaddr_un));
memcpy(&memory[0], &uaddr, memory.size());
return;
}
//Split into address and port.
s = tmp.find_last_of(":");
if(s >= tmp.length())
throw std::runtime_error("Port number has to be specified");
node = tmp.substr(0, s);
service = tmp.substr(s + 1);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
#ifdef AI_V4MAPPED
hints.ai_flags = AI_V4MAPPED;
#endif
#ifdef AI_ADDRCONFIG
hints.ai_flags = AI_ADDRCONFIG;
#endif
real = true;
r = getaddrinfo(node.c_str(), service.c_str(), &hints, &ainfo);
if(r < 0)
throw std::runtime_error(std::string("getaddrinfo: ") + gai_strerror(r));
establish:
family = ainfo->ai_family;
socktype = ainfo->ai_socktype;
protocol = ainfo->ai_protocol;
try {
memory.resize(ainfo->ai_addrlen);
memcpy(&memory[0], ainfo->ai_addr, ainfo->ai_addrlen);
} catch(...) {
freeaddrinfo(ainfo);
throw;
}
freeaddrinfo(ainfo);
}
socket_address socket_address::next()
{
std::vector<char> newaddr = memory;
struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&newaddr[0]);
socklen_t addrlen = memory.size();
switch(addr->sa_family) {
case AF_INET: { //IPv4
struct sockaddr_in* _addr = (struct sockaddr_in*)addr;
_addr->sin_port = htons(htons(_addr->sin_port) + 1);
break;
}
case AF_INET6: { //IPv6
struct sockaddr_in6* _addr = (struct sockaddr_in6*)addr;
_addr->sin6_port = htons(htons(_addr->sin6_port) + 1);
break;
}
case AF_UNIX: { //Unix domain sockets.
struct sockaddr_un* _addr = (struct sockaddr_un*)addr;
const char* b1 = (char*)_addr;
const char* b2 = (char*)&_addr->sun_path;
size_t maxpath = addrlen - (b2 - b1);
for(size_t i = 0; i < maxpath; i++)
if(i && !_addr->sun_path[i]) {
maxpath = i;
break;
}
if(!maxpath)
throw std::runtime_error("Eh, empty unix domain socket path?");
_addr->sun_path[maxpath - 1]++;
break;
}
default:
throw std::runtime_error("This address family is not supported, sorry.");
}
socket_address n(family, socktype, protocol);
n.memory = newaddr;
return n;
}
std::ostream& socket_address::connect()
{
int a = socket(family, socktype, protocol);
if(a < 0) {
int err = errno;
throw std::runtime_error(std::string("socket: ") + strerror(err));
}
int r;
struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&memory[0]);
socklen_t addrlen = memory.size();
#if defined(_WIN32) || defined(_WIN64)
r = ::connect(a, addr, addrlen) ? -1 : 0;
#else
r = ::connect(a, addr, addrlen);
#endif
if(r < 0) {
int err = errno;
::close(a);
throw std::runtime_error(std::string("connect: ") + strerror(err));
}
try {
return *new boost::iostreams::stream<socket_output>(a);
} catch(...) {
::close(a);
throw;
}
}
bool socket_address::supported()
{
return true;
}
socket_address::socket_address(int f, int st, int p)
{
family = f;
socktype = st;
protocol = p;
}
#endif