--- a/mpd2src/mpd2sc.cpp
+++ b/mpd2src/mpd2sc.cpp
@@ -63,6 +63,7 @@
#include "audioreader.h"
#include "openaudio.h"
#include "base64.hxx"
+#include "songcastsender.h"
using namespace std;
@@ -88,89 +89,16 @@
}
}
-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(TBool aEnabled);
- void Play();
- void PlayPause();
- void Stop();
- void Restart();
- ~PcmSender();
- void busyRdWr();
- TBool Paused();
-
-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(true)
- , 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);
-}
-
#define SOCK_PATH "/tmp/mpd2sc.sock"
#define BUF_SIZE 16
-void HandleUserCmd(OhmSender* sender, PcmSender* pcmsender)
+void HandleUserCmd(OhmSender* sender, SongcastSender* scsender)
{
struct sockaddr_un server_addr, client_addr;
socklen_t len;
ssize_t num_bytes;
char buf[BUF_SIZE];
- LOGDEB("PcmSender: Running user command handler\n");
+ LOGDEB("SongcastSender: Running user command handler\n");
g_sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (g_sockfd == -1) {
@@ -213,15 +141,17 @@
LOGDEB("HandleUserCmd: Received " << num_bytes << " bytes: " <<
string(buf, num_bytes) << endl);
if (strncmp("play", buf, num_bytes) == 0)
- pcmsender->Play();
+ scsender->Play();
else if (strncmp("stop", buf, num_bytes) == 0)
- pcmsender->Stop();
+ scsender->Stop();
else if (strncmp("playpause", buf, num_bytes) == 0)
- pcmsender->PlayPause();
+ scsender->PlayPause();
else if (strncmp("restart", buf, num_bytes) == 0)
- pcmsender->Restart();
+ scsender->Restart();
+ else if (strncmp("codec", buf, num_bytes) == 0)
+ response = scsender->CodecName();
else if (strncmp("state", buf, num_bytes) == 0)
- response = pcmsender->Paused() ? "Stopped" : "Playing";
+ response = scsender->Paused() ? "Stopped" : "Playing";
else if (strncmp("enable", buf, num_bytes) == 0)
sender->SetEnabled(true);
else if (strncmp("disable", buf, num_bytes) == 0)
@@ -244,229 +174,7 @@
if (g_sockfd > 0)
close(g_sockfd);
_leave:
- LOGDEB("PcmSender: Leaving user command handler\n");
-}
-
-// We return true if the main thread should pause.
-bool PcmSender::Start(TBool aEnabled)
-{
- 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. Enabled? " << aEnabled << endl);
- if (aEnabled)
- Play();
- return true;
- } else {
- LOGDEB("PcmSender::Start: block on reading only\n");
- busyRdWr();
- return false;
- }
-}
-
-void PcmSender::Play()
-{
- if (m_audio)
- m_audio->open();
-
- iMutex.Wait();
- iPaused = false;
- iLastTimeUs = 0;
- iTimeOffsetUs = 0;
- iTimer.FireIn(kPeriodMs);
- iMutex.Signal();
-}
-
-void PcmSender::Stop()
-{
- if (m_audio)
- m_audio->close();
-
- iMutex.Wait();
- iPaused = true;
- iMutex.Signal();
-}
-
-void PcmSender::PlayPause()
-{
- if (iPaused)
- Play();
- else
- Stop();
-}
-
-void PcmSender::Restart()
-{
- iMutex.Wait();
- iIndex = 0;
- iMutex.Signal();
-}
-
-TBool PcmSender::Paused()
-{
- return (iPaused);
-}
-
-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) {
- 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;
- }
-#ifdef HAVE_SENDAUDIO_PAUSE_FLAG
- iDriver->SendAudio(cp, iPacketBytes, iPaused);
-#else
- iDriver->SendAudio(cp, iPacketBytes);
-#endif
- }
-}
-
-void PcmSender::TimerExpired()
-{
- iMutex.Wait();
- ssize_t nread = 0;
- const unsigned char *cp = m_audio->data((size_t)iPacketBytes, nread);
-
- if (nread > 0) {
- if ((size_t)nread < iPacketBytes) {
- LOGDEB("PcmSender::TimerExpired: requested " << iPacketBytes
- << " bytes, read " << nread << " bytes" << endl);
- }
-#ifdef HAVE_SENDAUDIO_PAUSE_FLAG
- iDriver->SendAudio(cp, iPacketBytes, iPaused || g_quitrequest);
-#else
- iDriver->SendAudio(cp, iPacketBytes);
-#endif
- } else if (iPaused) {
- // 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.
-#ifdef HAVE_SENDAUDIO_PAUSE_FLAG
- LOGDEB("PcmSender::Send empty audio message\n");
- iDriver->SendAudio(cp, 0, iPaused || g_quitrequest);
-#endif
- }
-
- if (!iPaused && !g_quitrequest) {
- 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("PcmSender::TimerExpired: Sender is paused. Stop firing\n");
- }
-
- iMutex.Signal();
-}
-
-PcmSender::~PcmSender()
-{
- iTimer.Cancel();
- delete (iSender);
- delete (iDriver);
+ LOGDEB("SongcastSender: Leaving user command handler\n");
}
static char *thisprog;
@@ -476,6 +184,7 @@
" swap==1 if the input is little-endian. Set this only if the data can't be\n"
" obtained from the file. Conflicting values will cause an error. \n"
" -a, --adapter, [adapter] index of network adapter to use.\n"
+" -C, --codec, [PCM|FLAC] select streaming codec.\n"
" -c, --channel, [0..65535] sender channel.\n"
" -d, --disabled, [disabled] start up disabled.\n"
" -f, --file, [file] file name to read and send.\n"
@@ -509,10 +218,12 @@
#define OPT_p 0x100
#define OPT_t 0x200
#define OPT_u 0x400
+#define OPT_C 0x800
static struct option long_options[] = {
{"audio", required_argument, 0, 'A'},
{"adapter", required_argument, 0, 'a'},
+ {"codec", required_argument, 0, 'C'},
{"channel", required_argument, 0, 'c'},
{"disabled", 0, 0, 'd'},
{"file", required_argument, 0, 'f'},
@@ -532,13 +243,15 @@
int ret;
(void)op_flags;
string audioparams, sfile, sname("Openhome WavSender"), sudn("12345678");
+ string codec;
string ofile;
unsigned int adaptidx(0), channel(0), ttl(1), latency(100);
- while ((ret = getopt_long(argc, argv, "A:a:c:df:o:l:mn:pt:u:",
+ while ((ret = getopt_long(argc, argv, "A:a:C:c:df:o:l:mn:pt:u:",
long_options, NULL)) != -1) {
switch (ret) {
case 'A': audioparams = optarg;op_flags |= OPT_A; break;
case 'a': adaptidx = atoi(optarg);op_flags |= OPT_a; break;
+ case 'C': codec = optarg;op_flags |= OPT_C; break;
case 'c': channel = atoi(optarg);op_flags |= OPT_c; break;
case 'd': op_flags |= OPT_d; break;
case 'f': sfile = optarg;op_flags |= OPT_f; break;
@@ -626,9 +339,8 @@
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);
+ SongcastSender* scsender = new SongcastSender(lib->Env(), sender, driver,
+ file, audio, codec, needpace);
device->SetEnabled();
@@ -649,8 +361,8 @@
signal(SIGUSR1, sigcatcher);
signal(SIGINT, sigcatcher);
signal(SIGTERM, sigcatcher);
- if (pcmsender->Start(!disabled)) {
- HandleUserCmd(sender, pcmsender);
+ if (scsender->Start(!disabled)) {
+ HandleUserCmd(sender, scsender);
// If HandleUserCmd() fails with an error we do not want to exit, but
// wait for termination.
if (!g_quitrequest)
@@ -662,7 +374,7 @@
}
LOGDEB("Main: cleaning up\n");
- delete (pcmsender);
+ delete (scsender);
delete (device);