274 lines
7.4 KiB
C++
274 lines
7.4 KiB
C++
|
//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
|
||
|
|
||
|
#include "audio_pch.h"
|
||
|
#include "snd_mp3_source.h"
|
||
|
#include "vaudio/ivaudio.h"
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include "tier0/memdbgon.h"
|
||
|
|
||
|
extern IVAudio *vaudio;
|
||
|
|
||
|
static const int MP3_BUFFER_SIZE = 16384;
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Mixer for ADPCM encoded audio
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CAudioMixerWaveMP3 : public CAudioMixerWave, public IAudioStreamEvent
|
||
|
{
|
||
|
public:
|
||
|
CAudioMixerWaveMP3( IWaveData *data );
|
||
|
~CAudioMixerWaveMP3( void );
|
||
|
|
||
|
virtual void Mix( channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress );
|
||
|
virtual int GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
|
||
|
|
||
|
// need to override this to fixup blocks
|
||
|
// UNDONE: This doesn't quite work with MP3 - we need a MP3 position, not a sample position
|
||
|
void SetSampleStart( int newPosition );
|
||
|
|
||
|
int GetPositionForSave() { return m_pStream->GetPosition(); }
|
||
|
void SetPositionFromSaved(int position) { m_pStream->SetPosition(position); }
|
||
|
|
||
|
// IAudioStreamEvent
|
||
|
virtual int StreamRequestData( void *pBuffer, int bytesRequested, int offset );
|
||
|
|
||
|
virtual void SetStartupDelaySamples( int delaySamples );
|
||
|
virtual int GetMixSampleSize() { return CalcSampleSize( 16, m_channelCount ); }
|
||
|
|
||
|
bool IsValid() { return m_pStream != NULL; }
|
||
|
|
||
|
virtual int GetStreamOutputRate() { return m_pStream->GetOutputRate(); }
|
||
|
|
||
|
private:
|
||
|
bool DecodeBlock( void );
|
||
|
void GetID3HeaderOffset();
|
||
|
|
||
|
|
||
|
IAudioStream *m_pStream;
|
||
|
char m_samples[MP3_BUFFER_SIZE];
|
||
|
int m_sampleCount;
|
||
|
int m_samplePosition;
|
||
|
int m_channelCount;
|
||
|
int m_offset;
|
||
|
int m_delaySamples;
|
||
|
int m_headerOffset;
|
||
|
};
|
||
|
|
||
|
|
||
|
CAudioMixerWaveMP3::CAudioMixerWaveMP3( IWaveData *data ) : CAudioMixerWave( data )
|
||
|
{
|
||
|
m_sampleCount = 0;
|
||
|
m_samplePosition = 0;
|
||
|
m_offset = 0;
|
||
|
m_delaySamples = 0;
|
||
|
m_headerOffset = 0;
|
||
|
m_pStream = NULL;
|
||
|
if ( vaudio )
|
||
|
m_pStream = vaudio->CreateMP3StreamDecoder( static_cast<IAudioStreamEvent *>(this) );
|
||
|
if ( m_pStream )
|
||
|
{
|
||
|
m_channelCount = m_pStream->GetOutputChannels();
|
||
|
//Assert( m_pStream->GetOutputRate() == m_pData->Source().SampleRate() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
CAudioMixerWaveMP3::~CAudioMixerWaveMP3( void )
|
||
|
{
|
||
|
if ( m_pStream )
|
||
|
{
|
||
|
vaudio->DestroyMP3StreamDecoder( m_pStream );
|
||
|
m_pStream = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CAudioMixerWaveMP3::Mix( channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
|
||
|
{
|
||
|
if ( m_channelCount == 1 )
|
||
|
{
|
||
|
Device_Mix16Mono( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Device_Mix16Stereo( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// Some MP3 files are wrapped in ID3
|
||
|
void CAudioMixerWaveMP3::GetID3HeaderOffset()
|
||
|
{
|
||
|
char copyBuf[AUDIOSOURCE_COPYBUF_SIZE];
|
||
|
byte *pData;
|
||
|
|
||
|
int bytesRead = m_pData->ReadSourceData( (void **)&pData, 0, 10, copyBuf );
|
||
|
if ( bytesRead < 10 )
|
||
|
return;
|
||
|
|
||
|
m_headerOffset = 0;
|
||
|
if (( pData[ 0 ] == 0x49 ) &&
|
||
|
( pData[ 1 ] == 0x44 ) &&
|
||
|
( pData[ 2 ] == 0x33 ) &&
|
||
|
( pData[ 3 ] < 0xff ) &&
|
||
|
( pData[ 4 ] < 0xff ) &&
|
||
|
( pData[ 6 ] < 0x80 ) &&
|
||
|
( pData[ 7 ] < 0x80 ) &&
|
||
|
( pData[ 8 ] < 0x80 ) &&
|
||
|
( pData[ 9 ] < 0x80 ) )
|
||
|
{
|
||
|
// this is in id3 file
|
||
|
// compute the size of the wrapper and skip it
|
||
|
m_headerOffset = 10 + ( pData[9] | (pData[8]<<7) | (pData[7]<<14) | (pData[6]<<21) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int CAudioMixerWaveMP3::StreamRequestData( void *pBuffer, int bytesRequested, int offset )
|
||
|
{
|
||
|
if ( offset < 0 )
|
||
|
{
|
||
|
offset = m_offset;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_offset = offset;
|
||
|
}
|
||
|
// read the data out of the source
|
||
|
int totalBytesRead = 0;
|
||
|
|
||
|
if ( offset == 0 )
|
||
|
{
|
||
|
// top of file, check for ID3 wrapper
|
||
|
GetID3HeaderOffset();
|
||
|
}
|
||
|
|
||
|
offset += m_headerOffset; // skip any id3 header/wrapper
|
||
|
|
||
|
while ( bytesRequested > 0 )
|
||
|
{
|
||
|
char *pOutputBuffer = (char *)pBuffer;
|
||
|
pOutputBuffer += totalBytesRead;
|
||
|
|
||
|
void *pData = NULL;
|
||
|
int bytesRead = m_pData->ReadSourceData( &pData, offset + totalBytesRead, bytesRequested, pOutputBuffer );
|
||
|
|
||
|
if ( !bytesRead )
|
||
|
break;
|
||
|
if ( bytesRead > bytesRequested )
|
||
|
{
|
||
|
bytesRead = bytesRequested;
|
||
|
}
|
||
|
// if the source is buffering it, copy it to the MP3 decomp buffer
|
||
|
if ( pData != pOutputBuffer )
|
||
|
{
|
||
|
memcpy( pOutputBuffer, pData, bytesRead );
|
||
|
}
|
||
|
totalBytesRead += bytesRead;
|
||
|
bytesRequested -= bytesRead;
|
||
|
}
|
||
|
|
||
|
m_offset += totalBytesRead;
|
||
|
return totalBytesRead;
|
||
|
}
|
||
|
|
||
|
bool CAudioMixerWaveMP3::DecodeBlock()
|
||
|
{
|
||
|
m_sampleCount = m_pStream->Decode( m_samples, sizeof(m_samples) );
|
||
|
m_samplePosition = 0;
|
||
|
return m_sampleCount > 0;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Read existing buffer or decompress a new block when necessary
|
||
|
// Input : **pData - output data pointer
|
||
|
// sampleCount - number of samples (or pairs)
|
||
|
// Output : int - available samples (zero to stop decoding)
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int CAudioMixerWaveMP3::GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
|
||
|
{
|
||
|
if ( m_samplePosition >= m_sampleCount )
|
||
|
{
|
||
|
if ( !DecodeBlock() )
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if ( m_samplePosition < m_sampleCount )
|
||
|
{
|
||
|
int sampleSize = m_channelCount * 2;
|
||
|
*pData = (void *)(m_samples + m_samplePosition);
|
||
|
int available = m_sampleCount - m_samplePosition;
|
||
|
int bytesRequired = sampleCount * sampleSize;
|
||
|
if ( available > bytesRequired )
|
||
|
available = bytesRequired;
|
||
|
|
||
|
m_samplePosition += available;
|
||
|
|
||
|
int samples_loaded = available / sampleSize;
|
||
|
|
||
|
// update count of max samples loaded in CAudioMixerWave
|
||
|
|
||
|
CAudioMixerWave::m_sample_max_loaded += samples_loaded;
|
||
|
|
||
|
// update index of last sample loaded
|
||
|
|
||
|
CAudioMixerWave::m_sample_loaded_index += samples_loaded;
|
||
|
|
||
|
return samples_loaded;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Seek to a new position in the file
|
||
|
// NOTE: In most cases, only call this once, and call it before playing
|
||
|
// any data.
|
||
|
// Input : newPosition - new position in the sample clocks of this sample
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CAudioMixerWaveMP3::SetSampleStart( int newPosition )
|
||
|
{
|
||
|
// UNDONE: Implement this?
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : delaySamples -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CAudioMixerWaveMP3::SetStartupDelaySamples( int delaySamples )
|
||
|
{
|
||
|
m_delaySamples = delaySamples;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Abstract factory function for MP3 mixers
|
||
|
// Input : *data - wave data access object
|
||
|
// channels -
|
||
|
// Output : CAudioMixer
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CAudioMixer *CreateMP3Mixer( IWaveData *data, int *pSampleRate )
|
||
|
{
|
||
|
CAudioMixerWaveMP3 *pMixer = new CAudioMixerWaveMP3( data );
|
||
|
if ( pMixer->IsValid() )
|
||
|
{
|
||
|
// pass the sample rate back just in time to save parsing the MP3 file twice to get sample rate
|
||
|
if ( pSampleRate )
|
||
|
{
|
||
|
*pSampleRate = pMixer->GetStreamOutputRate();
|
||
|
}
|
||
|
return pMixer;
|
||
|
}
|
||
|
|
||
|
delete pMixer;
|
||
|
return NULL;
|
||
|
}
|