Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/servers/audio/effects/audio_effect_record.cpp
10278 views
1
/**************************************************************************/
2
/* audio_effect_record.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "audio_effect_record.h"
32
33
#include "core/io/marshalls.h"
34
35
void AudioEffectRecordInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
36
if (!is_recording) {
37
for (int i = 0; i < p_frame_count; i++) {
38
p_dst_frames[i] = p_src_frames[i];
39
}
40
return;
41
}
42
43
//Add incoming audio frames to the IO ring buffer
44
const AudioFrame *src = p_src_frames;
45
AudioFrame *rb_buf = ring_buffer.ptrw();
46
for (int i = 0; i < p_frame_count; i++) {
47
p_dst_frames[i] = p_src_frames[i];
48
rb_buf[ring_buffer_pos & ring_buffer_mask] = src[i];
49
ring_buffer_pos++;
50
}
51
}
52
53
void AudioEffectRecordInstance::_update_buffer() {
54
//Case: Frames are remaining in the buffer
55
while (ring_buffer_read_pos < ring_buffer_pos) {
56
//Read from the buffer into recording_data
57
_io_store_buffer();
58
}
59
}
60
61
void AudioEffectRecordInstance::_update(void *userdata) {
62
AudioEffectRecordInstance *ins = static_cast<AudioEffectRecordInstance *>(userdata);
63
ins->_update_buffer();
64
}
65
66
bool AudioEffectRecordInstance::process_silence() const {
67
return true;
68
}
69
70
void AudioEffectRecordInstance::_io_thread_process() {
71
while (is_recording) {
72
_update_buffer();
73
if (is_recording) {
74
//Wait to avoid too much busy-wait
75
OS::get_singleton()->delay_usec(500);
76
}
77
}
78
}
79
80
void AudioEffectRecordInstance::_io_store_buffer() {
81
int to_read = ring_buffer_pos - ring_buffer_read_pos;
82
83
AudioFrame *rb_buf = ring_buffer.ptrw();
84
85
while (to_read) {
86
AudioFrame buffered_frame = rb_buf[ring_buffer_read_pos & ring_buffer_mask];
87
recording_data.push_back(buffered_frame.left);
88
recording_data.push_back(buffered_frame.right);
89
90
ring_buffer_read_pos++;
91
to_read--;
92
}
93
}
94
95
void AudioEffectRecordInstance::_thread_callback(void *_instance) {
96
AudioEffectRecordInstance *aeri = reinterpret_cast<AudioEffectRecordInstance *>(_instance);
97
98
aeri->_io_thread_process();
99
}
100
101
void AudioEffectRecordInstance::init() {
102
//Reset recorder status
103
ring_buffer_pos = 0;
104
ring_buffer_read_pos = 0;
105
106
//We start a new recording
107
recording_data.clear(); //Clear data completely and reset length
108
is_recording = true;
109
110
io_thread.start(_thread_callback, this);
111
}
112
113
void AudioEffectRecordInstance::finish() {
114
is_recording = false;
115
if (io_thread.is_started()) {
116
io_thread.wait_to_finish();
117
}
118
}
119
120
Ref<AudioEffectInstance> AudioEffectRecord::instantiate() {
121
Ref<AudioEffectRecordInstance> ins;
122
ins.instantiate();
123
ins->is_recording = false;
124
125
// Reusing the buffer size calculations from audio_effect_delay.cpp.
126
float ring_buffer_max_size = IO_BUFFER_SIZE_MS;
127
ring_buffer_max_size /= 1000.0; //convert to seconds
128
ring_buffer_max_size *= AudioServer::get_singleton()->get_mix_rate();
129
130
int ringbuff_size = ring_buffer_max_size;
131
132
int bits = 0;
133
134
while (ringbuff_size > 0) {
135
bits++;
136
ringbuff_size /= 2;
137
}
138
139
ringbuff_size = 1 << bits;
140
ins->ring_buffer_mask = ringbuff_size - 1;
141
ins->ring_buffer_pos = 0;
142
143
ins->ring_buffer.resize(ringbuff_size);
144
145
ins->ring_buffer_read_pos = 0;
146
147
ensure_thread_stopped();
148
bool is_currently_recording = false;
149
if (current_instance.is_valid()) {
150
is_currently_recording = current_instance->is_recording;
151
}
152
if (is_currently_recording) {
153
ins->init();
154
}
155
current_instance = ins;
156
157
return ins;
158
}
159
160
void AudioEffectRecord::ensure_thread_stopped() {
161
if (current_instance.is_valid()) {
162
current_instance->finish();
163
}
164
}
165
166
void AudioEffectRecord::set_recording_active(bool p_record) {
167
if (p_record) {
168
if (current_instance.is_null()) {
169
WARN_PRINT("Recording should not be set as active before Godot has initialized.");
170
return;
171
}
172
ensure_thread_stopped();
173
current_instance->init();
174
} else {
175
if (current_instance.is_valid()) {
176
current_instance->is_recording = false;
177
}
178
}
179
}
180
181
bool AudioEffectRecord::is_recording_active() const {
182
if (current_instance.is_null()) {
183
return false;
184
} else {
185
return current_instance->is_recording;
186
}
187
}
188
189
void AudioEffectRecord::set_format(AudioStreamWAV::Format p_format) {
190
format = p_format;
191
}
192
193
AudioStreamWAV::Format AudioEffectRecord::get_format() const {
194
return format;
195
}
196
197
Ref<AudioStreamWAV> AudioEffectRecord::get_recording() const {
198
AudioStreamWAV::Format dst_format = format;
199
bool stereo = true; //forcing mono is not implemented
200
201
Vector<uint8_t> dst_data;
202
203
ERR_FAIL_COND_V(current_instance.is_null(), nullptr);
204
ERR_FAIL_COND_V(current_instance->recording_data.is_empty(), nullptr);
205
206
if (dst_format == AudioStreamWAV::FORMAT_8_BITS) {
207
int data_size = current_instance->recording_data.size();
208
dst_data.resize(data_size);
209
uint8_t *w = dst_data.ptrw();
210
211
for (int i = 0; i < data_size; i++) {
212
int8_t v = CLAMP(current_instance->recording_data[i] * 128, -128, 127);
213
w[i] = v;
214
}
215
} else if (dst_format == AudioStreamWAV::FORMAT_16_BITS) {
216
int data_size = current_instance->recording_data.size();
217
dst_data.resize(data_size * 2);
218
uint8_t *w = dst_data.ptrw();
219
220
for (int i = 0; i < data_size; i++) {
221
int16_t v = CLAMP(current_instance->recording_data[i] * 32768, -32768, 32767);
222
encode_uint16(v, &w[i * 2]);
223
}
224
} else if (dst_format == AudioStreamWAV::FORMAT_IMA_ADPCM) {
225
//byte interleave
226
Vector<float> left;
227
Vector<float> right;
228
229
int tframes = current_instance->recording_data.size() / 2;
230
left.resize(tframes);
231
right.resize(tframes);
232
233
for (int i = 0; i < tframes; i++) {
234
left.set(i, current_instance->recording_data[i * 2 + 0]);
235
right.set(i, current_instance->recording_data[i * 2 + 1]);
236
}
237
238
Vector<uint8_t> bleft;
239
Vector<uint8_t> bright;
240
241
AudioStreamWAV::_compress_ima_adpcm(left, bleft);
242
AudioStreamWAV::_compress_ima_adpcm(right, bright);
243
244
int dl = bleft.size();
245
dst_data.resize(dl * 2);
246
247
uint8_t *w = dst_data.ptrw();
248
const uint8_t *rl = bleft.ptr();
249
const uint8_t *rr = bright.ptr();
250
251
for (int i = 0; i < dl; i++) {
252
w[i * 2 + 0] = rl[i];
253
w[i * 2 + 1] = rr[i];
254
}
255
} else if (dst_format == AudioStreamWAV::FORMAT_QOA) {
256
qoa_desc desc = {};
257
desc.samples = current_instance->recording_data.size() / 2;
258
desc.samplerate = AudioServer::get_singleton()->get_mix_rate();
259
desc.channels = 2;
260
AudioStreamWAV::_compress_qoa(current_instance->recording_data, dst_data, &desc);
261
} else {
262
ERR_PRINT("Format not implemented.");
263
}
264
265
Ref<AudioStreamWAV> sample;
266
sample.instantiate();
267
sample->set_data(dst_data);
268
sample->set_format(dst_format);
269
sample->set_mix_rate(AudioServer::get_singleton()->get_mix_rate());
270
sample->set_loop_mode(AudioStreamWAV::LOOP_DISABLED);
271
sample->set_loop_begin(0);
272
sample->set_loop_end(0);
273
sample->set_stereo(stereo);
274
275
return sample;
276
}
277
278
void AudioEffectRecord::_bind_methods() {
279
ClassDB::bind_method(D_METHOD("set_recording_active", "record"), &AudioEffectRecord::set_recording_active);
280
ClassDB::bind_method(D_METHOD("is_recording_active"), &AudioEffectRecord::is_recording_active);
281
ClassDB::bind_method(D_METHOD("set_format", "format"), &AudioEffectRecord::set_format);
282
ClassDB::bind_method(D_METHOD("get_format"), &AudioEffectRecord::get_format);
283
ClassDB::bind_method(D_METHOD("get_recording"), &AudioEffectRecord::get_recording);
284
285
ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "set_format", "get_format");
286
}
287
288
AudioEffectRecord::AudioEffectRecord() {
289
format = AudioStreamWAV::FORMAT_16_BITS;
290
}
291
292
AudioEffectRecord::~AudioEffectRecord() {
293
ensure_thread_stopped();
294
}
295
296