[Merge] lp:~phablet-team/media-hub/introduce-audio-output-observer-interface into lp:media-hub
Thomas Voß
thomas.voss at canonical.com
Wed Mar 4 14:08:56 UTC 2015
Review: Needs Fixing
A minor niggle inline.
Diff comments:
> === modified file 'src/core/media/CMakeLists.txt'
> --- src/core/media/CMakeLists.txt 2015-03-03 18:07:53 +0000
> +++ src/core/media/CMakeLists.txt 2015-03-03 18:07:53 +0000
> @@ -91,6 +91,10 @@
> cover_art_resolver.cpp
> engine.cpp
>
> + audio/pulse_audio_output_observer.cpp
> + audio/ostream_reporter.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/ostream_reporter.cpp'
> --- src/core/media/audio/ostream_reporter.cpp 1970-01-01 00:00:00 +0000
> +++ src/core/media/audio/ostream_reporter.cpp 2015-03-03 18:07:53 +0000
> @@ -0,0 +1,54 @@
> +/*
> + * 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/ostream_reporter.h>
> +
> +namespace audio = core::ubuntu::media::audio;
> +
> +audio::OStreamReporter::OStreamReporter(std::ostream &out) : out{out}
> +{
> +}
> +
> +void audio::OStreamReporter::connected_to_pulse_audio()
> +{
> + out << "Connection to PulseAudio has been successfully established." << std::endl;
> +}
> +
> +void audio::OStreamReporter::query_for_default_sink_failed()
> +{
> + out << "Query for default sink failed." << std::endl;
> +}
> +
> +void audio::OStreamReporter::query_for_default_sink_finished(const std::string& sink_name)
> +{
> + out << "Default PulseAudio sync has been identified: " << sink_name << std::endl;
> +}
> +
> +void audio::OStreamReporter::query_for_sink_info_finished(const std::string& name, std::uint32_t index, const std::set<Port>& known_ports)
> +{
> + out << "PulseAudio sink details for " << name << " with index " << index << " is available:" << std::endl;
> + for (const auto& port : known_ports)
> + {
> + if (port.is_monitored)
> + out << " " << port.description << ": " << std::boolalpha << port.is_available << "\n";
> + }
> +}
> +void audio::OStreamReporter::sink_event_with_index(std::uint32_t index)
> +{
> + out << "PulseAudio event for sink with index " << index << " received." << std::endl;
> +}
>
> === added file 'src/core/media/audio/ostream_reporter.h'
> --- src/core/media/audio/ostream_reporter.h 1970-01-01 00:00:00 +0000
> +++ src/core/media/audio/ostream_reporter.h 2015-03-03 18:07:53 +0000
> @@ -0,0 +1,55 @@
> +/*
> + * 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_OSTREAM_REPORTER_H_
> +#define CORE_UBUNTU_MEDIA_AUDIO_OSTREAM_REPORTER_H_
> +
> +#include <core/media/audio/pulse_audio_output_observer.h>
> +
> +#include <iosfwd>
> +
> +namespace core
> +{
> +namespace ubuntu
> +{
> +namespace media
> +{
> +namespace audio
> +{
> +// A PulseAudioOutputObserver::Reporter implementation printing events to
> +// the configured output stream.
> +class OStreamReporter : public PulseAudioOutputObserver::Reporter
> +{
> +public:
> + // Constructs a new reporter instance, outputting events to the given stream.
> + OStreamReporter(std::ostream& out = std::cout);
> +
> + void connected_to_pulse_audio() override;
> + void query_for_default_sink_failed() override;
> + void query_for_default_sink_finished(const std::string& sink_name) override;
> + void query_for_sink_info_finished(const std::string& name, std::uint32_t index, const std::set<Port>& known_ports) override;
> + void sink_event_with_index(std::uint32_t index) override;
> +
> +private:
> + std::ostream& out;
> +};
> +}
> +}
> +}
> +}
> +
> +#endif // CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OSTREAM_REPORTER_H_
>
> === 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 2015-03-03 18:07:53 +0000
> @@ -0,0 +1,48 @@
> +/*
> + * 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 <core/media/audio/ostream_reporter.h>
> +
> +#include <iostream>
> +
> +namespace audio = core::ubuntu::media::audio;
> +
> +std::ostream& audio::operator<<(std::ostream& out, audio::OutputState state)
> +{
> + switch (state)
> + {
> + case audio::OutputState::Private:
> + return out << "OutputState::Private";
> + case audio::OutputState::Public:
> + return out << "OutputState::Public";
> + }
> +
> + return out;
> +}
> +
> +audio::OutputObserver::Ptr audio::make_platform_default_output_observer()
> +{
> + audio::PulseAudioOutputObserver::Configuration config;
> + config.sink = "sink.primary";
> + config.output_port_patterns = {std::regex{"output-wired_head.*|output-a2dp_headphones"}};
> + config.reporter = std::make_shared<audio::OStreamReporter>();
> + return std::make_shared<audio::PulseAudioOutputObserver>(config);
> +}
>
> === 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 2015-03-03 18:07:53 +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 via a Private device (i.e. Headphones).
> + Private,
As private/public as member names are not possible, could we name them {private, public}_output?
> + // The output is via a Public device (i.e. Speaker).
> + Public,
> +};
> +
> +// 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 2015-03-03 18:07:53 +0000
> @@ -0,0 +1,457 @@
> +/*
> + * 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)
> +{
> + pa_context_set_state_callback(ctxt.get(), cb, cookie);
> +}
> +
> +void set_subscribe_callback(ContextPtr ctxt, pa_context_subscribe_cb_t cb, void* cookie)
> +{
> + 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::int32_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 ||
> + info->ports[i]->available == PA_PORT_AVAILABLE_UNKNOWN)
> + 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_active_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_active_sink_finished(si);
> + }
> + }
> +
> + 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_primary_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::Public});
> + std::get<1>(outputs.back()) | properties.external_output_state;
> + std::get<1>(outputs.back()).changed().connect([](media::audio::OutputState state)
> + {
> + std::cout << "Connection state for port changed to: " << state << std::endl;
> + });
> + }
> +
> + 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()
> + {
> + config.reporter->connected_to_pulse_audio();
> +
> + 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);
> + }
> + else
> + {
> + properties.sink = config.sink;
> + // Get primary sink index (default)
> + pa::get_index_of_sink_by_name_async(context, main_loop, config.sink, Private::query_for_primary_sink_finished, this);
> + // Update active sink (could be == default)
> + pa::get_server_info_async(context, main_loop, Private::query_for_server_info_finished, this);
> + }
> + }
> +
> + // 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)
> + {
> + config.reporter->sink_event_with_index(index);
> +
> + // Update server info (active sink)
> + pa::get_server_info_async(context, main_loop, Private::query_for_server_info_finished, this);
> +
> + }
> +
> + void on_query_for_active_sink_finished(const pa_sink_info* info)
> + {
> + // Update active sink if a change is registered.
> + if (std::get<0>(active_sink) != info->index)
> + {
> + std::get<0>(active_sink) = info->index;
> + std::get<1>(active_sink) = info->name;
> + if (info->index != static_cast<std::uint32_t>(primary_sink_index))
> + for (auto& element : outputs)
> + std::get<1>(element) = audio::OutputState::Public;
> + }
> + }
> +
> + // Query for primary sink finished.
> + void on_query_for_primary_sink_finished(const pa_sink_info* info)
> + {
> + for (auto& element : outputs)
> + {
> + std::cout << "Checking if port is available " << " -> " << std::boolalpha << pa::is_port_available_on_sink(info, std::get<0>(element)) << std::endl;
> + audio::OutputState state = pa::is_port_available_on_sink(info, std::get<0>(element))
> + ? media::audio::OutputState::Private
> + : media::audio::OutputState::Public;
> +
> + // Only issue state change if the change happened on the active index.
> + if (std::get<0>(active_sink) != info->index)
> + continue;
> +
> + std::get<1>(element) = state;
> +
> + }
> +
> + std::set<Reporter::Port> known_ports;
> + for (std::uint32_t i = 0; i < info->n_ports; i++)
> + {
> + bool is_monitored = false;
> +
> + for (auto& element : outputs)
> + is_monitored |= std::regex_match(info->ports[i]->name, std::get<0>(element));
> +
> +
> + known_ports.insert(Reporter::Port
> + {
> + info->ports[i]->name,
> + info->ports[i]->description,
> + info->ports[i]->available == PA_PORT_AVAILABLE_YES,
> + is_monitored
> + });
> + }
> +
> + properties.known_ports = known_ports;
> +
> + // Initialize sink of primary index (onboard)
> + if (primary_sink_index == -1)
> + primary_sink_index = info->index;
> +
> + config.reporter->query_for_sink_info_finished(info->name, info->index, known_ports);
> + }
> +
> + 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)
> + {
> + config.reporter->query_for_default_sink_failed();
> + return;
> + }
> +
> + // Update active sink
> + if (info->default_sink_name != std::get<1>(active_sink))
> + pa::get_index_of_sink_by_name_async(context, main_loop, info->default_sink_name, Private::query_for_active_sink_finished, this);
> +
> + // Update wired output for primary sink (onboard)
> + pa::get_sink_info_by_index_async(context, main_loop, primary_sink_index, Private::query_for_primary_sink_finished, this);
> +
> + if (properties.sink.get() != config.sink)
> + {
> + config.reporter->query_for_default_sink_finished(info->default_sink_name);
> + 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);
> + }
> + }
> +
> + PulseAudioOutputObserver::Configuration config;
> + pa::ThreadedMainLoopPtr main_loop;
> + pa::ContextPtr context;
> + std::int32_t primary_sink_index;
> + std::tuple<uint32_t, std::string> active_sink;
> + std::vector<std::tuple<std::regex, core::Property<media::audio::OutputState>>> outputs;
> +
> + struct
> + {
> + core::Property<std::string> sink;
> + core::Property<std::set<audio::PulseAudioOutputObserver::Reporter::Port>> known_ports;
> + core::Property<audio::OutputState> external_output_state{audio::OutputState::Public};
> + } properties;
> +};
> +
> +bool audio::PulseAudioOutputObserver::Reporter::Port::operator==(const audio::PulseAudioOutputObserver::Reporter::Port& rhs) const
> +{
> + return name == rhs.name;
> +}
> +
> +bool audio::PulseAudioOutputObserver::Reporter::Port::operator<(const audio::PulseAudioOutputObserver::Reporter::Port& rhs) const
> +{
> + return name < rhs.name;
> +}
> +
> +audio::PulseAudioOutputObserver::Reporter::~Reporter()
> +{
> +}
> +
> +void audio::PulseAudioOutputObserver::Reporter::connected_to_pulse_audio()
> +{
> +}
> +
> +void audio::PulseAudioOutputObserver::Reporter::query_for_default_sink_failed()
> +{
> +}
> +
> +void audio::PulseAudioOutputObserver::Reporter::query_for_default_sink_finished(const std::string&)
> +{
> +}
> +
> +void audio::PulseAudioOutputObserver::Reporter::query_for_sink_info_finished(const std::string&, std::uint32_t, const std::set<Port>&)
> +{
> +}
> +
> +void audio::PulseAudioOutputObserver::Reporter::sink_event_with_index(std::uint32_t)
> +{
> +}
> +
> +// Constructs a new instance, or throws std::runtime_error
> +// if connection to pulseaudio fails.
> +audio::PulseAudioOutputObserver::PulseAudioOutputObserver(const Configuration& config) : d{new Private{config}}
> +{
> + if (not d->config.reporter) throw std::runtime_error
> + {
> + "PulseAudioOutputObserver: Cannot construct for invalid reporter instance."
> + };
> +}
> +
> +// 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<audio::PulseAudioOutputObserver::Reporter::Port>>& 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 2015-03-03 18:07:53 +0000
> @@ -0,0 +1,128 @@
> +/*
> + * 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;
> +
> + // Reporter is responsible for surfacing events from the implementation
> + // that help in resolving/tracking down issues. Default implementation is empty.
> + struct Reporter
> + {
> + // To save us some typing.
> + typedef std::shared_ptr<Reporter> Ptr;
> +
> + // Simple type to help in reporting.
> + struct Port
> + {
> + // Returns true iff the name of both ports are equal.
> + bool operator==(const Port& rhs) const;
> + // Returns true iff the name of the ports differ.
> + bool operator<(const Port& rhs) const;
> +
> + std::string name; // The name of the port.
> + std::string description; // Human-readable description of the port.
> + bool is_available; // True if the port is available.
> + bool is_monitored; // True if the port is monitored by the observer.
> + };
> +
> + virtual ~Reporter();
> + // connected_to_pulse_audio is called when a connection with pulse has been established.
> + virtual void connected_to_pulse_audio();
> + // query_for_default_sink_failed is called when no default sink was returned.
> + virtual void query_for_default_sink_failed();
> + // query_for_default_sink_finished is called when the default sink query against pulse
> + // has finished, reporting the name of the sink to observers.
> + virtual void query_for_default_sink_finished(const std::string& sink_name);
> + // query_for_sink_info_finished is called when a query for information about a specific sink
> + // has finished, reporting the name, index of the sink as well as the set of ports known to the sink.
> + virtual void query_for_sink_info_finished(const std::string& name, std::uint32_t index, const std::set<Port>& known_ports);
> + // sink_event_with_index is called when something happened on a sink, reporing the index of the
> + // sink.
> + virtual void sink_event_with_index(std::uint32_t index);
> + };
> +
> + // 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{".+"}
> + };
> + // The Reporter instance that the implementation reports
> + // events to. Must not be null.
> + Reporter::Ptr reporter{std::make_shared<Reporter>()};
> + };
> +
> + // Constructs a new instance, throws:
> + // * std::runtime_error if connection to pulseaudio fails.
> + // * std::runtime_error if reporter instance is null.
> + 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<Reporter::Port>>& 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/gstreamer/playbin.cpp'
> --- src/core/media/gstreamer/playbin.cpp 2015-03-03 18:07:53 +0000
> +++ src/core/media/gstreamer/playbin.cpp 2015-03-03 18:07:53 +0000
> @@ -406,6 +406,9 @@
> };
>
> auto ret = gst_element_set_state(pipeline, new_state);
> +
> + std::cout << __PRETTY_FUNCTION__ << ": requested state change." << std::endl;
> +
> bool result = false; GstState current, pending;
> switch(ret)
> {
> @@ -419,7 +422,7 @@
> pipeline,
> ¤t,
> &pending,
> - state_change_timeout.count());
> + state_change_timeout.count());
> break;
> }
>
>
> === modified file 'src/core/media/service_implementation.cpp'
> --- src/core/media/service_implementation.cpp 2015-03-03 18:07:53 +0000
> +++ src/core/media/service_implementation.cpp 2015-03-03 18:07:53 +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,349 +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)
> - {
> - if (state == media::RecordingState::started)
> - {
> - display_state_lock->request_acquire(media::power::DisplayState::on);
> - pause_playback();
> - }
> - else if (state == media::RecordingState::stopped)
> - {
> - display_state_lock->request_release(media::power::DisplayState::on);
> - }
> - }
> -
> - 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;
> @@ -411,22 +72,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. Also gets signaled when recording
> - // begins.
> - core::Signal<void> pause_playback;
> std::unique_ptr<CallMonitor> call_monitor;
> std::list<media::Player::PlayerKey> paused_sessions;
> };
> @@ -456,10 +103,18 @@
> 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::Private:
> + std::cout << "AudioOutputObserver reports that output is not Private." << std::endl;
> + break;
> + case audio::OutputState::Public:
> + std::cout << "AudioOutputObserver reports that output is now Public." << std::endl;
> + pause_all_multimedia_sessions();
> + break;
> + }
> });
>
> d->call_monitor->on_change([this](CallMonitor::State state) {
> @@ -475,6 +130,19 @@
> break;
> }
> });
> +
> + d->recorder_observer->recording_state().changed().connect([this](RecordingState state)
> + {
> + if (state == media::RecordingState::started)
> + {
> + d->display_state_lock->request_acquire(media::power::DisplayState::on);
> + pause_all_multimedia_sessions();
> + }
> + else if (state == media::RecordingState::stopped)
> + {
> + d->display_state_lock->request_release(media::power::DisplayState::on);
> + }
> + });
> }
>
> media::ServiceImplementation::~ServiceImplementation()
>
--
https://code.launchpad.net/~phablet-team/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