diff options
author | Gustav Lund <glu@gamblify.com> | 2018-01-18 16:03:53 +0100 |
---|---|---|
committer | Gustav Lund <glu@gamblify.com> | 2018-07-26 14:14:29 +0200 |
commit | cd2070c684c9befa4a0c8badfecfcded07d490c0 (patch) | |
tree | 0afd4e374d2879b74e9f669dbca3861c715c5cb7 /servers/audio/effects | |
parent | 8c9e10553cd429857086a9d635fc55305065ff76 (diff) |
Audio Recording module
Implements an Audio bus effect that outputs the audio from the bus into a wav file
Now channels audio recording into an AudioStreamSample instead of saving to wav
Diffstat (limited to 'servers/audio/effects')
-rw-r--r-- | servers/audio/effects/audio_effect_record.cpp | 259 | ||||
-rw-r--r-- | servers/audio/effects/audio_effect_record.h | 103 |
2 files changed, 362 insertions, 0 deletions
diff --git a/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp new file mode 100644 index 0000000000..ad5fad8464 --- /dev/null +++ b/servers/audio/effects/audio_effect_record.cpp @@ -0,0 +1,259 @@ +/*************************************************************************/ +/* audio_effect_record.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "audio_effect_record.h" + +void AudioEffectRecordInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) { + if (!is_recording) { + return; + } + + //Add incoming audio frames to the IO ring buffer + const AudioFrame *src = p_src_frames; + AudioFrame *rb_buf = ring_buffer.ptrw(); + for (int i = 0; i < p_frame_count; i++) { + rb_buf[ring_buffer_pos & ring_buffer_mask] = src[i]; + ring_buffer_pos++; + } +} + +bool AudioEffectRecordInstance::process_silence() { + return true; +} + +void AudioEffectRecordInstance::_io_thread_process() { + + //Reset recorder status + thread_active = true; + ring_buffer_pos = 0; + ring_buffer_read_pos = 0; + + //We start a new recording + recording_data.resize(0); //Clear data completely and reset length + is_recording = true; + + while (is_recording) { + //Check: The current recording has been requested to stop + if (is_recording && !base->should_record) { + is_recording = false; + } + + //Case: Frames are remaining in the buffer + if (ring_buffer_read_pos < ring_buffer_pos) { + //Read from the buffer into recording_data + _io_store_buffer(); + } + //Case: The buffer is empty + else if (is_recording) { + //Wait to avoid too much busy-wait + OS::get_singleton()->delay_usec(500); + } + } + + thread_active = false; +} + +void AudioEffectRecordInstance::_io_store_buffer() { + int to_read = ring_buffer_pos - ring_buffer_read_pos; + + AudioFrame *rb_buf = ring_buffer.ptrw(); + + while (to_read) { + AudioFrame buffered_frame = rb_buf[ring_buffer_read_pos & ring_buffer_mask]; + recording_data.push_back(buffered_frame.l); + recording_data.push_back(buffered_frame.r); + + ring_buffer_read_pos++; + to_read--; + } +} + +void AudioEffectRecordInstance::_thread_callback(void *_instance) { + + AudioEffectRecordInstance *aeri = reinterpret_cast<AudioEffectRecordInstance *>(_instance); + + aeri->_io_thread_process(); +} + +void AudioEffectRecordInstance::init() { + io_thread = Thread::create(_thread_callback, this); +} + +Ref<AudioEffectInstance> AudioEffectRecord::instance() { + Ref<AudioEffectRecordInstance> ins; + ins.instance(); + ins->base = Ref<AudioEffectRecord>(this); + ins->is_recording = false; + + //Re-using the buffer size calculations from audio_effect_delay.cpp + float ring_buffer_max_size = IO_BUFFER_SIZE_MS; + ring_buffer_max_size /= 1000.0; //convert to seconds + ring_buffer_max_size *= AudioServer::get_singleton()->get_mix_rate(); + + int ringbuff_size = ring_buffer_max_size; + + int bits = 0; + + while (ringbuff_size > 0) { + bits++; + ringbuff_size /= 2; + } + + ringbuff_size = 1 << bits; + ins->ring_buffer_mask = ringbuff_size - 1; + ins->ring_buffer_pos = 0; + + ins->ring_buffer.resize(ringbuff_size); + + ins->ring_buffer_read_pos = 0; + + ensure_thread_stopped(); + current_instance = ins; + if (should_record) { + ins->init(); + } + + return ins; +} + +void AudioEffectRecord::ensure_thread_stopped() { + should_record = false; + if (current_instance != 0 && current_instance->thread_active) { + Thread::wait_to_finish(current_instance->io_thread); + } +} + +void AudioEffectRecord::set_should_record(bool p_record) { + if (p_record) { + ensure_thread_stopped(); + current_instance->init(); + } + + should_record = p_record; +} + +bool AudioEffectRecord::get_should_record() const { + return should_record; +} + +void AudioEffectRecord::set_format(AudioStreamSample::Format p_format) { + format = p_format; +} + +AudioStreamSample::Format AudioEffectRecord::get_format() const { + return format; +} + +Ref<AudioStreamSample> AudioEffectRecord::get_recording() const { + AudioStreamSample::Format dst_format = format; + bool stereo = true; //forcing mono is not implemented + + PoolVector<uint8_t> dst_data; + + if (dst_format == AudioStreamSample::FORMAT_8_BITS) { + int data_size = current_instance->recording_data.size(); + dst_data.resize(data_size); + PoolVector<uint8_t>::Write w = dst_data.write(); + + for (int i = 0; i < data_size; i++) { + int8_t v = CLAMP(current_instance->recording_data[i] * 128, -128, 127); + w[i] = v; + } + } else if (dst_format == AudioStreamSample::FORMAT_16_BITS) { + int data_size = current_instance->recording_data.size(); + dst_data.resize(data_size * 2); + PoolVector<uint8_t>::Write w = dst_data.write(); + + for (int i = 0; i < data_size; i++) { + int16_t v = CLAMP(current_instance->recording_data[i] * 32768, -32768, 32767); + encode_uint16(v, &w[i * 2]); + } + } else if (dst_format == AudioStreamSample::FORMAT_IMA_ADPCM) { + //byte interleave + Vector<float> left; + Vector<float> right; + + int tframes = current_instance->recording_data.size() / 2; + left.resize(tframes); + right.resize(tframes); + + for (int i = 0; i < tframes; i++) { + left.set(i, current_instance->recording_data[i * 2 + 0]); + right.set(i, current_instance->recording_data[i * 2 + 1]); + } + + PoolVector<uint8_t> bleft; + PoolVector<uint8_t> bright; + + ResourceImporterWAV::_compress_ima_adpcm(left, bleft); + ResourceImporterWAV::_compress_ima_adpcm(right, bright); + + int dl = bleft.size(); + dst_data.resize(dl * 2); + + PoolVector<uint8_t>::Write w = dst_data.write(); + PoolVector<uint8_t>::Read rl = bleft.read(); + PoolVector<uint8_t>::Read rr = bright.read(); + + for (int i = 0; i < dl; i++) { + w[i * 2 + 0] = rl[i]; + w[i * 2 + 1] = rr[i]; + } + } else { + ERR_EXPLAIN("format not implemented"); + } + + Ref<AudioStreamSample> sample; + sample.instance(); + sample->set_data(dst_data); + sample->set_format(dst_format); + sample->set_mix_rate(AudioServer::get_singleton()->get_mix_rate()); + sample->set_loop_mode(AudioStreamSample::LOOP_DISABLED); + sample->set_loop_begin(0); + sample->set_loop_end(0); + sample->set_stereo(stereo); + + return sample; +} + +void AudioEffectRecord::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_should_record", "record"), &AudioEffectRecord::set_should_record); + ClassDB::bind_method(D_METHOD("get_should_record"), &AudioEffectRecord::get_should_record); + ClassDB::bind_method(D_METHOD("set_format", "format"), &AudioEffectRecord::set_format); + ClassDB::bind_method(D_METHOD("get_format"), &AudioEffectRecord::get_format); + ClassDB::bind_method(D_METHOD("get_recording"), &AudioEffectRecord::get_recording); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "should_record"), "set_should_record", "get_should_record"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format"); +} + +AudioEffectRecord::AudioEffectRecord() { + format = AudioStreamSample::FORMAT_16_BITS; +} diff --git a/servers/audio/effects/audio_effect_record.h b/servers/audio/effects/audio_effect_record.h new file mode 100644 index 0000000000..05c2d7352f --- /dev/null +++ b/servers/audio/effects/audio_effect_record.h @@ -0,0 +1,103 @@ +/*************************************************************************/ +/* audio_effect_record.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef AUDIOEFFECTRECORD_H +#define AUDIOEFFECTRECORD_H + +#include "core/os/thread.h" +#include "editor/import/resource_importer_wav.h" +#include "io/marshalls.h" +#include "os/file_access.h" +#include "os/os.h" +#include "scene/resources/audio_stream_sample.h" +#include "servers/audio/audio_effect.h" +#include "servers/audio_server.h" + +class AudioEffectRecord; + +class AudioEffectRecordInstance : public AudioEffectInstance { + GDCLASS(AudioEffectRecordInstance, AudioEffectInstance) + friend class AudioEffectRecord; + Ref<AudioEffectRecord> base; + + bool is_recording; + Thread *io_thread; + bool thread_active = false; + + Vector<AudioFrame> ring_buffer; + Vector<float> recording_data; + + unsigned int ring_buffer_pos; + unsigned int ring_buffer_mask; + unsigned int ring_buffer_read_pos; + + void _io_thread_process(); + void _io_store_buffer(); + static void _thread_callback(void *_instance); + void _init_recording(); + +public: + void init(); + virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count); + virtual bool process_silence(); +}; + +class AudioEffectRecord : public AudioEffect { + GDCLASS(AudioEffectRecord, AudioEffect) + + friend class AudioEffectRecordInstance; + + enum { + IO_BUFFER_SIZE_MS = 1500 + }; + + bool should_record; + Ref<AudioEffectRecordInstance> current_instance; + + AudioStreamSample::Format format; + + void ensure_thread_stopped(); + +protected: + static void _bind_methods(); + static void debug(uint64_t time_diff, int p_frame_count); + +public: + Ref<AudioEffectInstance> instance(); + void set_should_record(bool p_record); + bool get_should_record() const; + void set_format(AudioStreamSample::Format p_format); + AudioStreamSample::Format get_format() const; + Ref<AudioStreamSample> get_recording() const; + + AudioEffectRecord(); +}; + +#endif // AUDIOEFFECTRECORD_H |