Skip to content

Commit

Permalink
Merge pull request #4 from daschuer/1.12-shoutcast-fixes
Browse files Browse the repository at this point in the history
Fix some issues that rised with current implementation in Shoutcastengine
  • Loading branch information
illuusio committed Sep 6, 2015
2 parents 5309349 + 6d80d07 commit dca5947
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 20 deletions.
52 changes: 34 additions & 18 deletions src/engine/sidechain/enginenetworkstream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@

#include "sampleutil.h"

const int kBufferFrames = 32768; // 743 ms @ 44100 Hz
const int kNetworkLatencyFrames = 8192; // 743 ms @ 44100 Hz
const int kNetworkLatencyFrames = 8192; // 185 ms @ 44100 Hz
// Related chunk sizes:
// Mp3 frames = 1152 samples
// Ogg frames = 64 to 8192 samples.
// In Mixxx 1.11 we transmit every decoder-frames at once,
// Which results in case of ogg in a dynamic latency from 0.14 ms to to 180 ms
// Which results in case of ogg in a dynamic latency from 0.14 ms to to 185 ms
// Now we have switched to a fixed latency of 8192 frames (stereo samples) =
// which is 185 @ 44100 ms and twice the maximum of the max mixxx audio buffer
const int kBufferFrames = kNetworkLatencyFrames * 4; // 743 ms @ 44100 Hz
// normally * 2 is sufficient.
// We allow to buffer two extra chunks for a CPU overload case, when
// the shoutcast thread is not scheduled in time.

EngineNetworkStream::EngineNetworkStream(int numOutputChannels,
int numInputChannels)
Expand All @@ -29,7 +32,8 @@ EngineNetworkStream::EngineNetworkStream(int numOutputChannels,
m_sampleRate(0),
m_streamStartTimeUs(-1),
m_streamFramesWritten(0),
m_streamFramesRead(0) {
m_streamFramesRead(0),
m_writeOverflowCount(0) {
if (numOutputChannels) {
m_pOutputFifo = new FIFO<CSAMPLE>(numOutputChannels * kBufferFrames);
}
Expand Down Expand Up @@ -65,34 +69,44 @@ int EngineNetworkStream::getReadExpected() {
}

void EngineNetworkStream::write(const CSAMPLE* buffer, int frames) {

//qDebug() << "EngineNetworkStream::write()" << frames;
if (!m_pWorker->threadWaiting()) {
// no thread waiting, so we can advance the stream without
// buffering
m_streamFramesWritten += frames;
return;
}
int writeAvailable = m_pOutputFifo->writeAvailable();
int writeRequired = frames * m_numOutputChannels;
if (writeAvailable < writeRequired) {
// Flush outdated frames to free space for writing
int readRequired = writeRequired - writeAvailable;
qDebug() << "EngineNetworkStream::write flushed" << readRequired
<< "samples";
m_pOutputFifo->flushReadData(readRequired);
writeAvailable = m_pOutputFifo->writeAvailable();
qDebug() << "EngineNetworkStream::write() buffer full, loosing samples";
m_writeOverflowCount++;
}
int copyCount = math_min(writeAvailable, writeRequired);
if (copyCount > 0) {
(void)m_pOutputFifo->write(buffer, copyCount);
// we advance the frame only by the samples we have actually copied
// This means in case of buffer full (where we loose some frames)
// we do not get out of sync, and the syncing code tries to catch up the
// stream by writing silence, once the buffer is free.
m_streamFramesWritten += copyCount / m_numOutputChannels;
}
m_streamFramesWritten += frames;
scheduleWorker();
}

void EngineNetworkStream::writeSilence(int frames) {
//qDebug() << "EngineNetworkStream::writeSilence()" << frames;
if (!m_pWorker->threadWaiting()) {
// no thread waiting, so we can advance the stream without
// buffering
m_streamFramesWritten += frames;
return;
}
int writeAvailable = m_pOutputFifo->writeAvailable();
int writeRequired = frames * m_numOutputChannels;
if (writeAvailable < writeRequired) {
// Flush outdated frames to free space for writing
int readRequired = writeRequired - writeAvailable;
qDebug() << "EngineNetworkStream::writeSilence flushed" << readRequired
<< "samples";
m_pOutputFifo->flushReadData(readRequired);
writeAvailable = m_pOutputFifo->writeAvailable();
qDebug() << "EngineNetworkStream::write() buffer full";
}
int clearCount = math_min(writeAvailable, writeRequired);
if (clearCount > 0) {
Expand All @@ -107,8 +121,10 @@ void EngineNetworkStream::writeSilence(int frames) {
SampleUtil::clear(dataPtr2,size2);
}
m_pOutputFifo->releaseWriteRegions(clearCount);

// we advance the frame only by the samples we have actually cleared
m_streamFramesWritten += clearCount / m_numOutputChannels;
}
m_streamFramesWritten += frames;
scheduleWorker();
}

Expand Down
1 change: 1 addition & 0 deletions src/engine/sidechain/enginenetworkstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class EngineNetworkStream {
qint64 m_streamFramesWritten;
qint64 m_streamFramesRead;
QSharedPointer<SideChainWorker> m_pWorker;
int m_writeOverflowCount;
};

#endif /* ENGINENETWORKSTREAM_H_ */
10 changes: 9 additions & 1 deletion src/engine/sidechain/engineshoutcast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ EngineShoutcast::EngineShoutcast(ConfigObject<ConfigValue>* _config)
m_protocol_is_icecast2(false),
m_protocol_is_shoutcast(false),
m_ogg_dynamic_update(false),
m_bThreadQuit(false) {
m_bThreadQuit(false),
m_threadWaiting(false) {

#ifndef __WINDOWS__
// Ignore SIGPIPE signals that we get when the remote streaming server
Expand Down Expand Up @@ -729,9 +730,12 @@ void EngineShoutcast::run() {
unsigned static id = 0;
QThread::currentThread()->setObjectName(QString("EngineShoutcast %1").arg(++id));
qDebug() << "starting thread";
m_pOutputFifo->flushReadData(m_pOutputFifo->readAvailable());
m_threadWaiting = true;
for(;;) {
m_readSema.acquire();
if (m_bThreadQuit) {
m_threadWaiting = false;
if (m_encoder) {
m_encoder->flush();
delete m_encoder;
Expand All @@ -756,3 +760,7 @@ void EngineShoutcast::run() {
}
}
}

bool EngineShoutcast::threadWaiting() {
return m_threadWaiting;
}
3 changes: 3 additions & 0 deletions src/engine/sidechain/engineshoutcast.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class EngineShoutcast : public QThread, public EncoderCallback, public SideChain

virtual void outputAvailabe(FIFO<CSAMPLE>* pOutputFifo);

virtual bool threadWaiting();

virtual void run();

public slots:
Expand Down Expand Up @@ -130,6 +132,7 @@ class EngineShoutcast : public QThread, public EncoderCallback, public SideChain
bool m_ogg_dynamic_update;
QVector<struct shoutcastCacheObject *> m_pShoutcastCache;
bool m_bThreadQuit;
QAtomicInt m_threadWaiting;
QSemaphore m_readSema;
FIFO<CSAMPLE>* m_pOutputFifo;
};
Expand Down
3 changes: 3 additions & 0 deletions src/engine/sidechain/sidechainworker.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ class SideChainWorker {
virtual void outputAvailabe(FIFO<CSAMPLE>* pOutputFifo) {
Q_UNUSED(pOutputFifo);
};
virtual bool threadWaiting() {
return false;
}
};

#endif /* SIDECHAINWORKER_H */
2 changes: 1 addition & 1 deletion src/sounddevicenetwork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ void SoundDeviceNetwork::writeProcess() {
// We are not able to store all new frames
if (m_outputDrift) {
//qDebug() << "SoundDeviceNetwork::writeProcess() skip one frame"
// << (float)writeAvailable / outChunkSize << (float)readAvailable / outChunkSize;
// << (float)writeAvailable / outChunkSize << (float)readAvailable / outChunkSize;
++copyCount;
} else {
m_outputDrift = true;
Expand Down

0 comments on commit dca5947

Please sign in to comment.