--- 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);
+}