#include "pbd/error.h"
#include "pbd/pthread_utils.h"
+#include "ardour/debug.h"
#include "ardour/butler.h"
#include "ardour/io.h"
#include "ardour/midi_diskstream.h"
Butler::Butler(Session& s)
: SessionHandleRef (s)
, thread()
+ , have_thread (false)
, audio_dstream_capture_buffer_size(0)
, audio_dstream_playback_buffer_size(0)
, midi_dstream_buffer_size(0)
, pool_trash(16)
+ , _xthread (true)
{
g_atomic_int_set(&should_do_transport_work, 0);
SessionEvent::pool->set_trash (&pool_trash);
+ /* catch future changes to parameters */
Config->ParameterChanged.connect_same_thread (*this, boost::bind (&Butler::config_changed, this, _1));
}
}
void
-Butler::config_changed (std::string p)
+Butler::map_parameters ()
{
- if (p == "playback-buffer-seconds") {
- /* size is in Samples, not bytes */
- audio_dstream_playback_buffer_size = (uint32_t) floor (Config->get_audio_playback_buffer_seconds() * _session.frame_rate());
- _session.adjust_playback_buffering ();
- } else if (p == "capture-buffer-seconds") {
- audio_dstream_capture_buffer_size = (uint32_t) floor (Config->get_audio_capture_buffer_seconds() * _session.frame_rate());
- _session.adjust_capture_buffering ();
- }
+ /* use any current ones that we care about */
+ boost::function<void (std::string)> ff (boost::bind (&Butler::config_changed, this, _1));
+ Config->map_parameters (ff);
}
-#ifndef PLATFORM_WINDOWS
-int
-Butler::setup_request_pipe ()
+void
+Butler::config_changed (std::string p)
{
- if (pipe (request_pipe)) {
- error << string_compose(_("Cannot create transport request signal pipe (%1)"),
- strerror (errno)) << endmsg;
- return -1;
- }
-
- if (fcntl (request_pipe[0], F_SETFL, O_NONBLOCK)) {
- error << string_compose(_("UI: cannot set O_NONBLOCK on butler request pipe (%1)"),
- strerror (errno)) << endmsg;
- return -1;
- }
-
- if (fcntl (request_pipe[1], F_SETFL, O_NONBLOCK)) {
- error << string_compose(_("UI: cannot set O_NONBLOCK on butler request pipe (%1)"),
- strerror (errno)) << endmsg;
- return -1;
+ if (p == "playback-buffer-seconds") {
+ _session.adjust_playback_buffering ();
+ if (Config->get_buffering_preset() == Custom) {
+ /* size is in Samples, not bytes */
+ audio_dstream_playback_buffer_size = (uint32_t) floor (Config->get_audio_playback_buffer_seconds() * _session.frame_rate());
+ _session.adjust_playback_buffering ();
+ } else {
+ std::cerr << "Skip explicit buffer seconds, preset in use\n";
+ }
+ } else if (p == "capture-buffer-seconds") {
+ if (Config->get_buffering_preset() == Custom) {
+ audio_dstream_capture_buffer_size = (uint32_t) floor (Config->get_audio_capture_buffer_seconds() * _session.frame_rate());
+ _session.adjust_capture_buffering ();
+ } else {
+ std::cerr << "Skip explicit buffer seconds, preset in use\n";
+ }
+ } else if (p == "buffering-preset") {
+ Diskstream::set_buffering_parameters (Config->get_buffering_preset());
+ audio_dstream_capture_buffer_size = (uint32_t) floor (Config->get_audio_capture_buffer_seconds() * _session.frame_rate());
+ audio_dstream_playback_buffer_size = (uint32_t) floor (Config->get_audio_playback_buffer_seconds() * _session.frame_rate());
+ _session.adjust_capture_buffering ();
+ _session.adjust_playback_buffering ();
+ } else if (p == "midi-readahead") {
+ MidiDiskstream::set_readahead_frames ((framecnt_t) (Config->get_midi_readahead() * _session.frame_rate()));
}
- return 0;
}
-#endif
int
Butler::start_thread()
{
- const float rate = (float)_session.frame_rate();
+ // set up capture and playback buffering
+ Diskstream::set_buffering_parameters (Config->get_buffering_preset());
/* size is in Samples, not bytes */
+ const float rate = (float)_session.frame_rate();
audio_dstream_capture_buffer_size = (uint32_t) floor (Config->get_audio_capture_buffer_seconds() * rate);
audio_dstream_playback_buffer_size = (uint32_t) floor (Config->get_audio_playback_buffer_seconds() * rate);
/* size is in bytes
- * XXX: Jack needs to tell us the MIDI buffer size
+ * XXX: AudioEngine needs to tell us the MIDI buffer size
* (i.e. how many MIDI bytes we might see in a cycle)
*/
midi_dstream_buffer_size = (uint32_t) floor (Config->get_midi_track_buffer_seconds() * rate);
should_run = false;
-#ifndef PLATFORM_WINDOWS
- if (setup_request_pipe() != 0) return -1;
-#endif
-
if (pthread_create_and_store ("disk butler", &thread, _thread_work, this)) {
error << _("Session: could not create butler thread") << endmsg;
return -1;
}
//pthread_detach (thread);
+ have_thread = true;
+
+ // we are ready to request buffer adjustments
+ _session.adjust_capture_buffering ();
+ _session.adjust_playback_buffering ();
return 0;
}
void
Butler::terminate_thread ()
{
- void* status;
- queue_request (Request::Quit);
- pthread_join (thread, &status);
+ if (have_thread) {
+ void* status;
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: ask butler to quit @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time()));
+ queue_request (Request::Quit);
+ pthread_join (thread, &status);
+ }
}
void *
return ((Butler *) arg)->thread_work ();
}
-bool
-Butler::wait_for_requests ()
-{
-#ifndef PLATFORM_WINDOWS
- struct pollfd pfd[1];
-
- pfd[0].fd = request_pipe[0];
- pfd[0].events = POLLIN|POLLERR|POLLHUP;
-
- while(true) {
- if (poll (pfd, 1, -1) < 0) {
-
- if (errno == EINTR) {
- continue;
- }
-
- error << string_compose (_("poll on butler request pipe failed (%1)"),
- strerror (errno))
- << endmsg;
- break;
- }
-
- if (pfd[0].revents & ~POLLIN) {
- error << string_compose (_("Error on butler thread request pipe: fd=%1 err=%2"), pfd[0].fd, pfd[0].revents) << endmsg;
- break;
- }
-
- if (pfd[0].revents & POLLIN) {
- return true;
- }
- }
- return false;
-#else
- m_request_sem.wait ();
- return true;
-#endif
-}
-
-bool
-Butler::dequeue_request (Request::Type& r)
-{
-#ifndef PLATFORM_WINDOWS
- char req;
- size_t nread = ::read (request_pipe[0], &req, sizeof (req));
- if (nread == 1) {
- r = (Request::Type) req;
- return true;
- } else if (nread == 0) {
- return false;
- } else if (errno == EAGAIN) {
- return false;
- } else {
- fatal << _("Error reading from butler request pipe") << endmsg;
- /*NOTREACHED*/
- }
-#else
- r = (Request::Type) m_request_state.get();
-#endif
- return false;
-}
-
- void *
+void *
Butler::thread_work ()
{
uint32_t err = 0;
RouteList::iterator i;
while (true) {
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 butler main loop, disk work outstanding ? %2 @ %3\n", DEBUG_THREAD_SELF, disk_work_outstanding, g_get_monotonic_time()));
+
if(!disk_work_outstanding) {
- if (wait_for_requests ()) {
- Request::Type req;
-
- /* empty the pipe of all current requests */
-#ifdef PLATFORM_WINDOWS
- dequeue_request (req);
- {
-#else
- while(dequeue_request(req)) {
-#endif
- switch (req) {
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 butler waits for requests @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time()));
+
+ char msg;
+ /* empty the pipe of all current requests */
+ if (_xthread.receive (msg, true) >= 0) {
+ Request::Type req = (Request::Type) msg;
+ switch (req) {
case Request::Run:
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: butler asked to run @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time()));
should_run = true;
break;
case Request::Pause:
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: butler asked to pause @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time()));
should_run = false;
break;
case Request::Quit:
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: butler asked to quit @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time()));
return 0;
- /*NOTREACHED*/
+ abort(); /*NOTREACHED*/
break;
default:
break;
- }
}
}
}
-restart:
+ restart:
+ DEBUG_TRACE (DEBUG::Butler, "at restart for disk work\n");
disk_work_outstanding = false;
if (transport_work_requested()) {
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("do transport work @ %1\n", g_get_monotonic_time()));
_session.butler_transport_work ();
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("\ttransport work complete @ %1\n", g_get_monotonic_time()));
+ }
+
+ frameoffset_t audition_seek;
+ if (should_run && _session.is_auditioning()
+ && (audition_seek = _session.the_auditioner()->seek_frame()) >= 0) {
+ boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (_session.the_auditioner());
+ DEBUG_TRACE (DEBUG::Butler, "seek the auditioner\n");
+ tr->seek(audition_seek);
+ tr->do_refill ();
+ _session.the_auditioner()->seek_response(audition_seek);
}
boost::shared_ptr<RouteList> rl = _session.get_routes();
RouteList rl_with_auditioner = *rl;
rl_with_auditioner.push_back (_session.the_auditioner());
-// for (i = dsl->begin(); i != dsl->end(); ++i) {
-// cerr << "BEFORE " << (*i)->name() << ": pb = " << (*i)->playback_buffer_load() << " cp = " << (*i)->capture_buffer_load() << endl;
-// }
-
for (i = rl_with_auditioner.begin(); !transport_work_requested() && should_run && i != rl_with_auditioner.end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (io && !io->active()) {
/* don't read inactive tracks */
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("butler skips inactive track %1\n", tr->name()));
continue;
}
-
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("butler refills %1, playback load = %2\n", tr->name(), tr->playback_buffer_load()));
switch (tr->do_refill ()) {
case 0:
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("\ttrack refill done %1\n", tr->name()));
break;
-
+
case 1:
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("\ttrack refill unfinished %1\n", tr->name()));
disk_work_outstanding = true;
break;
default:
error << string_compose(_("Butler read ahead failure on dstream %1"), (*i)->name()) << endmsg;
+ std::cerr << string_compose(_("Butler read ahead failure on dstream %1"), (*i)->name()) << std::endl;
break;
}
}
if (!err && transport_work_requested()) {
+ DEBUG_TRACE (DEBUG::Butler, "transport work requested during refill, back to restart\n");
goto restart;
}
- for (i = rl->begin(); !transport_work_requested() && should_run && i != rl->end(); ++i) {
- // cerr << "write behind for " << (*i)->name () << endl;
-
- boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-
- if (!tr) {
- continue;
- }
-
- /* note that we still try to flush diskstreams attached to inactive routes
- */
-
- switch (tr->do_flush (ButlerContext)) {
- case 0:
- break;
-
- case 1:
- disk_work_outstanding = true;
- break;
-
- default:
- err++;
- error << string_compose(_("Butler write-behind failure on dstream %1"), (*i)->name()) << endmsg;
- /* don't break - try to flush all streams in case they
- are split across disks.
- */
- }
- }
+ disk_work_outstanding = flush_tracks_to_disk_normal (rl, err);
if (err && _session.actively_recording()) {
/* stop the transport and try to catch as much possible
captured state as we can.
*/
+ DEBUG_TRACE (DEBUG::Butler, "error occurred during recording - stop transport\n");
_session.request_stop ();
}
- if (i != rl->begin() && i != rl->end()) {
- /* we didn't get to all the streams */
- disk_work_outstanding = true;
- }
-
if (!err && transport_work_requested()) {
+ DEBUG_TRACE (DEBUG::Butler, "transport work requested during flush, back to restart\n");
goto restart;
}
_session.refresh_disk_space ();
}
-
{
Glib::Threads::Mutex::Lock lm (request_lock);
if (should_run && (disk_work_outstanding || transport_work_requested())) {
-// for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
-// cerr << "AFTER " << (*i)->name() << ": pb = " << (*i)->playback_buffer_load() << " cp = " << (*i)->capture_buffer_load() << endl;
-// }
-
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("at end, should run %1 disk work %2 transport work %3 ... goto restart\n",
+ should_run, disk_work_outstanding, transport_work_requested()));
goto restart;
}
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: butler signals pause @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time()));
paused.signal();
}
+ DEBUG_TRACE (DEBUG::Butler, "butler emptying pool trash\n");
empty_pool_trash ();
}
return (0);
}
+bool
+Butler::flush_tracks_to_disk_normal (boost::shared_ptr<RouteList> rl, uint32_t& errors)
+{
+ bool disk_work_outstanding = false;
+
+ for (RouteList::iterator i = rl->begin(); !transport_work_requested() && should_run && i != rl->end(); ++i) {
+
+ // cerr << "write behind for " << (*i)->name () << endl;
+
+ boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+
+ if (!tr) {
+ continue;
+ }
+
+ /* note that we still try to flush diskstreams attached to inactive routes
+ */
+
+ int ret;
+
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("butler flushes track %1 capture load %2\n", tr->name(), tr->capture_buffer_load()));
+ ret = tr->do_flush (ButlerContext, false);
+ switch (ret) {
+ case 0:
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("\tflush complete for %1\n", tr->name()));
+ break;
+
+ case 1:
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("\tflush not finished for %1\n", tr->name()));
+ disk_work_outstanding = true;
+ break;
+
+ default:
+ errors++;
+ error << string_compose(_("Butler write-behind failure on dstream %1"), (*i)->name()) << endmsg;
+ std::cerr << string_compose(_("Butler write-behind failure on dstream %1"), (*i)->name()) << std::endl;
+ /* don't break - try to flush all streams in case they
+ are split across disks.
+ */
+ }
+ }
+
+ return disk_work_outstanding;
+}
+
+bool
+Butler::flush_tracks_to_disk_after_locate (boost::shared_ptr<RouteList> rl, uint32_t& errors)
+{
+ bool disk_work_outstanding = false;
+
+ /* almost the same as the "normal" version except that we do not test
+ * for transport_work_requested() and we force flushes.
+ */
+
+ for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+
+ // cerr << "write behind for " << (*i)->name () << endl;
+
+ boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+
+ if (!tr) {
+ continue;
+ }
+
+ /* note that we still try to flush diskstreams attached to inactive routes
+ */
+
+ int ret;
+
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("butler flushes track %1 capture load %2\n", tr->name(), tr->capture_buffer_load()));
+ ret = tr->do_flush (ButlerContext, true);
+ switch (ret) {
+ case 0:
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("\tflush complete for %1\n", tr->name()));
+ break;
+
+ case 1:
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("\tflush not finished for %1\n", tr->name()));
+ disk_work_outstanding = true;
+ break;
+
+ default:
+ errors++;
+ error << string_compose(_("Butler write-behind failure on dstream %1"), (*i)->name()) << endmsg;
+ std::cerr << string_compose(_("Butler write-behind failure on dstream %1"), (*i)->name()) << std::endl;
+ /* don't break - try to flush all streams in case they
+ are split across disks.
+ */
+ }
+ }
+
+ return disk_work_outstanding;
+}
+
void
Butler::schedule_transport_work ()
{
void
Butler::queue_request (Request::Type r)
{
-#ifndef PLATFORM_WINDOWS
char c = r;
- (void) ::write (request_pipe[1], &c, 1);
-#else
- m_request_state.set (r);
- m_request_sem.post ();
-#endif
+ if (_xthread.deliver (c) != 1) {
+ /* the x-thread channel is non-blocking
+ * write may fail, but we really don't want to wait
+ * under normal circumstances.
+ *
+ * a lost "run" requests under normal RT operation
+ * is mostly harmless.
+ *
+ * TODO if ardour is freehweeling, wait & retry.
+ * ditto for Request::Type Quit
+ */
+ assert(1); // we're screwd
+ }
}
void
Butler::summon ()
{
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: summon butler to run @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time()));
queue_request (Request::Run);
}
Butler::stop ()
{
Glib::Threads::Mutex::Lock lm (request_lock);
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: asking butler to stop @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time()));
queue_request (Request::Pause);
paused.wait(request_lock);
}
Butler::wait_until_finished ()
{
Glib::Threads::Mutex::Lock lm (request_lock);
+ DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: waiting for butler to finish @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time()));
queue_request (Request::Pause);
paused.wait(request_lock);
}
void
Butler::drop_references ()
{
- cerr << "Butler drops pool trash\n";
+ std::cerr << "Butler drops pool trash\n";
SessionEvent::pool->set_trash (0);
}