scummvm/audio/decoders/mpc.cpp
Le Philousophe 9d5a5e092b AUDIO: Add support for libmpcdec 1.2.6
Previously, only the latest release was supported.
2024-09-07 00:15:07 +02:00

338 lines
9 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
#include "audio/decoders/mpc.h"
#ifdef USE_MPCDEC
#ifdef USE_MPCDEC_OLD_API
#include <mpcdec/mpcdec.h>
#else
#include <mpc/mpcdec.h>
#endif
#include "common/debug.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "audio/audiostream.h"
namespace Audio {
// These are wrapper functions to allow using a SeekableReadStream object to
// provide data to the mpc_reader object.
#ifdef USE_MPCDEC_OLD_API
static mpc_int32_t read_stream(void *data, void *ptr, mpc_int32_t size) {
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)data;
#else
static mpc_int32_t read_stream(mpc_reader *p_reader, void *ptr, mpc_int32_t size) {
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)p_reader->data;
#endif
return stream->read(ptr, size);
}
/// Seeks to byte position offset.
#ifdef USE_MPCDEC_OLD_API
static mpc_bool_t seek_stream(void *data, mpc_int32_t offset) {
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)data;
#else
static mpc_bool_t seek_stream(mpc_reader *p_reader, mpc_int32_t offset) {
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)p_reader->data;
#endif
return stream->seek(offset);
}
/// Returns the current byte offset in the stream.
#ifdef USE_MPCDEC_OLD_API
static mpc_int32_t tell_stream(void *data) {
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)data;
#else
static mpc_int32_t tell_stream(mpc_reader *p_reader) {
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)p_reader->data;
#endif
return stream->pos();
}
/// Returns the total length of the source stream, in bytes.
#ifdef USE_MPCDEC_OLD_API
static mpc_int32_t get_size_stream(void *data) {
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)data;
#else
static mpc_int32_t get_size_stream(mpc_reader *p_reader) {
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)p_reader->data;
#endif
return stream->size();
}
/// True if the stream is a seekable stream.
#ifdef USE_MPCDEC_OLD_API
static mpc_bool_t canseek_stream(void *p_reader) {
return TRUE;
}
#else
static mpc_bool_t canseek_stream(mpc_reader *p_reader) {
return MPC_TRUE;
}
#endif
#pragma mark -
#pragma mark --- Musepack stream ---
#pragma mark -
class MPCStream : public SeekableAudioStream {
protected:
Common::DisposablePtr<Common::SeekableReadStream> _inStream;
bool _isStereo;
int _rate;
Timestamp _length;
mpc_reader _reader;
mpc_streaminfo _si;
#ifdef USE_MPCDEC_OLD_API
mpc_decoder _decoder;
#else
mpc_demux *_demux;
#endif
MPC_SAMPLE_FORMAT _bufferDec[MPC_DECODER_BUFFER_LENGTH];
uint16 _buffer[MPC_DECODER_BUFFER_LENGTH];
const uint16 *_bufferEnd;
const uint16 *_pos;
public:
// startTime / duration are in milliseconds
MPCStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose);
~MPCStream();
int readBuffer(int16 *buffer, const int numSamples) override;
bool endOfData() const override { return _pos >= _bufferEnd; }
bool isStereo() const override { return _isStereo; }
int getRate() const override { return _rate; }
bool seek(const Timestamp &where) override;
Timestamp getLength() const override { return _length; }
protected:
bool refill();
};
MPCStream::MPCStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
_inStream(inStream, dispose),
_length(0, 1000),
_bufferEnd(ARRAYEND(_buffer)) {
_pos = _bufferEnd; // This will return endOfBuffer() if we're not properly inited
_reader.read = read_stream;
_reader.seek = seek_stream;
_reader.tell = tell_stream;
_reader.get_size = get_size_stream;
_reader.canseek = canseek_stream;
_reader.data = (void *)inStream;
#ifdef USE_MPCDEC_OLD_API
mpc_streaminfo_init(&_si);
if (mpc_streaminfo_read(&_si, &_reader) < 0) {
warning("Cannot read musepack stream info");
return;
}
mpc_decoder_setup(&_decoder, &_reader);
mpc_decoder_scale_output (&_decoder, 1.0);
if (!mpc_decoder_initialize(&_decoder, &_si)) {
warning("Cannot initialize musepack decoder");
return;
}
#else
_demux = mpc_demux_init(&_reader);
if (!_demux) {
warning("Cannot init musepack demuxer");
return;
}
mpc_demux_get_info(_demux, &_si);
#endif
_isStereo = _si.channels >= 2;
_rate = _si.sample_freq;
_length = Timestamp(uint32(mpc_streaminfo_get_length(&_si) * 1000.0), getRate());
int time = (int)mpc_streaminfo_get_length(&_si);
int minutes = time / 60;
int seconds = time % 60;
debug(9, "stream version %d", _si.stream_version);
debug(9, "encoder: %s", _si.encoder);
#ifdef USE_MPCDEC_OLD_API
debug(9, "profile: %s (q=%d)", _si.profile_name, _si.profile);
#else
debug(9, "profile: %s (q=%0.2f)", _si.profile_name, _si.profile - 5);
debug(9, "PNS: %s", _si.pns == 0xFF ? "unknow" : _si.pns ? "on" : "off");
#endif
debug(9, "mid/side stereo: %s", _si.ms ? "on" : "off");
debug(9, "gapless: %s", _si.is_true_gapless ? "on" : "off");
debug(9, "average bitrate: %6.1f kbps", _si.average_bitrate * 1.e-3);
debug(9, "samplerate: %d Hz", _si.sample_freq);
debug(9, "channels: %d", _si.channels);
debug(9, "length: %d:%.2d (%u samples)", minutes, seconds, (mpc_uint32_t)mpc_streaminfo_get_length_samples(&_si));
debug(9, "file size: %d Bytes", _si.total_file_length);
debug(9, "track peak: %2.2f dB", _si.peak_title / 256.f);
debug(9, "track gain: %2.2f dB / %2.2f dB", _si.gain_title / 256.f, _si.gain_title == 0 ? 0 : 64.82f - _si.gain_title / 256.f);
debug(9, "album peak: %2.2f dB", _si.peak_album / 256.f);
debug(9, "album gain: %2.2f dB / %2.2f dB", _si.gain_album / 256.f, _si.gain_album == 0 ? 0 : 64.82f - _si.gain_album / 256.f);
if (!refill())
return;
}
MPCStream::~MPCStream() {
#ifndef USE_MPCDEC_OLD_API
mpc_demux_exit(_demux);
#endif
}
int MPCStream::readBuffer(int16 *buffer, const int numSamples) {
int samples = 0;
while (samples < numSamples && _pos < _bufferEnd) {
const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
memcpy(buffer, _pos, len * 2);
buffer += len;
_pos += len;
samples += len;
if (_pos >= _bufferEnd) {
if (!refill())
break;
}
}
return samples;
}
bool MPCStream::seek(const Timestamp &where) {
#ifdef USE_MPCDEC_OLD_API
bool res = (mpc_decoder_seek_seconds(&_decoder, (double)where.msecs() / 1000.0) == TRUE);
#else
bool res = (mpc_demux_seek_second(_demux, (double)where.msecs() / 1000.0) == MPC_STATUS_OK);
#endif
if (!res) {
warning("Error seeking in musepack stream");
_pos = _bufferEnd;
return false;
}
return refill();
}
bool MPCStream::refill() {
bool result;
uint32 samples;
#ifdef USE_MPCDEC_OLD_API
uint32 vbr_update_acc, vbr_update_bits;
samples = mpc_decoder_decode(&_decoder, _bufferDec, &vbr_update_acc, &vbr_update_bits);
if (samples == 0) { // End of stream
_pos = _buffer;
_bufferEnd = _buffer;
return false;
}
if (samples == -1u) { // Corruptd stream
result = false;
samples = 0;
} else {
result = true;
}
#else
mpc_frame_info frame;
frame.buffer = _bufferDec;
result = (mpc_demux_decode(_demux, &frame) == MPC_STATUS_OK);
if (frame.bits == -1) { // End of stream
_pos = _buffer;
_bufferEnd = _buffer;
return false;
}
samples = frame.samples;
#endif
if (!result) {
// Possibly recoverable, just warn about it
warning("Corrupted data in musepack file");
}
#ifdef MPC_FIXED_POINT
for(int i = 0; i < MPC_DECODER_BUFFER_LENGTH; i++) {
int tmp = _bufferDec[i] >> MPC_FIXED_POINT_FRACTPART;
if (tmp > ((1 << 15) - 1)) tmp = ((1 << 15) - 1);
if (tmp < -(1 << 15)) tmp = -(1 << 15);
_buffer[i] = tmp;
}
#else
for (int i = 0; i < MPC_DECODER_BUFFER_LENGTH; i++) {
int tmp = nearbyintf(_bufferDec[i] * (1 << 15));
if (tmp > ((1 << 15) - 1))
tmp = ((1 << 15) - 1);
if (tmp < -(1 << 15))
tmp = -(1 << 15);
_buffer[i] = (uint16)tmp;
}
#endif
_pos = _buffer;
_bufferEnd = &_buffer[samples * _si.channels];
return true;
}
#pragma mark -
#pragma mark --- Ogg Vorbis factory functions ---
#pragma mark -
SeekableAudioStream *makeMPCStream(
Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeAfterUse) {
SeekableAudioStream *s = new MPCStream(stream, disposeAfterUse);
if (s && s->endOfData()) {
delete s;
return nullptr;
} else {
return s;
}
}
} // End of namespace Audio
#endif // #ifdef USE_MPCDEC