Select audio output on Linux
This commit is contained in:
@@ -97,6 +97,7 @@ else()
|
|||||||
find_package(FFmpeg REQUIRED)
|
find_package(FFmpeg REQUIRED)
|
||||||
set(PLATFORM_TARGET_FILES
|
set(PLATFORM_TARGET_FILES
|
||||||
sunshine/platform/linux/display.cpp
|
sunshine/platform/linux/display.cpp
|
||||||
|
sunshine/platform/linux/audio.cpp
|
||||||
sunshine/platform/linux/input.cpp)
|
sunshine/platform/linux/input.cpp)
|
||||||
|
|
||||||
set(PLATFORM_LIBRARIES
|
set(PLATFORM_LIBRARIES
|
||||||
|
|||||||
@@ -94,8 +94,8 @@
|
|||||||
# output_name = \\.\DISPLAY1
|
# output_name = \\.\DISPLAY1
|
||||||
|
|
||||||
# !! Linux only !!
|
# !! Linux only !!
|
||||||
# Set the display number to stream. I have no idea how they are numbered. They start from 1, usually.
|
# Set the display number to stream. I have no idea how they are numbered. They start from 0, usually.
|
||||||
# output_name = 1
|
# output_name = 0
|
||||||
|
|
||||||
###############################################
|
###############################################
|
||||||
# FFmpeg software encoding parameters
|
# FFmpeg software encoding parameters
|
||||||
|
|||||||
+75
-5
@@ -25,7 +25,9 @@ struct opus_stream_config_t {
|
|||||||
constexpr std::uint8_t map_stereo[] { 0, 1 };
|
constexpr std::uint8_t map_stereo[] { 0, 1 };
|
||||||
constexpr std::uint8_t map_surround51[] { 0, 4, 1, 5, 2, 3 };
|
constexpr std::uint8_t map_surround51[] { 0, 4, 1, 5, 2, 3 };
|
||||||
constexpr std::uint8_t map_high_surround51[] { 0, 1, 2, 3, 4, 5 };
|
constexpr std::uint8_t map_high_surround51[] { 0, 1, 2, 3, 4, 5 };
|
||||||
|
|
||||||
constexpr auto SAMPLE_RATE = 48000;
|
constexpr auto SAMPLE_RATE = 48000;
|
||||||
|
|
||||||
static opus_stream_config_t stereo = {
|
static opus_stream_config_t stereo = {
|
||||||
SAMPLE_RATE,
|
SAMPLE_RATE,
|
||||||
2,
|
2,
|
||||||
@@ -79,7 +81,78 @@ void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t confi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const platf::card_t *active_card(const std::vector<platf::card_t> &cards) {
|
||||||
|
for(auto &card : cards) {
|
||||||
|
if(card.active_profile) {
|
||||||
|
return &card;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data) {
|
void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data) {
|
||||||
|
//FIXME: Pick correct opus_stream_config_t based on config.channels
|
||||||
|
auto stream = &stereo;
|
||||||
|
|
||||||
|
auto control = platf::audio_control();
|
||||||
|
if(!control) {
|
||||||
|
BOOST_LOG(error) << "Couldn't create audio control"sv;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cards = control->card_info();
|
||||||
|
if(cards.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto card = active_card(cards);
|
||||||
|
if(!card || (card->stereo.empty() && card->surround51.empty() && card->surround71.empty())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const platf::profile_t *profile;
|
||||||
|
switch(config.channels) {
|
||||||
|
case 2:
|
||||||
|
if(!card->stereo.empty()) {
|
||||||
|
profile = &card->stereo[0];
|
||||||
|
}
|
||||||
|
else if(!card->surround51.empty()) {
|
||||||
|
profile = &card->surround51[0];
|
||||||
|
}
|
||||||
|
else if(!card->surround71.empty()) {
|
||||||
|
profile = &card->surround71[0];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
if(!card->surround51.empty()) {
|
||||||
|
profile = &card->surround51[0];
|
||||||
|
}
|
||||||
|
else if(!card->surround71.empty()) {
|
||||||
|
profile = &card->surround71[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
profile = &card->stereo[0];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
if(!card->surround71.empty()) {
|
||||||
|
profile = &card->surround71[0];
|
||||||
|
}
|
||||||
|
else if(!card->surround51.empty()) {
|
||||||
|
profile = &card->surround51[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
profile = &card->stereo[0];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(control->set_output(*card, *profile)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto samples = std::make_shared<sample_queue_t::element_type>(30);
|
auto samples = std::make_shared<sample_queue_t::element_type>(30);
|
||||||
std::thread thread { encodeThread, packets, samples, config, channel_data };
|
std::thread thread { encodeThread, packets, samples, config, channel_data };
|
||||||
|
|
||||||
@@ -90,13 +163,10 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co
|
|||||||
shutdown_event->view();
|
shutdown_event->view();
|
||||||
});
|
});
|
||||||
|
|
||||||
//FIXME: Pick correct opus_stream_config_t based on config.channels
|
|
||||||
auto stream = &stereo;
|
|
||||||
|
|
||||||
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
||||||
int samples_per_frame = frame_size * stream->channelCount;
|
int samples_per_frame = frame_size * stream->channelCount;
|
||||||
|
|
||||||
auto mic = platf::microphone(stream->sampleRate, frame_size);
|
auto mic = control->create_mic(stream->sampleRate, frame_size);
|
||||||
if(!mic) {
|
if(!mic) {
|
||||||
BOOST_LOG(error) << "Couldn't create audio input"sv;
|
BOOST_LOG(error) << "Couldn't create audio input"sv;
|
||||||
|
|
||||||
@@ -115,7 +185,7 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co
|
|||||||
continue;
|
continue;
|
||||||
case platf::capture_e::reinit:
|
case platf::capture_e::reinit:
|
||||||
mic.reset();
|
mic.reset();
|
||||||
mic = platf::microphone(stream->sampleRate, frame_size);
|
mic = control->create_mic(stream->sampleRate, frame_size);
|
||||||
if(!mic) {
|
if(!mic) {
|
||||||
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv;
|
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -77,8 +77,8 @@ namespace flag {
|
|||||||
enum flag_e : std::size_t {
|
enum flag_e : std::size_t {
|
||||||
PIN_STDIN = 0, // Read PIN from stdin instead of http
|
PIN_STDIN = 0, // Read PIN from stdin instead of http
|
||||||
FRESH_STATE, // Do not load or save state
|
FRESH_STATE, // Do not load or save state
|
||||||
FLAG_SIZE,
|
CONST_PIN = 4, // Use "universal" pin
|
||||||
CONST_PIN = 4 // Use "universal" pin
|
FLAG_SIZE
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#define SUNSHINE_COMMON_H
|
#define SUNSHINE_COMMON_H
|
||||||
|
|
||||||
#include "sunshine/utility.h"
|
#include "sunshine/utility.h"
|
||||||
|
#include <bitset>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -101,6 +102,23 @@ public:
|
|||||||
virtual ~img_t() = default;
|
virtual ~img_t() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct profile_t {
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
|
||||||
|
bool available;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct card_t {
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
|
||||||
|
std::optional<profile_t> active_profile;
|
||||||
|
std::vector<profile_t> stereo;
|
||||||
|
std::vector<profile_t> surround51;
|
||||||
|
std::vector<profile_t> surround71;
|
||||||
|
};
|
||||||
|
|
||||||
struct hwdevice_t {
|
struct hwdevice_t {
|
||||||
void *data {};
|
void *data {};
|
||||||
platf::img_t *img {};
|
platf::img_t *img {};
|
||||||
@@ -148,6 +166,16 @@ public:
|
|||||||
virtual ~mic_t() = default;
|
virtual ~mic_t() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class audio_control_t {
|
||||||
|
public:
|
||||||
|
virtual int set_output(const card_t &card, const profile_t &) = 0;
|
||||||
|
|
||||||
|
virtual std::unique_ptr<mic_t> create_mic(std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
|
||||||
|
|
||||||
|
virtual std::vector<card_t> card_info() = 0;
|
||||||
|
|
||||||
|
virtual ~audio_control_t() = default;
|
||||||
|
};
|
||||||
|
|
||||||
void freeInput(void *);
|
void freeInput(void *);
|
||||||
|
|
||||||
@@ -158,6 +186,7 @@ std::string get_mac_address(const std::string_view &address);
|
|||||||
std::string from_sockaddr(const sockaddr *const);
|
std::string from_sockaddr(const sockaddr *const);
|
||||||
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const);
|
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const);
|
||||||
|
|
||||||
|
std::unique_ptr<audio_control_t> audio_control();
|
||||||
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate, std::uint32_t frame_size);
|
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate, std::uint32_t frame_size);
|
||||||
std::shared_ptr<display_t> display(dev_type_e hwdevice_type);
|
std::shared_ptr<display_t> display(dev_type_e hwdevice_type);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,361 @@
|
|||||||
|
//
|
||||||
|
// Created by loki on 5/16/21.
|
||||||
|
//
|
||||||
|
#include <bitset>
|
||||||
|
#include <boost/regex.hpp>
|
||||||
|
|
||||||
|
#include <pulse/error.h>
|
||||||
|
#include <pulse/pulseaudio.h>
|
||||||
|
#include <pulse/simple.h>
|
||||||
|
|
||||||
|
#include "sunshine/platform/common.h"
|
||||||
|
|
||||||
|
#include "sunshine/config.h"
|
||||||
|
#include "sunshine/main.h"
|
||||||
|
#include "sunshine/thread_safe.h"
|
||||||
|
|
||||||
|
namespace platf {
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
|
struct mic_attr_t : public mic_t {
|
||||||
|
pa_sample_spec ss;
|
||||||
|
util::safe_ptr<pa_simple, pa_simple_free> mic;
|
||||||
|
|
||||||
|
explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate,
|
||||||
|
std::uint8_t channels) : ss { format, sample_rate, channels }, mic {} {}
|
||||||
|
|
||||||
|
capture_e sample(std::vector<std::int16_t> &sample_buf) override {
|
||||||
|
auto sample_size = sample_buf.size();
|
||||||
|
|
||||||
|
auto buf = sample_buf.data();
|
||||||
|
int status;
|
||||||
|
if(pa_simple_read(mic.get(), buf, sample_size * 2, &status)) {
|
||||||
|
BOOST_LOG(error) << "pa_simple_read() failed: "sv << pa_strerror(status);
|
||||||
|
|
||||||
|
return capture_e::error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return capture_e::ok;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate, std::uint32_t) {
|
||||||
|
auto mic = std::make_unique<mic_attr_t>(PA_SAMPLE_S16LE, sample_rate, 2);
|
||||||
|
|
||||||
|
int status;
|
||||||
|
|
||||||
|
const char *audio_sink = "@DEFAULT_MONITOR@";
|
||||||
|
if(!config::audio.sink.empty()) {
|
||||||
|
audio_sink = config::audio.sink.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
mic->mic.reset(
|
||||||
|
pa_simple_new(nullptr, "sunshine",
|
||||||
|
pa_stream_direction_t::PA_STREAM_RECORD, audio_sink,
|
||||||
|
"sunshine-record", &mic->ss, nullptr, nullptr, &status));
|
||||||
|
|
||||||
|
if(!mic->mic) {
|
||||||
|
auto err_str = pa_strerror(status);
|
||||||
|
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str;
|
||||||
|
|
||||||
|
log_flush();
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mic;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace pa {
|
||||||
|
template<class T>
|
||||||
|
void pa_free(T *p) {
|
||||||
|
pa_xfree(p);
|
||||||
|
}
|
||||||
|
using ctx_t = util::safe_ptr<pa_context, pa_context_unref>;
|
||||||
|
using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>;
|
||||||
|
using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
|
||||||
|
using string_t = util::safe_ptr<char, pa_free<char>>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using cb_t = std::function<void(ctx_t::pointer, const T *, int eol)>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void cb(ctx_t::pointer ctx, const T *i, int eol, void *userdata) {
|
||||||
|
auto &f = *(cb_t<T> *)userdata;
|
||||||
|
|
||||||
|
// For some reason, pulseaudio calls this callback after disconnecting
|
||||||
|
if(i && eol) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
f(ctx, i, eol);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
|
||||||
|
auto &f = *(std::function<void(ctx_t::pointer)> *)userdata;
|
||||||
|
|
||||||
|
f(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void success_cb(ctx_t::pointer ctx, int status, void *userdata) {
|
||||||
|
assert(userdata != nullptr);
|
||||||
|
|
||||||
|
auto alarm = (safe::alarm_raw_t<int> *)userdata;
|
||||||
|
alarm->ring(status ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
profile_t make(pa_card_profile_info2 *profile) {
|
||||||
|
return profile_t {
|
||||||
|
profile->name,
|
||||||
|
profile->description,
|
||||||
|
profile->available == 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
card_t make(const pa_card_info *card) {
|
||||||
|
boost::regex stereo_expr {
|
||||||
|
".*output(?!.*surround).*stereo.*"
|
||||||
|
};
|
||||||
|
|
||||||
|
boost::regex surround51_expr {
|
||||||
|
".*output.*surround(.*(^\\d|51).*|$)"
|
||||||
|
};
|
||||||
|
|
||||||
|
boost::regex surround71_expr {
|
||||||
|
".*output.*surround.*7.?1.*"
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<profile_t> stereo;
|
||||||
|
std::vector<profile_t> surround51;
|
||||||
|
std::vector<profile_t> surround71;
|
||||||
|
|
||||||
|
std::for_each_n(card->profiles2, card->n_profiles, [&](pa_card_profile_info2 *profile) {
|
||||||
|
if(boost::regex_match(profile->name, stereo_expr)) {
|
||||||
|
stereo.emplace_back(make(profile));
|
||||||
|
}
|
||||||
|
if(boost::regex_match(profile->name, surround51_expr)) {
|
||||||
|
surround51.emplace_back(make(profile));
|
||||||
|
}
|
||||||
|
if(boost::regex_match(profile->name, surround71_expr)) {
|
||||||
|
surround71.emplace_back(make(profile));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::optional<profile_t> active_profile;
|
||||||
|
if(card->active_profile2->name != "off"sv) {
|
||||||
|
active_profile = make(card->active_profile2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return card_t {
|
||||||
|
card->name,
|
||||||
|
card->driver,
|
||||||
|
std::move(active_profile),
|
||||||
|
std::move(stereo),
|
||||||
|
std::move(surround51),
|
||||||
|
std::move(surround71),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const card_t *active_card(const std::vector<card_t> &cards) {
|
||||||
|
for(auto &card : cards) {
|
||||||
|
if(card.active_profile) {
|
||||||
|
return &card;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
class server_t : public audio_control_t {
|
||||||
|
enum ctx_event_e : int {
|
||||||
|
ready,
|
||||||
|
terminated,
|
||||||
|
failed
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
loop_t loop;
|
||||||
|
ctx_t ctx;
|
||||||
|
|
||||||
|
std::unique_ptr<safe::event_t<ctx_event_e>> events;
|
||||||
|
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
|
||||||
|
|
||||||
|
std::thread worker;
|
||||||
|
int init() {
|
||||||
|
events = std::make_unique<safe::event_t<ctx_event_e>>();
|
||||||
|
loop.reset(pa_mainloop_new());
|
||||||
|
ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine"));
|
||||||
|
|
||||||
|
events_cb = std::make_unique<std::function<void(ctx_t::pointer)>>([this](ctx_t::pointer ctx) {
|
||||||
|
switch(pa_context_get_state(ctx)) {
|
||||||
|
case PA_CONTEXT_READY:
|
||||||
|
events->raise(ready);
|
||||||
|
break;
|
||||||
|
case PA_CONTEXT_TERMINATED:
|
||||||
|
BOOST_LOG(debug) << "Pulseadio context terminated"sv;
|
||||||
|
events->raise(terminated);
|
||||||
|
break;
|
||||||
|
case PA_CONTEXT_FAILED:
|
||||||
|
BOOST_LOG(debug) << "Pulseadio context failed"sv;
|
||||||
|
events->raise(failed);
|
||||||
|
break;
|
||||||
|
case PA_CONTEXT_CONNECTING:
|
||||||
|
BOOST_LOG(debug) << "Connecting to pulseaudio"sv;
|
||||||
|
case PA_CONTEXT_UNCONNECTED:
|
||||||
|
case PA_CONTEXT_AUTHORIZING:
|
||||||
|
case PA_CONTEXT_SETTING_NAME:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pa_context_set_state_callback(ctx.get(), ctx_state_cb, events_cb.get());
|
||||||
|
|
||||||
|
auto status = pa_context_connect(ctx.get(), nullptr, PA_CONTEXT_NOFLAGS, nullptr);
|
||||||
|
if(status) {
|
||||||
|
BOOST_LOG(error) << "Couldn't connect to pulseaudio: "sv << pa_strerror(status);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
worker = std::thread {
|
||||||
|
[](loop_t::pointer loop) {
|
||||||
|
int retval;
|
||||||
|
auto status = pa_mainloop_run(loop, &retval);
|
||||||
|
|
||||||
|
if(status < 0) {
|
||||||
|
BOOST_LOG(fatal) << "Couldn't run pulseaudio main loop"sv;
|
||||||
|
|
||||||
|
log_flush();
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loop.get()
|
||||||
|
};
|
||||||
|
|
||||||
|
auto event = events->pop();
|
||||||
|
if(event == failed) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<card_t> card_info() override {
|
||||||
|
auto alarm = safe::make_alarm<bool>();
|
||||||
|
|
||||||
|
std::vector<card_t> cards;
|
||||||
|
cb_t<pa_card_info> f = [&cards, alarm](ctx_t::pointer ctx, const pa_card_info *card_info, int eol) {
|
||||||
|
if(!card_info) {
|
||||||
|
if(!eol) {
|
||||||
|
BOOST_LOG(error) << "Couldn't get pulseaudio card info: "sv << pa_strerror(pa_context_errno(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
alarm->ring(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cards.emplace_back(make(card_info));
|
||||||
|
};
|
||||||
|
|
||||||
|
op_t op { pa_context_get_card_info_list(ctx.get(), cb<pa_card_info>, &f) };
|
||||||
|
|
||||||
|
if(!op) {
|
||||||
|
BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
alarm->wait();
|
||||||
|
|
||||||
|
return cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<mic_t> create_mic(std::uint32_t sample_rate, std::uint32_t frame_size) override {
|
||||||
|
return microphone(sample_rate, frame_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int set_output(const card_t &card, const profile_t &profile) override {
|
||||||
|
auto alarm = safe::make_alarm<int>();
|
||||||
|
|
||||||
|
op_t op { pa_context_set_card_profile_by_name(
|
||||||
|
ctx.get(), card.name.c_str(), profile.name.c_str(), success_cb, alarm.get())
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!op) {
|
||||||
|
BOOST_LOG(error) << "Couldn't create set profile operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
alarm->wait();
|
||||||
|
if(*alarm->status()) {
|
||||||
|
BOOST_LOG(error) << "Couldn't set profile ["sv << profile.name << "]: "sv << pa_strerror(pa_context_errno(ctx.get()));
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~server_t() override {
|
||||||
|
if(worker.joinable()) {
|
||||||
|
pa_context_disconnect(ctx.get());
|
||||||
|
|
||||||
|
KITTY_WHILE_LOOP(auto event = events->pop(), event != terminated && event != failed, {
|
||||||
|
event = events->pop();
|
||||||
|
})
|
||||||
|
|
||||||
|
pa_mainloop_quit(loop.get(), 0);
|
||||||
|
worker.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace pa
|
||||||
|
|
||||||
|
std::unique_ptr<audio_control_t> audio_control() {
|
||||||
|
auto audio = std::make_unique<pa::server_t>();
|
||||||
|
|
||||||
|
if(audio->init()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<deinit_t> init() {
|
||||||
|
pa::server_t server;
|
||||||
|
if(server.init()) {
|
||||||
|
return std::make_unique<deinit_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cards = server.card_info();
|
||||||
|
|
||||||
|
for(auto &card : cards) {
|
||||||
|
BOOST_LOG(info) << "---- CARD ----"sv;
|
||||||
|
BOOST_LOG(info) << "Name: ["sv << card.name << ']';
|
||||||
|
BOOST_LOG(info) << "Description: ["sv << card.description << ']';
|
||||||
|
|
||||||
|
if(card.active_profile) {
|
||||||
|
BOOST_LOG(info) << "Active profile:"sv;
|
||||||
|
BOOST_LOG(info) << " Name: [" << card.active_profile->name << ']';
|
||||||
|
BOOST_LOG(info) << " Description: ["sv << card.active_profile->description << ']';
|
||||||
|
BOOST_LOG(info) << " Available: ["sv << card.active_profile->available << ']';
|
||||||
|
BOOST_LOG(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BOOST_LOG(info) << " -- stereo --"sv;
|
||||||
|
for(auto &profile : card.stereo) {
|
||||||
|
BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG(info) << " -- surround 5.1 --"sv;
|
||||||
|
for(auto &profile : card.surround51) {
|
||||||
|
BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG(info) << " -- surround 7.1 --"sv;
|
||||||
|
for(auto &profile : card.surround71) {
|
||||||
|
BOOST_LOG(info) << " "sv << profile.name << ": "sv << profile.description << " ("sv << profile.available << ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<deinit_t>();
|
||||||
|
}
|
||||||
|
} // namespace platf
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include "sunshine/platform/common.h"
|
#include "sunshine/platform/common.h"
|
||||||
|
|
||||||
#include <bitset>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
@@ -20,9 +19,6 @@
|
|||||||
#include <xcb/shm.h>
|
#include <xcb/shm.h>
|
||||||
#include <xcb/xfixes.h>
|
#include <xcb/xfixes.h>
|
||||||
|
|
||||||
#include <pulse/error.h>
|
|
||||||
#include <pulse/simple.h>
|
|
||||||
|
|
||||||
#include "sunshine/config.h"
|
#include "sunshine/config.h"
|
||||||
#include "sunshine/main.h"
|
#include "sunshine/main.h"
|
||||||
#include "sunshine/task_pool.h"
|
#include "sunshine/task_pool.h"
|
||||||
@@ -359,28 +355,6 @@ struct shm_attr_t : public x11_attr_t {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mic_attr_t : public mic_t {
|
|
||||||
pa_sample_spec ss;
|
|
||||||
util::safe_ptr<pa_simple, pa_simple_free> mic;
|
|
||||||
|
|
||||||
explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate,
|
|
||||||
std::uint8_t channels) : ss { format, sample_rate, channels }, mic {} {}
|
|
||||||
|
|
||||||
capture_e sample(std::vector<std::int16_t> &sample_buf) override {
|
|
||||||
auto sample_size = sample_buf.size();
|
|
||||||
|
|
||||||
auto buf = sample_buf.data();
|
|
||||||
int status;
|
|
||||||
if(pa_simple_read(mic.get(), buf, sample_size * 2, &status)) {
|
|
||||||
BOOST_LOG(error) << "pa_simple_read() failed: "sv << pa_strerror(status);
|
|
||||||
|
|
||||||
return capture_e::error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return capture_e::ok;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<display_t> display(platf::dev_type_e hwdevice_type) {
|
std::shared_ptr<display_t> display(platf::dev_type_e hwdevice_type) {
|
||||||
if(hwdevice_type != platf::dev_type_e::none) {
|
if(hwdevice_type != platf::dev_type_e::none) {
|
||||||
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
|
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
|
||||||
@@ -409,32 +383,6 @@ std::shared_ptr<display_t> display(platf::dev_type_e hwdevice_type) {
|
|||||||
return x11_disp;
|
return x11_disp;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate, std::uint32_t) {
|
|
||||||
auto mic = std::make_unique<mic_attr_t>(PA_SAMPLE_S16LE, sample_rate, 2);
|
|
||||||
|
|
||||||
int status;
|
|
||||||
|
|
||||||
const char *audio_sink = "@DEFAULT_MONITOR@";
|
|
||||||
if(!config::audio.sink.empty()) {
|
|
||||||
audio_sink = config::audio.sink.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
mic->mic.reset(
|
|
||||||
pa_simple_new(nullptr, "sunshine",
|
|
||||||
pa_stream_direction_t::PA_STREAM_RECORD, audio_sink,
|
|
||||||
"sunshine-record", &mic->ss, nullptr, nullptr, &status));
|
|
||||||
|
|
||||||
if(!mic->mic) {
|
|
||||||
auto err_str = pa_strerror(status);
|
|
||||||
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str;
|
|
||||||
|
|
||||||
log_flush();
|
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
return mic;
|
|
||||||
}
|
|
||||||
|
|
||||||
ifaddr_t get_ifaddrs() {
|
ifaddr_t get_ifaddrs() {
|
||||||
ifaddrs *p { nullptr };
|
ifaddrs *p { nullptr };
|
||||||
|
|
||||||
|
|||||||
@@ -584,6 +584,4 @@ void freeInput(void *p) {
|
|||||||
auto *input = (input_raw_t *)p;
|
auto *input = (input_raw_t *)p;
|
||||||
delete input;
|
delete input;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<deinit_t> init() { return std::make_unique<deinit_t>(); }
|
|
||||||
} // namespace platf
|
} // namespace platf
|
||||||
|
|||||||
+89
-3
@@ -16,9 +16,9 @@
|
|||||||
namespace safe {
|
namespace safe {
|
||||||
template<class T>
|
template<class T>
|
||||||
class event_t {
|
class event_t {
|
||||||
|
public:
|
||||||
using status_t = util::optional_t<T>;
|
using status_t = util::optional_t<T>;
|
||||||
|
|
||||||
public:
|
|
||||||
template<class... Args>
|
template<class... Args>
|
||||||
void raise(Args &&...args) {
|
void raise(Args &&...args) {
|
||||||
std::lock_guard lg { _lock };
|
std::lock_guard lg { _lock };
|
||||||
@@ -131,10 +131,97 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
class queue_t {
|
class alarm_raw_t {
|
||||||
|
public:
|
||||||
using status_t = util::optional_t<T>;
|
using status_t = util::optional_t<T>;
|
||||||
|
|
||||||
|
alarm_raw_t() : _status { util::false_v<status_t> } {}
|
||||||
|
|
||||||
|
void ring(const status_t &status) {
|
||||||
|
std::lock_guard lg(_lock);
|
||||||
|
|
||||||
|
_status = status;
|
||||||
|
_cv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ring(status_t &&status) {
|
||||||
|
std::lock_guard lg(_lock);
|
||||||
|
|
||||||
|
_status = std::move(status);
|
||||||
|
_cv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Rep, class Period>
|
||||||
|
auto wait_for(const std::chrono::duration<Rep, Period> &rel_time) {
|
||||||
|
std::unique_lock ul(_lock);
|
||||||
|
|
||||||
|
return _cv.wait_for(ul, rel_time, [this]() { return (bool)status(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Rep, class Period, class Pred>
|
||||||
|
auto wait_for(const std::chrono::duration<Rep, Period> &rel_time, Pred &&pred) {
|
||||||
|
std::unique_lock ul(_lock);
|
||||||
|
|
||||||
|
return _cv.wait_for(ul, rel_time, [this, &pred]() { return (bool)status() || pred(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Rep, class Period>
|
||||||
|
auto wait_until(const std::chrono::duration<Rep, Period> &rel_time) {
|
||||||
|
std::unique_lock ul(_lock);
|
||||||
|
|
||||||
|
return _cv.wait_until(ul, rel_time, [this]() { return (bool)status(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Rep, class Period, class Pred>
|
||||||
|
auto wait_until(const std::chrono::duration<Rep, Period> &rel_time, Pred &&pred) {
|
||||||
|
std::unique_lock ul(_lock);
|
||||||
|
|
||||||
|
return _cv.wait_until(ul, rel_time, [this, &pred]() { return (bool)status() || pred(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
auto wait() {
|
||||||
|
std::unique_lock ul(_lock);
|
||||||
|
_cv.wait(ul, [this]() { return (bool)status(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Pred>
|
||||||
|
auto wait(Pred &&pred) {
|
||||||
|
std::unique_lock ul(_lock);
|
||||||
|
_cv.wait(ul, [this, &pred]() { return (bool)status() || pred(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
const status_t &status() const {
|
||||||
|
return _status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t &status() {
|
||||||
|
return _status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
_status = status_t {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex _lock;
|
||||||
|
std::condition_variable _cv;
|
||||||
|
|
||||||
|
status_t _status;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using alarm_t = std::shared_ptr<alarm_raw_t<T>>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
alarm_t<T> make_alarm() {
|
||||||
|
return std::make_shared<alarm_raw_t<T>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class queue_t {
|
||||||
public:
|
public:
|
||||||
|
using status_t = util::optional_t<T>;
|
||||||
|
|
||||||
queue_t(std::uint32_t max_elements) : _max_elements { max_elements } {}
|
queue_t(std::uint32_t max_elements) : _max_elements { max_elements } {}
|
||||||
|
|
||||||
template<class... Args>
|
template<class... Args>
|
||||||
@@ -202,7 +289,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<T> &unsafe() {
|
std::vector<T> &unsafe() {
|
||||||
std::lock_guard { _lock };
|
|
||||||
return _queue;
|
return _queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -53,6 +53,10 @@
|
|||||||
auto &b = std::get<1>(a##_##b##_##c); \
|
auto &b = std::get<1>(a##_##b##_##c); \
|
||||||
auto &c = std::get<2>(a##_##b##_##c)
|
auto &c = std::get<2>(a##_##b##_##c)
|
||||||
|
|
||||||
|
#define TUPLE_EL(a, b, expr) \
|
||||||
|
decltype(expr) a##_ = expr; \
|
||||||
|
auto &a = std::get<b>(a##_)
|
||||||
|
|
||||||
namespace util {
|
namespace util {
|
||||||
|
|
||||||
template<template<typename...> class X, class... Y>
|
template<template<typename...> class X, class... Y>
|
||||||
@@ -830,6 +834,5 @@ inline auto little(T x) { return endian_helper<T>::little(x); }
|
|||||||
template<class T>
|
template<class T>
|
||||||
inline auto big(T x) { return endian_helper<T>::big(x); }
|
inline auto big(T x) { return endian_helper<T>::big(x); }
|
||||||
} // namespace endian
|
} // namespace endian
|
||||||
|
|
||||||
} // namespace util
|
} // namespace util
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user