Switch to unified view

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