a b/src/sc2mpd.cpp
1
/*
2
Copyright 2011-14, Linn Products Ltd. All rights reserved.
3
4
Unless otherwise stated, all code in this project is licensed under the 2-clause
5
(Simplified) BSD license.  See BsdLicense.txt for details.
6
7
*/
8
/* Copyright (C) 2014 J.F.Dockes
9
 *     This program is free software; you can redistribute it and/or modify
10
 *     it under the terms of the GNU General Public License as published by
11
 *     the Free Software Foundation; either version 2 of the License, or
12
 *     (at your option) any later version.
13
 *
14
 *     This program is distributed in the hope that it will be useful,
15
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *     GNU General Public License for more details.
18
 *
19
 *     You should have received a copy of the GNU General Public License
20
 *     along with this program; if not, write to the
21
 *     Free Software Foundation, Inc.,
22
 *     59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
 */
24
#include "config.h"
25
26
#include <OpenHome/OhNetTypes.h>
27
#include <OpenHome/Private/Ascii.h>
28
#include <OpenHome/Private/Thread.h>
29
#include <OpenHome/Private/OptionParser.h>
30
#include <OpenHome/Private/Debug.h>
31
#include <OpenHome/Net/Core/OhNet.h>
32
33
#include "Debug.h"
34
#include "OhmReceiver.h"
35
36
#include "workqueue.h"
37
#include "rcvqueue.h"
38
#include "log.h"
39
#include "conftree.h"
40
41
#include <vector>
42
#include <stdio.h>
43
#include <iostream>
44
using namespace std;
45
46
WorkQueue<AudioMessage*> audioqueue("audioqueue", 2);
47
48
#ifdef _WIN32
49
50
#pragma warning(disable:4355) // use of 'this' in ctor lists safe in this case
51
52
#define CDECL __cdecl
53
54
#include <conio.h>
55
56
int mygetch()
57
{
58
    return (_getch());
59
}
60
61
#else
62
63
#define CDECL
64
65
#include <termios.h>
66
#include <unistd.h>
67
68
int mygetch()
69
{
70
    struct termios oldt, newt;
71
    int ch;
72
    tcgetattr(STDIN_FILENO, &oldt);
73
    newt = oldt;
74
    newt.c_lflag &= ~(ICANON | ECHO);
75
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
76
    ch = getchar();
77
    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
78
    return ch;
79
}
80
81
#endif
82
83
84
using namespace OpenHome;
85
using namespace OpenHome::Net;
86
using namespace OpenHome::TestFramework;
87
using namespace OpenHome::Av;
88
89
90
class OhmReceiverDriver : public IOhmReceiverDriver, public IOhmMsgProcessor {
91
public:
92
    OhmReceiverDriver(int port);
93
94
private:
95
    // IOhmReceiverDriver
96
    virtual void Add(OhmMsg& aMsg);
97
    virtual void Timestamp(OhmMsg& aMsg);
98
    virtual void Started();
99
    virtual void Connected();
100
    virtual void Playing();
101
    virtual void Disconnected();
102
    virtual void Stopped();
103
104
    // IOhmMsgProcessor
105
    virtual void Process(OhmMsgAudio& aMsg);
106
    virtual void Process(OhmMsgTrack& aMsg);
107
    virtual void Process(OhmMsgMetatext& aMsg);
108
109
private:
110
    TBool iReset;
111
    TUint iCount;
112
    TUint iFrame;
113
};
114
115
116
OhmReceiverDriver::OhmReceiverDriver(int port)
117
{
118
    iReset = true;
119
    iCount = 0;
120
    AudioEaterContext *ctxt = new AudioEaterContext(port);
121
    audioqueue.start(1, &audioEater, ctxt);
122
}
123
124
void OhmReceiverDriver::Add(OhmMsg& aMsg)
125
{
126
    aMsg.Process(*this);
127
    aMsg.RemoveRef();
128
}
129
130
void OhmReceiverDriver::Timestamp(OhmMsg& /*aMsg*/)
131
{
132
}
133
134
void OhmReceiverDriver::Started()
135
{
136
    LOGDEB("=== STARTED ====\n");
137
}
138
139
void OhmReceiverDriver::Connected()
140
{
141
    iReset = true;
142
    LOGDEB("=== CONNECTED ====\n");
143
}
144
145
void OhmReceiverDriver::Playing()
146
{
147
    LOGDEB("=== PLAYING ====\n");
148
    printf("PLAYING\n");
149
    fflush(stdout);
150
}
151
152
void OhmReceiverDriver::Disconnected()
153
{
154
    LOGDEB("=== DISCONNECTED ====\n");
155
}
156
157
void OhmReceiverDriver::Stopped()
158
{
159
    LOGDEB("=== STOPPED ====\n");
160
}
161
162
void OhmReceiverDriver::Process(OhmMsgAudio& aMsg)
163
{
164
    if (++iCount == 400 || aMsg.Halt()) {
165
        LOGDEB("OhmRcvDrv::Process:audio: samplerate " << aMsg.SampleRate() <<
166
               " bitdepth " << aMsg.BitDepth() << " channels " <<
167
               aMsg.Channels() << " samples " << aMsg.Samples() << 
168
               " Halted ? " << aMsg.Halt() << endl);
169
        iCount = 0;
170
    }
171
172
    if (iReset) {
173
        iFrame = aMsg.Frame();
174
        iReset = false;
175
    } else {
176
        if (aMsg.Frame() != iFrame + 1) {
177
            LOGINF("Missed frames between " << iFrame << " and " << 
178
                    aMsg.Frame());
179
        }
180
        iFrame = aMsg.Frame();
181
    }
182
183
    if (aMsg.Audio().Bytes() == 0)
184
        return;
185
186
    unsigned int bytes = 
187
        aMsg.Samples() * (aMsg.BitDepth() / 8) * aMsg.Channels();
188
189
    if (bytes != aMsg.Audio().Bytes()) {
190
        LOGERR("OhmRcvDrv::Process:audio: computed bytes " << bytes << 
191
               " !=  bufer's " << aMsg.Audio().Bytes() << endl);
192
        bytes = aMsg.Audio().Bytes();
193
    }
194
195
    char *buf = (char *)malloc(bytes);
196
    swab(aMsg.Audio().Ptr(), buf, bytes);
197
198
    AudioMessage *ap = new 
199
        AudioMessage(aMsg.BitDepth(), aMsg.Channels(), aMsg.Samples(),
200
                     aMsg.SampleRate(), buf);
201
202
    // There is nothing special we can do if put fails: no way to
203
    // return status. Should we just exit ?
204
    if (!audioqueue.put(ap, true)) {
205
    }
206
}
207
208
void OhmReceiverDriver::Process(OhmMsgTrack& aMsg)
209
{
210
    LOGDEB("OhmRcvDrv::Process:trk: TRACK SEQ " << aMsg.Sequence() << endl);
211
    Brhz uri(aMsg.Uri());
212
    LOGDEB("OhmRcvDrv::Process:trk: TRACK URI " << uri.CString() << endl);
213
    Brhz metadata(aMsg.Metadata());
214
    LOGDEB("OhmRcvDrv::Process:trk: TRACK METADATA " << metadata.CString() 
215
           << endl);
216
}
217
218
void OhmReceiverDriver::Process(OhmMsgMetatext& aMsg)
219
{
220
    LOGDEB("OhmRcvDrv::Process:meta: METATEXT SEQUENCE " <<  aMsg.Sequence() 
221
           << endl);
222
    Brhz metatext(aMsg.Metatext());
223
    LOGDEB("OhmRcvDrv::Process:meta: METATEXT " << metatext.CString() << endl);
224
}
225
226
int CDECL main(int aArgc, char* aArgv[])
227
{
228
    string logfilename;
229
    int loglevel(Logger::LLINF);
230
231
    OptionParser parser;
232
233
    OptionUint optionAdapter("-a", "--adapter", 0, 
234
                             "[adapter] index of network adapter to use");
235
    parser.AddOption(&optionAdapter);
236
237
    OptionUint optionTtl("-t", "--ttl", 1, "[ttl] ttl");
238
    parser.AddOption(&optionTtl);
239
240
    OptionUint optionInteract("-i", "--interact", 0, "[interact] interactive");
241
    parser.AddOption(&optionInteract);
242
243
    OptionString optionUri("-u", "--uri", Brn("mpus://0.0.0.0:0"), 
244
                           "[uri] uri of the sender");
245
    parser.AddOption(&optionUri);
246
247
    OptionString optionConfig("-c", "--config", Brn("/etc/upmpdcli.conf"), 
248
                              "[config] upmpdcli configuration file path");
249
    parser.AddOption(&optionConfig);
250
251
    if (!parser.Parse(aArgc, aArgv)) {
252
        return (1);
253
    }
254
255
    InitialisationParams* initParams = InitialisationParams::Create();
256
257
    Library* lib = new Library(initParams);
258
259
    std::vector<NetworkAdapter*>* subnetList = lib->CreateSubnetList();
260
    TIpAddress subnet = (*subnetList)[optionAdapter.Value()]->Subnet();
261
    TIpAddress adapter = (*subnetList)[optionAdapter.Value()]->Address();
262
    Library::DestroySubnetList(subnetList);
263
264
265
    TUint ttl = optionTtl.Value();
266
    Brhz uri(optionUri.Value());
267
268
    string uconfigfile = (const char *)optionConfig.Value().Ptr();
269
    if (uconfigfile.empty())
270
        uconfigfile = "/etc/upmpdcli.conf";
271
    ConfSimple config(uconfigfile.c_str(), 1, true);
272
    if (!config.ok()) {
273
        cerr << "Could not open config: " << uconfigfile << endl;
274
        return 1;
275
    }
276
277
    int port = 8888;
278
    string value;
279
    if (config.get("schttpport", value)) {
280
        port = atoi(value.c_str());
281
    }
282
    config.get("sclogfilename", logfilename);
283
    if (config.get("scloglevel", value))
284
        loglevel = atoi(value.c_str());
285
    if (Logger::getTheLog(logfilename) == 0) {
286
        cerr << "Can't initialize log" << endl;
287
        return 1;
288
    }
289
    Logger::getTheLog("")->setLogLevel(Logger::LogLevel(loglevel));
290
291
    LOGINF("scmpdcli: using subnet " << (subnet & 0xff) << "." << 
292
           ((subnet >> 8) & 0xff) << "." << ((subnet >> 16) & 0xff) << "." <<
293
           ((subnet >> 24) & 0xff) << endl);
294
295
    OhmReceiverDriver* driver = new OhmReceiverDriver(port);
296
297
    OhmReceiver* receiver = new OhmReceiver(lib->Env(), adapter, ttl, *driver);
298
299
    CpStack* cpStack = lib->StartCp(subnet);
300
    cpStack = cpStack; // avoid unused variable warning
301
302
    Debug::SetLevel(Debug::kMedia);
303
304
    if (optionInteract.Value()) {
305
        printf("q = quit\n");
306
        for (;;) {
307
            int key = mygetch();
308
309
            if (key == 'q') {
310
                printf("QUIT\n");
311
                break;
312
            } else if (key == 'p') {
313
                printf("PLAY %s\n", uri.CString());
314
                receiver->Play(uri);
315
            } else if (key == 's') {
316
                printf("STOP\n");
317
                receiver->Stop();
318
            }
319
        }
320
    } else {
321
        receiver->Play(uri);
322
        for (;;) {
323
            sleep(1000);
324
        }
325
    }
326
327
    delete(receiver);
328
329
    delete lib;
330
331
    if (optionInteract.Value())
332
        printf("\n");
333
334
    return (0);
335
}