Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,63 @@ namespace audio {
stream.coupledStreams = params.coupledStreams;
stream.mapping = params.mapping;
}

void mic_receive(safe::mail_t mail, config_t config, void *channel_data) {
if (!config::audio.enable_mic_passthrough) {
BOOST_LOG(warning) << "Microphone pass-through requested but disabled in config"sv;
return;
}

auto shutdown_event = mail->event<bool>(mail::shutdown);
auto packets = mail->queue<packet_t>(mail::mic_packets);

// Create microphone output device
auto audio_ctx = get_audio_ctx_ref();
if (!audio_ctx || !audio_ctx->control) {
BOOST_LOG(error) << "No audio control context available for microphone output"sv;
return;
}

const std::string &mic_sink = config::audio.mic_sink.empty() ? "default" : config::audio.mic_sink;
auto mic_output = audio_ctx->control->mic_output(1, 48000, mic_sink);
if (!mic_output) {
BOOST_LOG(error) << "Failed to initialize microphone output device: "sv << mic_sink;
return;
}

if (mic_output->start()) {
BOOST_LOG(error) << "Failed to start microphone output device"sv;
return;
}

BOOST_LOG(info) << "Started microphone receiver thread"sv;

auto opus_dec = opus_decoder_create(48000, 1, nullptr);
if (!opus_dec) {
BOOST_LOG(error) << "Failed to create Opus decoder for microphone"sv;
return;
}

std::vector<float> decode_buffer(960); // 20ms at 48kHz mono

while (auto packet = packets->pop()) {
if (shutdown_event->peek()) {
break;
}

auto opus_data = reinterpret_cast<const unsigned char*>(packet->first);
auto opus_size = packet->second.size();

int decoded_samples = opus_decode_float(opus_dec, opus_data, opus_size, decode_buffer.data(), decode_buffer.size(), 0);
if (decoded_samples > 0) {
decode_buffer.resize(decoded_samples);
mic_output->output_samples(decode_buffer);
decode_buffer.resize(960);
}
}

mic_output->stop();
opus_decoder_destroy(opus_dec);
BOOST_LOG(info) << "Stopped microphone receiver thread"sv;
}
} // namespace audio
1 change: 1 addition & 0 deletions src/audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ namespace audio {
using audio_ctx_ref_t = safe::shared_t<audio_ctx_t>::ptr_t;

void capture(safe::mail_t mail, config_t config, void *channel_data);
void mic_receive(safe::mail_t mail, config_t config, void *channel_data);

/**
* @brief Get the reference to the audio context.
Expand Down
2 changes: 2 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1162,8 +1162,10 @@ namespace config {

string_f(vars, "audio_sink", audio.sink);
string_f(vars, "virtual_sink", audio.virtual_sink);
string_f(vars, "mic_sink", audio.mic_sink);
bool_f(vars, "stream_audio", audio.stream);
bool_f(vars, "install_steam_audio_drivers", audio.install_steam_drivers);
bool_f(vars, "enable_mic_passthrough", audio.enable_mic_passthrough);

string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, {"pc"sv, "lan"sv, "wan"sv});

Expand Down
2 changes: 2 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,10 @@ namespace config {
struct audio_t {
std::string sink;
std::string virtual_sink;
std::string mic_sink;
bool stream;
bool install_steam_drivers;
bool enable_mic_passthrough;
};

constexpr int ENCRYPTION_MODE_NEVER = 0; // Never use video encryption, even if the client supports it
Expand Down
1 change: 1 addition & 0 deletions src/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ namespace mail {
MAIL(broadcast_shutdown);
MAIL(video_packets);
MAIL(audio_packets);
MAIL(mic_packets);
MAIL(switch_display);

// Local mail
Expand Down
11 changes: 11 additions & 0 deletions src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -550,12 +550,23 @@ namespace platf {
virtual ~mic_t() = default;
};

class mic_output_t {
public:
virtual int output_samples(const std::vector<float> &frame_buffer) = 0;
virtual int start() = 0;
virtual int stop() = 0;

virtual ~mic_output_t() = default;
};

class audio_control_t {
public:
virtual int set_sink(const std::string &sink) = 0;

virtual std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0;

virtual std::unique_ptr<mic_output_t> mic_output(int channels, std::uint32_t sample_rate, const std::string &device_name) = 0;

/**
* @brief Check if the audio sink is available in the system.
* @param sink Sink to be checked.
Expand Down
70 changes: 70 additions & 0 deletions src/platform/linux/audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,72 @@ namespace platf {
}
};

struct mic_output_pa_t: public mic_output_t {
util::safe_ptr<pa_simple, pa_simple_free> output;
std::string device_name;
bool started = false;

mic_output_pa_t(int channels, std::uint32_t sample_rate, const std::string &dev_name)
: device_name(dev_name) {

pa_sample_spec ss {PA_SAMPLE_FLOAT32, sample_rate, (std::uint8_t)channels};
pa_channel_map pa_map;

pa_map.channels = channels;
for (int i = 0; i < channels; i++) {
pa_map.map[i] = PA_CHANNEL_POSITION_MONO;
}

pa_buffer_attr pa_attr = {
.maxlength = uint32_t(-1),
.tlength = uint32_t(-1),
.prebuf = uint32_t(-1),
.minreq = uint32_t(-1),
.fragsize = uint32_t(-1)
};

int status;
output.reset(
pa_simple_new(nullptr, "sunshine-mic", PA_STREAM_PLAYBACK,
device_name.c_str(), "sunshine-mic-output",
&ss, &pa_map, &pa_attr, &status)
);

if (!output) {
BOOST_LOG(error) << "Failed to create PulseAudio mic output: "sv << pa_strerror(status);
}
}

int output_samples(const std::vector<float> &frame_buffer) override {
if (!output || !started) {
return -1;
}

int status;
if (pa_simple_write(output.get(), frame_buffer.data(),
frame_buffer.size() * sizeof(float), &status)) {
BOOST_LOG(error) << "pa_simple_write() failed: "sv << pa_strerror(status);
return -1;
}

return 0;
}

int start() override {
started = output != nullptr;
return started ? 0 : -1;
}

int stop() override {
started = false;
if (output) {
int status;
pa_simple_drain(output.get(), &status);
}
return 0;
}
};

std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
auto mic = std::make_unique<mic_attr_t>();

Expand Down Expand Up @@ -459,6 +525,10 @@ namespace platf {
return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name));
}

std::unique_ptr<mic_output_t> mic_output(int channels, std::uint32_t sample_rate, const std::string &device_name) override {
return std::make_unique<mic_output_pa_t>(channels, sample_rate, device_name);
}

bool is_sink_available(const std::string &sink) override {
BOOST_LOG(warning) << "audio_control_t::is_sink_available() unimplemented: "sv << sink;
return true;
Expand Down
60 changes: 60 additions & 0 deletions src/platform/macos/microphone.mm
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,62 @@ capture_e sample(std::vector<float> &sample_in) override {
}
};

struct av_mic_output_t: public mic_output_t {
AVAudio *av_audio_output;
std::string device_name;
bool started = false;

av_mic_output_t(int channels, std::uint32_t sample_rate, const std::string &dev_name)
: device_name(dev_name) {

av_audio_output = [[AVAudio alloc] init];

AVCaptureDevice *output_device = nullptr;
if (!device_name.empty() && device_name != "default") {
output_device = [AVAudio findMicrophone:[NSString stringWithUTF8String:device_name.c_str()]];
}

if (!output_device) {
output_device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
}

if ([av_audio_output setupMicrophone:output_device sampleRate:sample_rate frameSize:960 channels:channels]) {
BOOST_LOG(error) << "Failed to setup microphone output device."sv;
[av_audio_output release];
av_audio_output = nullptr;
}
}

int output_samples(const std::vector<float> &frame_buffer) override {
if (!av_audio_output || !started) {
return -1;
}

// For macOS, we would need to implement audio output through Core Audio
// This is a simplified placeholder - real implementation would need
// Audio Queue Services or Audio Unit for output
BOOST_LOG(debug) << "Outputting " << frame_buffer.size() << " audio samples"sv;
return 0;
}

int start() override {
started = av_audio_output != nullptr;
return started ? 0 : -1;
}

int stop() override {
started = false;
return 0;
}

~av_mic_output_t() override {
stop();
if (av_audio_output) {
[av_audio_output release];
}
}
};

struct macos_audio_control_t: public audio_control_t {
AVCaptureDevice *audio_capture_device {};

Expand Down Expand Up @@ -78,6 +134,10 @@ int set_sink(const std::string &sink) override {
return mic;
}

std::unique_ptr<mic_output_t> mic_output(int channels, std::uint32_t sample_rate, const std::string &device_name) override {
return std::make_unique<av_mic_output_t>(channels, sample_rate, device_name);
}

bool is_sink_available(const std::string &sink) override {
BOOST_LOG(warning) << "audio_control_t::is_sink_available() unimplemented: "sv << sink;
return true;
Expand Down
Loading