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
|