#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; #if 0 //def BIG_ENDIAN_ENABLED ret = s>>8; ret += s<<8; #endif return ret; } int AudioStreamPlaybackSpeex::mix(int16_t* p_buffer,int p_frames) { //printf("update, loops %i, read ofs %i\n", (int)loops, read_ofs); //printf("playing %i, paused %i\n", (int)playing, (int)paused); if (!active || !playing || !data.size()) return 0; /* if (read_ofs >= data.size()) { if (loops) { reload(); ++loop_count; } else { return; }; }; */ int todo = p_frames; if (todo < page_size) { return 0; }; int eos = 0; bool reloaded=false; 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 = p_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*stream_channels;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);*/ } p_buffer+=new_frame_size*stream_channels; 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; //break; } else { playing=false; unload(); break; }; } }; }; return p_frames-todo; }; void AudioStreamPlaybackSpeex::unload() { if (!active) return; speex_bits_destroy(&bits); if (st) speex_decoder_destroy(st); ogg_sync_clear(&oy); active = false; //data.resize(0); st = NULL; frame_size = 0; page_size = 0; loop_count = 0; } void *AudioStreamPlaybackSpeex::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 AudioStreamPlaybackSpeex::reload() { 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; stream_srate=rate; stream_channels=channels; stream_minbuff_size=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 AudioStreamPlaybackSpeex::_bind_methods() { //ObjectTypeDB::bind_method(_MD("set_file","file"),&AudioStreamPlaybackSpeex::set_file); // ObjectTypeDB::bind_method(_MD("get_file"),&AudioStreamPlaybackSpeex::get_file); ObjectTypeDB::bind_method(_MD("_set_bundled"),&AudioStreamPlaybackSpeex::_set_bundled); ObjectTypeDB::bind_method(_MD("_get_bundled"),&AudioStreamPlaybackSpeex::_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 AudioStreamPlaybackSpeex::_set_bundled(const Dictionary& dict) { ERR_FAIL_COND( !dict.has("filename")); ERR_FAIL_COND( !dict.has("data")); filename = dict["filename"]; data = dict["data"]; }; Dictionary AudioStreamPlaybackSpeex::_get_bundled() const { Dictionary d; d["filename"] = filename; d["data"] = data; return d; }; void AudioStreamPlaybackSpeex::set_data(const Vector<uint8_t>& p_data) { data=p_data; reload(); } void AudioStreamPlaybackSpeex::play(float p_from_pos) { reload(); if (!active) return; playing = true; } void AudioStreamPlaybackSpeex::stop(){ unload(); playing = false; } bool AudioStreamPlaybackSpeex::is_playing() const{ return playing; } void AudioStreamPlaybackSpeex::set_loop(bool p_enable){ loops = p_enable; } bool AudioStreamPlaybackSpeex::has_loop() const{ return loops; } float AudioStreamPlaybackSpeex::get_length() const{ return 0; } String AudioStreamPlaybackSpeex::get_stream_name() const{ return ""; } int AudioStreamPlaybackSpeex::get_loop_count() const{ return 0; } float AudioStreamPlaybackSpeex::get_pos() const{ return 0; } void AudioStreamPlaybackSpeex::seek_pos(float p_time){ }; AudioStreamPlaybackSpeex::AudioStreamPlaybackSpeex() { active=false; st = NULL; stream_channels=1; stream_srate=1; stream_minbuff_size=1; playing=false; } AudioStreamPlaybackSpeex::~AudioStreamPlaybackSpeex() { unload(); } //////////////////////////////////////// void AudioStreamSpeex::set_file(const String& p_file) { if (this->file == p_file) return; this->file=p_file; 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); this->file = p_file; data.resize(file->get_len()); int read = file->get_buffer(&data[0], data.size()); memdelete(file); } RES ResourceFormatLoaderAudioStreamSpeex::load(const String &p_path, const String& p_original_path, Error *r_error) { if (r_error) *r_error=OK; 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 ""; }