X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=libs%2Fardour%2Fbutler.cc;h=62f3a525ea82582c8dc866a627d0ef715a0de2e3;hb=7ffeb70b1b29df553b3b3d49b0215c7a0d5d4a4c;hp=03ae7867db5bcf4ccbefe50b0e7bd5354aab2ab8;hpb=a643c9fdc08f98c7750a2474004a75e143077be9;p=ardour.git diff --git a/libs/ardour/butler.cc b/libs/ardour/butler.cc index 03ae7867db..62f3a525ea 100644 --- a/libs/ardour/butler.cc +++ b/libs/ardour/butler.cc @@ -20,9 +20,14 @@ #include #include #include + +#ifndef PLATFORM_WINDOWS #include +#endif + #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" @@ -38,15 +43,18 @@ namespace ARDOUR { Butler::Butler(Session& s) : SessionHandleRef (s) - , thread(0) + , 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)); } @@ -55,30 +63,57 @@ Butler::~Butler() terminate_thread (); } +void +Butler::map_parameters () +{ + /* use any current ones that we care about */ + boost::function ff (boost::bind (&Butler::config_changed, this, _1)); + Config->map_parameters (ff); +} + void Butler::config_changed (std::string p) { - 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 (); - } + 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())); + } } 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); @@ -87,30 +122,17 @@ Butler::start_thread() should_run = false; - 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 (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; } @@ -118,10 +140,10 @@ Butler::start_thread() void Butler::terminate_thread () { - if (thread) { + if (have_thread) { void* status; - const char c = Request::Quit; - (void) ::write (request_pipe[1], &c, 1); + 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); } } @@ -139,84 +161,61 @@ Butler::thread_work () { uint32_t err = 0; - struct pollfd pfd[1]; bool disk_work_outstanding = false; RouteList::iterator i; while (true) { - pfd[0].fd = request_pipe[0]; - pfd[0].events = POLLIN|POLLERR|POLLHUP; - - if (poll (pfd, 1, (disk_work_outstanding ? 0 : -1)) < 0) { - - if (errno == EINTR) { - continue; - } - - error << string_compose (_("poll on butler request pipe failed (%1)"), - strerror (errno)) - << endmsg; - break; - } + 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 (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) { - - char req; + if(!disk_work_outstanding) { + 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 */ - - while (1) { - size_t nread = ::read (request_pipe[0], &req, sizeof (req)); - if (nread == 1) { - - switch ((Request::Type) req) { + 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; - } - - } else if (nread == 0) { - break; - } else if (errno == EAGAIN) { - break; - } else { - fatal << _("Error reading from butler request pipe") << endmsg; - /*NOTREACHED*/ } } } -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); } @@ -225,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); @@ -241,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; } @@ -265,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; } @@ -317,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 () { @@ -345,19 +408,38 @@ Butler::schedule_transport_work () summon (); } +void +Butler::queue_request (Request::Type r) +{ + char c = r; + 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 () { - char c = Request::Run; - (void) ::write (request_pipe[1], &c, 1); + DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: summon butler to run @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time())); + queue_request (Request::Run); } void Butler::stop () { Glib::Threads::Mutex::Lock lm (request_lock); - char c = Request::Pause; - (void) ::write (request_pipe[1], &c, 1); + 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); } @@ -365,8 +447,8 @@ void Butler::wait_until_finished () { Glib::Threads::Mutex::Lock lm (request_lock); - char c = Request::Pause; - (void) ::write (request_pipe[1], &c, 1); + 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); } @@ -409,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); }