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