X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=alsa_input.h;h=3b98885677832ec6f1763555fbb4f36ada45e429;hb=26e1ec466d4730b6abc0e20201d704cfdf41a6eb;hp=d3768c5d109ed3921452641b2d75df09ba7633c4;hpb=b590a9a091974607517a9f872e28cbfa65014e2a;p=nageru diff --git a/alsa_input.h b/alsa_input.h index d3768c5..3b98885 100644 --- a/alsa_input.h +++ b/alsa_input.h @@ -15,18 +15,27 @@ #include #include #include +#include #include #include "bmusb/bmusb.h" #include "timebase.h" +class ALSAPool; +class DeviceSpecProto; + class ALSAInput { public: typedef std::function audio_callback_t; - ALSAInput(const char *device, unsigned sample_rate, unsigned num_channels, audio_callback_t audio_callback); + ALSAInput(const char *device, unsigned sample_rate, unsigned num_channels, audio_callback_t audio_callback, ALSAPool *parent_pool, unsigned internal_dev_index); ~ALSAInput(); + // If not called before start_capture_thread(), the capture thread + // will call it until it succeeds. + bool open_device(); + + // Not valid before the device has been successfully opened. // NOTE: Might very well be different from the sample rate given to the // constructor, since the card might not support the one you wanted. unsigned get_sample_rate() const { return sample_rate; } @@ -37,7 +46,13 @@ public: private: void capture_thread_func(); int64_t frames_to_pts(uint64_t n) const; - void die_on_error(const char *func_name, int err); + + enum class CaptureEndReason { + REQUESTED_QUIT, + DEVICE_GONE, + OTHER_ERROR + }; + CaptureEndReason do_capture(); std::string device; unsigned sample_rate, num_channels, num_periods; @@ -46,10 +61,12 @@ private: bmusb::AudioFormat audio_format; audio_callback_t audio_callback; - snd_pcm_t *pcm_handle; + snd_pcm_t *pcm_handle = nullptr; std::thread capture_thread; std::atomic should_quit{false}; std::unique_ptr buffer; + ALSAPool *parent_pool; + unsigned internal_dev_index; }; // The class dealing with the collective of all ALSA cards in the system. @@ -89,7 +106,7 @@ public: // installed in place of this card, and then presumably be put // back into STARTING or RUNNING. DEAD - } state; + } state = State::EMPTY; std::string address; // E.g. “hw:0,0”. std::string name, info; @@ -103,6 +120,8 @@ public: // at any given time as a result of an error or hotplug event; // a card that is held will go to the DEAD state instead. bool held = false; + + std::string display_name() const { return name + " (" + info + ")"; } }; void init(); @@ -124,16 +143,64 @@ public: // Note: The card must be held. Returns OUTPUT_FREQUENCY if the card is in EMPTY or DEAD. unsigned get_capture_frequency(unsigned index); - // TODO: Add accessors and/or callbacks about changed state, so that - // the UI actually stands a chance in using that information. + // Note: The card must be held. + Device::State get_card_state(unsigned index); + + // Only for ALSAInput. + void set_card_state(unsigned index, Device::State state); + + // Just a short form for taking and then moving the card to + // EMPTY or DEAD state. Only for ALSAInput and for internal use. + void free_card(unsigned index); + + // Create a new card, mark it immediately as DEAD and hold it. + // Returns the new index. + unsigned create_dead_card(const std::string &name, const std::string &info, unsigned num_channels); + + // Make a protobuf representation of the given card, so that it can be + // matched against at a later stage. For AudioMixer only. + // The given card must be held. + void serialize_device(unsigned index, DeviceSpecProto *serialized); private: mutable std::mutex mu; std::vector devices; // Under mu. std::vector> inputs; // Under mu, corresponds 1:1 to devices. + // Keyed on device address (e.g. “hw:0,0”). If there's an entry here, + // it means we already have a thread doing retries, so we shouldn't + // start a new one. + std::unordered_map add_device_tries_left; // Under add_device_mutex. + std::mutex add_device_mutex; + + static constexpr int num_retries = 10; + + void inotify_thread_func(); void enumerate_devices(); - bool add_device(unsigned card_index, unsigned dev_index); + + // Try to add an input at the given card/device. If it succeeds, return + // synchronously. If not, fire off a background thread to try up to + // times. + void probe_device_with_retry(unsigned card_index, unsigned dev_index); + void probe_device_retry_thread_func(unsigned card_index, unsigned dev_index); + + enum class ProbeResult { + SUCCESS, + DEFER, + FAILURE + }; + ProbeResult probe_device_once(unsigned card_index, unsigned dev_index); + + void unplug_device(unsigned card_index, unsigned dev_index); + + // Must be called with held. Will allocate a new entry if needed. + // The returned entry will be set to READY state. + unsigned find_free_device_index(const std::string &name, + const std::string &info, + unsigned num_channels, + const std::string &address); + + friend class ALSAInput; }; #endif // !defined(_ALSA_INPUT_H)