aboutsummaryrefslogtreecommitdiff
path: root/drivers/speex/audio_stream_speex.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--drivers/speex/audio_stream_speex.cpp554
1 files changed, 554 insertions, 0 deletions
diff --git a/drivers/speex/audio_stream_speex.cpp b/drivers/speex/audio_stream_speex.cpp
new file mode 100644
index 000000000..a4209a422
--- /dev/null
+++ b/drivers/speex/audio_stream_speex.cpp
@@ -0,0 +1,554 @@
+#include "audio_stream_speex.h"
+
+#include "os_support.h"
+#include "os/os.h"
+#define READ_CHUNK 1024
+
+static _FORCE_INLINE_ uint16_t le_short(uint16_t s)
+{
+ uint16_t ret=s;
+#ifdef BIG_ENDIAN_ENABLED
+ ret = s>>8;
+ ret += s<<8;
+#endif
+ return ret;
+}
+
+
+void AudioStreamSpeex::update() {
+
+ _THREAD_SAFE_METHOD_;
+ //printf("update, loops %i, read ofs %i\n", (int)loops, read_ofs);
+ //printf("playing %i, paused %i\n", (int)playing, (int)paused);
+
+ if (!playing || paused || !data.size())
+ return;
+
+ /*
+ if (read_ofs >= data.size()) {
+ if (loops) {
+ reload();
+ ++loop_count;
+ } else {
+ return;
+ };
+ };
+ */
+
+ int todo = get_todo();
+ if (todo < page_size) {
+ return;
+ };
+
+ int eos = 0;
+
+ while (todo > page_size) {
+
+ int ret=0;
+ while ((todo>page_size && packets_available && !eos) || (ret = ogg_sync_pageout(&oy, &og))==1) {
+
+ if (!packets_available) {
+ /*Add page to the bitstream*/
+ ogg_stream_pagein(&os, &og);
+ page_granule = ogg_page_granulepos(&og);
+ page_nb_packets = ogg_page_packets(&og);
+ packet_no=0;
+ if (page_granule>0 && frame_size)
+ {
+ skip_samples = page_nb_packets*frame_size*nframes - (page_granule-last_granule);
+ if (ogg_page_eos(&og))
+ skip_samples = -skip_samples;
+ /*else if (!ogg_page_bos(&og))
+ skip_samples = 0;*/
+ } else
+ {
+ skip_samples = 0;
+ }
+
+
+ last_granule = page_granule;
+ packets_available=true;
+ }
+ /*Extract all available packets*/
+ //int packet_no=0;
+ while (todo > page_size && !eos) {
+
+ if (ogg_stream_packetout(&os, &op)!=1) {
+ packets_available=false;
+ break;
+ }
+
+ packet_no++;
+
+
+ /*End of stream condition*/
+ if (op.e_o_s)
+ eos=1;
+
+ /*Copy Ogg packet to Speex bitstream*/
+ speex_bits_read_from(&bits, (char*)op.packet, op.bytes);
+
+
+ for (int j=0;j!=nframes;j++)
+ {
+
+ int16_t* out = get_write_buffer();
+
+ int ret;
+ /*Decode frame*/
+ ret = speex_decode_int(st, &bits, out);
+
+ /*for (i=0;i<frame_size*channels;i++)
+ printf ("%d\n", (int)output[i]);*/
+
+ if (ret==-1) {
+ printf("decode returned -1\n");
+ break;
+ };
+ if (ret==-2)
+ {
+ OS::get_singleton()->printerr( "Decoding error: corrupted stream?\n");
+ break;
+ }
+ if (speex_bits_remaining(&bits)<0)
+ {
+ OS::get_singleton()->printerr( "Decoding overflow: corrupted stream?\n");
+ break;
+ }
+ //if (channels==2)
+ // speex_decode_stereo_int(output, frame_size, &stereo);
+
+
+ /*Convert to short and save to output file*/
+ for (int i=0;i<frame_size*get_channel_count();i++) {
+ out[i]=le_short(out[i]);
+ }
+
+
+ {
+
+ int frame_offset = 0;
+ int new_frame_size = frame_size;
+
+ /*printf ("packet %d %d\n", packet_no, skip_samples);*/
+ if (packet_no == 1 && j==0 && skip_samples > 0)
+ {
+ /*printf ("chopping first packet\n");*/
+ new_frame_size -= skip_samples;
+ frame_offset = skip_samples;
+ }
+ if (packet_no == page_nb_packets && skip_samples < 0)
+ {
+ int packet_length = nframes*frame_size+skip_samples;
+ new_frame_size = packet_length - j*frame_size;
+ if (new_frame_size<0)
+ new_frame_size = 0;
+ if (new_frame_size>frame_size)
+ new_frame_size = frame_size;
+ /*printf ("chopping end: %d %d %d\n", new_frame_size, packet_length, packet_no);*/
+ }
+
+
+ write(new_frame_size);
+ todo-=new_frame_size;
+ }
+ }
+
+ };
+ };
+ //todo = get_todo();
+
+ //todo is still greater than page size, can write more
+ if (todo > page_size || eos) {
+ if (read_ofs < data.size()) {
+
+ //char *buf;
+ int nb_read = MIN(data.size() - read_ofs, READ_CHUNK);
+
+ /*Get the ogg buffer for writing*/
+ char* ogg_dst = ogg_sync_buffer(&oy, nb_read);
+ /*Read bitstream from input file*/
+ copymem(ogg_dst, &data[read_ofs], nb_read);
+ read_ofs += nb_read;
+ ogg_sync_wrote(&oy, nb_read);
+ } else {
+ if (loops) {
+ reload();
+ ++loop_count;
+ } else {
+ playing=false;
+ unload();
+ break;
+ };
+ }
+ };
+ };
+};
+
+
+void AudioStreamSpeex::unload() {
+
+ _THREAD_SAFE_METHOD_
+
+ if (!active) return;
+
+ speex_bits_destroy(&bits);
+ if (st)
+ speex_decoder_destroy(st);
+ active = false;
+ //data.resize(0);
+ st = NULL;
+
+ frame_size = 0;
+ page_size = 0;
+ loop_count = 0;
+}
+
+void *AudioStreamSpeex::process_header(ogg_packet *op, int *frame_size, int *rate, int *nframes, int *channels, int *extra_headers) {
+
+ void *st;
+ SpeexHeader *header;
+ int modeID;
+ SpeexCallback callback;
+
+ header = speex_packet_to_header((char*)op->packet, op->bytes);
+ if (!header)
+ {
+ OS::get_singleton()->printerr( "Cannot read header\n");
+ return NULL;
+ }
+ if (header->mode >= SPEEX_NB_MODES)
+ {
+ OS::get_singleton()->printerr( "Mode number %d does not (yet/any longer) exist in this version\n",
+ header->mode);
+ return NULL;
+ }
+
+ modeID = header->mode;
+
+ const SpeexMode *mode = speex_lib_get_mode (modeID);
+
+ if (header->speex_version_id > 1)
+ {
+ 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);
+ return NULL;
+ }
+
+ if (mode->bitstream_version < header->mode_bitstream_version)
+ {
+ OS::get_singleton()->printerr( "The file was encoded with a newer version of Speex. You need to upgrade in order to play it.\n");
+ return NULL;
+ }
+ if (mode->bitstream_version > header->mode_bitstream_version)
+ {
+ 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");
+ return NULL;
+ }
+
+ void* state = speex_decoder_init(mode);
+ if (!state)
+ {
+ OS::get_singleton()->printerr( "Decoder initialization failed.\n");
+ return NULL;
+ }
+ //speex_decoder_ctl(state, SPEEX_SET_ENH, &enh_enabled);
+ speex_decoder_ctl(state, SPEEX_GET_FRAME_SIZE, frame_size);
+
+ if (!*rate)
+ *rate = header->rate;
+
+ speex_decoder_ctl(state, SPEEX_SET_SAMPLING_RATE, rate);
+
+ *nframes = header->frames_per_packet;
+
+ *channels = header->nb_channels;
+
+ if (*channels!=1) {
+ OS::get_singleton()->printerr("Only MONO speex streams supported\n");
+ return NULL;
+ }
+
+ *extra_headers = header->extra_headers;
+
+ speex_free(header);
+ return state;
+}
+
+
+
+void AudioStreamSpeex::reload() {
+
+ _THREAD_SAFE_METHOD_
+
+ if (active)
+ unload();
+
+ if (!data.size())
+ return;
+
+ ogg_sync_init(&oy);
+ speex_bits_init(&bits);
+
+ read_ofs = 0;
+// char *buf;
+
+ int packet_count = 0;
+ int extra_headers = 0;
+ int stream_init = 0;
+
+ page_granule=0;
+ last_granule=0;
+ skip_samples=0;
+ page_nb_packets=0;
+ packets_available=false;
+ packet_no=0;
+
+ int eos = 0;
+
+ do {
+
+ /*Get the ogg buffer for writing*/
+ int nb_read = MIN(data.size() - read_ofs, READ_CHUNK);
+ char* ogg_dst = ogg_sync_buffer(&oy, nb_read);
+ /*Read bitstream from input file*/
+ copymem(ogg_dst, &data[read_ofs], nb_read);
+ read_ofs += nb_read;
+ ogg_sync_wrote(&oy, nb_read);
+
+ /*Loop for all complete pages we got (most likely only one)*/
+ while (ogg_sync_pageout(&oy, &og)==1) {
+
+ int packet_no;
+ if (stream_init == 0) {
+ ogg_stream_init(&os, ogg_page_serialno(&og));
+ stream_init = 1;
+ }
+ /*Add page to the bitstream*/
+ ogg_stream_pagein(&os, &og);
+ page_granule = ogg_page_granulepos(&og);
+ page_nb_packets = ogg_page_packets(&og);
+ if (page_granule>0 && frame_size)
+ {
+ skip_samples = page_nb_packets*frame_size*nframes - (page_granule-last_granule);
+ if (ogg_page_eos(&og))
+ skip_samples = -skip_samples;
+ /*else if (!ogg_page_bos(&og))
+ skip_samples = 0;*/
+ } else
+ {
+ skip_samples = 0;
+ }
+
+
+ last_granule = page_granule;
+ /*Extract all available packets*/
+ packet_no=0;
+ while (!eos && ogg_stream_packetout(&os, &op)==1)
+ {
+ /*If first packet, process as Speex header*/
+ if (packet_count==0)
+ {
+ int rate = 0;
+ int channels;
+ st = process_header(&op, &frame_size, &rate, &nframes, &channels, &extra_headers);
+ if (!nframes)
+ nframes=1;
+ if (!st) {
+ unload();
+ return;
+ };
+
+ page_size = nframes * frame_size;
+
+ _setup(channels, rate,page_size);
+
+ } else if (packet_count==1)
+ {
+ } else if (packet_count<=1+extra_headers)
+ {
+ /* Ignore extra headers */
+ };
+ };
+ ++packet_count;
+ };
+
+ } while (packet_count <= extra_headers);
+
+ active = true;
+
+}
+
+void AudioStreamSpeex::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("set_file","file"),&AudioStreamSpeex::set_file);
+ ObjectTypeDB::bind_method(_MD("get_file"),&AudioStreamSpeex::get_file);
+
+ ObjectTypeDB::bind_method(_MD("_set_bundled"),&AudioStreamSpeex::_set_bundled);
+ ObjectTypeDB::bind_method(_MD("_get_bundled"),&AudioStreamSpeex::_get_bundled);
+
+ ADD_PROPERTY( PropertyInfo(Variant::DICTIONARY,"_bundled",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_BUNDLE),_SCS("_set_bundled"),_SCS("_get_bundled"));
+ ADD_PROPERTY( PropertyInfo(Variant::STRING,"file",PROPERTY_HINT_FILE,"*.spx"),_SCS("set_file"),_SCS("get_file"));
+};
+
+void AudioStreamSpeex::_set_bundled(const Dictionary& dict) {
+
+ ERR_FAIL_COND( !dict.has("filename"));
+ ERR_FAIL_COND( !dict.has("data"));
+
+ filename = dict["filename"];
+ data = dict["data"];
+};
+
+Dictionary AudioStreamSpeex::_get_bundled() const {
+
+ Dictionary d;
+ d["filename"] = filename;
+ d["data"] = data;
+ return d;
+};
+
+
+String AudioStreamSpeex::get_file() const {
+
+ return filename;
+};
+
+void AudioStreamSpeex::set_file(const String& p_file){
+
+ if (filename == p_file)
+ return;
+
+ if (active) {
+ unload();
+ }
+
+ if (p_file == "") {
+ data.resize(0);
+ return;
+ };
+
+ Error err;
+ FileAccess* file = FileAccess::open(p_file, FileAccess::READ,&err);
+ if (err != OK) {
+ data.resize(0);
+ };
+ ERR_FAIL_COND(err != OK);
+
+ filename = p_file;
+ data.resize(file->get_len());
+ int read = file->get_buffer(&data[0], data.size());
+ memdelete(file);
+
+ reload();
+}
+
+void AudioStreamSpeex::play() {
+
+ _THREAD_SAFE_METHOD_
+
+ reload();
+ if (!active)
+ return;
+ playing = true;
+
+}
+void AudioStreamSpeex::stop(){
+
+ _THREAD_SAFE_METHOD_
+ unload();
+ playing = false;
+ _clear();
+}
+bool AudioStreamSpeex::is_playing() const{
+
+ return _is_ready() && (playing || (get_total() - get_todo() -1 > 0));
+}
+
+void AudioStreamSpeex::set_paused(bool p_paused){
+
+ playing = !p_paused;
+ paused = p_paused;
+}
+bool AudioStreamSpeex::is_paused(bool p_paused) const{
+
+ return paused;
+}
+
+void AudioStreamSpeex::set_loop(bool p_enable){
+
+ loops = p_enable;
+}
+bool AudioStreamSpeex::has_loop() const{
+
+ return loops;
+}
+
+float AudioStreamSpeex::get_length() const{
+
+ return 0;
+}
+
+String AudioStreamSpeex::get_stream_name() const{
+
+ return "";
+}
+
+int AudioStreamSpeex::get_loop_count() const{
+
+ return 0;
+}
+
+float AudioStreamSpeex::get_pos() const{
+
+ return 0;
+}
+void AudioStreamSpeex::seek_pos(float p_time){
+
+
+};
+
+bool AudioStreamSpeex::_can_mix() const {
+
+ //return playing;
+ return data.size() != 0;
+};
+
+
+AudioStream::UpdateMode AudioStreamSpeex::get_update_mode() const {
+
+ return UPDATE_THREAD;
+}
+
+AudioStreamSpeex::AudioStreamSpeex() {
+
+ active=false;
+ st = NULL;
+}
+
+AudioStreamSpeex::~AudioStreamSpeex() {
+
+ unload();
+}
+
+RES ResourceFormatLoaderAudioStreamSpeex::load(const String &p_path,const String& p_original_path) {
+
+ AudioStreamSpeex *stream = memnew(AudioStreamSpeex);
+ stream->set_file(p_path);
+ return Ref<AudioStreamSpeex>(stream);
+}
+
+void ResourceFormatLoaderAudioStreamSpeex::get_recognized_extensions(List<String> *p_extensions) const {
+
+ p_extensions->push_back("spx");
+}
+bool ResourceFormatLoaderAudioStreamSpeex::handles_type(const String& p_type) const {
+
+ return (p_type=="AudioStream" || p_type=="AudioStreamSpeex");
+}
+
+String ResourceFormatLoaderAudioStreamSpeex::get_resource_type(const String &p_path) const {
+
+ if (p_path.extension().to_lower()=="spx")
+ return "AudioStreamSpeex";
+ return "";
+}