diff --git a/github b/github index a46f549..dda7ad2 160000 --- a/github +++ b/github @@ -1 +1 @@ -Subproject commit a46f54984b7ad9721c01273e77807469cd2c63a9 +Subproject commit dda7ad2b270b3c598f43757c0e9aaee1adafa92c diff --git a/modules/renderer/audio/AudioPlayer.ts b/modules/renderer/audio/AudioPlayer.ts index c7a7b0c..fdfb303 100644 --- a/modules/renderer/audio/AudioPlayer.ts +++ b/modules/renderer/audio/AudioPlayer.ts @@ -106,7 +106,7 @@ export class NativeAudioPlayer implements AudioBackend { } getDefaultDeviceId(): string { - return native.audio.available_devices().find(entry => entry.output_default).device_id; + return native.audio.available_devices().find(entry => entry.output_default)?.device_id || "default"; } isDeviceRefreshAvailable(): boolean { diff --git a/native/serverconnection/src/audio/AudioInput.cpp b/native/serverconnection/src/audio/AudioInput.cpp index 8f5e11f..def345e 100644 --- a/native/serverconnection/src/audio/AudioInput.cpp +++ b/native/serverconnection/src/audio/AudioInput.cpp @@ -111,10 +111,6 @@ bool AudioInput::record(std::string& error) { return false; } - if(this->input_recorder->sample_rate() != this->sample_rate()) { - this->resampler_ = std::make_unique(this->input_recorder->sample_rate(), this->sample_rate(), this->channel_count_); - } - this->input_recorder->register_consumer(this); if(!this->input_recorder->start(error)) { this->input_recorder->remove_consumer(this); @@ -134,6 +130,8 @@ void AudioInput::stop() { this->input_recorder->remove_consumer(this); this->input_recorder->stop_if_possible(); this->input_recorder.reset(); + + this->resampler_ = nullptr; } std::vector> AudioInput::consumers() { @@ -204,11 +202,24 @@ void AudioInput::allocate_input_buffer_samples(size_t samples) { } } -void AudioInput::consume(const void *input, size_t sample_count, size_t channels) { +void AudioInput::consume(const void *input, size_t sample_count, size_t sample_rate, size_t channels) { constexpr static auto kTempBufferMaxSampleCount{1024 * 8}; float temp_buffer[kTempBufferMaxSampleCount]; /* TODO: Short circuit for silence here */ + if(!this->resampler_ || this->current_input_sample_rate != sample_rate) { + log_info(category::audio, tr("Input sample rate changed from {} to {}"), this->resampler_ ? this->resampler_->output_rate() : 0, sample_rate); + this->current_input_sample_rate = sample_rate; + + this->resampler_ = std::make_unique(this->sample_rate(), sample_rate, this->channel_count()); + if(!this->resampler_->valid()) { + log_critical(category::audio, tr("Failed to allocate a new resampler. Audio input will be silent.")); + } + } + + if(!this->resampler_->valid()) { + return; + } if(channels != this->channel_count_) { if(channels < 1 || channels > 2) { @@ -225,25 +236,16 @@ void AudioInput::consume(const void *input, size_t sample_count, size_t channels input = temp_buffer; } - if(this->resampler_) { - const auto expected_size = this->resampler_->estimated_output_size(sample_count); - this->allocate_input_buffer_samples(expected_size); + const auto expected_size = this->resampler_->estimated_output_size(sample_count); + this->allocate_input_buffer_samples(expected_size); - size_t resampled_sample_count{expected_size}; - if(!this->resampler_->process(this->input_buffer.write_ptr(), input, sample_count, resampled_sample_count)) { - log_error(category::audio, tr("Failed to resample input audio.")); - return; - } - - this->input_buffer.advance_write_ptr(resampled_sample_count * this->channel_count_ * sizeof(float)); - } else { - this->allocate_input_buffer_samples(sample_count); - - const auto sample_byte_size = sample_count * this->channel_count_ * sizeof(float); - memcpy(this->input_buffer.write_ptr(), input, sample_byte_size); - this->input_buffer.advance_write_ptr(sample_byte_size); - } + size_t resampled_sample_count{expected_size}; + if(!this->resampler_->process(this->input_buffer.write_ptr(), input, sample_count, resampled_sample_count)) { + log_error(category::audio, tr("Failed to resample audio input.")); + return; + } + this->input_buffer.advance_write_ptr(resampled_sample_count * this->channel_count_ * sizeof(float)); audio::encode_event_loop->schedule(this->event_loop_entry); } diff --git a/native/serverconnection/src/audio/AudioInput.h b/native/serverconnection/src/audio/AudioInput.h index 5168f0c..0e1c2a7 100644 --- a/native/serverconnection/src/audio/AudioInput.h +++ b/native/serverconnection/src/audio/AudioInput.h @@ -69,7 +69,7 @@ namespace tc::audio { AudioInput* input{nullptr}; }; - void consume(const void *, size_t, size_t) override; + void consume(const void *, size_t, size_t, size_t) override; void process_audio(); void process_audio_chunk(float *); @@ -92,6 +92,8 @@ namespace tc::audio { std::unique_ptr resampler_{nullptr}; std::shared_ptr input_device{}; + size_t current_input_sample_rate{0}; + float volume_{1.f}; void allocate_input_buffer_samples(size_t /* sample count */); diff --git a/native/serverconnection/src/audio/AudioLevelMeter.cpp b/native/serverconnection/src/audio/AudioLevelMeter.cpp index ade5a72..fcf0ec4 100644 --- a/native/serverconnection/src/audio/AudioLevelMeter.cpp +++ b/native/serverconnection/src/audio/AudioLevelMeter.cpp @@ -94,7 +94,7 @@ void InputDeviceAudioLevelMeter::stop() { } /* Note the parameter order! */ -void InputDeviceAudioLevelMeter::consume(const void *buffer, size_t sample_count, size_t channel_count) { +void InputDeviceAudioLevelMeter::consume(const void *buffer, size_t sample_count, size_t /* sample rate */, size_t channel_count) { this->analyze_buffer((const float*) buffer, channel_count, sample_count); } diff --git a/native/serverconnection/src/audio/AudioLevelMeter.h b/native/serverconnection/src/audio/AudioLevelMeter.h index 969f265..d9b0ef1 100644 --- a/native/serverconnection/src/audio/AudioLevelMeter.h +++ b/native/serverconnection/src/audio/AudioLevelMeter.h @@ -55,7 +55,7 @@ namespace tc::audio { std::shared_ptr target_device{}; std::shared_ptr recorder_instance{}; - void consume(const void *, size_t, size_t) override; + void consume(const void *, size_t, size_t, size_t) override; }; class AudioInputAudioLevelMeter : public AbstractAudioLevelMeter { diff --git a/native/serverconnection/src/audio/driver/AudioDriver.h b/native/serverconnection/src/audio/driver/AudioDriver.h index 80d56b8..5b1b39f 100644 --- a/native/serverconnection/src/audio/driver/AudioDriver.h +++ b/native/serverconnection/src/audio/driver/AudioDriver.h @@ -11,7 +11,7 @@ namespace tc::audio { public: class Consumer { public: - virtual void consume(const void* /* buffer */, size_t /* samples */, size_t /* channel count */) = 0; + virtual void consume(const void* /* buffer */, size_t /* samples */, size_t /* sample rate */, size_t /* channel count */) = 0; }; [[nodiscard]] virtual size_t sample_rate() const = 0; diff --git a/native/serverconnection/src/audio/driver/PortAudio.h b/native/serverconnection/src/audio/driver/PortAudio.h index 4f5f11a..98558b2 100644 --- a/native/serverconnection/src/audio/driver/PortAudio.h +++ b/native/serverconnection/src/audio/driver/PortAudio.h @@ -10,8 +10,8 @@ namespace tc::audio::pa { class PortAudioPlayback : public AudioDevicePlayback { public: - static constexpr auto kChannelCount{2}; - static constexpr auto kSampleRate{48000}; + static constexpr auto kDefaultChannelCount{2}; + static constexpr std::array kSupportedSampleRates{48000, 44100}; explicit PortAudioPlayback(PaDeviceIndex index, const PaDeviceInfo* info); virtual ~PortAudioPlayback(); @@ -28,13 +28,14 @@ namespace tc::audio::pa { const PaDeviceInfo* info; PaStream* stream{nullptr}; + size_t source_sample_rate{0}; size_t source_channel_count{0}; }; class PortAudioRecord : public AudioDeviceRecord { public: - static constexpr auto kChannelCount{2}; - static constexpr auto kSampleRate{48000}; + static constexpr auto kDefaultChannelCount{2}; + static constexpr std::array kSupportedSampleRates{48000, 44100}; explicit PortAudioRecord(PaDeviceIndex index, const PaDeviceInfo* info); virtual ~PortAudioRecord(); @@ -47,6 +48,7 @@ namespace tc::audio::pa { private: void read_callback(const void *input, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags); + size_t source_sample_rate{0}; size_t source_channel_count{0}; PaDeviceIndex index; diff --git a/native/serverconnection/src/audio/driver/PortAudioPlayback.cpp b/native/serverconnection/src/audio/driver/PortAudioPlayback.cpp index 00d8722..c0b68d4 100644 --- a/native/serverconnection/src/audio/driver/PortAudioPlayback.cpp +++ b/native/serverconnection/src/audio/driver/PortAudioPlayback.cpp @@ -49,34 +49,48 @@ bool PortAudioPlayback::impl_start(std::string &error) { parameters.sampleFormat = paFloat32; parameters.suggestedLatency = this->info->defaultLowInputLatency; - if(this->info->maxOutputChannels < kChannelCount) { + if(this->info->maxOutputChannels < kDefaultChannelCount) { parameters.channelCount = (int) 1; this->source_channel_count = 1; } else { - parameters.channelCount = (int) kChannelCount; - this->source_channel_count = kChannelCount; + parameters.channelCount = (int) kDefaultChannelCount; + this->source_channel_count = kDefaultChannelCount; } - log_debug(category::audio, "Opening playback device {} (MaxChannels: {}, Channels: {})", this->info->name, this->info->maxOutputChannels, this->source_channel_count); + for(const auto& sample_rate : kSupportedSampleRates) { + auto err = Pa_OpenStream( + &this->stream, + nullptr, + ¶meters, + (double) sample_rate, + paFramesPerBufferUnspecified, + paClipOff, + proxied_write_callback, + this + ); + log_debug(category::audio, "Open result for playback device {} (MaxChannels: {}, Channels: {}, Sample rate: {}): {}", this->info->name, this->info->maxOutputChannels, this->source_channel_count, sample_rate, err); - auto err = Pa_OpenStream( - &this->stream, - nullptr, - ¶meters, - (double) kSampleRate, - paFramesPerBufferUnspecified, - paClipOff, - proxied_write_callback, - this - ); + if(err == paNoError) { + this->source_sample_rate = sample_rate; + break; + } - if(err != paNoError) { this->stream = nullptr; + if(err == paInvalidSampleRate) { + /* Try next sample rate */ + continue; + } + error = std::string{Pa_GetErrorText(err)} + " (open stream: " + std::to_string(err) + ")"; return false; } - err = Pa_StartStream(this->stream); + if(!this->stream) { + error = "no supported sample rate found"; + return false; + } + + auto err = Pa_StartStream(this->stream); if(err != paNoError) { error = std::string{Pa_GetErrorText(err)} + "(start stream: " + std::to_string(err) + ")"; err = Pa_CloseStream(this->stream); @@ -101,13 +115,12 @@ void PortAudioPlayback::impl_stop() { } size_t PortAudioPlayback::current_sample_rate() const { - /* We currently only support one sample rate */ - return (size_t) kSampleRate; + return this->source_sample_rate; } void PortAudioPlayback::write_callback(void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags) { (void) timeInfo; (void) statusFlags; - this->fill_buffer(output, frameCount, kSampleRate, this->source_channel_count); + this->fill_buffer(output, frameCount, this->source_sample_rate, this->source_channel_count); } \ No newline at end of file diff --git a/native/serverconnection/src/audio/driver/PortAudioRecord.cpp b/native/serverconnection/src/audio/driver/PortAudioRecord.cpp index 2103eeb..0226202 100644 --- a/native/serverconnection/src/audio/driver/PortAudioRecord.cpp +++ b/native/serverconnection/src/audio/driver/PortAudioRecord.cpp @@ -16,7 +16,6 @@ PortAudioRecord::~PortAudioRecord() { } bool PortAudioRecord::impl_start(std::string &error) { - //TODO: Detect correct sample rate { auto device_info = Pa_GetDeviceInfo(this->index); if(this->info != device_info) { @@ -42,35 +41,52 @@ bool PortAudioRecord::impl_start(std::string &error) { PaStreamParameters parameters{}; memset(¶meters, 0, sizeof(parameters)); - if(this->info->maxInputChannels < kChannelCount) { + if(this->info->maxInputChannels < kDefaultChannelCount) { parameters.channelCount = (int) 1; this->source_channel_count = 1; } else { - parameters.channelCount = (int) kChannelCount; - this->source_channel_count = kChannelCount; + parameters.channelCount = (int) kDefaultChannelCount; + this->source_channel_count = kDefaultChannelCount; } - log_debug(category::audio, "Opening record device {} (MaxChannels: {}, Channels: {})", this->info->name, this->info->maxInputChannels, this->source_channel_count); parameters.device = this->index; parameters.sampleFormat = paFloat32; parameters.suggestedLatency = this->info->defaultLowInputLatency; - auto err = Pa_OpenStream( - &this->stream, - ¶meters, - nullptr, - (double) kSampleRate, - paFramesPerBufferUnspecified, - paClipOff, - proxied_read_callback, - this); - if(err != paNoError) { + for(const auto& sample_rate : kSupportedSampleRates) { + auto err = Pa_OpenStream( + &this->stream, + ¶meters, + nullptr, + (double) sample_rate, + paFramesPerBufferUnspecified, + paClipOff, + proxied_read_callback, + this + ); + log_debug(category::audio, "Open result for record device {} (MaxChannels: {}, Channels: {}, Sample rate: {}): {}", this->info->name, this->info->maxInputChannels, this->source_channel_count, sample_rate, err); + + if(err == paNoError) { + this->source_sample_rate = sample_rate; + break; + } + this->stream = nullptr; + if(err == paInvalidSampleRate) { + /* Try next sample rate */ + continue; + } + error = std::string{Pa_GetErrorText(err)} + " (open stream: " + std::to_string(err) + ")"; return false; } - err = Pa_StartStream(this->stream); + if(!this->stream) { + error = "no supported sample rate found"; + return false; + } + + auto err = Pa_StartStream(this->stream); if(err != paNoError) { error = std::string{Pa_GetErrorText(err)} + "(start stream: " + std::to_string(err) + ")"; err = Pa_CloseStream(this->stream); @@ -98,13 +114,13 @@ void PortAudioRecord::impl_stop() { } size_t PortAudioRecord::sample_rate() const { - return kSampleRate; + return this->source_sample_rate; } void PortAudioRecord::read_callback(const void *input, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags) { std::lock_guard consumer_lock{this->consumer_lock}; for(auto& consumer : this->_consumers) { - consumer->consume(input, frameCount, this->source_channel_count); + consumer->consume(input, frameCount, this->source_sample_rate, this->source_channel_count); } } \ No newline at end of file diff --git a/native/serverconnection/src/audio/sounds/SoundPlayer.cpp b/native/serverconnection/src/audio/sounds/SoundPlayer.cpp index 76f514d..4843da0 100644 --- a/native/serverconnection/src/audio/sounds/SoundPlayer.cpp +++ b/native/serverconnection/src/audio/sounds/SoundPlayer.cpp @@ -190,6 +190,8 @@ namespace tc::audio::sounds { return false; } + /* The execute lock must be locked else some internal state could be altered */ + auto execute_lock = self->execute_lock(true); if(self->state_ == PLAYER_STATE_PLAYING) { log_warn(category::audio, tr("Having an audio underflow while playing a sound.")); } else if(self->state_ == PLAYER_STATE_AWAIT_FINISH) { @@ -204,6 +206,8 @@ namespace tc::audio::sounds { return; } + /* The execute lock must be locked else some internal state could be altered */ + auto execute_lock = self->execute_lock(true); if(self->could_enqueue_next_buffer() && self->state_ == PLAYER_STATE_PLAYING) { audio::decode_event_loop->schedule(self); } diff --git a/native/updater/util.cpp b/native/updater/util.cpp index 7ed090c..f545845 100644 --- a/native/updater/util.cpp +++ b/native/updater/util.cpp @@ -51,8 +51,6 @@ using namespace std; bool is_executable(const std::string& file) { chmod(file.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); - /* We chmod above but for some reasons access still fails. This should be fixed but I want to finish the linux client... */ - return true; return access(file.c_str(), F_OK | X_OK) == 0; } @@ -91,7 +89,7 @@ inline std::string build_callback_info(const std::string& error_id, const std::s void execute_callback_fail_exit(const std::string& error, const std::string& error_message) { file::rollback(); - if(!is_executable(config::callback_file)) { + if(!is_executable(config::callback_file)) {w logger::fatal("callback file (%s) is not executable! Ignoring fail callback", config::callback_file.c_str()); logger::flush(); exit(1);