diff options
author | Thomas Reufer <thomas@reufer.ch> | 2014-01-07 16:20:51 +0100 |
---|---|---|
committer | Thomas Reufer <thomas@reufer.ch> | 2014-01-07 16:20:51 +0100 |
commit | d2ed6de22829288c5cfd3a52b1da090e96d3329d (patch) | |
tree | ef84cba3610e96cc4c58359da3c8a69882aa03c9 | |
download | vdr-plugin-rpihddevice-0.0.1.tar.gz vdr-plugin-rpihddevice-0.0.1.tar.bz2 |
2013-09-27: Version 0.0.10.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...
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | HISTORY | 17 | ||||
-rw-r--r-- | Makefile | 138 | ||||
-rw-r--r-- | README | 18 | ||||
-rw-r--r-- | ilclient/Makefile | 23 | ||||
-rw-r--r-- | ilclient/ilclient.c | 1836 | ||||
-rw-r--r-- | ilclient/ilclient.h | 1039 | ||||
-rw-r--r-- | ilclient/ilcore.c | 308 | ||||
-rw-r--r-- | omxdevice.c | 782 | ||||
-rw-r--r-- | omxdevice.h | 90 | ||||
-rw-r--r-- | ovgosd.c | 297 | ||||
-rw-r--r-- | ovgosd.h | 52 | ||||
-rw-r--r-- | rpihddevice.c | 98 |
13 files changed, 5038 insertions, 0 deletions
@@ -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. @@ -0,0 +1,17 @@ +VDR Plugin 'rpihddevice' Revision History +----------------------------------------- + +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..090d5ce --- /dev/null +++ b/Makefile @@ -0,0 +1,138 @@ +# +# 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 --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc)) +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-write-strings -fpermissive + +INCLUDES += -I/opt/vc/include/ -I/opt/vc/include/interface/vcos/pthreads + +ILCDIR = ilclient +VCLIBDIR = /opt/vc/lib + +LDFLAGS += -L$(VCLIBDIR) -lbcm_host -lvcos -lvchiq_arm -lopenmaxil -lGLESv2 -lEGL -lpthread -lrt -lmpg123 +LDFLAGS += -Wl,--whole-archive $(ILCDIR)/libilclient.a -Wl,--no-whole-archive + +# -I/opt/vc/src/hello_pi/libs/vgfont + +### The object files (add further files here): + +ILCLIENT = $(ILCDIR)/libilclient.a +OBJS = $(PLUGIN).o omxdevice.o ovgosd.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) -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 + @@ -0,0 +1,18 @@ +This is a "plugin" for the Video Disk Recorder (VDR). + +Written by: Thomas Reufer <thomas@reufer.ch> + +Project's homepage: t.b.d. + +Latest version available at: t.b.d. + +See the file COPYING for license information. + +Description: + + Output device for Raspberry Pi. + +Requirements: + + - valid MPEG2 licence + - libmpg123 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..da08ad0 --- /dev/null +++ b/ilclient/ilclient.c @@ -0,0 +1,1836 @@ +/* +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; + int32_t status; + 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) + { + 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, ¶m) == 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, ¶m); + 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) +{ + int32_t status; + 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; + } + + 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, ¶m); + 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, + ¶m); + 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/omxdevice.c b/omxdevice.c new file mode 100644 index 0000000..2034e51 --- /dev/null +++ b/omxdevice.c @@ -0,0 +1,782 @@ +/* + * See the README file for copyright information and how to reach the author. + * + * $Id$ + */ + +#include "omxdevice.h" + +#include <vdr/remux.h> +#include <vdr/tools.h> + +#include <mpg123.h> + +#include <string.h> + +extern "C" +{ +#include "ilclient.h" +} + +#include "bcm_host.h" + +class cOmx +{ + +public: + + static const char* 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"; + }; + + enum eOmxComponent { + eClock = 0, + eVideoDecoder, + eVideoScheduler, + eVideoRender, + eAudioRender, + eNumComponents + }; + + enum eOmxTunnel { + eVideoDecoderToVideoScheduler = 0, + eVideoSchedulerToVideoRender, + eClockToVideoScheduler, + eClockToAudioRender, + eNumTunnels + }; + + ILCLIENT_T *client; + COMPONENT_T *comp[cOmx::eNumComponents + 1]; + TUNNEL_T tun[cOmx::eNumTunnels + 1]; + + static void OmxError(void *omxDevice, COMPONENT_T *comp, unsigned int data) + { + if (data != OMX_ErrorSameState) + esyslog("rpihddevice: OmxError(%s)", errStr((int)data)); + } + + static void OmxBufferEmpty(void *omxDevice, COMPONENT_T *comp) + { +// dsyslog("rpihddevice: OmxBufferEmpty()"); + } + + static void OmxPortSettingsChanged(void *omxDevice, COMPONENT_T *comp, unsigned int data) + { + cOmxDevice* dev = static_cast <cOmxDevice*> (omxDevice); + dev->HandlePortSettingsChanged(data); + } + + static void OmxEndOfStream(void *omxDevice, COMPONENT_T *comp, unsigned int data) + { + cOmxDevice* dev = static_cast <cOmxDevice*> (omxDevice); + dev->HandleEndOfStream(data); + } + + static void PtsToTicks(uint64_t pts, OMX_TICKS &ticks) + { + // ticks = pts * OMX_TICKS_PER_SECOND / PTSTICKS + pts = pts * 100 / 9; + ticks.nLowPart = (OMX_U32)pts; + ticks.nHighPart = (OMX_U32)(pts >> 32); + } + + static uint64_t TicksToPts(OMX_TICKS &ticks) + { + // pts = ticks * PTSTICKS / OMX_TICKS_PER_SECOND + uint64_t pts = ticks.nHighPart << 32 + ticks.nLowPart; + pts = pts * 9 / 100; + return pts; + } + +}; + +class cAudio +{ + +public: + + cAudio() : + sampleRate(0), + bitDepth(0), + nChannels(0), + m_handle(0) + { + int ret; + mpg123_init(); + m_handle = mpg123_new(NULL, &ret); + if (m_handle == NULL) + esyslog("rpihddevice: failed to create mpg123 handle!"); + + if (mpg123_open_feed(m_handle) == MPG123_ERR) + esyslog("rpihddevice: failed to open mpg123 feed!"); + + dsyslog("rpihddevice: new cAudio()"); + } + + ~cAudio() + { + mpg123_delete(m_handle); + dsyslog("rpihddevice: delete cAudio()"); + } + + unsigned int decode(const unsigned char *inbuf, unsigned int length, unsigned char *outbuf, unsigned int bufsize) + { + unsigned int done = 0; + if (mpg123_decode(m_handle, inbuf, length, outbuf, bufsize, &done) == MPG123_ERR) + esyslog("rpihddevice: failed to decode audio data!"); + return done; + } + + int sampleRate; + int bitDepth; + int nChannels; + + mpg123_handle *m_handle; +}; + +cOmxDevice::cOmxDevice(void (*onPrimaryDevice)(void)) : + cDevice(), + m_onPrimaryDevice(onPrimaryDevice), + m_omx(new cOmx()), + m_audio(new cAudio()), + m_eosEvent(0), + m_state(eStop), + m_firstVideoPacket(false), + m_firstAudioPacket(false) +{ + m_eosEvent = new cCondWait(); + m_mutex = new cMutex(); +} + +cOmxDevice::~cOmxDevice() +{ + OmxDeInit(); + delete m_omx; + delete m_audio; + delete m_mutex; + delete m_eosEvent; +} + +bool cOmxDevice::CanReplay(void) const +{ + // video codec de-initialization done + return (m_state == eStop); +} + +bool cOmxDevice::SetPlayMode(ePlayMode PlayMode) +{ + dsyslog("rpihddevice: SetPlayMode(%s)", + PlayMode == pmNone ? "none" : + PlayMode == pmAudioVideo ? "Audio/Video" : + PlayMode == pmAudioOnly ? "Audio only" : + PlayMode == pmAudioOnlyBlack ? "Audio only, black" : + PlayMode == pmVideoOnly ? "Video only" : + "unsupported"); + switch (PlayMode) + { + case pmNone: + Stop(); + break; + + case pmAudioVideo: + Start(); + break; + + case pmAudioOnly: + case pmAudioOnlyBlack: + case pmVideoOnly: + + break; + } + + return true; +} + +int cOmxDevice::PlayAudio(const uchar *Data, int Length, uchar Id) +{ + m_mutex->Lock(); + + if (m_state != ePlay) + { + m_mutex->Unlock(); + dsyslog("rpihddevice: PlayAudio() not replaying!"); + return 0; + } + + if (!PesHasLength(Data)) + { + esyslog("rpihddevice: audio packet dropped!"); + m_mutex->Unlock(); + return Length; + } + + int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0; + + const uchar *payload = Data + PesPayloadOffset(Data); + int length = PesLength(Data) - PesPayloadOffset(Data); + + // first packet of a new stream needs valid PTS + if (m_firstAudioPacket && pts == 0) + { + m_mutex->Unlock(); + return Length; + } + + if (m_firstAudioPacket) + OmxSetAudioCodec(payload); + + OMX_BUFFERHEADERTYPE *buf = ilclient_get_input_buffer(m_omx->comp[cOmx::eAudioRender], 100, 1); + if (buf == NULL) + { + esyslog("rpihddevice: failed to get audio buffer!"); + m_mutex->Unlock(); + return 0; + } + + // decode audio packet + buf->nFilledLen = m_audio->decode(payload, length, buf->pBuffer, buf->nAllocLen); + + buf->nFlags = m_firstAudioPacket ? OMX_BUFFERFLAG_STARTTIME : 0; + cOmx::PtsToTicks(pts, buf->nTimeStamp); + +// dsyslog("A: %u.%u - %lld", buf->nTimeStamp.nHighPart, buf->nTimeStamp.nLowPart, pts); +// dsyslog("rpihddevice: PlayAudio(%u.%u, %02x %02x %02x %02x, %d)", buf->nTimeStamp.nHighPart, buf->nTimeStamp.nLowPart, +// buf->pBuffer[0], buf->pBuffer[1], buf->pBuffer[2], buf->pBuffer[3], buf->nFilledLen); + + if (OMX_EmptyThisBuffer(ILC_GET_HANDLE(m_omx->comp[cOmx::eAudioRender]), buf) != OMX_ErrorNone) + esyslog("rpihddevice: failed to pass buffer to audio render!"); + + m_firstAudioPacket = false; + + m_mutex->Unlock(); + return Length; +} + +int cOmxDevice::PlayVideo(const uchar *Data, int Length) +{ + m_mutex->Lock(); + + if (m_state != ePlay) + { + m_mutex->Unlock(); + dsyslog("rpihddevice: PlayVideo() not replaying!"); + return 0; + } + + if (!PesHasLength(Data)) + { + esyslog("rpihddevice: video packet dropped!"); + m_mutex->Unlock(); + return Length; + } + + int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0; + + const uchar *payload = Data + PesPayloadOffset(Data); + int length = PesLength(Data) - PesPayloadOffset(Data); + + // first packet of a new stream needs valid PTS and MPEG start code + if (m_firstVideoPacket && + (pts == 0 || !(payload[0] == 0x00 && payload[1] == 0x00 && + payload[2] == 0x01 && payload[3] == 0xb3))) + { + m_mutex->Unlock(); + return Length; + } + + if (m_firstVideoPacket) + OmxSetVideoCodec(payload); + + OMX_BUFFERHEADERTYPE *buf = ilclient_get_input_buffer(m_omx->comp[cOmx::eVideoDecoder], 130, 1); + if (buf == NULL) + { + esyslog("rpihddevice: failed to get video buffer!"); + m_mutex->Unlock(); + return 0; + } + + cOmx::PtsToTicks(pts, buf->nTimeStamp); + buf->nFlags = m_firstVideoPacket ? OMX_BUFFERFLAG_STARTTIME : OMX_BUFFERFLAG_TIME_UNKNOWN; + + if (length <= buf->nAllocLen) + { + memcpy(buf->pBuffer, payload, length); + buf->nFilledLen = length; + } + else + esyslog("rpihddevice: video packet too long for video buffer!"); + +// dsyslog("V: %u.%u - %lld", buf->nTimeStamp.nHighPart, buf->nTimeStamp.nLowPart, pts); +// dsyslog("rpihddevice: PlayVideo(%u.%u, %02x %02x %02x %02x, %d)", buf->nTimeStamp.nHighPart, buf->nTimeStamp.nLowPart, +// buf->pBuffer[0], buf->pBuffer[1], buf->pBuffer[2], buf->pBuffer[3], buf->nFilledLen); + + if (OMX_EmptyThisBuffer(ILC_GET_HANDLE(m_omx->comp[cOmx::eVideoDecoder]), buf) != OMX_ErrorNone) + esyslog("rpihddevice: failed to pass buffer to video decoder!"); + + m_firstVideoPacket = false; + + m_mutex->Unlock(); + return Length; +} + +int64_t cOmxDevice::GetSTC(void) +{ + int64_t stc = -1; + +/* OMX_TIME_CONFIG_TIMESTAMPTYPE timestamp; + memset(×tamp, 0, sizeof(timestamp)); + timestamp.nSize = sizeof(timestamp); + timestamp.nVersion.nVersion = OMX_VERSION; + + if (OMX_GetConfig(ILC_GET_HANDLE(m_omx->comp[cOmx::eClock]), + OMX_IndexConfigTimeCurrentMediaTime, ×tamp) != OMX_ErrorNone) + esyslog("rpihddevice: failed get current clock reference!"); + else + stc = cOmx::TicksToPts(timestamp.nTimestamp); + + dsyslog("rpihddevice: GetSTC() = %llu", stc); +*/ + return stc; +} + +void cOmxDevice::Play(void) +{ + dsyslog("rpihddevice: Play()"); + +} + +bool cOmxDevice::Flush(int TimeoutMs) +{ + dsyslog("rpihddevice: flush()"); + + return true; +} + +void cOmxDevice::MakePrimaryDevice(bool On) +{ + if (On && m_onPrimaryDevice) + m_onPrimaryDevice(); + cDevice::MakePrimaryDevice(On); +} + +void cOmxDevice::HandleEndOfStream(unsigned int portId) +{ + dsyslog("rpihddevice: HandleEndOfStream(%d)", portId); + + switch (portId) + { + case 131: + break; + + case 11: + break; + + case 90: + m_eosEvent->Signal(); + break; + } +} + +void cOmxDevice::HandlePortSettingsChanged(unsigned int portId) +{ + dsyslog("rpihddevice: HandlePortSettingsChanged(%d)", portId); + + switch (portId) + { + case 131: + if (m_state == ePlay) + { + if (ilclient_setup_tunnel(&m_omx->tun[cOmx::eVideoDecoderToVideoScheduler], 0, 0) != 0) + esyslog("rpihddevice: failed to setup up tunnel from video decoder to video scheduler!"); + if (ilclient_change_component_state(m_omx->comp[cOmx::eVideoScheduler], OMX_StateExecuting) != 0) + esyslog("rpihddevice: failed to enable video scheduler!"); + } + else + esyslog("HandlePortSettingsChanged: a"); + break; + + case 11: + if (m_state == ePlay) + { + if (ilclient_setup_tunnel(&m_omx->tun[cOmx::eVideoSchedulerToVideoRender], 0, 1000) != 0) + esyslog("rpihddevice: failed to setup up tunnel from scheduler to render!"); + if (ilclient_change_component_state(m_omx->comp[cOmx::eVideoRender], OMX_StateExecuting) != 0) + esyslog("rpihddevice: failed to enable video render!"); + } + else + esyslog("HandlePortSettingsChanged: b"); + break; + } +} + +void cOmxDevice::SetClockState(eClockState clockState) +{ + dsyslog("rpihddevice: SetClockState(%s)", + clockState == eClockStateRunning ? "eClockStateRunning" : + clockState == eClockStateStopped ? "eClockStateStopped" : + clockState == eClockStateWaiting ? "eClockStateWaiting" : "unknown"); + + OMX_TIME_CONFIG_CLOCKSTATETYPE cstate; + memset(&cstate, 0, sizeof(cstate)); + cstate.nSize = sizeof(cstate); + cstate.nVersion.nVersion = OMX_VERSION; + + if (OMX_GetConfig(ILC_GET_HANDLE(m_omx->comp[cOmx::eClock]), OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) + esyslog("rpihddevice: failed get clock state!"); + + if ((clockState == eClockStateWaiting) && (cstate.eState == OMX_TIME_ClockStateRunning)) + { + esyslog("rpihddevice: need to disable clock first!"); + cstate.eState = OMX_TIME_ClockStateStopped; + if (OMX_SetConfig(ILC_GET_HANDLE(m_omx->comp[cOmx::eClock]), OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) + esyslog("rpihddevice: failed set clock state!"); + } + + switch (clockState) + { + case eClockStateRunning: + cstate.eState = OMX_TIME_ClockStateRunning; + break; + + case eClockStateStopped: + cstate.eState = OMX_TIME_ClockStateStopped; + break; + + case eClockStateWaiting: + cstate.eState = OMX_TIME_ClockStateWaitingForStartTime; + cstate.nWaitMask = OMX_CLOCKPORT0 | OMX_CLOCKPORT1; + break; + } + + if (OMX_SetConfig(ILC_GET_HANDLE(m_omx->comp[cOmx::eClock]), OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) + esyslog("rpihddevice: failed set clock state!"); +} + +void cOmxDevice::SetClockScale(int scale) +{ + OMX_TIME_CONFIG_SCALETYPE scaleType; + memset(&scaleType, 0, sizeof(scaleType)); + scaleType.xScale = scale; + if (OMX_SetConfig(ILC_GET_HANDLE(m_omx->comp[cOmx::eClock]), OMX_IndexConfigTimeScale, &scaleType) != OMX_ErrorNone) + esyslog("rpihddevice: failed configuring clock!"); +} + +int cOmxDevice::OmxInit() +{ + dsyslog("OmxInit()"); + + m_omx->client = ilclient_init(); + if (m_omx->client == NULL) + esyslog("rpihddevice: ilclient_init() failed!"); + + if (OMX_Init() != OMX_ErrorNone) + esyslog("rpihddevice: OMX_Init() failed!"); + + ilclient_set_error_callback(m_omx->client, cOmx::OmxError, this); + ilclient_set_empty_buffer_done_callback(m_omx->client, cOmx::OmxBufferEmpty, this); + ilclient_set_port_settings_callback(m_omx->client, cOmx::OmxPortSettingsChanged, this); + ilclient_set_eos_callback(m_omx->client, cOmx::OmxEndOfStream, this); + + // create video_decode + if (ilclient_create_component(m_omx->client, &m_omx->comp[cOmx::eVideoDecoder], + "video_decode", (ILCLIENT_CREATE_FLAGS_T)(ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS)) != 0) + esyslog("rpihddevice: failed creating video decoder!"); + + // create video_render + if (ilclient_create_component(m_omx->client, &m_omx->comp[cOmx::eVideoRender], + "video_render", ILCLIENT_DISABLE_ALL_PORTS) != 0) + esyslog("rpihddevice: failed creating video render!"); + + //create clock + if (ilclient_create_component(m_omx->client, &m_omx->comp[cOmx::eClock], + "clock", ILCLIENT_DISABLE_ALL_PORTS) != 0) + esyslog("rpihddevice: failed creating clock!"); + + // create audio_render + if (ilclient_create_component(m_omx->client, &m_omx->comp[cOmx::eAudioRender], + "audio_render", (ILCLIENT_CREATE_FLAGS_T)(ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS)) != 0) + esyslog("rpihddevice: failed creating audio render!"); + + //create video_scheduler + if (ilclient_create_component(m_omx->client, &m_omx->comp[cOmx::eVideoScheduler], + "video_scheduler", ILCLIENT_DISABLE_ALL_PORTS) != 0) + esyslog("rpihddevice: failed creating video scheduler!"); + + // setup tunnels + set_tunnel(&m_omx->tun[cOmx::eVideoDecoderToVideoScheduler], + m_omx->comp[cOmx::eVideoDecoder], 131, m_omx->comp[cOmx::eVideoScheduler], 10); + + set_tunnel(&m_omx->tun[cOmx::eVideoSchedulerToVideoRender], + m_omx->comp[cOmx::eVideoScheduler], 11, m_omx->comp[cOmx::eVideoRender], 90); + + set_tunnel(&m_omx->tun[cOmx::eClockToVideoScheduler], + m_omx->comp[cOmx::eClock], 80, m_omx->comp[cOmx::eVideoScheduler], 12); + + set_tunnel(&m_omx->tun[cOmx::eClockToAudioRender], + m_omx->comp[cOmx::eClock], 81, m_omx->comp[cOmx::eAudioRender], 101); + + // setup clock tunnels first + if (ilclient_setup_tunnel(&m_omx->tun[cOmx::eClockToVideoScheduler], 0, 0) != 0) + esyslog("rpihddevice: failed to setup up tunnel from clock to video scheduler!"); + + if (ilclient_setup_tunnel(&m_omx->tun[cOmx::eClockToAudioRender], 0, 0) != 0) + esyslog("rpihddevice: failed to setup up tunnel from clock to audio render!"); + + OMX_TIME_CONFIG_ACTIVEREFCLOCKTYPE refclock; + memset(&refclock, 0, sizeof(refclock)); + refclock.nSize = sizeof(refclock); + refclock.nVersion.nVersion = OMX_VERSION; + refclock.eClock = OMX_TIME_RefClockAudio; +// refclock.eClock = OMX_TIME_RefClockVideo; + + if (OMX_SetConfig(ILC_GET_HANDLE(m_omx->comp[cOmx::eClock]), OMX_IndexConfigTimeActiveRefClock, &refclock) != OMX_ErrorNone) + esyslog("rpihddevice: failed set active clock reference!"); + + ilclient_change_component_state(m_omx->comp[cOmx::eClock], OMX_StateExecuting); + ilclient_change_component_state(m_omx->comp[cOmx::eVideoDecoder], OMX_StateIdle); + + OMX_PARAM_PORTDEFINITIONTYPE param; + memset(¶m, 0, sizeof(param)); + param.nSize = sizeof(param); + param.nVersion.nVersion = OMX_VERSION; + param.nPortIndex = 100; + if (OMX_GetParameter(ILC_GET_HANDLE(m_omx->comp[cOmx::eAudioRender]), OMX_IndexParamPortDefinition, ¶m) != OMX_ErrorNone) + esyslog("rpihddevice: failed to get audio render port parameters!"); + + // set up the number and size of buffers + param.nBufferSize = 65536; + param.nBufferSize = (param.nBufferSize + 15) & ~15; // 16 byte aligned + param.nBufferCountActual = 16; + if (OMX_SetParameter(ILC_GET_HANDLE(m_omx->comp[cOmx::eAudioRender]), OMX_IndexParamPortDefinition, ¶m) != OMX_ErrorNone) + esyslog("rpihddevice: failed to get audio render port parameters!"); + + // configure audio render + OMX_AUDIO_PARAM_PCMMODETYPE pcmMode; + memset(&pcmMode, 0, sizeof(pcmMode)); + pcmMode.nSize = sizeof(pcmMode); + pcmMode.nVersion.nVersion = OMX_VERSION; + pcmMode.nPortIndex = 100; + pcmMode.nChannels = 2; + pcmMode.eNumData = OMX_NumericalDataSigned; + pcmMode.eEndian = OMX_EndianLittle; + pcmMode.bInterleaved = OMX_TRUE; + pcmMode.nBitPerSample = 16; + pcmMode.nSamplingRate = 48000; + pcmMode.ePCMMode = OMX_AUDIO_PCMModeLinear; + pcmMode.eChannelMapping[0] = OMX_AUDIO_ChannelLF; + pcmMode.eChannelMapping[1] = OMX_AUDIO_ChannelRF; + + if (OMX_SetParameter(ILC_GET_HANDLE(m_omx->comp[cOmx::eAudioRender]), OMX_IndexParamAudioPcm, &pcmMode) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set audio render parameters!"); + + OMX_CONFIG_BRCMAUDIODESTINATIONTYPE audioDest; + memset(&audioDest, 0, sizeof(audioDest)); + audioDest.nSize = sizeof(audioDest); + audioDest.nVersion.nVersion = OMX_VERSION; + strcpy((char *)audioDest.sName, "local"); + + if (OMX_SetConfig(ILC_GET_HANDLE(m_omx->comp[cOmx::eAudioRender]), OMX_IndexConfigBrcmAudioDestination, &audioDest) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set audio destination!"); + + return 0; +} + +void cOmxDevice::Start() +{ + dsyslog("rpihddevice: Start()"); + m_mutex->Lock(); + + m_state = ePlay; + m_firstVideoPacket = true; + m_firstAudioPacket = true; + + SetClockState(eClockStateWaiting); + + m_mutex->Unlock(); + dsyslog("rpihddevice: Start() end"); +} + +void cOmxDevice::Stop() +{ + dsyslog("rpihddevice: Stop()"); + m_mutex->Lock(); + + m_state = eStop; +/* + OMX_BUFFERHEADERTYPE *buf = ilclient_get_input_buffer(m_omx->comp[cOmx::eVideoDecoder], 130, 1); + if (buf == NULL) + return; + + buf->nFilledLen = 0; + buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN | OMX_BUFFERFLAG_EOS; + + if (OMX_EmptyThisBuffer(ILC_GET_HANDLE(m_omx->comp[cOmx::eVideoDecoder]), buf) != OMX_ErrorNone) + esyslog("rpihddevice: failed to send empty packet to video decoder!"); + + if (!m_eosEvent->Wait(10000)) + esyslog("rpihddevice: time out waiting for EOS event!"); +*/ + // need to flush the renderer to allow video_decode to disable its input port + + // put video decoder into idle + ilclient_change_component_state(m_omx->comp[cOmx::eVideoDecoder], OMX_StateIdle); + + // put video scheduler into idle + ilclient_flush_tunnels(&m_omx->tun[cOmx::eVideoDecoderToVideoScheduler], 1); + ilclient_disable_tunnel(&m_omx->tun[cOmx::eVideoDecoderToVideoScheduler]); + ilclient_flush_tunnels(&m_omx->tun[cOmx::eClockToVideoScheduler], 1); + ilclient_disable_tunnel(&m_omx->tun[cOmx::eClockToVideoScheduler]); + ilclient_change_component_state(m_omx->comp[cOmx::eVideoScheduler], OMX_StateIdle); + + // put video render into idle + ilclient_flush_tunnels(&m_omx->tun[cOmx::eVideoSchedulerToVideoRender], 1); + ilclient_disable_tunnel(&m_omx->tun[cOmx::eVideoSchedulerToVideoRender]); + ilclient_change_component_state(m_omx->comp[cOmx::eVideoRender], OMX_StateIdle); + + // put audio render onto idle + ilclient_flush_tunnels(&m_omx->tun[cOmx::eClockToAudioRender], 1); + ilclient_disable_tunnel(&m_omx->tun[cOmx::eClockToAudioRender]); + ilclient_change_component_state(m_omx->comp[cOmx::eAudioRender], OMX_StateIdle); + + // disable port buffers and allow video decoder and audio render to reconfig + ilclient_disable_port_buffers(m_omx->comp[cOmx::eVideoDecoder], 130, NULL, NULL, NULL); + ilclient_disable_port_buffers(m_omx->comp[cOmx::eAudioRender], 100, NULL, NULL, NULL); + + SetClockState(eClockStateStopped); + + m_mutex->Unlock(); + dsyslog("rpihddevice: Stop() end"); +} + +void cOmxDevice::OmxSetVideoCodec(const uchar *data) +{ + dsyslog("rpihddevice: OmxSetVideoCodec()"); + + // configure video decoder + OMX_VIDEO_PARAM_PORTFORMATTYPE videoFormat; + memset(&videoFormat, 0, sizeof(videoFormat)); + videoFormat.nSize = sizeof(videoFormat); + videoFormat.nVersion.nVersion = OMX_VERSION; + videoFormat.nPortIndex = 130; + videoFormat.eCompressionFormat = OMX_VIDEO_CodingMPEG2; + + if (OMX_SetParameter(ILC_GET_HANDLE(m_omx->comp[cOmx::eVideoDecoder]), OMX_IndexParamVideoPortFormat, &videoFormat) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set video decoder parameters!"); + + if (ilclient_enable_port_buffers(m_omx->comp[cOmx::eVideoDecoder], 130, NULL, NULL, NULL) != 0) + esyslog("rpihddevice: failed to enable port buffer on video decoder!"); + + if (ilclient_change_component_state(m_omx->comp[cOmx::eVideoDecoder], OMX_StateExecuting) != 0) + esyslog("rpihddevice: failed to set video decoder to executing state!"); + + // setup clock tunnels first + if (ilclient_setup_tunnel(&m_omx->tun[cOmx::eClockToVideoScheduler], 0, 0) != 0) + esyslog("rpihddevice: failed to setup up tunnel from clock to video scheduler!"); + + dsyslog("rpihddevice: OmxSetVideoCodec() end"); +} + +void cOmxDevice::OmxSetAudioCodec(const uchar *data) +{ + dsyslog("rpihddevice: OmxSetAudioCodec()"); + + if (ilclient_enable_port_buffers(m_omx->comp[cOmx::eAudioRender], 100, NULL, NULL, NULL) != 0) + esyslog("rpihddevice: failed to enable port buffer on audio render!"); + + ilclient_change_component_state(m_omx->comp[cOmx::eAudioRender], OMX_StateExecuting); + + if (ilclient_setup_tunnel(&m_omx->tun[cOmx::eClockToAudioRender], 0, 0) != 0) + esyslog("rpihddevice: failed to setup up tunnel from clock to video scheduler!"); + + dsyslog("rpihddevice: OmxSetAudioCodec() end"); +} + +int cOmxDevice::OmxDeInit() +{ + dsyslog("rpihddevice: OmxDeInit()"); + + // need to flush the renderer to allow video_decode to disable its input port +// ilclient_flush_tunnels(m_omx->tun, 0); + +// ilclient_disable_tunnel(&m_omx->tun[cOmx::eVideoDecoderToVideoScheduler]); +// ilclient_disable_tunnel(&m_omx->tun[cOmx::eVideoSchedulerToVideoRender]); +// ilclient_disable_tunnel(&m_omx->tun[cOmx::eClockToVideoScheduler]); +// ilclient_disable_tunnel(&m_omx->tun[cOmx::eClockToAudioRender]); + + ilclient_teardown_tunnels(m_omx->tun); + + ilclient_state_transition(m_omx->comp, OMX_StateIdle); + ilclient_state_transition(m_omx->comp, OMX_StateLoaded); + + ilclient_cleanup_components(m_omx->comp); + + OMX_Deinit(); + ilclient_destroy(m_omx->client); + + return 0; +} + +void cOmxDevice::GetDisplaySize(int &width, int &height, double &aspect) +{ + uint32_t screenWidth; + uint32_t screenHeight; + + if (graphics_get_display_size(0 /* LCD */, &screenWidth, &screenHeight) < 0) + esyslog("rpihddevice: failed to get display size!"); + else + { + width = (int)screenWidth; + height = (int)screenHeight; + aspect = 1; + } +} + + + diff --git a/omxdevice.h b/omxdevice.h new file mode 100644 index 0000000..a06d822 --- /dev/null +++ b/omxdevice.h @@ -0,0 +1,90 @@ +/* + * 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 <vdr/thread.h> + +class cOmx; +class cAudio; + +class cOmxDevice : cDevice +{ + +public: + + enum eState { + eStop, + ePlay + }; + + enum eClockState { + eClockStateRunning, + eClockStateStopped, + eClockStateWaiting + }; + + cOmxDevice(void (*onPrimaryDevice)(void)); + virtual ~cOmxDevice(); + + virtual bool HasDecoder(void) const { return true; }; + + virtual bool SetPlayMode(ePlayMode PlayMode); + + virtual bool CanReplay(void) const; + + virtual int PlayVideo(const uchar *Data, int Length); + virtual int PlayAudio(const uchar *Data, int Length, uchar Id); + + virtual int64_t GetSTC(void); + + virtual void Play(void); + + virtual bool Flush(int TimeoutMs = 0); + + virtual void HandlePortSettingsChanged(unsigned int portId); + virtual void HandleEndOfStream(unsigned int portId); + + virtual int OmxInit(); + virtual int OmxDeInit(); + + virtual void GetOsdSize(int &Width, int &Height, double &PixelAspect) + { cOmxDevice::GetDisplaySize(Width, Height, PixelAspect); } + + static void GetDisplaySize(int &width, int &height, double &aspect); + +protected: + + virtual void MakePrimaryDevice(bool On); + +private: + + void (*m_onPrimaryDevice)(void); + + virtual void Start(void); + virtual void Stop(void); + + virtual void SetClockScale(int scale); + virtual void SetClockState(eClockState clockState); + + virtual void OmxSetVideoCodec(const uchar *data); + virtual void OmxSetAudioCodec(const uchar *data); + + cOmx *m_omx; + cAudio *m_audio; + cCondWait *m_eosEvent; + cMutex *m_mutex; + + eState m_state; + + bool m_firstVideoPacket; + bool m_firstAudioPacket; + +}; + +#endif diff --git a/ovgosd.c b/ovgosd.c new file mode 100644 index 0000000..2e472f2 --- /dev/null +++ b/ovgosd.c @@ -0,0 +1,297 @@ +/* + * See the README file for copyright information and how to reach the author. + * + * $Id$ + */ + +#include <VG/openvg.h> +#include <VG/vgu.h> +#include <EGL/egl.h> +#include <GLES/gl.h> + +#include "ovgosd.h" +#include "omxdevice.h" + +class cOvg : public cThread +{ +public: + + cOvg() : + cThread(), + m_do(0), + m_done(0), + m_mutex(0), + m_width(0), + m_height(0), + m_pixmap(0), + m_aspect(0), + m_d(0), + m_x(0), + m_y(0), + m_w(0), + m_h(0), + m_clear(false) + { + cOmxDevice::GetDisplaySize(m_width, m_height, m_aspect); + + m_do = new cCondWait(); + m_done = new cCondWait(); + m_mutex = new cMutex(); + + Start(); + } + + ~cOvg() + { + Cancel(-1); + Clear(); + + delete m_do; + delete m_done; + delete m_mutex; + } + + void GetDisplaySize(int &width, int &height, double &aspect) + { + width = m_width; + height = m_height; + aspect = m_aspect; + } + + void DrawPixmap(int x, int y, int w, int h, int d, const uint8_t *data) + { + m_mutex->Lock(); + m_pixmap = data; + m_d = d; + m_x = x; + m_y = y; + m_w = w; + m_h = h; + m_do->Signal(); + m_done->Wait(); + m_mutex->Unlock(); + } + + void Clear() + { + m_mutex->Lock(); + m_clear = true; + m_do->Signal(); + m_done->Wait(); + m_mutex->Unlock(); + } + +protected: + + virtual void Action(void) + { + dsyslog("rpihddevice: cOvg() thread started"); + + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) + esyslog("rpihddevice: failed to get EGL display connection!"); + + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) + esyslog("rpihddevice: failed to init EGL display connection!"); + + eglBindAPI(EGL_OPENVG_API); + + const EGLint fbAttr[] = { + 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 + }; + + EGLConfig config; + EGLint numConfig; + + // get an appropriate EGL frame buffer configuration + if (eglChooseConfig(display, fbAttr, &config, 1, &numConfig) == EGL_FALSE) + esyslog("rpihddevice: failed to get EGL frame buffer config!"); + + // create an EGL rendering context + EGLContext context = eglCreateContext(display, config, NULL, NULL); + if (context == EGL_NO_CONTEXT) + esyslog("rpihddevice: failed to create EGL rendering context!"); + + DISPMANX_DISPLAY_HANDLE_T dispmanDisplay = vc_dispmanx_display_open(0 /* LCD */); + DISPMANX_UPDATE_HANDLE_T dispmanUpdate = vc_dispmanx_update_start(0); + + VC_RECT_T dstRect = { 0, 0, m_width, m_height }; + VC_RECT_T srcRect = { 0, 0, m_width << 16, m_height << 16 }; + + DISPMANX_ELEMENT_HANDLE_T dispmanElement = vc_dispmanx_element_add( + dispmanUpdate, dispmanDisplay, 2, &dstRect, 0, &srcRect, + DISPMANX_PROTECTION_NONE, 0, 0, (DISPMANX_TRANSFORM_T)0); + + vc_dispmanx_update_submit_sync(dispmanUpdate); + + EGL_DISPMANX_WINDOW_T nativewindow; + nativewindow.element = dispmanElement; + nativewindow.width = m_width; + nativewindow.height = m_height; + + const EGLint windowAttr[] = { + EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER, + EGL_NONE + }; + + EGLSurface surface = eglCreateWindowSurface(display, config, &nativewindow, windowAttr); + if (surface == EGL_NO_SURFACE) + esyslog("rpihddevice: failed to create EGL window surface!"); + + // connect the context to the surface + if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) + esyslog("rpihddevice: failed to connect context to surface!"); + + float color[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + vgSetfv(VG_CLEAR_COLOR, 4, color); + vgClear(0, 0, m_width, m_height); + eglSwapBuffers(display, surface); + + 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(); + vgScale(1.0f, -1.0f); + vgTranslate(0.0f, -m_height); + + VGImage image = vgCreateImage(VG_sARGB_8888, m_width, m_height, VG_IMAGE_QUALITY_BETTER); + + while (Running()) + { + m_do->Wait(); + if (m_pixmap) + { + vgClearImage(image, m_x, m_y, m_w, m_h); + vgImageSubData(image, m_pixmap, m_d, VG_sARGB_8888, m_x, m_y, m_w, m_h); + vgDrawImage(image); + m_pixmap = 0; + } + if (m_clear) + { + vgClearImage(image, 0, 0, m_width, m_height); + vgDrawImage(image); + m_clear = false; + } + eglSwapBuffers(display, surface); + m_done->Signal(); + } + + vgDestroyImage(image); + + // clear screen + glClear(GL_COLOR_BUFFER_BIT); + eglSwapBuffers(display, surface); + + // Release OpenGL resources + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(display, surface); + eglDestroyContext(display, context); + eglTerminate(display); + + dsyslog("rpihddevice: cOvg() thread ended"); + } + +private: + + cCondWait* m_do; + cCondWait* m_done; + cMutex* m_mutex; + + int m_width; + int m_height; + double m_aspect; + + const uint8_t *m_pixmap; + int m_d; + int m_x; + int m_y; + int m_w; + int m_h; + + bool m_clear; + +}; + +cRpiOsdProvider::cRpiOsdProvider() : + cOsdProvider(), + m_ovg(0) +{ + dsyslog("rpihddevice: new cOsdProvider()"); + m_ovg = new cOvg(); +} + +cRpiOsdProvider::~cRpiOsdProvider() +{ + dsyslog("rpihddevice: delete cOsdProvider()"); + delete m_ovg; +} + +cOsd *cRpiOsdProvider::CreateOsd(int Left, int Top, uint Level) +{ + return new cOvgOsd(Left, Top, Level, m_ovg); +} + +cOvgOsd::cOvgOsd(int Left, int Top, uint Level, cOvg *ovg) : + cOsd(Left, Top, Level), + m_ovg(ovg) +{ +} + +cOvgOsd::~cOvgOsd() +{ + m_ovg->Clear(); +} + +void cOvgOsd::Flush(void) +{ + if (IsTrueColor()) + { + LOCK_PIXMAPS; + + while (cPixmapMemory *pm = RenderPixmaps()) { + int w = pm->ViewPort().Width(); + int h = pm->ViewPort().Height(); + int d = w * sizeof(tColor); + m_ovg->DrawPixmap( + Left() + pm->ViewPort().X(), Top() + pm->ViewPort().Y(), + pm->ViewPort().Width(), pm->ViewPort().Height(), + pm->ViewPort().Width() * sizeof(tColor), pm->Data()); + delete pm; + } + + return; + } + + 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; + uint8_t *argb = (uint8_t *) malloc(w * h * sizeof(uint32_t)); + + for (int y = y1; y <= y2; ++y) + { + for (int x = x1; x <= x2; ++x) + { + ((uint32_t *) argb)[x - x1 + (y - y1) * w] = + bitmap->GetColor(x, y); + } + } + m_ovg->DrawPixmap(Left() + bitmap->X0() + x1, + Top() + bitmap->Y0() + y1, w, h, w * sizeof(tColor), argb); + bitmap->Clean(); + free(argb); + } + } +} + diff --git a/ovgosd.h b/ovgosd.h new file mode 100644 index 0000000..d4a7c0d --- /dev/null +++ b/ovgosd.h @@ -0,0 +1,52 @@ +/* + * 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 cOvg; + +class cRpiOsdProvider : public cOsdProvider +{ + +public: + + cRpiOsdProvider(); + ~cRpiOsdProvider(); + +protected: + + virtual cOsd *CreateOsd(int Left, int Top, uint Level); + virtual bool ProvidesTrueColor(void) { return true; } + virtual int StoreImageData(const cImage &Image) { return 0; } + virtual void DropImageData(int ImageHandle) {} + +private: + + cOvg *m_ovg; +}; + + +class cOvgOsd : public cOsd +{ + +public: + + cOvgOsd(int Left, int Top, uint Level, cOvg *ovg); + virtual ~cOvgOsd(); + + virtual void Flush(void); + +private: + + cOvg *m_ovg; + +}; + +#endif + diff --git a/rpihddevice.c b/rpihddevice.c new file mode 100644 index 0000000..b66af7e --- /dev/null +++ b/rpihddevice.c @@ -0,0 +1,98 @@ +/* + * 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 "bcm_host.h" + +static const char *VERSION = "0.0.1"; +static const char *DESCRIPTION = "HD output device for Raspberry Pi"; + +class cDummyDevice : cDevice +{ + +public: + + cDummyDevice() { } + virtual ~cDummyDevice() { } + virtual bool HasDecoder(void) const { return true; } + virtual bool SetPlayMode(ePlayMode PlayMode) { return true; } + virtual int PlayVideo(const uchar *Data, int Length) { return Length; } + virtual int PlayAudio(const uchar *Data, int Length, uchar Id) { return Length; } + virtual bool Poll(cPoller &Poller, int TimeoutMs = 0) { return true; } + virtual bool Flush(int TimeoutMs = 0) { return true; } + virtual void GetOsdSize(int &Width, int &Height, double &PixelAspect) + { cOmxDevice::GetDisplaySize(Width, Height, PixelAspect); } + bool Start(void) {return true;} + +protected: + virtual void MakePrimaryDevice(bool On) { if (On) new cRpiOsdProvider(); } + +}; + +class cPluginRpiHdDevice : public cPlugin +{ +private: + + cOmxDevice *m_device; + + static void OnPrimaryDevice() { new cRpiOsdProvider(); } + +public: + cPluginRpiHdDevice(void); + virtual ~cPluginRpiHdDevice(); + virtual const char *Version(void) { return VERSION; } + virtual const char *Description(void) { return DESCRIPTION; } + virtual const char *CommandLineHelp(void) { return NULL; } + virtual bool ProcessArgs(int argc, char *argv[]) { return true; } + 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) { return NULL; } + virtual bool SetupParse(const char *Name, const char *Value) { return false; }; +}; + +cPluginRpiHdDevice::cPluginRpiHdDevice(void) : + m_device(0) +{ + bcm_host_init(); + + m_device = new cOmxDevice(&cPluginRpiHdDevice::OnPrimaryDevice); + //new cDummyDevice(); +} + +cPluginRpiHdDevice::~cPluginRpiHdDevice() +{ + delete m_device; +} + +bool cPluginRpiHdDevice::Initialize(void) +{ + if (m_device) + return (m_device->OmxInit() == 0); + + return true; +} + +bool cPluginRpiHdDevice::Start(void) +{ + return true; +} + +void cPluginRpiHdDevice::Stop(void) +{ + if (m_device) + m_device->OmxDeInit(); +} + +VDRPLUGINCREATOR(cPluginRpiHdDevice); // Don't touch this! |