[Merge] lp:~thomas-voss/media-hub/introduce-audio-output-observer-interface into lp:media-hub

Jim Hodapp jim.hodapp at canonical.com
Fri Dec 5 20:39:07 UTC 2014


Review: Needs Fixing code

A few comments below.

Diff comments:

> === modified file 'src/core/media/CMakeLists.txt'
> --- src/core/media/CMakeLists.txt	2014-11-26 12:44:06 +0000
> +++ src/core/media/CMakeLists.txt	2014-11-26 12:44:08 +0000
> @@ -91,6 +91,9 @@
>      cover_art_resolver.cpp
>      engine.cpp
>  
> +    audio/pulse_audio_output_observer.cpp
> +    audio/output_observer.cpp
> +
>      power/battery_observer.cpp
>      power/state_controller.cpp
>  
> 
> === added directory 'src/core/media/audio'
> === added file 'src/core/media/audio/output_observer.cpp'
> --- src/core/media/audio/output_observer.cpp	1970-01-01 00:00:00 +0000
> +++ src/core/media/audio/output_observer.cpp	2014-11-26 12:44:08 +0000
> @@ -0,0 +1,43 @@
> +/*
> + * Copyright © 2014 Canonical Ltd.
> + *
> + * This program is free software: you can redistribute it and/or modify it
> + * under the terms of the GNU Lesser General Public License version 3,
> + * as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + *
> + * Authored by: Thomas Voß <thomas.voss at canonical.com>
> + */
> +
> +#include <core/media/audio/output_observer.h>
> +
> +#include <core/media/audio/pulse_audio_output_observer.h>
> +
> +#include <iostream>
> +
> +namespace audio = core::ubuntu::media::audio;
> +
> +std::ostream& audio::operator<<(std::ostream& out, audio::OutputState state)
> +{
> +    switch (state)
> +    {
> +    case audio::OutputState::connected:
> +        return out << "OutputState::connected";
> +    case audio::OutputState::disconnected:
> +        return out << "OutputState::disconnected";
> +    }
> +
> +    return out;
> +}
> +
> +audio::OutputObserver::Ptr audio::make_platform_default_output_observer()
> +{
> +    return std::make_shared<audio::PulseAudioOutputObserver>(audio::PulseAudioOutputObserver::Configuration{});
> +}
> 
> === added file 'src/core/media/audio/output_observer.h'
> --- src/core/media/audio/output_observer.h	1970-01-01 00:00:00 +0000
> +++ src/core/media/audio/output_observer.h	2014-11-26 12:44:08 +0000
> @@ -0,0 +1,74 @@
> +/*
> + * Copyright © 2014 Canonical Ltd.
> + *
> + * This program is free software: you can redistribute it and/or modify it
> + * under the terms of the GNU Lesser General Public License version 3,
> + * as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + *
> + * Authored by: Thomas Voß <thomas.voss at canonical.com>
> + */
> +#ifndef CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OBSERVER_H_
> +#define CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OBSERVER_H_
> +
> +#include <core/property.h>
> +
> +#include <iosfwd>
> +#include <memory>
> +
> +namespace core
> +{
> +namespace ubuntu
> +{
> +namespace media
> +{
> +namespace audio
> +{
> +// All known states of an audio output.
> +enum class OutputState
> +{
> +    // The output is connected.
> +    connected,
> +    // The output is disconnected.
> +    disconnected,
> +};
> +
> +// Models observation of audio outputs of a device.
> +// Right now, we are only interested in monitoring the
> +// state of external outputs to react accordingly if
> +// wired or bluetooth outputs are connected/disconnected.
> +class OutputObserver
> +{
> +public:
> +    // Save us some typing.
> +    typedef std::shared_ptr<OutputObserver> Ptr;
> +
> +    virtual ~OutputObserver() = default;
> +
> +    // Getable/observable property holding the state of external outputs.
> +    virtual const core::Property<OutputState>& external_output_state() const = 0;
> +
> +protected:
> +    OutputObserver() = default;
> +    OutputObserver(const OutputObserver&) = delete;
> +    OutputObserver& operator=(const OutputObserver&) = delete;
> +};
> +
> +// Pretty prints the given state to the given output stream.
> +std::ostream& operator<<(std::ostream&, OutputState);
> +
> +// Creats a platform default instance for observing audio outputs.
> +OutputObserver::Ptr make_platform_default_output_observer();
> +}
> +}
> +}
> +}
> +
> +#endif // CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OBSERVER_H_
> 
> === added file 'src/core/media/audio/pulse_audio_output_observer.cpp'
> --- src/core/media/audio/pulse_audio_output_observer.cpp	1970-01-01 00:00:00 +0000
> +++ src/core/media/audio/pulse_audio_output_observer.cpp	2014-11-26 12:44:08 +0000
> @@ -0,0 +1,337 @@
> +/*
> + * Copyright © 2014 Canonical Ltd.
> + *
> + * This program is free software: you can redistribute it and/or modify it
> + * under the terms of the GNU Lesser General Public License version 3,
> + * as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + *
> + * Authored by: Thomas Voß <thomas.voss at canonical.com>
> + *              Ricardo Mendoza <ricardo.mendoza at canonical.com>
> + */
> +
> +#include <core/media/audio/pulse_audio_output_observer.h>
> +
> +#include <pulse/pulseaudio.h>
> +
> +#include <cstdint>
> +
> +#include <map>
> +#include <regex>
> +#include <string>
> +
> +namespace audio = core::ubuntu::media::audio;
> +
> +namespace
> +{
> +// We wrap calls to the pulseaudio client api into its
> +// own namespace and make sure that only managed types
> +// can be passed to calls to pulseaudio. In addition,
> +// we add guards to the function calls to ensure that
> +// they are conly called on the correct thread.
> +namespace pa
> +{
> +typedef std::shared_ptr<pa_threaded_mainloop> ThreadedMainLoopPtr;
> +ThreadedMainLoopPtr make_threaded_main_loop()
> +{
> +    return ThreadedMainLoopPtr
> +    {
> +        pa_threaded_mainloop_new(),
> +        [](pa_threaded_mainloop* ml)
> +        {
> +            pa_threaded_mainloop_stop(ml);
> +            pa_threaded_mainloop_free(ml);
> +        }
> +    };
> +}
> +
> +void start_main_loop(ThreadedMainLoopPtr ml)
> +{
> +    pa_threaded_mainloop_start(ml.get());
> +}
> +
> +typedef std::shared_ptr<pa_context> ContextPtr;
> +ContextPtr make_context(ThreadedMainLoopPtr main_loop)
> +{
> +    return ContextPtr
> +    {
> +        pa_context_new(pa_threaded_mainloop_get_api(main_loop.get()), "MediaHubPulseContext"),
> +        pa_context_unref
> +    };
> +}
> +
> +void set_state_callback(ContextPtr ctxt, pa_context_notify_cb_t cb, void* cookie)

I'm curious what is the point in wrapping the straight pa_context_set_state_callback function? Can you just directly use it later?

> +{
> +    pa_context_set_state_callback(ctxt.get(), cb, cookie);
> +}
> +
> +void set_subscribe_callback(ContextPtr ctxt, pa_context_subscribe_cb_t cb, void* cookie)

I'm curious what is the point in wrapping the straight pa_context_set_subscribe_callback function? Can you just directly use it later?

> +{
> +    pa_context_set_subscribe_callback(ctxt.get(), cb, cookie);
> +}
> +
> +void throw_if_not_on_main_loop(ThreadedMainLoopPtr ml)
> +{
> +    if (not pa_threaded_mainloop_in_thread(ml.get())) throw std::logic_error
> +    {
> +        "Attempted to call into a pulseaudio object from another"
> +        "thread than the pulseaudio mainloop thread."
> +    };
> +}
> +
> +void throw_if_not_connected(ContextPtr ctxt)
> +{
> +    if (pa_context_get_state(ctxt.get()) != PA_CONTEXT_READY ) throw std::logic_error
> +    {
> +        "Attempted to issue a call against pulseaudio via a non-connected context."
> +    };
> +}
> +
> +void get_server_info_async(ContextPtr ctxt, ThreadedMainLoopPtr ml, pa_server_info_cb_t cb, void* cookie)
> +{
> +    throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
> +    pa_operation_unref(pa_context_get_server_info(ctxt.get(), cb, cookie));
> +}
> +
> +void subscribe_to_events(ContextPtr ctxt, ThreadedMainLoopPtr ml, pa_subscription_mask mask)
> +{
> +    throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
> +    pa_operation_unref(pa_context_subscribe(ctxt.get(), mask, nullptr, nullptr));
> +}
> +
> +void get_index_of_sink_by_name_async(ContextPtr ctxt, ThreadedMainLoopPtr ml, const std::string& name, pa_sink_info_cb_t cb, void* cookie)
> +{
> +    throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
> +    pa_operation_unref(pa_context_get_sink_info_by_name(ctxt.get(), name.c_str(), cb, cookie));
> +}
> +
> +void get_sink_info_by_index_async(ContextPtr ctxt, ThreadedMainLoopPtr ml, std::uint32_t index, pa_sink_info_cb_t cb, void* cookie)
> +{
> +    throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
> +    pa_operation_unref(pa_context_get_sink_info_by_index(ctxt.get(), index, cb, cookie));
> +}
> +
> +void connect_async(ContextPtr ctxt)
> +{
> +    pa_context_connect(ctxt.get(), nullptr, static_cast<pa_context_flags_t>(PA_CONTEXT_NOAUTOSPAWN | PA_CONTEXT_NOFAIL), nullptr);
> +}
> +
> +bool is_port_available_on_sink(const pa_sink_info* info, const std::regex& port_pattern)
> +{
> +    if (not info)
> +        return false;
> +
> +    for (std::uint32_t i = 0; i < info->n_ports; i++)
> +    {
> +        if (info->ports[i]->available == PA_PORT_AVAILABLE_NO)
> +            continue;
> +
> +        if (std::regex_match(std::string{info->ports[i]->name}, port_pattern))
> +            return true;
> +    }
> +
> +    return false;
> +}
> +}
> +}
> +
> +struct audio::PulseAudioOutputObserver::Private
> +{
> +    static void context_notification_cb(pa_context* ctxt, void* cookie)
> +    {
> +        if (auto thiz = static_cast<Private*>(cookie))
> +        {
> +            // Better safe than sorry: Check if we got signaled for the
> +            // context we are actually interested in.
> +            if (thiz->context.get() != ctxt)
> +                return;
> +
> +            switch (pa_context_get_state(ctxt))
> +            {
> +            case PA_CONTEXT_READY:
> +                thiz->on_context_ready();
> +                break;
> +            case PA_CONTEXT_FAILED:
> +                thiz->on_context_failed();
> +                break;
> +            default:
> +                break;
> +            }
> +        }
> +    }
> +
> +    static void context_subscription_cb(pa_context* ctxt, pa_subscription_event_type_t ev, uint32_t idx, void* cookie)
> +    {
> +        (void) idx;
> +
> +        if (auto thiz = static_cast<Private*>(cookie))
> +        {
> +            // Better safe than sorry: Check if we got signaled for the
> +            // context we are actually interested in.
> +            if (thiz->context.get() != ctxt)
> +                return;
> +
> +            if ((ev & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
> +                thiz->on_sink_event_with_index(idx);
> +        }
> +    }
> +
> +    static void query_for_primary_sink_finished(pa_context* ctxt, const pa_sink_info* si, int eol, void* cookie)
> +    {
> +        if (eol)
> +            return;
> +
> +        if (auto thiz = static_cast<Private*>(cookie))
> +        {
> +            // Better safe than sorry: Check if we got signaled for the
> +            // context we are actually interested in.
> +            if (thiz->context.get() != ctxt)
> +                return;
> +
> +            thiz->on_query_for_sink_finished(si);
> +        }
> +    }
> +
> +    static void query_for_server_info_finished(pa_context* ctxt, const pa_server_info* si, void* cookie)
> +    {
> +        if (not si)
> +            return;
> +
> +        if (auto thiz = static_cast<Private*>(cookie))
> +        {
> +            // Better safe than sorry: Check if we got signaled for the
> +            // context we are actually interested in.
> +            if (thiz->context.get() != ctxt)
> +                return;
> +
> +            thiz->on_query_for_server_info_finished(si);
> +        }
> +    }
> +
> +    Private(const audio::PulseAudioOutputObserver::Configuration& config)
> +        : config(config),
> +          main_loop{pa::make_threaded_main_loop()},
> +          context{pa::make_context(main_loop)}
> +    {
> +        for (const auto& pattern : config.output_port_patterns)
> +        {
> +            outputs.emplace_back(pattern, core::Property<media::audio::OutputState>{media::audio::OutputState::disconnected});
> +            std::get<1>(outputs.back()) | properties.external_output_state;
> +        }
> +
> +        pa::set_state_callback(context, Private::context_notification_cb, this);
> +        pa::set_subscribe_callback(context, Private::context_subscription_cb, this);
> +
> +        pa::connect_async(context);
> +        pa::start_main_loop(main_loop);
> +    }
> +
> +    // The connection attempt has been successful and we are connected
> +    // to pulseaudio now.
> +    void on_context_ready()
> +    {
> +        pa::subscribe_to_events(context, main_loop, PA_SUBSCRIPTION_MASK_SINK);
> +
> +        if (config.sink == "query.from.server")
> +        {
> +            pa::get_server_info_async(context, main_loop, Private::query_for_server_info_finished, this);

Add a try/catch around this since get_server_info_async throws an exception. I don't believe we want the exception bubbling up.

> +        }
> +        else
> +        {
> +            properties.sink = config.sink;
> +            pa::get_index_of_sink_by_name_async(context, main_loop, config.sink, Private::query_for_primary_sink_finished, this);

Add a try/catch around this since get_index_of_sink_by_name_async throws an exception. I don't believe we want the exception bubbling up.

> +        }
> +    }
> +
> +    // Either a connection attempt failed, or an existing connection
> +    // was unexpectedly terminated.
> +    void on_context_failed()
> +    {
> +        pa::connect_async(context);
> +    }
> +
> +    // Something changed on the sink with index idx.
> +    void on_sink_event_with_index(std::int32_t index)
> +    {
> +        if (index != sink_index)
> +            return;
> +
> +        pa::get_sink_info_by_index_async(context, main_loop, index, Private::query_for_primary_sink_finished, this);

Add a try/catch around this since get_sink_info_by_index_async throws an exception. I don't believe we want the exception bubbling up.

> +    }
> +
> +    // Query for primary sink finished.
> +    void on_query_for_sink_finished(const pa_sink_info* info)
> +    {
> +        for (auto& element : outputs)
> +        {
> +            std::get<1>(element) = pa::is_port_available_on_sink(info, std::get<0>(element))
> +                    ? media::audio::OutputState::connected
> +                    : media::audio::OutputState::disconnected;
> +        }
> +
> +        std::set<std::string> known_ports;
> +        for (std::uint32_t i = 0; i < info->n_ports; i++)
> +            known_ports.insert(info->ports[i]->name);
> +        properties.known_ports = known_ports;
> +
> +        sink_index = info->index;
> +    }
> +
> +    void on_query_for_server_info_finished(const pa_server_info* info)
> +    {
> +        // We bail out if we could not determine the default sink name.
> +        // In this case, we are not able to carry out audio output observation.
> +        if (not info->default_sink_name)
> +            return;
> +
> +        properties.sink = config.sink = info->default_sink_name;
> +        pa::get_index_of_sink_by_name_async(context, main_loop, config.sink, Private::query_for_primary_sink_finished, this);

Add a try/catch around this since get_index_of_sink_by_name_async throws an exception. I don't believe we want the exception bubbling up.

> +    }
> +
> +    PulseAudioOutputObserver::Configuration config;    
> +    pa::ThreadedMainLoopPtr main_loop;
> +    pa::ContextPtr context;
> +    std::uint32_t sink_index;
> +    std::vector<std::tuple<std::regex, core::Property<media::audio::OutputState>>> outputs;
> +
> +    struct
> +    {
> +        core::Property<std::string> sink;
> +        core::Property<std::set<std::string>> known_ports;
> +        core::Property<audio::OutputState> external_output_state{audio::OutputState::disconnected};
> +    } properties;
> +};
> +
> +// Constructs a new instance, or throws std::runtime_error
> +// if connection to pulseaudio fails.
> +audio::PulseAudioOutputObserver::PulseAudioOutputObserver(const Configuration& config) : d{new Private{config}}
> +{
> +}
> +
> +// We provide the name of the sink we are connecting to as a
> +// getable/observable property. This is specifically meant for
> +// consumption by test code.
> +const core::Property<std::string>& audio::PulseAudioOutputObserver::sink() const
> +{
> +    return d->properties.sink;
> +}
> +
> +// The set of ports that have been identified on the configured sink.
> +// Specifically meant for consumption by test code.
> +const core::Property<std::set<std::string>>& audio::PulseAudioOutputObserver::known_ports() const
> +{
> +    return d->properties.known_ports;
> +}
> +
> +// Getable/observable property holding the state of external outputs.
> +const core::Property<audio::OutputState>& audio::PulseAudioOutputObserver::external_output_state() const
> +{
> +    return d->properties.external_output_state;
> +}
> 
> === added file 'src/core/media/audio/pulse_audio_output_observer.h'
> --- src/core/media/audio/pulse_audio_output_observer.h	1970-01-01 00:00:00 +0000
> +++ src/core/media/audio/pulse_audio_output_observer.h	2014-11-26 12:44:08 +0000
> @@ -0,0 +1,87 @@
> +/*
> + * Copyright © 2014 Canonical Ltd.
> + *
> + * This program is free software: you can redistribute it and/or modify it
> + * under the terms of the GNU Lesser General Public License version 3,
> + * as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + *
> + * Authored by: Thomas Voß <thomas.voss at canonical.com>
> + *              Ricardo Mendoza <ricardo.mendoza at canonical.com>
> + */
> +#ifndef CORE_UBUNTU_MEDIA_AUDIO_PULSE_AUDIO_OUTPUT_OBSERVER_H_
> +#define CORE_UBUNTU_MEDIA_AUDIO_PULSE_AUDIO_OUTPUT_OBSERVER_H_
> +
> +#include <core/media/audio/output_observer.h>
> +
> +#include <iosfwd>
> +#include <memory>
> +#include <regex>
> +
> +namespace core
> +{
> +namespace ubuntu
> +{
> +namespace media
> +{
> +namespace audio
> +{
> +// Implements the audio::OutputObserver interface
> +// relying on pulse to query the connected ports
> +// of the primary card of the system.
> +class PulseAudioOutputObserver : public OutputObserver
> +{
> +public:
> +    // Save us some typing.
> +    typedef std::shared_ptr<PulseAudioOutputObserver> Ptr;
> +
> +    // Construction time arguments go here
> +    struct Configuration
> +    {
> +        // Name of the sink that we should consider.
> +        std::string sink
> +        {
> +            // A special value that requests the implementation to
> +            // query pulseaudio for the default configured sink.
> +            "query.from.server"
> +        };
> +        // Output port name patterns that should be observed on the configured sink.
> +        // All patterns have to be valid regular expressions.
> +        std::vector<std::regex> output_port_patterns
> +        {
> +            // Any port is considered with this special value.
> +            std::regex{".+"}
> +        };
> +    };
> +
> +    // Constructs a new instance, or throws std::runtime_error
> +    // if connection to pulseaudio fails.
> +    PulseAudioOutputObserver(const Configuration&);
> +
> +    // We provide the name of the sink we are connecting to as a
> +    // getable/observable property. This is specifically meant for
> +    // consumption by test code.
> +    const core::Property<std::string>& sink() const;
> +    // The set of ports that have been identified on the configured sink.
> +    // Specifically meant for consumption by test code.
> +    const core::Property<std::set<std::string>>& known_ports() const;
> +    // Getable/observable property holding the state of external outputs.
> +    const core::Property<OutputState>& external_output_state() const override;
> +
> +private:
> +    struct Private;
> +    std::shared_ptr<Private> d;
> +};
> +}
> +}
> +}
> +}
> +
> +#endif // CORE_UBUNTU_MEDIA_AUDIO_PULSE_AUDIO_OUTPUT_OBSERVER_H_
> 
> === modified file 'src/core/media/service_implementation.cpp'
> --- src/core/media/service_implementation.cpp	2014-11-26 12:44:06 +0000
> +++ src/core/media/service_implementation.cpp	2014-11-26 12:44:08 +0000
> @@ -22,6 +22,7 @@
>  
>  #include "service_implementation.h"
>  
> +#include "audio/output_observer.h"
>  #include "client_death_observer.h"
>  #include "call-monitor/call_monitor.h"
>  #include "player_configuration.h"
> @@ -57,93 +58,9 @@
>            display_state_lock(power_state_controller->display_state_lock()),
>            client_death_observer(media::platform_default_client_death_observer()),
>            recorder_observer(media::make_platform_default_recorder_observer()),
> -          pulse_mainloop_api(nullptr),
> -          pulse_context(nullptr),
> -          headphones_connected(false),
> -          a2dp_connected(false),
> -          primary_idx(-1),
> +          audio_output_observer(media::audio::make_platform_default_output_observer()),
>            call_monitor(new CallMonitor)
>      {
> -        // Spawn pulse watchdog
> -        pulse_mainloop = nullptr;
> -        pulse_worker = std::move(std::thread([this]()
> -        {
> -            std::unique_lock<std::mutex> lk(pulse_mutex);
> -            pcv.wait(lk,
> -                [this]{
> -                    if (pulse_mainloop != nullptr || pulse_context != nullptr)
> -                    {
> -                        // We come from instance death, destroy and create.
> -                        if (pulse_context != nullptr)
> -                        {
> -                            pa_threaded_mainloop_lock(pulse_mainloop);
> -                            pa_operation *o;
> -                            o = pa_context_drain(pulse_context,
> -                                [](pa_context *context, void *userdata)
> -                                {
> -                                    (void) context;
> -
> -                                    Private *p = reinterpret_cast<Private*>(userdata);
> -                                    pa_threaded_mainloop_signal(p->mainloop(), 0);
> -                                }, this);
> -                            
> -                            if (o)
> -                            {
> -                                while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
> -                                    pa_threaded_mainloop_wait(pulse_mainloop);
> -
> -                                pa_operation_unref(o);
> -                            }
> -
> -                            pa_context_set_state_callback(pulse_context, NULL, NULL);
> -                            pa_context_set_subscribe_callback(pulse_context, NULL, NULL);
> -                            pa_context_disconnect(pulse_context);
> -                            pa_context_unref(pulse_context);
> -                            pulse_context = nullptr;
> -                            pa_threaded_mainloop_unlock(pulse_mainloop);
> -                        }
> -                    }
> -        
> -                    if (pulse_mainloop == nullptr)
> -                    {
> -                        pulse_mainloop = pa_threaded_mainloop_new();
> -
> -                        if (pa_threaded_mainloop_start(pulse_mainloop) != 0)
> -                        {
> -                            std::cerr << "Unable to start pulseaudio mainloop, audio output detection will not function" << std::endl;
> -                            pa_threaded_mainloop_free(pulse_mainloop);
> -                            pulse_mainloop = nullptr;
> -                        }
> -                    }
> -                  
> -                    do {
> -                        create_pulse_context();
> -                    } while (pulse_context == nullptr);
> -                    
> -                    // Wait for next instance death.
> -                    return false;
> -                });
> -        }));
> -        
> -        recorder_observer->recording_state().changed().connect([this](media::RecordingState state)
> -        {
> -            media_recording_state_changed(state);
> -        });
> -    }
> -
> -    ~Private()
> -    {
> -        release_pulse_context();
> -
> -        if (pulse_mainloop != nullptr)
> -        {
> -            pa_threaded_mainloop_stop(pulse_mainloop);
> -            pa_threaded_mainloop_free(pulse_mainloop);
> -            pulse_mainloop = nullptr;
> -        }
> -
> -        if (pulse_worker.joinable())
> -            pulse_worker.join();
>      }
>  
>      void media_recording_state_changed(media::RecordingState state)
> @@ -154,249 +71,6 @@
>              display_state_lock->request_release(media::power::DisplayState::off);
>      }
>  
> -    pa_threaded_mainloop *mainloop()
> -    {
> -        return pulse_mainloop;
> -    }
> -
> -    bool is_port_available(pa_card_port_info **ports, uint32_t n_ports, const char *name)
> -    {
> -        bool ret = false;
> -
> -        if (ports != nullptr && n_ports > 0 && name != nullptr)
> -        {
> -            for (uint32_t i=0; i<n_ports; i++)
> -            {
> -                if (strstr(ports[i]->name, name) != nullptr && ports[i]->available != PA_PORT_AVAILABLE_NO)
> -                {
> -                    ret = true;
> -                    break;
> -                }
> -            }
> -        }
> -
> -        return ret;
> -    }
> -
> -    void update_wired_output()
> -    {
> -        const pa_operation *o = pa_context_get_card_info_by_index(pulse_context, primary_idx,
> -                [](pa_context *context, const pa_card_info *info, int eol, void *userdata)
> -                {
> -                    (void) context;
> -                    (void) eol;
> -
> -                    if (info == nullptr || userdata == nullptr)
> -                        return;
> -
> -                    Private *p = reinterpret_cast<Private*>(userdata);
> -                    if (p->is_port_available(info->ports, info->n_ports, "output-wired"))
> -                    {
> -                        if (!p->headphones_connected)
> -                            std::cout << "Wired headphones connected" << std::endl;
> -                        p->headphones_connected = true;
> -                    }
> -                    else if (p->headphones_connected == true)
> -                    {
> -                        std::cout << "Wired headphones disconnected" << std::endl;
> -                        p->headphones_connected = false;
> -                        p->pause_playback_if_necessary(std::get<0>(p->active_sink));
> -                    }
> -                }, this);
> -        (void) o;
> -    }
> -
> -    void pause_playback_if_necessary(int index)
> -    {
> -        // Catch uninitialized case (active index == -1)
> -        if (std::get<0>(active_sink) == -1)
> -            return;
> -
> -        if (headphones_connected)
> -            return;
> -
> -        // No headphones/fallback on primary sink? Pause.
> -        if (index == primary_idx)
> -            pause_playback();
> -    }
> -
> -    void set_active_sink(const char *name)
> -    {
> -        const pa_operation *o = pa_context_get_sink_info_by_name(pulse_context, name,
> -                [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
> -                {
> -                    (void) context;
> -
> -                    if (eol)
> -                        return;
> -
> -                    Private *p = reinterpret_cast<Private*>(userdata); 
> -                    std::tuple<uint32_t, uint32_t, std::string> new_sink(std::make_tuple(i->index, i->card, i->name));
> -
> -                    printf("pulsesink: active_sink=('%s',%d,%d) -> ('%s',%d,%d)\n",
> -                        std::get<2>(p->active_sink).c_str(), std::get<0>(p->active_sink),
> -                        std::get<1>(p->active_sink), i->name, i->index, i->card);
> -
> -                    p->pause_playback_if_necessary(i->index);
> -                    p->active_sink = new_sink;
> -                }, this);
> -     
> -        (void) o;
> -    }
> -
> -    void update_active_sink()
> -    {
> -        const pa_operation *o = pa_context_get_server_info(pulse_context,
> -                [](pa_context *context, const pa_server_info *i, void *userdata)
> -                {
> -                    (void) context;
> -
> -                    Private *p = reinterpret_cast<Private*>(userdata);
> -                    if (i->default_sink_name != std::get<2>(p->active_sink))
> -                        p->set_active_sink(i->default_sink_name);
> -                    p->update_wired_output();
> -                }, this);
> -
> -        (void) o;
> -    }
> -
> -    void create_pulse_context()
> -    {
> -        if (pulse_context != nullptr)
> -            return;
> -        
> -        active_sink = std::make_tuple(-1, -1, "");
> -
> -        bool keep_going = true, ok = true;
> -
> -        pulse_mainloop_api = pa_threaded_mainloop_get_api(pulse_mainloop);
> -        pa_threaded_mainloop_lock(pulse_mainloop);
> -
> -        pulse_context = pa_context_new(pulse_mainloop_api, "MediaHubPulseContext");
> -        pa_context_set_state_callback(pulse_context,
> -                [](pa_context *context, void *userdata)
> -                {
> -                    (void) context;
> -                    Private *p = reinterpret_cast<Private*>(userdata);
> -                    // Signals the pa_threaded_mainloop_wait below to proceed
> -                    pa_threaded_mainloop_signal(p->mainloop(), 0);
> -                }, this);
> -
> -        if (pulse_context == nullptr)
> -        {
> -            std::cerr << "Unable to create new pulseaudio context" << std::endl;
> -            pa_threaded_mainloop_unlock(pulse_mainloop);
> -            return;
> -        }
> -
> -        pa_context_connect(pulse_context, nullptr, pa_context_flags_t((int) PA_CONTEXT_NOAUTOSPAWN | (int) PA_CONTEXT_NOFAIL), nullptr); 
> -        pa_threaded_mainloop_wait(pulse_mainloop);
> -
> -        while (keep_going)
> -        {
> -            switch (pa_context_get_state(pulse_context))
> -            {
> -                case PA_CONTEXT_CONNECTING: // Wait for service to be available
> -                case PA_CONTEXT_AUTHORIZING:
> -                case PA_CONTEXT_SETTING_NAME:
> -                    break;
> -
> -                case PA_CONTEXT_READY:
> -                    std::cout << "Pulseaudio connection established." << std::endl;
> -                    keep_going = false;
> -                    break;
> -
> -                case PA_CONTEXT_FAILED:
> -                case PA_CONTEXT_TERMINATED:
> -                    keep_going = false;
> -                    ok = false;
> -                    break;
> -
> -                default:
> -                    std::cerr << "Pulseaudio connection failure: " << pa_strerror(pa_context_errno(pulse_context));
> -                    keep_going = false;
> -                    ok = false;
> -            }
> -
> -            if (keep_going)
> -                pa_threaded_mainloop_wait(pulse_mainloop);
> -        }
> -
> -        if (ok)
> -        {
> -            pa_context_set_state_callback(pulse_context,
> -                    [](pa_context *context, void *userdata)
> -                    {
> -                        (void) context;
> -                        (void) userdata;
> -                        Private *p = reinterpret_cast<Private*>(userdata);
> -                        std::unique_lock<std::mutex> lk(p->pulse_mutex);
> -                        switch (pa_context_get_state(context))
> -                        {
> -                            case PA_CONTEXT_FAILED:
> -                            case PA_CONTEXT_TERMINATED:
> -                                p->pcv.notify_all();
> -                                break;
> -                            default:
> -                                break;
> -                        }
> -                    }, this);
> -
> -            //FIXME: Get index for "sink.primary", the default onboard card on Touch.
> -            pa_context_get_sink_info_by_name(pulse_context, "sink.primary",
> -                    [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
> -                    {
> -                        (void) context;
> -
> -                        if (eol)
> -                            return;
> -                        
> -                        Private *p = reinterpret_cast<Private*>(userdata);
> -                        p->primary_idx = i->index;
> -                        p->update_wired_output();
> -                    }, this);
> -            
> -            update_active_sink(); 
> -
> -            pa_context_set_subscribe_callback(pulse_context,
> -                    [](pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
> -                    {
> -                        (void) context;
> -                        (void) idx;
> -
> -                        if (userdata == nullptr)
> -                            return;
> -
> -                        Private *p = reinterpret_cast<Private*>(userdata);
> -                        if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
> -                        {
> -                            p->update_active_sink(); 
> -                        }
> -                    }, this);
> -            pa_context_subscribe(pulse_context, PA_SUBSCRIPTION_MASK_SINK, nullptr, this);
> -        }
> -        else
> -        {
> -            std::cerr << "Connection to pulseaudio failed or was dropped." << std::endl;
> -            pa_context_unref(pulse_context);
> -            pulse_context = nullptr;
> -        }
> -
> -        pa_threaded_mainloop_unlock(pulse_mainloop);
> -    }
> -
> -    void release_pulse_context()
> -    {
> -        if (pulse_context != nullptr)
> -        {
> -            pa_threaded_mainloop_lock(pulse_mainloop);
> -            pa_context_disconnect(pulse_context);
> -            pa_context_unref(pulse_context);
> -            pa_threaded_mainloop_unlock(pulse_mainloop);
> -            pulse_context = nullptr;
> -        }
> -    }
> -
>      media::ServiceImplementation::Configuration configuration;
>      // This holds the key of the multimedia role Player instance that was paused
>      // when the battery level reached 10% or 5%
> @@ -406,21 +80,8 @@
>      media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock;
>      media::ClientDeathObserver::Ptr client_death_observer;
>      media::RecorderObserver::Ptr recorder_observer;
> -    // Pulse-specific
> -    pa_mainloop_api *pulse_mainloop_api;
> -    pa_threaded_mainloop *pulse_mainloop;
> -    pa_context *pulse_context;
> -    std::thread pulse_worker;
> -    std::mutex pulse_mutex;
> -    std::condition_variable pcv;
> -    bool headphones_connected;
> -    bool a2dp_connected;
> -    std::tuple<int, int, std::string> active_sink;
> -    int primary_idx;
> +    media::audio::OutputObserver::Ptr audio_output_observer;
>  
> -    // Gets signaled when both the headphone jack is removed or an A2DP device is
> -    // disconnected and playback needs pausing
> -    core::Signal<void> pause_playback;
>      std::unique_ptr<CallMonitor> call_monitor;
>      std::list<media::Player::PlayerKey> paused_sessions;
>  };
> @@ -450,10 +111,16 @@
>              resume_multimedia_session();
>      });
>  
> -    d->pause_playback.connect([this]()
> +    d->audio_output_observer->external_output_state().changed().connect([this](audio::OutputState state)
>      {
> -        std::cout << "Got pause_playback signal, pausing all multimedia sessions" << std::endl;
> -        pause_all_multimedia_sessions();
> +        switch (state)
> +        {
> +        case audio::OutputState::connected:
> +            break;
> +        case audio::OutputState::disconnected:
> +            pause_all_multimedia_sessions();
> +            break;
> +        }
>      });
>  
>      d->call_monitor->on_change([this](CallMonitor::State state) {
> 


-- 
https://code.launchpad.net/~thomas-voss/media-hub/introduce-audio-output-observer-interface/+merge/242914
Your team Ubuntu Phablet Team is subscribed to branch lp:media-hub.



More information about the Ubuntu-reviews mailing list