1 /*************************************************************************/
2 /*  audio_stream_speex.cpp                                               */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur.                 */
9 /*                                                                       */
10 /* Permission is hereby granted, free of charge, to any person obtaining */
11 /* a copy of this software and associated documentation files (the       */
12 /* "Software"), to deal in the Software without restriction, including   */
13 /* without limitation the rights to use, copy, modify, merge, publish,   */
14 /* distribute, sublicense, and/or sell copies of the Software, and to    */
15 /* permit persons to whom the Software is furnished to do so, subject to */
16 /* the following conditions:                                             */
17 /*                                                                       */
18 /* The above copyright notice and this permission notice shall be        */
19 /* included in all copies or substantial portions of the Software.       */
20 /*                                                                       */
21 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
22 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
23 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
24 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
25 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
26 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
27 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
28 /*************************************************************************/
29 #include "audio_stream_speex.h"
30 
31 #include "os/os.h"
32 #include "thirdparty/speex/os_support.h"
33 #define READ_CHUNK 1024
34 
le_short(uint16_t s)35 static _FORCE_INLINE_ uint16_t le_short(uint16_t s) {
36 	uint16_t ret = s;
37 #if 0 //def BIG_ENDIAN_ENABLED
38    ret =  s>>8;
39    ret += s<<8;
40 #endif
41 	return ret;
42 }
43 
mix(int16_t * p_buffer,int p_frames)44 int AudioStreamPlaybackSpeex::mix(int16_t *p_buffer, int p_frames) {
45 
46 	//printf("update, loops %i, read ofs %i\n", (int)loops, read_ofs);
47 	//printf("playing %i, paused %i\n", (int)playing, (int)paused);
48 
49 	if (!active || !playing || !data.size())
50 		return 0;
51 
52 	/*
53 	if (read_ofs >= data.size()) {
54 		if (loops) {
55 			reload();
56 			++loop_count;
57 		} else {
58 			return;
59 		};
60 	};
61 	*/
62 
63 	int todo = p_frames;
64 	if (todo < page_size) {
65 		return 0;
66 	};
67 
68 	int eos = 0;
69 
70 	while (todo > page_size) {
71 
72 		int ret = 0;
73 		while ((todo > page_size && packets_available && !eos) || (ret = ogg_sync_pageout(&oy, &og)) == 1) {
74 
75 			if (!packets_available) {
76 				/*Add page to the bitstream*/
77 				ogg_stream_pagein(&os, &og);
78 				page_granule = ogg_page_granulepos(&og);
79 				page_nb_packets = ogg_page_packets(&og);
80 				packet_no = 0;
81 				if (page_granule > 0 && frame_size) {
82 					skip_samples = page_nb_packets * frame_size * nframes - (page_granule - last_granule);
83 					if (ogg_page_eos(&og))
84 						skip_samples = -skip_samples;
85 					/*else if (!ogg_page_bos(&og))
86 					skip_samples = 0;*/
87 				} else {
88 					skip_samples = 0;
89 				}
90 
91 				last_granule = page_granule;
92 				packets_available = true;
93 			}
94 			/*Extract all available packets*/
95 			while (todo > page_size && !eos) {
96 
97 				if (ogg_stream_packetout(&os, &op) != 1) {
98 					packets_available = false;
99 					break;
100 				}
101 
102 				packet_no++;
103 
104 				/*End of stream condition*/
105 				if (op.e_o_s)
106 					eos = 1;
107 
108 				/*Copy Ogg packet to Speex bitstream*/
109 				speex_bits_read_from(&bits, (char *)op.packet, op.bytes);
110 
111 				for (int j = 0; j != nframes; j++) {
112 
113 					int16_t *out = p_buffer;
114 
115 					int ret;
116 					/*Decode frame*/
117 					ret = speex_decode_int(st, &bits, out);
118 
119 					/*for (i=0;i<frame_size*channels;i++)
120 					  printf ("%d\n", (int)output[i]);*/
121 
122 					if (ret == -1) {
123 						printf("decode returned -1\n");
124 						break;
125 					};
126 					if (ret == -2) {
127 						OS::get_singleton()->printerr("Decoding error: corrupted stream?\n");
128 						break;
129 					}
130 					if (speex_bits_remaining(&bits) < 0) {
131 						OS::get_singleton()->printerr("Decoding overflow: corrupted stream?\n");
132 						break;
133 					}
134 					//if (channels==2)
135 					//	speex_decode_stereo_int(output, frame_size, &stereo);
136 
137 					/*Convert to short and save to output file*/
138 					for (int i = 0; i < frame_size * stream_channels; i++) {
139 						out[i] = le_short(out[i]);
140 					}
141 
142 					{
143 
144 						int new_frame_size = frame_size;
145 
146 						/*printf ("packet %d %d\n", packet_no, skip_samples);*/
147 						if (packet_no == 1 && j == 0 && skip_samples > 0) {
148 							/*printf ("chopping first packet\n");*/
149 							new_frame_size -= skip_samples;
150 						}
151 						if (packet_no == page_nb_packets && skip_samples < 0) {
152 							int packet_length = nframes * frame_size + skip_samples;
153 							new_frame_size = packet_length - j * frame_size;
154 							if (new_frame_size < 0)
155 								new_frame_size = 0;
156 							if (new_frame_size > frame_size)
157 								new_frame_size = frame_size;
158 							/*printf ("chopping end: %d %d %d\n", new_frame_size, packet_length, packet_no);*/
159 						}
160 
161 						p_buffer += new_frame_size * stream_channels;
162 						todo -= new_frame_size;
163 					}
164 				}
165 			};
166 		};
167 		//todo = get_todo();
168 
169 		//todo is still greater than page size, can write more
170 		if (todo > page_size || eos) {
171 			if (read_ofs < data.size()) {
172 
173 				//char *buf;
174 				int nb_read = MIN(data.size() - read_ofs, READ_CHUNK);
175 
176 				/*Get the ogg buffer for writing*/
177 				char *ogg_dst = ogg_sync_buffer(&oy, nb_read);
178 				/*Read bitstream from input file*/
179 				copymem(ogg_dst, &data[read_ofs], nb_read);
180 				read_ofs += nb_read;
181 				ogg_sync_wrote(&oy, nb_read);
182 			} else {
183 				if (loops) {
184 					reload();
185 					++loop_count;
186 					//break;
187 				} else {
188 					playing = false;
189 					unload();
190 					break;
191 				};
192 			}
193 		};
194 	};
195 
196 	return p_frames - todo;
197 };
198 
unload()199 void AudioStreamPlaybackSpeex::unload() {
200 
201 	if (!active) return;
202 
203 	speex_bits_destroy(&bits);
204 	if (st)
205 		speex_decoder_destroy(st);
206 
207 	ogg_sync_clear(&oy);
208 	active = false;
209 	//data.resize(0);
210 	st = NULL;
211 
212 	frame_size = 0;
213 	page_size = 0;
214 	loop_count = 0;
215 }
216 
process_header(ogg_packet * op,int * frame_size,int * rate,int * nframes,int * channels,int * extra_headers)217 void *AudioStreamPlaybackSpeex::process_header(ogg_packet *op, int *frame_size, int *rate, int *nframes, int *channels, int *extra_headers) {
218 
219 	SpeexHeader *header;
220 	int modeID;
221 
222 	header = speex_packet_to_header((char *)op->packet, op->bytes);
223 	if (!header) {
224 		OS::get_singleton()->printerr("Cannot read header\n");
225 		return NULL;
226 	}
227 	if (header->mode >= SPEEX_NB_MODES) {
228 		OS::get_singleton()->printerr("Mode number %d does not (yet/any longer) exist in this version\n",
229 				header->mode);
230 		return NULL;
231 	}
232 
233 	modeID = header->mode;
234 
235 	const SpeexMode *mode = speex_lib_get_mode(modeID);
236 
237 	if (header->speex_version_id > 1) {
238 		OS::get_singleton()->printerr("This file was encoded with Speex bit-stream version %d, which I don't know how to decode\n", header->speex_version_id);
239 		return NULL;
240 	}
241 
242 	if (mode->bitstream_version < header->mode_bitstream_version) {
243 		OS::get_singleton()->printerr("The file was encoded with a newer version of Speex. You need to upgrade in order to play it.\n");
244 		return NULL;
245 	}
246 	if (mode->bitstream_version > header->mode_bitstream_version) {
247 		OS::get_singleton()->printerr("The file was encoded with an older version of Speex. You would need to downgrade the version in order to play it.\n");
248 		return NULL;
249 	}
250 
251 	void *state = speex_decoder_init(mode);
252 	if (!state) {
253 		OS::get_singleton()->printerr("Decoder initialization failed.\n");
254 		return NULL;
255 	}
256 	//speex_decoder_ctl(state, SPEEX_SET_ENH, &enh_enabled);
257 	speex_decoder_ctl(state, SPEEX_GET_FRAME_SIZE, frame_size);
258 
259 	if (!*rate)
260 		*rate = header->rate;
261 
262 	speex_decoder_ctl(state, SPEEX_SET_SAMPLING_RATE, rate);
263 
264 	*nframes = header->frames_per_packet;
265 
266 	*channels = header->nb_channels;
267 
268 	if (*channels != 1) {
269 		OS::get_singleton()->printerr("Only MONO speex streams supported\n");
270 		return NULL;
271 	}
272 
273 	*extra_headers = header->extra_headers;
274 
275 	speex_free(header);
276 	return state;
277 }
278 
reload()279 void AudioStreamPlaybackSpeex::reload() {
280 
281 	if (active)
282 		unload();
283 
284 	if (!data.size())
285 		return;
286 
287 	ogg_sync_init(&oy);
288 	speex_bits_init(&bits);
289 
290 	read_ofs = 0;
291 	//	char *buf;
292 
293 	int packet_count = 0;
294 	int extra_headers = 0;
295 	int stream_init = 0;
296 
297 	page_granule = 0;
298 	last_granule = 0;
299 	skip_samples = 0;
300 	page_nb_packets = 0;
301 	packets_available = false;
302 	packet_no = 0;
303 
304 	int eos = 0;
305 
306 	do {
307 
308 		/*Get the ogg buffer for writing*/
309 		int nb_read = MIN(data.size() - read_ofs, READ_CHUNK);
310 		char *ogg_dst = ogg_sync_buffer(&oy, nb_read);
311 		/*Read bitstream from input file*/
312 		copymem(ogg_dst, &data[read_ofs], nb_read);
313 		read_ofs += nb_read;
314 		ogg_sync_wrote(&oy, nb_read);
315 
316 		/*Loop for all complete pages we got (most likely only one)*/
317 		while (ogg_sync_pageout(&oy, &og) == 1) {
318 
319 			if (stream_init == 0) {
320 				ogg_stream_init(&os, ogg_page_serialno(&og));
321 				stream_init = 1;
322 			}
323 			/*Add page to the bitstream*/
324 			ogg_stream_pagein(&os, &og);
325 			page_granule = ogg_page_granulepos(&og);
326 			page_nb_packets = ogg_page_packets(&og);
327 			if (page_granule > 0 && frame_size) {
328 				skip_samples = page_nb_packets * frame_size * nframes - (page_granule - last_granule);
329 				if (ogg_page_eos(&og))
330 					skip_samples = -skip_samples;
331 				/*else if (!ogg_page_bos(&og))
332 				  skip_samples = 0;*/
333 			} else {
334 				skip_samples = 0;
335 			}
336 
337 			last_granule = page_granule;
338 			/*Extract all available packets*/
339 			while (!eos && ogg_stream_packetout(&os, &op) == 1) {
340 				/*If first packet, process as Speex header*/
341 				if (packet_count == 0) {
342 					int rate = 0;
343 					int channels;
344 					st = process_header(&op, &frame_size, &rate, &nframes, &channels, &extra_headers);
345 					if (!nframes)
346 						nframes = 1;
347 					if (!st) {
348 						unload();
349 						return;
350 					};
351 
352 					page_size = nframes * frame_size;
353 					stream_srate = rate;
354 					stream_channels = channels;
355 					stream_minbuff_size = page_size;
356 
357 				} else if (packet_count == 1) {
358 				} else if (packet_count <= 1 + extra_headers) {
359 					/* Ignore extra headers */
360 				};
361 			};
362 			++packet_count;
363 		};
364 
365 	} while (packet_count <= extra_headers);
366 
367 	active = true;
368 }
369 
_bind_methods()370 void AudioStreamPlaybackSpeex::_bind_methods() {
371 
372 	//ObjectTypeDB::bind_method(_MD("set_file","file"),&AudioStreamPlaybackSpeex::set_file);
373 	//	ObjectTypeDB::bind_method(_MD("get_file"),&AudioStreamPlaybackSpeex::get_file);
374 
375 	ObjectTypeDB::bind_method(_MD("_set_bundled"), &AudioStreamPlaybackSpeex::_set_bundled);
376 	ObjectTypeDB::bind_method(_MD("_get_bundled"), &AudioStreamPlaybackSpeex::_get_bundled);
377 
378 	ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_bundled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_BUNDLE), _SCS("_set_bundled"), _SCS("_get_bundled"));
379 	//ADD_PROPERTY( PropertyInfo(Variant::STRING,"file",PROPERTY_HINT_FILE,"*.spx"),_SCS("set_file"),_SCS("get_file"));
380 };
381 
_set_bundled(const Dictionary & dict)382 void AudioStreamPlaybackSpeex::_set_bundled(const Dictionary &dict) {
383 
384 	ERR_FAIL_COND(!dict.has("filename"));
385 	ERR_FAIL_COND(!dict.has("data"));
386 
387 	filename = dict["filename"];
388 	data = dict["data"];
389 };
390 
_get_bundled() const391 Dictionary AudioStreamPlaybackSpeex::_get_bundled() const {
392 
393 	Dictionary d;
394 	d["filename"] = filename;
395 	d["data"] = data;
396 	return d;
397 };
398 
set_data(const Vector<uint8_t> & p_data)399 void AudioStreamPlaybackSpeex::set_data(const Vector<uint8_t> &p_data) {
400 
401 	data = p_data;
402 	reload();
403 }
404 
play(float p_from_pos)405 void AudioStreamPlaybackSpeex::play(float p_from_pos) {
406 
407 	reload();
408 	if (!active)
409 		return;
410 	playing = true;
411 }
stop()412 void AudioStreamPlaybackSpeex::stop() {
413 
414 	unload();
415 	playing = false;
416 }
is_playing() const417 bool AudioStreamPlaybackSpeex::is_playing() const {
418 
419 	return playing;
420 }
421 
set_loop(bool p_enable)422 void AudioStreamPlaybackSpeex::set_loop(bool p_enable) {
423 
424 	loops = p_enable;
425 }
has_loop() const426 bool AudioStreamPlaybackSpeex::has_loop() const {
427 
428 	return loops;
429 }
430 
get_length() const431 float AudioStreamPlaybackSpeex::get_length() const {
432 
433 	return 0;
434 }
435 
get_stream_name() const436 String AudioStreamPlaybackSpeex::get_stream_name() const {
437 
438 	return "";
439 }
440 
get_loop_count() const441 int AudioStreamPlaybackSpeex::get_loop_count() const {
442 
443 	return 0;
444 }
445 
get_pos() const446 float AudioStreamPlaybackSpeex::get_pos() const {
447 
448 	return 0;
449 }
seek_pos(float p_time)450 void AudioStreamPlaybackSpeex::seek_pos(float p_time){
451 
452 };
453 
AudioStreamPlaybackSpeex()454 AudioStreamPlaybackSpeex::AudioStreamPlaybackSpeex() {
455 
456 	active = false;
457 	st = NULL;
458 	stream_channels = 1;
459 	stream_srate = 1;
460 	stream_minbuff_size = 1;
461 	playing = false;
462 }
463 
~AudioStreamPlaybackSpeex()464 AudioStreamPlaybackSpeex::~AudioStreamPlaybackSpeex() {
465 
466 	unload();
467 }
468 
469 ////////////////////////////////////////
470 
set_file(const String & p_file)471 void AudioStreamSpeex::set_file(const String &p_file) {
472 
473 	if (this->file == p_file)
474 		return;
475 
476 	this->file = p_file;
477 
478 	if (p_file == "") {
479 		data.resize(0);
480 		return;
481 	};
482 
483 	Error err;
484 	FileAccess *file = FileAccess::open(p_file, FileAccess::READ, &err);
485 	if (err != OK) {
486 		data.resize(0);
487 	};
488 	ERR_FAIL_COND(err != OK);
489 
490 	this->file = p_file;
491 	data.resize(file->get_len());
492 	int read = file->get_buffer(&data[0], data.size());
493 	memdelete(file);
494 }
495 
load(const String & p_path,const String & p_original_path,Error * r_error)496 RES ResourceFormatLoaderAudioStreamSpeex::load(const String &p_path, const String &p_original_path, Error *r_error) {
497 
498 	if (r_error)
499 		*r_error = OK;
500 
501 	AudioStreamSpeex *stream = memnew(AudioStreamSpeex);
502 	stream->set_file(p_path);
503 	return Ref<AudioStreamSpeex>(stream);
504 }
505 
get_recognized_extensions(List<String> * p_extensions) const506 void ResourceFormatLoaderAudioStreamSpeex::get_recognized_extensions(List<String> *p_extensions) const {
507 
508 	p_extensions->push_back("spx");
509 }
handles_type(const String & p_type) const510 bool ResourceFormatLoaderAudioStreamSpeex::handles_type(const String &p_type) const {
511 
512 	return (p_type == "AudioStream" || p_type == "AudioStreamSpeex");
513 }
514 
get_resource_type(const String & p_path) const515 String ResourceFormatLoaderAudioStreamSpeex::get_resource_type(const String &p_path) const {
516 
517 	if (p_path.extension().to_lower() == "spx")
518 		return "AudioStreamSpeex";
519 	return "";
520 }
521