Switch to side-by-side view

--- a
+++ b/upsend_src/upsend.cpp
@@ -0,0 +1,323 @@
+/* Copyright (C) 2013 J.F.Dockes
+ *	 This program is free software; you can redistribute it and/or modify
+ *	 it under the terms of the GNU General Public License as published by
+ *	 the Free Software Foundation; either version 2 of the License, or
+ *	 (at your option) any later version.
+ *
+ *	 This program is distributed in the hope that it will be useful,
+ *	 but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	 GNU General Public License for more details.
+ *
+ *	 You should have received a copy of the GNU General Public License
+ *	 along with this program; if not, write to the
+ *	 Free Software Foundation, Inc.,
+ *	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <strings.h>
+
+#include <string>
+#include <iostream>
+
+#include "libupnpp/upnpplib.hxx"
+#include "libupnpp/log.hxx"
+#include "libupnpp/soaphelp.hxx"
+#include "libupnpp/upnpputils.hxx"
+#include "libupnpp/control/mediarenderer.hxx"
+#include "libupnpp/control/avtransport.hxx"
+#include "libupnpp/control/discovery.hxx"
+
+#include "streamer.h"
+#include "wav.h"
+
+using namespace std;
+using namespace UPnPClient;
+using namespace UPnPP;
+
+WorkQueue<AudioMessage*> audioqueue("audioqueue", 4);
+
+// @param name can be uuid or friendly name, we try both. The chance that a
+//     device would have a uuid which would be the friendly name of
+//     another is small...
+MRDH getRenderer(const string& name)
+{
+    static UPnPDeviceDirectory *superdir;
+    if (superdir == 0) {
+        superdir = UPnPDeviceDirectory::getTheDir();
+        if (superdir == 0) {
+            cerr << "Discovery init failed\n";
+            return MRDH();
+        }
+    }
+    
+    UPnPDeviceDesc ddesc;
+    if (superdir->getDevByFName(name, ddesc)) {
+        return MRDH(new MediaRenderer(ddesc));
+    } else if (superdir->getDevByUDN(name, ddesc)) {
+        return MRDH(new MediaRenderer(ddesc));
+    }
+    cerr << "Can't connect to " << name << endl;
+    return MRDH();
+}
+
+static string path_suffix(const string& s)
+{
+    string::size_type dotp = s.rfind('.');
+    if (dotp == string::npos) {
+        return string();
+    }
+    return s.substr(dotp + 1);
+}
+
+static bool whatfile(const string& audiofile, AudioSink::Context *ctxt)
+{
+    if (access(audiofile.c_str(), R_OK) != 0) {
+        cerr << "No read access " << audiofile << " errno " << errno << endl;
+        return false;
+    }
+    struct stat st;
+    if (stat(audiofile.c_str(), &st)) {
+        cerr << "Can't stat " << audiofile << " errno " << errno << endl;
+        return false;
+    }
+
+    string ext = path_suffix(audiofile);
+    ctxt->filename = audiofile;
+    ctxt->filesize = st.st_size;
+    ctxt->ext = ext;
+    const char* cext = ext.c_str();
+    if (!strcasecmp("flac", cext)) {
+        ctxt->content_type = "audio/flac";
+    } else if (!strcasecmp("mp3", cext)) {
+        ctxt->content_type = "audio/mpeg";
+    } else if (!strcasecmp("wav", cext)) {
+        ctxt->content_type = "audio/wav";
+    } else {
+        cerr << "Unknown extension " << ext << endl;
+        return false;
+    }
+    return true;
+}
+
+void *readworker(void *a)
+{
+    AudioSink::Context *ctxt = (AudioSink::Context *)a;
+
+    int fd = 0;
+    if (ctxt->filename.compare("stdin")) {
+        if ((fd = open(ctxt->filename.c_str(), O_RDONLY)) < 0) {
+            cerr << "readWorker: can't open " << ctxt->filename <<
+                " for reading, errno " << errno << endl;
+            exit(1);
+        }
+    }
+
+    for (;;) {
+        unsigned int allocbytes = 4096;
+        char *buf = (char *)malloc(allocbytes);
+        if (buf == 0) {
+            cerr << "readWorker: can't allocate " << allocbytes << " bytes\n";
+            exit(1);
+        }
+        ssize_t readbytes = read(fd, buf, allocbytes);
+        //cerr << "readworker: got " << readbytes << "bytes\n";
+        if (readbytes < 0) {
+            cerr << "readWorker: read error on " << ctxt->filename <<
+                " errno " << errno << endl;
+            exit(1);
+        } else if (readbytes == 0) {
+            audioqueue.waitIdle();
+            audioqueue.setTerminateAndWait();
+            return nullptr;
+        }
+        AudioMessage *ap = new AudioMessage(buf, readbytes, allocbytes);
+        if (!audioqueue.put(ap, false)) {
+            cerr << "readWorker: queue dead: exiting\n";
+            exit(1);
+        }
+    }
+}
+
+string didlmake(const string& uri, const string& mime)
+{
+    ostringstream ss;
+    ss << "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+       "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
+       "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" "
+       "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" "
+       "xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\">"
+       << "<item restricted=\"1\">";
+
+    ss << "<dc:title>" << SoapHelp::xmlQuote("Streaming") << "</dc:title>";
+    ss << "<upnp:class>object.item.audioItem.musicTrack</upnp:class>";
+
+#warning "problem with resource values!"
+    ss << "<res " << "duration=\"" << upnpduration(30)
+       << "\" "
+       << "sampleFrequency=\"44100\" audioChannels=\"2\" "
+       << "protocolInfo=\"http-get:*:" << mime << ":*\""
+       << ">"
+       << SoapHelp::xmlQuote(uri)
+       << "</res>"
+       << "</item></DIDL-Lite>";
+    return ss.str();
+}
+
+static char *thisprog;
+static char usage [] =
+"<audiofile> <renderer> : play audio on given renderer\n"
+                                           ;
+static void Usage(void)
+{
+    fprintf(stderr, "%s: usage:\n%s", thisprog, usage);
+    exit(1);
+}
+static int	   op_flags;
+#define OPT_h    0x1
+#define OPT_p    0x2
+static struct option long_options[] = {
+    {"host", required_argument, 0, 'h'},
+    {"port", required_argument, 0, 'p'},
+    {0, 0, 0, 0}
+};
+
+
+int main(int argc, char *argv[])
+{
+    thisprog = argv[0];
+    string host = "localhost";
+    int port = 8869;
+    
+    int option_index = 0;
+    int ret;
+    while ((ret = getopt_long(argc, argv, "h:p:", 
+                              long_options, &option_index)) != -1) {
+        cerr << "ret is " << ret << endl;
+        switch (ret) {
+        case 'h': op_flags |= OPT_h; host = optarg; break;
+        case 'p': op_flags |= OPT_h; port = atoi(optarg); break;
+        default:  Usage();
+        }
+    }
+
+    if (optind != argc - 2) 
+        Usage();
+    string audiofile = argv[optind++];
+    string renderer = argv[optind++];
+            
+    if (Logger::getTheLog("stderr") == 0) {
+        cerr << "Can't initialize log" << endl;
+        return 1;
+    }
+    Logger::getTheLog("")->setLogLevel(Logger::LLDEB1);
+
+    string hwa;
+    LibUPnP *mylib = LibUPnP::getLibUPnP(false, &hwa);
+    if (!mylib) {
+        cerr << "Can't get LibUPnP" << endl;
+        return 1;
+    }
+
+    if (!(op_flags & OPT_h)) {
+#if FUTURE
+        host = mylib->host();
+        if (host.empty()) {
+            cerr << "Can't retrieve IP address\n";
+            return 1;
+        }
+#else
+        char hostname[1024];
+        if (gethostname(hostname, 1024)) {
+            perror("gethostname failed. use -h:");
+            return 1;
+        }
+        host = hostname;
+#endif
+    }
+    if (!mylib->ok()) {
+        cerr << "Lib init failed: " <<
+            mylib->errAsString("main", mylib->getInitError()) << endl;
+        return 1;
+    }
+    //mylib->setLogFileName("/tmp/libupnp.log", LibUPnP::LogLevelDebug);
+
+    MRDH rdr = getRenderer(renderer);
+    if (!rdr) {
+        cerr << "Can't connect to renderer\n";
+        return 1;
+    }
+    AVTH avth = rdr->avt();
+    if (!avth) {
+        cerr << "Device has no AVTransport service" << endl;
+        return 1;
+    }
+
+    
+    // Identify file
+    AudioSink::Context *ctxt = new AudioSink::Context(&audioqueue);
+    bool makewav = false;
+    if (!audiofile.compare("stdin")) {
+        ctxt->filename = audiofile;
+        ctxt->ext = "wav";
+        ctxt->content_type = "audio/wav";
+        makewav = true;
+    } else {
+        if (!whatfile(audiofile, ctxt)) {
+            cerr << "Can't identify file " << audiofile << endl;
+            return 1;
+        }
+    }
+
+    unordered_map<string,string> c{{"httpport", SoapHelp::i2s(port)},
+        {"httphost", host}};
+    ctxt->config = c;
+    
+    // Start the http thread
+    audioqueue.start(1, (void *(*)(void *))(httpAudioSink.worker), ctxt);
+
+    if (makewav) {
+        unsigned int allocbytes = 512;
+        char *buf = (char *)malloc(allocbytes);
+        if (buf == 0) {
+            cerr << "Can't allocate " << allocbytes << " bytes\n";
+            exit(1);
+        }
+        int freq = 44100;
+        int bits = 16;
+        int chans = 2;
+        int databytes = 2 * 1000 * 1000 * 1000;
+        // Using buf+bytes in case we ever insert icy before the audio
+        int sz = makewavheader(buf, allocbytes, freq, bits, chans, databytes);
+        AudioMessage *ap = new AudioMessage(buf, sz, allocbytes);
+        audioqueue.put(ap, false);
+    }
+    
+    // Start the reading thread
+    std::thread readthread(readworker, ctxt);
+
+    string uri("http://" + host + ":" + SoapHelp::i2s(port) + "/stream." +
+               ctxt->ext);
+
+    // We'd need a few options here to decide what to do if already playing:
+    // wait (would allow to queue multiple songs), or interrupt.
+
+    // Start the renderer
+    if (avth->setAVTransportURI(uri, didlmake(uri, ctxt->content_type)) != 0) {
+        cerr << "setAVTransportURI failed\n";
+        return 1;
+    }
+
+    if (avth->play() != 0) {
+        cerr << "play failed\n";
+        return 1;
+    }
+    readthread.join();
+    return 0;
+}