[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