Sunshine/sunshine/audio.cpp

239 lines
5.6 KiB
C++
Raw Normal View History

#include <thread>
#include <opus/opus_multistream.h>
#include "platform/common.h"
#include "audio.h"
2021-05-19 16:11:06 +00:00
#include "config.h"
#include "main.h"
2021-05-17 19:21:57 +00:00
#include "thread_safe.h"
#include "utility.h"
namespace audio {
using namespace std::literals;
2021-05-17 19:21:57 +00:00
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
2019-12-30 10:49:45 +00:00
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<std::int16_t>>>;
2021-05-19 16:11:06 +00:00
struct audio_ctx_t {
// We want to change the sink for the first stream only
std::unique_ptr<std::atomic_bool> sink_flag;
2021-05-19 16:11:06 +00:00
std::unique_ptr<platf::audio_control_t> control;
bool restore_sink;
2021-05-19 16:11:06 +00:00
platf::sink_t sink;
};
2021-05-19 16:11:06 +00:00
static int start_audio_control(audio_ctx_t &ctx);
static void stop_audio_control(audio_ctx_t &);
2021-05-18 11:36:12 +00:00
2021-05-19 16:11:06 +00:00
int map_stream(int channels, bool quality);
2021-05-18 11:36:12 +00:00
2021-05-19 16:11:06 +00:00
constexpr auto SAMPLE_RATE = 48000;
2021-05-19 16:11:06 +00:00
opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] {
{
SAMPLE_RATE,
2,
1,
1,
platf::speaker::map_stereo,
},
{
SAMPLE_RATE,
6,
4,
2,
platf::speaker::map_surround51,
},
{
SAMPLE_RATE,
6,
6,
0,
platf::speaker::map_surround51,
},
{
SAMPLE_RATE,
8,
5,
3,
platf::speaker::map_surround71,
},
{
SAMPLE_RATE,
8,
8,
0,
platf::speaker::map_surround71,
},
};
2021-05-19 16:11:06 +00:00
auto control_shared = safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control);
2020-02-08 15:26:38 +00:00
void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t config, void *channel_data) {
//FIXME: Pick correct opus_stream_config_t based on config.channels
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
2021-05-19 16:11:06 +00:00
opus_t opus { opus_multistream_encoder_create(
stream->sampleRate,
stream->channelCount,
stream->streams,
stream->coupledStreams,
stream->mapping,
OPUS_APPLICATION_AUDIO,
2021-05-17 19:21:57 +00:00
nullptr) };
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
while(auto sample = samples->pop()) {
2021-05-17 19:21:57 +00:00
packet_t packet { 16 * 1024 }; // 16KB
2019-12-30 10:49:45 +00:00
int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
if(bytes < 0) {
BOOST_LOG(error) << opus_strerror(bytes);
packets->stop();
return;
}
packet.fake_resize(bytes);
packets->raise(channel_data, std::move(packet));
}
}
2020-02-09 23:33:12 +00:00
void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data) {
2021-05-18 11:36:12 +00:00
//FIXME: Pick correct opus_stream_config_t based on config.channels
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
2021-05-18 11:36:12 +00:00
2021-05-19 16:11:06 +00:00
auto ref = control_shared.ref();
if(!ref) {
2021-05-18 11:36:12 +00:00
return;
}
2021-05-19 16:11:06 +00:00
auto &control = ref->control;
if(!control) {
BOOST_LOG(error) << "Couldn't create audio control"sv;
2021-05-18 11:36:12 +00:00
return;
}
std::string *sink =
config::audio.sink.empty() ? &ref->sink.host : &config::audio.sink;
2021-05-19 16:11:06 +00:00
if(ref->sink.null) {
auto &null = *ref->sink.null;
switch(stream->channelCount) {
case 2:
sink = &null.stereo;
break;
case 6:
sink = &null.surround51;
break;
case 8:
sink = &null.surround71;
break;
2021-05-18 11:36:12 +00:00
}
}
// Only the first to start a session may change the default sink
if(!ref->sink_flag->exchange(true, std::memory_order_acquire)) {
ref->restore_sink = !config.flags[config_t::HOST_AUDIO];
2021-05-19 16:11:06 +00:00
// If the client requests audio on the host, don't change the default sink
if(!config.flags[config_t::HOST_AUDIO] && control->set_sink(*sink)) {
return;
}
2021-05-18 11:36:12 +00:00
}
auto samples = std::make_shared<sample_queue_t::element_type>(30);
2020-02-08 15:26:38 +00:00
std::thread thread { encodeThread, packets, samples, config, channel_data };
auto fg = util::fail_guard([&]() {
samples->stop();
thread.join();
shutdown_event->view();
});
2021-05-17 19:21:57 +00:00
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
2021-05-14 19:44:20 +00:00
int samples_per_frame = frame_size * stream->channelCount;
2021-05-19 16:11:06 +00:00
auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if(!mic) {
2021-05-17 19:21:57 +00:00
BOOST_LOG(error) << "Couldn't create audio input"sv;
return;
}
2020-02-09 23:33:12 +00:00
while(!shutdown_event->peek()) {
std::vector<std::int16_t> sample_buffer;
sample_buffer.resize(samples_per_frame);
auto status = mic->sample(sample_buffer);
switch(status) {
2021-05-17 19:21:57 +00:00
case platf::capture_e::ok:
break;
case platf::capture_e::timeout:
continue;
case platf::capture_e::reinit:
mic.reset();
2021-05-19 16:11:06 +00:00
mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
2021-05-17 19:21:57 +00:00
if(!mic) {
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv;
return;
2021-05-17 19:21:57 +00:00
}
return;
default:
return;
}
samples->raise(std::move(sample_buffer));
}
}
2021-05-19 16:11:06 +00:00
int map_stream(int channels, bool quality) {
int shift = quality ? 1 : 0;
switch(channels) {
case 2:
return STEREO;
case 6:
return SURROUND51 + shift;
case 8:
return SURROUND71 + shift;
}
return STEREO;
}
int start_audio_control(audio_ctx_t &ctx) {
ctx.sink_flag = std::make_unique<std::atomic_bool>(false);
if(!(ctx.control = platf::audio_control())) {
return -1;
}
auto sink = ctx.control->sink_info();
if(!sink) {
return -1;
}
// The default sink has not been replaced yet.
ctx.restore_sink = false;
2021-05-19 16:11:06 +00:00
ctx.sink = std::move(*sink);
return 0;
}
void stop_audio_control(audio_ctx_t &ctx) {
// restore audio-sink if applicable
if(!ctx.restore_sink) {
return;
}
2021-05-19 16:11:06 +00:00
const std::string &sink = config::audio.sink.empty() ? ctx.sink.host : config::audio.sink;
if(!sink.empty()) {
// Best effort, it's allowed to fail
ctx.control->set_sink(sink);
}
}
2021-05-17 19:21:57 +00:00
} // namespace audio