// Audio stream type // FIXME these are MVP specific (probably!)
const static UCHAR MPEG2_PES = 2;
const static UCHAR MPEG1_PES = 3; // unused
+ const static UCHAR MP3 = 0; //media player
#ifdef DEV
virtual int test()=0;
--- /dev/null
+/*
+ Copyright 2004-2006 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "audioplayer.h"
+#include "vaudioplayer.h"
+#include "demuxeraudio.h"
+#include "timers.h"
+
+AudioPlayer * AudioPlayer::instance=NULL;
+
+AudioPlayer * AudioPlayer::getInstance(View * parent,bool create) {
+// Log::getInstance()->log("AudioPlayer",Log::DEBUG,"getInstance for view %p, instance=%p",parent,instance);
+ AudioPlayer *np=instance;
+ if (! np && ! create) return NULL;
+ if (! np) {
+ np=new AudioPlayer(parent);
+ np->run();
+ instance=np;
+ }
+ instance->threadLock();
+ instance->frontend=parent;
+ instance->threadUnlock();
+ return instance;
+}
+
+AudioPlayer::AudioPlayer(View *parent) : afeed(this)
+{
+ frontend=parent;
+ audio = Audio::getInstance();
+ logger = Log::getInstance();
+ vdr = VDR::getInstance();
+ logger->log("AudioPlayer", Log::DEBUG, "Audio player ctorI");
+ running=true;
+ playerRunnig=false;
+ streampos = 0;
+ bytesWritten=0;
+ state = S_STOP;
+ requestState=S_STOP;
+ feederState=FEEDER_STOP;
+
+ threadBuffer = NULL;
+
+ Video::getInstance()->turnVideoOff();
+ requestedSequence=0;
+ sequence=0;
+ playSequence=0;
+ currentPlaySequence=-1;
+ filename=NULL;
+ demuxer=new DemuxerAudio();
+ logger->log("AudioPlayer", Log::DEBUG, "Audio player ctorII");
+ if (!demuxer->init(this, audio, NULL, 0, DemuxerAudio::PACKET_SIZE+200))
+ {
+ logger->log("AudioPlayer", Log::ERR, "Demuxer failed to init");
+ shutdown();
+ return ;
+ }
+ logger->log("AudioPlayer", Log::DEBUG, "Audio player ctorIII");
+ afeed.init();
+ audio->reset();
+ lenInBytes=0;
+ logger->log("AudioPlayer", Log::DEBUG, "Audio player created");
+}
+
+AudioPlayer::~AudioPlayer()
+{
+ if (threadBuffer) free(threadBuffer);
+ Timers::getInstance()->cancelTimer(this,1);
+ controlFeeder(FEEDER_STOP);
+ audio->reset();
+ audio->setStreamType(Audio::MPEG2_PES);
+ delete demuxer;
+ demuxer=NULL;
+ delete filename;
+}
+
+void AudioPlayer::controlFeeder(int feederAction) {
+ logger->log("AudioPlayer",Log::DEBUG,"control feeder old=%d, new=%d",feederState,feederAction);
+ switch (feederAction) {
+ case FEEDER_START:
+ case FEEDER_UNPAUSE:
+ if (feederState == FEEDER_STOP)
+ afeed.start();
+ afeed.enable();
+ break;
+ case FEEDER_STOP:
+ if (feederState != FEEDER_STOP)
+ afeed.stop();
+ break;
+ case FEEDER_PAUSE:
+ afeed.disable();
+ break;
+ }
+ feederState=feederAction;
+}
+
+
+void AudioPlayer::run() {
+ if (playerRunnig) return;
+ playerRunnig=true;
+ threadStart();
+}
+
+
+void AudioPlayer::shutdown()
+{
+ running=false;
+ if (playerRunnig) {
+ threadSignalNoLock();
+ //wait for the player thread to stop
+ logger->log("AudioPlayer",Log::DEBUG,"shutdown - warting for player thread to stop");
+ //at most wait 10s
+ for (int loopcount=200;playerRunnig && loopcount > 0;loopcount--) {
+ MILLISLEEP(50);
+ }
+ if (playerRunnig) {
+ logger->log("AudioPlayer",Log::ERR,"shutdown - unable to stop player within 10s");
+ }
+ }
+ instance=NULL;
+ delete this;
+}
+
+bool AudioPlayer::isPlayerRunning() {
+ return playerRunnig;
+}
+
+int AudioPlayer::setRequestedState(UCHAR rstate) {
+ int rt=0;
+ threadLock();
+ requestState=rstate;
+ requestedSequence++;
+ rt=requestedSequence;
+ threadUnlock();
+ return rt;
+}
+
+
+UCHAR AudioPlayer::getState() {
+ UCHAR rt=0;
+ threadLock();
+ rt=state;
+ threadUnlock();
+ return rt;
+}
+
+void AudioPlayer::setState(UCHAR s) {
+ threadLock();
+ state=s;
+ threadUnlock();
+}
+
+//------------------- externally called functions --------------------------
+int AudioPlayer::play(const char * fn)
+{
+ logger->log("AudioPlayer", Log::DEBUG, "play request for %s", fn);
+ int rt=0;
+ threadLock();
+ if (filename) delete filename;
+ filename=new char[strlen(fn)+1];
+ strcpy(filename,fn);
+ requestState=S_PLAY;
+ requestedSequence++;
+ rt=requestedSequence;
+ playSequence++;
+ threadUnlock();
+ threadSignalNoLock();
+ return rt;
+}
+
+int AudioPlayer::stop()
+{
+ int rt= setRequestedState(S_STOP);
+ threadSignalNoLock();
+ return rt;
+}
+
+int AudioPlayer::pause()
+{
+ int rt= setRequestedState(S_PAUSE);
+ threadSignalNoLock();
+ return rt;
+}
+int AudioPlayer::unpause()
+{
+ int rt= setRequestedState(S_PLAY);
+ threadSignalNoLock();
+ return rt;
+}
+
+int AudioPlayer::fastForward(){
+ int rt=setRequestedState(S_FF);
+ threadSignalNoLock();
+ return rt;
+}
+
+int AudioPlayer::fastBackward(){
+ int rt=setRequestedState(S_BACK);
+ threadSignalNoLock();
+ return rt;
+}
+int AudioPlayer::jumpToPercent(double percent){
+ threadLock();
+ ULONG fsec=demuxer->getSecondsFromLen(lenInBytes);
+ ULONG npos=streampos;
+ if (fsec != 0) {
+ fsec=(ULONG)(((double)fsec*percent)/(double)100);
+ npos=demuxer->positionFromSeconds(fsec);
+ logger->log("AudioPlayer",Log::DEBUG,"new pos %ld from demux",npos);
+ }
+ if (npos == 0) {
+ //the demuxer cannot help us
+ npos=(ULONG)(((double)lenInBytes*percent)/(double)100);
+ logger->log("AudioPlayer",Log::DEBUG,"new pos %ld without demux",npos);
+ }
+ if (npos > lenInBytes) npos=lenInBytes-1;
+ requestedStreampos=npos;
+ requestState=S_POSITION;
+ requestedSequence++;
+ threadUnlock();
+ //no need to wait here...
+ return 0;
+}
+
+int AudioPlayer::skipForward(int seconds) {
+ threadLock();
+ ULONG curr=demuxer->getSecondsFromLen(streampos);
+ ULONG dest=demuxer->positionFromSeconds(curr+(UINT)seconds);
+ if (dest != 0) {
+ logger->log("AudioPlayer",Log::DEBUG,"new pos %ld skip %ds",dest,seconds);
+ requestedStreampos=dest;
+ }
+ requestState=S_POSITION;
+ requestedSequence++;
+ threadUnlock();
+ return 0;
+}
+int AudioPlayer::skipBackward(int seconds) {
+ threadLock();
+ ULONG curr=demuxer->getSecondsFromLen(streampos);
+ if (curr > (UINT)seconds) {
+ ULONG dest=demuxer->positionFromSeconds(curr-(UINT)seconds);
+ if (dest != 0) {
+ logger->log("AudioPlayer",Log::DEBUG,"new pos %ld skip %ds",dest,seconds);
+ requestedStreampos=dest;
+ requestState=S_POSITION;
+ requestedSequence++;
+ }
+ }
+ threadUnlock();
+ return 0;
+}
+
+
+
+// ----------------------------------- Internal functions
+
+
+void AudioPlayer::sendFrontendMessage(ULONG para)
+{
+ logger->log("AudioPlayer", Log::DEBUG, "sending frontend message %ld",para);
+ Message* m = new Message();
+ threadLock();
+ m->to = frontend;
+ threadUnlock();
+ m->from = this;
+ m->message = Message::PLAYER_EVENT;
+ m->parameter = para;
+ Command::getInstance()->postMessageFromOuterSpace(m);
+}
+
+void AudioPlayer::handleVDRerror(){
+ if (!vdr->isConnected())
+ {
+ logger->log("AudioPlayer", Log::ERR, "disconnect");
+ sendFrontendMessage(CONNECTION_LOST);
+ setState(S_ERROR);
+ }
+}
+//open a new file
+//called within the thread!
+int AudioPlayer::openFile() {
+ char * fn=NULL;
+ threadLock();
+ if (filename) {
+ fn=new char[strlen(filename)+1];
+ strcpy(fn,filename);
+ }
+ threadUnlock();
+ demuxer->reset();
+ streampos=0;
+ bytesWritten=0;
+ lenInBytes=vdr->loadImage(fn,0,0);
+ Log::getInstance()->log("Audioplayer", Log::DEBUG, "request file rt=%d file=%s",lenInBytes,fn);
+ handleVDRerror();
+ if (lenInBytes <= 0) {
+ return 1;
+ }
+ UINT rsize=0;
+ UCHAR *idbuf=vdr->getImageBlock(0,demuxer->headerBytes(),&rsize);
+ handleVDRerror();
+ if (rsize < demuxer->headerBytes() || idbuf == NULL) {
+ if (idbuf) free(idbuf);
+ Log::getInstance()->log("VAudioplayer", Log::DEBUG, "unable to get header for file %s",fn);
+ return 0;
+ }
+ threadLock();
+ int hdrpos=demuxer->checkStart(idbuf,rsize);
+ threadUnlock();
+ if (hdrpos >= 0) {
+ streampos=hdrpos;
+ }
+ if (idbuf) free(idbuf);
+ idbuf=NULL;
+ if (demuxer->getId3Tag() == NULL) {
+ //OK - look at the end
+ idbuf=vdr->getImageBlock(lenInBytes-demuxer->footerBytes(),demuxer->footerBytes(),&rsize);
+ handleVDRerror();
+ if (rsize < demuxer->footerBytes() || idbuf == NULL) {
+ if (idbuf) free(idbuf);
+ Log::getInstance()->log("VAudioplayer", Log::DEBUG, "unable to get footer for file %s",fn);
+ return 0;
+ }
+ threadLock();
+ hdrpos=demuxer->checkID3(idbuf,rsize);
+ threadUnlock();
+ if (hdrpos < 0) {
+ Log::getInstance()->log("VAudioplayer", Log::DEBUG, "no ID3 in footer for file %s",fn);
+ }
+ free(idbuf);
+ }
+ return 0;
+}
+//method called by the playing thread to handle
+//"commands" by the frontend
+UCHAR AudioPlayer::checkState()
+{
+ threadLock();
+ UCHAR rstate=requestState;
+ UCHAR cstate=state;
+ int rseq=requestedSequence;
+ int cseq=sequence;
+ int fseq=playSequence;
+ threadUnlock();
+ //flag to decide which message to send
+ //to frontend
+ bool newFile=false;
+
+ if ( rseq > cseq) {
+ logger->log("AudioPlayer", Log::DEBUG, "Switch state from %u to %u", cstate, rstate);
+ switch(rstate)
+ {
+ case S_PAUSE:
+ if (cstate != S_PLAY && cstate != S_FF) rstate=cstate; //ignore request
+ else {
+ skipfactor=0;
+ demuxer->setSkipFactor(0);
+ audio->mute();
+ audio->pause();
+ }
+ break;
+ case S_PLAY: // to S_PLAY
+ skipfactor=0;
+ demuxer->setSkipFactor(0);
+ if (fseq != currentPlaySequence || cstate == S_STOP || cstate == S_ERROR || cstate == S_DONE) {
+ //this is a new play request interrupting the current
+ logger->log("AudioPlayer", Log::DEBUG, "replay from start fseq=%d, startseq=%d", fseq, currentPlaySequence);
+ threadLock();
+ playSequence=fseq;
+ threadUnlock();
+ currentPlaySequence=fseq;
+ newFile=true;
+ if (cstate != S_DONE) audio->mute();
+ int rt=openFile();
+ if (rt != 0) {
+ rstate=S_ERROR;
+ }
+ else {
+ audio->unPause();
+ if (cstate != S_DONE) {
+ //flush only if we are not coming from stream end
+ thisRead=0;
+ thisWrite=0;
+ if (threadBuffer) free(threadBuffer);
+ threadBuffer=NULL;
+ controlFeeder(FEEDER_STOP);
+ demuxer->flush();
+ controlFeeder(FEEDER_START);
+ audio->reset();
+ audio->systemMuteOff();
+ audio->setStreamType(Audio::MP3);
+ }
+ audio->unMute();
+ audio->play();
+ }
+ }
+ else if (cstate == S_PAUSE){
+ newFile=true;
+ audio->unPause();
+ audio->unMute();
+ }
+ else if (cstate == S_FF || S_BACK) {
+ ;
+ }
+ else {
+ rstate=cstate;
+ }
+ break;
+ case S_DONE:
+ Timers::getInstance()->setTimerD(this,1,4);
+ //inform the frontend
+ break;
+ case S_FF:
+ if (cstate != S_PLAY && cstate != S_PAUSE && cstate != S_FF) {
+ rstate=cstate;
+ }
+ else {
+ if (skipfactor == 0) skipfactor=2;
+ else skipfactor=skipfactor<<1;
+ if (skipfactor > 16 ) skipfactor=2;
+ demuxer->setSkipFactor(skipfactor);
+ }
+ if (cstate == S_PAUSE) {
+ audio->unPause();
+ audio->unMute();
+ }
+ break;
+ case S_POSITION:
+ if (cstate != S_PLAY && cstate != S_PAUSE) {
+ rstate=cstate;
+ }
+ else {
+ audio->mute();
+ audio->unPause();
+ controlFeeder(FEEDER_STOP);
+ demuxer->flush();
+ thisRead=0;
+ thisWrite=0;
+ if (threadBuffer) free(threadBuffer);
+ threadBuffer=NULL;
+ streampos=requestedStreampos;
+ bytesWritten=streampos;
+ audio->reset();
+ audio->setStreamType(Audio::MP3);
+ controlFeeder(FEEDER_START);
+ audio->unMute();
+ audio->play();
+ rstate=S_PLAY;
+ }
+ break;
+ default: // to S_STOP
+ rstate=S_STOP;
+ audio->mute();
+ audio->stop();
+ audio->unPause();
+ controlFeeder(FEEDER_STOP);
+ demuxer->flush();
+ thisRead=0;
+ thisWrite=0;
+ if (threadBuffer) free(threadBuffer);
+ threadBuffer=NULL;
+ logger->log("AudioPlayer", Log::DEBUG, "stop handled fseq: %d startseq %d completed", playSequence, currentPlaySequence);
+ break;
+ }
+ threadLock();
+ state=rstate;
+ sequence=rseq;
+ threadUnlock();
+ if (newFile) sendFrontendMessage(NEW_SONG);
+ else if (cstate != rstate && rstate != S_DONE ) {
+ sendFrontendMessage(STATUS_CHANGE);
+ //any change after done cancels the "done" timer
+ Timers::getInstance()->cancelTimer(this,1);
+ }
+ logger->log("AudioPlayer", Log::DEBUG, "Switch state from %u to %u completed nf=%s", cstate, rstate,newFile?"true":"false");
+ //we return the newly set state
+ return rstate;
+ }
+ //rstate could be different but no new request - so return cstate
+ return cstate;
+}
+
+
+
+
+// ----------------------------------- Feed thread
+
+void AudioPlayer::waitTimed(int ms) {
+ threadLock();
+ struct timeval ct;
+ gettimeofday(&ct,NULL);
+ struct timespec nt;
+ int sec=ms/1000;
+ int us=1000*(ms - 1000*sec);
+ nt.tv_sec=ct.tv_sec+sec;
+ nt.tv_nsec=1000*us+1000*ct.tv_usec;
+ threadWaitForSignalTimed(&nt);
+ threadUnlock();
+}
+
+void AudioPlayer::threadMethod()
+{
+ logger->log("AudioPlayer", Log::DEBUG, "player thread started");
+ thisWrite=0;
+ thisRead=0;
+ while(1)
+ {
+ UCHAR cstate=checkState();
+ if (! running) {
+ break;
+ }
+ if (cstate != S_PLAY && cstate != S_FF && cstate != S_BACK) {
+ waitTimed(500);
+ continue;
+ }
+ threadCheckExit();
+
+ if (thisWrite == thisRead) {
+ //TODO: use normal blocks...
+ thisRead=0;
+ thisWrite=0;
+ threadBuffer = vdr->getImageBlock(streampos, BUFLEN , &thisRead);
+ handleVDRerror();
+ if (!threadBuffer || thisRead == 0) {
+ //OK we count this as end of stream
+ //hmm --- we should be able to detect if the audio has gone...
+ logger->log("AudioPlayer", Log::DEBUG, "stream end");
+ setRequestedState(S_DONE);
+ continue;
+ }
+ //logger->log("AudioPlayer", Log::DEBUG, "read %ld bytes at pos %ld",thisRead,streampos);
+ streampos+=thisRead;
+ }
+
+ threadCheckExit();
+ /*
+ MediaPacket p;
+ memset(&p,sizeof(p),0);
+ p.pos_buffer=0;
+ p.length=thisRead;
+ MediaPacketList pl;
+ pl.push_back(p);
+ audio->PrepareMediaSample(pl,0);
+ UINT bytesWritten=0;
+ UINT rt=audio->DeliverMediaSample(threadBuffer,&bytesWritten);
+ ULONG written=thisRead;
+ if (rt == 0)
+ written=bytesWritten;
+ */
+ ULONG written= demuxer->put(threadBuffer + thisWrite, thisRead - thisWrite);
+ thisWrite+=written;
+ bytesWritten+=written;
+ if (thisWrite < thisRead) {
+ if (written == 0) {
+ // demuxer is full and can't take anymore
+ waitTimed(200);
+ }
+ }
+ else {
+ //logger->log("AudioPlayer", Log::DEBUG, "block written %d", thisWrite);
+ thisWrite=0;
+ thisRead=0;
+ free(threadBuffer);
+ threadBuffer = NULL;
+ }
+
+ }
+
+ logger->log("AudioPlayer", Log::DEBUG, "finished");
+ playerRunnig=false;
+ return;
+}
+
+int AudioPlayer::waitForSequence(int timeout, int seq) {
+ time_t starttime=time(NULL)+timeout;
+ time_t curtime=0;
+ logger->log("AudioPlayer", Log::DEBUG, "waiting for sequence %d",seq);
+ while ((curtime=time(NULL)) < starttime) {
+ int cseq=getSequence();
+ if (cseq >= seq) return cseq;
+ MILLISLEEP(10);
+ }
+ return -1;
+}
+
+int AudioPlayer::getSequence() {
+ int rt=0;
+ threadLock();
+ rt=sequence;
+ threadUnlock();
+ return rt;
+}
+
+
+
+void AudioPlayer::threadPostStopCleanup()
+{
+ if (threadBuffer)
+ {
+ delete(threadBuffer);
+ threadBuffer = NULL;
+ }
+ playerRunnig=false;
+}
+
+void AudioPlayer::call(void *) {
+ threadSignalNoLock();
+}
+
+void AudioPlayer::timercall(int ref) {
+ if (ref == 1) {
+ logger->log("AudioPlayer", Log::DEBUG, "stream end - informing frontend");
+ sendFrontendMessage(STREAM_END);
+ }
+}
+
+//--------------------------- info functions -------------------
+
+char * AudioPlayer::getTitle() {
+ logger->log("AudioPlayer", Log::DEBUG, "getTitle");
+ threadLock();
+ const id3_tag * tag=demuxer->getId3Tag();
+ const char * title=NULL;
+ char *rt=NULL;
+ if (tag != NULL) {
+ title=tag->title;
+ }
+ if (title && strlen(title) != 0) {
+ rt=new char[strlen(title)+1];
+ strcpy(rt,title);
+ rt[strlen(title)]=0;
+ }
+ //let the frontend fill in something
+ threadUnlock();
+ return rt;
+}
+
+char * AudioPlayer::getID3Info() {
+ logger->log("AudioPlayer", Log::DEBUG, "getID3Info");
+ threadLock();
+ int len=0;
+ const id3_tag * tag=demuxer->getId3Tag();
+ int taglen=0;
+ if (tag) taglen=tag->stringlen(false);
+ len+=taglen;
+ const DemuxerAudio::mpegInfo *info=demuxer->getMpegInfo();
+ if (info) len+=30;
+ char * rt=NULL;
+ if (len > 0) {
+ char bitrateType='C';
+ if (info && info->avrBitrate != info->bitRate) bitrateType='V';
+ rt=new char[len];
+ if (!tag && info) {
+ snprintf(rt,len-1,"%s: %s/L%d %cBR,SR=%dk,%s\n",tr("MpegInfo"),
+ info->mpegVersion,info->mpegLayer,bitrateType,info->sampleRate/1000,
+ info->info);
+ }
+ else if (tag && info){
+ char tmp[taglen+1];
+ snprintf(rt,len-1,"%s\n"
+ "%s: %s/L%d %cBR,SR=%dk,%s\n",
+ tag->toString(tmp,taglen,false),
+ tr("MpegInfo"),
+ info->mpegVersion,info->mpegLayer,bitrateType,info->sampleRate/1000,
+ info->info);
+ }
+ else if (tag && !info){
+ char tmp[taglen+1];
+ snprintf(rt,len-1,"%s\n",
+ tag->toString(tmp,taglen,false));
+ }
+ rt[len-1]=0;
+ }
+ threadUnlock();
+ logger->log("AudioPlayer", Log::DEBUG, "getID3Info returns %s",rt);
+ return rt;
+}
+
+ULONG AudioPlayer::getCurrentTimes(){
+ ULONG rt=0;
+ threadLock();
+ if (streampos != 0){
+ rt=demuxer->getSecondsFromLen(bytesWritten);
+ if (rt == 0) {
+ //we can only guess
+ rt= bytesWritten/DEFAULT_BITRATE;
+ }
+ }
+ threadUnlock();
+ return rt;
+}
+
+ULONG AudioPlayer::getSonglen(){
+ ULONG rt=0;
+ threadLock();
+ const DemuxerAudio::vbrInfo * vbr=demuxer->getVBRINfo();
+ if (vbr) rt=vbr->fileSeconds;
+ else {
+ if (lenInBytes != 0) {
+ rt=demuxer->getSecondsFromLen(lenInBytes);
+ if (rt == 0) {
+ //we can only guess
+ rt= lenInBytes/DEFAULT_BITRATE;
+ }
+ }
+ }
+ threadUnlock();
+ return rt;
+}
+
+int AudioPlayer::getCurrentBitrate(){
+ int rt=DEFAULT_BITRATE;
+ threadLock();
+ const DemuxerAudio::mpegInfo *info=demuxer->getMpegInfo();
+ if (info) rt=info->bitRate;
+ threadUnlock();
+ return rt;
+}
--- /dev/null
+/*
+ Copyright 2004-2006 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef AUDIOPLAYER_H
+#define AUDIOPLAYER_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifndef WIN32
+#include <sys/time.h>
+#endif
+#include <time.h>
+
+#include "audio.h"
+#include "remote.h"
+#include "vdr.h"
+#include "callback.h"
+#include "message.h"
+#include "messagequeue.h"
+#include "thread.h"
+#include "afeed.h"
+#include "timerreceiver.h"
+
+#ifdef WIN32
+#include "threadwin.h"
+#else
+#include "threadp.h"
+#endif
+
+
+
+class View;
+class DemuxerAudio;
+
+
+class AudioPlayer : public Thread_TYPE, public Callback, public TimerReceiver
+{
+ public:
+ //the instance method to create the
+ //player instance - only call thie for
+ //the first time in the main thread (no lock!)
+ //later you can change the view the player is connect with
+ //a view that "goes" should call getInstance(NULL,false) to detach
+ //the last media view should shutdown the player
+ static AudioPlayer * getInstance(View * frontend, bool create=true);
+ //start the player thread
+ void run();
+
+ //is the player still running?
+ bool isPlayerRunning();
+
+ void shutdown();
+
+ //each of the commands works as a request
+ //only after getSequence returned the same sequence as those commands this is
+ //handled by the player and getError is valid
+ int play(const char * filename);
+ //stop the player without shutting it down
+ int stop();
+ int pause();
+ int unpause();
+ int fastForward();
+ int fastBackward();
+ int jumpToPercent(double percent);
+ int skipForward(int sec);
+ int skipBackward(int sec);
+
+ //info functions for frontend
+ //delete provided String afterwards
+ //get the played title from ID3 Tag - or NULL
+ char * getTitle();
+ //get info
+ //multi line String containing ID3 infos
+ char * getID3Info();
+
+ //wait for a particular sequence to be handled
+ //timeout in s, returnes current sequence, -1 on timeout
+ int waitForSequence(int timeout, int sequence);
+ int getSequence();
+
+ //info functions
+
+ //get current position in s
+ ULONG getCurrentTimes();
+ //get song len in s
+ ULONG getSonglen();
+ //current bitrate
+ int getCurrentBitrate();
+
+
+ virtual void call(void * caller);
+
+ UCHAR getState() ;
+
+ const static UCHAR S_PLAY = 1;
+ const static UCHAR S_PAUSE = 2;
+ const static UCHAR S_POSITION = 3;
+ //player finished a song - no reset, next will follow
+ const static UCHAR S_DONE=5;
+ const static UCHAR S_STOP = 6;
+ const static UCHAR S_ERROR = 8;
+ const static UCHAR S_FF = 9;
+ const static UCHAR S_BACK = 10;
+
+ //message parameters for frontend messages
+ const static ULONG CONNECTION_LOST=1;
+ const static ULONG STREAM_END=2;
+ const static ULONG STREAM_ERR=3;
+ const static ULONG STATUS_CHANGE=4; //some info has been changed
+ const static ULONG NEW_SONG=5; //some info has been changed
+ const static ULONG SHORT_UPDATE=6; //timer info update
+
+ virtual void timercall(int reference);
+
+ protected:
+ void threadMethod();
+ void threadPostStopCleanup();
+
+ private:
+ //to guess lengthes if the demux does not know
+ const static ULONG DEFAULT_BITRATE=128000;
+ AudioPlayer(View *frontend);
+ virtual ~AudioPlayer();
+ static AudioPlayer * instance;
+ bool playerRunnig;
+ Audio* audio;
+ VDR* vdr;
+ Log* logger;
+
+ DemuxerAudio *demuxer;
+ AFeed afeed;
+
+ //feeder control
+ const static int FEEDER_START=1;
+ const static int FEEDER_STOP=2;
+ const static int FEEDER_PAUSE=3;
+ const static int FEEDER_UNPAUSE=4;
+ void controlFeeder(int action) ;
+ int feederState;
+
+ //synchronized get/set methods for states
+ int setRequestedState(UCHAR st);
+ void setState(UCHAR st);
+
+ //to be called from within the thread
+ UCHAR checkState();
+
+ //variables used by the thread
+ UINT thisWrite;
+ UINT thisRead;
+ bool running;
+
+ UCHAR *threadBuffer;
+ UCHAR state;
+ UCHAR requestState;
+ ULONG streampos;
+ ULONG lenInBytes;
+ ULONG bytesWritten;
+ ULONG requestedStreampos;
+
+ int skipfactor;
+ //the buffer len in bytes
+ const static int BUFLEN=50*1024;
+ View *frontend;
+ //requested sequence
+ int requestedSequence;
+ //handled sequence
+ int sequence;
+ //startplay sequence
+ int currentPlaySequence;
+ //sequence that is changed for each new filename
+ int playSequence;
+
+ char * filename;
+ int openFile();
+ void handleVDRerror();
+
+ void sendFrontendMessage(ULONG para);
+
+ void waitTimed(int ms);
+
+
+};
+
+#endif
+
--- /dev/null
+/*
+ Copyright 2006 Mark Calderbank, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "demuxeraudio.h"
+#include "audio.h"
+#include "i18n.h"
+
+#define HDRBYTE1 0xff
+#define HDRBYTE2 0xe0
+#define HDRBYTE2MASK 0xe0
+
+
+
+class PacketBuffer {
+
+ public:
+ PacketBuffer(Stream *as,UCHAR strtype) {
+ log=Log::getInstance();
+ audio=as;
+ streamtype=strtype;
+ newStream();
+ }
+ //just handle the data (do not deal with headers)
+ int putInternal(UCHAR* buf,int len);
+ void reset(){
+ partPacket=0;
+ bytesWritten=0;
+ framelen=DemuxerAudio::PACKET_SIZE;
+ }
+ void newStream() {
+ reset();
+ numpackets=0;
+ numbytes=0;
+ skipfactor=0;
+ numskip=0;
+ }
+ bool bufferFull() {
+ return (partPacket>=framelen);
+ }
+ //can we write a new packet?
+ bool bufferEmpty() {
+ return partPacket==0;
+ }
+ //only set this, if buffer empty
+ //otherwise ignored!
+ bool setFramelen(int len) {
+ if (! bufferEmpty() ) return false;
+ if (len > (int)DemuxerAudio::PACKET_SIZE) return false;
+ framelen=len;
+ return true;
+ }
+ //how much bytes do we need to fill the packet?
+ int bytesMissing() {
+ return framelen-partPacket;
+ }
+ int getFramelen() {
+ return framelen;
+ }
+ void setSkipFactor(int factor) {
+ skipfactor=factor;
+ numskip=0;
+ }
+ private:
+ void packetWritten() {
+ numbytes+=framelen;
+ //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"written packet %ld l=%d, bytes %ld",numpackets,framelen,numbytes);
+ numpackets++;
+ reset();
+ }
+ bool doSkip();
+ UCHAR store[DemuxerAudio::PACKET_SIZE]; // Storage for partial packets
+ int partPacket; // Length of partial packet stored from previous put()
+ int bytesWritten; //if they are !=0 and != framelength the stream is full...
+ int framelen;
+ Log * log;
+ Stream * audio;
+ UCHAR streamtype;
+ //global counters
+ ULONG numpackets;
+ ULONG numbytes;
+ int skipfactor;
+ int numskip;
+};
+
+
+DemuxerAudio::DemuxerAudio(int p_vID, int p_aID)
+{
+ inSync=false;
+ isStarting=true;
+ log=Log::getInstance();
+ readHeaders=0;
+ streamtype=Audio::MP3;
+ buffer=new PacketBuffer(&audiostream,streamtype);
+// buffer=new PacketBuffer(&teststream,streamtype);
+ globalBytesWritten=0;
+ id3=NULL;
+ info=NULL;
+ vbr=NULL;
+ reset();
+}
+
+DemuxerAudio::~DemuxerAudio() {
+ delete buffer;
+ if(info) delete info;
+ if(id3) delete id3;
+ if (vbr) delete vbr;
+}
+
+void DemuxerAudio::flush()
+{
+ Demuxer::flushAudio();
+ buffer->newStream();
+ tmpFill=0;
+}
+
+void DemuxerAudio::reset() {
+ buffer->newStream();
+ tmpFill=0;
+ readHeaders=0;
+ outOfSync=0;
+ globalBytesWritten=0;
+ if (id3) delete id3;
+ id3=NULL;
+ if (info) delete info;
+ info=NULL;
+ if (vbr) delete vbr;
+ vbr=NULL;
+ inSync=false;
+ hasHdrInfo=false;
+ hasVBRInfo=false;
+ isStarting=true;
+ hdrBitrate=128000;
+ hdrSamplingRate=44100;
+ avrBitrate=0;
+ hdrFramelen=0;
+ isStarting=true;
+}
+
+int DemuxerAudio::scan(UCHAR *buf, int len)
+{
+ //no differend pids here
+ return 0;
+}
+
+void DemuxerAudio::setVID(int p_vID)
+{
+}
+
+void DemuxerAudio::setAID(int p_aID)
+{
+}
+
+static char * id3_1_genre[] = {
+ "Blueshhh",
+ "Classic Rock",
+ "Country",
+ "Dance",
+ "Disco",
+ "Funk",
+ "Grunge",
+ "Hip-Hop",
+ "Jazz",
+ "Metal",
+ "New Age",
+ "Oldies",
+ "Other",
+ "Pop",
+ "R&B",
+ "Rap",
+ "Reggae",
+ "Rock",
+ "Techno",
+ "Industrial",
+ "Alternative",
+ "Ska",
+ "Death Metal",
+ "Pranks",
+ "Soundtrack",
+ "Euro-Techno",
+ "Ambient",
+ "Trip-Hop",
+ "Vocal",
+ "Jazz+Funk",
+ "Fusion",
+ "Trance",
+ "Classical",
+ "Instrumental",
+ "Acid",
+ "House",
+ "Game",
+ "Sound Clip",
+ "Gospel",
+ "Noise",
+ "AlternRock",
+ "Bass",
+ "Soul",
+ "Punk",
+ "Space",
+ "Meditative",
+ "Instrumental Pop",
+ "Instrumental Rock",
+ "Ethnic",
+ "Gothic",
+ "Darkwave",
+ "Techno-Industrial",
+ "Electronic",
+ "Pop-Folk",
+ "Eurodance",
+ "Dream",
+ "Southern Rock",
+ "Comedy",
+ "Cult",
+ "Gangsta",
+ "Top 40",
+ "Christian Rap",
+ "Pop/Funk",
+ "Jungle",
+ "Native American",
+ "Cabaret",
+ "New Wave",
+ "Psychadelic",
+ "Rave",
+ "Showtunes",
+ "Trailer",
+ "Lo-Fi",
+ "Tribal",
+ "Acid Punk",
+ "Acid Jazz",
+ "Polka",
+ "Retro",
+ "Musical",
+ "Rock & Roll",
+ "Hard Rock"
+};
+
+
+
+static int bitrateTable[16][5]={
+/* L1,L2,L3,2L1,2L2 */
+/*0000*/ {-1,-1,-1,-1,-1},
+/*0001*/ {32,32,32,32,8},
+/*0010*/ {64,48,40,48,16},
+/*0011*/ {96,56,48,56,24},
+/*0100*/ {128,64,56,64,32},
+/*0101*/ {160,80,64,80,40},
+/*0110*/ {192,96,80,96,48},
+/*0111*/ {224,112,96,112,56},
+/*1000*/ {256,128,112,128,64},
+/*1001*/ {288,160,128,144,80},
+/*1010*/ {320,192,160,160,96},
+/*1011*/ {352,224,192,176,112},
+/*1100*/ {384,256,224,192,128},
+/*1101*/ {416,320,256,224,144},
+/*1110*/ {448,384,320,256,160},
+/*1111*/ {-1,-1,-1,-1,-1} };
+
+static int samplingRateTable[4][3]={
+/*00*/ {44100,22050,11025},
+/*01*/ {48000,24000,12000},
+/*10*/ {32000,16000,8000},
+/*11*/ {-1,-1,-1}};
+
+//max 7 char!
+static const char * mpegString(UCHAR code) {
+ switch(code) {
+ case 0:
+ return "MPEG2.5";
+ case 1:
+ return "RESERV";
+ case 2:
+ return "MPEG 2";
+ case 3:
+ return "MPEG 1";
+ }
+ return "UNKNOWN";
+}
+
+static const char * layerString(UCHAR code) {
+ switch(code) {
+ case 0:
+ return "Layer reserved";
+ case 1:
+ return "Layer III";
+ case 2:
+ return "Layer II";
+ case 3:
+ return "Layer I";
+ }
+ return "Layer UNKNOWN";
+}
+/**
+ * parse an id3 Header
+ * provided by Brian Walton
+ * @returns -1 of nothing found
+ */
+
+int DemuxerAudio::id3_2_3_FrameParse(unsigned char buf[], id3_frame *frame)
+{
+ if (buf[0] < 0x20 || buf[1] < 0x20 || buf [2] < 0x20 ) return -1;
+ frame->size = (buf[4] & 0x7F) << 21 | (buf[5] & 0x7F) << 14 | (buf[6] & 0x7F) << 7 | (buf[7] & 0x7F);
+ if (frame->size == 0) return -1;
+ //TODO. clearify flags against:
+ //http://id3.org/id3v2.3.0#head-697d09c50ed7fa96fb66c6b0a9d93585e2652b0b
+ frame->flags.tagAlterPreserv = (buf[8] & 0x80) >> 7;
+ frame->flags.filelterPreserv = (buf[8] & 0x40) >> 6;
+ frame->flags.readOnly = (buf[8] & 0x20) >> 5;
+ frame->flags.groupId = (buf[9] & 0x20) >> 5;
+ frame->flags.compression = (buf[9] & 0x80) >> 7;
+ frame->flags.encryption = (buf[9] & 0x40) >> 6;
+ frame->flags.unsync = 0;
+ frame->flags.dataLen = 0;
+ return 0;
+}
+
+ /**
+ * parse an id3 Header
+ * provided by Brian Walton
+ * @returns -1 of nothing found
+ */
+
+int DemuxerAudio::id3_2_2_FrameParse(unsigned char buf[], id3_frame *frame)
+{
+ if (buf[0] < 0x20 || buf[1] < 0x20 || buf[2] < 0x20) return -1;
+ frame->size = (buf[3] & 0x7F) << 14 | (buf[4] & 0x7F) << 7 | (buf[5] & 0x7F);
+ if (frame->size == 0) return -1;
+ return 0;
+}
+
+
+//fill an id3tag from a frame payload
+//http://id3.org/id3v2.3.0#head-697d09c50ed7fa96fb66c6b0a9d93585e2652b0b
+//http://id3.org/id3v2-00
+static struct tagid {
+ const char * bytes;
+ int index;
+} knownFrames[]= {
+ //ID3V2.3
+ {"TIT2",1}, //title
+ {"TPE1",2}, //artist
+ {"TCON",3}, //genre
+ {"TRCK",6}, //track
+ {"TYER",4}, //year
+ {"TALB",5}, //album
+ {"TCOM",7}, //composer
+ {"COMM",8}, //comment
+ //Text encoding $xx
+ //Language $xx xx xx
+ //Short content descrip. <text string according to encoding> $00 (00)
+ //The actual text <full text string according to encoding>
+ //ID3V2.0
+ {"TT2",1 },
+ {"TP1",2 },
+ {"TCM",7 },
+ {"TCO",3 }, //(genreNumber)
+ {"TAL",5 },
+ {"TRK",6 },
+ {"TYE",4 },
+ {"COM",8 }
+};
+#define NUMKNOWN (sizeof(knownFrames)/sizeof(knownFrames[0]))
+
+/*fill in infos
+ from an ID3 V2.x, V2.3 frame into the tags structure
+ frameData must point to the header
+ framelen is the len without header (10 Bytes for V23, 6 Bytes for v2x)
+ */
+
+#define MAXLEN(tagtype) ((UINT)frameLen<sizeof(tag->tagtype)-1?(UINT)frameLen:sizeof(tag->tagtype)-1)
+bool DemuxerAudio::fillId3Tag(id3_tag * tag,UCHAR * frameData, int frameLen, int dataOffset, bool v23) {
+ int tl=v23?4:3;
+ int tagIndex=-1;
+ if (tag == NULL) return false;
+ if (frameLen < 2) return false;
+ for (UINT i=0;i< NUMKNOWN;i++) {
+ if(strncmp((char *)frameData,knownFrames[i].bytes,tl) == 0) {
+ tagIndex=knownFrames[i].index;
+ break;
+ }
+ }
+ if (tagIndex < 0) return false;
+ UCHAR encoding=*(frameData+dataOffset);
+ dataOffset++;
+ frameLen--;
+ if (encoding != 0) {
+ log->log("DemuxerAudio",Log::DEBUG,"unknown encoding for tag %d, tagid %s",encoding,
+ knownFrames[tagIndex].bytes);
+ return false;
+ }
+ switch(tagIndex) {
+ case 1: //title
+ strncpy(tag->title,(char*)(frameData+dataOffset),MAXLEN(title));
+ tag->title[MAXLEN(title)]=0;
+ break;
+ case 2: //artist
+ strncpy(tag->artist,(char*)(frameData+dataOffset),MAXLEN(artist));
+ tag->artist[MAXLEN(artist)]=0;
+ break;
+ case 3: //genre
+ {
+ UCHAR * st=frameData+dataOffset;
+ int genre=0;
+ if (*st=='(') {
+ genre=atoi((const char *)(st+1)) && 31;
+ st=(UCHAR *)id3_1_genre[genre];
+ }
+ strncpy(tag->genre,(char*)st,MAXLEN(genre));
+ tag->genre[MAXLEN(genre)]=0;
+ break;
+ }
+ case 4: //year
+ strncpy(tag->year,(char *)(frameData+dataOffset),MAXLEN(year));
+ tag->year[MAXLEN(year)]=0;
+ break;
+ case 5: //album
+ strncpy(tag->album,(char *)(frameData+dataOffset),MAXLEN(album));
+ tag->album[MAXLEN(album)]=0;
+ break;
+ case 6: //track
+ strncpy(tag->track,(char *)(frameData+dataOffset),MAXLEN(track));
+ tag->track[MAXLEN(track)]=0;
+ break;
+ case 7: //composer
+ strncpy(tag->composer,(char *)(frameData+dataOffset),MAXLEN(composer));
+ tag->composer[MAXLEN(composer)]=0;
+ break;
+ case 8: //comment
+ strncpy(tag->comment,(char *)(frameData+dataOffset),MAXLEN(comment));
+ tag->comment[MAXLEN(comment)]=0;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * parse an id3 Header
+ * based on code provided by Brian Walton
+ * @returns -1 of nothing found
+ * otherwise the id3 info is filled
+ */
+
+int DemuxerAudio::parseID3V2(UCHAR *data, int len) {
+ int debug=0;
+ UCHAR * start=data;
+ id3_header id3header;
+ id3_frame id3frame;
+ id3_tag * id3tag=NULL;
+ //len = read(fd, data, 10);
+ if (len < 10) {
+ delete id3tag;
+ return -1;
+ }
+ len-=10;
+ if(data[0]=='I' && data[1]=='D' && data[2]=='3')
+ {
+ id3tag=new id3_tag();
+ id3header.major = data[3];
+ id3header.minor = data[4];
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"ID3 V2.%d.%d found\n", id3header.major, id3header.minor);
+ id3header.flags.unsynchronisation = (data[5] & 0x80)>>7;
+ id3header.flags.extended_header = (data[5] & 0x40)>>6;
+ id3header.flags.experimental = (data[5] & 0x20)>>5;
+ id3header.flags.footer = (data[5] & 0x10)>>4;
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Unsynchronisation flag: %d\n", id3header.flags.unsynchronisation);
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Extended header flag: %d\n", id3header.flags.extended_header);
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Experimental indicator flag: %d\n", id3header.flags.experimental);
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Footer present flag: %d\n", id3header.flags.footer);
+ id3header.size = (data[6] & 0x7F) << 21 | (data[7] & 0x7F) << 14 | (data[8] & 0x7F) << 7 | (data[9] & 0x7F);
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"ID3 Size: %d\n", id3header.size);
+ data=start+10;
+ if (len <= id3header.size) {
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"header size to big %d, only %d bytes available\n",id3header.size,len);
+ delete id3tag;
+ return -1;
+ }
+ if (id3header.flags.extended_header)
+ {
+ int extended_hdr_hdr=4; //still to be discussed (id3.org...)
+ //read extended header size
+ if (len < extended_hdr_hdr) {
+ if (debug) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"extended header found but cannot read\n");
+ delete id3tag;
+ return -1;
+ }
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"remaining %d chars after extended hdr hdr\n", len);
+ id3header.extended_header.size = (data[0] & 0x7F) << 21 | (data[1] & 0x7F) << 14 | (data[2] & 0x7F) << 7 | (data[3] & 0x7F);
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Extended header size: %d\n", id3header.extended_header.size);
+ if (len <= id3header.extended_header.size+extended_hdr_hdr) {
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"extended Header to big, only %d bytes available\n",len);
+ delete id3tag;
+ return -1;
+ }
+ //lseek(fd, id3header.extended_header.size - 6, SEEK_CUR);
+ data+=id3header.extended_header.size+extended_hdr_hdr;
+ len-=id3header.extended_header.size+extended_hdr_hdr;
+ }
+ //set the end of the header
+ UCHAR * eob=start+id3header.size+10;
+ bool readNext=true;
+ while (data < eob && readNext)
+ {
+ //skip over some padding - found this in lame MCDI tag...
+ if (*data == 0) {
+ data++;
+ continue;
+ }
+ readNext=false;
+ switch(id3header.major)
+ {
+ case 2: //ID3 V2.2.x
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"version 2.2 frame, %d : %c %c %c\n", data-start,*data,*(data+1),*(data+2));
+ if (data + 6 >= eob)
+ {
+ break;
+ }
+ if (id3_2_2_FrameParse(data, &id3frame) < 0)
+ {
+ break;
+ }
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"frame size: %d\n", id3frame.size);
+ fillId3Tag(id3tag,data,id3frame.size,6,false);
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"frame payload: %s\n", data + 6 +1);
+ data+=6+id3frame.size;
+ readNext=true;
+ break;
+ case 3: //ID3 V2.3.x
+ {
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"version 2.3 frame, %d : %c %c %c %c\n", data-start,
+ *data,*(data+1),*(data+2),*(data+3));
+ if (data + 10 >= eob)
+ {
+ break;
+ }
+ if (id3_2_3_FrameParse(data, &id3frame) <0)
+ {
+ break;
+ }
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame size: %d\n", id3frame.size);
+ int dataOffset=10;
+ if (id3frame.flags.groupId)
+ {
+ dataOffset++;
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame group: %d\n", data[dataOffset]);
+ }
+ if (id3frame.flags.compression)
+ {
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame compressed: %d\n", id3frame.flags.compression);
+ }
+ if (id3frame.flags.encryption)
+ {
+ dataOffset+=1;
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame encryption method: %d\n", data[dataOffset]);
+ }
+ fillId3Tag(id3tag,data,id3frame.size-dataOffset+10,dataOffset,true);
+ if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"frame payload: %s\n", data + dataOffset +1);
+ data+=10+id3frame.size;
+ readNext=true;
+ break;
+ }
+ default:
+ //don't support this version
+ delete id3tag;
+ return -1;
+ }
+ }
+
+ data=eob;
+ //store the found tag
+ if (id3) delete id3;
+ id3=id3tag;
+ return data-start;
+ }
+ return -1;
+}
+
+/**
+ * parse an id3v1 Header
+ * based on code provided by Brian Walton
+ * @returns -1 of nothing found
+ * otherwise the id3 info is filled
+ */
+#define MEMCPY(type,len,offset) {int type##max=sizeof(tag->type)-1<len?sizeof(tag->type)-1:len;strncpy(tag->type,(char*)&data[offset],type##max);tag->type[type##max]=0;}
+
+int DemuxerAudio::parseID3V1(UCHAR *data, int len) {
+ int debug=1;
+ if (len < 128) return -1;
+ if(data[0]=='T' && data[1]=='A' && data[2]=='G')
+ {
+ id3_tag * tag=new id3_tag();
+ if (debug != 0)log->log("DemuxerAudio::parseID3V1",Log::DEBUG,"ID3 V1 tag found\n");
+ MEMCPY(title,30,3);
+ MEMCPY(artist,30,33);
+ MEMCPY(album,30,63);
+ MEMCPY(year,4,93);
+ if (data[125]==0 && data[126]!=0)
+ { //ID3 V1.1
+ if (debug != 0)log->log("DemuxerAudio::parseID3V1",Log::DEBUG,"ID3 V1.1 tag\n");
+ MEMCPY(comment,29,97);
+ sprintf(tag->track, "%d", data[126]);
+ } else {
+ if (debug != 0)log->log("DemuxerAudio::parseID3V1",Log::DEBUG,"ID3 V1.0 tag\n");
+ MEMCPY(comment,30,97);
+ }
+ if (data[127] < sizeof(id3_1_genre)/sizeof(id3_1_genre[0]))
+ {
+ sprintf(tag->genre, id3_1_genre[data[127]]);
+ }
+ if (id3) delete id3;
+ id3=tag;
+ return 0;
+ }
+ return -1;
+}
+
+//infos from http://www.multiweb.cz/twoinches/MP3inside.htm
+int DemuxerAudio::parseVBR(UCHAR *data, int len) {
+ UCHAR *hdr=findHeader(data,len);
+ //we expect the header exactly here
+ if (hdr != data) return -1;
+ const static char * VBRHDR="Xing";
+ int vbridpos=36;
+ UCHAR mpgtype=(data[1] & 0x18)>>3;
+ UCHAR chmode=(data[2] & 0xc0) >> 6;
+ UCHAR layer=(data[2] & 0x06) >>1;
+ if ( mpgtype == 3 && chmode == 11) vbridpos=21;
+ if ( mpgtype != 3 && chmode != 11) vbridpos=21;
+ if ( mpgtype != 3 && chmode == 11) vbridpos=13;
+ //check for the header ID
+ if (vbridpos+(int)strlen(VBRHDR)+4 >= len) {
+ Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"frame to short for VBR header %d",len);
+ return -1;
+ }
+ for (int i=4;i<vbridpos;i++) {
+ if (data[i] != 0) {
+ Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"garbage when searching VBR header at pos %d",i);
+ return -1;
+ }
+ }
+ if ( strncmp((char *)&data[vbridpos],VBRHDR,strlen(VBRHDR)) != 0) {
+ Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"no VBR header at pos %d",vbridpos);
+ return -1;
+ }
+ int framedata=vbridpos+strlen(VBRHDR);
+ int expectedLen=0;
+ //OK we should now have a valid vbr header
+ bool hasFramenum=data[framedata+3] & 1;
+ bool hasBytes=data[framedata+3] & 0x2;
+ bool hasTOC=data[framedata+3] & 0x4;
+ expectedLen+=8;
+ if (hasTOC) expectedLen+=100;
+ if (framedata+expectedLen > len) {
+ Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"frame to short for VBR header data %d, expected %d",
+ len,framedata+expectedLen);
+ return -1;
+ }
+ if (!hasFramenum || ! hasBytes || ! hasTOC) {
+ //not usefull for us..
+ Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"not all parts in VBR header - ignore");
+ return -1;
+ }
+ framedata+=4;
+ if (vbr) delete vbr;
+ vbr=new vbrInfo();
+ vbr->numFrames=data[framedata] << 24 | data[framedata+1]<<16|
+ data[framedata+2]<<8 |data[framedata+3];
+ framedata+=4;
+ vbr->numBytes=data[framedata] << 24 | data[framedata+1]<<16|
+ data[framedata+2]<<8 |data[framedata+3];
+ framedata+=4;
+ for (int ti=0;ti<100;ti++) {
+ vbr->table[ti]=data[framedata+ti];
+ }
+ //compute file size in seconds
+ //should be (#of frames -1) *samplesPerFrame / sampleRate
+ //TODO: difference for Mono?
+ ULONG samplesPerFrame=384; //layer1
+ if (layer != 3) samplesPerFrame=1152;
+ vbr->fileSeconds=(vbr->numFrames-1)*samplesPerFrame/hdrSamplingRate;
+ Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"successfully read VBR %ldbytes, %ld frames, %ldsec",
+ vbr->numBytes,vbr->numFrames,vbr->fileSeconds);
+ return hdrFramelen;
+}
+
+
+
+
+
+
+
+UCHAR * DemuxerAudio::findHeader(UCHAR *buf, int len, bool writeInfo) {
+ while (len >= 3) //assume hdr+crc
+ {
+ UCHAR pattern=*buf;
+ buf++; len--;
+ if (pattern != HDRBYTE1 ) continue;
+ if ((*buf & HDRBYTE2MASK) != HDRBYTE2) continue;
+ if (readHeader((buf-1),4,writeInfo) != 0) continue;
+ return buf-1;
+ }
+ return NULL;
+}
+
+
+
+int DemuxerAudio::readHeader(UCHAR * hbuf,int len,bool writeInfo) {
+ int curFramelen=0;
+ int curBitrate=0;
+ int curSamplingRate=0;
+ if (*hbuf != HDRBYTE1) return -1;
+ hbuf++;
+ if ((*hbuf & HDRBYTE2MASK) != HDRBYTE2) return -1;
+ UCHAR mpgtype=(*hbuf & 0x18)>>3;
+ if (mpgtype == 1) {
+ log->log("DemuxerAudio",Log::DEBUG,"header invalid mpgtype %s %i %i %i",
+ mpegString(mpgtype),*hbuf,*(hbuf+1),*(hbuf+2));
+ return 1;
+ }
+ UCHAR layer=(*hbuf & 0x06) >>1;
+ //bool hasCRC=!(*hbuf & 1);
+ hbuf++;
+ UCHAR bitrateCode=(*hbuf & 0xf0) >>4;
+ UCHAR samplingCode=(*hbuf & 0x0c) >> 2;
+ bool padding=*hbuf & 0x02;
+ hbuf++;
+ //0 Stereo, 1 JointStereo, 2 Dual, 3 Mono
+ UCHAR chmode=(*hbuf & 0xc0) >> 6;
+ //UCHAR extension=(*hbuf & 0x30) >> 4;
+
+ //layercode: 1-L3, 2-L2, 3-L1
+ //columns 0,1,2 for MPEG1
+ UCHAR bitrateColumn=3-layer;
+ if (bitrateColumn > 2) {
+ log->log("DemuxerAudio",Log::DEBUG,"header invalid layer %s %i %i %i",
+ layerString(layer),*(hbuf-2),*(hbuf-1),*hbuf);
+ return 1;
+ }
+ if (mpgtype != 3) bitrateColumn+=3;
+ if (bitrateColumn>4) bitrateColumn=4;
+ curBitrate=1000*bitrateTable[bitrateCode][bitrateColumn];
+ UCHAR sampleRateColumn=0;
+ if (mpgtype == 10) sampleRateColumn=1;
+ if (mpgtype == 0) sampleRateColumn=2;
+ curSamplingRate=samplingRateTable[samplingCode][sampleRateColumn];
+ if (curSamplingRate < 0 || curBitrate < 0) {
+ log->log("DemuxerAudio",Log::DEBUG,"header invalid rates br=%d sr=%d %i %i %i",
+ curBitrate,curSamplingRate,*(hbuf-2),*(hbuf-1),*hbuf);
+ return 1;
+ }
+ int padbytes=0;
+ if (padding) {
+ if (layer == 3) padbytes=4;
+ else padbytes=1;
+ }
+ if (layer == 3) {
+ //Layer 1
+ //FrameLengthInBytes = (12 * BitRate / SampleRate + Padding) * 4
+ curFramelen=(12*curBitrate/curSamplingRate+padbytes) * 4;
+ }
+ else {
+ //Layer 2/3
+ //FrameLengthInBytes = 144 * BitRate / SampleRate + Padding
+ curFramelen=144*curBitrate/curSamplingRate+padbytes;
+ }
+ //the header itself
+ if (curFramelen < 32) {
+ log->log("DemuxerAudio",Log::DEBUG,"read header %ld mpgv=%s lc=%s br=%d sr=%d, fl=%d-invalid %i %i %i",
+ readHeaders,mpegString(mpgtype),layerString(layer),
+ curBitrate,curSamplingRate,curFramelen,*(hbuf-2),*(hbuf-1),*hbuf);
+ return 1;
+ }
+ if (writeInfo || isStarting){
+ log->log("DemuxerAudio",Log::DEBUG,"read header %ld mpgv=%s lc=%s br=%d sr=%d, fl=%d %i %i %i",
+ readHeaders,mpegString(mpgtype),layerString(layer),
+ curBitrate,curSamplingRate,curFramelen,*(hbuf-2),*(hbuf-1),*hbuf);
+ if (info) delete info;
+ info=new mpegInfo();
+ strcpy(info->mpegVersion,mpegString(mpgtype));
+ info->mpegLayer=4-layer;
+ info->bitRate=curBitrate;
+ info->avrBitrate=curBitrate;
+ info->sampleRate=curSamplingRate;
+ const char *chmodStr=tr("Stereo");
+ switch (chmode) {
+ case 1: chmodStr=tr("JointStero");break;
+ case 2: chmodStr=tr("Dual");break;
+ case 3: chmodStr=tr("Mono");break;
+ }
+ snprintf(info->info,sizeof(info->info)-1,"%s",chmodStr);
+ }
+ if (isStarting) avrBitrate=curBitrate;
+ isStarting=false;
+ readHeaders++;
+ //moving average F=0.005
+ avrBitrate=avrBitrate+((5*(curBitrate-avrBitrate))/1024);
+ hdrBitrate=curBitrate;
+ hdrFramelen=curFramelen;
+ hdrSamplingRate=curSamplingRate;
+ hasHdrInfo=true;
+ return 0;
+}
+
+int DemuxerAudio::findPTS(UCHAR* buf, int len, ULLONG* dest)
+{
+ //we have no PTS number ...
+ *dest=0;
+ return (findHeader(buf,len) != NULL)?1:0;
+}
+
+bool PacketBuffer::doSkip() {
+ if (!bufferFull()) return false;
+ if (skipfactor == 0) return false;
+ numskip++;
+ if (numskip >= skipfactor) {
+ //sent at least always 2 packets
+ if (numskip > skipfactor) numskip=0;
+ return false;
+ }
+ packetWritten();
+ return true;
+}
+
+// just handle the real stream without dealing with the header
+int PacketBuffer::putInternal(UCHAR * wbuf, int len)
+{
+ if (bufferFull()) {
+ if (doSkip()) return 0;
+ //we are still full - so try to write
+ int sent=audio->put(store+bytesWritten,framelen-bytesWritten,streamtype);
+ //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"written %d bytes to stream (still full) pp=%d, framelen=%d, written=%d",sent,partPacket,framelen, bytesWritten );
+ if (sent < (framelen - bytesWritten)) {
+ //packet still not written
+ bytesWritten+=sent;
+ return 0;
+ }
+ packetWritten();
+ //let the demuxer come back with the rest - need to check header first
+ return 0;
+ }
+ if (partPacket+len >= framelen) {
+ //now we have at least a complete packet
+ int bytesConsumed=framelen-partPacket;
+ memcpy(store+partPacket,wbuf,bytesConsumed);
+ partPacket=framelen;
+ //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"stored packet %ld, length %d (last %d) for stream %p",numpackets,framelen,bytesConsumed,audio );
+ if (doSkip()) return bytesConsumed;
+ int sent=audio->put(store,framelen,streamtype);
+ bytesWritten+=sent;
+ //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"written %d bytes to stream",sent );
+ if (bytesWritten < framelen) {
+ //still not completely written
+ return bytesConsumed;
+ }
+ packetWritten();
+ //let the player come back...
+ return bytesConsumed;
+ }
+ //OK packet still not complete
+ if (len == 0) return 0;
+ int bytesConsumed=len;
+ memcpy(store+partPacket,wbuf,bytesConsumed);
+ partPacket+=bytesConsumed;
+ return bytesConsumed;
+}
+
+/**
+ major entry for data from a player
+ the demuxer is either in the state headerSearch (packet written or
+ just at the beginning), writing garbage (inSync=false) or
+ handling data (none set)
+ A header is expected at the first byte after the previous packet -
+ otherwise we switch to garbage where we always search for a header
+ (but anyway provide the data to the underlying device - it's probably
+ more intelligent then we are...
+ We only loose a correct position display.
+ */
+int DemuxerAudio::put(UCHAR* wbuf, int len)
+{
+ //return audiostream.put(wbuf,len,streamtype);
+ int framelen=PACKET_SIZE;
+ UCHAR *hdr;
+ int bytesConsumed=0;
+ int oldBytes=0;
+ if (tmpFill != 0 || (buffer->bufferEmpty() && len < HDRLEN)) {
+ //OK we have to copy everything to the tmp buffer
+ int cp=(UINT)len<(PACKET_SIZE-tmpFill)?(UINT)len:(PACKET_SIZE-tmpFill);
+ memcpy(&tmpBuffer[tmpFill],wbuf,cp);
+ oldBytes=tmpFill;
+ tmpFill+=cp;
+ wbuf=tmpBuffer;
+ len=tmpFill;
+ //if there is no header here and our buffer
+ //is empty - just wait for the next header
+ if (len < HDRLEN && buffer->bufferEmpty()) {
+ log->log("DemuxerAudio",Log::DEBUG,"len to small for header %d at bytes %ld",len,globalBytesWritten);
+ return cp;
+ }
+ }
+ while (bytesConsumed < len ) {
+ if (buffer->bufferFull()) {
+ //if this is the first part of the loop, try to write to the stream
+ if (bytesConsumed == 0) buffer->putInternal(wbuf,0);
+ //if the buffer is full, no need to continue
+ if (buffer->bufferFull()) break;
+ }
+ //either we are in a packet (buffer != full && buffer != empty)
+ //or we are searching a header
+ if (buffer->bufferEmpty()) {
+ if (len-bytesConsumed < HDRLEN) {
+ // we cannot still search
+ if (tmpFill != 0) {
+ //we are already working at the buffer
+ break;
+ }
+ memcpy(tmpBuffer,wbuf,len-bytesConsumed);
+ tmpFill=len-bytesConsumed;
+ log->log("DemuxerAudio",Log::DEBUG,"len to small for header %d at bytes %ld",len,globalBytesWritten);
+ return len;
+ }
+
+ int lastFramelen=hdrFramelen;
+ //if the header has been valid before and we are searching
+ //it should be here
+ int maxsearch=((len-bytesConsumed) < (int)PACKET_SIZE)?len-bytesConsumed:(int)PACKET_SIZE;
+ hdr=findHeader(wbuf,maxsearch);
+ if (hdr != NULL) {
+ //log->log("DemuxerAudio",Log::DEBUG,"header found at offset %d",hdr-wbuf);
+ }
+ //hdr now points to the new header
+ if (hdr != wbuf) {
+ //still at least bytes before the header
+ inSync=false;
+ outOfSync++;
+ int garbageBytes=(hdr!=NULL)?(hdr-wbuf):maxsearch;
+ //we consider the garbage now as an own packet
+ //we can consume at most our buffer
+ //basically the buffer should be empty here (we are
+ //in header search - and we fill up each garbage
+
+ int nframesize=garbageBytes;
+ if (nframesize > (int)PACKET_SIZE) {
+ nframesize=(int)PACKET_SIZE;
+ }
+ if (! buffer->bufferEmpty()) {
+ log->log("DemuxerAudio",Log::WARN,"buffer not empty when having garbage to store");
+ //at the end no big problem, we only write the remaining bytes that we have...
+ }
+ else {
+ buffer->setFramelen(nframesize);
+ }
+ log->log("DemuxerAudio",Log::DEBUG,"garbage found at packet %ld (bytes %ld) of length %d, "
+ "framelen set to %d (last fl=%d)",
+ readHeaders,globalBytesWritten,garbageBytes,buffer->getFramelen(),lastFramelen);
+ //hmm - we assume that he low level driver is more intelligent
+ //and give him the data "as is"
+ int written=buffer->putInternal(wbuf,garbageBytes);
+ globalBytesWritten+=written;
+ bytesConsumed+=written;
+ if (written != garbageBytes || hdr == NULL ) {
+ break;
+ }
+ //OK either all the "garbage" is written or
+ //we found the next header as expected
+ //continue with the next package
+ wbuf=hdr;
+ }
+ //we have to wait now until the buffer is
+ //free for the next package
+ if ( ! buffer->bufferEmpty()) return bytesConsumed;
+ //this is the place where we start a new packet
+ framelen=hdrFramelen;
+ if ( !buffer->setFramelen(framelen) ) {
+ log->log("DemuxerAudio",Log::DEBUG,"unable to set framelen should=%d, current=%d",
+ framelen,buffer->getFramelen());
+ }
+ inSync=true;
+ }
+ //now we are surely within a packet
+ int written=buffer->putInternal(wbuf,len-bytesConsumed);
+ //update the status
+ globalBytesWritten+=written;
+ wbuf+=written;
+ bytesConsumed+=written;
+ if (written == 0) {
+ //stream is full
+ break;
+ }
+ //OK - handle the rest
+ }
+ if (bytesConsumed >= oldBytes) {
+ //if we consumed more than the old bytes - OK the buffer
+ //can be thrown away
+ tmpFill=0;
+ return bytesConsumed-oldBytes;
+ }
+ else {
+ //only consumed bytes from the buffer
+ if (bytesConsumed == 0) {
+ //restore the buffer
+ tmpFill=oldBytes;
+ return 0;
+ }
+ //shift bytes in buffer
+ for (int i=0;i<oldBytes-bytesConsumed;i++) {
+ tmpBuffer[i]=tmpBuffer[i+bytesConsumed];
+ }
+ tmpFill=oldBytes-bytesConsumed;
+ return 0;
+ }
+}
+
+//info functions
+const id3_tag * DemuxerAudio::getId3Tag() {
+ return id3;
+
+}
+const DemuxerAudio::mpegInfo * DemuxerAudio::getMpegInfo() {
+ if (! hasHdrInfo) return NULL;
+ info->avrBitrate=avrBitrate;
+ info->bitRate=hdrBitrate;
+ return info;
+}
+
+const DemuxerAudio::vbrInfo * DemuxerAudio::getVBRINfo() {
+ return vbr;
+}
+
+int DemuxerAudio::checkStart(UCHAR *b, int len) {
+ UCHAR *start=b;
+ int id3len=parseID3V2(b,len);
+ if (id3len > 0) {
+ char * str=id3->toString();
+ log->log("DemuxerAudio",Log::DEBUG,"parseID3V2 %d bytes of %d",id3len,len);
+ log->log("DemuxerAudio",Log::DEBUG,"parseID3V2 found:%s",str);
+ delete str;
+ b+=id3len;
+ len-=id3len;
+ }
+ int vbrlen=parseVBR(b,len);
+ if (vbrlen > 0 ) {
+ hasVBRInfo=true;
+ b+=vbrlen;
+ len-=vbrlen;
+ }
+ UCHAR * hdr=findHeader(b,len,true);
+ if (hdr == NULL) return -1;
+ return hdr-start;
+}
+
+int DemuxerAudio::checkID3(UCHAR *b, int len) {
+ if (len != 128) return -1;
+ int rt=parseID3V1(b,len);
+ if (rt >= 0) {
+ char * str=id3->toString();
+ log->log("DemuxerAudio",Log::DEBUG,"parseID3V1 found:%s",str);
+ delete str;
+ }
+ return rt;
+}
+
+bool DemuxerAudio::isSync() {
+ return inSync;
+}
+
+UINT DemuxerAudio::getSyncErrors() {
+ return outOfSync;
+}
+
+ULONG DemuxerAudio::getBytesPerSecond()
+{
+ ULONG bps=hdrBitrate;
+ if (! hasHdrInfo) return 0;
+ if (hdrBitrate != avrBitrate) {
+ //we seem to have vbr
+ if (hasVBRInfo) {
+ //vbr stuff TODO
+ bps=avrBitrate;
+ }
+ else {
+ //some guessing
+ bps=avrBitrate;
+ }
+ }
+ bps=bps/8;
+ return bps;
+}
+
+ULONG DemuxerAudio::getSecondsFromLen(ULONG len) {
+ if (! hasHdrInfo) return 0;
+ if (vbr) {
+ //first find the index where we are between
+ //rough starting point:
+ ULONG idx=100*len/vbr->numBytes;
+ if (idx >= 100) idx=99;
+ ULONG idxPos=(vbr->table[idx]) * vbr->numBytes/256;
+ ULONG pbefore=idxPos;
+ ULONG pafter=idxPos;
+ //OK now we know whether we have to go up or down
+ if (idxPos > len) {
+ //down
+ while (idxPos > len && idx > 0) {
+ pafter=idxPos;
+ idx--;
+ idxPos=(vbr->table[idx]) * vbr->numBytes/256;
+ pbefore=idxPos;
+ }
+ //OK we are now before our postion
+ }
+ else {
+ //up
+ while (idxPos < len && idx < 100 ) {
+ pbefore=idxPos;
+ idx++;
+ idxPos=(vbr->table[idx]) * vbr->numBytes/256;
+ pafter=idxPos;
+ }
+ //after our position
+ if (idx > 0) idx --;
+ }
+ //idx is now the index before our position
+ //approximate between the 2 points
+ ULONG idxTime=idx * vbr->fileSeconds/100;
+ if (pafter == pbefore) return idxTime;
+ ULONG rt=idxTime+ (len-pbefore)* vbr->fileSeconds/(100 * (pafter-pbefore)) ;
+ if (rt > vbr -> fileSeconds) return vbr->fileSeconds;
+ if (rt < idxTime) return idxTime;
+ return rt;
+ }
+ else {
+ ULONG bps=getBytesPerSecond();
+ if (bps == 0) return 0;
+ return len/bps;
+ }
+}
+
+ULONG DemuxerAudio::positionFromSeconds(ULONG seconds) {
+ if (! hasHdrInfo) return 0;
+ if (vbr) {
+ ULONG idx=seconds*100/vbr->fileSeconds;
+ if (idx > 99) idx=99;
+ ULONG idxPos=vbr->table[idx] * vbr->numBytes/256;
+ ULONG idx2=idx;
+ if (idx < 99) idx2++;
+ ULONG nextPos=vbr->table[idx] * vbr->numBytes/256;
+ ULONG rt=idxPos+(nextPos-idxPos) * (seconds - idx * vbr->fileSeconds /256);
+ if (rt < idxPos) return idxPos;
+ if ( rt > vbr->numBytes) return vbr->numBytes;
+ return rt;
+ }
+ else {
+ ULONG bps=getBytesPerSecond();
+ return bps*seconds;
+ }
+}
+
+int id3_tag::stringlen(bool withTitle) const {
+ if (!withTitle)
+ return strlen(artist)+strlen(genre)+strlen(year)+strlen(album)+
+ strlen(track)+strlen(composer)+strlen(comment)+8+3+
+ 90; //30 chars for each name...
+ else
+ return strlen(title)+strlen(artist)+strlen(genre)+strlen(year)+strlen(album)+
+ strlen(track)+strlen(composer)+strlen(comment)+8+3+
+ 120; //30 chars for each name...
+}
+//create a string out of the id3 tag info
+//delete this string afterwards if you did not provide the buffer
+char * id3_tag::toString(char *b, int len, bool withTitle) const {
+ char *ta=tr("Artist");
+//char *tg=tr("Genre");
+//char *ty=tr("Year");
+ char *tx=tr("Album");
+//char *to=tr("Composer");
+//char *tc=tr("Comment");
+ char *tn=tr("Track");
+ /* in the moment:
+ Title:
+ Artist:
+ Album: year - name
+ Track:
+ */
+ if (b == NULL) {
+ len=stringlen(withTitle);
+ b=new char[len];
+ }
+ const char * del=" - ";
+ if (strlen(year) == 0) del="";
+ if (withTitle){
+ char *tt=tr("Title");
+ snprintf(b,len-1,"%s: %s\n%s: %s\n%s: %s%s%s\n%s: %s",
+ tt,title,ta,artist,tx,year,del,album,tn,track);
+ }
+ else {
+ snprintf(b,len-1,"%s: %s\n%s: %s%s%s\n%s: %s",
+ ta,artist,tx,year,del,album,tn,track);
+ }
+ b[len-1]=0;
+ return b;
+}
+
+void DemuxerAudio::setSkipFactor(int factor) {
+ Log::getInstance()->log("DemuxerAudio",Log::DEBUG,"set skipfactor %d\n",factor);
+ buffer->setSkipFactor(factor);
+}
+
--- /dev/null
+/*
+ Copyright 2006 Mark Calderbank, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef DEMUXERAUDIO_H
+#define DEMUXERAUDIO_H
+
+#include "demuxer.h"
+#include "defines.h"
+#include "id3.h"
+
+class PacketBuffer;
+class DemuxerAudio : public Demuxer
+{
+ public:
+ typedef struct {
+ char mpegVersion[8]; //MPEG1/2/2,5
+ int mpegLayer; //1,2,3
+ int bitRate;
+ int avrBitrate;
+ int sampleRate;
+ char info[20]; //channel mode,...
+ } mpegInfo;
+
+ typedef struct {
+ ULONG numFrames;
+ ULONG numBytes;
+ ULONG fileSeconds;
+ UCHAR table[100];
+ } vbrInfo;
+ DemuxerAudio(int p_vID = 0, int p_aID = 0);
+ virtual ~DemuxerAudio();
+ virtual void flush();
+ virtual void reset();
+ virtual int scan(UCHAR* buf, int len);
+ virtual int findPTS(UCHAR* buf, int len, ULLONG* dest);
+ virtual void setVID(int p_vID);
+ virtual void setAID(int p_aID);
+ virtual int put(UCHAR* buf, int len);
+
+ //special functions for the audioplayer
+ bool isSync() ;
+ UINT getSyncErrors();
+
+ //set the skip factor
+ void setSkipFactor(int faktor);
+
+ //how many bytes do we need at the file start
+ //ID3V2, VBR + some spare to detect a first header
+ static UINT headerBytes() {
+ return 4096;
+ }
+ //how many bytes do we need at the end
+ static UINT footerBytes() {
+ return 128;
+ }
+ //return a reference to an ID3 tag
+ //if not found (from checkStart or checkID3)
+ //NULL will be returned
+ const id3_tag * getId3Tag();
+ const mpegInfo * getMpegInfo();
+ const vbrInfo * getVBRINfo();
+
+ //check for a valid header at the beginning (and parse all we find)
+ //will return the offset in buffer where the first valid
+ //header was found (with potentially skipping ID3 and VBR)
+ //return -1 if nothing found
+ int checkStart(UCHAR * buffer, int len);
+ //check for ID3V1 (at the end)
+ //return 0 if found
+ int checkID3(UCHAR * buffer, int len);
+
+ //return length if we can do this
+ //otherwise return 0
+ ULONG getSecondsFromLen(ULONG len);
+
+ //position within a file
+ //return 0 if we cannot
+ ULONG positionFromSeconds(ULONG seconds);
+
+ //try to read the iD3 tag value (V2xx) and
+ //fill it into the ID3 store
+ bool fillId3Tag(id3_tag * tag,UCHAR * frameData, int frameLen, int dataOffset, bool v23);
+
+ const static UINT PACKET_SIZE=4096;
+ private:
+ /*max size of a packet
+ see http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
+ assumptions: max br 448kb/s, min sample 16k
+ L2/L3 FrameLengthInBytes = 144 * BitRate / SampleRate + Padding
+ L2/3 28*144+8 -> 4040
+ L1 FrameLengthInBytes = (12 * BitRate / SampleRate + Padding) * 4
+ L1 (336+32)*4 = 1472
+ */
+ Log * log;
+
+ int readHeader(UCHAR* buf, int len, bool writeInfo=false);
+
+ //info flags for the player
+ bool inSync; //set if we read a valid header
+ UINT outOfSync; //counts each invalid read headers
+ bool hasHdrInfo; //if we have been able to successfully
+ //parse the first header
+ bool hasVBRInfo; //we have VBR and the necessary info
+ //infos from header
+ int hdrFramelen;
+ int hdrBitrate;
+ int hdrSamplingRate;
+ //a little bit guessing for VBR
+ int avrBitrate;
+
+ int streamtype;
+ bool isStarting;
+ ULONG readHeaders;
+ ULONG globalBytesWritten;
+
+
+ //search for the start of a header
+ //return NULL if none found
+ UCHAR * findHeader(UCHAR * buf, int len, bool writeInfo=false);
+ //we need a buffer to be able to read at least a complete header
+ const static int HDRLEN=4;
+ UCHAR tmpBuffer[PACKET_SIZE+HDRLEN];
+ UINT tmpFill;
+
+ //compute the bytes/second from the BR info
+ //return 0 if not there;
+ ULONG getBytesPerSecond();
+
+ //parse ID3V2 at the beginning
+ //set info if there
+ //return length of header, -1 if not found
+ int parseID3V2(UCHAR *data, int len);
+ int id3_2_3_FrameParse(unsigned char buf[], id3_frame *frame);
+ int id3_2_2_FrameParse(unsigned char buf[], id3_frame *frame);
+
+ //parse ID3V1 at the end
+ //set info if there, return -1 otherwise
+ int parseID3V1(UCHAR *data, int len) ;
+
+ //parse a VBR header and set VBR info
+ //input has to point to firts MPEG header in file
+ //potentially after ID3V2
+ int parseVBR(UCHAR *data, int len);
+
+
+ class PacketBuffer* buffer;
+
+ id3_tag *id3;
+ mpegInfo *info;
+ vbrInfo *vbr;
+
+
+};
+
+#endif
--- /dev/null
+/*
+ Copyright 2006 Brian Walton , Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef ID3TAGS_H
+#define ID3TAGS_H
+
+typedef struct id3_flags_t
+{
+ int unsynchronisation;
+ int extended_header;
+ int experimental;
+ int footer;
+} id3_flags;
+
+typedef struct extended_header_t
+{
+ int size;
+ int flag_bytes;
+ int flags;
+} extended_header;
+
+typedef struct id3_header_t
+{
+ int major;
+ int minor;
+ id3_flags flags;
+ int size;
+ struct extended_header_t extended_header;
+} id3_header;
+
+typedef struct id3_frame_flags_t
+{
+ int tagAlterPreserv;
+ int filelterPreserv;
+ int readOnly;
+ int groupId;
+ int compression;
+ int encryption;
+ int unsync;
+ int dataLen;
+} id3_frame_flags;
+
+typedef struct id3_frame_t
+{
+ int size;
+ id3_frame_flags flags;
+} id3_frame;
+
+typedef struct id3_tag_t
+{
+ public:
+ id3_tag_t() {
+ title[0]=0;
+ artist[0]=0;
+ genre[0]=0;
+ year[0]=0;
+ album[0]=0;
+ track[0]=0;
+ composer[0]=0;
+ comment[0]=0;
+ };
+ int stringlen(bool withTitle=false) const;
+ //write to buffer, delete it afterwards (if you did not provide it)
+ char *toString(char * buf=NULL, int len=0,bool withTitle=false) const;
+ char title[50];
+ char artist[50];
+ char genre[50];
+ char year[5];
+ char album[50];
+ char track[7];
+ char composer[50];
+ char comment[29];
+} id3_tag;
+
+#endif
"",
"86",
},
- // End marker.
+ //Media Player
+ //we have a new main menu with this...
+ { "5. MediaPlayer",
+ "5. MedienWiedergabe",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "87",
+ },
+ { "6. Options",
+ "6. Einstellungen",
+ "6. Inställningar",
+ "6. BeállÃtások",
+ "6. Asetukset",
+ "6. Options",
+ "6. Instellingen",
+ "88",
+ },
+ { "7. Reboot",
+ "7. Neustart",
+ "7. Starta om",
+ "7. ÚjraindÃtás",
+ "7. Uudelleenkäynnistys",
+ "7. Redemarrer",
+ "7. Herstarten",
+ "89",
+ },
+ { "Loading",
+ "Laden",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "90",
+ },
+ { "rotate",
+ "Drehen",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "91",
+ },
+ { "No Media found",
+ "Keine Medien Datei gefunden",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "92",
+ },
+ { "no VDR connection",
+ "Keine Verbindung zum VDR",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "93",
+ },
+ { "Directory",
+ "Verzeichnis",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "94",
+ },
+ { "Format(px)",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "95",
+ },
+ { "Filesize",
+ "Dateigröße",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "96",
+ },
+ { "Time",
+ "Zeit",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "97",
+ },
+ { "Rotation",
+ "Drehung",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "98",
+ },
+ { "Scale",
+ "Skalierung",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "99",
+ },
+ { "MpegInfo",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "100",
+ },
+ { "FileName",
+ "Dateiname",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "101",
+ },
+ { "Playlist",
+ "Playlist", //Wiedergabeliste???
+ "",
+ "",
+ "",
+ "",
+ "",
+ "102",
+ },
+ { "unable to open audio file",
+ "kann die Audio Datei nicht öffnen",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "103",
+ },
+ { "Rand",
+ "Zuf."
+ "",
+ "",
+ "",
+ "",
+ "",
+ "104",
+ },
+ { "unable to get media list",
+ "kann die Medienliste nicht laden",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "105",
+ },
+ { "JointStereo",
+ "JointStereo",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "106",
+ },
+ { "Stereo",
+ "Stereo",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "107",
+ },
+ { "Dual",
+ "Dual",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "108",
+ },
+ { "Mono",
+ "Mono",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "109",
+ },
+ { "Artist",
+ "Künstler",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "110",
+ },
+ { "Year",
+ "Jahr",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "111",
+ },
+ { "Genre",
+ "Genre",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "112",
+ },
+ { "Album",
+ "Album",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "113",
+ },
+ { "Title",
+ "Titel",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "114",
+ },
+ { "Track",
+ "Nummer",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "115",
+ },
+ { "Composer",
+ "Komponist",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "116",
+ },
+ { "Comment",
+ "Kommentar",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "117",
+ },
+ // End marker.
{ NULL }
};
#ifndef MARK_H
#define MARK_H
+#include <vector>
#include <stdio.h>
#include "defines.h"
+using namespace std;
class Mark
{
int pos;
};
+typedef vector<Mark*> MarkList;
+
#endif
--- /dev/null
+/*
+ Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "media.h"
+#include "vdr.h"
+#include <time.h>
+
+Media* Media::recInfoFor = NULL;
+
+
+Media::Media()
+{
+ start = 0;
+ displayName = NULL;
+ fileName = NULL;
+ index = -1;
+ markList = NULL;
+ mediaType=MEDIA_TYPE_UNKNOWN;
+}
+
+Media::~Media()
+{
+ if (displayName) { delete[] displayName; displayName = NULL; }
+ if (fileName) { delete[] fileName; fileName = NULL; }
+ index = -1; // just in case
+
+ if (markList && markList->size())
+ {
+ for(UINT i = 0; i < markList->size(); i++)
+ {
+ delete (*markList)[i];
+ }
+ markList->clear();
+ Log::getInstance()->log("Media", Log::DEBUG, "Media destructor, marks list deleted");
+ }
+
+ if (markList) delete markList;
+}
+
+ULONG Media::getTime() const
+{
+ return start;
+}
+
+const char* Media::getDisplayName() const
+{
+ if (displayName) return displayName;
+ return fileName;
+}
+
+const char* Media::getFileName() const
+{
+ return fileName;
+}
+
+void Media::setTime(ULONG tstartTime)
+{
+ start = tstartTime;
+}
+
+void Media::setMediaType(int mtype)
+{
+ mediaType=mtype;
+}
+
+int Media::getMediaType() const
+{
+ return mediaType;
+}
+
+void Media::setDisplayName(char* tDisplayName)
+{
+ if (displayName) delete[] displayName;
+
+ displayName = new char[strlen(tDisplayName) + 1];
+ if (displayName) strcpy(displayName, tDisplayName);
+}
+
+void Media::setFileName(char* tFileName)
+{
+ if (fileName) delete[] fileName;
+
+ fileName = new char[strlen(tFileName) + 1];
+ if (fileName) strcpy(fileName, tFileName);
+}
+
+char * Media::getTimeString(char * buffer) const {
+ if (! buffer) buffer=new char[TIMEBUFLEN];
+ struct tm ltime;
+ time_t recStartTime = (time_t)getTime();
+ struct tm *btime = localtime_r(&recStartTime,<ime);
+ if (btime && recStartTime != 0) {
+#ifndef _MSC_VER
+ strftime(buffer,TIMEBUFLEN, "%0g/%0m/%0d %0H:%0M ", btime);
+#else
+ strftime(buffer, TIMEBUFLEN, "%g/%m/%d %H:%M ", btime);
+#endif
+ }
+ else {
+ snprintf(buffer,TIMEBUFLEN,"00/00/00 00:00 ");
+ }
+ return buffer;
+}
+
+void Media::loadMarks()
+{
+ markList = VDR::getInstance()->getMarks(fileName);
+}
+
+
+
+int Media::getPrevMark(int currentFrame)
+{
+ MarkList::reverse_iterator i;
+ Mark* loopMark = NULL;
+
+ if (!markList || !markList->size()) return 0;
+
+ for(i = markList->rbegin(); i != markList->rend(); i++)
+ {
+ loopMark = *i;
+ Log::getInstance()->log("Media", Log::NOTICE, "findprev:comparing Frame %i with current Frame %i",loopMark->pos,currentFrame);
+
+ if (loopMark->pos < currentFrame)
+ {
+ Log::getInstance()->log("Media", Log::NOTICE, "findprev:setting pos %i to jumpframe_target",loopMark->pos);
+ return loopMark->pos;
+ }
+ }
+
+ // No previous mark
+ return 0;
+}
+
+int Media::getNextMark(int currentFrame)
+{
+ MarkList::iterator i;
+ Mark* loopMark = NULL;
+
+ if (!markList || !markList->size()) return 0;
+
+ for(i = markList->begin(); i != markList->end(); i++)
+ {
+ loopMark = *i;
+ Log::getInstance()->log("Media", Log::NOTICE, "findnext:comparing Frame %i with current Frame %i",loopMark->pos,currentFrame);
+
+ if (loopMark->pos > currentFrame)
+ {
+ Log::getInstance()->log("Media", Log::NOTICE, "findnext:setting pos %i to jumpframe_target",loopMark->pos);
+ return loopMark->pos;
+ }
+ }
+
+ // No next mark
+ return 0;
+}
+
+bool Media::hasMarks()
+{
+ return (markList && markList->size());
+}
+
+MarkList* Media::getMarkList()
+{
+ return markList;
+}
--- /dev/null
+/*
+ Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef MEDIA_H
+#define MEDIA_H
+
+#include <stdio.h>
+#include <string.h>
+#include "defines.h"
+#include "recinfo.h"
+#include "mark.h"
+#include "log.h"
+#include "demuxer.h"
+
+/* media types form a bitmask
+ so you can add them to have > 1*/
+#define MEDIA_TYPE_DIR 1
+#define MEDIA_TYPE_AUDIO 2
+#define MEDIA_TYPE_VIDEO 4
+#define MEDIA_TYPE_PICTURE 8
+#define MEDIA_TYPE_UNKNOWN 0
+
+#define MEDIA_TYPE_ALL (1+2+4+8)
+
+
+
+class Media
+{
+ public:
+ Media();
+ ~Media();
+
+ void setTime(ULONG startTime);
+ void setDisplayName(char* displayName);
+ void setFileName(char* fileName);
+ void setMediaType(int mtype);
+
+ ULONG getTime() const;
+ const char* getDisplayName() const;
+ const char* getFileName() const;
+ //return the time as a string
+ //if the user provides a buffer, this one is used, if NULL
+ //is given a new buffer is allocated
+ //caller must delete the buffer after usage!
+ char * getTimeString(char *buffer) const;
+ //length for the time display buffer
+ const static int TIMEBUFLEN=100;
+ int index;
+ int getMediaType() const;
+
+ void loadMarks();
+ int getPrevMark(int currentFrame);
+ int getNextMark(int currentFrame);
+ bool hasMarks();
+ MarkList* getMarkList();
+
+
+ private:
+ ULONG start;
+ char* displayName;
+ char* fileName;
+ int mediaType;
+
+ // I only want 1 RecInfo loaded at a time
+ // if (recInfoFor == this) then recInfo is valid
+ // else delete recInfo and reload for this recording
+ static Media* recInfoFor;
+
+ MarkList* markList;
+};
+typedef vector<Media*> MediaList;
+
+#endif
widget.o wselectlist.o wjpeg.o wsymbol.o wbutton.o \
woptionbox.o wtextbox.o wwss.o \
fonts/helvB24.o fonts/helvB18.o \
- remote.o led.o mtd.o video.o audio.o osd.o surface.o
+ remote.o led.o mtd.o video.o audio.o osd.o surface.o \
+ vmedialist.o media.o vpicture.o vpicturebanner.o \
+ vaudioplayer.o audioplayer.o demuxeraudio.o
startup = true;
audio->reset();
+ audio->setStreamType(Audio::MPEG2_PES);
audio->systemMuteOff();
video->reset();
demuxer->reset();
video->stop();
video->reset();
audio->reset();
+ audio->setStreamType(Audio::MPEG2_PES);
demuxer->flush();
demuxer->seek();
currentFrameNumber = newFrame;
// if execution gets to here, threadFeedScan hit the start, go to play mode
state = S_PLAY;
audio->reset();
+ audio->setStreamType(Audio::MPEG2_PES);
demuxer->flush();
demuxer->seek();
demuxer->setFrameNum(currentFrameNumber);
startup = true;
audio->reset();
+ audio->setStreamType(Audio::MPEG2_PES);
audio->systemMuteOff();
demuxer->reset();
if (isRecording)
afeed.stop();
threadStop();
audio->reset();
+ audio->setStreamType(Audio::MPEG2_PES);
demuxer->flush();
currentPacketNumber = newPacket;
demuxer->setPacketNum(newPacket);
--- /dev/null
+Media PLayer Functions
+Author: Andreas Vogel (andvogel@online.de)
+Version: 2007/03/14
+
+Basics:
+Adding some media player functions to VOMP.
+This should enable VOMP to:
+ - work as a picture viewer (for digicam pictures)
+ - play audio (at least mp3)
+ - play (mpeg) video
+For me the priorities are like above, i.e. Prio 1 for the (jpeg) pictures, afterwards
+audio, later video.
+It requires some extensions on the server side (of course).
+
+First Version (delivered 2007/03/14):
+ Basic picture viewer functions
+ You can add a list of directories to vomp.conf that will be used as base dirs to
+ find media files.
+ Example:
+ [Media]
+ Dir.1=/home/Andreas/pictest
+ Dir.2=/home/video
+ Dir.3=/home/bilder
+ Dir.4=/media
+
+ You can have up to 10 directories there.
+ The recognition of media types is based on extensions today. In the moment it recognizes:
+ JPEG,jpeg,JPG,jpg as Jpeg
+ mp3,MP3 as mp3 (not played yet)
+
+ In the vom main menu a new item is added (5 MediaPlayer).
+ This will open a menu with the configured base dirs (/ if none configured).
+ SELECTOR WINDOW
+ You can traverse through the dirs with the normal keys.
+ When pointing to a media file (currently only jpeg) you can activate the viewer with
+ [OK] (normal view) or [PLAY] (slide show).
+ PICTURE VIEWER
+ When viewing a picture, this gets loaded from the server (currently still a little bit slow),
+ scaled (jpeg libs allows for 1/2, 1/4 and 1/8 only - but seems to be OK for many photos), and
+ displayed. With [OK] you can toggle the status line, With [RED] you can rotate the picture clockwise,
+ [GREEN] brings up an info box with some picture data. [PLAY] starts the slideshow, |<- and ->| as
+ well as [UP], [DOWN] travers trough all pictures within the directory.
+ << and >> will slower/fasten the slide show (time getting displayed in the status line). II will
+ pause the slide show [STOP] will pause and reset time to 5s.
+
+Version 2007/03/23
+ Added playing audio files.
+ In the moment playing is (on the server side) limited to mp3 (MP3) files. The client side is
+ prepared to play all MPEG1 files - although only tested for MP3 CBR/VBR stereo, 44,1kBit.
+ Player control is similar to the other available players (recordings).
+ You can use 0...9 to position by x0 percent in the file. |<- and ->| will skip each 10s back/ forward.
+ In principle >> does also work - but seems not to be very usefull in the moment.
+ [UP] and [DOWN] will move backward/forward in a play list.
+ You can start playing all MP3's in a dir from the media view by pressing [PLAY], a single file by pressing OK.
+ The player window will disappear app. 30s after the last key press.
+ The players parses ID3V1,V2.2,V2.3 (thanks to Walt for the supported code) and displays selected info.
+ It also parses VBR headers ("Xing") and uses this info for display and movements.
+ If the player cannot parse the files, it tries to play them anyway, it will (for the display) assume
+ 128kBit/s CBR.
+ Currently the player uses the same media stream like the picture viewer -so no parallel viewing and
+ listening is possible (will be changed soon).
+
+ The media view has some minor changes:
+ Added the option to sort also randomly (e.g. for playing audio files).
+ Directories are marked with [ ] in the display, they are always displayed on top.
+
+ Additionally some bug fixes (thanks to muellerph and Walt for reporting them) for error handling.
+
+
+Development Issues
+1. Major Changes
+1.1 Surface
+To be able to handle deeply nested directories, the number of views has been extended to 120. This made
+dynamic surface handling necessary. Therefore I changed the surface member. Box has a virtual
+getSurface method, that gets implemented by Widget and View. The View really has a surface member,
+Widgets have a parent (Box) member and query this for the surface.
+At the View there is a new unmap() method, that releases the surface. The next getSurface will
+acquire a new one and will redraw the view (calling it's draw method). This has to be used with
+care...
+1.2. Jpeg
+The WJpeg has been extended to be able to load arbitrary jpeg data via a reader class. It will
+scale the jpeg if possible to fit onto the screen and will rotate it on demand.
+1.3. VDR and server
+VDR has new commands to get a media list, to initialize picture data transfer and to transfer
+picture blocks (used a new command there for possible parallel action with audio later on...).
+On the server side the media list reading has been added and a simple file reader to retrieve the
+Jpeg data.
+1.4. extensions
+New classes for Media, MediaList, VMediaList, VPicture, VPicturebanner, VPictureinfo.
+2007/03/23
+New classes for audioplayer, demuxeraudio, vaudioplayer
+
+2. Problems
+2.1. setting up devenv needed some changes...
+2.2. -Wno-format was necessary to allow for %g for the date display
+2.3. merging with today's CVS:
+I encountered big problems with the new timer handling. Dangerous situations:
+- timer retrieved from list, waiting to fire but destination gone (canceled timer),
+ nextTimer will not be NULL there... - so need a add. check
+- cancel called within the timer firing - deadlock today, could delete timer from list..
+OK -I added the patch.
+2007/03/23
+
+3. Open/Next Steps
+For me the next important steps would be:
+Audio Playing - basicly done 2007/03/23
+
+ Performance Improvements
+1. have the transfer being handled in a separate thread (would save 50% on large files)
+2. make a scaling (+pot. decoding) on the server side, could lower the sizes and fit
+ better to the screen (but needs some jpeg stuff on the server - I will try to grep from
+ mediamvp - worked on this already...).
+Picture Functions
+1. Colour handling - we would need some colour profile for appropriate picture
+ viewing...
+4. sorting - multiple sort options for media lists..
+ done 2007/03/23
+
+Translations
+English and German are there...
translist[RECORD] = RECORD;
translist[STOP] = STOP;
translist[PAUSE] = PAUSE;
+ translist[PLAY] = PLAY;
translist[SKIPBACK] = SKIPBACK;
translist[SKIPFORWARD] = SKIPFORWARD;
--- /dev/null
+/*
+ Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "vaudioplayer.h"
+#include "audioplayer.h"
+#include <time.h>
+
+VAudioplayer::VAudioplayer(VMediaList *p)
+{
+ parent=p;
+ Video* video = Video::getInstance();
+ create(video->getScreenWidth(),video->getScreenHeight());
+ banner=NULL;
+ fullname=NULL;
+ filename=NULL;
+ ftime=0;
+ playall=false;
+ currentMedia=NULL;
+ justPlaying=false;
+ numentries=p->getNumEntries(MEDIA_TYPE_AUDIO);
+ bannerTime=0;
+
+ barBlue.set(0, 0, 150, 150);
+
+}
+
+AudioPlayer * VAudioplayer::getPlayer(bool createIfNeeded)
+{
+ AudioPlayer* rt=AudioPlayer::getInstance(this,false);
+ if (! createIfNeeded && rt == NULL) return NULL;
+ if (rt == NULL) {
+ rt=AudioPlayer::getInstance(this);
+ rt->run();
+ }
+ return rt;
+}
+
+VAudioplayer::~VAudioplayer()
+{
+ if (banner) ViewMan::getInstance()->removeView(banner);
+ if (fullname) delete fullname;
+ if (filename) delete filename;
+ Timers::getInstance()->cancelTimer(this,1);
+ Timers::getInstance()->cancelTimer(this,2);
+ Timers::getInstance()->cancelTimer(this,3);
+ //TODO leave this to medialist and stop only...
+ if (getPlayer(false)) {
+ AudioPlayer::getInstance(NULL,false)->shutdown();
+ }
+}
+
+
+
+void VAudioplayer::draw()
+{
+ Log::getInstance()->log("VAudioplayer::draw", Log::DEBUG, "p=%p", this);
+}
+
+
+int VAudioplayer::handleCommand(int command)
+{
+ Log::getInstance()->log("VAudioplayer::handleCommand", Log::DEBUG, "p=%p,cmd=%d", this,command);
+ Timers::getInstance()->cancelTimer(this,1);
+ int rt=1;
+ switch(command)
+ {
+ case Remote::DF_UP:
+ case Remote::UP:
+ play(VMediaList::MV_PREV);
+ ViewMan::getInstance()->updateView(this);
+ rt= 2;
+ break;
+ case Remote::FORWARD:
+ getPlayer()->fastForward();
+ rt=2;
+ break;
+ case Remote::DF_DOWN:
+ case Remote::DOWN:
+ play(VMediaList::MV_NEXT);
+ ViewMan::getInstance()->updateView(this);
+ rt= 2;
+ break;
+ case Remote::SKIPFORWARD:
+ getPlayer()->skipForward(10);
+ rt=2;
+ break;
+ case Remote::SKIPBACK:
+ getPlayer()->skipBackward(10);
+ rt=2;
+ break;
+ case Remote::REVERSE:
+ rt=2;
+ break;
+ case Remote::ZERO:
+ getPlayer()->jumpToPercent(0);
+ rt=2;
+ break;
+ case Remote::ONE:
+ getPlayer()->jumpToPercent(10);
+ rt=2;
+ break;
+ case Remote::TWO:
+ getPlayer()->jumpToPercent(20);
+ rt=2;
+ break;
+ case Remote::THREE:
+ getPlayer()->jumpToPercent(30);
+ rt=2;
+ break;
+ case Remote::FOUR:
+ getPlayer()->jumpToPercent(40);
+ rt=2;
+ break;
+ case Remote::FIVE:
+ getPlayer()->jumpToPercent(50);
+ rt=2;
+ break;
+ case Remote::SIX:
+ getPlayer()->jumpToPercent(60);
+ rt=2;
+ break;
+ case Remote::SEVEN:
+ getPlayer()->jumpToPercent(70);
+ rt=2;
+ break;
+ case Remote::EIGHT:
+ getPlayer()->jumpToPercent(80);
+ rt=2;
+ break;
+ case Remote::NINE:
+ getPlayer()->jumpToPercent(90);
+ rt=2;
+ break;
+ case Remote::OK:
+ case Remote::GREEN:
+ {
+ if (banner) {
+ destroyBanner();
+ }
+ else showBanner(true);
+ if (getPlayer()->getState() == AudioPlayer::S_ERROR) {
+ if (playall) play(VMediaList::MV_NEXT);
+ }
+ rt= 2;
+ }
+ break;
+ case Remote::PLAY:
+ {
+ getPlayer()->unpause();
+ if (getPlayer()->getState() != AudioPlayer::S_ERROR) justPlaying=true;
+ else if (playall) play(VMediaList::MV_NEXT);
+ rt= 2;
+ }
+ break;
+ case Remote::PAUSE:
+ //playall=false;
+ getPlayer()->pause();
+ justPlaying=false;
+ rt= 2;
+ break;
+ case Remote::STOP:
+ getPlayer()->stop();
+ justPlaying=false;
+ rt= 2;
+ break;
+ case Remote::BACK:
+ {
+ rt= 4;
+ }
+ break;
+ }
+ if (playall) startPlayall();
+ if (rt == 2) retriggerBanner();
+ // stop command getting to any more views
+ Log::getInstance()->log("VAudioplayer::handleCommand", Log::DEBUG, "returns p=%p,cmd=%d, rt=%d", this,command,rt);
+ return rt;
+}
+
+void VAudioplayer::processMessage(Message* m)
+{
+ if (m->message == Message::MOUSE_MOVE)
+ {
+ ;
+ }
+ else if (m->message == Message::MOUSE_LBDOWN)
+ {
+
+ //check if press is outside this view! then simulate cancel
+ int x=(m->parameter>>16)-getScreenX();
+ int y=(m->parameter&0xFFFF)-getScreenY();
+ if (x<0 || y <0 || x>getWidth() || y>getHeight())
+ {
+ ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press
+ }
+ }
+ else if (m->message == Message::PLAYER_EVENT) {
+ switch (m->parameter) {
+ case AudioPlayer::STREAM_ERR:
+ case AudioPlayer::STREAM_END:
+ if (playall) play(VMediaList::MV_NEXT);
+ else sendViewMsg(this,true);
+ break;
+ case AudioPlayer::STATUS_CHANGE:
+ updateBanner(false);
+ break;
+ case AudioPlayer::SHORT_UPDATE:
+ if (banner) {
+ drawClocks();
+ ViewMan::getInstance()->updateView(banner,&clocksRegion);
+ ViewMan::getInstance()->updateView(banner,&barRegion);
+ Timers::getInstance()->setTimerD(this, 3, 1);
+ }
+ break;
+ case AudioPlayer::NEW_SONG:
+ updateBanner(true);
+ break;
+ case AudioPlayer::CONNECTION_LOST:
+ destroyBanner();
+ if (AudioPlayer *player=getPlayer(false)) {
+ player->shutdown();
+ player=NULL;
+ }
+ Command::getInstance()->connectionLost();
+ break;
+ }
+ }
+}
+
+VAudioplayer * VAudioplayer::createPlayer(VMediaList * mparent, bool bplayall) {
+ Log::getInstance()->log("VAudioplayer::createPlayer", Log::DEBUG, "p=%p",
+ mparent);
+ VAudioplayer *vmn=new VAudioplayer(mparent);
+ ViewMan::getInstance()->add(vmn);
+ ViewMan::getInstance()->updateView(vmn);
+ vmn->play();
+ if (bplayall) vmn->startPlayall();
+ ViewMan::getInstance()->updateView(vmn);
+ return vmn;
+}
+
+void VAudioplayer::startPlayall() {
+ playall=true;
+}
+
+void VAudioplayer::play(ULONG move) {
+ currentMedia=parent->getMedia(MEDIA_TYPE_AUDIO,move);
+ mediaError=NULL;
+ const char *fname=currentMedia->getFileName();
+ ftime=currentMedia->getTime();
+ int len=strlen(fname)+2+strlen(parent->getDirname());
+ if (fullname) {
+ delete fullname;
+ fullname=NULL;
+ }
+ fullname=new char[len];
+ sprintf(fullname,"%s/%s",parent->getDirname(),fname);
+ if (filename) delete filename;
+ len=strlen(fname)+1;
+ filename=new char[len];
+ strcpy(filename,fname);
+ Log::getInstance()->log("VAudioplayer::load", Log::DEBUG, "filename=%s,p=%p",
+ fullname,this);
+ VDR* vdr=VDR::getInstance();
+ if (vdr->isConnected()) {
+ Log::getInstance()->log("VAudioplayer", Log::DEBUG, "request file %s",filename);
+ int wseq=getPlayer()->play(fullname);
+ if (getPlayer()->waitForSequence(wseq,2)<0) {
+ mediaError=tr("unable to open audio file");
+ }
+ justPlaying=true;
+ //leave this to the update message from the player:
+ //showBanner();
+ }
+ else {
+ if (AudioPlayer * player=getPlayer(false)) {
+ player->shutdown();
+ }
+ Command::getInstance()->connectionLost();
+ }
+ if (mediaError) {
+ showBanner(true);
+ }
+ Log::getInstance()->log("VAudioplayer", Log::DEBUG, "player started for %s",filename);
+}
+
+void VAudioplayer::retriggerBanner() {
+ if (! banner) return;
+ bannerTime=time(NULL);
+}
+
+void VAudioplayer::showBanner(bool forceNewTime) {
+ //if the loading flag is set,
+ //we are in the main thread - so we can (and must) safely hard destroy/create the banner
+ Timers::getInstance()->cancelTimer(this,1);
+ Timers::getInstance()->cancelTimer(this,3);
+ if (! filename || ! currentMedia) {
+ //hmm...
+ destroyBanner();
+ return;
+ }
+ drawBanner();
+ time_t curtime=time(NULL);
+ if (forceNewTime) retriggerBanner();
+ time_t remainingTime=BANNER_TIME+2-(curtime-bannerTime);
+ if (remainingTime < 2) remainingTime=2;
+ bool playerError=getPlayer()->getState()==AudioPlayer::S_ERROR;
+ if (remainingTime < BANNER_ERROR_TIME+2 && playerError) remainingTime=BANNER_ERROR_TIME+2;
+ Timers::getInstance()->setTimerD(this,1,remainingTime);
+ if (playerError) Timers::getInstance()->setTimerD(this,2,BANNER_ERROR_TIME);
+ Timers::getInstance()->setTimerD(this,3, 1);
+ ViewMan::getInstance()->updateView(banner);
+ }
+
+void VAudioplayer::destroyBanner() {
+ if (banner) {
+ ViewMan::getInstance()->removeView(banner);
+ banner=NULL;
+ }
+ Timers::getInstance()->cancelTimer(this,1);
+ Timers::getInstance()->cancelTimer(this,3);
+}
+void VAudioplayer::updateBanner(bool restart) {
+ time_t currTime=time(NULL);
+ time_t lastBanner=bannerTime;
+ if (currTime - lastBanner > BANNER_TIME){
+ destroyBanner();
+ }
+ if (banner||restart) {
+ showBanner(restart);
+ }
+}
+void VAudioplayer::timercall(int clientref) {
+ Log::getInstance()->log("VAudioplayer::timercall", Log::DEBUG, "id=%d",clientref);
+ switch(clientref)
+ {
+ case 1:
+ case 3:
+ {
+ Message* m = new Message();
+ //we misuse PLAYER_EVENT here
+ m->message = Message::PLAYER_EVENT;
+ m->to = this;
+ m->from = this;
+ m->parameter=(clientref==1)?AudioPlayer::STATUS_CHANGE:
+ AudioPlayer::SHORT_UPDATE;
+ Command::getInstance()->postMessageFromOuterSpace(m);
+ break;
+ }
+ case 2:
+ {
+ bool stillError=false;
+ if (AudioPlayer * player=getPlayer(false)) {
+ stillError=player->getState()==AudioPlayer::S_ERROR;
+ }
+ if (stillError) {
+ //send a stream end to trigger nex in playlist
+ Message* m = new Message();
+ //we misuse PLAYER_EVENT here
+ m->message = Message::PLAYER_EVENT;
+ m->to = this;
+ m->from = this;
+ m->parameter=AudioPlayer::STREAM_END;
+ Command::getInstance()->postMessageFromOuterSpace(m);
+ }
+ break;
+ }
+ }
+
+ }
+
+void VAudioplayer::sendViewMsg(View *v,bool vdestroy) {
+ Message* m = new Message();
+ m->message = vdestroy?Message::CLOSE_ME:Message::ADD_VIEW;
+ m->to = ViewMan::getInstance();
+ m->from = v;
+ m->parameter=(ULONG)v;
+ Command::getInstance()->postMessageFromOuterSpace(m);
+}
+
+View * VAudioplayer::createBannerView(int numlines) {
+ View *rt=new View();
+ UINT height=numlines*30+60;
+ UINT vheight=Video::getInstance()->getScreenHeight();
+ UINT vwidth=Video::getInstance()->getScreenWidth();
+ if (height > vheight-2*BANNER_BOTTOM_MARGIN)
+ height=vheight-2*BANNER_BOTTOM_MARGIN;
+ rt->create(vwidth -2*BANNER_X_MARGIN, height);
+ if (Video::getInstance()->getFormat() == Video::PAL)
+ {
+ rt->setScreenPos(BANNER_X_MARGIN, vheight-height-BANNER_BOTTOM_MARGIN);
+ }
+ else
+ {
+ rt->setScreenPos(BANNER_X_MARGIN, vheight-height-BANNER_BOTTOM_MARGIN);
+ }
+
+ rt->setBackgroundColour(Colour::VIEWBACKGROUND);
+ rt->setTitleBarOn(0);
+ //from vvideorec
+ //set the regions for the closcks and bars on banner
+ barRegion.x = rt->area.w-BARLEN-20;
+ barRegion.y = rt->area.h - 30; // FIXME, need to be - 1? and below?
+ barRegion.w = rt->area.w-BARLEN+10;
+ barRegion.h = 30;
+
+ clocksRegion.x = 130;
+ clocksRegion.y = barRegion.y + 3;
+ clocksRegion.w = 190;
+ clocksRegion.h = surface->getFontHeight();
+ Log::getInstance()->log("VAudioPlayer",Log::DEBUG,"created banner %p",rt);
+ ViewMan::getInstance()->add(rt);
+ return rt;
+}
+
+
+void VAudioplayer::drawBanner(){
+ Log::getInstance()->log("VAudioPlayer",Log::DEBUG, "draw banner for %p",banner);
+ char *title=NULL;
+ char *playerTitle=getPlayer()->getTitle();
+ if (playerTitle) title=playerTitle;
+ else title=filename;
+ char *pl=tr("Playlist");
+ int num=parent->getNumEntries(MEDIA_TYPE_AUDIO,currentMedia->index);
+ bool playerError=getPlayer()->getState() == AudioPlayer::S_ERROR;
+ //1more line for long dirs
+ int numlines=playall?5:4;
+ int len=0;
+ char *first=NULL;
+ char *playerInfo=NULL;
+ if (playerError) {
+ numlines=3;
+ first=tr("Unable to play audio file");
+ len=strlen(first)+3;
+ }
+ else {
+ playerInfo=getPlayer()->getID3Info();
+ len=strlen(filename)+strlen(pl)+30+strlen(parent->getDirname())+Media::TIMEBUFLEN+100;
+ if (playerInfo) {
+ for (UINT i=0;i<strlen(playerInfo);i++)
+ if (playerInfo[i] == '\n') numlines++;
+ len+=strlen(playerInfo);
+ first=playerInfo;
+ }
+ else {
+ first="";
+ }
+ }
+ if (numlines != bannerlines) {
+ if (banner) destroyBanner();
+ banner=createBannerView(numlines);
+ }
+ if (! banner) {
+ banner=createBannerView(numlines);
+ }
+ bannerlines=numlines;
+ char buf[len];
+ if (playerError) {
+ snprintf(buf,len,"%s",first);
+ }
+ else {
+ char tbuf[Media::TIMEBUFLEN];
+ if (playall) {
+ snprintf(buf,len,"%s\n"
+ "%s:%s\n"
+ "%s: %d/%d\n"
+ "%s:%s\n"
+ "%s:%s\n",
+ first,
+ tr("FileName"),currentMedia->getFileName(),
+ pl,num,numentries,
+ tr("Directory"),parent->getDirname(),
+ tr("Time"),currentMedia->getTimeString(tbuf));
+ }
+ else {
+ snprintf(buf,len,"%s\n"
+ "%s:%s\n"
+ "%s:%s\n"
+ "%s:%s\n",
+ first,
+ tr("FileName"),currentMedia->getFileName(),
+ tr("Directory"),parent->getDirname(),
+ tr("Time"),currentMedia->getTimeString(tbuf));
+ }
+ }
+ Log::getInstance()->log("VAudioPlayer",Log::DEBUG,"info: (%d)%s",strlen(buf),buf);
+ //now the real drawing functions
+ banner->draw();
+ WSymbol w;
+ w.setSurface(banner->surface);
+ //title
+ banner->rectangle(0, 0, banner->area.w, 30, Colour::TITLEBARBACKGROUND);
+ banner->drawText(title, 5, 5, Colour::LIGHTTEXT);
+ banner->drawPara(buf,5,32,Colour::LIGHTTEXT);
+ int x=10;
+ int ybottom=banner->area.h;
+ banner->rectangle(0, ybottom - barRegion.h, banner->area.w, barRegion.h, Colour::TITLEBARBACKGROUND);
+ bool drawSymbol=true;
+ switch(getPlayer()->getState()) {
+ case AudioPlayer::S_PAUSE:
+ w.nextSymbol = WSymbol::PAUSE;
+ break;
+ case AudioPlayer::S_PLAY:
+ w.nextSymbol = WSymbol::PLAY;
+ break;
+ case AudioPlayer::S_DONE:
+ if (playall)
+ w.nextSymbol = WSymbol::PLAY;
+ else
+ drawSymbol=false;
+ break;
+ case AudioPlayer::S_BACK:
+ w.nextSymbol = WSymbol::FBWD ;
+ break;
+ case AudioPlayer::S_FF:
+ w.nextSymbol = WSymbol::FFWD ;
+ break;
+ default:
+ drawSymbol=false;
+ break;
+ }
+ if (drawSymbol) {
+ w.setSurfaceOffset(x, ybottom-24);
+ w.draw();
+ }
+ else {
+ //stop symbol
+ banner->rectangle(x, ybottom - 23, 18, 16, Colour::LIGHTTEXT);
+ }
+ x+=30;
+ if (playerInfo) delete playerInfo;
+ if (playerTitle) delete playerTitle;
+ drawClocks();
+}
+
+void VAudioplayer::drawClocks() {
+ if (! banner) return;
+ //draw clocks and bar
+ banner->rectangle(clocksRegion, Colour::TITLEBARBACKGROUND);
+
+ time_t currentSec = (time_t)(getPlayer()->getCurrentTimes());
+ time_t lengthSec=(time_t)(getPlayer()->getSonglen());
+ struct tm cpos;
+ struct tm slen;
+ gmtime_r(¤tSec,&cpos);
+ gmtime_r(&lengthSec,&slen);
+
+ char buffer[100];
+ if (currentSec >= lengthSec)
+ {
+ snprintf(buffer,99, "-:--:-- / -:--:-- %03dk",getPlayer()->getCurrentBitrate()/1000);
+ }
+ else
+ {
+ SNPRINTF(buffer, 99, "%01i:%02i:%02i / %01i:%02i:%02i %03dk", cpos.tm_hour, cpos.tm_min, cpos.tm_sec, slen.tm_hour, slen.tm_min, slen.tm_sec,
+ getPlayer()->getCurrentBitrate()/1000);
+ //Log::getInstance()->log("VAudioplayer", Log::DEBUG, buffer);
+ }
+
+ banner->drawText(buffer, clocksRegion.x, clocksRegion.y, Colour::LIGHTTEXT);
+
+ // Draw progress bar
+ int progBarXbase = 0;
+ int barlen=250;
+
+ banner->rectangle(barRegion.x + progBarXbase, barRegion.y + 3, barlen+10, 24, Colour::LIGHTTEXT);
+ banner->rectangle(barRegion.x + progBarXbase + 2, barRegion.y + 5, barlen+6, 20, barBlue);
+
+ if (currentSec > lengthSec) currentSec=lengthSec;
+ if (lengthSec == 0) return;
+
+ // Draw yellow portion
+ int progressWidth = (barlen+2) * currentSec / lengthSec;
+ banner->rectangle(barRegion.x + progBarXbase + 4, barRegion.y + 7, progressWidth, 16, Colour::SELECTHIGHLIGHT);
+
+}
+
+
--- /dev/null
+/*
+ Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef VAUDIOPLAYER_H
+#define VAUDIOPLAYER_H
+
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+
+#include "view.h"
+#include "remote.h"
+#include "wsymbol.h"
+#include "viewman.h"
+#include "vdr.h"
+#include "media.h"
+#include "colour.h"
+#include "i18n.h"
+#include "vmedialist.h"
+#include "wjpeg.h"
+
+/**
+ * the audio player frontend
+ * will ineract with the parent List for ff,back, slide show...
+ *
+*/
+class AudioPlayer;
+
+class VAudioplayer : public View,public TimerReceiver
+{
+ public:
+ ~VAudioplayer();
+
+ void processMessage(Message* m);
+ int handleCommand(int command);
+ void draw();
+ void timercall(int clientReference);
+ //factory method
+ //return NULL on error
+ static VAudioplayer *createPlayer(VMediaList * parent, bool startPlayall=false);
+ //show the picture currently selected in the parent
+ //potentially moving to next/previous one
+ void play(ULONG move=VMediaList::MV_NONE);
+
+ //start a playall
+ void startPlayall();
+
+
+ private:
+ const static int BANNER_TIME=30;
+ //show time of an error
+ const static int BANNER_ERROR_TIME=8;
+
+
+ //margin on SCREEN on each side
+ const static int BANNER_X_MARGIN=50;
+ //margin on bottom of screen
+ const static int BANNER_BOTTOM_MARGIN=30;
+ //length of the progress bar
+ const static int BARLEN=250;
+
+ VAudioplayer(VMediaList * plist);
+ void retriggerBanner();
+ void showBanner(bool forceRestart=false);
+ void destroyBanner();
+ void updateBanner(bool restart=false);
+ void drawBanner();
+ void drawClocks();
+ View *createBannerView(int numlines);
+ int bannerlines;
+ //add or destroy a view (banner, info)
+ void sendViewMsg(View *v,bool destroy=false);
+ //get the player - create it if necessary
+ AudioPlayer* getPlayer(bool create=true);
+ VMediaList *parent;
+ View *banner;
+ char * fullname;
+ char * filename;
+ ULONG ftime;
+ bool playall;
+ char * mediaError;
+ Media * currentMedia;
+ bool justPlaying;
+ int numentries;
+ time_t bannerTime;
+ Region barRegion;
+ Region clocksRegion;
+ Colour barBlue;
+
+};
+
+#endif
return toReturn;
}
+UCHAR* VDR::getImageBlock(ULONG position, UINT maxAmount, UINT* amountReceived)
+{
+ return getBlock(position, maxAmount, amountReceived, VDR_GETIMAGEBLOCK);
+}
+
UCHAR* VDR::getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived)
+{
+ return getBlock(position, maxAmount, amountReceived, VDR_GETBLOCK);
+}
+
+UCHAR* VDR::getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived, ULONG cmd)
{
UCHAR buffer[20];
*(unsigned long*)&buffer[0] = htonl(16);
- *(unsigned long*)&buffer[4] = htonl(VDR_GETBLOCK);
+ *(unsigned long*)&buffer[4] = htonl(cmd);
*(ULLONG*)&buffer[8] = htonll(position);
*(unsigned long*)&buffer[16] = htonl(maxAmount);
return ;
}
+
+/**
+ * media List Request:
+ * 4 length
+ * 4 VDR_GETMEDIALIST
+ * 4 flags (currently unused)
+ * n dirname
+ * n+1 0
+ * Media List response:
+ * 4 length
+ * 4 VDR_
+ * 4 numentries
+ * per entry:
+ * 4 media type
+ * 4 time stamp
+ * 4 flags
+ * 4 strlen (incl. 0 Byte)
+ * string
+ * 0
+*/
+MediaList* VDR::getMediaList(const char* parent,int mediaType)
+{
+ Log::getInstance()->log("VDR", Log::DEBUG, "getMediaList %s,type=%d",
+ (parent?parent:"NULL"), mediaType);
+ unsigned long totalLength = 12;
+ if (parent) totalLength+=strlen(parent) + 1;
+ UCHAR* buffer = new UCHAR[totalLength];
+
+ *(unsigned long*)&buffer[0] = htonl(totalLength - 4);
+ *(unsigned long*)&buffer[4] = htonl(VDR_GETMEDIALIST);
+ //unused flags
+ for (int i=8;i<12;i++) buffer[i]=0;
+ //name
+ if (parent) {
+ strcpy((char*)&buffer[12], parent);
+ buffer[totalLength-1]=0;
+ }
+ MUTEX_LOCK(&mutex);
+ if (!connected) { MUTEX_UNLOCK(&mutex); return 0; }
+
+ unsigned int a = tcp->sendPacket(buffer, totalLength);
+ delete []buffer;
+
+ if (a != totalLength)
+ {
+ disconnect();
+ MUTEX_UNLOCK(&mutex);
+ return NULL;
+ }
+
+ if (!getPacket())
+ {
+ MUTEX_UNLOCK(&mutex);
+ return NULL;
+ }
+
+ if (serverError())
+ {
+ MUTEX_UNLOCK(&mutex);
+ return NULL;
+ }
+ if (packetLength < 12) {
+ Log::getInstance()->log("VDR", Log::ERR, "receiveMediaList packet too short, expected 12, got %d", packetLength);
+ freePacket();
+ MUTEX_UNLOCK(&mutex);
+ return NULL;
+ }
+ MediaList* mediaList = new MediaList();
+ ULONG code=0;
+ code=extractULONG();
+ ULONG numEntries=extractULONG();
+ Log::getInstance()->log("VDR", Log::DEBUG, "receiveMediaList with %d entries",numEntries);
+ while (packetPos < packetLength && numEntries >0)
+ {
+ Media* m = new Media();
+ ULONG mtype = extractULONG();
+ ULONG mtime=extractULONG();
+ ULONG flags=0;
+ flags=extractULONG();
+ ULONG stsize=extractULONG();
+ char * name=extractString();
+ if (! name || stsize != (strlen(name)+1)) {
+ Log::getInstance()->log("VDR", Log::ERR, "receiveMediaList invalid packet entry, read size %d, strlen %d", stsize, strlen(name)+1);
+ delete m;
+ delete mediaList;
+ freePacket();
+ MUTEX_UNLOCK(&mutex);
+ return NULL;
+ }
+ //ignore . and .. entries
+ if (strcmp(name,".") == 0 || strcmp(name,"..")==0) {
+ delete m;
+ continue;
+ }
+ m->setFileName(name);
+ m->setTime(mtime);
+ m->setMediaType(mtype);
+ mediaList->push_back(m);
+ Log::getInstance()->log("VDR", Log::DEBUG, "Have added a media to list. %s, type=%d, time=%d", name,mtype,mtime);
+ numEntries--;
+ }
+
+ freePacket();
+ MUTEX_UNLOCK(&mutex);
+
+ return mediaList;
+}
+/**
+ * get image Request:
+ * 4 length
+ * 4 VDR_GETIMAGE
+ * 4 flags (currently unused)
+ * 4 x size
+ * 4 y size
+ * n filename
+ * n+1 0
+ * get image response:
+ * 4 length
+ * 4 VDR_GETIMAGE
+ * 4 len of image
+*/
+ULONG VDR::loadImage(const char* fileName, ULONG x, ULONG y)
+{
+ unsigned long totalLength = 20 + strlen(fileName) + 1;
+ UCHAR* buffer = new UCHAR[totalLength];
+
+ *(unsigned long*)&buffer[0] = htonl(totalLength - 4);
+ *(unsigned long*)&buffer[4] = htonl(VDR_GETIMAGE);
+ *(unsigned long*)&buffer[8] = htonl(0);
+ *(unsigned long*)&buffer[8] = htonl(x);
+ *(unsigned long*)&buffer[8] = htonl(y);
+ strcpy((char*)&buffer[12], fileName);
+
+ MUTEX_LOCK(&mutex);
+ if (!connected) { MUTEX_UNLOCK(&mutex); return 0; }
+
+ unsigned int a = tcp->sendPacket(buffer, totalLength);
+ delete []buffer;
+
+ if (a != totalLength)
+ {
+ disconnect();
+ MUTEX_UNLOCK(&mutex);
+ return 0;
+ }
+
+ if (!getPacket())
+ {
+ MUTEX_UNLOCK(&mutex);
+ return 0;
+ }
+ ULONG cmd=0;
+ cmd=extractULONG();
+ ULONG lengthBytes = extractULONG();
+ freePacket();
+ MUTEX_UNLOCK(&mutex);
+
+ Log::getInstance()->log("VDR", Log::DEBUG, "getImage %s: %lu", fileName,lengthBytes);
+
+ return lengthBytes;
+}
#include "recinfo.h"
#include "mark.h"
#include "wol.h"
+#include "media.h"
using namespace std;
typedef vector<Event*> EventList;
typedef vector<Channel*> ChannelList;
typedef vector<RecTimer*> RecTimerList;
-typedef vector<Mark*> MarkList;
struct VDRServer
{
void getChannelPids(Channel* channel);
UCHAR* getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived);
+ //get image blocks separate - we can do this in parallel
+ UCHAR* getImageBlock(ULONG position, UINT maxAmount, UINT* amountReceived);
int stopStreaming();
EventList* getChannelSchedule(ULONG number);
EventList* getChannelSchedule(ULONG number, time_t start, ULONG duration);
ULONG setEventTimer(char* timerString);
RecTimerList* getRecTimersList();
+ /**
+ * ge a list of media entries
+ * if parent==NULL this is the configured base list
+ */
+ MediaList* getMediaList(const char* parent=NULL,int mediaType=MEDIA_TYPE_ALL);
+ /**
+ * start loading a JPEG image
+ * return size, 0 if not found
+ */
+ ULONG loadImage(const char * filename, ULONG xsize=0,ULONG ysize=0);
// end
private:
+ UCHAR* getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived, ULONG cmd);
static VDR* instance;
Log* logger;
int initted;
const static ULONG VDR_GETRECINFO = 20;
const static ULONG VDR_GETMARKS = 21;
const static ULONG VDR_GETCHANNELPIDS = 22;
-
+ const static ULONG VDR_GETMEDIALIST = 30;
+ const static ULONG VDR_GETIMAGE = 31;
+ const static ULONG VDR_GETIMAGEBLOCK = 32;
int getPacket();
void freePacket();
int serverError();
--- /dev/null
+/*
+ Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <vector>
+#include "unistd.h"
+#include "vmedialist.h"
+#include <time.h>
+#include <string.h>
+#include "vpicture.h"
+#include "vaudioplayer.h"
+
+
+using namespace std;
+class MediaDirectory {
+ private:
+ char * dirname;
+ char * displayname;
+ char * fullpath;
+ int selection;
+ int sortorder;
+ ULONG mtype;
+ public:
+ void setDirname(const char * n) {
+ if (dirname) delete dirname;
+ dirname=NULL;
+ if (!n) return;
+ dirname=new char[strlen(n)+1];
+ strcpy(dirname,n);
+ }
+ void setDisplayname(const char * n) {
+ if (displayname) delete displayname;
+ displayname=NULL;
+ if (!n) return;
+ displayname=new char[strlen(n)+1];
+ strcpy(displayname,n);
+ }
+ void setFullpath(const char * n) {
+ if (fullpath) delete fullpath;
+ fullpath=NULL;
+ if (!n) return;
+ fullpath=new char[strlen(n)+1];
+ strcpy(fullpath,n);
+ }
+ void setSelection(int s) {
+ selection=s;
+ }
+ void setMediaType(ULONG n) {mtype=n;}
+ void setSortorder(int s) {
+ sortorder=s;
+ }
+ const char * getDirname() {
+ return dirname;
+ }
+ const char * getDisplayname() {
+ if (displayname) return displayname;
+ if (fullpath) return fullpath;
+ if (dirname) return dirname;
+ return "/";
+ }
+ const char * getFullPath() {
+ return fullpath;
+ }
+ int getSelection() {
+ return selection;
+ }
+ ULONG getMediaType(){return mtype;}
+ int getSortorder() {
+ return sortorder;
+ }
+ public:
+ MediaDirectory(const char *d) : dirname(NULL),displayname(NULL),fullpath(NULL),selection(0),
+ sortorder(0),mtype(MEDIA_TYPE_ALL){
+ setDirname(d);
+ }
+ MediaDirectory(MediaDirectory &c) {
+ MediaDirectory(c.getDirname());
+ setDisplayname(c.getDisplayname());
+ setSelection(c.getSelection());
+ setMediaType(c.getMediaType());
+ setSortorder(c.getSortorder());
+ }
+ ~MediaDirectory() {
+ if (dirname) delete dirname;
+ if (displayname) delete displayname;
+ if (fullpath) delete fullpath;
+ }
+};
+
+typedef vector<MediaDirectory*> MDirList;
+class DirList {
+ private:
+ int current;
+ MDirList list;
+ public:
+ DirList() {
+ current=0;
+ list.push_back(new MediaDirectory(NULL));
+ }
+ ~DirList() {
+ MDirList::iterator it;
+ for (it=list.begin();it<list.end();it++) {
+ delete *it;
+ }
+ }
+ MediaDirectory *getCurrent() {
+ return list[current];
+ }
+ const char * getPath() {
+ return getCurrent()->getFullPath();
+ }
+ int dropTop() {
+ if (current > 0) {
+ current--;
+ delete list.back();
+ list.pop_back();
+ }
+ return current;
+ }
+ int append(const char *dirname) {
+ if (! dirname) return current;
+ MediaDirectory* md=new MediaDirectory(dirname);
+ const char *cp=getCurrent()->getFullPath();
+ int len=strlen(dirname)+2;
+ if (cp) len+=strlen(cp);
+ char * fp=new char[len];
+ *fp=0;
+ if (cp) {
+ strcpy(fp,cp);
+ strcat(fp,"/");
+ }
+ else if (*dirname != '/' ) strcpy(fp,"/");
+ strcat(fp,dirname);
+ md->setFullpath(fp);
+ delete fp;
+ list.push_back(md);
+ current++;
+ return current;
+ }
+
+ int getLevel() {
+ return current;
+ }
+
+};
+
+
+class MediaSorterName
+{
+ public:
+ bool operator() (const Media* a, const Media* b)
+ {
+ if (b->getMediaType() == MEDIA_TYPE_DIR &&
+ a->getMediaType() != MEDIA_TYPE_DIR)
+ return false;
+ if ( b->getMediaType() != MEDIA_TYPE_DIR &&
+ a->getMediaType() == MEDIA_TYPE_DIR)
+ return true;
+ int c = strcmp(b->getFileName(), a->getFileName());
+ if (c > 0) return true;
+ return false;
+ }
+};
+
+//a sorter with a randomly initialized order
+class MediaSorterRandom
+{
+ public:
+ MediaSorterRandom(int start) {
+ mystart=start;
+ }
+ bool operator() (const Media* a, const Media* b)
+ {
+ if (b->getMediaType() == MEDIA_TYPE_DIR &&
+ a->getMediaType() != MEDIA_TYPE_DIR)
+ return false;
+ if ( b->getMediaType() != MEDIA_TYPE_DIR &&
+ a->getMediaType() == MEDIA_TYPE_DIR)
+ return true;
+ unsigned char suma=sum(a->getFileName(),(unsigned char)mystart);
+ unsigned char sumb=sum(b->getFileName(),(unsigned char)mystart);
+ if (sumb > suma) return true;
+ return false;
+ }
+ private:
+ unsigned char sum(const char *name,unsigned char start) {
+ for (;*name!=0;name++) start=start ^ (unsigned char)*name;
+ return start;
+ }
+ int mystart;
+};
+
+
+struct MediaSorterTime
+{
+ bool operator() (const Media* a, const Media* b)
+ {
+ if (b->getMediaType() == MEDIA_TYPE_DIR &&
+ a->getMediaType() != MEDIA_TYPE_DIR)
+ return false;
+ if ( b->getMediaType() != MEDIA_TYPE_DIR &&
+ a->getMediaType() == MEDIA_TYPE_DIR)
+ return true;
+ if (b->getTime()>a->getTime()) return true;
+ return false;
+ }
+};
+
+
+VMediaList::VMediaList()
+{
+ dirlist=new DirList();
+ Log::getInstance()->log("VMediaList::VMediaList", Log::DEBUG, "dirlist=%p,curren=%p",dirlist,dirlist->getCurrent());
+ dirlist->getCurrent()->setSortorder(SORT_NAME);
+ create(570, 420);
+ if (Video::getInstance()->getFormat() == Video::PAL)
+ {
+ setScreenPos(80, 70);
+ }
+ else
+ {
+ setScreenPos(70, 35);
+ }
+
+ setBackgroundColour(Colour::VIEWBACKGROUND);
+ setTitleBarOn(1);
+ setTitleBarColour(Colour::TITLEBARBACKGROUND);
+
+ sl.setSurface(surface);
+ sl.setSurfaceOffset(10, 30 + 5);
+ sl.setDimensions(area.w - 20, area.h - 30 - 15 - 30);
+ sl.addColumn(0);
+ sl.addColumn(60);
+ mediaList=NULL;
+ loading=true;
+ sortOrder=SORT_NONE;
+
+
+}
+
+VMediaList::~VMediaList()
+{
+ delete dirlist;
+ clearMediaList();
+}
+
+void VMediaList::clearMediaList() {
+ if (mediaList)
+ {
+ for (UINT i = 0; i < mediaList->size(); i++)
+ {
+ delete (*mediaList)[i];
+ }
+
+ mediaList->clear();
+ delete mediaList;
+ }
+}
+
+
+
+int VMediaList::getNumEntries(int mediaType,int lowerThen) {
+ if (mediaType == MEDIA_TYPE_ALL && lowerThen < 0) return mediaList->size();
+ if (lowerThen < 0) lowerThen=mediaList->size();
+ int rt=0;
+ for (int i=0;i<(int)(mediaList->size()) && i <= lowerThen;i++) {
+ if ((*mediaList)[i]->getMediaType() & mediaType) rt++;
+ }
+ return rt;
+}
+
+void VMediaList::setList(MediaList* tlist)
+{
+ if (mediaList != tlist) {
+ clearMediaList();
+ }
+ sortOrder=SORT_NONE;
+ mediaList = tlist;
+ sortList(dirlist->getCurrent()->getSortorder());
+ updateSelectList(dirlist->getCurrent()->getSelection());
+}
+
+
+void VMediaList::updateSelectList(){
+ updateSelectList(-1);
+}
+void VMediaList::updateSelectList(int currentNumber){
+ char str[5000];
+ char tempA[Media::TIMEBUFLEN];
+ Log::getInstance()->log("VMediaList::updateSelectList", Log::DEBUG, "media=%p",mediaList);
+
+ ULONG currentSelection=0;
+ if (sl.getNumOptions() >= 1 && currentNumber < 0) {
+ currentSelection=sl.getCurrentOptionData();
+ }
+ sl.clear();
+
+ Media* media;
+ int first = 1;
+ if (mediaList)
+ {
+ for (UINT i = 0; i < mediaList->size(); i++)
+ {
+ media = (*mediaList)[i];
+ if (media->getMediaType() == MEDIA_TYPE_DIR) {
+ sprintf(str, "%04u %s [%s]", i,media->getTimeString(tempA), media->getDisplayName());
+ //Log::getInstance()->log("VMediaList", Log::DEBUG, "add to select list %s",str);
+ media->index = sl.addOption(str, (ULONG)media, first);
+ }
+ else {
+ sprintf(str, "%04u %s %s", i,media->getTimeString(tempA), media->getDisplayName());
+ //Log::getInstance()->log("VMediaList", Log::DEBUG, "add to select list %s",str);
+ media->index = sl.addOption(str, (ULONG)media, first);
+ }
+ first = 0;
+ }
+ }
+ if (currentNumber >= 0) sl.hintSetCurrent(currentNumber);
+ else sl.hintSetCurrent(0);
+ if (currentSelection != 0) {
+ bool found=false;
+ //position to the previous selection
+ for (int i=0;i<sl.getNumOptions();i++) {
+ sl.hintSetCurrent(i);
+ if (sl.getCurrentOptionData() == currentSelection) {
+ found=true;
+ break;
+ }
+ }
+ if (! found) sl.hintSetCurrent(0);
+ }
+ loading=false;
+ if (sl.getNumOptions() > 0)
+ sl.draw();
+ doShowingBar();
+}
+
+void VMediaList::highlightMedia(Media* media)
+{
+ sl.hintSetCurrent(media->index);
+ sl.draw();
+ doShowingBar();
+ ViewMan::getInstance()->updateView(this);
+}
+
+void VMediaList::draw()
+{
+ Log::getInstance()->log("VMediaList::draw", Log::DEBUG, "namestr=%s",dirlist->getCurrent()->getDisplayname());
+ char title[400];
+ SNPRINTF(title, 398, tr("Media - %s"), dirlist->getCurrent()->getDisplayname());
+ title[399]=0;
+ setTitleText(title);
+ View::draw();
+ if (loading)
+ {
+ drawText(tr("Loading..."), 240, 180, Colour::LIGHTTEXT);
+ }
+ else {
+ if (sl.getNumOptions() > 0) sl.draw();
+
+ // Put the status stuff at the bottom
+
+ WSymbol w;
+ w.setSurface(surface);
+
+ w.nextSymbol = WSymbol::UP;
+ w.setSurfaceOffset(20, 385);
+ w.draw();
+
+ w.nextSymbol = WSymbol::DOWN;
+ w.setSurfaceOffset(50, 385);
+ w.draw();
+
+ w.nextSymbol = WSymbol::SKIPBACK;
+ w.setSurfaceOffset(85, 385);
+ w.draw();
+
+ w.nextSymbol = WSymbol::SKIPFORWARD;
+ w.setSurfaceOffset(115, 385);
+ w.draw();
+
+ w.nextSymbol = WSymbol::PLAY;
+ w.setSurfaceOffset(150, 385);
+ w.draw();
+
+ doShowingBar();
+ }
+}
+
+void VMediaList::doShowingBar()
+{
+ int topOption = sl.getTopOption() + 1;
+ if (sl.getNumOptions() == 0) topOption = 0;
+
+ char showing[250];
+ char * strmode=tr("Name");
+ switch (sortOrder) {
+ case SORT_TIME:
+ strmode=tr("Rand");
+ break;
+ case SORT_NAME:
+ strmode=tr("Time");
+ break;
+ default:
+ break;
+ }
+ snprintf(showing, 250,tr("%i to %i of %i"),
+ topOption, sl.getBottomOption(), sl.getNumOptions());
+
+// Box b;
+// b.setSurfaceOffset(220, 385);
+// b.setDimensions(160, 25);
+// b.fillColour(Colour::VIEWBACKGROUND);
+// b.drawText(showing, 0, 0, Colour::LIGHTTEXT);
+
+ rectangle(200, 384, 18, 16, Colour::VIDEOBLUE);
+ rectangle(220, 385, 220+160, 385+25, Colour::VIEWBACKGROUND);
+ drawText(strmode, 220, 385, Colour::LIGHTTEXT);
+ drawText(showing, 280, 385, Colour::LIGHTTEXT);
+ if (sl.getCurrentOptionData() != 0) Log::getInstance()->log("VMediaList",Log::DEBUG,"selected %s",((Media *)sl.getCurrentOptionData())->getDisplayName());
+}
+
+Media * VMediaList::getMedia(int ltype,ULONG move) {
+ int cur=sl.getCurrentOption();
+ Media *m;
+ bool more=true;
+ while (more) {
+ int last=sl.getCurrentOption();
+ switch (move) {
+ case MV_NEXT:
+ sl.down();
+ break;
+ case MV_PREV:
+ sl.up();
+ break;
+ default:
+ more=false;
+ break;
+ }
+ m=(Media*)sl.getCurrentOptionData();
+ if (m->getMediaType() & ltype) {
+ sl.draw();
+ return m;
+ }
+ //stop if we are done
+ if (sl.getCurrentOption() == cur || sl.getCurrentOption() == last) break;
+ }
+ return NULL;
+}
+
+int VMediaList::handleCommand(int command)
+{
+ switch(command)
+ {
+ case Remote::ONE:
+ {
+ sl.hintSetCurrent(0);
+ sl.draw();
+ doShowingBar();
+ ViewMan::getInstance()->updateView(this);
+ return 2;
+ }
+ case Remote::DF_UP:
+ case Remote::UP:
+ {
+ sl.up();
+ sl.draw();
+
+ doShowingBar();
+ ViewMan::getInstance()->updateView(this);
+ return 2;
+ }
+ case Remote::DF_DOWN:
+ case Remote::DOWN:
+ {
+ sl.down();
+ sl.draw();
+
+ doShowingBar();
+ ViewMan::getInstance()->updateView(this);
+ return 2;
+ }
+ case Remote::SKIPBACK:
+ {
+ sl.pageUp();
+ sl.draw();
+
+ doShowingBar();
+ ViewMan::getInstance()->updateView(this);
+ return 2;
+ }
+ case Remote::SKIPFORWARD:
+ {
+ sl.pageDown();
+ sl.draw();
+
+ doShowingBar();
+ ViewMan::getInstance()->updateView(this);
+ return 2;
+ }
+ case Remote::BLUE:
+ {
+ switch(sortOrder) {
+ case SORT_NAME:
+ sortList(SORT_TIME);
+ ViewMan::getInstance()->updateView(this);
+ return 2;
+ case SORT_TIME:
+ sortList(SORT_RANDOM);
+ ViewMan::getInstance()->updateView(this);
+ return 2;
+ default:
+ sortList(SORT_NAME);
+ ViewMan::getInstance()->updateView(this);
+ return 2;
+ }
+ }
+ case Remote::OK:
+ case Remote::PLAY:
+ {
+ Media* media = NULL;
+ if (mediaList) media = (Media*)sl.getCurrentOptionData();
+ if (media == NULL) return 2;
+ Log::getInstance()->log("VMediaList", Log::DEBUG, "activated %lu", media->index);
+ switch(media->getMediaType())
+ {
+ case MEDIA_TYPE_DIR:
+ {
+ //create child
+ Log::getInstance()->log("VMediaList", Log::DEBUG, "create child for %s",media->getFileName());
+ if (media->getFileName() == NULL ) return 2;
+ if (sl.getNumOptions() >=1) {
+ dirlist->getCurrent()->setSelection(sl.getCurrentOption());
+ }
+ dirlist->getCurrent()->setSortorder(sortOrder);
+ dirlist->append(media->getFileName());
+ //same sort order for next level
+ dirlist->getCurrent()->setSortorder(sortOrder);
+ load();
+ break;
+ }
+ case MEDIA_TYPE_AUDIO:
+ Log::getInstance()->log("VMediaList", Log::DEBUG, "play audio file %s",
+ media->getFileName());
+ VAudioplayer::createPlayer(this,command==Remote::PLAY);
+ break;
+ case MEDIA_TYPE_VIDEO:
+ Log::getInstance()->log("VMediaList", Log::DEBUG, "play video file %s",
+ media->getFileName());
+ //play video
+ break;
+ case MEDIA_TYPE_PICTURE:
+ Log::getInstance()->log("VMediaList", Log::DEBUG, "show picture file %s",
+ media->getFileName());
+ VPicture::createViewer(this,command==Remote::PLAY);
+ //play video
+ break;
+ default:
+ Log::getInstance()->log("VMediaList", Log::DEBUG, "unknown media type %d file %s",
+ media->getMediaType(),media->getFileName());
+
+ }
+ /*
+ VVideoLive* v = new VVideoLive(mediaList, media->type, this);
+
+ v->draw();
+ ViewMan::getInstance()->add(v);
+ ViewMan::getInstance()->updateView(v);
+
+ v->medianelChange(VVideoLive::NUMBER, media->number);
+ */
+
+ return 2;
+ }
+ case Remote::BACK:
+ {
+ if (dirlist->getLevel() < 1) return 4;
+ dirlist->dropTop();
+ load();
+ }
+ }
+ // stop command getting to any more views
+ return 1;
+}
+
+void VMediaList::processMessage(Message* m)
+{
+ if (m->message == Message::MOUSE_MOVE)
+ {
+ if (sl.mouseMove((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
+ {
+ sl.draw();
+ doShowingBar();
+ ViewMan::getInstance()->updateView(this);
+ }
+ }
+ else if (m->message == Message::MOUSE_LBDOWN)
+ {
+ if (sl.mouseLBDOWN((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
+ {
+ ViewMan::getInstance()->handleCommand(Remote::OK); //simulate OK press
+ }
+ else
+ { //check if press is outside this view! then simulate cancel
+ int x=(m->parameter>>16)-getScreenX();
+ int y=(m->parameter&0xFFFF)-getScreenY();
+ if (x<0 || y <0 || x>getWidth() || y>getHeight())
+ {
+ ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press
+ }
+ }
+ }
+}
+
+int VMediaList::createList() {
+ Log::getInstance()->log("VMediaList::createList", Log::DEBUG, "");
+ VMediaList *vmn=new VMediaList();
+ //show the "loading" indicator
+ ViewMan::getInstance()->add(vmn);
+ int rt=vmn->load();
+ if ( rt != 0) {
+ ViewMan::getInstance()->removeView(vmn);
+ }
+ return rt;
+}
+
+void VMediaList::sortList(int newSort) {
+ if (sortOrder == newSort) return;
+ Log::getInstance()->log("VMediaList::sortList", Log::DEBUG, "p=%p,sort=%d, size=%d",this,newSort,mediaList->size());
+ if (mediaList->begin() != mediaList->end()) {
+ switch (newSort) {
+ case SORT_TIME:
+ ::sort(mediaList->begin(),mediaList->end(),MediaSorterTime());
+ break;
+ case SORT_NAME:
+ ::sort(mediaList->begin(),mediaList->end(),MediaSorterName());
+ break;
+ case SORT_RANDOM:
+ ::sort(mediaList->begin(),mediaList->end(),MediaSorterRandom(time(NULL)));
+ break;
+ }
+ }
+ sortOrder=newSort;
+ updateSelectList();
+}
+
+
+int VMediaList::load() {
+
+ loading=true;
+ draw();
+ ViewMan::getInstance()->updateView(this);
+ VDR* vdr=VDR::getInstance();
+ Log::getInstance()->log("VMediaList::load", Log::DEBUG, "load list for %s",dirlist->getPath());
+ if (vdr->isConnected()) {
+ MediaDirectory *md=dirlist->getCurrent();
+ MediaList *mn=vdr->getMediaList(md->getFullPath(),md->getMediaType());
+ if (mn != NULL) {
+ setList(mn);
+ draw();
+ ViewMan::getInstance()->updateView(this);
+ return 0;
+ }
+ }
+ if (! vdr->isConnected()) {
+ Command::getInstance()->connectionLost();
+ }
+ else {
+ Log::getInstance()->log("VMediaList", Log::ERR, "unable to get MediaList for %s",dirlist->getPath());
+ VInfo* vi = new VInfo();
+ vi->create(400, 150);
+ vi->setExitable();
+ vi->setBorderOn(1);
+ vi->setTitleBarOn(0);
+
+ if (Video::getInstance()->getFormat() == Video::PAL)
+ vi->setScreenPos(170, 200);
+ else
+ vi->setScreenPos(160, 150);
+ vi->setOneLiner(tr("unable to get media list"));
+ vi->draw();
+
+ Message* m = new Message();
+ m->message = Message::ADD_VIEW;
+ m->to = ViewMan::getInstance();
+ m->parameter = (ULONG)vi;
+ Command::getInstance()->postMessageNoLock(m);
+ }
+ return 1;
+}
+
+const char * VMediaList::getDirname() const {
+ return dirlist->getCurrent()->getFullPath();
+}
--- /dev/null
+/*
+ Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef VMEDIALIST_H
+#define VMEDIALIST_H
+
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+
+#include "view.h"
+#include "wselectlist.h"
+#include "remote.h"
+#include "wsymbol.h"
+#include "viewman.h"
+#include "vdr.h"
+#include "media.h"
+#include "vvideolive.h"
+#include "colour.h"
+#include "video.h"
+#include "i18n.h"
+
+class DirList;
+class VMediaList : public View
+{
+ public:
+ VMediaList();
+ ~VMediaList();
+
+ void setList(MediaList* chanList);
+ void highlightMedia(Media* media);
+ const char *getDirname() const;
+ void processMessage(Message* m);
+ int handleCommand(int command);
+ void draw();
+
+ //factory method
+ //return 0 on success
+ static int createList();
+
+ //move functions for getMedia
+ const static ULONG MV_NEXT=1;
+ const static ULONG MV_PREV=2;
+ const static ULONG MV_RND=3;
+ const static ULONG MV_NONE=0;
+
+ //move selection to the next matching media
+ //with the given move
+ //return NULL if none found
+ Media * getMedia(int type,ULONG move=MV_NONE);
+
+ //get the number of media entries of particular types in this list
+ //if lowerThen is set, only count entries lt this one
+ int getNumEntries(int mediaType,int lowerThen=-1);
+
+ private:
+ /**
+ * fill the medialist basing on the current dirname
+ */
+ int load();
+ MediaList *mediaList;
+ WSelectList sl;
+ bool loading;
+ void doShowingBar();
+ int sortOrder;
+ //sort list defined by new order
+ void sortList(int order);
+ static const int SORT_NONE=0;
+ static const int SORT_NAME=1;
+ static const int SORT_TIME=2;
+ static const int SORT_RANDOM=3;
+ void clearMediaList();
+ void updateSelectList();
+ void updateSelectList(int current);
+ DirList* dirlist;
+
+};
+
+#endif
--- /dev/null
+/*
+ Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "vpicture.h"
+#include "vpicturebanner.h"
+#include <time.h>
+
+Colour VPicture::pictureBack=Colour(140,140,140);
+Colour VPicture::infoBack=Colour(110,110,110);
+//the jpeg reader
+
+class VPreader : public JpegReader {
+ private:
+ VPicture * parent;
+ public:
+ VPreader(VPicture *p){
+ parent=p;};
+ virtual ULONG readChunk(ULONG offset,ULONG len,char ** buf) {
+ Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "read chunk o=%d,len=%d,buf=%p",
+ offset,len,*buf);
+ UINT numrec=0;
+ *buf=(char *)VDR::getInstance()->getImageBlock(offset,(UINT)len,&numrec);
+ Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "got n=%d,buf=%p",
+ numrec,*buf);
+ return numrec;
+ }
+ virtual ULONG initRead(const char *filename) {
+ Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "load image %s",filename);
+ Video* video = Video::getInstance();
+ ULONG size=VDR::getInstance()->loadImage(filename,video->getScreenWidth(), video->getScreenHeight());
+ Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "load image %s returned %d",filename,size);
+ return size;
+ }
+};
+
+VPicture::VPicture(VMediaList *p)
+{
+ parent=p;
+ reader=new VPreader(this);
+ needDraw=false;
+ Video* video = Video::getInstance();
+ create(video->getScreenWidth(), video->getScreenHeight());
+ jpeg.setSurface(surface);
+ jpeg.setDimensions(area.w,area.h);
+ banner=NULL;
+ fullname=NULL;
+ filename=NULL;
+ ftime=0;
+ slideshow=false;
+ showtime=INITIAL_SHOWTIME;
+ mediaError=NULL;
+ currentMedia=NULL;
+ shortBanner=false;
+ rotate=0;
+ info=NULL;
+ jpeg.setBackgroundColour(pictureBack);
+}
+
+VPicture::~VPicture()
+{
+ delete reader;
+ if (banner) ViewMan::getInstance()->removeView(banner);
+ if (fullname) delete fullname;
+ if (filename) delete filename;
+ Timers::getInstance()->cancelTimer(this,1);
+ Timers::getInstance()->cancelTimer(this,2);
+ Timers::getInstance()->cancelTimer(this,3);
+ destroyInfo();
+
+}
+
+void VPicture::draw()
+{
+ Log::getInstance()->log("VPicture::draw", Log::DEBUG, "needDraw=%d,p=%p",
+ needDraw,this);
+ View::draw();
+ if (mediaError) {
+ drawText(mediaError,20,area.h-10,Colour::LIGHTTEXT);
+ return;
+ }
+ if (needDraw) {
+ jpeg.draw();
+ }
+}
+
+
+int VPicture::handleCommand(int command)
+{
+ Timers::getInstance()->cancelTimer(this,1);
+ int rt=1;
+ switch(command)
+ {
+ case Remote::DF_UP:
+ case Remote::UP:
+ case Remote::SKIPBACK:
+ showPicture(VMediaList::MV_PREV);
+ ViewMan::getInstance()->updateView(this);
+ rt= 2;
+ break;
+ case Remote::FORWARD:
+ if (showtime > 1) showtime--;
+ updateBanner(true);
+ break;
+ case Remote::DF_DOWN:
+ case Remote::DOWN:
+ case Remote::SKIPFORWARD:
+ showPicture(VMediaList::MV_NEXT);
+ ViewMan::getInstance()->updateView(this);
+ rt= 2;
+ break;
+ case Remote::REVERSE:
+ if (showtime < 50 ) showtime++;
+ updateBanner(true);
+ break;
+ case Remote::OK:
+ {
+ if (banner) {
+ destroyBanner();
+ destroyInfo();
+ }
+ else showBanner();
+ rt= 2;
+ }
+ break;
+ case Remote::PLAY:
+ {
+ slideshow=true;
+ showPicture(VMediaList::MV_NEXT);
+ ViewMan::getInstance()->updateView(this);
+ rt= 2;
+ }
+ break;
+ case Remote::PAUSE:
+ slideshow=false;
+ updateBanner();
+ rt= 2;
+ break;
+ case Remote::STOP:
+ slideshow=false;
+ showtime=INITIAL_SHOWTIME;
+ updateBanner();
+ rt= 2;
+ break;
+ case Remote::RED:
+ switch(rotate) {
+ case WJpeg::ROT_0:
+ rotate=WJpeg::ROT_90;
+ break;
+ case WJpeg::ROT_90:
+ rotate=WJpeg::ROT_180;
+ break;
+ case WJpeg::ROT_180:
+ rotate=WJpeg::ROT_270;
+ break;
+ case WJpeg::ROT_270:
+ rotate=WJpeg::ROT_0;
+ break;
+ }
+ showPicture(VMediaList::MV_NONE,rotate);
+ ViewMan::getInstance()->updateView(this);
+ rt=2;
+ break;
+ case Remote::GREEN:
+ if (info) destroyInfo();
+ else showInfo();
+ rt=2;
+ break;
+ case Remote::BACK:
+ {
+ rt= 4;
+ }
+ break;
+ }
+ if (slideshow) startSlideshow();
+ // stop command getting to any more views
+ return rt;
+}
+
+void VPicture::processMessage(Message* m)
+{
+ if (m->message == Message::MOUSE_MOVE)
+ {
+ ;
+ }
+ else if (m->message == Message::MOUSE_LBDOWN)
+ {
+
+ //check if press is outside this view! then simulate cancel
+ int x=(m->parameter>>16)-getScreenX();
+ int y=(m->parameter&0xFFFF)-getScreenY();
+ if (x<0 || y <0 || x>getWidth() || y>getHeight())
+ {
+ ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press
+ }
+ }
+}
+
+VPicture * VPicture::createViewer(VMediaList * mparent, bool bslideshow) {
+ Log::getInstance()->log("VPicture::createViewer", Log::DEBUG, "p=%p",
+ mparent);
+ VPicture *vmn=new VPicture(mparent);
+ ViewMan::getInstance()->add(vmn);
+ ViewMan::getInstance()->updateView(vmn);
+ vmn->showPicture();
+ if (bslideshow) vmn->startSlideshow();
+ vmn->showBanner();
+ ViewMan::getInstance()->updateView(vmn);
+ return vmn;
+}
+
+void VPicture::startSlideshow() {
+ slideshow=true;
+ Timers::getInstance()->setTimerD(this,1,showtime);
+}
+
+void VPicture::showPicture(ULONG move, int rt) {
+ rotate=rt;
+ currentMedia=parent->getMedia(MEDIA_TYPE_PICTURE,move);
+ load(currentMedia);
+ if (mediaError || jpeg.hasError()) destroyInfo();
+ else updateInfo();
+ ViewMan::getInstance()->updateView(this);
+}
+
+
+int VPicture::load(Media *md) {
+ jpeg.init(NULL,false,NULL);
+ mediaError=tr("No media found");
+ needDraw=false;
+ if (! md) return 1;
+ const char *fname=md->getFileName();
+ ftime=md->getTime();
+ int len=strlen(fname)+2+strlen(parent->getDirname());
+ if (fullname) {
+ delete fullname;
+ fullname=NULL;
+ }
+ fullname=new char[len];
+ sprintf(fullname,"%s/%s",parent->getDirname(),fname);
+ if (filename) delete filename;
+ len=strlen(fname)+1;
+ filename=new char[len];
+ strcpy(filename,fname);
+ Log::getInstance()->log("VPicture::load", Log::DEBUG, "filename=%s,p=%p",
+ fullname,this);
+ VDR* vdr=VDR::getInstance();
+ //do we have a banner?
+ bool haveBanner=banner!=NULL && ! shortBanner;
+ if (vdr->isConnected()) {
+ showBanner(true);
+ jpeg.init(fullname,false,reader);
+ jpeg.setRotate(rotate);
+ needDraw=true;
+ mediaError=NULL;
+ draw();
+ //only show the banner if it was there before
+ if (haveBanner) showBanner();
+ else {
+ if(banner) destroyBanner();
+ }
+ Log::getInstance()->log("VPicture::load", Log::DEBUG, "success: filename=%s,p=%p",
+ fullname,this);
+ return 0;
+ }
+ else {
+ mediaError=tr("no VDR connection");
+ }
+ return 1;
+}
+
+void VPicture::showBanner(bool loading,int shortDisplay) {
+ //if the loading flag is set,
+ //we are in the main thread - so we can (and must) safely hard destroy/create the banner
+ Timers::getInstance()->cancelTimer(this,2);
+ if (! filename || ! currentMedia) {
+ //hmm...
+ destroyBanner(!loading);
+ return;
+ }
+ if (banner) destroyBanner(!loading);
+ banner= new VPictureBanner(this, loading, slideshow);
+ banner->setBackgroundColour(infoBack);
+ if (! loading) {
+ int len=strlen(filename)+Media::TIMEBUFLEN+20;
+ char buf[len];
+ char tbuf[Media::TIMEBUFLEN];
+ snprintf(buf,len,"%c%02ds%c %s %s ",
+ slideshow?' ':'[',
+ showtime,
+ slideshow?' ':']',
+ currentMedia->getTimeString(tbuf),
+ filename);
+ banner->setText(buf);
+ }
+ else {
+ banner->setText(filename);
+ }
+ banner->draw();
+ if (shortDisplay != 0) shortBanner=true;
+ if (! slideshow && shortDisplay == 0) shortDisplay=8;
+ //OK we start timer if we don't load and either shortDisplay or no slideshow
+ if (! loading && shortDisplay != 0) Timers::getInstance()->setTimerD(this,2,shortDisplay);
+ if (! loading) sendViewMsg(banner,false);
+ else {
+ ViewMan::getInstance()->add(banner);
+ ViewMan::getInstance()->updateView(banner);
+ }
+
+ }
+
+void VPicture::destroyBanner(bool fromTimer) {
+ shortBanner=false;
+ if (banner) {
+ if (fromTimer) sendViewMsg(banner,true);
+ else ViewMan::getInstance()->removeView(banner);
+ banner=NULL;
+ if (! fromTimer) Timers::getInstance()->cancelTimer(this,2);
+ }
+}
+void VPicture::updateBanner(bool shortDisplay) {
+ if (banner && ! shortBanner) {
+ showBanner(false);
+ }
+ else if (shortDisplay) {
+ showBanner(false,2);
+ }
+}
+void VPicture::timercall(int clientref) {
+ switch(clientref)
+ {
+ case 1:
+ if (! slideshow) return;
+ Log::getInstance()->log("VPicture::timercall", Log::DEBUG, "slideshow");
+ sendCommandMsg(Remote::PLAY);
+ break;
+ case 2:
+ destroyBanner(true);
+ break;
+ case 3:
+ destroyInfo(true);
+ break;
+ }
+
+ }
+
+#define INFOBUF 1000
+void VPicture::showInfo(){
+ if (info) destroyInfo();
+ if (! currentMedia) return;
+ info=new VInfo();
+ info->setTitleText(currentMedia->getFileName());
+ info->setDropThrough();
+ info->create(500, 300);
+ info->setBorderOn(1);
+ info->setTitleBarOn(1);
+
+ if (Video::getInstance()->getFormat() == Video::PAL)
+ info->setScreenPos(100, 180);
+ else
+ info->setScreenPos(100, 150);
+ char buf[INFOBUF];
+ char tbuf[Media::TIMEBUFLEN];
+ snprintf(buf,INFOBUF,"%s= %s\n%s= %ld x %ld\n%s= %ld kBytes\n%s= %s\n%s= %ld\n%s= 1/%ld",
+ tr("Directory"), parent->getDirname(),
+ tr("Format(px)"),jpeg.getJpegInfo(WJpeg::JPEG_WIDTH),jpeg.getJpegInfo(WJpeg::JPEG_HEIGHT),
+ tr("Filesize"),jpeg.getJpegInfo(WJpeg::JPEG_SIZE)/1000,
+ tr("Time"),currentMedia->getTimeString(tbuf),
+ tr("Rotation"),90*jpeg.getJpegInfo(WJpeg::JPEG_ROTATE),
+ tr("Scale"),jpeg.getJpegInfo(WJpeg::JPEG_SCALE));
+ info->setMainText(buf);
+ info->draw();
+ sendViewMsg(info,false);
+ Timers::getInstance()->setTimerD(this,3,8);
+}
+void VPicture::updateInfo(){
+ if (info) {
+ showInfo();
+ }
+}
+void VPicture::destroyInfo(bool fromTimer){
+ if (info) {
+ sendViewMsg(info,true);
+ info=NULL;
+ }
+ if (! fromTimer) Timers::getInstance()->cancelTimer(this,3);
+}
+
+void VPicture::sendViewMsg(View *v,bool vdestroy) {
+ Message* m = new Message();
+ m->message = vdestroy?Message::CLOSE_ME:Message::ADD_VIEW;
+ m->to = ViewMan::getInstance();
+ m->from = v;
+ m->parameter=(ULONG)v;
+ Command::getInstance()->postMessageFromOuterSpace(m);
+}
+void VPicture::sendCommandMsg(int command) {
+ Message* m = new Message();
+ m->message = Message::UDP_BUTTON;
+ m->to = Command::getInstance();
+ m->from = this;
+ m->parameter=command;
+ Command::getInstance()->postMessageFromOuterSpace(m);
+}
--- /dev/null
+/*
+ Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef VPICTURE_H
+#define VPICTURE_H
+
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+
+#include "view.h"
+#include "wselectlist.h"
+#include "remote.h"
+#include "wsymbol.h"
+#include "viewman.h"
+#include "vdr.h"
+#include "media.h"
+#include "vvideolive.h"
+#include "colour.h"
+#include "video.h"
+#include "vinfo.h"
+#include "i18n.h"
+#include "vmedialist.h"
+#include "wjpeg.h"
+
+/**
+ * the picture viewer
+ * will ineract with the parent List for ff,back, slide show...
+ *
+*/
+class VPictureBanner;
+
+class VPicture : public View,public TimerReceiver
+{
+ public:
+ ~VPicture();
+
+ void processMessage(Message* m);
+ int handleCommand(int command);
+ void draw();
+ void timercall(int clientReference);
+ //factory method
+ //return NULL on error
+ static VPicture *createViewer(VMediaList * parent, bool startSlideshow=false);
+ //show the picture currently selected in the parent
+ //potentially moving to next/previous one
+ void showPicture(ULONG move=VMediaList::MV_NONE,int rotate=WJpeg::ROT_0);
+
+ //start a slideshow
+ void startSlideshow();
+
+
+ private:
+ VPicture(VMediaList * plist);
+ void showBanner(bool loading=false, int shortDisplayTime=0);
+ void destroyBanner(bool fromTimer=false);
+ void updateBanner(bool shortDisplay=false);
+ void showInfo();
+ void updateInfo();
+ void destroyInfo(bool fromTimer=false);
+ //add or destroy a view (banner, info)
+ void sendViewMsg(View *v,bool destroy=false);
+ //send command in main thread
+ void sendCommandMsg(int command);
+ VMediaList *parent;
+ int load(Media *m);
+ JpegReader *reader;
+ bool needDraw;
+ WJpeg jpeg;
+ VPictureBanner *banner;
+ char * fullname;
+ char * filename;
+ ULONG ftime;
+ bool slideshow;
+ int showtime;
+ char * mediaError;
+ Media * currentMedia;
+ const static int INITIAL_SHOWTIME=5;
+ bool shortBanner;
+ int rotate;
+ VInfo * info;
+ static Colour pictureBack;
+ static Colour infoBack;
+};
+
+#endif
--- /dev/null
+/*
+ Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "vpicturebanner.h"
+#include "vpicture.h"
+
+
+VPictureBanner::VPictureBanner(VPicture *p, bool ld, bool sl)
+{
+ loading=ld;
+ slideshow=sl;
+ parent=p;
+ Video *v=Video::getInstance();
+ create(v->getScreenWidth()-100, 36);
+ setScreenPos(50, v->getScreenHeight()-50);
+ setBackgroundColour(Colour::VIEWBACKGROUND);
+ setTitleBarOn(0);
+ info=NULL;
+ Log::getInstance()->log("VPictureBanner",Log::DEBUG,"created %p",this);
+ //TODO compute sizes from text
+ rotsize=70;
+ infsize=50;
+}
+
+VPictureBanner::~VPictureBanner()
+{
+ Log::getInstance()->log("VPictureBanner",Log::DEBUG,"deleted %p",this);
+}
+
+
+
+void VPictureBanner::draw()
+{
+ View::draw();
+ if (loading) {
+ if (info) {
+ char buf[strlen(info)+100];
+ sprintf(buf,"%s %s",tr("Loading"),info);
+ drawText(buf,5,area.h-25,Colour::LIGHTTEXT);
+ }
+ else drawText(tr("Loading"),5,3,Colour::LIGHTTEXT);
+ }
+ else {
+ int x=5;
+ rectangle(x, area.h - 24, 18, 16, Colour::RED);
+ x+=18+3;
+ drawText(tr("rotate"), x, area.h - 25, Colour::LIGHTTEXT);
+ x+=rotsize+3;
+ rectangle(x, area.h - 24, 18, 16, Colour::GREEN);
+ x+=18+3;
+ drawText(tr("info"), 5+18+3+rotsize+3+18+3, area.h - 25, Colour::LIGHTTEXT);
+ x+=infsize+3;
+ WSymbol w;
+ w.setSurface(surface);
+ if (slideshow) {
+ w.nextSymbol = WSymbol::PAUSE;
+ }
+ else {
+ w.nextSymbol = WSymbol::PLAY;
+ }
+ w.setSurfaceOffset(x, area.h-24);
+ w.draw();
+ x+=20+3;
+ if (info) {
+ drawText(info,x,area.h - 25,Colour::LIGHTTEXT);
+ }
+ }
+}
+
+int VPictureBanner::handleCommand(int command)
+{
+ //don not handle commands - leave this to the picture viewer
+ return 0;
+}
+
+
+
+void VPictureBanner::processMessage(Message* m)
+{
+ if (m->message == Message::MOUSE_MOVE)
+ {
+ ;
+ }
+ else if (m->message == Message::MOUSE_LBDOWN)
+ {
+ //check if press is outside this view! then simulate cancel
+ int x=(m->parameter>>16)-getScreenX();
+ int y=(m->parameter&0xFFFF)-getScreenY();
+ if (x<0 || y <0 || x>getWidth() || y>getHeight())
+ {
+ ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press
+ }
+ else if (y>=(int)area.h-24 && y<=(int)area.h-6)
+ {
+ //y coordinate is right!
+ if (x>=7 &&x<=25)
+ {
+ ViewMan::getInstance()->handleCommand(Remote::RED); //simulate red press
+ }
+ else if (x>=110 &&x<=128)
+ {
+ ViewMan::getInstance()->handleCommand(Remote::GREEN); //simulate red press
+ }
+ }
+ }
+}
+
+void VPictureBanner::setText(const char * text) {
+ if (info) delete info;
+ info=NULL;
+ if(!text) return;
+ info=new char[strlen(text)+1];
+ strcpy(info,text);
+}
+
+
+
+
+
--- /dev/null
+/*
+ Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+ This file is part of VOMP.
+
+ VOMP 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.
+
+ VOMP 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 VOMP; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef VPICTUREBANNER_H
+#define VPICTUREBANNER_H
+
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+#include <algorithm>
+
+#include "view.h"
+#include "remote.h"
+#include "colour.h"
+#include "video.h"
+#include "vinfo.h"
+#include "viewman.h"
+#include "i18n.h"
+
+class VPicture;
+
+class VPictureBanner : public View
+{
+ public:
+ VPictureBanner(VPicture *p,bool loading, bool slideshow);
+ ~VPictureBanner();
+ int handleCommand(int command);
+ void processMessage(Message* m);
+ void draw();
+ void setText(const char * text);
+
+ private:
+ VPicture * parent;
+ char * info;
+ bool loading;
+ bool slideshow;
+ int rotsize;
+ int infsize;
+};
+
+#endif
clockRegion.w = 60;
clockRegion.h = 30;
- create(460, 200);
+ create(460, 220);
if (Video::getInstance()->getFormat() == Video::PAL)
{
setScreenPos(140, 170);
sl.setSurface(surface);
sl.setSurfaceOffset(20, 40);
- sl.setDimensions(170, 140);
+ sl.setDimensions(200, 160);
setTitleText(tr("Welcome"));
sl.addOption(tr("1. Live TV"), 1, 1);
sl.addOption(tr("2. Radio"), 2, 0);
sl.addOption(tr("3. Recordings"), 3, 0);
sl.addOption(tr("4. Timers"), 4, 0);
- sl.addOption(tr("5. Options"), 5, 0);
- sl.addOption(tr("6. Reboot"), 6, 0);
+ sl.addOption(tr("5. MediaPlayer"), 5, 0);
+ sl.addOption(tr("6. Options"), 6, 0);
+ sl.addOption(tr("7. Reboot"), 7, 0);
jpeg.setSurface(surface);
jpeg.setSurfaceOffset(240, 60);
}
case Remote::FIVE:
{
- doOptions();
+ doMediaList();
return 2;
}
case Remote::SIX:
+ {
+ doOptions();
+ return 2;
+ }
+ case Remote::SEVEN:
{
Command::getInstance()->doReboot();
}
}
else if (option == 5)
{
- doOptions();
+ doMediaList();
return 2;
}
else if (option == 6)
+ {
+ doOptions();
+ return 2;
+ }
+ else if (option == 7)
{
Command::getInstance()->doReboot();
return 2;
}
}
+void VWelcome::doMediaList()
+{
+ VMediaList::createList();
+}
void VWelcome::doTimersList()
{
VTimerList* vtl = new VTimerList();
#include "i18n.h"
#include "timers.h"
#include "vscreensaver.h"
+#include "vmedialist.h"
class VWelcome : public View, public TimerReceiver
{
void doTimersList();
void doOptions();
void drawClock();
+ void doMediaList();
Region clockRegion;
};
*/
#include "wjpeg.h"
+#include <setjmp.h>
+#include "i18n.h"
-int WJpeg::init(char* tfileName)
+
+extern "C" void
+jpeg_memio_src (j_decompress_ptr cinfo, void * userdata);
+extern "C" void
+jpeg_memio_cleanup (j_decompress_ptr cinfo);
+
+WJpeg::WJpeg(){
+ fileName=NULL;
+ reader=NULL;
+ useImageDimensions=true;
+ jheight=0;
+ jwidth=0;
+ jsize=0;
+ jerror=true;
+ rotate=0;
+ jscale=1;
+}
+
+WJpeg::~WJpeg() {
+ if (fileName) delete fileName;
+}
+
+int WJpeg::init(char* tfileName,bool useImage, JpegReader *rdr)
{
- fileName = tfileName;
+ rotate=0;
+ if (fileName) delete fileName;
+ fileName=NULL;
+ if (tfileName) {
+ fileName = new char[strlen(tfileName)+1];
+ strcpy(fileName,tfileName);
+ }
+ reader=rdr;
+ useImageDimensions=useImage;
return 1;
}
-void WJpeg::draw()
+
+ULONG WJpeg::getJpegInfo(ULONG tag){
+ switch(tag) {
+ case JPEG_HEIGHT:
+ return jheight;
+ break;
+ case JPEG_WIDTH:
+ return jwidth;
+ break;
+ case JPEG_SIZE:
+ return jsize;
+ break;
+ case JPEG_ROTATE:
+ return rotate;
+ break;
+ case JPEG_SCALE:
+ return jscale;
+ break;
+ }
+ return 0;
+}
+
+int WJpeg::getJpegInfo(ULONG tag, char * buffer) {
+ switch (tag) {
+ case JPEG_FILENAME:
+ strncpy(buffer,fileName,INFO_BUFFER-1);
+ buffer[INFO_BUFFER-1]=0;
+ return 0;
+ break;
+ }
+ return -1;
+}
+
+void WJpeg::setRotate(int amount) {
+ rotate=amount;
+}
+
+extern "C" {
+
+
+struct my_error_mgr {
+ struct jpeg_error_mgr pub; /* "public" fields */
+ FILE *infile; /* to be used in error handler */
+
+ jmp_buf setjmp_buffer; /* for return to caller */
+};
+
+typedef struct my_error_mgr * my_error_ptr;
+
+/*
+ * Here's the routine that will replace the standard error_exit method:
+ */
+
+METHODDEF(void)
+my_error_exit (j_common_ptr cinfo)
{
-#ifndef WIN32
- Log* logger = Log::getInstance();
+ /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
+ my_error_ptr myerr = (my_error_ptr) cinfo->err;
- FILE* infile = fopen(fileName, "r");
- if (infile == NULL)
- {
- logger->log("BJpeg", Log::ERR, "Can't open JPEG");
- return;
+ /* Always display the message. */
+ /* We could postpone this until after returning, if we chose. */
+ (*cinfo->err->output_message) (cinfo);
+ if (myerr->infile) fclose(myerr->infile);
+ /* Return control to the setjmp point */
+ longjmp(myerr->setjmp_buffer, 1);
+}
+
+}
+
+bool WJpeg::hasError() {
+ return jerror;
+}
+void WJpeg::draw()
+{
+ jerror=false;
+ if (drawJpeg() != 0) {
+ jerror=true;
+ drawTextCentre(tr("Jpeg ERROR"), 240, 180, Colour::LIGHTTEXT);
}
- logger->log("BJpeg", Log::DEBUG, "File opened");
+}
+/* handle picture rotation
+ 90: xr=h-y
+ yr=x
+ 180:xr=w-x
+ yr=h-y
+ 270:xr=y
+ yr=w-x
+*/
+void WJpeg::drawPixel(int x, int y,int w,int h,int xpos, int ypos,Colour c){
+ int xb=0;
+ int yb=0;
+ switch(rotate) {
+ case ROT_0:
+ xb=x;
+ yb=y;
+ break;
+ case ROT_90:
+ xb=h-y;
+ yb=x;
+ break;
+ case ROT_180:
+ xb=w-x;
+ yb=h-y;
+ break;
+ case ROT_270:
+ xb=y;
+ yb=w-x;
+ break;
+ }
+ xb+=xpos;
+ yb+=ypos;
+ if (xb < 0 || yb < 0 ) {
+ Log::getInstance()->log("WJpeg:drawPixel",Log::ERR,"pixpos < 0 x=%d, y=%d",xb,yb);
+ return;
+ }
+ Box::drawPixel((UINT)xb,(UINT)yb,c);
+}
+
+int WJpeg::drawJpeg() {
+#ifndef WIN32
+ Log* logger = Log::getInstance();
+ unsigned char* buffer =NULL;
struct jpeg_decompress_struct cinfo;
- struct jpeg_error_mgr jerr;
- cinfo.err = jpeg_std_error(&jerr);
+ struct my_error_mgr jerr;
+ jerr.infile=NULL;
+ cinfo.err = jpeg_std_error(&jerr.pub);
+ jerr.pub.error_exit = my_error_exit;
+ /* Establish the setjmp return context for my_error_exit to use. */
+ if (setjmp(jerr.setjmp_buffer)) {
+ /* If we get here, the JPEG code has signaled an error.
+ * We need to clean up the JPEG object, close the input file, and return.
+ */
+ if (reader) jpeg_memio_cleanup(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ logger->log("BJpeg", Log::ERR, "JPEG error");
+ if (buffer) free(buffer);
+ return 1;
+ }
+ int xpos=0;
+ int ypos=0;
jpeg_create_decompress(&cinfo);
- jpeg_stdio_src(&cinfo, infile);
+ if (fileName && ! reader) {
+ jsize=0; //TODO: compute size for local files
+ jerr.infile=fopen(fileName, "r");
+ if (jerr.infile == NULL)
+ {
+ logger->log("BJpeg", Log::ERR, "Can't open JPEG");
+ jpeg_destroy_decompress(&cinfo);
+ return 1;
+ }
+ logger->log("BJpeg", Log::DEBUG, "File opened");
+ jpeg_stdio_src(&cinfo, jerr.infile);
+ }
+ else if (reader) {
+ jsize=reader->initRead(fileName);
+ if (jsize <= 0) {
+ logger->log("BJpeg", Log::ERR, "Can't init JPEG transfer");
+ jpeg_destroy_decompress(&cinfo);
+ return 1;
+ }
+ jpeg_memio_src(&cinfo,(void *)reader);
+ }
+ else {
+ logger->log("BJpeg", Log::ERR, "neither filename nor reader set");
+ jpeg_destroy_decompress(&cinfo);
+ return 1;
+ }
jpeg_read_header(&cinfo, TRUE);
- jpeg_start_decompress(&cinfo);
+ logger->log("BJpeg", Log::DEBUG, "JPEG read header w=%i h=%i, rot=%i", cinfo.image_width, cinfo.image_height, rotate);
+ int picturew=cinfo.image_width;
+ int pictureh=cinfo.image_height;
+ switch (rotate){
+ case ROT_90:
+ case ROT_270:
+ pictureh=cinfo.image_width;
+ picturew=cinfo.image_height;
+ break;
+ }
+ if (! useImageDimensions) {
+ //scale - we can have factors 1,2,4,8
+ int scalew=getWidth()*1000/picturew;
+ int scaleh=getHeight()*1000/pictureh;
+ int scale=scaleh;
+ if (scalew < scaleh) scale=scalew;
+ int fac=8;
+ //we allow for 10% bigger...
+ if (scale >= 900) fac=1;
+ else if (scale >= 450 ) fac=2;
+ else if (scale >= 225 ) fac=4;
+ cinfo.scale_denom=fac;
+ logger->log("BJpeg", Log::DEBUG, "JPEG scaling (1/1000) pw=%i ph=%i w=%i h=%i r=%i f=%i",
+ getWidth(),
+ getHeight(),
+ scalew,scaleh,scale,fac);
+ jscale=fac;
+ }
- logger->log("BJpeg", Log::DEBUG, "JPEG startup done %i %i", cinfo.output_width, cinfo.output_height);
+ //remember picture parameters
+ jheight=pictureh;
+ jwidth=picturew;
- // Init the surface
- setDimensions(cinfo.output_width, cinfo.output_height);
+ jpeg_start_decompress(&cinfo);
+ //recompute width based on rotation (consider scale)
+ picturew=cinfo.output_width;
+ pictureh=cinfo.output_height;
+ switch (rotate){
+ case ROT_90:
+ case ROT_270:
+ pictureh=cinfo.output_width;
+ picturew=cinfo.output_height;
+ break;
+ }
+ //if our image is smaller - center it
+ if (! useImageDimensions) {
+ xpos=(getWidth()-picturew)/2;
+ if (xpos <0) xpos=0;
+ ypos=(getHeight()-pictureh)/2;
+ if (ypos <0) ypos=0;
+ }
+ //center point for rotation
+ int w=cinfo.output_width;
+ int h=cinfo.output_height;
+ logger->log("BJpeg", Log::DEBUG, "JPEG startup done pw=%i ph=%i, xo=%i,yo=%i, iw=%i, ih=%i", picturew, pictureh,xpos,ypos,w,h);
+ // Init the surface
+ if (useImageDimensions) setDimensions(picturew, pictureh);
+ fillColour(backgroundColour);
+ //line len in bytes (3 bytes per Pixel) - for buffer (can be bigger then surface)
+ int linelen=cinfo.output_width*3;
+#ifdef USE_BUFFER
// MAKE THE 2D ARRAY
+ buffer = (unsigned char*)malloc(config.output_height * linelen);
+ logger->log("BJpeg", Log::DEBUG, "Buffer allocated at %p, width = %i height = %i", buffer, cinfo.output_height, linelen);
+ if (buffer == NULL) {
+ if (reader) jpeg_memio_cleanup(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ if (jerr.infile) fclose(jerr.infile);
+ logger->log("BJpeg", Log::ERR, "JPEG error - no room for buffer");
+ return 1;
+ }
+#endif
- unsigned char* buffer = (unsigned char*)malloc(area.w * area.h * 3);
- logger->log("BJpeg", Log::DEBUG, "Buffer allocated at %p, width = %i height = %i", buffer, area.w, area.h);
-
- unsigned char* bufferPointers[area.h];
- for(UINT ps = 0; ps < area.h; ps++) bufferPointers[ps] = buffer + (ps * area.w * 3);
-
- logger->log("BJpeg", Log::DEBUG, "done array check");
+ //unsigned char* bufferPointers[area.h];
+ //for(UINT ps = 0; ps < area.h; ps++) bufferPointers[ps] = buffer + (ps * area.w * 3);
+
+ logger->log("BJpeg", Log::DEBUG, "header w=%d,h=%d",cinfo.output_width,cinfo.output_height);
+#ifndef USE_BUFFER
+ unsigned char lbuf[linelen];
+ unsigned char * ptr=lbuf;
+#else
+ unsigned char * ptr=buffer;
+#endif
int rowsread = 0;
+
+ Colour c;
while (cinfo.output_scanline < cinfo.output_height)
{
// logger->log("BJpeg", Log::DEBUG, "%i", rowsread);
- rowsread += jpeg_read_scanlines(&cinfo, &bufferPointers[rowsread], area.h);
+ rowsread += jpeg_read_scanlines(&cinfo,&ptr,1);
+#ifdef USE_BUFFER
+ ptr+=linelen;
+#else
+ int x=0;
+ for (unsigned char * lp=ptr;lp < (ptr+linelen);lp+=3,x++) {
+ c.red=*lp ;
+ c.green=*(lp+1);
+ c.blue=*(lp+2);
+ drawPixel(x, rowsread-1,w,h,xpos,ypos, c);
+ }
+
+#endif
}
logger->log("BJpeg", Log::DEBUG, "Done all jpeg_read");
jpeg_finish_decompress(&cinfo);
+ if (reader) jpeg_memio_cleanup(&cinfo);
jpeg_destroy_decompress(&cinfo);
- fclose(infile);
+ if (jerr.infile) fclose(jerr.infile);
logger->log("BJpeg", Log::DEBUG, "jpeg shutdown done, x, y %u %u", area.w, area.h);
-
+#ifdef USE_BUFFER
Colour c;
UINT x, y, xoff;
unsigned char* p;
- for (y = 0; y < area.h; y++)
+ for (y = 0; y < numlines; y++)
{
p = bufferPointers[y];
- for (x = 0; x < area.w; x++)
+ for (x = 0; x < sfclen; x++)
{
xoff = x * 3;
c.green = p[xoff + 1];
c.blue = p[xoff + 2];
- drawPixel(x, y, c);
+ drawPixel(x, y, w,h,xpos,ypos,c);
}
}
free(buffer);
+#endif
logger->log("BJpeg", Log::DEBUG, "deleted buffer");
#else
DWORD width,height;
setDimensions(width, height);
#endif
+ return 0;
+}
+
+
+extern "C" {
+ULONG jpeg_call_reader(ULONG offset,ULONG size,char ** buf,void *cb) {
+ JpegReader *rd=(JpegReader *)cb;
+ return rd->readChunk(offset,size,buf);
+}
+}
+//the memory buffer reader for the jpeg lib
+//taken from jdatasrc.c
+
+extern "C" {
+/* Expanded data source object for stdio input */
+#include "jinclude.h"
+#include "jpeglib.h"
+#include "jerror.h"/* Expanded data source object for stdio input */
+
+typedef struct {
+ struct jpeg_source_mgr pub; /* public fields */
+
+ JOCTET * buffer; /* start of buffer */
+ boolean start_of_file; /* have we gotten any data yet? */
+ void * userdata; /* used for callback */
+ ULONG offset;
+} my_source_mgr;
+
+typedef my_source_mgr * my_src_ptr;
+
+#define INPUT_BUF_SIZE (64*4096) /* choose an efficiently fread'able size */
+
+
+/*
+ * Initialize source --- called by jpeg_read_header
+ * before any data is actually read.
+ */
+
+METHODDEF(void)
+linit_source (j_decompress_ptr cinfo)
+{
+ my_src_ptr src = (my_src_ptr) cinfo->src;
+
+ /* We reset the empty-input-file flag for each image,
+ * but we don't clear the input buffer.
+ * This is correct behavior for reading a series of images from one source.
+ */
+ src->start_of_file = TRUE;
+ src->offset=0;
+}
+
+
+/*
+ * Fill the input buffer --- called whenever buffer is emptied.
+ *
+ * In typical applications, this should read fresh data into the buffer
+ * (ignoring the current state of next_input_byte & bytes_in_buffer),
+ * reset the pointer & count to the start of the buffer, and return TRUE
+ * indicating that the buffer has been reloaded. It is not necessary to
+ * fill the buffer entirely, only to obtain at least one more byte.
+ *
+ * There is no such thing as an EOF return. If the end of the file has been
+ * reached, the routine has a choice of ERREXIT() or inserting fake data into
+ * the buffer. In most cases, generating a warning message and inserting a
+ * fake EOI marker is the best course of action --- this will allow the
+ * decompressor to output however much of the image is there. However,
+ * the resulting error message is misleading if the real problem is an empty
+ * input file, so we handle that case specially.
+ *
+ * In applications that need to be able to suspend compression due to input
+ * not being available yet, a FALSE return indicates that no more data can be
+ * obtained right now, but more may be forthcoming later. In this situation,
+ * the decompressor will return to its caller (with an indication of the
+ * number of scanlines it has read, if any). The application should resume
+ * decompression after it has loaded more data into the input buffer. Note
+ * that there are substantial restrictions on the use of suspension --- see
+ * the documentation.
+ *
+ * When suspending, the decompressor will back up to a convenient restart point
+ * (typically the start of the current MCU). next_input_byte & bytes_in_buffer
+ * indicate where the restart point will be if the current call returns FALSE.
+ * Data beyond this point must be rescanned after resumption, so move it to
+ * the front of the buffer rather than discarding it.
+ */
+
+METHODDEF(boolean)
+lfill_input_buffer (j_decompress_ptr cinfo)
+{
+ my_src_ptr src = (my_src_ptr) cinfo->src;
+ size_t nbytes;
+ if (src->buffer) free(src->buffer);
+ src->buffer=NULL;
+ nbytes = jpeg_call_reader(src->offset, INPUT_BUF_SIZE,(char **)&(src->buffer), src->userdata);
+
+ if (nbytes <= 0) {
+ WARNMS(cinfo, JWRN_JPEG_EOF);
+ src->buffer = (JOCTET *)malloc(2);
+ src->buffer[0] = (JOCTET) 0xFF;
+ src->buffer[1] = (JOCTET) JPEG_EOI;
+ nbytes = 2;
+
+ }
+ src->offset+=nbytes;
+
+ src->pub.next_input_byte = src->buffer;
+ src->pub.bytes_in_buffer = nbytes;
+ src->start_of_file = FALSE;
+
+ return TRUE;
+}
+
+
+/*
+ * Skip data --- used to skip over a potentially large amount of
+ * uninteresting data (such as an APPn marker).
+ *
+ * Writers of suspendable-input applications must note that skip_input_data
+ * is not granted the right to give a suspension return. If the skip extends
+ * beyond the data currently in the buffer, the buffer can be marked empty so
+ * that the next read will cause a fill_input_buffer call that can suspend.
+ * Arranging for additional bytes to be discarded before reloading the input
+ * buffer is the application writer's problem.
+ */
+
+METHODDEF(void)
+lskip_input_data (j_decompress_ptr cinfo, long num_bytes)
+{
+ my_src_ptr src = (my_src_ptr) cinfo->src;
+
+ /* Just a dumb implementation for now. Could use fseek() except
+ * it doesn't work on pipes. Not clear that being smart is worth
+ * any trouble anyway --- large skips are infrequent.
+ */
+ if (num_bytes > 0) {
+ while (num_bytes > (long) src->pub.bytes_in_buffer) {
+ num_bytes -= (long) src->pub.bytes_in_buffer;
+ (void) lfill_input_buffer(cinfo);
+ /* note we assume that fill_input_buffer will never return FALSE,
+ * so suspension need not be handled.
+ */
+ }
+ src->pub.next_input_byte += (size_t) num_bytes;
+ src->pub.bytes_in_buffer -= (size_t) num_bytes;
+ }
+}
+
+
+/*
+ * An additional method that can be provided by data source modules is the
+ * resync_to_restart method for error recovery in the presence of RST markers.
+ * For the moment, this source module just uses the default resync method
+ * provided by the JPEG library. That method assumes that no backtracking
+ * is possible.
+ */
+
+
+/*
+ * Terminate source --- called by jpeg_finish_decompress
+ * after all data has been read. Often a no-op.
+ *
+ * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
+ * application must deal with any cleanup that should happen even
+ * for error exit.
+ */
+
+METHODDEF(void)
+lterm_source (j_decompress_ptr cinfo)
+{
+ /* no work necessary here */
+}
+
+
+/*
+ * Prepare for input from a stdio stream.
+ * The caller must have already opened the stream, and is responsible
+ * for closing it after finishing decompression.
+ */
+
+extern "C" void
+jpeg_memio_src (j_decompress_ptr cinfo, void * userdata)
+{
+ my_src_ptr src;
+
+ /* The source object and input buffer are made permanent so that a series
+ * of JPEG images can be read from the same file by calling jpeg_stdio_src
+ * only before the first one. (If we discarded the buffer at the end of
+ * one image, we'd likely lose the start of the next one.)
+ * This makes it unsafe to use this manager and a different source
+ * manager serially with the same JPEG object. Caveat programmer.
+ */
+ if (cinfo->src == NULL) { /* first time for this JPEG object? */
+ cinfo->src = (struct jpeg_source_mgr *)
+ (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
+ SIZEOF(my_source_mgr));
+ src = (my_src_ptr) cinfo->src;
+ src->buffer = NULL;
+ }
+
+ src = (my_src_ptr) cinfo->src;
+ src->pub.init_source = linit_source;
+ src->pub.fill_input_buffer = lfill_input_buffer;
+ src->pub.skip_input_data = lskip_input_data;
+ src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
+ src->pub.term_source = lterm_source;
+ src->userdata=userdata;
+ src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
+ src->pub.next_input_byte = NULL; /* until buffer loaded */
+ src->offset=0;
+ src->userdata=userdata;
+ if (src->buffer) {
+ free(src->buffer);
+ src->buffer=NULL;
+ }
+}
+/* cleanup to be called before cleanup of cinfo*/
+extern "C" void
+jpeg_memio_cleanup (j_decompress_ptr cinfo) {
+ my_src_ptr src=(my_src_ptr)cinfo->src;
+ Log::getInstance()->log("BJpeg", Log::DEBUG, "cleanup src, src=%p, buf=%p",src,(src?src->buffer:0));
+ if (src && src->buffer) {
+ free(src->buffer);
+ src->buffer=NULL;
+ }
+}
}
#include "widget.h"
#include "surface.h"
+//a reader to be implemented by the caller
+class JpegReader {
+ public:
+ //init reading the file
+ //return <=0 if error or size of image
+ virtual ULONG initRead(const char *filename)=0;
+ //read the next chunk of jpeg data
+ //offset - from start of file
+ //len I buf len (max bytes to read)
+ //return read len, 0 on EOF, -1 on error, *buf set to buffer
+ //will be released with free(!!!) after decoding
+ virtual ULONG readChunk(ULONG offset,ULONG len,char **buf)=0;
+ virtual ~JpegReader(){};
+};
+
class WJpeg : public Widget
{
public:
-
- int init(char* fileName);
+ WJpeg();
+ virtual ~WJpeg();
+ int init(char* fileName, bool useImage=true, JpegReader *rdr=NULL);
+ //rotate (with next draw!) by 90/180/270
+ void setRotate(int amount);
+ static const int ROT_0=0;
+ static const int ROT_90=1;
+ static const int ROT_180=2;
+ static const int ROT_270=3;
void draw();
+ bool hasError();
+ /**
+ * get an integer info from the current picture
+ */
+ ULONG getJpegInfo(ULONG tag);
+ //in tags
+ static const ULONG JPEG_WIDTH=1; //picture width
+ static const ULONG JPEG_HEIGHT=2; //picture height
+ static const ULONG JPEG_SIZE=3; //picture (compressed) size
+ static const ULONG JPEG_ROTATE=4; //rotate setting
+ static const ULONG JPEG_SCALE=5; //scale setting (1/n)
+ /**
+ * get a string info from the picture
+ * the user must provide a buffer of
+ * INFO_BUFFER size
+ */
+ int getJpegInfo(ULONG tag, char * buffer);
+ static const int INFO_BUFFER=200;
+ //string info tags
+ static const ULONG JPEG_FILENAME=100; //abbreviated filename
private:
+ int drawJpeg();
+ //our drawPixel with considers rotation
+ void drawPixel(int x, int y,int w,int h,int xpos, int ypos,Colour c);
char* fileName;
+ JpegReader *reader;
+ bool useImageDimensions;
+ ULONG jsize;
+ ULONG jheight;
+ ULONG jwidth;
+ ULONG jscale;
+ bool jerror;
+ int rotate;
};
#endif