mirror of
https://github.com/WinampDesktop/winamp.git
synced 2024-09-24 15:54:12 +00:00
675 lines
20 KiB
C++
675 lines
20 KiB
C++
|
#if 0
|
||
|
#ifdef _WIN32
|
||
|
#include <winsock2.h>
|
||
|
#endif
|
||
|
#include <stdio.h>
|
||
|
#include "protocol_m4aClient.h"
|
||
|
#include "ripList.h"
|
||
|
#include "stats.h"
|
||
|
#include "streamData.h"
|
||
|
#include "w3cLog.h"
|
||
|
#include "global.h"
|
||
|
#include "bandwidth.h"
|
||
|
#include "MP3Header.h"
|
||
|
#include "ADTSHeader.h"
|
||
|
#include "file/fileUtils.h"
|
||
|
#include "services/stdServiceImpl.h"
|
||
|
|
||
|
using namespace std;
|
||
|
using namespace uniString;
|
||
|
using namespace stringUtil;
|
||
|
|
||
|
#define DEBUG_LOG(x) { if (gOptions.m4aClientDebug()) DLOG((x)); }
|
||
|
#define AD_DEBUG_LOG(x) { if (gOptions.adMetricsDebug()) DLOG((x)); }
|
||
|
|
||
|
protocol_m4aClient::protocol_m4aClient(const socketOps::tSOCKET s, const streamData::streamID_t streamID,
|
||
|
const utf8 &hostName, const utf8 &addr, const u_short port,
|
||
|
const utf8 &userAgent, const utf8 &XFF, const utf8 &referer,
|
||
|
const bool headRequest) throw(exception)
|
||
|
: protocol_shoutcastClient(s, port, streamData::M4A, hostName,
|
||
|
(streamID <= 0 || streamID > INT_MAX ? DEFAULT_CLIENT_STREAM_ID : streamID),
|
||
|
stats::getNewClientId(), userAgent, referer, addr, XFF, headRequest),
|
||
|
|
||
|
m_state(&protocol_m4aClient::state_AttachToStream), m_nextState(0)
|
||
|
{
|
||
|
DEBUG_LOG(m_clientLogString + __FUNCTION__);
|
||
|
}
|
||
|
|
||
|
protocol_m4aClient::~protocol_m4aClient() throw()
|
||
|
{
|
||
|
cleanup("M4A", gOptions.m4aClientDebug());
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void protocol_m4aClient::timeSlice() throw(exception)
|
||
|
{
|
||
|
int ret = doTimeSlice(result);
|
||
|
if (ret == 1)
|
||
|
{
|
||
|
m_state = &protocol_m4aClient::state_Stream;
|
||
|
return;
|
||
|
}
|
||
|
else if (ret == 2)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
(this->*m_state)();
|
||
|
}
|
||
|
|
||
|
void protocol_m4aClient::state_Close() throw(exception)
|
||
|
{
|
||
|
DEBUG_LOG(m_clientLogString + __FUNCTION__);
|
||
|
|
||
|
m_result.done();
|
||
|
}
|
||
|
|
||
|
void protocol_m4aClient::state_SendText() throw(exception)
|
||
|
{
|
||
|
#if defined(_DEBUG) || defined(DEBUG)
|
||
|
DEBUG_LOG(m_clientLogString + __FUNCTION__);
|
||
|
#endif
|
||
|
|
||
|
if (sendText())
|
||
|
{
|
||
|
m_metaIntervalCounter = 0;
|
||
|
m_state = m_nextState;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// find the appropriate stream and try to attach to it
|
||
|
void protocol_m4aClient::state_AttachToStream() throw(exception)
|
||
|
{
|
||
|
DEBUG_LOG(m_clientLogString + __FUNCTION__);
|
||
|
|
||
|
m_streamData = streamData::accessStream(m_streamID);
|
||
|
if (!m_streamData)
|
||
|
{
|
||
|
if (processReject("M4A", bandWidth::CLIENT_M4A_SENT, MSG_ICY_HTTP401,
|
||
|
MSG_ICY_HTTP401_LEN, &read_bitrate, &dataType))
|
||
|
{
|
||
|
goto fall_through;
|
||
|
}
|
||
|
|
||
|
m_state = &protocol_m4aClient::state_SendText;
|
||
|
m_nextState = &protocol_m4aClient::state_Close;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fall_through:
|
||
|
const utf8 movedUrl = gOptions.stream_movedUrl(m_streamID);
|
||
|
if (movedUrl.empty())
|
||
|
{
|
||
|
const int add = processAdd("FLV", bandWidth::CLIENT_M4A_SENT,
|
||
|
MSG_ICY_HTTP401, MSG_ICY_HTTP401_LEN, movedUrl,
|
||
|
(m_streamData ? m_streamData->streamBackupServer() : ""));
|
||
|
if (add != 1)
|
||
|
{
|
||
|
m_state = &protocol_m4aClient::state_SendText;
|
||
|
m_nextState = &protocol_m4aClient::state_Close;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const bool isPodcast = (!m_streamData && (gOptions.getBackupLoop(m_streamID) == 1));
|
||
|
m_OKResponse = MSG_ICY_HTTP200 + "Content-Type:audio/mp4\r\n";
|
||
|
|
||
|
if (!m_streamData)
|
||
|
{
|
||
|
utf8 path = getStreamPath(m_streamID);
|
||
|
if (!path.empty() && path.find(utf8("/")) == 0)
|
||
|
{
|
||
|
path = path.substr(1);
|
||
|
}
|
||
|
|
||
|
if (isPodcast)
|
||
|
{
|
||
|
m_OKResponse += "Content-Disposition:attachment;filename=\"" + path + "\"\r\n"
|
||
|
"Content-Length:" + tos(m_backupFile.size()) + "\r\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (gOptions.clacks())
|
||
|
{
|
||
|
m_OKResponse += "X-Clacks-Overhead:GNU Terry Pratchett\r\n";
|
||
|
}
|
||
|
m_OKResponse += "\r\n";
|
||
|
|
||
|
DEBUG_LOG(m_clientLogString + "Sending [" + eol() + stripWhitespace(m_OKResponse) + eol() + "]");
|
||
|
m_outBuffer = m_OKResponse.c_str();
|
||
|
bandWidth::updateAmount(bandWidth::CLIENT_M4A_SENT, (m_outBufferSize = (int)m_OKResponse.size()));
|
||
|
m_state = &protocol_m4aClient::state_SendText;
|
||
|
if (!m_headRequest)
|
||
|
{
|
||
|
m_nextState = &protocol_m4aClient::state_InitiateStream;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_removeClientFromStats = false;
|
||
|
m_ignoreDisconnect = true;
|
||
|
m_nextState = &protocol_m4aClient::state_Close;
|
||
|
}
|
||
|
m_result.write();
|
||
|
m_result.timeoutSID(m_streamID);
|
||
|
m_result.run();
|
||
|
|
||
|
// when the client is added, we get back the unique id of the connection
|
||
|
// but we now check for being > 0 as we need to filter out some of the
|
||
|
// YP connections from being counted as valid clients for stats, etc
|
||
|
reportNewListener("M4A");
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// if we get to here then we attempt to redirect the clients to the moved url
|
||
|
// which is useful if the stream has moved hosting or it has been deprecated.
|
||
|
streamMovedOrRejected("M4A", bandWidth::CLIENT_M4A_SENT, movedUrl, 2);
|
||
|
m_state = &protocol_m4aClient::state_SendText;
|
||
|
m_nextState = &protocol_m4aClient::state_Close;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void protocol_m4aClient::state_SendIntro() throw(exception)
|
||
|
{
|
||
|
state_SendIntroFile();
|
||
|
if (m_introFile.empty())
|
||
|
{
|
||
|
acquireIntroFile();
|
||
|
if (m_introFile.empty())
|
||
|
{
|
||
|
m_state = &protocol_m4aClient::state_Stream;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_state = &protocol_m4aClient::state_SendIntroFile;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void protocol_m4aClient::state_InitiateStream() throw(exception)
|
||
|
{
|
||
|
DEBUG_LOG(m_clientLogString + __FUNCTION__);
|
||
|
|
||
|
resetReadPtr();
|
||
|
|
||
|
if (!m_streamData || (m_streamData && m_introFile.empty()))
|
||
|
{
|
||
|
// send intro file if we have it
|
||
|
acquireIntroFile();
|
||
|
m_state = (m_introFile.empty() ? &protocol_m4aClient::state_Stream : &protocol_m4aClient::state_SendIntroFile);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_state = &protocol_m4aClient::state_SendIntro;
|
||
|
}
|
||
|
|
||
|
setW3CState();
|
||
|
|
||
|
m_result.run();
|
||
|
}
|
||
|
|
||
|
// handle state where we are sending intro files
|
||
|
void protocol_m4aClient::state_SendIntroFile() throw(exception)
|
||
|
{
|
||
|
DEBUG_LOG(m_clientLogString + __FUNCTION__);
|
||
|
|
||
|
resetCommon();
|
||
|
|
||
|
time_t cur_time;
|
||
|
const bool debug = gOptions.m4aClientDebug();
|
||
|
const int autoDumpTime = detectAutoDumpTimeout(cur_time, m_lastActivityTime, (m_clientLogString +
|
||
|
"Timeout waiting to send data"),
|
||
|
(!m_ignoreDisconnect && debug), m_streamID);
|
||
|
|
||
|
if (m_shortSend.empty() && (m_frameCount > calculateFrameLimit(cur_time)))
|
||
|
{
|
||
|
if (calculateDelay((autoDumpTime - (int)(cur_time - m_lastActivityTime))))
|
||
|
{
|
||
|
// if we're at the limit then we're going to need to sit and spin
|
||
|
// which will need to be a bit short of the required frame rate
|
||
|
// so that we've got a bit of leeway on scheduling delays, etc
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int amt = (int)(m_introFile.size() - m_introFileOffset);
|
||
|
if (amt == 0)
|
||
|
{
|
||
|
// we're done with the intro file
|
||
|
m_introFile.clear();
|
||
|
m_introFile.resize(0);
|
||
|
m_introFileOffset = 0;
|
||
|
m_lastActivityTime = ::time(NULL);
|
||
|
m_state = &protocol_m4aClient::state_Stream;
|
||
|
resetReadPtr();
|
||
|
m_result.run();
|
||
|
}
|
||
|
else if (amt > 0)
|
||
|
{
|
||
|
const bool debug = gOptions.m4aClientDebug();
|
||
|
checkListenerIsValid(debug);
|
||
|
|
||
|
if (!m_lastSentMetadata.empty())
|
||
|
{
|
||
|
logW3C();
|
||
|
m_lastSentMetadata.clear();
|
||
|
}
|
||
|
|
||
|
amt = min(amt, SEND_SIZE);
|
||
|
|
||
|
int len = (int)amt,
|
||
|
frames = 0; // this will be uvox frames as we pre-process earlier to ensure
|
||
|
// that the audio frames are frame-synced when this is created
|
||
|
bool advert = false;
|
||
|
doM4AFrameSync(m_streamData->streamUvoxDataType(), debug, m_introFileOffset,
|
||
|
(const char*)&m_introFile[0], cur_time, m_streamData->streamBitrate(),
|
||
|
m_streamData->streamSampleRate(), len, frames, advert);
|
||
|
|
||
|
int rval = doSend(debug, cur_time, autoDumpTime);
|
||
|
if (rval > 0)
|
||
|
{
|
||
|
bandWidth::updateAmount(bandWidth::CLIENT_M4A_SENT, rval);
|
||
|
m_bytesSentForCurrentTitle += rval;
|
||
|
m_totalBytesSent += rval;
|
||
|
m_lastActivityTime = ::time(NULL);
|
||
|
m_metaIntervalCounter += rval;
|
||
|
// we adjust by the pre-M4A size
|
||
|
m_introFileOffset += len;
|
||
|
}
|
||
|
|
||
|
updateFrameCount(frames, cur_time);
|
||
|
|
||
|
handleShortSend(autoDumpTime, cur_time, rval);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// handle state where we are sending backup files
|
||
|
void protocol_m4aClient::state_SendBackupFile() throw(exception)
|
||
|
{
|
||
|
DEBUG_LOG(m_clientLogString + __FUNCTION__);
|
||
|
|
||
|
resetCommon();
|
||
|
|
||
|
int amt = (int)(m_backupFile.size() - m_backupFileOffset);
|
||
|
|
||
|
if (streamData::isSourceConnected(m_streamID))
|
||
|
{
|
||
|
if (m_streamData)
|
||
|
{
|
||
|
m_streamData->releaseStream();
|
||
|
}
|
||
|
m_streamData = streamData::accessStream(m_streamID);
|
||
|
|
||
|
// we're done with the backup file
|
||
|
m_backupFile.clear();
|
||
|
m_backupFile.resize(0);
|
||
|
m_backupFileOffset = 0;
|
||
|
m_backupLoopTries = 0;
|
||
|
m_lastActivityTime = ::time(NULL);
|
||
|
m_state = &protocol_m4aClient::state_Stream;
|
||
|
resetReadPtr();
|
||
|
m_result.run();
|
||
|
}
|
||
|
else if (amt == 0)
|
||
|
{
|
||
|
const int backuploop = gOptions.getBackupLoop(m_streamID);
|
||
|
if (!backuploop || (m_backupLoopTries <= backuploop))
|
||
|
{
|
||
|
// we're done with the backup file. get more data
|
||
|
acquireBackupFile();
|
||
|
if (!m_backupFile.empty())
|
||
|
{
|
||
|
++m_backupLoopTries;
|
||
|
resetReadPtr();
|
||
|
m_lastActivityTime = ::time(NULL);
|
||
|
m_state = &protocol_m4aClient::state_Stream;
|
||
|
}
|
||
|
}
|
||
|
else if (backuploop && (m_backupLoopTries < backuploop))
|
||
|
{
|
||
|
m_backupFileOffset = 0;
|
||
|
m_backupFile.clear();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
resetReadPtr();
|
||
|
m_backupFileOffset = 0;
|
||
|
m_lastActivityTime = ::time(NULL);
|
||
|
m_state = &protocol_m4aClient::state_Stream;
|
||
|
}
|
||
|
|
||
|
m_result.run();
|
||
|
}
|
||
|
else if (amt > 0)
|
||
|
{
|
||
|
const bool debug = gOptions.m4aClientDebug();
|
||
|
checkListenerIsValid(debug);
|
||
|
|
||
|
time_t cur_time;
|
||
|
const int autoDumpTime = detectAutoDumpTimeout(cur_time, m_lastActivityTime, (m_clientLogString +
|
||
|
"Timeout waiting to send data"),
|
||
|
(!m_ignoreDisconnect && debug), m_streamID);
|
||
|
|
||
|
if (m_shortSend.empty() && (m_frameCount > calculateFrameLimit(cur_time)))
|
||
|
{
|
||
|
if (calculateDelay((autoDumpTime - (int)(cur_time - m_lastActivityTime))))
|
||
|
{
|
||
|
// if we're at the limit then we're going to need to sit and spin
|
||
|
// which will need to be a bit short of the required frame rate
|
||
|
// so that we've got a bit of leeway on scheduling delays, etc
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!m_lastSentMetadata.empty())
|
||
|
{
|
||
|
logW3C();
|
||
|
m_lastSentMetadata.clear();
|
||
|
}
|
||
|
|
||
|
amt = min(amt, SEND_SIZE);
|
||
|
|
||
|
int len = (int)amt, backup_type = MP3_DATA,
|
||
|
frames = 0; // this will be uvox frames as we pre-process earlier to ensure
|
||
|
// that the audio frames are frame-synced when this is created
|
||
|
if (!m_streamData)
|
||
|
{
|
||
|
utf8 backupFile = gOptions.stream_backupFile(m_streamID);
|
||
|
if (!gOptions.read_stream_backupFile(m_streamID))
|
||
|
{
|
||
|
backupFile = gOptions.backupFile();
|
||
|
}
|
||
|
if (!backupFile.empty())
|
||
|
{
|
||
|
backup_type = ((backupFile.rfind((utf8)".aac") == utf8::npos) ? MP3_DATA : AACP_DATA);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool advert = false;
|
||
|
doM4AFrameSync((m_streamData ? m_streamData->streamUvoxDataType() : backup_type),
|
||
|
debug, m_backupFileOffset, (const char*)&m_backupFile[0], cur_time,
|
||
|
(m_streamData ? m_streamData->streamBitrate() : 0),
|
||
|
(m_streamData ? m_streamData->streamSampleRate() : 0), len, frames, advert);
|
||
|
|
||
|
int rval = doSend(debug, cur_time, autoDumpTime);
|
||
|
if (rval > 0)
|
||
|
{
|
||
|
bandWidth::updateAmount(bandWidth::CLIENT_M4A_SENT, rval);
|
||
|
m_bytesSentForCurrentTitle += rval;
|
||
|
m_totalBytesSent += rval;
|
||
|
m_lastActivityTime = ::time(NULL);
|
||
|
m_metaIntervalCounter += rval;
|
||
|
// we adjust by the pre-M4A size
|
||
|
m_backupFileOffset += len;
|
||
|
}
|
||
|
|
||
|
updateFrameCount(frames, cur_time);
|
||
|
|
||
|
handleShortSend(autoDumpTime, cur_time, rval);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// handle state where we are sending advert content
|
||
|
void protocol_m4aClient::state_SendAdverts() throw(exception)
|
||
|
{
|
||
|
#if defined(_DEBUG) || defined(DEBUG)
|
||
|
DEBUG_LOG(m_clientLogString + __FUNCTION__);
|
||
|
#endif
|
||
|
|
||
|
resetCommon();
|
||
|
|
||
|
streamData::specialFileData *ad = m_adAccess.getAd(m_streamID, m_streamData->advertGroups, !!m_streamData->m_adTest);
|
||
|
int amt = (ad ? (int)(ad->m_sc1Buffer.size() - m_adAccess.offset) : 0);
|
||
|
|
||
|
while (true)
|
||
|
{
|
||
|
// DLOG ("amount remaining to be sent " + tos(amt));
|
||
|
if (amt > 0)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
if (m_adAccess.anotherAd(m_streamID, m_streamData->advertGroups, !!m_streamData->m_adTest))
|
||
|
{
|
||
|
ad = m_adAccess.getAd(m_streamID, m_streamData->advertGroups, !!m_streamData->m_adTest);
|
||
|
amt = (ad ? (int)(ad->m_sc1Buffer.size() - m_adAccess.offset) : 0);
|
||
|
continue;
|
||
|
}
|
||
|
// no more adverts
|
||
|
m_lastActivityTime = ::time(NULL);
|
||
|
m_state = &protocol_m4aClient::state_Stream;
|
||
|
m_adAccess.stopAdRun(m_streamID, m_streamData->advertGroups, !!m_streamData->m_adTest);
|
||
|
// go to the latest point in the ring buffer.
|
||
|
resetReadPtr();
|
||
|
m_result.run();
|
||
|
m_result.timeoutSID(m_streamID);
|
||
|
|
||
|
ILOG(m_clientLogString + "Transitioning back to stream [Agent: `" +
|
||
|
m_userAgent + "', UID: " + tos(m_unique) + ", GRID: " + tos(m_group) + "]");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
const bool debug = gOptions.m4aClientDebug();
|
||
|
checkListenerIsValid(debug);
|
||
|
|
||
|
time_t cur_time;
|
||
|
const int autoDumpTime = detectAutoDumpTimeout(cur_time, m_lastActivityTime, (m_clientLogString +
|
||
|
"Timeout waiting to send data"),
|
||
|
(!m_ignoreDisconnect && debug), m_streamID);
|
||
|
|
||
|
if (m_shortSend.empty() && (m_frameCount > calculateFrameLimit(cur_time)))
|
||
|
{
|
||
|
if (calculateDelay((autoDumpTime - (int)(cur_time - m_lastActivityTime))))
|
||
|
{
|
||
|
// if we're at the limit then we're going to need to sit and spin
|
||
|
// which will need to be a bit short of the required frame rate
|
||
|
// so that we've got a bit of leeway on scheduling delays, etc
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!m_lastSentMetadata.empty())
|
||
|
{
|
||
|
logW3C();
|
||
|
m_lastSentMetadata.clear();
|
||
|
}
|
||
|
|
||
|
amt = min(amt, SEND_SIZE);
|
||
|
|
||
|
int len = (int)amt,
|
||
|
frames = 0; // this will be uvox frames as we pre-process earlier to ensure
|
||
|
// that the audio frames are frame-synced when this is created
|
||
|
bool advert = false;
|
||
|
doM4AFrameSync(m_streamData->streamUvoxDataType(), (int)m_adAccess.offset,
|
||
|
debug, (const char*)&ad->m_sc1Buffer[0], cur_time,
|
||
|
m_streamData->streamBitrate(), m_streamData->streamSampleRate(),
|
||
|
len, frames, advert);
|
||
|
|
||
|
int rval = doSend(debug, cur_time, autoDumpTime);
|
||
|
if (rval > 0)
|
||
|
{
|
||
|
bandWidth::updateAmount(bandWidth::CLIENT_M4A_SENT, rval);
|
||
|
m_bytesSentForCurrentTitle += rval;
|
||
|
m_totalBytesSent += rval;
|
||
|
m_lastActivityTime = ::time(NULL);
|
||
|
m_metaIntervalCounter += rval;
|
||
|
// we adjust by the pre-M4A size
|
||
|
m_adAccess.offset += len;
|
||
|
|
||
|
m_nextState = &protocol_m4aClient::state_SendText;
|
||
|
}
|
||
|
|
||
|
updateFrameCount(frames, cur_time);
|
||
|
|
||
|
handleShortSend(autoDumpTime, cur_time, rval);
|
||
|
}
|
||
|
catch (std::runtime_error)
|
||
|
{
|
||
|
m_adAccess.stopAdRun(m_streamID, m_streamData->advertGroups, !!m_streamData->m_adTest);
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void protocol_m4aClient::state_Stream() throw(exception)
|
||
|
{
|
||
|
#if defined(_DEBUG) || defined(DEBUG)
|
||
|
DEBUG_LOG(m_clientLogString + __FUNCTION__);
|
||
|
#endif
|
||
|
|
||
|
resetCommon();
|
||
|
|
||
|
const time_t cur_time = ::time(NULL);
|
||
|
const bool debug = gOptions.m4aClientDebug();
|
||
|
const int autoDumpTime = gOptions.getAutoDumpTime(m_streamID); // don't want this value to change during this call
|
||
|
if ((autoDumpTime > 0) && ((cur_time - m_lastActivityTime) >= autoDumpTime))
|
||
|
{
|
||
|
throwEx<runtime_error>((!m_ignoreDisconnect && debug ?
|
||
|
(m_clientLogString + "Timeout waiting to send data (" +
|
||
|
tos(cur_time) + " " + tos(m_lastActivityTime) + " [" +
|
||
|
tos(cur_time - m_lastActivityTime) + "] )") : (utf8)""));
|
||
|
}
|
||
|
|
||
|
if (m_shortSend.empty() && (m_frameCount > calculateFrameLimit(cur_time)))
|
||
|
{
|
||
|
if (calculateDelay((autoDumpTime - (int)(cur_time - m_lastActivityTime))))
|
||
|
{
|
||
|
// if we're at the limit then we're going to need to sit and spin
|
||
|
// which will need to be a bit short of the required frame rate
|
||
|
// so that we've got a bit of leeway on scheduling delays, etc
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const streamData::ringBuffer_t rb = (m_streamData ? m_streamData->getSc1RingBuffer() : streamData::ringBuffer_t());
|
||
|
streamData::ringBufferAccess_t amt = (m_streamData ? (rb.m_writePtr - m_readPtr) : 0);
|
||
|
if ((amt > 0) && (amt > rb.m_data.size()))
|
||
|
{
|
||
|
// the pointers are too far apart. Underrun
|
||
|
resetReadPtr(&amt);
|
||
|
}
|
||
|
|
||
|
std::vector<__uint8>& rem = getRemainder();
|
||
|
const streamData::ringBufferAccess_t offset = (m_readPtr & rb.m_ptrMask);
|
||
|
// clamp again so we don't read pass the end of the buffer
|
||
|
//
|
||
|
// if we've got more in remainder than what we're wanting
|
||
|
// to send then we'll prioritise the remainder data first
|
||
|
// before trying to acquire more new data to try to send.
|
||
|
amt = min(amt, min((rb.m_data.size() - offset), (streamData::ringBufferAccess_t)max(0, (SEND_SIZE - rem.size()))));
|
||
|
|
||
|
bool advert = false;
|
||
|
int remainder = 0, len = (int)amt,
|
||
|
frames = 0; // this will be uvox frames as we pre-process earlier to ensure
|
||
|
// that the audio frames are frame-synced when this is created
|
||
|
if ((len > 0) || !rem.empty() || !m_shortSend.empty())
|
||
|
{
|
||
|
const std::vector<__uint8>::const_iterator pos = rb.m_data.begin();
|
||
|
std::vector<__uint8>& tempBuf;
|
||
|
// if we had anything left over then now we
|
||
|
// need to copy it back into the buffer and
|
||
|
// adjust the max data amount to be read in
|
||
|
if (!rem.empty() && ((len + rem.size()) <= (BUF_SIZE * 4)))
|
||
|
{
|
||
|
tempBuf.insert(tempBuf.end(), rem.begin(), rem.end());
|
||
|
tempBuf.insert(tempBuf.end(), pos + offset, pos + (offset + len));
|
||
|
len += m_remainderSize;
|
||
|
}
|
||
|
else if (len > 0)
|
||
|
{
|
||
|
tempBuf.insert(tempBuf.end(), pos + offset, pos + (offset + len));
|
||
|
}
|
||
|
rem.clear();
|
||
|
|
||
|
remainder = doM4AFrameSync((m_streamData ? m_streamData->streamUvoxDataType() : MP3_DATA),
|
||
|
debug, 0, cur_time, (m_streamData ? m_streamData->streamBitrate() : 0),
|
||
|
(m_streamData ? m_streamData->streamSampleRate() : 0),
|
||
|
len, frames, advert, true);
|
||
|
}
|
||
|
|
||
|
// if no data then we need to just go to adverts
|
||
|
// otherwise we need to see what we've got and
|
||
|
// if we can, then transition to adverts or spin
|
||
|
// and wait for the current data to be sent and
|
||
|
// then we should be able to re-process into ads
|
||
|
if (m_output.empty())
|
||
|
{
|
||
|
// this will close the stream or go to backups as needed
|
||
|
if (handleNoData("M4A", remainder, amt, autoDumpTime, cur_time))
|
||
|
{
|
||
|
m_state = &protocol_m4aClient::state_SendBackupFile;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (processAdvertTrigger(advert))
|
||
|
{
|
||
|
m_state = &protocol_m4aClient::state_SendAdverts;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// at this point, we're in a bit of a weird state
|
||
|
// as we've not been able to generate / retrieve
|
||
|
// anything and if we don't slwo down things then
|
||
|
// we need to delay things to allow it to catchup
|
||
|
if (m_frameCount > calculateFrameLimit(cur_time))
|
||
|
{
|
||
|
// determine things based on the 'next' second when working out
|
||
|
// the differential as that's how long we want to wait to run
|
||
|
m_result.schedule((int)((__uint64)((cur_time + 1) * 1000) - m_result.m_currentTime));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// just go with an arbitrary value as we cannot
|
||
|
// be certain when we're going to get data next
|
||
|
// and we don't know the impact on playback etc
|
||
|
m_result.schedule(100);
|
||
|
}
|
||
|
|
||
|
m_result.timeout((autoDumpTime - (int)(cur_time - m_lastActivityTime)));
|
||
|
m_result.write();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
processTitleW3C();
|
||
|
|
||
|
int rval = doSend(debug, cur_time, autoDumpTime);
|
||
|
if (rval > 0)
|
||
|
{
|
||
|
bandWidth::updateAmount(bandWidth::CLIENT_M4A_SENT, rval);
|
||
|
m_bytesSentForCurrentTitle += rval;
|
||
|
m_totalBytesSent += rval;
|
||
|
m_lastActivityTime = ::time(NULL);
|
||
|
m_metaIntervalCounter += rval;
|
||
|
|
||
|
if (processAdvertTrigger(advert))
|
||
|
{
|
||
|
m_state = &protocol_m4aClient::state_SendAdverts;
|
||
|
remainder = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// as we're keeping a copy of the remainder / non-sent data
|
||
|
// we need to move the readptr on so that it doesn't cause
|
||
|
// the stream to effectively stick on the same point and in
|
||
|
// turn then lead to resetReadPtr() being called (skipping)
|
||
|
m_readPtr += amt;
|
||
|
updateFrameCount(frames, cur_time);
|
||
|
|
||
|
handleShortSend(autoDumpTime, cur_time, rval);
|
||
|
|
||
|
// we only keep the remainder if
|
||
|
// there's no advert to provide
|
||
|
m_remainderSize = remainder;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void protocol_m4aClient::return_403(void)
|
||
|
{
|
||
|
protocol_shoutcastClient::return_403();
|
||
|
m_state = &protocol_m4aClient::state_SendText;
|
||
|
m_nextState = &protocol_m4aClient::state_Close;
|
||
|
}
|
||
|
#endif
|