/*************************************************************************** * Copyright (C) 2006 Paul Cifarelli * * * * 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. * * * ***************************************************************************/ /*************************************************************************** basically this implements a rudamentary rpc mechanism so the we can have each player in a separate process. this is solely done to implement a reliable crossfade (or more precicely, put the crossfade in the hands of the alsa guys ***************************************************************************/ #include #include #include #include #include #include #include #include #include using namespace std; #include "hxplayercontrol.h" class HSPPlayerControlled : public HelixSimplePlayer { public: HSPPlayerControlled(PlayerControl *pcntl, int index) : m_pcntl(pcntl), m_index(index) {} virtual ~HSPPlayerControlled() {} virtual void notifyUser(unsigned long code, const char *moreinfo, const char *moreinfourl); virtual void interruptUser(unsigned long code, const char *moreinfo, const char *moreinfourl); virtual void onContacting(const char *host); virtual void onBuffering(int percentage); private: PlayerControl *m_pcntl; int m_index; }; void HSPPlayerControlled::notifyUser(unsigned long code, const char *moreinfo, const char *moreinfourl) { m_pcntl->sendnotifyuser(code, moreinfo, moreinfourl); } void HSPPlayerControlled::interruptUser(unsigned long code, const char *moreinfo, const char *moreinfourl) { m_pcntl->sendinterruptuser(code, moreinfo, moreinfourl); } void HSPPlayerControlled::onContacting(const char *host) { m_pcntl->sendcontacting(host); } void HSPPlayerControlled::onBuffering(int percentage) { m_pcntl->sendbuffering(percentage); } PlayerControl::PlayerControl() : m_eq_enabled(false), m_preamp(0), m_err(0), iamparent(0), m_index(0), nNumPlayers(0), m_inited(false), m_api( HelixSimplePlayer::OSS ), m_device(0), mimehead(0), mimelistlen(0), m_numPlugins(0), m_pluginInfo(0) { memset(m_children, 0, sizeof(m_children)); } PlayerControl::~PlayerControl() { tearDown(); print2stderr("In PlayerControl::~PlayerControl(), m_index=%d\n", m_index); delete m_device; if (pmapped) munmap(pmapped, sizeof(stateStuff) * 2); } // init functions void PlayerControl::init(const char *corelibpath, const char *pluginslibpath, const char *codecspath, int numPlayers) { int err; iamparent = 0; nNumPlayers = numPlayers; m_err = 0; print2stderr("In PlayerControl::init(), m_api=%d, m_device=%s\n", m_api, m_device ? m_device : "DEVICE NOT SET"); if (numPlayers > 2) // it's impossible { m_err = -1; return; } memset(&m_children, 0, numPlayers * sizeof(struct playerChildren)); m_inited = false; // create a shared memory region for state like stuff if ( MAP_FAILED == (pmapped = (stateStuff *) mmap( (void *) statestuff, sizeof(stateStuff) * 2, PROT_READ | PROT_WRITE, #ifdef __linux__ MAP_SHARED | MAP_ANONYMOUS, #else MAP_SHARED | MAP_ANON, #endif -1, 0)) ) pmapped = 0; // we do this the old fashioned way, so that we don't have to include an executable with our plugin... for (int i = 0; i < numPlayers; i++) { if (pmapped) { m_children[i].current_time = &(pmapped[i].current_time); m_children[i].duration = &(pmapped[i].duration); m_children[i].md = &(pmapped[i].md); m_children[i].m_current = &(pmapped[i].m_current); m_children[i].m_consumed = &(pmapped[i].m_consumed); //m_children[i].q = pmapped[i].q; //for (int j=0; jsetOutputSink(m_api); if (m_device) player->setDevice(m_device); player->init(corelibpath, pluginslibpath, codecspath, 1); m_inited = true; if (!m_index) // only player 0 need send the mime and plugin stuff { sendplugins(wfd, player); sendmimetypes(wfd, player); } cerr << "%%%%%% player " << m_index << " child sends ready\n"; sendready(wfd); } else cerr << "CHILD " << m_index << " sz not right in INIT, sz=" << sz << endl; break; case SETURL: { bool islocal = (bool) buf[0]; int len = strlen((const char *)&buf[1]); // not that this would prevent a crash... if (m_inited) { cerr << "CHILD " << m_index << " setURL for " << (const char *)&buf[1] << ",islocal=" << islocal << ",len=" << len << endl; if (sz == len + 2) player->setURL((const char *)&buf[1], 0, islocal); // remember, we sent the null... else cerr << "CHILD " << m_index << " sz not right in SETURL, sz=" << sz << endl; } } break; case START: { bool fadein = (bool) buf[0]; unsigned long fadetime; if (m_inited) { if (pmapped) *m_children[m_index].current_time = 0; if (sz == sizeof(unsigned long) + 1) { cerr << "CHILD " << m_index << " gets START\n"; memcpy((void *)&fadetime, (void *)&buf[1], sizeof(unsigned long)); playing = true; player->start(0, fadein, fadetime); } else cerr << "CHILD " << m_index << " sz not right in START, sz=" << sz << endl; } } break; case STOP: if (m_inited) if (!sz) { player->stop(); playing = false; cerr << "CHILD " << m_index << " gets STOP\n"; } else cerr << "CHILD " << m_index << " sz not right in STOP, sz=" << sz << endl; break; case PAUSE: if (m_inited) if (!sz) player->pause(); else cerr << "CHILD " << m_index << " sz not right in PAUSE, sz=" << sz << endl; break; case RESUME: if (m_inited) if (!sz) player->resume(); else cerr << "CHILD " << m_index << " sz not right in RESUME, sz=" << sz << endl; break; case SEEK: if (m_inited) if (sz == sizeof(unsigned long)) { unsigned long pos; memcpy( (void *) &pos, (void *) buf, sizeof(unsigned long) ); if (pos < player->duration(0)) player->seek(pos, 0); } else cerr << "CHILD " << m_index << " sz not right in SEEK, sz=" << sz << endl; break; case SETVOLUME: { if (m_inited) if (sz == sizeof(unsigned long)) { memcpy( (void *) &m_volume, (void *) buf, sizeof(unsigned long)); cerr << "CHILD: received setvolume request " << m_volume <setVolume(m_volume); } else cerr << "CHILD " << m_index << " sz not right in SETVOLUME, sz=" << sz << endl; } break; case OUTPUTSINK: if (m_inited) if (sz == 1) { m_api = (HelixSimplePlayer::AUDIOAPI) buf[0]; cerr << "CHILD: received OUTPUTSINK: " << m_api <= 2) { memcpy( (void *) &m_preamp, (void *) buf, sizeof(m_preamp) ); memcpy( (void *) &n, (void *) &buf[ sizeof(m_preamp) ], sizeof(int) ); player->m_preamp = m_preamp; } else cerr << "CHILD " << m_index << " sz not right in UPDATEETQGAINS, sz=" << sz << endl; if ((unsigned)sz == sizeof(m_preamp) + sizeof(int) + n * sizeof(int)) { if (n > 0) { m_equalizerGains.resize(n); cerr << "CHILD " << m_index << " receives " << n << " equalizer gains\n"; for (i=0; im_equalizerGains = m_equalizerGains; if (!m_eq_enabled) player->enableEQ(true); player->updateEQgains(); player->enableEQ(m_eq_enabled); } } else cerr << "CHILD " << m_index << " sz not right in UPDATEETQGAINS, sz=" << sz << endl; } } break; case SCOPECLEAR: if (m_inited) { player->clearScopeQ(0); if (pmapped) *m_children[m_index].m_consumed = *m_children[m_index].m_current = 0; } break; case TEARDOWN: if (!sz) { cerr << "CHILD: " << m_index << " received shutdown request\n"; player->stop(0); delete player; raise(15); exit(0); } else cerr << "CHILD " << m_index << " sz not right in TEARDOWN, sz=" << sz << endl; break; default: // send an error to the parent cerr << "CHILD " << m_index << " received unhandled message, sz=" << sz << endl; break; } } else { cerr << "CHILD " << m_index << " gets EOD\n"; break; } } if (m_inited) { player->dispatch(); if (playing && player->done(0)) { player->stop(0); player->clearScopeQ(0); senddone(wfd); playing = false; if (pmapped) { *m_children[m_index].current_time = 0; *m_children[m_index].duration = 0; } } if (pmapped) { *m_children[m_index].current_time = player->where(0); *m_children[m_index].duration = player->duration(0); HelixSimplePlayer::metaData *md = player->getMetaData(0); if (md) memcpy((void *) m_children[m_index].md, (void *) md, sizeof(HelixSimplePlayer::metaData)); struct DelayQueue *item; //int j; while ((item = player->getScopeBuf(0))) { //j = (*m_children[m_index].m_current + 1) % NUM_SCOPEBUFS; //cerr << "player:" << m_index << " j=" << j << " time=" << item->time << " etime=" << item->etime << " len=" << item->len << endl; //m_children[m_index].q[j].len = item->len; //m_children[m_index].q[j].time = item->time; //m_children[m_index].q[j].etime = item->etime; //m_children[m_index].q[j].nchan = item->nchan; //m_children[m_index].q[j].bps = item->bps; //m_children[m_index].q[j].tps = item->tps; //m_children[m_index].q[j].spb = item->spb; //memcpy((void *)m_children[m_index].q[j].buf, (void *) item->buf, item->len ); //*m_children[m_index].m_current = j; //cerr << "player:" << m_index << " time=" << item->time << " etime=" << item->etime << endl; sendscopebuf(wfd, item); delete item; } } } timeout.tv_sec = 0; timeout.tv_usec = 10000; } cerr << "CHILD " << m_index << " will exit!\n"; } else { int i; bool done = false, dead = false; sendsetoutputsink(); sendsetdevice(); sendinit(); // wait for ready from children while (!done && !dead) { dispatch(); done = true; for (i=0; i n) n = rfd; } if (n < 0) return; ntot = select(n + 1, &rdset, 0, 0, &timeout); for (i=0; ntot && i < nNumPlayers; i++) { rfd = m_children[i].m_pipeA[0]; wfd = m_children[i].m_pipeB[1]; if ( FD_ISSET(rfd, &rdset) ) { msgid m; unsigned char buf[65536]; int sz = 0; if (getmessage(rfd, m, buf, sz)) { switch (m) { case READY: print2stderr("CHILD %d is READY\n", i); m_children[i].isready = true;; break; case DONE: print2stderr("CHILD %d is DONE\n", i); if (!sz) { m_children[i].isplaying = false; clearScopeQ(i); play_finished(i); } else print2stderr("PARENT: sz does not agree in DONE\n"); break; case MIMETYPES: { int len, slen; MimeList *entry; char *tmp; char tmpbuf[65536]; mimehead = 0; memcpy( (void *) &mimelistlen, (void *) buf, sizeof(mimelistlen) ); len = sizeof(mimelistlen); print2stderr("%%%%%%% Received %d mimetypes\n", mimelistlen); for (int j = 0; j < mimelistlen; j++) { tmp = (char *) &buf[len]; slen = strlen(tmp); strcpy(tmpbuf, tmp); tmp += slen + 1; len += slen + 1; entry = new MimeList(tmpbuf, tmp); slen = strlen(tmp); len += slen + 1; entry->fwd = mimehead; mimehead = entry; } if (sz != len) // sanity check cerr << "PARENT: sz not = len in MIMETYPES " << sz << " " << len << endl; } break; case PLUGINS: { int len, slen; int nplugins; char *tmp; memcpy( (void *) &nplugins, (void *) buf, sizeof(nplugins) ); len = sizeof(nplugins); m_pluginInfo = new pluginInfo* [nplugins]; m_numPlugins = nplugins; print2stderr("%%%%%%% Received %d plugins\n", nplugins); for (int j = 0; j < nplugins; j++) { m_pluginInfo[j] = new pluginInfo; tmp = (char *) &buf[len]; slen = strlen(tmp); m_pluginInfo[j]->description = new char[ slen + 1 ]; strcpy(m_pluginInfo[j]->description, tmp); len += slen + 1; tmp = (char *) &buf[len]; slen = strlen(tmp); m_pluginInfo[j]->copyright = new char[ slen + 1 ]; strcpy(m_pluginInfo[j]->copyright, tmp); len += slen + 1; tmp = (char *) &buf[len]; slen = strlen(tmp); m_pluginInfo[j]->moreinfourl = new char[ slen + 1 ]; strcpy(m_pluginInfo[j]->moreinfourl, tmp); len += slen + 1; } if (sz != len) // sanity check cerr << "PARENT: sz not = len in PLUGINS " << sz << " " << len << endl; } break; case CONTACTING: { int len = strlen((const char *)buf); if (sz == len + 1) onContacting((const char *)buf); else cerr << "PARENT: sz not right in CONTACTING sz=" << sz << endl; } break; case BUFFERING: { unsigned long percent; if (sz == sizeof(unsigned long)) { memcpy((void *)&percent, (void *) buf, sizeof(unsigned long)); onBuffering(percent); } else cerr << "PARENT: sz not right in BUFFERING sz=" << sz << endl; } break; case NOTIFYUSER: { int len1, len2; unsigned long code; const char *moreinfo, *moreinfourl; memcpy((void *) &code, (void *) buf, sizeof(unsigned long)); moreinfo = (const char *) &buf[sizeof(unsigned long)]; len1 = strlen(moreinfo); moreinfourl = (const char *) &moreinfo[ len1 + 1]; len2 = strlen(moreinfourl); // sanity check if ((unsigned) sz != sizeof(unsigned long) + len1 + len2 + 2) cerr << "PARENT: sz not right in NOTIFYUSER sz=" << sz << endl; notifyUser(code, moreinfo, moreinfourl); } break; case INTERRUPTUSER: { int len1, len2; unsigned long code; const char *moreinfo, *moreinfourl; memcpy((void *) &code, (void *) buf, sizeof(unsigned long)); moreinfo = (const char *) &buf[sizeof(unsigned long)]; len1 = strlen(moreinfo); moreinfourl = (const char *) &moreinfo[ len1 + 1]; len2 = strlen(moreinfourl); // sanity check if ((unsigned) sz != sizeof(unsigned long) + len1 + len2 + 2) cerr << "PARENT: sz not right in INTERRUPTUSER sz=" << sz << endl; interruptUser(code, moreinfo, moreinfourl); } break; case SCOPEBUF: { DelayQueue *item; int bufsz, len = 0; memcpy( (void *) &bufsz, (void *) buf, sizeof(int) ); len += sizeof(int); if ((int) (bufsz + 2 * sizeof(unsigned long) + 4 * sizeof(int) + sizeof(double)) == sz) { item = new DelayQueue(bufsz); memcpy( (void *) &item->time, (void *) &buf[len], sizeof(unsigned long) ); len += sizeof(unsigned long); memcpy( (void *) &item->etime, (void *) &buf[len], sizeof(unsigned long) ); len += sizeof(unsigned long); memcpy( (void *) &item->nchan, (void *) &buf[len], sizeof(int) ); len += sizeof(int); memcpy( (void *) &item->bps, (void *) &buf[len], sizeof(int) ); len += sizeof(int); memcpy( (void *) &item->tps, (void *) &buf[len], sizeof(double) ); len += sizeof(double); memcpy( (void *) &item->spb, (void *) &buf[len], sizeof(int) ); len += sizeof(int); memcpy( (void *) item->buf, (void *) &buf[len], item->len ); len += item->len; addScopeBuf(item, i); } else cerr << "PARENT: sz not right in SCOPEBUF sz=" << sz << endl; } break; default: print2stderr("PARENT recvd unhandled message %d\n", m); break; } } else { m_children[i].isdead = true; return; // should never happen } } } for (i=0; pmapped && igetMimeListLen(); const MimeList *mimelisthead = player->getMimeList(); memcpy( (void *) buf, (void *) &mimelistlen, sizeof(mimelistlen) ); sz = sizeof(mimelistlen); while (mimelisthead) { slen = strlen(mimelisthead->mimetypes); memcpy( (void *) &buf[sz], (void *) mimelisthead->mimetypes, slen + 1 ); // expecting the null sz += slen + 1; slen = strlen(mimelisthead->mimeexts); memcpy( (void *) &buf[sz], (void *) mimelisthead->mimeexts, slen + 1 ); // expecting the null sz += slen + 1; mimelisthead = mimelisthead->fwd; } return sendmessage(fd, MIMETYPES, buf, sz); } bool PlayerControl::sendscopebuf(int fd, DelayQueue *item) { unsigned char buf[65536]; int len = 0; if (len + 2 * sizeof(unsigned long) + 4 * sizeof(int) + sizeof(double) < 65536) { memcpy( (void *) buf, (void *) &item->len, sizeof(int) ); len += sizeof(int); memcpy( (void *) &buf[len], (void *) &item->time, sizeof(unsigned long) ); len += sizeof(unsigned long); memcpy( (void *) &buf[len], (void *) &item->etime, sizeof(unsigned long) ); len += sizeof(unsigned long); memcpy( (void *) &buf[len], (void *) &item->nchan, sizeof(int) ); len += sizeof(int); memcpy( (void *) &buf[len], (void *) &item->bps, sizeof(int) ); len += sizeof(int); memcpy( (void *) &buf[len], (void *) &item->tps, sizeof(double) ); len += sizeof(double); memcpy( (void *) &buf[len], (void *) &item->spb, sizeof(int) ); len += sizeof(int); memcpy( (void *) &buf[len], (void *) item->buf, item->len ); len += item->len; return sendmessage(fd, SCOPEBUF, buf, len); } return false; }