MP3 support


#1

Hi there,

I´m new to Juce and already pretty amazed about it (especially because it is a one man project).

What, for me, is definately lacking, is a (good) support for MP3 reading; perhaps also writing.

Wouldn´t it be possible to write an object that reads the MP3´s via Quicktime on Mac OSX and via Directshow or ACM on Windows?

I think doing it that way, one would also not need to pay Fraunhofer´s MP3 license as Microsoft and Apple have already paid huge amounts to Fraunhofer to include the MP3 technology in their Operating Systems - in fact this scheme would mean only interfacing Juce to already licensed “products” ? Please correct me if I am not right here.

I have at least done it that way in an application written by myself. It would also be useful to incorporate a MP3 frame-scanner into the MP3 reader object: This one would scan, if wished, the MP3 for frames once, and store all the corresponding sample-accurate starting times of them, so it would be possible to afterwards jump around in the MP3 with sample-accuracy and start reading from there. I have programmed such a frame-scanner for my own application in cojunction with ACM MP3 decoding and it works.


#2

I had the same idea, and wrote a decoder that used Quicktime. However, Quicktime was applying a fade in and fade out to the start and end and I never figured out how to disable this. In the end, I just required the user to have LAME on their system. If LAME is found, mp3 encoding and decoding works, otherwise it doesn’t.

I think you are correct about MS/Apple already having paid the license fees. However, in both cases, I don’t think you can encode without installing a codec.

Also, as I understand it, juce could have mp3 decoding/encoding, since it is distributed as source. It would be up to the application developer to worry about the royalties. I don’t understand how anybody but large companies can support mp3, as the minimum royalties are $15,000 a year.

I found this mini mp3 decoder which might be of interest to somebody: http://keyj.s2000.ws/?p=59


#3

Hi, I think you´re right about encoding license fees; and MP3 listening/decoding should theoretically at least be free when using already OS bundled technologies. I think Fraunhofer anyway do not care about little developers using MP3 decoding/encoding, otherwise they would have to sue millions of them :slight_smile:

The MiniMP3 decoder produces awful (really!) quality results compared to Windows Media Player or others.

Concerning the fade in/out: I had a very similar problem with ACM decoding, and I just read a good amount of frames in advance (i think 10), throw away the rendered sample data of these frames, and then start reading further frames beyond, from which I start playing the sample data. This works. Did you try this?


#4

Have you ever tried the MAD MPEG decoder available under the terms of the GPL. It produces amazing sound quality I’ve ever experienced. I’ve already proposed MAD decoder to Julian to use in JUCE…


#5

I’m guessing that will never happen since JUCE is released under licenses other than the GPL.

There is also FFmpeg, which is LGPL. It supports the following codecs. I’m planning on doing a JUCE wrapper for FFmpeg, but if anybody beats me to it, that would be ok.

* Apple Lossless (decoding only)
* Cook Codec (decoding only)
* FLAC (decoding only)
* Intel Music Coder (decoding only)
* MP2
* MP3 (native decoder, encoding through LAME)
* Shorten (decoding only)
* QDM2 (decoding only)
* RealAudio 1.0 (decoding only)
* RealAudio 2.0 (decoding only)
* Vorbis (decoding only)
* WavPack (decoding only)
* Windows Media Audio 1
* Windows Media Audio 2
* TTA (decoding only)
* ATRAC3 (decoding only)
* Truespeech (decoding only)
* AC-3

#6

That’s interesting. I guess it just calls other libraries to handle all the proprietary formats, rather than actually writing codecs for them?


#7

Nope, they’ve reverse engineered and re-implemented them. FFmpeg is the backend that is used for MPlayer.

http://ffmpeg.mplayerhq.hu/


#8

I am trying to write a mp3 reader-only object. Should I best inherit from AudioFormat or AudioFormatReader ?
How do I do a sleep(xxx milliseconds) in this mp3 reader without inheriting from Thread class?


#9

Both.

You need to make an MP3AudioFormat that inherits from AudioFormat

Then MP3AudioFormat::createReaderFor() needs to return a MP3AudioFormatReader that inherits from AudioFormatReader.

Look how WavAudioFormat for a good example.

Use the static method in Thread.

Thread::sleep(xxx);


#10

Nope, they’ve reverse engineered and re-implemented them. FFmpeg is the backend that is used for MPlayer.

http://ffmpeg.mplayerhq.hu/[/quote]

Wow. They’ve been pretty busy then!


#11

Weird coincidence, this coming up now. I just wrote a (hacky) MP3AudioFormat class myself, based on the Audiere library. It’s kind of obscure, but it was the only library I could find with high level methods for seeking through MP3s and returning the length in samples. I looked at libmad, but got fed up with the poor documentation and trying to work out how to seek through files without first decoding the entire file. If I’d known about that mini mp3 decoder I’d probably have used that - both it and audiere are based on the FFMPEG code anyway (I did have a look at using FFMPEG directly, but couldn’t decipher which parts of the library I actually needed, and again I ran into documentation issues).

It would be great to have access to all the FFMPEG-supported formats in JUCE though :slight_smile:

  • Niall.

#12

Cool, that´s nice, so it can read just from somewhere in the file with sample-accuracy?
My class (non JUCE-based, just C++ with Win32) in MSVC can do that, but unfortunately it is Windows-only. Anyway, I´d be very interested to have a look at your hacked class if that´s ok for you.


#13

Here you go, complete with doxygen-style comments. It’s mostly just a wrapper around Audiere’s SampleSource class. I’ve found one file it won’t play (maybe it’s unseekable? I’m not entirely sure what that means), but otherwise it seems to work pretty well. Note that it only works with FileInputStreams - see the comments below.

[code]// MP3AudioFormat.h - Audiere-based audio format for reading MP3s.

#ifndef MP3AUDIOFORMAT_H_
#define MP3AUDIOFORMAT_H_

#include “juce.h”

/// Audiere-based audio format for reading MP3s.
/*!
Please note this format can only be used with MP3 files.
The Audiere library has its own file classes which cannot be simply
distentangled from the MP3-reading classes. As such, the InputStream
passed into createReaderFor() is assumed to be a FileInputStream, and we
just pass the filepath to the Audiere decoding class (and ignore the
InputStream entirely). Obviously, MemoryInputStreams etc. will not work,
though I stuck a dynamic_cast in there so you should at least be safe from
bad things happening if you tried.

Also, this implementation doesn't handle writing to MP3, as I don't need
that myself (and Audiere only does decoding anyway - you'd need something
like lame to handle the encoding).

\todo Hack out the MP3 decoding code from Audiere's MP3InputStream class to
be implemented directly here so we can use all kinds of InputStreams.

*/
class MP3AudioFormat : public AudioFormat
{
public:
/// Constructor.
MP3AudioFormat();
/// Destructor.
~MP3AudioFormat();

///	Returns an array of samplerates the format can handle.
const Array<int> getPossibleSampleRates();
///	Returns an array of bitdepths the format can handle.
const Array<int> getPossibleBitDepths();
///	Returns whether or not the format can handle stereo.
bool canDoStereo() {return true;};
///	Returns whether or not the format can handle mono.
bool canDoMono() {return true;};
///	Returns whether or not the format is compressed.
bool isCompressed() {return true;};
///	Returns an array of quality options.
const StringArray getQualityOptions();

///	Creates an AudioFormatReader for MP3s.
AudioFormatReader* createReaderFor(InputStream *sourceStream,
                                   const bool deleteStreamIfOpeningFails);
///	(Doesn't) Create an AudioFormatWriter for MP3s.
AudioFormatWriter* createWriterFor(OutputStream *streamToWriteTo,
                                   double sampleRateToUse,
                                   unsigned int numberOfChannels,
                                   int bitsPerSample,
                                   const StringPairArray& metadataValues,
                                   int qualityOptionIndex);

juce_UseDebuggingNewOperator

};

#endif
[/code]

[code]// MP3AudioFormat.cpp - Audiere-based audio format for reading MP3s.

#include “MP3AudioFormat.h”

#include “audiere.h”

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
#define formatName TRANS(“MP3 file”)
static const tchar* const extensions[] = { T(".mp3"), 0 };

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
class MP3Reader : public AudioFormatReader
{
public:
//--------------------------------------------------------------------------
MP3Reader(InputStream * const inp):
AudioFormatReader(inp, formatName),
sampleSource(0)
{
FileInputStream *phil = dynamic_cast<FileInputStream *>(inp);

	//We can only handle files directly for now.
	if(phil)
	{
		sampleSource = audiere::OpenSampleSource((const char *)(phil->getFile().getFullPathName()));

		if(sampleSource)
		{
			//Un-seekable files are a problem, so we just don't bother with
			//them.
			if(sampleSource->isSeekable())
			{
				int channels;
				int srate;
				audiere::SampleFormat format;

				sampleSource->getFormat(channels, srate, format);
				numChannels = channels;
				sampleRate = (double)srate;
				switch(format) //I'm not sure this is necessary?
				{
					case audiere::SF_U8:
						bitsPerSample = 8;
						break;
					case audiere::SF_S16:
						bitsPerSample = 16;
						break;
				}
				lengthInSamples = sampleSource->getLength();
				//We have to convert it to 32-bits anyway, we might as well
				//make it float.
				usesFloatingPointData = true;

				tempBuffer.setSize(16384); //Reasonable amount?
			}
			else
			{
				sampleSource->unref();
				sampleSource = 0;
			}
		}
	}
};

//--------------------------------------------------------------------------
~MP3Reader()
{
	if(sampleSource)
		sampleSource->unref();
};

//--------------------------------------------------------------------------
bool read(int **destSamples,
          int64 startSampleInFile,
          int numSamples)
{
	int i;
	int numRead = 0;
	bool retval = true;
	short *tempData = 0;
	float **destFloat = (float **)destSamples;

	if(numSamples == 0)
		return true;

	if(sampleSource)
	{
		//Resize tempBuffer if necessary.
		//(we assume all MP3s are 16-bit - is that right?)
		if((numSamples * numChannels * 2) > (unsigned)tempBuffer.getSize())
			tempBuffer.setSize(numSamples * numChannels * 2);

		//Read samples.
		if(startSampleInFile < sampleSource->getLength())
		{
			//Check position.
			if(sampleSource->getPosition() != startSampleInFile)
				sampleSource->setPosition((int)startSampleInFile);

			numRead = sampleSource->read(numSamples, tempBuffer.getData());
		}
		else
			numRead = 0;

		//Copy tempBuffer samples to destSamples.
		tempData = (short *)(tempBuffer.getData());
		if((numChannels == 2) && (destFloat[1] != 0))
		{
			for(i=0;i<numRead;++i)
			{
				destFloat[0][i] = (float)tempData[i*2]/32768.0f;
				destFloat[1][i] = (float)tempData[(i*2)+1]/32768.0f;
			}
		}
		else if((numChannels == 1) || (destFloat[1] == 0))
		{
			for(i=0;i<numRead;++i)
				destFloat[0][i] = (float)tempData[i]/32768.0f;
		}

		//Pad with zeros if we've gone past the end of the file.
		if(numRead < numSamples)
		{
			if((numChannels == 2) && (destFloat[1] != 0))
			{
				for(i=numRead;i<numSamples;++i)
				{
					destFloat[0][i] = 0.0f;
					destFloat[1][i] = 0.0f;
				}
			}
			else if((numChannels == 1) || (destFloat[1] == 0))
			{
				for(i=numRead;i<numSamples;++i)
					destFloat[0][i] = 0.0f;
			}
		}

		retval = true;
	}

	return retval;
};

juce_UseDebuggingNewOperator

private:
/// Our Audiere sample source.
audiere::SampleSource *sampleSource;
/// Temporary storage.
MemoryBlock tempBuffer;
};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
MP3AudioFormat::MP3AudioFormat():
AudioFormat(formatName, (const tchar**)extensions)
{

}

//------------------------------------------------------------------------------
MP3AudioFormat::~MP3AudioFormat()
{

}

//------------------------------------------------------------------------------
const Array MP3AudioFormat::getPossibleSampleRates()
{
//Placeholder.
const int rates[] = {0};
return Array (rates);
}

//------------------------------------------------------------------------------
const Array MP3AudioFormat::getPossibleBitDepths()
{
//Placeholder.
const int rates[] = {0};
return Array (rates);
}

//------------------------------------------------------------------------------
const StringArray MP3AudioFormat::getQualityOptions()
{
return StringArray();
}

//------------------------------------------------------------------------------
AudioFormatReader* MP3AudioFormat::createReaderFor(InputStream sourceStream,
const bool deleteStreamIfOpeningFails)
{
//Copied from the Ogg AudioFormat.
MP3Reader
r = new MP3Reader(sourceStream);

if(r->sampleRate == 0)
{
    if(!deleteStreamIfOpeningFails)
        r->input = 0;

    deleteAndZero(r);
}

return r;

}

//------------------------------------------------------------------------------
AudioFormatWriter* MP3AudioFormat::createWriterFor(OutputStream *streamToWriteTo,
double sampleRateToUse,
unsigned int numberOfChannels,
int bitsPerSample,
const StringPairArray& metadataValues,
int qualityOptionIndex)
{
//Placeholder.
return 0;
}
[/code]

  • Niall.

#14

Cool, I´ll check it out today. Thanks!!


#15

Just tried, works fine, but not for all MP3s :frowning:


#16

Anyone tried compiling and using Audiere to decode MP3s on OS X ? Just interested to know if this works without hassle…


#17

Yeah, I don’t really know why this is. I tried creating a bunch of MP3s with different settings (constant bit rate, variable bit rate…) from Audacity via Lame, and it played all of them fine :?, so I’m not sure what’s different about the ones it doesn’t like, though I haven’t looked into it in detail.

According to the users page it’s used in the Irrlicht engine, which has an OS X port, so I’d guess it will work on OS X (the MP3 decoding part is just reading a file, after all). You might have to create your own XCode project or makefile for it though.

  • Niall.

#18

Did you try the trick with the X frames pre-reading that I suggested?


#19

Nope, I gave up on it, I cant find the code anymore either. I just use lame.


#20

Well, if anybody ever writes some FFMPEG AudioReader wrapper I’ll kiss his feet :slight_smile: