398 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			398 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright (C) 2002-2007 Nikolaus Gebhardt
 | |
| // Part of the code for this plugin for irrKlang is based on:
 | |
| //  MP3 input for Audiere by Matt Campbell <mattcampbell@pobox.com>, based on
 | |
| //  libavcodec from ffmpeg (http://ffmpeg.sourceforge.net/).
 | |
| // See license.txt for license details of this plugin.
 | |
| 
 | |
| #include "CIrrKlangAudioStreamMP3.h"
 | |
| #include <memory.h>
 | |
| #include <stdlib.h> // free, malloc and realloc
 | |
| #include <string.h>
 | |
| 
 | |
| namespace irrklang
 | |
| {
 | |
| 
 | |
| CIrrKlangAudioStreamMP3::CIrrKlangAudioStreamMP3(IFileReader* file)
 | |
| : File(file), TheMPAuDecContext(0), InputPosition(0), InputLength(0),
 | |
| 	DecodeBuffer(0), FirstFrameRead(false), EndOfFileReached(0),
 | |
| 	FileBegin(0), Position(0)
 | |
| {
 | |
| 	if (File)
 | |
| 	{
 | |
| 		File->grab();
 | |
| 
 | |
| 		TheMPAuDecContext = new MPAuDecContext();
 | |
| 
 | |
| 		if (!TheMPAuDecContext || mpaudec_init(TheMPAuDecContext) < 0)
 | |
| 		{
 | |
| 			File->drop();
 | |
| 			File = 0;
 | |
| 			delete TheMPAuDecContext;
 | |
| 			TheMPAuDecContext = 0;
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// init, get format
 | |
| 
 | |
| 		DecodeBuffer = new ik_u8[MPAUDEC_MAX_AUDIO_FRAME_SIZE];
 | |
| 
 | |
| 		if (File->getSize()>0)
 | |
| 		{
 | |
| 			// seekable file, now parse file to get size
 | |
| 			// (needed to make it possible for the engine to loop a stream correctly)
 | |
| 
 | |
| 			skipID3IfNecessary();
 | |
| 
 | |
| 			TheMPAuDecContext->parse_only = 1;
 | |
| 			Format.FrameCount = 0;
 | |
| 
 | |
| 			while(!EndOfFileReached)
 | |
| 			{
 | |
| 				if (!decodeFrame())
 | |
| 					break;
 | |
| 
 | |
| 				Format.FrameCount += TheMPAuDecContext->frame_size;
 | |
| 
 | |
| 				if (!EndOfFileReached /*&& File->isSeekable()*/ )
 | |
| 				{
 | |
| 					// to be able to seek in the stream, store offsets and sizes
 | |
| 
 | |
| 					SFramePositionData data;
 | |
| 					data.size = TheMPAuDecContext->frame_size;
 | |
| 					data.offset = File->getPos() - (InputLength - InputPosition) - TheMPAuDecContext->coded_frame_size;
 | |
| 
 | |
| 					FramePositionData.push_back(data);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			TheMPAuDecContext->parse_only = 0;
 | |
| 			setPosition(0);
 | |
| 		}
 | |
| 		else
 | |
| 			decodeFrame(); // decode first frame to read audio format
 | |
| 
 | |
| 		if (!TheMPAuDecContext->channels ||
 | |
| 			!TheMPAuDecContext->sample_rate )
 | |
| 		{
 | |
| 			File->drop();
 | |
| 			File = 0;
 | |
| 			delete TheMPAuDecContext;
 | |
| 			TheMPAuDecContext = 0;
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| CIrrKlangAudioStreamMP3::~CIrrKlangAudioStreamMP3()
 | |
| {
 | |
| 	if (File)
 | |
| 		File->drop();
 | |
| 
 | |
| 	if (TheMPAuDecContext)
 | |
| 	{
 | |
| 		mpaudec_clear(TheMPAuDecContext);
 | |
| 		delete TheMPAuDecContext;
 | |
| 	}
 | |
| 
 | |
| 	delete [] DecodeBuffer;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| //! returns format of the audio stream
 | |
| SAudioStreamFormat CIrrKlangAudioStreamMP3::getFormat()
 | |
| {
 | |
| 	return Format;
 | |
| }
 | |
| 
 | |
| 
 | |
| //! tells the audio stream to read n audio frames into the specified buffer
 | |
| ik_s32 CIrrKlangAudioStreamMP3::readFrames(void* target, ik_s32 frameCountToRead)
 | |
| {
 | |
| 	const int frameSize = Format.getFrameSize();
 | |
| 
 | |
| 	int framesRead = 0;
 | |
| 	ik_u8* out = (ik_u8*)target;
 | |
| 
 | |
| 	while (framesRead < frameCountToRead)
 | |
| 	{
 | |
| 		// no more samples?  ask the MP3 for more
 | |
| 		if (DecodedQueue.getSize() < frameSize)
 | |
| 		{
 | |
| 			if (!decodeFrame() || EndOfFileReached)
 | |
| 				return framesRead;
 | |
| 
 | |
| 			// if the buffer is still empty, we are done
 | |
| 			if (DecodedQueue.getSize() < frameSize)
 | |
| 				return framesRead;
 | |
| 		}
 | |
| 
 | |
| 		const int framesLeft = frameCountToRead - framesRead;
 | |
| 		const int dequeSize = DecodedQueue.getSize() / frameSize;
 | |
| 		const int framesToRead = framesLeft < dequeSize ? framesLeft : dequeSize;
 | |
| 
 | |
| 		DecodedQueue.read(out, framesToRead * frameSize);
 | |
| 
 | |
| 		out += framesToRead * frameSize;
 | |
| 		framesRead += framesToRead;
 | |
| 		Position += framesToRead;
 | |
| 	}
 | |
| 
 | |
| 	return framesRead;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| bool CIrrKlangAudioStreamMP3::decodeFrame()
 | |
| {
 | |
|     int outputSize = 0;
 | |
| 
 | |
| 	while (!outputSize)
 | |
| 	{
 | |
| 		if (InputPosition == InputLength)
 | |
| 		{
 | |
| 			InputPosition = 0;
 | |
| 			InputLength = File->read(InputBuffer, IKP_MP3_INPUT_BUFFER_SIZE);
 | |
| 
 | |
| 			if (InputLength == 0)
 | |
| 			{
 | |
| 				EndOfFileReached = true;
 | |
| 				return true;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		int rv = mpaudec_decode_frame( TheMPAuDecContext, (ik_s16*)DecodeBuffer,
 | |
| 									   &outputSize,
 | |
| 									   (ik_u8*)InputBuffer + InputPosition,
 | |
| 									   InputLength - InputPosition);
 | |
| 
 | |
| 		if (rv < 0)
 | |
| 			return false;
 | |
| 
 | |
| 		InputPosition += rv;
 | |
| 	} // end while
 | |
| 
 | |
| 	if (!FirstFrameRead)
 | |
| 	{
 | |
| 		Format.ChannelCount = TheMPAuDecContext->channels;
 | |
| 		Format.SampleRate = TheMPAuDecContext->sample_rate;
 | |
| 		Format.SampleFormat = ESF_S16;
 | |
| 		Format.FrameCount = -1; // unknown lenght
 | |
| 
 | |
| 		FirstFrameRead = true;
 | |
| 	}
 | |
| 	else
 | |
| 	if (TheMPAuDecContext->channels != Format.ChannelCount ||
 | |
| 		TheMPAuDecContext->sample_rate != Format.SampleRate)
 | |
| 	{
 | |
| 		// Can't handle format changes mid-stream.
 | |
| 		return false;
 | |
|     }
 | |
| 
 | |
| 	if (!TheMPAuDecContext->parse_only)
 | |
| 	{
 | |
| 		if (outputSize < 0)
 | |
| 		{
 | |
| 			// Couldn't decode this frame.  Too bad, already lost it.
 | |
| 			// This should only happen when seeking.
 | |
| 
 | |
| 			outputSize = TheMPAuDecContext->frame_size;
 | |
| 			memset(DecodeBuffer, 0, outputSize * Format.getFrameSize());
 | |
| 		}
 | |
| 
 | |
| 		DecodedQueue.write(DecodeBuffer, outputSize);
 | |
| 	}
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| //! sets the position of the audio stream.
 | |
| /** For example to let the stream be read from the beginning of the file again,
 | |
| setPosition(0) would be called. This is usually done be the sound engine to
 | |
| loop a stream after if has reached the end. Return true if sucessful and 0 if not. */
 | |
| bool CIrrKlangAudioStreamMP3::setPosition(ik_s32 pos)
 | |
| {
 | |
| 	if (!File || !TheMPAuDecContext)
 | |
| 		return false;
 | |
| 
 | |
| 	if (pos == 0)
 | |
| 	{
 | |
| 		// usually done for looping, just reset to start
 | |
| 
 | |
| 		File->seek(FileBegin); // skip possible ID3 header
 | |
| 
 | |
| 		EndOfFileReached = false;
 | |
| 
 | |
| 		DecodedQueue.clear();
 | |
| 
 | |
| 		MPAuDecContext oldContext = *TheMPAuDecContext;
 | |
| 
 | |
| 		mpaudec_clear(TheMPAuDecContext);
 | |
| 		mpaudec_init(TheMPAuDecContext);
 | |
| 
 | |
| 		TheMPAuDecContext->bit_rate = oldContext.bit_rate;
 | |
| 		TheMPAuDecContext->channels = oldContext.channels;
 | |
| 		TheMPAuDecContext->frame_size = oldContext.frame_size;
 | |
| 		TheMPAuDecContext->sample_rate = oldContext.sample_rate;
 | |
| 
 | |
| 		InputPosition = 0;
 | |
| 		InputLength = 0;
 | |
| 		Position = 0;
 | |
| 		CurrentFramePosition = 0;
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		// user wants to seek in the stream, so do this here
 | |
| 
 | |
| 		int scan_position = 0;
 | |
| 		int target_frame = 0;
 | |
| 		int frame_count = (int)FramePositionData.size();
 | |
| 
 | |
| 		while (target_frame < frame_count)
 | |
| 		{
 | |
| 			int frame_size = FramePositionData[target_frame].size;
 | |
| 
 | |
| 			if (pos <= scan_position + frame_size)
 | |
| 				break;
 | |
| 			else
 | |
| 			{
 | |
| 				scan_position += frame_size;
 | |
| 				target_frame++;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 
 | |
| 		const int MAX_FRAME_DEPENDENCY = 10;
 | |
| 		target_frame = std::max(0, target_frame - MAX_FRAME_DEPENDENCY);
 | |
| 		setPosition(0);
 | |
| 
 | |
| 		File->seek(FramePositionData[target_frame].offset, false);
 | |
| 
 | |
| 		int i;
 | |
| 		for (i = 0; i < target_frame; i++)
 | |
| 		{
 | |
| 			if (i>=(int)FramePositionData.size())
 | |
| 			{
 | |
| 				// internal error
 | |
| 				setPosition(0);
 | |
| 				return false;
 | |
| 			}
 | |
| 
 | |
| 			Position += FramePositionData[i].size;
 | |
| 		}
 | |
| 
 | |
| 		if (!decodeFrame() || EndOfFileReached)
 | |
| 		{
 | |
| 			setPosition(0);
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		int frames_to_consume = pos - Position; // PCM frames now
 | |
| 		if (frames_to_consume > 0)
 | |
| 		{
 | |
| 			ik_u8 *buf = new ik_u8[frames_to_consume * Format.getFrameSize()];
 | |
| 			readFrames(buf, frames_to_consume);
 | |
| 			delete[] buf;
 | |
| 		}
 | |
| 
 | |
|       	return true;
 | |
| 	}
 | |
| 
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| CIrrKlangAudioStreamMP3::QueueBuffer::QueueBuffer()
 | |
| {
 | |
| 	Capacity = 256;
 | |
| 	Size = 0;
 | |
| 
 | |
| 	Buffer = (ik_u8*)malloc(Capacity);
 | |
| }
 | |
| 
 | |
| 
 | |
| CIrrKlangAudioStreamMP3::QueueBuffer::~QueueBuffer()
 | |
| {
 | |
| 	free(Buffer);
 | |
| }
 | |
| 
 | |
| int CIrrKlangAudioStreamMP3::QueueBuffer::getSize()
 | |
| {
 | |
| 	return Size;
 | |
| }
 | |
| 
 | |
| void CIrrKlangAudioStreamMP3::QueueBuffer::write(const void* buffer, int size)
 | |
| {
 | |
| 	bool needRealloc = false;
 | |
| 
 | |
| 	while (size + Size > Capacity)
 | |
| 	{
 | |
| 		Capacity *= 2;
 | |
| 		needRealloc = true;
 | |
| 	}
 | |
| 
 | |
|     if (needRealloc)
 | |
| 	{
 | |
|         Buffer = (ik_u8*)realloc(Buffer, Capacity);
 | |
|     }
 | |
| 
 | |
| 	memcpy(Buffer + Size, buffer, size);
 | |
| 	Size += size;
 | |
| }
 | |
| 
 | |
| 
 | |
| int CIrrKlangAudioStreamMP3::QueueBuffer::read(void* buffer, int size)
 | |
| {
 | |
| 	int toRead = size < Size ? size : Size;
 | |
| 
 | |
| 	memcpy(buffer, Buffer, toRead);
 | |
| 	memmove(Buffer, Buffer + toRead, Size - toRead);
 | |
| 
 | |
| 	Size -= toRead;
 | |
| 	return toRead;
 | |
| }
 | |
| 
 | |
| 
 | |
| void CIrrKlangAudioStreamMP3::QueueBuffer::clear()
 | |
| {
 | |
| 	Size = 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| void CIrrKlangAudioStreamMP3::skipID3IfNecessary()
 | |
| {
 | |
| 	char header[10];
 | |
| 	int read = File->read(&header, 10);
 | |
| 
 | |
| 	if (read == 10 &&
 | |
| 		header[0] == 'I' && header[1] == 'D' && header[2] == '3')
 | |
| 	{
 | |
| 		int versionMajor = header[3];
 | |
| 		int versionMinor = header[4];
 | |
| 		int flags = header[5];
 | |
| 
 | |
| 		// IDv2 size looks like the following: ID3v2 size  4 * %0xxxxxxx.
 | |
| 		// Sick, but that's how it works.
 | |
| 
 | |
| 		int size = 0;
 | |
| 		size  = (header[6] & 0x7f) << (3*7);
 | |
| 		size |= (header[7] & 0x7f) << (2*7);
 | |
| 		size |= (header[8] & 0x7f) << (1*7);
 | |
| 		size |= (header[9] & 0x7f) ;
 | |
| 
 | |
| 		size += 10; // header size
 | |
| 
 | |
| 		FileBegin = size;
 | |
| 		File->seek(FileBegin);
 | |
| 	}
 | |
| 	else
 | |
| 		File->seek(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| } // end namespace irrklang
 |