X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fbutler.cc;h=62f3a525ea82582c8dc866a627d0ef715a0de2e3;hb=8882ef79d3b4a536daa00e3a20e2c50e0c49bbe8;hp=424312a8a4d8ec4d72e73574e6275e8a4a12da03;hpb=1e0c1751a53999a8b9ac6978393486afbf317529;p=ardour.git diff --git a/libs/ardour/butler.cc b/libs/ardour/butler.cc index 424312a8a4..62f3a525ea 100644 --- a/libs/ardour/butler.cc +++ b/libs/ardour/butler.cc @@ -49,10 +49,12 @@ Butler::Butler(Session& s) , 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)); } @@ -62,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); @@ -118,10 +122,6 @@ 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; @@ -129,6 +129,11 @@ Butler::start_thread() //pthread_detach (thread); have_thread = true; + + // we are ready to request buffer adjustments + _session.adjust_capture_buffering (); + _session.adjust_playback_buffering (); + return 0; } @@ -137,7 +142,7 @@ Butler::terminate_thread () { if (have_thread) { void* status; - DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: ask butler to quit @ %2\n", pthread_self(), g_get_monotonic_time())); + 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); } @@ -151,69 +156,6 @@ 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; - } - - DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: butler awake at @ %2\n", pthread_self(), 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) { - 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; - abort(); /*NOTREACHED*/ - } -#else - r = (Request::Type) m_request_state.get(); -#endif - return false; -} - void * Butler::thread_work () { @@ -223,47 +165,40 @@ Butler::thread_work () RouteList::iterator i; while (true) { - DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 butler main loop, disk work outstanding ? %2 @ %3\n", pthread_self(), disk_work_outstanding, g_get_monotonic_time())); + 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) { - DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 butler waits for requests @ %2\n", pthread_self(), g_get_monotonic_time())); - 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", pthread_self(), g_get_monotonic_time())); + 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", pthread_self(), g_get_monotonic_time())); + 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", pthread_self(), g_get_monotonic_time())); + DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: butler asked to quit @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time())); return 0; abort(); /*NOTREACHED*/ break; default: break; - } -#ifndef PLATFORM_WINDOWS } -#endif } } - + restart: DEBUG_TRACE (DEBUG::Butler, "at restart for disk work\n"); disk_work_outstanding = false; @@ -280,6 +215,7 @@ Butler::thread_work () 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); } @@ -308,7 +244,7 @@ Butler::thread_work () 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; @@ -332,44 +268,7 @@ Butler::thread_work () 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 - */ - - gint64 before, after; - int ret; - - DEBUG_TRACE (DEBUG::Butler, string_compose ("butler flushes track %1 capture load %2\n", tr->name(), tr->capture_buffer_load())); - before = g_get_monotonic_time (); - ret = tr->do_flush (ButlerContext); - after = g_get_monotonic_time (); - switch (ret) { - case 0: - DEBUG_TRACE (DEBUG::Butler, string_compose ("\tflush complete for %1, %2 usecs\n", tr->name(), after - before)); - break; - - case 1: - DEBUG_TRACE (DEBUG::Butler, string_compose ("\tflush not finished for %1, %2 usecs\n", tr->name(), after - before)); - disk_work_outstanding = true; - break; - - default: - err++; - 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. - */ - } - } + 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 @@ -379,12 +278,6 @@ Butler::thread_work () _session.request_stop (); } - if (i != rl->begin() && i != rl->end()) { - /* we didn't get to all the streams */ - DEBUG_TRACE (DEBUG::Butler, "not all tracks processed, will need to go back for more\n"); - disk_work_outstanding = true; - } - if (!err && transport_work_requested()) { DEBUG_TRACE (DEBUG::Butler, "transport work requested during flush, back to restart\n"); goto restart; @@ -394,7 +287,6 @@ Butler::thread_work () _session.refresh_disk_space (); } - { Glib::Threads::Mutex::Lock lm (request_lock); @@ -404,7 +296,7 @@ Butler::thread_work () goto restart; } - DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: butler signals pause @ %2\n", pthread_self(), g_get_monotonic_time())); + DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: butler signals pause @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time())); paused.signal(); } @@ -415,6 +307,100 @@ Butler::thread_work () 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 () { @@ -425,19 +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", pthread_self(), g_get_monotonic_time())); + DEBUG_TRACE (DEBUG::Butler, string_compose ("%1: summon butler to run @ %2\n", DEBUG_THREAD_SELF, g_get_monotonic_time())); queue_request (Request::Run); } @@ -445,7 +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", pthread_self(), g_get_monotonic_time())); + 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); } @@ -454,7 +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", pthread_self(), g_get_monotonic_time())); + 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); }