Skip to content

Commit a99feae

Browse files
committed
Add and fix mp3 audio format
1 parent 93d910b commit a99feae

File tree

12 files changed

+760
-1
lines changed

12 files changed

+760
-1
lines changed

cmake/yup_utilities.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ function (_yup_collect_upstream_candidate_paths module_name module_path output_v
209209
set (candidate_paths "${module_path}/upstream")
210210

211211
set (candidate_root "${CMAKE_BINARY_DIR}")
212-
set (max_depth 6)
212+
set (max_depth 10)
213213
while (max_depth GREATER 0)
214214
list (APPEND candidate_paths "${candidate_root}/externals/${module_name}/upstream")
215215

164 KB
Binary file not shown.
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/*
2+
==============================================================================
3+
This file is part of the YUP library.
4+
Copyright (c) 2025 - [email protected]
5+
YUP is an open source library subject to open-source licensing.
6+
The code included in this file is provided under the terms of the ISC license
7+
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
8+
to use, copy, modify, and/or distribute this software for any purpose with or
9+
without fee is hereby granted provided that the above copyright notice and
10+
this permission notice appear in all copies.
11+
YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
12+
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
13+
DISCLAIMED.
14+
==============================================================================
15+
*/
16+
17+
namespace yup
18+
{
19+
20+
namespace
21+
{
22+
23+
//==============================================================================
24+
25+
class Mp3AudioFormatReader : public AudioFormatReader
26+
{
27+
public:
28+
Mp3AudioFormatReader (InputStream* sourceStream);
29+
~Mp3AudioFormatReader() override;
30+
31+
bool readSamples (float* const* destChannels,
32+
int numDestChannels,
33+
int startOffsetInDestBuffer,
34+
int64 startSampleInFile,
35+
int numSamples) override;
36+
37+
private:
38+
static size_t readCallback (void* pUserData, void* pBufferOut, size_t bytesToRead)
39+
{
40+
auto* stream = static_cast<InputStream*> (pUserData);
41+
return (size_t) stream->read (pBufferOut, (int) bytesToRead);
42+
}
43+
44+
static drmp3_bool32 seekCallback (void* pUserData, int offset, drmp3_seek_origin origin)
45+
{
46+
auto* stream = static_cast<InputStream*> (pUserData);
47+
48+
if (origin == DRMP3_SEEK_SET)
49+
return stream->setPosition (offset) ? DRMP3_TRUE : DRMP3_FALSE;
50+
else if (origin == DRMP3_SEEK_CUR)
51+
return stream->setPosition (stream->getPosition() + offset) ? DRMP3_TRUE : DRMP3_FALSE;
52+
53+
return DRMP3_FALSE;
54+
}
55+
56+
static drmp3_bool32 tellCallback (void* pUserData, drmp3_int64* pCursor)
57+
{
58+
auto* stream = static_cast<InputStream*> (pUserData);
59+
*pCursor = stream->getPosition();
60+
return DRMP3_TRUE;
61+
}
62+
63+
static void metaCallback (void* pUserData, const drmp3_metadata* pMetadata)
64+
{
65+
auto* reader = static_cast<Mp3AudioFormatReader*> (pUserData);
66+
if (reader && pMetadata && pMetadata->pRawData)
67+
{
68+
// Handle metadata based on type
69+
switch (pMetadata->type)
70+
{
71+
case DRMP3_METADATA_TYPE_ID3V1:
72+
case DRMP3_METADATA_TYPE_ID3V2:
73+
case DRMP3_METADATA_TYPE_APE:
74+
{
75+
// For now, we'll just store the raw metadata. In a real implementation,
76+
// you would parse the metadata and extract useful information like
77+
// title, artist, album, etc.
78+
break;
79+
}
80+
case DRMP3_METADATA_TYPE_XING:
81+
case DRMP3_METADATA_TYPE_VBRI:
82+
{
83+
// Xing/VBRI headers contain VBR information and seek tables
84+
break;
85+
}
86+
default:
87+
break;
88+
}
89+
}
90+
}
91+
92+
drmp3 mp3 = {};
93+
HeapBlock<float> tempBuffer;
94+
size_t tempBufferSize = 0;
95+
bool isOpen = false;
96+
int64 currentPCMFrame = 0;
97+
98+
YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Mp3AudioFormatReader)
99+
};
100+
101+
Mp3AudioFormatReader::Mp3AudioFormatReader (InputStream* sourceStream)
102+
: AudioFormatReader (sourceStream, "MP3 file")
103+
{
104+
if (sourceStream == nullptr)
105+
return;
106+
107+
isOpen = drmp3_init (&mp3, readCallback, seekCallback, tellCallback, metaCallback, sourceStream, nullptr) == DRMP3_TRUE;
108+
109+
if (isOpen)
110+
{
111+
sampleRate = mp3.sampleRate;
112+
bitsPerSample = 16; // MP3 always outputs 16-bit samples
113+
lengthInSamples = mp3.totalPCMFrameCount;
114+
numChannels = mp3.channels;
115+
usesFloatingPointData = false; // MP3 outputs 16-bit integer samples
116+
117+
// Allocate temp buffer for reading
118+
const auto bytesPerFrame = numChannels * (bitsPerSample / 8);
119+
tempBufferSize = bytesPerFrame * 4096;
120+
tempBuffer.allocate (tempBufferSize / sizeof (float), true);
121+
}
122+
}
123+
124+
Mp3AudioFormatReader::~Mp3AudioFormatReader()
125+
{
126+
if (isOpen)
127+
drmp3_uninit (&mp3);
128+
}
129+
130+
bool Mp3AudioFormatReader::readSamples (float* const* destChannels,
131+
int numDestChannels,
132+
int startOffsetInDestBuffer,
133+
int64 startSampleInFile,
134+
int numSamples)
135+
{
136+
if (! isOpen)
137+
return false;
138+
139+
if (numSamples <= 0)
140+
return true;
141+
142+
// Seek to the start position if needed
143+
if (startSampleInFile != currentPCMFrame)
144+
{
145+
if (! drmp3_seek_to_pcm_frame (&mp3, startSampleInFile))
146+
return false;
147+
currentPCMFrame = startSampleInFile;
148+
}
149+
150+
const auto numChannelsToRead = jmin (numDestChannels, numChannels);
151+
const auto bytesPerSample = bitsPerSample / 8;
152+
const auto bytesPerFrame = numChannels * bytesPerSample;
153+
154+
// Create output channel pointers offset by the start position
155+
HeapBlock<float*> offsetDestChannels;
156+
offsetDestChannels.malloc (numDestChannels);
157+
158+
for (int ch = 0; ch < numDestChannels; ++ch)
159+
{
160+
offsetDestChannels[ch] = destChannels[ch] + startOffsetInDestBuffer;
161+
}
162+
163+
drmp3_uint64 framesRead = 0;
164+
int samplesToRead = numSamples;
165+
166+
while (samplesToRead > 0)
167+
{
168+
const auto framesToRead = jmin (samplesToRead, (int) (tempBufferSize / (numChannels * sizeof (float))));
169+
170+
if (framesToRead <= 0)
171+
break;
172+
173+
// Read MP3 frames into temp buffer
174+
auto framesJustRead = drmp3_read_pcm_frames_f32 (&mp3, framesToRead, tempBuffer.getData());
175+
176+
if (framesJustRead == 0)
177+
break;
178+
179+
// Convert and deinterleave the samples
180+
using SourceFormat = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>;
181+
using DestFormat = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>;
182+
183+
AudioData::deinterleaveSamples (AudioData::InterleavedSource<SourceFormat> { tempBuffer.getData(), numChannels },
184+
AudioData::NonInterleavedDest<DestFormat> { offsetDestChannels.getData(), numChannelsToRead },
185+
(int) framesJustRead);
186+
187+
// Fill remaining channels with copies if requested
188+
for (int ch = numChannelsToRead; ch < numDestChannels; ++ch)
189+
{
190+
if (offsetDestChannels[ch] != nullptr)
191+
zeromem (offsetDestChannels[ch], sizeof (float) * framesJustRead);
192+
}
193+
194+
// Update pointers and counters
195+
for (int ch = 0; ch < numDestChannels; ++ch)
196+
{
197+
if (offsetDestChannels[ch] != nullptr)
198+
offsetDestChannels[ch] += framesJustRead;
199+
}
200+
201+
framesRead += framesJustRead;
202+
samplesToRead -= (int) framesJustRead;
203+
currentPCMFrame += framesJustRead;
204+
}
205+
206+
return framesRead > 0;
207+
}
208+
209+
} // namespace
210+
211+
//==============================================================================
212+
// Mp3AudioFormat implementation
213+
Mp3AudioFormat::Mp3AudioFormat()
214+
: formatName ("MP3 file")
215+
{
216+
}
217+
218+
Mp3AudioFormat::~Mp3AudioFormat() = default;
219+
220+
const String& Mp3AudioFormat::getFormatName() const
221+
{
222+
return formatName;
223+
}
224+
225+
Array<String> Mp3AudioFormat::getFileExtensions() const
226+
{
227+
return { ".mp3" };
228+
}
229+
230+
std::unique_ptr<AudioFormatReader> Mp3AudioFormat::createReaderFor (InputStream* sourceStream)
231+
{
232+
auto reader = std::make_unique<Mp3AudioFormatReader> (sourceStream);
233+
234+
if (reader->sampleRate > 0 && reader->numChannels > 0)
235+
return reader;
236+
237+
return nullptr;
238+
}
239+
240+
std::unique_ptr<AudioFormatWriter> Mp3AudioFormat::createWriterFor (OutputStream* streamToWriteTo,
241+
double sampleRate,
242+
int numberOfChannels,
243+
int bitsPerSample,
244+
const StringPairArray& metadataValues,
245+
int qualityOptionIndex)
246+
{
247+
// MP3 encoding is not implemented in this version
248+
return nullptr;
249+
}
250+
251+
Array<int> Mp3AudioFormat::getPossibleBitDepths() const
252+
{
253+
return { 16 };
254+
}
255+
256+
Array<int> Mp3AudioFormat::getPossibleSampleRates() const
257+
{
258+
return { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 };
259+
}
260+
261+
} // namespace yup

0 commit comments

Comments
 (0)