From ad40eaa28e6e9f0fa594937453b5ae53b88dce75 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sun, 28 Aug 2005 18:00:00 +0200 Subject: =?UTF-8?q?Version=201.3.31=20-=20Added=20missing=20German=20OSD?= =?UTF-8?q?=20texts=20for=20'Audio=20language'.=20-=20The=20Setup/CICAM=20?= =?UTF-8?q?menu=20now=20only=20contains=20the=20devices=20that=20actually?= =?UTF-8?q?=20have=20a=20CI=20and=20=20=20dynamically=20detects=20the=20nu?= =?UTF-8?q?mber=20of=20slots=20a=20CI=20provides.=20-=20Implemented=20cAud?= =?UTF-8?q?ioRepacker=20for=20better=20handling=20of=20audio=20PES=20packe?= =?UTF-8?q?ts=20(thanks=20to=20=20=20Reinhard=20Nissl).=20-=20Modified=20h?= =?UTF-8?q?andling=20of=20audio=20packets=20for=20radio=20channels=20in=20?= =?UTF-8?q?remux.c=20(thanks=20to=20=20=20Reinhard=20Nissl).=20-=20Updated?= =?UTF-8?q?=20the=20Danish=20OSD=20texts=20(thanks=20to=20Mogens=20Elneff)?= =?UTF-8?q?.=20-=20Fixed=20the=20EPG=20scan,=20so=20that=20it=20doesn't=20?= =?UTF-8?q?use=20the=20primary=20device=20if=20that=20is=20=20=20currently?= =?UTF-8?q?=20in=20Transfer-Mode=20from=20itself=20(thanks=20to=20Marcus?= =?UTF-8?q?=20Hilbrich=20for=20a=20bug=20=20=20report=20that=20lead=20to?= =?UTF-8?q?=20this).=20-=20Removed=20the=20TUNER=5FLOCK=5FTIMEOUT=20in=20c?= =?UTF-8?q?Device::AttachReceiver()=20since=20it=20caused=20more=20=20=20t?= =?UTF-8?q?rouble=20than=20it=20fixed.=20-=20Fixed=20detecting=20short=20c?= =?UTF-8?q?hannel=20names=20for=20"Kabel=20Deutschland",=20who=20uses=20a?= =?UTF-8?q?=20comma=20=20=20as=20delimiter=20(thanks=20to=20Marco=20Schl?= =?UTF-8?q?=C3=BC=C3=9Fler).=20-=20Moved=20cMenuEditTimer=20and=20cMenuEve?= =?UTF-8?q?nt=20to=20menu.h=20so=20that=20plugins=20can=20use=20it=20(sugg?= =?UTF-8?q?ested=20=20=20by=20Thomas=20G=C3=BCnther).=20-=20The=20new=20st?= =?UTF-8?q?atic=20function=20cString::sprintf()=20can=20be=20used=20to=20e?= =?UTF-8?q?asily=20create=20a=20formatted=20=20=20string.=20-=20Plugins=20?= =?UTF-8?q?can=20now=20implement=20their=20own=20SVDRP=20commands=20(based?= =?UTF-8?q?=20on=20a=20patch=20from=20Hardy=20=20=20Flor).=20See=20PLUGINS?= =?UTF-8?q?.html,=20section=20"SVDRP=20commands"=20for=20details.=20The=20?= =?UTF-8?q?SVDRP=20commands=20=20=20of=20a=20plugin=20are=20accessed=20thr?= =?UTF-8?q?ough=20the=20new=20SVDRP=20command=20PLUG.=20=20=20See=20PLUGIN?= =?UTF-8?q?S/src/svdrpdemo=20for=20an=20example=20of=20how=20to=20use=20th?= =?UTF-8?q?is=20feature.=20-=20The=20new=20SVDRP=20command=20PLAY=20can=20?= =?UTF-8?q?be=20used=20to=20start=20replaying=20a=20recording=20(thanks=20?= =?UTF-8?q?to=20=20=20Hardy=20Flor).=20-=20The=20new=20SVDRP=20command=20E?= =?UTF-8?q?DIT=20can=20be=20used=20to=20start=20the=20editing=20process=20?= =?UTF-8?q?of=20a=20recording=20=20=20(based=20on=20the=20CUTR=20patch=20b?= =?UTF-8?q?y=20Harald=20Milz).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTORS | 16 + HISTORY | 30 ++ PLUGINS.html | 127 ++++++- PLUGINS/src/svdrpdemo/COPYING | 340 +++++++++++++++++ PLUGINS/src/svdrpdemo/HISTORY | 6 + PLUGINS/src/svdrpdemo/Makefile | 82 +++++ PLUGINS/src/svdrpdemo/README | 13 + PLUGINS/src/svdrpdemo/svdrpdemo.c | 60 +++ channels.conf | 18 +- config.h | 6 +- device.c | 7 +- eitscan.c | 4 +- i18n.c | 14 +- menu.c | 128 +++---- menu.h | 26 +- newplugin | 16 +- plugin.c | 12 +- plugin.h | 4 +- remux.c | 749 +++++++++++++++++++++++++++++--------- remux.h | 11 +- sdt.c | 11 +- svdrp.c | 253 +++++++++++-- svdrp.h | 6 +- tools.c | 12 +- tools.h | 3 +- 25 files changed, 1616 insertions(+), 338 deletions(-) create mode 100644 PLUGINS/src/svdrpdemo/COPYING create mode 100644 PLUGINS/src/svdrpdemo/HISTORY create mode 100644 PLUGINS/src/svdrpdemo/Makefile create mode 100644 PLUGINS/src/svdrpdemo/README create mode 100644 PLUGINS/src/svdrpdemo/svdrpdemo.c diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ff546dd..0a9c8ae 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -976,6 +976,8 @@ Reinhard Nissl for making cDvbPlayer::Goto() append a Sequence End Code to get the image shown immediately with softdevices for fixing cDvbSpuBitmap::putPixel() + for implementing cAudioRepacker in remux.c + for modifying handling of audio packets for radio channels in remux.c Richard Robson for reporting freezing replay if a timer starts while in Transfer Mode from the @@ -1212,6 +1214,7 @@ Marco Schl is waiting for improving resetting CAM connections for fixing handling EPG data for time shifted events + for fixing detecting short channel names for "Kabel Deutschland" Jürgen Schmitz for reporting a bug in displaying the current channel when switching via the SVDRP @@ -1436,7 +1439,20 @@ Klaus Heppenheimer Thomas Günther for fixing handling the frame number display if '7' is pressed before the first editing mark, or '9' after the last one + for suggesting to move cMenuEditTimer and cMenuEvent to menu.h so that plugins + can use it David Woodhouse for his help in replacing the get/put_unaligned() macros from asm/unaligned.h with own inline functions to avoid problems on platforms that don't provide these + +Marcus Hilbrich + for a bug report that lead to fixing the EPG scan, so that it doesn't use the + primary device if that is currently in Transfer-Mode from itself + +Hardy Flor + for a patch that was used as a base to implement SVDRP commands for plugins + for implementing the SVDRP command PLAY + +Harald Milz + for his CUTR patch, which was used as a base to implement the SVDRP command EDIT diff --git a/HISTORY b/HISTORY index 016b6f5..dda7c86 100644 --- a/HISTORY +++ b/HISTORY @@ -3716,3 +3716,33 @@ Video Disk Recorder Revision History - Replaced the get/put_unaligned() macros from with own inline functions to avoid problems on platforms that don't provide these (thanks to David Woodhouse for his help). + +2005-08-28: Version 1.3.31 + +- Added missing German OSD texts for 'Audio language'. +- The Setup/CICAM menu now only contains the devices that actually have a CI and + dynamically detects the number of slots a CI provides. +- Implemented cAudioRepacker for better handling of audio PES packets (thanks to + Reinhard Nissl). +- Modified handling of audio packets for radio channels in remux.c (thanks to + Reinhard Nissl). +- Updated the Danish OSD texts (thanks to Mogens Elneff). +- Fixed the EPG scan, so that it doesn't use the primary device if that is + currently in Transfer-Mode from itself (thanks to Marcus Hilbrich for a bug + report that lead to this). +- Removed the TUNER_LOCK_TIMEOUT in cDevice::AttachReceiver() since it caused more + trouble than it fixed. +- Fixed detecting short channel names for "Kabel Deutschland", who uses a comma + as delimiter (thanks to Marco Schlüßler). +- Moved cMenuEditTimer and cMenuEvent to menu.h so that plugins can use it (suggested + by Thomas Günther). +- The new static function cString::sprintf() can be used to easily create a formatted + string. +- Plugins can now implement their own SVDRP commands (based on a patch from Hardy + Flor). See PLUGINS.html, section "SVDRP commands" for details. The SVDRP commands + of a plugin are accessed through the new SVDRP command PLUG. + See PLUGINS/src/svdrpdemo for an example of how to use this feature. +- The new SVDRP command PLAY can be used to start replaying a recording (thanks to + Hardy Flor). +- The new SVDRP command EDIT can be used to start the editing process of a recording + (based on the CUTR patch by Harald Milz). diff --git a/PLUGINS.html b/PLUGINS.html index 29d8351..203d870 100644 --- a/PLUGINS.html +++ b/PLUGINS.html @@ -14,18 +14,18 @@ Copyright © 2005 Klaus Schmidinger
www.cadsoft.de/vdr

-
  -Important modifications introduced in version 1.3.19 are marked like this. -
-
  +
  Important modifications introduced in version 1.3.20 are marked like this.
-
  +
  Important modifications introduced in version 1.3.21 are marked like this.
-
  +
  Important modifications introduced in version 1.3.30 are marked like this.
+
  +Important modifications introduced in version 1.3.31 are marked like this. +

VDR provides an easy to use plugin interface that allows additional functionality to be added to the program by implementing a dynamically loadable library file. @@ -58,7 +58,7 @@ structures and allows it to hook itself into specific areas to perform special a

  • Command line arguments
  • Command line help
  • Getting started -
      +
     
  • Shutting down
  • Main menu entry @@ -68,9 +68,12 @@ structures and allows it to hook itself into specific areas to perform special a
  • The Setup menu
  • Configuration files
  • Internationalization -
      +
     
  • Custom services
  • +
      +
  • SVDRP commands +
  • Loading plugins into VDR
  • Building the distribution package @@ -84,7 +87,7 @@ structures and allows it to hook itself into specific areas to perform special a
  • Skins
  • Themes
  • Devices -
      +
     
  • Audio
  • Remote Control @@ -311,7 +314,7 @@ since VDR, for instance, has to create the plugin objects in order to get their command line help - and after that immediately destroys them again.

    The destructor has to clean up any data created by the plugin. -
      +
      Any threads the plugin may have created shall be stopped in the Stop() function.
    @@ -509,7 +512,7 @@ VDR to exit. If the plugin doesn't implement any background functionality or internationalized texts, it doesn't need to implement either of these functions. -
      +
     

    Shutting down

    Stop it, right there!

    @@ -869,7 +872,7 @@ Texts are first searched for in the Phrases registered for this plugin (i and then in the global VDR texts. So a plugin can make use of texts defined by the core VDR code. -
      +
     

    Custom services

    What can I do for you?

    @@ -934,12 +937,106 @@ will send the request to all plugins until one plugin handles it. The function returns a pointer to the plugin that handled the request, or NULL if no plugin handled it.

    -To send a messages to all plugins, a plugin can call the function +To send a message to all plugins, a plugin can call the function cPluginManager::CallAllServices(). This function returns true if any plugin handled the request, or false if no plugin handled the request.

    +
      +

    SVDRP commands

    + +
    Infinite Diversity in Infinite Combinations

    + +A plugin can implement its own SVDRP commands through the two functions + +

    +virtual const char **SVDRPHelpPages(void);
    +virtual cString SVDRPCommand(const char *Cmd, const char *Option, int &ReplyCode);
    +

    + +The SVDRPHelpPages() function must return a pointer to a list of help +strings for all of the plugin's SVDRP commands, like this + +

    +const char **cPluginSvdrpdemo::SVDRPHelpPages(void)
    +{
    +  static const char *HelpPages[] = {
    +    "DATE\n"
    +    "    Print the current date.",
    +    "TIME [ raw ]\n"
    +    "    Print the current time.\n"
    +    "    If the optional keyword 'raw' is given, the result will be the\n"
    +    "    raw time_t data.",
    +    NULL
    +    };
    +  return HelpPages;
    +}
    +

    + +Note that the first line of each entry contains the actual command and its +parameters, while the following lines explain what the command does and what +the parameters (if any) mean. All lines of the explanation shall be indented +by exactly 4 blanks (no tabs), and none of them shall be longer than 79 characters +(to avoid messy output on 80 character wide terminals). The last entry in the +list must be NULL. +

    +The command names HELP and MAIN are reserverd and cannot +be used by a plugin. +

    +The actual processing of SVDRP commands for a plugin is done in its +SVDRPCommand() function. +Here's an example of such a function, which implements the commands advertised in +the above help texts: + +

    +cString cPluginSvdrpdemo::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
    +{
    +  if (strcasecmp(Command, "DATE") == 0) {
    +     // we use the default reply code here
    +     return DateString(time(NULL));
    +     }
    +  else if (strcasecmp(Command, "TIME") == 0) {
    +     ReplyCode = 901;
    +     if (*Option) {
    +        if (strcasecmp(Option, "RAW") == 0)
    +           return cString::sprintf("%ld\nThis is the number of seconds since the epoch\n"
    +                                   "and a demo of a multi-line reply", time(NULL));
    +        else {
    +           ReplyCode = 504;
    +           return cString::sprintf("Unknown option: \"%s\"", Option);
    +           }
    +        }
    +     return TimeString(time(NULL));
    +     }
    +  return NULL;
    +}
    +

    + +The command is given to this function in the Command parameter, and any optional parameters +are given in the Option string. Command always points to an actual, non-empty string, while +Option may point to an empty string (it is never NULL, though). +

    +If a plugin doesn't implement the given command, it shall return NULL, and VDR will +automatically issue a proper error message. If it encounters an unknown or invalid +option, it shall set the ReplyCode to one of the codes defined in VDR/svdrp.c +and return a proper error message. +

    +The default ReplyCode is 900, and if the plugin doesn't care about reply +codes, it doesn't have to set it to anything else (unless there is an error, of +course). The codes in the range 901..999 are reserved for plugins that want +to use special reply codes. Any plugin can use any of these values and doesn't +have to coordinate this with any other plugin, since the caller knows which +plugin was called, and will therefore process the values according to the +particular plugin's definitions. +

    +The returned string may consist of several lines, separated by the newline character +('\n'). Each of these lines will be preceeded with the ReplyCode +when presenting them to the caller, and the continuation character ('-') +will be set for all but the last one. + +

    +

    Loading plugins into VDR

    Saddling up!

    @@ -1276,9 +1373,7 @@ public: }; cMyReceiver::cMyReceiver(int Pid) -
      :cReceiver(0, -1, Pid) -
    { } @@ -1735,7 +1830,7 @@ private: virtual void Action(void); public: cMyAudio(void); -
      +
      virtual void Play(const uchar *Data, int Length, uchar Id);
    virtual void Mute(bool On); diff --git a/PLUGINS/src/svdrpdemo/COPYING b/PLUGINS/src/svdrpdemo/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/PLUGINS/src/svdrpdemo/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/PLUGINS/src/svdrpdemo/HISTORY b/PLUGINS/src/svdrpdemo/HISTORY new file mode 100644 index 0000000..78014f2 --- /dev/null +++ b/PLUGINS/src/svdrpdemo/HISTORY @@ -0,0 +1,6 @@ +VDR Plugin 'svdrpdemo' Revision History +--------------------------------------- + +2005-08-27: Version 0.0.1 + +- Initial revision. diff --git a/PLUGINS/src/svdrpdemo/Makefile b/PLUGINS/src/svdrpdemo/Makefile new file mode 100644 index 0000000..5714bdc --- /dev/null +++ b/PLUGINS/src/svdrpdemo/Makefile @@ -0,0 +1,82 @@ +# +# Makefile for a Video Disk Recorder plugin +# +# $Id: Makefile 1.1 2005/08/27 11:26:49 kls Exp $ + +# The official name of this plugin. +# This name will be used in the '-P...' option of VDR to load the plugin. +# By default the main source file also carries this name. +# +PLUGIN = svdrpdemo + +### The version number of this plugin (taken from the main source file): + +VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g') + +### The C++ compiler and options: + +CXX ?= g++ +CXXFLAGS ?= -O2 -Wall -Woverloaded-virtual + +### The directory environment: + +DVBDIR = ../../../../DVB +VDRDIR = ../../.. +LIBDIR = ../../lib +TMPDIR = /tmp + +### Allow user defined options to overwrite defaults: + +-include $(VDRDIR)/Make.config + +### The version number of VDR (taken from VDR's "config.h"): + +VDRVERSION = $(shell grep 'define VDRVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g') + +### The name of the distribution archive: + +ARCHIVE = $(PLUGIN)-$(VERSION) +PACKAGE = vdr-$(ARCHIVE) + +### Includes and Defines (add further entries here): + +INCLUDES += -I$(VDRDIR)/include -I$(DVBDIR)/include + +DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' + +### The object files (add further files here): + +OBJS = $(PLUGIN).o + +### Implicit rules: + +%.o: %.c + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +# Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ + +-include $(DEPFILE) + +### Targets: + +all: libvdr-$(PLUGIN).so + +libvdr-$(PLUGIN).so: $(OBJS) + $(CXX) $(CXXFLAGS) -shared $(OBJS) -o $@ + @cp $@ $(LIBDIR)/$@.$(VDRVERSION) + +dist: clean + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @mkdir $(TMPDIR)/$(ARCHIVE) + @cp -a * $(TMPDIR)/$(ARCHIVE) + @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @echo Distribution package created as $(PACKAGE).tgz + +clean: + @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ diff --git a/PLUGINS/src/svdrpdemo/README b/PLUGINS/src/svdrpdemo/README new file mode 100644 index 0000000..27b767a --- /dev/null +++ b/PLUGINS/src/svdrpdemo/README @@ -0,0 +1,13 @@ +This is a "plugin" for the Video Disk Recorder (VDR). + +Written by: Klaus Schmidinger + +Project's homepage: http://www.cadsoft.de/vdr + +Latest version available at: http://www.cadsoft.de/vdr + +See the file COPYING for license information. + +Description: + +This plugin shows how to add SVDRP support to a plugin. diff --git a/PLUGINS/src/svdrpdemo/svdrpdemo.c b/PLUGINS/src/svdrpdemo/svdrpdemo.c new file mode 100644 index 0000000..b6ee9a7 --- /dev/null +++ b/PLUGINS/src/svdrpdemo/svdrpdemo.c @@ -0,0 +1,60 @@ +/* + * svdrpdemo.c: A plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id: svdrpdemo.c 1.1 2005/08/27 16:28:58 kls Exp $ + */ + +#include + +static const char *VERSION = "0.0.1"; +static const char *DESCRIPTION = "How to add SVDRP support to a plugin"; +static const char *MAINMENUENTRY = NULL; + +class cPluginSvdrpdemo : public cPlugin { +private: + // Add any member variables or functions you may need here. +public: + virtual const char *Version(void) { return VERSION; } + virtual const char *Description(void) { return DESCRIPTION; } + virtual const char **SVDRPHelpPages(void); + virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); + }; + +const char **cPluginSvdrpdemo::SVDRPHelpPages(void) +{ + static const char *HelpPages[] = { + "DATE\n" + " Print the current date.", + "TIME [ raw ]\n" + " Print the current time.\n" + " If the optional keyword 'raw' is given, the result will be the\n" + " raw time_t data.", + NULL + }; + return HelpPages; +} + +cString cPluginSvdrpdemo::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) +{ + if (strcasecmp(Command, "DATE") == 0) { + // we use the default reply code here + return DateString(time(NULL)); + } + else if (strcasecmp(Command, "TIME") == 0) { + ReplyCode = 901; + if (*Option) { + if (strcasecmp(Option, "RAW") == 0) + return cString::sprintf("%ld\nThis is the number of seconds since the epoch\nand a demo of a multi-line reply", time(NULL)); + else { + ReplyCode = 504; + return cString::sprintf("Unknown option: \"%s\"", Option); + } + } + return TimeString(time(NULL)); + } + return NULL; +} + +VDRPLUGINCREATOR(cPluginSvdrpdemo); // Don't touch this! diff --git a/channels.conf b/channels.conf index 872bbbe..a6034e2 100644 --- a/channels.conf +++ b/channels.conf @@ -5,18 +5,18 @@ RTL2;RTL World:12187:hC34:S19.2E:27500:166:128=deu:68:0:12020:1:1089:0 Das Erste;ARD:11836:hC34:S19.2E:27500:101:102=deu:104:0:28106:1:1101:0 Bayerisches FS;ARD:11836:hC34:S19.2E:27500:201:202=deu:204:0:28107:1:1101:0 hr-fernsehen;ARD:11836:hC34:S19.2E:27500:301:302=deu:304:0:28108:1:1101:0 -NDR FS MV;ARD:12109:hC34:S19.2E:27500:2401:2402=deu:2404:0:28224:1:1073:0 +NDR FS MV;ARD:12109:hC34:S19.2E:27500:301:302=deu:2404:0:28224:1:1073:0 SR SÜDWEST Ferns.;ARD:11836:hC34:S19.2E:27500:501:502=deu:504:0:28110:1:1101:0 WDR Köln;ARD:11836:hC34:S19.2E:27500:601:602=deu:604:0:28111:1:1101:0 -BR-alpha;ARD:11836:hC34:S19.2E:27500:701:702=deu;703:704:0:28112:1:1101:0 +BR-alpha;ARD:11836:hC34:S19.2E:27500:701:702=deu:704:0:28112:1:1101:0 SÜDWEST Ferns. BW;ARD:11836:hC34:S19.2E:27500:801:802=deu:804:0:28113:1:1101:0 Phoenix;ARD:11836:hC34:S19.2E:27500:901:902=deu:904:0:28114:1:1101:0 ZDF;ZDFvision:11953:hC34:S19.2E:27500:110:120=deu,121=2ch;125=dd:130:0:28006:1:1079:0 -3sat;ZDFvision:11953:hC34:S19.2E:27500:210:220=deu,221=2ch:230:0:28007:1:1079:0 +3sat;ZDFvision:11953:hC34:S19.2E:27500:210:220=deu,221=2ch;225=dd:230:0:28007:1:1079:0 KiKa;ZDFvision:11953:hC34:S19.2E:27500:310:320=deu:330:0:28008:1:1079:0 arte;ARD:11836:hC34:S19.2E:27500:401:402=deu,403=fra:404:0:28109:1:1101:0 -ORF1;ORF:12692:hC56:S19.2E:22000:160:161=deu;163=deu:165:1762,D05,1702,1801:13001:1:1117:0 -ORF2;ORF:12692:hC56:S19.2E:22000:500:501=deu;503=deu:505:1762,D05,1702,1801:13002:1:1117:0 +ORF1;ORF:12692:hC56:S19.2E:22000:160:161=deu;163=deu:165:3:13001:1:1117:0 +ORF2;ORF:12692:hC56:S19.2E:22000:500:501=deu;503=deu:505:3:13002:1:1117:0 ZDFinfokanal;ZDFvision:11953:hC34:S19.2E:27500:610:620=deu:130:0:28011:1:1079:0 CNN Int.;CNN:11778:vC34:S19.2E:27500:165:100=eng:47:0:28522:1:1068:0 Super RTL,S RTL;RTL World:12187:hC34:S19.2E:27500:165:120=deu:65:0:12040:1:1089:0 @@ -27,7 +27,7 @@ DSF;BetaDigital:12480:vC34:S19.2E:27500:1023:1024=deu:39:0:900:133:33:0 HSE24,HSE24;BetaDigital:12480:vC34:S19.2E:27500:1279:1280=deu:37:0:40:133:33:0 Bloomberg TV Germany;Bloomberg:12551:vC56:S19.2E:22000:162:99=deu:0:0:12160:1:1108:0 EURONEWS;CSAT:11817:vC34:S19.2E:27500:163:92=fra,93=eng,94=ita,95=esl,91=rus,98=por,99=deu:0:0:8004:1:1070:0 -rbb Brandenburg;ARD:12109:hC34:S19.2E:27500:601:602=deu:604:0:28205:1:1073:0 +rbb Brandenburg;ARD:12109:hC34:S19.2E:27500:501:502=deu:504:0:28205:1:1073:0 Sky News:11597:vC56:S19.2E:22000:305+131:306=eng:0:0:28707:1:1026:0 Veronica/JETIX;CANALDIGITAAL:12574:hC56:S19.2E:22000:518+8190:92=dut:38:622,100:5020:53:1109:0 BVN;CANALDIGITAAL:12574:hC56:S19.2E:22000:515+8190:96=dut:36:0:5025:53:1109:0 @@ -44,9 +44,9 @@ MDR FERNSEHEN;ARD:12109:hC34:S19.2E:27500:401:402=deu:404:0:28204:1:1073:0 rbb Berlin;ARD:12109:hC34:S19.2E:27500:601:602=deu:604:0:28206:1:1073:0 :Premiere World PREMIERE START,START;PREMIERE:11797:hC34:S19.2E:27500:255:256=deu:32:1:8:133:2:0 -PREMIERE 1,PREM 1;PREMIERE:11797:hC34:S19.2E:27500:511:512=deu,513=deu;515=deu:32:1722,1702,1801:10:133:2:0 +PREMIERE 1,PREM 1;PREMIERE:11797:hC34:S19.2E:27500:511:512=deu,513=deu;515=deu:32:1:10:133:2:0 PREMIERE 2,PREM 2;PREMIERE:11797:hC34:S19.2E:27500:1791:1792=deu,1793=deu;1795=deu:32:1801,1722,1702:11:133:2:0 -PREMIERE 3,PREM 3;PREMIERE:11797:hC34:S19.2E:27500:2303:2304=deu:32:1722,1801,1702:43:133:2:0 +PREMIERE 3,PREM 3;PREMIERE:11797:hC34:S19.2E:27500:2303:2304=deu,2305=deu:32:1722,1801,1702:43:133:2:0 PREMIERE 4,PREM 4;PREMIERE:11797:hC34:S19.2E:27500:767:768=deu:32:1801,1722,1702:9:133:2:0 PREMIERE 5,PREM 5;PREMIERE:11797:hC34:S19.2E:27500:1279:1280=deu:32:1722,1801,1702:29:133:2:0 PREMIERE 6,PREM 6;PREMIERE:11797:hC34:S19.2E:27500:1535:1536=deu:32:1702,1801,1722:41:133:2:0 @@ -108,7 +108,7 @@ Sky Cinema 1;BSkyB:12285:vC23:S28.2E:27500:519+8190:647=eng,667=NAR:583:960,961: Sky Cinema 2;BSkyB:12285:vC23:S28.2E:27500:517+8190:645=eng,665=NAR:581:960,961:4802:2:2030:0 :@900 Some 'seed' channels Chelsea TV;BskyB:11778:vC23:S28.2E:27500:2308+2304:2309=eng:0:960,961:9307:2:2004:0 -WDR Münster;ARD:12421:hC34:S19.2E:27500:101:102=deu:104:0:28310:1:1201:0 +WDR Münster;ARD:12421:hC34:S19.2E:27500:701:702=deu:104:0:28310:1:1201:0 Animal Plnt+;BSkyB:12070:hC23:S28.2E:27500:2314+2307:2315=eng:0:960,961:50002:2:2019:0 S1T;BSkyB:12285:vC23:S28.2E:27500:513+8190:641=eng,661=NAR:577:960,961:4409:2:2030:0 CNN;BSkyB:12051:vC23:S28.2E:27500:2313:2315=eng:2314:0:7140:2:2018:0 diff --git a/config.h b/config.h index a2e6c35..8bfbc85 100644 --- a/config.h +++ b/config.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.224 2005/08/20 10:29:35 kls Exp $ + * $Id: config.h 1.225 2005/08/26 12:28:40 kls Exp $ */ #ifndef __CONFIG_H @@ -20,8 +20,8 @@ #include "i18n.h" #include "tools.h" -#define VDRVERSION "1.3.30" -#define VDRVERSNUM 10330 // Version * 10000 + Major * 100 + Minor +#define VDRVERSION "1.3.31" +#define VDRVERSNUM 10331 // Version * 10000 + Major * 100 + Minor #define MAXPRIORITY 99 #define MAXLIFETIME 99 diff --git a/device.c b/device.c index 2f61498..2380803 100644 --- a/device.c +++ b/device.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: device.c 1.106 2005/08/21 08:56:49 kls Exp $ + * $Id: device.c 1.107 2005/08/27 09:01:09 kls Exp $ */ #include "device.h" @@ -139,7 +139,6 @@ int cPesAssembler::PacketSize(const uchar *data) // The default priority for non-primary devices: #define DEFAULTPRIORITY -1 -#define TUNER_LOCK_TIMEOUT 5000 // ms int cDevice::numDevices = 0; int cDevice::useDevice = 0; @@ -1186,10 +1185,6 @@ bool cDevice::AttachReceiver(cReceiver *Receiver) return false; if (Receiver->device == this) return true; - if (!HasLock(TUNER_LOCK_TIMEOUT)) { - esyslog("ERROR: device %d has no lock, can't attach receiver!", CardIndex() + 1); - return false; - } cMutexLock MutexLock(&mutexReceiver); for (int i = 0; i < MAXRECEIVERS; i++) { if (!receiver[i]) { diff --git a/eitscan.c b/eitscan.c index 3894bca..10739c3 100644 --- a/eitscan.c +++ b/eitscan.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: eitscan.c 1.27 2005/08/07 11:29:54 kls Exp $ + * $Id: eitscan.c 1.28 2005/08/26 15:37:06 kls Exp $ */ #include "eitscan.h" @@ -150,7 +150,7 @@ void cEITScanner::Process(void) if (!Channel->Ca() || Channel->Ca() == Device->DeviceNumber() + 1 || Channel->Ca() >= 0x0100) { if (Device->ProvidesTransponder(Channel)) { if (!Device->Receiving()) { - bool IsPrimaryDeviceReplaying = Device == cDevice::PrimaryDevice() && Device->Replaying(); + bool IsPrimaryDeviceReplaying = Device == cDevice::PrimaryDevice() && Device->Replaying() && cTransferControl::ReceiverDevice() != cDevice::PrimaryDevice(); if (Device != cDevice::ActualDevice() || (Device->ProvidesTransponderExclusively(Channel) && (IsPrimaryDeviceReplaying || now - lastActivity > Setup.EPGScanTimeout * 3600))) { if (!IsPrimaryDeviceReplaying && Device == cDevice::ActualDevice() && !currentChannel) { if (cTransferControl::ReceiverDevice()) diff --git a/i18n.c b/i18n.c index d7c6873..346ad86 100644 --- a/i18n.c +++ b/i18n.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: i18n.c 1.198 2005/08/14 12:03:47 kls Exp $ + * $Id: i18n.c 1.200 2005/08/26 13:39:07 kls Exp $ * * Translations provided by: * @@ -271,7 +271,7 @@ const tI18nPhrase Phrases[] = { "",// TODO "",// TODO "",// TODO - "",// TODO + "Optagelses info", }, { "Setup", "Einstellungen", @@ -2924,7 +2924,7 @@ const tI18nPhrase Phrases[] = { "¿àŞÚàãâÚĞ áâàĞİØæ ÜÕİî", "Listaj po stranicama", "Lehekülje kerimine", - "Rul sider", + "Scroll sidevis", }, { "Setup.OSD$Scroll wraps", "Rundum scrollen", @@ -2945,7 +2945,7 @@ const tI18nPhrase Phrases[] = { "",// TODO "S kraja skoèi na poèetak", "",// TODO - "",// TODO + "Scroll rundt", }, { "Setup.OSD$Sort timers", "Timer sortieren", @@ -3389,7 +3389,7 @@ const tI18nPhrase Phrases[] = { "tilf. ny transp.", }, { "Setup.DVB$Audio languages", // note the plural - "",//TODO + "Audio Sprachen", "",//TODO "",//TODO "Audio talen", @@ -3410,7 +3410,7 @@ const tI18nPhrase Phrases[] = { "Audio sprog (ant.)", }, { "Setup.DVB$Audio language", // note the singular - "",//TODO + "Audio Sprache", "",//TODO "",//TODO "Audio taal", @@ -5407,7 +5407,7 @@ const tI18nPhrase Phrases[] = { "",// TODO "Bez titla", "",// TODO - "",// TODO + "Ingen titel", }, { NULL } }; diff --git a/menu.c b/menu.c index 3385707..1e51474 100644 --- a/menu.c +++ b/menu.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 1.355 2005/08/14 15:14:29 kls Exp $ + * $Id: menu.c 1.357 2005/08/27 09:37:23 kls Exp $ */ #include "menu.h" @@ -19,7 +19,6 @@ #include "eitscan.h" #include "i18n.h" #include "interface.h" -#include "menuitems.h" #include "plugin.h" #include "recording.h" #include "remote.h" @@ -605,20 +604,6 @@ eOSState cMenuText::ProcessKey(eKeys Key) // --- cMenuEditTimer -------------------------------------------------------- -class cMenuEditTimer : public cOsdMenu { -private: - cTimer *timer; - cTimer data; - int channel; - bool addIfConfirmed; - cMenuEditDateItem *firstday; - void SetFirstDayItem(void); -public: - cMenuEditTimer(cTimer *Timer, bool New = false); - virtual ~cMenuEditTimer(); - virtual eOSState ProcessKey(eKeys Key); - }; - cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New) :cOsdMenu(tr("Edit timer"), 12) { @@ -904,15 +889,6 @@ eOSState cMenuTimers::ProcessKey(eKeys Key) // --- cMenuEvent ------------------------------------------------------------ -class cMenuEvent : public cOsdMenu { -private: - const cEvent *event; -public: - cMenuEvent(const cEvent *Event, bool CanSwitch = false); - virtual void Display(void); - virtual eOSState ProcessKey(eKeys Key); -}; - cMenuEvent::cMenuEvent(const cEvent *Event, bool CanSwitch) :cOsdMenu(tr("Event")) { @@ -2122,11 +2098,28 @@ eOSState cMenuSetupLNB::ProcessKey(eKeys Key) // --- cMenuSetupCICAM ------------------------------------------------------- +class cMenuSetupCICAMItem : public cOsdItem { +private: + cCiHandler *ciHandler; + int slot; +public: + cMenuSetupCICAMItem(int Device, cCiHandler *CiHandler, int Slot); + cCiHandler *CiHandler(void) { return ciHandler; } + int Slot(void) { return slot; } + }; + +cMenuSetupCICAMItem::cMenuSetupCICAMItem(int Device, cCiHandler *CiHandler, int Slot) +{ + ciHandler = CiHandler; + slot = Slot; + char buffer[32]; + const char *CamName = CiHandler->GetCamName(slot); + snprintf(buffer, sizeof(buffer), "%s%d %d\t%s", tr("Setup.CICAM$CICAM DVB"), Device + 1, slot + 1, CamName ? CamName : "-"); + SetText(buffer); +} + class cMenuSetupCICAM : public cMenuSetupBase { private: - int helpKeys; - void SetHelpKeys(void); - cCiHandler *GetCurrentCiHandler(int *Slot = NULL); eOSState Menu(void); eOSState Reset(void); public: @@ -2136,66 +2129,43 @@ public: cMenuSetupCICAM::cMenuSetupCICAM(void) { - helpKeys = -1; SetSection(tr("CICAM")); for (int d = 0; d < cDevice::NumDevices(); d++) { cDevice *Device = cDevice::GetDevice(d); - cCiHandler *CiHandler = Device->CiHandler(); - for (int Slot = 0; Slot < 2; Slot++) { - char buffer[32]; - int CardIndex = Device->CardIndex(); - const char *CamName = CiHandler ? CiHandler->GetCamName(Slot) : NULL; - if (!CamName) - CamName = "-"; - snprintf(buffer, sizeof(buffer), "%s%d %d\t%s", tr("Setup.CICAM$CICAM DVB"), CardIndex + 1, Slot + 1, CamName); - Add(new cOsdItem(buffer)); - } + if (Device) { + cCiHandler *CiHandler = Device->CiHandler(); + if (CiHandler) { + for (int Slot = 0; Slot < CiHandler->NumSlots(); Slot++) + Add(new cMenuSetupCICAMItem(Device->CardIndex(), CiHandler, Slot)); + } + } } - SetHelpKeys(); -} - -cCiHandler *cMenuSetupCICAM::GetCurrentCiHandler(int *Slot) -{ - cDevice *Device = cDevice::GetDevice(Current() / 2); - if (Slot) - *Slot = Current() % 2; - return Device ? Device->CiHandler() : NULL; -} - -void cMenuSetupCICAM::SetHelpKeys(void) -{ - int NewHelpKeys = helpKeys; - NewHelpKeys = GetCurrentCiHandler() ? 1 : 0; - if (NewHelpKeys != helpKeys) { - switch (NewHelpKeys) { - case 0: SetHelp(NULL); break; - case 1: SetHelp(tr("Menu"), tr("Reset")); - } - helpKeys = NewHelpKeys; - } + SetHelp(tr("Menu"), tr("Reset")); } eOSState cMenuSetupCICAM::Menu(void) { - int Slot = 0; - cCiHandler *CiHandler = GetCurrentCiHandler(&Slot); - if (CiHandler && CiHandler->EnterMenu(Slot)) - return osEnd; // the CAM menu will be executed explicitly from the main loop - else - Skins.Message(mtError, tr("Can't open CAM menu!")); + cMenuSetupCICAMItem *item = (cMenuSetupCICAMItem *)Get(Current()); + if (item) { + if (item->CiHandler()->EnterMenu(item->Slot())) + return osEnd; // the CAM menu will be executed explicitly from the main loop + else + Skins.Message(mtError, tr("Can't open CAM menu!")); + } return osContinue; } eOSState cMenuSetupCICAM::Reset(void) { - int Slot = 0; - cCiHandler *CiHandler = GetCurrentCiHandler(&Slot); - if (CiHandler && CiHandler->Reset(Slot)) { - Skins.Message(mtInfo, tr("CAM has been reset")); - return osEnd; + cMenuSetupCICAMItem *item = (cMenuSetupCICAMItem *)Get(Current()); + if (item) { + if (item->CiHandler()->Reset(item->Slot())) { + Skins.Message(mtInfo, tr("CAM has been reset")); + return osEnd; + } + else + Skins.Message(mtError, tr("Can't reset CAM!")); } - else - Skins.Message(mtError, tr("Can't reset CAM!")); return osContinue; } @@ -2205,17 +2175,11 @@ eOSState cMenuSetupCICAM::ProcessKey(eKeys Key) if (state == osUnknown) { switch (Key) { - case kRed: if (helpKeys == 1) - return Menu(); - break; - case kGreen: if (helpKeys == 1) - return Reset(); - break; + case kRed: return Menu(); + case kGreen: return Reset(); default: break; } } - if (Key != kNone) - SetHelpKeys(); return state; } diff --git a/menu.h b/menu.h index 93712e4..f327811 100644 --- a/menu.h +++ b/menu.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.h 1.70 2005/05/15 14:34:54 kls Exp $ + * $Id: menu.h 1.71 2005/08/27 09:37:33 kls Exp $ */ #ifndef __MENU_H @@ -15,6 +15,7 @@ #include "epg.h" #include "osdbase.h" #include "dvbplayer.h" +#include "menuitems.h" #include "recorder.h" #include "skins.h" @@ -29,6 +30,29 @@ public: virtual eOSState ProcessKey(eKeys Key); }; +class cMenuEditTimer : public cOsdMenu { +private: + cTimer *timer; + cTimer data; + int channel; + bool addIfConfirmed; + cMenuEditDateItem *firstday; + void SetFirstDayItem(void); +public: + cMenuEditTimer(cTimer *Timer, bool New = false); + virtual ~cMenuEditTimer(); + virtual eOSState ProcessKey(eKeys Key); + }; + +class cMenuEvent : public cOsdMenu { +private: + const cEvent *event; +public: + cMenuEvent(const cEvent *Event, bool CanSwitch = false); + virtual void Display(void); + virtual eOSState ProcessKey(eKeys Key); + }; + class cMenuMain : public cOsdMenu { private: time_t lastActivity; diff --git a/newplugin b/newplugin index 8badddf..90d6113 100755 --- a/newplugin +++ b/newplugin @@ -12,7 +12,7 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: newplugin 1.19 2005/08/21 09:32:08 kls Exp $ +# $Id: newplugin 1.20 2005/08/27 16:13:29 kls Exp $ $PLUGIN_NAME = $ARGV[0] || die "Usage: newplugin \n"; @@ -171,6 +171,8 @@ public: virtual cMenuSetupPage *SetupMenu(void); virtual bool SetupParse(const char *Name, const char *Value); virtual bool Service(const char *Id, void *Data = NULL); + virtual const char **SVDRPHelpPages(void); + virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); }; cPlugin${PLUGIN_CLASS}::cPlugin$PLUGIN_CLASS(void) @@ -243,6 +245,18 @@ bool cPlugin${PLUGIN_CLASS}::Service(const char *Id, void *Data) return false; } +const char **cPlugin${PLUGIN_CLASS}::SVDRPHelpPages(void) +{ + // Return help text for SVDRP commands this plugin implements + return NULL; +} + +const char *cPlugin${PLUGIN_CLASS}::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) +{ + // Process SVDRP commands this plugin implements + return NULL; +} + VDRPLUGINCREATOR(cPlugin$PLUGIN_CLASS); // Don't touch this! }; diff --git a/plugin.c b/plugin.c index 8a316cc..9916313 100644 --- a/plugin.c +++ b/plugin.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: plugin.c 1.14 2005/08/21 09:35:28 kls Exp $ + * $Id: plugin.c 1.15 2005/08/27 16:13:24 kls Exp $ */ #include "plugin.h" @@ -104,6 +104,16 @@ bool cPlugin::Service(const char *Id, void *Data) return false; } +const char **cPlugin::SVDRPHelpPages(void) +{ + return NULL; +} + +cString cPlugin::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) +{ + return NULL; +} + void cPlugin::RegisterI18n(const tI18nPhrase * const Phrases) { I18nRegister(Phrases, Name()); diff --git a/plugin.h b/plugin.h index 3befbbb..2d9f983 100644 --- a/plugin.h +++ b/plugin.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: plugin.h 1.9 2005/08/21 09:32:08 kls Exp $ + * $Id: plugin.h 1.10 2005/08/27 16:13:17 kls Exp $ */ #ifndef __PLUGIN_H @@ -51,6 +51,8 @@ public: void RegisterI18n(const tI18nPhrase * const Phrases); virtual bool Service(const char *Id, void *Data = NULL); + virtual const char **SVDRPHelpPages(void); + virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); static void SetConfigDirectory(const char *Dir); static const char *ConfigDirectory(const char *PluginName = NULL); diff --git a/remux.c b/remux.c index c980bd8..1028050 100644 --- a/remux.c +++ b/remux.c @@ -8,10 +8,10 @@ * the Linux DVB driver's 'tuxplayer' example and were rewritten to suit * VDR's needs. * - * The cDolbyRepacker code was originally written by Reinhard Nissl , + * The cRepacker family's code was originally written by Reinhard Nissl , * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de. * - * $Id: remux.c 1.37 2005/08/21 08:58:58 kls Exp $ + * $Id: remux.c 1.42 2005/08/28 11:46:44 kls Exp $ */ #include "remux.h" @@ -20,49 +20,23 @@ #include "thread.h" #include "tools.h" -// --- cRepacker ------------------------------------------------------------- - -class cRepacker { -protected: - int maxPacketSize; - uint8_t subStreamId; - static void DroppedData(const char *Reason, int Count) { esyslog("%s (dropped %d bytes)", Reason, Count); } - static int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) - { - int n = ResultBuffer->Put(Data, Count); - if (n != Count) - esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count); - return n; - } - static int AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = 0); -public: - cRepacker(void) { maxPacketSize = 6 + 65535; subStreamId = 0; } - virtual ~cRepacker() {} - virtual void Reset(void) {} - virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) = 0; - virtual int BreakAt(const uchar *Data, int Count) = 0; - virtual int QuerySnoopSize(void) { return 0; } - void SetMaxPacketSize(int MaxPacketSize) { maxPacketSize = MaxPacketSize; } - void SetSubStreamId(uint8_t SubStreamId) { subStreamId = SubStreamId; } - }; - -int cRepacker::AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader) +ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader) { if (Count < 7) - return -1; // too short + return phNeedMoreData; // too short if ((Data[6] & 0xC0) == 0x80) { // MPEG 2 if (Count < 9) - return -1; // too short + return phNeedMoreData; // too short PesPayloadOffset = 6 + 3 + Data[8]; if (Count < PesPayloadOffset) - return -1; // too short + return phNeedMoreData; // too short if (ContinuationHeader) *ContinuationHeader = ((Data[6] == 0x80) && !Data[7] && !Data[8]); - return 2; // MPEG 2 + return phMPEG2; // MPEG 2 } // check for MPEG 1 ... @@ -74,7 +48,7 @@ int cRepacker::AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOff break; if (Count <= ++PesPayloadOffset) - return -1; // too short + return phNeedMoreData; // too short } // skip STD_buffer_scale/size @@ -82,7 +56,7 @@ int cRepacker::AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOff PesPayloadOffset += 2; if (Count <= PesPayloadOffset) - return -1; // too short + return phNeedMoreData; // too short } if (ContinuationHeader) @@ -104,18 +78,50 @@ int cRepacker::AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOff *ContinuationHeader = true; } else - return 0; // unknown + return phInvalid; // unknown if (Count < PesPayloadOffset) - return -1; // too short + return phNeedMoreData; // too short - return 1; // MPEG 1 + return phMPEG1; // MPEG 1 } -// --- cVideoRepacker -------------------------------------------------------- +// --- cRepacker ------------------------------------------------------------- -class cVideoRepacker : public cRepacker { -private: +class cRepacker { +protected: + bool initiallySyncing; + int maxPacketSize; + uint8_t subStreamId; + static void DroppedData(const char *Reason, int Count) { esyslog("%s (dropped %d bytes)", Reason, Count); } +public: + static int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded); + cRepacker(void) { initiallySyncing = true; maxPacketSize = 6 + 65535; subStreamId = 0; } + virtual ~cRepacker() {} + virtual void Reset(void) { /* initiallySyncing = true; */ } + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) = 0; + virtual int BreakAt(const uchar *Data, int Count) = 0; + virtual int QuerySnoopSize(void) { return 0; } + void SetMaxPacketSize(int MaxPacketSize) { maxPacketSize = MaxPacketSize; } + void SetSubStreamId(uint8_t SubStreamId) { subStreamId = SubStreamId; } + }; + +int cRepacker::Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded) +{ + if (CapacityNeeded >= Count && ResultBuffer->Free() < CapacityNeeded) { + esyslog("ERROR: possible result buffer overflow, dropped %d out of %d byte", CapacityNeeded, CapacityNeeded); + return 0; + } + int n = ResultBuffer->Put(Data, Count); + if (n != Count) + esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count); + return n; +} + +// --- cCommonRepacker -------------------------------------------------------- + +class cCommonRepacker : public cRepacker { +protected: int skippedBytes; int packetTodo; uchar fragmentData[6 + 65535 + 3]; @@ -125,40 +131,25 @@ private: uchar pesHeaderBackup[6 + 3 + 255]; int pesHeaderBackupLen; uint32_t scanner; - enum eState { - syncing, - findPicture, - scanPicture - } state; uint32_t localScanner; int localStart; bool PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); -public: - cVideoRepacker(void); - virtual void Reset(void); - virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); - virtual int BreakAt(const uchar *Data, int Count); virtual int QuerySnoopSize() { return 4; } + virtual void Reset(void); }; -cVideoRepacker::cVideoRepacker(void) -{ - Reset(); -} - -void cVideoRepacker::Reset(void) +void cCommonRepacker::Reset(void) { + cRepacker::Reset(); skippedBytes = 0; packetTodo = maxPacketSize - 6 - 3; fragmentLen = 0; pesHeaderLen = 0; pesHeaderBackupLen = 0; - scanner = 0xFFFFFFFF; - state = syncing; localStart = -1; } -bool cVideoRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +bool cCommonRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) { // enter packet length into PES header ... if (fragmentLen > 0) { // ... which is contained in the fragment buffer @@ -166,11 +157,17 @@ bool cVideoRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar int PacketLen = fragmentLen + Count - 6; fragmentData[ 4 ] = PacketLen >> 8; fragmentData[ 5 ] = PacketLen & 0xFF; + // just skip packets with no payload + int PesPayloadOffset = 0; + if (AnalyzePesHeader(fragmentData, fragmentLen, PesPayloadOffset) <= phInvalid) + esyslog("cCommonRepacker: invalid PES packet encountered in fragment buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) + return true; // skip empty packet // amount of data to put into result buffer: a negative Count value means // to strip off any partially contained start code. int Bite = fragmentLen + (Count >= 0 ? 0 : Count); // put data into result buffer - int n = Put(ResultBuffer, fragmentData, Bite); + int n = Put(ResultBuffer, fragmentData, Bite, 6 + PacketLen); fragmentLen = 0; if (n != Bite) return false; @@ -179,11 +176,17 @@ bool cVideoRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar int PacketLen = pesHeaderLen + Count - 6; pesHeader[ 4 ] = PacketLen >> 8; pesHeader[ 5 ] = PacketLen & 0xFF; + // just skip packets with no payload + int PesPayloadOffset = 0; + if (AnalyzePesHeader(pesHeader, pesHeaderLen, PesPayloadOffset) <= phInvalid) + esyslog("cCommonRepacker: invalid PES packet encountered in header buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) + return true; // skip empty packet // amount of data to put into result buffer: a negative Count value means // to strip off any partially contained start code. int Bite = pesHeaderLen + (Count >= 0 ? 0 : Count); // put data into result buffer - int n = Put(ResultBuffer, pesHeader, Bite); + int n = Put(ResultBuffer, pesHeader, Bite, 6 + PacketLen); pesHeaderLen = 0; if (n != Bite) return false; @@ -193,7 +196,7 @@ bool cVideoRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar // amount of data to put into result buffer int Bite = Count; // put data into result buffer - int n = Put(ResultBuffer, Data, Bite); + int n = Put(ResultBuffer, Data, Bite, Bite); if (n != Bite) return false; } @@ -201,6 +204,34 @@ bool cVideoRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar return true; } +// --- cVideoRepacker -------------------------------------------------------- + +class cVideoRepacker : public cCommonRepacker { +private: + enum eState { + syncing, + findPicture, + scanPicture + } state; +public: + cVideoRepacker(void); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +cVideoRepacker::cVideoRepacker(void) +{ + Reset(); +} + +void cVideoRepacker::Reset(void) +{ + cCommonRepacker::Reset(); + scanner = 0xFFFFFFFF; + state = syncing; +} + void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) { // synchronisation is detected some bytes after frame start. @@ -211,8 +242,8 @@ void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int pesPayloadOffset = 0; bool continuationHeader = false; - int mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); - if (mpegLevel <= 0) { + ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); + if (mpegLevel <= phInvalid) { DroppedData("cVideoRepacker: no valid PES packet header found", Count); return; } @@ -264,16 +295,14 @@ void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, // the byte count get's negative then the current buffer ends in a // partitial start code that must be stripped off, as it shall be put // in the next packet. - if (!PushOutPacket(ResultBuffer, payload, data - 3 - payload)) { - DroppedData("cVideoRepacker: result buffer overflow", Count - (done - 3)); - return; - } + PushOutPacket(ResultBuffer, payload, data - 3 - payload); // go on with syncing to the next picture state = syncing; } if (state == syncing) { - // report that syncing dropped some bytes - if (skippedBytes > SkippedBytesLimit) + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes esyslog("cVideoRepacker: skipped %d bytes to sync on next picture", skippedBytes - SkippedBytesLimit); skippedBytes = 0; // if there is a PES header available, then use it ... @@ -298,7 +327,7 @@ void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, pesHeader[pesHeaderLen++] = 0x00; // length still unknown pesHeader[pesHeaderLen++] = 0x00; // length still unknown - if (mpegLevel == 2) { + if (mpegLevel == phMPEG2) { pesHeader[pesHeaderLen++] = 0x80; pesHeader[pesHeaderLen++] = 0x00; pesHeader[pesHeaderLen++] = 0x00; @@ -379,10 +408,7 @@ void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, const uchar *excessData = fragmentData + fragmentLen + bite; // a negative byte count means to drop some bytes from the current // fragment's tail, to not exceed the maximum packet size. - if (!PushOutPacket(ResultBuffer, payload, bite)) { - DroppedData("cVideoRepacker: result buffer overflow", Count - done); - return; - } + PushOutPacket(ResultBuffer, payload, bite); // create a continuation PES header pesHeaderLen = 0; pesHeader[pesHeaderLen++] = 0x00; @@ -392,7 +418,7 @@ void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, pesHeader[pesHeaderLen++] = 0x00; // length still unknown pesHeader[pesHeaderLen++] = 0x00; // length still unknown - if (mpegLevel == 2) { + if (mpegLevel == phMPEG2) { pesHeader[pesHeaderLen++] = 0x80; pesHeader[pesHeaderLen++] = 0x00; pesHeader[pesHeaderLen++] = 0x00; @@ -434,7 +460,8 @@ void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, } // report that syncing dropped some bytes if (skippedBytes > SkippedBytesLimit) { - esyslog("cVideoRepacker: skipped %d bytes while syncing on next picture", skippedBytes - SkippedBytesLimit); + if (!initiallySyncing) // omit report for the typical initial case + esyslog("cVideoRepacker: skipped %d bytes while syncing on next picture", skippedBytes - SkippedBytesLimit); skippedBytes = SkippedBytesLimit; } } @@ -443,7 +470,7 @@ int cVideoRepacker::BreakAt(const uchar *Data, int Count) { int PesPayloadOffset = 0; - if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= 0) + if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= phInvalid) return -1; // not enough data for test // just detect end of picture @@ -475,6 +502,381 @@ int cVideoRepacker::BreakAt(const uchar *Data, int Count) return PesPayloadOffset + packetTodo + 4; } +// --- cAudioRepacker -------------------------------------------------------- + +class cAudioRepacker : public cCommonRepacker { +private: + static int bitRates[2][3][16]; + enum eState { + syncing, + scanFrame + } state; + int frameTodo; + int frameSize; + static bool IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize = NULL); +public: + cAudioRepacker(void); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +int cAudioRepacker::bitRates[2][3][16] = { // all values are specified as kbits/s + // MPEG 1, Layer I + 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1, + // MPEG 1, Layer II + 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1, + // MPEG 1, Layer III + 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1, + // MPEG 2, Layer I + 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1, + // MPEG 2, Layer II/III + 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1, + // MPEG 2, Layer II/III + 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 + }; + +cAudioRepacker::cAudioRepacker(void) +{ + Reset(); +} + +void cAudioRepacker::Reset(void) +{ + cCommonRepacker::Reset(); + scanner = 0; + state = syncing; + frameTodo = 0; + frameSize = 0; +} + +bool cAudioRepacker::IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize) +{ + int syncword = (Header & 0xFFF00000) >> 20; + int id = (Header & 0x00080000) >> 19; + int layer = (Header & 0x00060000) >> 17; +//int protection_bit = (Header & 0x00010000) >> 16; + int bitrate_index = (Header & 0x0000F000) >> 12; + int sampling_frequency = (Header & 0x00000C00) >> 10; + int padding_bit = (Header & 0x00000200) >> 9; +//int private_bit = (Header & 0x00000100) >> 8; +//int mode = (Header & 0x000000C0) >> 6; +//int mode_extension = (Header & 0x00000030) >> 4; +//int copyright = (Header & 0x00000008) >> 3; +//int orignal_copy = (Header & 0x00000004) >> 2; + int emphasis = (Header & 0x00000003); + + if (syncword != 0xFFF) + return false; + + if (id == 0 && !Mpeg2) // reserved in MPEG 1 + return false; + + if (layer == 0) // reserved + return false; + + if (bitrate_index == 0xF) // forbidden + return false; + + if (sampling_frequency == 3) // reserved + return false; + + if (emphasis == 2) // reserved + return false; + + if (FrameSize) { + if (bitrate_index == 0) + *FrameSize = 0; + else { + static int samplingFrequencies[2][4] = { // all values are specified in Hz + // MPEG 1 + 44100, 48000, 32000, -1, + // MPEG 2 + 22050, 24000, 16000, -1 + }; + + static int slots_per_frame[2][3] = { + // MPEG 1, Layer I, II, III + 12, 144, 144, + // MPEG 2, Layer I, II, III + 12, 144, 72 + }; + + int mpegIndex = 1 - id; + int layerIndex = 3 - layer; + + // Layer I (i. e., layerIndex == 0) has a larger slot size + int slotSize = (layerIndex == 0) ? 4 : 1; // bytes + + int br = 1000 * bitRates[mpegIndex][layerIndex][bitrate_index]; // bits/s + int sf = samplingFrequencies[mpegIndex][sampling_frequency]; + + int N = slots_per_frame[mpegIndex][layerIndex] * br / sf; // slots + + *FrameSize = (N + padding_bit) * slotSize; // bytes + } + } + + return true; +} + +void cAudioRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // reset local scanner + localStart = -1; + + int pesPayloadOffset = 0; + bool continuationHeader = false; + ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); + if (mpegLevel <= phInvalid) { + DroppedData("cAudioRepacker: no valid PES packet header found", Count); + return; + } + if (!continuationHeader) { + // backup PES header + pesHeaderBackupLen = pesPayloadOffset; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = pesPayloadOffset; + int todo = Count - done; + const uchar *data = Data + done; + // remember start of the data + const uchar *payload = data; + + while (todo > 0) { + // collect number of skipped bytes while syncing + if (state <= syncing) + skippedBytes++; + else if (frameTodo > 0) { + frameTodo--; + if (frameTodo == 0 && state == scanFrame) { + // the current audio frame is is done now. So push out the packet to + // start a new packet for the next audio frame. + PushOutPacket(ResultBuffer, payload, data - payload); + // go on with syncing to the next audio frame + state = syncing; + } + } + // did we reach an audio frame header? + scanner <<= 8; + scanner |= *data; + if ((scanner & 0xFFF00000) == 0xFFF00000) { + if (frameTodo <= 0 && IsValidAudioHeader(scanner, mpegLevel == phMPEG2, &frameSize)) { + if (state == scanFrame) { + // As a new audio frame starts here, the previous one is done. So push + // out the packet to start a new packet for the next audio frame. If + // the byte count gets negative then the current buffer ends in a + // partitial audio frame header that must be stripped off, as it shall + // be put in the next packet. + PushOutPacket(ResultBuffer, payload, data - 3 - payload); + // go on with syncing to the next audio frame + state = syncing; + } + if (state == syncing) { + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + esyslog("cAudioRepacker: skipped %d bytes to sync on next audio frame", skippedBytes - SkippedBytesLimit); + skippedBytes = 0; + // if there is a PES header available, then use it ... + if (pesHeaderBackupLen > 0) { + // ISO 13818-1 says: + // In the case of audio, if a PTS is present in a PES packet header + // it shall refer to the access unit commencing in the PES packet. An + // audio access unit commences in a PES packet if the first byte of + // the audio access unit is present in the PES packet. + memcpy(pesHeader, pesHeaderBackup, pesHeaderBackupLen); + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + } + else { + // ... otherwise create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + } + // append the first three bytes of the audio frame header + pesHeader[pesHeaderLen++] = 0xFF; + pesHeader[pesHeaderLen++] = (scanner >> 16) & 0xFF; + pesHeader[pesHeaderLen++] = (scanner >> 8) & 0xFF; + // the next packet's payload will begin with the fourth byte of + // the audio frame header (= the actual byte) + payload = data; + // maximum we can hold in one PES packet + packetTodo = maxPacketSize - pesHeaderLen; + // expected remainder of audio frame: so far we have read 3 bytes from the frame header + frameTodo = frameSize - 3; + // go on with collecting the frame's data + ((int &)state)++; + } + } + } + data++; + done++; + todo--; + // do we have to start a new packet as there is no more space left? + if (--packetTodo <= 0) { + // We connot start a new packet here if the current might end in an audio + // frame header and this header shall possibly be put in the next packet. So + // overfill the current packet until we can safely detect that we won't + // break an audio frame header into pieces: + // + // A) the last four bytes were an audio frame header. + // B) the last three bytes introduce an audio frame header. + // C) the last two bytes introduce an audio frame header. + // D) the last byte introduces an audio frame header. + // + // Todo : Data : Rule : Result + // -----:-------------------------------:------:------- + // : XX XX FF Fz zz zz|YY YY YY YY : : + // 0 : ^^| : A : push + // -----:-------------------------------:------:------- + // : XX XX XX FF Fz zz|zz YY YY YY : : + // 0 : ^^| : B : wait + // -1 : |^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX FF Fz|zz zz YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : B : wait + // -2 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX XX FF|Fz zz zz YY : : + // 0 : ^^| : D : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : B : wait + // -3 : | ^^ : A : push + // -----:-------------------------------:------:------- + bool A = ((scanner & 0xFFF00000) == 0xFFF00000); + bool B = ((scanner & 0xFFF000) == 0xFFF000); + bool C = ((scanner & 0xFFF0) == 0xFFF0); + bool D = ((scanner & 0xFF) == 0xFF); + if (A || (!B && !C && !D)) { + // Actually we cannot push out an overfull packet. So we'll have to + // adjust the byte count and payload start as necessary. If the byte + // count gets negative we'll have to append the excess from fragment's + // tail to the next PES header. + int bite = data + packetTodo - payload; + const uchar *excessData = fragmentData + fragmentLen + bite; + // A negative byte count means to drop some bytes from the current + // fragment's tail, to not exceed the maximum packet size. + PushOutPacket(ResultBuffer, payload, bite); + // create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + + // copy any excess data + while (bite++ < 0) { + // append the excess data here + pesHeader[pesHeaderLen++] = *excessData++; + packetTodo++; + } + // the next packet's payload will begin here + payload = data + packetTodo; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo += maxPacketSize - pesHeaderLen; + } + } + } + // The packet is done. Now store any remaining data into fragment buffer + // if we are no longer syncing. + if (state != syncing) { + // append the PES header ... + int bite = pesHeaderLen; + pesHeaderLen = 0; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, pesHeader, bite); + fragmentLen += bite; + } + // append payload. It may contain part of an audio frame header at it's + // end, which will be removed when the next packet gets processed. + bite = data - payload; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, payload, bite); + fragmentLen += bite; + } + } + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + if (!initiallySyncing) // omit report for the typical initial case + esyslog("cAudioRepacker: skipped %d bytes while syncing on next audio frame", skippedBytes - SkippedBytesLimit); + skippedBytes = SkippedBytesLimit; + } +} + +int cAudioRepacker::BreakAt(const uchar *Data, int Count) +{ + int PesPayloadOffset = 0; + + ePesHeader MpegLevel = AnalyzePesHeader(Data, Count, PesPayloadOffset); + if (MpegLevel <= phInvalid) + return -1; // not enough data for test + + // determine amount of data to fill up packet and to append next audio frame header + int packetRemainder = PesPayloadOffset + packetTodo + 4; + + // just detect end of an audio frame + if (state == scanFrame) { + // when remaining audio frame size is known, then omit scanning + if (frameTodo > 0) { + // determine amount of data to fill up audio frame and to append next audio frame header + int remaining = PesPayloadOffset + frameTodo + 4; + if (remaining < packetRemainder) + return remaining; + return packetRemainder; + } + // setup local scanner + if (localStart < 0) { + localScanner = scanner; + localStart = 0; + } + // start where we've stopped at the last run + const uchar *data = Data + PesPayloadOffset + localStart; + const uchar *limit = Data + Count; + // scan data + while (data < limit) { + localStart++; + localScanner <<= 8; + localScanner |= *data++; + // check whether the next audio frame follows + if (((localScanner & 0xFFF00000) == 0xFFF00000) && IsValidAudioHeader(localScanner, MpegLevel == phMPEG2)) + return data - Data; + } + } + // just fill up packet and append next audio frame header + return packetRemainder; +} + // --- cDolbyRepacker -------------------------------------------------------- class cDolbyRepacker : public cRepacker { @@ -501,8 +903,8 @@ private: int skippedBytes; void ResetPesHeader(bool ContinuationFrame = false); void AppendSubStreamID(bool ContinuationFrame = false); - bool FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Done, int &Bite); - bool StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Done, int &Bite); + bool FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); + bool StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); public: cDolbyRepacker(void); virtual void Reset(void); @@ -569,6 +971,7 @@ void cDolbyRepacker::ResetPesHeader(bool ContinuationFrame) void cDolbyRepacker::Reset(void) { + cRepacker::Reset(); ResetPesHeader(); state = find_0b; ac3todo = 0; @@ -580,26 +983,24 @@ void cDolbyRepacker::Reset(void) skippedBytes = 0; } -bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Done, int &Bite) +bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) { + bool success = true; // enough data available to put PES packet into buffer? if (fragmentTodo <= Todo) { // output a previous fragment first if (fragmentLen > 0) { Bite = fragmentLen; - int n = Put(ResultBuffer, fragmentData, Bite); - if (Bite != n) { - Reset(); - return false; - } + int n = Put(ResultBuffer, fragmentData, Bite, fragmentLen + fragmentTodo); + if (Bite != n) + success = false; fragmentLen = 0; } Bite = fragmentTodo; - int n = Put(ResultBuffer, Data, Bite); - if (Bite != n) { - Reset(); - Done += n; - return false; + if (success) { + int n = Put(ResultBuffer, Data, Bite, Bite); + if (Bite != n) + success = false; } fragmentTodo = 0; // ac3 frame completely processed? @@ -609,19 +1010,16 @@ bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const ucha else { // copy the fragment into separate buffer for later processing Bite = Todo; - if (fragmentLen + Bite > (int)sizeof(fragmentData)) { - Reset(); - return false; - } memcpy(fragmentData + fragmentLen, Data, Bite); fragmentLen += Bite; fragmentTodo -= Bite; } - return true; + return success; } -bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Done, int &Bite) +bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) { + bool success = true; int packetLen = pesHeaderLen + ac3todo; // limit packet to maximum size if (packetLen > maxPacketSize) @@ -631,17 +1029,14 @@ bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar Bite = pesHeaderLen; // enough data available to put PES packet into buffer? if (packetLen - pesHeaderLen <= Todo) { - int n = Put(ResultBuffer, pesHeader, Bite); - if (Bite != n) { - Reset(); - return false; - } + int n = Put(ResultBuffer, pesHeader, Bite, packetLen); + if (Bite != n) + success = false; Bite = packetLen - pesHeaderLen; - n = Put(ResultBuffer, Data, Bite); - if (Bite != n) { - Reset(); - Done += n; - return false; + if (success) { + n = Put(ResultBuffer, Data, Bite, Bite); + if (Bite != n) + success = false; } // ac3 frame completely processed? if (Bite >= ac3todo) @@ -650,24 +1045,16 @@ bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar else { fragmentTodo = packetLen; // copy the pesheader into separate buffer for later processing - if (fragmentLen + Bite > (int)sizeof(fragmentData)) { - Reset(); - return false; - } memcpy(fragmentData + fragmentLen, pesHeader, Bite); fragmentLen += Bite; fragmentTodo -= Bite; // copy the fragment into separate buffer for later processing Bite = Todo; - if (fragmentLen + Bite > (int)sizeof(fragmentData)) { - Reset(); - return false; - } memcpy(fragmentData + fragmentLen, Data, Bite); fragmentLen += Bite; fragmentTodo -= Bite; } - return true; + return success; } void cDolbyRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) @@ -761,8 +1148,9 @@ void cDolbyRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, state = find_0b; continue; } - // report that syncing dropped some bytes - if (skippedBytes > SkippedBytesLimit) + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes esyslog("cDolbyRepacker: skipped %d bytes to sync on next AC3 frame", skippedBytes - SkippedBytesLimit); skippedBytes = 0; // append read data to header for common output processing @@ -776,18 +1164,11 @@ void cDolbyRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, case output_packet: { int bite = 0; // finish remainder of ac3 frame? - if (fragmentTodo > 0) { - if (!FinishRemainder(ResultBuffer, data, todo, done, bite)) { - DroppedData("cDolbyRepacker: result buffer overflow", Count - done); - return; - } - } + if (fragmentTodo > 0) + FinishRemainder(ResultBuffer, data, todo, bite); else { // start a new packet - if (!StartNewPacket(ResultBuffer, data, todo, done, bite)) { - DroppedData("cDolbyRepacker: result buffer overflow", Count - done); - return; - } + StartNewPacket(ResultBuffer, data, todo, bite); // prepare for next (continuation) packet ResetPesHeader(state == output_packet); } @@ -800,7 +1181,8 @@ void cDolbyRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, } // report that syncing dropped some bytes if (skippedBytes > SkippedBytesLimit) { - esyslog("cDolbyRepacker: skipped %d bytes while syncing on next AC3 frame", skippedBytes - 4); + if (!initiallySyncing) // omit report for the typical initial case + esyslog("cDolbyRepacker: skipped %d bytes while syncing on next AC3 frame", skippedBytes - 4); skippedBytes = SkippedBytesLimit; } } @@ -890,7 +1272,8 @@ private: uint8_t hlength; int mpeg; uint8_t check; - int which; + int mpeg1_required; + int mpeg1_stuffing; bool done; cRingBufferLinear *resultBuffer; int tsErrors; @@ -956,11 +1339,8 @@ void cTS2PES::store(uint8_t *Data, int Count) { if (repacker) repacker->Repack(resultBuffer, Data, Count); - else { - int n = resultBuffer->Put(Data, Count); - if (n != Count) - esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count); - } + else + cRepacker::Put(resultBuffer, Data, Count, Count); } void cTS2PES::reset_ipack(void) @@ -973,7 +1353,8 @@ void cTS2PES::reset_ipack(void) hlength = 0; mpeg = 0; check = 0; - which = 0; + mpeg1_required = 0; + mpeg1_stuffing = 0; done = false; count = 0; } @@ -1057,7 +1438,7 @@ void cTS2PES::instant_repack(const uint8_t *Buf, int Count) { int c = 0; - while (c < Count && (mpeg == 0 || (mpeg == 1 && found < 7) || (mpeg == 2 && found < 9)) && (found < 5 || !done)) { + while (c < Count && (mpeg == 0 || (mpeg == 1 && found < mpeg1_required) || (mpeg == 2 && found < 9)) && (found < 5 || !done)) { switch (found ) { case 0: case 1: @@ -1103,6 +1484,7 @@ void cTS2PES::instant_repack(const uint8_t *Buf, int Count) plength = ntohs(*pl); c += 2; found += 2; + mpeg1_stuffing = 0; } else { plen[0] = Buf[c]; @@ -1115,32 +1497,54 @@ void cTS2PES::instant_repack(const uint8_t *Buf, int Count) unsigned short *pl = (unsigned short *)plen; plength = ntohs(*pl); found++; + mpeg1_stuffing = 0; } break; case 6: if (!done) { flag1 = Buf[c++]; found++; - if ((flag1 & 0xC0) == 0x80 ) - mpeg = 2; - else { - hlength = 0; - which = 0; - mpeg = 1; - flag2 = 0; + if (mpeg1_stuffing == 0) { // first stuffing iteration: determine MPEG level + if ((flag1 & 0xC0) == 0x80) + mpeg = 2; + else { + mpeg = 1; + mpeg1_required = 7; + } + } + if (mpeg == 1) { + if (flag1 == 0xFF) { // MPEG1 stuffing + if (++mpeg1_stuffing > 16) + found = 0; // invalid MPEG1 header + else { // ignore stuffing + found--; + if (plength > 0) + plength--; + } + } + else if ((flag1 & 0xC0) == 0x40) // STD_buffer_scale/size + mpeg1_required += 2; + else if (flag1 != 0x0F && (flag1 & 0xF0) != 0x20 && (flag1 & 0xF0) != 0x30) + found = 0; // invalid MPEG1 header + else { + flag2 = 0; + hlength = 0; + } } } break; case 7: - if (!done && mpeg == 2) { + if (!done && (mpeg == 2 || mpeg1_required > 7)) { flag2 = Buf[c++]; found++; } break; case 8: - if (!done && mpeg == 2) { + if (!done && (mpeg == 2 || mpeg1_required > 7)) { hlength = Buf[c++]; found++; + if (mpeg == 1 && hlength != 0x0F && (hlength & 0xF0) != 0x20 && (hlength & 0xF0) != 0x30) + found = 0; // invalid MPEG1 header } break; default: @@ -1151,7 +1555,7 @@ void cTS2PES::instant_repack(const uint8_t *Buf, int Count) if (!plength) plength = MMAX_PLENGTH - 6; - if (done || ((mpeg == 2 && found >= 9) || (mpeg == 1 && found >= 7))) { + if (done || ((mpeg == 2 && found >= 9) || (mpeg == 1 && found >= mpeg1_required))) { switch (cid) { case AUDIO_STREAM_S ... AUDIO_STREAM_E: case VIDEO_STREAM_S ... VIDEO_STREAM_E: @@ -1163,8 +1567,13 @@ void cTS2PES::instant_repack(const uint8_t *Buf, int Count) write_ipack(&hlength, 1); } - if (mpeg == 1 && found == 7) + if (mpeg == 1 && found == mpeg1_required) { write_ipack(&flag1, 1); + if (mpeg1_required > 7) { + write_ipack(&flag2, 1); + write_ipack(&hlength, 1); + } + } if (mpeg == 2 && (flag2 & PTS_ONLY) && found < 14) { while (c < Count && found < 14) { @@ -1277,7 +1686,12 @@ cRemux::cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, b if (APids) { int n = 0; while (*APids && numTracks < MAXTRACKS && n < MAXAPIDS) +#define TEST_cAudioRepacker +#ifdef TEST_cAudioRepacker + ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n++, 0x00, new cAudioRepacker); +#else ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n++); +#endif } if (DPids) { int n = 0; @@ -1321,10 +1735,9 @@ int cRemux::ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &Pic // If the return value is -1 the packet was not completely in the buffer. int Length = GetPacketLength(Data, Count, Offset); if (Length > 0) { - if (Length >= 8) { - int i = Offset + 8; // the minimum length of the video packet header - i += Data[i] + 1; // possible additional header bytes - for (; i < Offset + Length - 5; i++) { + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) { + for (int i = Offset + PesPayloadOffset; i < Offset + Length - 5; i++) { if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) { switch (Data[i + 3]) { case SC_PICTURE: PictureType = (Data[i + 5] >> 3) & 0x07; @@ -1409,19 +1822,6 @@ uchar *cRemux::Get(int &Count, uchar *PictureType) return resultBuffer->Get(Count); #endif - // Special VPID case to enable recording radio channels: - - if (isRadio) { - // XXX actually '0' should be enough, but '1' must be used with encrypted channels (driver bug?) - // XXX also allowing 0x1FFF to not break Michael Paar's original patch, - // XXX but it would probably be best to only use '0' - // Force syncing of radio channels to avoid "no useful data" error - synced = true; - if (PictureType) - *PictureType = I_FRAME; - return resultBuffer->Get(Count); - } - // Check for frame borders: if (PictureType) @@ -1466,6 +1866,18 @@ uchar *cRemux::Get(int &Count, uchar *PictureType) l = GetPacketLength(data, resultCount, i); if (l < 0) return resultData; + if (isRadio) { + if (!synced) { + if (PictureType) + *PictureType = I_FRAME; + resultSkipped = i; // will drop everything before this position + synced = true; + } + else if (Count) + return resultData; + else if (PictureType) + *PictureType = I_FRAME; + } } if (synced) { if (!Count) @@ -1499,8 +1911,9 @@ void cRemux::Clear(void) void cRemux::SetBrokenLink(uchar *Data, int Length) { - if (Length > 9 && Data[0] == 0 && Data[1] == 0 && Data[2] == 1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) { - for (int i = Data[8] + 9; i < Length - 7; i++) { // +9 to skip video packet header + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data, Length, PesPayloadOffset) >= phMPEG1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) { + for (int i = PesPayloadOffset; i < Length - 7; i++) { if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1 && Data[i + 3] == 0xB8) { if (!(Data[i + 7] & 0x40)) // set flag only if GOP is not closed Data[i + 7] |= 0x20; diff --git a/remux.h b/remux.h index 0cd570e..ab29655 100644 --- a/remux.h +++ b/remux.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.h 1.14 2005/08/07 10:28:07 kls Exp $ + * $Id: remux.h 1.15 2005/08/26 13:22:19 kls Exp $ */ #ifndef __REMUX_H @@ -15,6 +15,15 @@ #include "ringbuffer.h" #include "tools.h" +enum ePesHeader { + phNeedMoreData = -1, + phInvalid = 0, + phMPEG1 = 1, + phMPEG2 = 2 + }; + +ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = NULL); + // Picture types: #define NO_PICTURE 0 #define I_FRAME 1 diff --git a/sdt.c b/sdt.c index dcf6dc1..74c5503 100644 --- a/sdt.c +++ b/sdt.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: sdt.c 1.14 2005/05/14 09:39:46 kls Exp $ + * $Id: sdt.c 1.15 2005/08/27 09:27:47 kls Exp $ */ #include "sdt.h" @@ -64,11 +64,14 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length char *ps = compactspace(ShortNameBuf); if (!*ps && cSource::IsCable(Source())) { // Some cable providers don't mark short channel names according to the - // standard, but rather go their own way and use "name>short name": - char *p = strchr(pn, '>'); + // standard, but rather go their own way and use "name>short name" or + // "name, short name": + char *p = strchr(pn, '>'); // fix for UPC Wien + if (!p) + p = strchr(pn, ','); // fix for "Kabel Deutschland" if (p && p > pn) { *p++ = 0; - strcpy(ShortNameBuf, p); + strcpy(ShortNameBuf, skipspace(p)); } } sd->providerName.getText(ProviderNameBuf, sizeof(ProviderNameBuf)); diff --git a/svdrp.c b/svdrp.c index 093bd99..889ffe4 100644 --- a/svdrp.c +++ b/svdrp.c @@ -10,7 +10,7 @@ * and interact with the Video Disk Recorder - or write a full featured * graphical interface that sits on top of an SVDRP connection. * - * $Id: svdrp.c 1.74 2005/08/07 14:20:41 kls Exp $ + * $Id: svdrp.c 1.77 2005/08/28 14:12:00 kls Exp $ */ #include "svdrp.h" @@ -28,10 +28,12 @@ #include #include "channels.h" #include "config.h" +#include "cutter.h" #include "device.h" #include "eitscan.h" #include "keys.h" #include "menu.h" +#include "plugin.h" #include "remote.h" #include "timers.h" #include "tools.h" @@ -194,6 +196,10 @@ const char *HelpPages[] = { " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!", "DELT \n" " Delete timer.", + "EDIT \n" + " Edit the recording with the given number. Before a recording can be\n" + " edited, an LSTR command must have been executed in order to retrieve\n" + " the recording numbers.", "GRAB [ jpeg | pnm [ [ ] ] ]\n" " Grab the current frame and save it to the given file. Images can\n" " be stored as JPEG (default) or PNM, at the given quality (default\n" @@ -253,6 +259,22 @@ const char *HelpPages[] = { " zero, this means that the timer is currently recording and has started\n" " at the given time. The first value in the resulting line is the number\n" " of the timer.", + "PLAY [ begin | ]\n" + " Play the recording with the given number. Before a recording can be\n" + " played, an LSTR command must have been executed in order to retrieve\n" + " the recording numbers.\n" + " The keyword 'begin' plays the recording from its very beginning, while\n" + " a (given as hh:mm:ss[.ff] or framenumber) starts at that\n" + " position. If neither 'begin' nor a are given, replay is resumed\n" + " at the position where any previous replay was stopped, or from the beginning\n" + " by default. To control or stop the replay session, use the usual remote\n" + " control keypresses via the HITK command.", + "PLUG [ [ ]]\n" + " Send a command to a plugin.\n" + " The PLUG command without any parameters lists all plugins.\n" + " If only a name is given, all commands known to that plugin are listed.\n" + " If a command is given (optionally followed by parameters), that command\n" + " is sent to the plugin, and the result will be displayed.", "PUTE\n" " Put data into the EPG list. The data entered has to strictly follow the\n" " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n" @@ -294,6 +316,8 @@ const char *HelpPages[] = { 504 Command parameter not implemented 550 Requested action not taken 554 Transaction failed + 900 Default plugin reply code + 901..999 Plugin specific reply codes */ @@ -315,15 +339,16 @@ const char *GetHelpTopic(const char *HelpPage) return NULL; } -const char *GetHelpPage(const char *Cmd) +const char *GetHelpPage(const char *Cmd, const char **p) { - const char **p = HelpPages; - while (*p) { - const char *t = GetHelpTopic(*p); - if (strcasecmp(Cmd, t) == 0) - return *p; - p++; - } + if (p) { + while (*p) { + const char *t = GetHelpTopic(*p); + if (strcasecmp(Cmd, t) == 0) + return *p; + p++; + } + } return NULL; } @@ -376,14 +401,14 @@ void cSVDRP::Reply(int Code, const char *fmt, ...) va_start(ap, fmt); char *buffer; vasprintf(&buffer, fmt, ap); - char *nl = strchr(buffer, '\n'); - if (Code > 0 && nl && *(nl + 1)) // trailing newlines don't count! - Code = -Code; - char number[16]; - sprintf(number, "%03d%c", abs(Code), Code < 0 ? '-' : ' '); const char *s = buffer; while (s && *s) { const char *n = strchr(s, '\n'); + char cont = ' '; + if (Code < 0 || n && *(n + 1)) // trailing newlines don't count! + cont = '-'; + char number[16]; + sprintf(number, "%03d%c", abs(Code), cont); if (!(Send(number) && Send(s, n ? n - s : -1) && Send("\r\n"))) { Close(); break; @@ -400,6 +425,32 @@ void cSVDRP::Reply(int Code, const char *fmt, ...) } } +void cSVDRP::PrintHelpTopics(const char **hp) +{ + int NumPages = 0; + if (hp) { + while (*hp) { + NumPages++; + hp++; + } + hp -= NumPages; + } + const int TopicsPerLine = 5; + int x = 0; + for (int y = 0; (y * TopicsPerLine + x) < NumPages; y++) { + char buffer[TopicsPerLine * MAXHELPTOPIC + 5]; + char *q = buffer; + q += sprintf(q, " "); + for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) { + const char *topic = GetHelpTopic(hp[(y * TopicsPerLine + x)]); + if (topic) + q += sprintf(q, "%*s", -MAXHELPTOPIC, topic); + } + x = 0; + Reply(-214, buffer); + } +} + void cSVDRP::CmdCHAN(const char *Option) { if (*Option) { @@ -561,6 +612,36 @@ void cSVDRP::CmdDELT(const char *Option) Reply(501, "Missing timer number"); } +void cSVDRP::CmdEDIT(const char *Option) +{ + if (*Option) { + if (isnumber(Option)) { + cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); + if (recording) { + cMarks Marks; + if (Marks.Load(recording->FileName()) && Marks.Count()) { + if (!cCutter::Active()) { + if (cCutter::Start(recording->FileName())) + Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title()); + else + Reply(554, "Can't start editing process"); + } + else + Reply(554, "Editing process already active"); + } + else + Reply(554, "No editing marks defined"); + } + else + Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before editing)"); + } + else + Reply(501, "Error in recording number \"%s\"", Option); + } + else + Reply(501, "Missing recording number"); +} + void cSVDRP::CmdGRAB(const char *Option) { char *FileName = NULL; @@ -626,7 +707,7 @@ void cSVDRP::CmdGRAB(const char *Option) void cSVDRP::CmdHELP(const char *Option) { if (*Option) { - const char *hp = GetHelpPage(Option); + const char *hp = GetHelpPage(Option, HelpPages); if (hp) Reply(214, hp); else { @@ -637,24 +718,13 @@ void cSVDRP::CmdHELP(const char *Option) else { Reply(-214, "This is VDR version %s", VDRVERSION); Reply(-214, "Topics:"); - const char **hp = HelpPages; - int NumPages = 0; - while (*hp) { - NumPages++; - hp++; - } - const int TopicsPerLine = 5; - int x = 0; - for (int y = 0; (y * TopicsPerLine + x) < NumPages; y++) { - char buffer[TopicsPerLine * (MAXHELPTOPIC + 5)]; - char *q = buffer; - for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) { - const char *topic = GetHelpTopic(HelpPages[(y * TopicsPerLine + x)]); - if (topic) - q += sprintf(q, " %s", topic); - } - x = 0; - Reply(-214, buffer); + PrintHelpTopics(HelpPages); + cPlugin *plugin; + for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++) { + const char **hp = plugin->SVDRPHelpPages(); + if (hp) + Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description()); + PrintHelpTopics(hp); } Reply(-214, "To report bugs in the implementation send email to"); Reply(-214, " vdr-bugs@cadsoft.de"); @@ -1042,6 +1112,120 @@ void cSVDRP::CmdNEXT(const char *Option) Reply(550, "No active timers"); } +void cSVDRP::CmdPLAY(const char *Option) +{ + if (*Option) { + char *opt = strdup(Option); + char *num = skipspace(opt); + char *option = num; + while (*option && !isspace(*option)) + option++; + char c = *option; + *option = 0; + if (isnumber(num)) { + cRecording *recording = Recordings.Get(strtol(num, NULL, 10) - 1); + if (recording) { + if (c) + option = skipspace(++option); + cReplayControl::SetRecording(NULL, NULL); + cControl::Shutdown(); + if (*option) { + int pos = 0; + if (strcasecmp(option, "BEGIN") != 0) { + int h, m = 0, s = 0, f = 1; + int x = sscanf(option, "%d:%d:%d.%d", &h, &m, &s, &f); + if (x == 1) + pos = h; + else if (x >= 3) + pos = (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1; + } + cResumeFile resume(recording->FileName()); + if (pos <= 0) + resume.Delete(); + else + resume.Save(pos); + } + cReplayControl::SetRecording(recording->FileName(), recording->Title()); + cControl::Launch(new cReplayControl); + cControl::Attach(); + Reply(250, "Playing recording \"%s\" [%s]", num, recording->Title()); + } + else + Reply(550, "Recording \"%s\" not found%s", num, Recordings.Count() ? "" : " (use LSTR before playing)"); + } + else + Reply(501, "Error in recording number \"%s\"", num); + free(opt); + } + else + Reply(501, "Missing recording number"); +} + +void cSVDRP::CmdPLUG(const char *Option) +{ + if (*Option) { + char *opt = strdup(Option); + char *name = skipspace(opt); + char *option = name; + while (*option && !isspace(*option)) + option++; + char c = *option; + *option = 0; + cPlugin *plugin = cPluginManager::GetPlugin(name); + if (plugin) { + if (c) + option = skipspace(++option); + char *cmd = option; + while (*option && !isspace(*option)) + option++; + if (*option) { + *option++ = 0; + option = skipspace(option); + } + if (!*cmd || strcasecmp(cmd, "HELP") == 0) { + if (*cmd && *option) { + const char *hp = GetHelpPage(option, plugin->SVDRPHelpPages()); + if (hp) { + Reply(-214, hp); + Reply(214, "End of HELP info"); + } + else + Reply(504, "HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->Name()); + } + else { + Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description()); + const char **hp = plugin->SVDRPHelpPages(); + if (hp) { + Reply(-214, "SVDRP commands:"); + PrintHelpTopics(hp); + Reply(214, "End of HELP info"); + } + else + Reply(214, "This plugin has no SVDRP commands"); + } + } + else { + int ReplyCode = 900; + cString s = plugin->SVDRPCommand(cmd, option, ReplyCode); + if (s) + Reply(abs(ReplyCode), *s); + else + Reply(500, "Command unrecognized: \"%s\"", cmd); + } + } + else + Reply(550, "Plugin \"%s\" not found (use PLUG for a list of plugins)", name); + free(opt); + } + else { + Reply(-214, "Available plugins:"); + cPlugin *plugin; + for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++) + Reply(-214, "%s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description()); + Reply(214, "End of plugin list"); + } +} + void cSVDRP::CmdPUTE(const char *Option) { delete PUTEhandler; @@ -1152,6 +1336,7 @@ void cSVDRP::Execute(char *Cmd) else if (CMD("DELC")) CmdDELC(s); else if (CMD("DELR")) CmdDELR(s); else if (CMD("DELT")) CmdDELT(s); + else if (CMD("EDIT")) CmdEDIT(s); else if (CMD("GRAB")) CmdGRAB(s); else if (CMD("HELP")) CmdHELP(s); else if (CMD("HITK")) CmdHITK(s); @@ -1167,6 +1352,8 @@ void cSVDRP::Execute(char *Cmd) else if (CMD("NEWC")) CmdNEWC(s); else if (CMD("NEWT")) CmdNEWT(s); else if (CMD("NEXT")) CmdNEXT(s); + else if (CMD("PLAY")) CmdPLAY(s); + else if (CMD("PLUG")) CmdPLUG(s); else if (CMD("PUTE")) CmdPUTE(s); else if (CMD("SCAN")) CmdSCAN(s); else if (CMD("STAT")) CmdSTAT(s); diff --git a/svdrp.h b/svdrp.h index 815c91e..baa4fb3 100644 --- a/svdrp.h +++ b/svdrp.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: svdrp.h 1.20 2004/01/17 13:30:52 kls Exp $ + * $Id: svdrp.h 1.23 2005/08/28 14:10:32 kls Exp $ */ #ifndef __SVDRP_H @@ -52,11 +52,13 @@ private: void Close(bool Timeout = false); bool Send(const char *s, int length = -1); void Reply(int Code, const char *fmt, ...); + void PrintHelpTopics(const char **hp); void CmdCHAN(const char *Option); void CmdCLRE(const char *Option); void CmdDELC(const char *Option); void CmdDELR(const char *Option); void CmdDELT(const char *Option); + void CmdEDIT(const char *Option); void CmdGRAB(const char *Option); void CmdHELP(const char *Option); void CmdHITK(const char *Option); @@ -72,6 +74,8 @@ private: void CmdNEWC(const char *Option); void CmdNEWT(const char *Option); void CmdNEXT(const char *Option); + void CmdPLAY(const char *Option); + void CmdPLUG(const char *Option); void CmdPUTE(const char *Option); void CmdSCAN(const char *Option); void CmdSTAT(const char *Option); diff --git a/tools.c b/tools.c index ca4b0b8..46d3ce2 100644 --- a/tools.c +++ b/tools.c @@ -4,13 +4,14 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.c 1.96 2005/08/06 09:53:21 kls Exp $ + * $Id: tools.c 1.97 2005/08/27 14:43:55 kls Exp $ */ #include "tools.h" #include #include #include +#include #include #include #include @@ -530,6 +531,15 @@ cString &cString::operator=(const cString &String) return *this; } +cString cString::sprintf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char *buffer; + vasprintf(&buffer, fmt, ap); + return cString(buffer, true); +} + cString WeekDayName(int WeekDay) { char buffer[4]; diff --git a/tools.h b/tools.h index 0a8ee8b..41a0049 100644 --- a/tools.h +++ b/tools.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.h 1.74 2005/08/21 14:06:38 kls Exp $ + * $Id: tools.h 1.75 2005/08/27 14:40:08 kls Exp $ */ #ifndef __TOOLS_H @@ -81,6 +81,7 @@ public: operator const char * () const { return s; } // for use in (const char *) context const char * operator*() const { return s; } // for use in (const void *) context (printf() etc.) cString &operator=(const cString &String); + static cString sprintf(const char *fmt, ...); }; ssize_t safe_read(int filedes, void *buffer, size_t size); -- cgit v1.2.3