#include #include "coding.h" #include "../util.h" #ifdef VGM_USE_VORBIS #include #define OGG_DEFAULT_BITSTREAM 0 /* opaque struct */ struct ogg_vorbis_codec_data { OggVorbis_File ogg_vorbis_file; int ovf_init; int bitstream; int disable_reordering; /* Xiph Ogg must reorder channels on output, but some pre-ordered games don't need it */ ogg_vorbis_io io; vorbis_comment *comment; int comment_number; vorbis_info *info; }; static void pcm_convert_float_to_16(int channels, sample_t *outbuf, int samples_to_do, float **pcm, int disable_ordering); static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasource); static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence); static long ov_tell_func(void *datasource); static int ov_close_func(void *datasource); ogg_vorbis_codec_data* init_ogg_vorbis(STREAMFILE *sf, off_t start, off_t size, ogg_vorbis_io *io) { ogg_vorbis_codec_data *data = NULL; ov_callbacks callbacks = {0}; //todo clean up callbacks.read_func = ov_read_func; callbacks.seek_func = ov_seek_func; callbacks.close_func = ov_close_func; callbacks.tell_func = ov_tell_func; /* test if this is a proper Ogg Vorbis file, with the current (from init_x) STREAMFILE * (quick test without having to malloc first, though if one invoked this it'll probably success) */ { OggVorbis_File temp_ovf = {0}; ogg_vorbis_io temp_io = {0}; temp_io.streamfile = sf; temp_io.start = start; temp_io.offset = 0; temp_io.size = size; if (io != NULL) { temp_io.decryption_callback = io->decryption_callback; temp_io.scd_xor = io->scd_xor; temp_io.scd_xor_length = io->scd_xor_length; temp_io.xor_value = io->xor_value; } /* open the ogg vorbis file for testing */ if (ov_test_callbacks(&temp_io, &temp_ovf, NULL, 0, callbacks)) goto fail; /* we have to close this as it has the init_vgmstream meta-reading STREAMFILE */ ov_clear(&temp_ovf); } /* proceed to init codec_data and reopen a STREAMFILE for this codec */ { data = calloc(1,sizeof(ogg_vorbis_codec_data)); if (!data) goto fail; data->io.streamfile = reopen_streamfile(sf, 0); if (!data->io.streamfile) goto fail; data->io.start = start; data->io.offset = 0; data->io.size = size; if (io != NULL) { data->io.decryption_callback = io->decryption_callback; data->io.scd_xor = io->scd_xor; data->io.scd_xor_length = io->scd_xor_length; data->io.xor_value = io->xor_value; } /* open the ogg vorbis file for real */ if (ov_open_callbacks(&data->io, &data->ogg_vorbis_file, NULL, 0, callbacks)) goto fail; data->ovf_init = 1; } //todo could set bitstreams as subsongs? /* get info from bitstream */ data->bitstream = OGG_DEFAULT_BITSTREAM; data->comment = ov_comment(&data->ogg_vorbis_file, data->bitstream); data->info = ov_info(&data->ogg_vorbis_file, data->bitstream); return data; fail: free_ogg_vorbis(data); return NULL; } static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasource) { ogg_vorbis_io *io = datasource; size_t bytes_read, items_read; off_t real_offset = io->start + io->offset; size_t max_bytes = size * nmemb; /* clamp for virtual filesize */ if (max_bytes > io->size - io->offset) max_bytes = io->size - io->offset; bytes_read = read_streamfile(ptr, real_offset, max_bytes, io->streamfile); items_read = bytes_read / size; /* may be encrypted */ if (io->decryption_callback) { io->decryption_callback(ptr, size, items_read, io); } io->offset += items_read * size; return items_read; } static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) { ogg_vorbis_io *io = datasource; ogg_int64_t base_offset, new_offset; switch (whence) { case SEEK_SET: base_offset = 0; break; case SEEK_CUR: base_offset = io->offset; break; case SEEK_END: base_offset = io->size; break; default: return -1; break; } new_offset = base_offset + offset; if (new_offset < 0 || new_offset > io->size) { return -1; /* *must* return -1 if stream is unseekable */ } else { io->offset = new_offset; return 0; } } static long ov_tell_func(void *datasource) { ogg_vorbis_io *io = datasource; return io->offset; } static int ov_close_func(void *datasource) { /* needed as setting ov_close_func in ov_callbacks to NULL doesn't seem to work * (closing the streamfile is done in free_ogg_vorbis) */ return 0; } /* ********************************************** */ void decode_ogg_vorbis(ogg_vorbis_codec_data *data, sample_t *outbuf, int32_t samples_to_do, int channels) { int samples_done = 0; long rc; float **pcm_channels; /* pointer to Xiph's double array buffer */ while (samples_done < samples_to_do) { rc = ov_read_float( &data->ogg_vorbis_file, /* context */ &pcm_channels, /* buffer pointer */ (samples_to_do - samples_done), /* samples to produce */ &data->bitstream); /* bitstream */ if (rc <= 0) goto fail; /* rc is samples done */ pcm_convert_float_to_16(channels, outbuf, rc, pcm_channels, data->disable_reordering); outbuf += rc * channels; samples_done += rc; #if 0 // alt decoding /* we use ov_read_float as to reuse the xiph's buffer for easier remapping, * but seems ov_read is slightly faster due to optimized (asm) float-to-int. */ rc = ov_read( &data->ogg_vorbis_file, /* context */ (char *)(outbuf), /* buffer */ (samples_to_do - samples_done) * sizeof(sample_t) * channels, /* length in bytes */ 0, /* pcm endianness */ sizeof(sample), /* pcm size */ 1, /* pcm signedness */ &data->bitstream); /* bitstream */ if (rc <= 0) goto fail; /* rc is bytes done (for all channels) */ swap_samples_le(outbuf, rc / sizeof(sample_t)); /* endianness is a bit weird with ov_read though */ outbuf += rc / sizeof(sample_t); samples_done += rc / sizeof(sample_t) / channels; #endif } return; fail: VGM_LOG("OGG: error %lx during decode\n", rc); memset(outbuf, 0, (samples_to_do - samples_done) * channels * sizeof(sample)); } /* vorbis encodes channels in non-standard order, so we remap during conversion to fix this oddity. * (feels a bit weird as one would think you could leave as-is and set the player's output * order, but that isn't possible and remapping is what FFmpeg and every other plugin does). */ static const int xiph_channel_map[8][8] = { { 0 }, /* 1ch: FC > same */ { 0, 1 }, /* 2ch: FL FR > same */ { 0, 2, 1 }, /* 3ch: FL FC FR > FL FR FC */ { 0, 1, 2, 3 }, /* 4ch: FL FR BL BR > same */ { 0, 2, 1, 3, 4 }, /* 5ch: FL FC FR BL BR > FL FR FC BL BR */ { 0, 2, 1, 5, 3, 4 }, /* 6ch: FL FC FR BL BR LFE > FL FR FC LFE BL BR */ { 0, 2, 1, 6, 5, 3, 4 }, /* 7ch: FL FC FR SL SR BC LFE > FL FR FC LFE BC SL SR */ { 0, 2, 1, 7, 5, 6, 3, 4 }, /* 8ch: FL FC FR SL SR BL BR LFE > FL FR FC LFE BL BR SL SR */ }; /* converts from internal Vorbis format to standard PCM and remaps (mostly from Xiph's decoder_example.c) */ static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm, int disable_ordering) { int ch, s, ch_map; sample_t *ptr; float *channel; /* convert float PCM (multichannel float array, with pcm[0]=ch0, pcm[1]=ch1, pcm[2]=ch0, etc) * to 16 bit signed PCM ints (host order) and interleave + fix clipping */ for (ch = 0; ch < channels; ch++) { ch_map = disable_ordering ? ch : (channels > 8) ? ch : xiph_channel_map[channels - 1][ch]; /* put Vorbis' ch to other outbuf's ch */ ptr = outbuf + ch; channel = pcm[ch_map]; for (s = 0; s < samples_to_do; s++) { int val = (int)floor(channel[s] * 32767.0f + 0.5f); /* use floorf? doesn't seem any faster */ if (val > 32767) val = 32767; else if (val < -32768) val = -32768; *ptr = val; ptr += channels; } } } /* ********************************************** */ void reset_ogg_vorbis(VGMSTREAM *vgmstream) { ogg_vorbis_codec_data *data = vgmstream->codec_data; if (!data) return; /* this seek cleans internal buffers */ ov_pcm_seek(&data->ogg_vorbis_file, 0); } void seek_ogg_vorbis(ogg_vorbis_codec_data *data, int32_t num_sample) { if (!data) return; /* this seek crosslaps to avoid possible clicks, so seeking to 0 will * decode a bit differently than ov_pcm_seek */ ov_pcm_seek_lap(&data->ogg_vorbis_file, num_sample); } void free_ogg_vorbis(ogg_vorbis_codec_data *data) { if (!data) return; if (data->ovf_init) ov_clear(&data->ogg_vorbis_file); close_streamfile(data->io.streamfile); free(data); } /* ********************************************** */ int ogg_vorbis_get_comment(ogg_vorbis_codec_data *data, const char **comment) { if (!data) return 0; /* dumb reset */ if (comment == NULL) { data->comment_number = 0; return 1; } if (data->comment_number >= data->comment->comments) return 0; *comment = data->comment->user_comments[data->comment_number]; data->comment_number++; return 1; } void ogg_vorbis_get_info(ogg_vorbis_codec_data *data, int *p_channels, int *p_sample_rate) { if (!data) { if (p_channels) *p_channels = 0; if (p_sample_rate) *p_sample_rate = 0; return; } if (p_channels) *p_channels = data->info->channels; if (p_sample_rate) *p_sample_rate = (int)data->info->rate; } void ogg_vorbis_get_samples(ogg_vorbis_codec_data *data, int *p_samples) { if (!data) { if (p_samples) *p_samples = 0; return; } if (p_samples) *p_samples = ov_pcm_total(&data->ogg_vorbis_file,-1); } void ogg_vorbis_set_disable_reordering(ogg_vorbis_codec_data *data, int set) { if (!data) return; data->disable_reordering = set; } STREAMFILE* ogg_vorbis_get_streamfile(ogg_vorbis_codec_data *data) { if (!data) return NULL; return data->io.streamfile; } #endif