diff options
author | lvw <lvw@e10066b5-e1e2-0310-b819-94efdf66514b> | 2004-05-28 15:29:19 +0000 |
---|---|---|
committer | lvw <lvw@e10066b5-e1e2-0310-b819-94efdf66514b> | 2004-05-28 15:29:19 +0000 |
commit | 616adfc77dc1d08f3bfcd79991a78c6350e4e2f6 (patch) | |
tree | a47349dfb9e7729736c7b5892681a7c1c61dff89 | |
parent | 1a95137eb0f4d533bc38377558de8ccc442e5de6 (diff) | |
download | vdr-plugin-muggle-616adfc77dc1d08f3bfcd79991a78c6350e4e2f6.tar.gz vdr-plugin-muggle-616adfc77dc1d08f3bfcd79991a78c6350e4e2f6.tar.bz2 |
Merged player branch back on HEAD branch.
git-svn-id: https://vdr-muggle.svn.sourceforge.net/svnroot/vdr-muggle/trunk/muggle-plugin@98 e10066b5-e1e2-0310-b819-94efdf66514b
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | TODO | 113 | ||||
-rw-r--r-- | gd_content_interface.c | 121 | ||||
-rw-r--r-- | gd_content_interface.h | 187 | ||||
-rw-r--r-- | i18n.c | 66 | ||||
-rw-r--r-- | i18n.h | 2 | ||||
-rwxr-xr-x | mg_content_interface.c | 155 | ||||
-rwxr-xr-x | mg_content_interface.h | 247 | ||||
-rw-r--r-- | mg_database.c | 13 | ||||
-rw-r--r-- | mg_database.h | 37 | ||||
-rw-r--r-- | mg_filters.c | 14 | ||||
-rw-r--r-- | mg_filters.h | 6 | ||||
-rw-r--r-- | mg_media.c | 70 | ||||
-rw-r--r-- | mg_media.h | 67 | ||||
-rw-r--r-- | mg_playlist.c | 209 | ||||
-rw-r--r-- | mg_playlist.h | 143 | ||||
-rw-r--r-- | mg_tools.h | 61 | ||||
-rw-r--r-- | muggle.c | 122 | ||||
-rw-r--r-- | muggle.h | 25 | ||||
-rw-r--r-- | vdr_config.h | 119 | ||||
-rw-r--r-- | vdr_decoder.c | 137 | ||||
-rw-r--r-- | vdr_decoder.h | 158 | ||||
-rw-r--r-- | vdr_decoder_mp3.c | 450 | ||||
-rw-r--r-- | vdr_decoder_mp3.h | 114 | ||||
-rw-r--r-- | vdr_menu.c | 373 | ||||
-rw-r--r-- | vdr_menu.h | 92 | ||||
-rw-r--r-- | vdr_network.h | 45 | ||||
-rw-r--r-- | vdr_player.c | 1073 | ||||
-rw-r--r-- | vdr_player.h | 137 | ||||
-rw-r--r-- | vdr_setup.c | 70 | ||||
-rw-r--r-- | vdr_setup.h | 76 | ||||
-rw-r--r-- | vdr_sound.c | 609 | ||||
-rw-r--r-- | vdr_stream.c | 332 | ||||
-rw-r--r-- | vdr_stream.h | 57 |
34 files changed, 4907 insertions, 602 deletions
@@ -1,7 +1,7 @@ # # Makefile for a Video Disk Recorder plugin # -# $Id: Makefile,v 1.8 2004/02/23 15:41:21 RaK Exp $ +# $Id: Makefile,v 1.9 2004/05/28 15:29:18 lvw Exp $ # The official name of this plugin. # This name will be used in the '-P...' option of VDR to load the plugin. @@ -46,8 +46,9 @@ DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' ### The object files (add further files here): -OBJS = $(PLUGIN).o vdr_menu.o mg_database.o mg_content_interface.o gd_content_interface.o mg_tools.o mg_media.o mg_filters.o i18n.o -BINOBJS = mg_database.o mg_content_interface.o gd_content_interface.o mg_tools.o mg_media.o mg_filters.o +OBJS = $(PLUGIN).o i18n.o vdr_menu.o mg_database.o mg_content_interface.o gd_content_interface.o mg_tools.o mg_media.o mg_filters.o mg_playlist.o vdr_decoder_mp3.o vdr_stream.o vdr_decoder.o vdr_player.o vdr_setup.o + +LIBS = -lmad -lmysqlclient ### Targets: @@ -68,7 +69,7 @@ $(DEPFILE): Makefile $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< libvdr-$(PLUGIN).so: $(OBJS) - $(CXX) $(CXXFLAGS) -shared $(OBJS) -lmysqlclient -o $@ + $(CXX) $(CXXFLAGS) -shared $(OBJS) $(LIBS) -o $@ @cp $@ $(LIBDIR)/$@.$(VDRVERSION) dist: clean @@ -3,45 +3,126 @@ TODO File for Muggle Code polishing ============== -- check for memory leaks -- check for (reasonably) consistent usage of char*/string -- mg_database is not used +- Check for memory leaks +- Check for (reasonably) consistent usage of char*/string +- mgDatabase is not used? mgPlayer used what for? +- Check for unnecessary log commands +- Add logs, documentation (generate in HTML) +- Generate HTML documentation using doxygen, dotty/gv for state machines Short term items ================ +- Adapt scripts + - use working directory by default or argument + - how to install into correct path OSD --- -- DisplayTrackInfo -- DisplayAlbumInfo -- Edit playlist (means moving tracks around?) Content ------- +- Handle loop mode in mgPlaylist +- Handle shuffle mode in mgPlaylist +- Save/load playlists to database + +Player +------ +- Determine max. framecount (needed for rewinding) ? +- Init scale/level/normalize? +- Check play speed +- Add ogg decoder + +- Add a simple progress display (song title, artist, ...) +- DisplayTrackInfo (part of the player!) +- DisplayAlbumInfo (part of the player!) Medium term items ================= -- Add a player for various formats - - from mp3 plugin - - use mplayer in slave mode - - check mpg123 (used in GD) +- really abstract from specific queries etc. +- mgDatabase should abstract database stuff!? -Long term items -=============== +OSD +--- +- Type numbers to enter characters and jump to first title accordingly +- Add separators for first letter (A, B, C, ...) that can be skipped using left/right +Content +------- +- Save/load filter sets +- Apply filter set as dynamic playlist + +Player +------ +- Shuffle: toggle and loop keys. Shuffle only songs not already played, not easy though - Display covers as still pictures +- Add flac decoder + +Web interface +------------- +- Look at PHP stuff from c't 11/04 and adapt to schema + +Long term ideas +=============== +- daapd integration? +- Display arbitrary images while playing music Visions ======= - -- handle films -- handle off-line media (CDs, DVDs) -- handle EPG +- handle off-line films (CDs, DVDs, recordings) +- handle streams (live TV with channel list, MP3 radio,..., EPG) +- handle images (possibly in sync with music/radio) +- muggle content syndication (e.g. via DAAPD) +- provide a stream (e.g. icecast) of the currently played music? Already Done ============ +- Delete selected item +- Add command line option for top level directory + - prepended to filename in non-GD-mode + - searched in GD-mode +- Edit playlist (move tracks like channels in VDR channel list) + (OK in playlist view) +- Instant play = empty current playlist, append tracks of current node and play + (easy, in submenu of browser) +- Clear playlist (submenu action) +- Find files from database entry based on GD compatibility flag +- Handle Next/PrevFile in mgPlaylist (vdr_player.c) +- Add plugin parameters for database name/host/user/pass +- Add plugin parameter for GD filename compatibility - handle filters: - create tracklist from filter - create tree from filter - i18n (english and german) + +************************************************************ +* +* $Log: TODO,v $ +* Revision 1.7 2004/05/28 15:29:18 lvw +* Merged player branch back on HEAD branch. +* +* Revision 1.1.2.12 2004/05/27 07:58:38 lvw +* Removed bugs in moving and removing tracks from playlists +* +* Revision 1.1.2.11 2004/05/25 21:57:58 lvw +* Updated TODO list +* +* Revision 1.1.2.10 2004/05/25 00:10:45 lvw +* Code cleanup and added use of real database source files +* +* Revision 1.1.2.9 2004/05/24 11:48:35 lvw +* Extended TODO list +* +* Revision 1.1.2.8 2004/05/13 06:48:00 lvw +* Updated TODO list +* +* Revision 1.1.2.7 2004/05/12 22:38:37 lvw +* Some cleanup +* +* Revision 1.1.2.6 2004/05/11 06:35:16 lvw +* Added debugging while hunting stop bug. +* +* Revision 1.1.2.5 2004/05/07 06:46:41 lvw +* Removed a bug in playlist deallocation. Added infrastructure to display information while playing. +* +***********************************************************/ diff --git a/gd_content_interface.c b/gd_content_interface.c index ba5429a..9c339d6 100644 --- a/gd_content_interface.c +++ b/gd_content_interface.c @@ -1,19 +1,16 @@ -/*******************************************************************/ /*! \file content_interface.cpp - * \brief Data Objects for content (e.g. mp3 files, movies) - * for the vdr muggle plugindatabase - ******************************************************************** - * \version $Revision: 1.21 $ - * \date $Date: 2004/02/23 17:03:24 $ + * \brief Data Objects for content (e.g. mp3 files, movies) for the vdr muggle plugindatabase + * + * \version $Revision: 1.22 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: RaK $ + * \author Responsible author: $Author: lvw $ * - * DUMMY * Implements main classes of for content items and interfaces to SQL databases * * This file implements the following classes * - GdPlaylist a playlist - * - mgGdTrack a single track (content item). e.g. an mp3 file + * - mgGdTrack a single track (content item). e.g. an mp3 file * - mgSelection a set of tracks (e.g. a database subset matching certain criteria) * */ @@ -21,8 +18,10 @@ #define DEBUG #include "gd_content_interface.h" + #include "mg_tools.h" #include "i18n.h" +#include "vdr_setup.h" using namespace std; @@ -35,16 +34,23 @@ using namespace std; // non-member function int GdInitDatabase(MYSQL *db) { - if(mysql_init(db) == NULL) + if( mysql_init(db) == NULL ) { return -1; } - if(mysql_real_connect(db,"localhost","root","", - "GiantDisc",0,NULL,0) == NULL) - { + // if(mysql_real_connect( db, "localhost", "root", "", + // "GiantDisc2", 0, NULL, 0) == NULL) + if( mysql_real_connect( db, + the_setup.DbHost, + the_setup.DbUser, + the_setup.DbPass, + the_setup.DbName, + the_setup.DbPort, + NULL, 0 ) == NULL ) + { return -2; - } + } return 0; } @@ -63,20 +69,12 @@ vector<string> *GdGetStoredPlaylists(MYSQL db) return list; } -//------------------------------------------------------------------ -//------------------------------------------------------------------ -// -// class gdTrackFilters -// -//------------------------------------------------------------------ -//------------------------------------------------------------------ - /*! - ******************************************************************* + * * \brief constructor, constracts a number >=1 of filter sets * * the first set (index 0 ) is active by default - ********************************************************************/ + */ gdFilterSets::gdFilterSets() { mgFilter* filter; @@ -102,8 +100,9 @@ gdFilterSets::gdFilterSets() filter = new mgFilterString(tr("artist"), ""); set->push_back(filter); // genre filter = new mgFilterString(tr("genre"), ""); set->push_back(filter); - // rating - filter = new mgFilterChoice(tr("rating"), 1, rating); set->push_back(filter); + + // rating. TODO: Currently buggy. LVW + // filter = new mgFilterChoice(tr("rating"), 1, rating); set->push_back(filter); m_sets.push_back(set); @@ -170,7 +169,8 @@ string gdFilterSets::computeRestriction(int *viewPrt) { string sql_str = "1"; - switch (m_activeSetId) { + switch (m_activeSetId) + { case 0: *viewPrt = 100; // tracks (flatlist for mountain man ;-)) break; @@ -261,7 +261,7 @@ string gdFilterSets::computeRestriction(int *viewPrt) //------------------------------------------------------------------ //------------------------------------------------------------------ // -// class mgTack +// class mgTrack // //------------------------------------------------------------------ //------------------------------------------------------------------ @@ -269,7 +269,7 @@ mgGdTrack mgGdTrack::UNDEFINED = mgGdTrack(); /*! ***************************************************************************** - * \brief Constructor: creates a mgGdTrack obect + * \brief Constructor: creates a mgGdTrack object * * \param sqlIdentifier identifies a unique row in the track database * \param dbase database which stores the track table @@ -296,16 +296,15 @@ mgGdTrack::mgGdTrack(const mgGdTrack& org) m_db = org.m_db; m_retrieved = org.m_retrieved; if(m_retrieved) - { - m_artist = org.m_artist; - m_title = org.m_title; - m_mp3file = org.m_mp3file; - m_album = org.m_album; - m_genre = org.m_genre; - m_year = org.m_year; - m_rating = org.m_rating; - } - + { + m_artist = org.m_artist; + m_title = org.m_title; + m_mp3file = org.m_mp3file; + m_album = org.m_album; + m_genre = org.m_genre; + m_year = org.m_year; + m_rating = org.m_rating; + } } @@ -459,6 +458,7 @@ vector<mgFilter*> *mgGdTrack::getTrackInfo() { return new vector<mgFilter*>(); } + bool mgGdTrack::setTrackInfo(vector<mgFilter*> *info) { return false; @@ -766,7 +766,7 @@ GdPlaylist::GdPlaylist(unsigned int sql_identifier, MYSQL db_handle) m_listname = row[0]; m_author = row[1]; m_sqlId = sql_identifier; - // now read allentries of the playlist and + // now read all entries of the playlist and // write them into the tracklist insertDataFromSQL(); } @@ -1391,6 +1391,46 @@ mgContentItem* GdTreeNode::getSingleTrack() /* -------------------- begin CVS log --------------------------------- * $Log: gd_content_interface.c,v $ + * Revision 1.22 2004/05/28 15:29:18 lvw + * Merged player branch back on HEAD branch. + * + * + * Revision 1.21 2004/02/23 17:03:24 RaK + * - error in filter view while trying to switch or using the colour keys + * workaround: first filter criteria is inttype. than it works, dont ask why ;-( + * + * Revision 1.20 2004/02/23 16:30:58 RaK + * - album search error because of i18n corrected + * + * Revision 1.19 2004/02/23 15:56:19 RaK + * - i18n + * + * Revision 1.18 2004/02/23 15:17:51 RaK + * - i18n + * + * Revision 1.17 2004/02/23 15:41:21 RaK + * - first i18n attempt + * + * Revision 1.16 2004/02/14 22:02:45 RaK + * - mgFilterChoice Debuged + * fehlendes m_type = CHOICE in mg_filters.c + * falscher iterator in vdr_menu.c + * + * Revision 1.15 2004/02/12 09:15:07 LarsAC + * Moved filter classes into separate files + * + * Revision 1.14.2.4 2004/05/26 14:34:58 lvw + * Formatting changed + * + * Revision 1.14.2.3 2004/05/25 00:10:45 lvw + * Code cleanup and added use of real database source files + * + * Revision 1.14.2.2 2004/03/14 17:57:30 lvw + * Linked against libmad. Introduced config options into code. + * + * Revision 1.14.2.1 2004/03/02 07:05:50 lvw + * Initial adaptations from MP3 plugin added (untested) + * * Revision 1.21 2004/02/23 17:03:24 RaK * - error in filter view while trying to switch or using the colour keys * workaround: first filter criteria is inttype. than it works, dont ask why ;-( @@ -1415,6 +1455,7 @@ mgContentItem* GdTreeNode::getSingleTrack() * Revision 1.15 2004/02/12 09:15:07 LarsAC * Moved filter classes into separate files * +>>>>>>> 1.14.2.4 * Revision 1.14 2004/02/12 07:56:46 RaK * - SQL Fehler bei der Playlist Search korrigiert * diff --git a/gd_content_interface.h b/gd_content_interface.h index c572c75..8e95267 100644 --- a/gd_content_interface.h +++ b/gd_content_interface.h @@ -1,24 +1,23 @@ -/*******************************************************************/ -/*! \file gd_content_interface.h - * \brief Data Objects for content (e.g. mp3 files, movies) - * for the vdr muggle plugindatabase - ******************************************************************** - * \version $Revision: 1.6 $ - * \date $Date: 2004/02/23 15:41:21 $ +/*! + * \file gd_content_interface.h + * \brief Data objects for content (e.g. mp3 files, movies) + * for the vdr muggle plugin database + * + * \version $Revision: 1.7 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: RaK $ + * \author Responsible author: $Author: lvw $ * - * Declares main classes of for content items and interfaces to SQL databases + * Declares main classes for content items and interfaces to SQL databases * * This file defines the following classes - * - mgPlaylist a playlist - * - mgGdTrack a single track (content item). e.g. an mp3 file - * - mgSelection a set of tracks (e.g. a database subset matching certain criteria) + * - gdFilterSets + * - mgGdTrack a single track (content item). e.g. an mp3 file + * - mgSelection a set of tracks (e.g. a database subset matching certain criteria) * */ -/*******************************************************************/ -/* makes sure we dont use parse the same declarations twice */ +/* makes sure we dont use the same declarations twice */ #ifndef _GD_CONTENT_INTERFACE_H #define _GD_CONTENT_INTERFACE_H @@ -29,75 +28,158 @@ #include "mg_content_interface.h" #include "mg_media.h" -#include "mg_filters.h" +#include "mg_playlist.h" +#include "mg_filters.h" #include "i18n.h" -// non-member function +// non-member functions int GdInitDatabase(MYSQL *db); std::vector<std::string> *GdGetStoredPlaylists(MYSQL db); +/*! + * \brief A set of filters to search for content + */ class gdFilterSets : public mgFilterSets { public: + + /*! + * \brief the default constructor + * + * Constructs a number ( >= 1 ) of filter sets where + * the first (index 0) is active by default. + */ gdFilterSets(); - // constructor, constracts a number >=1 of filter sets - // the first set (index 0 ) is active by default + /*! + * \brief the destructor + */ virtual ~gdFilterSets(); - // destructor + /*! + * \brief compute restriction w.r.t active filters + * + * Computes the (e.g. sql) restrictions specified by + * the active filter sets. + * + * \param viewPort - after call, contains the index of the appropriate default view in + */ virtual std::string computeRestriction(int *viewPrt); - // computes the (e.g. sql-) restrictions specified by the active filter set - // and returns the index of the appropriate defualt view in viewPrt - }; /*! - ******************************************************************* - * \class mgGdTrack - * * \brief represents a a single track + * This may be any content item. e.g. a mp3 fileselection - * * The object is initially created with a database identifier. * The actual data is only read when a content field is accessed for - * The first time - ********************************************************************/ + * the first time. For subsequent access, cached values are used. + */ class mgGdTrack : public mgContentItem { private: + + /*! + * \brief the database in which the track resides + * */ MYSQL m_db; - bool m_retrieved; // false if content field values have not yet been - // retrieved from database. This is done on demand + + /*! + * \brief a dirty flag + * + * false, if content field values have not yet been retrieved + * from the database. Set to true when contents are retrieved + * (on demand only). + */ + bool m_retrieved; // content fields + /*! + * \brief the artist name + */ std::string m_artist; + + /*! + * \brief the track title + */ std::string m_title; + + /*! + * \brief the filename + */ std::string m_mp3file; + + /*! + * \brief The album to which the file belongs + */ std::string m_album; + + /*! + * \brief The genre of the music + */ std::string m_genre; + + /*! + * \brief The year in which the track appeared + */ int m_year; + + /*! + * \brief The rating by the user + */ int m_rating; + + /*! + * \brief The length of the file in bytes. + * \todo TODO: is this true? Or what length are we talking about? + */ int m_length; + /*! + * \brief Access the data of the item from the database + */ bool readData(); public: - - /* constructor */ - mgGdTrack(){ m_uniqID = -1;} // creates invalid item - mgGdTrack(int sqlIdentifier, MYSQL dbase); + /*! + * \brief a constructor + * + * Creates an invalid item. + * + * \todo does this make sense? + */ + mgGdTrack() + { + m_uniqID = -1; + } + + /*! + * \brief a constructor for a specific item + * + * The constructor creates a specific item in a given database + * + * \param sqlIdentifier - a unique ID of the item which will be represented + * \param dbase - the database in which the item exists + */ + mgGdTrack( int sqlIdentifier, MYSQL dbase ); + + /*! + * \brief a copy constructor + */ mgGdTrack(const mgGdTrack&); - /* destructor */ + /*! + * \brief the destructor + */ virtual ~mgGdTrack(); virtual mgContentItem::contentType getContentType(){return mgContentItem::GD_AUDIO;} + virtual mgMediaPlayer getPlayer() { return mgMediaPlayer(); @@ -106,13 +188,17 @@ private: /* data acess */ //virtual functions of the base class virtual std::string getSourceFile(); + virtual std::string getTitle(); - virtual std::string getLabel(int col); + + virtual std::string getLabel(int col = 0); virtual std::vector<mgFilter*> *getTrackInfo(); + virtual bool setTrackInfo(std::vector<mgFilter*>*); virtual std::string getGenre(); + virtual int getRating(); // additional class-specific functions @@ -221,12 +307,41 @@ public: /* -------------------- begin CVS log --------------------------------- * $Log: gd_content_interface.h,v $ + * Revision 1.7 2004/05/28 15:29:18 lvw + * Merged player branch back on HEAD branch. + * + * + * Revision 1.6 2004/02/23 15:41:21 RaK + * - first i18n attempt + * + * Revision 1.5 2004/02/12 09:15:07 LarsAC + * Moved filter classes into separate files + * + * Revision 1.4.2.6 2004/05/25 00:10:45 lvw + * Code cleanup and added use of real database source files + * + * Revision 1.4.2.5 2004/04/01 21:35:32 lvw + * Minor corrections, some debugging aid. + * + * Revision 1.4.2.4 2004/03/14 17:57:30 lvw + * Linked against libmad. Introduced config options into code. + * + * Revision 1.4.2.3 2004/03/10 13:11:24 lvw + * Added documentation + * + * Revision 1.4.2.2 2004/03/08 07:14:27 lvw + * Preliminary changes to muggle player + * + * Revision 1.4.2.1 2004/03/02 07:05:50 lvw + * Initial adaptations from MP3 plugin added (untested) + * * Revision 1.6 2004/02/23 15:41:21 RaK * - first i18n attempt * * Revision 1.5 2004/02/12 09:15:07 LarsAC * Moved filter classes into separate files * +>>>>>>> 1.4.2.6 * Revision 1.4 2004/02/09 19:27:52 MountainMan * filter set implemented * @@ -3,7 +3,7 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: i18n.c,v 1.5 2004/02/23 17:07:08 RaK Exp $ + * $Id: i18n.c,v 1.6 2004/05/28 15:29:18 lvw Exp $ */ #include "i18n.h" @@ -554,5 +554,69 @@ const tI18nPhrase Phrases[] = { "",// TODO "",// TODO }, + { "Load", + "Laden", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + }, + { "Save", + "Speichern", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + }, + { "Clear", + "Loeschen", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + }, + { "Mainmenu", + "Hauptmenue", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + }, { NULL } }; @@ -3,7 +3,7 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: i18n.h,v 1.1 2004/02/23 15:41:21 RaK Exp $ + * $Id: i18n.h,v 1.2 2004/05/28 15:29:18 lvw Exp $ */ #ifndef _I18N__H diff --git a/mg_content_interface.c b/mg_content_interface.c index 5d60358..437d8b5 100755 --- a/mg_content_interface.c +++ b/mg_content_interface.c @@ -1,14 +1,11 @@ -/*******************************************************************/ -/*! \file content_interface.c - * \brief Data Objects for content (e.g. mp3 files, movies) - * for the vdr muggle plugindatabase - ******************************************************************** - * \version $Revision: 1.4 $ - * \date $Date: 2004/02/23 15:41:21 $ +/*! \file mg_content_interface.c + * \brief Data Objects for content (e.g. mp3 files, movies) for the vdr muggle plugin + * + * \version $Revision: 1.5 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: RaK $ + * \author Responsible author: $Author: lvw $ * - * DUMMY * Implements main classes of for content items and interfaces to SQL databases * * This file implements the following classes @@ -17,7 +14,6 @@ * - mgSelection a set of tracks (e.g. a database subset matching certain criteria) * */ -/*******************************************************************/ #define DEBUG #include "mg_content_interface.h" @@ -31,22 +27,20 @@ mgContentItem mgContentItem::UNDEFINED = mgContentItem(); using namespace std; /*! - ***************************************************************************** - * \brief construcor + * \brief constructor * - * creates empty tracklist - ****************************************************************************/ + * create an empty tracklist + */ mgTracklist::mgTracklist() { } /*! - ***************************************************************************** * \brief destrucor * * Deletes all items in the tracklist and removes the list itself - ****************************************************************************/ + */ mgTracklist::~mgTracklist() { mgContentItem* ptr; @@ -183,11 +177,15 @@ mgContentItem* mgTracklist::getItem(unsigned int position) ****************************************************************************/ bool mgTracklist::remove(unsigned int position) { - if(position >= m_list.size()) return false; + if( position >= m_list.size() ) + { + return false; + } vector<mgContentItem*>::iterator iter; iter = m_list.begin()+ position; m_list.erase(iter); + return true; } @@ -213,104 +211,6 @@ int mgTracklist::remove(mgContentItem* item) } -/*=================================================================*/ -/* */ -/* class mgPlaylist */ -/* */ -/*=================================================================*/ -mgPlaylist::mgPlaylist() -{ -} -mgPlaylist::mgPlaylist(string listname) -{ - m_listname = listname; -} - - /*==== destructor ====*/ -mgPlaylist::~mgPlaylist() -{ - -} -/*==== add/ remove tracks ====*/ - -/* adds a song at the end of the playlist */ -void mgPlaylist::append(mgContentItem* item) -{ - m_list.push_back(item); -} - -void mgPlaylist::appendList(vector<mgContentItem*> *tracks) -{ - vector<mgContentItem*>::iterator iter; - mgDebug(3, "Adding %d tracks to the playlist",tracks->size()); - for(iter = tracks->begin(); iter != tracks->end(); iter++) - { - m_list.push_back(*iter); - } - tracks->clear(); -} - - -/* adds a song after 'position' */ -void mgPlaylist::insert(mgContentItem* item, unsigned int position) -{ - if(position >= m_list.size()) - m_list.push_back(item); - else - m_list.insert(m_list.begin()+position, item); -} - - - -/*==== access tracks ====*/ -string mgPlaylist::getListname() { return m_listname; } -void mgPlaylist::setListname(string name){ m_listname = name;} - - - -// returns the first item of the list -mgContentItem* mgPlaylist::getFirst() -{ - m_current = m_list.begin(); - return *m_current; -} - -// returns the nth track from the playlist -mgContentItem* mgPlaylist::getPosition(unsigned int position) -{ - if(position >= m_list.size()) - return &(mgContentItem::UNDEFINED); //invalid - m_current = m_list.begin()+position; - return *m_current; -} - -// proceeds to the next item -mgContentItem* mgPlaylist::skipFwd() -{ - if(m_current+1 == m_list.end()) - return &(mgContentItem::UNDEFINED); //invalid - else - return * (++m_current); -} - -// goes back to the previous item -mgContentItem* mgPlaylist::skipBack() -{ - if(m_current == m_list.begin()) - return &(mgContentItem::UNDEFINED); //invalid - else - return * (--m_current); -} - -// get next track, do not update data structures -mgContentItem* mgPlaylist::sneakNext() -{ - if(m_current+1 == m_list.end()) - return &(mgContentItem::UNDEFINED); //invalid - else - return * (m_current+1); -} - /*=================================================================*/ /* */ @@ -402,6 +302,31 @@ string mgSelectionTreeNode::getRestrictions() /* -------------------- begin CVS log --------------------------------- * $Log: mg_content_interface.c,v $ + * Revision 1.5 2004/05/28 15:29:18 lvw + * Merged player branch back on HEAD branch. + * + * + * Revision 1.4 2004/02/23 15:41:21 RaK + * - first i18n attempt + * + * Revision 1.3.2.6 2004/05/27 07:58:38 lvw + * Removed bugs in moving and removing tracks from playlists + * + * Revision 1.3.2.5 2004/05/25 00:10:45 lvw + * Code cleanup and added use of real database source files + * + * Revision 1.3.2.4 2004/05/04 16:51:53 lvw + * Debugging aids added. + * + * Revision 1.3.2.3 2004/03/08 21:42:22 lvw + * Added count method. Some comments for further todos added. + * + * Revision 1.3.2.2 2004/03/08 07:14:27 lvw + * Preliminary changes to muggle player + * + * Revision 1.3.2.1 2004/03/02 07:08:12 lvw + * 118 additions + * * Revision 1.4 2004/02/23 15:41:21 RaK * - first i18n attempt * diff --git a/mg_content_interface.h b/mg_content_interface.h index b7bc7a3..bb99999 100755 --- a/mg_content_interface.h +++ b/mg_content_interface.h @@ -1,19 +1,21 @@ /*******************************************************************/ -/*! \file content_interface.h - * \brief Data Objects for content (e.g. mp3 files, movies) - * for the vdr muggle plugindatabase +/*! \file mg_content_interface.h + * \brief data Objects for content (e.g. mp3 files, movies) for the vdr muggle plugin + * ******************************************************************** - * \version $Revision: 1.3 $ - * \date $Date: 2004/02/09 19:27:52 $ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: MountainMan $ + * + * \version $Revision: 1.4 $ + * \date $Date: 2004/05/28 15:29:18 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author file owner: $Author: lvw $ * - * Declares main classes of for content items and interfaces to SQL databases + * Declares generic classes of for content items and interfaces to SQL databases * - * This file defines the following classes - * - mgPlaylist a playlist - * - mgTrack a single track (content item). e.g. an mp3 file - * - mgSelection a set of tracks (e.g. a database subset matching certain criteria) + * This file defines the following classes + * - mgMediaMplayer + * - mgContentItem + * - mgTracklist + * - mgSelectionTreeNode * */ /*******************************************************************/ @@ -28,82 +30,150 @@ #include <mysql/mysql.h> #define ILLEGAL_ID -1 + class mgFilter; +class mgPlaylist; /*! - ******************************************************************* * \class mgMediaPlayer - * * \brief dummy player class - ********************************************************************/ + */ class mgMediaPlayer { - public: - mgMediaPlayer(){;} - ~mgMediaPlayer(){;} - + public: + + mgMediaPlayer() + { } + + ~mgMediaPlayer() + { } }; /*! - ******************************************************************* - * \class mgContentItem - * * \brief Generic class that represents a single content item. + * * This is the parent class from which classes like mgGdTrack are derived - ********************************************************************/ + * + */ class mgContentItem { + public: + typedef enum contentType + { + ABSTRACT =0, + GD_AUDIO, + EPG + }; - public: - typedef enum contentType{ - ABSTRACT =0, - GD_AUDIO, - EPG - }contentType; - - protected: + protected: int m_uniqID; // internal identifier to uniquely identify a content item; - public: - /* constructor */ - mgContentItem(){ m_uniqID = -1;} - mgContentItem(int id){m_uniqID = id;} - mgContentItem(const mgContentItem& org){m_uniqID = org.m_uniqID;} - /* destructor */ - virtual ~mgContentItem(){}; - - /* data acess */ - int getId(){ return m_uniqID;} + public: - // what type of content are we looking at (e.g. audio, video, epg) - virtual contentType getContentType(){return ABSTRACT;} + /*! \brief default constructor + */ + mgContentItem() + : m_uniqID( -1 ) + { } + + /*! \brief constructor with explicit id + */ + mgContentItem( int id ) + : m_uniqID( id ) + { } + + /*! \brief copy constructor + */ + mgContentItem( const mgContentItem& org ) + : m_uniqID( org.m_uniqID ) + { } + + /*! \brief destructor + */ + virtual ~mgContentItem() + { }; + + /*! \brief acess unique id + */ + int getId() + { + return m_uniqID; + } - // return (global?) object that is used to play the content items - virtual mgMediaPlayer getPlayer(){return mgMediaPlayer();} + /*! \brief determine what type of content are we looking at (e.g. audio, video, epg) + */ + virtual contentType getContentType() + { + return ABSTRACT; + } + /*! \brief return a (global?) object that is used to play content items + * \todo What for? Interesting properties? Last state, play info, ...? + */ + virtual mgMediaPlayer getPlayer() + { + return mgMediaPlayer(); + } - // get the "file" (or URL) that is passed to the player - virtual std::string getSourceFile(){return "";} + /*! \brief get the "file" (or URL) that is passed to the player + */ + virtual std::string getSourceFile() + { + return ""; + } // ============ data access ================= - virtual std::string getTitle(){return "";} // return the title - virtual std::string getLabel(int col){return "";} // return the title - - virtual std::string getDescription()// return a short textual description - {return "";} - virtual std::vector<mgFilter*> *getTrackInfo(){return NULL;} - virtual bool updateTrackInfo(std::vector<mgFilter*>*){return false;} + /*! \brief return the title + */ + virtual std::string getTitle() + { + return ""; + } + + /*! \brief return a specific label + */ + virtual std::string getLabel(int col = 0) + { + return ""; + } + + /*! \brief return a short textual description + */ + virtual std::string getDescription() + { + return ""; + } + + virtual std::vector<mgFilter*> *getTrackInfo() + { + return NULL; + } + + virtual bool updateTrackInfo(std::vector<mgFilter*>*) + { + return false; + } + + virtual std::string getGenre() + { + return ""; + } - virtual std::string getGenre(){return "";} virtual int getRating() { return 0; } - virtual bool operator == (mgContentItem o){return m_uniqID == o.m_uniqID;} + virtual bool operator == (mgContentItem o) + { + return m_uniqID == o.m_uniqID; + } // check, if usable - virtual bool isValid() {return (m_uniqID >=0);} + virtual bool isValid() + { + return ( m_uniqID >= 0 ); + } static mgContentItem UNDEFINED; }; @@ -136,54 +206,6 @@ class mgTracklist virtual bool remove(unsigned int position); // remove item at position }; -/*! - ******************************************************************* - * \class mgPlaylist - * - * Represents a generic playlist, i.e. an ordered collection of tracks - * The derived classes take care of specifics of certain media types - ********************************************************************/ -class mgPlaylist : public mgTracklist -{ - protected: - std::string m_listname; - std::vector<mgContentItem*>::iterator m_current; - public: - - /*==== constructors ====*/ - mgPlaylist(); - mgPlaylist(std::string listname); - - /*==== destructor ====*/ - virtual ~mgPlaylist(); - - /*==== add/ remove tracks ====*/ - - /* adds a song at the end of the playlist */ - virtual void append(mgContentItem* item); - virtual void appendList(std::vector<mgContentItem*> *tracks); - /* adds a song after 'position' */ - virtual void insert(mgContentItem* item, unsigned int position); - - /*==== access tracks ====*/ - std::string getListname() ; - void setListname(std::string name); - - // returns the first item of the list - virtual mgContentItem* getFirst(); - - // returns the nth track from the playlist - virtual mgContentItem* getPosition(unsigned int position); - - // proceeds to the next item - virtual mgContentItem* skipFwd(); - - // goes back to the previous item - virtual mgContentItem* skipBack(); - - virtual mgContentItem* sneakNext(); - -}; class mgSelectionTreeNode { @@ -247,6 +269,21 @@ public: /* -------------------- begin CVS log --------------------------------- * $Log: mg_content_interface.h,v $ + * Revision 1.4 2004/05/28 15:29:18 lvw + * Merged player branch back on HEAD branch. + * + * Revision 1.3.2.4 2004/05/25 00:10:45 lvw + * Code cleanup and added use of real database source files + * + * Revision 1.3.2.3 2004/04/01 21:35:32 lvw + * Minor corrections, some debugging aid. + * + * Revision 1.3.2.2 2004/03/08 21:42:22 lvw + * Added count method. Some comments for further todos added. + * + * Revision 1.3.2.1 2004/03/08 07:14:28 lvw + * Preliminary changes to muggle player + * * Revision 1.3 2004/02/09 19:27:52 MountainMan * filter set implemented * diff --git a/mg_database.c b/mg_database.c index 9a85cb8..eabff6d 100644 --- a/mg_database.c +++ b/mg_database.c @@ -2,10 +2,10 @@ /*! \file mg_database.c * \brief A capsule around MySql database access ******************************************************************** - * \version $Revision: 1.1 $ - * \date $Date: 2004/02/01 18:22:53 $ + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: LarsAC $ + * \author file owner: $Author: lvw $ */ /*******************************************************************/ @@ -17,7 +17,9 @@ mgDB::mgDB() { } -mgDB::mgDB(string user, string pass) +mgDB::mgDB(string host, string name, + string user, string pass, + int port) { } @@ -27,9 +29,8 @@ mgDB::~mgDB() MYSQL mgDB::getDBHandle() { - MYSQL m; - return m; + return m_dbase; } diff --git a/mg_database.h b/mg_database.h index b979df4..4e00826 100644 --- a/mg_database.h +++ b/mg_database.h @@ -1,34 +1,47 @@ -/*******************************************************************/ -/*! \file mg_database.h - * \brief A capsule around MySql database access - ******************************************************************** - * \version $Revision: 1.1 $ - * \date $Date: 2004/02/01 18:22:53 $ +/*! + * \file mg_database.h + * \brief A capsule around MySql database access + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: LarsAC $ + * \author Responsible author: $Author: lvw $ */ -/*******************************************************************/ - #ifndef __MG_DATABASE_H #define __MG_DATABASE_H #include <string> - #include <mysql/mysql.h> class mgDB { public: + /*! \brief default constructor + */ mgDB( ); - mgDB( std::string user, std::string pass ); + + /*! \brief constructor + * + * \param host + * \param name + * \param user + * \param pass + * \param port + */ + mgDB( std::string host, std::string name, + std::string user, std::string pass, + int port ); + + /*! \brief constructor */ ~mgDB(); + /*! \brief obtain database handle*/ MYSQL getDBHandle(); private: - // MYSQL m_dbase; + MYSQL m_dbase; }; #endif diff --git a/mg_filters.c b/mg_filters.c index c5859e0..f8c164d 100644 --- a/mg_filters.c +++ b/mg_filters.c @@ -2,10 +2,10 @@ /*! \file mg_filters.c * \brief ******************************************************************** - * \version $Revision: 1.2 $ - * \date $Date: 2004/02/14 22:02:45 $ + * \version $Revision: 1.3 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: RaK $ + * \author file owner: $Author: lvw $ */ /*******************************************************************/ @@ -284,14 +284,14 @@ void mgFilterChoice::restore() void mgFilterChoice::clear() { m_stored_val = m_default_val; - m_selval = m_default_val; + m_selval = m_default_val; } bool mgFilterChoice::isSet() { if(m_stored_val == m_default_val) - { - return false; - } + { + return false; + } return true; } diff --git a/mg_filters.h b/mg_filters.h index 8a983f7..5501d63 100644 --- a/mg_filters.h +++ b/mg_filters.h @@ -3,10 +3,10 @@ * \brief Top level access to media in vdr plugin muggle * for the vdr muggle plugindatabase ******************************************************************** - * \version $Revision: 1.1 $ - * \date $Date: 2004/02/12 09:15:07 $ + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: LarsAC $ + * \author file owner: $Author: lvw $ */ /*******************************************************************/ @@ -1,40 +1,38 @@ -/*******************************************************************/ /*! \file mg_media.c * \brief Top level access to media in vdr plugin muggle - * for the vdr muggle plugindatabase - ******************************************************************** - * \version $Revision: 1.12 $ - * \date $Date: 2004/02/23 16:30:58 $ + * + * \version $Revision: 1.13 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: RaK $ + * \author Responsible author: $Author: lvw $ */ -/*******************************************************************/ /* makes sure we dont parse the same declarations twice */ #include "mg_media.h" + #include "mg_tools.h" #include "mg_content_interface.h" + #include "gd_content_interface.h" +#include "vdr_setup.h" + using namespace std; //------------------------------------------------------------------- // mgFilterSets //------------------------------------------------------------------- /*! - ******************************************************************* - * \brief constructor - * - * constructor of the base class - ********************************************************************/ + * \brief constructor + * + * constructor of the base class + */ + mgFilterSets::mgFilterSets() { // nothing to be done in the base class } - /*! - ******************************************************************* - * \brief destructor - ********************************************************************/ + mgFilterSets::~mgFilterSets() { vector<mgFilter*> *set; @@ -52,7 +50,7 @@ mgFilterSets::~mgFilterSets() } m_sets.clear(); } - /*! +/*! ******************************************************************* * \brief returns the number of available sets ********************************************************************/ @@ -139,9 +137,8 @@ void mgFilterSets::select(int n) /*! - ******************************************************************* * \brief return title of the active filter set - ********************************************************************/ + */ string mgFilterSets::getTitle() { if(m_activeSetId < (int) m_titles.size()) @@ -156,15 +153,11 @@ string mgFilterSets::getTitle() } } -//------------------------------------------------------------------- -// mgFilterSets -//------------------------------------------------------------------- /*! - ******************************************************************* * \class mgMedia * * \brief mein class to access content in the vdr plugin muggle - ********************************************************************/ + */ mgMedia::mgMedia(contentType mediatype) { int errval = 0; @@ -180,7 +173,7 @@ mgMedia::mgMedia(contentType mediatype) case GD_MP3: { errval = GdInitDatabase(&m_db); - mgDebug(3, "Successfully conntected to sql database 'GiantDisc'"); + mgDebug(3, "Successfully conntected to sql database %s", the_setup.DbName ); } } if(errval < 0) @@ -193,8 +186,8 @@ mgMedia::mgMedia(contentType mediatype) { case GD_MP3: { - errval = GdInitDatabase(&m_db); - mgDebug(3, "Successfully conntected to sql database 'GiantDisc'"); + errval = GdInitDatabase( &m_db ); // TODO: why duplicate this? LVW + mgDebug(3, "Successfully conntected to sql database %s", the_setup.DbName ); } } } @@ -220,7 +213,9 @@ string mgMedia::getMediaTypeName() switch(m_mediatype) { case GD_MP3: + { return "GiantDisc"; + } break; } mgError("implementation Error"); // we should never get here return ""; @@ -429,6 +424,24 @@ mgSelectionTreeNode *mgMedia::applyActiveFilter() /* -------------------- begin CVS log --------------------------------- * $Log: mg_media.c,v $ + * Revision 1.13 2004/05/28 15:29:18 lvw + * Merged player branch back on HEAD branch. + * + * Revision 1.12 2004/02/23 16:30:58 RaK + * - album search error because of i18n corrected + * + * Revision 1.11 2004/02/12 09:15:07 LarsAC + * Moved filter classes into separate files + * + * Revision 1.10.2.3 2004/05/25 00:10:45 lvw + * Code cleanup and added use of real database source files + * + * Revision 1.10.2.2 2004/03/14 17:57:30 lvw + * Linked against libmad. Introduced config options into code. + * + * Revision 1.10.2.1 2004/03/02 07:05:50 lvw + * Initial adaptations from MP3 plugin added (untested) + * * Revision 1.12 2004/02/23 16:30:58 RaK * - album search error because of i18n corrected * @@ -451,8 +464,5 @@ mgSelectionTreeNode *mgMedia::applyActiveFilter() * Revision 1.7 2004/02/02 22:48:04 MountainMan * added CVS $Log * - * * --------------------- end CVS log ---------------------------------- */ - - @@ -1,15 +1,14 @@ -/*******************************************************************/ -/*! \file mgmedia.h +/*! + * \file mgmedia.h * \brief Top level access to media in vdr plugin muggle - * for the vdr muggle plugindatabase - ******************************************************************** - * \version $Revision: 1.10 $ - * \date $Date: 2004/02/12 09:15:07 $ + * + * \version $Revision: 1.11 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: LarsAC $ + * \author Responsible author: $Author: lvw $ */ -/*******************************************************************/ -/* makes sure we dont use parse the same declarations twice */ + +// makes sure we dont use parse the same declarations twice #ifndef _MG_MEDIA_H #define _MG_MEDIA_H @@ -25,63 +24,61 @@ class mgFilter; class mgFilterSets; /*! - ******************************************************************* * \class mgFilterSets * - * Represents one or several sets of filters to set and memorize - * search constraint - ********************************************************************/ -class mgFilterSets { + * Represents one or several sets of filters to set and memorize search constraint + */ +class mgFilterSets +{ protected: int m_activeSetId; - std::vector<mgFilter*> *m_activeSet; // pointer to the active filter set + std::vector<mgFilter*> *m_activeSet; // pointer to the active filter set - std::vector< std::vector<mgFilter*>*> m_sets; // stores name-value pairs, even if a different set is active + std::vector< std::vector<mgFilter*>*> m_sets; - std::vector<std::string> m_titles; // stores the titles for all filters + std::vector<std::string> m_titles; public: - mgFilterSets(); // constructor, constracts a number >=1 of filter sets // the first set (index 0 ) is active by default + mgFilterSets(); - virtual ~mgFilterSets(); // destructor + virtual ~mgFilterSets(); - int numSets(); // returns the number of available sets + int numSets(); - void nextSet(); // proceeds to the next one in a circular fashion + void nextSet(); - void select(int n); // activates a specific set by index + void select(int n); - virtual void clear(); // restores the default values for all filter values in the active set // normally, the default values represent 'no restrictions' + virtual void clear(); - void accept(); // stores the current filter values + void accept(); - std::vector<mgFilter*> *getFilters(); // returns the active set to the application // the application may temporarily modify the filter values // accept() needs to be called to memorize the changed values + std::vector<mgFilter*> *getFilters(); - virtual std::string computeRestriction(int *viewPrt)=0; // computes the (e.g. sql-) restrictions specified by the active filter set // and returns the index of the appropriate defualt view in viewPrt + virtual std::string computeRestriction(int *viewPrt)=0; - std::string getTitle(); // returns title of active filter set + std::string getTitle(); }; /*! - ******************************************************************* * \class mgMedia * * \brief main class to access content in the vdr plugin muggle @@ -90,7 +87,7 @@ class mgFilterSets { * where the data type is explicitelymentioned. * The class provides a set of objects that abstract from the data * type and source - ********************************************************************/ + */ class mgMedia { @@ -149,6 +146,18 @@ class mgMedia /* -------------------- begin CVS log --------------------------------- * $Log: mg_media.h,v $ + * Revision 1.11 2004/05/28 15:29:18 lvw + * Merged player branch back on HEAD branch. + * + * Revision 1.10 2004/02/12 09:15:07 LarsAC + * Moved filter classes into separate files + * + * Revision 1.9.2.2 2004/05/25 00:10:45 lvw + * Code cleanup and added use of real database source files + * + * Revision 1.9.2.1 2004/03/02 07:05:50 lvw + * Initial adaptations from MP3 plugin added (untested) + * * Revision 1.10 2004/02/12 09:15:07 LarsAC * Moved filter classes into separate files * diff --git a/mg_playlist.c b/mg_playlist.c new file mode 100644 index 0000000..f6387b0 --- /dev/null +++ b/mg_playlist.c @@ -0,0 +1,209 @@ +/*! + * \file mg_playlist.c + * \brief defines functions to be executed on playlists for the vdr muggle plugindatabase + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:18 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * This file implements the class mgPlaylist which maintains a playlist + * and supports editing (e.g. adding or moving tracks), navigating it + * (e.g. obtaining arbitrary items or accessing them sequentially) or + * making it persistent in some database. + */ + +#include "mg_playlist.h" +#include "mg_tools.h" + +#include <string> +#include <vector> + +using namespace std; + +/* ==== constructors ==== */ + +mgPlaylist::mgPlaylist() +{ + m_current_idx = 0; + + char *buffer; + asprintf( &buffer, "Playlist-%ld", random() ); + + m_listname = buffer; +} + +mgPlaylist::mgPlaylist(string listname) +{ + m_current_idx = 0; + m_listname = listname; +} + +/* ==== destructor ==== */ + +mgPlaylist::~mgPlaylist() +{ +} + +void mgPlaylist::toggleShuffle() +{ +} + +void mgPlaylist::toggleLoop() +{ +} + +void mgPlaylist::initialize() +{ + m_current = m_list.begin(); +} + +/* ==== add/remove tracks ==== */ + +/* adds a song at the end of the playlist */ +void mgPlaylist::append(mgContentItem* item) +{ + m_list.push_back(item); +} + +/* append a list of tracks at the end of the playlist */ +void mgPlaylist::appendList(vector<mgContentItem*> *tracks) +{ + vector<mgContentItem*>::iterator iter; + + mgDebug( 3, "Adding %d tracks to the playlist", tracks->size() ); + + for( iter = tracks->begin(); iter != tracks->end(); iter++ ) + { + m_list.push_back(*iter); + } + + // TODO: why is this vector cleared? shouldn't the caller take care of this? LVW + tracks->clear(); +} + +/* add a song after 'position' */ +void mgPlaylist::insert(mgContentItem* item, unsigned int position) +{ + if( position >= m_list.size() ) + { + m_list.push_back(item); + } + else + { + m_list.insert( m_list.begin() + position, item ); + } +} + +void mgPlaylist::clear() +{ + // TODO: who takes care of memory allocation/deallocation of mgItems? + + vector<mgContentItem*>::iterator iter; + + for( iter = m_list.begin(); iter != m_list.end(); iter++ ) + { // delete each item in the list + delete *iter; + } + + // finally clear the list itself + m_list.clear(); +} + +void mgPlaylist::move( int from, int to ) +{ + vector<mgContentItem*>::iterator from_iter = m_list.begin() + from; + vector<mgContentItem*>::iterator to_iter = m_list.begin() + to; + + m_list.insert( to_iter, *from_iter); + m_list.erase( from_iter ); +} + +/*==== access tracks ====*/ +string mgPlaylist::getListname() +{ + return m_listname; +} + +void mgPlaylist::setListname(string name) +{ + m_listname = name; +} + +// returns the count of items in the list +int mgPlaylist::count() +{ + return m_list.size(); +} + +// returns the first item of the list +mgContentItem* mgPlaylist::getFirst() +{ + m_current = m_list.begin(); + + return *m_current; +} + +// returns the nth track from the playlist +mgContentItem* mgPlaylist::getPosition(unsigned int position) +{ + if( position >= m_list.size() ) + { + // TODO: why not return a NULL pointer? LVW + return &(mgContentItem::UNDEFINED); //invalid + } + m_current = m_list.begin() + position; + + return *m_current; +} + +// proceeds to the next item +mgContentItem* mgPlaylist::skipFwd() +{ + mgContentItem* next; + + if( m_current + 1 == m_list.end() ) // unless loop mode + { + // TODO: why not return a NULL pointer? LVW + next = &(mgContentItem::UNDEFINED); //invalid item + } + else + { + return * (++ m_current); + } + + return next; +} + +// goes back to the previous item +mgContentItem* mgPlaylist::skipBack() +{ + mgContentItem* prev; + + if( m_current == m_list.begin() ) + { + // TODO: why not return a NULL pointer? LVW + prev = &(mgContentItem::UNDEFINED); //invalid + } + else + { + prev = * (--m_current); + } + + return prev; +} + +// get next track, do not update data structures +mgContentItem* mgPlaylist::sneakNext() +{ + if( m_current+1 == m_list.end() ) + { + // TODO: why not return a NULL pointer? LVW + return &(mgContentItem::UNDEFINED); //invalid + } + else + { + return * (m_current+1); + } +} + diff --git a/mg_playlist.h b/mg_playlist.h new file mode 100644 index 0000000..dcec71c --- /dev/null +++ b/mg_playlist.h @@ -0,0 +1,143 @@ +/*! + * \file mg_playlist.c + * \brief defines functions to be executed on playlists for the vdr muggle plugindatabase + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:18 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * This file implements the class mgPlaylist which maintains a playlist + * and supports editing (e.g. adding or moving tracks), navigating it + * (e.g. obtaining arbitrary items or accessing them sequentially) or + * making it persistent in some database. + */ +#ifndef __MG_PLAYLIST +#define __MG_PLAYLIST + +#include "mg_content_interface.h" + +/*! + * \class mgPlaylist + * + * \brief Represents a generic playlist, i.e. an ordered collection of tracks + * Derived classes take care of specifics of certain media types + */ +class mgPlaylist : public mgTracklist +{ + +private: + //! \brief current index in the playlist + // TODO: should be a property of the player? + int m_current_idx; + +protected: + + // TODO: Why not make these private? Subclasses should use access functions. LVW + + //! \brief the name of the playlist + std::string m_listname; + + //! \brief the current item as an iterator + std::vector<mgContentItem*>::iterator m_current; + +public: + + /* ==== constructors and initialization ==== */ + + //! \brief the default constructor (random listname) + mgPlaylist(); + + /*! \brief constructor with a specified listname + * + * \param listname - initial name of the list + */ + mgPlaylist( std::string listname ); + + void initialize(); + + /* ==== destructor ==== */ + //! \brief the destructor + virtual ~mgPlaylist(); + + /* === control behavior */ + + //! \brief toggle the loop mode. TODO. + void toggleLoop(); + + //! \brief toggle the shuffle mode. TODO. + void toggleShuffle(); + + /* ==== add/ remove tracks ==== */ + + /*! \brief adds a song at the end of the playlist + * + * \param item - the item to be appended + */ + virtual void append(mgContentItem* item); + + /*! \brief add a vector of songs at the end of the playlist + * + * \param tracks - the vector of tracks to be added + */ + virtual void appendList(std::vector<mgContentItem*> *tracks); + + /*! \brief add a song after a specified position + * + * Might be merged with append by using a default argument for the position + * + * \param item - the item to be added + * \param position - the position where the item should be added + */ + virtual void insert(mgContentItem* item, unsigned int position); + + //! \brief clear all tracks + virtual void clear(); + + /*! \brief move tracks within playlist + * + * \param from - the item of the index to be moved + * \param to - the target index of the item + */ + void move( int from, int to ); + + /*! \brief remove a track from the playlist + * + * \param pos - the index of the track to be removed + */ + // bool remove( int pos ); + + /* ==== access tracks ==== */ + + //! \brief obtain the listname + std::string getListname() ; + + /*! \brief set the listname + * + * \param name - the new name of this list + */ + void setListname(std::string name); + + //! \brief returns the count of items in the list + int count(); + + //! \brief returns the first item of the list + virtual mgContentItem* getFirst(); + + /*! \brief returns the nth track from the playlist + * + * \param position - the position to skip to + */ + virtual mgContentItem* getPosition(unsigned int position); + + //! \brief proceeds to the next item + virtual mgContentItem* skipFwd(); + + //! \brief goes back to the previous item + virtual mgContentItem* skipBack(); + + //! \brief obtain the next item without skipping the current position + virtual mgContentItem* sneakNext(); +}; + +#endif @@ -3,10 +3,10 @@ * \brief A few util functions for standalone and plugin messaging * for the vdr muggle plugindatabase ******************************************************************** - * \version $Revision: 1.2 $ - * \date $Date: 2004/02/02 22:48:04 $ + * \version $Revision: 1.3 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: MountainMan $ + * \author file owner: $Author: lvw $ * */ /*******************************************************************/ @@ -14,6 +14,9 @@ #ifndef _MUGGLE_TOOLS_H #define _MUGGLE_TOOLS_H +#include <iostream> +#include <string> + #include "mysql/mysql.h" #define STANDALONE 1 @@ -24,12 +27,62 @@ void mgDebug( const char *fmt, ... ); void mgWarning(const char *fmt, ...); void mgError(const char *fmt, ...); - MYSQL_RES* mgSqlReadQuery(MYSQL *db, const char *fmt, ...); void mgSqlWriteQuery(MYSQL *db, const char *fmt, ...); +#ifdef DEBUG +#define MGLOG(x) mgLog __thelog(x) +#else +#define MGLOG(x) {} +#endif + +#ifdef DEBUG +#define MGLOGSTREAM __thelog.getStream() +#else +#define MGLOGSTREAM __thelog.getStream() +#endif + + +class mgLog +{ + public: + enum + { + LOG, WARNING, ERROR, FATAL + } mgLogLevel; + + std::ostream& getStream() + { + return std::cout; + } + + mgLog( std::string methodname ) : m_methodname( methodname ) + { + getStream() << m_methodname << " entered" << std::endl; + }; + + ~mgLog() + { + getStream() << m_methodname << " terminated" << std::endl; + } + + private: + + std::string m_methodname; + +}; + /* -------------------- begin CVS log --------------------------------- * $Log: mg_tools.h,v $ + * Revision 1.3 2004/05/28 15:29:18 lvw + * Merged player branch back on HEAD branch. + * + * Revision 1.2.2.2 2004/04/18 14:08:41 lvw + * Added some more logging/debugging code + * + * Revision 1.2.2.1 2004/04/09 16:14:48 lvw + * Added further code for logging/debugging. + * * Revision 1.2 2004/02/02 22:48:04 MountainMan * added CVS $Log * @@ -1,28 +1,33 @@ -/*******************************************************************/ -/*! \file muggle.c - * \brief Implements a plugin for browsing media libraries within VDR - ******************************************************************** - * \version $Revision: 1.6 $ - * \date $Date: 2004/02/23 16:18:15 $ +/*! + * \file muggle.c + * \brief Implements a plugin for browsing media libraries within VDR + * + * \version $Revision: 1.7 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: RaK $ + * \author Responsible author: $Author: lvw $ + * + * $Id: muggle.c,v 1.7 2004/05/28 15:29:18 lvw Exp $ */ -/*******************************************************************/ -static const char *VERSION = "0.0.1"; -static const char *DESCRIPTION = "Access GiantDisc database contents"; -static const char *MAINMENUENTRY = "Muggle"; +#include <getopt.h> #include "muggle.h" #include "vdr_menu.h" - +#include "vdr_setup.h" #include "mg_tools.h" #include "mg_content_interface.h" #include "mg_media.h" #include "i18n.h" +static const char *VERSION = "0.0.1"; +static const char *DESCRIPTION = "Media juggle plugin for VDR"; +static const char *MAINMENUENTRY = "Muggle"; + +using namespace std; + const char* mgMuggle::Version(void) { return VERSION; @@ -40,9 +45,14 @@ const char* mgMuggle::MainMenuEntry(void) mgMuggle::mgMuggle(void) { - // Initialize any member variables here. - // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL - // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT! + // defaults for database arguments + the_setup.DbHost = "localhost"; + the_setup.DbPort = 0; + the_setup.DbName = "GiantDisc2"; + the_setup.DbUser = ""; + the_setup.DbPass = "" ; + the_setup.GdCompatibility = false; + the_setup.ToplevelDir = ""; } mgMuggle::~mgMuggle() @@ -53,12 +63,70 @@ mgMuggle::~mgMuggle() const char *mgMuggle::CommandLineHelp(void) { // Return a string that describes all known command line options. - return NULL; + return + " -h HHHH, --host=HHHH specify database host (default is localhost)\n" + " -n NNNN, --name=NNNN specify database name (overridden by -g)\n" + " -p PPPP, --port=PPPP specify port of database server (default is )\n" + " -u UUUU, --user=UUUU specify database user (default is )\n" + " -w WWWW, --password=WWWW specify database password (default is empty)\n" + " -t TTTT, --toplevel=TTTT specify toplevel directory for music\n" + " -g, --giantdisc enable full Giantdisc compatibility mode\n"; } bool mgMuggle::ProcessArgs(int argc, char *argv[]) { + cout << "mgMuggle::ProcessArgs" << endl << flush; + // Implement command line argument processing here if applicable. + static struct option long_options[] = + { + { "host", required_argument, NULL, 'h' }, + { "name", required_argument, NULL, 'n' }, + { "port", required_argument, NULL, 'p' }, + { "user", required_argument, NULL, 'u' }, + { "password", required_argument, NULL, 'w' }, + { "toplevel", required_argument, NULL, 't' }, + { "giantdisc", no_argument, NULL, 'g' }, + { NULL } + }; + + int c, option_index = 0; + while( ( c = getopt_long( argc, argv, "h:p:u:w:g:", long_options, &option_index ) ) != -1 ) + { + switch (c) + { + case 'h': + { + the_setup.DbHost = optarg; + } break; + case 'n': + { + the_setup.DbName = optarg; + } + case 'p': + { + the_setup.DbPort = atoi( optarg ); + } break; + case 'u': + { + the_setup.DbUser = optarg; + } break; + case 'w': + { + the_setup.DbPass = optarg; + } break; + case 't': + { + the_setup.ToplevelDir = optarg; + } break; + case 'g': + { + the_setup.GdCompatibility = true; + } break; + default: return false; + } + } + return true; } @@ -73,9 +141,9 @@ bool mgMuggle::Start(void) // Start any background activities the plugin shall perform. mgSetDebugLevel( 99 ); - RegisterI18n(Phrases); - m_media = new mgMedia( mgMedia::GD_MP3 ); - m_root = m_media->getSelectionRoot(); + RegisterI18n( Phrases ); + m_media = new mgMedia( mgMedia::GD_MP3 ); + m_root = m_media->getSelectionRoot(); m_playlist = m_media->createTemporaryPlaylist(); m_media->initFilterSet(); return true; @@ -96,14 +164,22 @@ cOsdObject *mgMuggle::MainMenuAction(void) cMenuSetupPage *mgMuggle::SetupMenu(void) { - // Return a setup menu in case the plugin supports one. - return NULL; + return new mgMenuSetup(); } bool mgMuggle::SetupParse(const char *Name, const char *Value) { - // Parse your own setup parameters and store their values. - return false; + if (!strcasecmp(Name, "InitLoopMode")) the_setup.InitLoopMode = atoi(Value); + else if (!strcasecmp(Name, "InitShuffleMode")) the_setup.InitShuffleMode = atoi(Value); + else if (!strcasecmp(Name, "AudioMode")) the_setup.AudioMode = atoi(Value); + else if (!strcasecmp(Name, "DisplayMode")) the_setup.DisplayMode = atoi(Value); + else if (!strcasecmp(Name, "BackgrMode")) the_setup.BackgrMode = atoi(Value); + else if (!strcasecmp(Name, "TargetLevel")) the_setup.TargetLevel = atoi(Value); + else if (!strcasecmp(Name, "LimiterLevel")) the_setup.LimiterLevel = atoi(Value); + else if (!strcasecmp(Name, "Only48kHz")) the_setup.Only48kHz = atoi(Value); + else return false; + + return true; } VDRPLUGINCREATOR(mgMuggle); // Don't touch this! @@ -1,24 +1,19 @@ -/*******************************************************************/ -/*! \file muggle.h - * \brief Implements a plugin for browsing media libraries within VDR - ******************************************************************** - * \version $Revision: 1.4 $ - * \date $Date: 2004/02/03 19:28:46 $ +/*! + * \file muggle.h + * \brief Implements a plugin for browsing media libraries within VDR + * + * \version $Revision: 1.5 $ + * \date $Date: 2004/05/28 15:29:18 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: LarsAC $ + * \author file owner: $Author: lvw $ + * + * $Id: muggle.h,v 1.5 2004/05/28 15:29:18 lvw Exp $ */ -/*******************************************************************/ #ifndef _MUGGLE_H #define _MUGGLE_H -#undef SHELL_TEST - -#ifdef SHELL_TEST - #include "shell_plugin.h" -#else - #include <plugin.h> -#endif +#include <plugin.h> class mgMedia; class mgSelectionTreeNode; diff --git a/vdr_config.h b/vdr_config.h new file mode 100644 index 0000000..6178a34 --- /dev/null +++ b/vdr_config.h @@ -0,0 +1,119 @@ +/*! + * \file vdr_menu.c + * \brief Implements menu handling for browsing media libraries within VDR + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:18 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_config.h,v 1.2 2004/05/28 15:29:18 lvw Exp $ + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> + */ + +// This files contains some option switches/values for compile time +// configuration of the MP3/MPlayer plugin. You should only alter +// this file, if you have understand the source code parts which deal +// with the changed value. + +// After changing this file you should do a "make plugins-clean ; make plugins" +// to recompile vdr. + +#ifndef ___CONFIG_H +#define ___CONFIG_H + +// The buffer size in bytes for the decoded audio data which is about to +// be send to the dvb driver. Should not be made to big, as this delays the +// pause function too much. +#define MP3BUFSIZE (100*1024) + +// The number of consecutive frame decoding error that may occure during +// decoding before aborting the file. +#define MAX_FRAME_ERR 10 + +// Defines the min. gain required to launch the normalizer. Don't bother normalizing +// songs which wouldn't change much in volumen. +#define MIN_GAIN 0.03 + +// Comment this out to disable the fast limiter function in the normalizer. The fast function +// uses a lookup table and is 12-14 times faster than the normal function, but less +// accurate (rel. error about 1e-7). +#define USE_FAST_LIMITER + +// Defines the filename extention to use for playlist files. +#define PLAYLISTEXT ".m3u" + +// Defines the text to identify WinAmp-Style playlists. +#define WINAMPEXT "#EXTM3U" + +// Comment this out, if you don't want to use mmap() to access the data files. +// Normaly there is no need to disable mmap(), as the code switches to normal +// file i/o if mmap() is not available for a specific file/filesystem. +#define USE_MMAP + +// Defines the max. memory size used for mmapping. This should not exceed the +// available free memory on your machine. +#define MAX_MMAP_SIZE (32*1024*1024) + +// The buffer size in bytes to use for normal file i/o if a file cannot be +// mmap()ed. If set to large on slow media, may cause audio drop outs as the +// audio buffer may underrun while filling this buffer. +#define MP3FILE_BUFSIZE (32*1024) + +// The filename to save the cached id3 information. The file is located in the +// video directory and this definition must not contain any path (no "/" ) +#define CACHEFILENAME "id3info.cache" + +// The interval in seconds in which the id3 cache is saved to disk (only +// if any changes occured). +#define CACHESAVETIMEOUT 120 + +// How many days to keep unused entries in the id3 cache. Unused entries from +// undefined mp3sources are purged after timeout/10 days. +#define CACHEPURGETIMEOUT 120 + +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- + +// Defines the timeout in seconds for functions which use a single key +// (e.g. openning the playlist window). If the key is repressed during +// the timeout, the secondary function is activated. +#define MULTI_TIMEOUT 3 + +// Defines the timeout in ms for entering the single digits in direct song +// selection. +#define SELECT_TIMEOUT 1000 + +// If the progress display is closed on direct song selection, the display +// is opend temporarily. This defines the time in seconds after the display +// is closed again. +#define SELECTHIDE_TIMEOUT 3 + +// Defines the time in seconds to jump inside a song with left/right. +#define JUMPSIZE 3 + +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- + +// DEBUGGING OPTIONS +// (not needed for normal operation) + +// Uncomment to enable generic debugging messages to the console. This may slow +// down operation in some cases. +#define DEBUG + +// Uncomment to disable audio output to the dvb driver. The audio data is +// simply discarded. +//#define DUMMY_OUTPUT + +// Uncomment to enable dumping the normalize limiter lookup table to the file +// "/tmp/limiter". The generated file will be about 3MB in size. This option shouldn't +// be enabled for day-by-day operation. +//#define ACC_DUMP + +#endif //___CONFIG_H diff --git a/vdr_decoder.c b/vdr_decoder.c new file mode 100644 index 0000000..8b4cf70 --- /dev/null +++ b/vdr_decoder.c @@ -0,0 +1,137 @@ +/*! + * \file vdr_decoder.h + * \brief A generic decoder for a VDR media plugin (muggle) + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:18 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_decoder.c,v 1.2 2004/05/28 15:29:18 lvw Exp $ + * + * Adapted from: + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001-2003 Stefan Huelswitt <huels@iname.com> + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/vfs.h> + +#include <videodir.h> +#include <interface.h> + +// #include "common.h" +// #include "data-mp3.h" +// #include "decoder-core.h" +// #include "decoder-mp3-stream.h" +// #include "decoder-snd.h" +// #include "decoder-ogg.h" + +#include "vdr_decoder.h" +#include "vdr_decoder_mp3.h" + +using namespace std; + +// --- mgDecoders --------------------------------------------------------------- + +mgMediaType mgDecoders::getMediaType( string s ) +{ + // TODO: currently handles only mp3. LVW + return MT_MP3; +} + +mgDecoder *mgDecoders::findDecoder( string filename ) +{ + mgDecoder *decoder = 0; + + switch( getMediaType( filename ) ) + { + case MT_MP3: + { + // prepend filename with path to music library?? TODO + printf( "mp3 file detected, launching mgMP3Decoder\n" ); + decoder = new mgMP3Decoder(filename); + } break; + + /* + case MT_MP3_STREAM: decoder = new mgMP3StreamDecoder(full); break; + #ifdef HAVE_SNDFILE + case MT_SND: decoder = new cSndDecoder(full); break; + #endif + #ifdef HAVE_VORBISFILE + case MT_OGG: decoder = new mgOggDecoder(full); break; + #endif + */ + default: + { + esyslog("ERROR: unknown media type" ); + } break; + } + + if( decoder && !decoder->valid() ) + { + // no decoder found or decoder doesn't match + + delete decoder; // might be carried out on NULL pointer! + decoder = 0; + + esyslog("ERROR: no valid decoder found for %s", filename.c_str() ); + } + return decoder; +} + +// --- mgDecoder ---------------------------------------------------------------- + +mgDecoder::mgDecoder(string filename) +{ + m_filename = filename; + m_locked = 0; + m_urgentLock = false; + m_playing = false; +} + +mgDecoder::~mgDecoder() +{ +} + +void mgDecoder::lock(bool urgent) +{ + m_locklock.Lock(); + + if( urgent && m_locked ) + { + m_urgentLock = true; // signal other locks to release quickly + } + m_locked ++; + + m_locklock.Unlock(); // don't hold the "locklock" when locking "lock", may cause a deadlock + m_lock.Lock(); + m_urgentLock = false; +} + +void mgDecoder::unlock(void) +{ + m_locklock.Lock(); + + m_locked--; + + m_lock.Unlock(); + m_locklock.Unlock(); +} + +bool mgDecoder::tryLock(void) +{ + bool res = false; + m_locklock.Lock(); + + if( !m_locked && !m_playing ) + { + lock(); + res = true; + } + m_locklock.Unlock(); + return res; +} diff --git a/vdr_decoder.h b/vdr_decoder.h new file mode 100644 index 0000000..74b200e --- /dev/null +++ b/vdr_decoder.h @@ -0,0 +1,158 @@ +/*! + * \file vdr_decoder.h + * \brief A generic decoder for a VDR media plugin (muggle) + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:18 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_decoder.h,v 1.2 2004/05/28 15:29:18 lvw Exp $ + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> + */ + +#ifndef ___DECODER_H +#define ___DECODER_H + +#include <string> + +#include <thread.h> +#include <string> + +#define DEC_ID(a,b,c,d) (((a)<<24)+((b)<<16)+((c)<<8)+(d)) + +// --------From decoder_core.h ------------------------------------ + +/*! + * \brief The current status of the decoder + */ +enum eDecodeStatus + { + dsOK=0, dsPlay, dsSkip, dsEof, dsError, dsSoftError + }; + +// ---------------------------------------------------------------- + +/*! + * \brief A data structure to put decoded PCM data + */ +struct mgDecode +{ + eDecodeStatus status; + int index; + struct mad_pcm *pcm; +}; + +// ---------------------------------------------------------------- + +/*! + * \brief Information about ??? + */ +class mgPlayInfo +{ +public: + int m_index, m_total; +}; + +// ---------------------------------------------------------------- + +/*! + * \brief Media types + */ +enum mgMediaType +{ + MT_MP3, MT_MP3_STREAM, MT_OGG, MT_FLAC +}; + +/*! + * \brief A generic decoder class to handle conversion into PCM format + */ +class mgDecoder +{ +protected: + + /*! \brief The currently playing file */ + std::string m_filename; + + /*! \brief Mutexes to coordinate threads */ + cMutex m_lock, m_locklock; + int m_locked; + bool m_urgentLock; + + /*! \brief Whether the decoder is currently active */ + bool m_playing; + + /*! \brief ??? */ + mgPlayInfo m_playinfo; + + /*! \brief Place a lock */ + virtual void lock( bool urgent = false ); + + /*! \brief Release a lock */ + virtual void unlock(void); + + /*! \brief Try to obtain a lock */ + virtual bool tryLock(void); + +public: + + /*! \brief The constructor */ + mgDecoder( std::string filename ); + + /*! \brief The destructor */ + virtual ~mgDecoder(); + + /*! \brief Whether a decoder instance is able to play the given file */ + virtual bool valid() = 0; + + /*! \brief Whether a stream (i.e. from the network is being decoded */ + virtual bool isStream() + { + return false; + } + + /*! \brief Start decoding */ + virtual bool start() = 0; + + /*! \brief Stop decoding */ + virtual bool stop() = 0; + + /*! \brief Skip an amount of time. Impossible by default */ + virtual bool skip( int seconds, int avail, int rate) + { + return false; + } + + /*! \brief Return decoded data */ + virtual struct mgDecode *decode() = 0; + + /*! \brief Information about the current playback status */ + virtual mgPlayInfo *playInfo() + { + return 0; + } +}; + +// ---------------------------------------------------------------- + +/*! + * \brief A generic decoder manager class to handle different decoders + */ +class mgDecoders +{ +public: + + /*! \brief Try to find a valid decoder for a file + */ + static mgDecoder *findDecoder( std::string filename ); + + /*! \brief determine the media type for a given source + */ + static mgMediaType getMediaType( std::string filename ); + +}; + +#endif //___DECODER_H diff --git a/vdr_decoder_mp3.c b/vdr_decoder_mp3.c new file mode 100644 index 0000000..436d7b6 --- /dev/null +++ b/vdr_decoder_mp3.c @@ -0,0 +1,450 @@ +/*! + * \file vdr_decoder_mp3.h + * \brief An mp3 decoder for a VDR media plugin (muggle) + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:19 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_decoder_mp3.c,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> + */ + +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <unistd.h> +#include <cmath> +#include <iostream> + +#include "vdr_config.h" +#include "vdr_decoder_mp3.h" +#include "vdr_stream.h" + +#include "mg_tools.h" + +#define d(x) x + +using namespace std; + +// ---------------------------------------------------------------- + +int mgMadStream(struct mad_stream *stream, mgStream *str) +{ + unsigned char *data; + unsigned long len; + if( str->stream( data, len, stream->next_frame ) ) + { + if( len > 0 ) + { + mad_stream_buffer(stream, data, len); + } + return len; + } + return -1; +} + +// --- mgMP3Decoder ------------------------------------------------------------- + +mgMP3Decoder::mgMP3Decoder(string filename, bool preinit) + : mgDecoder(filename) +{ + MGLOG( "mgMP3Decoder::mgMP3Decoder" ); + + m_stream = 0; + m_isStream = false; + + if( preinit ) + { + m_stream = new mgStream( filename ); + } + m_madframe = 0; + m_madsynth = 0; + + memset( &m_madstream, 0, sizeof(m_madstream) ); + + init(); +} + +mgMP3Decoder::~mgMP3Decoder() +{ + MGLOG( "mgMP3Decoder::~mgMP3Decoder" ); + + clean(); + delete m_stream; +} + +void mgMP3Decoder::init() +{ + MGLOG( "mgMP3Decoder::init" ); + + clean(); + mad_stream_init( &m_madstream ); + + m_madframe = new mad_frame; + mad_frame_init( m_madframe ); + + m_madsynth = new mad_synth; + mad_synth_init( m_madsynth ); + + mad_stream_options( &m_madstream, MAD_OPTION_IGNORECRC); + + m_playtime = mad_timer_zero; + m_skiptime = mad_timer_zero; + m_framenum = m_framemax = 0; + m_mute = m_errcount = 0; +} + +void mgMP3Decoder::clean() +{ + MGLOG( "mgMP3Decoder::clean" ); + + m_playing = false; + if( m_madsynth ) + { + mad_synth_finish( m_madsynth ); + delete m_madsynth; + m_madsynth = 0; + } + + if( m_madframe ) + { + mad_frame_finish( m_madframe ); + delete m_madframe; + m_madframe = 0; + } + mad_stream_finish( &m_madstream ); + + /* + if( m_frameinfo ) + { + cout << "mgMP3Decoder::clean: m_frameinfo deleted." << endl << flush; + delete m_frameinfo; + cout << "mgMP3Decoder::clean: m_frameinfo deleted." << endl << flush; + m_frameinfo = 0; + } + */ +} + +bool mgMP3Decoder::valid(void) +{ + MGLOG( "mgMP3Decoder::valid" ); + + bool res = false; + if( tryLock() ) + { + if( start() ) + { + struct mgDecode *dd; + int count = 10; + do + { + dd = decode(); + if( dd->status == dsEof ) + { + count=0; + } + if( dd->status != dsPlay) + { + break; + } + } while( --count ); + if( !count ) + { + res=true; + } + stop(); + } + unlock(); + } + return res; +} + +mgPlayInfo *mgMP3Decoder::playInfo() +{ + MGLOG( "mgMP3Decoder::playInfo" ); + if( m_playing ) + { + m_playinfo.m_index = mad_timer_count( m_playtime, MAD_UNITS_SECONDS ); + // m_playinfo.Total = m_scan->Total; // TODO + + return &m_playinfo; + } + return 0; +} + +bool mgMP3Decoder::start() +{ + MGLOG( "mgMP3Decoder::start" ); + + lock(true); + init(); + m_playing = true; + + if( m_stream->open(true) ) + { + if(!m_isStream) + { + m_stream->seek(); + + printf( "mgMP3Decoder::start: m_framemax not determined, rewinding disabled!!!\n" ); + + /* m_framemax = scan->Frames+20; // TODO + + m_frameinfo = new struct FrameInfo[m_framemax]; + if(!m_frameinfo) + { + printf( "mgMP3Decoder::start: no memory for frame index, rewinding disabled" ); + } + */ + } + unlock(); + + printf( "mgMP3Decoder::start: true\n" ); + return true; + } + m_stream->close(); + clean(); + unlock(); + + printf( "mgMP3Decoder::start: false" ); + return false; +} + +bool mgMP3Decoder::stop(void) +{ + MGLOG( "mgMP3Decoder::stop" ); + + lock(); + + if( m_playing ) + { + m_stream->close(); + clean(); + } + unlock(); + return true; +} + +struct mgDecode *mgMP3Decoder::done(eDecodeStatus status) +{ + // MGLOG( "mgMP3Decoder::done" ); + + m_ds.status = status; + m_ds.index = mad_timer_count( m_playtime, MAD_UNITS_MILLISECONDS ); + m_ds.pcm = &m_madsynth->pcm; + unlock(); // release the lock from Decode() + + return &m_ds; +} + +eDecodeStatus mgMP3Decoder::decodeError(bool hdr) +{ + MGLOG( "mgMP3Decoder::decodeError" ); + + if( m_madstream.error == MAD_ERROR_BUFLEN || m_madstream.error == MAD_ERROR_BUFPTR ) + { + int s = mgMadStream( &m_madstream, m_stream ); + if( s < 0 ) + { + printf( "mgMP3Decoder::decodeError: dsError returned\n" ); + return dsError; + } + if( s == 0 ) + { + printf( "mgMP3Decoder::decodeError: dsEof returned\n" ); + return dsEof; + } + } + else if( !MAD_RECOVERABLE( m_madstream.error ) ) + { + printf( "mgMP3Decoder::decodeError: mad decode %sfailed, frame=%d: %s. Returning dsError\n", + hdr? "hdr " : "", m_framenum, + mad_stream_errorstr( &m_madstream ) ); + return dsError; + } + else + { + m_errcount += hdr? 1: 100; + printf( "mgMP3Decoder::decodeError: mad decode %s error, frame=%d count=%d: %s. Returning dsOK\n", + hdr? "hdr ": "", m_framenum, m_errcount, + mad_stream_errorstr( &m_madstream ) ); + } + return dsOK; +} + +struct mgDecode *mgMP3Decoder::decode() +{ + // MGLOG( "mgMP3Decoder::decode" ); + + lock(); // this is released in Done() + eDecodeStatus r; + + while( m_playing ) + { + if( m_errcount >= MAX_FRAME_ERR*100 ) + { + printf( "mgMP3Decoder::decode: excessive decoding errors," + " aborting file %s\n", m_filename.c_str() ); + return done(dsError); + } + + if( mad_header_decode( &m_madframe->header, &m_madstream) == -1) + { + if( (r = decodeError(true) ) ) + { + return done(r); + } + } + else + { + if(!m_isStream) + { +#ifdef DEBUG2 + if( m_framenum >= m_framemax ) + { + printf( "mgMP3Decoder::start: framenum >= framemax!!!!\n" ); + } +#endif + if( m_frameinfo && m_framenum < m_framemax ) + { + m_frameinfo[m_framenum].Pos = + m_stream->bufferPos() + + ( m_madstream.this_frame - m_madstream.buffer ); + m_frameinfo[m_framenum].Time = m_playtime; + } + } + + mad_timer_add( &m_playtime, m_madframe->header.duration); + m_framenum ++; + + if( mad_timer_compare(m_playtime, m_skiptime) >= 0 ) + { + m_skiptime = mad_timer_zero; + } + else + { + return done(dsSkip); // skipping, decode next header + } + + if( mad_frame_decode( m_madframe, &m_madstream ) == -1 ) + { + if( ( r = decodeError(false) ) ) + { + return done(r); + } + } + else + { + m_errcount = 0; + + // TODO: // m_scan->InfoHook( &frame->header ); + + mad_synth_frame( m_madsynth, m_madframe); + + if( m_mute ) + { + m_mute--; + return done( dsSkip ); + } + return done( dsPlay ); + } + } + } + return done( dsError ); +} + +void mgMP3Decoder::makeSkipTime( mad_timer_t *skiptime, + mad_timer_t playtime, + int secs, int avail, int dvbrate) +{ + mad_timer_t time; + + *skiptime = playtime; + + mad_timer_set( &time, abs(secs), 0, 0 ); + + if( secs < 0 ) + { + mad_timer_negate(&time); + } + + mad_timer_add( skiptime, time ); + + float bufsecs = (float)avail / (float)(dvbrate * (16/8 * 2)); // Byte/s = samplerate * 16 bit * 2 chan + + printf( "mgMP3Decoder::makeSkipTime: skip: avail=%d bufsecs=%f\n", avail, bufsecs ); + + int full = (int)bufsecs; + bufsecs -= (float)full; + + mad_timer_set( &time, full, (int)(bufsecs*1000.0), 1000); + + mad_timer_negate(&time); + + mad_timer_add( skiptime, time ); + + printf( "mgMP3Decoder::makeSkipTime: skip: playtime=%ld secs=%d full=%d bufsecs=%f skiptime=%ld\n", + mad_timer_count(playtime,MAD_UNITS_MILLISECONDS), + secs, full, bufsecs, + mad_timer_count(*skiptime,MAD_UNITS_MILLISECONDS ) ); +} + +bool mgMP3Decoder::skip( int seconds, int avail, int rate ) +{ + lock(); + + bool res = false; + if( m_playing && !m_isStream ) + { + if( !mad_timer_compare( m_skiptime, mad_timer_zero ) ) + { // allow only one skip at any time + mad_timer_t time; + makeSkipTime( &time, m_playtime, seconds, avail, rate); + + if( mad_timer_compare( m_playtime, time ) <= 0 ) + { // forward skip +#ifdef DEBUG + int i = mad_timer_count( time, MAD_UNITS_SECONDS ); + printf( "mgMP3Decoder::skip: forward skipping to %02d:%02d\n", i/60, i%60 ); +#endif + m_skiptime = time; + m_mute=1; + res = true; + } + else + { // backward skip + if( m_frameinfo ) + { +#ifdef DEBUG + int i = mad_timer_count( time, MAD_UNITS_SECONDS ); + printf( "mgMP3Decoder::skip: rewinding to %02d:%02d\n", i/60, i%60 ); +#endif + while( m_framenum && mad_timer_compare( time, m_frameinfo[--m_framenum].Time) < 0) ; + m_mute = 2; + if( m_framenum >= 2) + { + m_framenum-=2; + } + m_playtime = m_frameinfo[m_framenum].Time; + m_stream->seek( m_frameinfo[m_framenum].Pos ); + mad_stream_finish( &m_madstream ); // reset stream buffer + mad_stream_init( &m_madstream ); +#ifdef DEBUG + i = mad_timer_count( m_playtime, MAD_UNITS_MILLISECONDS ); + printf( "mgMP3Decoder::skip: new playtime=%d framenum=%d filepos=%lld\n", i, m_framenum, m_frameinfo[m_framenum].Pos); +#endif + res = true; + } + } + } + } + unlock(); + return res; +} diff --git a/vdr_decoder_mp3.h b/vdr_decoder_mp3.h new file mode 100644 index 0000000..abc8eed --- /dev/null +++ b/vdr_decoder_mp3.h @@ -0,0 +1,114 @@ +/*! + * \file vdr_decoder_mp3.h + * \brief An mp3 decoder for a VDR media plugin (muggle) + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:19 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_decoder_mp3.h,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> + */ + +#ifndef ___DECODER_MP3_H +#define ___DECODER_MP3_H + +#define DEC_MP3 DEC_ID('M','P','3',' ') +#define DEC_MP3_STR "MP3" + +#include <mad.h> +#include <string> + +#include "vdr_decoder.h" + +#if MAD_F_FRACBITS != 28 +#warning libmad with MAD_F_FRACBITS != 28 not tested +#endif + +class mgStream; + +// ---------------------------------------------------------------- + +/*! + * \brief A class to decode mp3 songs into PCM using libmad + */ +class mgMP3Decoder : public mgDecoder +{ +private: + struct mgDecode m_ds; + + // + struct mad_stream m_madstream; + struct mad_frame *m_madframe; + struct mad_synth *m_madsynth; + mad_timer_t m_playtime, m_skiptime; + + // + struct FrameInfo + { + unsigned long long Pos; + mad_timer_t Time; + } *m_frameinfo; + + int m_framenum, m_framemax, m_errcount, m_mute; + // + void init(); + + void clean(); + + struct mgDecode *done( eDecodeStatus status ); + + virtual mgPlayInfo *playInfo(); + + eDecodeStatus decodeError(bool hdr); + + void makeSkipTime(mad_timer_t *skiptime, mad_timer_t playtime, + int secs, int avail, int dvbrate); + +protected: + mgStream *m_stream; + bool m_isStream; + +public: + + /*! + * \brief construct a decoder from a filename + */ + mgMP3Decoder( std::string filename, bool preinit = true ); + + /*! + * \brief the destructor + */ + virtual ~mgMP3Decoder(); + + /*! + * \brief check, whether the file contains useable MP3 content + */ + virtual bool valid(); + + /*! + * \brief start the decoding process + */ + virtual bool start(); + + /*! + * \brief stop the decoding process + */ + virtual bool stop(); + + /*! + * \brief skip an amount of seconds + */ + virtual bool skip( int seconds, int avail, int rate ); + + /*! + * \brief the actual decoding function (uses libmad) + */ + virtual struct mgDecode *decode(); +}; + +#endif //___DECODER_MP3_H @@ -1,34 +1,36 @@ -/*******************************************************************/ -/*! \file vdr_menu.c - * \brief Implements menu handling for browsing media libraries within VDR - ******************************************************************** - * \version $Revision: 1.19 $ - * \date $Date: 2004/02/23 17:03:24 $ +/*! + * \file vdr_menu.c + * \brief Implements menu handling for browsing media libraries within VDR + * + * \version $Revision: 1.20 $ + * \date $Date: 2004/05/28 15:29:19 $ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: RaK $ + * \author Responsible author: $Author: lvw $ * - * $Id: vdr_menu.c,v 1.19 2004/02/23 17:03:24 RaK Exp $ + * $Id: vdr_menu.c,v 1.20 2004/05/28 15:29:19 lvw Exp $ */ -/*******************************************************************/ + +#include <string> +#include <vector> + +#include <mysql/mysql.h> #include <menuitems.h> #include <tools.h> -#include <mysql/mysql.h> #include "vdr_menu.h" +#include "vdr_player.h" +#include "i18n.h" #include "mg_content_interface.h" +#include "mg_playlist.h" +#define DEBUG #include "mg_tools.h" #include "mg_media.h" #include "mg_filters.h" #include "gd_content_interface.h" -#include "i18n.h" - -#include <string> -#include <vector> - using namespace std; // ----------------------- mgMenuTreeItem ------------------ @@ -51,9 +53,6 @@ void mgMenuTreeItem::Set() SetText( buffer, false ); } -// ----------------------- mgMenuTrackItem ------------------ - - // ----------------------- mgMainMenu ---------------------- mgMainMenu::mgMainMenu(mgMedia *media, mgSelectionTreeNode *root, mgPlaylist *playlist) @@ -64,28 +63,6 @@ mgMainMenu::mgMainMenu(mgMedia *media, mgSelectionTreeNode *root, mgPlaylist *pl SetTitle( tr("Muggle Media Database") ); SetButtons(); - m_filtername = new char[32]; - strcpy( m_filtername, "none" ); - - m_title = new char[32]; - strcpy( m_title, "none" ); - - m_interpret = new char[32]; - strcpy( m_interpret, "none" ); - - m_album = new char[32]; - strcpy( m_album, "none" ); - - m_playlist = new char[32]; - strcpy( m_playlist, "none" ); - - m_year_min = 1900; - m_year_max = 2100; - - m_filter = 0; - - m_tracklist = NULL; - DisplayTree( m_root ); } @@ -110,17 +87,17 @@ void mgMainMenu::SetButtons( ) { SetHelp( tr("Add"), tr("Cycle tree"), tr("Playlist"), tr("Submenu") ); } - else if( m_state == PLAYLIST ) + else if( m_state == TREE_SUBMENU ) { - SetHelp( tr("Edit PL"), tr("Track info"), tr("Filter"), tr("Submenu") ); + SetHelp( tr("Instant Play"), tr("2"), tr("3"), tr("Mainmenu") ); } - else if( m_state == PLAYLIST_TRACKINFO ) + else if( m_state == PLAYLIST ) { - SetHelp( tr("Edit PL?"), tr("Album info"), tr("Filter"), tr("Submenu") ); + SetHelp( tr("Play"), tr("Move"), tr("Filter"), tr("Submenu") ); } - else if( m_state == PLAYLIST_ALBUMINFO ) + else if( m_state == PLAYLIST_SUBMENU ) { - SetHelp( tr("Edit PL?"), tr("Playlist"), tr("Filter"), tr("Submenu") ); + SetHelp( tr("Load"), tr("Save"), tr("Delete"), tr("Mainmenu") ); } else if( m_state == FILTER ) { @@ -132,9 +109,17 @@ void mgMainMenu::SetButtons( ) } } +void mgMainMenu::Move( int from, int to ) +{ + m_current_playlist->move( from, to ); + + cOsdMenu::Move( from, to ); + Display(); +} + eOSState mgMainMenu::ProcessKey(eKeys key) { - mgDebug( 1, "mgMainMenu::ProcessKey" ); + MGLOG( "mgMainMenu::ProcessKey" ); eOSState state = cOsdMenu::ProcessKey(key); if( m_state == TREE ) @@ -169,10 +154,10 @@ eOSState mgMainMenu::ProcessKey(eKeys key) if( tracks ) { - m_current_playlist->appendList(tracks); char buffer[256]; sprintf( buffer, "%d tracks sent to current playlist", (int) tracks->size() ); + m_current_playlist->appendList(tracks); Interface->Status( buffer ); Interface->Flush(); } @@ -201,8 +186,7 @@ eOSState mgMainMenu::ProcessKey(eKeys key) } break; case kBlue: { - mgDebug( 1, "mgMainMenu: submenu (todo)" ); - + m_last_osd_index = Current(); DisplayTreeSubmenu(); state = osContinue; @@ -248,18 +232,25 @@ eOSState mgMainMenu::ProcessKey(eKeys key) { case k0 ... k9: { - int n = key - k0; - + int n = key - k0; TreeSubmenuAction( n ); state = osContinue; - } - case kOk: + } break; + case kBlue: { - TreeSubmenuAction( Current() ); + m_state = TREE; + // restore last selected entry + int last = m_history.back(); + DisplayTree( m_node, last ); + state = osContinue; } break; + case kOk: + { + state = TreeSubmenuAction( Current() ); + } break; default: { state = osContinue; @@ -268,11 +259,10 @@ eOSState mgMainMenu::ProcessKey(eKeys key) } else if( state == osBack ) { - mgDebug( 1, "mgMainMenu: return from tree view submenu" ); - + m_state = TREE; + // restore last selected entry int last = m_history.back(); - DisplayTree( m_node, last ); state = osContinue; @@ -280,65 +270,91 @@ eOSState mgMainMenu::ProcessKey(eKeys key) } else if( m_state == PLAYLIST ) { - mgDebug( 1, "mgMainMenu: in state PLAYLIST" ); if( state == osUnknown ) { switch( key ) { case kOk: { - mgDebug( 1, "mgMainMenu: playlist ok" ); + // show some more information? state = osContinue; } break; case kRed: { - mgDebug( 1, "mgMainMenu: edit playlist" ); - Mark(); // Mark (to move), moving done by VDR, calls Move - } + // TODO: what happens if the user presses play and the player is already active? + Play( m_current_playlist ); + state = osEnd; + } break; case kGreen: { - if( m_state == PLAYLIST ) - { - mgDebug( 1, "mgMainMenu: switch to TrackInfo" ); - DisplayTrackInfo(); - } - else if( m_state == PLAYLIST_TRACKINFO ) - { - mgDebug( 1, "mgMainMenu: switch to AlbumInfo" ); - DisplayAlbumInfo(); - } - else if( m_state == PLAYLIST_ALBUMINFO ) - { - mgDebug( 1, "mgMainMenu: switch to Playlist" ); - DisplayPlaylist(); - } + Mark(); + state = osContinue; } break; case kYellow: { - mgDebug( 1, "mgMainMenu: cycle playlist to filter" ); DisplayFilter(); state = osContinue; } break; case kBlue: { // Submenu - mgDebug( 1, "mgMainMenu: playlist submenu (todo)" ); - + m_last_osd_index = Current(); + DisplayPlaylistSubmenu(); + state = osContinue; } break; default: { - mgDebug( 1, "mgMainMenu: default" ); state = osContinue; }; } } } + else if( m_state == PLAYLIST_SUBMENU ) + { + if( state == osUnknown ) + { + switch( key ) + { + case k0 ... k9: + { + int n = key - k0; + PlaylistSubmenuAction( n ); + + state = osContinue; + } break; + case kYellow: + { + PlaylistSubmenuAction( 3 ); + } break; + case kBlue: + { + m_state = PLAYLIST; + DisplayPlaylist( m_last_osd_index ); + + state = osContinue; + } break; + case kOk: + { + state = PlaylistSubmenuAction( Current() ); + } break; + default: + { + state = osContinue; + } break; + } + } + else if( state == osBack ) + { + m_state = PLAYLIST; + DisplayPlaylist( m_last_osd_index ); + + state = osContinue; + } + } else if( m_state == FILTER ) { - mgDebug( 1, "mgMainMenu: in state FILTER" ); - if( state == osUnknown ) { switch( key ) @@ -399,8 +415,7 @@ eOSState mgMainMenu::ProcessKey(eKeys key) else { mgDebug(1, "Process key: else"); - mgDebug(1, "Process key: %d", (int) state); - + mgDebug(1, "Process key: %d", (int) state); } return state; @@ -461,22 +476,43 @@ void mgMainMenu::DisplayTreeSubmenu() SetTitle( strcat("Muggle - ",tr("Tree View Commands") ) ); // Add items - Add( new cOsdItem( "1 - Test1" ) ); + Add( new cOsdItem( "1 - Instant play" ) ); Add( new cOsdItem( "9 - Test9" ) ); Add( new cOsdItem( "0 - Test0" ) ); Display(); } -void mgMainMenu::TreeSubmenuAction( int n ) +eOSState mgMainMenu::TreeSubmenuAction( int n ) { mgDebug( "mgMainMenu: TreeSubmenuAction( %d )", n ); + eOSState state = osContinue; switch( n ) { case 0: { - // action 0 + // action 0: instant play of current node, might need a security question + + mgSelectionTreeNode *current = CurrentNode(); + if( current ) + { + // clear playlist + m_current_playlist->clear(); + + // append current node + vector<mgContentItem*> *tracks = current->getTracks(); + + if( tracks ) + { + m_current_playlist->appendList( tracks ); + + // play + Play( m_current_playlist ); + + state = osEnd; + } + } } break; case 1: { @@ -487,16 +523,16 @@ void mgMainMenu::TreeSubmenuAction( int n ) // undefined action } break; } + + return state; } -void mgMainMenu::DisplayPlaylist() +void mgMainMenu::DisplayPlaylist( int index_current ) { m_state = PLAYLIST; - mgDebug( 1, "mgBrowseMenu::DisplayPlaylist"); // make sure we have a current playlist Clear(); - SetButtons(); vector<mgContentItem*>* list = m_current_playlist-> getAll(); @@ -506,68 +542,109 @@ void mgMainMenu::DisplayPlaylist() tr("items") ); SetTitle( titlestr ); - mgDebug( 1, "mgBrowseMenu::DisplayPlaylist: %d elements received", - list->size() ); - for( unsigned int i = 0; i < m_current_playlist->getNumItems(); i++) { string label = m_current_playlist->getLabel( i, " " ); Add( new cOsdItem( label.c_str() ) ); } + + if( index_current >= 0 ) + { + cOsdItem *item = Get( m_last_osd_index ); + SetCurrent( item ); + RefreshCurrent(); + DisplayCurrent( true ); + } Display(); } -void mgMainMenu::DisplayTrackInfo() +void mgMainMenu::DisplayPlaylistSubmenu() { - m_state = PLAYLIST_TRACKINFO; - SetButtons(); - - // show info of the currently playing track -} + m_state = PLAYLIST_SUBMENU; -void mgMainMenu::DisplayAlbumInfo() -{ - m_state = PLAYLIST_ALBUMINFO; + Clear(); SetButtons(); + SetTitle( "Muggle - Playlist View Commands" ); - // show info of the currently playing album + // Add items + Add( new cOsdItem( "1 - Load playlist" ) ); + Add( new cOsdItem( "2 - Save playlist" ) ); + Add( new cOsdItem( "3 - Clear playlist" ) ); + Add( new cOsdItem( "4 - Remove entry from list" ) ); + + Display(); } -void mgMainMenu::Move( int from, int to ) +eOSState mgMainMenu::PlaylistSubmenuAction( int n ) { - // check current view, perform move in the content view - if( m_state == PLAYLIST ) + cout << "mgMainMenu::PlaylistSubmenuAction: " << n << endl << flush; + eOSState state = osContinue; + + switch( n ) { - // resort + case 0: + { + Interface->Status( "Load not yet implemented" ); + Interface->Flush(); + } break; + case 1: + { + Interface->Status( "Save not yet implemented" ); + Interface->Flush(); + } break; + case 2: + { // clear playlist + m_current_playlist->clear(); + + // confirmation + Interface->Status( "Playlist cleared" ); + Interface->Flush(); + + state = osContinue; + } break; + case 3: + { // remove selected title + m_current_playlist->remove( m_last_osd_index ); + if( m_last_osd_index > 0 ) + { + m_last_osd_index --; + } + DisplayPlaylist( m_last_osd_index ); + + // confirmation + Interface->Status( "Entry removed" ); + Interface->Flush(); + } + default: + { + // undefined action + } break; } - // what now? + return state; } void mgMainMenu::DisplayFilter() { m_state = FILTER; - Clear(); - - mgDebug( 1, "Creating Muggle filter view" ); + + Clear(); SetButtons(); SetTitle( m_media->getActiveFilterTitle().c_str() ); - mgDebug( 1, "filter view2" ); - vector<mgFilter*> *filter_list = m_media->getActiveFilters(); - mgDebug( 1, "filter view3" ); int i=0; for( vector<mgFilter*>::iterator iter = filter_list->begin(); iter != filter_list->end(); iter ++ ) { - mgDebug( 1, "Filter %d/%dint filter %s='%s'", - i, filter_list->size(), - (*iter)->getName(), - (*iter)->getStrVal().c_str()); + mgDebug( 1, "Filter %d/%dint filter %s='%s'", + i, filter_list->size(), + (*iter)->getName(), + (*iter)->getStrVal().c_str()); + switch( (*iter)->getType() ) { case mgFilter::INT: @@ -637,9 +714,29 @@ void mgMainMenu::DisplayFilterSelector() // show available filters, load on OK? } +void mgMainMenu::Play(mgPlaylist *plist) +{ + MGLOG( "mgMainMenu::Play" ); + cControl *control = cControl::Control(); + + if( control && typeid(*control) == typeid(mgPlayerControl) ) + { // is there a running MP3 player? + cout << "mgMainMenu::Play: signal new playlist to existing control" << endl; + static_cast<mgPlayerControl*>(control)->NewPlaylist(plist); // signal the running player to load the new playlist + } + else + { + cout << "mgMainMenu::Play: starting new control" << endl; + cControl::Launch( new mgPlayerControl(plist) ); + } +} + /************************************************************ * * $Log: vdr_menu.c,v $ + * Revision 1.20 2004/05/28 15:29:19 lvw + * Merged player branch back on HEAD branch. + * * Revision 1.19 2004/02/23 17:03:24 RaK * - error in filter view while trying to switch or using the colour keys * workaround: first filter criteria is inttype. than it works, dont ask why ;-( @@ -661,6 +758,42 @@ void mgMainMenu::DisplayFilterSelector() * Revision 1.14 2004/02/12 09:08:48 LarsAC * Added handling of filter choices (untested) * + * Revision 1.13.2.12 2004/05/27 07:58:38 lvw + * Removed bugs in moving and removing tracks from playlists + * + * Revision 1.13.2.11 2004/05/26 14:31:04 lvw + * Added submenu for playlist view + * + * Revision 1.13.2.10 2004/05/25 21:58:45 lvw + * Handle submenus for views + * + * Revision 1.13.2.9 2004/05/25 00:10:45 lvw + * Code cleanup and added use of real database source files + * + * Revision 1.13.2.8 2004/05/11 06:35:16 lvw + * Added debugging while hunting stop bug. + * + * Revision 1.13.2.7 2004/05/04 16:51:53 lvw + * Debugging aids added. + * + * Revision 1.13.2.6 2004/04/29 06:48:21 lvw + * Output statements to aid debugging added + * + * Revision 1.13.2.5 2004/04/25 18:44:07 lvw + * Removed bugs in menu handling + * + * Revision 1.13.2.4 2004/03/14 12:30:56 lvw + * Menu now calls player + * + * Revision 1.13.2.3 2004/03/11 07:22:32 lvw + * Added setup menu + * + * Revision 1.13.2.2 2004/03/08 22:28:40 lvw + * Added documentation headers. + * + * Revision 1.13.2.1 2004/03/02 07:07:27 lvw + * Initial adaptations from MP3 plugin added (untested) + * * Revision 1.13 2004/02/09 19:27:52 MountainMan * filter set implemented * @@ -1,30 +1,23 @@ -/*******************************************************************/ -/*! \file vdr_menu.h - * \brief Implements menu handling for broswing media libraries within VDR - ******************************************************************** - * \version $Revision: 1.9 $ - * \date $Date: 2004/02/23 15:41:21 $ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: RaK $ +/*! + * \file vdr_menu.h + * \brief Implements menu handling for broswing media libraries within VDR * - * $Id: vdr_menu.h,v 1.9 2004/02/23 15:41:21 RaK Exp $ + * \version $Revision: 1.10 $ + * \date $Date: 2004/05/28 15:29:19 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ * + * $Id: vdr_menu.h,v 1.10 2004/05/28 15:29:19 lvw Exp $ */ -/*******************************************************************/ #ifndef _VDR_MENU_H #define _VDR_MENU_H -#undef SHELL_TEST +#include <list> -#ifdef SHELL_TEST - #include "myosd.h" - #include "mymenuitems.h" -#else - #include <osd.h> -#endif +#include <osd.h> -#include <list> +#include "i18n.h" #include "i18n.h" @@ -33,6 +26,9 @@ class mgSelectionTreeNode; class mgPlaylist; class mgTracklist; +/*! + * \brief a special menu item + */ class mgMenuTreeItem : public cOsdItem { public: @@ -49,6 +45,9 @@ class mgMenuTreeItem : public cOsdItem }; +/*! + * \brief the muggle main OSD + */ class mgMainMenu : public cOsdMenu { public: @@ -59,50 +58,51 @@ class mgMainMenu : public cOsdMenu mgMenuTreeItem *CurrentItem(); eOSState ProcessKey(eKeys Key); - void Move( int from, int to); protected: enum MuggleStatus { TREE, TREE_SUBMENU, - PLAYLIST, PLAYLIST_TRACKINFO, PLAYLIST_ALBUMINFO, - FILTER + PLAYLIST, PLAYLIST_SUBMENU, + FILTER, FILTER_SUBMENU }; void SetButtons(); + void Move( int from, int to ); // Tree view handling void DisplayTree( mgSelectionTreeNode *node, int select = 0 ); void DisplayTreeViewSelector(); - void DisplayTreeSubmenu(); - void TreeSubmenuAction( int n ); - // Filter view handling - void DisplayFilter(); - void DisplayFilterSelector(); + void DisplayTreeSubmenu(); + eOSState TreeSubmenuAction( int n ); // Playlist view handling - void DisplayPlaylist(); + void DisplayPlaylist( int index_current = -1 ); void DisplayTrackInfo(); void DisplayAlbumInfo(); + void DisplayPlaylistSubmenu(); + eOSState PlaylistSubmenuAction( int n ); + + // Filter view handling + void DisplayFilter(); + void DisplayFilterSelector(); + private: + void Play(mgPlaylist *plist); // content stuff mgMedia *m_media; mgSelectionTreeNode *m_root; mgSelectionTreeNode *m_node; mgPlaylist *m_current_playlist; - mgTracklist *m_tracklist; - - // filter items - char *m_title, *m_interpret, *m_album, *m_playlist, *m_filtername; - int m_year_min, m_year_max, m_filter; MuggleStatus m_state; - std::list<int> m_history; + + int m_last_osd_index; }; #endif @@ -110,9 +110,33 @@ class mgMainMenu : public cOsdMenu /************************************************************ * * $Log: vdr_menu.h,v $ + * Revision 1.10 2004/05/28 15:29:19 lvw + * Merged player branch back on HEAD branch. + * * Revision 1.9 2004/02/23 15:41:21 RaK * - first i18n attempt * + * Revision 1.8.2.7 2004/05/27 07:58:38 lvw + * Removed bugs in moving and removing tracks from playlists + * + * Revision 1.8.2.6 2004/05/26 14:31:04 lvw + * Added submenu for playlist view + * + * Revision 1.8.2.5 2004/05/25 21:58:54 lvw + * Handle submenus for views + * + * Revision 1.8.2.4 2004/03/14 12:30:56 lvw + * Menu now calls player + * + * Revision 1.8.2.3 2004/03/11 07:22:32 lvw + * Added setup menu + * + * Revision 1.8.2.2 2004/03/08 22:28:40 lvw + * Added documentation headers. + * + * Revision 1.8.2.1 2004/03/02 07:07:27 lvw + * Initial adaptations from MP3 plugin added (untested) + * * Revision 1.8 2004/02/08 10:48:44 LarsAC * Made major revisions in OSD behavior * @@ -125,6 +149,4 @@ class mgMainMenu : public cOsdMenu * Revision 1.5 2004/02/03 00:13:24 LarsAC * Improved OSD handling of collapse/back * - * - ************************************************************ */ diff --git a/vdr_network.h b/vdr_network.h new file mode 100644 index 0000000..182fa23 --- /dev/null +++ b/vdr_network.h @@ -0,0 +1,45 @@ +/* + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> + */ + +#ifndef ___NETWORK_H +#define ___NETWORK_H + +#include <thread.h> +#include <ringbuffer.h> +#include <config.h> + +class cRingBufferLinear; + +// ---------------------------------------------------------------- + +class mgNet : public cRingBufferLinear, cThread +{ +private: + int m_fd; + bool m_connected, m_netup; + int m_deferedErrno; + int m_rwTimeout, m_conTimeout; + unsigned char m_lineBuff[4096]; + int m_count; + // + void close(void); + int ringRead(unsigned char *dest, int len); + void copyFromBuff(unsigned char *dest, int n); +protected: + virtual void action(void); +public: + mgNet(int size, int ConTimeoutMs, int RwTimeoutMs); + ~mgNet(); + bool connect(const char *hostname, const int port); + void disconnect(void); + bool connected(void) { return m_connected; } + int gets(char *dest, int len); + int puts(char *dest); + int read(unsigned char *dest, int len); + int write(unsigned char *dest, int len); +}; + +#endif //___NETWORK_H diff --git a/vdr_player.c b/vdr_player.c new file mode 100644 index 0000000..090bbc6 --- /dev/null +++ b/vdr_player.c @@ -0,0 +1,1073 @@ +/*! + * \file vdr_player.c + * \brief A generic PCM player for a VDR media plugin (muggle) + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:19 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_player.c,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> + */ + +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <unistd.h> +#include <math.h> + +#include <string> +#include <iostream> + +#include <mad.h> +#include <id3tag.h> + +#include <player.h> +#include <device.h> +#include <thread.h> +#include <ringbuffer.h> +#include <tools.h> +#include <recording.h> + +#include "vdr_player.h" +#include "vdr_decoder.h" +#include "vdr_config.h" +#include "vdr_setup.h" + +#include "mg_tools.h" +#include "mg_playlist.h" +#include "mg_content_interface.h" + +using namespace std; + +// ---------------------------------------------------------------- + +// TODO: check for use of constants +#define OUT_BITS 16 // output 16 bit samples to DVB driver +#define OUT_FACT (OUT_BITS/8*2) // output factor is 16 bit & 2 channels -> 4 bytes + +// cResample +#define MAX_NSAMPLES (1152*7) // max. buffer for resampled frame + +// cNormalize +#define MAX_GAIN 3.0 // max. allowed gain +#define LIM_ACC 12 // bit, accuracy for lookup table +#define F_LIM_MAX (mad_fixed_t)((1<<(MAD_F_FRACBITS+2))-1) // max. value covered by lookup table +#define LIM_SHIFT (MAD_F_FRACBITS-LIM_ACC) // shift value for table lookup +#define F_LIM_JMP (mad_fixed_t)(1<<LIM_SHIFT) // lookup table jump between values + +// cLevel +#define POW_WIN 100 // window width for smoothing power values +#define EPSILON 0.00000000001 // anything less than EPSILON is considered zero + +// cMP3Player +#define MAX_FRAMESIZE 2048 // max. frame size allowed for DVB driver +#define HDR_SIZE 9 +#define LPCM_SIZE 7 +#define LEN_CORR 3 +#define SPEEDCHECKSTART ((MP3BUFSIZE*1000/32000/2/2)+1000) // play time to fill buffers before speed check starts (ms) +#define SPEEDCHECKTIME 3000 // play time for speed check (ms) + +/* +struct LPCMHeader { int id:8; // id + int frame_count:8; // number of frames + int access_ptr:16; // first acces unit pointer, i.e. start of audio frame + bool emphasis:1; // audio emphasis on-off + bool mute:1; // audio mute on-off + bool reserved:1; // reserved + int frame_number:5; // audio frame number + int quant_wlen:2; // quantization word length + int sample_freq:2; // audio sampling frequency (48khz=0, 96khz=1, 44,1khz=2, 32khz=3) + bool reserved2:1; // reserved + int chan_count:3; // number of audio channels - 1 (e.g. stereo = 1) + int dyn_range_ctrl:8; // dynamic range control (0x80 if off) + }; +*/ + +struct LPCMFrame +{ + unsigned char PES[HDR_SIZE]; + unsigned char LPCM[LPCM_SIZE]; + unsigned char Data[MAX_FRAMESIZE-HDR_SIZE-LPCM_SIZE]; +}; + +#include "vdr_sound.c" + + +// --- mgPCMPlayer ---------------------------------------------------------- + +/*! + * \brief a generic PCM player class + * + * this class implements a state machine that obtains decoded data from a generic data + * and moves it to the DVB device. It inherits from cPlayer in order to be hooked into + * VDR as a player and inherits from cThread in order to implement a separate thread + * for the decoding process. + */ +class mgPCMPlayer : public cPlayer, cThread +{ +private: + + //! \brief indicates, whether the player is currently active + bool m_active; + + //! \brief indicates, whether the player has been started + bool m_started; + + //! \brief a buffer for decoded sound + cRingBufferFrame *m_ringbuffer; + + //! \brief a mutex for the playmode + cMutex m_playmode_mutex; + + //! \brief a condition to signal playmode changes + cCondVar m_playmode_cond; + + //! \brief the current playlist + mgPlaylist *m_playlist; + + //! \brief the currently played or to be played item + mgContentItem *m_current; + + //! \brief the currently playing item + mgContentItem *m_playing; + + //! \brief the decoder responsible for the currently playing item + mgDecoder *m_decoder; + + cFrame *m_rframe, *m_pframe; + + enum ePlayMode + { + pmPlay, + pmStopped, + pmPaused, + pmStartup + }; + ePlayMode m_playmode; + + enum eState + { + msStart, msStop, + msDecode, msNormalize, + msResample, msOutput, + msError, msEof, msWait + }; + eState m_state; + + bool levelgood; + unsigned int dvbSampleRate; + + // + int m_index; + + void Empty(void); + bool NextFile(void); + bool PrevFile(void); + void StopPlay(void); + + void SetPlayMode(ePlayMode mode); + void WaitPlayMode(ePlayMode mode, bool inv); + + string getSourceFile(); +protected: + virtual void Activate(bool On); + virtual void Action(void); + +public: + mgPCMPlayer(mgPlaylist *plist); + virtual ~mgPCMPlayer(); + + bool Active(void) { return m_active; } + void Pause(void); + void Play(void); + void Forward(void); + void Backward(void); + void Goto(int Index, bool Still=false); + void SkipSeconds(int secs); + void ToggleShuffle(void); + void ToggleLoop(void); + virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame=false); + // bool GetPlayInfo(cMP3PlayInfo *rm); // LVW + void NewPlaylist(mgPlaylist *plist); + }; + +mgPCMPlayer::mgPCMPlayer(mgPlaylist *plist) + : cPlayer( the_setup.BackgrMode? pmAudioOnly: pmAudioOnlyBlack ) +{ + m_playlist = plist; + + m_active = true; + m_started = false; + + m_ringbuffer = new cRingBufferFrame( MP3BUFSIZE ); + + m_rframe = 0; + m_pframe = 0; + m_decoder = 0; + + m_playmode = pmStartup; + m_state = msStop; + + m_index = 0; + m_playing = 0; +} + +mgPCMPlayer::~mgPCMPlayer() +{ + Detach(); + + delete m_ringbuffer; +} + +void mgPCMPlayer::Activate(bool on) +{ + MGLOG( "mgPCMPlayer::Activate" ); + if( on ) + { + if( m_playlist && !m_started ) + { + m_playmode = pmStartup; + Start(); + + m_started = true; + m_current = 0; + + m_playmode_mutex.Lock(); + WaitPlayMode( pmStartup, true ); // wait for the decoder to become ready + m_playmode_mutex.Unlock(); + + Lock(); + m_playlist->initialize(); + if( NextFile() ) + { + Play(); + } + Unlock(); + } + } + else if( m_started && m_active ) + { + Lock(); + StopPlay(); + Unlock(); + + m_active = false; + SetPlayMode( pmStartup ); + + Cancel(2); + } +} + +void mgPCMPlayer::NewPlaylist( mgPlaylist *plist ) +{ + MGLOG( "mgPCMPlayer::NewPlaylist" ); + + Lock(); + StopPlay(); + Unlock(); + + // memory management of playlists should happen elsewhere (menu, content) + m_playlist = plist; + m_current = 0; + + if( NextFile() ) + { + Play(); + } +} + +void mgPCMPlayer::SetPlayMode(ePlayMode mode) +{ + m_playmode_mutex.Lock(); + if( mode != m_playmode ) + { + m_playmode = mode; + m_playmode_cond.Broadcast(); + } + m_playmode_mutex.Unlock(); +} + +void mgPCMPlayer::WaitPlayMode(ePlayMode mode, bool inv) +{ + // must be called with m_playmode_mutex LOCKED !!! + + while( m_active && ( (!inv && mode != m_playmode) || (inv && mode == m_playmode) ) ) + { + m_playmode_cond.Wait(m_playmode_mutex); + } +} + +void mgPCMPlayer::Action(void) +{ + MGLOG( "mgPCMPlayer::Action" ); + + struct mgDecode *ds=0; + struct mad_pcm *pcm=0; + cResample resample[2]; + unsigned int nsamples[2]; + const mad_fixed_t *data[2]; + cScale scale; + cLevel level; + cNormalize norm; + bool haslevel=false; + struct LPCMFrame lpcmFrame; + const unsigned char *p=0; + int pc = 0, only48khz = the_setup.Only48kHz; + cPoller poll; +#ifdef DEBUG + int beat=0; +#endif + + dsyslog( "muggle: player thread started (pid=%d)", getpid() ); + + memset( &lpcmFrame, 0, sizeof(lpcmFrame) ); + lpcmFrame.PES[2]=0x01; + lpcmFrame.PES[3]=0xbd; + lpcmFrame.PES[6]=0x87; + lpcmFrame.LPCM[0]=0xa0; // substream ID + lpcmFrame.LPCM[1]=0xff; + lpcmFrame.LPCM[5]=0x01; + lpcmFrame.LPCM[6]=0x80; + + dvbSampleRate = 48000; + m_state = msStop; + SetPlayMode( pmStopped ); + cout << "Playmode set." << endl << flush; + + while( m_active ) + { +#ifdef DEBUG + if(time(0)>=beat+30) + { + cout << "mgPCMPlayer::Action: heartbeat buffer=" << m_ringbuffer->Available() << endl << flush; + scale.Stats(); if(haslevel) norm.Stats(); + beat=time(0); + } +#endif + + Lock(); + + if( !m_rframe && m_playmode == pmPlay ) + { + switch( m_state ) + { + case msStart: + { + m_index = 0; + m_playing = m_current; + + string filename = getSourceFile(); + + if( ( m_decoder = mgDecoders::findDecoder( filename ) ) && m_decoder->start() ) + { + levelgood = true; + haslevel = false; + + scale.Init(); + level.Init(); + + m_state = msDecode; + + break; + } + + m_state = msEof; + } break; + case msDecode: + { + ds = m_decoder->decode(); + switch( ds->status ) + { + case dsPlay: + { + pcm = ds->pcm; + m_index = ds->index/1000; + m_state = msNormalize; + } break; + case dsSkip: + case dsSoftError: + { + // skipping, state unchanged, next decode + } break; + case dsEof: + { + m_state = msEof; + } break; + case dsOK: + case dsError: + { + m_state = msError; + } break; + } + } break; + case msNormalize: + { + if(!haslevel) + { + if( levelgood ) + { + level.GetPower( pcm ); + } + } + else + { + norm.AddGain( pcm ); + } + m_state = msResample; + } break; + case msResample: + { +#ifdef DEBUG + { + static unsigned int oldrate=0; + if(oldrate!=pcm->samplerate) + { + cout << "mgPCMPlayer::Action: new input sample rate " << pcm->samplerate << endl << flush; + oldrate = pcm->samplerate; + } + } +#endif + nsamples[0] = nsamples[1] = pcm->length; + data[0] = pcm->samples[0]; + data[1] = pcm->channels > 1 ? pcm->samples[1]: 0; + + lpcmFrame.LPCM[5]&=0xcf; + dvbSampleRate=48000; + if(!only48khz) + { + switch(pcm->samplerate) + { // If one of the supported frequencies, do it without resampling. + case 96000: + { // Select a "even" upsampling frequency if possible, too. + lpcmFrame.LPCM[5] |= 1 << 4; + dvbSampleRate = 96000; + } break; + + //case 48000: // this is already the default ... + // lpcmFrame.LPCM[5]|=0<<4; + // dvbSampleRate=48000; + // break; + case 11025: + case 22050: + case 44100: + { + lpcmFrame.LPCM[5]|=2<<4; + dvbSampleRate = 44100; + } break; + case 8000: + case 16000: + case 32000: + { + lpcmFrame.LPCM[5]|=3<<4; + dvbSampleRate = 32000; + } break; + } + } + + if( dvbSampleRate != pcm->samplerate ) + { + if( resample[0].SetInputRate( pcm->samplerate, dvbSampleRate ) ) + { + nsamples[0] = resample[0].ResampleBlock( nsamples[0], data[0] ); + data[0] = resample[0].Resampled(); + } + if(data[1] && resample[1].SetInputRate( pcm->samplerate, dvbSampleRate ) ) + { + nsamples[1] = resample[1].ResampleBlock( nsamples[1], data[1] ); + data[1] = resample[1].Resampled(); + } + } + m_state=msOutput; + } break; + case msOutput: + { + if( nsamples[0] > 0 ) + { + unsigned int outlen = scale.ScaleBlock( lpcmFrame.Data, + sizeof(lpcmFrame.Data), + nsamples[0], data[0], + data[1], + the_setup.AudioMode? amDither: amRound ); + if( outlen ) + { + outlen += sizeof(lpcmFrame.LPCM)+LEN_CORR; + lpcmFrame.PES[4] = outlen >> 8; + lpcmFrame.PES[5] = outlen; + m_rframe = new cFrame( (unsigned char *)&lpcmFrame, + outlen + sizeof( lpcmFrame.PES ) - LEN_CORR ); + } + } + else + { + m_state=msDecode; + } + } break; + case msError: + case msEof: + { + if( NextFile() ) + { + m_state = msStart; + } + else + { + m_state = msWait; + } + } // fall through + case msStop: + { + m_playing = 0; + if( m_decoder ) + { // who deletes decoder? + m_decoder->stop(); + m_decoder = 0; + } + + levelgood = false; + + scale.Stats(); + if( haslevel ) + { + norm.Stats(); + } + if( m_state == msStop ) + { // might be unequal in case of fall through from eof/error + SetPlayMode( pmStopped ); + } + } break; + case msWait: + { + if( m_ringbuffer->Available() == 0 ) + { + m_active = false; + SetPlayMode(pmStopped); + } + } break; + } + } + + if( m_rframe && m_ringbuffer->Put( m_rframe ) ) + { + m_rframe = 0; + } + + if( !m_pframe && m_playmode == pmPlay ) + { + m_pframe = m_ringbuffer->Get(); + if( m_pframe ) + { + p = m_pframe->Data(); + pc = m_pframe->Count(); + } + } + + if( m_pframe ) + { + int w = PlayVideo( p, pc ); + if( w > 0 ) + { + p += w; + pc -= w; + + if( pc <= 0 ) + { + m_ringbuffer->Drop(m_pframe); + m_pframe=0; + } + } + else if( w < 0 && FATALERRNO ) + { + LOG_ERROR; + break; + } + } + + Unlock(); + + if( (m_rframe || m_state == msWait) && m_pframe ) + { + // Wait for output to become ready + DevicePoll( poll, 500 ); + } + else + { + if( m_playmode != pmPlay ) + { + m_playmode_mutex.Lock(); + + if( m_playmode != pmPlay ) + { + WaitPlayMode( m_playmode, true ); // Wait on playMode change + } + m_playmode_mutex.Unlock(); + } + } + } + + Lock(); + + if( m_rframe ) + { + delete m_rframe; + m_rframe=0; + } + + if( m_decoder ) + { // who deletes decoder? + m_decoder->stop(); + m_decoder = 0; + } + + m_playing = 0; + + SetPlayMode(pmStopped); + + Unlock(); + + m_active = false; + + dsyslog( "muggle: player thread ended (pid=%d)", getpid() ); +} + +void mgPCMPlayer::Empty(void) +{ + MGLOG( "mgPCMPlayer::Empty" ); + + Lock(); + + m_ringbuffer->Clear(); + DeviceClear(); + + delete m_rframe; + m_rframe = 0; + m_pframe = 0; + + Unlock(); +} + +void mgPCMPlayer::StopPlay() +{ // StopPlay() must be called in locked state!!! + MGLOG( "mgPCMPlayer::StopPlay" ); + if( m_playmode != pmStopped ) + { + Empty(); + m_state = msStop; + SetPlayMode( pmPlay ); + Unlock(); // let the decode thread process the stop signal + + m_playmode_mutex.Lock(); + WaitPlayMode( pmStopped, false ); + m_playmode_mutex.Unlock(); + + Lock(); + } +} + +string mgPCMPlayer::getSourceFile() +{ + string filename; + + if( !the_setup.GdCompatibility ) + { // use filename itself + filename = string( the_setup.ToplevelDir ) + m_playing->getSourceFile(); + } + else + { // find the unique name within any directory, but what is top? video? + char *cmd = NULL; + asprintf( &cmd, "find %s -follow -type f -name '%s'|sort ", + the_setup.ToplevelDir, m_playing->getSourceFile().c_str() ); + + FILE *p = popen(cmd, "r"); + if (p) + { + char *s; + if( (s = readline(p) ) != NULL ) + { + filename = string( s ); + } + } + pclose( p ); + delete cmd; + } + + cout << "mgPCMPlayer::getSourceFile: found filename " << filename << endl << flush; + return "/test.mp3"; +} + +bool mgPCMPlayer::NextFile() +{ + bool res = false; + + mgContentItem *newcurr = m_playlist->skipFwd(); + + if( newcurr && newcurr != &(mgContentItem::UNDEFINED) ) + { + m_current = newcurr; + res = true; + } + + return res; +} + +bool mgPCMPlayer::PrevFile(void) +{ + bool res = false; + + mgContentItem *newcurr = m_playlist->skipBack(); + + if( newcurr && newcurr != &(mgContentItem::UNDEFINED) ) + { + m_current = newcurr; + res = true; + } + + return res; +} + +void mgPCMPlayer::ToggleShuffle() +{ + m_playlist->toggleShuffle(); +} + +void mgPCMPlayer::ToggleLoop(void) +{ + m_playlist->toggleLoop(); +} + +void mgPCMPlayer::Pause(void) +{ + if( m_playmode == pmPaused ) + { + Play(); + } + else + { + if( m_playmode == pmPlay ) + { + // DeviceFreeze(); + SetPlayMode( pmPaused ); + } + } +} + +void mgPCMPlayer::Play(void) +{ + MGLOG( "mgPCMPlayer::Play" ); + + Lock(); + if( m_playmode != pmPlay && m_current ) + { + if( m_playmode == pmStopped ) + { + m_state = msStart; + } + // DevicePlay(); // TODO? Commented out in original code, too + SetPlayMode( pmPlay ); + } + Unlock(); +} + +void mgPCMPlayer::Forward(void) +{ + MGLOG( "mgPCMPlayer::Forward" ); + + Lock(); + if( NextFile() ) + { + StopPlay(); + Play(); + } + Unlock(); +} + +void mgPCMPlayer::Backward(void) +{ + Lock(); + if( PrevFile() ) + { + StopPlay(); + Play(); + } + Unlock(); +} + +void mgPCMPlayer::Goto( int index, bool still ) +{ + mgContentItem *next = m_playlist->getPosition( index-1 ); + + if( next != &(mgContentItem::UNDEFINED) ) //invalid + { + Lock(); + StopPlay(); + m_current = next; + Play(); + Unlock(); + } +} + +void mgPCMPlayer::SkipSeconds(int secs) +{ + if( m_playmode != pmStopped ) + { + Lock(); + if( m_playmode == pmPaused ) + { + SetPlayMode( pmPlay ); + } + if( m_decoder && m_decoder->skip( secs, m_ringbuffer->Available(), dvbSampleRate ) ) + { + levelgood=false; + } + Empty(); + Unlock(); + } +} + +bool mgPCMPlayer::GetIndex( int ¤t, int &total, bool snaptoiframe ) +{ + bool res = false; + current = SecondsToFrames(m_index); + total = -1; + + return res; +} + +// --- mgPlayerControl ------------------------------------------------------- + +mgPlayerControl::mgPlayerControl( mgPlaylist *plist ) + : cControl( m_player = new mgPCMPlayer(plist) ) +{ + MGLOG( "mgPlayerControl::mgPlayerControl" ); + + m_visible = false; + m_has_osd = false; +} + +mgPlayerControl::~mgPlayerControl() +{ + Hide(); + Stop(); +} + +bool mgPlayerControl::Active(void) +{ + MGLOG( "mgPlayerControl::Active" ); + + return m_player && m_player->Active(); +} + +void mgPlayerControl::Stop(void) +{ + if( m_player ) + { + delete m_player; + m_player = 0; + } +} + +void mgPlayerControl::Pause(void) +{ + if( m_player ) + { + m_player->Pause(); + } +} + +void mgPlayerControl::Play(void) +{ + if( m_player ) + { + m_player->Play(); + } +} + +void mgPlayerControl::Forward(void) +{ + if( m_player ) + { + m_player->Forward(); + } +} + +void mgPlayerControl::Backward(void) +{ + if( m_player ) + { + m_player->Backward(); + } +} + +void mgPlayerControl::SkipSeconds(int Seconds) +{ + if( m_player ) + { + m_player->SkipSeconds(Seconds); + } +} + +void mgPlayerControl::Goto(int Position, bool Still) +{ + if( m_player ) + { + m_player->Goto(Position, Still); + } +} + +void mgPlayerControl::ToggleShuffle(void) +{ + if( m_player ) + { + m_player->ToggleShuffle(); + } +} + +void mgPlayerControl::ToggleLoop(void) +{ + if( m_player ) + { + m_player->ToggleLoop(); + } +} + +void mgPlayerControl::NewPlaylist(mgPlaylist *plist) +{ + if( m_player ) + { + m_player->NewPlaylist(plist); + } +} + +void mgPlayerControl::ShowProgress() +{ + if( m_visible ) + { + if( !m_has_osd ) + { + // open the osd if its not already there... + Interface->Open(); + m_has_osd = true; + } + + // now an osd is open, go on + + int w = Interface->Width(); + int h = Interface->Height(); + + Interface->WriteText( w/2, h/2, "Muggle is active!" ); + + // Add: song info (name, artist, pos in playlist, time, ...) + // Add: progress bar + + Interface->Flush(); + } + else + { + Hide(); + } +} + +void mgPlayerControl::Hide() +{ + if( m_has_osd ) + { + Interface->Close(); + m_has_osd = false; + } +} + +eOSState mgPlayerControl::ProcessKey(eKeys key) +{ + if( !Active() ) + { + return osEnd; + } + + ShowProgress(); + + eOSState state = cControl::ProcessKey(key); + + if( state == osUnknown ) + { + switch( key ) + { + case kUp: + { + Forward(); + } break; + case kDown: + { + Backward(); + } break; + case kPause: + case kYellow: + { + Pause(); + } break; + case kStop: + case kBlue: + { + Hide(); + Stop(); + + return osEnd; + } break; + case kOk: + { + m_visible = !m_visible; + ShowProgress(); + + return osContinue; + } break; + case kBack: + { + Hide(); + Stop(); + + return osEnd; + } break; + default: + { + return osUnknown; + } + } + } + return osContinue; +} + +/************************************************************ + * + * $Log: vdr_player.c,v $ + * Revision 1.2 2004/05/28 15:29:19 lvw + * Merged player branch back on HEAD branch. + * + * Revision 1.1.2.19 2004/05/26 14:30:27 lvw + * Removed bug in finding correct mp3 file in GD mode + * + * Revision 1.1.2.18 2004/05/25 06:48:24 lvw + * Documentation and code polishing. + * + * Revision 1.1.2.17 2004/05/25 00:10:45 lvw + * Code cleanup and added use of real database source files + * + * Revision 1.1.2.16 2004/05/24 11:48:52 lvw + * Debugging info added to find deadlock + * + * Revision 1.1.2.15 2004/05/12 22:38:37 lvw + * Some cleanup + * + * Revision 1.1.2.14 2004/05/11 06:35:16 lvw + * Added debugging while hunting stop bug. + * + * Revision 1.1.2.13 2004/05/07 06:46:41 lvw + * Removed a bug in playlist deallocation. Added infrastructure to display information while playing. + * + * + ***********************************************************/ diff --git a/vdr_player.h b/vdr_player.h new file mode 100644 index 0000000..6ce2f1b --- /dev/null +++ b/vdr_player.h @@ -0,0 +1,137 @@ +/*! + * \file vdr_player.h + * \brief A player/control combination to let VDR play music + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:19 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_player.h,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> + */ + + +#ifndef ___DVB_MP3_H +#define ___DVB_MP3_H + +#include <player.h> + +// ------------------------------------------------------------------- + +class mgPCMPlayer; +class mgPlaylist; + +// ------------------------------------------------------------------- + +/*! + * \brief exerts control over the player itself + * + * This control is launched from the main menu and manages a link + * to the player. Key events are caught and signaled to the player. + */ +class mgPlayerControl : public cControl +{ +private: + + //! \brief the reference to the player + mgPCMPlayer *m_player; + + //! \brief indicates, whether the osd should be visible + bool m_visible; + + //! \brief indicates, whether an osd is currently displayed + bool m_has_osd; + +public: + + /*! \brief construct a control with a playlist + * + * \param plist - the playlist to be played + */ + mgPlayerControl(mgPlaylist *plist); + + /*! \brief destructor + */ + virtual ~mgPlayerControl(); + + //! \brief indicate, whether the corresponding player is active + bool Active(); + + //! \brief stop the corresponding player + void Stop(); + + //! \brief toggle the pause mode of the corresponding player + void Pause(); + + //! \brief start playing + void Play(); + + //! \brief skip to the next song + void Forward(); + + //! \brief skip to the previous song + void Backward(); + + /*! \brief skip a specified number of seconds + * + * \param seconds - the number of seconds to skip + */ + void SkipSeconds(int seconds); + + /*! \brief goto a certain position in the playlist + * + * \param index - the position in the playlist to skip to + * \param still - currently unused + */ + void Goto(int index, bool still = false); + + //! \brief toggle the shuffle mode of the corresponding player + void ToggleShuffle(); + + //! \brief toggle the loop mode of the corresponding player + void ToggleLoop(); + + /*! \brief signal a new playlist + * + * The caller has to take care of deallocating the previous list + * + * \param plist - the new playlist to be played + */ + void NewPlaylist( mgPlaylist *plist ); + + //! \brief a progress display + void ShowProgress(); + + //! \brief hide the osd, if present + void Hide(); + + //! \brief process key events + eOSState ProcessKey(eKeys key); +}; + +#endif //___DVB_MP3_H + +/************************************************************ + * + * $Log: vdr_player.h,v $ + * Revision 1.2 2004/05/28 15:29:19 lvw + * Merged player branch back on HEAD branch. + * + * Revision 1.1.2.9 2004/05/25 06:48:24 lvw + * Documentation and code polishing. + * + * Revision 1.1.2.8 2004/05/25 00:10:45 lvw + * Code cleanup and added use of real database source files + * + * Revision 1.1.2.7 2004/05/11 06:35:16 lvw + * Added debugging while hunting stop bug. + * + * Revision 1.1.2.6 2004/05/07 06:46:41 lvw + * Removed a bug in playlist deallocation. Added infrastructure to display information while playing. + * + * + ***********************************************************/ diff --git a/vdr_setup.c b/vdr_setup.c new file mode 100644 index 0000000..04ba06b --- /dev/null +++ b/vdr_setup.c @@ -0,0 +1,70 @@ +/*! + * \file vdr_setup.c + * \brief A setup class for a VDR media plugin (muggle) + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:19 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_setup.c,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * + * Partially adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> + */ + +#include <string.h> + +#include "vdr_setup.h" +#include "i18n.h" + +mgSetup the_setup; + +// --- mgMenuSetup ----------------------------------------------------------- + +mgMenuSetup::mgMenuSetup() +{ + static const char allowed[] = { "abcdefghijklmnopqrstuvwxyz0123456789-_" }; + + m_data = the_setup; + + SetSection( tr("Muggle") ); + + Add(new cMenuEditBoolItem(tr("Setup.Muggle$Initial loop mode"), &m_data.InitLoopMode)); + Add(new cMenuEditBoolItem(tr("Setup.Muggle$Initial shuffle mode"), &m_data.InitShuffleMode)); + Add(new cMenuEditBoolItem(tr("Setup.Muggle$Audio mode"), &m_data.AudioMode, tr("Round"), tr("Dither"))); + Add(new cMenuEditBoolItem(tr("Setup.Muggle$Use 48kHz mode only"), &m_data.Only48kHz)); + Add(new cMenuEditIntItem( tr("Setup.Muggle$Display mode"), &m_data.DisplayMode, 1, 3)); + Add(new cMenuEditBoolItem(tr("Setup.Muggle$Background mode"), &m_data.BackgrMode, tr("Black"), tr("Live"))); + Add(new cMenuEditIntItem( tr("Setup.Muggle$Normalizer level"), &m_data.TargetLevel, 0, MAX_TARGET_LEVEL)); + Add(new cMenuEditIntItem( tr("Setup.Muggle$Limiter level"), &m_data.LimiterLevel, MIN_LIMITER_LEVEL, 100)); +} + +void mgMenuSetup::Store(void) +{ + the_setup = m_data; + + SetupStore("InitLoopMode", the_setup.InitLoopMode ); + SetupStore("InitShuffleMode", the_setup.InitShuffleMode); + SetupStore("AudioMode", the_setup.AudioMode ); + SetupStore("DisplayMode", the_setup.DisplayMode ); + SetupStore("BackgrMode", the_setup.BackgrMode ); + SetupStore("TargetLevel", the_setup.TargetLevel ); + SetupStore("LimiterLevel", the_setup.LimiterLevel ); + SetupStore("Only48kHz", the_setup.Only48kHz ); +} + +// --- mgSetup --------------------------------------------------------------- + +mgSetup::mgSetup() +{ + InitLoopMode = 0; + InitShuffleMode = 0; + AudioMode = 1; + DisplayMode = 3; + BackgrMode = 1; + TargetLevel = DEFAULT_TARGET_LEVEL; + LimiterLevel = DEFAULT_LIMITER_LEVEL; + Only48kHz = 0; +} diff --git a/vdr_setup.h b/vdr_setup.h new file mode 100644 index 0000000..a77cf75 --- /dev/null +++ b/vdr_setup.h @@ -0,0 +1,76 @@ +/*! + * \file vdr_setup.h + * \brief A setup class for a VDR media plugin (muggle) + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:19 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_setup.h,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> + */ + +#ifndef ___SETUP_MG_H +#define ___SETUP_MG_H + +// #include <osd.h> +#include <menuitems.h> + +#define MAX_STRING_LEN 128 + +#define DEFAULT_TARGET_LEVEL 25 +#define MAX_TARGET_LEVEL 50 +#define DEFAULT_LIMITER_LEVEL 70 +#define MIN_LIMITER_LEVEL 25 + +/*! + * \brief storage for setup data + */ +class mgSetup +{ +public: + int InitLoopMode; + int InitShuffleMode; + int AudioMode; + int DisplayMode; + int BackgrMode; + int MenuMode; + int TargetLevel; + int LimiterLevel; + int Only48kHz; + + char *DbHost; + char *DbName; + char *DbUser; + char *DbPass; + int DbPort; + bool GdCompatibility; + char *ToplevelDir; + + char PathPrefix[MAX_STRING_LEN]; + + public: + mgSetup(void); +}; + +extern mgSetup the_setup; + +/*! + * \brief allow user to modify setup on OSD + */ +class mgMenuSetup : public cMenuSetupPage +{ +private: + mgSetup m_data; +protected: + virtual void Store(); +public: + mgMenuSetup(); +}; + + +#endif //___SETUP_MP3_H diff --git a/vdr_sound.c b/vdr_sound.c new file mode 100644 index 0000000..b06061c --- /dev/null +++ b/vdr_sound.c @@ -0,0 +1,609 @@ +/*! + * \file vdr_setup.c + * \brief Sound manipulation classes for a VDR media plugin (muggle) + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:19 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_sound.c,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> + */ + +// --- cResample ------------------------------------------------------------ + +// The resample code has been adapted from the madplay project +// (resample.c) found in the libmad distribution + +class cResample +{ +private: + mad_fixed_t ratio; + mad_fixed_t step; + mad_fixed_t last; + mad_fixed_t resampled[MAX_NSAMPLES]; +public: + bool SetInputRate(unsigned int oldrate, unsigned int newrate); + unsigned int ResampleBlock(unsigned int nsamples, const mad_fixed_t *old); + const mad_fixed_t *Resampled(void) { return resampled; } + }; + +bool cResample::SetInputRate(unsigned int oldrate, unsigned int newrate) +{ + if(oldrate<8000 || oldrate>newrate*6) { // out of range + esyslog("WARNING: samplerate %d out of range 8000-%d\n",oldrate,newrate*6); + return 0; + } + ratio=mad_f_tofixed((double)oldrate/(double)newrate); + step=0; last=0; +#ifdef DEBUG + static mad_fixed_t oldratio=0; + if(oldratio!=ratio) { + printf("mad: new resample ratio %f (from %d kHz to %d kHz)\n",mad_f_todouble(ratio),oldrate,newrate); + oldratio=ratio; + } +#endif + return ratio!=MAD_F_ONE; +} + +unsigned int cResample::ResampleBlock(unsigned int nsamples, const mad_fixed_t *old) +{ + // This resampling algorithm is based on a linear interpolation, which is + // not at all the best sounding but is relatively fast and efficient. + // + // A better algorithm would be one that implements a bandlimited + // interpolation. + + mad_fixed_t *nsam=resampled; + const mad_fixed_t *end=old+nsamples; + const mad_fixed_t *begin=nsam; + + if(step < 0) { + step = mad_f_fracpart(-step); + + while (step < MAD_F_ONE) { + *nsam++ = step ? last+mad_f_mul(*old-last,step) : last; + step += ratio; + if(((step + 0x00000080L) & 0x0fffff00L) == 0) + step = (step + 0x00000080L) & ~0x0fffffffL; + } + step -= MAD_F_ONE; + } + + while (end - old > 1 + mad_f_intpart(step)) { + old += mad_f_intpart(step); + step = mad_f_fracpart(step); + *nsam++ = step ? *old + mad_f_mul(old[1] - old[0], step) : *old; + step += ratio; + if (((step + 0x00000080L) & 0x0fffff00L) == 0) + step = (step + 0x00000080L) & ~0x0fffffffL; + } + + if (end - old == 1 + mad_f_intpart(step)) { + last = end[-1]; + step = -step; + } + else step -= mad_f_fromint(end - old); + + return nsam-begin; +} + +// --- cLevel ---------------------------------------------------------------- + +// The normalize algorithm and parts of the code has been adapted from the +// Normalize 0.7 project. (C) 1999-2002, Chris Vaill <cvaill@cs.columbia.edu> + +// A little background on how normalize computes the volume +// of a wav file, in case you want to know just how your +// files are being munged: +// +// The volumes calculated are RMS amplitudes, which corre +// spond (roughly) to perceived volume. Taking the RMS ampli +// tude of an entire file would not give us quite the measure +// we want, though, because a quiet song punctuated by short +// loud parts would average out to a quiet song, and the +// adjustment we would compute would make the loud parts +// excessively loud. +// +// What we want is to consider the maximum volume of the +// file, and normalize according to that. We break up the +// signal into 100 chunks per second, and get the signal +// power of each chunk, in order to get an estimation of +// "instantaneous power" over time. This "instantaneous +// power" signal varies too much to get a good measure of the +// original signal's maximum sustained power, so we run a +// smoothing algorithm over the power signal (specifically, a +// mean filter with a window width of 100 elements). The max +// imum point of the smoothed power signal turns out to be a +// good measure of the maximum sustained power of the file. +// We can then take the square root of the power to get maxi +// mum sustained RMS amplitude. + +class cLevel { +private: + double maxpow; + mad_fixed_t peak; + struct Power { + // smooth + int npow, wpow; + double powsum, pows[POW_WIN]; + // sum + unsigned int nsum; + double sum; + } power[2]; + // + inline void AddPower(struct Power *p, double pow); +public: + void Init(void); + void GetPower(struct mad_pcm *pcm); + double GetLevel(void); + double GetPeak(void); + }; + +void cLevel::Init(void) +{ + for(int l=0 ; l<2 ; l++) { + struct Power *p=&power[l]; + p->sum=p->powsum=0.0; p->wpow=p->npow=p->nsum=0; + for(int i=POW_WIN-1 ; i>=0 ; i--) p->pows[i]=0.0; + } + maxpow=0.0; peak=0; +} + +void cLevel::GetPower(struct mad_pcm *pcm) +{ + for(int i=0 ; i<pcm->channels ; i++) { + struct Power *p=&power[i]; + mad_fixed_t *data=pcm->samples[i]; + for(int n=pcm->length ; n>0 ; n--) { + if(*data < -peak) peak = -*data; + if(*data > peak) peak = *data; + double s=mad_f_todouble(*data++); + p->sum+=(s*s); + if(++(p->nsum)>=pcm->samplerate/100) { + AddPower(p,p->sum/(double)p->nsum); + p->sum=0.0; p->nsum=0; + } + } + } +} + +void cLevel::AddPower(struct Power *p, double pow) +{ + p->powsum+=pow; + if(p->npow>=POW_WIN) { + if(p->powsum>maxpow) maxpow=p->powsum; + p->powsum-=p->pows[p->wpow]; + } + else p->npow++; + p->pows[p->wpow]=pow; + p->wpow=(p->wpow+1) % POW_WIN; +} + +double cLevel::GetLevel(void) +{ + if(maxpow<EPSILON) { + // Either this whole file has zero power, or was too short to ever + // fill the smoothing buffer. In the latter case, we need to just + // get maxpow from whatever data we did collect. + + if(power[0].powsum>maxpow) maxpow=power[0].powsum; + if(power[1].powsum>maxpow) maxpow=power[1].powsum; + } + double level=sqrt(maxpow/(double)POW_WIN); // adjust for the smoothing window size and root + printf("norm: new volumen level=%f peak=%f\n",level,mad_f_todouble(peak)); + return level; +} + +double cLevel::GetPeak(void) +{ + return mad_f_todouble(peak); +} + +// --- cNormalize ------------------------------------------------------------ + +class cNormalize { +private: + mad_fixed_t gain; + double d_limlvl, one_limlvl; + mad_fixed_t limlvl; + bool dogain, dolimit; +#ifdef DEBUG + // stats + unsigned long limited, clipped, total; + mad_fixed_t peak; +#endif + // limiter +#ifdef USE_FAST_LIMITER + mad_fixed_t *table, tablestart; + int tablesize; + inline mad_fixed_t FastLimiter(mad_fixed_t x); +#endif + inline mad_fixed_t Limiter(mad_fixed_t x); +public: + cNormalize(void); + ~cNormalize(); + void Init(double Level, double Peak); + void Stats(void); + void AddGain(struct mad_pcm *pcm); + }; + +cNormalize::cNormalize(void) +{ + d_limlvl = (double)the_setup.LimiterLevel / 100.0; + one_limlvl = 1 - d_limlvl; + limlvl = mad_f_tofixed(d_limlvl); + printf( "norm: lim_lev=%f lim_acc=%d\n", d_limlvl, LIM_ACC ); + +#ifdef USE_FAST_LIMITER + mad_fixed_t start=limlvl & ~(F_LIM_JMP-1); + tablestart=start; + tablesize=(unsigned int)(F_LIM_MAX-start)/F_LIM_JMP + 2; + table=new mad_fixed_t[tablesize]; + if(table) { + printf("norm: table size=%d start=%08x jump=%08x\n",tablesize,start,F_LIM_JMP); + for(int i=0 ; i<tablesize ; i++) { + table[i]=Limiter(start); + start+=F_LIM_JMP; + } + tablesize--; // avoid a -1 in FastLimiter() + + // do a quick accuracy check, just to be sure that FastLimiter() is working + // as expected :-) +#ifdef ACC_DUMP + FILE *out=fopen("/tmp/limiter","w"); +#endif + mad_fixed_t maxdiff=0; + for(mad_fixed_t x=F_LIM_MAX ; x>=limlvl ; x-=mad_f_tofixed(1e-4)) { + mad_fixed_t diff=mad_f_abs(Limiter(x)-FastLimiter(x)); + if(diff>maxdiff) maxdiff=diff; +#ifdef ACC_DUMP + fprintf(out,"%0.10f\t%0.10f\t%0.10f\t%0.10f\t%0.10f\n", + mad_f_todouble(x),mad_f_todouble(Limiter(x)),mad_f_todouble(FastLimiter(x)),mad_f_todouble(diff),mad_f_todouble(maxdiff)); + if(ferror(out)) break; +#endif + } +#ifdef ACC_DUMP + fclose(out); +#endif + printf("norm: accuracy %.12f\n",mad_f_todouble(maxdiff)); + if(mad_f_todouble(maxdiff)>1e-6) + { + esyslog("ERROR: accuracy check failed, normalizer disabled"); + delete table; table=0; + } + } + else esyslog("ERROR: no memory for lookup table, normalizer disabled"); +#endif // USE_FAST_LIMITER +} + +cNormalize::~cNormalize() +{ +#ifdef USE_FAST_LIMITER + delete table; +#endif +} + +void cNormalize::Init(double Level, double Peak) +{ + double Target=(double)the_setup.TargetLevel/100.0; + double dgain=Target/Level; + if(dgain>MAX_GAIN) dgain=MAX_GAIN; + gain=mad_f_tofixed(dgain); + // Check if we actually need to apply a gain + dogain=(Target>0.0 && fabs(1-dgain)>MIN_GAIN); +#ifdef USE_FAST_LIMITER + if(!table) dogain=false; +#endif + // Check if we actually need to do limiting: + // we have to if limiter is enabled, if gain>1 and if the peaks will clip. + dolimit=(d_limlvl<1.0 && dgain>1.0 && Peak*dgain>1.0); +#ifdef DEBUG + printf("norm: gain=%f dogain=%d dolimit=%d (target=%f level=%f peak=%f)\n",dgain,dogain,dolimit,Target,Level,Peak); + limited=clipped=total=0; peak=0; +#endif +} + +void cNormalize::Stats(void) +{ +#ifdef DEBUG + if(total) + printf("norm: stats tot=%ld lim=%ld/%.3f%% clip=%ld/%.3f%% peak=%.3f\n", + total,limited,(double)limited/total*100.0,clipped,(double)clipped/total*100.0,mad_f_todouble(peak)); +#endif +} + +mad_fixed_t cNormalize::Limiter(mad_fixed_t x) +{ +// Limiter function: +// +// / x (for x <= lev) +// x' = | +// \ tanh((x - lev) / (1-lev)) * (1-lev) + lev (for x > lev) +// +// call only with x>=0. For negative samples, preserve sign outside this function +// +// With limiter level = 0, this is equivalent to a tanh() function; +// with limiter level = 1, this is equivalent to clipping. + + if(x>limlvl) { +#ifdef DEBUG + if(x>MAD_F_ONE) clipped++; + limited++; +#endif + x=mad_f_tofixed(tanh((mad_f_todouble(x)-d_limlvl) / one_limlvl) * one_limlvl + d_limlvl); + } + return x; +} + +#ifdef USE_FAST_LIMITER +mad_fixed_t cNormalize::FastLimiter(mad_fixed_t x) +{ +// The fast algorithm is based on a linear interpolation between the +// the values in the lookup table. Relays heavly on libmads fixed point format. + + if(x>limlvl) { + int i=(unsigned int)(x-tablestart)/F_LIM_JMP; +#ifdef DEBUG + if(x>MAD_F_ONE) clipped++; + limited++; + if(i>=tablesize) printf("norm: overflow x=%f x-ts=%f i=%d tsize=%d\n", + mad_f_todouble(x),mad_f_todouble(x-tablestart),i,tablesize); +#endif + mad_fixed_t r=x & (F_LIM_JMP-1); + x=MAD_F_ONE; + if(i<tablesize) { + mad_fixed_t *ptr=&table[i]; + x=*ptr; + mad_fixed_t d=*(ptr+1)-x; + //x+=mad_f_mul(d,r)<<LIM_ACC; // this is not accurate as mad_f_mul() does >>MAD_F_FRACBITS + // which is senseless in the case of following <<LIM_ACC. + x+=((long long)d*(long long)r)>>LIM_SHIFT; // better, don't know if works on all machines + } + } + return x; +} +#endif + +#ifdef USE_FAST_LIMITER +#define LIMITER_FUNC FastLimiter +#else +#define LIMITER_FUNC Limiter +#endif + +void cNormalize::AddGain(struct mad_pcm *pcm) +{ + if(dogain) { + for(int i=0 ; i<pcm->channels ; i++) { + mad_fixed_t *data=pcm->samples[i]; +#ifdef DEBUG + total+=pcm->length; +#endif + if(dolimit) { + for(int n=pcm->length ; n>0 ; n--) { + mad_fixed_t s=mad_f_mul(*data,gain); + if(s<0) { + s=-s; +#ifdef DEBUG + if(s>peak) peak=s; +#endif + s=LIMITER_FUNC(s); + s=-s; + } + else { +#ifdef DEBUG + if(s>peak) peak=s; +#endif + s=LIMITER_FUNC(s); + } + *data++=s; + } + } + else { + for(int n=pcm->length ; n>0 ; n--) { + mad_fixed_t s=mad_f_mul(*data,gain); +#ifdef DEBUG + if(s>peak) peak=s; + else if(-s>peak) peak=-s; +#endif + if(s>MAD_F_ONE) s=MAD_F_ONE; // do clipping + if(s<-MAD_F_ONE) s=-MAD_F_ONE; + *data++=s; + } + } + } + } +} + +// --- cScale ---------------------------------------------------------------- + +// The dither code has been adapted from the madplay project +// (audio.c) found in the libmad distribution + +enum eAudioMode { amRound, amDither }; + +class cScale { +private: + enum { MIN=-MAD_F_ONE, MAX=MAD_F_ONE - 1 }; +#ifdef DEBUG + // audio stats + unsigned long clipped_samples; + mad_fixed_t peak_clipping; + mad_fixed_t peak_sample; +#endif + // dither + struct dither { + mad_fixed_t error[3]; + mad_fixed_t random; + } leftD, rightD; + // + inline mad_fixed_t Clip(mad_fixed_t sample, bool stats=true); + inline signed long LinearRound(mad_fixed_t sample); + inline unsigned long Prng(unsigned long state); + inline signed long LinearDither(mad_fixed_t sample, struct dither *dither); +public: + void Init(void); + void Stats(void); + unsigned int ScaleBlock(unsigned char *data, unsigned int size, unsigned int &nsamples, const mad_fixed_t * &left, const mad_fixed_t * &right, eAudioMode mode); + }; + +void cScale::Init(void) +{ +#ifdef DEBUG + clipped_samples=0; peak_clipping=peak_sample=0; +#endif + memset(&leftD,0,sizeof(leftD)); + memset(&rightD,0,sizeof(rightD)); +} + +void cScale::Stats(void) +{ +#ifdef DEBUG + printf("mp3: scale stats clipped=%ld peak_clip=%f peak=%f\n", + clipped_samples,mad_f_todouble(peak_clipping),mad_f_todouble(peak_sample)); +#endif +} + +// gather signal statistics while clipping +mad_fixed_t cScale::Clip(mad_fixed_t sample, bool stats) +{ +#ifndef DEBUG + if (sample > MAX) sample = MAX; + if (sample < MIN) sample = MIN; +#else + if(!stats) { + if (sample > MAX) sample = MAX; + if (sample < MIN) sample = MIN; + } + else { + if (sample >= peak_sample) { + if (sample > MAX) { + ++clipped_samples; + if (sample - MAX > peak_clipping) + peak_clipping = sample - MAX; + sample = MAX; + } + peak_sample = sample; + } + else if (sample < -peak_sample) { + if (sample < MIN) { + ++clipped_samples; + if (MIN - sample > peak_clipping) + peak_clipping = MIN - sample; + sample = MIN; + } + peak_sample = -sample; + } + } +#endif + return sample; +} + +// generic linear sample quantize routine +signed long cScale::LinearRound(mad_fixed_t sample) +{ + // round + sample += (1L << (MAD_F_FRACBITS - OUT_BITS)); + // clip + sample=Clip(sample); + // quantize and scale + return sample >> (MAD_F_FRACBITS + 1 - OUT_BITS); +} + +// 32-bit pseudo-random number generator +unsigned long cScale::Prng(unsigned long state) +{ + return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; +} + +// generic linear sample quantize and dither routine +signed long cScale::LinearDither(mad_fixed_t sample, struct dither *dither) +{ + unsigned int scalebits; + mad_fixed_t output, mask, random; + + // noise shape + sample += dither->error[0] - dither->error[1] + dither->error[2]; + dither->error[2] = dither->error[1]; + dither->error[1] = dither->error[0] / 2; + // bias + output = sample + (1L << (MAD_F_FRACBITS + 1 - OUT_BITS - 1)); + scalebits = MAD_F_FRACBITS + 1 - OUT_BITS; + mask = (1L << scalebits) - 1; + // dither + random = Prng(dither->random); + output += (random & mask) - (dither->random & mask); + dither->random = random; + // clip + output=Clip(output); + sample=Clip(sample,false); + // quantize + output &= ~mask; + // error feedback + dither->error[0] = sample - output; + // scale + return output >> scalebits; +} + +// write a block of signed 16-bit big-endian PCM samples +unsigned int cScale::ScaleBlock(unsigned char *data, unsigned int size, unsigned int &nsamples, const mad_fixed_t * &left, const mad_fixed_t * &right, eAudioMode mode) +{ + signed int sample; + unsigned int len, res; + + len=size/OUT_FACT; res=size; + if(len>nsamples) { len=nsamples; res=len*OUT_FACT; } + nsamples-=len; + + if(right) { // stereo + switch (mode) { + case amRound: + while (len--) { + sample = LinearRound(*left++); + *data++ = sample >> 8; + *data++ = sample >> 0; + sample = LinearRound(*right++); + *data++ = sample >> 8; + *data++ = sample >> 0; + } + break; + case amDither: + while (len--) { + sample = LinearDither(*left++,&leftD); + *data++ = sample >> 8; + *data++ = sample >> 0; + sample = LinearDither(*right++,&rightD); + *data++ = sample >> 8; + *data++ = sample >> 0; + } + break; + } + } + else { // mono, duplicate left channel + switch (mode) { + case amRound: + while (len--) { + sample = LinearRound(*left++); + *data++ = sample >> 8; + *data++ = sample >> 0; + *data++ = sample >> 8; + *data++ = sample >> 0; + } + break; + case amDither: + while (len--) { + sample = LinearDither(*left++,&leftD); + *data++ = sample >> 8; + *data++ = sample >> 0; + *data++ = sample >> 8; + *data++ = sample >> 0; + } + break; + } + } + return res; +} diff --git a/vdr_stream.c b/vdr_stream.c new file mode 100644 index 0000000..d82877e --- /dev/null +++ b/vdr_stream.c @@ -0,0 +1,332 @@ +/*! + * \file vdr_stream.c + * \brief Implementation of media stream classes + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:19 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_stream.c,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001-2003 Stefan Huelswitt <huels@iname.com> + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <sys/statfs.h> +#include <iostream> + +#include <interface.h> + +#include "mg_tools.h" + +// #include "setup-mp3.h" +#include "vdr_stream.h" +#include "vdr_network.h" +#include "vdr_config.h" +// #include "i18n.h" +// #include "version.h" + +#define tr(x) x + +#ifdef USE_MMAP +#include <sys/mman.h> +#endif + +#define DEFAULT_PORT 80 // default port for streaming (HTTP) + +using namespace std; + +// --- mgStream ----------------------------------------------------------------- + +mgStream::mgStream( string filename ) + : m_filename( filename ) +{ + m_fd = -1; + m_ismmap = false; + m_buffer = 0; +} + +mgStream::~mgStream() +{ + close(); +} + +bool mgStream::open(bool log) +{ + if( m_fd >= 0 ) + { + return seek(); + } + + // just check, whether file exists? + if( fileinfo( log ) ) + { + printf( "mgStream::open: fileinfo == true\n"); + + if( ( m_fd = ::open( m_filename.c_str(), O_RDONLY ) ) >=0 ) + { + printf( "mgStream::open: file opened\n" ); + + m_buffpos = m_readpos = 0; + m_fill = 0; + + printf( "mgStream::open: buffpos, readpos, fill set\n" ); + + /* +#ifdef USE_MMAP + if( m_filesize <= MAX_MMAP_SIZE ) + { + m_buffer = (unsigned char*)mmap( 0, m_filesize, PROT_READ, + MAP_SHARED, m_fd, 0 ); + if( m_buffer != MAP_FAILED ) + { + m_ismmap = true; + return true; + } + else + { + dsyslog("mmap() failed for %s: %s", m_filename.c_str(), strerror(errno) ); + } + } +#endif + */ + printf( "mgStream::open: allocating buffer: %d\n", MP3FILE_BUFSIZE ); + m_buffer = new unsigned char[MP3FILE_BUFSIZE]; + printf( "mgStream::open: buffer allocated\n" ); + + if( m_buffer ) + { + printf( "mgStream::open: buffer allocated, returning true\n" ); + + return true; + } + else + { + esyslog("ERROR: not enough memory for buffer: %s", m_filename.c_str() ); + } + } + else + { + if( log ) + { + esyslog("ERROR: failed to open file %s: %s", m_filename.c_str(), strerror(errno) ); + } + } + } + + close(); + printf( "mgStream::open: returning false\n" ); + return false; +} + +void mgStream::close(void) +{ +#ifdef USE_MMAP + if( m_ismmap ) + { + munmap( m_buffer, m_filesize ); + m_buffer = 0; + m_ismmap = false; + } + else + { +#endif + delete m_buffer; + m_buffer = 0; +#ifdef USE_MMAP + } +#endif + if( m_fd >= 0 ) + { + ::close( m_fd ); + m_fd = -1; + } +} + +bool mgStream::seek(unsigned long long pos) +{ + printf( "mgStream::seek\n" ); + if( m_fd >= 0 && pos >= 0 && pos <= m_filesize ) + { + printf( "mgStream::seek valid file and position detected\n" ); + + m_buffpos = 0; + m_fill = 0; + + if( m_ismmap ) + { + m_readpos = pos; + + printf( "mgStream::seek: returning true\n" ); + return true; + } + else + { + if( ( m_readpos = lseek64( m_fd, pos, SEEK_SET ) ) >=0 ) + { + if( m_readpos != pos ) + { + dsyslog( "seek mismatch in %s, wanted %lld, got %lld", m_filename.c_str(), pos, m_readpos ); + } + printf( "mgStream::seek: returning true\n" ); + return true; + } + else + { + esyslog( "ERROR: seeking failed in %s: %d,%s", m_filename.c_str(), errno, strerror(errno) ); + } + } + } + else + { + printf( "mp3: bad seek call fd=%d pos=%lld name=%s\n", m_fd, pos, m_filename.c_str() ); + } + + printf( "mgStream::seek: returning false\n" ); + return false; +} + +bool mgStream::stream(unsigned char * &data, + unsigned long &len, + const unsigned char *rest) +{ + if( m_fd >= 0 ) + { + if( m_readpos < m_filesize ) + { + if( m_ismmap ) + { + if( rest && m_fill ) + { + m_readpos = (rest - m_buffer); // take care of remaining data + } + m_fill = m_filesize - m_readpos; + data = m_buffer + m_readpos; + len = m_fill; + m_buffpos = m_readpos; + m_readpos += m_fill; + + return true; + } + else + { + if( rest && m_fill ) + { // copy remaining data to start of buffer + m_fill -= ( rest - m_buffer); // remaing bytes + memmove( m_buffer, rest, m_fill ); + } + else + { + m_fill = 0; + } + + int r; + do + { + r = read( m_fd, m_buffer + m_fill, + MP3FILE_BUFSIZE - m_fill ); + } while( r == -1 && errno == EINTR ); + + if( r >= 0 ) + { + m_buffpos = m_readpos - m_fill; + m_readpos += r; + m_fill += r; + data = m_buffer; + len = m_fill; + + return true; + } + else + { + esyslog("ERROR: read failed in %s: %d,%s", m_filename.c_str(), errno, strerror(errno) ); + } + } + } + else + { + len = 0; + return true; + } + } + return false; +} + +bool mgStream::removable() +{ + // we do not handle removable media at this time + return false; +} + +bool mgStream::fileinfo( bool log ) +{ + struct stat64 ds; + + if( !stat64( m_filename.c_str(), &ds ) ) + { + printf( "mgStream::fileinfo: stat64 == 0\n" ); + + if( S_ISREG( ds.st_mode ) ) + { + m_fsID = ""; + m_fsType = 0; + + struct statfs64 sfs; + + if( !statfs64( m_filename.c_str(), &sfs) ) + { + if( removable() ) + { + char *tmpbuf; + asprintf( &tmpbuf, "%llx:%llx", sfs.f_blocks, sfs.f_files ); + m_fsID = tmpbuf; + free( tmpbuf ); + } + m_fsType = sfs.f_type; + } + else + { + if( errno != ENOSYS && log ) + { + esyslog("ERROR: can't statfs %s: %s", m_filename.c_str(), strerror(errno) ); + } + } + + m_filesize = ds.st_size; + m_ctime = ds.st_ctime; + +#ifdef CDFS_MAGIC + if( m_fsType == CDFS_MAGIC ) + { + m_ctime=0; // CDFS returns mount time as ctime + } +#endif + // infodone tells that info has been read, like a cache flag + // InfoDone(); + return true; + } + else + { + if(log) + { + esyslog("ERROR: %s is not a regular file", m_filename.c_str() ); + } + } + } + else + { + if(log) + { + esyslog("ERROR: can't stat %s: %s", m_filename.c_str(), strerror(errno) ); + } + + printf( "mgStream::fileinfo: stat64 != 0 for %s\n", m_filename.c_str() ); + } + + return false; +} diff --git a/vdr_stream.h b/vdr_stream.h new file mode 100644 index 0000000..68f7ea7 --- /dev/null +++ b/vdr_stream.h @@ -0,0 +1,57 @@ +/*! + * \file vdr_stream.h + * \brief Definitions of media streams + * + * \version $Revision: 1.2 $ + * \date $Date: 2004/05/28 15:29:19 $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Responsible author: $Author: lvw $ + * + * $Id: vdr_stream.h,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001-2003 Stefan Huelswitt <huels@iname.com> + */ + +#ifndef ___STREAM_H +#define ___STREAM_H + +#include <string> + +#include "vdr_decoder.h" + +class cNet; + +// ---------------------------------------------------------------- + +class mgStream // : public mgFileInfo +{ +private: + int m_fd; + bool m_ismmap; + + // from cFileInfo + std::string m_filename, m_fsID; + unsigned long long m_filesize; + time_t m_ctime; + long m_fsType; + + bool fileinfo( bool log ); + bool removable(); + +protected: + unsigned char *m_buffer; + unsigned long long m_readpos, m_buffpos; + unsigned long m_fill; +public: + mgStream( std::string filename ); + virtual ~mgStream(); + virtual bool open(bool log = true); + virtual void close(); + virtual bool stream( unsigned char *&data, unsigned long &len, const unsigned char *rest=NULL ); + virtual bool seek( unsigned long long pos = 0 ); + virtual unsigned long long bufferPos() { return m_buffpos; } +}; + +#endif //___STREAM_H |