diff options
Diffstat (limited to 'src/lib/util/flac.c')
-rw-r--r-- | src/lib/util/flac.c | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/src/lib/util/flac.c b/src/lib/util/flac.c new file mode 100644 index 00000000000..aaefe301779 --- /dev/null +++ b/src/lib/util/flac.c @@ -0,0 +1,598 @@ +/*************************************************************************** + + flac.c + + FLAC compression wrappers + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +***************************************************************************/ + +#include "flac.h" +#include <assert.h> +#include <new> + + +//************************************************************************** +// FLAC ENCODER +//************************************************************************** + +//------------------------------------------------- +// flac_encoder - constructors +//------------------------------------------------- + +flac_encoder::flac_encoder() +{ + init_common(); +} + + +flac_encoder::flac_encoder(void *buffer, UINT32 buflength) +{ + init_common(); + reset(buffer, buflength); +} + + +flac_encoder::flac_encoder(core_file &file) +{ + init_common(); + reset(file); +} + + +//------------------------------------------------- +// ~flac_encoder - destructor +//------------------------------------------------- + +flac_encoder::~flac_encoder() +{ + // delete the encoder + FLAC__stream_encoder_delete(m_encoder); +} + + +//------------------------------------------------- +// reset - reset state with the original +// parameters +//------------------------------------------------- + +bool flac_encoder::reset() +{ + // configure the output + m_compressed_offset = 0; + m_ignore_bytes = m_strip_metadata ? 4 : 0; + m_found_audio = !m_strip_metadata; + + // configure the encoder in a standard way + // note we do this on each reset; if we don't, results are NOT consistent! + FLAC__stream_encoder_set_verify(m_encoder, false); +// FLAC__stream_encoder_set_do_md5(m_encoder, false); + FLAC__stream_encoder_set_compression_level(m_encoder, 8); + FLAC__stream_encoder_set_channels(m_encoder, m_channels); + FLAC__stream_encoder_set_bits_per_sample(m_encoder, 16); + FLAC__stream_encoder_set_sample_rate(m_encoder, m_sample_rate); + FLAC__stream_encoder_set_total_samples_estimate(m_encoder, 0); + FLAC__stream_encoder_set_streamable_subset(m_encoder, false); + FLAC__stream_encoder_set_blocksize(m_encoder, m_block_size); + + // re-start processing + return (FLAC__stream_encoder_init_stream(m_encoder, write_callback_static, NULL, NULL, NULL, this) == FLAC__STREAM_ENCODER_INIT_STATUS_OK); +} + + +//------------------------------------------------- +// reset - reset state with new memory parameters +//------------------------------------------------- + +bool flac_encoder::reset(void *buffer, UINT32 buflength) +{ + // configure the output + m_compressed_start = reinterpret_cast<FLAC__byte *>(buffer); + m_compressed_length = buflength; + m_file = NULL; + return reset(); +} + + +//------------------------------------------------- +// reset - reset state with new file parameters +//------------------------------------------------- + +bool flac_encoder::reset(core_file &file) +{ + // configure the output + m_compressed_start = NULL; + m_compressed_length = 0; + m_file = &file; + return reset(); +} + + +//------------------------------------------------- +// encode_interleaved - encode a buffer with +// interleaved samples +//------------------------------------------------- + +bool flac_encoder::encode_interleaved(const INT16 *samples, UINT32 samples_per_channel, bool swap_endian) +{ + int shift = swap_endian ? 8 : 0; + + // loop over source samples + int num_channels = FLAC__stream_encoder_get_channels(m_encoder); + UINT32 srcindex = 0; + while (samples_per_channel != 0) + { + // process in batches of 2k samples + FLAC__int32 converted_buffer[2048]; + FLAC__int32 *dest = converted_buffer; + UINT32 cur_samples = MIN(ARRAY_LENGTH(converted_buffer) / num_channels, samples_per_channel); + + // convert a buffers' worth + for (UINT32 sampnum = 0; sampnum < cur_samples; sampnum++) + for (int channel = 0; channel < num_channels; channel++, srcindex++) + *dest++ = INT16((UINT16(samples[srcindex]) << shift) | (UINT16(samples[srcindex]) >> shift)); + + // process this batch + if (!FLAC__stream_encoder_process_interleaved(m_encoder, converted_buffer, cur_samples)) + return false; + samples_per_channel -= cur_samples; + } + return true; +} + + +//------------------------------------------------- +// encode - encode a buffer with individual +// sample streams +//------------------------------------------------- + +bool flac_encoder::encode(INT16 *const *samples, UINT32 samples_per_channel, bool swap_endian) +{ + int shift = swap_endian ? 8 : 0; + + // loop over source samples + int num_channels = FLAC__stream_encoder_get_channels(m_encoder); + UINT32 srcindex = 0; + while (samples_per_channel != 0) + { + // process in batches of 2k samples + FLAC__int32 converted_buffer[2048]; + FLAC__int32 *dest = converted_buffer; + UINT32 cur_samples = MIN(ARRAY_LENGTH(converted_buffer) / num_channels, samples_per_channel); + + // convert a buffers' worth + for (UINT32 sampnum = 0; sampnum < cur_samples; sampnum++, srcindex++) + for (int channel = 0; channel < num_channels; channel++) + *dest++ = INT16((UINT16(samples[channel][srcindex]) << shift) | (UINT16(samples[channel][srcindex]) >> shift)); + + // process this batch + if (!FLAC__stream_encoder_process_interleaved(m_encoder, converted_buffer, cur_samples)) + return false; + samples_per_channel -= cur_samples; + } + return true; +} + + +//------------------------------------------------- +// finish - complete encoding and flush the +// stream +//------------------------------------------------- + +UINT32 flac_encoder::finish() +{ + // process the data and return the amount written + FLAC__stream_encoder_finish(m_encoder); + return (m_file != NULL) ? core_ftell(m_file) : m_compressed_offset; +} + + +//------------------------------------------------- +// init_common - common initialization +//------------------------------------------------- + +void flac_encoder::init_common() +{ + // allocate the encoder + m_encoder = FLAC__stream_encoder_new(); + if (m_encoder == NULL) + throw std::bad_alloc(); + + // initialize default state + m_file = NULL; + m_compressed_offset = 0; + m_compressed_start = NULL; + m_compressed_length = 0; + m_sample_rate = 44100; + m_channels = 2; + m_block_size = 0; + m_strip_metadata = false; + m_ignore_bytes = 0; + m_found_audio = false; +} + + +//------------------------------------------------- +// write_callback - handle writes to the +// output stream +//------------------------------------------------- + +FLAC__StreamEncoderWriteStatus flac_encoder::write_callback_static(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame, void *client_data) +{ + return reinterpret_cast<flac_encoder *>(client_data)->write_callback(buffer, bytes, samples, current_frame); +} + +FLAC__StreamEncoderWriteStatus flac_encoder::write_callback(const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame) +{ + // loop over output data + size_t offset = 0; + while (offset < bytes) + { + // if we're ignoring, continue to do so + if (m_ignore_bytes != 0) + { + int ignore = MIN(bytes - offset, m_ignore_bytes); + offset += ignore; + m_ignore_bytes -= ignore; + } + + // if we haven't hit the end of metadata, process a new piece + else if (!m_found_audio) + { + assert(bytes - offset >= 4); + m_found_audio = ((buffer[offset] & 0x80) != 0); + m_ignore_bytes = (buffer[offset + 1] << 16) | (buffer[offset + 2] << 8) | buffer[offset + 3]; + offset += 4; + } + + // otherwise process as audio data and copy to the output + else + { + int count = bytes - offset; + if (m_file != NULL) + core_fwrite(m_file, buffer, count); + else + { + if (m_compressed_offset + count <= m_compressed_length) + memcpy(m_compressed_start + m_compressed_offset, buffer, count); + m_compressed_offset += count; + } + offset += count; + } + } + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; +} + + + +//************************************************************************** +// FLAC DECODER +//************************************************************************** + +//------------------------------------------------- +// flac_decoder - constructor +//------------------------------------------------- + +flac_decoder::flac_decoder() + : m_decoder(FLAC__stream_decoder_new()), + m_file(NULL), + m_compressed_offset(0), + m_compressed_start(NULL), + m_compressed_length(0), + m_compressed2_start(NULL), + m_compressed2_length(0) +{ +} + + +//------------------------------------------------- +// flac_decoder - constructor +//------------------------------------------------- + +flac_decoder::flac_decoder(const void *buffer, UINT32 length, const void *buffer2, UINT32 length2) + : m_decoder(FLAC__stream_decoder_new()), + m_file(NULL), + m_compressed_offset(0), + m_compressed_start(reinterpret_cast<const FLAC__byte *>(buffer)), + m_compressed_length(length), + m_compressed2_start(reinterpret_cast<const FLAC__byte *>(buffer2)), + m_compressed2_length(length2) +{ + reset(); +} + + +//------------------------------------------------- +// flac_decoder - constructor +//------------------------------------------------- + +flac_decoder::flac_decoder(core_file &file) + : m_decoder(FLAC__stream_decoder_new()), + m_file(&file), + m_compressed_offset(0), + m_compressed_start(NULL), + m_compressed_length(0), + m_compressed2_start(NULL), + m_compressed2_length(0) +{ + reset(); +} + + +//------------------------------------------------- +// flac_decoder - destructor +//------------------------------------------------- + +flac_decoder::~flac_decoder() +{ + FLAC__stream_decoder_delete(m_decoder); +} + + +//------------------------------------------------- +// reset - reset state with the original +// parameters +//------------------------------------------------- + +bool flac_decoder::reset() +{ + m_compressed_offset = 0; + if (FLAC__stream_decoder_init_stream(m_decoder, &flac_decoder::read_callback_static, NULL, NULL, NULL, NULL, &flac_decoder::write_callback_static, NULL, &flac_decoder::error_callback_static, this) != FLAC__STREAM_DECODER_INIT_STATUS_OK) + return false; + return FLAC__stream_decoder_process_until_end_of_metadata(m_decoder); +} + + +//------------------------------------------------- +// reset - reset state with new memory parameters +//------------------------------------------------- + +bool flac_decoder::reset(const void *buffer, UINT32 length, const void *buffer2, UINT32 length2) +{ + m_file = NULL; + m_compressed_start = reinterpret_cast<const FLAC__byte *>(buffer); + m_compressed_length = length; + m_compressed2_start = reinterpret_cast<const FLAC__byte *>(buffer2); + m_compressed2_length = length2; + return reset(); +} + + +//------------------------------------------------- +// reset - reset state with new memory parameters +// and a custom-generated header +//------------------------------------------------- + +bool flac_decoder::reset(UINT32 sample_rate, UINT8 num_channels, UINT32 block_size, const void *buffer, UINT32 length) +{ + // modify the template header with our parameters + static const UINT8 s_header_template[0x2a] = + { + 0x66, 0x4C, 0x61, 0x43, // +00: 'fLaC' stream header + 0x80, // +04: metadata block type 0 (STREAMINFO), + // flagged as last block + 0x00, 0x00, 0x22, // +05: metadata block length = 0x22 + 0x00, 0x00, // +08: minimum block size + 0x00, 0x00, // +0A: maximum block size + 0x00, 0x00, 0x00, // +0C: minimum frame size (0 == unknown) + 0x00, 0x00, 0x00, // +0F: maximum frame size (0 == unknown) + 0x0A, 0xC4, 0x42, 0xF0, 0x00, 0x00, 0x00, 0x00, // +12: sample rate (0x0ac44 == 44100), + // numchannels (2), sample bits (16), + // samples in stream (0 == unknown) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // +1A: MD5 signature (0 == none) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // + // +2A: start of stream data + }; + memcpy(m_custom_header, s_header_template, sizeof(s_header_template)); + m_custom_header[0x08] = m_custom_header[0x0a] = block_size >> 8; + m_custom_header[0x09] = m_custom_header[0x0b] = block_size & 0xff; + m_custom_header[0x12] = sample_rate >> 12; + m_custom_header[0x13] = sample_rate >> 4; + m_custom_header[0x14] = (sample_rate << 4) | ((num_channels - 1) << 1); + + // configure the header ahead of the provided buffer + m_file = NULL; + m_compressed_start = reinterpret_cast<const FLAC__byte *>(m_custom_header); + m_compressed_length = sizeof(m_custom_header); + m_compressed2_start = reinterpret_cast<const FLAC__byte *>(buffer); + m_compressed2_length = length; + return reset(); +} + + +//------------------------------------------------- +// reset - reset state with new file parameter +//------------------------------------------------- + +bool flac_decoder::reset(core_file &file) +{ + m_file = &file; + m_compressed_start = NULL; + m_compressed_length = 0; + m_compressed2_start = NULL; + m_compressed2_length = 0; + return reset(); +} + + +//------------------------------------------------- +// decode_interleaved - decode to an interleaved +// sound stream +//------------------------------------------------- + +bool flac_decoder::decode_interleaved(INT16 *samples, UINT32 num_samples, bool swap_endian) +{ + // configure the uncompressed buffer + memset(m_uncompressed_start, 0, sizeof(m_uncompressed_start)); + m_uncompressed_start[0] = samples; + m_uncompressed_offset = 0; + m_uncompressed_length = num_samples; + m_uncompressed_swap = swap_endian; + + // loop until we get everything we want + while (m_uncompressed_offset < m_uncompressed_length) + if (!FLAC__stream_decoder_process_single(m_decoder)) + return false; + return true; +} + + +//------------------------------------------------- +// decode - decode to an multiple independent +// data streams +//------------------------------------------------- + +bool flac_decoder::decode(INT16 **samples, UINT32 num_samples, bool swap_endian) +{ + // make sure we don't have too many channels + int chans = channels(); + if (chans > ARRAY_LENGTH(m_uncompressed_start)) + return false; + + // configure the uncompressed buffer + memset(m_uncompressed_start, 0, sizeof(m_uncompressed_start)); + for (int curchan = 0; curchan < chans; curchan++) + m_uncompressed_start[curchan] = samples[curchan]; + m_uncompressed_offset = 0; + m_uncompressed_length = num_samples; + m_uncompressed_swap = swap_endian; + + // loop until we get everything we want + while (m_uncompressed_offset < m_uncompressed_length) + if (!FLAC__stream_decoder_process_single(m_decoder)) + return false; + return true; +} + + +//------------------------------------------------- +// finish - finish up the decode +//------------------------------------------------- + +void flac_decoder::finish() +{ + FLAC__stream_decoder_finish(m_decoder); +} + + +//------------------------------------------------- +// read_callback - handle reads from the input +// stream +//------------------------------------------------- + +FLAC__StreamDecoderReadStatus flac_decoder::read_callback_static(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) +{ + return reinterpret_cast<flac_decoder *>(client_data)->read_callback(buffer, bytes); +} + +FLAC__StreamDecoderReadStatus flac_decoder::read_callback(FLAC__byte buffer[], size_t *bytes) +{ + UINT32 expected = *bytes; + + // if a file, just read + if (m_file != NULL) + *bytes = core_fread(m_file, buffer, expected); + + // otherwise, copy from memory + else + { + // copy from primary buffer first + UINT32 outputpos = 0; + if (outputpos < *bytes && m_compressed_offset < m_compressed_length) + { + UINT32 bytes_to_copy = MIN(*bytes - outputpos, m_compressed_length - m_compressed_offset); + memcpy(&buffer[outputpos], m_compressed_start + m_compressed_offset, bytes_to_copy); + outputpos += bytes_to_copy; + m_compressed_offset += bytes_to_copy; + } + + // once we're out of that, copy from the secondary buffer + if (outputpos < *bytes && m_compressed_offset < m_compressed_length + m_compressed2_length) + { + UINT32 bytes_to_copy = MIN(*bytes - outputpos, m_compressed2_length - (m_compressed_offset - m_compressed_length)); + memcpy(&buffer[outputpos], m_compressed2_start + m_compressed_offset - m_compressed_length, bytes_to_copy); + outputpos += bytes_to_copy; + m_compressed_offset += bytes_to_copy; + } + *bytes = outputpos; + } + + // return based on whether we ran out of data + return (*bytes < expected) ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + + +//------------------------------------------------- +// write_callback - handle writes to the output +// stream +//------------------------------------------------- + +FLAC__StreamDecoderWriteStatus flac_decoder::write_callback_static(const FLAC__StreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) +{ + return reinterpret_cast<flac_decoder *>(client_data)->write_callback(frame, buffer); +} + +FLAC__StreamDecoderWriteStatus flac_decoder::write_callback(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]) +{ + assert(frame->header.channels == channels()); + + // interleaved case + int shift = m_uncompressed_swap ? 8 : 0; + int blocksize = frame->header.blocksize; + if (m_uncompressed_start[1] == NULL) + { + INT16 *dest = m_uncompressed_start[0] + m_uncompressed_offset * frame->header.channels; + for (int sampnum = 0; sampnum < blocksize && m_uncompressed_offset < m_uncompressed_length; sampnum++, m_uncompressed_offset++) + for (int chan = 0; chan < frame->header.channels; chan++) + *dest++ = INT16((UINT16(buffer[chan][sampnum]) << shift) | (UINT16(buffer[chan][sampnum]) >> shift)); + } + + // non-interleaved case + else + { + for (int sampnum = 0; sampnum < blocksize && m_uncompressed_offset < m_uncompressed_length; sampnum++, m_uncompressed_offset++) + for (int chan = 0; chan < frame->header.channels; chan++) + if (m_uncompressed_start[chan] != NULL) + m_uncompressed_start[chan][m_uncompressed_offset] = INT16((UINT16(buffer[chan][sampnum]) << shift) | (UINT16(buffer[chan][sampnum]) >> shift)); + } + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + + +//------------------------------------------------- +// error_callback - handle errors (ignore them) +//------------------------------------------------- + +void flac_decoder::error_callback_static(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ +} |