[vdr-plugin-rpihddevice] 01/02: Imported Upstream version 0.1.0

Tobias Grimm tiber-guest at moszumanska.debian.org
Mon Aug 31 07:06:15 UTC 2015


This is an automated email from the git hooks/post-receive script.

tiber-guest pushed a commit to branch master
in repository vdr-plugin-rpihddevice.

commit 8d6398d36ed14df9a768fe7e7ce0cff3b7889553
Author: etobi <git at e-tobi.net>
Date:   Sun Aug 9 15:16:12 2015 +0200

    Imported Upstream version 0.1.0
---
 COPYING             |  340 +++++++
 HISTORY             |  221 +++++
 Makefile            |  184 ++++
 README              |  113 +++
 audio.c             | 1415 ++++++++++++++++++++++++++
 audio.h             |   65 ++
 display.c           |  364 +++++++
 display.h           |   99 ++
 ilclient/Makefile   |   23 +
 ilclient/ilclient.c | 1834 ++++++++++++++++++++++++++++++++++
 ilclient/ilclient.h | 1039 +++++++++++++++++++
 ilclient/ilcore.c   |  308 ++++++
 omx.c               | 1380 ++++++++++++++++++++++++++
 omx.h               |  181 ++++
 omxdevice.c         |  780 +++++++++++++++
 omxdevice.h         |  186 ++++
 ovgosd.c            | 2736 +++++++++++++++++++++++++++++++++++++++++++++++++++
 ovgosd.h            |   39 +
 po/de_DE.po         |   78 ++
 po/fi_FI.po         |   77 ++
 po/hu_HU.po         |   78 ++
 rpihddevice.c       |  105 ++
 setup.c             |  355 +++++++
 setup.h             |  168 ++++
 tools.h             |  178 ++++
 25 files changed, 12346 insertions(+)

diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..f90922e
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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 Lesser 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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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.
+
+  <signature of Ty Coon>, 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 Lesser General
+Public License instead of this License.
diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..0006722
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,221 @@
+VDR Plugin 'rpihddevice' Revision History
+-----------------------------------------
+
+2015-04-29: Version 0.1.0
+-------------------------
+- new:
+  - reworked clock stretching in live mode based on buffer usage
+  - added command line argument to disable OSD
+  - combined digital audio format options to one single setup option
+  - added font kerning
+  - support for GPU accelerated pixmaps
+- fixed:
+  - skip audio packets when replaying non-radio recordings in trick modes
+  - fixed displaying of current position when changing replay speed
+  - fixed PTS wrap around (reported by Klaus Schmidinger)
+  - increased audio decoder thread priority
+  - don't depend on multi channel PCM support for digital audio pass-through
+  - improved video frame rate detection to be more tolerant to inaccurate values
+  - adapted cOvgRawOsd::Flush() to new cOsd::RenderPixmaps() of vdr-2.1.10
+  - fixed calculation of string height (reported by Klaus Schmidinger)
+  - additionally set number of in/out channels when setting up audio resampler
+  - don't clear audio PTS when parser shrinks the packet buffer
+
+2015-01-21: Version 0.0.11
+--------------------------
+- new:
+  - reworked audio decoder, increase buffering of audio samples
+  - reworked omxdevice and improved clock stretching for transfer mode
+  - added Hungarian translations (thanks to Füley István)
+  - updated Finnish translations and fixed tr() usage (thanks to Rolf Ahrenberg)
+  - use fast deinterlacer for HD streams
+  - added high level OSD with GPU support
+- fixed:
+  - start H.264 video with valid frame only, requires firmware from 2015/01/18 
+    or newer
+
+2014-11-02: Version 0.0.10
+--------------------------
+- new:
+  - added DTS audio codec support
+  - add setup options to control video mode and its behaviour on stream changes
+  - set HDMI speaker layout to fix channel mapping for multi channel PCM output
+  - support building against external ffmpeg/libav by setting EXT_LIBAV
+  - support for >=ffmpeg-1.2 and >=libav-0.8 with resampling
+  - setup option for video framing mode in case of incompatible aspect ratio
+  - redirect ffmpeg messages to plugin/VDR logs
+  - added cppcheck as Makefile target (suggested by Rolf Ahrenberg)
+  - reworked setup parameter handling
+  - implemented proper command queue for OVG-OSD
+- fixed:
+  - increased latency target for live mode to 500ms
+  - reworked OVG image processing for low level OSD
+  - upmix mono audio channels to stereo when using PCM output on HDMI
+  - skip non-video packets in StillPicture(), fixes artifacts in PES recordings
+  - skip audio substream header for PES recordings with AC3 audio track
+  - reworked audio decoding loop and set correct dstSize before resampling
+  - avoid flushing OMX video chain twice when calling SetPlayMode(pmNone)
+  - skip packets with invalid payload offset in PlayVideo() and PlayAudio()
+  - play multiple video PES packets sequentially in StillPicture()
+  - updated parameter when setting clock latency target according omxplayer
+  - code clean up based on cppcheck's results (thanks to Rolf Ahrenberg)
+- known issues:
+  - direct switching from fast forward to fast rewind will freeze replay
+
+2014-04-24: Version 0.0.9
+-------------------------
+- new:
+  - added Finnish translations (thanks to Rolf Ahrenberg)
+  - setup option to ignore HDMI audio EDID
+- fixed:
+  - add prefix to plugin specific class names (suggested by Rolf Ahrenberg)
+  - move cAudioParser to avoid duplicate class name (reported by Patrick Maier)
+  - maintain list of rejected OMX buffers
+  - thread-safe container for OMX events
+  - report valid VideoAspect if video is stopped (reported by Rolf Ahrenberg)
+  - fixed buffer stall after StillPicture()
+  - increased latency target for transfer mode to 200ms
+  - reworked clock handling for normal play back and trick modes
+  - simplified OMX buffer polling
+  - added proper buffering at audio parser and reduced OMX audio buffers
+  - OMX shutdown when no stream has been played (reported by Klaus Schmidinger)
+  - displaying subtitles when OSD is active (fix by Klaus Schmidinger)
+  - default quality when grabbing jpeg image (reported by Klaus Schmidinger)
+  - suppress buffer stall when clock is halted
+  - set clock latency target according omxplayer
+- known issues:
+  - direct switching from fast forward to fast rewind will freeze replay
+  - artifacts with StillImage() and PES recordings
+- missing:
+  - plugin specific option for video mode adaption
+  
+2014-02-10: Version 0.0.8
+-------------------------
+- new:
+  - image grabbing
+  - implemented proper handling in case of buffer stall
+  - reporting video size
+  - support letter box and center cut out set by VDR
+  - support video scaling
+- fixed:
+  - increased number of audio buffer to fix replay issues with PES recordings
+  - return correct number of audio bytes written from PlayAudio()
+  - fixed start up in audio only mode
+  - fixed still image with deinterlacer
+  - fixed crash during deinitialization
+  - fixed crash when copying 5.1 PCM audio
+  - use cThread::mutex for locking
+  - implement cOvgOsd::SetAreas() and cOvgOsd::SetActive()
+  - audio codec clean up, drop AAC-LATM and rename ADTS to AAC
+  - audio decoding thread clean up
+- known issues
+  - StillImage() will cause buffer stall
+  - artifacts with StillImage() and PES recordings
+  - speed to fast when fast replaying audio only recordings
+
+2013-12-30: Version 0.0.7
+-------------------------
+- new:
+  - support audio sampling rates other than 48kHz
+  - changed setting of trick speed with APIVERSNUM >= 20103
+  - added deinterlacer
+- fixed:
+  - improved audio parser
+  - fixed still image for H264 video
+  - mute audio render if volume is set to zero
+- missing:
+  - image grabbing
+  - video format/output options
+
+2013-12-16: Version 0.0.6a
+--------------------------
+- fixed:
+  - removed OMX mutex
+
+2013-12-15: Version 0.0.6
+-------------------------
+- new:
+  - still picture
+  - trick speeds
+- fixed:
+  - reworked audio detection and decoding, fixed several issues
+  - reworked stream starting behavior, fixed audio-/video-only play back
+  - fixed several issues with unsupported video codec (e.g. without MPEG2 key)
+  - improved fast forward/reverse mode
+  - several minor bugfixes
+- missing:
+  - deinterlacer
+  - image grabbing
+  - video format/output options
+
+2013-11-17: Version 0.0.5
+-------------------------
+- new:
+  - improved audio format detection (taken from softhddevice)
+  - separate thread for audio decoding
+- fixed:
+  - jump forward/backward in recordings
+  - several minor bugfixes
+- missing:
+  - still picture
+  - trick modes
+  - deinterlacer
+  - video format/output options
+
+2013-10-14: Version 0.0.4
+-------------------------
+- new:
+  - changed to libav for audio decoding
+  - added support multi-channel audio codecs
+  - added audio format/output options
+- fixed:
+  - removed drawing of black box in front of console which lead to malfunction
+    due to memory bandwidth problem. console blank out will be handled with
+    video format/output options in future versions.
+- missing:
+  - trick modes
+  - deinterlacer
+  - video format/output options
+  - much more...
+  
+2013-10-02: Version 0.0.3
+-------------------------
+- new:
+  - tracking number of free buffers and provide Poll() method
+- fixed:
+  - audio only mode
+  - replay start/stop/pause
+  - improved H264 detection
+  - blank out console
+- missing:
+  - trick modes
+  - other audio formats
+  - much more...
+  
+2013-09-29: Version 0.0.2
+-------------------------
+- new:
+  - volume control
+  - H264 support
+- fixed:
+  - added missing includes to Makefile
+  - PTS/OMX_TICKS conversion
+- missing:
+  - audio only play mode
+  - buffer handling for proper replay support
+  - other audio formats
+  - much more...
+
+2013-09-27: Version 0.0.1
+-------------------------
+initial prototype
+- limitations:
+  - video codec hard coded to MPEG2, output on HDMI
+  - audio codec hard coded to MP3, output on phone jack
+- tested:
+  - OSD
+  - SDTV live view and replay
+- missing:
+  - dynamic switching between MPEG2 and H264 video codec
+  - trick speeds
+  - much more...
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3d2167a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,184 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id: Makefile 2.18 2013/01/12 13:45:01 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 = rpihddevice
+
+### 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 directory environment:
+
+# Use package data if installed...otherwise assume we're under the VDR source directory:
+PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell PKG_CONFIG_PATH="$$PKG_CONFIG_PATH:../../.." pkg-config --variable=$(1) vdr))
+LIBDIR = $(call PKGCFG,libdir)
+LOCDIR = $(call PKGCFG,locdir)
+PLGCFG = $(call PKGCFG,plgcfg)
+#
+TMPDIR ?= /tmp
+
+### The compiler options:
+
+export CFLAGS   = $(call PKGCFG,cflags)
+export CXXFLAGS = $(call PKGCFG,cxxflags)
+
+### The version number of VDR's plugin API:
+
+APIVERSION = $(call PKGCFG,apiversion)
+
+### Allow user defined options to overwrite defaults:
+
+-include $(PLGCFG)
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### The name of the shared object file:
+
+SOFILE = libvdr-$(PLUGIN).so
+
+### Includes and Defines (add further entries here):
+
+DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+DEFINES += -DHAVE_LIBOPENMAX=2 -DOMX -DOMX_SKIP64BIT -DUSE_EXTERNAL_OMX -DHAVE_LIBBCM_HOST -DUSE_EXTERNAL_LIBBCM_HOST -DUSE_VCHIQ_ARM
+DEFINES += -Wno-psabi -Wno-write-strings -fpermissive
+
+CXXFLAGS += -D__STDC_CONSTANT_MACROS
+
+ILCDIR   =ilclient
+VCINCDIR =$(SDKSTAGE)/opt/vc/include
+VCLIBDIR =$(SDKSTAGE)/opt/vc/lib
+
+INCLUDES += -I$(ILCDIR) -I$(VCINCDIR) -I$(VCINCDIR)/interface/vcos/pthreads 
+INCLUDES += -I$(VCINCDIR)/interface/vmcs_host/linux
+ 
+LDLIBS  += -lbcm_host -lvcos -lvchiq_arm -lopenmaxil -lGLESv2 -lEGL -lpthread -lrt
+LDLIBS  += -Wl,--whole-archive $(ILCDIR)/libilclient.a -Wl,--no-whole-archive
+LDFLAGS += -L$(VCLIBDIR)
+
+DEBUG ?= 0
+ifeq ($(DEBUG), 1)
+    DEFINES += -DDEBUG
+endif
+
+DEBUG_BUFFERSTAT ?= 0
+ifeq ($(DEBUG_BUFFERSTAT), 1)
+    DEFINES += -DDEBUG_BUFFERSTAT
+endif
+
+DEBUG_BUFFERS ?= 0
+ifeq ($(DEBUG_BUFFERS), 1)
+    DEFINES += -DDEBUG_BUFFERS
+endif
+
+# ffmpeg/libav configuration
+ifdef EXT_LIBAV
+	LIBAV_PKGCFG = $(shell PKG_CONFIG_PATH=$(EXT_LIBAV)/lib/pkgconfig pkg-config $(1))
+else
+	LIBAV_PKGCFG = $(shell pkg-config $(1))
+endif
+
+LDLIBS   += $(call LIBAV_PKGCFG,--libs libavcodec) $(call LIBAV_PKGCFG,--libs libavformat)
+INCLUDES += $(call LIBAV_PKGCFG,--cflags libavcodec) $(call LIBAV_PKGCFG,--cflags libavformat)
+
+ifeq ($(call LIBAV_PKGCFG,--exists libswresample && echo 1), 1)
+	DEFINES  += -DHAVE_LIBSWRESAMPLE
+	LDLIBS   += $(call LIBAV_PKGCFG,--libs libswresample)
+	INCLUDES += $(call LIBAV_PKGCFG,--cflags libswresample)
+else
+ifeq ($(call LIBAV_PKGCFG,--exists libavresample && echo 1), 1)
+	DEFINES  += -DHAVE_LIBAVRESAMPLE
+	LDLIBS   += $(call LIBAV_PKGCFG,--libs libavresample)
+	INCLUDES += $(call LIBAV_PKGCFG,--cflags libavresample)
+endif
+endif
+
+LDLIBS   += $(shell pkg-config --libs freetype2)
+INCLUDES += $(shell pkg-config --cflags freetype2)
+
+### The object files (add further files here):
+
+ILCLIENT = $(ILCDIR)/libilclient.a
+OBJS = $(PLUGIN).o setup.o omx.o audio.o omxdevice.o ovgosd.o display.o
+
+### The main target:
+
+all: $(SOFILE) i18n
+
+### Implicit rules:
+
+%.o: %.c
+	$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
+
+### Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+	@$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR     = po
+I18Npo    = $(wildcard $(PODIR)/*.po)
+I18Nmo    = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
+I18Nmsgs  = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Npot   = $(PODIR)/$(PLUGIN).pot
+
+%.mo: %.po
+	msgfmt -c -o $@ $<
+
+$(I18Npot): $(wildcard *.c)
+	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<see README>' -o $@ `ls $^`
+
+%.po: $(I18Npot)
+	msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
+	@touch $@
+
+$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+	install -D -m644 $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmo) $(I18Npot)
+
+install-i18n: $(I18Nmsgs)
+
+### Targets:
+
+$(SOFILE): $(ILCLIENT) $(OBJS)
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) $(LDLIBS) -o $@
+
+$(ILCLIENT):
+	$(MAKE) --no-print-directory -C $(ILCDIR) all
+
+install-lib: $(SOFILE)
+	install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
+
+install: install-lib install-i18n
+
+dist: $(I18Npo) 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 $(PODIR)/*.mo $(PODIR)/*.pot
+	@-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~
+	$(MAKE) --no-print-directory -C $(ILCDIR) clean
+
+.PHONY:	cppcheck
+cppcheck:
+	@cppcheck --language=c++ --enable=all --suppress=unusedFunction -v -f .
diff --git a/README b/README
new file mode 100644
index 0000000..99ec55b
--- /dev/null
+++ b/README
@@ -0,0 +1,113 @@
+This is a "plugin" for the Video Disk Recorder (VDR).
+
+Written by:                  Thomas Reufer <thomas at reufer.ch>
+
+Project's homepage:          http://projects.vdr-developer.org/projects/plg-rpihddevice
+
+Latest version available at: git://projects.vdr-developer.org/vdr-plugin-rpihddevice.git
+
+See the file COPYING for license information.
+
+Description:
+
+  VDR HD output device for Raspberry Pi. The plugin makes use of the Raspberry
+  Pi's VideoCore GPU and provides a lightweight implementation for a VDR output
+  device.
+
+Features:
+
+  - MPEG-2 and H264 high-profile video codec up to 1080p30
+  - MPEG-1 Layer II, (E)AC-3, AAC and DTS audio codec at 32kHz, 44.1kHz or 48kHz
+    with 2.0 (Stereo) or 5.1 channels
+  - HDMI multi channel LPCM audio output
+  - HDMI digital audio pass-through
+  - Analog stereo audio output
+  - Box (letter-box/pillar-box), Crop and Stretch video display modes
+  - True color OSD with GPU support
+  - Video scaling and grabbing support
+
+Requirements:
+
+  - libavcodec, libavformat and libavutil for audio decoding, provided by ffmpeg
+    or libav
+  - libswresample when using ffmpeg-1.2 or newer
+  - libavresample when using libav-9 or newer
+  - freetype2 for GPU accelerated text drawing
+  - valid MPEG-2 license when watching MPEG-2 streams
+  - Raspberry Pi userland libraries: https://github.com/raspberrypi/userland
+  - Raspberry Pi firmware version of 2015/01/18 or newer
+  
+Install:
+
+  Get the source code either as archive or with git and compile like any other
+  VDR plugin:
+
+  $ cd /usr/src/vdr/PLUGINS/src
+  $ git clone git://projects.vdr-developer.org/vdr-plugin-rpihddevice.git rpihddevice
+  $ cd rpihddevice
+  $ make
+  $ make install
+  
+  If you want to link the plugin against a specific version of ffmpeg/libav, set
+  EXT_LIBAV accordingly when compiling the plugin:
+  
+  $ make EXT_LIBAV=/usr/src/ffmpeg-1.2.6
+  
+Usage:
+
+  To start the plugin, just add '-P rpihddevice' to the VDR command line.
+
+  The plugin simply adds two new dispmanx layers on top of the framebuffer, one 
+  for video and one for the OSD. The plugin does not clear the current console 
+  or change any video mode settings. So it's the user's choice, what's being 
+  displayed when no video is shown, e.g. during channel switches or for radio
+  channels.
+
+Options:
+
+  -d, --disable-osd  Disables creation of OSD layer and prevents the plugin of
+                     allocating any OSD related resources. If set, VDR's dummy
+                     OSD is used, when selecting rpihddevice as primary device.
+
+Plugin-Setup:
+
+  Resolution: Set video resolution. Possible values are: "default",
+  "follow video", "720x480", "720x576", "1280x720", "1920x1080"
+
+  Frame Rate: Set video frame rate. Possible values are: "default",
+  "follow video", "24p", "25p", "30p", "50i", "50p", "60i", "60p"
+
+  When set to "default", the resolution/frame rate will not be changed and the
+  plugin keeps the current setting of the framebuffer, normally set by the mode
+  number in /boot/config.txt or changed with 'tvservice'. To let the plugin
+  automatically set a value matching the current video stream, choose
+  "follow video". In general, video setting are only applied if both, resolution
+  and frame rate, fits to a video mode supported by the connected device,
+  indicated by its EDID.
+
+  EDID information can by overridden with various settings in /boot/config.txt,
+  see the official documentation for further information:
+  https://www.raspberrypi.org/documentation/configuration/config-txt.md
+
+  Video Framing: Determines how the video frame is displayed on the screen in
+  case the aspect ratio differs. "box" and "cut" will preserve the video's
+  aspect ratio, while "box" (often called "letter box", however "pillar box" is
+  used to show 4:3 videos on a wide screen) will fit the image on the 
+  screen by adding transparent borders. On the other hand, "cut" is cropping 
+  away the overlapping part of the video, so the entire display area is filled.
+  When setting to "stretch", the videos' aspect ratio is adapted to the screen
+  and the resulting image might appear distorted.
+  
+  Audio Port: Set the audio output port to "analog" or "HDMI". When set to
+  analog out, multi channel audio is sampled down to stereo.
+  
+  Digital Audio Format: Specify the audio format when using the HDMI port as
+  output. If set to "pass through", (E)AC-3 and DTS audio can be decoded by the
+  connected HDMI device, if EDID indicates specific codec support. For local
+  decoding, select "mutli channel PCM" or "Stereo PCM" if additional stereo
+  dowmix of mutli channel audio is desired.
+  
+  Use GPU accelerated OSD: Use GPU capabilities to draw the on screen display.
+  Disable acceleration in case of OSD problems to use VDR's internal rendering
+  and report error to the author.
+  
\ No newline at end of file
diff --git a/audio.c b/audio.c
new file mode 100644
index 0000000..7c7b460
--- /dev/null
+++ b/audio.c
@@ -0,0 +1,1415 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include "audio.h"
+#include "setup.h"
+#include "omx.h"
+
+#include <vdr/tools.h>
+#include <vdr/remux.h>
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavutil/log.h>
+#include <libavutil/opt.h>
+
+// ffmpeg's resampling
+#ifdef HAVE_LIBSWRESAMPLE
+#  include <libswresample/swresample.h>
+#  define DO_RESAMPLE
+#endif
+
+// libav's resampling
+#ifdef HAVE_LIBAVRESAMPLE
+#  include <libavresample/avresample.h>
+#  include <libavutil/samplefmt.h>
+#  define DO_RESAMPLE
+#  define SwrContext AVAudioResampleContext
+#  define swr_alloc  avresample_alloc_context
+#  define swr_init   avresample_open
+#  define swr_free   avresample_free
+#  define swr_convert(ctx, dst, out_cnt, src, in_cnt) \
+		avresample_convert(ctx, dst, 0, out_cnt, (uint8_t**)src, 0, in_cnt)
+#endif
+
+// legacy libavcodec
+#if LIBAVCODEC_VERSION_MAJOR < 55
+#  define av_frame_alloc   avcodec_alloc_frame
+#  define av_frame_free    avcodec_free_frame
+#  define av_frame_unref   avcodec_get_frame_defaults
+#  define AV_CODEC_ID_MP3  CODEC_ID_MP3
+#  define AV_CODEC_ID_AC3  CODEC_ID_AC3
+#  define AV_CODEC_ID_EAC3 CODEC_ID_EAC3
+#  define AV_CODEC_ID_AAC  CODEC_ID_AAC
+#  define AV_CODEC_ID_DTS  CODEC_ID_DTS
+#endif
+
+#if LIBAVCODEC_VERSION_MAJOR < 54
+#  define avcodec_free_frame av_free
+#endif
+
+// prevent depreciated warnings for >ffmpeg-1.2.x and >libav-9.x
+#if LIBAVCODEC_VERSION_MAJOR > 54
+#  undef FF_API_REQUEST_CHANNELS
+#endif
+}
+
+#include <queue>
+#include <string.h>
+
+#define AVPKT_BUFFER_SIZE (KILOBYTE(256))
+
+class cRpiAudioDecoder::cParser
+{
+
+public:
+
+	cParser() :
+		m_mutex(new cMutex()),
+		m_codec(cAudioCodec::eInvalid),
+		m_channels(0),
+		m_samplingRate(0),
+		m_size(0),
+		m_parsed(true)
+	{
+	}
+
+	~cParser()
+	{
+		delete m_mutex;
+	}
+
+	AVPacket* Packet(void)
+	{
+		return &m_packet;
+	}
+
+	cAudioCodec::eCodec GetCodec(void)
+	{
+		if (!m_parsed)
+			Parse();
+		return m_codec;
+	}
+
+	unsigned int GetChannels(void)
+	{
+		if (!m_parsed)
+			Parse();
+		return m_channels;
+	}
+
+	unsigned int GetSamplingRate(void)
+	{
+		if (!m_parsed)
+			Parse();
+		return m_samplingRate;
+	}
+
+	unsigned int GetFrameSize(void)
+	{
+		if (!m_parsed)
+			Parse();
+		return m_packet.size;
+	}
+
+	uint64_t GetPts(void)
+	{
+		uint64_t pts = 0;
+		m_mutex->Lock();
+
+		if (!m_ptsQueue.empty())
+			pts = m_ptsQueue.front()->pts;
+
+		m_mutex->Unlock();
+		return pts;
+	}
+
+	unsigned int GetFreeSpace(void)
+	{
+		return AVPKT_BUFFER_SIZE - m_size - FF_INPUT_BUFFER_PADDING_SIZE;
+	}
+
+	bool Empty(void)
+	{
+		if (!m_parsed)
+			Parse();
+		return m_packet.size == 0;
+	}
+
+	int Init(void)
+	{
+		if (!av_new_packet(&m_packet, AVPKT_BUFFER_SIZE))
+		{
+			Reset();
+			return 0;
+		}
+		return -1;
+	}
+
+	int DeInit(void)
+	{
+		av_free_packet(&m_packet);
+		return 0;
+	}
+
+	void Reset(void)
+	{
+		m_mutex->Lock();
+		m_codec = cAudioCodec::eInvalid;
+		m_channels = 0;
+		m_samplingRate = 0;
+		m_packet.size = 0;
+		m_size = 0;
+		m_parsed = true; // parser is empty, no need for parsing
+		memset(m_packet.data, 0, FF_INPUT_BUFFER_PADDING_SIZE);
+
+		while (!m_ptsQueue.empty())
+		{
+			delete m_ptsQueue.front();
+			m_ptsQueue.pop();
+		}
+		m_mutex->Unlock();
+	}
+
+	bool Append(const unsigned char *data, uint64_t pts, unsigned int length)
+	{
+		m_mutex->Lock();
+		bool ret = true;
+
+		if (m_size + length + FF_INPUT_BUFFER_PADDING_SIZE > AVPKT_BUFFER_SIZE)
+			ret = false;
+		else
+		{
+			memcpy(m_packet.data + m_size, data, length);
+			m_size += length;
+			memset(m_packet.data + m_size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
+
+			Pts* entry = new Pts(pts, length);
+			m_ptsQueue.push(entry);
+
+			m_parsed = false;
+		}
+		m_mutex->Unlock();
+		return ret;
+	}
+
+	void Shrink(unsigned int length, bool retainPts = false)
+	{
+		m_mutex->Lock();
+
+		if (length < m_size)
+		{
+			memmove(m_packet.data, m_packet.data + length, m_size - length);
+			m_size -= length;
+			memset(m_packet.data + m_size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
+
+			while (!m_ptsQueue.empty() && length)
+			{
+				if (m_ptsQueue.front()->length <= length)
+				{
+					length -= m_ptsQueue.front()->length;
+					delete m_ptsQueue.front();
+					m_ptsQueue.pop();
+				}
+				else
+				{
+					// clear current PTS since it's not valid anymore after
+					// shrinking the packet
+					if (!retainPts)
+						m_ptsQueue.front()->pts = 0;
+
+					m_ptsQueue.front()->length -= length;
+					length = 0;
+				}
+			}
+
+			m_parsed = false;
+		}
+		else
+			Reset();
+
+		m_mutex->Unlock();
+	}
+	
+private:
+
+	cParser(const cParser&);
+	cParser& operator= (const cParser&);
+
+	// Check format of first audio packet in buffer. If format has been
+	// guessed, but packet is not yet complete, codec is set with a length
+	// of 0. Once the buffer contains either the exact amount of expected
+	// data or another valid packet start after the first frame, packet
+	// size is set to the first frame length.
+	// Valid packets are always moved to the buffer start, if no valid
+	// audio frame has been found, packet gets cleared.
+
+	void Parse()
+	{
+		m_mutex->Lock();
+
+		cAudioCodec::eCodec codec = cAudioCodec::eInvalid;
+		unsigned int channels = 0;
+		unsigned int offset = 0;
+		unsigned int frameSize = 0;
+		unsigned int samplingRate = 0;
+
+		while (m_size - offset >= 4)
+		{
+			// 0xFFE...      MPEG audio
+			// 0x0B77...     (E)AC-3 audio
+			// 0xFFF...      AAC audio
+			// 0x7FFE8001... DTS audio
+			// PCM audio can't be found
+
+			const uint8_t *p = m_packet.data + offset;
+			unsigned int n = m_size - offset;
+
+			switch (FastCheck(p))
+			{
+			case cAudioCodec::eMPG:
+				if (MpegCheck(p, n, frameSize, channels, samplingRate))
+					codec = cAudioCodec::eMPG;
+				break;
+
+			case cAudioCodec::eAC3:
+				if (Ac3Check(p, n, frameSize, channels, samplingRate))
+				{
+					codec = cAudioCodec::eAC3;
+					if (n > 5 && p[5] > (10 << 3))
+						codec = cAudioCodec::eEAC3;
+				}
+				break;
+			case cAudioCodec::eAAC:
+				if (AdtsCheck(p, n, frameSize, channels, samplingRate))
+					codec = cAudioCodec::eAAC;
+				break;
+
+			case cAudioCodec::eDTS:
+				if (DtsCheck(p, n, frameSize, channels, samplingRate))
+					codec = cAudioCodec::eDTS;
+				break;
+
+			default:
+				break;
+			}
+
+			if (codec != cAudioCodec::eInvalid)
+			{
+				// if there is enough data in buffer, check if predicted next
+				// frame start is valid
+				if (n < frameSize + 4 ||
+						FastCheck(p + frameSize) != cAudioCodec::eInvalid)
+				{
+					// if codec has been detected but buffer does not yet
+					// contains a complete frame, set size to zero to prevent
+					// frame from being decoded
+					if (frameSize > n)
+						frameSize = 0;
+
+					break;
+				}
+			}
+
+			++offset;
+		}
+
+		if (offset)
+		{
+			DBG("audio parser skipped %u of %u bytes", offset, m_size);
+			Shrink(offset, true);
+		}
+
+		if (codec != cAudioCodec::eInvalid)
+		{
+			m_codec = codec;
+			m_channels = channels;
+			m_samplingRate = samplingRate;
+			m_packet.size = frameSize;
+		}
+		else
+			m_packet.size = 0;
+
+		m_parsed = true;
+		m_mutex->Unlock();
+	}
+
+	struct Pts
+	{
+		Pts(uint64_t _pts, unsigned int _length)
+			: pts(_pts), length(_length) { };
+
+		uint64_t 		pts;
+		unsigned int 	length;
+	};
+
+	cMutex*				m_mutex;
+	AVPacket 			m_packet;
+	cAudioCodec::eCodec m_codec;
+	unsigned int		m_channels;
+	unsigned int		m_samplingRate;
+	unsigned int		m_size;
+	std::queue<Pts*> 	m_ptsQueue;
+	bool				m_parsed;
+
+	/* ---------------------------------------------------------------------- */
+	/*     audio codec parser helper functions, based on vdr-softhddevice     */
+	/* ---------------------------------------------------------------------- */
+
+	static const uint16_t BitRateTable[2][3][16];
+	static const uint16_t MpegSampleRateTable[4];
+	static const uint32_t Mpeg4SampleRateTable[16];
+	static const uint16_t Ac3SampleRateTable[4];
+	static const uint16_t Ac3FrameSizeTable[38][3];
+	static const uint32_t DtsSampleRateTable[16];
+
+	static cAudioCodec::eCodec FastCheck(const uint8_t *p)
+	{
+		return 	FastMpegCheck(p)  ? cAudioCodec::eMPG :
+				FastAc3Check (p)  ? cAudioCodec::eAC3 :
+				FastAdtsCheck(p)  ? cAudioCodec::eAAC :
+				FastDtsCheck (p)  ? cAudioCodec::eDTS :
+									cAudioCodec::eInvalid;
+	}
+
+	///
+	///	Fast check for MPEG audio.
+	///
+	///	0xFFE... MPEG audio
+	///
+	static bool FastMpegCheck(const uint8_t *p)
+	{
+		if (p[0] != 0xFF)			// 11bit frame sync
+			return false;
+		if ((p[1] & 0xE0) != 0xE0)
+			return false;
+		if ((p[1] & 0x18) == 0x08)	// version ID - 01 reserved
+			return false;
+		if (!(p[1] & 0x06))			// layer description - 00 reserved
+			return false;
+		if ((p[2] & 0xF0) == 0xF0)	// bit rate index - 1111 reserved
+			return false;
+		if ((p[2] & 0x0C) == 0x0C)	// sampling rate index - 11 reserved
+			return false;
+		return true;
+	}
+
+	///	Check for MPEG audio.
+	///
+	///	0xFFEx already checked.
+	///
+	///	From: http://www.mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
+	///
+	///	AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
+	///
+	///	o a 11x Frame sync
+	///	o b 2x	MPEG audio version (2.5, reserved, 2, 1)
+	///	o c 2x	Layer (reserved, III, II, I)
+	///	o e 2x	BitRate index
+	///	o f 2x	SampleRate index (41000, 48000, 32000, 0)
+	///	o g 1x	Padding bit
+	/// o h 1x  Private bit
+	/// o i 2x  Channel mode
+	///	o ..	Doesn't care
+	///
+	///	frame length:
+	///	Layer I:
+	///		FrameLengthInBytes = (12 * BitRate / SampleRate + Padding) * 4
+	///	Layer II & III:
+	///		FrameLengthInBytes = 144 * BitRate / SampleRate + Padding
+	///
+	static bool MpegCheck(const uint8_t *p, unsigned int size,
+			unsigned int &frameSize, unsigned int &channels,
+			unsigned int &samplingRate)
+	{
+		frameSize = size;
+		if (size < 4)
+			return true;
+
+		int cmode = (p[3] >> 6) & 0x03;
+		int mpeg2 = !(p[1] & 0x08) && (p[1] & 0x10);
+		int mpeg25 = !(p[1] & 0x08) && !(p[1] & 0x10);
+		int layer = 4 - ((p[1] >> 1) & 0x03);
+		int padding = (p[2] >> 1) & 0x01;
+
+		// channel mode = [ stereo, joint stereo, dual channel, mono]
+		channels = cmode == 0x03 ? 1 : 2;
+
+		samplingRate = MpegSampleRateTable[(p[2] >> 2) & 0x03];
+		if (!samplingRate)
+			return false;
+
+		samplingRate >>= mpeg2;		// MPEG 2 half rate
+		samplingRate >>= mpeg25;	// MPEG 2.5 quarter rate
+
+		int bit_rate =
+				BitRateTable[mpeg2 | mpeg25][layer - 1][(p[2] >> 4) & 0x0F];
+		if (!bit_rate)
+			return false;
+
+		switch (layer)
+		{
+		case 1:
+			frameSize = (12000 * bit_rate) / samplingRate;
+			frameSize = (frameSize + padding) * 4;
+			break;
+		case 2:
+		case 3:
+		default:
+			frameSize = (144000 * bit_rate) / samplingRate;
+			frameSize = frameSize + padding;
+			break;
+		}
+		return true;
+	}
+
+	///
+	///	Fast check for (E-)AC-3 audio.
+	///
+	///	0x0B77... AC-3 audio
+	///
+	static bool FastAc3Check(const uint8_t *p)
+	{
+		if (p[0] != 0x0B)			// 16bit sync
+			return false;
+		if (p[1] != 0x77)
+			return false;
+		return true;
+	}
+
+	///
+	///	Check for (E-)AC-3 audio.
+	///
+	///	0x0B77xxxxxx already checked.
+	///
+	///	o AC-3 Header
+	///	AAAAAAAA AAAAAAAA BBBBBBBB BBBBBBBB CCDDDDDD EEEEEFFF GGGxxxxx
+	///
+	///	o a 16x Frame sync, always 0x0B77
+	///	o b 16x CRC 16
+	///	o c 2x	Sample rate ( 48000, 44100, 32000, reserved )
+	///	o d 6x	Frame size code
+	///	o e 5x	Bit stream ID
+	///	o f 3x	Bit stream mode
+	/// o g 3x  Audio coding mode
+	///
+	///	o E-AC-3 Header
+	///	AAAAAAAA AAAAAAAA BBCCCDDD DDDDDDDD EEFFGGGH IIIII...
+	///
+	///	o a 16x Frame sync, always 0x0B77
+	///	o b 2x	Frame type
+	///	o c 3x	Sub stream ID
+	///	o d 11x Frame size - 1 in words
+	///	o e 2x	Frame size code
+	///	o f 2x	Frame size code 2
+	/// o g 3x  Channel mode
+	/// 0 h 1x  LFE on
+	///
+	static bool Ac3Check(const uint8_t *p, unsigned int size,
+			unsigned int &frameSize, unsigned int &channels,
+			unsigned int &samplingRate)
+	{
+		frameSize = size;
+		if (size < 7)
+			return true;
+
+		int acmod;
+		bool lfe;
+		int fscod = (p[4] & 0xC0) >> 6;
+
+		samplingRate = Ac3SampleRateTable[fscod];
+
+		if (p[5] > (10 << 3))		// E-AC-3
+		{
+			if (fscod == 0x03)
+			{
+				int fscod2 = (p[4] & 0x30) >> 4;
+				if (fscod2 == 0x03)
+					return false;		// invalid fscod & fscod2
+
+				samplingRate = Ac3SampleRateTable[fscod2] / 2;
+			}
+
+			acmod = (p[4] & 0x0E) >> 1;	// number of channels, LFE excluded
+			lfe = p[4] & 0x01;
+
+			frameSize = ((p[2] & 0x07) << 8) + p[3] + 1;
+			frameSize *= 2;
+		}
+		else						// AC-3
+		{
+			if (fscod == 0x03)		// invalid sample rate
+				return false;
+
+			int frmsizcod = p[4] & 0x3F;
+			if (frmsizcod > 37)		// invalid frame size
+				return false;
+
+			acmod = p[6] >> 5;		// number of channels, LFE excluded
+
+			int lfe_bptr = 51;		// position of LFE bit in header for 2.0
+			if ((acmod & 0x01) && (acmod != 0x01))
+				lfe_bptr += 2;		// skip center mix level
+			if (acmod & 0x04)
+				lfe_bptr += 2;		// skip surround mix level
+			if (acmod == 0x02)
+				lfe_bptr += 2;		// skip surround mode
+			lfe = (p[lfe_bptr / 8] & (1 << (7 - (lfe_bptr % 8))));
+
+			// invalid is checked above
+			frameSize = Ac3FrameSizeTable[frmsizcod][fscod] * 2;
+		}
+
+		channels =
+			acmod == 0x00 ? 2 : 	// Ch1, Ch2
+			acmod == 0x01 ? 1 : 	// C
+			acmod == 0x02 ? 2 : 	// L, R
+			acmod == 0x03 ? 3 : 	// L, C, R
+			acmod == 0x04 ? 3 : 	// L, R, S
+			acmod == 0x05 ? 4 : 	// L, C, R, S
+			acmod == 0x06 ? 4 : 	// L, R, RL, RR
+			acmod == 0x07 ? 5 : 0;	// L, C, R, RL, RR
+
+		if (lfe) channels++;
+		return true;
+	}
+
+#if 0
+	///
+	///	Fast check for AAC LATM audio.
+	///
+	///	0x56E... AAC LATM audio
+	///
+	static bool FastLatmCheck(const uint8_t *p)
+	{
+		if (p[0] != 0x56)			// 11bit sync
+			return false;
+		if ((p[1] & 0xE0) != 0xE0)
+			return false;
+		return true;
+	}
+
+	///
+	///	Check for AAC LATM audio.
+	///
+	///	0x56Exxx already checked.
+	///
+	static bool LatmCheck(const uint8_t *p, unsigned int size,
+			unsigned int &frameSize, unsigned int &channels,
+			unsigned int &samplingRate)
+	{
+		frameSize = size;
+		if (size < 3)
+			return true;
+
+		// to do: determine channels
+		channels = 2;
+
+		// to do: determine sampling rate
+		samplingRate = 48000;
+
+		// 13 bit frame size without header
+		frameSize = ((p[1] & 0x1F) << 8) + p[2];
+		frameSize += 3;
+		return true;
+	}
+#endif
+	
+	///
+	///	Fast check for ADTS Audio Data Transport Stream.
+	///
+	///	0xFFF...  ADTS audio
+	///
+	static bool FastAdtsCheck(const uint8_t *p)
+	{
+		if (p[0] != 0xFF)			// 12bit sync
+			return false;
+		if ((p[1] & 0xF6) != 0xF0)	// sync + layer must be 0
+			return false;
+		if ((p[2] & 0x3C) == 0x3C)	// sampling frequency index != 15
+			return false;
+		return true;
+	}
+
+	///
+	///	Check for ADTS Audio Data Transport Stream.
+	///
+	///	0xFFF already checked.
+	///
+	///	AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP
+	///	(QQQQQQQQ QQQQQQQ)
+	///
+	///	o A*12	sync word 0xFFF
+	///	o B*1	MPEG Version: 0 for MPEG-4, 1 for MPEG-2
+	///	o C*2	layer: always 0
+	///	o ..
+	///	o F*4	sampling frequency index (15 is invalid)
+	///	o ..
+	/// o H*3	MPEG-4 channel configuration
+	/// o ...
+	///	o M*13	frame length
+	///
+	static bool AdtsCheck(const uint8_t *p, unsigned int size,
+			unsigned int &frameSize, unsigned int &channels,
+			unsigned int &samplingRate)
+	{
+		frameSize = size;
+		if (size < 6)
+			return true;
+
+		samplingRate = Mpeg4SampleRateTable[(p[2] >> 2) & 0x0F];
+
+		frameSize = (p[3] & 0x03) << 11;
+		frameSize |= (p[4] & 0xFF) << 3;
+		frameSize |= (p[5] & 0xE0) >> 5;
+
+	    int cConf = (p[2] & 0x01) << 7;
+	    cConf |= (p[3] & 0xC0) >> 6;
+	    channels =
+	    	cConf == 0x00 ? 0 : // defined in AOT specific config
+			cConf == 0x01 ? 1 : // C
+	    	cConf == 0x02 ? 2 : // L, R
+	    	cConf == 0x03 ? 3 : // C, L, R
+	    	cConf == 0x04 ? 4 : // C, L, R, RC
+	    	cConf == 0x05 ? 5 : // C, L, R, RL, RR
+	    	cConf == 0x06 ? 6 : // C, L, R, RL, RR, LFE
+	    	cConf == 0x07 ? 8 : // C, L, R, SL, SR, RL, RR, LFE
+				0;
+
+		if (!samplingRate || !channels)
+			return false;
+
+	    return true;
+	}
+
+	///
+	///	Fast check for DTS Audio Data Transport Stream.
+	///
+	///	0x7FFE8001....  DTS audio
+	///
+	static bool FastDtsCheck(const uint8_t *p)
+	{
+		if (p[0] != 0x7F)			// 32bit sync
+			return false;
+		if (p[1] != 0xFE)
+			return false;
+		if (p[2] != 0x80)
+			return false;
+		if (p[3] != 0x01)
+			return false;
+		return true;
+	}
+
+	///
+	///	Check for DTS Audio Data Transport Stream.
+	///
+	///	0x7FFE8001 already checked.
+	///
+	///	AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA BCCCCCDE EEEEEEFF FFFFFFFF FFFFGGGG
+	/// GGHHHHII IIIJKLMN OOOPQRRS TTTTTTTT TTTTTTTT UVVVVWWX XXYZaaaa
+	///
+	///	o A*32	sync word 0x7FFE8001
+	///	o B*1   frame type
+	///	o C*5   deficit sample count
+	///	o D*1   CRC present flag
+	///	o E*7   number of PCM sample blocks
+	///	o F*14  primary frame size
+	///	o G*6   audio channel arrangement
+	///	o H*4   core audio sampling frequency
+	///	o I*5   transmission bit rate
+	///	o J*1   embedded downmix enabled
+	///	o K*1   embedded dynamic range flag
+	///	o L*1   embedded time stamp flag
+	///	o M*1   auxiliary data flag
+	///	o N*1   HDCD
+	///	o O*3   extension audio descriptor flag
+	///	o P*1   extended coding flag
+	///	o Q*1   audio sync word insertion flag
+	///	o R*2   low frequency effects flag
+	///	o S*1   predictor history flag
+	///	o T*16  header CRC check (if CRC present flag set)
+	///	o U*1   multi rate interpolator switch
+	///	o V*4   encoder software revision
+	///	o W*2   copy history
+	///	o X*3   source PCM resolution
+	///	o Y*1   front sum/difference flag
+	///	o Z*1   surrounds sum/difference flag
+	///	o a*4   dialog normalization parameter
+	///
+	static bool DtsCheck(const uint8_t *p, unsigned int size,
+			unsigned int &frameSize, unsigned int &channels,
+			unsigned int &samplingRate)
+	{
+		frameSize = size;
+		if (size < 11)
+			return true;
+
+		frameSize = ((p[5] & 0x03) << 12) + (p[6] << 4) + ((p[7] & 0xF0) >> 4);
+		frameSize++;
+
+		samplingRate = DtsSampleRateTable[(p[8] & 0x3C) >> 2];
+
+		int amode = ((p[7] & 0x0F) << 2) + ((p[8] & 0xC0) >> 6);
+		channels =
+			amode == 0x00 ? 1 : 	// mono
+			amode == 0x02 ? 2 : 	// L, R
+			amode == 0x03 ? 2 : 	// (L + R), (L - R)
+			amode == 0x04 ? 2 : 	// LT, RT
+			amode == 0x05 ? 3 : 	// L, R, C
+			amode == 0x06 ? 3 : 	// L, R, S
+			amode == 0x08 ? 4 : 	// L, R, RL, RR
+			amode == 0x09 ? 5 : 0;	// L, C, R, RL, RR
+
+		if (!samplingRate || !channels)
+			return false;
+
+		if (p[10] & 0x06) channels++;
+		return true;
+	}
+};
+
+///
+///	MPEG bit rate table.
+///
+///	BitRateTable[Version][Layer][Index]
+///
+const uint16_t cRpiAudioDecoder::cParser::BitRateTable[2][3][16] =
+{
+	{	// MPEG Version 1
+		{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0},
+		{0, 32, 48, 56,  64,  80,  96, 112, 128, 160, 192, 224, 256, 320, 384, 0},
+		{0, 32, 40, 48,  56,  64,  80,  96, 112, 128, 160, 192, 224, 256, 320, 0}
+	},
+	{	// MPEG Version 2 & 2.5
+		{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0},
+		{0,  8, 16, 24, 32, 40, 48,  56,  64,  80,  96, 112, 128, 144, 160, 0},
+		{0,  8, 16, 24, 32, 40, 48,  56,  64,  80,  96, 112, 128, 144, 160, 0}
+	}
+};
+
+///
+///	MPEG sample rate table.
+///
+const uint16_t cRpiAudioDecoder::cParser::MpegSampleRateTable[4] =
+	{ 44100, 48000, 32000, 0 };
+
+///
+///	MPEG-4 sample rate table.
+///
+const uint32_t cRpiAudioDecoder::cParser::Mpeg4SampleRateTable[16] = {
+		96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
+		16000, 12000, 11025,  8000,  7350,     0,     0,     0
+};
+
+///
+///	AC-3 sample rate table.
+///
+const uint16_t cRpiAudioDecoder::cParser::Ac3SampleRateTable[4] =
+	{ 48000, 44100, 32000, 0 };
+
+///
+///	Possible AC-3 frame sizes.
+///
+///	from ATSC A/52 table 5.18 frame size code table.
+///
+const uint16_t cRpiAudioDecoder::cParser::Ac3FrameSizeTable[38][3] =
+{
+	{  64,   69,   96}, {  64,   70,   96}, {  80,   87,  120}, { 80,  88,  120},
+	{  96,  104,  144}, {  96,  105,  144}, { 112,  121,  168}, {112, 122,  168},
+	{ 128,  139,  192}, { 128,  140,  192}, { 160,  174,  240}, {160, 175,  240},
+	{ 192,  208,  288}, { 192,  209,  288}, { 224,  243,  336}, {224, 244,  336},
+	{ 256,  278,  384}, { 256,  279,  384}, { 320,  348,  480}, {320, 349,  480},
+	{ 384,  417,  576}, { 384,  418,  576}, { 448,  487,  672}, {448, 488,  672},
+	{ 512,  557,  768}, { 512,  558,  768}, { 640,  696,  960}, {640, 697,  960},
+	{ 768,  835, 1152}, { 768,  836, 1152}, { 896,  975, 1344}, {896, 976, 1344},
+	{1024, 1114, 1536}, {1024, 1115, 1536}, {1152, 1253, 1728},
+	{1152, 1254, 1728}, {1280, 1393, 1920}, {1280, 1394, 1920},
+};
+
+///
+///	DTS sample rate table.
+///
+const uint32_t cRpiAudioDecoder::cParser::DtsSampleRateTable[16] =
+	{ 0,  8000, 16000, 32000, 64000,
+	  0, 11025, 22050, 44100, 88200,
+	  0, 12000, 24000, 48000, 96000, 0 };
+
+/* ------------------------------------------------------------------------- */
+
+#define AV_CH_LAYOUT(ch) ( \
+		ch == 1 ? AV_CH_LAYOUT_MONO    : \
+		ch == 2 ? AV_CH_LAYOUT_STEREO  : \
+		ch == 3 ? AV_CH_LAYOUT_2POINT1 : \
+		ch == 6 ? AV_CH_LAYOUT_5POINT1 : 0)
+
+#define AV_SAMPLE_STR(fmt) ( \
+		fmt == AV_SAMPLE_FMT_U8   ? "U8"             : \
+		fmt == AV_SAMPLE_FMT_S16  ? "S16"            : \
+		fmt == AV_SAMPLE_FMT_S32  ? "S32"            : \
+		fmt == AV_SAMPLE_FMT_FLT  ? "float"          : \
+		fmt == AV_SAMPLE_FMT_DBL  ? "double"         : \
+		fmt == AV_SAMPLE_FMT_U8P  ? "U8, planar"     : \
+		fmt == AV_SAMPLE_FMT_S16P ? "S16, planar"    : \
+		fmt == AV_SAMPLE_FMT_S32P ? "S32, planar"    : \
+		fmt == AV_SAMPLE_FMT_FLTP ? "float, planar"  : \
+		fmt == AV_SAMPLE_FMT_DBLP ? "double, planar" : "unknown")
+
+/* ------------------------------------------------------------------------- */
+
+class cRpiAudioRender
+{
+
+public:
+
+	cRpiAudioRender(cOmx *omx) :
+		m_mutex(new cMutex()),
+		m_omx(omx),
+		m_port(cRpiAudioPort::eLocal),
+		m_codec(cAudioCodec::eInvalid),
+		m_inChannels(0),
+		m_outChannels(0),
+		m_samplingRate(0),
+		m_frameSize(0),
+		m_configured(false),
+		m_running(false),
+#ifdef DO_RESAMPLE
+		m_resample(0),
+		m_resamplerConfigured(false),
+#endif
+		m_pcmSampleFormat(AV_SAMPLE_FMT_NONE),
+		m_pts(0)
+	{
+	}
+
+	~cRpiAudioRender()
+	{
+		Flush();
+#ifdef DO_RESAMPLE
+		swr_free(&m_resample);
+#endif
+		delete m_mutex;
+	}
+
+	int WriteSamples(uint8_t** data, int samples, uint64_t pts,
+			AVSampleFormat sampleFormat = AV_SAMPLE_FMT_NONE)
+	{
+		if (!Ready())
+			return 0;
+
+		m_mutex->Lock();
+		int copied = 0;
+
+		if (sampleFormat == AV_SAMPLE_FMT_NONE)
+		{
+			// pass through
+			while (samples > copied)
+			{
+				OMX_BUFFERHEADERTYPE *buf = m_omx->GetAudioBuffer(pts);
+				if (!buf)
+					break;
+
+				unsigned int len = samples - copied;
+				if (len > buf->nAllocLen)
+					len = buf->nAllocLen;
+
+				memcpy(buf->pBuffer, *data + copied, len);
+				buf->nFilledLen = len;
+
+				if (!m_omx->EmptyAudioBuffer(buf))
+					break;
+
+				copied += len;
+				pts = 0;
+			}
+		}
+		else
+		{
+#ifdef DO_RESAMPLE
+			// local decode, do resampling
+			if (!m_resamplerConfigured || m_pcmSampleFormat != sampleFormat)
+			{
+				m_pcmSampleFormat = sampleFormat;
+				ApplyResamplerSettings();
+			}
+			if (m_resample)
+			{
+				m_pts = pts ? pts : m_pts;
+				OMX_BUFFERHEADERTYPE *buf = m_omx->GetAudioBuffer(m_pts);
+				if (buf)
+				{
+					if (buf->nAllocLen >= (samples * m_outChannels *
+						av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)))
+					{
+						uint8_t *dst[] = { buf->pBuffer };
+						int copiedSamples = swr_convert(m_resample,
+							dst, samples, (const uint8_t **)data, samples);
+
+						buf->nFilledLen = av_samples_get_buffer_size(NULL,
+							m_outChannels, copiedSamples, AV_SAMPLE_FMT_S16, 1);
+
+						m_pts += copiedSamples * 90000 / m_samplingRate;
+					}
+					copied = m_omx->EmptyAudioBuffer(buf) ? samples : 0;
+				}
+			}
+#else
+			// local decode, no resampling
+			m_pts = pts ? pts : m_pts;
+			OMX_BUFFERHEADERTYPE *buf = m_omx->GetAudioBuffer(m_pts);
+			if (buf)
+			{
+				unsigned int size = samples * m_outChannels *
+						av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
+				if (buf->nAllocLen >= size)
+				{
+					memcpy(buf->pBuffer, *data, size);
+					buf->nFilledLen = size;
+					m_pts += samples * 90000 / m_samplingRate;
+				}
+				copied = m_omx->EmptyAudioBuffer(buf) ? samples : 0;
+			}
+#endif
+		}
+		m_mutex->Unlock();
+		return copied;
+	}
+
+	void Flush(void)
+	{
+		m_mutex->Lock();
+		if (m_running)
+			m_omx->StopAudio();
+		m_configured = false;
+		m_running = false;
+		m_pts = 0;
+		m_mutex->Unlock();
+	}
+
+	void SetCodec(cAudioCodec::eCodec codec, unsigned int channels,
+			unsigned int samplingRate, unsigned int frameSize)
+	{
+		m_mutex->Lock();
+		if (codec != cAudioCodec::eInvalid && channels > 0)
+		{
+			m_inChannels = channels;
+			cRpiAudioPort::ePort newPort = cRpiSetup::GetAudioPort();
+			cAudioCodec::eCodec newCodec = cAudioCodec::ePCM;
+
+			DLOG("new audio codec: %dch %s", channels, cAudioCodec::Str(codec));
+
+			if (newPort == cRpiAudioPort::eHDMI)
+			{
+				// check if pass through is possible
+				if (cRpiSetup::IsAudioFormatSupported(codec, channels,
+							samplingRate))
+					newCodec = codec;
+
+				// check for multi channel PCM, stereo downmix if not supported
+				else if (!cRpiSetup::IsAudioFormatSupported(cAudioCodec::ePCM,
+						channels, samplingRate))
+					channels = 2;
+			}
+			else
+				channels = 2;
+
+			// if the user changes the port, this should change immediately
+			if (newPort != m_port)
+				Flush();
+
+			// save new settings to be applied when render is ready
+			if (newPort != m_port || m_codec != newCodec ||
+					m_outChannels != channels || m_samplingRate != samplingRate)
+			{
+				m_configured = false;
+				m_port = newPort;
+				m_codec = newCodec;
+				m_outChannels = channels;
+				m_samplingRate = samplingRate;
+				m_frameSize = frameSize;
+			}
+#ifdef DO_RESAMPLE
+			m_resamplerConfigured = false;
+#endif
+		}
+		m_mutex->Unlock();
+	}
+
+	bool IsPassthrough(void)
+	{
+		return m_codec != cAudioCodec::ePCM;
+	}
+
+	int GetChannels(void)
+	{
+		return m_outChannels;
+	}
+
+	bool Ready(void)
+	{
+		if (!m_configured)
+		{
+			// wait until render is ready before applying new settings
+			if (m_running && m_omx->GetAudioLatency())
+				return false;
+
+			ApplyRenderSettings();
+		}
+		return true;
+	}
+
+private:
+
+	cRpiAudioRender(const cRpiAudioRender&);
+	cRpiAudioRender& operator= (const cRpiAudioRender&);
+
+	void ApplyRenderSettings(void)
+	{
+		if (m_running)
+			m_omx->StopAudio();
+
+		if (m_codec != cAudioCodec::eInvalid)
+		{
+			m_omx->SetupAudioRender(m_codec, m_outChannels, m_port,
+					m_samplingRate, m_frameSize);
+
+			DLOG("set %s audio output format to %dch %s, %d.%dkHz%s",
+					cRpiAudioPort::Str(m_port), m_outChannels,
+					cAudioCodec::Str(m_codec),
+					m_samplingRate / 1000, (m_samplingRate % 1000) / 100,
+					m_codec != cAudioCodec::ePCM ? " (pass-through)" : "");
+
+			if (m_port == cRpiAudioPort::eHDMI)
+				cRpiSetup::SetHDMIChannelMapping(m_codec != cAudioCodec::ePCM,
+						m_outChannels);
+		}
+		m_running = m_codec != cAudioCodec::eInvalid;
+		m_configured = true;
+	}
+
+#ifdef DO_RESAMPLE
+	void ApplyResamplerSettings(void)
+	{
+		swr_free(&m_resample);
+		m_resample = swr_alloc();
+		if (m_resample)
+		{
+			av_opt_set_int(m_resample, "in_sample_rate", m_samplingRate, 0);
+			av_opt_set_int(m_resample, "in_sample_fmt", m_pcmSampleFormat, 0);
+			av_opt_set_int(m_resample, "in_channel_count", m_inChannels, 0);
+			av_opt_set_int(m_resample, "in_channel_layout",
+					AV_CH_LAYOUT(m_inChannels), 0);
+
+			av_opt_set_int(m_resample, "out_sample_rate", m_samplingRate, 0);
+			av_opt_set_int(m_resample, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
+			av_opt_set_int(m_resample, "out_channel_count", m_outChannels, 0);
+			av_opt_set_int(m_resample, "out_channel_layout",
+					AV_CH_LAYOUT(m_outChannels), 0);
+
+			swr_init(m_resample);
+			m_resamplerConfigured = true;
+		}
+		else
+			ELOG("failed to allocate resampling context!");
+	}
+#endif
+
+	cMutex		        *m_mutex;
+	cOmx		        *m_omx;
+
+	cRpiAudioPort::ePort m_port;
+	cAudioCodec::eCodec  m_codec;
+	unsigned int         m_inChannels;
+	unsigned int         m_outChannels;
+	unsigned int         m_samplingRate;
+	unsigned int         m_frameSize;
+	bool                 m_configured;
+	bool                 m_running;
+
+#ifdef DO_RESAMPLE
+	SwrContext          *m_resample;
+	bool                 m_resamplerConfigured;
+#endif
+
+	AVSampleFormat       m_pcmSampleFormat;
+	uint64_t             m_pts;
+};
+
+/* ------------------------------------------------------------------------- */
+
+cRpiAudioDecoder::cRpiAudioDecoder(cOmx *omx) :
+	cThread("audio decoder"),
+	m_passthrough(false),
+	m_reset(false),
+	m_setupChanged(true),
+	m_wait(new cCondWait()),
+	m_parser(new cParser()),
+	m_render(new cRpiAudioRender(omx))
+{
+	memset(m_codecs, 0, sizeof(m_codecs));
+}
+
+cRpiAudioDecoder::~cRpiAudioDecoder()
+{
+	if (Active())
+		Reset();
+
+	delete m_render;
+	delete m_parser;
+	delete m_wait;
+}
+
+extern int SysLogLevel;
+
+int cRpiAudioDecoder::Init(void)
+{
+	int ret = m_parser->Init();
+	if (ret)
+		return ret;
+
+	avcodec_register_all();
+
+	av_log_set_level(
+			SysLogLevel > 2 ? AV_LOG_VERBOSE :
+			SysLogLevel > 1 ? AV_LOG_INFO : AV_LOG_ERROR);
+	av_log_set_callback(&Log);
+
+	m_codecs[cAudioCodec::ePCM ].codec = NULL;
+	m_codecs[cAudioCodec::eMPG ].codec = avcodec_find_decoder(AV_CODEC_ID_MP3);
+	m_codecs[cAudioCodec::eAC3 ].codec = avcodec_find_decoder(AV_CODEC_ID_AC3);
+	m_codecs[cAudioCodec::eEAC3].codec = avcodec_find_decoder(AV_CODEC_ID_EAC3);
+	m_codecs[cAudioCodec::eAAC ].codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
+	m_codecs[cAudioCodec::eDTS ].codec = avcodec_find_decoder(AV_CODEC_ID_DTS);
+
+	for (int i = 0; i < cAudioCodec::eNumCodecs; i++)
+	{
+		cAudioCodec::eCodec codec = static_cast<cAudioCodec::eCodec>(i);
+		if (m_codecs[codec].codec)
+		{
+			m_codecs[codec].context = avcodec_alloc_context3(m_codecs[codec].codec);
+			if (!m_codecs[codec].context)
+			{
+				ELOG("failed to allocate %s context!", cAudioCodec::Str(codec));
+				ret = -1;
+				break;
+			}
+			if (avcodec_open2(m_codecs[codec].context, m_codecs[codec].codec, NULL) < 0)
+			{
+				ELOG("failed to open %s decoder!", cAudioCodec::Str(codec));
+				ret = -1;
+				break;
+			}
+		}
+	}
+
+	if (!ret)
+	{
+		cRpiSetup::SetAudioSetupChangedCallback(&OnAudioSetupChanged, this);
+		Start();
+	}
+	else
+		DeInit();
+
+	return ret;
+}
+
+int cRpiAudioDecoder::DeInit(void)
+{
+	Lock();
+
+	Reset();
+	Cancel(-1);
+	m_wait->Signal();
+
+	while (Active())
+		cCondWait::SleepMs(5);
+
+	m_render->Flush();
+	cRpiSetup::SetAudioSetupChangedCallback(0);
+
+	for (int i = 0; i < cAudioCodec::eNumCodecs; i++)
+	{
+		cAudioCodec::eCodec codec = static_cast<cAudioCodec::eCodec>(i);
+		if (m_codecs[codec].codec)
+			avcodec_close(m_codecs[codec].context);
+	}
+
+	av_log_set_callback(&av_log_default_callback);
+	m_parser->DeInit();
+
+	Unlock();
+	return 0;
+}
+
+bool cRpiAudioDecoder::WriteData(const unsigned char *buf, unsigned int length,
+		uint64_t pts)
+{
+	Lock();
+
+	bool ret = m_parser->Append(buf, pts, length);
+	if (ret)
+		m_wait->Signal();
+
+	Unlock();
+	return ret;
+}
+
+void cRpiAudioDecoder::Reset(void)
+{
+	Lock();
+	m_reset = true;
+	m_wait->Signal();
+	while (m_reset)
+		cCondWait::SleepMs(5);
+	Unlock();
+}
+
+bool cRpiAudioDecoder::Poll(void)
+{
+	return m_parser->GetFreeSpace() > KILOBYTE(16);
+}
+
+void cRpiAudioDecoder::HandleAudioSetupChanged()
+{
+	DBG("HandleAudioSetupChanged()");
+	m_setupChanged = true;
+}
+
+void cRpiAudioDecoder::Action(void)
+{
+	SetPriority(-15);
+	DLOG("cAudioDecoder() thread started");
+
+	unsigned int channels = 0;
+	unsigned int samplingRate = 0;
+	cAudioCodec::eCodec codec = cAudioCodec::eInvalid;
+
+	AVFrame *frame = av_frame_alloc();
+	if (!frame)
+	{
+		ELOG("failed to allocate audio frame!");
+		return;
+	}
+
+	while (Running())
+	{
+		if (m_reset)
+		{
+			m_parser->Reset();
+			m_render->Flush();
+			av_frame_unref(frame);
+			m_reset = false;
+		}
+
+		// test for codec change if there is data in parser and no left over
+		if (!m_parser->Empty() && !frame->nb_samples)
+			m_setupChanged |= codec != m_parser->GetCodec() ||
+				channels != m_parser->GetChannels() ||
+				samplingRate != m_parser->GetSamplingRate();
+
+		// if necessary, set up audio codec
+		if (!m_parser->Empty() && m_setupChanged)
+		{
+			if (codec != m_parser->GetCodec() && codec != cAudioCodec::eInvalid)
+				avcodec_flush_buffers(m_codecs[codec].context);
+
+			codec = m_parser->GetCodec();
+			channels = m_parser->GetChannels();
+			samplingRate = m_parser->GetSamplingRate();
+
+			// validate channel layout and apply new audio parameters
+			if (AV_CH_LAYOUT(channels))
+			{
+				m_setupChanged = false;
+				m_render->SetCodec(codec, channels, samplingRate,
+						m_parser->GetFrameSize());
+
+#ifndef DO_RESAMPLE
+#if FF_API_REQUEST_CHANNELS
+				// if there's no libswresample, let decoder do the down mix
+				m_codecs[codec].context->request_channels =
+						m_render->GetChannels();
+#endif
+				m_codecs[codec].context->request_channel_layout =
+						AV_CH_LAYOUT(m_render->GetChannels());
+#endif
+			}
+			m_reset = m_setupChanged;
+			continue;
+		}
+
+		// if there's audio data available...
+		if (!m_parser->Empty())
+		{
+			// ... either pass through if render is ready
+			if (m_render->IsPassthrough())
+			{
+				if (m_render->Ready())
+				{
+					int len = m_render->WriteSamples(&m_parser->Packet()->data,
+							m_parser->Packet()->size, m_parser->GetPts());
+					if (len)
+					{
+						m_parser->Shrink(len);
+						continue;
+					}
+				}
+			}
+			// ... or decode if there's no leftover
+			else if (!frame->nb_samples)
+			{
+				int gotFrame = 0;
+				int len = avcodec_decode_audio4(m_codecs[codec].context,
+						frame, &gotFrame, m_parser->Packet());
+
+				if (len > 0 && gotFrame)
+				{
+					frame->pts = m_parser->GetPts();
+					m_parser->Shrink(len);
+				}
+				else
+				{
+					ELOG("failed to decode audio frame!");
+					m_parser->Reset();
+					av_frame_unref(frame);
+					continue;
+				}
+			}
+		}
+		// if there's leftover, pass decoded audio data to render when ready
+		if (frame->nb_samples && m_render->Ready())
+		{
+			int len = m_render->WriteSamples(frame->extended_data,
+					frame->nb_samples, frame->pts,
+					(AVSampleFormat)frame->format);
+			if (len)
+			{
+				av_frame_unref(frame);
+				continue;
+			}
+		}
+		// nothing to be done...
+		m_wait->Wait(50);
+	}
+
+	av_frame_free(&frame);
+	DLOG("cAudioDecoder() thread ended");
+}
+
+void cRpiAudioDecoder::Log(void* ptr, int level, const char* fmt, va_list vl)
+{
+	if (level == AV_LOG_QUIET)
+		return;
+
+	char line[128];
+	vsnprintf(line, sizeof(line), fmt, vl);
+
+	if (level <= AV_LOG_ERROR)
+		ELOG("[libav] %s", line);
+	else if (level <= AV_LOG_INFO)
+		ILOG("[libav] %s", line);
+	else if (level <= AV_LOG_VERBOSE)
+		DLOG("[libav] %s", line);
+}
diff --git a/audio.h b/audio.h
new file mode 100644
index 0000000..9560513
--- /dev/null
+++ b/audio.h
@@ -0,0 +1,65 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef AUDIO_H
+#define AUDIO_H
+
+#include <vdr/thread.h>
+
+#include "tools.h"
+#include "omx.h"
+
+class cRpiAudioRender;
+
+class cRpiAudioDecoder : public cThread
+{
+
+public:
+
+    cRpiAudioDecoder(cOmx *omx);
+	virtual ~cRpiAudioDecoder();
+
+	virtual int Init(void);
+	virtual int DeInit(void);
+
+	virtual bool WriteData(const unsigned char *buf, unsigned int length,
+			uint64_t pts = 0);
+
+	virtual bool Poll(void);
+	virtual void Reset(void);
+
+protected:
+
+	virtual void Action(void);
+
+	static void OnAudioSetupChanged(void *data)
+		{ (static_cast <cRpiAudioDecoder*> (data))->HandleAudioSetupChanged(); }
+
+	void HandleAudioSetupChanged();
+
+	static void Log(void* ptr, int level, const char* fmt, va_list vl);
+
+	struct Codec
+	{
+		class AVCodec		 *codec;
+	    class AVCodecContext *context;
+	};
+
+private:
+
+	class cParser;
+
+	Codec		  	m_codecs[cAudioCodec::eNumCodecs];
+	bool		  	m_passthrough;
+	bool		  	m_reset;
+	bool		  	m_setupChanged;
+
+	cCondWait	 	*m_wait;
+	cParser		 	*m_parser;
+	cRpiAudioRender	*m_render;
+};
+
+#endif
diff --git a/display.c b/display.c
new file mode 100644
index 0000000..c839d59
--- /dev/null
+++ b/display.c
@@ -0,0 +1,364 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include "display.h"
+#include "ovgosd.h"
+#include "tools.h"
+#include "setup.h"
+
+#include <vdr/tools.h>
+
+extern "C" {
+#include "interface/vmcs_host/vc_tvservice.h"
+#include "interface/vmcs_host/vc_dispmanx.h"
+}
+
+cRpiDisplay* cRpiDisplay::s_instance = 0;
+
+cRpiDisplay* cRpiDisplay::GetInstance(void)
+{
+	if (!s_instance)
+	{
+		TV_DISPLAY_STATE_T tvstate;
+		memset(&tvstate, 0, sizeof(TV_DISPLAY_STATE_T));
+
+		if (!vc_tv_get_display_state(&tvstate))
+		{
+			// HDMI
+			if (tvstate.state & (VC_HDMI_HDMI | VC_HDMI_DVI))
+				s_instance = new cRpiHDMIDisplay(
+						tvstate.display.hdmi.width,
+						tvstate.display.hdmi.height,
+						tvstate.display.hdmi.frame_rate,
+						tvstate.display.hdmi.group,
+						tvstate.display.hdmi.mode);
+			else
+				s_instance = new cRpiCompositeDisplay(
+						tvstate.display.sdtv.width,
+						tvstate.display.sdtv.height,
+						tvstate.display.sdtv.frame_rate);
+		}
+		else
+		{
+			ELOG("failed to get display parameters - assuming analog SDTV!");
+			s_instance = new cRpiCompositeDisplay(720, 576, 50);
+		}
+	}
+
+	return s_instance;
+}
+
+void cRpiDisplay::DropInstance(void)
+{
+	delete s_instance;
+	s_instance = 0;
+}
+
+cRpiDisplay::cRpiDisplay(int width, int height, int frameRate,
+		cRpiVideoPort::ePort port) :
+	m_width(width),
+	m_height(height),
+	m_frameRate(frameRate),
+	m_interlaced(false),
+	m_port(port)
+{
+}
+
+cRpiDisplay::~cRpiDisplay()
+{
+}
+
+int cRpiDisplay::GetSize(int &width, int &height)
+{
+	cRpiDisplay* instance = GetInstance();
+	if (instance)
+	{
+		width = instance->m_width;
+		height = instance->m_height;
+		return 0;
+	}
+	return -1;
+}
+
+int cRpiDisplay::GetSize(int &width, int &height, double &aspect)
+{
+	cRpiDisplay* instance = GetInstance();
+	if (instance)
+	{
+		width = instance->m_width;
+		height = instance->m_height;
+		aspect = (double)width / height;
+		return 0;
+	}
+	return -1;
+}
+
+int cRpiDisplay::SetVideoFormat(int width, int height, int frameRate,
+		bool interlaced)
+{
+	cRpiDisplay* instance = GetInstance();
+	if (instance)
+		return instance->Update(width, height, frameRate, interlaced);
+
+	return -1;
+}
+
+cRpiVideoPort::ePort cRpiDisplay::GetVideoPort(void)
+{
+	cRpiDisplay* instance = GetInstance();
+	if (instance)
+		return instance->m_port;
+
+	return cRpiVideoPort::eComposite;
+}
+
+bool cRpiDisplay::IsProgressive(void)
+{
+	cRpiDisplay* instance = GetInstance();
+	if (instance)
+		return !instance->m_interlaced;
+
+	return false;
+}
+
+int cRpiDisplay::Snapshot(unsigned char* frame, int width, int height)
+{
+	cRpiDisplay* instance = GetInstance();
+	if (instance)
+	{
+		DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(
+				instance->m_port == cRpiVideoPort::eHDMI ? 0 : 1);
+
+		if (display)
+		{
+			uint32_t image;
+			DISPMANX_RESOURCE_HANDLE_T res = vc_dispmanx_resource_create(
+					VC_IMAGE_RGB888, width, height, &image);
+
+			vc_dispmanx_snapshot(display, res,
+					(DISPMANX_TRANSFORM_T)(DISPMANX_SNAPSHOT_PACK));
+
+			VC_RECT_T rect = { 0, 0, width, height };
+			vc_dispmanx_resource_read_data(res, &rect, frame, width * 3);
+
+			vc_dispmanx_resource_delete(res);
+			vc_dispmanx_display_close(display);
+			return 0;
+		}
+	}
+	return -1;
+}
+
+int cRpiDisplay::Update(int width, int height, int frameRate, bool interlaced)
+{
+	if (cRpiSetup::GetVideoResolution() == cVideoResolution::eDontChange &&
+				cRpiSetup::GetVideoFrameRate() == cVideoFrameRate::eDontChange)
+		return 0;
+
+	int newWidth = m_width;
+	int newHeight = m_height;
+	int newFrameRate = m_frameRate;
+	bool newInterlaced = m_interlaced;
+
+	switch (cRpiSetup::GetVideoResolution())
+	{
+	case cVideoResolution::e480:  newWidth = 720;  newHeight = 480;  break;
+	case cVideoResolution::e576:  newWidth = 720;  newHeight = 576;  break;
+	case cVideoResolution::e720:  newWidth = 1280; newHeight = 720;  break;
+	case cVideoResolution::e1080: newWidth = 1920; newHeight = 1080; break;
+
+	case cVideoResolution::eFollowVideo:
+		if (width && height)
+		{
+			newWidth = width;
+			newHeight = height;
+		}
+		break;
+
+	default:
+	case cVideoResolution::eDontChange:
+		break;
+	}
+
+	switch (cRpiSetup::GetVideoFrameRate())
+	{
+	case cVideoFrameRate::e24p: newFrameRate = 24; newInterlaced = false; break;
+	case cVideoFrameRate::e25p: newFrameRate = 25; newInterlaced = false; break;
+	case cVideoFrameRate::e30p: newFrameRate = 30; newInterlaced = false; break;
+	case cVideoFrameRate::e50i: newFrameRate = 50; newInterlaced = true;  break;
+	case cVideoFrameRate::e50p: newFrameRate = 50; newInterlaced = false; break;
+	case cVideoFrameRate::e60i: newFrameRate = 60; newInterlaced = true;  break;
+	case cVideoFrameRate::e60p: newFrameRate = 60; newInterlaced = false; break;
+
+	case cVideoFrameRate::eFollowVideo:
+		if (frameRate)
+		{
+			newFrameRate = frameRate;
+			newInterlaced = interlaced;
+		}
+		break;
+
+	default:
+	case cVideoFrameRate::eDontChange:
+		break;
+	}
+
+	// set new mode only if necessary
+	if (newWidth != m_width || newHeight != m_height ||
+			newFrameRate != m_frameRate || newInterlaced != m_interlaced)
+		return SetMode(newWidth, newHeight, newFrameRate, newInterlaced);
+
+	return 0;
+}
+
+/* ------------------------------------------------------------------------- */
+
+#define HDMI_MAX_MODES 64
+
+class cRpiHDMIDisplay::ModeList {
+public:
+
+	TV_SUPPORTED_MODE_NEW_T modes[HDMI_MAX_MODES];
+	int nModes;
+};
+
+cRpiHDMIDisplay::cRpiHDMIDisplay(int width, int height, int frameRate,
+		int group, int mode) :
+	cRpiDisplay(width, height, frameRate, cRpiVideoPort::eHDMI),
+	m_modes(new cRpiHDMIDisplay::ModeList()),
+	m_group(group),
+	m_mode(mode),
+	m_startGroup(group),
+	m_startMode(mode),
+	m_modified(false)
+{
+	vc_tv_register_callback(TvServiceCallback, 0);
+
+	m_modes->nModes = vc_tv_hdmi_get_supported_modes_new(HDMI_RES_GROUP_CEA,
+			m_modes->modes, HDMI_MAX_MODES, NULL, NULL);
+
+	m_modes->nModes += vc_tv_hdmi_get_supported_modes_new(HDMI_RES_GROUP_DMT,
+			&m_modes->modes[m_modes->nModes], HDMI_MAX_MODES - m_modes->nModes,
+			NULL, NULL);
+
+	if (m_modes->nModes)
+	{
+		DLOG("supported HDMI modes:");
+		for (int i = 0; i < m_modes->nModes; i++)
+		{
+			DLOG("%s[%02d]: %4dx%4d@%2d%s | %s | %3d.%03dMHz%s%s",
+				m_modes->modes[i].group == HDMI_RES_GROUP_CEA ? "CEA" :
+				m_modes->modes[i].group == HDMI_RES_GROUP_DMT ? "DMT" : "---",
+				m_modes->modes[i].code,
+				m_modes->modes[i].width,
+				m_modes->modes[i].height,
+				m_modes->modes[i].frame_rate,
+				m_modes->modes[i].scan_mode ? "i" : "p",
+				m_modes->modes[i].aspect_ratio == HDMI_ASPECT_4_3   ? " 4:3 " :
+				m_modes->modes[i].aspect_ratio == HDMI_ASPECT_14_9  ? "14:9 " :
+				m_modes->modes[i].aspect_ratio == HDMI_ASPECT_16_9  ? "16:9 " :
+				m_modes->modes[i].aspect_ratio == HDMI_ASPECT_5_4   ? " 5:4 " :
+				m_modes->modes[i].aspect_ratio == HDMI_ASPECT_16_10 ? "16:10" :
+				m_modes->modes[i].aspect_ratio == HDMI_ASPECT_15_9  ? "15:9 " :
+				m_modes->modes[i].aspect_ratio == HDMI_ASPECT_21_9  ? "21:9 " :
+					"unknown aspect ratio",
+				m_modes->modes[i].pixel_freq / 1000000,
+				m_modes->modes[i].pixel_freq % 1000000 / 1000,
+				m_modes->modes[i].native ? " (native)" : "",
+				m_modes->modes[i].code == m_mode &&
+					m_modes->modes[i].group == m_group ? " (current)" : "");
+
+			// update initial parameters
+			if (m_modes->modes[i].code == m_mode &&
+					m_modes->modes[i].group == m_group)
+			{
+				m_width = m_modes->modes[i].width;
+				m_height = m_modes->modes[i].height;
+				m_frameRate = m_modes->modes[i].frame_rate;
+				m_interlaced = m_modes->modes[i].scan_mode;
+			}
+		}
+	}
+	else
+		ELOG("failed to read HDMI EDID information!");
+
+}
+
+cRpiHDMIDisplay::~cRpiHDMIDisplay()
+{
+	vc_tv_unregister_callback(TvServiceCallback);
+
+	// if mode has been changed, set back to previous state
+	if (m_modified)
+		SetMode(m_startGroup, m_startMode);
+
+	delete m_modes;
+}
+
+int cRpiHDMIDisplay::SetMode(int width, int height, int frameRate,
+		bool interlaced)
+{
+	for (int i = 0; i < m_modes->nModes; i++)
+	{
+		if (m_modes->modes[i].width == width &&
+				m_modes->modes[i].height == height &&
+				m_modes->modes[i].frame_rate == frameRate &&
+				m_modes->modes[i].scan_mode == interlaced)
+		{
+			DLOG("setting HDMI mode to %dx%d@%2d%s", width, height,
+					frameRate, interlaced ? "i" : "p");
+
+			m_width = width;
+			m_height = height;
+			m_frameRate = frameRate;
+			m_interlaced = interlaced;
+			return SetMode(m_modes->modes[i].group, m_modes->modes[i].code);
+		}
+	}
+
+	DLOG("failed to set HDMI mode to %dx%d@%2d%s",
+		width, height, frameRate, interlaced ? "i" : "p");
+	return -1;
+}
+
+int cRpiHDMIDisplay::SetMode(int group, int mode)
+{
+	int ret = 0;
+	if (group != m_group || mode != m_mode)
+	{
+		DBG("cHDMI::SetMode(%s, %d)",
+			group == HDMI_RES_GROUP_DMT ? "DMT" :
+			group == HDMI_RES_GROUP_CEA ? "CEA" : "unknown", mode);
+
+		ret = vc_tv_hdmi_power_on_explicit_new(HDMI_MODE_HDMI,
+				(HDMI_RES_GROUP_T)group, mode);
+		if (ret)
+			ELOG("failed to set HDMI mode!");
+		else
+		{
+			m_group = group;
+			m_mode = mode;
+			m_modified = true;
+		}
+	}
+	return ret;
+}
+
+void cRpiHDMIDisplay::TvServiceCallback(void *data, unsigned int reason,
+		unsigned int param1, unsigned int param2)
+{
+	if (reason & VC_HDMI_DVI + VC_HDMI_HDMI)
+		cRpiOsdProvider::ResetOsd();
+}
+
+/* ------------------------------------------------------------------------- */
+
+cRpiCompositeDisplay::cRpiCompositeDisplay(int width, int height,
+		int frameRate) :
+	cRpiDisplay(width, height, frameRate, cRpiVideoPort::eComposite)
+{
+
+}
diff --git a/display.h b/display.h
new file mode 100644
index 0000000..d954a2e
--- /dev/null
+++ b/display.h
@@ -0,0 +1,99 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef DISPLAY_H
+#define DISPLAY_H
+
+#include "tools.h"
+
+class cRpiDisplay
+{
+
+public:
+
+	static cRpiDisplay* GetInstance(void);
+	static void DropInstance(void);
+
+	static int GetSize(int &width, int &height);
+	static int GetSize(int &width, int &height, double &aspect);
+
+	static cRpiVideoPort::ePort GetVideoPort(void);
+	static bool IsProgressive(void);
+
+	static int Snapshot(unsigned char* frame, int width, int height);
+
+	static int SetVideoFormat(int width, int height, int frameRate,
+			bool interlaced);
+
+protected:
+
+	cRpiDisplay(int width, int height, int frameRate,
+			cRpiVideoPort::ePort port);
+	virtual ~cRpiDisplay();
+
+	int Update(int width, int height, int frameRate, bool interlaced);
+
+	virtual int SetMode(int width, int height, int frameRate, bool interlaced) {
+		return -1;
+	}
+
+	int m_width;
+	int m_height;
+	int m_frameRate;
+	bool m_interlaced;
+	cRpiVideoPort::ePort m_port;
+
+	static cRpiDisplay *s_instance;
+
+private:
+
+	cRpiDisplay(const cRpiDisplay&);
+	cRpiDisplay& operator= (const cRpiDisplay&);
+
+};
+
+class cRpiHDMIDisplay : public cRpiDisplay
+{
+
+public:
+
+	cRpiHDMIDisplay(int width, int height, int frameRate, int group, int mode);
+	virtual ~cRpiHDMIDisplay();
+
+private:
+
+	virtual int SetMode(int width, int height, int frameRate, bool interlaced);
+	int SetMode(int group, int mode);
+
+	static void TvServiceCallback(void *data, unsigned int reason,
+			unsigned int param1, unsigned int param2);
+
+	class ModeList;
+	ModeList *m_modes;
+
+	int m_group;
+	int m_mode;
+
+	int m_startGroup;
+	int m_startMode;
+	bool m_modified;
+};
+
+class cRpiCompositeDisplay : public cRpiDisplay
+{
+
+public:
+
+	cRpiCompositeDisplay(int width, int height, int frameRate);
+
+private:
+
+	virtual int SetMode(int width, int height, int frameRate, bool interlaced) {
+		return 0;
+	}
+};
+
+#endif
diff --git a/ilclient/Makefile b/ilclient/Makefile
new file mode 100644
index 0000000..7a967f4
--- /dev/null
+++ b/ilclient/Makefile
@@ -0,0 +1,23 @@
+OBJS=ilclient.o ilcore.o
+LIB=libilclient.a
+
+CFLAGS+=-DSTANDALONE -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DTARGET_POSIX -D_LINUX -fPIC -DPIC -D_REENTRANT -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -U_FORTIFY_SOURCE -Wall -g -DHAVE_LIBOPENMAX=2 -DOMX -DOMX_SKIP64BIT -ftree-vectorize -pipe -DUSE_EXTERNAL_OMX -DHAVE_LIBBCM_HOST -DUSE_EXTERNAL_LIBBCM_HOST -DUSE_VCHIQ_ARM -Wno-psabi
+
+INCLUDES+=-I$(SDKSTAGE)/opt/vc/include/ -I$(SDKSTAGE)/opt/vc/include/interface/vcos/pthreads -I$(SDKSTAGE)/opt/vc/include/interface/vmcs_host/linux
+
+all: $(LIB)
+
+%.o: %.c
+	@rm -f $@ 
+	$(CC) $(CFLAGS) $(INCLUDES) -g -c $< -o $@ -Wno-deprecated-declarations
+
+%.a: $(OBJS)
+	$(AR) r $@ $^
+
+clean:
+	for i in $(OBJS); do (if test -e "$$i"; then ( rm $$i ); fi ); done
+	@rm -f $(BIN) $(LIB)
+
+
+
+
diff --git a/ilclient/ilclient.c b/ilclient/ilclient.c
new file mode 100644
index 0000000..49f758b
--- /dev/null
+++ b/ilclient/ilclient.c
@@ -0,0 +1,1834 @@
+/*
+Copyright (c) 2012, Broadcom Europe Ltd
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the copyright holder nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ * \file
+ *
+ * \brief This API defines helper functions for writing IL clients.
+ *
+ * This file defines an IL client side library.  This is useful when
+ * writing IL clients, since there tends to be much repeated and
+ * common code across both single and multiple clients.  This library
+ * seeks to remove that common code and abstract some of the
+ * interactions with components.  There is a wrapper around a
+ * component and tunnel, and some operations can be done on lists of
+ * these.  The callbacks from components are handled, and specific
+ * events can be checked or waited for.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "interface/vcos/vcos.h"
+#include "interface/vcos/vcos_logging.h"
+#include "interface/vmcs_host/vchost.h"
+
+#include "IL/OMX_Broadcom.h"
+#include "ilclient.h"
+
+#define VCOS_LOG_CATEGORY (&ilclient_log_category)
+
+#ifndef ILCLIENT_THREAD_DEFAULT_STACK_SIZE
+#define ILCLIENT_THREAD_DEFAULT_STACK_SIZE   (6<<10)
+#endif
+
+static VCOS_LOG_CAT_T ilclient_log_category;
+
+/******************************************************************************
+Static data and types used only in this file.
+******************************************************************************/
+
+struct _ILEVENT_T {
+   OMX_EVENTTYPE eEvent;
+   OMX_U32 nData1;
+   OMX_U32 nData2;
+   OMX_PTR pEventData;
+   struct _ILEVENT_T *next;
+};
+
+#define NUM_EVENTS 100
+struct _ILCLIENT_T {
+   ILEVENT_T *event_list;
+   VCOS_SEMAPHORE_T event_sema;
+   ILEVENT_T event_rep[NUM_EVENTS];
+
+   ILCLIENT_CALLBACK_T port_settings_callback;
+   void *port_settings_callback_data;
+   ILCLIENT_CALLBACK_T eos_callback;
+   void *eos_callback_data;
+   ILCLIENT_CALLBACK_T error_callback;
+   void *error_callback_data;
+   ILCLIENT_BUFFER_CALLBACK_T fill_buffer_done_callback;
+   void *fill_buffer_done_callback_data;
+   ILCLIENT_BUFFER_CALLBACK_T empty_buffer_done_callback;
+   void *empty_buffer_done_callback_data;
+   ILCLIENT_CALLBACK_T configchanged_callback;
+   void *configchanged_callback_data;
+};
+
+struct _COMPONENT_T {
+   OMX_HANDLETYPE comp;
+   ILCLIENT_CREATE_FLAGS_T flags;
+   VCOS_SEMAPHORE_T sema;
+   VCOS_EVENT_FLAGS_T event;
+   struct _COMPONENT_T *related;
+   OMX_BUFFERHEADERTYPE *out_list;
+   OMX_BUFFERHEADERTYPE *in_list;
+   char name[32];
+   char bufname[32];
+   unsigned int error_mask;
+   unsigned int private;
+   ILEVENT_T *list;
+   ILCLIENT_T *client;
+};
+
+#define random_wait()
+static char *states[] = {"Invalid", "Loaded", "Idle", "Executing", "Pause", "WaitingForResources"};
+
+typedef enum {
+   ILCLIENT_ERROR_UNPOPULATED  = 0x1,
+   ILCLIENT_ERROR_SAMESTATE    = 0x2,
+   ILCLIENT_ERROR_BADPARAMETER = 0x4
+} ILERROR_MASK_T;
+
+/******************************************************************************
+Static functions.
+******************************************************************************/
+
+static OMX_ERRORTYPE ilclient_empty_buffer_done(OMX_IN OMX_HANDLETYPE hComponent,
+      OMX_IN OMX_PTR pAppData,
+      OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
+static OMX_ERRORTYPE ilclient_empty_buffer_done_error(OMX_IN OMX_HANDLETYPE hComponent,
+      OMX_IN OMX_PTR pAppData,
+      OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
+static OMX_ERRORTYPE ilclient_fill_buffer_done(OMX_OUT OMX_HANDLETYPE hComponent,
+      OMX_OUT OMX_PTR pAppData,
+      OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer);
+static OMX_ERRORTYPE ilclient_fill_buffer_done_error(OMX_OUT OMX_HANDLETYPE hComponent,
+      OMX_OUT OMX_PTR pAppData,
+      OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer);
+static OMX_ERRORTYPE ilclient_event_handler(OMX_IN OMX_HANDLETYPE hComponent,
+      OMX_IN OMX_PTR pAppData,
+      OMX_IN OMX_EVENTTYPE eEvent,
+      OMX_IN OMX_U32 nData1,
+      OMX_IN OMX_U32 nData2,
+      OMX_IN OMX_PTR pEventData);
+static void ilclient_lock_events(ILCLIENT_T *st);
+static void ilclient_unlock_events(ILCLIENT_T *st);
+
+/******************************************************************************
+Global functions
+******************************************************************************/
+
+/***********************************************************
+ * Name: ilclient_init
+ *
+ * Description: Creates ilclient pointer
+ *
+ * Returns: pointer to client structure
+ ***********************************************************/
+ILCLIENT_T *ilclient_init()
+{
+   ILCLIENT_T *st = vcos_malloc(sizeof(ILCLIENT_T), "ilclient");
+   int i;
+   
+   if (!st)
+      return NULL;
+   
+   vcos_log_set_level(VCOS_LOG_CATEGORY, VCOS_LOG_WARN);
+   vcos_log_register("ilclient", VCOS_LOG_CATEGORY);
+
+   memset(st, 0, sizeof(ILCLIENT_T));
+
+   i = vcos_semaphore_create(&st->event_sema, "il:event", 1);
+   vc_assert(i == VCOS_SUCCESS);
+
+   ilclient_lock_events(st);
+   st->event_list = NULL;
+   for (i=0; i<NUM_EVENTS; i++)
+   {
+      st->event_rep[i].eEvent = -1; // mark as unused
+      st->event_rep[i].next = st->event_list;
+      st->event_list = st->event_rep+i;
+   }
+   ilclient_unlock_events(st);
+   return st;
+}
+
+/***********************************************************
+ * Name: ilclient_destroy
+ *
+ * Description: frees client state
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_destroy(ILCLIENT_T *st)
+{
+   vcos_semaphore_delete(&st->event_sema);
+   vcos_free(st);
+   vcos_log_unregister(VCOS_LOG_CATEGORY);
+}
+
+/***********************************************************
+ * Name: ilclient_set_port_settings_callback
+ *
+ * Description: sets the callback used when receiving port settings
+ * changed messages.  The data field in the callback function will be
+ * the port index reporting the message.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_set_port_settings_callback(ILCLIENT_T *st, ILCLIENT_CALLBACK_T func, void *userdata)
+{
+   st->port_settings_callback = func;
+   st->port_settings_callback_data = userdata;
+}
+
+/***********************************************************
+ * Name: ilclient_set_eos_callback
+ *
+ * Description: sets the callback used when receiving eos flags.  The
+ * data parameter in the callback function will be the port index
+ * reporting an eos flag.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_set_eos_callback(ILCLIENT_T *st, ILCLIENT_CALLBACK_T func, void *userdata)
+{
+   st->eos_callback = func;
+   st->eos_callback_data = userdata;
+}
+
+/***********************************************************
+ * Name: ilclient_set_error_callback
+ *
+ * Description: sets the callback used when receiving error events.
+ * The data parameter in the callback function will be the error code
+ * being reported.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_set_error_callback(ILCLIENT_T *st, ILCLIENT_CALLBACK_T func, void *userdata)
+{
+   st->error_callback = func;
+   st->error_callback_data = userdata;
+}
+
+/***********************************************************
+ * Name: ilclient_set_fill_buffer_done_callback
+ *
+ * Description: sets the callback used when receiving
+ * fill_buffer_done event
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_set_fill_buffer_done_callback(ILCLIENT_T *st, ILCLIENT_BUFFER_CALLBACK_T func, void *userdata)
+{
+   st->fill_buffer_done_callback = func;
+   st->fill_buffer_done_callback_data = userdata;
+}
+
+/***********************************************************
+ * Name: ilclient_set_empty_buffer_done_callback
+ *
+ * Description: sets the callback used when receiving
+ * empty_buffer_done event
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_set_empty_buffer_done_callback(ILCLIENT_T *st, ILCLIENT_BUFFER_CALLBACK_T func, void *userdata)
+{
+   st->empty_buffer_done_callback = func;
+   st->empty_buffer_done_callback_data = userdata;
+}
+
+/***********************************************************
+ * Name: ilclient_set_configchanged_callback
+ *
+ * Description: sets the callback used when a config changed
+ * event is received
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_set_configchanged_callback(ILCLIENT_T *st, ILCLIENT_CALLBACK_T func, void *userdata)
+{
+   st->configchanged_callback = func;
+   st->configchanged_callback_data = userdata;
+}
+
+/***********************************************************
+ * Name: ilclient_create_component
+ *
+ * Description: initialises a component state structure and creates
+ * the IL component.
+ *
+ * Returns: 0 on success, -1 on failure
+ ***********************************************************/
+int ilclient_create_component(ILCLIENT_T *client, COMPONENT_T **comp, char *name,
+                              ILCLIENT_CREATE_FLAGS_T flags)
+{
+   OMX_CALLBACKTYPE callbacks;
+   OMX_ERRORTYPE error;
+   char component_name[128];
+   int32_t status;
+
+   *comp = vcos_malloc(sizeof(COMPONENT_T), "il:comp");
+   if(!*comp)
+      return -1;
+
+   memset(*comp, 0, sizeof(COMPONENT_T));
+
+#define COMP_PREFIX "OMX.broadcom."
+
+   status = vcos_event_flags_create(&(*comp)->event,"il:comp");
+   vc_assert(status == VCOS_SUCCESS);
+   status = vcos_semaphore_create(&(*comp)->sema, "il:comp", 1);
+   vc_assert(status == VCOS_SUCCESS);
+   (*comp)->client = client;
+
+   vcos_snprintf((*comp)->name, sizeof((*comp)->name), "cl:%s", name);
+   vcos_snprintf((*comp)->bufname, sizeof((*comp)->bufname), "cl:%s buffer", name);
+   vcos_snprintf(component_name, sizeof(component_name), "%s%s", COMP_PREFIX, name);
+
+   (*comp)->flags = flags;
+
+   callbacks.EventHandler = ilclient_event_handler;
+   callbacks.EmptyBufferDone = (flags & ILCLIENT_ENABLE_INPUT_BUFFERS) ? ilclient_empty_buffer_done : ilclient_empty_buffer_done_error;
+   callbacks.FillBufferDone = (flags & ILCLIENT_ENABLE_OUTPUT_BUFFERS) ? ilclient_fill_buffer_done : ilclient_fill_buffer_done_error;
+
+   error = OMX_GetHandle(&(*comp)->comp, component_name, *comp, &callbacks);
+
+   if (error == OMX_ErrorNone)
+   {
+      OMX_UUIDTYPE uid;
+      char name[128];
+      OMX_VERSIONTYPE compVersion, specVersion;
+
+      if(OMX_GetComponentVersion((*comp)->comp, name, &compVersion, &specVersion, &uid) == OMX_ErrorNone)
+      {
+         char *p = (char *) uid + strlen(COMP_PREFIX);
+
+         vcos_snprintf((*comp)->name, sizeof((*comp)->name), "cl:%s", p);
+         (*comp)->name[sizeof((*comp)->name)-1] = 0;
+         vcos_snprintf((*comp)->bufname, sizeof((*comp)->bufname), "cl:%s buffer", p);
+         (*comp)->bufname[sizeof((*comp)->bufname)-1] = 0;
+      }
+
+      if(flags & (ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_OUTPUT_ZERO_BUFFERS))
+      {
+         OMX_PORT_PARAM_TYPE ports;
+         OMX_INDEXTYPE types[] = {OMX_IndexParamAudioInit, OMX_IndexParamVideoInit, OMX_IndexParamImageInit, OMX_IndexParamOtherInit};
+         int i;
+
+         ports.nSize = sizeof(OMX_PORT_PARAM_TYPE);
+         ports.nVersion.nVersion = OMX_VERSION;
+
+         for(i=0; i<4; i++)
+         {
+            OMX_ERRORTYPE error = OMX_GetParameter((*comp)->comp, types[i], &ports);
+            if(error == OMX_ErrorNone)
+            {
+               uint32_t j;
+               for(j=0; j<ports.nPorts; j++)
+               {
+                  if(flags & ILCLIENT_DISABLE_ALL_PORTS)
+                     ilclient_disable_port(*comp, ports.nStartPortNumber+j);
+                  
+                  if(flags & ILCLIENT_OUTPUT_ZERO_BUFFERS)
+                  {
+                     OMX_PARAM_PORTDEFINITIONTYPE portdef;
+                     portdef.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
+                     portdef.nVersion.nVersion = OMX_VERSION;
+                     portdef.nPortIndex = ports.nStartPortNumber+j;
+                     if(OMX_GetParameter((*comp)->comp, OMX_IndexParamPortDefinition, &portdef) == OMX_ErrorNone)
+                     {
+                        if(portdef.eDir == OMX_DirOutput && portdef.nBufferCountActual > 0)
+                        {
+                           portdef.nBufferCountActual = 0;
+                           OMX_SetParameter((*comp)->comp, OMX_IndexParamPortDefinition, &portdef);
+                        }
+                     }
+                  }
+               }
+            }
+         }
+      }
+      return 0;
+   }
+   else
+   {
+      vcos_event_flags_delete(&(*comp)->event);
+      vcos_semaphore_delete(&(*comp)->sema);
+      vcos_free(*comp);
+      *comp = NULL;
+      return -1;
+   }
+}
+
+/***********************************************************
+ * Name: ilclient_remove_event
+ *
+ * Description: Removes an event from a component event list.  ignore1
+ * and ignore2 are flags indicating whether to not match on nData1 and
+ * nData2 respectively.
+ *
+ * Returns: 0 if the event was removed.  -1 if no matching event was
+ * found.
+ ***********************************************************/
+int ilclient_remove_event(COMPONENT_T *st, OMX_EVENTTYPE eEvent,
+                          OMX_U32 nData1, int ignore1, OMX_IN OMX_U32 nData2, int ignore2)
+{
+   ILEVENT_T *cur, *prev;
+   uint32_t set;
+   ilclient_lock_events(st->client);
+
+   cur = st->list;
+   prev = NULL;
+
+   while (cur && !(cur->eEvent == eEvent && (ignore1 || cur->nData1 == nData1) && (ignore2 || cur->nData2 == nData2)))
+   {
+      prev = cur;
+      cur = cur->next;
+   }
+
+   if (cur == NULL)
+   {
+      ilclient_unlock_events(st->client);
+      return -1;
+   }
+
+   if (prev == NULL)
+      st->list = cur->next;
+   else
+      prev->next = cur->next;
+
+   // add back into spare list
+   cur->next = st->client->event_list;
+   st->client->event_list = cur;
+   cur->eEvent = -1; // mark as unused
+
+   // if we're removing an OMX_EventError or OMX_EventParamOrConfigChanged event, then clear the error bit from the eventgroup,
+   // since the user might have been notified through the error callback, and then 
+   // can't clear the event bit - this will then cause problems the next time they
+   // wait for an error.
+   if(eEvent == OMX_EventError)
+      vcos_event_flags_get(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR_CONSUME, 0, &set);
+   else if(eEvent == OMX_EventParamOrConfigChanged)
+      vcos_event_flags_get(&st->event, ILCLIENT_CONFIG_CHANGED, VCOS_OR_CONSUME, 0, &set);
+
+   ilclient_unlock_events(st->client);
+   return 0;
+}
+
+/***********************************************************
+ * Name: ilclient_state_transition
+ *
+ * Description: Transitions a null terminated list of IL components to
+ * a given state.  All components are told to transition in a random
+ * order before any are checked for transition completion.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_state_transition(COMPONENT_T *list[], OMX_STATETYPE state)
+{
+   OMX_ERRORTYPE error;
+   int i, num;
+   uint32_t set;
+
+   num=0;
+   while (list[num])
+      num++;
+
+   // if we transition the supplier port first, it will call freebuffer on the non
+   // supplier, which will correctly signal a port unpopulated error.  We want to
+   // ignore these errors.
+   if (state == OMX_StateLoaded)
+      for (i=0; i<num; i++)
+         list[i]->error_mask |= ILCLIENT_ERROR_UNPOPULATED;
+   for (i=0; i<num; i++)
+      list[i]->private = ((rand() >> 13) & 0xff)+1;
+
+   for (i=0; i<num; i++)
+   {
+      // transition the components in a random order
+      int j, min = -1;
+      for (j=0; j<num; j++)
+         if (list[j]->private && (min == -1 || list[min]->private > list[j]->private))
+            min = j;
+
+      list[min]->private = 0;
+
+      random_wait();
+      //Clear error event for this component
+      vcos_event_flags_get(&list[min]->event, ILCLIENT_EVENT_ERROR, VCOS_OR_CONSUME, 0, &set);
+
+      error = OMX_SendCommand(list[min]->comp, OMX_CommandStateSet, state, NULL);
+      vc_assert(error == OMX_ErrorNone);
+   }
+
+   random_wait();
+
+   for (i=0; i<num; i++)
+      if(ilclient_wait_for_command_complete(list[i], OMX_CommandStateSet, state) < 0)
+         vc_assert(0);
+
+   if (state == OMX_StateLoaded)
+      for (i=0; i<num; i++)
+         list[i]->error_mask &= ~ILCLIENT_ERROR_UNPOPULATED;
+}
+
+/***********************************************************
+ * Name: ilclient_teardown_tunnels
+ *
+ * Description: tears down a null terminated list of tunnels.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_teardown_tunnels(TUNNEL_T *tunnel)
+{
+   int i;
+   OMX_ERRORTYPE error;
+
+   i=0;;
+   while (tunnel[i].source)
+   {
+      error = OMX_SetupTunnel(tunnel[i].source->comp, tunnel[i].source_port, NULL, 0);
+      vc_assert(error == OMX_ErrorNone);
+
+      error = OMX_SetupTunnel(tunnel[i].sink->comp, tunnel[i].sink_port, NULL, 0);
+      vc_assert(error == OMX_ErrorNone);
+      i++;
+   }
+}
+
+/***********************************************************
+ * Name: ilclient_disable_tunnel
+ *
+ * Description: disables a tunnel by disabling the ports.  Allows
+ * ports to signal same state error if they were already disabled.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_disable_tunnel(TUNNEL_T *tunnel)
+{
+   OMX_ERRORTYPE error;
+   
+   if(tunnel->source == 0 || tunnel->sink == 0)
+      return;
+
+   tunnel->source->error_mask |= ILCLIENT_ERROR_UNPOPULATED;
+   tunnel->sink->error_mask |= ILCLIENT_ERROR_UNPOPULATED;
+
+   error = OMX_SendCommand(tunnel->source->comp, OMX_CommandPortDisable, tunnel->source_port, NULL);
+   vc_assert(error == OMX_ErrorNone);
+
+   error = OMX_SendCommand(tunnel->sink->comp, OMX_CommandPortDisable, tunnel->sink_port, NULL);
+   vc_assert(error == OMX_ErrorNone);
+
+   if(ilclient_wait_for_command_complete(tunnel->source, OMX_CommandPortDisable, tunnel->source_port) < 0)
+      vc_assert(0);
+
+   if(ilclient_wait_for_command_complete(tunnel->sink, OMX_CommandPortDisable, tunnel->sink_port) < 0)
+      vc_assert(0);
+
+   tunnel->source->error_mask &= ~ILCLIENT_ERROR_UNPOPULATED;
+   tunnel->sink->error_mask &= ~ILCLIENT_ERROR_UNPOPULATED;
+}
+
+/***********************************************************
+ * Name: ilclient_enable_tunnel
+ *
+ * Description: enables a tunnel by enabling the ports
+ *
+ * Returns: 0 on success, -1 on failure
+ ***********************************************************/
+int ilclient_enable_tunnel(TUNNEL_T *tunnel)
+{
+   OMX_STATETYPE state;
+   OMX_ERRORTYPE error;
+
+   ilclient_debug_output("ilclient: enable tunnel from %x/%d to %x/%d",
+                         tunnel->source, tunnel->source_port,
+                         tunnel->sink, tunnel->sink_port);
+
+   error = OMX_SendCommand(tunnel->source->comp, OMX_CommandPortEnable, tunnel->source_port, NULL);
+   vc_assert(error == OMX_ErrorNone);
+
+   error = OMX_SendCommand(tunnel->sink->comp, OMX_CommandPortEnable, tunnel->sink_port, NULL);
+   vc_assert(error == OMX_ErrorNone);
+
+   // to complete, the sink component can't be in loaded state
+   error = OMX_GetState(tunnel->sink->comp, &state);
+   vc_assert(error == OMX_ErrorNone);
+   if (state == OMX_StateLoaded)
+   {
+      int ret = 0;
+
+      if(ilclient_wait_for_command_complete(tunnel->sink, OMX_CommandPortEnable, tunnel->sink_port) != 0 ||
+         OMX_SendCommand(tunnel->sink->comp, OMX_CommandStateSet, OMX_StateIdle, NULL) != OMX_ErrorNone ||
+         (ret = ilclient_wait_for_command_complete_dual(tunnel->sink, OMX_CommandStateSet, OMX_StateIdle, tunnel->source)) < 0)
+      {
+         if(ret == -2)
+         {
+            // the error was reported fom the source component: clear this error and disable the sink component
+            ilclient_wait_for_command_complete(tunnel->source, OMX_CommandPortEnable, tunnel->source_port);
+            ilclient_disable_port(tunnel->sink, tunnel->sink_port);
+         }
+
+         ilclient_debug_output("ilclient: could not change component state to IDLE");
+         ilclient_disable_port(tunnel->source, tunnel->source_port);
+         return -1;
+      }
+   }
+   else
+   {
+      if (ilclient_wait_for_command_complete(tunnel->sink, OMX_CommandPortEnable, tunnel->sink_port) != 0)
+      {
+         ilclient_debug_output("ilclient: could not change sink port %d to enabled", tunnel->sink_port);
+
+         //Oops failed to enable the sink port
+         ilclient_disable_port(tunnel->source, tunnel->source_port);
+         //Clean up the port enable event from the source port.
+         ilclient_wait_for_event(tunnel->source, OMX_EventCmdComplete,
+                                 OMX_CommandPortEnable, 0, tunnel->source_port, 0,
+                                 ILCLIENT_PORT_ENABLED | ILCLIENT_EVENT_ERROR, VCOS_EVENT_FLAGS_SUSPEND);
+         return -1;
+      }
+   }
+
+   if(ilclient_wait_for_command_complete(tunnel->source, OMX_CommandPortEnable, tunnel->source_port) != 0)
+   {
+      ilclient_debug_output("ilclient: could not change source port %d to enabled", tunnel->source_port);
+
+      //Failed to enable the source port
+      ilclient_disable_port(tunnel->sink, tunnel->sink_port);
+      return -1;
+   }
+
+   return 0;
+}
+
+
+/***********************************************************
+ * Name: ilclient_flush_tunnels
+ *
+ * Description: flushes all ports used in a null terminated list of
+ * tunnels.  max specifies the maximum number of tunnels to flush from
+ * the list, where max=0 means all tunnels.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_flush_tunnels(TUNNEL_T *tunnel, int max)
+{
+   OMX_ERRORTYPE error;
+   int i;
+
+   i=0;
+   while (tunnel[i].source && (max == 0 || i < max))
+   {
+      error = OMX_SendCommand(tunnel[i].source->comp, OMX_CommandFlush, tunnel[i].source_port, NULL);
+      vc_assert(error == OMX_ErrorNone);
+
+      error = OMX_SendCommand(tunnel[i].sink->comp, OMX_CommandFlush, tunnel[i].sink_port, NULL);
+      vc_assert(error == OMX_ErrorNone);
+
+      ilclient_wait_for_event(tunnel[i].source, OMX_EventCmdComplete,
+                              OMX_CommandFlush, 0, tunnel[i].source_port, 0,
+                              ILCLIENT_PORT_FLUSH, VCOS_EVENT_FLAGS_SUSPEND);
+      ilclient_wait_for_event(tunnel[i].sink, OMX_EventCmdComplete,
+                              OMX_CommandFlush, 0, tunnel[i].sink_port, 0,
+                              ILCLIENT_PORT_FLUSH, VCOS_EVENT_FLAGS_SUSPEND);
+      i++;
+   }
+}
+
+
+/***********************************************************
+ * Name: ilclient_return_events
+ *
+ * Description: Returns all events from a component event list to the
+ * list of unused event structures.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_return_events(COMPONENT_T *comp)
+{
+   ilclient_lock_events(comp->client);
+   while (comp->list)
+   {
+      ILEVENT_T *next = comp->list->next;
+      comp->list->next = comp->client->event_list;
+      comp->client->event_list = comp->list;
+      comp->list = next;
+   }
+   ilclient_unlock_events(comp->client);
+}
+
+/***********************************************************
+ * Name: ilclient_cleanup_components
+ *
+ * Description: frees all components from a null terminated list and
+ * deletes resources used in component state structure.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_cleanup_components(COMPONENT_T *list[])
+{
+   int i;
+   OMX_ERRORTYPE error;
+
+   i=0;
+   while (list[i])
+   {
+      ilclient_return_events(list[i]);
+      if (list[i]->comp)
+      {
+         error = OMX_FreeHandle(list[i]->comp);
+
+         vc_assert(error == OMX_ErrorNone);
+      }
+      i++;
+   }
+
+   i=0;
+   while (list[i])
+   {
+      vcos_event_flags_delete(&list[i]->event);
+      vcos_semaphore_delete(&list[i]->sema);
+      vcos_free(list[i]);
+      list[i] = NULL;
+      i++;
+   }
+}
+
+/***********************************************************
+ * Name: ilclient_change_component_state
+ *
+ * Description: changes the state of a single component.  Note: this
+ * may not be suitable if the component is tunnelled and requires
+ * connected components to also change state.
+ *
+ * Returns: 0 on success, -1 on failure (note - trying to change to
+ * the same state which causes a OMX_ErrorSameState is treated as
+ * success)
+ ***********************************************************/
+int ilclient_change_component_state(COMPONENT_T *comp, OMX_STATETYPE state)
+{
+   OMX_ERRORTYPE error;
+   error = OMX_SendCommand(comp->comp, OMX_CommandStateSet, state, NULL);
+   vc_assert(error == OMX_ErrorNone);
+   if(ilclient_wait_for_command_complete(comp, OMX_CommandStateSet, state) < 0)
+   {
+      ilclient_debug_output("ilclient: could not change component state to %d", state);
+      ilclient_remove_event(comp, OMX_EventError, 0, 1, 0, 1);
+      return -1;
+   }
+   return 0;
+}
+
+/***********************************************************
+ * Name: ilclient_disable_port
+ *
+ * Description: disables a port on a given component.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_disable_port(COMPONENT_T *comp, int portIndex)
+{
+   OMX_ERRORTYPE error;
+   error = OMX_SendCommand(comp->comp, OMX_CommandPortDisable, portIndex, NULL);
+   vc_assert(error == OMX_ErrorNone);
+   if(ilclient_wait_for_command_complete(comp, OMX_CommandPortDisable, portIndex) < 0)
+      vc_assert(0);
+}
+
+/***********************************************************
+ * Name: ilclient_enabled_port
+ *
+ * Description: enables a port on a given component.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_enable_port(COMPONENT_T *comp, int portIndex)
+{
+   OMX_ERRORTYPE error;
+   error = OMX_SendCommand(comp->comp, OMX_CommandPortEnable, portIndex, NULL);
+   vc_assert(error == OMX_ErrorNone);
+   if(ilclient_wait_for_command_complete(comp, OMX_CommandPortEnable, portIndex) < 0)
+      vc_assert(0);
+}
+
+
+/***********************************************************
+ * Name: ilclient_enable_port_buffers
+ *
+ * Description: enables a port on a given component which requires
+ * buffers to be supplied by the client.
+ *
+ * Returns: void
+ ***********************************************************/
+int ilclient_enable_port_buffers(COMPONENT_T *comp, int portIndex,
+                                 ILCLIENT_MALLOC_T ilclient_malloc,
+                                 ILCLIENT_FREE_T ilclient_free,
+                                 void *private)
+{
+   OMX_ERRORTYPE error;
+   OMX_PARAM_PORTDEFINITIONTYPE portdef;
+   OMX_BUFFERHEADERTYPE *list = NULL, **end = &list;
+   OMX_STATETYPE state;
+   int i;
+
+   memset(&portdef, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+   portdef.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
+   portdef.nVersion.nVersion = OMX_VERSION;
+   portdef.nPortIndex = portIndex;
+   
+   // work out buffer requirements, check port is in the right state
+   error = OMX_GetParameter(comp->comp, OMX_IndexParamPortDefinition, &portdef);
+   if(error != OMX_ErrorNone || portdef.bEnabled != OMX_FALSE || portdef.nBufferCountActual == 0 || portdef.nBufferSize == 0)
+      return -1;
+
+   // check component is in the right state to accept buffers
+   error = OMX_GetState(comp->comp, &state);
+   if (error != OMX_ErrorNone || !(state == OMX_StateIdle || state == OMX_StateExecuting || state == OMX_StatePause))
+      return -1;
+
+   // send the command
+   error = OMX_SendCommand(comp->comp, OMX_CommandPortEnable, portIndex, NULL);
+   vc_assert(error == OMX_ErrorNone);
+
+   for (i=0; i != portdef.nBufferCountActual; i++)
+   {
+      unsigned char *buf;
+      if(ilclient_malloc)
+         buf = ilclient_malloc(private, portdef.nBufferSize, portdef.nBufferAlignment, comp->bufname);
+      else
+         buf = vcos_malloc_aligned(portdef.nBufferSize, portdef.nBufferAlignment, comp->bufname);
+
+      if(!buf)
+         break;
+
+      error = OMX_UseBuffer(comp->comp, end, portIndex, NULL, portdef.nBufferSize, buf);
+      if(error != OMX_ErrorNone)
+      {
+         if(ilclient_free)
+            ilclient_free(private, buf);
+         else
+            vcos_free(buf);
+
+         break;
+      }
+      end = (OMX_BUFFERHEADERTYPE **) &((*end)->pAppPrivate);
+   }
+
+   // queue these buffers
+   vcos_semaphore_wait(&comp->sema);
+
+   if(portdef.eDir == OMX_DirInput)
+   {
+      *end = comp->in_list;
+      comp->in_list = list;
+   }
+   else
+   {
+      *end = comp->out_list;
+      comp->out_list = list;
+   }
+
+   vcos_semaphore_post(&comp->sema);
+
+   if(i != portdef.nBufferCountActual ||
+      ilclient_wait_for_command_complete(comp, OMX_CommandPortEnable, portIndex) < 0)
+   {
+      ilclient_disable_port_buffers(comp, portIndex, NULL, ilclient_free, private);
+
+      // at this point the first command might have terminated with an error, which means that
+      // the port is disabled before the disable_port_buffers function is called, so we're left
+      // with the error bit set and an error event in the queue.  Clear these now if they exist.
+      ilclient_remove_event(comp, OMX_EventError, 0, 1, 1, 0);
+
+      return -1;
+   }
+
+   // success
+   return 0;
+}
+
+
+/***********************************************************
+ * Name: ilclient_disable_port_buffers
+ *
+ * Description: disables a port on a given component which has
+ * buffers supplied by the client.
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_disable_port_buffers(COMPONENT_T *comp, int portIndex,
+                                   OMX_BUFFERHEADERTYPE *bufferList,
+                                   ILCLIENT_FREE_T ilclient_free,
+                                   void *private)
+{
+   OMX_ERRORTYPE error;
+   OMX_BUFFERHEADERTYPE *list = bufferList;
+   OMX_BUFFERHEADERTYPE **head, *clist, *prev;
+   OMX_PARAM_PORTDEFINITIONTYPE portdef;
+   int num;
+
+   // get the buffers off the relevant queue
+   memset(&portdef, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+   portdef.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
+   portdef.nVersion.nVersion = OMX_VERSION;
+   portdef.nPortIndex = portIndex;
+   
+   // work out buffer requirements, check port is in the right state
+   error = OMX_GetParameter(comp->comp, OMX_IndexParamPortDefinition, &portdef);
+   if(error != OMX_ErrorNone || portdef.bEnabled != OMX_TRUE || portdef.nBufferCountActual == 0 || portdef.nBufferSize == 0)
+      return;
+   
+   num = portdef.nBufferCountActual;
+   
+   error = OMX_SendCommand(comp->comp, OMX_CommandPortDisable, portIndex, NULL);
+   vc_assert(error == OMX_ErrorNone);
+      
+   while(num > 0)
+   {
+      VCOS_UNSIGNED set;
+
+      if(list == NULL)
+      {
+         vcos_semaphore_wait(&comp->sema);
+         
+         // take buffers for this port off the relevant queue
+         head = portdef.eDir == OMX_DirInput ? &comp->in_list : &comp->out_list;
+         clist = *head;
+         prev = NULL;
+         
+         while(clist)
+         {
+            if((portdef.eDir == OMX_DirInput ? clist->nInputPortIndex : clist->nOutputPortIndex) == portIndex)
+            {
+               OMX_BUFFERHEADERTYPE *pBuffer = clist;
+               
+               if(!prev)
+                  clist = *head = (OMX_BUFFERHEADERTYPE *) pBuffer->pAppPrivate;
+               else
+                  clist = prev->pAppPrivate = (OMX_BUFFERHEADERTYPE *) pBuffer->pAppPrivate;
+               
+               pBuffer->pAppPrivate = list;
+               list = pBuffer;
+            }
+            else
+            {
+               prev = clist;
+               clist = (OMX_BUFFERHEADERTYPE *) &(clist->pAppPrivate);
+            }
+         }
+         
+         vcos_semaphore_post(&comp->sema);
+      }
+
+      while(list)
+      {
+         void *buf = list->pBuffer;
+         OMX_BUFFERHEADERTYPE *next = list->pAppPrivate;
+         
+         error = OMX_FreeBuffer(comp->comp, portIndex, list);
+         vc_assert(error == OMX_ErrorNone);
+         
+         if(ilclient_free)
+            ilclient_free(private, buf);
+         else
+            vcos_free(buf);
+         
+         num--;
+         list = next;
+      }
+
+      if(num)
+      {
+         OMX_U32 mask = ILCLIENT_PORT_DISABLED | ILCLIENT_EVENT_ERROR;
+         mask |= (portdef.eDir == OMX_DirInput ? ILCLIENT_EMPTY_BUFFER_DONE : ILCLIENT_FILL_BUFFER_DONE);
+
+         // also wait for command complete/error in case we didn't have all the buffers allocated
+         vcos_event_flags_get(&comp->event, mask, VCOS_OR_CONSUME, -1, &set);
+
+         if((set & ILCLIENT_EVENT_ERROR) && ilclient_remove_event(comp, OMX_EventError, 0, 1, 1, 0) >= 0)
+            return;
+
+         if((set & ILCLIENT_PORT_DISABLED) && ilclient_remove_event(comp, OMX_EventCmdComplete, OMX_CommandPortDisable, 0, portIndex, 0) >= 0)
+            return;
+      }            
+   }
+  
+   if(ilclient_wait_for_command_complete(comp, OMX_CommandPortDisable, portIndex) < 0)
+      vc_assert(0);
+}
+
+
+/***********************************************************
+ * Name: ilclient_setup_tunnel
+ *
+ * Description: creates a tunnel between components that require that
+ * ports be inititially disabled, then enabled after tunnel setup.  If
+ * timeout is non-zero, it will initially wait until a port settings
+ * changes message has been received by the output port.  If port
+ * streams are supported by the output port, the requested port stream
+ * will be selected.
+ *
+ * Returns: 0 indicates success, negative indicates failure.
+ * -1: a timeout waiting for the parameter changed
+ * -2: an error was returned instead of parameter changed
+ * -3: no streams are available from this port
+ * -4: requested stream is not available from this port
+ * -5: the data format was not acceptable to the sink
+ ***********************************************************/
+int ilclient_setup_tunnel(TUNNEL_T *tunnel, unsigned int portStream, int timeout)
+{
+   OMX_ERRORTYPE error;
+   OMX_PARAM_U32TYPE param;
+   OMX_STATETYPE state;
+   int enable_error;
+
+   // source component must at least be idle, not loaded
+   error = OMX_GetState(tunnel->source->comp, &state);
+   vc_assert(error == OMX_ErrorNone);
+   if (state == OMX_StateLoaded && ilclient_change_component_state(tunnel->source, OMX_StateIdle) < 0)
+      return -2;
+
+   // wait for the port parameter changed from the source port
+   if(timeout)
+   {
+	   int32_t status = ilclient_wait_for_event(tunnel->source, OMX_EventPortSettingsChanged,
+                                       tunnel->source_port, 0, -1, 1,
+                                       ILCLIENT_PARAMETER_CHANGED | ILCLIENT_EVENT_ERROR, timeout);
+      
+      if (status < 0)
+      {
+         ilclient_debug_output(
+            "ilclient: timed out waiting for port settings changed on port %d", tunnel->source_port);
+         return status;
+      }
+   }
+
+   // disable ports
+   ilclient_disable_tunnel(tunnel);
+
+   // if this source port uses port streams, we need to select one of them before proceeding
+   // if getparameter causes an error that's fine, nothing needs selecting
+   param.nSize = sizeof(OMX_PARAM_U32TYPE);
+   param.nVersion.nVersion = OMX_VERSION;
+   param.nPortIndex = tunnel->source_port;
+   if (OMX_GetParameter(tunnel->source->comp, OMX_IndexParamNumAvailableStreams, &param) == OMX_ErrorNone)
+   {
+      if (param.nU32 == 0)
+      {
+         // no streams available
+         // leave the source port disabled, and return a failure
+         return -3;
+      }
+      if (param.nU32 <= portStream)
+      {
+         // requested stream not available
+         // no streams available
+         // leave the source port disabled, and return a failure
+         return -4;
+      }
+
+      param.nU32 = portStream;
+      error = OMX_SetParameter(tunnel->source->comp, OMX_IndexParamActiveStream, &param);
+      vc_assert(error == OMX_ErrorNone);
+   }
+
+   // now create the tunnel
+   error = OMX_SetupTunnel(tunnel->source->comp, tunnel->source_port, tunnel->sink->comp, tunnel->sink_port);
+
+   enable_error = 0;
+
+   if (error != OMX_ErrorNone || (enable_error=ilclient_enable_tunnel(tunnel)) < 0)
+   {
+      // probably format not compatible
+      error = OMX_SetupTunnel(tunnel->source->comp, tunnel->source_port, NULL, 0);
+      vc_assert(error == OMX_ErrorNone);
+      error = OMX_SetupTunnel(tunnel->sink->comp, tunnel->sink_port, NULL, 0);
+      vc_assert(error == OMX_ErrorNone);
+
+      if(enable_error)
+      {
+         //Clean up the errors. This does risk removing an error that was nothing to do with this tunnel :-/
+         ilclient_remove_event(tunnel->sink, OMX_EventError, 0, 1, 0, 1);
+         ilclient_remove_event(tunnel->source, OMX_EventError, 0, 1, 0, 1);
+      }
+
+      ilclient_debug_output("ilclient: could not setup/enable tunnel (setup=0x%x,enable=%d)",
+                             error, enable_error);
+      return -5;
+   }
+
+   return 0;
+}
+
+/***********************************************************
+ * Name: ilclient_wait_for_event
+ *
+ * Description: waits for a given event to appear on a component event
+ * list.  If not immediately present, will wait on that components
+ * event group for the given event flag.
+ *
+ * Returns: 0 indicates success, negative indicates failure.
+ * -1: a timeout was received.
+ * -2: an error event was received.
+ * -3: a config change event was received.
+ ***********************************************************/
+int ilclient_wait_for_event(COMPONENT_T *comp, OMX_EVENTTYPE event,
+                            OMX_U32 nData1, int ignore1, OMX_IN OMX_U32 nData2, int ignore2,
+                            int event_flag, int suspend)
+{
+   uint32_t set;
+
+   while (ilclient_remove_event(comp, event, nData1, ignore1, nData2, ignore2) < 0)
+   {
+      // if we want to be notified of errors, check the list for an error now
+      // before blocking, the event flag may have been cleared already.
+      if(event_flag & ILCLIENT_EVENT_ERROR)
+      {
+         ILEVENT_T *cur;
+         ilclient_lock_events(comp->client);
+         cur = comp->list;
+         while(cur && cur->eEvent != OMX_EventError)            
+            cur = cur->next;
+         
+         if(cur)
+         {
+            // clear error flag
+            vcos_event_flags_get(&comp->event, ILCLIENT_EVENT_ERROR, VCOS_OR_CONSUME, 0, &set);
+            ilclient_unlock_events(comp->client);
+            return -2;
+         }
+
+         ilclient_unlock_events(comp->client);
+      }
+      // check for config change event if we are asked to be notified of that
+      if(event_flag & ILCLIENT_CONFIG_CHANGED)
+      {
+         ILEVENT_T *cur;
+         ilclient_lock_events(comp->client);
+         cur = comp->list;
+         while(cur && cur->eEvent != OMX_EventParamOrConfigChanged)
+            cur = cur->next;
+         
+         ilclient_unlock_events(comp->client);
+
+         if(cur)
+            return ilclient_remove_event(comp, event, nData1, ignore1, nData2, ignore2) == 0 ? 0 : -3;
+      }
+
+      int32_t status = vcos_event_flags_get(&comp->event, event_flag, VCOS_OR_CONSUME,
+                                    suspend, &set);
+      if (status != 0)
+         return -1;
+      if (set & ILCLIENT_EVENT_ERROR)
+         return -2;
+      if (set & ILCLIENT_CONFIG_CHANGED)
+         return ilclient_remove_event(comp, event, nData1, ignore1, nData2, ignore2) == 0 ? 0 : -3;
+   }
+
+   return 0;
+}
+
+
+
+/***********************************************************
+ * Name: ilclient_wait_for_command_complete_dual
+ *
+ * Description: Waits for an event signalling command completion.  In
+ * this version we may also return failure if there is an error event
+ * that has terminated a command on a second component.
+ *
+ * Returns: 0 on success, -1 on failure of comp, -2 on failure of other
+ ***********************************************************/
+int ilclient_wait_for_command_complete_dual(COMPONENT_T *comp, OMX_COMMANDTYPE command, OMX_U32 nData2, COMPONENT_T *other)
+{
+   OMX_U32 mask = ILCLIENT_EVENT_ERROR;
+   int ret = 0;
+
+   switch(command) {
+   case OMX_CommandStateSet:    mask |= ILCLIENT_STATE_CHANGED; break;
+   case OMX_CommandPortDisable: mask |= ILCLIENT_PORT_DISABLED; break;
+   case OMX_CommandPortEnable:  mask |= ILCLIENT_PORT_ENABLED;  break;
+   default: return -1;
+   }
+
+   if(other)
+      other->related = comp;
+
+   while(1)
+   {
+      ILEVENT_T *cur, *prev = NULL;
+      VCOS_UNSIGNED set;
+
+      ilclient_lock_events(comp->client);
+
+      cur = comp->list;
+      while(cur &&
+            !(cur->eEvent == OMX_EventCmdComplete && cur->nData1 == command && cur->nData2 == nData2) &&
+            !(cur->eEvent == OMX_EventError && cur->nData2 == 1))
+      {
+         prev = cur;
+         cur = cur->next;
+      }
+
+      if(cur)
+      {
+         if(prev == NULL)
+            comp->list = cur->next;
+         else
+            prev->next = cur->next;
+
+         // work out whether this was a success or a fail event
+         ret = cur->eEvent == OMX_EventCmdComplete || cur->nData1 == OMX_ErrorSameState ? 0 : -1;
+
+         if(cur->eEvent == OMX_EventError)
+            vcos_event_flags_get(&comp->event, ILCLIENT_EVENT_ERROR, VCOS_OR_CONSUME, 0, &set);
+
+         // add back into spare list
+         cur->next = comp->client->event_list;
+         comp->client->event_list = cur;
+         cur->eEvent = -1; // mark as unused
+         
+         ilclient_unlock_events(comp->client);
+         break;
+      }
+      else if(other != NULL)
+      {
+         // check the other component for an error event that terminates a command
+         cur = other->list;
+         while(cur && !(cur->eEvent == OMX_EventError && cur->nData2 == 1))
+            cur = cur->next;
+
+         if(cur)
+         {
+            // we don't remove the event in this case, since the user
+            // can confirm that this event errored by calling wait_for_command on the
+            // other component
+
+            ret = -2;
+            ilclient_unlock_events(comp->client);
+            break;
+         }
+      }
+
+      ilclient_unlock_events(comp->client);
+
+      vcos_event_flags_get(&comp->event, mask, VCOS_OR_CONSUME, VCOS_SUSPEND, &set);
+   }
+
+   if(other)
+      other->related = NULL;
+
+   return ret;
+}
+
+
+/***********************************************************
+ * Name: ilclient_wait_for_command_complete
+ *
+ * Description: Waits for an event signalling command completion.
+ *
+ * Returns: 0 on success, -1 on failure.
+ ***********************************************************/
+int ilclient_wait_for_command_complete(COMPONENT_T *comp, OMX_COMMANDTYPE command, OMX_U32 nData2)
+{
+   return ilclient_wait_for_command_complete_dual(comp, command, nData2, NULL);
+}
+
+/***********************************************************
+ * Name: ilclient_get_output_buffer
+ *
+ * Description: Returns an output buffer returned from a component
+ * using the OMX_FillBufferDone callback from the output list for the
+ * given component and port index.
+ *
+ * Returns: pointer to buffer if available, otherwise NULL
+ ***********************************************************/
+OMX_BUFFERHEADERTYPE *ilclient_get_output_buffer(COMPONENT_T *comp, int portIndex, int block)
+{
+   OMX_BUFFERHEADERTYPE *ret = NULL, *prev = NULL;
+   VCOS_UNSIGNED set;
+
+   do {
+      vcos_semaphore_wait(&comp->sema);
+      ret = comp->out_list;
+      while(ret != NULL && ret->nOutputPortIndex != portIndex)
+      {
+         prev = ret;
+         ret = ret->pAppPrivate;
+      }
+      
+      if(ret)
+      {
+         if(prev == NULL)
+            comp->out_list = ret->pAppPrivate;
+         else
+            prev->pAppPrivate = ret->pAppPrivate;
+         
+         ret->pAppPrivate = NULL;
+      }
+      vcos_semaphore_post(&comp->sema);
+
+      if(block && !ret)
+         vcos_event_flags_get(&comp->event, ILCLIENT_FILL_BUFFER_DONE, VCOS_OR_CONSUME, -1, &set);
+
+   } while(block && !ret);
+
+   return ret;
+}
+
+/***********************************************************
+ * Name: ilclient_get_input_buffer
+ *
+ * Description: Returns an input buffer return from a component using
+ * the OMX_EmptyBufferDone callback from the output list for the given
+ * component and port index.
+ *
+ * Returns: pointer to buffer if available, otherwise NULL
+ ***********************************************************/
+OMX_BUFFERHEADERTYPE *ilclient_get_input_buffer(COMPONENT_T *comp, int portIndex, int block)
+{
+   OMX_BUFFERHEADERTYPE *ret = NULL, *prev = NULL;
+
+   do {
+      VCOS_UNSIGNED set;
+
+      vcos_semaphore_wait(&comp->sema);
+      ret = comp->in_list;
+      while(ret != NULL && ret->nInputPortIndex != portIndex)
+      {
+         prev = ret;
+         ret = ret->pAppPrivate;
+      }
+      
+      if(ret)
+      {
+         if(prev == NULL)
+            comp->in_list = ret->pAppPrivate;
+         else
+            prev->pAppPrivate = ret->pAppPrivate;
+         
+         ret->pAppPrivate = NULL;
+      }
+      vcos_semaphore_post(&comp->sema);
+
+      if(block && !ret)
+         vcos_event_flags_get(&comp->event, ILCLIENT_EMPTY_BUFFER_DONE, VCOS_OR_CONSUME, -1, &set);
+
+   } while(block && !ret);
+
+   return ret;
+}
+
+/***********************************************************
+ * Name: ilclient_debug_output
+ *
+ * Description: prints a varg message to the log or the debug screen
+ * under win32
+ *
+ * Returns: void
+ ***********************************************************/
+void ilclient_debug_output(char *format, ...)
+{
+   va_list args;
+
+   va_start(args, format);
+   vcos_vlog_info(format, args);
+   va_end(args);
+}
+
+/******************************************************************************
+Static functions
+******************************************************************************/
+
+/***********************************************************
+ * Name: ilclient_lock_events
+ *
+ * Description: locks the client event structure
+ *
+ * Returns: void
+ ***********************************************************/
+static void ilclient_lock_events(ILCLIENT_T *st)
+{
+   vcos_semaphore_wait(&st->event_sema);
+}
+
+/***********************************************************
+ * Name: ilclient_unlock_events
+ *
+ * Description: unlocks the client event structure
+ *
+ * Returns: void
+ ***********************************************************/
+static void ilclient_unlock_events(ILCLIENT_T *st)
+{
+   vcos_semaphore_post(&st->event_sema);
+}
+
+/***********************************************************
+ * Name: ilclient_event_handler
+ *
+ * Description: event handler passed to core to use as component
+ * callback
+ *
+ * Returns: success
+ ***********************************************************/
+static OMX_ERRORTYPE ilclient_event_handler(OMX_IN OMX_HANDLETYPE hComponent,
+                                            OMX_IN OMX_PTR pAppData,
+                                            OMX_IN OMX_EVENTTYPE eEvent,
+                                            OMX_IN OMX_U32 nData1,
+                                            OMX_IN OMX_U32 nData2,
+                                            OMX_IN OMX_PTR pEventData)
+{
+   COMPONENT_T *st = (COMPONENT_T *) pAppData;
+   ILEVENT_T *event;
+   OMX_ERRORTYPE error = OMX_ErrorNone;
+
+   ilclient_lock_events(st->client);
+
+   // go through the events on this component and remove any duplicates in the
+   // existing list, since the client probably doesn't need them.  it's better
+   // than asserting when we run out.
+   event = st->list;
+   while(event != NULL)
+   {
+      ILEVENT_T **list = &(event->next);
+      while(*list != NULL)
+      {
+         if((*list)->eEvent == event->eEvent &&
+            (*list)->nData1 == event->nData1 &&
+            (*list)->nData2 == event->nData2)
+         {
+            // remove this duplicate
+            ILEVENT_T *rem = *list;
+            ilclient_debug_output("%s: removing %d/%d/%d", st->name, event->eEvent, event->nData1, event->nData2);            
+            *list = rem->next;
+            rem->eEvent = -1;
+            rem->next = st->client->event_list;
+            st->client->event_list = rem;
+         }
+         else
+            list = &((*list)->next);
+      }
+
+      event = event->next;
+   }
+
+   vc_assert(st->client->event_list);
+   event = st->client->event_list;
+
+   switch (eEvent) {
+   case OMX_EventCmdComplete:
+      switch (nData1) {
+      case OMX_CommandStateSet:
+         ilclient_debug_output("%s: callback state changed (%s)", st->name, states[nData2]);
+         vcos_event_flags_set(&st->event, ILCLIENT_STATE_CHANGED, VCOS_OR);
+         break;
+      case OMX_CommandPortDisable:
+         ilclient_debug_output("%s: callback port disable %d", st->name, nData2);
+         vcos_event_flags_set(&st->event, ILCLIENT_PORT_DISABLED, VCOS_OR);
+         break;
+      case OMX_CommandPortEnable:
+         ilclient_debug_output("%s: callback port enable %d", st->name, nData2);
+         vcos_event_flags_set(&st->event, ILCLIENT_PORT_ENABLED, VCOS_OR);
+         break;
+      case OMX_CommandFlush:
+         ilclient_debug_output("%s: callback port flush %d", st->name, nData2);
+         vcos_event_flags_set(&st->event, ILCLIENT_PORT_FLUSH, VCOS_OR);
+         break;
+      case OMX_CommandMarkBuffer:
+         ilclient_debug_output("%s: callback mark buffer %d", st->name, nData2);
+         vcos_event_flags_set(&st->event, ILCLIENT_MARKED_BUFFER, VCOS_OR);
+         break;
+      default:
+         vc_assert(0);
+      }
+      break;
+   case OMX_EventError:
+      {
+         // check if this component failed a command, and we have to notify another command
+         // of this failure
+         if(nData2 == 1 && st->related != NULL)
+            vcos_event_flags_set(&st->related->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+
+         error = nData1;
+         switch (error) {
+         case OMX_ErrorPortUnpopulated:
+            if (st->error_mask & ILCLIENT_ERROR_UNPOPULATED)
+            {
+               ilclient_debug_output("%s: ignore error: port unpopulated (%d)", st->name, nData2);
+               event = NULL;
+               break;
+            }
+            ilclient_debug_output("%s: port unpopulated %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         case OMX_ErrorSameState:
+            if (st->error_mask & ILCLIENT_ERROR_SAMESTATE)
+            {
+               ilclient_debug_output("%s: ignore error: same state (%d)", st->name, nData2);
+               event = NULL;
+               break;
+            }
+            ilclient_debug_output("%s: same state %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         case OMX_ErrorBadParameter:
+            if (st->error_mask & ILCLIENT_ERROR_BADPARAMETER)
+            {
+               ilclient_debug_output("%s: ignore error: bad parameter (%d)", st->name, nData2);
+               event = NULL;
+               break;
+            }
+            ilclient_debug_output("%s: bad parameter %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         case OMX_ErrorIncorrectStateTransition:
+            ilclient_debug_output("%s: incorrect state transition %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         case OMX_ErrorBadPortIndex:
+            ilclient_debug_output("%s: bad port index %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         case OMX_ErrorStreamCorrupt:
+            ilclient_debug_output("%s: stream corrupt %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         case OMX_ErrorInsufficientResources:
+            ilclient_debug_output("%s: insufficient resources %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         case OMX_ErrorUnsupportedSetting:
+            ilclient_debug_output("%s: unsupported setting %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         case OMX_ErrorOverflow:
+            ilclient_debug_output("%s: overflow %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         case OMX_ErrorDiskFull:
+            ilclient_debug_output("%s: disk full %x (%d)", st->name, error, nData2);
+            //we do not set the error
+            break;
+         case OMX_ErrorMaxFileSize:
+            ilclient_debug_output("%s: max file size %x (%d)", st->name, error, nData2);
+            //we do not set the error
+            break;
+         case OMX_ErrorDrmUnauthorised:
+            ilclient_debug_output("%s: drm file is unauthorised %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         case OMX_ErrorDrmExpired:
+            ilclient_debug_output("%s: drm file has expired %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         case OMX_ErrorDrmGeneral:
+            ilclient_debug_output("%s: drm library error %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         default:
+            vc_assert(0);
+            ilclient_debug_output("%s: unexpected error %x (%d)", st->name, error, nData2);
+            vcos_event_flags_set(&st->event, ILCLIENT_EVENT_ERROR, VCOS_OR);
+            break;
+         }
+      }
+      break;
+   case OMX_EventBufferFlag:
+      ilclient_debug_output("%s: buffer flag %d/%x", st->name, nData1, nData2);
+      if (nData2 & OMX_BUFFERFLAG_EOS)
+      {
+         vcos_event_flags_set(&st->event, ILCLIENT_BUFFER_FLAG_EOS, VCOS_OR);
+         nData2 = OMX_BUFFERFLAG_EOS;
+      }
+      else
+         vc_assert(0);
+      break;
+   case OMX_EventPortSettingsChanged:
+      ilclient_debug_output("%s: port settings changed %d", st->name, nData1);
+      vcos_event_flags_set(&st->event, ILCLIENT_PARAMETER_CHANGED, VCOS_OR);
+      break;
+   case OMX_EventMark:
+      ilclient_debug_output("%s: buffer mark %p", st->name, pEventData);
+      vcos_event_flags_set(&st->event, ILCLIENT_BUFFER_MARK, VCOS_OR);
+      break;
+   case OMX_EventParamOrConfigChanged:
+      ilclient_debug_output("%s: param/config 0x%X on port %d changed", st->name, nData2, nData1);
+      vcos_event_flags_set(&st->event, ILCLIENT_CONFIG_CHANGED, VCOS_OR);
+      break;
+   default:
+      vc_assert(0);
+      break;
+   }
+
+   if (event)
+   {
+      // fill in details
+      event->eEvent = eEvent;
+      event->nData1 = nData1;
+      event->nData2 = nData2;
+      event->pEventData = pEventData;
+
+      // remove from top of spare list
+      st->client->event_list = st->client->event_list->next;
+
+      // put at head of component event queue
+      event->next = st->list;
+      st->list = event;
+   }
+   ilclient_unlock_events(st->client);
+
+   // now call any callbacks without the event lock so the client can 
+   // remove the event in context
+   switch(eEvent) {
+   case OMX_EventError:
+      if(event && st->client->error_callback)
+         st->client->error_callback(st->client->error_callback_data, st, error);
+      break;
+   case OMX_EventBufferFlag:
+      if ((nData2 & OMX_BUFFERFLAG_EOS) && st->client->eos_callback)
+         st->client->eos_callback(st->client->eos_callback_data, st, nData1);
+      break;
+   case OMX_EventPortSettingsChanged:
+      if (st->client->port_settings_callback)
+         st->client->port_settings_callback(st->client->port_settings_callback_data, st, nData1);
+      break;
+   case OMX_EventParamOrConfigChanged:
+      if (st->client->configchanged_callback)
+         st->client->configchanged_callback(st->client->configchanged_callback_data, st, nData2);
+      break;
+   default:
+      // ignore
+      break;
+   }
+
+   return OMX_ErrorNone;
+}
+
+/***********************************************************
+ * Name: ilclient_empty_buffer_done
+ *
+ * Description: passed to core to use as component callback, puts
+ * buffer on list
+ *
+ * Returns:
+ ***********************************************************/
+static OMX_ERRORTYPE ilclient_empty_buffer_done(OMX_IN OMX_HANDLETYPE hComponent,
+      OMX_IN OMX_PTR pAppData,
+      OMX_IN OMX_BUFFERHEADERTYPE* pBuffer)
+{
+   COMPONENT_T *st = (COMPONENT_T *) pAppData;
+   OMX_BUFFERHEADERTYPE *list;
+
+   ilclient_debug_output("%s: empty buffer done %p", st->name, pBuffer);
+
+   vcos_semaphore_wait(&st->sema);
+   // insert at end of the list, so we process buffers in
+   // the same order
+   list = st->in_list;
+   while(list && list->pAppPrivate)
+      list = list->pAppPrivate;
+
+   if(!list)
+      st->in_list = pBuffer;
+   else
+      list->pAppPrivate = pBuffer;
+
+   pBuffer->pAppPrivate = NULL;
+   vcos_semaphore_post(&st->sema);
+
+   vcos_event_flags_set(&st->event, ILCLIENT_EMPTY_BUFFER_DONE, VCOS_OR);
+
+   if (st->client->empty_buffer_done_callback)
+      st->client->empty_buffer_done_callback(st->client->empty_buffer_done_callback_data, st);
+
+   return OMX_ErrorNone;
+}
+
+/***********************************************************
+ * Name: ilclient_empty_buffer_done_error
+ *
+ * Description: passed to core to use as component callback, asserts
+ * on use as client not expecting component to use this callback.
+ *
+ * Returns:
+ ***********************************************************/
+static OMX_ERRORTYPE ilclient_empty_buffer_done_error(OMX_IN OMX_HANDLETYPE hComponent,
+      OMX_IN OMX_PTR pAppData,
+      OMX_IN OMX_BUFFERHEADERTYPE* pBuffer)
+{
+   vc_assert(0);
+   return OMX_ErrorNone;
+}
+
+/***********************************************************
+ * Name: ilclient_fill_buffer_done
+ *
+ * Description: passed to core to use as component callback, puts
+ * buffer on list
+ *
+ * Returns:
+ ***********************************************************/
+static OMX_ERRORTYPE ilclient_fill_buffer_done(OMX_OUT OMX_HANDLETYPE hComponent,
+      OMX_OUT OMX_PTR pAppData,
+      OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer)
+{
+   COMPONENT_T *st = (COMPONENT_T *) pAppData;
+   OMX_BUFFERHEADERTYPE *list;
+
+   ilclient_debug_output("%s: fill buffer done %p", st->name, pBuffer);
+
+   vcos_semaphore_wait(&st->sema);
+   // insert at end of the list, so we process buffers in
+   // the correct order
+   list = st->out_list;
+   while(list && list->pAppPrivate)
+      list = list->pAppPrivate;
+
+   if(!list)
+      st->out_list = pBuffer;
+   else
+      list->pAppPrivate = pBuffer;
+      
+   pBuffer->pAppPrivate = NULL;
+   vcos_semaphore_post(&st->sema);
+
+   vcos_event_flags_set(&st->event, ILCLIENT_FILL_BUFFER_DONE, VCOS_OR);
+
+   if (st->client->fill_buffer_done_callback)
+      st->client->fill_buffer_done_callback(st->client->fill_buffer_done_callback_data, st);
+
+   return OMX_ErrorNone;
+}
+
+/***********************************************************
+ * Name: ilclient_fill_buffer_done_error
+ *
+ * Description: passed to core to use as component callback, asserts
+ * on use as client not expecting component to use this callback.
+ *
+ * Returns:
+ ***********************************************************/
+static OMX_ERRORTYPE ilclient_fill_buffer_done_error(OMX_OUT OMX_HANDLETYPE hComponent,
+      OMX_OUT OMX_PTR pAppData,
+      OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer)
+{
+   vc_assert(0);
+   return OMX_ErrorNone;
+}
+
+
+
+OMX_HANDLETYPE ilclient_get_handle(COMPONENT_T *comp)
+{
+   vcos_assert(comp);
+   return comp->comp;
+}
+
+
+static struct {
+   OMX_PORTDOMAINTYPE dom;
+   int param;
+} port_types[] = {
+   { OMX_PortDomainVideo, OMX_IndexParamVideoInit },
+   { OMX_PortDomainAudio, OMX_IndexParamAudioInit },
+   { OMX_PortDomainImage, OMX_IndexParamImageInit },
+   { OMX_PortDomainOther, OMX_IndexParamOtherInit },
+};
+
+int ilclient_get_port_index(COMPONENT_T *comp, OMX_DIRTYPE dir, OMX_PORTDOMAINTYPE type, int index)
+{
+   uint32_t i;
+   // for each possible port type...
+   for (i=0; i<sizeof(port_types)/sizeof(port_types[0]); i++)
+   {
+      if ((port_types[i].dom == type) || (type == (OMX_PORTDOMAINTYPE) -1))
+      {
+         OMX_PORT_PARAM_TYPE param;
+         OMX_ERRORTYPE error;
+         uint32_t j;
+
+         param.nSize = sizeof(param);
+         param.nVersion.nVersion = OMX_VERSION;
+         error = OMX_GetParameter(ILC_GET_HANDLE(comp), port_types[i].param, &param);
+         assert(error == OMX_ErrorNone);
+
+         // for each port of this type...
+         for (j=0; j<param.nPorts; j++)
+         {
+            int port = param.nStartPortNumber+j;
+
+            OMX_PARAM_PORTDEFINITIONTYPE portdef;
+            portdef.nSize = sizeof(portdef);
+            portdef.nVersion.nVersion = OMX_VERSION;
+            portdef.nPortIndex = port;
+
+            error = OMX_GetParameter(ILC_GET_HANDLE(comp), OMX_IndexParamPortDefinition, &portdef);
+            assert(error == OMX_ErrorNone);
+
+            if (portdef.eDir == dir)
+            {
+               if (index-- == 0)
+                  return port;
+            }
+         }
+      }
+   }
+   return -1;
+}
+
+int ilclient_suggest_bufsize(COMPONENT_T *comp, OMX_U32 nBufSizeHint)
+{
+   OMX_PARAM_BRCMOUTPUTBUFFERSIZETYPE param;
+   OMX_ERRORTYPE error;
+
+   param.nSize = sizeof(param);
+   param.nVersion.nVersion = OMX_VERSION;
+   param.nBufferSize = nBufSizeHint;
+   error = OMX_SetParameter(ILC_GET_HANDLE(comp), OMX_IndexParamBrcmOutputBufferSize,
+                            &param);
+   assert(error == OMX_ErrorNone);
+
+   return (error == OMX_ErrorNone) ? 0 : -1;
+}
+
+unsigned int ilclient_stack_size(void)
+{
+   return ILCLIENT_THREAD_DEFAULT_STACK_SIZE;
+}
+
diff --git a/ilclient/ilclient.h b/ilclient/ilclient.h
new file mode 100644
index 0000000..8478f15
--- /dev/null
+++ b/ilclient/ilclient.h
@@ -0,0 +1,1039 @@
+/*
+Copyright (c) 2012, Broadcom Europe Ltd
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the copyright holder nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ * \file
+ *
+ * \brief This API defines helper functions for writing IL clients.
+ *
+ * This file defines an IL client side library.  This is useful when
+ * writing IL clients, since there tends to be much repeated and
+ * common code across both single and multiple clients.  This library
+ * seeks to remove that common code and abstract some of the
+ * interactions with components.  There is a wrapper around a
+ * component and tunnel, and some operations can be done on lists of
+ * these.  The callbacks from components are handled, and specific
+ * events can be checked or waited for.
+ */
+
+#ifndef _IL_CLIENT_H
+#define _IL_CLIENT_H
+
+#include "IL/OMX_Broadcom.h"
+#include "interface/vcos/vcos.h"
+
+/**
+ * The <DFN>ILCLIENT_T</DFN> structure encapsulates the state needed for the IL
+ * Client API.  It contains a set of callback functions used to
+ * communicate with the user.  It also includes a linked list of free
+ * event structures.
+ ***********************************************************/
+typedef struct _ILCLIENT_T ILCLIENT_T;
+
+
+/**
+ * Each <DFN>ILEVENT_T</DFN> structure stores the result of an <DFN>EventHandler</DFN>
+ * callback from a component, storing the event message type and any
+ * parameters returned.
+ ***********************************************************/
+typedef struct _ILEVENT_T ILEVENT_T;
+
+
+
+struct _COMPONENT_T;
+
+/**
+ * The <DFN>COMPONENT_T</DFN> structure represents an IL component,
+ * together with the necessary extra information required by the IL
+ * Client API.  This structure stores the handle to the OMX component,
+ * as well as the event list containing all events sent by this
+ * component.  The component state structure also holds a pair of
+ * buffer queues, for input and output buffers returned to the client
+ * by the <DFN>FillBufferDone</DFN> and <DFN>EmptyBufferDone</DFN>
+ * callbacks.  As some operations result in error callbacks that can
+ * be ignored, an error mask is maintained to allow some errors to be
+ * ignored.  A pointer to the client state structure is also added.
+ ***********************************************************/
+typedef struct _COMPONENT_T COMPONENT_T;
+
+
+/**
+ * The generic callback function is used for communicating events from
+ * a particular component to the user.
+ *
+ * @param userdata The data returned from when the callback was registered.
+ *
+ * @param comp The component structure representing the component that
+ * originated this event.
+ *
+ * @param data The relevant data field from the event.
+ *
+ * @return Void.
+ ***********************************************************/
+typedef void (*ILCLIENT_CALLBACK_T)(void *userdata, COMPONENT_T *comp, OMX_U32 data);
+
+
+/**
+ * The buffer callback function is used for indicating that a
+ * component has returned a buffer on a port using client buffer
+ * communication.
+ *
+ * @param data The data returned from when the callback was registered.
+ *
+ * @param comp The component from which the buffer originated.
+ *
+ * @return Void.
+ ***********************************************************/
+typedef void (*ILCLIENT_BUFFER_CALLBACK_T)(void *data, COMPONENT_T *comp);
+
+
+/**
+ * The malloc function is passed into
+ * <DFN>ilclient_enable_port_buffers()</DFN> and used for allocating the
+ * buffer payload.
+ *
+ * @param userdata Private pointer passed into
+ * <DFN>ilclient_enable_port_buffers()</DFN> call.
+ *
+ * @param size Size in bytes of the requested memory region.
+ *
+ * @param align Alignment requirement in bytes for the base memory address.
+ *
+ * @param description Text description of the memory being allocated.
+ *
+ * @return The memory address on success, <DFN>NULL</DFN> on failure.
+ ***********************************************************/
+typedef void *(*ILCLIENT_MALLOC_T)(void *userdata, VCOS_UNSIGNED size, VCOS_UNSIGNED align, const char *description);
+
+
+/**
+ * The free function is passed into
+ * <DFN>ilclient_enable_port_buffers()</DFN> and
+ * <DFN>ilclient_disable_port_buffers()</DFN> and used for freeing the
+ * buffer payload.
+ *
+ * @param userdata Private pointer passed into
+ * <DFN>ilclient_enable_port_buffers()</DFN> and
+ * <DFN>ilclient_disable_port_buffers()</DFN>.
+ *
+ * @param pointer Memory address to free, that was previously returned
+ * from <DFN>ILCLIENT_MALLOC_T</DFN> function.
+ *
+ * @return Void.
+ ***********************************************************/
+typedef void (*ILCLIENT_FREE_T)(void *userdata, void *pointer);
+
+
+/**
+ * The event mask enumeration describes the possible events that the
+ * user can ask to wait for when waiting for a particular event.
+ ***********************************************************/
+typedef enum {
+   ILCLIENT_EMPTY_BUFFER_DONE  = 0x1,   /**< Set when a buffer is
+                                           returned from an input
+                                           port */
+
+   ILCLIENT_FILL_BUFFER_DONE   = 0x2,   /**< Set when a buffer is
+                                           returned from an output
+                                           port */
+
+   ILCLIENT_PORT_DISABLED      = 0x4,   /**< Set when a port indicates
+                                           it has completed a disable
+                                           command. */
+
+   ILCLIENT_PORT_ENABLED       = 0x8,   /**< Set when a port indicates
+                                           is has completed an enable
+                                           command. */
+
+   ILCLIENT_STATE_CHANGED      = 0x10,  /**< Set when a component
+                                           indicates it has completed
+                                           a state change command. */
+
+   ILCLIENT_BUFFER_FLAG_EOS    = 0x20,  /**< Set when a port signals
+                                           an EOS event. */
+
+   ILCLIENT_PARAMETER_CHANGED  = 0x40,  /**< Set when a port signals a
+                                           port settings changed
+                                           event. */
+
+   ILCLIENT_EVENT_ERROR        = 0x80,  /**< Set when a component
+                                           indicates an error. */
+
+   ILCLIENT_PORT_FLUSH         = 0x100, /**< Set when a port indicates
+                                           is has completed a flush
+                                           command. */
+
+   ILCLIENT_MARKED_BUFFER      = 0x200, /**< Set when a port indicates
+                                           it has marked a buffer. */
+
+   ILCLIENT_BUFFER_MARK        = 0x400, /**< Set when a port indicates
+                                           it has received a buffer
+                                           mark. */
+
+   ILCLIENT_CONFIG_CHANGED     = 0x800  /**< Set when a config parameter
+                                           changed. */
+} ILEVENT_MASK_T;
+
+
+/**
+ * On component creation the user can set flags to control the 
+ * creation of that component.
+ ***********************************************************/
+typedef enum {
+   ILCLIENT_FLAGS_NONE            = 0x0, /**< Used if no flags are
+                                            set. */
+
+   ILCLIENT_ENABLE_INPUT_BUFFERS  = 0x1, /**< If set we allow the
+                                            client to communicate with
+                                            input ports via buffer
+                                            communication, rather than
+                                            tunneling with another
+                                            component. */
+
+   ILCLIENT_ENABLE_OUTPUT_BUFFERS = 0x2, /**< If set we allow the
+                                            client to communicate with
+                                            output ports via buffer
+                                            communication, rather than
+                                            tunneling with another
+                                            component. */
+
+   ILCLIENT_DISABLE_ALL_PORTS     = 0x4, /**< If set we disable all
+                                            ports on creation. */
+
+   ILCLIENT_HOST_COMPONENT        = 0x8, /**< Create a host component.
+                                            The default host ilcore
+                                            only can create host components
+                                            by being locally hosted
+                                            so should only be used for testing
+                                            purposes. */
+
+   ILCLIENT_OUTPUT_ZERO_BUFFERS   = 0x10 /**< All output ports will have
+                                            nBufferCountActual set to zero,
+                                            if supported by the component. */                                            
+} ILCLIENT_CREATE_FLAGS_T;
+  
+
+/**
+ * \brief This structure represents a tunnel in the OpenMAX IL API.
+ *
+ * Some operations in this API act on a tunnel, so the tunnel state
+ * structure (<DFN>TUNNEL_T</DFN>) is a convenient store of the source and sink
+ * of the tunnel.  For each, a pointer to the relevant component state
+ * structure and the port index is stored.
+ ***********************************************************/
+typedef struct {
+   COMPONENT_T *source;  /**< The source component */
+   int source_port;      /**< The output port index on the source component */
+   COMPONENT_T *sink;    /**< The sink component */
+   int sink_port;        /**< The input port index on the sink component */
+} TUNNEL_T;
+
+
+/**
+ * The <DFN>set_tunnel</DFN> macro is a useful function that initialises a
+ * <DFN>TUNNEL_T</DFN> structure.
+ ***********************************************************/
+#define set_tunnel(t,a,b,c,d)  do {TUNNEL_T *_ilct = (t); \
+  _ilct->source = (a); _ilct->source_port = (b); \
+  _ilct->sink = (c); _ilct->sink_port = (d);} while(0)
+
+/**
+ * For calling OpenMAX IL methods directory, we need to access the
+ * <DFN>OMX_HANDLETYPE</DFN> corresponding to the <DFN>COMPONENT_T</DFN> structure.  This
+ * macro enables this while keeping the <DFN>COMPONENT_T</DFN> structure opaque.
+ * The parameter <DFN>x</DFN> should be of the type <DFN>*COMPONENT_T</DFN>.
+ ***********************************************************/
+#define ILC_GET_HANDLE(x) ilclient_get_handle(x)
+
+/**
+ * An IL Client structure is created by the <DFN>ilclient_init()</DFN>
+ * method.  This structure is used when creating components, but
+ * otherwise is not needed in other API functions as a pointer to this
+ * structure is maintained in the <DFN>COMPONENT_T</DFN> structure.
+ *
+ * @return pointer to client structure
+ ***********************************************************/
+VCHPRE_ ILCLIENT_T VCHPOST_ *ilclient_init(void);
+
+/**
+ * When all components have been deleted, the IL Client structure can
+ * be destroyed by calling the <DFN>ilclient_destroy()</DFN> function.
+ *
+ * @param handle The client handle.  After calling this function, this
+ * handle should not be used.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_destroy(ILCLIENT_T *handle);
+
+/**
+ * The <DFN>ilclient_set_port_settings_callback()</DFN> function registers a
+ * callback to be used when the <DFN>OMX_EventPortSettingsChanged</DFN> event is
+ * received.  When the event is received, a pointer to the component
+ * structure and port index is returned by the callback.
+ *
+ * @param handle The client handle
+ *
+ * @param func The callback function to use.  Calling this function
+ * with a <DFN>NULL</DFN> function pointer will deregister any existing
+ * registered callback.
+ *
+ * @param userdata Data to be passed back when calling the callback
+ * function.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_set_port_settings_callback(ILCLIENT_T *handle,
+                                                          ILCLIENT_CALLBACK_T func,
+                                                          void *userdata);
+
+/**
+ * The <DFN>ilclient_set_eos_callback()</DFN> function registers a callback to be
+ * used when the <DFN>OMX_EventBufferFlag</DFN> is received with the
+ * <DFN>OMX_BUFFERFLAG_EOS</DFN> flag set. When the event is received, a pointer
+ * to the component structure and port index is returned by the
+ * callback.
+ *
+ * @param handle The client handle
+ *
+ * @param func The callback function to use.  Calling this function
+ * with a <DFN>NULL</DFN> function pointer will deregister any existing
+ * registered callback.
+ *
+ * @param userdata Data to be passed back when calling the callback
+ * function.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_set_eos_callback(ILCLIENT_T *handle,
+                                                ILCLIENT_CALLBACK_T func,
+                                                void *userdata);
+
+/**
+ * The <DFN>ilclient_set_error_callback()</DFN> function registers a callback to be
+ * used when the <DFN>OMX_EventError</DFN> is received from a component.  When
+ * the event is received, a pointer to the component structure and the
+ * error code are reported by the callback.
+ *
+ * @param handle The client handle
+ *
+ * @param func The callback function to use.  Calling this function
+ * with a <DFN>NULL</DFN> function pointer will deregister any existing
+ * registered callback.
+ *
+ * @param userdata Data to be passed back when calling the callback
+ * function.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_set_error_callback(ILCLIENT_T *handle,
+                                                  ILCLIENT_CALLBACK_T func,
+                                                  void *userdata);
+
+/**
+ * The <DFN>ilclient_set_configchanged_callback()</DFN> function
+ * registers a callback to be used when an
+ * <DFN>OMX_EventParamOrConfigChanged</DFN> event occurs.  When the
+ * event is received, a pointer to the component structure and the
+ * index value that has changed are reported by the callback.  The
+ * user may then use an <DFN>OMX_GetConfig</DFN> call with the index
+ * as specified to retrieve the updated information.
+ *
+ * @param handle The client handle
+ *
+ * @param func The callback function to use.  Calling this function
+ * with a <DFN>NULL</DFN> function pointer will deregister any existing
+ * registered callback.
+ *
+ * @param userdata Data to be passed back when calling the callback
+ * function.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_set_configchanged_callback(ILCLIENT_T *handle, 
+                                                          ILCLIENT_CALLBACK_T func, 
+                                                          void *userdata);
+
+
+/**
+ * The <DFN>ilclient_set_fill_buffer_done_callback()</DFN> function registers a
+ * callback to be used when a buffer passed to an output port using the
+ * <DFN>OMX_FillBuffer</DFN> call is returned with the <DFN>OMX_FillBufferDone</DFN>
+ * callback.  When the event is received, a pointer to the component
+ * structure is returned by the callback.  The user may then use the
+ * <DFN>ilclient_get_output_buffer()</DFN> function to retrieve the buffer.
+ *
+ * @param handle The client handle
+ *
+ * @param func The callback function to use.  Calling this function
+ * with a <DFN>NULL</DFN> function pointer will deregister any existing
+ * registered callback.
+ *
+ * @param userdata Data to be passed back when calling the callback
+ * function.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_set_fill_buffer_done_callback(ILCLIENT_T *handle,
+                                                             ILCLIENT_BUFFER_CALLBACK_T func,
+                                                             void *userdata);
+
+/**
+ * The <DFN>ilclient_set_empty_buffer_done_callback()</DFN> function registers a
+ * callback to be used when a buffer passed to an input port using the
+ * <DFN>OMX_EmptyBuffer</DFN> call is returned with the <DFN>OMX_EmptyBufferDone</DFN>
+ * callback.  When the event is received, a pointer to the component
+ * structure is returned by the callback.  The user may then use the
+ * <DFN>ilclient_get_input_buffer()</DFN> function to retrieve the buffer.
+ *
+ * @param handle The client handle
+ *
+ * @param func The callback function to use.  Calling this function
+ * with a <DFN>NULL</DFN> function pointer will deregister any existing
+ * registered callback.
+ *
+ * @param userdata Data to be passed back when calling the callback
+ * function.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_set_empty_buffer_done_callback(ILCLIENT_T *handle,
+                                                              ILCLIENT_BUFFER_CALLBACK_T func,
+                                                              void *userdata);
+
+
+/**
+ * Components are created using the <DFN>ilclient_create_component()</DFN>
+ * function.  
+ *
+ * @param handle The client handle
+ *
+ * @param comp On successful creation, the component structure pointer
+ * will be written back into <DFN>comp</DFN>.
+ *
+ * @param name The name of the component to be created.  Component
+ * names as provided are automatically prefixed with
+ * <DFN>"OMX.broadcom."</DFN> before passing to the IL core.  The name
+ * provided will also be used in debugging messages added about this
+ * component.
+ *
+ * @param flags The client can specify some creation behaviour by using
+ * the <DFN>flags</DFN> field.  The meaning of each flag is defined 
+ * by the <DFN>ILCLIENT_CREATE_FLAGS_T</DFN> type.
+ *
+ * @return 0 on success, -1 on failure
+ ***********************************************************/
+VCHPRE_ int VCHPOST_ ilclient_create_component(ILCLIENT_T *handle,
+                                               COMPONENT_T **comp,
+                                               char *name,
+                                               ILCLIENT_CREATE_FLAGS_T flags);
+
+/**
+ * The <DFN>ilclient_cleanup_components()</DFN> function deallocates all
+ * state associated with components and frees the OpenMAX component
+ * handles. All tunnels connecting components should have been torn
+ * down explicitly, and all components must be in loaded state.
+ *
+ * @param list A null-terminated list of component pointers to be
+ * deallocated.
+ * 
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_cleanup_components(COMPONENT_T *list[]);
+
+
+/**
+ * The <DFN>ilclient_change_component_state()</DFN> function changes the
+ * state of an individual component.  This will trigger the state
+ * change, and also wait for that state change to be completed.  It
+ * should not be called if this state change has dependencies on other
+ * components also changing states.  Trying to change a component to
+ * its current state is treated as success.
+ *
+ * @param comp The component to change.
+ *
+ * @param state The new state to transition to.
+ *
+ * @return 0 on success, -1 on failure.
+ ***********************************************************/
+VCHPRE_ int VCHPOST_ ilclient_change_component_state(COMPONENT_T *comp,
+                                                     OMX_STATETYPE state);
+
+
+/**
+ * The <DFN>ilclient_state_transition()</DFN> function transitions a set of
+ * components that need to perform a simultaneous state transition; 
+ * for example, when two components are tunnelled and the buffer
+ * supplier port needs to allocate and pass buffers to a non-supplier
+ * port.  All components are sent a command to change state, then the
+ * function will wait for all components to signal that they have
+ * changed state.
+ *
+ * @param list A null-terminated list of component pointers.
+ *
+ * @param state The new state to which to transition all components.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_  ilclient_state_transition(COMPONENT_T *list[],
+                                                 OMX_STATETYPE state);
+
+
+/**
+ * The <DFN>ilclient_disable_port()</DFN> function disables a port on a
+ * given component.  This function sends the disable port message to
+ * the component and waits for the component to signal that this has
+ * taken place.  If the port is already disabled, this is treated as a
+ * sucess.
+ *
+ * @param comp The component containing the port to disable.
+ *
+ * @param portIndex The port index of the port to disable.  This must
+ * be a named port index, rather than a <DFN>OMX_ALL</DFN> value.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_disable_port(COMPONENT_T *comp,
+                                            int portIndex);
+
+
+/**
+ * The <DFN>ilclient_enable_port()</DFN> function enables a port on a
+ * given component.  This function sends the enable port message to
+ * the component and waits for the component to signal that this has
+ * taken place.  If the port is already disabled, this is treated as a
+ * sucess.
+ *
+ * @param comp The component containing the port to enable.
+ *
+ * @param portIndex The port index of the port to enable.  This must
+ * be a named port index, rather than a <DFN>OMX_ALL</DFN> value.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_enable_port(COMPONENT_T *comp,
+                                           int portIndex);
+
+
+
+/**
+ * The <DFN>ilclient_enable_port_buffers()</DFN> function enables a port
+ * in base profile mode on a given component.  The port is not
+ * tunneled, so requires buffers to be allocated.
+ *
+ * @param comp The component containing the port to enable.
+ *
+ * @param portIndex The port index of the port to enable.  This must
+ * be a named port index, rather than a <DFN>OMX_ALL</DFN> value.
+ *
+ * @param ilclient_malloc This function will be used to allocate
+ * buffer payloads.  If <DFN>NULL</DFN> then
+ * <DFN>vcos_malloc_aligned</DFN> will be used.
+ *
+ * @param ilclient_free If an error occurs, this function is used to
+ * free previously allocated payloads.  If <DFN>NULL</DFN> then
+ * <DFN>vcos_free</DFN> will be used.
+ *
+ * @param userdata The first argument to calls to
+ * <DFN>ilclient_malloc</DFN> and <DFN>ilclient_free</DFN>, if these
+ * arguments are not <DFN>NULL</DFN>.
+ *
+ * @return 0 indicates success. -1 indicates failure.
+ ***********************************************************/
+VCHPRE_ int VCHPOST_ ilclient_enable_port_buffers(COMPONENT_T *comp,
+                                                  int portIndex,
+                                                  ILCLIENT_MALLOC_T ilclient_malloc,
+                                                  ILCLIENT_FREE_T ilclient_free,
+                                                  void *userdata);
+
+
+/**
+ * The <DFN>ilclient_disable_port_buffers()</DFN> function disables a
+ * port in base profile mode on a given component.  The port is not
+ * tunneled, and has been supplied with buffers by the client.
+ *
+ * @param comp The component containing the port to disable.
+ *
+ * @param portIndex The port index of the port to disable.  This must
+ * be a named port index, rather than a <DFN>OMX_ALL</DFN> value.
+ *
+ * @param bufferList A list of buffers, using <DFN>pAppPrivate</DFN>
+ * as the next pointer that were allocated on this port, and currently
+ * held by the application.  If buffers on this port are held by IL
+ * client or the component then these are automatically freed.
+ *
+ * @param ilclient_free This function is used to free the buffer payloads.
+ * If <DFN>NULL</DFN> then <DFN>vcos_free</DFN> will be used.
+ *
+ * @param userdata The first argument to calls to
+ * <DFN>ilclient_free</DFN>.
+ *
+ * @return void
+ */
+VCHPRE_ void VCHPOST_ ilclient_disable_port_buffers(COMPONENT_T *comp,
+                                                    int portIndex,
+                                                    OMX_BUFFERHEADERTYPE *bufferList,
+                                                    ILCLIENT_FREE_T ilclient_free,
+                                                    void *userdata);
+
+
+/**
+ * With a populated tunnel structure, the
+ * <DFN>ilclient_setup_tunnel()</DFN> function connects the tunnel.  It
+ * first transitions the source component to idle if currently in
+ * loaded state, and then optionally checks the source event list for
+ * a port settings changed event from the source port.  If this event
+ * is not in the event queue then this function optionally waits for
+ * it to arrive.  To disable this check for the port settings changed
+ * event, set <DFN>timeout</DFN> to zero.
+ *
+ * Both ports are then disabled, and the source port is inspected for
+ * a port streams parameter.  If this is supported, then the
+ * <DFN>portStream</DFN> argument is used to select which port stream
+ * to use.  The two ports are then tunnelled using the
+ * <DFN>OMX_SetupTunnel</DFN> function.  If this is successful, then
+ * both ports are enabled.  Note that for disabling and enabling the
+ * tunnelled ports, the functions <DFN>ilclient_disable_tunnel()</DFN>
+ * and <DFN>ilclient_enable_tunnel()</DFN> are used, so the relevant
+ * documentation for those functions applies here.
+ *
+ * @param tunnel The tunnel structure representing the tunnel to
+ * set up.
+ *
+ * @param portStream If port streams are supported on the output port
+ * of the tunnel, then this parameter indicates the port stream to
+ * select on this port.
+ *
+ * @param timeout The time duration in milliseconds to wait for the
+ * output port to signal a port settings changed event before
+ * returning a timeout failure.  If this is 0, then we do not check
+ * for a port settings changed before setting up the tunnel.
+ *
+ * @return 0 indicates success, negative indicates failure:
+ *  - -1: a timeout waiting for the parameter changed
+ *  - -2: an error was returned instead of parameter changed
+ *  - -3: no streams are available from this port
+ *  - -4: requested stream is not available from this port
+ *  - -5: the data format was not acceptable to the sink
+ ***********************************************************/
+VCHPRE_ int VCHPOST_ ilclient_setup_tunnel(TUNNEL_T *tunnel,
+                                           unsigned int portStream,
+                                           int timeout);
+
+
+/**
+ * The <DFN>ilclient_disable_tunnel()</DFN> function disables both ports listed in
+ * the tunnel structure.  It will send a port disable command to each
+ * port, then waits for both to indicate they have completed the
+ * transition.  The errors <DFN>OMX_ErrorPortUnpopulated</DFN> and
+ * <DFN>OMX_ErrorSameState</DFN> are both ignored by this function; the former
+ * since the first port to disable may deallocate buffers before the
+ * second port has been disabled, leading to the second port reporting
+ * the unpopulated error.
+ *
+ * @param tunnel The tunnel to disable.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_disable_tunnel(TUNNEL_T *tunnel);
+
+
+/**
+ * The <DFN>ilclient_enable_tunnel()</DFN> function enables both ports listed in
+ * the tunnel structure.  It will first send a port enable command to
+ * each port.  It then checks whether the sink component is not in
+ * loaded state - if so, the function waits for both ports to complete
+ * the requested port enable.  If the sink component was in loaded
+ * state, then this component is transitioned to idle to allow the
+ * ports to exchange buffers and enable the ports.  This is since
+ * typically this function is used when creating a tunnel between two
+ * components, where the source component is processing data to enable
+ * it to report the port settings changed event, and the sink port has
+ * yet to be used.  Before transitioning the sink component to idle,
+ * this function waits for the sink port to be enabled - since the
+ * component is in loaded state, this will happen quickly.  If the
+ * transition to idle fails, the sink component is transitioned back
+ * to loaded and the source port disabled.  If the transition
+ * succeeds, the function then waits for the source port to complete
+ * the requested port enable.
+ *
+ * @param tunnel The tunnel to enable.
+ *
+ * @return 0 on success, -1 on failure.
+ ***********************************************************/
+VCHPRE_ int VCHPOST_ ilclient_enable_tunnel(TUNNEL_T *tunnel);
+
+
+/**
+ * The <DFN>ilclient_flush_tunnels()</DFN> function will flush a number of tunnels
+ * from the list of tunnels presented.  For each tunnel that is to be
+ * flushed, both source and sink ports are sent a flush command.  The
+ * function then waits for both ports to report they have completed
+ * the flush operation.
+ *
+ * @param tunnel List of tunnels.  The list must be terminated with a
+ * tunnel structure with <DFN>NULL</DFN> component entries.
+ *
+ * @param max The maximum number of tunnels to flush from the list.
+ * A value of 0 indicates that all tunnels in the list are flushed.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_flush_tunnels(TUNNEL_T *tunnel,
+                                             int max);
+
+
+/**
+ * The <DFN>ilclient_teardown_tunnels()</DFN> function tears down all tunnels in
+ * the list of tunnels presented.  For each tunnel in the list, the
+ * <DFN>OMX_SetupTunnel</DFN> is called on the source port and on the sink port,
+ * where for both calls the destination component is <DFN>NULL</DFN> and the
+ * destination port is zero.  The VMCSX IL implementation requires
+ * that all tunnels are torn down in this manner before components are
+ * freed. 
+ *
+ * @param tunnels List of tunnels to teardown.  The list must be
+ * terminated with a tunnel structure with <DFN>NULL</DFN> component entries.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_teardown_tunnels(TUNNEL_T *tunnels);
+
+
+/**
+ * The <DFN>ilclient_get_output_buffer()</DFN> function returns a buffer
+ * that was sent to an output port and that has been returned from a
+ * component using the <DFN>OMX_FillBufferDone</DFN> callback.
+ *
+ * @param comp The component that returned the buffer.
+ *
+ * @param portIndex The port index on the component that the buffer
+ * was returned from.
+ *
+ * @param block If non-zero, the function will block until a buffer is available.
+ *
+ * @return Pointer to buffer if available, otherwise <DFN>NULL</DFN>.
+ ***********************************************************/
+VCHPRE_ OMX_BUFFERHEADERTYPE* VCHPOST_ ilclient_get_output_buffer(COMPONENT_T *comp,
+                                                                  int portIndex,
+                                                                  int block);
+
+
+/**
+ * The <DFN>ilclient_get_input_buffer()</DFN> function returns a buffer
+ * that was sent to an input port and that has been returned from a
+ * component using the <DFN>OMX_EmptyBufferDone</DFN> callback.
+ *
+ * @param comp The component that returned the buffer.
+ *
+ * @param portIndex The port index on the component from which the buffer
+ * was returned.
+ *
+ * @param block If non-zero, the function will block until a buffer is available.
+ *
+ * @return pointer to buffer if available, otherwise <DFN>NULL</DFN>
+ ***********************************************************/
+VCHPRE_ OMX_BUFFERHEADERTYPE* VCHPOST_ ilclient_get_input_buffer(COMPONENT_T *comp,
+                                                                 int portIndex,
+                                                                 int block);
+
+
+/**
+ * The <DFN>ilclient_remove_event()</DFN> function queries the event list for the
+ * given component, matching against the given criteria.  If a matching
+ * event is found, it is removed and added to the free event list.
+ *
+ * @param comp The component that returned the matching event.
+ *
+ * @param event The event type of the matching event.
+ *
+ * @param nData1 The <DFN>nData1</DFN> field of the matching event.
+ *
+ * @param ignore1 Whether to ignore the <DFN>nData1</DFN> field when finding a
+ * matching event.  A value of 0 indicates that <DFN>nData1</DFN> must match, a
+ * value of 1 indicates that <DFN>nData1</DFN> does not have to match.
+ *
+ * @param nData2 The <DFN>nData2</DFN> field of the matching event.
+ *
+ * @param ignore2 Whether to ignore the <DFN>nData2</DFN> field when finding a
+ * matching event.  A value of 0 indicates that <DFN>nData2</DFN> must match, a
+ * value of 1 indicates that <DFN>nData2</DFN> does not have to match.
+ *
+ * @return 0 if the event was removed.  -1 if no matching event was
+ * found.
+ ***********************************************************/
+VCHPRE_ int VCHPOST_ ilclient_remove_event(COMPONENT_T *comp,
+                                           OMX_EVENTTYPE event,
+                                           OMX_U32 nData1,
+                                           int ignore1,
+                                           OMX_U32 nData2,
+                                           int ignore2);
+
+
+/**
+ * The <DFN>ilclient_return_events()</DFN> function removes all events
+ * from a component event list and adds them to the IL client free
+ * event list.  This function is automatically called when components
+ * are freed.
+ *
+ * @param comp The component from which all events should be moved to
+ * the free list.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_return_events(COMPONENT_T *comp);
+
+
+/**
+ * The <DFN>ilclient_wait_for_event()</DFN> function is similar to
+ * <DFN>ilclient_remove_event()</DFN>, but allows the caller to block until that
+ * event arrives.
+ *
+ * @param comp The component that returned the matching event.
+ *
+ * @param event The event type of the matching event.
+ *
+ * @param nData1 The <DFN>nData1</DFN> field of the matching event.
+ *
+ * @param ignore1 Whether to ignore the <DFN>nData1</DFN> field when finding a
+ * matching event.  A value of 0 indicates that <DFN>nData1</DFN> must match, a
+ * value of 1 indicates that <DFN>nData1</DFN> does not have to match.
+ *
+ * @param nData2 The <DFN>nData2</DFN> field of the matching event.
+ *
+ * @param ignore2 Whether to ignore the <DFN>nData2</DFN> field when finding a
+ * matching event.  A value of 0 indicates that <DFN>nData2</DFN> must match, a
+ * value of 1 indicates that <DFN>nData2</DFN> does not have to match.
+ *
+ * @param event_flag Specifies a bitfield of IL client events to wait
+ * for, given in <DFN>ILEVENT_MASK_T</DFN>.  If any of these events
+ * are signalled by the component, the event list is then re-checked
+ * for a matching event.  If the <DFN>ILCLIENT_EVENT_ERROR</DFN> bit
+ * is included, and an error is signalled by the component, then the
+ * function will return an error code.  If the
+ * <DFN>ILCLIENT_CONFIG_CHANGED</DFN> bit is included, and this bit is
+ * signalled by the component, then the function will return an error
+ * code.
+ *
+ * @param timeout Specifies how long to block for in milliseconds
+ * before returning a failure.
+ *
+ * @return 0 indicates success, a matching event has been removed from
+ * the component's event queue.  A negative return indicates failure,
+ * in this case no events have been removed from the component's event
+ * queue.
+ *  - -1: a timeout was received.
+ *  - -2: an error event was received.
+ *  - -3: a config changed event was received.
+ ***********************************************************/
+VCHPRE_ int VCHPOST_ ilclient_wait_for_event(COMPONENT_T *comp,
+                                             OMX_EVENTTYPE event,
+                                             OMX_U32 nData1,
+                                             int ignore1,
+                                             OMX_U32 nData2,
+                                             int ignore2,
+                                             int event_flag,
+                                             int timeout);
+
+
+/**
+ * The <DFN>ilclient_wait_for_command_complete()</DFN> function waits
+ * for a message from a component that indicates that the command
+ * has completed.  This is either a command success message, or an 
+ * error message that signals the completion of an event.
+ * 
+ * @param comp The component currently processing a command.
+ *
+ * @param command The command being processed.  This must be either
+ * <DFN>OMX_CommandStateSet</DFN>, <DFN>OMX_CommandPortDisable</DFN>
+ * or <DFN>OMX_CommandPortEnable</DFN>.
+ *
+ * @param nData2 The expected value of <DFN>nData2</DFN> in the
+ * command complete message.
+ *
+ * @return 0 indicates success, either the command successfully completed
+ * or the <DFN>OMX_ErrorSameState</DFN> was returned.  -1 indicates
+ * that the command terminated with a different error message.
+ ***********************************************************/
+VCHPRE_ int VCHPOST_ ilclient_wait_for_command_complete(COMPONENT_T *comp,
+                                                        OMX_COMMANDTYPE command,
+                                                        OMX_U32 nData2);
+
+
+/**
+ * The <DFN>ilclient_wait_for_command_complete_dual()</DFN> function
+ * is similar to <DFN>ilclient_wait_for_command_complete()</DFN>.  The
+ * difference is that while waiting for the component to complete the
+ * event or raise an error, we can also report if another reports an
+ * error that terminates a command.  This is useful if the two
+ * components are tunneled, and we need to wait for one component to
+ * enable a port, or change state to <DFN>OMX_StateIdle</DFN>.  If the
+ * other component is the buffer supplier and reports an error, then
+ * it will not allocate buffers, so our first component may stall.
+ * 
+ * @param comp The component currently processing a command.
+ *
+ * @param command The command being processed.  This must be either
+ * <DFN>OMX_CommandStateSet</DFN>, <DFN>OMX_CommandPortDisable</DFN>
+ * or <DFN>OMX_CommandPortEnable</DFN>.
+ *
+ * @param nData2 The expected value of <DFN>nData2</DFN> in the
+ * command complete message.
+ *
+ * @param related Another component, where we will return if this
+ * component raises an error that terminates a command.
+ *
+ * @return 0 indicates success, either the command successfully
+ * completed or the <DFN>OMX_ErrorSameState</DFN> was returned.  -1
+ * indicates that the command terminated with a different error
+ * message. -2 indicates that the related component raised an error.
+ * In this case, the error is not cleared from the related
+ * component's event list.
+ ***********************************************************/
+VCHPRE_ int VCHPOST_ ilclient_wait_for_command_complete_dual(COMPONENT_T *comp,
+                                                             OMX_COMMANDTYPE command,
+                                                             OMX_U32 nData2,
+                                                             COMPONENT_T *related);
+                                                             
+
+/**
+ * The <DFN>ilclient_debug_output()</DFN> function adds a message to a 
+ * host-specific debug display.  For a local VideoCore host the message is
+ * added to the internal message log.  For a Win32 host the message is
+ * printed to the debug display.  This function should be customised
+ * when IL client is ported to another platform.
+ * 
+ * @param format A message to add, together with the variable
+ * argument list similar to <DFN>printf</DFN> and other standard C functions.
+ *
+ * @return void
+ ***********************************************************/
+VCHPRE_ void VCHPOST_ ilclient_debug_output(char *format, ...);
+
+/**
+ * The <DFN>ilclient_get_handle()</DFN> function returns the
+ * underlying OMX component held by an IL component handle.  This is
+ * needed when calling methods such as <DFN>OMX_SetParameter</DFN> on
+ * a component created using the IL client library.
+ *
+ * @param comp  IL component handle
+ *
+ * @return The <DFN>OMX_HANDLETYPE</DFN> value for the component.
+ ***********************************************************/
+VCHPRE_ OMX_HANDLETYPE VCHPOST_ ilclient_get_handle(COMPONENT_T *comp);
+
+
+#ifndef OMX_SKIP64BIT
+
+/**
+ * Macro to return <DFN>OMX_TICKS</DFN> from a signed 64 bit value.
+ * This is the version where <DFN>OMX_TICKS</DFN> is a signed 64 bit
+ * value, an alternative definition is used when <DFN>OMX_TICKS</DFN>
+ * is a structure.
+ */
+#define ilclient_ticks_from_s64(s) (s)
+
+/**
+ * Macro to return signed 64 bit value from <DFN>OMX_TICKS</DFN>.
+ * This is the version where <DFN>OMX_TICKS</DFN> is a signed 64 bit
+ * value, an alternative definition is used when <DFN>OMX_TICKS</DFN>
+ * is a structure.
+ */
+#define ilclient_ticks_to_s64(t)   (t)
+
+#else
+
+/**
+ * Inline function to return <DFN>OMX_TICKS</DFN> from a signed 64 bit
+ * value.  This is the version where <DFN>OMX_TICKS</DFN> is a
+ * structure, an alternative definition is used when
+ * <DFN>OMX_TICKS</DFN> is a signed 64 bit value.
+ */
+static inline OMX_TICKS ilclient_ticks_from_s64(int64_t s) {
+   OMX_TICKS ret;
+   ret.nLowPart = s;
+   ret.nHighPart = s>>32;
+   return ret;
+}
+
+/**
+ * Inline function to return signed 64 bit value from
+ * <DFN>OMX_TICKS</DFN>.  This is the version where
+ * <DFN>OMX_TICKS</DFN> is a structure, an alternative definition is
+ * used when <DFN>OMX_TICKS</DFN> is a signed 64 bit value.
+ */
+static inline int64_t ilclient_ticks_to_s64(OMX_TICKS t) {
+   uint64_t u = t.nLowPart | ((uint64_t)t.nHighPart << 32);
+   return u;
+}
+
+
+#endif /* OMX_SKIP64BIT */
+
+/**
+ * The <DFN>ilclient_get_port_index()</DFN> function returns the n'th
+ * port index of the specified type and direction for the given
+ * component.
+ *
+ * @param comp    The component of interest
+ * @param dir     The direction
+ * @param type    The type, or -1 for any type.
+ * @param index   Which port (counting from 0).
+ *
+ * @return        The port index, or -1 if not found.
+ ***********************************************************/
+VCHPRE_ int VCHPOST_ ilclient_get_port_index(COMPONENT_T *comp,
+                                             OMX_DIRTYPE dir,
+                                             OMX_PORTDOMAINTYPE type,
+                                             int index);
+
+
+/**
+ * The <DFN>ilclient_suggest_bufsize()</DFN> function gives a
+ * component a hint about the size of buffer it should use.  This size
+ * is set on the component by setting the
+ * <DFN>OMX_IndexParamBrcmOutputBufferSize</DFN> index on the given
+ * component.
+ *
+ * @param comp         IL component handle
+ * @param nBufSizeHint Size of buffer in bytes
+ *
+ * @return             0 indicates success, -1 indicates failure.
+ ***********************************************************/
+VCHPRE_ int VCHPOST_ ilclient_suggest_bufsize(COMPONENT_T *comp,
+                                              OMX_U32 nBufSizeHint);
+
+
+/**
+ * The <DFN>ilclient_stack_size()</DFN> function suggests a minimum
+ * stack size that tasks calling into with API will require.
+ *
+ * @return    Suggested stack size in bytes.
+ ***********************************************************/
+VCHPRE_ unsigned int VCHPOST_ ilclient_stack_size(void);
+
+#endif /* ILCLIENT_H */
diff --git a/ilclient/ilcore.c b/ilclient/ilcore.c
new file mode 100644
index 0000000..356733d
--- /dev/null
+++ b/ilclient/ilcore.c
@@ -0,0 +1,308 @@
+/*
+Copyright (c) 2012, Broadcom Europe Ltd
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the copyright holder nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ * \file
+ *
+ * \brief Host core implementation.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+
+//includes
+#include <memory.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "IL/OMX_Component.h"
+#include "interface/vcos/vcos.h"
+
+#include "interface/vmcs_host/vcilcs.h"
+#include "interface/vmcs_host/vchost.h"
+#include "interface/vmcs_host/vcilcs_common.h"
+
+static int coreInit = 0;
+static int nActiveHandles = 0;
+static ILCS_SERVICE_T *ilcs_service = NULL;
+static VCOS_MUTEX_T lock;
+static VCOS_ONCE_T once = VCOS_ONCE_INIT;
+
+/* Atomic creation of lock protecting shared state */
+static void initOnce(void)
+{
+   VCOS_STATUS_T status;
+   status = vcos_mutex_create(&lock, VCOS_FUNCTION);
+   vcos_demand(status == VCOS_SUCCESS);
+}
+
+/* OMX_Init */
+OMX_ERRORTYPE OMX_APIENTRY OMX_Init(void)
+{
+   VCOS_STATUS_T status;
+   OMX_ERRORTYPE err = OMX_ErrorNone;
+
+   status = vcos_once(&once, initOnce);
+   vcos_demand(status == VCOS_SUCCESS);
+
+   vcos_mutex_lock(&lock);
+   
+   if(coreInit == 0)
+   {
+      // we need to connect via an ILCS connection to VideoCore
+      VCHI_INSTANCE_T initialise_instance;
+      VCHI_CONNECTION_T *connection;
+      ILCS_CONFIG_T config;
+
+      vc_host_get_vchi_state(&initialise_instance, &connection);
+
+      vcilcs_config(&config);
+
+      ilcs_service = ilcs_init((VCHIQ_INSTANCE_T) initialise_instance, (void **) &connection, &config, 0);
+
+      if(ilcs_service == NULL)
+      {
+         err = OMX_ErrorHardware;
+         goto end;
+      }
+
+      coreInit = 1;
+   }
+   else
+      coreInit++;
+
+end:
+   vcos_mutex_unlock(&lock);
+   return err;
+}
+
+/* OMX_Deinit */
+OMX_ERRORTYPE OMX_APIENTRY OMX_Deinit(void)
+{
+   if(coreInit == 0) // || (coreInit == 1 && nActiveHandles > 0))
+      return OMX_ErrorNotReady;
+
+   vcos_mutex_lock(&lock);
+
+   coreInit--;
+
+   if(coreInit == 0)
+   {
+      // we need to teardown the ILCS connection to VideoCore
+      ilcs_deinit(ilcs_service);
+      ilcs_service = NULL;
+   }
+
+   vcos_mutex_unlock(&lock);
+   
+   return OMX_ErrorNone;
+}
+
+
+/* OMX_ComponentNameEnum */
+OMX_ERRORTYPE OMX_APIENTRY OMX_ComponentNameEnum(
+   OMX_OUT OMX_STRING cComponentName,
+   OMX_IN  OMX_U32 nNameLength,
+   OMX_IN  OMX_U32 nIndex)
+{
+   if(ilcs_service == NULL)
+      return OMX_ErrorBadParameter;
+
+   return vcil_out_component_name_enum(ilcs_get_common(ilcs_service), cComponentName, nNameLength, nIndex);
+}
+
+
+/* OMX_GetHandle */
+OMX_ERRORTYPE OMX_APIENTRY OMX_GetHandle(
+   OMX_OUT OMX_HANDLETYPE* pHandle,
+   OMX_IN  OMX_STRING cComponentName,
+   OMX_IN  OMX_PTR pAppData,
+   OMX_IN  OMX_CALLBACKTYPE* pCallBacks)
+{
+   OMX_ERRORTYPE eError;
+   OMX_COMPONENTTYPE *pComp;
+   OMX_HANDLETYPE hHandle = 0;
+
+   if (pHandle == NULL || cComponentName == NULL || pCallBacks == NULL || ilcs_service == NULL)
+   {
+      if(pHandle)
+         *pHandle = NULL;
+      return OMX_ErrorBadParameter;
+   }
+
+   {
+      pComp = (OMX_COMPONENTTYPE *)malloc(sizeof(OMX_COMPONENTTYPE));
+      if (!pComp)
+      {
+         vcos_assert(0);
+         return OMX_ErrorInsufficientResources;
+      }
+      memset(pComp, 0, sizeof(OMX_COMPONENTTYPE));
+      hHandle = (OMX_HANDLETYPE)pComp;
+      pComp->nSize = sizeof(OMX_COMPONENTTYPE);
+      pComp->nVersion.nVersion = OMX_VERSION;
+      eError = vcil_out_create_component(ilcs_get_common(ilcs_service), hHandle, cComponentName);
+
+      if (eError == OMX_ErrorNone) {
+         // Check that all function pointers have been filled in.
+         // All fields should be non-zero.
+         int i;
+         uint32_t *p = (uint32_t *) pComp;
+         for(i=0; i<sizeof(OMX_COMPONENTTYPE)>>2; i++)
+            if(*p++ == 0)
+               eError = OMX_ErrorInvalidComponent;
+
+         if(eError != OMX_ErrorNone && pComp->ComponentDeInit)
+            pComp->ComponentDeInit(hHandle);
+      }      
+
+      if (eError == OMX_ErrorNone) {
+         eError = pComp->SetCallbacks(hHandle,pCallBacks,pAppData);
+         if (eError != OMX_ErrorNone)
+            pComp->ComponentDeInit(hHandle);
+      }
+      if (eError == OMX_ErrorNone) {
+         *pHandle = hHandle;
+      }
+      else {
+         *pHandle = NULL;
+         free(pComp);
+      }
+   } 
+
+   if (eError == OMX_ErrorNone) {
+      vcos_mutex_lock(&lock);
+      nActiveHandles++;
+      vcos_mutex_unlock(&lock);
+   }
+
+   return eError;
+}
+
+/* OMX_FreeHandle */
+OMX_ERRORTYPE OMX_APIENTRY OMX_FreeHandle(
+   OMX_IN  OMX_HANDLETYPE hComponent)
+{
+   OMX_ERRORTYPE eError = OMX_ErrorNone;
+   OMX_COMPONENTTYPE *pComp;
+
+   if (hComponent == NULL || ilcs_service == NULL)
+      return OMX_ErrorBadParameter;
+
+   pComp = (OMX_COMPONENTTYPE*)hComponent;
+
+   if (ilcs_service == NULL)
+      return OMX_ErrorBadParameter;
+
+   eError = (pComp->ComponentDeInit)(hComponent);
+   if (eError == OMX_ErrorNone) {
+      vcos_mutex_lock(&lock);
+      --nActiveHandles;
+      vcos_mutex_unlock(&lock);
+      free(pComp);
+   }
+
+   vcos_assert(nActiveHandles >= 0);
+
+   return eError;
+}
+
+/* OMX_SetupTunnel */
+OMX_ERRORTYPE OMX_APIENTRY OMX_SetupTunnel(
+   OMX_IN  OMX_HANDLETYPE hOutput,
+   OMX_IN  OMX_U32 nPortOutput,
+   OMX_IN  OMX_HANDLETYPE hInput,
+   OMX_IN  OMX_U32 nPortInput)
+{
+   OMX_ERRORTYPE eError = OMX_ErrorNone;
+   OMX_COMPONENTTYPE *pCompIn, *pCompOut;
+   OMX_TUNNELSETUPTYPE oTunnelSetup;
+
+   if ((hOutput == NULL && hInput == NULL) || ilcs_service == NULL)
+      return OMX_ErrorBadParameter;
+
+   oTunnelSetup.nTunnelFlags = 0;
+   oTunnelSetup.eSupplier = OMX_BufferSupplyUnspecified;
+
+   pCompOut = (OMX_COMPONENTTYPE*)hOutput;
+
+   if (hOutput){
+      eError = pCompOut->ComponentTunnelRequest(hOutput, nPortOutput, hInput, nPortInput, &oTunnelSetup);
+   }
+
+   if (eError == OMX_ErrorNone && hInput) {
+      pCompIn = (OMX_COMPONENTTYPE*)hInput;
+      eError = pCompIn->ComponentTunnelRequest(hInput, nPortInput, hOutput, nPortOutput, &oTunnelSetup);
+
+      if (eError != OMX_ErrorNone && hOutput) {
+         /* cancel tunnel request on output port since input port failed */
+         pCompOut->ComponentTunnelRequest(hOutput, nPortOutput, NULL, 0, NULL);
+      }
+   }
+   return eError;
+}
+
+/* OMX_GetComponentsOfRole */
+OMX_ERRORTYPE OMX_GetComponentsOfRole (
+   OMX_IN      OMX_STRING role,
+   OMX_INOUT   OMX_U32 *pNumComps,
+   OMX_INOUT   OMX_U8  **compNames)
+{
+   OMX_ERRORTYPE eError = OMX_ErrorNone;
+
+   *pNumComps = 0;
+   return eError;
+}
+
+/* OMX_GetRolesOfComponent */
+OMX_ERRORTYPE OMX_GetRolesOfComponent (
+   OMX_IN      OMX_STRING compName,
+   OMX_INOUT   OMX_U32 *pNumRoles,
+   OMX_OUT     OMX_U8 **roles)
+{
+   OMX_ERRORTYPE eError = OMX_ErrorNone;
+
+   *pNumRoles = 0;
+   return eError;
+}
+
+/* OMX_GetDebugInformation */
+OMX_ERRORTYPE OMX_GetDebugInformation (
+   OMX_OUT    OMX_STRING debugInfo,
+   OMX_INOUT  OMX_S32 *pLen)
+{
+   if(ilcs_service == NULL)
+      return OMX_ErrorBadParameter;
+
+   return vcil_out_get_debug_information(ilcs_get_common(ilcs_service), debugInfo, pLen);
+}
+
+
+
+/* File EOF */
+
diff --git a/omx.c b/omx.c
new file mode 100644
index 0000000..eb04028
--- /dev/null
+++ b/omx.c
@@ -0,0 +1,1380 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <queue>
+
+#include "omx.h"
+#include "display.h"
+
+#include <vdr/tools.h>
+#include <vdr/thread.h>
+
+extern "C" {
+#include "ilclient.h"
+}
+
+#include "bcm_host.h"
+
+#define OMX_PRE_ROLL 0
+
+// default: 20x 81920 bytes, now 128x 64k (8M)
+#define OMX_VIDEO_BUFFERS 128
+#define OMX_VIDEO_BUFFERSIZE KILOBYTE(64);
+
+// default: 16x 4096 bytes, now 128x 16k (2M)
+#define OMX_AUDIO_BUFFERS 128
+#define OMX_AUDIO_BUFFERSIZE KILOBYTE(16);
+
+#define OMX_INIT_STRUCT(a) \
+	memset(&(a), 0, sizeof(a)); \
+	(a).nSize = sizeof(a); \
+	(a).nVersion.s.nVersionMajor = OMX_VERSION_MAJOR; \
+	(a).nVersion.s.nVersionMinor = OMX_VERSION_MINOR; \
+	(a).nVersion.s.nRevision = OMX_VERSION_REVISION; \
+	(a).nVersion.s.nStep = OMX_VERSION_STEP
+
+#define OMX_AUDIO_CHANNEL_MAPPING(s, c) \
+switch (c) { \
+case 4: \
+	(s).eChannelMapping[0] = OMX_AUDIO_ChannelLF; \
+	(s).eChannelMapping[1] = OMX_AUDIO_ChannelRF; \
+	(s).eChannelMapping[2] = OMX_AUDIO_ChannelLR; \
+	(s).eChannelMapping[3] = OMX_AUDIO_ChannelRR; \
+	break; \
+case 1: \
+	(s).eChannelMapping[0] = OMX_AUDIO_ChannelCF; \
+	break; \
+case 8: \
+	(s).eChannelMapping[6] = OMX_AUDIO_ChannelLS; \
+	(s).eChannelMapping[7] = OMX_AUDIO_ChannelRS; \
+case 6: \
+	(s).eChannelMapping[2] = OMX_AUDIO_ChannelCF; \
+	(s).eChannelMapping[3] = OMX_AUDIO_ChannelLFE; \
+	(s).eChannelMapping[4] = OMX_AUDIO_ChannelLR; \
+	(s).eChannelMapping[5] = OMX_AUDIO_ChannelRR; \
+case 2: \
+default: \
+	(s).eChannelMapping[0] = OMX_AUDIO_ChannelLF; \
+	(s).eChannelMapping[1] = OMX_AUDIO_ChannelRF; \
+	break; }
+
+class cOmxEvents
+{
+
+public:
+
+	enum eEvent {
+		ePortSettingsChanged,
+		eConfigChanged,
+		eEndOfStream,
+		eBufferEmptied
+	};
+
+	struct Event
+	{
+		Event(eEvent _event, int _data)
+			: event(_event), data(_data) { };
+		eEvent 	event;
+		int		data;
+	};
+
+	cOmxEvents() :
+		m_signal(new cCondWait()),
+		m_mutex(new cMutex())
+	{ }
+
+	virtual ~cOmxEvents()
+	{
+		while (!m_events.empty())
+		{
+			delete m_events.front();
+			m_events.pop();
+		}
+		delete m_signal;
+		delete m_mutex;
+	}
+
+	Event* Get(void)
+	{
+		Event* event = 0;
+		if (!m_events.empty())
+		{
+			m_mutex->Lock();
+			event = m_events.front();
+			m_events.pop();
+			m_mutex->Unlock();
+		}
+		return event;
+	}
+
+	void Add(Event* event)
+	{
+		m_mutex->Lock();
+		m_events.push(event);
+		m_mutex->Unlock();
+		m_signal->Signal();
+	}
+
+private:
+
+	cOmxEvents(const cOmxEvents&);
+	cOmxEvents& operator= (const cOmxEvents&);
+
+	cCondWait*	m_signal;
+	cMutex*		m_mutex;
+	std::queue<Event*> m_events;
+};
+
+const char* cOmx::errStr(int err)
+{
+	return 	err == OMX_ErrorNone                               ? "None"                               :
+			err == OMX_ErrorInsufficientResources              ? "InsufficientResources"              :
+			err == OMX_ErrorUndefined                          ? "Undefined"                          :
+			err == OMX_ErrorInvalidComponentName               ? "InvalidComponentName"               :
+			err == OMX_ErrorComponentNotFound                  ? "ComponentNotFound"                  :
+			err == OMX_ErrorInvalidComponent                   ? "InvalidComponent"                   :
+			err == OMX_ErrorBadParameter                       ? "BadParameter"                       :
+			err == OMX_ErrorNotImplemented                     ? "NotImplemented"                     :
+			err == OMX_ErrorUnderflow                          ? "Underflow"                          :
+			err == OMX_ErrorOverflow                           ? "Overflow"                           :
+			err == OMX_ErrorHardware                           ? "Hardware"                           :
+			err == OMX_ErrorInvalidState                       ? "InvalidState"                       :
+			err == OMX_ErrorStreamCorrupt                      ? "StreamCorrupt"                      :
+			err == OMX_ErrorPortsNotCompatible                 ? "PortsNotCompatible"                 :
+			err == OMX_ErrorResourcesLost                      ? "ResourcesLost"                      :
+			err == OMX_ErrorNoMore                             ? "NoMore"                             :
+			err == OMX_ErrorVersionMismatch                    ? "VersionMismatch"                    :
+			err == OMX_ErrorNotReady                           ? "NotReady"                           :
+			err == OMX_ErrorTimeout                            ? "Timeout"                            :
+			err == OMX_ErrorSameState                          ? "SameState"                          :
+			err == OMX_ErrorResourcesPreempted                 ? "ResourcesPreempted"                 :
+			err == OMX_ErrorPortUnresponsiveDuringAllocation   ? "PortUnresponsiveDuringAllocation"   :
+			err == OMX_ErrorPortUnresponsiveDuringDeallocation ? "PortUnresponsiveDuringDeallocation" :
+			err == OMX_ErrorPortUnresponsiveDuringStop         ? "PortUnresponsiveDuringStop"         :
+			err == OMX_ErrorIncorrectStateTransition           ? "IncorrectStateTransition"           :
+			err == OMX_ErrorIncorrectStateOperation            ? "IncorrectStateOperation"            :
+			err == OMX_ErrorUnsupportedSetting                 ? "UnsupportedSetting"                 :
+			err == OMX_ErrorUnsupportedIndex                   ? "UnsupportedIndex"                   :
+			err == OMX_ErrorBadPortIndex                       ? "BadPortIndex"                       :
+			err == OMX_ErrorPortUnpopulated                    ? "PortUnpopulated"                    :
+			err == OMX_ErrorComponentSuspended                 ? "ComponentSuspended"                 :
+			err == OMX_ErrorDynamicResourcesUnavailable        ? "DynamicResourcesUnavailable"        :
+			err == OMX_ErrorMbErrorsInFrame                    ? "MbErrorsInFrame"                    :
+			err == OMX_ErrorFormatNotDetected                  ? "FormatNotDetected"                  :
+			err == OMX_ErrorContentPipeOpenFailed              ? "ContentPipeOpenFailed"              :
+			err == OMX_ErrorContentPipeCreationFailed          ? "ContentPipeCreationFailed"          :
+			err == OMX_ErrorSeperateTablesUsed                 ? "SeperateTablesUsed"                 :
+			err == OMX_ErrorTunnelingUnsupported               ? "TunnelingUnsupported"               :
+			err == OMX_ErrorKhronosExtensions                  ? "KhronosExtensions"                  :
+			err == OMX_ErrorVendorStartUnused                  ? "VendorStartUnused"                  :
+			err == OMX_ErrorDiskFull                           ? "DiskFull"                           :
+			err == OMX_ErrorMaxFileSize                        ? "MaxFileSize"                        :
+			err == OMX_ErrorDrmUnauthorised                    ? "DrmUnauthorised"                    :
+			err == OMX_ErrorDrmExpired                         ? "DrmExpired"                         :
+			err == OMX_ErrorDrmGeneral                         ? "DrmGeneral"                         :
+			"unknown";
+}
+
+void cOmx::Action(void)
+{
+	cTimeMs timer;
+	while (Running())
+	{
+		while (cOmxEvents::Event* event = m_portEvents->Get())
+		{
+			switch (event->event)
+			{
+			case cOmxEvents::ePortSettingsChanged:
+				if (m_handlePortEvents)
+					HandlePortSettingsChanged(event->data);
+				break;
+
+			case cOmxEvents::eConfigChanged:
+				if (event->data == OMX_IndexConfigBufferStall)
+					if (IsBufferStall() && !IsClockFreezed() && m_onBufferStall)
+						m_onBufferStall(m_onBufferStallData);
+				break;
+
+			case cOmxEvents::eEndOfStream:
+				if (event->data == 90 && m_onEndOfStream)
+					m_onEndOfStream(m_onEndOfStreamData);
+				break;
+
+			case cOmxEvents::eBufferEmptied:
+				HandlePortBufferEmptied((eOmxComponent)event->data);
+				break;
+
+			default:
+				break;
+			}
+
+			delete event;
+		}
+		cCondWait::SleepMs(10);
+
+		if (timer.TimedOut())
+		{
+			timer.Set(100);
+			Lock();
+			for (int i = BUFFERSTAT_FILTER_SIZE - 1; i > 0; i--)
+			{
+				m_usedAudioBuffers[i] = m_usedAudioBuffers[i - 1];
+				m_usedVideoBuffers[i] = m_usedVideoBuffers[i - 1];
+			}
+			Unlock();
+		}
+	}
+}
+
+bool cOmx::PollVideo(void)
+{
+	return (m_usedVideoBuffers[0] * 100 / OMX_VIDEO_BUFFERS) < 90;
+}
+
+void cOmx::GetBufferUsage(int &audio, int &video)
+{
+	audio = 0;
+	video = 0;
+	for (int i = 0; i < BUFFERSTAT_FILTER_SIZE; i++)
+	{
+		audio += m_usedAudioBuffers[i];
+		video += m_usedVideoBuffers[i];
+	}
+	audio = audio * 100 / BUFFERSTAT_FILTER_SIZE / OMX_AUDIO_BUFFERS;
+	video = video * 100 / BUFFERSTAT_FILTER_SIZE / OMX_VIDEO_BUFFERS;
+}
+
+void cOmx::HandlePortBufferEmptied(eOmxComponent component)
+{
+	Lock();
+
+	switch (component)
+	{
+	case eVideoDecoder:
+		m_usedVideoBuffers[0]--;
+		break;
+
+	case eAudioRender:
+		m_usedAudioBuffers[0]--;
+		break;
+
+	default:
+		ELOG("HandlePortBufferEmptied: invalid component!");
+		break;
+	}
+	Unlock();
+}
+
+void cOmx::HandlePortSettingsChanged(unsigned int portId)
+{
+	Lock();
+	DBG("HandlePortSettingsChanged(%d)", portId);
+
+	switch (portId)
+	{
+	case 191:
+		if (ilclient_setup_tunnel(&m_tun[eVideoFxToVideoScheduler], 0, 0) != 0)
+			ELOG("failed to setup up tunnel from video fx to scheduler!");
+		if (ilclient_change_component_state(m_comp[eVideoScheduler], OMX_StateExecuting) != 0)
+			ELOG("failed to enable video scheduler!");
+		break;
+
+	case 131:
+		OMX_PARAM_PORTDEFINITIONTYPE portdef;
+		OMX_INIT_STRUCT(portdef);
+		portdef.nPortIndex = 131;
+		if (OMX_GetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]), OMX_IndexParamPortDefinition,
+				&portdef) != OMX_ErrorNone)
+			ELOG("failed to get video decoder port format!");
+
+		OMX_CONFIG_INTERLACETYPE interlace;
+		OMX_INIT_STRUCT(interlace);
+		interlace.nPortIndex = 131;
+		if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eVideoDecoder]), OMX_IndexConfigCommonInterlace,
+				&interlace) != OMX_ErrorNone)
+			ELOG("failed to get video decoder interlace config!");
+
+		m_videoFormat.width = portdef.format.video.nFrameWidth;
+		m_videoFormat.height = portdef.format.video.nFrameHeight;
+		m_videoFormat.interlaced = interlace.eMode != OMX_InterlaceProgressive;
+
+		// discard 4 least significant bits, since there might be some deviation
+		// due to jitter in time stamps
+		m_videoFormat.frameRate = ALIGN_UP(
+				portdef.format.video.xFramerate & 0xfffffff0, 1 << 16) >> 16;
+
+		// workaround for progressive streams detected as interlaced video by
+		// the decoder due to missing SEI parsing
+		// see: https://github.com/raspberrypi/firmware/issues/283
+		// update: with FW from 2015/01/18 this is not necessary anymore
+		if (m_videoFormat.interlaced && m_videoFormat.frameRate >= 50)
+		{
+			DLOG("%di looks implausible, you should use a recent firmware...",
+					m_videoFormat.frameRate * 2);
+			//m_videoFormat.interlaced = false;
+		}
+
+		if (m_videoFormat.interlaced)
+			m_videoFormat.frameRate = m_videoFormat.frameRate * 2;
+
+		if (m_onStreamStart)
+			m_onStreamStart(m_onStreamStartData);
+
+		OMX_CONFIG_IMAGEFILTERPARAMSTYPE filterparam;
+		OMX_INIT_STRUCT(filterparam);
+		filterparam.nPortIndex = 191;
+		filterparam.eImageFilter = OMX_ImageFilterNone;
+
+		OMX_PARAM_U32TYPE extraBuffers;
+		OMX_INIT_STRUCT(extraBuffers);
+		extraBuffers.nPortIndex = 130;
+
+		if (cRpiDisplay::IsProgressive() && m_videoFormat.interlaced)
+		{
+			bool fastDeinterlace = portdef.format.video.nFrameWidth *
+					portdef.format.video.nFrameHeight > 576 * 720;
+
+			filterparam.nNumParams = fastDeinterlace ? 1 : 2;
+			filterparam.nParams[0] = 3;
+
+			// explicitly set frame interval for advanced deinterlacer
+			// see: https://github.com/raspberrypi/firmware/issues/234
+			filterparam.nParams[1] = 1000000 /
+					(portdef.format.video.xFramerate >> 16);
+
+			filterparam.eImageFilter = fastDeinterlace ?
+					OMX_ImageFilterDeInterlaceFast :
+					OMX_ImageFilterDeInterlaceAdvanced;
+
+			if (fastDeinterlace)
+				extraBuffers.nU32 = -2;
+		}
+		if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eVideoFx]),
+				OMX_IndexConfigCommonImageFilterParameters, &filterparam) != OMX_ErrorNone)
+			ELOG("failed to set deinterlacing paramaters!");
+
+		if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eVideoFx]),
+				OMX_IndexParamBrcmExtraBuffers, &extraBuffers) != OMX_ErrorNone)
+			ELOG("failed to set video fx extra buffers!");
+
+		if (ilclient_setup_tunnel(&m_tun[eVideoDecoderToVideoFx], 0, 0) != 0)
+			ELOG("failed to setup up tunnel from video decoder to fx!");
+		if (ilclient_change_component_state(m_comp[eVideoFx], OMX_StateExecuting) != 0)
+			ELOG("failed to enable video fx!");
+
+		break;
+
+	case 11:
+		if (ilclient_setup_tunnel(&m_tun[eVideoSchedulerToVideoRender], 0, 0) != 0)
+			ELOG("failed to setup up tunnel from scheduler to render!");
+		if (ilclient_change_component_state(m_comp[eVideoRender], OMX_StateExecuting) != 0)
+			ELOG("failed to enable video render!");
+		break;
+	}
+
+	Unlock();
+}
+
+void cOmx::OnBufferEmpty(void *instance, COMPONENT_T *comp)
+{
+	cOmx* omx = static_cast <cOmx*> (instance);
+	omx->m_portEvents->Add(
+			new cOmxEvents::Event(cOmxEvents::eBufferEmptied,
+					comp == omx->m_comp[eVideoDecoder] ? eVideoDecoder :
+					comp == omx->m_comp[eAudioRender] ? eAudioRender :
+							eInvalidComponent));
+}
+
+void cOmx::OnPortSettingsChanged(void *instance, COMPONENT_T *comp, OMX_U32 data)
+{
+	cOmx* omx = static_cast <cOmx*> (instance);
+	omx->m_portEvents->Add(
+			new cOmxEvents::Event(cOmxEvents::ePortSettingsChanged, data));
+}
+
+void cOmx::OnConfigChanged(void *instance, COMPONENT_T *comp, OMX_U32 data)
+{
+	cOmx* omx = static_cast <cOmx*> (instance);
+	omx->m_portEvents->Add(
+			new cOmxEvents::Event(cOmxEvents::eConfigChanged, data));
+}
+
+void cOmx::OnEndOfStream(void *instance, COMPONENT_T *comp, OMX_U32 data)
+{
+	cOmx* omx = static_cast <cOmx*> (instance);
+	omx->m_portEvents->Add(
+			new cOmxEvents::Event(cOmxEvents::eEndOfStream, data));
+}
+
+void cOmx::OnError(void *instance, COMPONENT_T *comp, OMX_U32 data)
+{
+	if ((OMX_S32)data != OMX_ErrorSameState)
+		ELOG("OmxError(%s)", errStr((int)data));
+}
+
+cOmx::cOmx() :
+	cThread(),
+	m_client(NULL),
+	m_setAudioStartTime(false),
+	m_setVideoStartTime(false),
+	m_setVideoDiscontinuity(false),
+	m_spareAudioBuffers(0),
+	m_spareVideoBuffers(0),
+	m_clockReference(eClockRefNone),
+	m_clockScale(0),
+	m_portEvents(new cOmxEvents()),
+	m_handlePortEvents(false),
+	m_onBufferStall(0),
+	m_onBufferStallData(0),
+	m_onEndOfStream(0),
+	m_onEndOfStreamData(0),
+	m_onStreamStart(0),
+	m_onStreamStartData(0)
+{
+	memset(m_tun, 0, sizeof(m_tun));
+	memset(m_comp, 0, sizeof(m_comp));
+
+	m_videoFormat.width = 0;
+	m_videoFormat.height = 0;
+	m_videoFormat.frameRate = 0;
+	m_videoFormat.interlaced = false;
+}
+
+cOmx::~cOmx()
+{
+	delete m_portEvents;
+}
+
+int cOmx::Init(void)
+{
+	m_client = ilclient_init();
+	if (m_client == NULL)
+		ELOG("ilclient_init() failed!");
+
+	if (OMX_Init() != OMX_ErrorNone)
+		ELOG("OMX_Init() failed!");
+
+	ilclient_set_error_callback(m_client, OnError, this);
+	ilclient_set_empty_buffer_done_callback(m_client, OnBufferEmpty, this);
+	ilclient_set_port_settings_callback(m_client, OnPortSettingsChanged, this);
+	ilclient_set_eos_callback(m_client, OnEndOfStream, this);
+	ilclient_set_configchanged_callback(m_client, OnConfigChanged, this);
+
+	// create video_decode
+	if (ilclient_create_component(m_client, &m_comp[eVideoDecoder],
+		"video_decode",	(ILCLIENT_CREATE_FLAGS_T)
+		(ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS)) != 0)
+		ELOG("failed creating video decoder!");
+
+	// create image_fx
+	if (ilclient_create_component(m_client, &m_comp[eVideoFx],
+		"image_fx",	ILCLIENT_DISABLE_ALL_PORTS) != 0)
+		ELOG("failed creating video fx!");
+
+	// create video_render
+	if (ilclient_create_component(m_client, &m_comp[eVideoRender],
+		"video_render",	ILCLIENT_DISABLE_ALL_PORTS) != 0)
+		ELOG("failed creating video render!");
+
+	//create clock
+	if (ilclient_create_component(m_client, &m_comp[eClock],
+		"clock", ILCLIENT_DISABLE_ALL_PORTS) != 0)
+		ELOG("failed creating clock!");
+
+	// create audio_render
+	if (ilclient_create_component(m_client, &m_comp[eAudioRender],
+		"audio_render",	(ILCLIENT_CREATE_FLAGS_T)
+		(ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS)) != 0)
+		ELOG("failed creating audio render!");
+
+	//create video_scheduler
+	if (ilclient_create_component(m_client, &m_comp[eVideoScheduler],
+		"video_scheduler", ILCLIENT_DISABLE_ALL_PORTS) != 0)
+		ELOG("failed creating video scheduler!");
+
+	// setup tunnels
+	set_tunnel(&m_tun[eVideoDecoderToVideoFx],
+		m_comp[eVideoDecoder], 131, m_comp[eVideoFx], 190);
+
+	set_tunnel(&m_tun[eVideoFxToVideoScheduler],
+		m_comp[eVideoFx], 191, m_comp[eVideoScheduler], 10);
+
+	set_tunnel(&m_tun[eVideoSchedulerToVideoRender],
+		m_comp[eVideoScheduler], 11, m_comp[eVideoRender], 90);
+
+	set_tunnel(&m_tun[eClockToVideoScheduler],
+		m_comp[eClock], 80, m_comp[eVideoScheduler], 12);
+
+	set_tunnel(&m_tun[eClockToAudioRender],
+		m_comp[eClock], 81, m_comp[eAudioRender], 101);
+
+	// setup clock tunnels first
+	if (ilclient_setup_tunnel(&m_tun[eClockToVideoScheduler], 0, 0) != 0)
+		ELOG("failed to setup up tunnel from clock to video scheduler!");
+
+	if (ilclient_setup_tunnel(&m_tun[eClockToAudioRender], 0, 0) != 0)
+		ELOG("failed to setup up tunnel from clock to audio render!");
+
+	ilclient_change_component_state(m_comp[eClock], OMX_StateExecuting);
+	ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateIdle);
+	ilclient_change_component_state(m_comp[eVideoFx], OMX_StateIdle);
+	ilclient_change_component_state(m_comp[eAudioRender], OMX_StateIdle);
+
+	SetClockLatencyTarget();
+	SetBufferStallThreshold(20000);
+	SetClockReference(cOmx::eClockRefVideo);
+
+	FlushVideo();
+	FlushAudio();
+
+	Start();
+
+	return 0;
+}
+
+int cOmx::DeInit(void)
+{
+	Cancel(-1);
+	m_portEvents->Add(0);
+
+	for (int i = 0; i < eNumTunnels; i++)
+		ilclient_disable_tunnel(&m_tun[i]);
+
+	ilclient_teardown_tunnels(m_tun);
+	ilclient_state_transition(m_comp, OMX_StateIdle);
+	ilclient_state_transition(m_comp, OMX_StateLoaded);
+	ilclient_cleanup_components(m_comp);
+
+	OMX_Deinit();
+
+	ilclient_destroy(m_client);
+
+	return 0;
+}
+
+void cOmx::SetBufferStallCallback(void (*onBufferStall)(void*), void* data)
+{
+	m_onBufferStall = onBufferStall;
+	m_onBufferStallData = data;
+}
+
+void cOmx::SetEndOfStreamCallback(void (*onEndOfStream)(void*), void* data)
+{
+	m_onEndOfStream = onEndOfStream;
+	m_onEndOfStreamData = data;
+}
+
+void cOmx::SetStreamStartCallback(void (*onStreamStart)(void*), void* data)
+{
+	m_onStreamStart = onStreamStart;
+	m_onStreamStartData = data;
+}
+
+OMX_TICKS cOmx::ToOmxTicks(int64_t val)
+{
+	OMX_TICKS ticks;
+	ticks.nLowPart = val;
+	ticks.nHighPart = val >> 32;
+	return ticks;
+}
+
+int64_t cOmx::FromOmxTicks(OMX_TICKS &ticks)
+{
+	int64_t ret = ticks.nLowPart | ((int64_t)(ticks.nHighPart) << 32);
+	return ret;
+}
+
+void cOmx::PtsToTicks(uint64_t pts, OMX_TICKS &ticks)
+{
+	// ticks = pts * OMX_TICKS_PER_SECOND / PTSTICKS
+	pts = pts * 100 / 9;
+	ticks.nLowPart = pts;
+	ticks.nHighPart = pts >> 32;
+}
+
+uint64_t cOmx::TicksToPts(OMX_TICKS &ticks)
+{
+	// pts = ticks * PTSTICKS / OMX_TICKS_PER_SECOND
+	uint64_t pts = ticks.nHighPart;
+	pts = (pts << 32) + ticks.nLowPart;
+	pts = pts * 9 / 100;
+	return pts;
+}
+
+int64_t cOmx::GetSTC(void)
+{
+	int64_t stc = -1;
+	OMX_TIME_CONFIG_TIMESTAMPTYPE timestamp;
+	OMX_INIT_STRUCT(timestamp);
+	timestamp.nPortIndex = OMX_ALL;
+
+	if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eClock]),
+		OMX_IndexConfigTimeCurrentMediaTime, &timestamp) != OMX_ErrorNone)
+		ELOG("failed get current clock reference!");
+	else
+		stc = TicksToPts(timestamp.nTimestamp);
+
+	return stc;
+}
+
+bool cOmx::IsClockRunning(void)
+{
+	OMX_TIME_CONFIG_CLOCKSTATETYPE cstate;
+	OMX_INIT_STRUCT(cstate);
+
+	if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eClock]),
+			OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone)
+		ELOG("failed get clock state!");
+
+	if (cstate.eState == OMX_TIME_ClockStateRunning)
+		return true;
+	else
+		return false;
+}
+
+void cOmx::StartClock(bool waitForVideo, bool waitForAudio)
+{
+	DBG("StartClock(%svideo, %saudio)",
+			waitForVideo ? "" : "no ",
+			waitForAudio ? "" : "no ");
+
+	OMX_TIME_CONFIG_CLOCKSTATETYPE cstate;
+	OMX_INIT_STRUCT(cstate);
+
+	cstate.eState = OMX_TIME_ClockStateRunning;
+	cstate.nOffset = ToOmxTicks(-1000LL * OMX_PRE_ROLL);
+
+	if (waitForVideo)
+	{
+		cstate.eState = OMX_TIME_ClockStateWaitingForStartTime;
+		m_setVideoStartTime = true;
+		cstate.nWaitMask |= OMX_CLOCKPORT0;
+	}
+	if (waitForAudio)
+	{
+		cstate.eState = OMX_TIME_ClockStateWaitingForStartTime;
+		m_setAudioStartTime = true;
+		cstate.nWaitMask |= OMX_CLOCKPORT1;
+	}
+
+	if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]),
+			OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone)
+		ELOG("failed to start clock!");
+}
+
+void cOmx::StopClock(void)
+{
+	OMX_TIME_CONFIG_CLOCKSTATETYPE cstate;
+	OMX_INIT_STRUCT(cstate);
+
+	cstate.eState = OMX_TIME_ClockStateStopped;
+	cstate.nOffset = ToOmxTicks(-1000LL * OMX_PRE_ROLL);
+
+	if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]),
+			OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone)
+		ELOG("failed to stop clock!");
+}
+
+void cOmx::SetClockScale(OMX_S32 scale)
+{
+	if (scale != m_clockScale)
+	{
+		OMX_TIME_CONFIG_SCALETYPE scaleType;
+		OMX_INIT_STRUCT(scaleType);
+		scaleType.xScale = scale;
+
+		if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]),
+				OMX_IndexConfigTimeScale, &scaleType) != OMX_ErrorNone)
+			ELOG("failed to set clock scale (%d)!", scale);
+		else
+			m_clockScale = scale;
+	}
+}
+
+void cOmx::ResetClock(void)
+{
+	OMX_TIME_CONFIG_TIMESTAMPTYPE timeStamp;
+	OMX_INIT_STRUCT(timeStamp);
+
+	if (m_clockReference == eClockRefAudio || m_clockReference == eClockRefNone)
+	{
+		timeStamp.nPortIndex = 81;
+		if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]),
+			OMX_IndexConfigTimeCurrentAudioReference, &timeStamp)
+				!= OMX_ErrorNone)
+			ELOG("failed to set current audio reference time!");
+	}
+
+	if (m_clockReference == eClockRefVideo || m_clockReference == eClockRefNone)
+	{
+		timeStamp.nPortIndex = 80;
+		if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]),
+			OMX_IndexConfigTimeCurrentVideoReference, &timeStamp)
+				!= OMX_ErrorNone)
+			ELOG("failed to set current video reference time!");
+	}
+}
+
+unsigned int cOmx::GetAudioLatency(void)
+{
+	unsigned int ret = 0;
+
+	OMX_PARAM_U32TYPE u32;
+	OMX_INIT_STRUCT(u32);
+	u32.nPortIndex = 100;
+
+	if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eAudioRender]),
+		OMX_IndexConfigAudioRenderingLatency, &u32) != OMX_ErrorNone)
+		ELOG("failed get audio render latency!");
+	else
+		ret = u32.nU32;
+
+	return ret;
+}
+
+void cOmx::SetClockReference(eClockReference clockReference)
+{
+	if (m_clockReference != clockReference)
+	{
+		OMX_TIME_CONFIG_ACTIVEREFCLOCKTYPE refClock;
+		OMX_INIT_STRUCT(refClock);
+		refClock.eClock =
+			(clockReference == eClockRefAudio) ? OMX_TIME_RefClockAudio :
+			(clockReference == eClockRefVideo) ? OMX_TIME_RefClockVideo :
+				OMX_TIME_RefClockNone;
+
+		if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]),
+				OMX_IndexConfigTimeActiveRefClock, &refClock) != OMX_ErrorNone)
+			ELOG("failed set active clock reference!");
+		else
+			DBG("set active clock reference to %s",
+					clockReference == eClockRefAudio ? "audio" :
+					clockReference == eClockRefVideo ? "video" : "none");
+
+		m_clockReference = clockReference;
+	}
+}
+
+void cOmx::SetClockLatencyTarget(void)
+{
+	OMX_CONFIG_LATENCYTARGETTYPE latencyTarget;
+	OMX_INIT_STRUCT(latencyTarget);
+
+	// latency target for clock
+	// values set according reference implementation in omxplayer
+	latencyTarget.nPortIndex = OMX_ALL;
+	latencyTarget.bEnabled = OMX_TRUE;
+	latencyTarget.nFilter = 10;
+	latencyTarget.nTarget = 0;
+	latencyTarget.nShift = 3;
+	latencyTarget.nSpeedFactor = -60;
+	latencyTarget.nInterFactor = 100;
+	latencyTarget.nAdjCap = 100;
+
+	if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]),
+			OMX_IndexConfigLatencyTarget, &latencyTarget) != OMX_ErrorNone)
+		ELOG("failed set clock latency target!");
+
+	// latency target for video render
+	// values set according reference implementation in omxplayer
+	latencyTarget.nPortIndex = 90;
+	latencyTarget.bEnabled = OMX_TRUE;
+	latencyTarget.nFilter = 2;
+	latencyTarget.nTarget = 4000;
+	latencyTarget.nShift = 3;
+	latencyTarget.nSpeedFactor = -135;
+	latencyTarget.nInterFactor = 500;
+	latencyTarget.nAdjCap = 20;
+
+	if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eVideoRender]),
+			OMX_IndexConfigLatencyTarget, &latencyTarget) != OMX_ErrorNone)
+		ELOG("failed set video render latency target!");
+}
+
+void cOmx::SetBufferStallThreshold(int delayMs)
+{
+	if (delayMs > 0)
+	{
+		OMX_CONFIG_BUFFERSTALLTYPE stallConf;
+		OMX_INIT_STRUCT(stallConf);
+		stallConf.nPortIndex = 131;
+		stallConf.nDelay = delayMs * 1000;
+		if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eVideoDecoder]),
+				OMX_IndexConfigBufferStall, &stallConf) != OMX_ErrorNone)
+			ELOG("failed to set video decoder stall config!");
+	}
+
+	// set buffer stall call back
+	OMX_CONFIG_REQUESTCALLBACKTYPE reqCallback;
+	OMX_INIT_STRUCT(reqCallback);
+	reqCallback.nPortIndex = 131;
+	reqCallback.nIndex = OMX_IndexConfigBufferStall;
+	reqCallback.bEnable = delayMs > 0 ? OMX_TRUE : OMX_FALSE;
+	if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eVideoDecoder]),
+			OMX_IndexConfigRequestCallback, &reqCallback) != OMX_ErrorNone)
+		ELOG("failed to set video decoder stall call back!");
+}
+
+bool cOmx::IsBufferStall(void)
+{
+	OMX_CONFIG_BUFFERSTALLTYPE stallConf;
+	OMX_INIT_STRUCT(stallConf);
+	stallConf.nPortIndex = 131;
+	if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eVideoDecoder]),
+			OMX_IndexConfigBufferStall, &stallConf) != OMX_ErrorNone)
+		ELOG("failed to get video decoder stall config!");
+
+	return stallConf.bStalled == OMX_TRUE;
+}
+
+void cOmx::SetVolume(int vol)
+{
+	OMX_AUDIO_CONFIG_VOLUMETYPE volume;
+	OMX_INIT_STRUCT(volume);
+	volume.nPortIndex = 100;
+	volume.bLinear = OMX_TRUE;
+	volume.sVolume.nValue = vol * 100 / 255;
+
+	if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eAudioRender]),
+			OMX_IndexConfigAudioVolume, &volume) != OMX_ErrorNone)
+		ELOG("failed to set volume!");
+}
+
+void cOmx::SetMute(bool mute)
+{
+	OMX_AUDIO_CONFIG_MUTETYPE amute;
+	OMX_INIT_STRUCT(amute);
+	amute.nPortIndex = 100;
+	amute.bMute = mute ? OMX_TRUE : OMX_FALSE;
+
+	if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eAudioRender]),
+			OMX_IndexConfigAudioMute, &amute) != OMX_ErrorNone)
+		ELOG("failed to set mute state!");
+}
+
+void cOmx::StopVideo(void)
+{
+	Lock();
+
+	// put video decoder into idle
+	ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateIdle);
+
+	// put video fx into idle
+	ilclient_flush_tunnels(&m_tun[eVideoDecoderToVideoFx], 1);
+	ilclient_disable_tunnel(&m_tun[eVideoDecoderToVideoFx]);
+	ilclient_change_component_state(m_comp[eVideoFx], OMX_StateIdle);
+
+	// put video scheduler into idle
+	ilclient_flush_tunnels(&m_tun[eVideoFxToVideoScheduler], 1);
+	ilclient_disable_tunnel(&m_tun[eVideoFxToVideoScheduler]);
+	ilclient_flush_tunnels(&m_tun[eClockToVideoScheduler], 1);
+	ilclient_disable_tunnel(&m_tun[eClockToVideoScheduler]);
+	ilclient_change_component_state(m_comp[eVideoScheduler], OMX_StateIdle);
+
+	// put video render into idle
+	ilclient_flush_tunnels(&m_tun[eVideoSchedulerToVideoRender], 1);
+	ilclient_disable_tunnel(&m_tun[eVideoSchedulerToVideoRender]);
+	ilclient_change_component_state(m_comp[eVideoRender], OMX_StateIdle);
+
+	// disable port buffers and allow video decoder to reconfig
+	ilclient_disable_port_buffers(m_comp[eVideoDecoder], 130,
+			m_spareVideoBuffers, NULL, NULL);
+
+	m_spareVideoBuffers = 0;
+
+	m_videoFormat.width = 0;
+	m_videoFormat.height = 0;
+	m_videoFormat.frameRate = 0;
+	m_videoFormat.interlaced = false;
+
+	m_handlePortEvents = false;
+	Unlock();
+}
+
+void cOmx::StopAudio(void)
+{
+	Lock();
+
+	// put audio render onto idle
+	ilclient_flush_tunnels(&m_tun[eClockToAudioRender], 1);
+	ilclient_disable_tunnel(&m_tun[eClockToAudioRender]);
+	ilclient_change_component_state(m_comp[eAudioRender], OMX_StateIdle);
+	ilclient_disable_port_buffers(m_comp[eAudioRender], 100,
+			m_spareAudioBuffers, NULL, NULL);
+
+	m_spareAudioBuffers = 0;
+	Unlock();
+}
+
+void cOmx::SetVideoErrorConcealment(bool startWithValidFrame)
+{
+	OMX_PARAM_BRCMVIDEODECODEERRORCONCEALMENTTYPE ectype;
+	OMX_INIT_STRUCT(ectype);
+	ectype.bStartWithValidFrame = startWithValidFrame ? OMX_TRUE : OMX_FALSE;
+	if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]),
+			OMX_IndexParamBrcmVideoDecodeErrorConcealment, &ectype) != OMX_ErrorNone)
+		ELOG("failed to set video decode error concealment failed\n");
+}
+
+void cOmx::FlushAudio(void)
+{
+	Lock();
+
+	if (OMX_SendCommand(ILC_GET_HANDLE(m_comp[eAudioRender]), OMX_CommandFlush, 100, NULL) != OMX_ErrorNone)
+		ELOG("failed to flush audio render!");
+
+	ilclient_wait_for_event(m_comp[eAudioRender], OMX_EventCmdComplete,
+		OMX_CommandFlush, 0, 100, 0, ILCLIENT_PORT_FLUSH,
+		VCOS_EVENT_FLAGS_SUSPEND);
+
+	ilclient_flush_tunnels(&m_tun[eClockToAudioRender], 1);
+	Unlock();
+}
+
+void cOmx::FlushVideo(bool flushRender)
+{
+	Lock();
+
+	if (OMX_SendCommand(ILC_GET_HANDLE(m_comp[eVideoDecoder]), OMX_CommandFlush, 130, NULL) != OMX_ErrorNone)
+		ELOG("failed to flush video decoder!");
+
+	ilclient_wait_for_event(m_comp[eVideoDecoder], OMX_EventCmdComplete,
+		OMX_CommandFlush, 0, 130, 0, ILCLIENT_PORT_FLUSH,
+		VCOS_EVENT_FLAGS_SUSPEND);
+
+	ilclient_flush_tunnels(&m_tun[eVideoDecoderToVideoFx], 1);
+	ilclient_flush_tunnels(&m_tun[eVideoFxToVideoScheduler], 1);
+
+	if (flushRender)
+		ilclient_flush_tunnels(&m_tun[eVideoSchedulerToVideoRender], 1);
+
+	ilclient_flush_tunnels(&m_tun[eClockToVideoScheduler], 1);
+
+	m_setVideoDiscontinuity = true;
+	Unlock();
+}
+
+int cOmx::SetVideoCodec(cVideoCodec::eCodec codec)
+{
+	Lock();
+
+	if (ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateIdle) != 0)
+		ELOG("failed to set video decoder to idle state!");
+
+	// configure video decoder
+	OMX_VIDEO_PARAM_PORTFORMATTYPE videoFormat;
+	OMX_INIT_STRUCT(videoFormat);
+	videoFormat.nPortIndex = 130;
+	videoFormat.eCompressionFormat =
+			codec == cVideoCodec::eMPEG2 ? OMX_VIDEO_CodingMPEG2 :
+			codec == cVideoCodec::eH264  ? OMX_VIDEO_CodingAVC   :
+					OMX_VIDEO_CodingAutoDetect;
+
+	if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]),
+			OMX_IndexParamVideoPortFormat, &videoFormat) != OMX_ErrorNone)
+		ELOG("failed to set video decoder parameters!");
+
+	OMX_PARAM_PORTDEFINITIONTYPE param;
+	OMX_INIT_STRUCT(param);
+	param.nPortIndex = 130;
+	if (OMX_GetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]),
+			OMX_IndexParamPortDefinition, &param) != OMX_ErrorNone)
+		ELOG("failed to get video decoder port parameters!");
+
+	param.nBufferSize = OMX_VIDEO_BUFFERSIZE;
+	param.nBufferCountActual = OMX_VIDEO_BUFFERS;
+	for (int i = 0; i < BUFFERSTAT_FILTER_SIZE; i++)
+		m_usedVideoBuffers[i] = 0;
+
+	if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]),
+			OMX_IndexParamPortDefinition, &param) != OMX_ErrorNone)
+		ELOG("failed to set video decoder port parameters!");
+
+	// start with valid frames only if codec is MPEG2
+	// update: with FW from 2015/01/18 this is not necessary anymore
+	SetVideoErrorConcealment(true /*codec == cVideoCodec::eMPEG2*/);
+
+	// update: with FW from 2014/02/04 this is not necessary anymore
+	//SetVideoDecoderExtraBuffers(3);
+
+	if (ilclient_enable_port_buffers(m_comp[eVideoDecoder], 130, NULL, NULL, NULL) != 0)
+		ELOG("failed to enable port buffer on video decoder!");
+
+	if (ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateExecuting) != 0)
+		ELOG("failed to set video decoder to executing state!");
+
+	// setup clock tunnels first
+	if (ilclient_setup_tunnel(&m_tun[eClockToVideoScheduler], 0, 0) != 0)
+		ELOG("failed to setup up tunnel from clock to video scheduler!");
+
+	m_handlePortEvents = true;
+
+	Unlock();
+	return 0;
+}
+
+void cOmx::SetVideoDecoderExtraBuffers(int extraBuffers)
+{
+	OMX_PARAM_U32TYPE u32;
+	OMX_INIT_STRUCT(u32);
+	u32.nPortIndex = 130;
+	u32.nU32 = extraBuffers;
+	if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]),
+			OMX_IndexParamBrcmExtraBuffers, &u32) != OMX_ErrorNone)
+		ELOG("failed to set video decoder extra buffers!");
+}
+
+int cOmx::SetupAudioRender(cAudioCodec::eCodec outputFormat, int channels,
+		cRpiAudioPort::ePort audioPort, int samplingRate, int frameSize)
+{
+	Lock();
+
+	OMX_AUDIO_PARAM_PORTFORMATTYPE format;
+	OMX_INIT_STRUCT(format);
+	format.nPortIndex = 100;
+	if (OMX_GetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]),
+			OMX_IndexParamAudioPortFormat, &format) != OMX_ErrorNone)
+		ELOG("failed to get audio port format parameters!");
+
+	format.eEncoding =
+		outputFormat == cAudioCodec::ePCM  ? OMX_AUDIO_CodingPCM :
+		outputFormat == cAudioCodec::eMPG  ? OMX_AUDIO_CodingMP3 :
+		outputFormat == cAudioCodec::eAC3  ? OMX_AUDIO_CodingDDP :
+		outputFormat == cAudioCodec::eEAC3 ? OMX_AUDIO_CodingDDP :
+		outputFormat == cAudioCodec::eAAC  ? OMX_AUDIO_CodingAAC :
+		outputFormat == cAudioCodec::eDTS  ? OMX_AUDIO_CodingDTS :
+				OMX_AUDIO_CodingAutoDetect;
+
+	if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]),
+			OMX_IndexParamAudioPortFormat, &format) != OMX_ErrorNone)
+		ELOG("failed to set audio port format parameters!");
+
+	switch (outputFormat)
+	{
+	case cAudioCodec::eMPG:
+		OMX_AUDIO_PARAM_MP3TYPE mp3;
+		OMX_INIT_STRUCT(mp3);
+		mp3.nPortIndex = 100;
+		mp3.nChannels = channels;
+		mp3.nSampleRate = samplingRate;
+		mp3.eChannelMode = OMX_AUDIO_ChannelModeStereo; // ?
+		mp3.eFormat = OMX_AUDIO_MP3StreamFormatMP1Layer3; // should be MPEG-1 layer 2
+
+		if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]),
+				OMX_IndexParamAudioMp3, &mp3) != OMX_ErrorNone)
+			ELOG("failed to set audio render mp3 parameters!");
+		break;
+
+	case cAudioCodec::eAC3:
+	case cAudioCodec::eEAC3:
+		OMX_AUDIO_PARAM_DDPTYPE ddp;
+		OMX_INIT_STRUCT(ddp);
+		ddp.nPortIndex = 100;
+		ddp.nChannels = channels;
+		ddp.nSampleRate = samplingRate;
+		OMX_AUDIO_CHANNEL_MAPPING(ddp, channels);
+
+		if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]),
+				OMX_IndexParamAudioDdp, &ddp) != OMX_ErrorNone)
+			ELOG("failed to set audio render ddp parameters!");
+		break;
+
+	case cAudioCodec::eAAC:
+		OMX_AUDIO_PARAM_AACPROFILETYPE aac;
+		OMX_INIT_STRUCT(aac);
+		aac.nPortIndex = 100;
+		aac.nChannels = channels;
+		aac.nSampleRate = samplingRate;
+		aac.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS;
+
+		if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]),
+				OMX_IndexParamAudioAac, &aac) != OMX_ErrorNone)
+			ELOG("failed to set audio render aac parameters!");
+		break;
+
+	case cAudioCodec::eDTS:
+		OMX_AUDIO_PARAM_DTSTYPE dts;
+		OMX_INIT_STRUCT(dts);
+		dts.nPortIndex = 100;
+		dts.nChannels = channels;
+		dts.nSampleRate = samplingRate;
+		dts.nDtsType = 1;
+		dts.nFormat = 3; /* 16bit, LE */
+		dts.nDtsFrameSizeBytes = frameSize;
+		OMX_AUDIO_CHANNEL_MAPPING(dts, channels);
+
+		if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]),
+				OMX_IndexParamAudioDts, &dts) != OMX_ErrorNone)
+			ELOG("failed to set audio render dts parameters!");
+		break;
+
+	case cAudioCodec::ePCM:
+		OMX_AUDIO_PARAM_PCMMODETYPE pcm;
+		OMX_INIT_STRUCT(pcm);
+		pcm.nPortIndex = 100;
+		pcm.nChannels = channels;
+		pcm.eNumData = OMX_NumericalDataSigned;
+		pcm.eEndian = OMX_EndianLittle;
+		pcm.bInterleaved = OMX_TRUE;
+		pcm.nBitPerSample = 16;
+		pcm.nSamplingRate = samplingRate;
+		pcm.ePCMMode = OMX_AUDIO_PCMModeLinear;
+		OMX_AUDIO_CHANNEL_MAPPING(pcm, channels);
+
+		if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]),
+				OMX_IndexParamAudioPcm, &pcm) != OMX_ErrorNone)
+			ELOG("failed to set audio render pcm parameters!");
+		break;
+
+	default:
+		ELOG("output codec not supported: %s!",
+				cAudioCodec::Str(outputFormat));
+		break;
+	}
+
+	OMX_CONFIG_BRCMAUDIODESTINATIONTYPE audioDest;
+	OMX_INIT_STRUCT(audioDest);
+	strcpy((char *)audioDest.sName,
+			audioPort == cRpiAudioPort::eLocal ? "local" : "hdmi");
+
+	if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eAudioRender]),
+			OMX_IndexConfigBrcmAudioDestination, &audioDest) != OMX_ErrorNone)
+		ELOG("failed to set audio destination!");
+
+	// set up the number and size of buffers for audio render
+	OMX_PARAM_PORTDEFINITIONTYPE param;
+	OMX_INIT_STRUCT(param);
+	param.nPortIndex = 100;
+	if (OMX_GetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]),
+			OMX_IndexParamPortDefinition, &param) != OMX_ErrorNone)
+		ELOG("failed to get audio render port parameters!");
+
+	param.nBufferSize = OMX_AUDIO_BUFFERSIZE;
+	param.nBufferCountActual = OMX_AUDIO_BUFFERS;
+	for (int i = 0; i < BUFFERSTAT_FILTER_SIZE; i++)
+		m_usedAudioBuffers[i] = 0;
+
+	if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]),
+			OMX_IndexParamPortDefinition, &param) != OMX_ErrorNone)
+		ELOG("failed to set audio render port parameters!");
+
+	if (ilclient_enable_port_buffers(m_comp[eAudioRender], 100, NULL, NULL, NULL) != 0)
+		ELOG("failed to enable port buffer on audio render!");
+
+	ilclient_change_component_state(m_comp[eAudioRender], OMX_StateExecuting);
+
+	if (ilclient_setup_tunnel(&m_tun[eClockToAudioRender], 0, 0) != 0)
+		ELOG("failed to setup up tunnel from clock to audio render!");
+
+	Unlock();
+	return 0;
+}
+
+void cOmx::GetVideoFormat(int &width, int &height, int &frameRate,
+		bool &interlaced)
+{
+	width = m_videoFormat.width;
+	height = m_videoFormat.height;
+	frameRate = m_videoFormat.frameRate;
+	interlaced = m_videoFormat.interlaced;
+}
+
+void cOmx::SetDisplayMode(bool fill, bool noaspect)
+{
+	OMX_CONFIG_DISPLAYREGIONTYPE region;
+	OMX_INIT_STRUCT(region);
+	region.nPortIndex = 90;
+	region.set = (OMX_DISPLAYSETTYPE)
+			(OMX_DISPLAY_SET_MODE | OMX_DISPLAY_SET_NOASPECT);
+
+	region.noaspect = noaspect ? OMX_TRUE : OMX_FALSE;
+	region.mode = fill ? OMX_DISPLAY_MODE_FILL : OMX_DISPLAY_MODE_LETTERBOX;
+
+	if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eVideoRender]),
+			OMX_IndexConfigDisplayRegion, &region) != OMX_ErrorNone)
+		ELOG("failed to set display region!");
+}
+
+void cOmx::SetDisplayRegion(int x, int y, int width, int height)
+{
+	OMX_CONFIG_DISPLAYREGIONTYPE region;
+	OMX_INIT_STRUCT(region);
+	region.nPortIndex = 90;
+	region.set = (OMX_DISPLAYSETTYPE)
+			(OMX_DISPLAY_SET_FULLSCREEN | OMX_DISPLAY_SET_DEST_RECT);
+
+	region.fullscreen = (!x && !y && !width && !height) ? OMX_TRUE : OMX_FALSE;
+	region.dest_rect.x_offset = x;
+	region.dest_rect.y_offset = y;
+	region.dest_rect.width = width;
+	region.dest_rect.height = height;
+
+	if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eVideoRender]),
+			OMX_IndexConfigDisplayRegion, &region) != OMX_ErrorNone)
+		ELOG("failed to set display region!");
+}
+
+OMX_BUFFERHEADERTYPE* cOmx::GetAudioBuffer(uint64_t pts)
+{
+	Lock();
+	OMX_BUFFERHEADERTYPE* buf = 0;
+	if (m_spareAudioBuffers)
+	{
+		buf = m_spareAudioBuffers;
+		m_spareAudioBuffers =
+				static_cast <OMX_BUFFERHEADERTYPE*>(buf->pAppPrivate);
+		buf->pAppPrivate = 0;
+	}
+	else
+	{
+		buf = ilclient_get_input_buffer(m_comp[eAudioRender], 100, 0);
+		if (buf)
+			m_usedAudioBuffers[0]++;
+	}
+
+	if (buf)
+	{
+		buf->nFilledLen = 0;
+		buf->nOffset = 0;
+		buf->nFlags = 0;
+
+		if (m_setAudioStartTime)
+			buf->nFlags |= OMX_BUFFERFLAG_STARTTIME;
+		else if (!pts)
+			buf->nFlags |= OMX_BUFFERFLAG_TIME_UNKNOWN;
+
+		cOmx::PtsToTicks(pts, buf->nTimeStamp);
+		m_setAudioStartTime = false;
+	}
+	Unlock();
+	return buf;
+}
+
+OMX_BUFFERHEADERTYPE* cOmx::GetVideoBuffer(uint64_t pts)
+{
+	Lock();
+	OMX_BUFFERHEADERTYPE* buf = 0;
+	if (m_spareVideoBuffers)
+	{
+		buf = m_spareVideoBuffers;
+		m_spareVideoBuffers =
+				static_cast <OMX_BUFFERHEADERTYPE*>(buf->pAppPrivate);
+		buf->pAppPrivate = 0;
+	}
+	else
+	{
+		buf = ilclient_get_input_buffer(m_comp[eVideoDecoder], 130, 0);
+		if (buf)
+			m_usedVideoBuffers[0]++;
+	}
+
+	if (buf)
+	{
+		buf->nFilledLen = 0;
+		buf->nOffset = 0;
+		buf->nFlags = 0;
+
+		if (m_setVideoStartTime)
+			buf->nFlags |= OMX_BUFFERFLAG_STARTTIME;
+		else if (!pts)
+			buf->nFlags |= OMX_BUFFERFLAG_TIME_UNKNOWN;
+
+		if (m_setVideoDiscontinuity)
+			buf->nFlags |= OMX_BUFFERFLAG_DISCONTINUITY;
+
+		cOmx::PtsToTicks(pts, buf->nTimeStamp);
+		m_setVideoStartTime = false;
+		m_setVideoDiscontinuity = false;
+	}
+	Unlock();
+	return buf;
+}
+
+#ifdef DEBUG_BUFFERS
+void cOmx::DumpBuffer(OMX_BUFFERHEADERTYPE *buf, const char *prefix)
+{
+	DLOG("%s: TS=%8x%08x, LEN=%5d/%5d: %02x %02x %02x %02x... "
+			"FLAGS: %s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+		prefix,
+		buf->nTimeStamp.nHighPart, buf->nTimeStamp.nLowPart,
+		buf->nFilledLen, buf->nAllocLen,
+		buf->pBuffer[0], buf->pBuffer[1], buf->pBuffer[2], buf->pBuffer[3],
+		buf->nFlags & OMX_BUFFERFLAG_EOS             ? "EOS "           : "",
+		buf->nFlags & OMX_BUFFERFLAG_STARTTIME       ? "STARTTIME "     : "",
+		buf->nFlags & OMX_BUFFERFLAG_DECODEONLY      ? "DECODEONLY "    : "",
+		buf->nFlags & OMX_BUFFERFLAG_DATACORRUPT     ? "DATACORRUPT "   : "",
+		buf->nFlags & OMX_BUFFERFLAG_ENDOFFRAME      ? "ENDOFFRAME "    : "",
+		buf->nFlags & OMX_BUFFERFLAG_SYNCFRAME       ? "SYNCFRAME "     : "",
+		buf->nFlags & OMX_BUFFERFLAG_EXTRADATA       ? "EXTRADATA "     : "",
+		buf->nFlags & OMX_BUFFERFLAG_CODECCONFIG     ? "CODECCONFIG "   : "",
+		buf->nFlags & OMX_BUFFERFLAG_TIME_UNKNOWN    ? "TIME_UNKNOWN "  : "",
+		buf->nFlags & OMX_BUFFERFLAG_CAPTURE_PREVIEW ? "CAPTURE_PREV "  : "",
+		buf->nFlags & OMX_BUFFERFLAG_ENDOFNAL        ? "ENDOFNAL "      : "",
+		buf->nFlags & OMX_BUFFERFLAG_FRAGMENTLIST    ? "FRAGMENTLIST "  : "",
+		buf->nFlags & OMX_BUFFERFLAG_DISCONTINUITY   ? "DISCONTINUITY " : "",
+		buf->nFlags & OMX_BUFFERFLAG_CODECSIDEINFO   ? "CODECSIDEINFO " : ""
+	);
+}
+#endif
+
+bool cOmx::EmptyAudioBuffer(OMX_BUFFERHEADERTYPE *buf)
+{
+	if (!buf)
+		return false;
+
+	Lock();
+	bool ret = true;
+#ifdef DEBUG_BUFFERS
+	DumpBuffer(buf, "A");
+#endif
+
+	if (OMX_EmptyThisBuffer(ILC_GET_HANDLE(m_comp[eAudioRender]), buf)
+			!= OMX_ErrorNone)
+	{
+		ELOG("failed to empty OMX audio buffer");
+
+		if (buf->nFlags & OMX_BUFFERFLAG_STARTTIME)
+			m_setAudioStartTime = true;
+
+		if (buf->nFlags & OMX_BUFFERFLAG_DISCONTINUITY)
+			m_setVideoDiscontinuity = true;
+
+		buf->nFilledLen = 0;
+		buf->pAppPrivate = m_spareAudioBuffers;
+		m_spareAudioBuffers = buf;
+		ret = false;
+	}
+	Unlock();
+	return ret;
+}
+
+bool cOmx::EmptyVideoBuffer(OMX_BUFFERHEADERTYPE *buf)
+{
+	if (!buf)
+		return false;
+
+	Lock();
+	bool ret = true;
+#ifdef DEBUG_BUFFERS
+	DumpBuffer(buf, "V");
+#endif
+
+	if (OMX_EmptyThisBuffer(ILC_GET_HANDLE(m_comp[eVideoDecoder]), buf)
+			!= OMX_ErrorNone)
+	{
+		ELOG("failed to empty OMX video buffer");
+
+		if (buf->nFlags & OMX_BUFFERFLAG_STARTTIME)
+			m_setVideoStartTime = true;
+
+		buf->nFilledLen = 0;
+		buf->pAppPrivate = m_spareVideoBuffers;
+		m_spareVideoBuffers = buf;
+		ret = false;
+	}
+	Unlock();
+	return ret;
+}
diff --git a/omx.h b/omx.h
new file mode 100644
index 0000000..6988ad4
--- /dev/null
+++ b/omx.h
@@ -0,0 +1,181 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef OMX_H
+#define OMX_H
+
+#include <vdr/thread.h>
+#include "tools.h"
+
+extern "C"
+{
+#include "ilclient.h"
+}
+
+class cOmxEvents;
+
+class cOmx : public cThread
+{
+
+public:
+
+	cOmx();
+	virtual ~cOmx();
+	int Init(void);
+	int DeInit(void);
+
+	void SetBufferStallCallback(void (*onBufferStall)(void*), void* data);
+	void SetEndOfStreamCallback(void (*onEndOfStream)(void*), void* data);
+	void SetStreamStartCallback(void (*onStreamStart)(void*), void* data);
+
+	static OMX_TICKS ToOmxTicks(int64_t val);
+	static int64_t FromOmxTicks(OMX_TICKS &ticks);
+	static void PtsToTicks(uint64_t pts, OMX_TICKS &ticks);
+	static uint64_t TicksToPts(OMX_TICKS &ticks);
+
+	int64_t GetSTC(void);
+	bool IsClockRunning(void);
+
+	enum eClockState {
+		eClockStateRun,
+		eClockStateStop,
+		eClockStateWaitForVideo,
+		eClockStateWaitForAudio,
+		eClockStateWaitForAudioVideo
+	};
+
+	void StartClock(bool waitForVideo = false, bool waitForAudio = false);
+	void StopClock(void);
+	void ResetClock(void);
+
+	void SetClockScale(OMX_S32 scale);
+	bool IsClockFreezed(void) { return m_clockScale == 0; }
+	unsigned int GetAudioLatency(void);
+
+	enum eClockReference {
+		eClockRefAudio,
+		eClockRefVideo,
+		eClockRefNone
+	};
+
+	void SetClockReference(eClockReference clockReference);
+	void SetClockLatencyTarget(void);
+	void SetVolume(int vol);
+	void SetMute(bool mute);
+	void StopVideo(void);
+	void StopAudio(void);
+
+	void SetVideoErrorConcealment(bool startWithValidFrame);
+	void SetVideoDecoderExtraBuffers(int extraBuffers);
+
+	void FlushAudio(void);
+	void FlushVideo(bool flushRender = false);
+
+	int SetVideoCodec(cVideoCodec::eCodec codec);
+	int SetupAudioRender(cAudioCodec::eCodec outputFormat,
+			int channels, cRpiAudioPort::ePort audioPort,
+			int samplingRate = 0, int frameSize = 0);
+	void GetVideoFormat(int &width, int &height, int &frameRate,
+			bool &interlaced);
+
+	void SetDisplayMode(bool letterbox, bool noaspect);
+	void SetDisplayRegion(int x, int y, int width, int height);
+
+	OMX_BUFFERHEADERTYPE* GetAudioBuffer(uint64_t pts = 0);
+	OMX_BUFFERHEADERTYPE* GetVideoBuffer(uint64_t pts = 0);
+
+	bool PollVideo(void);
+
+	bool EmptyAudioBuffer(OMX_BUFFERHEADERTYPE *buf);
+	bool EmptyVideoBuffer(OMX_BUFFERHEADERTYPE *buf);
+
+	void GetBufferUsage(int &audio, int &video);
+
+private:
+
+	virtual void Action(void);
+
+	static const char* errStr(int err);
+
+#ifdef DEBUG_BUFFERS
+	static void DumpBuffer(OMX_BUFFERHEADERTYPE *buf, const char *prefix = "");
+#endif
+
+	enum eOmxComponent {
+		eClock = 0,
+		eVideoDecoder,
+		eVideoFx,
+		eVideoScheduler,
+		eVideoRender,
+		eAudioRender,
+		eNumComponents,
+		eInvalidComponent
+	};
+
+	enum eOmxTunnel {
+		eVideoDecoderToVideoFx = 0,
+		eVideoFxToVideoScheduler,
+		eVideoSchedulerToVideoRender,
+		eClockToVideoScheduler,
+		eClockToAudioRender,
+		eNumTunnels
+	};
+
+	ILCLIENT_T 	*m_client;
+	COMPONENT_T	*m_comp[cOmx::eNumComponents + 1];
+	TUNNEL_T 	 m_tun[cOmx::eNumTunnels + 1];
+
+	struct VideoFormat
+	{
+		int width;
+		int height;
+		int frameRate;
+		bool interlaced;
+	};
+
+	VideoFormat m_videoFormat;
+
+	bool m_setAudioStartTime;
+	bool m_setVideoStartTime;
+	bool m_setVideoDiscontinuity;
+
+#define BUFFERSTAT_FILTER_SIZE 64
+
+	int m_usedAudioBuffers[BUFFERSTAT_FILTER_SIZE];
+	int m_usedVideoBuffers[BUFFERSTAT_FILTER_SIZE];
+
+	OMX_BUFFERHEADERTYPE* m_spareAudioBuffers;
+	OMX_BUFFERHEADERTYPE* m_spareVideoBuffers;
+
+	eClockReference	m_clockReference;
+	OMX_S32 m_clockScale;
+
+	cOmxEvents *m_portEvents;
+	bool m_handlePortEvents;
+
+	void (*m_onBufferStall)(void*);
+	void *m_onBufferStallData;
+
+	void (*m_onEndOfStream)(void*);
+	void *m_onEndOfStreamData;
+
+	void (*m_onStreamStart)(void*);
+	void *m_onStreamStartData;
+
+	void HandlePortBufferEmptied(eOmxComponent component);
+	void HandlePortSettingsChanged(unsigned int portId);
+	void SetBufferStallThreshold(int delayMs);
+	bool IsBufferStall(void);
+
+	static void OnBufferEmpty(void *instance, COMPONENT_T *comp);
+	static void OnPortSettingsChanged(void *instance, COMPONENT_T *comp, OMX_U32 data);
+	static void OnEndOfStream(void *instance, COMPONENT_T *comp, OMX_U32 data);
+	static void OnError(void *instance, COMPONENT_T *comp, OMX_U32 data);
+	static void OnConfigChanged(void *instance, COMPONENT_T *comp, OMX_U32 data);
+
+};
+
+#endif
diff --git a/omxdevice.c b/omxdevice.c
new file mode 100644
index 0000000..a470be6
--- /dev/null
+++ b/omxdevice.c
@@ -0,0 +1,780 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include "omxdevice.h"
+#include "omx.h"
+#include "audio.h"
+#include "display.h"
+#include "setup.h"
+
+#include <vdr/thread.h>
+#include <vdr/remux.h>
+#include <vdr/tools.h>
+#include <vdr/skins.h>
+
+#include <string.h>
+
+#define S(x) ((int)(floor(x * pow(2, 16))))
+#define PTS_START_OFFSET (32 * (MAX33BIT + 1))
+
+// trick speeds as defined in vdr/dvbplayer.c
+const int cOmxDevice::s_playbackSpeeds[eNumDirections][eNumPlaybackSpeeds] = {
+	{ S(0.0f), S( 0.125f), S( 0.25f), S( 0.5f), S( 1.0f), S( 2.0f), S( 4.0f), S( 12.0f) },
+	{ S(0.0f), S(-0.125f), S(-0.25f), S(-0.5f), S(-1.0f), S(-2.0f), S(-4.0f), S(-12.0f) }
+};
+
+// speed correction factors for live mode
+// HDMI specification allows a tolerance of 1000ppm, however on the Raspberry Pi
+// it's limited to 175ppm to avoid audio drops one some A/V receivers
+const int cOmxDevice::s_liveSpeeds[eNumLiveSpeeds] = {
+	S(0.999f), S(0.99985f), S(1.000f), S(1.00015), S(1.001)
+};
+
+const uchar cOmxDevice::PesVideoHeader[14] = {
+	0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x80, 0x80, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+cOmxDevice::cOmxDevice(void (*onPrimaryDevice)(void)) :
+	cDevice(),
+	m_onPrimaryDevice(onPrimaryDevice),
+	m_omx(new cOmx()),
+	m_audio(new cRpiAudioDecoder(m_omx)),
+	m_mutex(new cMutex()),
+	m_timer(new cTimeMs()),
+	m_videoCodec(cVideoCodec::eInvalid),
+	m_playMode(pmNone),
+	m_liveSpeed(eNoCorrection),
+	m_playbackSpeed(eNormal),
+	m_direction(eForward),
+	m_hasVideo(false),
+	m_hasAudio(false),
+	m_skipAudio(false),
+	m_playDirection(0),
+	m_trickRequest(0),
+	m_audioPts(0),
+	m_videoPts(0),
+	m_lastStc(0)
+{
+}
+
+cOmxDevice::~cOmxDevice()
+{
+	DeInit();
+
+	delete m_omx;
+	delete m_audio;
+	delete m_mutex;
+	delete m_timer;
+}
+
+int cOmxDevice::Init(void)
+{
+	if (m_omx->Init() < 0)
+	{
+		ELOG("failed to initialize OMX!");
+		return -1;
+	}
+	if (m_audio->Init() < 0)
+	{
+		ELOG("failed to initialize audio!");
+		return -1;
+	}
+	m_omx->SetBufferStallCallback(&OnBufferStall, this);
+	m_omx->SetEndOfStreamCallback(&OnEndOfStream, this);
+	m_omx->SetStreamStartCallback(&OnStreamStart, this);
+
+	cRpiSetup::SetVideoSetupChangedCallback(&OnVideoSetupChanged, this);
+
+	return 0;
+}
+
+int cOmxDevice::DeInit(void)
+{
+	cRpiSetup::SetVideoSetupChangedCallback(0);
+	if (m_audio->DeInit() < 0)
+	{
+		ELOG("failed to deinitialize audio!");
+		return -1;
+	}
+	if (m_omx->DeInit() < 0)
+	{
+		ELOG("failed to deinitialize OMX!");
+		return -1;
+	}
+	return 0;
+}
+
+bool cOmxDevice::Start(void)
+{
+	HandleVideoSetupChanged();
+	return true;
+}
+
+void cOmxDevice::GetOsdSize(int &Width, int &Height, double &PixelAspect)
+{
+	cRpiDisplay::GetSize(Width, Height, PixelAspect);
+}
+
+void cOmxDevice::GetVideoSize(int &Width, int &Height, double &VideoAspect)
+{
+	bool interlaced;
+	int frameRate;
+
+	m_omx->GetVideoFormat(Width, Height, frameRate, interlaced);
+
+	if (Height)
+		VideoAspect = (double)Width / Height;
+	else
+		VideoAspect = 1.0;
+}
+
+void cOmxDevice::ScaleVideo(const cRect &Rect)
+{
+	DBG("ScaleVideo(%d, %d, %d, %d)",
+		Rect.X(), Rect.Y(), Rect.Width(), Rect.Height());
+
+	m_omx->SetDisplayRegion(Rect.X(), Rect.Y(), Rect.Width(), Rect.Height());
+}
+
+bool cOmxDevice::SetPlayMode(ePlayMode PlayMode)
+{
+	m_mutex->Lock();
+	DBG("SetPlayMode(%s)",
+		PlayMode == pmNone			 ? "none" 			   :
+		PlayMode == pmAudioVideo	 ? "Audio/Video" 	   :
+		PlayMode == pmAudioOnly		 ? "Audio only" 	   :
+		PlayMode == pmAudioOnlyBlack ? "Audio only, black" :
+		PlayMode == pmVideoOnly		 ? "Video only" 	   : 
+									   "unsupported");
+
+	// Stop audio / video if play mode is set to pmNone. Start
+	// is triggered once a packet is going to be played, since
+	// we don't know what kind of stream we'll get (audio-only,
+	// video-only or both) after SetPlayMode() - VDR will always
+	// pass pmAudioVideo as argument.
+
+	switch (PlayMode)
+	{
+	case pmNone:
+		FlushStreams(true);
+		m_omx->StopVideo();
+		m_hasAudio = false;
+		m_hasVideo = false;
+		m_videoCodec = cVideoCodec::eInvalid;
+		m_playMode = pmNone;
+		break;
+
+	case pmAudioVideo:
+	case pmAudioOnly:
+	case pmAudioOnlyBlack:
+	case pmVideoOnly:
+		m_playbackSpeed = eNormal;
+		m_direction = eForward;
+		break;
+
+	default:
+		break;
+	}
+
+	m_mutex->Unlock();
+	return true;
+}
+
+void cOmxDevice::StillPicture(const uchar *Data, int Length)
+{
+	if (Data[0] == 0x47)
+		cDevice::StillPicture(Data, Length);
+	else
+	{
+		DBG("StillPicture()");
+		int pesLength = 0;
+		uchar *pesPacket = 0;
+
+		cVideoCodec::eCodec codec = ParseVideoCodec(Data, Length);
+		if (codec != cVideoCodec::eInvalid)
+		{
+			// some plugins deliver raw MPEG data, but PlayVideo() needs a
+			// complete PES packet with valid header
+			pesLength = Length + sizeof(PesVideoHeader);
+			pesPacket = MALLOC(uchar, pesLength);
+			if (!pesPacket)
+				return;
+
+			memcpy(pesPacket, PesVideoHeader, sizeof(PesVideoHeader));
+			memcpy(pesPacket + sizeof(PesVideoHeader), Data, Length);
+		}
+		else
+			codec = ParseVideoCodec(Data + PesPayloadOffset(Data),
+					Length - PesPayloadOffset(Data));
+
+		if (codec == cVideoCodec::eInvalid)
+			return;
+
+		m_mutex->Lock();
+		m_playbackSpeed = eNormal;
+		m_direction = eForward;
+		m_omx->StopClock();
+
+		// to get a picture displayed, PlayVideo() needs to be called
+		// 4x for MPEG2 and 10x for H264... ?
+		int repeat = codec == cVideoCodec::eMPEG2 ? 4 : 10;
+		while (repeat--)
+		{
+			int length = pesPacket ? pesLength : Length;
+			const uchar *data = pesPacket ? pesPacket : Data;
+
+			// play every single PES packet, rise ENDOFFRAME flag on last
+			while (PesLongEnough(length))
+			{
+				int pktLen = PesHasLength(data) ? PesLength(data) : length;
+
+				// skip non-video packets as they may occur in PES recordings
+				if ((data[3] & 0xf0) == 0xe0)
+					PlayVideo(data, pktLen, pktLen == length);
+
+				data += pktLen;
+				length -= pktLen;
+			}
+		}
+		SubmitEOS();
+		m_mutex->Unlock();
+
+		if (pesPacket)
+			free(pesPacket);
+	}
+}
+
+int cOmxDevice::PlayAudio(const uchar *Data, int Length, uchar Id)
+{
+	// ignore audio packets during trick speeds for non-radio recordings
+	if (m_playbackSpeed != eNormal && m_playMode != pmAudioOnly)
+	{
+		DLOG("audio packet ignored!");
+		return Length;
+	}
+
+	m_mutex->Lock();
+	int ret = Length;
+	int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0;
+
+	if (!m_hasAudio)
+	{
+		m_hasAudio = true;
+		m_omx->SetClockReference(cOmx::eClockRefAudio);
+
+		if (!m_hasVideo)
+		{
+			DBG("audio first");
+			m_omx->SetClockScale(s_playbackSpeeds[m_direction][m_playbackSpeed]);
+			m_omx->StartClock(m_hasVideo, m_hasAudio);
+			m_audioPts = PTS_START_OFFSET + pts;
+			m_playMode = pmAudioOnly;
+		}
+		else
+		{
+			m_audioPts = m_videoPts + PtsDiff(m_videoPts & MAX33BIT, pts);
+			m_playMode = pmAudioVideo;
+		}
+	}
+
+	if (pts)
+	{
+		int64_t ptsDiff = PtsDiff(m_audioPts & MAX33BIT, pts);
+		m_audioPts += ptsDiff;
+
+		// keep track of direction in case of trick speed
+		if (m_trickRequest && ptsDiff)
+			PtsTracker(ptsDiff);
+	}
+
+	int length = Length - PesPayloadOffset(Data);
+
+	// ignore packets with invalid payload offset
+	if (length > 0)
+	{
+		const uchar *data = Data + PesPayloadOffset(Data);
+
+		// remove audio substream header as seen in PES recordings with AC3
+		// audio track (0x80: AC3, 0x88: DTS, 0xA0: LPCM)
+		if ((data[0] == 0x80 || data[0] == 0x88 || data[0] == 0xa0)
+				&& data[0] == Id)
+		{
+			data += 4;
+			length -= 4;
+		}
+		if (!m_audio->WriteData(data, length, pts ? m_audioPts : 0))
+			ret = 0;
+	}
+	m_mutex->Unlock();
+
+	if (Transferring() && !ret)
+		DBG("failed to write %d bytes of audio packet!", Length);
+
+	if (ret && Transferring())
+		AdjustLiveSpeed();
+
+	return ret;
+}
+
+int cOmxDevice::PlayVideo(const uchar *Data, int Length, bool EndOfFrame)
+{
+	// prevent writing incomplete frames
+	if (m_hasVideo && !m_omx->PollVideo())
+		return 0;
+
+	m_mutex->Lock();
+	int ret = Length;
+	int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0;
+
+	if (!m_hasVideo && pts && m_videoCodec == cVideoCodec::eInvalid)
+	{
+		m_videoCodec = ParseVideoCodec(Data + PesPayloadOffset(Data),
+				Length - PesPayloadOffset(Data));
+
+		if (m_videoCodec != cVideoCodec::eInvalid)
+		{
+			if (cRpiSetup::IsVideoCodecSupported(m_videoCodec))
+			{
+				m_omx->SetVideoCodec(m_videoCodec);
+				DLOG("set video codec to %s", cVideoCodec::Str(m_videoCodec));
+			}
+			else
+				Skins.QueueMessage(mtError, tr("video format not supported!"));
+		}
+	}
+
+	if (!m_hasVideo && pts && cRpiSetup::IsVideoCodecSupported(m_videoCodec))
+	{
+		m_hasVideo = true;
+		if (!m_hasAudio)
+		{
+			DBG("video first");
+			m_omx->SetClockReference(cOmx::eClockRefVideo);
+			m_omx->SetClockScale(s_playbackSpeeds[m_direction][m_playbackSpeed]);
+			m_omx->StartClock(m_hasVideo, m_hasAudio);
+			m_videoPts = PTS_START_OFFSET + pts;
+			m_playMode = pmVideoOnly;
+		}
+		else
+		{
+			m_videoPts = m_audioPts + PtsDiff(m_audioPts & MAX33BIT, pts);
+			m_playMode = pmAudioVideo;
+		}
+	}
+
+	if (m_hasVideo)
+	{
+		if (pts)
+		{
+			int64_t ptsDiff = PtsDiff(m_videoPts & MAX33BIT, pts);
+			m_videoPts += ptsDiff;
+
+			// keep track of direction in case of trick speed
+			if (m_trickRequest && ptsDiff)
+				PtsTracker(ptsDiff);
+		}
+
+		// skip PES header, proceed with payload towards OMX
+		Length -= PesPayloadOffset(Data);
+		Data += PesPayloadOffset(Data);
+
+		while (Length > 0)
+		{
+			if (OMX_BUFFERHEADERTYPE *buf =
+					m_omx->GetVideoBuffer(pts ? m_videoPts : 0))
+			{
+				buf->nFilledLen = buf->nAllocLen < (unsigned)Length ?
+						buf->nAllocLen : Length;
+
+				memcpy(buf->pBuffer, Data, buf->nFilledLen);
+				Length -= buf->nFilledLen;
+				Data += buf->nFilledLen;
+
+				if (EndOfFrame && !Length)
+					buf->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME;
+
+				if (!m_omx->EmptyVideoBuffer(buf))
+				{
+					ret = 0;
+					ELOG("failed to pass buffer to video decoder!");
+					break;
+				}
+			}
+			else
+			{
+				ret = 0;
+				break;
+			}
+			pts = 0;
+		}
+	}
+	m_mutex->Unlock();
+
+	if (Transferring() && !ret)
+		DBG("failed to write %d bytes of video packet!", Length);
+
+	if (ret && Transferring())
+		AdjustLiveSpeed();
+
+	return ret;
+}
+
+bool cOmxDevice::SubmitEOS(void)
+{
+	DBG("SubmitEOS()");
+	OMX_BUFFERHEADERTYPE *buf = m_omx->GetVideoBuffer(0);
+	if (buf)
+		buf->nFlags = /*OMX_BUFFERFLAG_ENDOFFRAME | */ OMX_BUFFERFLAG_EOS;
+
+	return m_omx->EmptyVideoBuffer(buf);
+}
+
+int64_t cOmxDevice::GetSTC(void)
+{
+	int64_t stc = m_omx->GetSTC();
+	if (stc)
+		m_lastStc = stc;
+	return m_lastStc & MAX33BIT;
+}
+
+uchar *cOmxDevice::GrabImage(int &Size, bool Jpeg, int Quality,
+		int SizeX, int SizeY)
+{
+	DBG("GrabImage(%s, %dx%d)", Jpeg ? "JPEG" : "PNM", SizeX, SizeY);
+
+	uint8_t* ret = NULL;
+	int width, height;
+	cRpiDisplay::GetSize(width, height);
+
+	SizeX = (SizeX > 0) ? SizeX : width;
+	SizeY = (SizeY > 0) ? SizeY : height;
+	Quality = (Quality >= 0) ? Quality : 100;
+
+	// bigger than needed, but uint32_t ensures proper alignment
+	uint8_t* frame = (uint8_t*)(MALLOC(uint32_t, SizeX * SizeY));
+
+	if (!frame)
+	{
+		ELOG("failed to allocate image buffer!");
+		return ret;
+	}
+
+	if (cRpiDisplay::Snapshot(frame, SizeX, SizeY))
+	{
+		ELOG("failed to grab image!");
+		free(frame);
+		return ret;
+	}
+
+	if (Jpeg)
+		ret = RgbToJpeg(frame, SizeX, SizeY, Size, Quality);
+	else
+	{
+		char buf[32];
+		snprintf(buf, sizeof(buf), "P6\n%d\n%d\n255\n", SizeX, SizeY);
+		int l = strlen(buf);
+		Size = l + SizeX * SizeY * 3;
+		ret = MALLOC(uint8_t, Size);
+		if (ret)
+		{
+			memcpy(ret, buf, l);
+			memcpy(ret + l, frame, SizeX * SizeY * 3);
+		}
+	}
+	free(frame);
+	return ret;
+}
+
+void cOmxDevice::Clear(void)
+{
+	DBG("Clear()");
+	m_mutex->Lock();
+
+	FlushStreams();
+	m_hasAudio = false;
+	m_hasVideo = false;
+
+	m_mutex->Unlock();
+	cDevice::Clear();
+}
+
+void cOmxDevice::Play(void)
+{
+	DBG("Play()");
+	m_mutex->Lock();
+
+	m_playbackSpeed = eNormal;
+	m_direction = eForward;
+	m_omx->SetClockScale(s_playbackSpeeds[m_direction][m_playbackSpeed]);
+
+	m_mutex->Unlock();
+	cDevice::Play();
+}
+
+void cOmxDevice::Freeze(void)
+{
+	DBG("Freeze()");
+	m_mutex->Lock();
+
+	m_omx->SetClockScale(s_playbackSpeeds[eForward][ePause]);
+
+	m_mutex->Unlock();
+	cDevice::Freeze();
+}
+
+#if APIVERSNUM >= 20103
+void cOmxDevice::TrickSpeed(int Speed, bool Forward)
+{
+	m_mutex->Lock();
+	ApplyTrickSpeed(Speed, Forward);
+	m_mutex->Unlock();
+}
+#else
+void cOmxDevice::TrickSpeed(int Speed)
+{
+	m_mutex->Lock();
+	m_audioPts = 0;
+	m_videoPts = 0;
+	m_playDirection = 0;
+
+	// play direction is ambiguous for fast modes, start PTS tracking
+	if (Speed == 1 || Speed == 3 || Speed == 6)
+		m_trickRequest = Speed;
+	else
+		ApplyTrickSpeed(Speed, (Speed == 8 || Speed == 4 || Speed == 2));
+
+	m_mutex->Unlock();
+}
+#endif
+
+void cOmxDevice::ApplyTrickSpeed(int trickSpeed, bool forward)
+{
+	m_direction = forward ? eForward : eBackward;
+	m_playbackSpeed =
+
+		// slow forward
+		trickSpeed ==  8 ? eSlowest :
+		trickSpeed ==  4 ? eSlower  :
+		trickSpeed ==  2 ? eSlow    :
+
+		// fast for-/backward
+		trickSpeed ==  6 ? eFast    :
+		trickSpeed ==  3 ? eFaster  :
+		trickSpeed ==  1 ? eFastest :
+
+		// slow backward
+		trickSpeed == 63 ? eSlowest :
+		trickSpeed == 48 ? eSlower  :
+		trickSpeed == 24 ? eSlow    : eNormal;
+
+	m_omx->SetClockScale(s_playbackSpeeds[m_direction][m_playbackSpeed]);
+
+	DBG("ApplyTrickSpeed(%s, %s)",
+			PlaybackSpeedStr(m_playbackSpeed), DirectionStr(m_direction));
+	return;
+}
+
+void cOmxDevice::PtsTracker(int64_t ptsDiff)
+{
+	DBG("PtsTracker(%lld)", ptsDiff);
+
+	if (ptsDiff < 0)
+		--m_playDirection;
+	else if (ptsDiff > 0)
+		m_playDirection += 2;
+
+	if (m_playDirection < -2 || m_playDirection > 3)
+	{
+		ApplyTrickSpeed(m_trickRequest, m_playDirection > 0);
+		m_trickRequest = 0;
+	}
+}
+
+bool cOmxDevice::HasIBPTrickSpeed(void)
+{
+	return !m_hasVideo;
+}
+
+void cOmxDevice::AdjustLiveSpeed(void)
+{
+	if (m_timer->TimedOut())
+	{
+		int usedBuffers, usedAudioBuffers, usedVideoBuffers;
+		m_omx->GetBufferUsage(usedAudioBuffers, usedVideoBuffers);
+		usedBuffers = m_hasAudio ? usedAudioBuffers : usedVideoBuffers;
+
+		if (usedBuffers < 5)
+			m_liveSpeed = eNegCorrection;
+
+		else if (usedBuffers > 15)
+			m_liveSpeed = ePosCorrection;
+
+		else if ((usedBuffers > 10 && m_liveSpeed == eNegCorrection) ||
+				(usedBuffers < 10 && m_liveSpeed == ePosCorrection))
+			m_liveSpeed = eNoCorrection;
+
+#ifdef DEBUG_BUFFERSTAT
+		DLOG("buffer usage: A=%3d%%, V=%3d%%, Corr=%d",
+				usedAudioBuffers, usedVideoBuffers,
+				m_liveSpeed == eNegMaxCorrection ? -2 :
+				m_liveSpeed == eNegCorrection    ? -1 :
+				m_liveSpeed == eNoCorrection     ?  0 :
+				m_liveSpeed == ePosCorrection    ?  1 :
+				m_liveSpeed == ePosMaxCorrection ?  2 : 0);
+#endif
+		m_omx->SetClockScale(s_liveSpeeds[m_liveSpeed]);
+		m_timer->Set(1000);
+	}
+}
+
+void cOmxDevice::HandleBufferStall()
+{
+	ELOG("buffer stall!");
+	m_mutex->Lock();
+
+	FlushStreams();
+	m_hasAudio = false;
+	m_hasVideo = false;
+
+	m_mutex->Unlock();
+}
+
+void cOmxDevice::HandleEndOfStream()
+{
+	DBG("HandleEndOfStream()");
+	m_mutex->Lock();
+
+	// flush pipes and restart clock after still image
+	FlushStreams();
+	m_omx->SetClockScale(s_playbackSpeeds[m_direction][m_playbackSpeed]);
+	m_omx->StartClock(m_hasVideo, m_hasAudio);
+
+	m_mutex->Unlock();
+}
+
+void cOmxDevice::HandleStreamStart()
+{
+	DBG("HandleStreamStart()");
+
+	int width, height, frameRate;
+	bool interlaced;
+
+	m_omx->GetVideoFormat(width, height, frameRate, interlaced);
+
+	DLOG("video stream started %dx%d@%d%s",
+			width, height, frameRate, interlaced ? "i" : "p");
+
+	cRpiDisplay::SetVideoFormat(width, height, frameRate, interlaced);
+}
+
+void cOmxDevice::HandleVideoSetupChanged()
+{
+	DBG("HandleVideoSettingsChanged()");
+
+	switch (cRpiSetup::GetVideoFraming())
+	{
+	default:
+	case cVideoFraming::eFrame:
+		m_omx->SetDisplayMode(false, false);
+		break;
+
+	case cVideoFraming::eCut:
+		m_omx->SetDisplayMode(true, false);
+		break;
+
+	case cVideoFraming::eStretch:
+		m_omx->SetDisplayMode(true, true);
+		break;
+	}
+
+	int width, height, frameRate;
+	bool interlaced;
+
+	m_omx->GetVideoFormat(width, height, frameRate, interlaced);
+	cRpiDisplay::SetVideoFormat(width, height, frameRate, interlaced);
+}
+
+void cOmxDevice::FlushStreams(bool flushVideoRender)
+{
+	DBG("FlushStreams(%s)", flushVideoRender ? "flushVideoRender" : "");
+	m_omx->StopClock();
+
+	if (m_hasVideo)
+		m_omx->FlushVideo(flushVideoRender);
+
+	if (m_hasAudio)
+		m_audio->Reset();
+
+	m_omx->ResetClock();
+}
+
+void cOmxDevice::SetVolumeDevice(int Volume)
+{
+	DBG("SetVolume(%d)", Volume);
+	if (Volume)
+	{
+		m_omx->SetVolume(Volume);
+		m_omx->SetMute(false);
+	}
+	else
+		m_omx->SetMute(true);
+}
+
+bool cOmxDevice::Poll(cPoller &Poller, int TimeoutMs)
+{
+	cTimeMs timer(TimeoutMs);
+	while (!m_omx->PollVideo() || !m_audio->Poll())
+	{
+		if (timer.TimedOut())
+			return false;
+		cCondWait::SleepMs(5);
+	}
+	return true;
+}
+
+void cOmxDevice::MakePrimaryDevice(bool On)
+{
+	if (On && m_onPrimaryDevice)
+		m_onPrimaryDevice();
+	cDevice::MakePrimaryDevice(On);
+}
+
+cVideoCodec::eCodec cOmxDevice::ParseVideoCodec(const uchar *data, int length)
+{
+	const uchar *p = data;
+
+	for (int i = 0; (i < 5) && (i + 4 < length); i++)
+	{
+		// find start code prefix - should be right at the beginning of payload
+		if ((!p[i] && !p[i + 1] && p[i + 2] == 0x01))
+		{
+			if (p[i + 3] == 0xb3)		// sequence header
+				return cVideoCodec::eMPEG2;
+
+			//p[i + 3] = 0xf0
+			else if (p[i + 3] == 0x09)	// slice
+			{
+				// quick hack for converted mkvs
+				if (p[i + 4] == 0xf0)
+					return cVideoCodec::eH264;
+
+				switch (p[i + 4] >> 5)
+				{
+				case 0: case 3: case 5: // I frame
+					return cVideoCodec::eH264;
+
+				case 2: case 7:			// B frame
+				case 1: case 4: case 6:	// P frame
+				default:
+//					return cVideoCodec::eInvalid;
+					return cVideoCodec::eH264;
+				}
+			}
+			return cVideoCodec::eInvalid;
+		}
+	}
+	return cVideoCodec::eInvalid;
+}
diff --git a/omxdevice.h b/omxdevice.h
new file mode 100644
index 0000000..e57137c
--- /dev/null
+++ b/omxdevice.h
@@ -0,0 +1,186 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef OMX_DEVICE_H
+#define OMX_DEVICE_H
+
+#include <vdr/device.h>
+
+#include "tools.h"
+
+class cOmx;
+class cRpiAudioDecoder;
+class cMutex;
+
+class cOmxDevice : cDevice
+{
+
+public:
+
+	cOmxDevice(void (*onPrimaryDevice)(void));
+	virtual ~cOmxDevice();
+
+	virtual int Init(void);
+	virtual int DeInit(void);
+
+	virtual bool Start(void);
+
+	virtual bool HasDecoder(void) const { return true; }
+	virtual bool CanReplay(void)  const { return true; }
+	virtual bool HasIBPTrickSpeed(void);
+
+	virtual void GetOsdSize(int &Width, int &Height, double &PixelAspect);
+	virtual void GetVideoSize(int &Width, int &Height, double &VideoAspect);
+
+	virtual cRect CanScaleVideo(const cRect &Rect, int Alignment = taCenter)
+		{ return Rect; }
+	virtual void ScaleVideo(const cRect &Rect = cRect::Null);
+
+	virtual bool SetPlayMode(ePlayMode PlayMode);
+
+	virtual void StillPicture(const uchar *Data, int Length);
+
+	virtual int PlayAudio(const uchar *Data, int Length, uchar Id);
+	virtual int PlayVideo(const uchar *Data, int Length)
+		{ return PlayVideo(Data, Length, false); }
+
+	virtual int PlayVideo(const uchar *Data, int Length, bool EndOfFrame);
+
+	virtual int64_t GetSTC(void);
+
+	virtual uchar *GrabImage(int &Size, bool Jpeg = true, int Quality = -1,
+			int SizeX = -1, int SizeY = -1);
+
+#if APIVERSNUM >= 20103
+	virtual void TrickSpeed(int Speed, bool Forward);
+#else
+	virtual void TrickSpeed(int Speed);
+#endif
+
+	virtual void Clear(void);
+	virtual void Play(void);
+	virtual void Freeze(void);
+
+	virtual void SetVolumeDevice(int Volume);
+
+	virtual bool Poll(cPoller &Poller, int TimeoutMs = 0);
+
+protected:
+
+	virtual void MakePrimaryDevice(bool On);
+
+	enum eDirection {
+		eForward,
+		eBackward,
+		eNumDirections
+	};
+
+	static const char* DirectionStr(eDirection dir) {
+		return 	dir == eForward ? "forward" :
+				dir == eBackward ? "backward" : "unknown";
+	}
+
+	enum ePlaybackSpeed {
+		ePause,
+		eSlowest,
+		eSlower,
+		eSlow,
+		eNormal,
+		eFast,
+		eFaster,
+		eFastest,
+		eNumPlaybackSpeeds
+	};
+
+	static const char* PlaybackSpeedStr(ePlaybackSpeed speed) {
+		return 	speed == ePause   ? "pause"   :
+				speed == eSlowest ? "slowest" :
+				speed == eSlower  ? "slower"  :
+				speed == eSlow    ? "slow"    :
+				speed == eNormal  ? "normal"  :
+				speed == eFast    ? "fast"    :
+				speed == eFaster  ? "faster"  :
+				speed == eFastest ? "fastest" : "unknown";
+	}
+
+	enum eLiveSpeed {
+		eNegMaxCorrection,
+		eNegCorrection,
+		eNoCorrection,
+		ePosCorrection,
+		ePosMaxCorrection,
+		eNumLiveSpeeds
+	};
+
+	static const char* LiveSpeedStr(eLiveSpeed corr) {
+		return	corr == eNegMaxCorrection ? "max negative" :
+				corr == eNegCorrection    ? "negative"     :
+				corr == eNoCorrection     ? "no"           :
+				corr == ePosCorrection    ? "positive"     :
+				corr == ePosMaxCorrection ? "max positive" : "unknown";
+	}
+
+	static const int s_playbackSpeeds[eNumDirections][eNumPlaybackSpeeds];
+	static const int s_liveSpeeds[eNumLiveSpeeds];
+
+	static const uchar PesVideoHeader[14];
+
+private:
+
+	void (*m_onPrimaryDevice)(void);
+	virtual cVideoCodec::eCodec ParseVideoCodec(const uchar *data, int length);
+
+	static void OnBufferStall(void *data)
+		{ (static_cast <cOmxDevice*> (data))->HandleBufferStall(); }
+
+	static void OnEndOfStream(void *data)
+		{ (static_cast <cOmxDevice*> (data))->HandleEndOfStream(); }
+
+	static void OnStreamStart(void *data)
+		{ (static_cast <cOmxDevice*> (data))->HandleStreamStart(); }
+
+	static void OnVideoSetupChanged(void *data)
+		{ (static_cast <cOmxDevice*> (data))->HandleVideoSetupChanged(); }
+
+	void HandleBufferStall();
+	void HandleEndOfStream();
+	void HandleStreamStart();
+	void HandleVideoSetupChanged();
+
+	void FlushStreams(bool flushVideoRender = false);
+	bool SubmitEOS(void);
+
+	void ApplyTrickSpeed(int trickSpeed, bool forward);
+	void PtsTracker(int64_t ptsDiff);
+
+	void AdjustLiveSpeed(void);
+
+	cOmx			 *m_omx;
+	cRpiAudioDecoder *m_audio;
+	cMutex			 *m_mutex;
+	cTimeMs 		 *m_timer;
+
+	cVideoCodec::eCodec	m_videoCodec;
+
+	ePlayMode           m_playMode;
+	eLiveSpeed          m_liveSpeed;
+	ePlaybackSpeed      m_playbackSpeed;
+	eDirection          m_direction;
+
+	bool	m_hasVideo;
+	bool	m_hasAudio;
+
+	bool	m_skipAudio;
+	int		m_playDirection;
+	int		m_trickRequest;
+
+	uint64_t	m_audioPts;
+	uint64_t	m_videoPts;
+
+	int64_t 	m_lastStc;
+};
+
+#endif
diff --git a/ovgosd.c b/ovgosd.c
new file mode 100644
index 0000000..c9963e5
--- /dev/null
+++ b/ovgosd.c
@@ -0,0 +1,2736 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <vector>
+#include <queue>
+#include <algorithm>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include <VG/openvg.h>
+#include <VG/vgu.h>
+#include <EGL/egl.h>
+#include <GLES/gl.h>
+
+#include "ovgosd.h"
+#include "display.h"
+#include "omxdevice.h"
+#include "setup.h"
+#include "tools.h"
+
+/* ------------------------------------------------------------------------- */
+
+// glyphs containing kerning cache, based on VDR's implementation
+
+class cOvgGlyph : public cListObject
+{
+public:
+
+	cOvgGlyph(uint charCode, VGfloat advanceX, VGfloat advanceY) :
+		m_charCode(charCode), m_advanceX(advanceX), m_advanceY(advanceY) { }
+
+	virtual ~cOvgGlyph() { }
+
+	uint    CharCode(void) { return m_charCode; }
+	VGfloat AdvanceX(void) { return m_advanceX; }
+	VGfloat AdvanceY(void) { return m_advanceY; }
+
+	bool GetKerningCache(uint prevSym, VGfloat &kerning)
+	{
+		for (int i = m_kerningCache.Size(); --i > 0; )
+			if (m_kerningCache[i].m_prevSym == prevSym)
+			{
+				kerning = m_kerningCache[i].m_kerning;
+				return true;
+			}
+
+		return false;
+	}
+
+	void SetKerningCache(uint prevSym, VGfloat kerning)
+	{
+		m_kerningCache.Append(tKerning(prevSym, kerning));
+	}
+
+private:
+
+	struct tKerning
+	{
+	public:
+
+		tKerning(uint prevSym, VGfloat kerning = 0.0f)
+		{
+			m_prevSym = prevSym;
+			m_kerning = kerning;
+		}
+
+		uint m_prevSym;
+		VGfloat m_kerning;
+	};
+
+	uint m_charCode;
+
+	VGfloat m_advanceX;
+	VGfloat m_advanceY;
+
+	cVector<tKerning> m_kerningCache;
+};
+
+/* ------------------------------------------------------------------------- */
+
+#define CHAR_HEIGHT (1 << 14)
+
+class cOvgFont : public cListObject
+{
+public:
+
+	static cOvgFont *Get(const char *name)
+	{
+		if (!s_fonts)
+			Init();
+
+		cOvgFont *font;
+		for (font = s_fonts->First(); font; font = s_fonts->Next(font))
+			if (!strcmp(font->Name(), name))
+				return font;
+
+		font = 0;
+		bool retry = true;
+		while (!font)
+		{
+			font = new cOvgFont(s_ftLib, name);
+			if (vgGetError() == VG_OUT_OF_MEMORY_ERROR)
+			{
+				delete font;
+				font = 0;
+				s_fonts->Clear();
+				if (!retry)
+				{
+					ELOG("[OpenVG] out of memory - failed to load font!");
+					font = new cOvgFont();
+					break;
+				}
+				retry = false;
+			}
+		}
+		s_fonts->Add(font);
+		return font;
+	}
+
+	static void CleanUp(void)
+	{
+		delete s_fonts;
+		s_fonts = 0;
+
+		if (FT_Done_FreeType(s_ftLib))
+			ELOG("failed to deinitialize FreeType library!");
+	}
+
+	cOvgGlyph* Glyph(uint charCode) const
+	{
+		cOvgGlyph *glyph = 0;
+		for (glyph = m_glyphs.First(); glyph; glyph = m_glyphs.Next(glyph))
+			if (glyph->CharCode() == charCode)
+				return glyph;
+
+		glyph = ConvertChar(charCode);
+		if (glyph)
+			m_glyphs.Add(glyph);
+
+		return glyph;
+	}
+
+	VGfloat Kerning(cOvgGlyph *glyph, uint prevSym) const
+	{
+		VGfloat kerning = 0.0f;
+		if (glyph && prevSym)
+		{
+			if (!glyph->GetKerningCache(prevSym, kerning))
+			{
+				FT_Vector delta;
+				FT_UInt cur = FT_Get_Char_Index(m_face,	glyph->CharCode());
+				FT_UInt prev = FT_Get_Char_Index(m_face, prevSym);
+				FT_Get_Kerning(m_face, prev, cur, FT_KERNING_DEFAULT, &delta);
+
+				kerning = (VGfloat)delta.x / CHAR_HEIGHT;
+				glyph->SetKerningCache(prevSym, kerning);
+			}
+		}
+		return kerning;
+	}
+
+	VGfloat     Height(void)    { return  m_height;    }
+	VGfloat     Descender(void) { return  m_descender; }
+	VGFont      Font(void)      { return  m_font;      }
+	const char* Name(void)      { return *m_name;      }
+
+private:
+
+	cOvgFont(void) :
+		m_font(VG_INVALID_HANDLE),
+		m_name(""),
+		m_height(0.0f),
+		m_descender(0.0f),
+		m_face(0)
+	{ }
+
+	cOvgFont(FT_Library lib, const char *name) :
+		m_name(name)
+	{
+		ILOG("loading %s ...", *m_name);
+
+		if (FT_New_Face(lib, name, 0, &m_face))
+			ELOG("failed to open %s!", name);
+
+		m_font = vgCreateFont(m_face->num_glyphs);
+		if (m_font == VG_INVALID_HANDLE)
+		{
+			ELOG("failed to allocate new OVG font!");
+			return;
+		}
+
+		FT_Set_Char_Size(m_face, 0, CHAR_HEIGHT, 0, 0);
+		m_height = (VGfloat)(m_face->size->metrics.height) / CHAR_HEIGHT;
+		m_descender = (VGfloat)(abs(m_face->size->metrics.descender)) /
+				CHAR_HEIGHT;
+#if 0
+		FT_UInt glyphIndex;
+		FT_ULong ch = FT_Get_First_Char(m_face, &glyphIndex);
+
+		while (ch != 0)
+		{
+			if (FT_Load_Glyph(m_face, glyphIndex, FT_LOAD_DEFAULT))
+				break;
+
+			FT_Outline *ot = &m_face->glyph->outline;
+			VGPath path = ConvertOutline(ot);
+
+			VGfloat origin[] = { 0.0f, 0.0f };
+			VGfloat esc[] = {
+					(VGfloat)(m_face->glyph->advance.x) / CHAR_HEIGHT,
+					(VGfloat)(m_face->glyph->advance.y) / CHAR_HEIGHT
+			};
+
+			vgSetGlyphToPath(m_font, ch, path, VG_FALSE, origin, esc);
+
+			m_glyphs.Add(new cOvgGlyph(ch, esc[0], esc[1]));
+
+			if (path != VG_INVALID_HANDLE)
+				vgDestroyPath(path);
+
+			ch = FT_Get_Next_Char(m_face, ch, &glyphIndex);
+		}
+#endif
+	}
+
+	~cOvgFont()
+	{
+		vgDestroyFont(m_font);
+		FT_Done_Face(m_face);
+	}
+
+	static void Init(void)
+	{
+		s_fonts = new cList<cOvgFont>;
+		if (FT_Init_FreeType(&s_ftLib))
+			ELOG("failed to initialize FreeType library!");
+	}
+
+	cOvgGlyph *ConvertChar(uint charCode) const
+	{
+		FT_UInt glyphIndex = FT_Get_Char_Index(m_face, charCode);
+		if (FT_Load_Glyph(m_face, glyphIndex, FT_LOAD_DEFAULT))
+			return 0;
+
+		FT_Outline *ot = &m_face->glyph->outline;
+		VGPath path = ConvertOutline(ot);
+
+		VGfloat origin[] = { 0.0f, 0.0f };
+		VGfloat esc[] = {
+				(VGfloat)(m_face->glyph->advance.x) / CHAR_HEIGHT,
+				(VGfloat)(m_face->glyph->advance.y) / CHAR_HEIGHT
+		};
+
+		vgSetGlyphToPath(m_font, charCode, path, VG_FALSE, origin, esc);
+		if (path != VG_INVALID_HANDLE)
+			vgDestroyPath(path);
+
+		return new cOvgGlyph(charCode, esc[0], esc[1]);
+	}
+
+	// convert freetype outline to OpenVG path,
+	// based on Raspberry Pi's vgfont library
+
+	VGPath ConvertOutline(FT_Outline *outline) const
+	{
+		if (outline->n_contours == 0)
+			return VG_INVALID_HANDLE;
+
+		std::vector<VGubyte> segments;
+		std::vector<VGshort> coord;
+		segments.reserve(256);
+		coord.reserve(1024);
+
+		FT_Vector *points = outline->points;
+		const char *tags = outline->tags;
+		const short* contour = outline->contours;
+		short nCont = outline->n_contours;
+
+		for (short point = 0; nCont != 0; contour++, nCont--)
+		{
+			short nextContour = *contour + 1;
+			bool firstTag = true;
+			char lastTag = 0;
+			short firstPoint = point;
+
+			for (; point < nextContour; point++)
+			{
+				char tag = tags[point];
+				FT_Vector fpoint = points[point];
+				if (firstTag)
+				{
+					segments.push_back(VG_MOVE_TO);
+					firstTag = false;
+				}
+				else if (tag & 0x1)
+				{
+					if (lastTag & 0x1)
+						segments.push_back(VG_LINE_TO);
+					else if (lastTag & 0x2)
+						segments.push_back(VG_CUBIC_TO);
+					else
+						segments.push_back(VG_QUAD_TO);
+				}
+				else
+				{
+					if (!(tag & 0x2) && !(lastTag & 0x1))
+					{
+						segments.push_back(VG_QUAD_TO);
+						int coord_size = coord.size();
+
+						VGshort x = (coord[coord_size-2] + fpoint.x) >> 1;
+						VGshort y = (coord[coord_size-1] + fpoint.y) >> 1;
+
+						coord.push_back(x);
+						coord.push_back(y);
+					}
+				}
+				lastTag = tag;
+				coord.push_back(fpoint.x);
+				coord.push_back(fpoint.y);
+			}
+			if (!(lastTag & 0x1))
+			{
+				if (lastTag & 0x2)
+					segments.push_back(VG_CUBIC_TO);
+				else
+					segments.push_back(VG_QUAD_TO);
+
+				coord.push_back(points[firstPoint].x);
+				coord.push_back(points[firstPoint].y);
+			}
+			segments.push_back(VG_CLOSE_PATH);
+		}
+
+		VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD,
+				VG_PATH_DATATYPE_S_16, 1.0f / (VGfloat)CHAR_HEIGHT, 0.0f,
+				segments.size(), coord.size(), VG_PATH_CAPABILITY_APPEND_TO);
+
+		if (path != VG_INVALID_HANDLE)
+			vgAppendPathData(path, segments.size(), &segments[0], &coord[0]);
+
+		return path;
+	}
+
+	VGFont m_font;
+	cString m_name;
+	VGfloat m_height;
+	VGfloat m_descender;
+
+	mutable cList<cOvgGlyph> m_glyphs;
+
+	FT_Face m_face;
+
+	static FT_Library s_ftLib;
+	static cList<cOvgFont> *s_fonts;
+};
+
+FT_Library cOvgFont::s_ftLib = 0;
+cList<cOvgFont> *cOvgFont::s_fonts = 0;
+
+/* ------------------------------------------------------------------------- */
+
+class cOvgString
+{
+public:
+
+	cOvgString(const unsigned int *symbols, cOvgFont *font) :
+		m_width(0.0f), m_height(font->Height()), m_descender(font->Descender()),
+		m_font(font)
+	{
+		uint prevSym = 0;
+		for (int i = 0; symbols[i]; i++)
+			if (cOvgGlyph *g = font->Glyph(symbols[i]))
+			{
+				VGfloat kerning = 0.0f;
+				if (prevSym)
+				{
+					kerning = m_font->Kerning(g, prevSym);
+					m_kerning.push_back(kerning);
+				}
+				m_width += g->AdvanceX() + kerning;
+				m_glyphIds.push_back(symbols[i]);
+				prevSym = symbols[i];
+			}
+	}
+
+	~cOvgString() { }
+
+	      VGFont   Font(void)         { return  m_font->Font();    }
+	      VGint    Length(void)       { return  m_glyphIds.size(); }
+	      VGfloat  Width(void)        { return  m_width;           }
+	      VGfloat  Height(void)       { return  m_height;          }
+	      VGfloat  Descender(void)    { return  m_descender;       }
+	const VGuint  *GlyphIds(void)     { return &m_glyphIds[0];     }
+	const VGfloat *Kerning(void)      { return &m_kerning[0];      }
+
+private:
+
+	std::vector<VGuint> m_glyphIds;
+	std::vector<VGfloat> m_kerning;
+
+	VGfloat m_width;
+	VGfloat m_height;
+	VGfloat m_descender;
+	cOvgFont *m_font;
+};
+
+/* ------------------------------------------------------------------------- */
+
+class cOvgPaintBox
+{
+public:
+
+	static void Draw(VGPath path)
+	{
+		vgDrawPath(path, VG_FILL_PATH);
+	}
+
+	static void Draw(cOvgString *string)
+	{
+		vgDrawGlyphs(string->Font(), string->Length(), string->GlyphIds(),
+				string->Kerning(), NULL, VG_FILL_PATH, VG_TRUE);
+	}
+
+	static VGPath Rect(void)
+	{
+		if (!s_initialized)
+			SetUp();
+		return s_rect;
+	}
+
+	static VGPath Ellipse(int quadrants)
+	{
+		if (!s_initialized)
+			SetUp();
+		return s_ellipse[(quadrants < -4 || quadrants > 8) ? 4 : quadrants + 4];
+	}
+
+	static VGPath Slope(int type)
+	{
+		if (!s_initialized)
+			SetUp();
+		return s_slope[(type < 0 || type > 7) ? 0 : type];
+	}
+
+	static void CleanUp(void)
+	{
+		vgDestroyPaint(s_paint);
+		vgDestroyPath(s_rect);
+
+		for (int i = 0; i < 8; i++)
+			vgDestroyPath(s_slope[i]);
+
+		for (int i = 0; i < 13; i++)
+			vgDestroyPath(s_ellipse[i]);
+
+		s_initialized = false;
+	}
+
+	static void SetColor(tColor color)
+	{
+		if (!s_initialized)
+			SetUp();
+
+		vgSetParameteri(s_paint, VG_PAINT_TYPE,	VG_PAINT_TYPE_COLOR);
+		vgSetColor(s_paint, (color << 8) + (color >> 24));
+		vgSetPaint(s_paint, VG_FILL_PATH);
+	}
+
+	static void SetAlpha(int alpha)
+	{
+		if (!s_initialized)
+			SetUp();
+
+		alpha = constrain(alpha, ALPHA_TRANSPARENT, ALPHA_OPAQUE);
+		VGfloat values[] = {
+				1.0f, 1.0f, 1.0f, alpha / 255.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+
+		vgSetfv(VG_COLOR_TRANSFORM_VALUES, 8, values);
+		vgSeti(VG_COLOR_TRANSFORM, alpha == ALPHA_OPAQUE ? VG_FALSE : VG_TRUE);
+	}
+
+	static void SetPattern(VGImage image = VG_INVALID_HANDLE)
+	{
+		if (!s_initialized)
+			SetUp();
+
+		vgPaintPattern(s_paint, image);
+		if (image == VG_INVALID_HANDLE)
+			return;
+
+		vgSetParameteri(s_paint, VG_PAINT_TYPE, VG_PAINT_TYPE_PATTERN);
+		vgSetParameteri(s_paint, VG_PAINT_PATTERN_TILING_MODE, VG_TILE_REPEAT);
+		vgSetPaint(s_paint, VG_FILL_PATH);
+	}
+
+	static void SetScissoring(int x = 0, int y = 0, int w = 0, int h = 0)
+	{
+		VGint cropArea[4] = { x, y, w, h };
+		vgSetiv(VG_SCISSOR_RECTS, 4, cropArea);
+		vgSeti(VG_SCISSORING, w && h ? VG_TRUE : VG_FALSE);
+	}
+
+private:
+
+	static void SetUp(void)
+	{
+		// paint
+		s_paint = vgCreatePaint();
+		vgSetPaint(s_paint, VG_FILL_PATH);
+
+		// rectangle
+		s_rect = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F,
+				1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
+		vguRect(s_rect, 0.0f, 0.0f, 1.0f, 1.0f);
+
+		// ellipses
+		for (int i = 0; i < 13; i++)
+			s_ellipse[i] = vgCreatePath(VG_PATH_FORMAT_STANDARD,
+				VG_PATH_DATATYPE_F,	1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
+
+		vguArc(s_ellipse[0], 0.0f, 1.0f, 2.0f, 2.0f, 270,  90, VGU_ARC_OPEN);
+		vguArc(s_ellipse[1], 1.0f, 1.0f, 2.0f, 2.0f, 180,  90, VGU_ARC_OPEN);
+		vguArc(s_ellipse[2], 1.0f, 0.0f, 2.0f, 2.0f,  90,  90, VGU_ARC_OPEN);
+		vguArc(s_ellipse[3], 0.0f, 0.0f, 2.0f, 2.0f,   0,  90, VGU_ARC_OPEN);
+
+		// close path via corner opposed of center of arc for inverted arcs
+		VGubyte cornerSeg[] = { VG_LINE_TO_ABS, VG_CLOSE_PATH };
+		VGfloat cornerData[][2] = {
+				{ 1.0f, 0.0f }, { 0.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 1.0f }
+		};
+		for (int i = 0; i < 4; i++)
+			vgAppendPathData(s_ellipse[i], 2, cornerSeg, cornerData[i]);
+
+		vguEllipse(s_ellipse[4], 0.5f, 0.5f, 1.0f, 1.0f);
+
+		vguArc(s_ellipse[5],  0.0f, 0.0f, 2.0f, 2.0f,   0,  90, VGU_ARC_PIE);
+		vguArc(s_ellipse[6],  1.0f, 0.0f, 2.0f, 2.0f,  90,  90, VGU_ARC_PIE);
+		vguArc(s_ellipse[7],  1.0f, 1.0f, 2.0f, 2.0f, 180,  90, VGU_ARC_PIE);
+		vguArc(s_ellipse[8],  0.0f, 1.0f, 2.0f, 2.0f, 270,  90, VGU_ARC_PIE);
+
+		vguArc(s_ellipse[9],  0.0f, 0.5f, 2.0f, 1.0f, 270, 180, VGU_ARC_PIE);
+		vguArc(s_ellipse[10], 0.5f, 0.0f, 1.0f, 2.0f,   0, 180, VGU_ARC_PIE);
+		vguArc(s_ellipse[11], 1.0f, 0.5f, 2.0f, 1.0f,  90, 180, VGU_ARC_PIE);
+		vguArc(s_ellipse[12], 0.5f, 1.0f, 1.0f, 2.0f, 180, 180, VGU_ARC_PIE);
+
+		// slopes
+		VGubyte slopeSeg[] = {
+				VG_MOVE_TO_ABS, VG_LINE_TO_ABS, VG_CUBIC_TO_ABS, VG_CLOSE_PATH
+		};
+		// gradient of the slope: VDR uses 0.5 but 0.6 looks nicer...
+		const VGfloat s = 0.6f;
+		VGfloat slopeData[] = {
+				1.0f, 0.0f, 1.0f, 1.0f, 1.0f - s, 1.0f, s, 0.0f, 0.0f, 0.0f
+		};
+		VGfloat slopeScale[][2] = {
+				{ -1.0f, -1.0f }, { -1.0f,  1.0f }, { 1.0f, -1.0f },
+				{ -1.0f,  1.0f }, {  1.0f, -1.0f }, {-1.0f, -1.0f },
+				{  1.0f,  1.0f }
+		};
+		VGfloat slopeTrans[][2] = {
+				{ -1.0f, -1.0f }, { -1.0f,  0.0f }, {  0.0f, -1.0f },
+				{ -1.0f, -1.0f }, {  0.0f,  0.0f }, { -1.0f,  0.0f },
+				{  0.0f, -1.0f }
+		};
+		VGfloat slopeRot[] = { 0.0f, 0.0f, 0.0f, 90.0f, 90.0f, 90.0f, 90.0f };
+
+		VGfloat backupMatrix[9];
+		vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
+		vgGetMatrix(backupMatrix);
+
+		for (int i = 0; i < 8; i++)
+		{
+			s_slope[i] = vgCreatePath(VG_PATH_FORMAT_STANDARD,
+				VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
+
+			if (!i)
+				// draw the basic form...
+				vgAppendPathData(s_slope[0], 4, slopeSeg, slopeData);
+			else
+			{
+				// .. and translate the variants
+				vgLoadIdentity();
+				vgRotate(slopeRot[i - 1]);
+				vgScale(slopeScale[i - 1][0], slopeScale[i - 1][1]);
+				vgTranslate(slopeTrans[i - 1][0], slopeTrans[i - 1][1]);
+				vgTransformPath(s_slope[i], s_slope[0]);
+			}
+		}
+
+		vgLoadMatrix(backupMatrix);
+		s_initialized = true;
+	}
+
+	cOvgPaintBox();
+	~cOvgPaintBox();
+
+	static VGPaint s_paint;
+	static VGPath  s_rect;
+	static VGPath  s_ellipse[13];
+	static VGPath  s_slope[8];
+
+	static bool s_initialized;
+};
+
+VGPaint cOvgPaintBox::s_paint       =   VG_INVALID_HANDLE;
+VGPath  cOvgPaintBox::s_rect        =   VG_INVALID_HANDLE;
+VGPath  cOvgPaintBox::s_ellipse[13] = { VG_INVALID_HANDLE };
+VGPath  cOvgPaintBox::s_slope[8]    = { VG_INVALID_HANDLE };
+
+bool cOvgPaintBox::s_initialized = false;
+
+/* ------------------------------------------------------------------------- */
+
+class cEgl
+{
+public:
+
+	EGLDisplay display;
+	EGLContext context;
+	EGLConfig  config;
+	EGLint     nConfig;
+	EGLSurface surface;
+	EGLSurface currentSurface;
+	EGL_DISPMANX_WINDOW_T window;
+
+	static const char* errStr(EGLint error)
+	{
+		return 	error == EGL_SUCCESS             ? "success"             :
+				error == EGL_NOT_INITIALIZED     ? "not initialized"     :
+				error == EGL_BAD_ACCESS          ? "bad access"          :
+				error == EGL_BAD_ALLOC           ? "bad alloc"           :
+				error == EGL_BAD_ATTRIBUTE       ? "bad attribute"       :
+				error == EGL_BAD_CONTEXT         ? "bad context"         :
+				error == EGL_BAD_CONFIG          ? "bad config"          :
+				error == EGL_BAD_CURRENT_SURFACE ? "bad current surface" :
+				error == EGL_BAD_DISPLAY         ? "bad display"         :
+				error == EGL_BAD_SURFACE         ? "bad surface"         :
+				error == EGL_BAD_MATCH           ? "bad match"           :
+				error == EGL_BAD_PARAMETER       ? "bad parameter"       :
+				error == EGL_BAD_NATIVE_PIXMAP   ? "bad native pixmap"   :
+				error == EGL_BAD_NATIVE_WINDOW   ? "bad native window"   :
+				error == EGL_CONTEXT_LOST        ? "context lost"        :
+						"unknown error";
+	}
+};
+
+/* ------------------------------------------------------------------------- */
+
+struct tOvgImageRef
+{
+	VGImage image;
+	bool used;
+};
+
+/* ------------------------------------------------------------------------- */
+
+class cOvgSavedRegion
+{
+public:
+
+	cOvgSavedRegion() : image(VG_INVALID_HANDLE), rect(cRect()) { }
+	VGImage image;
+	cRect rect;
+};
+
+/* ------------------------------------------------------------------------- */
+
+class cOvgRenderTarget
+{
+public:
+
+	cOvgRenderTarget(int _width = 0, int _height = 0) :
+		surface(EGL_NO_SURFACE),
+		image(VG_INVALID_HANDLE),
+		width(_width),
+		height(_height),
+		initialized(false) { }
+
+	virtual ~cOvgRenderTarget() { }
+
+	static bool MakeDefault(cEgl *egl)
+	{
+		if (eglMakeCurrent(egl->display, egl->surface, egl->surface,
+				egl->context) == EGL_FALSE)
+		{
+			ELOG("[EGL] failed to connect context to surface: %s!",
+					cEgl::errStr(eglGetError()));
+			return false;
+		}
+		egl->currentSurface = egl->surface;
+		return true;
+	}
+
+	virtual bool MakeCurrent(cEgl *egl)
+	{
+		// if this is a window surface, check for an update after OSD reset
+		if (image == VG_INVALID_HANDLE && surface != egl->surface)
+		{
+			surface = egl->surface;
+			width = egl->window.width;
+			height = egl->window.height;
+		}
+
+		if (egl->currentSurface == surface)
+			return true;
+
+		if (eglMakeCurrent(egl->display, surface, surface, egl->context) ==
+				EGL_FALSE)
+		{
+			ELOG("[EGL] failed to connect context to surface: %s!",
+					cEgl::errStr(eglGetError()));
+			return false;
+		}
+		egl->currentSurface = surface;
+		return true;
+	}
+
+	EGLSurface surface;
+	VGImage    image;
+	int        width;
+	int        height;
+	bool       initialized;
+
+private:
+
+	cOvgRenderTarget(const cOvgRenderTarget&);
+	cOvgRenderTarget& operator= (const cOvgRenderTarget&);
+};
+
+/* ------------------------------------------------------------------------- */
+
+class cOvgCmd
+{
+public:
+
+	cOvgCmd(cOvgRenderTarget *target) : m_target(target) { }
+	virtual ~cOvgCmd() { }
+
+	virtual bool Execute(cEgl *egl) = 0;
+	virtual const char* Description(void) = 0;
+
+protected:
+
+	cOvgRenderTarget *m_target;
+
+private:
+
+	cOvgCmd(const cOvgCmd&);
+	cOvgCmd& operator= (const cOvgCmd&);
+};
+
+class cOvgCmdFlush : public cOvgCmd
+{
+public:
+
+	cOvgCmdFlush(cOvgRenderTarget *target) :
+		cOvgCmd(target) { }
+
+	virtual const char* Description(void) { return "Flush"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		eglSwapBuffers(egl->display, m_target->surface);
+		return true;
+	}
+};
+
+class cOvgCmdReset : public cOvgCmd
+{
+public:
+
+	cOvgCmdReset(bool cleanup = false) :
+		cOvgCmd(0), m_cleanup(cleanup) { }
+
+	virtual const char* Description(void) { return "Reset"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (m_cleanup)
+		{
+			cOvgFont::CleanUp();
+			cOvgPaintBox::CleanUp();
+		}
+		return false;
+	}
+
+private:
+
+	bool m_cleanup;
+};
+
+class cOvgCmdCreatePixelBuffer : public cOvgCmd
+{
+public:
+
+	cOvgCmdCreatePixelBuffer(cOvgRenderTarget *target) : cOvgCmd(target) { }
+
+	virtual const char* Description(void) { return "CreatePixelBuffer"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		m_target->image = vgCreateImage(VG_sARGB_8888, m_target->width,
+				m_target->height, VG_IMAGE_QUALITY_BETTER);
+
+		if (m_target->image == VG_INVALID_HANDLE)
+			ELOG("[OpenVG] failed to allocate %dpx x %dpx pixel buffer!",
+					m_target->width, m_target->height);
+		else
+		{
+			m_target->surface = eglCreatePbufferFromClientBuffer(egl->display,
+					EGL_OPENVG_IMAGE, (EGLClientBuffer)m_target->image,
+					egl->config, NULL);
+
+			if (m_target->surface == EGL_NO_SURFACE)
+			{
+				ELOG("[EGL] failed to create pixel buffer surface: %s!",
+						cEgl::errStr(eglGetError()));
+				vgDestroyImage(m_target->image);
+				m_target->image = VG_INVALID_HANDLE;
+			}
+			else
+			{
+				if (eglSurfaceAttrib(egl->display, m_target->surface,
+						EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED) == EGL_FALSE)
+				{
+					ELOG("[EGL] failed to set surface attributes!");
+					eglDestroySurface(egl->display, m_target->surface);
+					vgDestroyImage(m_target->image);
+					m_target->image = VG_INVALID_HANDLE;
+				}
+			}
+		}
+		m_target->initialized = true;
+		return true;
+	}
+};
+
+class cOvgCmdDestroySurface : public cOvgCmd
+{
+public:
+
+	cOvgCmdDestroySurface(cOvgRenderTarget *target) : cOvgCmd(target) { }
+
+	virtual const char* Description(void) { return "DestroySurface"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!cOvgRenderTarget::MakeDefault(egl))
+			return false;
+
+		// only destroy pixel buffer surfaces
+		if (m_target->image != VG_INVALID_HANDLE)
+		{
+			if (eglDestroySurface(egl->display, m_target->surface) == EGL_FALSE)
+				ELOG("[EGL] failed to destroy surface: %s!",
+						cEgl::errStr(eglGetError()));
+
+			vgDestroyImage(m_target->image);
+		}
+		delete m_target;
+		return true;
+	}
+};
+
+class cOvgCmdClear : public cOvgCmd
+{
+public:
+
+	cOvgCmdClear(cOvgRenderTarget *target, tColor color = clrTransparent) :
+		cOvgCmd(target), m_color(color) { }
+
+	virtual const char* Description(void) { return "Clear"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		VGfloat color[4] = {
+				(m_color >> 16 & 0xff) / 255.0f,
+				(m_color >>  8 & 0xff) / 255.0f,
+				(m_color       & 0xff) / 255.0f,
+				(m_color >> 24 & 0xff) / 255.0f
+		};
+
+	    vgSetfv(VG_CLEAR_COLOR, 4, color);
+	    vgClear(0, 0, m_target->width, m_target->height);
+		return true;
+	}
+
+private:
+
+	tColor m_color;
+};
+
+class cOvgCmdSaveRegion : public cOvgCmd
+{
+public:
+
+	cOvgCmdSaveRegion(cOvgRenderTarget *target, cOvgSavedRegion *savedRegion,
+			int x, int y, int w, int h) : cOvgCmd(target),
+			m_x(x), m_y(y), m_w(w), m_h(h), m_savedRegion(savedRegion)
+	{ }
+
+	virtual const char* Description(void) { return "SaveRegion"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		if (m_savedRegion->image != VG_INVALID_HANDLE)
+			vgDestroyImage(m_savedRegion->image);
+
+		if (m_w && m_h)
+		{
+			m_savedRegion->image = vgCreateImage(VG_sARGB_8888,
+					m_w, m_h, VG_IMAGE_QUALITY_BETTER);
+
+			if (m_savedRegion->image == VG_INVALID_HANDLE)
+			{
+				ELOG("failed to allocate image!");
+				return false;
+			}
+
+			m_savedRegion->rect.Set(m_x, m_y, m_w, m_h);
+			vgGetPixels(m_savedRegion->image, 0, 0,
+					m_x, m_target->height - m_h - m_y - 1, m_w, m_h);
+		}
+		return true;
+	}
+
+private:
+
+	int m_x;
+	int m_y;
+	int m_w;
+	int m_h;
+	cOvgSavedRegion *m_savedRegion;
+};
+
+class cOvgCmdRestoreRegion : public cOvgCmd
+{
+public:
+
+	cOvgCmdRestoreRegion(cOvgRenderTarget *target, cOvgSavedRegion *savedRegion)
+		: cOvgCmd(target), m_savedRegion(savedRegion) { }
+
+	virtual const char* Description(void) { return "RestoreRegion"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		if (m_savedRegion && m_savedRegion->image != VG_INVALID_HANDLE)
+			vgSetPixels(m_savedRegion->rect.X(),
+					m_target->height - m_savedRegion->rect.Bottom() - 1,
+					m_savedRegion->image, 0, 0, m_savedRegion->rect.Width(),
+					m_savedRegion->rect.Height());
+
+		return true;
+	}
+
+private:
+
+	cOvgSavedRegion *m_savedRegion;
+};
+
+class cOvgCmdDropRegion : public cOvgCmd
+{
+public:
+
+	cOvgCmdDropRegion(cOvgSavedRegion *savedRegion) :
+		cOvgCmd(0), m_savedRegion(savedRegion) { }
+
+	virtual const char* Description(void) { return "DropRegion"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (m_savedRegion)
+		{
+			if (m_savedRegion->image != VG_INVALID_HANDLE)
+				vgDestroyImage(m_savedRegion->image);
+
+			delete m_savedRegion;
+		}
+		return true;
+	}
+
+private:
+
+	cOvgSavedRegion *m_savedRegion;
+};
+
+class cOvgCmdDrawPixel : public cOvgCmd
+{
+public:
+
+	cOvgCmdDrawPixel(cOvgRenderTarget *target, int x, int y, tColor color,
+			bool alphablend) :
+		cOvgCmd(target), m_x(x), m_y(y), m_color(color),
+		m_alphablend(alphablend) { }
+
+	virtual const char* Description(void) { return "DrawPixel"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		if (m_alphablend)
+		{
+			tColor dstPixel;
+			vgReadPixels(&dstPixel, 0, VG_sARGB_8888,
+					m_x, m_target->height - 1 - m_y, 1, 1);
+
+			m_color = AlphaBlend(m_color, dstPixel);
+		}
+		vgWritePixels(&m_color, 0, VG_sARGB_8888,
+				m_x, m_target->height - 1 - m_y, 1, 1);
+		return true;
+	}
+
+private:
+
+	int m_x;
+	int m_y;
+	tColor m_color;
+	bool m_alphablend;
+};
+
+class cOvgCmdDrawRectangle : public cOvgCmd
+{
+public:
+
+	cOvgCmdDrawRectangle(cOvgRenderTarget *target,
+			int x, int y, int w, int h, tColor color) :
+		cOvgCmd(target), m_x(x), m_y(y), m_w(w), m_h(h), m_color(color) { }
+
+	virtual const char* Description(void) { return "DrawRectangle"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
+		vgSeti(VG_BLEND_MODE, VG_BLEND_SRC);
+
+		vgLoadIdentity();
+		vgTranslate(m_x, m_target->height - m_h - m_y);
+		vgScale(m_w, m_h);
+
+		cOvgPaintBox::SetColor(m_color);
+		cOvgPaintBox::Draw(cOvgPaintBox::Rect());
+		return true;
+	}
+
+private:
+
+	int m_x;
+	int m_y;
+	int m_w;
+	int m_h;
+	tColor m_color;
+};
+
+class cOvgCmdDrawEllipse : public cOvgCmd
+{
+public:
+
+	cOvgCmdDrawEllipse(cOvgRenderTarget *target,
+			int x, int y, int w, int h, tColor color, int quadrants) :
+		cOvgCmd(target), m_x(x), m_y(y), m_w(w), m_h(h), m_quadrants(quadrants),
+		m_color(color) { }
+
+	virtual const char* Description(void) { return "DrawEllipse"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
+		vgSeti(VG_BLEND_MODE, VG_BLEND_SRC);
+
+		vgLoadIdentity();
+		vgTranslate(m_x, m_target->height - m_h - m_y);
+		vgScale(m_w, m_h);
+
+		cOvgPaintBox::SetColor(m_color);
+		cOvgPaintBox::Draw(cOvgPaintBox::Ellipse(m_quadrants));
+		return true;
+	}
+
+private:
+
+	int m_x;
+	int m_y;
+	int m_w;
+	int m_h;
+	int m_quadrants;
+	tColor m_color;
+};
+
+class cOvgCmdDrawSlope : public cOvgCmd
+{
+public:
+
+	cOvgCmdDrawSlope(cOvgRenderTarget *target,
+			int x, int y, int w, int h, tColor color, int type) :
+		cOvgCmd(target), m_x(x), m_y(y), m_w(w), m_h(h), m_type(type),
+		m_color(color) { }
+
+	virtual const char* Description(void) { return "DrawSlope"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
+		vgSeti(VG_BLEND_MODE, VG_BLEND_SRC);
+
+		vgLoadIdentity();
+		vgTranslate(m_x, m_target->height - m_h - m_y);
+		vgScale(m_w, m_h);
+
+		cOvgPaintBox::SetColor(m_color);
+		cOvgPaintBox::Draw(cOvgPaintBox::Slope(m_type));
+		return true;
+	}
+
+private:
+
+	int m_x;
+	int m_y;
+	int m_w;
+	int m_h;
+	int m_type;
+	tColor m_color;
+};
+
+class cOvgCmdDrawText : public cOvgCmd
+{
+public:
+
+	cOvgCmdDrawText(cOvgRenderTarget *target,
+			int x, int y, unsigned int *symbols, cString *fontName,
+			int fontSize, tColor colorFg,	tColor colorBg, int w, int h,
+			int alignment) :
+		cOvgCmd(target), m_x(x), m_y(y), m_w(w), m_h(h),
+		m_symbols(symbols), m_fontName(fontName), m_fontSize(fontSize),
+		m_colorFg(colorFg), m_colorBg(colorBg),	m_alignment(alignment) { }
+
+	virtual ~cOvgCmdDrawText()
+	{
+		free(m_symbols);
+		delete m_fontName;
+	}
+
+	virtual const char* Description(void) { return "DrawText"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		cOvgFont *font = cOvgFont::Get(*m_fontName);
+		if (!font)
+			return false;
+
+		cOvgString *string = new cOvgString(m_symbols, font);
+
+		VGfloat offsetX = 0;
+		VGfloat offsetY = 0;
+		VGfloat width = string->Width() * (VGfloat)m_fontSize;
+		VGfloat height = string->Height() * (VGfloat)m_fontSize;
+		VGfloat descender = string->Descender() * (VGfloat)m_fontSize;
+
+		if (m_w)
+		{
+			if (m_alignment & taLeft)
+			{
+				if (m_alignment & taBorder)
+					offsetX += max(height / TEXT_ALIGN_BORDER, 1.0f);
+			}
+			else if (m_alignment & taRight)
+			{
+				if (width < m_w)
+					offsetX += m_w - width;
+				if (m_alignment & taBorder)
+					offsetX -= max(height / TEXT_ALIGN_BORDER, 1.0f);
+			}
+			else
+			{
+				if (width < m_w)
+					offsetX += (m_w - width) / 2;
+			}
+		}
+		if (m_h)
+		{
+			if (m_alignment & taTop) { }
+			else if (m_alignment & taBottom)
+			{
+				if (height < m_h)
+					offsetY += m_h - height;
+			}
+			else
+			{
+				if (height < m_h)
+					offsetY += (m_h - height) / 2;
+			}
+		}
+
+		vgSeti(VG_BLEND_MODE, VG_BLEND_SRC);
+		vgSeti(VG_MATRIX_MODE, VG_MATRIX_GLYPH_USER_TO_SURFACE);
+
+		// some magic offset to conform with VDR's text rendering
+		offsetY -= 0.06f * m_fontSize;
+
+		vgLoadIdentity();
+		vgTranslate(m_x + offsetX,
+				m_target->height - m_y - m_fontSize - offsetY + 1);
+		vgScale(m_fontSize, m_fontSize);
+
+		VGfloat origin[2] = { 0.0f, 0.0f };
+		vgSetfv(VG_GLYPH_ORIGIN, 2, origin);
+
+		cOvgPaintBox::SetScissoring(
+				m_w ? m_x : m_x + floor(offsetX),
+				m_h ? m_target->height - m_y - m_h : m_target->height - m_y -
+						m_fontSize - floor(descender) + 1,
+				m_w ? m_w : floor(width) + 1,
+				m_h ? m_h : m_fontSize + floor(descender) - 1);
+
+		if (m_colorBg != clrTransparent)
+		{
+			VGfloat color[4] = {
+					(m_colorBg >> 16 & 0xff) / 255.0f,
+					(m_colorBg >>  8 & 0xff) / 255.0f,
+					(m_colorBg       & 0xff) / 255.0f,
+					(m_colorBg >> 24 & 0xff) / 255.0f
+			};
+		    vgSetfv(VG_CLEAR_COLOR, 4, color);
+		    vgClear(0, 0, m_target->width, m_target->height);
+		}
+
+		if (string->Length())
+		{
+			cOvgPaintBox::SetColor(m_colorFg);
+			cOvgPaintBox::Draw(string);
+		}
+
+		cOvgPaintBox::SetScissoring();
+		delete string;
+		return true;
+	}
+
+private:
+
+	int m_x;
+	int m_y;
+	int m_w;
+	int m_h;
+	unsigned int *m_symbols;
+	cString *m_fontName;
+	int m_fontSize;
+	tColor m_colorFg;
+	tColor m_colorBg;
+	int m_alignment;
+};
+
+class cOvgCmdRenderPixels : public cOvgCmd
+{
+public:
+
+	cOvgCmdRenderPixels(cOvgRenderTarget *target, cOvgRenderTarget *source,
+			int dx, int dy, int sx, int sy, int w, int h, int alpha) :
+		cOvgCmd(target), m_source(source), m_dx(dx), m_dy(dy),
+		m_sx(sx), m_sy(sy), m_w(w), m_h(h), m_alpha(alpha) { }
+
+	virtual const char* Description(void) { return "RenderPixels"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		cOvgPaintBox::SetAlpha(m_alpha);
+		cOvgPaintBox::SetScissoring(m_dx,
+				m_target->height - m_dy - m_h, m_w, m_h);
+
+		vgSeti(VG_BLEND_MODE, VG_BLEND_SRC_OVER);
+		vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
+		vgSeti(VG_IMAGE_MODE, VG_DRAW_IMAGE_NORMAL);
+		vgSeti(VG_IMAGE_QUALITY, VG_IMAGE_QUALITY_BETTER);
+
+		vgLoadIdentity();
+		vgTranslate(m_dx - m_sx,
+				m_target->height - m_source->height - m_dy + m_sy);
+
+		vgDrawImage(m_source->image);
+
+		cOvgPaintBox::SetAlpha(255);
+		cOvgPaintBox::SetScissoring();
+		return true;
+	}
+
+private:
+
+	cOvgRenderTarget *m_source;
+	int m_dx;
+	int m_dy;
+	int m_sx;
+	int m_sy;
+	int m_w;
+	int m_h;
+	int m_alpha;
+};
+
+class cOvgCmdRenderPattern : public cOvgCmd
+{
+public:
+
+	cOvgCmdRenderPattern(cOvgRenderTarget *target, cOvgRenderTarget *source,
+			int dx, int dy, int sx, int sy, int w, int h, int alpha) :
+		cOvgCmd(target), m_source(source), m_dx(dx), m_dy(dy),
+		m_sx(sx), m_sy(sy), m_w(w), m_h(h), m_alpha(alpha) { }
+
+	virtual const char* Description(void) { return "RenderPattern"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		int sx = m_dx - m_sx;
+		int sy = m_target->height - m_dy - m_source->height  + m_sy;
+		int dy = m_target->height - m_dy - m_h;
+
+		while (sx > m_dx)
+			sx -= m_source->width;
+
+		while (sy > dy)
+			sy -= m_source->height;
+
+		int nx = (m_dx + m_w - sx) / m_source->width;
+		if ((m_dx + m_w - sx) % m_source->width) nx++;
+
+		int ny = (dy + m_h - sy) / m_source->height;
+		if ((dy + m_h - sy) % m_source->height) ny++;
+
+		cOvgPaintBox::SetAlpha(m_alpha);
+		cOvgPaintBox::SetScissoring(m_dx, dy, m_w, m_h);
+
+		VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD,
+				VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0,
+				VG_PATH_CAPABILITY_TRANSFORM_TO);
+
+		vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
+		vgLoadIdentity();
+		vgScale(m_source->width * nx, m_source->height * ny);
+		vgTransformPath(path, cOvgPaintBox::Rect());
+
+		cOvgPaintBox::SetPattern(m_source->image);
+
+		vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
+		vgLoadIdentity();
+		vgTranslate(sx, sy);
+
+		cOvgPaintBox::Draw(path);
+
+		cOvgPaintBox::SetAlpha(255);
+		cOvgPaintBox::SetScissoring();
+		cOvgPaintBox::SetPattern();
+		vgDestroyPath(path);
+		return true;
+	}
+
+private:
+
+	cOvgRenderTarget *m_source;
+	int m_dx;
+	int m_dy;
+	int m_sx;
+	int m_sy;
+	int m_w;
+	int m_h;
+	int m_alpha;
+};
+
+class cOvgCmdCopyPixels : public cOvgCmd
+{
+public:
+
+	cOvgCmdCopyPixels(cOvgRenderTarget *target, cOvgRenderTarget *source,
+			int dx, int dy, int sx, int sy, int w, int h) :
+		cOvgCmd(target), m_source(source),
+		m_dx(dx), m_dy(dy), m_sx(sx), m_sy(sy), m_w(w), m_h(h) { }
+
+	virtual const char* Description(void) { return "CopyPixels"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		vgSetPixels(m_dx, m_target->height - m_h - m_dy, m_source->image,
+				m_sx, m_source->height - m_h - m_sy, m_w, m_h);
+		return true;
+	}
+
+private:
+
+	cOvgRenderTarget *m_source;
+	int m_dx;
+	int m_dy;
+	int m_sx;
+	int m_sy;
+	int m_w;
+	int m_h;
+};
+
+class cOvgCmdMovePixels : public cOvgCmd
+{
+public:
+
+	cOvgCmdMovePixels(cOvgRenderTarget *target,
+			int dx, int dy, int sx, int sy, int w, int h) :
+		cOvgCmd(target),
+		m_dx(dx), m_dy(dy), m_sx(sx), m_sy(sy), m_w(w), m_h(h) { }
+
+	virtual const char* Description(void) { return "MovePixels"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		vgCopyPixels(m_dx, m_target->height - m_h - m_dy,
+				m_sx, m_target->height - m_h - m_sy, m_w, m_h);
+		return true;
+	}
+
+private:
+
+	int m_dx;
+	int m_dy;
+	int m_sx;
+	int m_sy;
+	int m_w;
+	int m_h;
+};
+
+class cOvgCmdStoreImage : public cOvgCmd
+{
+public:
+
+	cOvgCmdStoreImage(tOvgImageRef *image, int w, int h, tColor *argb) :
+		cOvgCmd(0), m_image(image), m_w(w), m_h(h), m_argb(argb) { }
+
+	virtual ~cOvgCmdStoreImage()
+	{
+		free(m_argb);
+	}
+
+	virtual const char* Description(void) { return "StoreImage"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		m_image->image = vgCreateImage(VG_sARGB_8888, m_w, m_h,
+				VG_IMAGE_QUALITY_BETTER);
+
+		if (m_image->image != VG_INVALID_HANDLE)
+			vgImageSubData(m_image->image, m_argb, m_w * sizeof(tColor),
+					VG_sARGB_8888, 0, 0, m_w, m_h);
+		else
+		{
+			m_image->used = false;
+			ELOG("[OpenVG] failed to allocate %dpx x %dpx image!", m_w, m_h);
+		}
+		return true;
+	}
+
+private:
+
+	tOvgImageRef *m_image;
+	int m_w;
+	int m_h;
+	tColor *m_argb;
+};
+
+class cOvgCmdDropImage : public cOvgCmd
+{
+public:
+
+	cOvgCmdDropImage(tOvgImageRef *image) :
+		cOvgCmd(0), m_image(image) { }
+
+	virtual const char* Description(void) { return "DropImage"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (m_image->image != VG_INVALID_HANDLE)
+			vgDestroyImage(m_image->image);
+
+		m_image->used = false;
+		return true;
+	}
+
+private:
+
+	tOvgImageRef *m_image;
+};
+
+class cOvgCmdDrawImage : public cOvgCmd
+{
+public:
+
+	cOvgCmdDrawImage(cOvgRenderTarget *target, VGImage *image, int x, int y) :
+		cOvgCmd(target), m_image(image), m_x(x), m_y(y) { }
+
+	virtual const char* Description(void) { return "DrawImage"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		VGint height = vgGetParameteri(*m_image, VG_IMAGE_HEIGHT);
+
+		vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
+		vgSeti(VG_IMAGE_MODE, VG_DRAW_IMAGE_NORMAL);
+		vgSeti(VG_IMAGE_QUALITY, VG_IMAGE_QUALITY_BETTER);
+		vgSeti(VG_BLEND_MODE, VG_BLEND_SRC);
+
+		vgLoadIdentity();
+		vgTranslate(m_x, m_target->height - height - m_y);
+
+		vgDrawImage(*m_image);
+		return true;
+	}
+
+protected:
+
+	VGImage *m_image;
+	int m_x;
+	int m_y;
+};
+
+class cOvgCmdDrawBitmap : public cOvgCmd
+{
+public:
+
+	cOvgCmdDrawBitmap(cOvgRenderTarget *target,
+			int x, int y, int w, int h, tColor *argb,
+			bool overlay = false, double scaleX = 1.0f, double scaleY = 1.0f) :
+		cOvgCmd(target), m_x(x), m_y(y), m_w(w), m_h(h), m_argb(argb),
+		m_overlay(overlay), m_scaleX(scaleX), m_scaleY(scaleY) { }
+
+	virtual ~cOvgCmdDrawBitmap()
+	{
+		free(m_argb);
+	}
+
+	virtual const char* Description(void) { return "DrawBitmap"; }
+
+	virtual bool Execute(cEgl *egl)
+	{
+		int w = min((m_x < 0 ? m_w + m_x : m_w), m_target->width);
+		int h = min((m_y < 0 ? m_h + m_y : m_h), m_target->height);
+
+		int x = m_x < 0 ? 0 : m_x;
+		int y = m_y < 0 ? 0 : m_y;
+
+		w = min(w, m_target->width - x);
+		h = min(h, m_target->height - y);
+
+		if (w <= 0 || h <= 0)
+			return true;
+
+		if (!m_target->MakeCurrent(egl))
+			return false;
+
+		vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
+		vgSeti(VG_IMAGE_MODE, VG_DRAW_IMAGE_NORMAL);
+		vgSeti(VG_IMAGE_QUALITY, VG_IMAGE_QUALITY_BETTER);
+		vgSeti(VG_BLEND_MODE, m_overlay ? VG_BLEND_SRC_OVER : VG_BLEND_SRC);
+
+		vgLoadIdentity();
+		vgScale(1.0f, -1.0f);
+		vgTranslate(x, y - m_target->height);
+		vgScale(m_scaleX, m_scaleY);
+
+		VGImage image = vgCreateImage(VG_sARGB_8888, w, h,
+				VG_IMAGE_QUALITY_BETTER);
+
+		if (image == VG_INVALID_HANDLE)
+		{
+			ELOG("failed to allocate image!");
+			return false;
+		}
+
+		int offsetX = m_x < 0 ? -m_x : 0;
+		int offsetY = m_y < 0 ? -m_y : 0;
+
+		vgImageSubData(image, m_argb + offsetY * m_w + offsetX,
+				m_w * sizeof(tColor), VG_sARGB_8888, 0, 0, w, h);
+		vgDrawImage(image);
+
+		vgDestroyImage(image);
+		return true;
+	}
+
+protected:
+
+	int m_x;
+	int m_y;
+	int m_w;
+	int m_h;
+	tColor *m_argb;
+	bool m_overlay;
+	double m_scaleX;
+	double m_scaleY;
+};
+
+/* ------------------------------------------------------------------------- */
+
+#define OVG_MAX_OSDIMAGES 256
+#define OVG_CMDQUEUE_SIZE 2048
+
+class cOvgThread : public cThread
+{
+public:
+
+	cOvgThread() :
+		cThread("ovgthread"), m_wait(new cCondWait()), m_stalled(false)
+	{
+		for (int i = 0; i < OVG_MAX_OSDIMAGES; i++)
+			m_images[i].used = false;
+
+		Start();
+	}
+
+	virtual ~cOvgThread()
+	{
+		Cancel(-1);
+		DoCmd(new cOvgCmdReset(), true);
+
+		while (Active())
+			cCondWait::SleepMs(50);
+
+		delete m_wait;
+	}
+
+	void DoCmd(cOvgCmd* cmd, bool signal = false)
+	{
+		while (m_stalled)
+			cCondWait::SleepMs(10);
+
+		Lock();
+		m_commands.push(cmd);
+		Unlock();
+
+		if (m_commands.size() > OVG_CMDQUEUE_SIZE)
+		{
+			m_stalled = true;
+			ILOG("[OpenVG] command queue stalled!");
+		}
+
+		if (signal || m_stalled)
+			m_wait->Signal();
+	}
+
+	virtual int StoreImageData(const cImage &image)
+	{
+		if (image.Width() > m_maxImageSize.Width() ||
+				image.Height() > m_maxImageSize.Height())
+		{
+			DLOG("[OpenVG] cannot store image of %dpx x %dpx "
+					"(maximum size is %dpx x %dpx) - falling back to "
+					"cOsdProvider::StoreImageData()",
+					image.Width(), image.Height(),
+					m_maxImageSize.Width(), m_maxImageSize.Height());
+			return 0;
+		}
+
+		int imageHandle = GetFreeImageHandle();
+		if (imageHandle)
+		{
+			tColor *argb = MALLOC(tColor, image.Width() * image.Height());
+			if (!argb)
+			{
+				FreeImageHandle(imageHandle);
+				imageHandle = 0;
+			}
+			else
+			{
+				memcpy(argb, image.Data(),
+						sizeof(tColor) * image.Width() * image.Height());
+
+				tOvgImageRef *imageRef = GetImageRef(imageHandle);
+				DoCmd(new cOvgCmdStoreImage(imageRef,
+						image.Width(), image.Height(), argb), true);
+
+				cTimeMs timer(5000);
+				while (imageRef->used && imageRef->image == VG_INVALID_HANDLE
+						&& !timer.TimedOut())
+					cCondWait::SleepMs(2);
+
+				if (imageRef->image == VG_INVALID_HANDLE)
+				{
+					ELOG("failed to store OSD image! (%s)",	timer.TimedOut() ?
+							"timed out" : "allocation failed");
+					DropImageData(imageHandle);
+					imageHandle = 0;
+				}
+			}
+		}
+		return imageHandle;
+	}
+
+	virtual void DropImageData(int imageHandle)
+	{
+		DoCmd(new cOvgCmdDropImage(GetImageRef(imageHandle)));
+	}
+
+	virtual const cSize &MaxImageSize(void) const
+	{
+		return m_maxImageSize;
+	}
+
+	tOvgImageRef *GetImageRef(int imageHandle)
+	{
+		int i = -imageHandle - 1;
+		if (0 <= i && i < OVG_MAX_OSDIMAGES)
+			return &m_images[i];
+
+		return 0;
+	}
+
+protected:
+
+	virtual int GetFreeImageHandle(void)
+	{
+		Lock();
+		int imageHandle = 0;
+		for (int i = 0; i < OVG_MAX_OSDIMAGES && !imageHandle; i++)
+			if (!m_images[i].used)
+			{
+				m_images[i].used = true;
+				m_images[i].image = VG_INVALID_HANDLE;
+				imageHandle = -i - 1;
+			}
+		Unlock();
+		return imageHandle;
+	}
+
+	virtual void FreeImageHandle(int imageHandle)
+	{
+		int i = -imageHandle - 1;
+		if (i >= 0 && i < OVG_MAX_OSDIMAGES)
+			m_images[i].used = false;
+	}
+
+	virtual void Action(void)
+	{
+		DLOG("cOvgThread() thread started");
+
+		cEgl egl;
+		egl.display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+
+		if (egl.display == EGL_NO_DISPLAY)
+			ELOG("[EGL] failed to get display connection!");
+
+		if (eglInitialize(egl.display, NULL, NULL) == EGL_FALSE)
+			ELOG("[EGL] failed to init display connection!");
+
+		eglBindAPI(EGL_OPENVG_API);
+
+		const EGLint attr[] = {
+			EGL_RED_SIZE, 8,
+			EGL_GREEN_SIZE, 8,
+			EGL_BLUE_SIZE, 8,
+			EGL_ALPHA_SIZE, 8,
+			EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
+			EGL_CONFORMANT, EGL_OPENVG_BIT,
+			EGL_NONE
+		};
+
+		// get an appropriate EGL frame buffer configuration
+		if (eglChooseConfig(egl.display, attr, &egl.config, 1, &egl.nConfig)
+				== EGL_FALSE)
+			ELOG("[EGL] failed to get frame buffer config!");
+
+		// create an EGL rendering context
+		egl.context = eglCreateContext(egl.display, egl.config, NULL, NULL);
+		if (egl.context == EGL_NO_CONTEXT)
+			ELOG("[EGL] failed to create rendering context!");
+
+		while (Running())
+		{
+			DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(
+				cRpiDisplay::GetVideoPort() == cRpiVideoPort::eHDMI ? 0 : 1);
+			DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0);
+
+			cRpiDisplay::GetSize(egl.window.width, egl.window.height);
+
+			VC_RECT_T srcRect = { 0, 0,
+					egl.window.width << 16, egl.window.height << 16 };
+			VC_RECT_T dstRect = { 0, 0, egl.window.width, egl.window.height };
+
+			egl.window.element = vc_dispmanx_element_add(
+					update, display, 2 /*layer*/, &dstRect, 0, &srcRect,
+					DISPMANX_PROTECTION_NONE, 0, 0, (DISPMANX_TRANSFORM_T)0);
+
+			vc_dispmanx_update_submit_sync(update);
+
+			// create background surface
+			const EGLint attr[] = {
+				EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
+				EGL_NONE
+			};
+
+			egl.surface = eglCreateWindowSurface(egl.display, egl.config,
+					&egl.window, attr);
+			if (egl.surface == EGL_NO_SURFACE)
+				ELOG("[EGL] failed to create window surface: %s!",
+						cEgl::errStr(eglGetError()));
+
+			if (eglSurfaceAttrib(egl.display, egl.surface,
+					EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED) == EGL_FALSE)
+				ELOG("[EGL] failed to set surface attributes: %s!",
+						cEgl::errStr(eglGetError()));
+
+			if (eglMakeCurrent(egl.display, egl.surface, egl.surface,
+					egl.context) ==	EGL_FALSE)
+				ELOG("failed to connect context to surface: %s!",
+						cEgl::errStr(eglGetError()));
+
+			egl.currentSurface = egl.surface;
+
+			m_maxImageSize.Set(vgGeti(VG_MAX_IMAGE_WIDTH),
+					vgGeti(VG_MAX_IMAGE_HEIGHT));
+
+			float color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+			vgSetfv(VG_CLEAR_COLOR, 4, color);
+			vgClear(0, 0, egl.window.width, egl.window.height);
+
+			bool reset = false;
+			while (!reset)
+			{
+				if (m_commands.empty())
+					m_wait->Wait(20);
+				else
+				{
+					Lock();
+					cOvgCmd* cmd = m_commands.front();
+					m_commands.pop();
+					Unlock();
+
+					reset = cmd ? !cmd->Execute(&egl) : true;
+
+					VGErrorCode err = vgGetError();
+					if (cmd && err != VG_NO_ERROR)
+						ELOG("[OpenVG] %s error: %s",
+								cmd->Description(), errStr(err));
+
+					//ELOG("[OpenVG] %s", cmd->Description());
+					delete cmd;
+
+					if (m_stalled && m_commands.size() < OVG_CMDQUEUE_SIZE / 2)
+						m_stalled = false;
+				}
+			}
+
+			if (eglDestroySurface(egl.display, egl.surface) == EGL_FALSE)
+				ELOG("[EGL] failed to destroy surface: %s!",
+						cEgl::errStr(eglGetError()));
+
+			vc_dispmanx_element_remove(update, egl.window.element);
+			vc_dispmanx_display_close(display);
+
+			DLOG("cOvgThread() thread reset");
+		}
+
+		for (int i = 0; i < OVG_MAX_OSDIMAGES; i++)
+			if (m_images[i].used)
+				vgDestroyImage(m_images[i].image);
+
+		cOvgFont::CleanUp();
+		cOvgPaintBox::CleanUp();
+		vgFinish();
+
+		eglDestroyContext(egl.display, egl.context);
+		eglTerminate(egl.display);
+
+		DLOG("cOvgThread() thread ended");
+	}
+
+private:
+
+	static const char* errStr(VGErrorCode error)
+	{
+		return
+			error == VG_NO_ERROR                       ? "no error"            :
+			error == VG_BAD_HANDLE_ERROR               ? "bad handle"          :
+			error == VG_ILLEGAL_ARGUMENT_ERROR         ? "illegal argument"    :
+			error == VG_OUT_OF_MEMORY_ERROR            ? "out of memory"       :
+			error == VG_PATH_CAPABILITY_ERROR          ? "path capability"     :
+			error == VG_UNSUPPORTED_IMAGE_FORMAT_ERROR ? "unsup. image format" :
+			error == VG_UNSUPPORTED_PATH_FORMAT_ERROR  ? "unsup. path format"  :
+			error == VG_IMAGE_IN_USE_ERROR             ? "image in use"        :
+			error == VG_NO_CONTEXT_ERROR               ? "no context"          :
+						"unknown error";
+	}
+
+	std::queue<cOvgCmd*> m_commands;
+	cCondWait *m_wait;
+	bool m_stalled;
+
+	tOvgImageRef m_images[OVG_MAX_OSDIMAGES];
+
+	cSize m_maxImageSize;
+};
+
+/* ------------------------------------------------------------------------- */
+
+class cOvgPixmap : public cPixmap
+{
+public:
+
+	cOvgPixmap(int Layer, cOvgThread *ovg, cOvgRenderTarget *buffer,
+			const cRect &ViewPort, const cRect &DrawPort) :
+		cPixmap(Layer, ViewPort, DrawPort),
+		m_ovg(ovg),
+		m_buffer(buffer),
+		m_dirty(false)
+	{ }
+
+	virtual ~cOvgPixmap()
+	{
+		m_ovg->DoCmd(new cOvgCmdDestroySurface(m_buffer));
+	}
+
+	virtual void SetAlpha(int Alpha)
+	{
+		Alpha = constrain(Alpha, ALPHA_TRANSPARENT, ALPHA_OPAQUE);
+		if (Alpha != cPixmap::Alpha())
+		{
+			cPixmap::SetAlpha(Alpha);
+			SetDirty();
+		}
+	}
+
+	virtual void SetTile(bool Tile)
+	{
+		cPixmap::SetTile(Tile);
+		SetDirty();
+	}
+
+	virtual void SetViewPort(const cRect &Rect)
+	{
+		cPixmap::SetViewPort(Rect);
+		SetDirty();
+	}
+
+	virtual void SetDrawPortPoint(const cPoint &Point, bool Dirty = true)
+	{
+		cPixmap::SetDrawPortPoint(Point, Dirty);
+		if (Dirty)
+			SetDirty();
+	}
+
+	virtual void Clear(void)
+	{
+		LOCK_PIXMAPS;
+		m_ovg->DoCmd(new cOvgCmdClear(m_buffer));
+		SetDirty();
+		MarkDrawPortDirty(DrawPort());
+	}
+
+	virtual void Fill(tColor Color)
+	{
+		LOCK_PIXMAPS;
+		m_ovg->DoCmd(new cOvgCmdClear(m_buffer, Color));
+		SetDirty();
+		MarkDrawPortDirty(DrawPort());
+	}
+
+	virtual void DrawImage(const cPoint &Point, const cImage &Image)
+	{
+		LOCK_PIXMAPS;
+		tColor *argb = MALLOC(tColor, Image.Width() * Image.Height());
+		if (!argb)
+			return;
+
+		memcpy(argb, Image.Data(),
+				sizeof(tColor) * Image.Width() * Image.Height());
+
+		m_ovg->DoCmd(new cOvgCmdDrawBitmap(m_buffer, Point.X(), Point.Y(),
+				Image.Width(), Image.Height(), argb, false));
+
+		SetDirty();
+		MarkDrawPortDirty(cRect(Point, cSize(Image.Width(),
+				Image.Height())).Intersected(DrawPort().Size()));
+	}
+
+	virtual void DrawImage(const cPoint &Point, int ImageHandle)
+	{
+		if (ImageHandle < 0 && m_ovg->GetImageRef(ImageHandle))
+			m_ovg->DoCmd(new cOvgCmdDrawImage(m_buffer,
+					&m_ovg->GetImageRef(ImageHandle)->image,
+					Point.X(), Point.Y()));
+		else
+			if (cRpiOsdProvider::GetImageData(ImageHandle))
+				DrawImage(Point, *cRpiOsdProvider::GetImageData(ImageHandle));
+
+		SetDirty();
+	}
+
+	virtual void DrawPixel(const cPoint &Point, tColor Color)
+	{
+		LOCK_PIXMAPS;
+		m_ovg->DoCmd(new cOvgCmdDrawPixel(m_buffer, Point.X(), Point.Y(),
+				Color, Layer() == 0 && !IS_OPAQUE(Color)));
+
+		SetDirty();
+		MarkDrawPortDirty(Point);
+	}
+
+	virtual void DrawBitmap(const cPoint &Point, const cBitmap &Bitmap,
+			tColor ColorFg = 0, tColor ColorBg = 0, bool Overlay = false)
+	{
+		LOCK_PIXMAPS;
+		bool specialColors = ColorFg || ColorBg;
+		tColor *argb = MALLOC(tColor, Bitmap.Width() * Bitmap.Height());
+		if (!argb)
+			return;
+
+		tColor *p = argb;
+		for (int py = 0; py < Bitmap.Height(); py++)
+			for (int px = 0; px < Bitmap.Width(); px++)
+			{
+				tIndex index = *Bitmap.Data(px, py);
+				*p++ = (!index && Overlay) ? clrTransparent : (specialColors ?
+						(index == 0 ? ColorBg :	index == 1 ? ColorFg :
+								Bitmap.Color(index)) : Bitmap.Color(index));
+			}
+
+		m_ovg->DoCmd(new cOvgCmdDrawBitmap(m_buffer, Point.X(), Point.Y(),
+				Bitmap.Width(), Bitmap.Height(), argb, Overlay));
+
+		SetDirty();
+		MarkDrawPortDirty(cRect(Point, cSize(Bitmap.Width(),
+				Bitmap.Height())).Intersected(DrawPort().Size()));
+	}
+
+	virtual void DrawScaledBitmap(const cPoint &Point, const cBitmap &Bitmap,
+			double FactorX, double FactorY, bool AntiAlias = false)
+	{
+		LOCK_PIXMAPS;
+		tColor *argb = MALLOC(tColor, Bitmap.Width() * Bitmap.Height());
+		if (!argb)
+			return;
+
+		tColor *p = argb;
+		for (int py = 0; py < Bitmap.Height(); py++)
+			for (int px = 0; px < Bitmap.Width(); px++)
+				*p++ = Bitmap.Color(*Bitmap.Data(px, py));
+
+		m_ovg->DoCmd(new cOvgCmdDrawBitmap(m_buffer, Point.X(), Point.Y(),
+				Bitmap.Width(), Bitmap.Height(), argb, false,
+				FactorX, FactorY));
+
+		SetDirty();
+		MarkDrawPortDirty(cRect(Point, cSize(
+				int(round(Bitmap.Width() * FactorX)) + 1,
+				int(round(Bitmap.Height() * FactorY)) + 1)).Intersected(
+						DrawPort().Size()));
+	}
+
+	virtual void DrawText(const cPoint &Point, const char *s, tColor ColorFg,
+			tColor ColorBg, const cFont *Font, int Width = 0, int Height = 0,
+			int Alignment = taDefault)
+	{
+		LOCK_PIXMAPS;
+		int len = s ? Utf8StrLen(s) : 0;
+		unsigned int *symbols = MALLOC(unsigned int, len + 1);
+		if (!symbols)
+			return;
+
+		if (len)
+			Utf8ToArray(s, symbols, len + 1);
+		else
+			symbols[0] = 0;
+
+		m_ovg->DoCmd(new cOvgCmdDrawText(m_buffer, Point.X(), Point.Y(),
+				symbols, new cString(Font->FontName()), Font->Size(),
+				ColorFg, ColorBg, Width, Height, Alignment));
+
+		SetDirty();
+		MarkDrawPortDirty(cRect(Point.X(), Point.Y(),
+				Width ? Width : DrawPort().Width() - Point.X(),
+				Height ? Height : DrawPort().Height() - Point.Y()));
+	}
+
+	virtual void DrawRectangle(const cRect &Rect, tColor Color)
+	{
+		LOCK_PIXMAPS;
+		m_ovg->DoCmd(new cOvgCmdDrawRectangle(m_buffer,
+				Rect.X(), Rect.Y(),	Rect.Width(), Rect.Height(), Color));
+
+		SetDirty();
+		MarkDrawPortDirty(Rect);
+	}
+
+	virtual void DrawEllipse(const cRect &Rect, tColor Color, int Quadrants = 0)
+	{
+		LOCK_PIXMAPS;
+		m_ovg->DoCmd(new cOvgCmdDrawEllipse(m_buffer,
+				Rect.X(), Rect.Y(),	Rect.Width(), Rect.Height(),
+				Color, Quadrants));
+
+		SetDirty();
+		MarkDrawPortDirty(Rect);
+	}
+
+	virtual void DrawSlope(const cRect &Rect, tColor Color, int Type)
+	{
+		LOCK_PIXMAPS;
+		m_ovg->DoCmd(new cOvgCmdDrawSlope(m_buffer,
+				Rect.X(), Rect.Y(),	Rect.Width(), Rect.Height(), Color, Type));
+
+		SetDirty();
+		MarkDrawPortDirty(Rect);
+	}
+
+	virtual void Render(const cPixmap *Pixmap, const cRect &Source,
+			const cPoint &Dest)
+	{
+		LOCK_PIXMAPS;
+		if (Pixmap->Alpha() == ALPHA_TRANSPARENT)
+			return;
+
+		if (const cOvgPixmap *pm = dynamic_cast<const cOvgPixmap *>(Pixmap))
+		{
+			m_ovg->DoCmd(new cOvgCmdRenderPixels(m_buffer, pm->m_buffer,
+					Dest.X(), Dest.Y(), Source.X(), Source.Y(),
+					Source.Width(), Source.Height(), pm->Alpha()));
+
+			SetDirty();
+			MarkDrawPortDirty(DrawPort());
+		}
+	}
+
+	virtual void Copy(const cPixmap *Pixmap, const cRect &Source,
+			const cPoint &Dest)
+	{
+		LOCK_PIXMAPS;
+		if (const cOvgPixmap *pm = dynamic_cast<const cOvgPixmap *>(Pixmap))
+		{
+			m_ovg->DoCmd(new cOvgCmdCopyPixels(m_buffer, pm->m_buffer,
+					Dest.X(), Dest.Y(), Source.X(), Source.Y(),
+					Source.Width(), Source.Height()));
+
+			SetDirty();
+			MarkDrawPortDirty(DrawPort());
+		}
+	}
+
+	virtual void Scroll(const cPoint &Dest, const cRect &Source, bool pan)
+	{
+		LOCK_PIXMAPS;
+		cRect s;
+		if (&Source == &cRect::Null)
+			s = DrawPort().Shifted(-DrawPort().Point());
+		else
+			s = Source.Intersected(DrawPort().Size());
+
+		if (Dest != s.Point())
+		{
+			m_ovg->DoCmd(new cOvgCmdMovePixels(m_buffer, Dest.X(), Dest.Y(),
+					s.X(), s.Y(), s.Width(), s.Height()));
+
+			if (pan)
+				SetDrawPortPoint(DrawPort().Point().Shifted(s.Point() -	Dest),
+						false);
+			else
+				MarkDrawPortDirty(Dest);
+			SetDirty();
+		}
+	}
+
+	virtual void Scroll(const cPoint &Dest, const cRect &Source = cRect::Null)
+	{
+		Scroll(Dest, Source, false);
+	}
+
+	virtual void Pan(const cPoint &Dest, const cRect &Source = cRect::Null)
+	{
+		Scroll(Dest, Source, true);
+	}
+
+	virtual void CopyToTarget(cOvgRenderTarget *target, int left, int top)
+	{
+		LOCK_PIXMAPS;
+		cRect d = ViewPort().Shifted(left, top);
+		cPoint s = -DrawPort().Point();
+
+		m_ovg->DoCmd(new cOvgCmdCopyPixels(target, m_buffer,
+				d.X(), d.Y(), s.X(), s.Y(), d.Width(), d.Height()));
+
+		SetDirty(false);
+	}
+
+	virtual void RenderToTarget(cOvgRenderTarget *target, int left, int top)
+	{
+		LOCK_PIXMAPS;
+		cRect d = ViewPort().Shifted(left, top);
+		cPoint s = -DrawPort().Point();
+
+		if (Tile())
+			m_ovg->DoCmd(new cOvgCmdRenderPattern(target, m_buffer,
+					d.X(), d.Y(), s.X(), s.Y(), d.Width(), d.Height(),
+					Alpha()));
+		else
+			m_ovg->DoCmd(new cOvgCmdRenderPixels(target, m_buffer,
+					d.X(), d.Y(), s.X(), s.Y(), d.Width(), d.Height(),
+					Alpha()));
+
+		SetDirty(false);
+	}
+
+	virtual bool IsDirty(void) { return m_dirty; }
+	virtual void SetDirty(bool dirty = true) { m_dirty = dirty; }
+
+private:
+
+	cOvgPixmap(const cOvgPixmap&);
+	cOvgPixmap& operator= (const cOvgPixmap&);
+
+	cOvgThread       *m_ovg;
+	cOvgRenderTarget *m_buffer;
+
+	bool m_dirty;
+};
+
+/* ------------------------------------------------------------------------- */
+
+class cOvgOsd : public cOsd
+{
+public:
+
+	cOvgOsd(int Left, int Top, uint Level, cOvgThread *ovg) :
+		cOsd(Left, Top, Level),
+		m_ovg(ovg),
+		m_surface(new cOvgRenderTarget()),
+		m_savedRegion(new cOvgSavedRegion())
+	{ }
+
+	virtual ~cOvgOsd()
+	{
+		SetActive(false);
+		m_ovg->DoCmd(new cOvgCmdDropRegion(m_savedRegion));
+		m_ovg->DoCmd(new cOvgCmdDestroySurface(m_surface));
+	}
+
+	virtual eOsdError SetAreas(const tArea *Areas, int NumAreas)
+	{
+		cRect r;
+		for (int i = 0; i < NumAreas; i++)
+			r.Combine(cRect(Areas[i].x1, Areas[i].y1,
+					Areas[i].Width(), Areas[i].Height()));
+
+		tArea area = { r.Left(), r.Top(), r.Right(), r.Bottom(), 32 };
+
+		for (int i = 0; i < m_pixmaps.Size(); i++)
+			m_pixmaps[i] = NULL;
+
+		return cOsd::SetAreas(&area, 1);
+	}
+
+	virtual const cSize &MaxPixmapSize(void) const
+	{
+		return m_ovg->MaxImageSize();
+	}
+
+	virtual cPixmap *CreatePixmap(int Layer, const cRect &ViewPort,
+			const cRect &DrawPort = cRect::Null)
+	{
+		LOCK_PIXMAPS;
+		int width = DrawPort.IsEmpty() ? ViewPort.Width() : DrawPort.Width();
+		int height = DrawPort.IsEmpty() ? ViewPort.Height() : DrawPort.Height();
+
+#if APIVERSNUM >= 20301
+		if (width > m_ovg->MaxImageSize().Width() ||
+				height > m_ovg->MaxImageSize().Height())
+		{
+			ELOG("[OpenVG] cannot allocate pixmap of %dpx x %dpx, "
+					"maximum size is %dpx x %dpx!", width, height,
+					m_ovg->MaxImageSize().Width(),
+					m_ovg->MaxImageSize().Height());
+
+			return NULL;
+		}
+#else
+		if (width > m_ovg->MaxImageSize().Width() ||
+				height > m_ovg->MaxImageSize().Height())
+		{
+			ELOG("[OpenVG] cannot allocate pixmap of %dpx x %dpx, "
+					"clipped to %dpx x %dpx!", width, height,
+					min(width, m_ovg->MaxImageSize().Width()),
+					min(height, m_ovg->MaxImageSize().Height()));
+
+			width = min(width, m_ovg->MaxImageSize().Width());
+			height = min(height, m_ovg->MaxImageSize().Height());
+		}
+#endif
+		// create pixel buffer and wait until command has been completed
+		cOvgRenderTarget *buffer = new cOvgRenderTarget(width, height);
+		m_ovg->DoCmd(new cOvgCmdCreatePixelBuffer(buffer), true);
+
+		cTimeMs timer(10000);
+		while (!buffer->initialized && !timer.TimedOut())
+			cCondWait::SleepMs(2);
+
+		if (buffer->initialized && buffer->image != VG_INVALID_HANDLE)
+		{
+			cOvgPixmap *pm = new cOvgPixmap(Layer, m_ovg, buffer,
+					ViewPort, DrawPort);
+
+			if (cOsd::AddPixmap(pm))
+			{
+				for (int i = 0; i < m_pixmaps.Size(); i++)
+					if (!m_pixmaps[i])
+						return m_pixmaps[i] = pm;
+
+				m_pixmaps.Append(pm);
+				return pm;
+			}
+			delete pm;
+		}
+		else
+		{
+			ELOG("[OpenVG] failed to create pixmap! (%s)",
+					timer.TimedOut() ? "timed out" : "allocation failed");
+			m_ovg->DoCmd(new cOvgCmdDestroySurface(buffer));
+		}
+		return NULL;
+	}
+
+	virtual void DestroyPixmap(cPixmap *Pixmap)
+	{
+		if (Pixmap)
+		{
+			LOCK_PIXMAPS;
+			for (int i = 1; i < m_pixmaps.Size(); i++)
+				if (m_pixmaps[i] == Pixmap)
+				{
+					if (Pixmap->Layer() >= 0)
+						m_pixmaps[0]->SetDirty();
+
+					m_pixmaps[i] = NULL;
+					cOsd::DestroyPixmap(Pixmap);
+					return;
+				}
+		}
+	}
+
+	virtual void SaveRegion(int x1, int y1, int x2, int y2)
+	{
+		if (!Active())
+			return;
+
+		m_ovg->DoCmd(new cOvgCmdSaveRegion(m_surface, m_savedRegion,
+				x1 + Left(), y1 + Top(), x2 - x1 + 1, y2 - y1 + 1));
+	}
+
+	virtual void RestoreRegion(void)
+	{
+		if (!Active())
+			return;
+
+		m_ovg->DoCmd(new cOvgCmdRestoreRegion(m_surface, m_savedRegion));
+		m_ovg->DoCmd(new cOvgCmdFlush(m_surface), true);
+	}
+
+	virtual void DrawPixel(int x, int y, tColor Color)
+	{
+		if (!m_pixmaps[0])
+			return;
+
+		m_pixmaps[0]->DrawPixel(
+				cPoint(x, y) - m_pixmaps[0]->ViewPort().Point(), Color);
+	}
+
+	virtual void DrawBitmap(int x, int y, const cBitmap &Bitmap,
+			tColor ColorFg = 0, tColor ColorBg = 0,
+			bool ReplacePalette = false, bool Overlay = false)
+	{
+		if (!m_pixmaps[0])
+			return;
+
+		m_pixmaps[0]->DrawBitmap(
+				cPoint(x, y) - m_pixmaps[0]->ViewPort().Point(),
+				Bitmap, ColorFg, ColorBg, Overlay);
+	}
+
+	virtual void DrawScaledBitmap(int x, int y, const cBitmap &Bitmap,
+			double FactorX, double FactorY, bool AntiAlias = false)
+	{
+		if (!m_pixmaps[0])
+			return;
+
+		m_pixmaps[0]->DrawScaledBitmap(
+				cPoint(x, y) - m_pixmaps[0]->ViewPort().Point(),
+				Bitmap, FactorX, FactorY);
+	}
+
+	virtual void DrawImage(const cPoint &Point, int ImageHandle)
+	{
+		if (!m_pixmaps[0])
+			return;
+
+		m_pixmaps[0]->DrawImage(Point - m_pixmaps[0]->ViewPort().Point(),
+				ImageHandle);
+	}
+
+	virtual void DrawImage(const cPoint &Point, const cImage &Image)
+	{
+		if (!m_pixmaps[0])
+			return;
+
+		m_pixmaps[0]->DrawImage(Point - m_pixmaps[0]->ViewPort().Point(),
+				Image);
+	}
+
+	virtual void DrawRectangle(int x1, int y1, int x2, int y2, tColor Color)
+	{
+		if (!m_pixmaps[0])
+			return;
+
+		m_pixmaps[0]->DrawRectangle(
+				cRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1).Shifted(
+				- m_pixmaps[0]->ViewPort().Point()), Color);
+	}
+
+	virtual void DrawEllipse(int x1, int y1, int x2, int y2, tColor Color,
+			int Quadrants = 0)
+	{
+		if (!m_pixmaps[0])
+			return;
+
+		m_pixmaps[0]->DrawEllipse(
+				cRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1).Shifted(
+				- m_pixmaps[0]->ViewPort().Point()), Color, Quadrants);
+	}
+
+	virtual void DrawSlope(int x1, int y1, int x2, int y2, tColor Color,
+			int Type)
+	{
+		if (!m_pixmaps[0])
+			return;
+
+		m_pixmaps[0]->DrawSlope(
+				cRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1).Shifted(
+				- m_pixmaps[0]->ViewPort().Point()), Color, Type);
+	}
+
+	virtual void DrawText(int x, int y, const char *s, tColor ColorFg,
+			tColor ColorBg, const cFont *Font, int Width = 0, int Height = 0,
+			int Alignment = taDefault)
+	{
+		if (!m_pixmaps[0])
+			return;
+
+		m_pixmaps[0]->DrawText(cPoint(x, y) - m_pixmaps[0]->ViewPort().Point(),
+				s, ColorFg, ColorBg, Font, Width, Height, Alignment);
+	}
+
+	virtual void Flush(void)
+	{
+		if (!Active())
+			return;
+
+		LOCK_PIXMAPS;
+
+//#define USE_VDRS_RENDERING
+#ifdef USE_VDRS_RENDERING
+		while (cOvgPixmap *pm =	dynamic_cast<cOvgPixmap *>(RenderPixmaps()))
+		{
+			pm->CopyToTarget(m_surface, Left(), Top());
+
+#if APIVERSNUM >= 20110
+			DestroyPixmap(pm);
+#else
+			delete pm;
+#endif
+		}
+#else
+		bool dirty = false;
+		for (int i = 0; i < m_pixmaps.Size() && !dirty; i++)
+			if (m_pixmaps[i] &&
+					m_pixmaps[i]->Layer() >= 0 && m_pixmaps[i]->IsDirty())
+				dirty = true;
+
+		if (!dirty)
+			return;
+
+		m_ovg->DoCmd(new cOvgCmdClear(m_surface));
+
+		for (int layer = 0; layer < MAXPIXMAPLAYERS; layer++)
+			for (int i = 0; i < m_pixmaps.Size(); i++)
+				if (m_pixmaps[i])
+					if (m_pixmaps[i]->Layer() == layer)
+						m_pixmaps[i]->RenderToTarget(m_surface, Left(), Top());
+#endif
+		m_ovg->DoCmd(new cOvgCmdFlush(m_surface), true);
+		return;
+	}
+
+protected:
+
+	virtual void SetActive(bool On)
+	{
+		if (On != Active())
+		{
+			cOsd::SetActive(On);
+			if (!On)
+				Clear();
+			else
+				if (GetBitmap(0))
+					Flush();
+		}
+	}
+
+	virtual void Clear(void)
+	{
+		m_ovg->DoCmd(new cOvgCmdClear(m_surface));
+		m_ovg->DoCmd(new cOvgCmdFlush(m_surface));
+	}
+
+private:
+
+	cOvgThread           *m_ovg;
+	cOvgRenderTarget     *m_surface;
+	cVector<cOvgPixmap *> m_pixmaps;
+	cOvgSavedRegion      *m_savedRegion;
+};
+
+/* ------------------------------------------------------------------------- */
+
+class cOvgRawOsd : public cOsd
+{
+public:
+
+	cOvgRawOsd(int Left, int Top, uint Level, cOvgThread *ovg) :
+		cOsd(Left, Top, Level),
+		m_ovg(ovg),
+		m_surface(new cOvgRenderTarget())
+	{ }
+
+	virtual ~cOvgRawOsd()
+	{
+		SetActive(false);
+		m_ovg->DoCmd(new cOvgCmdDestroySurface(m_surface));
+	}
+
+	virtual void Flush(void)
+	{
+		if (!Active())
+			return;
+
+		if (IsTrueColor())
+		{
+			LOCK_PIXMAPS;
+			while (cPixmapMemory *pm =
+					dynamic_cast<cPixmapMemory *>(RenderPixmaps()))
+			{
+				if (tColor* argb = MALLOC(tColor,
+						pm->DrawPort().Width() * pm->DrawPort().Height()))
+				{
+					memcpy(argb, pm->Data(), sizeof(tColor) *
+							pm->DrawPort().Width() * pm->DrawPort().Height());
+
+					m_ovg->DoCmd(new cOvgCmdDrawBitmap(m_surface,
+							Left() + pm->ViewPort().Left(),
+							Top() + pm->ViewPort().Top(),
+							pm->DrawPort().Width(), pm->DrawPort().Height(),
+							argb));
+				}
+#if APIVERSNUM >= 20110
+				DestroyPixmap(pm);
+#else
+				delete pm;
+#endif
+			}
+		}
+		else
+		{
+			for (int i = 0; cBitmap *bitmap = GetBitmap(i); ++i)
+			{
+				int x1, y1, x2, y2;
+				if (bitmap->Dirty(x1, y1, x2, y2))
+				{
+					int w = x2 - x1 + 1;
+					int h = y2 - y1 + 1;
+					tColor *argb = MALLOC(tColor, w * h);
+					if (!argb)
+						return;
+
+					tColor *p = argb;
+					for (int y = y1; y <= y2; ++y)
+						for (int x = x1; x <= x2; ++x)
+							*p++ = bitmap->GetColor(x, y);
+
+					m_ovg->DoCmd(new cOvgCmdDrawBitmap(m_surface,
+							Left() + bitmap->X0() + x1,
+							Top() + bitmap->Y0() + y1, w, h, argb));
+
+					bitmap->Clean();
+				}
+			}
+		}
+		m_ovg->DoCmd(new cOvgCmdFlush(m_surface), true);
+	}
+
+	virtual eOsdError SetAreas(const tArea *Areas, int NumAreas)
+	{
+		eOsdError error;
+		cBitmap *bitmap;
+
+		if (Active())
+			Clear();
+
+		error = cOsd::SetAreas(Areas, NumAreas);
+
+		for (int i = 0; (bitmap = GetBitmap(i)) != NULL; i++)
+			bitmap->Clean();
+
+		return error;
+	}
+
+protected:
+
+	virtual void SetActive(bool On)
+	{
+		if (On != Active())
+		{
+			cOsd::SetActive(On);
+			if (!On)
+				Clear();
+			else
+				if (GetBitmap(0))
+					Flush();
+		}
+	}
+
+	virtual void Clear(void)
+	{
+		m_ovg->DoCmd(new cOvgCmdClear(m_surface));
+		m_ovg->DoCmd(new cOvgCmdFlush(m_surface));
+	}
+
+private:
+
+	cOvgThread       *m_ovg;
+	cOvgRenderTarget *m_surface;
+
+};
+
+/* ------------------------------------------------------------------------- */
+
+cRpiOsdProvider* cRpiOsdProvider::s_instance = 0;
+
+cRpiOsdProvider::cRpiOsdProvider() :
+	cOsdProvider(),
+	m_ovg(0)
+{
+	DLOG("new cOsdProvider()");
+	m_ovg = new cOvgThread();
+	s_instance = this;
+}
+
+cRpiOsdProvider::~cRpiOsdProvider()
+{
+	DLOG("delete cOsdProvider()");
+	s_instance = 0;
+	delete m_ovg;
+}
+
+cOsd *cRpiOsdProvider::CreateOsd(int Left, int Top, uint Level)
+{
+	if (cRpiSetup::IsHighLevelOsd())
+		return new cOvgOsd(Left, Top, Level, m_ovg);
+	else
+		return new cOvgRawOsd(Left, Top, Level, m_ovg);
+}
+
+int cRpiOsdProvider::StoreImageData(const cImage &Image)
+{
+	int id = m_ovg->StoreImageData(Image);
+	return id ? id : cOsdProvider::StoreImageData(Image);
+}
+
+void cRpiOsdProvider::DropImageData(int ImageHandle)
+{
+	if (ImageHandle < 0)
+		m_ovg->DropImageData(ImageHandle);
+	else
+		cOsdProvider::DropImageData(ImageHandle);
+}
+
+const cImage *cRpiOsdProvider::GetImageData(int ImageHandle)
+{
+	return cOsdProvider::GetImageData(ImageHandle);
+}
+
+void cRpiOsdProvider::ResetOsd(bool cleanup)
+{
+	if (s_instance)
+		s_instance->m_ovg->DoCmd(new cOvgCmdReset(cleanup));
+
+	UpdateOsdSize(true);
+}
diff --git a/ovgosd.h b/ovgosd.h
new file mode 100644
index 0000000..a8d309d
--- /dev/null
+++ b/ovgosd.h
@@ -0,0 +1,39 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef OVG_OSD_H
+#define OVG_OSD_H
+
+#include <vdr/osd.h>
+
+class cOvgThread;
+
+class cRpiOsdProvider : public cOsdProvider
+{
+
+public:
+
+	cRpiOsdProvider();
+	~cRpiOsdProvider();
+
+	static void ResetOsd(bool cleanup = false);
+	static const cImage *GetImageData(int ImageHandle);
+
+protected:
+
+	virtual cOsd *CreateOsd(int Left, int Top, uint Level);
+	virtual bool ProvidesTrueColor(void) { return true; }
+	virtual int StoreImageData(const cImage &Image);
+	virtual void DropImageData(int ImageHandle);
+
+private:
+
+	cOvgThread *m_ovg;
+	static cRpiOsdProvider *s_instance;
+};
+
+#endif
+
diff --git a/po/de_DE.po b/po/de_DE.po
new file mode 100644
index 0000000..bb2c49d
--- /dev/null
+++ b/po/de_DE.po
@@ -0,0 +1,78 @@
+# German translations for vdr-rpihddevice package.
+# Copyright (C) 2013 THE vdr-rpihddevice'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the vdr-rpihddevice package.
+#  <thomas at reufer.ch>, 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-rpihddevice 0.0.4\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2015-03-14 19:22+0100\n"
+"PO-Revision-Date: 2013-10-14 13:36+0200\n"
+"Last-Translator:  <thomas at reufer.ch>\n"
+"Language-Team: German <translation-team-de at lists.sourceforge.net>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "video format not supported!"
+msgstr "Videoformat nicht unterstützt!"
+
+msgid "HD output device for Raspberry Pi"
+msgstr "HD Ausgabegerät für Raspberry Pi"
+
+msgid "analog"
+msgstr "analog"
+
+msgid "HDMI"
+msgstr "HDMI"
+
+msgid "pass through"
+msgstr "Pass-Through"
+
+msgid "multi channel PCM"
+msgstr "Mehrkanal-PCM"
+
+msgid "stereo PCM"
+msgstr "Stereo-PCM"
+
+msgid "box"
+msgstr "einrahmen"
+
+msgid "crop"
+msgstr "abschneiden"
+
+msgid "stretch"
+msgstr "dehnen"
+
+msgid "default"
+msgstr "Voreinstellung"
+
+msgid "follow video"
+msgstr "wie Video"
+
+msgid "Resolution"
+msgstr "Auflösung"
+
+msgid "Frame Rate"
+msgstr "Bildwiederholrate"
+
+msgid "Video Framing"
+msgstr "Seitenverhältnis-Anpassung"
+
+msgid "Audio Port"
+msgstr "Audioanschluss"
+
+msgid "Digital Audio Format"
+msgstr "Digitales Audioformat"
+
+msgid "Use GPU accelerated OSD"
+msgstr "OSD mit GPU-Unterstützung"
+
+#~ msgid "Digital Audio Pass-Through"
+#~ msgstr "Digitalton durchreichen"
+
+#~ msgid "Ignore Audio EDID"
+#~ msgstr "Audio EDID ignorieren"
diff --git a/po/fi_FI.po b/po/fi_FI.po
new file mode 100644
index 0000000..834946d
--- /dev/null
+++ b/po/fi_FI.po
@@ -0,0 +1,77 @@
+# Finnish translations for vdr-rpihddevice package.
+# Copyright (C) 2014 THE vdr-rpihddevice'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the vdr-rpihddevice package.
+# Rolf Ahrenberg, 2014
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-rpihddevice 0.0.8\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2015-03-14 19:22+0100\n"
+"PO-Revision-Date: 2014-03-22 03:22+0200\n"
+"Last-Translator: Rolf Ahrenberg\n"
+"Language-Team: Finnish <vdr at linuxtv.org>\n"
+"Language: fi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "video format not supported!"
+msgstr "Kuvaformaatti ei ole tuettu!"
+
+msgid "HD output device for Raspberry Pi"
+msgstr "Raspberry Pi HD-ulostulolaite"
+
+msgid "analog"
+msgstr "analoginen"
+
+msgid "HDMI"
+msgstr "HDMI"
+
+msgid "pass through"
+msgstr ""
+
+msgid "multi channel PCM"
+msgstr ""
+
+msgid "stereo PCM"
+msgstr ""
+
+msgid "box"
+msgstr "kehystys"
+
+msgid "crop"
+msgstr "leikkaus"
+
+msgid "stretch"
+msgstr "venytys"
+
+msgid "default"
+msgstr "oletus"
+
+msgid "follow video"
+msgstr "lähetteen mukaan"
+
+msgid "Resolution"
+msgstr "Resoluutio"
+
+msgid "Frame Rate"
+msgstr "Ruudunpäivitys"
+
+msgid "Video Framing"
+msgstr "Kuvan rajaustapa"
+
+msgid "Audio Port"
+msgstr "Äänilähtö"
+
+msgid "Digital Audio Format"
+msgstr ""
+
+msgid "Use GPU accelerated OSD"
+msgstr "Käytä GPU-kiihdytettyä OSD:tä"
+
+#~ msgid "Digital Audio Pass-Through"
+#~ msgstr "Digitaaliäänen läpivienti"
+
+#~ msgid "Ignore Audio EDID"
+#~ msgstr "Jätä äänen EDID huomioimatta"
diff --git a/po/hu_HU.po b/po/hu_HU.po
new file mode 100644
index 0000000..d40c9ee
--- /dev/null
+++ b/po/hu_HU.po
@@ -0,0 +1,78 @@
+# Copyright (C) 2013 THE vdr-rpihddevice'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the vdr-rpihddevice package.
+#  <thomas at reufer.ch>, 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-rpihddevice 0.0.4\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2015-03-14 19:22+0100\n"
+"PO-Revision-Date: 2014-12-03 12:18+0200\n"
+"Last-Translator: Füley István <ifuley at tigercomp dot ro>\n"
+"Language-Team: \n"
+"Language: hu\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 1.6.11\n"
+
+msgid "video format not supported!"
+msgstr "A videoformátum nem támogatott!"
+
+msgid "HD output device for Raspberry Pi"
+msgstr "Raspberry Pi HD kimeneti eszköz "
+
+msgid "analog"
+msgstr "analog"
+
+msgid "HDMI"
+msgstr "HDMI"
+
+msgid "pass through"
+msgstr ""
+
+msgid "multi channel PCM"
+msgstr ""
+
+msgid "stereo PCM"
+msgstr ""
+
+msgid "box"
+msgstr "keretben"
+
+msgid "crop"
+msgstr "kivágva"
+
+msgid "stretch"
+msgstr "széthúzva"
+
+msgid "default"
+msgstr "alapértelmezett"
+
+msgid "follow video"
+msgstr "video szerinti"
+
+msgid "Resolution"
+msgstr "Felbontás"
+
+msgid "Frame Rate"
+msgstr "Képfrissítés"
+
+msgid "Video Framing"
+msgstr "Video képarány"
+
+msgid "Audio Port"
+msgstr "Hang kimenet"
+
+msgid "Digital Audio Format"
+msgstr ""
+
+msgid "Use GPU accelerated OSD"
+msgstr "Hardveresen (GPU) gyorsított OSD"
+
+#~ msgid "Digital Audio Pass-Through"
+#~ msgstr "Digitális hang átjátszás"
+
+#~ msgid "Ignore Audio EDID"
+#~ msgstr "Audio EDID figyelmen kivül hagyása"
diff --git a/rpihddevice.c b/rpihddevice.c
new file mode 100644
index 0000000..aef1a48
--- /dev/null
+++ b/rpihddevice.c
@@ -0,0 +1,105 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <vdr/plugin.h>
+#include <vdr/config.h>
+
+#include "ovgosd.h"
+#include "omxdevice.h"
+#include "setup.h"
+#include "display.h"
+#include "tools.h"
+
+static const char *VERSION        = "0.1.0";
+static const char *DESCRIPTION    = trNOOP("HD output device for Raspberry Pi");
+
+class cPluginRpiHdDevice : public cPlugin
+{
+private:
+
+	cOmxDevice *m_device;
+
+	static void OnPrimaryDevice(void)
+	{
+		if (cRpiSetup::HasOsd())
+			new cRpiOsdProvider();
+	}
+
+public:
+	cPluginRpiHdDevice(void);
+	virtual ~cPluginRpiHdDevice();
+	virtual const char *Version(void) { return VERSION; }
+	virtual const char *Description(void) { return tr(DESCRIPTION); }
+	virtual const char *CommandLineHelp(void);
+	virtual bool ProcessArgs(int argc, char *argv[]);
+	virtual bool Initialize(void);
+	virtual bool Start(void);
+	virtual void Stop(void);
+	virtual void Housekeeping(void) {}
+	virtual const char *MainMenuEntry(void) { return NULL; }
+	virtual cOsdObject *MainMenuAction(void) { return NULL; }
+	virtual cMenuSetupPage *SetupMenu(void);
+	virtual bool SetupParse(const char *Name, const char *Value);
+};
+
+cPluginRpiHdDevice::cPluginRpiHdDevice(void) : 
+	m_device(0)
+{
+}
+
+cPluginRpiHdDevice::~cPluginRpiHdDevice()
+{
+	cRpiSetup::DropInstance();
+	cRpiDisplay::DropInstance();
+}
+
+bool cPluginRpiHdDevice::Initialize(void)
+{
+	if (!cRpiSetup::HwInit())
+		return false;
+
+	// test whether MPEG2 license is available
+	if (!cRpiSetup::IsVideoCodecSupported(cVideoCodec::eMPEG2))
+		DLOG("MPEG2 video decoder not enabled!");
+
+	m_device = new cOmxDevice(&OnPrimaryDevice);
+
+	if (m_device)
+		return !m_device->Init();
+
+	return false;
+}
+
+bool cPluginRpiHdDevice::Start(void)
+{
+	return m_device->Start();
+}
+
+void cPluginRpiHdDevice::Stop(void)
+{
+}
+
+cMenuSetupPage* cPluginRpiHdDevice::SetupMenu(void)
+{
+	return cRpiSetup::GetInstance()->GetSetupPage();
+}
+
+bool cPluginRpiHdDevice::SetupParse(const char *Name, const char *Value)
+{
+	return cRpiSetup::GetInstance()->Parse(Name, Value);
+}
+
+bool cPluginRpiHdDevice::ProcessArgs(int argc, char *argv[])
+{
+	return cRpiSetup::GetInstance()->ProcessArgs(argc, argv);
+}
+
+const char *cPluginRpiHdDevice::CommandLineHelp(void)
+{
+	return cRpiSetup::GetInstance()->CommandLineHelp();
+}
+
+VDRPLUGINCREATOR(cPluginRpiHdDevice); // Don't touch this! okay.
diff --git a/setup.c b/setup.c
new file mode 100644
index 0000000..8bc1d74
--- /dev/null
+++ b/setup.c
@@ -0,0 +1,355 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include "setup.h"
+#include "display.h"
+#include "ovgosd.h"
+
+#include <vdr/tools.h>
+#include <vdr/menuitems.h>
+
+#include <getopt.h>
+
+#include <bcm_host.h>
+#include "interface/vchiq_arm/vchiq_if.h"
+#include "interface/vmcs_host/vc_tvservice.h"
+
+/* ------------------------------------------------------------------------- */
+
+class cRpiSetupPage : public cMenuSetupPage
+{
+
+public:
+
+	cRpiSetupPage(
+			cRpiSetup::AudioParameters audio,
+			cRpiSetup::VideoParameters video,
+			cRpiSetup::OsdParameters osd) :
+
+		m_audio(audio),
+		m_video(video),
+		m_osd(osd)
+	{
+		m_audioPort[0] = tr("analog");
+		m_audioPort[1] = tr("HDMI");
+
+		m_audioFormat[0] = tr("pass through");
+		m_audioFormat[1] = tr("multi channel PCM");
+		m_audioFormat[2] = tr("stereo PCM");
+
+		m_videoFraming[0] = tr("box");
+		m_videoFraming[1] = tr("crop");
+		m_videoFraming[2] = tr("stretch");
+
+		m_videoResolution[0] = tr("default");
+		m_videoResolution[1] = tr("follow video");
+		m_videoResolution[2] = "720x480";
+		m_videoResolution[3] = "720x576";
+		m_videoResolution[4] = "1280x720";
+		m_videoResolution[5] = "1920x1080";
+
+		m_videoFrameRate[0] = tr("default");
+		m_videoFrameRate[1] = tr("follow video");
+		m_videoFrameRate[2] = "24p";
+		m_videoFrameRate[3] = "25p";
+		m_videoFrameRate[4] = "30p";
+		m_videoFrameRate[5] = "50i";
+		m_videoFrameRate[6] = "50p";
+		m_videoFrameRate[7] = "60i";
+		m_videoFrameRate[8] = "60p";
+
+		Setup();
+	}
+
+	eOSState ProcessKey(eKeys Key)
+	{
+		int newAudioPort = m_audio.port;
+		eOSState state = cMenuSetupPage::ProcessKey(Key);
+
+		if (Key != kNone)
+		{
+			if (newAudioPort != m_audio.port)
+				Setup();
+		}
+
+		return state;
+	}
+
+protected:
+
+	virtual void Store(void)
+	{
+		SetupStore("AudioPort", m_audio.port);
+		SetupStore("AudioFormat", m_audio.format);
+
+		SetupStore("VideoFraming", m_video.framing);
+		SetupStore("Resolution", m_video.resolution);
+		SetupStore("FrameRate", m_video.frameRate);
+
+		SetupStore("AcceleratedOsd", m_osd.accelerated);
+
+		cRpiSetup::GetInstance()->Set(m_audio, m_video, m_osd);
+}
+
+private:
+
+	void Setup(void)
+	{
+		int current = Current();
+		Clear();
+
+		if (cRpiDisplay::GetVideoPort() == cRpiVideoPort::eHDMI)
+		{
+			Add(new cMenuEditStraItem(
+				tr("Resolution"), &m_video.resolution, 6, m_videoResolution));
+
+			Add(new cMenuEditStraItem(
+				tr("Frame Rate"), &m_video.frameRate, 9, m_videoFrameRate));
+		}
+
+		Add(new cMenuEditStraItem(
+				tr("Video Framing"), &m_video.framing, 3, m_videoFraming));
+
+		Add(new cMenuEditStraItem(
+				tr("Audio Port"), &m_audio.port, 2, m_audioPort));
+
+		if (m_audio.port == 1)
+		{
+			Add(new cMenuEditStraItem(tr("Digital Audio Format"),
+					&m_audio.format, 3, m_audioFormat));
+		}
+
+		Add(new cMenuEditBoolItem(
+				tr("Use GPU accelerated OSD"), &m_osd.accelerated));
+
+		SetCurrent(Get(current));
+		Display();
+	}
+
+	cRpiSetup::AudioParameters m_audio;
+	cRpiSetup::VideoParameters m_video;
+	cRpiSetup::OsdParameters   m_osd;
+
+	const char *m_audioPort[2];
+	const char *m_audioFormat[3];
+	const char *m_videoFraming[3];
+	const char *m_videoResolution[6];
+	const char *m_videoFrameRate[9];
+};
+
+/* ------------------------------------------------------------------------- */
+
+cRpiSetup* cRpiSetup::s_instance = 0;
+
+cRpiSetup* cRpiSetup::GetInstance(void)
+{
+	if (!s_instance)
+		s_instance = new cRpiSetup();
+
+	return s_instance;
+}
+
+void cRpiSetup::DropInstance(void)
+{
+	delete s_instance;
+	s_instance = 0;
+
+	bcm_host_deinit();
+}
+
+bool cRpiSetup::HwInit(void)
+{
+	cRpiSetup* instance = GetInstance();
+	if (!instance)
+		return false;
+
+	bcm_host_init();
+
+	if (!vc_gencmd_send("codec_enabled MPG2"))
+	{
+		char buffer[1024];
+		if (!vc_gencmd_read_response(buffer, sizeof(buffer)))
+		{
+			if (!strcasecmp(buffer,"MPG2=enabled"))
+				GetInstance()->m_mpeg2Enabled = true;
+		}
+	}
+
+	int width, height;
+	if (!cRpiDisplay::GetSize(width, height))
+	{
+		ILOG("HwInit() done, using %s video out at %dx%d",
+		cRpiVideoPort::Str(cRpiDisplay::GetVideoPort()), width, height);
+	}
+	else
+		ELOG("failed to get video port information!");
+
+	return true;
+}
+
+void cRpiSetup::SetAudioSetupChangedCallback(void (*callback)(void*), void* data)
+{
+	GetInstance()->m_onAudioSetupChanged = callback;
+	GetInstance()->m_onAudioSetupChangedData = data;
+}
+
+void cRpiSetup::SetVideoSetupChangedCallback(void (*callback)(void*), void* data)
+{
+	GetInstance()->m_onVideoSetupChanged = callback;
+	GetInstance()->m_onVideoSetupChangedData = data;
+}
+
+bool cRpiSetup::IsAudioFormatSupported(cAudioCodec::eCodec codec,
+		int channels, int samplingRate)
+{
+	// MPEG-1 layer 2 audio pass-through not supported by audio render
+	// and AAC audio pass-through not yet working
+	if (codec == cAudioCodec::eMPG || codec == cAudioCodec::eAAC)
+		return false;
+
+	switch (GetAudioFormat())
+	{
+	case cAudioFormat::ePassThrough:
+		return (vc_tv_hdmi_audio_supported(
+					codec == cAudioCodec::eMPG  ? EDID_AudioFormat_eMPEG1 :
+					codec == cAudioCodec::eAC3  ? EDID_AudioFormat_eAC3   :
+					codec == cAudioCodec::eEAC3 ? EDID_AudioFormat_eEAC3  :
+					codec == cAudioCodec::eAAC  ? EDID_AudioFormat_eAAC   :
+					codec == cAudioCodec::eDTS  ? EDID_AudioFormat_eDTS   :
+							EDID_AudioFormat_ePCM, channels,
+					samplingRate ==  32000 ? EDID_AudioSampleRate_e32KHz  :
+					samplingRate ==  44100 ? EDID_AudioSampleRate_e44KHz  :
+					samplingRate ==  88200 ? EDID_AudioSampleRate_e88KHz  :
+					samplingRate ==  96000 ? EDID_AudioSampleRate_e96KHz  :
+					samplingRate == 176000 ? EDID_AudioSampleRate_e176KHz :
+					samplingRate == 192000 ? EDID_AudioSampleRate_e192KHz :
+							EDID_AudioSampleRate_e48KHz,
+							EDID_AudioSampleSize_16bit) == 0);
+
+	case cAudioFormat::eMultiChannelPCM:
+		return codec == cAudioCodec::ePCM;
+
+	default:
+	case cAudioFormat::eStereoPCM:
+		return codec == cAudioCodec::ePCM && channels == 2;
+	}
+}
+
+void cRpiSetup::SetHDMIChannelMapping(bool passthrough, int channels)
+{
+	char command[80], response[80];
+
+	sprintf(command, "hdmi_stream_channels %d", passthrough ? 1 : 0);
+	vc_gencmd(response, sizeof response, command);
+
+	uint32_t channel_map = 0;
+
+	if (!passthrough && channels > 0 && channels <= 6)
+	{
+		const unsigned char ch_mapping[6][8] =
+		{
+			{ 0, 0, 0, 0, 0, 0, 0, 0 }, // not supported
+			{ 1, 2, 0, 0, 0, 0, 0, 0 }, // 2.0
+			{ 1, 2, 4, 0, 0, 0, 0, 0 }, // 2.1
+			{ 0, 0, 0, 0, 0, 0, 0, 0 }, // not supported
+			{ 0, 0, 0, 0, 0, 0, 0, 0 }, // not supported
+			{ 1, 2, 4, 3, 5, 6, 0, 0 }, // 5.1
+		};
+
+		// speaker layout according CEA 861, Table 28: Audio InfoFrame, byte 4
+		const unsigned char cea_map[] =
+		{
+			0xff,	// not supported
+			0x00,	// 2.0
+			0x01,	// 2.1
+			0xff,	// not supported
+			0xff,	// not supported
+			0x0b	// 5.1
+		};
+
+		for (int ch = 0; ch < channels; ch++)
+			if (ch_mapping[channels - 1][ch])
+				channel_map |= (ch_mapping[channels - 1][ch] - 1) << (3 * ch);
+
+		channel_map |= cea_map[channels - 1] << 24;
+	}
+
+	sprintf(command, "hdmi_channel_map 0x%08x", channel_map);
+	vc_gencmd(response, sizeof response, command);
+}
+
+cMenuSetupPage* cRpiSetup::GetSetupPage(void)
+{
+	return new cRpiSetupPage(m_audio, m_video, m_osd);
+}
+
+bool cRpiSetup::Parse(const char *name, const char *value)
+{
+	if (!strcasecmp(name, "AudioPort"))
+		m_audio.port = atoi(value);
+	else if (!strcasecmp(name, "AudioFormat"))
+		m_audio.format = atoi(value);
+	else if (!strcasecmp(name, "VideoFraming"))
+		m_video.framing = atoi(value);
+	else if (!strcasecmp(name, "Resolution"))
+		m_video.resolution = atoi(value);
+	else if (!strcasecmp(name, "FrameRate"))
+		m_video.frameRate = atoi(value);
+	else if (!strcasecmp(name, "AcceleratedOsd"))
+		m_osd.accelerated = atoi(value);
+	else return false;
+
+	return true;
+}
+
+void cRpiSetup::Set(AudioParameters audio, VideoParameters video,
+		OsdParameters osd)
+{
+	if (audio != m_audio)
+	{
+		m_audio = audio;
+		if (m_onAudioSetupChanged)
+			m_onAudioSetupChanged(m_onAudioSetupChangedData);
+	}
+
+	if (video != m_video)
+	{
+		m_video = video;
+		if (m_onVideoSetupChanged)
+			m_onVideoSetupChanged(m_onVideoSetupChangedData);
+	}
+
+	if (osd != m_osd)
+	{
+		m_osd = osd;
+		cRpiOsdProvider::ResetOsd(false);
+	}
+}
+
+bool cRpiSetup::ProcessArgs(int argc, char *argv[])
+{
+	static struct option long_options[] = {
+			{ "disable-osd", no_argument, NULL, 'd' },
+	};
+	int c;
+	while ((c = getopt_long(argc, argv, "d", long_options, NULL)) != -1)
+	{
+		switch (c)
+		{
+		case 'd':
+			m_plugin.hasOsd = false;
+			break;
+		default:
+			return false;
+		}
+	}
+	return true;
+}
+
+const char *cRpiSetup::CommandLineHelp(void)
+{
+	return "  -d,       --disable-osd  disable OSD\n";
+}
diff --git a/setup.h b/setup.h
new file mode 100644
index 0000000..72f8195
--- /dev/null
+++ b/setup.h
@@ -0,0 +1,168 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef SETUP_H
+#define SETUP_H
+
+#include "omx.h"
+#include "tools.h"
+
+class cRpiSetup
+{
+
+public:
+
+	struct AudioParameters
+	{
+		AudioParameters() :
+			port(0),
+			format(0) { }
+
+		int port;
+		int format;
+
+		bool operator!=(const AudioParameters& a) {
+			return (a.port != port) || (a.format != format);
+		}
+	};
+
+	struct VideoParameters
+	{
+		VideoParameters() :
+			framing(0),
+			resolution(0),
+			frameRate(0) { }
+
+		int framing;
+		int resolution;
+		int frameRate;
+
+		bool operator!=(const VideoParameters& a) {
+			return (a.framing != framing) || (a.resolution != resolution) ||
+					(a.frameRate != frameRate);
+		}
+	};
+
+	struct OsdParameters
+	{
+		OsdParameters() :
+			accelerated(1) { }
+
+		int accelerated;
+
+		bool operator!=(const OsdParameters& a) {
+			return (a.accelerated != accelerated);
+		}
+	};
+
+	struct PluginParameters
+	{
+		PluginParameters() :
+			hasOsd(true) { }
+
+		bool hasOsd;
+	};
+
+	static bool HwInit(void);
+
+	static cRpiAudioPort::ePort GetAudioPort(void) {
+		return (GetInstance()->m_audio.port) ?
+				cRpiAudioPort::eHDMI : cRpiAudioPort::eLocal; }
+
+	static cAudioFormat::eFormat GetAudioFormat(void) {
+		return  GetInstance()->m_audio.format == 0 ? cAudioFormat::ePassThrough :
+				GetInstance()->m_audio.format == 1 ? cAudioFormat::eMultiChannelPCM :
+						cAudioFormat::eStereoPCM;
+	}
+
+	static cVideoFraming::eFraming GetVideoFraming(void) {
+		return GetInstance()->m_video.framing == 0 ? cVideoFraming::eFrame :
+			   GetInstance()->m_video.framing == 1 ? cVideoFraming::eCut :
+					   cVideoFraming::eStretch;
+	}
+
+	static cVideoResolution::eResolution GetVideoResolution(void) {
+		return	GetInstance()->m_video.resolution == 1 ? cVideoResolution::eFollowVideo :
+				GetInstance()->m_video.resolution == 2 ? cVideoResolution::e480 :
+				GetInstance()->m_video.resolution == 3 ? cVideoResolution::e576 :
+				GetInstance()->m_video.resolution == 4 ? cVideoResolution::e720 :
+				GetInstance()->m_video.resolution == 5 ? cVideoResolution::e1080 :
+						cVideoResolution::eDontChange;
+	}
+
+	static cVideoFrameRate::eFrameRate GetVideoFrameRate(void) {
+		return 	GetInstance()->m_video.frameRate == 1 ? cVideoFrameRate::eFollowVideo :
+				GetInstance()->m_video.frameRate == 2 ? cVideoFrameRate::e24p :
+				GetInstance()->m_video.frameRate == 3 ? cVideoFrameRate::e25p :
+				GetInstance()->m_video.frameRate == 4 ? cVideoFrameRate::e30p :
+				GetInstance()->m_video.frameRate == 5 ? cVideoFrameRate::e50i :
+				GetInstance()->m_video.frameRate == 6 ? cVideoFrameRate::e50p :
+				GetInstance()->m_video.frameRate == 7 ? cVideoFrameRate::e60i :
+				GetInstance()->m_video.frameRate == 8 ? cVideoFrameRate::e60p :
+						cVideoFrameRate::eDontChange;
+	}
+
+	static bool IsAudioFormatSupported(cAudioCodec::eCodec codec,
+			int channels, int samplingRate);
+
+	static bool IsVideoCodecSupported(cVideoCodec::eCodec codec) {
+		return codec == cVideoCodec::eMPEG2 ? GetInstance()->m_mpeg2Enabled :
+			   codec == cVideoCodec::eH264 ? true : false;
+	}
+
+	static bool IsHighLevelOsd(void) {
+		return GetInstance()->m_osd.accelerated != 0;
+	}
+
+	static bool HasOsd(void) {
+		return GetInstance()->m_plugin.hasOsd;
+	}
+
+	static void SetHDMIChannelMapping(bool passthrough, int channels);
+
+	static cRpiSetup* GetInstance(void);
+	static void DropInstance(void);
+
+	class cMenuSetupPage* GetSetupPage(void);
+	bool Parse(const char *name, const char *value);
+
+	void Set(AudioParameters audio, VideoParameters video, OsdParameters osd);
+
+	static void SetAudioSetupChangedCallback(void (*callback)(void*), void* data = 0);
+	static void SetVideoSetupChangedCallback(void (*callback)(void*), void* data = 0);
+
+	bool ProcessArgs(int argc, char *argv[]);
+	const char *CommandLineHelp(void);
+
+private:
+
+	cRpiSetup() :
+		m_mpeg2Enabled(false),
+		m_onAudioSetupChanged(0),
+		m_onAudioSetupChangedData(0),
+		m_onVideoSetupChanged(0),
+		m_onVideoSetupChangedData(0)
+	{ }
+
+	virtual ~cRpiSetup() { };
+
+	static cRpiSetup* s_instance;
+
+	AudioParameters  m_audio;
+	VideoParameters  m_video;
+	OsdParameters    m_osd;
+	PluginParameters m_plugin;
+
+	bool m_mpeg2Enabled;
+
+	void (*m_onAudioSetupChanged)(void*);
+	void *m_onAudioSetupChangedData;
+
+	void (*m_onVideoSetupChanged)(void*);
+	void *m_onVideoSetupChangedData;
+};
+
+#endif
diff --git a/tools.h b/tools.h
new file mode 100644
index 0000000..2589e56
--- /dev/null
+++ b/tools.h
@@ -0,0 +1,178 @@
+/*
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef TOOLS_H
+#define TOOLS_H
+
+#define ELOG(a...) esyslog("rpihddevice: " a)
+#define ILOG(a...) isyslog("rpihddevice: " a)
+#define DLOG(a...) dsyslog("rpihddevice: " a)
+
+#ifdef DEBUG
+#define DBG(a...)  dsyslog("rpihddevice: " a)
+#else
+#define DBG(a...)  void()
+#endif
+
+class cVideoResolution
+{
+public:
+
+	enum eResolution {
+		eDontChange = 0,
+		eFollowVideo,
+		e480,
+		e576,
+		e720,
+		e1080
+	};
+
+	static const char* Str(eResolution resolution) {
+		return	(resolution == eDontChange)  ? "don't change" :
+				(resolution == eFollowVideo) ? "follow video" :
+				(resolution == e480)         ? "480"          :
+				(resolution == e576)         ? "576"          :
+				(resolution == e720)         ? "720"          :
+				(resolution == e1080)        ? "1080"         :	"unknown";
+	}
+};
+
+class cVideoFrameRate
+{
+public:
+
+	enum eFrameRate {
+		eDontChange = 0,
+		eFollowVideo,
+		e24p,
+		e25p,
+		e30p,
+		e50i,
+		e50p,
+		e60i,
+		e60p
+	};
+
+	static const char* Str(eFrameRate frameRate) {
+		return	(frameRate == eDontChange)  ? "don't change" :
+				(frameRate == eFollowVideo) ? "follow video" :
+				(frameRate == e24p)         ? "p24"          :
+				(frameRate == e25p)         ? "p25"          :
+				(frameRate == e30p)         ? "p30"          :
+				(frameRate == e50i)         ? "i50"          :
+				(frameRate == e50p)         ? "p50"          :
+				(frameRate == e60i)         ? "i60"          :
+				(frameRate == e60p)         ? "p60"          : "unknown";
+	}
+};
+
+class cVideoFraming
+{
+public:
+
+	enum eFraming {
+		eFrame,
+		eCut,
+		eStretch
+	};
+
+	static const char* Str(eFraming framing) {
+		return  (framing == eFrame)   ? "frame"   :
+				(framing == eCut)     ? "cut"     :
+				(framing == eStretch) ? "stretch" : "unknown";
+	}
+};
+
+class cAudioCodec
+{
+public:
+
+	enum eCodec {
+		ePCM,
+		eMPG,
+		eAC3,
+		eEAC3,
+		eAAC,
+		eDTS,
+		eNumCodecs,
+		eInvalid
+	};
+
+	static const char* Str(eCodec codec) {
+		return  (codec == ePCM)  ? "PCM"   :
+				(codec == eMPG)  ? "MPEG"  :
+				(codec == eAC3)  ? "AC3"   :
+				(codec == eEAC3) ? "E-AC3" :
+				(codec == eAAC)  ? "AAC"   :
+				(codec == eDTS)  ? "DTS"   : "unknown";
+	}
+};
+
+class cAudioFormat
+{
+public:
+
+	enum eFormat {
+		ePassThrough,
+		eMultiChannelPCM,
+		eStereoPCM
+	};
+
+	static const char* Str(eFormat format) {
+		return  (format == ePassThrough)     ? "pass through"      :
+				(format == eMultiChannelPCM) ? "multi channel PCM" :
+				(format == eStereoPCM)       ? "stereo PCM"        : "unknown";
+	}
+};
+
+class cVideoCodec
+{
+public:
+
+	enum eCodec {
+		eMPEG2,
+		eH264,
+		eNumCodecs,
+		eInvalid
+	};
+
+	static const char* Str(eCodec codec) {
+		return  (codec == eMPEG2) ? "MPEG2" :
+				(codec == eH264)  ? "H264"  : "unknown";
+	}
+};
+
+class cRpiAudioPort
+{
+public:
+
+	enum ePort {
+		eLocal,
+		eHDMI
+	};
+
+	static const char* Str(ePort port) {
+		return 	(port == eLocal) ? "local" :
+				(port == eHDMI)  ? "HDMI"  : "unknown";
+	}
+};
+
+class cRpiVideoPort
+{
+public:
+
+	enum ePort {
+		eComposite,
+		eHDMI
+	};
+
+	static const char* Str(ePort port) {
+		return 	(port == eComposite) ? "composite" :
+				(port == eHDMI)      ? "HDMI"      : "unknown";
+	}
+};
+
+#endif

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-vdr-dvb/vdr-plugin-rpihddevice.git



More information about the pkg-vdr-dvb-changes mailing list