13
0
livetrax/gtk2_ardour/video_monitor.cc
Robin Gareus 4050ca5633
Update GPL boilerplate and (C)
Copyright-holder and year information is extracted from git log.

git history begins in 2005. So (C) from 1998..2005 is lost. Also some
(C) assignment of commits where the committer didn't use --author.
2019-08-03 15:53:15 +02:00

577 lines
17 KiB
C++

/*
* Copyright (C) 2013-2016 Tim Mayberry <mojofunk@gmail.com>
* Copyright (C) 2013-2018 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
* Copyright (C) 2013 John Emmas <john@creativepost.co.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "pbd/file_utils.h"
#include "pbd/convert.h"
#include "gui_thread.h"
#include "timers.h"
#include "utils.h"
#include <stdio.h>
#include "public_editor.h"
#include "editor.h"
#include "video_monitor.h"
#include "pbd/i18n.h"
using namespace std;
using namespace PBD;
using namespace ARDOUR_UI_UTILS;
VideoMonitor::VideoMonitor (PublicEditor *ed, std::string xjadeo_bin_path)
: editor (ed)
{
manually_seeked_frame = 0;
fps =0.0; // = _session->timecode_frames_per_second();
sync_by_manual_seek = true;
_restore_settings_mask = 0;
clock_connection = sigc::connection();
state_connection = sigc::connection();
debug_enable = false;
state_clk_divide = 0;
starting = 0;
osdmode = 10; // 1: frameno, 2: timecode, 8: box
process = new ARDOUR::SystemExec(xjadeo_bin_path, X_("-R -J"));
process->ReadStdout.connect_same_thread (*this, boost::bind (&VideoMonitor::parse_output, this, _1 ,_2));
process->Terminated.connect (*this, invalidator (*this), boost::bind (&VideoMonitor::terminated, this), gui_context());
XJKeyEvent.connect (*this, invalidator (*this), boost::bind (&VideoMonitor::forward_keyevent, this, _1), gui_context());
}
VideoMonitor::~VideoMonitor ()
{
if (clock_connection.connected()) {
clock_connection.disconnect();
}
if (state_connection.connected()) {
state_connection.disconnect();
}
delete process;
}
bool
VideoMonitor::start ()
{
if (is_started()) {
return true;
}
manually_seeked_frame = 0;
sync_by_manual_seek = false;
if (clock_connection.connected()) { clock_connection.disconnect(); }
if (process->start (debug_enable ? SystemExec::MergeWithStdin : SystemExec::IgnoreAndClose)) {
return false;
}
return true;
}
void
VideoMonitor::query_full_state (bool wait)
{
knownstate = 0;
process->write_to_stdin("get windowsize\n");
process->write_to_stdin("get windowpos\n");
process->write_to_stdin("get letterbox\n");
process->write_to_stdin("get fullscreen\n");
process->write_to_stdin("get ontop\n");
process->write_to_stdin("get offset\n");
process->write_to_stdin("get osdcfg\n");
int timeout = 40;
if (wait && knownstate !=127 && --timeout) {
Glib::usleep(50000);
sched_yield();
}
}
void
VideoMonitor::quit ()
{
if (!is_started()) return;
if (state_connection.connected()) { state_connection.disconnect(); }
if (clock_connection.connected()) { clock_connection.disconnect(); }
query_full_state(true);
process->write_to_stdin("quit\n");
/* the 'quit' command should result in process termination
* but in case it fails (communication failure, SIGSTOP, ??)
* here's a timeout..
*/
int timeout = 40;
while (is_started() && --timeout) {
Glib::usleep(50000);
sched_yield();
}
if (timeout <= 0) {
process->terminate();
}
}
void
VideoMonitor::open (std::string filename)
{
if (!is_started()) return;
manually_seeked_frame = 0;
osdmode = 10; // 1: frameno, 2: timecode, 8: box
starting = 15;
process->write_to_stdin("load " + filename + "\n");
process->write_to_stdin("set fps -1\n");
process->write_to_stdin("window resize 100%\n");
process->write_to_stdin("window ontop on\n");
process->write_to_stdin("set seekmode 1\n");
/* override bitwise flags -- see xjadeo.h
* 0x0001 : ignore 'q', ESC / quit
* 0x0002 : ignore "window closed by WM" / quit
* 0x0004 : (osx only) menu-exit / quit
* 0x0008 : ignore mouse-button 1 -- resize
* 0x0010 : no A/V offset control with keyboard
* 0x0020 : don't use jack-session
* 0x0040 : disable jack transport control
* 0x0080 : disallow sync source change (OSX menu)
* 0x0100 : disallow file open (OSX menu, X11 DnD)
*/
process->write_to_stdin("set override 504\n");
process->write_to_stdin("notify keyboard\n");
process->write_to_stdin("notify settings\n");
process->write_to_stdin("window letterbox on\n");
process->write_to_stdin("osd mode 10\n");
for(XJSettings::const_iterator it = xjadeo_settings.begin(); it != xjadeo_settings.end(); ++it) {
if (skip_setting(it->first)) { continue; }
process->write_to_stdin(it->first + " " + it->second + "\n");
}
if (!state_connection.connected()) {
starting = 15;
querystate();
state_clk_divide = 0;
/* TODO once every two second or so -- state_clk_divide hack below */
state_connection = Timers::rapid_connect (sigc::mem_fun (*this, &VideoMonitor::querystate));
}
sync_by_manual_seek = true;
clock_connection = Timers::fps_connect (sigc::mem_fun (*this, &VideoMonitor::srsupdate));
xjadeo_sync_setup();
}
void
VideoMonitor::querystate ()
{
/* clock-divider hack -- RapidScreenUpdate == every_point_one_seconds */
state_clk_divide = (state_clk_divide + 1) % 300; // 30 secs
if (state_clk_divide == 0) {
// every 30 seconds
query_full_state(false);
return;
}
if (state_clk_divide%25 != 0) {
return;
}
// every 2.5 seconds:
process->write_to_stdin("get fullscreen\n");
process->write_to_stdin("get ontop\n");
process->write_to_stdin("get osdcfg\n");
process->write_to_stdin("get letterbox\n");
}
bool
VideoMonitor::skip_setting (std::string which)
{
if (_restore_settings_mask & XJ_OSD && which == "osd mode") { return true; }
if (_restore_settings_mask & XJ_LETTERBOX && which == "window letterbox") { return true; }
if (_restore_settings_mask & XJ_WINDOW_SIZE && which == "window size") { return true; }
if (_restore_settings_mask & XJ_WINDOW_POS && which == "window xy") { return true; }
if (_restore_settings_mask & XJ_WINDOW_ONTOP && which == "window ontop") { return true; }
if (_restore_settings_mask & XJ_LETTERBOX && which == "window letterbox") { return true; }
if (_restore_settings_mask & XJ_OFFSET && which == "set offset") { return true; }
if (_restore_settings_mask & XJ_FULLSCREEN && which == "window zoom") { return true; }
return false;
}
void
VideoMonitor::send_cmd (int what, int param)
{
bool osd_update = false;
int prev_osdmode = osdmode;
if (!is_started()) return;
switch (what) {
case 1:
if (param) process->write_to_stdin("window ontop on\n");
else process->write_to_stdin("window ontop off\n");
break;
case 2:
if (param) osdmode |= 2;
else osdmode &= ~2;
osd_update = (prev_osdmode != osdmode);
break;
case 3:
if (param) osdmode |= 1;
else osdmode &= ~1;
osd_update = (prev_osdmode != osdmode);
break;
case 4:
if (param) osdmode |= 8;
else osdmode &= ~8;
osd_update = (prev_osdmode != osdmode);
break;
case 5:
if (param) process->write_to_stdin("window zoom on\n");
else process->write_to_stdin("window zoom off\n");
break;
case 6:
if (param) process->write_to_stdin("window letterbox on\n");
else process->write_to_stdin("window letterbox off\n");
break;
case 7:
process->write_to_stdin("window resize 100%");
break;
default:
break;
}
if (osd_update) {
std::ostringstream osstream; osstream << "osd mode " << osdmode << "\n";
process->write_to_stdin(osstream.str());
}
}
bool
VideoMonitor::is_started ()
{
return process->is_running();
}
void
VideoMonitor::forward_keyevent (unsigned int keyval)
{
emulate_key_event (keyval);
}
void
VideoMonitor::parse_output (std::string d, size_t /*s*/)
{
std::string line = d;
std::string::size_type start = 0;
std::string::size_type end = 0;
while (1) {
end = d.find('\n', start);
if (end == std::string::npos) break;
line = d.substr(start,end-start);
start=end+1;
if (line.length() <4 || line.at(0)!='@') continue;
#if 1 /* DEBUG */
if (debug_enable) {
printf("xjadeo: '%s'\n", line.c_str());
}
#endif
int status = atoi(line.substr(1,3));
switch(status / 100) {
case 4: /* errors */
if (status == 403) {
PBD::warning << _("Video Monitor: File Not Found.") << endmsg;
/* check: we should only write from the main thread.
* However it should not matter for 'quit'.
*/
process->write_to_stdin("quit\n");
}
#ifdef DEBUG_XJCOM
else
printf("xjadeo: error '%s'\n", line.c_str());
#endif
break;
case 3: /* async notifications */
{
std::string::size_type equalsign = line.find('=');
std::string::size_type comment = line.find('#');
if (comment != std::string::npos) { line = line.substr(0,comment); }
if (equalsign != std::string::npos) {
std::string key = line.substr(5, equalsign - 5);
std::string value = line.substr(equalsign + 1);
if (status == 310 && key=="keypress") {
/* keyboard event */
XJKeyEvent((unsigned int)atoi(value));
}
#ifdef DEBUG_XJCOM
else {
std::string msg = line.substr(5);
printf("xjadeo: async '%s' -> '%s'\n", key, value);
}
#endif
}
#ifdef DEBUG_XJCOM
else {
std::string msg = line.substr(5);
printf("xjadeo: async '%s'\n", msg.c_str());
}
#endif
} break;
case 1: /* text messages - command reply */
break;
case 8: /* comments / info for humans */
break;
case 2:
/* replies:
* 201: var=<int>
* 202: var=<double>
* 210: var=<int>x<int>
* 220: var=<string>
* 228: var=<smpte-string>
*/
{
std::string::size_type equalsign = line.find('=');
std::string::size_type comment = line.find('#');
if (comment != std::string::npos) { line = line.substr(0,comment); }
if (equalsign != std::string::npos) {
std::string key = line.substr(5, equalsign - 5);
std::string value = line.substr(equalsign + 1);
#if 0 /* DEBUG */
std::cout << "parsed: " << key << " => " << value << std::endl;
#endif
if (key == "windowpos") {
knownstate |= 16;
if (xjadeo_settings["window xy"] != value) {
if (!starting && _session) _session->set_dirty ();
}
xjadeo_settings["window xy"] = value;
} else if(key == "windowsize") {
knownstate |= 32;
if (xjadeo_settings["window size"] != value) {
if (!starting && _session) _session->set_dirty ();
}
xjadeo_settings["window size"] = value;
} else if(key == "windowontop") {
knownstate |= 2;
if (starting || xjadeo_settings["window ontop"] != value) {
if (!starting && _session) _session->set_dirty ();
if (atoi(value)) { UiState("xjadeo-window-ontop-on"); }
else { UiState("xjadeo-window-ontop-off"); }
starting &= ~2;
}
xjadeo_settings["window ontop"] = value;
} else if(key == "fullscreen") {
knownstate |= 4;
if (starting || xjadeo_settings["window zoom"] != value) {
if (!starting && _session) _session->set_dirty ();
if (atoi(value)) { UiState("xjadeo-window-fullscreen-on"); }
else { UiState("xjadeo-window-fullscreen-off"); }
starting &= ~4;
}
xjadeo_settings["window zoom"] = value;
} else if(key == "letterbox") {
knownstate |= 8;
if (starting || xjadeo_settings["window letterbox"] != value) {
if (!starting && _session) _session->set_dirty ();
if (atoi(value)) { UiState("xjadeo-window-letterbox-on"); }
else { UiState("xjadeo-window-letterbox-off"); }
starting &= ~8;
}
xjadeo_settings["window letterbox"] = value;
} else if(key == "osdmode") {
knownstate |= 1;
osdmode = atoi(value);
if (starting || atoi(xjadeo_settings["osd mode"]) != osdmode) {
if (!starting && _session) _session->set_dirty ();
if ((osdmode & 1) == 1) { UiState("xjadeo-window-osd-frame-on"); }
if ((osdmode & 1) == 0) { UiState("xjadeo-window-osd-frame-off"); }
if ((osdmode & 2) == 2) { UiState("xjadeo-window-osd-timecode-on"); }
if ((osdmode & 2) == 0) { UiState("xjadeo-window-osd-timecode-off"); }
if ((osdmode & 8) == 8) { UiState("xjadeo-window-osd-box-on"); }
if ((osdmode & 8) == 0) { UiState("xjadeo-window-osd-box-off"); }
}
starting &= ~1;
xjadeo_settings["osd mode"] = value;
} else if(key == "offset") {
knownstate |= 64;
if (xjadeo_settings["set offset"] != value) {
if (!starting && _session) _session->set_dirty ();
}
xjadeo_settings["set offset"] = value;
#ifdef DEBUG_XJCOM
} else {
printf("xjadeo: '%s' -> '%s'\n", key.c_str(), value.c_str());
#endif
}
}
}
break;
default:
break;
}
}
}
void
VideoMonitor::terminated ()
{
process->terminate(); // from gui-context clean up
Terminated();
}
void
VideoMonitor::save_session ()
{
if (!_session) { return; }
XMLNode* node = _session->extra_xml (X_("XJSettings"), true);
if (!node) return;
node->remove_nodes_and_delete("XJSetting");
for(XJSettings::const_iterator it = xjadeo_settings.begin(); it != xjadeo_settings.end(); ++it) {
XMLNode* child = node->add_child (X_("XJSetting"));
child->set_property (X_("k"), it->first);
child->set_property (X_("v"), it->second);
}
}
void
VideoMonitor::set_session (ARDOUR::Session *s)
{
SessionHandlePtr::set_session (s);
if (!_session) { return; }
ARDOUR::Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&VideoMonitor::parameter_changed, this, _1), gui_context());
_session->config.ParameterChanged.connect (*this, invalidator (*this), ui_bind (&VideoMonitor::parameter_changed, this, _1), gui_context());
XMLNode* node = _session->extra_xml (X_("XJSettings"));
if (!node) { return;}
xjadeo_settings.clear();
XMLNodeList nlist = node->children();
XMLNodeConstIterator niter;
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
xjadeo_settings[(*niter)->property(X_("k"))->value()] = (*niter)->property(X_("v"))->value();
}
}
bool
VideoMonitor::set_custom_setting (const std::string k, const std::string v)
{
xjadeo_settings[k] = v;
return true; /* TODO: check if key is valid */
}
const std::string
VideoMonitor::get_custom_setting (const std::string k)
{
return (xjadeo_settings[k]);
}
#define NO_OFFSET (Temporal::max_samplepos) //< skip setting or modifying offset
void
VideoMonitor::srsupdate ()
{
if (!_session) { return; }
if (editor->dragging_playhead()) { return ;}
manual_seek(_session->audible_sample(), false, NO_OFFSET);
}
void
VideoMonitor::set_offset (ARDOUR::sampleoffset_t offset)
{
if (!is_started()) { return; }
if (!_session) { return; }
if (offset == NO_OFFSET ) { return; }
samplecnt_t video_frame_offset;
samplecnt_t audio_sample_rate;
if (_session->config.get_videotimeline_pullup()) {
audio_sample_rate = _session->sample_rate();
} else {
audio_sample_rate = _session->nominal_sample_rate();
}
/* Note: pull-up/down are applied here: sample_rate() vs. nominal_sample_rate() */
if (_session->config.get_use_video_file_fps()) {
video_frame_offset = floor(offset * fps / audio_sample_rate);
} else {
video_frame_offset = floor(offset * _session->timecode_frames_per_second() / audio_sample_rate);
}
if (video_offset == video_frame_offset) { return; }
video_offset = video_frame_offset;
std::ostringstream osstream1; osstream1 << -1 * video_frame_offset;
process->write_to_stdin("set offset " + osstream1.str() + "\n");
}
void
VideoMonitor::manual_seek (samplepos_t when, bool /*force*/, ARDOUR::sampleoffset_t offset)
{
if (!is_started()) { return; }
if (!_session) { return; }
samplecnt_t video_frame;
samplecnt_t audio_sample_rate;
if (_session->config.get_videotimeline_pullup()) {
audio_sample_rate = _session->sample_rate();
} else {
audio_sample_rate = _session->nominal_sample_rate();
}
/* Note: pull-up/down are applied here: sample_rate() vs. nominal_sample_rate() */
if (_session->config.get_use_video_file_fps()) {
video_frame = floor(when * fps / audio_sample_rate);
} else {
video_frame = floor(when * _session->timecode_frames_per_second() / audio_sample_rate);
}
if (video_frame < 0 ) video_frame = 0;
if (video_frame == manually_seeked_frame) { return; }
manually_seeked_frame = video_frame;
#if 0 /* DEBUG */
std::cout <<"seek: " << video_frame << std::endl;
#endif
std::ostringstream osstream; osstream << video_frame;
process->write_to_stdin("seek " + osstream.str() + "\n");
set_offset(offset);
}
void
VideoMonitor::parameter_changed (std::string const & p)
{
if (!is_started()) { return; }
if (!_session) { return; }
if (p != "external-sync" && p != "sync-source") {
return;
}
xjadeo_sync_setup();
}
void
VideoMonitor::xjadeo_sync_setup ()
{
if (!is_started()) { return; }
if (!_session) { return; }
bool my_manual_seek = true;
if (_session->synced_to_engine ()) {
my_manual_seek = false;
}
if (my_manual_seek != sync_by_manual_seek) {
if (sync_by_manual_seek) {
if (clock_connection.connected()) {
clock_connection.disconnect();
}
process->write_to_stdin("jack connect\n");
} else {
process->write_to_stdin("jack disconnect\n");
clock_connection = Timers::fps_connect (sigc::mem_fun (*this, &VideoMonitor::srsupdate));
}
sync_by_manual_seek = my_manual_seek;
}
}