Some changes

This commit is contained in:
WolverinDEV 2021-05-09 12:53:58 +02:00
parent 4976384e4f
commit 8a1a50f286
12 changed files with 109 additions and 72 deletions

2
github

@ -1 +1 @@
Subproject commit a46f54984b7ad9721c01273e77807469cd2c63a9 Subproject commit dda7ad2b270b3c598f43757c0e9aaee1adafa92c

View File

@ -106,7 +106,7 @@ export class NativeAudioPlayer implements AudioBackend {
} }
getDefaultDeviceId(): string { 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 { isDeviceRefreshAvailable(): boolean {

View File

@ -111,10 +111,6 @@ bool AudioInput::record(std::string& error) {
return false; return false;
} }
if(this->input_recorder->sample_rate() != this->sample_rate()) {
this->resampler_ = std::make_unique<AudioResampler>(this->input_recorder->sample_rate(), this->sample_rate(), this->channel_count_);
}
this->input_recorder->register_consumer(this); this->input_recorder->register_consumer(this);
if(!this->input_recorder->start(error)) { if(!this->input_recorder->start(error)) {
this->input_recorder->remove_consumer(this); this->input_recorder->remove_consumer(this);
@ -134,6 +130,8 @@ void AudioInput::stop() {
this->input_recorder->remove_consumer(this); this->input_recorder->remove_consumer(this);
this->input_recorder->stop_if_possible(); this->input_recorder->stop_if_possible();
this->input_recorder.reset(); this->input_recorder.reset();
this->resampler_ = nullptr;
} }
std::vector<std::shared_ptr<AudioInputConsumer>> AudioInput::consumers() { std::vector<std::shared_ptr<AudioInputConsumer>> 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}; constexpr static auto kTempBufferMaxSampleCount{1024 * 8};
float temp_buffer[kTempBufferMaxSampleCount]; float temp_buffer[kTempBufferMaxSampleCount];
/* TODO: Short circuit for silence here */ /* 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<AudioResampler>(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 != this->channel_count_) {
if(channels < 1 || channels > 2) { 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; input = temp_buffer;
} }
if(this->resampler_) { const auto expected_size = this->resampler_->estimated_output_size(sample_count);
const auto expected_size = this->resampler_->estimated_output_size(sample_count); this->allocate_input_buffer_samples(expected_size);
this->allocate_input_buffer_samples(expected_size);
size_t resampled_sample_count{expected_size}; size_t resampled_sample_count{expected_size};
if(!this->resampler_->process(this->input_buffer.write_ptr(), input, sample_count, resampled_sample_count)) { 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.")); log_error(category::audio, tr("Failed to resample audio input."));
return; 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);
}
this->input_buffer.advance_write_ptr(resampled_sample_count * this->channel_count_ * sizeof(float));
audio::encode_event_loop->schedule(this->event_loop_entry); audio::encode_event_loop->schedule(this->event_loop_entry);
} }

View File

@ -69,7 +69,7 @@ namespace tc::audio {
AudioInput* input{nullptr}; 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();
void process_audio_chunk(float *); void process_audio_chunk(float *);
@ -92,6 +92,8 @@ namespace tc::audio {
std::unique_ptr<AudioResampler> resampler_{nullptr}; std::unique_ptr<AudioResampler> resampler_{nullptr};
std::shared_ptr<AudioDevice> input_device{}; std::shared_ptr<AudioDevice> input_device{};
size_t current_input_sample_rate{0};
float volume_{1.f}; float volume_{1.f};
void allocate_input_buffer_samples(size_t /* sample count */); void allocate_input_buffer_samples(size_t /* sample count */);

View File

@ -94,7 +94,7 @@ void InputDeviceAudioLevelMeter::stop() {
} }
/* Note the parameter order! */ /* 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); this->analyze_buffer((const float*) buffer, channel_count, sample_count);
} }

View File

@ -55,7 +55,7 @@ namespace tc::audio {
std::shared_ptr<AudioDevice> target_device{}; std::shared_ptr<AudioDevice> target_device{};
std::shared_ptr<AudioDeviceRecord> recorder_instance{}; std::shared_ptr<AudioDeviceRecord> 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 { class AudioInputAudioLevelMeter : public AbstractAudioLevelMeter {

View File

@ -11,7 +11,7 @@ namespace tc::audio {
public: public:
class Consumer { class Consumer {
public: 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; [[nodiscard]] virtual size_t sample_rate() const = 0;

View File

@ -10,8 +10,8 @@
namespace tc::audio::pa { namespace tc::audio::pa {
class PortAudioPlayback : public AudioDevicePlayback { class PortAudioPlayback : public AudioDevicePlayback {
public: public:
static constexpr auto kChannelCount{2}; static constexpr auto kDefaultChannelCount{2};
static constexpr auto kSampleRate{48000}; static constexpr std::array<size_t, 2> kSupportedSampleRates{48000, 44100};
explicit PortAudioPlayback(PaDeviceIndex index, const PaDeviceInfo* info); explicit PortAudioPlayback(PaDeviceIndex index, const PaDeviceInfo* info);
virtual ~PortAudioPlayback(); virtual ~PortAudioPlayback();
@ -28,13 +28,14 @@ namespace tc::audio::pa {
const PaDeviceInfo* info; const PaDeviceInfo* info;
PaStream* stream{nullptr}; PaStream* stream{nullptr};
size_t source_sample_rate{0};
size_t source_channel_count{0}; size_t source_channel_count{0};
}; };
class PortAudioRecord : public AudioDeviceRecord { class PortAudioRecord : public AudioDeviceRecord {
public: public:
static constexpr auto kChannelCount{2}; static constexpr auto kDefaultChannelCount{2};
static constexpr auto kSampleRate{48000}; static constexpr std::array<size_t, 2> kSupportedSampleRates{48000, 44100};
explicit PortAudioRecord(PaDeviceIndex index, const PaDeviceInfo* info); explicit PortAudioRecord(PaDeviceIndex index, const PaDeviceInfo* info);
virtual ~PortAudioRecord(); virtual ~PortAudioRecord();
@ -47,6 +48,7 @@ namespace tc::audio::pa {
private: private:
void read_callback(const void *input, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags); 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}; size_t source_channel_count{0};
PaDeviceIndex index; PaDeviceIndex index;

View File

@ -49,34 +49,48 @@ bool PortAudioPlayback::impl_start(std::string &error) {
parameters.sampleFormat = paFloat32; parameters.sampleFormat = paFloat32;
parameters.suggestedLatency = this->info->defaultLowInputLatency; parameters.suggestedLatency = this->info->defaultLowInputLatency;
if(this->info->maxOutputChannels < kChannelCount) { if(this->info->maxOutputChannels < kDefaultChannelCount) {
parameters.channelCount = (int) 1; parameters.channelCount = (int) 1;
this->source_channel_count = 1; this->source_channel_count = 1;
} else { } else {
parameters.channelCount = (int) kChannelCount; parameters.channelCount = (int) kDefaultChannelCount;
this->source_channel_count = kChannelCount; 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,
&parameters,
(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( if(err == paNoError) {
&this->stream, this->source_sample_rate = sample_rate;
nullptr, break;
&parameters, }
(double) kSampleRate,
paFramesPerBufferUnspecified,
paClipOff,
proxied_write_callback,
this
);
if(err != paNoError) {
this->stream = nullptr; this->stream = nullptr;
if(err == paInvalidSampleRate) {
/* Try next sample rate */
continue;
}
error = std::string{Pa_GetErrorText(err)} + " (open stream: " + std::to_string(err) + ")"; error = std::string{Pa_GetErrorText(err)} + " (open stream: " + std::to_string(err) + ")";
return false; 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) { if(err != paNoError) {
error = std::string{Pa_GetErrorText(err)} + "(start stream: " + std::to_string(err) + ")"; error = std::string{Pa_GetErrorText(err)} + "(start stream: " + std::to_string(err) + ")";
err = Pa_CloseStream(this->stream); err = Pa_CloseStream(this->stream);
@ -101,13 +115,12 @@ void PortAudioPlayback::impl_stop() {
} }
size_t PortAudioPlayback::current_sample_rate() const { size_t PortAudioPlayback::current_sample_rate() const {
/* We currently only support one sample rate */ return this->source_sample_rate;
return (size_t) kSampleRate;
} }
void PortAudioPlayback::write_callback(void *output, unsigned long frameCount, void PortAudioPlayback::write_callback(void *output, unsigned long frameCount,
const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags) { const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags) {
(void) timeInfo; (void) timeInfo;
(void) statusFlags; (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);
} }

View File

@ -16,7 +16,6 @@ PortAudioRecord::~PortAudioRecord() {
} }
bool PortAudioRecord::impl_start(std::string &error) { bool PortAudioRecord::impl_start(std::string &error) {
//TODO: Detect correct sample rate
{ {
auto device_info = Pa_GetDeviceInfo(this->index); auto device_info = Pa_GetDeviceInfo(this->index);
if(this->info != device_info) { if(this->info != device_info) {
@ -42,35 +41,52 @@ bool PortAudioRecord::impl_start(std::string &error) {
PaStreamParameters parameters{}; PaStreamParameters parameters{};
memset(&parameters, 0, sizeof(parameters)); memset(&parameters, 0, sizeof(parameters));
if(this->info->maxInputChannels < kChannelCount) { if(this->info->maxInputChannels < kDefaultChannelCount) {
parameters.channelCount = (int) 1; parameters.channelCount = (int) 1;
this->source_channel_count = 1; this->source_channel_count = 1;
} else { } else {
parameters.channelCount = (int) kChannelCount; parameters.channelCount = (int) kDefaultChannelCount;
this->source_channel_count = kChannelCount; 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.device = this->index;
parameters.sampleFormat = paFloat32; parameters.sampleFormat = paFloat32;
parameters.suggestedLatency = this->info->defaultLowInputLatency; parameters.suggestedLatency = this->info->defaultLowInputLatency;
auto err = Pa_OpenStream(
&this->stream,
&parameters,
nullptr,
(double) kSampleRate,
paFramesPerBufferUnspecified,
paClipOff,
proxied_read_callback,
this);
if(err != paNoError) { for(const auto& sample_rate : kSupportedSampleRates) {
auto err = Pa_OpenStream(
&this->stream,
&parameters,
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; this->stream = nullptr;
if(err == paInvalidSampleRate) {
/* Try next sample rate */
continue;
}
error = std::string{Pa_GetErrorText(err)} + " (open stream: " + std::to_string(err) + ")"; error = std::string{Pa_GetErrorText(err)} + " (open stream: " + std::to_string(err) + ")";
return false; 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) { if(err != paNoError) {
error = std::string{Pa_GetErrorText(err)} + "(start stream: " + std::to_string(err) + ")"; error = std::string{Pa_GetErrorText(err)} + "(start stream: " + std::to_string(err) + ")";
err = Pa_CloseStream(this->stream); err = Pa_CloseStream(this->stream);
@ -98,13 +114,13 @@ void PortAudioRecord::impl_stop() {
} }
size_t PortAudioRecord::sample_rate() const { size_t PortAudioRecord::sample_rate() const {
return kSampleRate; return this->source_sample_rate;
} }
void PortAudioRecord::read_callback(const void *input, unsigned long frameCount, void PortAudioRecord::read_callback(const void *input, unsigned long frameCount,
const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags) { const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags) {
std::lock_guard consumer_lock{this->consumer_lock}; std::lock_guard consumer_lock{this->consumer_lock};
for(auto& consumer : this->_consumers) { 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);
} }
} }

View File

@ -190,6 +190,8 @@ namespace tc::audio::sounds {
return false; 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) { if(self->state_ == PLAYER_STATE_PLAYING) {
log_warn(category::audio, tr("Having an audio underflow while playing a sound.")); log_warn(category::audio, tr("Having an audio underflow while playing a sound."));
} else if(self->state_ == PLAYER_STATE_AWAIT_FINISH) { } else if(self->state_ == PLAYER_STATE_AWAIT_FINISH) {
@ -204,6 +206,8 @@ namespace tc::audio::sounds {
return; 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) { if(self->could_enqueue_next_buffer() && self->state_ == PLAYER_STATE_PLAYING) {
audio::decode_event_loop->schedule(self); audio::decode_event_loop->schedule(self);
} }

View File

@ -51,8 +51,6 @@ using namespace std;
bool is_executable(const std::string& file) { bool is_executable(const std::string& file) {
chmod(file.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 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; 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) { void execute_callback_fail_exit(const std::string& error, const std::string& error_message) {
file::rollback(); 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::fatal("callback file (%s) is not executable! Ignoring fail callback", config::callback_file.c_str());
logger::flush(); logger::flush();
exit(1); exit(1);