Make video dumping to go through generic A/V snooping interface

This commit is contained in:
Ilari Liusvaara 2011-09-15 16:42:52 +03:00
parent b015451c38
commit 6877c2f63b
9 changed files with 420 additions and 188 deletions

View file

@ -1,13 +1,18 @@
FONT_SRC := unifontfull-5.1.20080820.hex
CC := g++-4.5
HOSTCC = $(CC)
OBJECTS = controllerdata.o fieldsplit.o memorymanip.o misc.o movie.o moviefile.o render.o rom.o zip.o fonts/font.o videodumper.o videodumper2.o keymapper.o window.o window-sdl.o settings.o framerate.o mainloop.o rrdata.o specialframes.o png.o lsnesrc.o memorywatch.o command.o
OBJECTS = controllerdata.o fieldsplit.o memorymanip.o misc.o movie.o moviefile.o render.o rom.o zip.o fonts/font.o keymapper.o window.o window-sdl.o settings.o framerate.o mainloop.o rrdata.o specialframes.o png.o lsnesrc.o memorywatch.o command.o avsnoop.o
#AVI dumper
OBJECTS += avidump/avidump-control.o avidump/avidump.o
PROGRAMS = lsnes.exe movietrunctest.exe
CFLAGS = $(shell sdl-config --cflags) $(USER_CFLAGS)
CFLAGS = -I. $(shell sdl-config --cflags) $(USER_CFLAGS)
HOSTCCFLAGS = $(USER_HOSTCCFLAGS)
LDFLAGS = $(shell sdl-config --libs) $(USER_LDFLAGS)
#Lua.
ifdef NO_LUA
OBJECTS += lua-dummy.o
else

View file

@ -1,28 +1,94 @@
#include "videodumper.hpp"
#include "lua.hpp"
#include "avidump.hpp"
#include "settings.hpp"
#include "videodumper2.hpp"
#include <iomanip>
#include <cassert>
#include <cstring>
#include <sstream>
#include <zlib.h>
#include "misc.hpp"
#include "fieldsplit.hpp"
#include "avsnoop.hpp"
#include "command.hpp"
avidumper* vid_dumper = NULL;
void update_movie_state();
namespace
{
screen dscr;
boolean_setting dump_large("large-video", false);
class avi_avsnoop : public av_snooper
{
public:
avi_avsnoop(const std::string& prefix, struct avi_info parameters) throw(std::bad_alloc)
{
vid_dumper = new avidumper(prefix, parameters);
}
~avi_avsnoop() throw()
{
delete vid_dumper;
}
void frame(struct lcscreen& _frame, uint32_t fps_n, uint32_t fps_d, window* win, bool dummy)
throw(std::bad_alloc, std::runtime_error)
{
vid_dumper->wait_idle();
uint32_t hscl = 1;
uint32_t vscl = 1;
if(dump_large && _frame.width < 400)
hscl = 2;
if(dump_large && _frame.height < 400)
vscl = 2;
uint32_t _magic = 403703808;
uint8_t* magic = reinterpret_cast<uint8_t*>(&_magic);
dscr.set_palette(magic[2], magic[1], magic[0]);
struct lua_render_context lrc;
render_queue rq;
lrc.left_gap = 0;
lrc.right_gap = 0;
lrc.bottom_gap = 0;
lrc.top_gap = 0;
lrc.queue = &rq;
lrc.width = _frame.width * hscl;
lrc.height = _frame.height * vscl;
lrc.rshift = magic[2];
lrc.gshift = magic[1];
lrc.bshift = magic[0];
lua_callback_do_video(&lrc, win);
dscr.reallocate(lrc.left_gap + hscl * _frame.width + lrc.right_gap, lrc.top_gap + vscl *
_frame.height + lrc.bottom_gap, lrc.left_gap, lrc.top_gap, true);
dscr.copy_from(_frame, hscl, vscl);
rq.run(dscr);
vid_dumper->on_frame(dscr.memory, dscr.width, dscr.height, fps_n, fps_d);
}
void sample(short l, short r) throw(std::bad_alloc, std::runtime_error)
{
vid_dumper->on_sample(l, r);
}
void end() throw(std::bad_alloc, std::runtime_error)
{
vid_dumper->on_end();
}
void gameinfo(const std::string& gamename, const std::list<std::pair<std::string, std::string>>&
authors, double gametime, const std::string& rerecords) throw(std::bad_alloc,
std::runtime_error)
{
//We don't have place for this info and thus ignore it.
}
private:
avidumper* vid_dumper;
screen dscr;
};
avi_avsnoop* vid_dumper;
class dump_video_command : public command
{
public:
dump_video_command() throw(std::bad_alloc) : command("dump-video") {}
dump_video_command() throw(std::bad_alloc) : command("dump-avi") {}
void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
{
tokensplitter t(args);
@ -31,7 +97,7 @@ namespace
if(prefix == "")
throw std::runtime_error("Expected prefix");
if(vid_dumper)
throw std::runtime_error("Video dumping already in progress");
throw std::runtime_error("AVI dumping already in progress");
unsigned long level2;
try {
level2 = parse_value<unsigned long>(level);
@ -40,7 +106,7 @@ namespace
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::runtime_error& e) {
throw std::runtime_error("Bad video compression level '" + level + "': " + e.what());
throw std::runtime_error("Bad AVI compression level '" + level + "': " + e.what());
}
struct avi_info parameters;
parameters.compression_level = (level2 > 9) ? (level2 - 9) : level2;
@ -50,7 +116,7 @@ namespace
parameters.audio_native_sampling_rate = 32040.5;
parameters.keyframe_interval = (level2 > 9) ? 300 : 1;
try {
vid_dumper = new avidumper(prefix, parameters);
vid_dumper = new avi_avsnoop(prefix, parameters);
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
@ -59,13 +125,12 @@ namespace
throw std::runtime_error(x.str());
}
out(win) << "Dumping to " << prefix << " at level " << level2 << std::endl;
update_movie_state();
}
std::string get_short_help() throw(std::bad_alloc) { return "Start video capture"; }
std::string get_short_help() throw(std::bad_alloc) { return "Start AVI capture"; }
std::string get_long_help() throw(std::bad_alloc)
{
return "Syntax: dump-video <level> <prefix>\n"
"Start video capture to <prefix> using compression\n"
return "Syntax: dump-avi <level> <prefix>\n"
"Start AVI capture to <prefix> using compression\n"
"level <level> (0-18).\n";
}
} dump_video;
@ -73,7 +138,7 @@ namespace
class end_video_command : public command
{
public:
end_video_command() throw(std::bad_alloc) : command("end-video") {}
end_video_command() throw(std::bad_alloc) : command("end-avi") {}
void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
{
if(args != "")
@ -81,7 +146,7 @@ namespace
if(!vid_dumper)
throw std::runtime_error("No video dump in progress");
try {
vid_dumper->on_end();
vid_dumper->end();
out(win) << "Dump finished" << std::endl;
} catch(std::bad_alloc& e) {
OOM_panic(win);
@ -90,89 +155,12 @@ namespace
}
delete vid_dumper;
vid_dumper = NULL;
update_movie_state();
}
std::string get_short_help() throw(std::bad_alloc) { return "End video capture"; }
std::string get_short_help() throw(std::bad_alloc) { return "End AVI capture"; }
std::string get_long_help() throw(std::bad_alloc)
{
return "Syntax: end-video\n"
"End a video capture.\n";
return "Syntax: end-avi\n"
"End a AVI capture.\n";
}
} end_vieo;
}
void end_vid_dump() throw(std::bad_alloc, std::runtime_error)
{
if(vid_dumper)
try {
vid_dumper->on_end();
} catch(std::bad_alloc& e) {
throw;
} catch(std::exception& e) {
std::cerr << "Error ending dump: " << e.what() << std::endl;
}
}
void dump_frame(lcscreen& ls, render_queue* rq, uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
bool region, window* win) throw(std::bad_alloc, std::runtime_error)
{
if(vid_dumper)
try {
vid_dumper->wait_idle();
uint32_t hscl = 1;
uint32_t vscl = 1;
if(dump_large && ls.width < 400)
hscl = 2;
if(dump_large && ls.height < 400)
vscl = 2;
uint32_t _magic = 403703808;
uint8_t* magic = reinterpret_cast<uint8_t*>(&_magic);
dscr.reallocate(left + hscl * ls.width + right, top + vscl * ls.height + bottom, left, top,
true);
dscr.set_palette(magic[2], magic[1], magic[0]);
dscr.copy_from(ls, hscl, vscl);
if(rq)
rq->run(dscr);
assert(dscr.memory);
assert(dscr.width);
assert(dscr.height);
uint32_t fps_n = 10738636;
uint32_t fps_d = 178683;
if(region) {
fps_n = 322445;
fps_d = 6448;
}
vid_dumper->on_frame(dscr.memory, dscr.width, dscr.height, fps_n, fps_d);
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
out(win) << "Error sending video frame: " << e.what() << std::endl;
}
if(rq)
rq->clear();
}
void dump_audio_sample(int16_t l_sample, int16_t r_sample, window* win) throw(std::bad_alloc, std::runtime_error)
{
if(vid_dumper)
try {
vid_dumper->on_sample(l_sample, r_sample);
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
out(win) << "Error sending audio sample: " << e.what() << std::endl;
}
}
bool dump_in_progress() throw()
{
return (vid_dumper != NULL);
}
void video_fill_shifts(uint32_t& r, uint32_t& g, uint32_t& b)
{
r = dscr.active_rshift;
g = dscr.active_gshift;
b = dscr.active_bshift;
}

View file

@ -1,4 +1,4 @@
#include "videodumper.hpp"
#include "avidump.hpp"
#include <iomanip>
#include <cassert>
#include <cstring>

View file

@ -1,5 +1,5 @@
#ifndef _videodumper__hpp__included__
#define _videodumper__hpp__included__
#ifndef _avidump__hpp__included__
#define _avidump__hpp__included__
#include <cstdint>
#include <iostream>

140
avsnoop.cpp Normal file
View file

@ -0,0 +1,140 @@
#include "avsnoop.hpp"
#include "misc.hpp"
namespace
{
std::list<av_snooper*>* snoopers;
std::list<av_snooper::dump_notification*> notifiers;
std::string s_gamename;
std::string s_rerecords;
double s_gametime;
std::list<std::pair<std::string, std::string>> s_authors;
bool gameinfo_set;
}
av_snooper::av_snooper() throw(std::bad_alloc)
{
if(!snoopers)
snoopers = new std::list<av_snooper*>();
snoopers->push_back(this);
for(auto i = notifiers.begin(); i != notifiers.end(); i++)
(*i)->dump_starting();
}
av_snooper::~av_snooper() throw()
{
if(!snoopers)
return;
for(auto i = snoopers->begin(); i != snoopers->end(); i++)
if(*i == this) {
snoopers->erase(i);
break;
}
for(auto i = notifiers.begin(); i != notifiers.end(); i++)
(*i)->dump_ending();
}
void av_snooper::frame(struct lcscreen& _frame, uint32_t fps_n, uint32_t fps_d, window* win) throw()
{
if(!snoopers)
return;
for(auto i = snoopers->begin(); i != snoopers->end(); i++)
try {
(*i)->frame(_frame, fps_n, fps_d, win, true);
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
try {
win->message(std::string("Error dumping frame: ") + e.what());
} catch(...) {
}
}
}
void av_snooper::sample(short l, short r, window* win) throw()
{
if(!snoopers)
return;
for(auto i = snoopers->begin(); i != snoopers->end(); i++)
try {
(*i)->sample(l, r);
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
try {
win->message(std::string("Error dumping sample: ") + e.what());
} catch(...) {
}
}
}
void av_snooper::end(window* win) throw()
{
if(!snoopers)
return;
for(auto i = snoopers->begin(); i != snoopers->end(); i++)
try {
(*i)->end();
} catch(std::bad_alloc& e) {
OOM_panic(win);
} catch(std::exception& e) {
try {
win->message(std::string("Error ending dump: ") + e.what());
} catch(...) {
}
}
}
void av_snooper::gameinfo(const std::string& gamename, const std::list<std::pair<std::string, std::string>>&
authors, double gametime, const std::string& rerecords, window* win) throw(std::bad_alloc)
{
if(!snoopers)
return;
s_gamename = gamename;
s_authors = authors;
s_gametime = gametime;
s_rerecords = rerecords;
gameinfo_set = true;
for(auto i = snoopers->begin(); i != snoopers->end(); i++)
(*i)->send_gameinfo();
}
void av_snooper::send_gameinfo() throw()
{
if(gameinfo_set)
try {
gameinfo(s_gamename, s_authors, s_gametime, s_rerecords);
} catch(...) {
}
}
bool av_snooper::dump_in_progress() throw()
{
return (snoopers && !snoopers->empty());
}
av_snooper::dump_notification::~dump_notification() throw()
{
}
void av_snooper::dump_notification::dump_starting() throw()
{
}
void av_snooper::dump_notification::dump_ending() throw()
{
}
void av_snooper::add_dump_notifier(av_snooper::dump_notification& notifier) throw(std::bad_alloc)
{
notifiers.push_back(&notifier);
}
void av_snooper::remove_dump_notifier(av_snooper::dump_notification& notifier) throw()
{
for(auto i = notifiers.begin(); i != notifiers.end(); i++)
if(*i == &notifier) {
notifiers.erase(i);
return;
}
}

157
avsnoop.hpp Normal file
View file

@ -0,0 +1,157 @@
#ifndef _avsnoop__hpp__included__
#define _avsnoop__hpp__included__
#include "render.hpp"
#include <list>
#include <string>
#include <stdexcept>
#include "window.hpp"
/**
* A/V snooper.
*/
class av_snooper
{
public:
/**
* Create new A/V snooper.
*
* throws std::bad_alloc: Not enough memory.
*/
av_snooper() throw(std::bad_alloc);
/**
* Destroy A/V snooper. This will not call end method.
*/
~av_snooper() throw();
/**
* Dump a frame.
*
* parameter _frame: The frame to dump.
* parameter fps_n: Current fps numerator.
* parameter fps_d: Current fps denomerator.
* throws std::bad_alloc: Not enough memory.
* throws std::runtime_error: Error dumping frame.
*/
virtual void frame(struct lcscreen& _frame, uint32_t fps_n, uint32_t fps_d, window* win, bool dummy)
throw(std::bad_alloc, std::runtime_error) = 0;
/**
* Dump a frame.
*
* parameter _frame: The frame to dump.
* parameter fps_n: Current fps numerator.
* parameter fps_d: Current fps denomerator.
* parameter win: Graphics system handle.
*/
static void frame(struct lcscreen& _frame, uint32_t fps_n, uint32_t fps_d, window* win) throw();
/**
* Dump a sample.
*
* parameter l: Left channel sample.
* parameter r: Right channel sample.
* throws std::bad_alloc: Not enough memory.
* throws std::runtime_error: Error dumping sample.
*/
virtual void sample(short l, short r) throw(std::bad_alloc, std::runtime_error) = 0;
/**
* Dump a sample.
*
* parameter l: Left channel sample.
* parameter r: Right channel sample.
* parameter win: Graphics system handle.
*/
static void sample(short l, short r, window* win) throw();
/**
* End dump.
*
* throws std::bad_alloc: Not enough memory.
* throws std::runtime_error: Error dumping sample.
*/
virtual void end() throw(std::bad_alloc, std::runtime_error) = 0;
/**
* End dump.
*
* parameter win: Graphics system handle.
*/
static void end(window* win) throw();
/**
* Notify game information.
*
* parameter gamename: Name of the game.
* parameter authors: Authors of the run.
* parameter gametime: Game time.
* parameter rercords: Rerecord count.
* throws std::bad_alloc: Not enough memory.
* throws std::runtime_error: Error recording this info.
*/
virtual void gameinfo(const std::string& gamename, const std::list<std::pair<std::string, std::string>>&
authors, double gametime, const std::string& rerecords) throw(std::bad_alloc, std::runtime_error) = 0;
/**
* Notify game information.
*
* parameter gamename: Name of the game.
* parameter authors: Authors of the run.
* parameter gametime: Game time.
* parameter rercords: Rerecord count.
* parameter win: Graphics system handle.
* throws std::bad_alloc Not enough memory.
*/
static void gameinfo(const std::string& gamename, const std::list<std::pair<std::string, std::string>>&
authors, double gametime, const std::string& rerecords, window* win) throw(std::bad_alloc);
/**
* Send game info. This causes gameinfo method to be called on object this method is called on.
*/
void send_gameinfo() throw();
/**
* Is there dump in progress?
*
* returns: True if dump is in progress, false if not.
*/
static bool dump_in_progress() throw();
/**
* Notifier for dumps starting/ending.
*/
class dump_notification
{
public:
/**
* Destructor.
*/
virtual ~dump_notification() throw();
/**
* New dump starting.
*/
virtual void dump_starting() throw();
/**
* Dump ending.
*/
virtual void dump_ending() throw();
};
/**
* Add a notifier.
*
* parameter notifier: New notifier to add.
*/
static void add_dump_notifier(dump_notification& notifier) throw(std::bad_alloc);
/**
* Remove a notifier.
*
* parameter notifier: Existing notifier to remove.
*/
static void remove_dump_notifier(dump_notification& notifier) throw();
};
#endif

View file

@ -5,7 +5,6 @@
#include <map>
#include <set>
#include "misc.hpp"
#include "videodumper2.hpp"
#include "memorymanip.hpp"
#include "fieldsplit.hpp"
#include "command.hpp"

View file

@ -1,4 +1,5 @@
#include "mainloop.hpp"
#include "avsnoop.hpp"
#include "command.hpp"
#include <iomanip>
#include "framerate.hpp"
@ -20,7 +21,6 @@
#include "memorymanip.hpp"
#include "keymapper.hpp"
#include "render.hpp"
#include "videodumper2.hpp"
#include <iostream>
#include "lsnes.hpp"
#include <sys/time.h>
@ -710,6 +710,18 @@ namespace
queued_saves.insert(filename);
win->message("Pending save on '" + filename + "'");
}
class dump_watch : public av_snooper::dump_notification
{
void dump_starting() throw()
{
update_movie_state();
}
void dump_ending() throw()
{
update_movie_state();
}
} dumpwatch;
}
std::vector<char>& get_host_memory()
@ -745,7 +757,7 @@ void update_movie_state()
x << "PLAY ";
else
x << "REC ";
if(dump_in_progress())
if(av_snooper::dump_in_progress())
x << "CAP ";
_status["Flags"] = x.str();
}
@ -833,19 +845,15 @@ class my_interface : public SNES::Interface
location_special = SPECIAL_FRAME_VIDEO;
update_movie_state();
redraw_framebuffer();
struct lua_render_context lrc;
render_queue rq;
lrc.left_gap = 0;
lrc.right_gap = 0;
lrc.bottom_gap = 0;
lrc.top_gap = 0;
lrc.queue = &rq;
lrc.width = framebuffer.width;
lrc.height = framebuffer.height;
video_fill_shifts(lrc.rshift, lrc.gshift, lrc.bshift);
lua_callback_do_video(&lrc, win);
dump_frame(framebuffer, &rq, lrc.left_gap, lrc.right_gap, lrc.top_gap, lrc.bottom_gap, region, win);
uint32_t fps_n, fps_d;
if(region) {
fps_n = 322445;
fps_d = 6448;
} else {
fps_n = 10738636;
fps_d = 178683;
}
av_snooper::frame(ls, fps_n, fps_d, win);
}
void audio_sample(int16_t l_sample, int16_t r_sample)
@ -853,14 +861,14 @@ class my_interface : public SNES::Interface
uint16_t _l = l_sample;
uint16_t _r = r_sample;
win->play_audio_sample(_l + 32768, _r + 32768);
dump_audio_sample(_l, _r, win);
av_snooper::sample(_l, _r, win);
}
void audio_sample(uint16_t l_sample, uint16_t r_sample)
{
//Yes, this interface is broken. The samples are signed but are passed as unsigned!
win->play_audio_sample(l_sample + 32768, r_sample + 32768);
dump_audio_sample(l_sample, r_sample, win);
av_snooper::sample(l_sample, r_sample, win);
}
int16_t input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id)
@ -1639,7 +1647,7 @@ void main_loop(window* _win, struct loaded_rom& rom, struct moviefile& initial)
lua_callback_startup(win);
//print_controller_mappings();
av_snooper::add_dump_notifier(dumpwatch);
win->set_main_surface(scr);
redraw_framebuffer();
win->paused(false);
@ -1689,6 +1697,6 @@ void main_loop(window* _win, struct loaded_rom& rom, struct moviefile& initial)
win->wait_msec(to_wait_frame(get_ticks_msec()));
first_round = false;
}
end_vid_dump();
av_snooper::end(win);
SNES::system.interface = old_inteface;
}

View file

@ -1,65 +0,0 @@
#ifndef _videodumper2__hpp__included__
#define _videodumper2__hpp__included__
#include <cstdint>
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include <stdexcept>
#include "window.hpp"
#include "videodumper.hpp"
/**
* Forcibly ends dumping. Mainly useful for quitting.
*
* throws std::bad_alloc: Not enough memory.
* throws std::runtime_error: Failed to end dump.
*/
void end_vid_dump() throw(std::bad_alloc, std::runtime_error);
/**
* Dumps a frame. Does nothing if dumping is not in progress.
*
* parameter ls: Screen to dump.
* parameter rq: Render queue to run.
* parameter left: Left border.
* parameter right: Right border.
* parameter top: Top border.
* parameter bottom: Bottom border.
* parameter region: True if PAL, false if NTSC.
* parameter win: Graphics system handle.
* throws std::bad_alloc: Not enough memory.
* throws std::runtime_error: Failed to dump frame.
*/
void dump_frame(lcscreen& ls, render_queue* rq, uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
bool region, window* win) throw(std::bad_alloc, std::runtime_error);
/**
* Dumps one sample of audio. Does nothing if dumping is not in progress.
*
* parameter l_sample Left channel sample (-32768-32767)
* parameter r_sample Right channel sample (-32768-32767)
* parameter win Graphics System handle.
* throws std::bad_alloc: Not enough memory.
* throws std::runtime_error: Failed to dump sample.
*/
void dump_audio_sample(int16_t l_sample, int16_t r_sample, window* win) throw(std::bad_alloc, std::runtime_error);
/**
* Is the dump in progress?
*
* returns: True if dump is in progress, false if not.
*/
bool dump_in_progress() throw();
/**
* Fill rendering shifts.
*
* parameter r: Shift for red component is written here.
* parameter g: Shift for green component is written here.
* parameter b: Shift for blue component is written here.
*/
void video_fill_shifts(uint32_t& r, uint32_t& g, uint32_t& b);
#endif