From bc4d900f7df779862617b8254bdcdde92fdc40d1 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Jun 2018 06:57:06 -0700 Subject: [PATCH] Support MP3 files as sound assets --- libraries/audio/src/Sound.cpp | 110 ++++++++++++++++++++++++++++++++++ libraries/audio/src/Sound.h | 1 + 2 files changed, 111 insertions(+) diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index cd93f7b52e..2fb9ca0cb3 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -31,6 +31,8 @@ #include "AudioLogging.h" #include "AudioSRC.h" +#include "flump3dec.h" + QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) { return engine->newQObject(new SoundScriptingInterface(in), QScriptEngine::ScriptOwnership); } @@ -90,7 +92,9 @@ void SoundProcessor::run() { QString fileName = _url.fileName().toLower(); static const QString WAV_EXTENSION = ".wav"; + static const QString MP3_EXTENSION = ".mp3"; static const QString RAW_EXTENSION = ".raw"; + if (fileName.endsWith(WAV_EXTENSION)) { QByteArray outputAudioByteArray; @@ -103,6 +107,20 @@ void SoundProcessor::run() { } downSample(outputAudioByteArray, sampleRate); + + } else if (fileName.endsWith(MP3_EXTENSION)) { + + QByteArray outputAudioByteArray; + + int sampleRate = interpretAsMP3(rawAudioByteArray, outputAudioByteArray); + if (sampleRate == 0) { + qCDebug(audio) << "Unsupported MP3 file type"; + emit onError(300, "Failed to load sound file, reason: unsupported MP3 file type"); + return; + } + + downSample(outputAudioByteArray, sampleRate); + } else if (fileName.endsWith(RAW_EXTENSION)) { // check if this was a stereo raw file // since it's raw the only way for us to know that is if the file was called .stereo.raw @@ -113,6 +131,7 @@ void SoundProcessor::run() { // Process as 48khz RAW file downSample(rawAudioByteArray, 48000); + } else { qCDebug(audio) << "Unknown sound file type"; emit onError(300, "Failed to load sound file, reason: unknown sound file type"); @@ -286,3 +305,94 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA _duration = (float)(outputAudioByteArraySize / (wave.sampleRate * wave.numChannels * wave.bitsPerSample / 8.0f)); return wave.sampleRate; } + +// returns MP3 sample rate, used for resampling +int SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) { + using namespace flump3dec; + + static const int MP3_SAMPLES_MAX = 1152; + static const int MP3_CHANNELS_MAX = 2; + static const int MP3_BUFFER_SIZE = MP3_SAMPLES_MAX * MP3_CHANNELS_MAX * sizeof(int16_t); + uint8_t mp3Buffer[MP3_BUFFER_SIZE]; + + // create bitstream + Bit_stream_struc *bitstream = bs_new(); + if (bitstream == nullptr) { + return 0; + } + + // create decoder + mp3tl *decoder = mp3tl_new(bitstream, MP3TL_MODE_16BIT); + if (decoder == nullptr) { + bs_free(bitstream); + return 0; + } + + // initialize + bs_set_data(bitstream, (uint8_t*)inputAudioByteArray.data(), inputAudioByteArray.size()); + int frameCount = 0; + int sampleRate = 0; + int numChannels = 0; + + // skip ID3 tag, if present + Mp3TlRetcode result = mp3tl_skip_id3(decoder); + + while (!(result == MP3TL_ERR_NO_SYNC || result == MP3TL_ERR_NEED_DATA)) { + + mp3tl_sync(decoder); + + // find MP3 header + const fr_header *header = nullptr; + result = mp3tl_decode_header(decoder, &header); + + if (result == MP3TL_ERR_OK) { + + if (frameCount++ == 0) { + + qCDebug(audio) << "Decoding MP3 with bitrate =" << header->bitrate + << "sample rate =" << header->sample_rate + << "channels =" << header->channels; + + // save header info + sampleRate = header->sample_rate; + numChannels = header->channels; + + // skip Xing header, if present + result = mp3tl_skip_xing(decoder, header); + } + + // decode MP3 frame + if (result == MP3TL_ERR_OK) { + + result = mp3tl_decode_frame(decoder, mp3Buffer, MP3_BUFFER_SIZE); + + // fill bad frames with silence + int len = header->frame_samples * header->channels * sizeof(int16_t); + if (result == MP3TL_ERR_BAD_FRAME) { + memset(mp3Buffer, 0, len); + } + + if (result == MP3TL_ERR_OK || result == MP3TL_ERR_BAD_FRAME) { + outputAudioByteArray.append((char*)mp3Buffer, len); + } + } + } + } + + // free decoder + mp3tl_free(decoder); + + // free bitstream + bs_free(bitstream); + + int outputAudioByteArraySize = outputAudioByteArray.size(); + if (outputAudioByteArraySize == 0) { + qCDebug(audio) << "Error decoding MP3 file"; + return 0; + } + + _isStereo = (numChannels == 2); + _isAmbisonic = false; + _duration = (float)outputAudioByteArraySize / (sampleRate * numChannels * sizeof(int16_t)); + return sampleRate; +} diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 4cfdac7792..061c4a2417 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -62,6 +62,7 @@ public: void downSample(const QByteArray& rawAudioByteArray, int sampleRate); int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); + int interpretAsMP3(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); signals: void onSuccess(QByteArray data, bool stereo, bool ambisonic, float duration);