--- a
+++ b/mpd2src/mpd2sc.cpp
@@ -0,0 +1,440 @@
+#include <OpenHome/OhNetTypes.h>
+#include <OpenHome/Net/Core/DvDevice.h>
+#include <OpenHome/Net/Core/OhNet.h>
+#include <OpenHome/Net/Core/CpDevice.h>
+#include <OpenHome/Net/Core/CpDeviceUpnp.h>
+#include <OpenHome/Private/Ascii.h>
+#include <OpenHome/Private/Thread.h>
+#include <OpenHome/Private/OptionParser.h>
+#include <OpenHome/Private/Debug.h>
+#include <OpenHome/Os.h>
+#include <OpenHome/Private/Env.h>
+
+#include <vector>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include "OhmSender.h"
+
+#include "log.h"
+#include "icon.h"
+#include "audioreader.h"
+#include "openaudio.h"
+#include "base64.hxx"
+
+using namespace std;
+
+using namespace OpenHome;
+using namespace OpenHome::Net;
+using namespace OpenHome::TestFramework;
+using namespace OpenHome::Av;
+
+class PcmSender {
+
+public:
+    static const TUint kPeriodMs = 10;
+    static const TUint kSpeedNormal = 100;
+    static const TUint kSpeedMin = 75;
+    static const TUint kSpeedMax = 150;
+    static const TUint kMaxPacketBytes = 4096;
+        
+public:
+    PcmSender(Environment& aEnv, OhmSender* aSender,
+              OhmSenderDriver* aDriver, const Brx& aUri,
+              AudioReader* audio, bool paced);
+    bool Start();
+    void Pause();
+    void Restart();
+    ~PcmSender();
+    void busyRdWr();
+        
+private:
+    void CalculatePacketBytes();
+    void TimerExpired();
+        
+private:
+    Environment& iEnv;
+    OhmSender* iSender;
+    OhmSenderDriver* iDriver;
+    Bws<OhmSender::kMaxTrackUriBytes> iUri;
+    AudioReader *m_audio;
+    Timer iTimer;
+    Mutex iMutex;
+    TBool iPaused;
+    TUint iSpeed;           // percent, 100%=normal
+    TUint iIndex;           // byte offset read position in source data
+    TUint iPacketBytes;     // how many bytes of audio in each packet
+    TUint iPacketFrames;    // how many audio frames in each packet
+    TUint iPacketTime;      // how much audio time in each packet, uS
+    TUint64 iLastTimeUs;    // last time stamp from system
+    TInt32 iTimeOffsetUs;   // running offset in usec from ideal time
+    //  <0 means sender is behind
+    //  >0 means sender is ahead
+    TBool iVerbose;
+    TBool iPaced;
+};
+
+PcmSender::PcmSender(Environment& aEnv, OhmSender* aSender,
+                     OhmSenderDriver* aDriver,
+                     const Brx& aUri, AudioReader* audio, bool paced)
+    : iEnv(aEnv)
+    , iSender(aSender)
+    , iDriver(aDriver)
+    , iUri(aUri)
+    , m_audio(audio)
+    , iTimer(aEnv, MakeFunctor(*this, &PcmSender::TimerExpired), "PcmSender")
+    , iMutex("WAVP")
+    , iPaused(false)
+    , iSpeed(kSpeedNormal)
+    , iIndex(0)
+    , iLastTimeUs(0)
+    , iTimeOffsetUs(0)
+    , iVerbose(false)
+    , iPaced(paced)
+      
+{
+    CalculatePacketBytes();
+    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 PcmSender::Start()
+{
+    iDriver->SetAudioFormat(m_audio->sampleRate(), m_audio->byteRate() * 8,
+                            m_audio->numChannels(),
+                            m_audio->bitsPerSample(), true, Brn("WAV"));
+    iSender->SetEnabled(true);
+
+    iSender->SetTrack(iUri, Brx::Empty(), m_audio->sampleCount(), 0);
+    iSender->SetMetatext(Brn("PcmSender 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("PcmSender::Start: using timers\n");
+        iTimer.FireIn(kPeriodMs);
+        return true;
+    } else {
+        LOGDEB("PcmSender::Start: block on reading only\n");
+        busyRdWr();
+        return false;
+    }
+}
+
+void PcmSender::Pause()
+{
+    iMutex.Wait();
+
+    if (iPaused) {
+        iPaused = false;
+        iLastTimeUs = 0;
+        iTimeOffsetUs = 0;
+        iTimer.FireIn(kPeriodMs);
+    } else {
+        iPaused = true;
+    }
+        
+    iMutex.Signal();
+}
+
+void PcmSender::Restart()
+{
+    iMutex.Wait();
+    iIndex = 0;
+    iMutex.Signal();
+}
+
+void PcmSender::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 PcmSender::busyRdWr()
+{
+    LOGDEB("PcmSender:busyRdWr: packetbytes " << iPacketBytes << endl);
+    while (true) {
+        const unsigned char *cp = m_audio->data((size_t)iPacketBytes);
+        if (cp == 0) {
+            return;
+        }
+        iDriver->SendAudio(cp, iPacketBytes);
+    }
+}
+
+void PcmSender::TimerExpired()
+{
+    iMutex.Wait();
+        
+    if (!iPaused) {
+        TUint64 now = OsTimeInUs(iEnv.OsCtx());
+
+        const unsigned char *cp = m_audio->data((size_t)iPacketBytes);
+        if (cp == 0) {
+            static bool sigsent = false;
+            if (!sigsent) {
+                LOGDEB("PcmSender::TimerExpired: killing myself\n");
+                kill(getpid(), SIGUSR1);
+                sigsent = true;
+            }
+        } else {
+            iDriver->SendAudio(cp, iPacketBytes);
+        }
+
+        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;
+        }
+    }
+        
+    iMutex.Signal();
+}
+
+PcmSender::~PcmSender()
+{
+    iTimer.Cancel();
+    delete (iSender);
+    delete (iDriver);
+}
+
+// Sig catcher so that we can interrupt the pause() which will be waiting
+// for playing to end
+void sigcatcher(int)
+{
+    LOGDEB("sigcatcher\n");
+}
+
+int main(int aArgc, char* aArgv[])
+{
+    OptionParser parser;
+    
+    OptionString optionAudioParams("-A", "--audio", Brn(""), "[44100:16:2] audio params only if they can't be obtained from file. Conflicting values will cause error");
+    parser.AddOption(&optionAudioParams);
+
+    OptionUint optionAdapter("-a", "--adapter", 0, "[adapter] index of network adapter to use");
+    parser.AddOption(&optionAdapter);
+
+    OptionUint optionChannel("-c", "--channel", 0, "[0..65535] sender channel");
+    parser.AddOption(&optionChannel);
+
+    OptionBool optionDisabled("-d", "--disabled", "[disabled] start up disabled");
+    parser.AddOption(&optionDisabled);
+
+    OptionString optionFile("-f", "--file", Brn(""), "[file] wav file to send");
+    parser.AddOption(&optionFile);
+    
+    OptionUint optionLatency("-l", "--latency", 100, "[latency] latency in ms");
+    parser.AddOption(&optionLatency);
+
+    OptionBool optionMulticast("-m", "--multicast", "[multicast] use multicast instead of unicast");
+    parser.AddOption(&optionMulticast);
+
+    OptionBool optionNeedPace("-p", "--pace", "Use internal timer to pace source. Implicit for regular files.");
+    parser.AddOption(&optionNeedPace);
+    
+    OptionString optionName("-n", "--name", Brn("Openhome WavSender"), "[name] name of the sender");
+    parser.AddOption(&optionName);
+
+    OptionUint optionTtl("-t", "--ttl", 1, "[ttl] ttl");
+    parser.AddOption(&optionTtl);
+
+    OptionString optionUdn("-u", "--udn", Brn("12345678"), "[udn] udn for the upnp device");
+    parser.AddOption(&optionUdn);
+
+//    OptionBool optionPacketLogging("-z", "--logging", "[logging] toggle packet logging");
+//    parser.AddOption(&optionPacketLogging);
+
+    if (!parser.Parse(aArgc, aArgv)) {
+        return (1);
+    }
+
+    InitialisationParams* initParams = InitialisationParams::Create();
+
+    Library* lib = new Library(initParams);
+
+    std::vector<NetworkAdapter*>* subnetList = lib->CreateSubnetList();
+    LOGDEB("adapter list:\n");
+    for (unsigned i=0; i<subnetList->size(); ++i) {
+        TIpAddress addr = (*subnetList)[i]->Address();
+        LOGDEB("  " << i << ": " << (addr&0xff) << "." <<
+               ((addr>>8)&0xff) << "." << ((addr>>16)&0xff) << "." <<
+               ((addr>>24)&0xff) << endl);
+    }
+    if (subnetList->size() <= optionAdapter.Value()) {
+        LOGERR("ERROR: adapter " << optionAdapter.Value() << "doesn't exist\n");
+        return (1);
+    }
+
+    TIpAddress subnet = (*subnetList)[optionAdapter.Value()]->Subnet();
+    TIpAddress adapter = (*subnetList)[optionAdapter.Value()]->Address();
+    Library::DestroySubnetList(subnetList);
+    lib->SetCurrentSubnet(subnet);
+
+    LOGDEB("using subnet " << (subnet&0xff) << "." << ((subnet>>8)&0xff) << "."
+           << ((subnet>>16)&0xff) << "." <<  ((subnet>>24)&0xff) << endl);
+
+    Brhz file(optionFile.Value());
+    
+    if (file.Bytes() == 0) {
+        LOGERR("No input file specified\n");
+        return (1);
+    }
+    
+    Brhz udn(optionUdn.Value());
+    Brhz name(optionName.Value());
+    Brhz audioparams(optionAudioParams.Value());
+    TUint channel = optionChannel.Value();
+    TUint ttl = optionTtl.Value();
+    TUint latency = optionLatency.Value();
+    TBool multicast = optionMulticast.Value();
+    TBool disabled = optionDisabled.Value();
+    //TBool logging = optionPacketLogging.Value();
+    TBool needpace = optionNeedPace.Value();
+
+    AudioReader *audio = openAudio(file.CString(), audioparams.CString(),
+                                   !needpace);
+    if (!audio || !audio->open()) {
+        cerr << "Audio file open failed" << endl;
+        return 1;
+    }
+    needpace = !audio->isblocking();
+    LOGDEB("sample rate:        " << audio->sampleRate() << endl);
+    LOGDEB("sample size:        " << audio->bytesPerSample() << endl);
+    LOGDEB("channels:           " << audio->numChannels() << endl);
+    
+    DvStack* dvStack = lib->StartDv();
+
+    DvDeviceStandard* device = new DvDeviceStandard(*dvStack, udn);
+    
+    device->SetAttribute("Upnp.Domain", "av.openhome.org");
+    device->SetAttribute("Upnp.Type", "Sender");
+    device->SetAttribute("Upnp.Version", "1");
+    device->SetAttribute("Upnp.FriendlyName", name.CString());
+    device->SetAttribute("Upnp.Manufacturer", "Openhome");
+    device->SetAttribute("Upnp.ManufacturerUrl", "http://www.openhome.org");
+    device->SetAttribute("Upnp.ModelDescription", "Openhome WavSender");
+    device->SetAttribute("Upnp.ModelName", "Openhome WavSender");
+    device->SetAttribute("Upnp.ModelNumber", "1");
+    device->SetAttribute("Upnp.ModelUrl", "http://www.openhome.org");
+    device->SetAttribute("Upnp.SerialNumber", "");
+    device->SetAttribute("Upnp.Upc", "");
+
+    OhmSenderDriver* driver = new OhmSenderDriver(lib->Env());
+    
+    Brn icon(icon_png, icon_png_len);
+
+    OhmSender* sender =
+        new OhmSender(lib->Env(), *device, *driver, name, channel, adapter, ttl,
+                      latency, multicast, !disabled, icon, Brn("image/png"), 0);
+        
+    PcmSender* pcmsender = new PcmSender(lib->Env(), sender, driver, file,
+                                         audio, needpace);
+    
+    device->SetEnabled();
+
+    const Brx& suri(sender->SenderUri());
+    string uri((const char*)suri.Ptr(), suri.Bytes());
+    const Brx& smeta(sender->SenderMetadata());
+    string meta((const char*)smeta.Ptr(), smeta.Bytes());
+    cout << "URI " << UPnPP::base64_encode(uri) <<
+        " METADATA " << UPnPP::base64_encode(meta) << endl;
+//    cout << "URI " << uri << " METADATA " << meta << endl;
+    cout.flush();
+
+    signal(SIGUSR1, sigcatcher);
+    signal(SIGINT, sigcatcher);
+    signal(SIGTERM, sigcatcher);
+    if (pcmsender->Start()) {
+        pause();
+    }
+
+    LOGDEB("Main: cleaning up\n");
+    delete (pcmsender);
+
+    delete (device);
+    
+    UpnpLibrary::Close();
+
+    return (0);
+}