godot/drivers/speex/audio_stream_speex.cpp

590 lines
14 KiB
C++

/*************************************************************************/
/* audio_stream_speex.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* http://www.godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_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 "";
}