|
a/mpd2src/mpd2sc.cpp |
|
b/mpd2src/mpd2sc.cpp |
|
... |
|
... |
61 |
#include "log.h"
|
61 |
#include "log.h"
|
62 |
#include "icon.h"
|
62 |
#include "icon.h"
|
63 |
#include "audioreader.h"
|
63 |
#include "audioreader.h"
|
64 |
#include "openaudio.h"
|
64 |
#include "openaudio.h"
|
65 |
#include "base64.hxx"
|
65 |
#include "base64.hxx"
|
|
|
66 |
#include "songcastsender.h"
|
66 |
|
67 |
|
67 |
using namespace std;
|
68 |
using namespace std;
|
68 |
|
69 |
|
69 |
using namespace OpenHome;
|
70 |
using namespace OpenHome;
|
70 |
using namespace OpenHome::Net;
|
71 |
using namespace OpenHome::Net;
|
|
... |
|
... |
86 |
if (g_sockfd > 0) {
|
87 |
if (g_sockfd > 0) {
|
87 |
shutdown(g_sockfd, SHUT_RD);
|
88 |
shutdown(g_sockfd, SHUT_RD);
|
88 |
}
|
89 |
}
|
89 |
}
|
90 |
}
|
90 |
|
91 |
|
91 |
class PcmSender {
|
|
|
92 |
|
|
|
93 |
public:
|
|
|
94 |
static const TUint kPeriodMs = 10;
|
|
|
95 |
static const TUint kSpeedNormal = 100;
|
|
|
96 |
static const TUint kSpeedMin = 75;
|
|
|
97 |
static const TUint kSpeedMax = 150;
|
|
|
98 |
static const TUint kMaxPacketBytes = 4096;
|
|
|
99 |
|
|
|
100 |
public:
|
|
|
101 |
PcmSender(Environment& aEnv, OhmSender* aSender,
|
|
|
102 |
OhmSenderDriver* aDriver, const Brx& aUri,
|
|
|
103 |
AudioReader* audio, bool paced);
|
|
|
104 |
bool Start(TBool aEnabled);
|
|
|
105 |
void Play();
|
|
|
106 |
void PlayPause();
|
|
|
107 |
void Stop();
|
|
|
108 |
void Restart();
|
|
|
109 |
~PcmSender();
|
|
|
110 |
void busyRdWr();
|
|
|
111 |
TBool Paused();
|
|
|
112 |
|
|
|
113 |
private:
|
|
|
114 |
void CalculatePacketBytes();
|
|
|
115 |
void TimerExpired();
|
|
|
116 |
|
|
|
117 |
private:
|
|
|
118 |
Environment& iEnv;
|
|
|
119 |
OhmSender* iSender;
|
|
|
120 |
OhmSenderDriver* iDriver;
|
|
|
121 |
Bws<OhmSender::kMaxTrackUriBytes> iUri;
|
|
|
122 |
AudioReader *m_audio;
|
|
|
123 |
Timer iTimer;
|
|
|
124 |
Mutex iMutex;
|
|
|
125 |
TBool iPaused;
|
|
|
126 |
TUint iSpeed; // percent, 100%=normal
|
|
|
127 |
TUint iIndex; // byte offset read position in source data
|
|
|
128 |
TUint iPacketBytes; // how many bytes of audio in each packet
|
|
|
129 |
TUint iPacketFrames; // how many audio frames in each packet
|
|
|
130 |
TUint iPacketTime; // how much audio time in each packet, uS
|
|
|
131 |
TUint64 iLastTimeUs; // last time stamp from system
|
|
|
132 |
TInt32 iTimeOffsetUs; // running offset in usec from ideal time
|
|
|
133 |
// <0 means sender is behind
|
|
|
134 |
// >0 means sender is ahead
|
|
|
135 |
TBool iVerbose;
|
|
|
136 |
TBool iPaced;
|
|
|
137 |
};
|
|
|
138 |
|
|
|
139 |
PcmSender::PcmSender(Environment& aEnv, OhmSender* aSender,
|
|
|
140 |
OhmSenderDriver* aDriver,
|
|
|
141 |
const Brx& aUri, AudioReader* audio, bool paced)
|
|
|
142 |
: iEnv(aEnv)
|
|
|
143 |
, iSender(aSender)
|
|
|
144 |
, iDriver(aDriver)
|
|
|
145 |
, iUri(aUri)
|
|
|
146 |
, m_audio(audio)
|
|
|
147 |
, iTimer(aEnv, MakeFunctor(*this, &PcmSender::TimerExpired), "PcmSender")
|
|
|
148 |
, iMutex("WAVP")
|
|
|
149 |
, iPaused(true)
|
|
|
150 |
, iSpeed(kSpeedNormal)
|
|
|
151 |
, iIndex(0)
|
|
|
152 |
, iLastTimeUs(0)
|
|
|
153 |
, iTimeOffsetUs(0)
|
|
|
154 |
, iVerbose(false)
|
|
|
155 |
, iPaced(paced)
|
|
|
156 |
|
|
|
157 |
{
|
|
|
158 |
CalculatePacketBytes();
|
|
|
159 |
LOGDEB("bytes per packet: " << iPacketBytes << endl);
|
|
|
160 |
LOGDEB("frames per packet: " << iPacketFrames << endl);
|
|
|
161 |
LOGDEB("usec per packet: "<< iPacketTime << endl);
|
|
|
162 |
}
|
|
|
163 |
|
|
|
164 |
#define SOCK_PATH "/tmp/mpd2sc.sock"
|
92 |
#define SOCK_PATH "/tmp/mpd2sc.sock"
|
165 |
#define BUF_SIZE 16
|
93 |
#define BUF_SIZE 16
|
166 |
void HandleUserCmd(OhmSender* sender, PcmSender* pcmsender)
|
94 |
void HandleUserCmd(OhmSender* sender, SongcastSender* scsender)
|
167 |
{
|
95 |
{
|
168 |
struct sockaddr_un server_addr, client_addr;
|
96 |
struct sockaddr_un server_addr, client_addr;
|
169 |
socklen_t len;
|
97 |
socklen_t len;
|
170 |
ssize_t num_bytes;
|
98 |
ssize_t num_bytes;
|
171 |
char buf[BUF_SIZE];
|
99 |
char buf[BUF_SIZE];
|
172 |
|
100 |
|
173 |
LOGDEB("PcmSender: Running user command handler\n");
|
101 |
LOGDEB("SongcastSender: Running user command handler\n");
|
174 |
|
102 |
|
175 |
g_sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
|
103 |
g_sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
|
176 |
if (g_sockfd == -1) {
|
104 |
if (g_sockfd == -1) {
|
177 |
LOGERR("Error: Cannot create socket: " << strerror(errno) << endl);
|
105 |
LOGERR("Error: Cannot create socket: " << strerror(errno) << endl);
|
178 |
goto _leave;
|
106 |
goto _leave;
|
|
... |
|
... |
211 |
string response("OK");
|
139 |
string response("OK");
|
212 |
|
140 |
|
213 |
LOGDEB("HandleUserCmd: Received " << num_bytes << " bytes: " <<
|
141 |
LOGDEB("HandleUserCmd: Received " << num_bytes << " bytes: " <<
|
214 |
string(buf, num_bytes) << endl);
|
142 |
string(buf, num_bytes) << endl);
|
215 |
if (strncmp("play", buf, num_bytes) == 0)
|
143 |
if (strncmp("play", buf, num_bytes) == 0)
|
216 |
pcmsender->Play();
|
144 |
scsender->Play();
|
217 |
else if (strncmp("stop", buf, num_bytes) == 0)
|
145 |
else if (strncmp("stop", buf, num_bytes) == 0)
|
218 |
pcmsender->Stop();
|
146 |
scsender->Stop();
|
219 |
else if (strncmp("playpause", buf, num_bytes) == 0)
|
147 |
else if (strncmp("playpause", buf, num_bytes) == 0)
|
220 |
pcmsender->PlayPause();
|
148 |
scsender->PlayPause();
|
221 |
else if (strncmp("restart", buf, num_bytes) == 0)
|
149 |
else if (strncmp("restart", buf, num_bytes) == 0)
|
222 |
pcmsender->Restart();
|
150 |
scsender->Restart();
|
|
|
151 |
else if (strncmp("codec", buf, num_bytes) == 0)
|
|
|
152 |
response = scsender->CodecName();
|
223 |
else if (strncmp("state", buf, num_bytes) == 0)
|
153 |
else if (strncmp("state", buf, num_bytes) == 0)
|
224 |
response = pcmsender->Paused() ? "Stopped" : "Playing";
|
154 |
response = scsender->Paused() ? "Stopped" : "Playing";
|
225 |
else if (strncmp("enable", buf, num_bytes) == 0)
|
155 |
else if (strncmp("enable", buf, num_bytes) == 0)
|
226 |
sender->SetEnabled(true);
|
156 |
sender->SetEnabled(true);
|
227 |
else if (strncmp("disable", buf, num_bytes) == 0)
|
157 |
else if (strncmp("disable", buf, num_bytes) == 0)
|
228 |
sender->SetEnabled(false);
|
158 |
sender->SetEnabled(false);
|
229 |
else if (strncmp("multicast", buf, num_bytes) == 0)
|
159 |
else if (strncmp("multicast", buf, num_bytes) == 0)
|
|
... |
|
... |
242 |
|
172 |
|
243 |
_close_socket:
|
173 |
_close_socket:
|
244 |
if (g_sockfd > 0)
|
174 |
if (g_sockfd > 0)
|
245 |
close(g_sockfd);
|
175 |
close(g_sockfd);
|
246 |
_leave:
|
176 |
_leave:
|
247 |
LOGDEB("PcmSender: Leaving user command handler\n");
|
177 |
LOGDEB("SongcastSender: Leaving user command handler\n");
|
248 |
}
|
|
|
249 |
|
|
|
250 |
// We return true if the main thread should pause.
|
|
|
251 |
bool PcmSender::Start(TBool aEnabled)
|
|
|
252 |
{
|
|
|
253 |
iDriver->SetAudioFormat(m_audio->sampleRate(), m_audio->byteRate() * 8,
|
|
|
254 |
m_audio->numChannels(),
|
|
|
255 |
m_audio->bitsPerSample(), true, Brn("WAV"));
|
|
|
256 |
iSender->SetEnabled(true);
|
|
|
257 |
|
|
|
258 |
iSender->SetTrack(iUri, Brx::Empty(), m_audio->sampleCount(), 0);
|
|
|
259 |
iSender->SetMetatext(Brn("PcmSender repeated play"));
|
|
|
260 |
|
|
|
261 |
// It seems that both hijacking the main thread and using the
|
|
|
262 |
// timer with a short timeout (see TimerExpired()) work.
|
|
|
263 |
// Don't know what's best. The timer approach is closer to the original
|
|
|
264 |
// code and leaves the main thread free for control ops if needed.
|
|
|
265 |
// Otoh, if no data appears on the fifo, the timer thread is stuck in read
|
|
|
266 |
// and the upnp side stops working (no sender advertised).
|
|
|
267 |
// Maybe the best approach would be to start a separate thread and
|
|
|
268 |
// use busyreading. Using the main thread for now.
|
|
|
269 |
static const bool optionbusy = true;
|
|
|
270 |
if (iPaced || !optionbusy) {
|
|
|
271 |
LOGDEB("PcmSender::Start: using timers. Enabled? " << aEnabled << endl);
|
|
|
272 |
if (aEnabled)
|
|
|
273 |
Play();
|
|
|
274 |
return true;
|
|
|
275 |
} else {
|
|
|
276 |
LOGDEB("PcmSender::Start: block on reading only\n");
|
|
|
277 |
busyRdWr();
|
|
|
278 |
return false;
|
|
|
279 |
}
|
|
|
280 |
}
|
|
|
281 |
|
|
|
282 |
void PcmSender::Play()
|
|
|
283 |
{
|
|
|
284 |
if (m_audio)
|
|
|
285 |
m_audio->open();
|
|
|
286 |
|
|
|
287 |
iMutex.Wait();
|
|
|
288 |
iPaused = false;
|
|
|
289 |
iLastTimeUs = 0;
|
|
|
290 |
iTimeOffsetUs = 0;
|
|
|
291 |
iTimer.FireIn(kPeriodMs);
|
|
|
292 |
iMutex.Signal();
|
|
|
293 |
}
|
|
|
294 |
|
|
|
295 |
void PcmSender::Stop()
|
|
|
296 |
{
|
|
|
297 |
if (m_audio)
|
|
|
298 |
m_audio->close();
|
|
|
299 |
|
|
|
300 |
iMutex.Wait();
|
|
|
301 |
iPaused = true;
|
|
|
302 |
iMutex.Signal();
|
|
|
303 |
}
|
|
|
304 |
|
|
|
305 |
void PcmSender::PlayPause()
|
|
|
306 |
{
|
|
|
307 |
if (iPaused)
|
|
|
308 |
Play();
|
|
|
309 |
else
|
|
|
310 |
Stop();
|
|
|
311 |
}
|
|
|
312 |
|
|
|
313 |
void PcmSender::Restart()
|
|
|
314 |
{
|
|
|
315 |
iMutex.Wait();
|
|
|
316 |
iIndex = 0;
|
|
|
317 |
iMutex.Signal();
|
|
|
318 |
}
|
|
|
319 |
|
|
|
320 |
TBool PcmSender::Paused()
|
|
|
321 |
{
|
|
|
322 |
return (iPaused);
|
|
|
323 |
}
|
|
|
324 |
|
|
|
325 |
void PcmSender::CalculatePacketBytes()
|
|
|
326 |
{
|
|
|
327 |
// in order to let wavsender change the playback rate,
|
|
|
328 |
// we keep constant it's idea of how much audio time is in each packet,
|
|
|
329 |
// but vary the amount of data that is actually sent
|
|
|
330 |
|
|
|
331 |
// calculate the amount of time in each packet
|
|
|
332 |
TUint norm_bytes = (m_audio->sampleRate() * m_audio->bytesPerFrame() *
|
|
|
333 |
kPeriodMs) / 1000;
|
|
|
334 |
if (norm_bytes > kMaxPacketBytes) {
|
|
|
335 |
norm_bytes = kMaxPacketBytes;
|
|
|
336 |
}
|
|
|
337 |
TUint norm_packet_samples = norm_bytes / m_audio->bytesPerFrame();
|
|
|
338 |
iPacketTime = (norm_packet_samples*1000000/(m_audio->sampleRate()/10) + 5)/10;
|
|
|
339 |
|
|
|
340 |
// calculate the adjusted speed packet size
|
|
|
341 |
TUint bytes = (norm_bytes * iSpeed) / 100;
|
|
|
342 |
if (bytes > kMaxPacketBytes) {
|
|
|
343 |
bytes = kMaxPacketBytes;
|
|
|
344 |
}
|
|
|
345 |
iPacketFrames = bytes / m_audio->bytesPerFrame();
|
|
|
346 |
iPacketBytes = iPacketFrames * m_audio->bytesPerFrame();
|
|
|
347 |
}
|
|
|
348 |
|
|
|
349 |
void PcmSender::busyRdWr()
|
|
|
350 |
{
|
|
|
351 |
LOGDEB("PcmSender:busyRdWr: packetbytes " << iPacketBytes << endl);
|
|
|
352 |
while (true) {
|
|
|
353 |
if (g_quitrequest) {
|
|
|
354 |
return;
|
|
|
355 |
}
|
|
|
356 |
ssize_t nread = 0;
|
|
|
357 |
const unsigned char *cp = m_audio->data((size_t)iPacketBytes, nread);
|
|
|
358 |
if (cp == 0) {
|
|
|
359 |
return;
|
|
|
360 |
}
|
|
|
361 |
if (g_quitrequest) {
|
|
|
362 |
return;
|
|
|
363 |
}
|
|
|
364 |
#ifdef HAVE_SENDAUDIO_PAUSE_FLAG
|
|
|
365 |
iDriver->SendAudio(cp, iPacketBytes, iPaused);
|
|
|
366 |
#else
|
|
|
367 |
iDriver->SendAudio(cp, iPacketBytes);
|
|
|
368 |
#endif
|
|
|
369 |
}
|
|
|
370 |
}
|
|
|
371 |
|
|
|
372 |
void PcmSender::TimerExpired()
|
|
|
373 |
{
|
|
|
374 |
iMutex.Wait();
|
|
|
375 |
ssize_t nread = 0;
|
|
|
376 |
const unsigned char *cp = m_audio->data((size_t)iPacketBytes, nread);
|
|
|
377 |
|
|
|
378 |
if (nread > 0) {
|
|
|
379 |
if ((size_t)nread < iPacketBytes) {
|
|
|
380 |
LOGDEB("PcmSender::TimerExpired: requested " << iPacketBytes
|
|
|
381 |
<< " bytes, read " << nread << " bytes" << endl);
|
|
|
382 |
}
|
|
|
383 |
#ifdef HAVE_SENDAUDIO_PAUSE_FLAG
|
|
|
384 |
iDriver->SendAudio(cp, iPacketBytes, iPaused || g_quitrequest);
|
|
|
385 |
#else
|
|
|
386 |
iDriver->SendAudio(cp, iPacketBytes);
|
|
|
387 |
#endif
|
|
|
388 |
} else if (iPaused) {
|
|
|
389 |
// The audio stream was paused and no data could be read from the
|
|
|
390 |
// audio source anymore. To notify the receivers, send an empty audio
|
|
|
391 |
// message with the halt flag set.
|
|
|
392 |
#ifdef HAVE_SENDAUDIO_PAUSE_FLAG
|
|
|
393 |
LOGDEB("PcmSender::Send empty audio message\n");
|
|
|
394 |
iDriver->SendAudio(cp, 0, iPaused || g_quitrequest);
|
|
|
395 |
#endif
|
|
|
396 |
}
|
|
|
397 |
|
|
|
398 |
if (!iPaused && !g_quitrequest) {
|
|
|
399 |
TUint64 now = OsTimeInUs(iEnv.OsCtx());
|
|
|
400 |
|
|
|
401 |
if (!iPaced) {
|
|
|
402 |
// Means we're doing blocking reads on the source, and
|
|
|
403 |
// it's setting the pace. I'd like to actually use 0 here
|
|
|
404 |
// (ala qt processEvents()), but this appears to busyloop
|
|
|
405 |
// and not let the sender do its thing. Anyway, as long
|
|
|
406 |
// as we can read from the fifo in much less than (period-2),
|
|
|
407 |
// which should always be true, we should be ok.
|
|
|
408 |
// I can see not much difference between doing this or
|
|
|
409 |
// hijacking the main thread for busy read/write
|
|
|
410 |
iTimer.FireIn(2);
|
|
|
411 |
} else {
|
|
|
412 |
// skip the first packet, and any time the clock value wraps
|
|
|
413 |
if (iLastTimeUs && iLastTimeUs < now) {
|
|
|
414 |
|
|
|
415 |
// will contain the new time out in ms
|
|
|
416 |
TUint new_timer_ms = kPeriodMs;
|
|
|
417 |
|
|
|
418 |
// the difference in usec from where we should be
|
|
|
419 |
TInt32 diff = (TInt32)(now - iLastTimeUs) - iPacketTime;
|
|
|
420 |
|
|
|
421 |
// increment running offset
|
|
|
422 |
iTimeOffsetUs -= diff;
|
|
|
423 |
|
|
|
424 |
// determine new timer value based upon current offset from ideal
|
|
|
425 |
if (iTimeOffsetUs < -1000) {
|
|
|
426 |
// we are late
|
|
|
427 |
TInt32 time_offset_ms = iTimeOffsetUs/1000;
|
|
|
428 |
if (time_offset_ms < 1-(TInt32)kPeriodMs) {
|
|
|
429 |
// in case callback is severely late, we can only
|
|
|
430 |
// catch up so much
|
|
|
431 |
new_timer_ms = 1;
|
|
|
432 |
} else {
|
|
|
433 |
new_timer_ms = kPeriodMs + time_offset_ms;
|
|
|
434 |
}
|
|
|
435 |
} else if (iTimeOffsetUs > 1000) {
|
|
|
436 |
// we are early
|
|
|
437 |
new_timer_ms = kPeriodMs+1;
|
|
|
438 |
} else {
|
|
|
439 |
// we are about on time
|
|
|
440 |
new_timer_ms = kPeriodMs;
|
|
|
441 |
}
|
|
|
442 |
|
|
|
443 |
// set timer
|
|
|
444 |
iTimer.FireIn(new_timer_ms);
|
|
|
445 |
|
|
|
446 |
// logging
|
|
|
447 |
if (iVerbose) {
|
|
|
448 |
if (iTimeOffsetUs >= 1000)
|
|
|
449 |
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);
|
|
|
450 |
else
|
|
|
451 |
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);
|
|
|
452 |
}
|
|
|
453 |
} else {
|
|
|
454 |
iTimer.FireIn(kPeriodMs);
|
|
|
455 |
}
|
|
|
456 |
iLastTimeUs = now;
|
|
|
457 |
}
|
|
|
458 |
} else {
|
|
|
459 |
LOGDEB("PcmSender::TimerExpired: Sender is paused. Stop firing\n");
|
|
|
460 |
}
|
|
|
461 |
|
|
|
462 |
iMutex.Signal();
|
|
|
463 |
}
|
|
|
464 |
|
|
|
465 |
PcmSender::~PcmSender()
|
|
|
466 |
{
|
|
|
467 |
iTimer.Cancel();
|
|
|
468 |
delete (iSender);
|
|
|
469 |
delete (iDriver);
|
|
|
470 |
}
|
178 |
}
|
471 |
|
179 |
|
472 |
static char *thisprog;
|
180 |
static char *thisprog;
|
473 |
static char usage [] =
|
181 |
static char usage [] =
|
474 |
" -h, --help, show this help message and exit.\n"
|
182 |
" -h, --help, show this help message and exit.\n"
|
475 |
" -A, --audio, [44100:16:2:0/1] freq:bits:chans:swap.\n"
|
183 |
" -A, --audio, [44100:16:2:0/1] freq:bits:chans:swap.\n"
|
476 |
" swap==1 if the input is little-endian. Set this only if the data can't be\n"
|
184 |
" swap==1 if the input is little-endian. Set this only if the data can't be\n"
|
477 |
" obtained from the file. Conflicting values will cause an error. \n"
|
185 |
" obtained from the file. Conflicting values will cause an error. \n"
|
478 |
" -a, --adapter, [adapter] index of network adapter to use.\n"
|
186 |
" -a, --adapter, [adapter] index of network adapter to use.\n"
|
|
|
187 |
" -C, --codec, [PCM|FLAC] select streaming codec.\n"
|
479 |
" -c, --channel, [0..65535] sender channel.\n"
|
188 |
" -c, --channel, [0..65535] sender channel.\n"
|
480 |
" -d, --disabled, [disabled] start up disabled.\n"
|
189 |
" -d, --disabled, [disabled] start up disabled.\n"
|
481 |
" -f, --file, [file] file name to read and send.\n"
|
190 |
" -f, --file, [file] file name to read and send.\n"
|
482 |
" Use xx.wav for an actual wav,\n"
|
191 |
" Use xx.wav for an actual wav,\n"
|
483 |
" xx or xx.fifo for a fifo, stdin for stdin.\n"
|
192 |
" xx or xx.fifo for a fifo, stdin for stdin.\n"
|
|
... |
|
... |
507 |
#define OPT_m 0x40
|
216 |
#define OPT_m 0x40
|
508 |
#define OPT_n 0x80
|
217 |
#define OPT_n 0x80
|
509 |
#define OPT_p 0x100
|
218 |
#define OPT_p 0x100
|
510 |
#define OPT_t 0x200
|
219 |
#define OPT_t 0x200
|
511 |
#define OPT_u 0x400
|
220 |
#define OPT_u 0x400
|
|
|
221 |
#define OPT_C 0x800
|
512 |
|
222 |
|
513 |
static struct option long_options[] = {
|
223 |
static struct option long_options[] = {
|
514 |
{"audio", required_argument, 0, 'A'},
|
224 |
{"audio", required_argument, 0, 'A'},
|
515 |
{"adapter", required_argument, 0, 'a'},
|
225 |
{"adapter", required_argument, 0, 'a'},
|
|
|
226 |
{"codec", required_argument, 0, 'C'},
|
516 |
{"channel", required_argument, 0, 'c'},
|
227 |
{"channel", required_argument, 0, 'c'},
|
517 |
{"disabled", 0, 0, 'd'},
|
228 |
{"disabled", 0, 0, 'd'},
|
518 |
{"file", required_argument, 0, 'f'},
|
229 |
{"file", required_argument, 0, 'f'},
|
519 |
{"output", required_argument, 0, 'o'},
|
230 |
{"output", required_argument, 0, 'o'},
|
520 |
{"latency", required_argument, 0, 'l'},
|
231 |
{"latency", required_argument, 0, 'l'},
|
|
... |
|
... |
530 |
{
|
241 |
{
|
531 |
thisprog = argv[0];
|
242 |
thisprog = argv[0];
|
532 |
int ret;
|
243 |
int ret;
|
533 |
(void)op_flags;
|
244 |
(void)op_flags;
|
534 |
string audioparams, sfile, sname("Openhome WavSender"), sudn("12345678");
|
245 |
string audioparams, sfile, sname("Openhome WavSender"), sudn("12345678");
|
|
|
246 |
string codec;
|
535 |
string ofile;
|
247 |
string ofile;
|
536 |
unsigned int adaptidx(0), channel(0), ttl(1), latency(100);
|
248 |
unsigned int adaptidx(0), channel(0), ttl(1), latency(100);
|
537 |
while ((ret = getopt_long(argc, argv, "A:a:c:df:o:l:mn:pt:u:",
|
249 |
while ((ret = getopt_long(argc, argv, "A:a:C:c:df:o:l:mn:pt:u:",
|
538 |
long_options, NULL)) != -1) {
|
250 |
long_options, NULL)) != -1) {
|
539 |
switch (ret) {
|
251 |
switch (ret) {
|
540 |
case 'A': audioparams = optarg;op_flags |= OPT_A; break;
|
252 |
case 'A': audioparams = optarg;op_flags |= OPT_A; break;
|
541 |
case 'a': adaptidx = atoi(optarg);op_flags |= OPT_a; break;
|
253 |
case 'a': adaptidx = atoi(optarg);op_flags |= OPT_a; break;
|
|
|
254 |
case 'C': codec = optarg;op_flags |= OPT_C; break;
|
542 |
case 'c': channel = atoi(optarg);op_flags |= OPT_c; break;
|
255 |
case 'c': channel = atoi(optarg);op_flags |= OPT_c; break;
|
543 |
case 'd': op_flags |= OPT_d; break;
|
256 |
case 'd': op_flags |= OPT_d; break;
|
544 |
case 'f': sfile = optarg;op_flags |= OPT_f; break;
|
257 |
case 'f': sfile = optarg;op_flags |= OPT_f; break;
|
545 |
case 'o': ofile = optarg; break;
|
258 |
case 'o': ofile = optarg; break;
|
546 |
case 'h': Usage(stdout);break;
|
259 |
case 'h': Usage(stdout);break;
|
|
... |
|
... |
624 |
Brn icon(icon_png, icon_png_len);
|
337 |
Brn icon(icon_png, icon_png_len);
|
625 |
|
338 |
|
626 |
OhmSender* sender =
|
339 |
OhmSender* sender =
|
627 |
new OhmSender(lib->Env(), *device, *driver, name, channel, adapter, ttl,
|
340 |
new OhmSender(lib->Env(), *device, *driver, name, channel, adapter, ttl,
|
628 |
latency, multicast, !disabled, icon, Brn("image/png"), 0);
|
341 |
latency, multicast, !disabled, icon, Brn("image/png"), 0);
|
629 |
|
|
|
630 |
PcmSender* pcmsender = new PcmSender(lib->Env(), sender, driver, file,
|
342 |
SongcastSender* scsender = new SongcastSender(lib->Env(), sender, driver,
|
631 |
audio, needpace);
|
343 |
file, audio, codec, needpace);
|
632 |
|
344 |
|
633 |
device->SetEnabled();
|
345 |
device->SetEnabled();
|
634 |
|
346 |
|
635 |
const Brx& suri(sender->SenderUri());
|
347 |
const Brx& suri(sender->SenderUri());
|
636 |
string uri((const char*)suri.Ptr(), suri.Bytes());
|
348 |
string uri((const char*)suri.Ptr(), suri.Bytes());
|
|
... |
|
... |
647 |
" METADATA " << UPnPP::base64_encode(meta) << endl << flush;
|
359 |
" METADATA " << UPnPP::base64_encode(meta) << endl << flush;
|
648 |
|
360 |
|
649 |
signal(SIGUSR1, sigcatcher);
|
361 |
signal(SIGUSR1, sigcatcher);
|
650 |
signal(SIGINT, sigcatcher);
|
362 |
signal(SIGINT, sigcatcher);
|
651 |
signal(SIGTERM, sigcatcher);
|
363 |
signal(SIGTERM, sigcatcher);
|
652 |
if (pcmsender->Start(!disabled)) {
|
364 |
if (scsender->Start(!disabled)) {
|
653 |
HandleUserCmd(sender, pcmsender);
|
365 |
HandleUserCmd(sender, scsender);
|
654 |
// If HandleUserCmd() fails with an error we do not want to exit, but
|
366 |
// If HandleUserCmd() fails with an error we do not want to exit, but
|
655 |
// wait for termination.
|
367 |
// wait for termination.
|
656 |
if (!g_quitrequest)
|
368 |
if (!g_quitrequest)
|
657 |
pause();
|
369 |
pause();
|
658 |
else
|
370 |
else
|
|
... |
|
... |
660 |
// we set g_quitrequest.
|
372 |
// we set g_quitrequest.
|
661 |
sleep(1);
|
373 |
sleep(1);
|
662 |
}
|
374 |
}
|
663 |
|
375 |
|
664 |
LOGDEB("Main: cleaning up\n");
|
376 |
LOGDEB("Main: cleaning up\n");
|
665 |
delete (pcmsender);
|
377 |
delete (scsender);
|
666 |
|
378 |
|
667 |
delete (device);
|
379 |
delete (device);
|
668 |
|
380 |
|
669 |
UpnpLibrary::Close();
|
381 |
UpnpLibrary::Close();
|
670 |
|
382 |
|