X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fbutler.cc;h=228188621a80ba47226bbeaa65844a50729520ab;hb=cf52d6e4b40111eb04b244ec054055a4ec15dbe0;hp=1fe15246183c89575782e5e4f505c562bcbf6bb7;hpb=300b484cf6ac14c15e365c4062345d64a61c4b18;p=ardour.git diff --git a/libs/ardour/butler.cc b/libs/ardour/butler.cc index 1fe1524618..228188621a 100644 --- a/libs/ardour/butler.cc +++ b/libs/ardour/butler.cc @@ -27,6 +27,7 @@ #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" @@ -34,7 +35,7 @@ #include "ardour/track.h" #include "ardour/auditioner.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace PBD; @@ -43,14 +44,17 @@ namespace ARDOUR { 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)); } @@ -60,54 +64,56 @@ Butler::~Butler() } 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 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); @@ -116,16 +122,17 @@ Butler::start_thread() 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; } @@ -133,9 +140,12 @@ Butler::start_thread() 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 * @@ -146,68 +156,7 @@ Butler::_thread_work (void* arg) 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; @@ -216,45 +165,58 @@ Butler::thread_work () 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 tr = boost::dynamic_pointer_cast (_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 rl = _session.get_routes(); @@ -262,10 +224,6 @@ restart: 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 tr = boost::dynamic_pointer_cast (*i); @@ -278,19 +236,23 @@ restart: 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; } @@ -302,51 +264,22 @@ restart: } 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 tr = boost::dynamic_pointer_cast (*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; } @@ -354,27 +287,120 @@ 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 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 tr = boost::dynamic_pointer_cast (*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 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 tr = boost::dynamic_pointer_cast (*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 () { @@ -385,18 +411,26 @@ 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); } @@ -404,6 +438,7 @@ void 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); } @@ -412,6 +447,7 @@ void 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); } @@ -455,7 +491,7 @@ Butler::empty_pool_trash () void Butler::drop_references () { - cerr << "Butler drops pool trash\n"; + std::cerr << "Butler drops pool trash\n"; SessionEvent::pool->set_trash (0); }