MP3 playback support

This commit is contained in:
Iliyas Jorio 2022-07-22 08:33:17 +02:00
parent 2acbe6cc86
commit 5d3fcb6ec6
8 changed files with 2034 additions and 12 deletions

View File

@ -54,6 +54,12 @@ else()
add_compile_definitions(POMME_NO_SOUND_FORMATS)
endif()
if (NOT(POMME_NO_MP3))
list(APPEND POMME_SOURCES ${POMME_SRCDIR}/SoundFormats/mp3.cpp)
else()
add_compile_definitions(POMME_NO_MP3)
endif()
if (NOT(POMME_NO_SOUND_MIXER))
list(APPEND POMME_SOURCES
${POMME_SRCDIR}/SoundMixer/ChannelImpl.cpp

View File

@ -168,3 +168,15 @@ by Tristan Brindle (Boost license 1.0):
> FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
> ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
> DEALINGS IN THE SOFTWARE.
--------
Portions copied from [lieff/minimp3](https://github.com/lieff/minimp3)
(Creative Commons Zero v1.0 Universal license):
- minimp3.h
> To the extent possible under law, the author(s) have dedicated all copyright
> and related and neighboring rights to this software to the public domain
> worldwide.
> This software is distributed without any warranty.
> See <http://creativecommons.org/publicdomain/zero/1.0/>.

View File

@ -32,6 +32,8 @@ namespace Pomme::Sound
int8_t baseNote;
uint32_t loopStart;
uint32_t loopEnd;
SndListHandle MakeStandaloneResource(char** dataOffsetOut = nullptr) const;
};
class Codec
@ -101,7 +103,8 @@ namespace Pomme::Sound
void GetSoundInfoFromSndResource(Handle sndHandle, SampledSoundInfo& info);
std::streampos GetSoundInfoFromAIFF(std::istream& input, SampledSoundInfo& info);
SndListHandle LoadAIFFAsResource(std::istream& input);
SndListHandle LoadMP3AsResource(std::istream& input);
std::unique_ptr<Pomme::Sound::Codec> GetCodec(uint32_t fourCC);
}

View File

@ -88,9 +88,9 @@ static void ParseINST(Pomme::BigEndianIStream& f, Pomme::Sound::SampledSoundInfo
}
}
std::streampos Pomme::Sound::GetSoundInfoFromAIFF(std::istream& input, SampledSoundInfo& info)
static std::streampos GetSoundInfoFromAIFF(std::istream& input, Pomme::Sound::SampledSoundInfo& info)
{
BigEndianIStream f(input);
Pomme::BigEndianIStream f(input);
AIFFAssert('FORM' == f.Read<uint32_t>(), "AIFF: invalid FORM");
auto formSize = f.Read<uint32_t>();
@ -181,3 +181,16 @@ std::streampos Pomme::Sound::GetSoundInfoFromAIFF(std::istream& input, SampledSo
return sampledSoundDataOffset;
}
SndListHandle Pomme::Sound::LoadAIFFAsResource(std::istream& stream)
{
Pomme::Sound::SampledSoundInfo info = {};
std::streampos ssndStart = GetSoundInfoFromAIFF(stream, info);
char* dataOffset = nullptr;
SndListHandle h = info.MakeStandaloneResource(&dataOffset);
stream.seekg(ssndStart, std::ios::beg);
stream.read(dataOffset, info.compressedLength);
return h;
}

View File

@ -230,24 +230,61 @@ void Pomme::Sound::GetSoundInfoFromSndResource(Handle sndHandle, SampledSoundInf
GetSoundInfo(sndhdr, info);
}
SndListHandle Pomme::Sound::SampledSoundInfo::MakeStandaloneResource(char** dataOffsetOut) const
{
const char* data = dataStart;
SampledSoundInfo info = *this;
Handle h = NewHandleClear(2 + 4 + sizeof(info) + compressedLength);
Ptr p = *h;
memcpy(p, "poPOMM", 6); // "po": see kSoundResourceType_Pomme; "POMM": see GetSoundInfo
p += 6;
info.dataStart = p + sizeof(info);
memcpy(p, &info, sizeof(info));
p += sizeof(info);
if (data != nullptr)
{
memcpy(p, data, info.compressedLength);
}
if (dataOffsetOut != nullptr)
{
*dataOffsetOut = p;
}
return (SndListHandle) h;
}
//-----------------------------------------------------------------------------
// Extension: load AIFF file as resource
SndListHandle Pomme_SndLoadFileAsResource(short fRefNum)
{
auto& spec = Pomme::Files::GetSpec(fRefNum);
auto& stream = Pomme::Files::GetStream(fRefNum);
Pomme::Sound::SampledSoundInfo info = {};
std::streampos ssndStart = Pomme::Sound::GetSoundInfoFromAIFF(stream, info);
std::string fileName(spec.cName);
std::transform(fileName.begin(), fileName.end(), fileName.begin(), tolower);
stream.seekg(ssndStart, std::ios::beg);
// Guess media container from extension
if (fileName.ends_with(".aiff")
|| fileName.ends_with(".aifc")
|| fileName.ends_with(".aif"))
{
return LoadAIFFAsResource(stream);
}
else if (fileName.ends_with(".mp3"))
{
#ifndef POMME_NO_MP3
return LoadMP3AsResource(stream);
#endif
}
Handle h = NewHandleClear(2 + 4 + sizeof(SampledSoundInfo) + info.compressedLength);
memcpy(*h, "poPOMM", 6);
memcpy(*h+6, &info, sizeof(SampledSoundInfo));
stream.read(*h+6+sizeof(SampledSoundInfo), info.compressedLength);
return (SndListHandle) h;
return nullptr;
}
//-----------------------------------------------------------------------------

1865
src/SoundFormats/minimp3.h Normal file

File diff suppressed because it is too large Load Diff

80
src/SoundFormats/mp3.cpp Normal file
View File

@ -0,0 +1,80 @@
#ifndef POMME_NO_MP3
#include "PommeSound.h"
#include <vector>
#include <algorithm>
#define MINIMP3_IMPLEMENTATION
#include "SoundFormats/minimp3.h"
#define MINIMP3_IO_SIZE (128*1024) // io buffer size for streaming functions, must be greater than MINIMP3_BUF_SIZE
#define MINIMP3_BUF_SIZE (16*1024) // buffer which can hold minimum 10 consecutive mp3 frames (~16KB) worst case
SndListHandle Pomme::Sound::LoadMP3AsResource(std::istream& stream)
{
mp3dec_t context = {};
mp3dec_init(&context);
mp3dec_frame_info_t frameInfo;
std::vector<uint8_t> fileBuf;
std::vector<mp3d_sample_t> songPCM;
std::vector<mp3d_sample_t> tempPCM(MINIMP3_MAX_SAMPLES_PER_FRAME);
int totalSamples = 0;
while (true)
{
// Refill buffer
if (fileBuf.size() < MINIMP3_BUF_SIZE)
{
if (stream.eof())
{
// bail
break;
}
auto oldSize = fileBuf.size();
auto toRead = MINIMP3_BUF_SIZE - oldSize;
fileBuf.resize(MINIMP3_BUF_SIZE);
stream.read((char*) (fileBuf.data() + oldSize), (int) toRead);
auto didRead = stream.gcount();
fileBuf.resize(oldSize + didRead);
}
int numDecodedSamples = mp3dec_decode_frame(&context, fileBuf.data(), (int) fileBuf.size(), tempPCM.data(), &frameInfo);
if (numDecodedSamples > 0)
{
size_t minCapacity = songPCM.size() + (numDecodedSamples * frameInfo.channels);
if (songPCM.capacity() < minCapacity)
{
songPCM.reserve(2 * minCapacity);
}
songPCM.insert(songPCM.end(), tempPCM.begin(), tempPCM.begin() + (numDecodedSamples * frameInfo.channels));
totalSamples += numDecodedSamples;
}
fileBuf.erase(fileBuf.begin(), fileBuf.begin() + frameInfo.frame_bytes);
}
Pomme::Sound::SampledSoundInfo info = {};
info.compressionType = 'swot';
info.bigEndian = false;
info.isCompressed = false;
info.baseNote = 60; // Middle C
info.codecBitDepth = 8 * sizeof(mp3d_sample_t);
info.sampleRate = frameInfo.hz;
info.nChannels = frameInfo.channels;
info.nPackets = totalSamples;
info.decompressedLength = totalSamples * info.nChannels * sizeof(mp3d_sample_t);
info.compressedLength = info.decompressedLength;
info.dataStart = (char*) songPCM.data();
return info.MakeStandaloneResource();
}
#endif // POMME_NO_MP3

View File

@ -334,6 +334,12 @@ OSErr SndStartFilePlay(
}
SndListHandle sndListHandle = Pomme_SndLoadFileAsResource(fRefNum);
if (!sndListHandle)
{
return badFileFormat;
}
long offset = 0;
GetSoundHeaderOffset(sndListHandle, &offset);
InstallSoundInChannel(chan, ((Ptr) *sndListHandle) + offset, true);