--- a
+++ b/mpd2src/songcastsender.cpp
@@ -0,0 +1,270 @@
+/* Copyright (C) 2015 J.F.Dockes
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "songcastsender.h"
+
+extern bool g_quitrequest;
+
+SongcastSender::SongcastSender(Environment& aEnv, OhmSender* aSender,
+ OhmSenderDriver* aDriver, const Brx& aUri,
+ AudioReader* audio, const string& codec,
+ bool paced)
+ : iEnv(aEnv)
+ , iSender(aSender)
+ , iDriver(aDriver)
+ , iUri(aUri)
+ , m_audio(audio)
+ , iTimer(aEnv, MakeFunctor(*this, &SongcastSender::TimerExpired), "SongcastSender")
+ , iMutex("SCSP")
+ , iPaused(true)
+ , iSpeed(kSpeedNormal)
+ , iIndex(0)
+ , iLastTimeUs(0)
+ , iTimeOffsetUs(0)
+ , iVerbose(false)
+ , iPaced(paced)
+
+{
+ CalculatePacketBytes();
+ m_encoder = AudioEncoderFactory::CreateAudioEncoder(codec, audio, aDriver,
+ iPacketBytes);
+ LOGDEB("bytes per packet: " << iPacketBytes << endl);
+ LOGDEB("frames per packet: " << iPacketFrames << endl);
+ LOGDEB("usec per packet: "<< iPacketTime << endl);
+}
+
+// We return true if the main thread should pause.
+bool SongcastSender::Start(TBool aEnabled)
+{
+ iSender->SetEnabled(true);
+
+ iSender->SetTrack(iUri, Brx::Empty(), m_audio->sampleCount(), 0);
+ iSender->SetMetatext(Brn("SongcastSender repeated play"));
+
+ // It seems that both hijacking the main thread and using the
+ // timer with a short timeout (see TimerExpired()) work.
+ // Don't know what's best. The timer approach is closer to the original
+ // code and leaves the main thread free for control ops if needed.
+ // Otoh, if no data appears on the fifo, the timer thread is stuck in read
+ // and the upnp side stops working (no sender advertised).
+ // Maybe the best approach would be to start a separate thread and
+ // use busyreading. Using the main thread for now.
+ static const bool optionbusy = true;
+ if (iPaced || !optionbusy) {
+ LOGDEB("SongcastSender::Start: using timers. Enabled? " << aEnabled << endl);
+ if (aEnabled)
+ Play();
+ return true;
+ } else {
+ LOGDEB("SongcastSender::Start: block on reading only\n");
+ busyRdWr();
+ return false;
+ }
+}
+
+void SongcastSender::Play()
+{
+ iMutex.Wait();
+ if (iPaused) {
+ m_encoder->start();
+ if (m_audio)
+ m_audio->open();
+ iPaused = false;
+ iLastTimeUs = 0;
+ iTimeOffsetUs = 0;
+ iTimer.FireIn(kPeriodMs);
+ }
+ iMutex.Signal();
+}
+
+void SongcastSender::Stop()
+{
+ iMutex.Wait();
+ if (!iPaused) {
+ m_encoder->finish();
+ if (m_audio)
+ m_audio->close();
+ iPaused = true;
+ }
+ iMutex.Signal();
+}
+
+void SongcastSender::PlayPause()
+{
+ if (iPaused)
+ Play();
+ else
+ Stop();
+}
+
+void SongcastSender::Restart()
+{
+ iMutex.Wait();
+ iIndex = 0;
+ iMutex.Signal();
+}
+
+TBool SongcastSender::Paused()
+{
+ return (iPaused);
+}
+
+const string SongcastSender::CodecName()
+{
+ return m_encoder->name();
+}
+
+void SongcastSender::CalculatePacketBytes()
+{
+ // in order to let wavsender change the playback rate,
+ // we keep constant it's idea of how much audio time is in each packet,
+ // but vary the amount of data that is actually sent
+
+ // calculate the amount of time in each packet
+ TUint norm_bytes = (m_audio->sampleRate() * m_audio->bytesPerFrame() *
+ kPeriodMs) / 1000;
+ if (norm_bytes > kMaxPacketBytes) {
+ norm_bytes = kMaxPacketBytes;
+ }
+ TUint norm_packet_samples = norm_bytes / m_audio->bytesPerFrame();
+ iPacketTime = (norm_packet_samples*1000000/(m_audio->sampleRate()/10) + 5)/10;
+
+ // calculate the adjusted speed packet size
+ TUint bytes = (norm_bytes * iSpeed) / 100;
+ if (bytes > kMaxPacketBytes) {
+ bytes = kMaxPacketBytes;
+ }
+ iPacketFrames = bytes / m_audio->bytesPerFrame();
+ iPacketBytes = iPacketFrames * m_audio->bytesPerFrame();
+}
+
+void SongcastSender::busyRdWr()
+{
+ LOGDEB("SongcastSender:busyRdWr: packetbytes " << iPacketBytes << endl);
+ while (true) {
+ if (g_quitrequest) {
+ return;
+ }
+ ssize_t nread = 0;
+ const unsigned char *cp = m_audio->data((size_t)iPacketBytes, nread);
+ if (cp == 0) {
+ return;
+ }
+ if (g_quitrequest) {
+ return;
+ }
+ m_encoder->encode(cp, nread, iPaused || g_quitrequest);
+ }
+}
+
+void SongcastSender::TimerExpired()
+{
+ iMutex.Wait();
+ bool pause = iPaused || g_quitrequest;
+ ssize_t nread = 0;
+ const unsigned char *cp = m_audio->data((size_t)iPacketBytes, nread);
+
+ if (nread > 0) {
+ if ((size_t)nread < iPacketBytes) {
+ LOGDEB("SongcastSender::TimerExpired: requested " << iPacketBytes
+ << " bytes, read " << nread << " bytes" << endl);
+ }
+ m_encoder->encode(cp, nread, pause);
+ } else if (pause) {
+ // The audio stream was paused and no data could be read from the
+ // audio source anymore. To notify the receivers, send an empty audio
+ // message with the halt flag set.
+ LOGDEB("SongcastSender::Send empty audio message with pause flag set\n");
+ m_encoder->encode(cp, 0, true);
+ }
+
+ if (!pause) {
+ TUint64 now = OsTimeInUs(iEnv.OsCtx());
+
+ if (!iPaced) {
+ // Means we're doing blocking reads on the source, and
+ // it's setting the pace. I'd like to actually use 0 here
+ // (ala qt processEvents()), but this appears to busyloop
+ // and not let the sender do its thing. Anyway, as long
+ // as we can read from the fifo in much less than (period-2),
+ // which should always be true, we should be ok.
+ // I can see not much difference between doing this or
+ // hijacking the main thread for busy read/write
+ iTimer.FireIn(2);
+ } else {
+ // skip the first packet, and any time the clock value wraps
+ if (iLastTimeUs && iLastTimeUs < now) {
+
+ // will contain the new time out in ms
+ TUint new_timer_ms = kPeriodMs;
+
+ // the difference in usec from where we should be
+ TInt32 diff = (TInt32)(now - iLastTimeUs) - iPacketTime;
+
+ // increment running offset
+ iTimeOffsetUs -= diff;
+
+ // determine new timer value based upon current offset from ideal
+ if (iTimeOffsetUs < -1000) {
+ // we are late
+ TInt32 time_offset_ms = iTimeOffsetUs/1000;
+ if (time_offset_ms < 1-(TInt32)kPeriodMs) {
+ // in case callback is severely late, we can only
+ // catch up so much
+ new_timer_ms = 1;
+ } else {
+ new_timer_ms = kPeriodMs + time_offset_ms;
+ }
+ } else if (iTimeOffsetUs > 1000) {
+ // we are early
+ new_timer_ms = kPeriodMs+1;
+ } else {
+ // we are about on time
+ new_timer_ms = kPeriodMs;
+ }
+
+ // set timer
+ iTimer.FireIn(new_timer_ms);
+
+ // logging
+ if (iVerbose) {
+ if (iTimeOffsetUs >= 1000)
+ printf ("tnow:%d tlast:%d actual:%4d diff:%4d offset:%5d timer:%d\n", (TUint)now, (TUint)iLastTimeUs, (TUint)(now-iLastTimeUs), diff, iTimeOffsetUs, new_timer_ms);
+ else
+ printf ("tnow:%d tlast:%d actual:%4d diff:%4d offset:%4d timer:%d\n", (TUint)now, (TUint)iLastTimeUs, (TUint)(now-iLastTimeUs), diff, iTimeOffsetUs, new_timer_ms);
+ }
+ } else {
+ iTimer.FireIn(kPeriodMs);
+ }
+ iLastTimeUs = now;
+ }
+ } else {
+ LOGDEB("SongcastSender::TimerExpired: Sender is paused. Stop firing\n");
+ m_encoder->finish();
+ if (m_audio)
+ m_audio->close();
+ }
+
+ iMutex.Signal();
+}
+
+SongcastSender::~SongcastSender()
+{
+ iTimer.Cancel();
+ delete (iSender);
+ delete (iDriver);
+}