📄 shoutcaststream.cpp
字号:
/*
* ==============================================================================
* Name : ShoutcastStream.cpp
* Part of : Shoutcast Engine
* Interface :
* Description : Implementation of shoutcast stream class
* Version : 2
*
* Copyright (c) 2006, Nokia Corporation All rights reserved. Redistribution
* and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: Redistributions
* of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer. Redistributions in binary form
* must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided
* with the distribution. Neither the name of the Nokia Corporation nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission. THIS
* SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ==============================================================================
*/
// INCLUDE FILES
#include <e32base.h>
#include <f32file.h>
#include <libc/stdlib.h>
#include <libc/string.h>
#include <libc/sys/unistd.h> //sleep
#include <errno.h>
#include <assert.h>
#include "ShoutcastDefs.h"
#include "ShoutcastErrors.h"
#include "ShoutcastUIDs.hrh"
#include "ShoutcastStream.h"
#include "ShoutcastEventDispatcher.h"
// CONSTANTS
// Formating strings
_LIT(KSCBufFormat, "Buf:%3d");
_LIT(KSCTechDataFormat, "%dkb/s %dkHz ");
_LIT(KSCDataRcvdFormat, "%3.1fMB");
_LIT(KSCMetaPriceFormat, "price: %3.1f");
_LIT(KSCMetaBitrateFormat, "Link:%d/%d");
// Audio formats
_LIT(KSCMono, "Mono");
_LIT(KSCStereo, "Stereo");
_LIT(KSCMp3, " MP3");
_LIT(KSCAacp, " AAC+");
// HTTP Request
_LIT8(KSCGet, "GET ");
_LIT8(KSCHttp10, " HTTP/1.0\r\n");
_LIT8(KSCHost, "Host: ");
_LIT8(KSCUserAgent, "User-Agent:S60InternetRadio/2.0\r\n");
_LIT8(KSCAccept, "Accept:*/*\r\n");
_LIT8(KSCIcyMetadata, "icy-metadata:1\r\n");
_LIT8(KSCConnectionClose, "Connection:close\r\n\r\n");
// HTTP Response
_LIT8(KSCOKHeader, "200 OK");
_LIT8(KSCContentTypeTag, "content-type");
_LIT(KSCMimeTypeAudioMpeg, "audio/mpeg");
_LIT(KSCMimeTypeAudioAacp, "audio/aacp");
_LIT(KSCMimeTypeAudioAac, "audio/aac");
_LIT8(KSCIcyMetaintTag, "icy-metaint");
_LIT8(KSCIcyNameTag, "icy-name");
_LIT8(KSCIcyGenreTag, "icy-genre");
_LIT8(KSCIcyBrTag, "icy-br");
// Internal metadata names
_LIT(KSCMetaServer, "Server");
_LIT(KSCMetaTechDetails, "TechDetails");
_LIT(KSCMetaArtistTitle, "ArtistTitle");
_LIT(KSCMetaGenre, "Genre");
_LIT(KSCMetaBytes, "Bytes");
_LIT(KSCMetaPrice, "Price");
_LIT(KSCMetaBuffer, "Buffer");
_LIT(KSCMetaBitrate, "Bitrate");
_LIT8(KSCStreamTitle, "StreamTitle='");
const TInt K100kBFactor = 104857;
const TReal K1MBFactor = 1048576.0;
// Bit rates in bits/sec supported by MPEG2, MPEG1 and MPEG2.5 respectively
const TInt16 KMp3BitRateTable[3][16] =
{
{-1,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0},
{-1,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0},
{-1,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0},
};
// Sampling frequencies supported by MPEG2, MPEG1 and MPEG2.5 respectively
const TUint16 KMp3SamplingFrequencyTable[3][4] =
{
{22050,24000,16000,0},
{44100,48000,32000,0},
{11025,12000,8000,0},
};
const TUint KAacSamplingFrequencyTable[16] =
{
96000,88200,64000,48000,44100,32000,24000,22050,16000,12000,11025,8000,0,0,0,0
};
// Maximum MP3 frame size
const TInt KMp3MaxFrameSize = 1440; // 320kbit/s @ 32kHz
// The size of MP3 header, header must include bits for determining frame length
const TInt KMp3FrameHeaderSize = 5;
const TInt KAacFrameHeaderSize = 7;
// ============================ MEMBER FUNCTIONS ===============================
// -----------------------------------------------------------------------------
// CShoutcastStream::CShoutcastStream
// C++ default constructor can NOT contain any code, that might leave.
// -----------------------------------------------------------------------------
//
CShoutcastStream::CShoutcastStream()
: CActive(EPriorityStandard),
iState(ENotConnected),
iDispatcher(NULL),
iStreamOutput(NULL),
iPtrBuffer2Decode(NULL,0,0),
iPtrBuffer2Play(NULL,0,0),
iInstantBitrate(0),
iMaxFrameSize(0),
iVolume(0),
iMetadata(NR_METADATA_FIELDS)
{
}
// -----------------------------------------------------------------------------
// CShoutcastStream::NewL
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
CShoutcastStream* CShoutcastStream::NewL(
const TDesC8& aUrl,
MShoutcastStreamObserver& aObserver )
{
CShoutcastStream* self = new (ELeave) CShoutcastStream();
CleanupStack::PushL(self);
self->ConstructL(aUrl, aObserver);
CleanupStack::Pop(self);
return self;
}
// -----------------------------------------------------------------------------
// CShoutcastStream::ConstructL
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CShoutcastStream::ConstructL(
const TDesC8& aUrl,
MShoutcastStreamObserver& aObserver )
{
LOG_START;
LOG("CShoutcastStream::ConstructL");
iURL.Copy(aUrl);
iTempMetadata.Copy(iURL);
LOG1("CShoutcastStream::OpenL: %S",&iTempMetadata);
//here gotUrl should be true and iURL should be filled!
GetIPFromURL();
iDispatcher = CEventDispatcher::NewL(aObserver);
// Defaults to MP3 encoded stream
iDataType.Set(KMMFFourCCCodeMP3);
CActiveScheduler::Add(this);
LOG("CShoutcastStream::ConstructL OK");
}
// Destructor
CShoutcastStream::~CShoutcastStream()
{
LOG("CShoutcastStream::~CShoutcastStream");
Cancel();
CloseAndClean();
delete iAddr;iAddr=NULL;
if ( iStreamOutput )
{
delete iStreamOutput;
iStreamOutput = NULL;
}
delete iDispatcher;
LOG("CShoutcastStream Destructor OK");
LOG_END;
}
// -----------------------------------------------------------------------------
// CShoutcastStream::CloseAndClean
// Close connections and clean up resources
// -----------------------------------------------------------------------------
//
void CShoutcastStream::CloseAndClean()
{
if ( iState == ENotConnected )
return;
iState = EDisconnecting;
//buffering stuff
iPtrBuffer2Decode.Set(NULL,0,0);
iPtrBuffer2Play.Set(NULL,0,0);
delete iBuffer2Decode;
iBuffer2Decode = NULL;
iLenBuffer2Decode = iPosBuffer2Decode=0;
iLenBuffer2Play = 0;
delete iBuffer2Play;
iBuffer2Play = NULL;
//close connection
iSock.Close();
iSocksvr.Close();
//metadata
iMetadata.ResetAndDestroy();
delete iMetadataBuffer;
iMetadataBuffer = NULL;
iState = ENotConnected;
}
// -----------------------------------------------------------------------------
// CShoutcastStream::MaoscBufferCopied
// The descriptor buffer has been copied, fill it again with encoded data.
// -----------------------------------------------------------------------------
//
void CShoutcastStream::MaoscBufferCopied(
TInt aError,
const TDesC8& /*aBuffer*/)
{
//LOG1("MaoscBufferCopied: iState = %d ",iState);
ASSERT(iState == EData);
iWritingToStream = EFalse;
// if no error, write more data to the output stream
if ( !aError )
{
TRAPD(err, FillBufferL());
if ( err )
{
iDispatcher->SendEvent(TUid::Uid(KShoutcastStreamUid),err);
}
}
}
// -----------------------------------------------------------------------------
// CShoutcastStream::FillBuffer
// Fill the play buffer with encoded data and send it to the stream output.
// -----------------------------------------------------------------------------
//
void CShoutcastStream::FillBufferL()
{
//LOG1("FillBufferL: iState = %d", iState);
ASSERT(iState == EData);
if ( iWritingToStream )
{
// Still busy writing to stream. We have not received MaoscBufferCopied.
// Bailing out...
return;
}
else
{
iWritingToStream = ETrue;
}
TInt ldTotal;
iLenBuffer2Play=iPtrBuffer2Play.MaxLength();
if ( iLenBuffer2Decode+iReadData < iLenBuffer2Play )
{
//LOG("FillBufferL: Not enough data to play. Rebuffering");
//not enough data to play
iDispatcher->SendEvent(TUid::Uid(KShoutcastStreamUid),KShoutcastEvent_BufferEmpty);
iLenBuffer2Play=0;
iPausedForBuffering = ETrue;
iWritingToStream = EFalse;
}
else
{
//there is enough data to be played!
if ( iLenBuffer2Decode >= iLenBuffer2Play )
{
//copy all the data into the buffer
iPtrBuffer2Play.Copy(iBuffer2Decode+iPosBuffer2Decode,iLenBuffer2Play);
iPosBuffer2Decode+=iLenBuffer2Play;
iLenBuffer2Decode-=iLenBuffer2Play;
}
else
{
//we have to copy the entire end of the buffer, then also the beginning of the buffer
//copy the end of the buffer
iPtrBuffer2Play.Copy(iBuffer2Decode+iPosBuffer2Decode, iLenBuffer2Decode);
TInt dataLeft = iLenBuffer2Play-iLenBuffer2Decode;
//we need to go from case 2 to case 1 (when there is no buffer to copy)
ASSERT(iReadData > 0);
iPosBuffer2Decode=0;
iLenBuffer2Decode=iReadData;
iReadData=-1;//going from case 2 to case 1
//append the remaining data
ASSERT(dataLeft <= iLenBuffer2Decode);//due to the first if from the HwDevice else
iPtrBuffer2Play.Append(iBuffer2Decode+iPosBuffer2Decode,dataLeft);
iPosBuffer2Decode+=dataLeft;
iLenBuffer2Decode-=dataLeft;
}
}
ASSERT(iLenBuffer2Play >= 0);
ldTotal = iLenBuffer2Play;
if ( iLenBuffer2Play > 0 )
{
//LOG1("FillBufferL: Calling WriteL for %d bytes",iLenBuffer2Play);
iStreamOutput->WriteL(iPtrBuffer2Play);
}
//buffering stuff
if(iPosBuffer2Decode>BUFFER2DECODE_SIZE-iMaxFrameSize-READ_EPSILON && iLenBuffer2Decode<iMaxFrameSize)
{
//we passed the upper safety limit!
//LOG2("FillBufferL: Upper limit passed (pos=%d, len=%d). ",iPosBuffer2Decode,iLenBuffer2Decode);
ASSERT(iLenBuffer2Decode < iMaxFrameSize);
ASSERT(iReadData >= 0);
//copy the upper part somewhere down
memmove(iBuffer2Decode+iMaxFrameSize-iLenBuffer2Decode,iBuffer2Decode+iPosBuffer2Decode,iLenBuffer2Decode);
iPosBuffer2Decode=iMaxFrameSize-iLenBuffer2Decode;
iLenBuffer2Decode+=iReadData;
iReadData=-1;//going from case 2 to case 1
};
//buffering fill
TInt bufferFill;
if(iReadData==-1)
{
bufferFill=(TInt)(100.0*iLenBuffer2Decode/(BUFFER2DECODE_SIZE-iMaxFrameSize));
}
else
{
bufferFill=(TInt)(100.0*(iLenBuffer2Decode+iReadData)/(BUFFER2DECODE_SIZE-iMaxFrameSize));
}
//fill buffering metadata
iTempMetadata.Format(KSCBufFormat, bufferFill);
iMetadata[6]->SetValueL(iTempMetadata);
//update metadata on the client/controller
if ( !iPausedForBuffering )
{
iDispatcher->SendEvent(TUid::Uid(KShoutcastStreamUid),0x0140);
}
//restarting buffering, if needed!
if(!iReadingActive && ldTotal>0)
{
//reading is stopped and we need to restart it!
ReadRequest();
};
//LOG2("FillBufferL OK (pos=%d, len=%d)",iPosBuffer2Decode, iLenBuffer2Decode);
}
// -----------------------------------------------------------------------------
// CShoutcastStream::ResetBufferVars
// Reset buffer variables
// -----------------------------------------------------------------------------
//
void CShoutcastStream::ResetBufferVars()
{
iPosBuffer2Decode=0;
iLenBuffer2Decode=0;
iReadData=-1;
iReadingActive=EFalse;
iPtrBuffer2Decode.Set(iBuffer2Decode,0,BUFFER2DECODE_SIZE);
iPtrBuffer2Play.Set(iBuffer2Play,0,BUFFER2PLAY_SIZE);
iBitsRead.Reset();
iTimeIntervals.Reset();
iLastReading=0;
}
// -----------------------------------------------------------------------------
// CShoutcastStream::InitStreamOutputL
// Create and initialize the output stream This must be done before playing can
// began.
// -----------------------------------------------------------------------------
//
void CShoutcastStream::InitStreamOutputL()
{
LOG("CShoutcastStream::InitStreamOutputL");
ASSERT(iState == EInitStreamOutput);
iStreamOutput = CMdaAudioOutputStream::NewL(*this);
// iStreamOutput->SetPriority(EMdaPriorityMin , EMdaPriorityPreferenceTimeAndQuality);
iStreamOutput->SetPriority(80 , EMdaPriorityPreferenceTimeAndQuality);
iStreamOutput->Open(&iSettings);
LOG("CShoutcastStream::InitStreamOutputL OK");
}
// -----------------------------------------------------------------------------
// CShoutcastStream::MaoscOpenComplete
// Once the stream is opened, we can set audio properties, volume, and priority.
// -----------------------------------------------------------------------------
//
void CShoutcastStream::MaoscOpenComplete(
TInt aError )
{
LOG("MaoscOpenComplete");
TInt err;
if ( aError == KErrNone )
{
// If this leaves with KErrNotSupported, no decoder was found that support the
// specified encoded data stream.
TRAP(err, iStreamOutput->SetDataTypeL(iDataType.FourCC()));
if ( err )
{
if ( err == KErrNotSupported )
{
iDispatcher->SendEvent(TUid::Uid(KShoutcastStreamUid), KErrShoutcast_FormatNotSupported);
}
else
{
iDispatcher->SendEvent(TUid::Uid(KShoutcastStreamUid), err);
}
LOG1("SetDataType: Leaving with %d err",err);
return;
}
LOG2("Setting audio properties: sampleRate=%d channels=%d",iAudioSettings.iSampleRate,iAudioSettings.iChannels);
TRAP(err, iStreamOutput->SetAudioPropertiesL(iAudioSettings.iSampleRate, iAudioSettings.iChannels));
if ( err )
{
iDispatcher->SendEvent(TUid::Uid(KShoutcastStreamUid), err);
LOG1("SetAudioPropertiesL: Leaving with %d err", err);
return;
}
LOG1("SetAudioPropertiesL: Max Volume = %d", iStreamOutput->MaxVolume());
iStreamOutput->SetVolume(iVolume);
//announce the client that we are connected
iDispatcher->SendEvent(TUid::Uid(KShoutcastStreamUid),KShoutcastEvent_Connected);
iState = EData;//this should be the only place where iState gets assigned the EData value!
}
else
{
iDispatcher->SendEvent(TUid::Uid(KShoutcastStreamUid), aError);
}
LOG("MaoscOpenComplete OK");
}
// -----------------------------------------------------------------------------
// CShoutcastStream::MaoscPlayComplete
// Called when the end of the sound data has been reached or the
// playing has stopped for some other reason.
// KErrUnderflow = "end of the sound data has been reached"
// -----------------------------------------------------------------------------
//
void CShoutcastStream::MaoscPlayComplete(
TInt aError )
{
LOG1("MaoscPlayComplete: err=%d",aError);
if ( aError == KErrUnderflow )
{
// This case where we run out of data to play and needs to rebuffer.
iPausedForBuffering = ETrue;
}
if ( aError == KErrDied || aError == KErrAccessDenied )
{
Stop();
iDispatcher->SendEvent(TUid::Uid(KShoutcastStreamUid), aError);
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -