diff --git a/.gitignore b/.gitignore index 75fce5654357e86b1752ff9999a8f8bfb062a076..53cb0216899fc56d433bad65e0e6c358928419fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +libs/lib/Frameworks/GStreamer.framework *.swp *.nfs CMakeFiles diff --git a/QGCApplication.pro b/QGCApplication.pro index 61d8f15d3f3ea04bd1ee289b19a60753e4c1ee48..a91292bd982faa74bb0470b71d4b0a26dd096a30 100644 --- a/QGCApplication.pro +++ b/QGCApplication.pro @@ -122,18 +122,6 @@ include(libs/qextserialport/src/qextserialport.pri) include(QGCExternalLibs.pri) -# -# Post link configuration -# - -include(QGCSetup.pri) - -# -# Installer targets -# - -include(QGCInstaller.pri) - # # Main QGroundControl portion of project file # @@ -702,3 +690,16 @@ AndroidBuild { $$PWD/android/src/org/qgroundcontrol/qgchelper/UsbDeviceJNI.java \ $$PWD/android/src/org/qgroundcontrol/qgchelper/UsbIoManager.java } + +#------------------------------------------------------------------------------------- +# +# Post link configuration +# + +include(QGCSetup.pri) + +# +# Installer targets +# + +include(QGCInstaller.pri) diff --git a/QGCInstaller.pri b/QGCInstaller.pri index 220dc563e88ba3415ce54d45b8dcfb6d04dc28eb..77ebc2d27a969fe7c35f7b8b745e2d1cb22b2f39 100644 --- a/QGCInstaller.pri +++ b/QGCInstaller.pri @@ -19,6 +19,19 @@ installer { MacBuild { + VideoEnabled { + # Install the gstreamer framework + # This will: + # Copy from the original distibution into libs/lib/Framworks (if not already there) + # Prune the framework, removing stuff we don't need + # Relocate all dylibs so they can work under @executable_path/... + # Copy the result into the app bundle + # Make sure qgroundcontrol can find them + message("Preparing GStreamer Framework") + QMAKE_POST_LINK += && $$BASEDIR/tools/prepare_gstreamer_framework.sh $$BASEDIR/libs/lib/Frameworks/ $$DESTDIR/$${TARGET}.app $${TARGET} + } else { + message("Skipping GStreamer Framework") + } # We cd to release directory so we can run macdeployqt without a path to the # qgroundcontrol.app file. If you specify a path to the .app file the symbolic # links to plugins will not be created correctly. @@ -27,7 +40,6 @@ installer { QMAKE_POST_LINK += && cd .. QMAKE_POST_LINK += && hdiutil create -layout SPUD -srcfolder $${DESTDIR}/qgroundcontrol.app -volname QGroundControl $${DESTDIR}/qgroundcontrol.dmg } - WindowsBuild { # The pdb moving command are commented out for now since we are including the .pdb in the installer. This makes it much # easier to debug user crashes. diff --git a/QGCSetup.pri b/QGCSetup.pri index bad8f2dc18a81d667499fb77e880b8ea7072692b..799f09f6a73b2387060693df894efe64c34a606f 100644 --- a/QGCSetup.pri +++ b/QGCSetup.pri @@ -55,9 +55,10 @@ WindowsBuild { # MacBuild { + # Copy non-standard libraries and frameworks into app package QMAKE_POST_LINK += && $$QMAKE_COPY_DIR $$BASEDIR/libs/lib/mac64/lib $$DESTDIR/$${TARGET}.app/Contents/libs - QMAKE_POST_LINK += && $$QMAKE_COPY_DIR -L $$BASEDIR/libs/lib/Frameworks $$DESTDIR/$${TARGET}.app/Contents + QMAKE_POST_LINK += && rsync -a --delete $$BASEDIR/libs/lib/Frameworks $$DESTDIR/$${TARGET}.app/Contents/ # Fix library paths inside executable diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc index f6da705d3072fafc77e9d9810925ca330c9ad550..87897a4b6d3f7bbbe16d95a8761d3f3c696041da 100644 --- a/src/QGCApplication.cc +++ b/src/QGCApplication.cc @@ -141,6 +141,11 @@ static QObject* mavManagerSingletonFactory(QQmlEngine*, QJSEngine*) * @param argv The string array of parameters **/ +static void qgcputenv(const QString& key, const QString& root, const QString& path) +{ + QString value = root + path; + qputenv(key.toStdString().c_str(), QByteArray(value.toStdString().c_str())); +} QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting) : QApplication(argc, argv) @@ -266,8 +271,28 @@ QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting) //---------------------------------------------------------------- //-- Video Streaming +#if defined(QGC_GST_STREAMING) +#ifdef Q_OS_MAC +#ifndef __ios__ + QString currentDir = QCoreApplication::applicationDirPath(); + qgcputenv("GST_PLUGIN_SCANNER", currentDir, "/gst-plugin-scanner"); + qgcputenv("GTK_PATH", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current"); + qgcputenv("GIO_EXTRA_MODULES", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gio/modules"); + qgcputenv("GST_PLUGIN_SYSTEM_PATH_1_0", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0"); + qgcputenv("GST_PLUGIN_SYSTEM_PATH", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0"); + qgcputenv("GST_PLUGIN_PATH_1_0", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0"); + qgcputenv("GST_PLUGIN_PATH", currentDir, "/../Frameworks/GStreamer.framework/Versions/Current/lib/gstreamer-1.0"); +// QStringList env = QProcessEnvironment::systemEnvironment().keys(); +// foreach(QString key, env) { +// qDebug() << key << QProcessEnvironment::systemEnvironment().value(key); +// } +#endif +#endif +#endif + qmlRegisterType("QGroundControl.QgcQtGStreamer", 1, 0, "VideoItem"); qmlRegisterUncreatableType("QGroundControl.QgcQtGStreamer", 1, 0, "VideoSurface", QLatin1String("VideoSurface from QML is not supported")); + #if defined(QGC_GST_STREAMING) GError* error = NULL; if (!gst_init_check(&argc, &argv, &error)) { diff --git a/src/VideoStreaming/VideoReceiver.cc b/src/VideoStreaming/VideoReceiver.cc index 23030afe51a79d35510bd724027fa6e4a9eef1fd..72ed9b72a7600cc2dd70914a633b855db68a428c 100644 --- a/src/VideoStreaming/VideoReceiver.cc +++ b/src/VideoStreaming/VideoReceiver.cc @@ -87,34 +87,41 @@ void VideoReceiver::start() do { if ((_pipeline = gst_pipeline_new("receiver")) == NULL) { + qCritical() << "VideoReceiver::start() failed. Error with gst_pipeline_new()"; break; } if ((dataSource = gst_element_factory_make("udpsrc", "udp-source")) == NULL) { + qCritical() << "VideoReceiver::start() failed. Error with gst_element_factory_make('udpsrc')"; break; } if ((caps = gst_caps_from_string("application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H264")) == NULL) { + qCritical() << "VideoReceiver::start() failed. Error with gst_caps_from_string()"; break; } g_object_set(G_OBJECT(dataSource), "uri", qPrintable(_uri), "caps", caps, NULL); if ((demux = gst_element_factory_make("rtph264depay", "rtp-h264-depacketizer")) == NULL) { + qCritical() << "VideoReceiver::start() failed. Error with gst_element_factory_make('rtph264depay')"; break; } if ((parser = gst_element_factory_make("h264parse", "h264-parser")) == NULL) { + qCritical() << "VideoReceiver::start() failed. Error with gst_element_factory_make('h264parse')"; break; } if ((decoder = gst_element_factory_make("avdec_h264", "h264-decoder")) == NULL) { + qCritical() << "VideoReceiver::start() failed. Error with gst_element_factory_make('avdec_h264')"; break; } gst_bin_add_many(GST_BIN(_pipeline), dataSource, demux, parser, decoder, _videoSink, NULL); if (gst_element_link_many(dataSource, demux, parser, decoder, _videoSink, NULL) != (gboolean)TRUE) { + qCritical() << "VideoReceiver::start() failed. Error with gst_element_link_many()"; break; } diff --git a/src/VideoStreaming/VideoStreaming.pri b/src/VideoStreaming/VideoStreaming.pri index 368c9db110d82a988c3ca51f150b2ef6abbd0556..e20149bd01c9af606f0a3cb1dab80bbf02441205 100644 --- a/src/VideoStreaming/VideoStreaming.pri +++ b/src/VideoStreaming/VideoStreaming.pri @@ -27,7 +27,6 @@ LinuxBuild { CONFIG += link_pkgconfig packagesExist(gstreamer-1.0) { - message("Including support for video streaming") PKGCONFIG += gstreamer-1.0 gstreamer-video-1.0 CONFIG += VideoEnabled } @@ -35,7 +34,6 @@ LinuxBuild { #- gstreamer framework installed by the gstreamer devel installer GST_ROOT = /Library/Frameworks/GStreamer.framework exists($$GST_ROOT) { - message("Including support for video streaming") CONFIG += VideoEnabled INCLUDEPATH += $$GST_ROOT/Headers LIBS += -F/Library/Frameworks -framework GStreamer @@ -44,7 +42,6 @@ LinuxBuild { #- gstreamer framework installed by the gstreamer iOS SDK installer (default to home directory) GST_ROOT = $$(HOME)/Library/Developer/GStreamer/iPhone.sdk/GStreamer.framework exists($$GST_ROOT) { - message("Including support for video streaming") CONFIG += VideoEnabled INCLUDEPATH += $$GST_ROOT/Headers LIBS += -F$$(HOME)/Library/Developer/GStreamer/iPhone.sdk -framework GStreamer @@ -53,7 +50,6 @@ LinuxBuild { #- gstreamer installed by default under c:/gstreamer GST_ROOT = c:/gstreamer/1.0/x86 exists($$GST_ROOT) { - message("Including support for video streaming") CONFIG += VideoEnabled LIBS += -L$$GST_ROOT/lib/gstreamer-1.0/static -lgstreamer-1.0 -lgstvideo-1.0 -lgstbase-1.0 LIBS += -L$$GST_ROOT/lib -lglib-2.0 -lintl -lgobject-2.0 @@ -67,6 +63,8 @@ LinuxBuild { VideoEnabled { + message("Including support for video streaming") + DEFINES += \ QGC_GST_STREAMING \ GST_PLUGIN_BUILD_STATIC \ @@ -123,6 +121,25 @@ VideoEnabled { $$PWD/gstqtvideosink/utils/utils.cpp \ } else { - message("Skipping support for video streaming (Unsupported platform)") + LinuxBuild|MacBuild|iOSBuild|WindowsBuild { + message("Skipping support for video streaming (GStreamer libraries not installed)") + MacBuild { + message(" You can download it from http://gstreamer.freedesktop.org/data/pkg/osx/") + message(" Select the devel package and install it (gstreamer-1.0-devel-1.x.x-x86_64.pkg)") + message(" It will be installed in /Libraries/Frameworks") + } + LinuxBuild { + message(" You can install it using apt-get") + message(" sudo apt-get install gstreamer1.0*") + message(" sudo apt-get install libgstreamer1.0*") + } + WindowsBuild { + message(" You can download it from http://gstreamer.freedesktop.org/data/pkg/windows/") + message(" Select the devel AND runtime packages and install them (x86, not the 64-Bit)") + message(" It will be installed in C:/gstreamer. You need to update you PATH to point to the bin directory.") + } + } else { + message("Skipping support for video streaming (Unsupported platform)") + } } diff --git a/src/main.cc b/src/main.cc index 47fc27fca3e2aacb9518e3bffab445a3b12f0bf5..3a1a4bf584970bd95eab2349c0019306302b405d 100644 --- a/src/main.cc +++ b/src/main.cc @@ -28,12 +28,13 @@ This file is part of the QGROUNDCONTROL project * */ +#include #include #include #ifndef __mobile__ #include #endif - +#include #include "QGCApplication.h" #include "MainWindow.h" #include "configuration.h" diff --git a/tools/osxrelocator.py b/tools/osxrelocator.py new file mode 100755 index 0000000000000000000000000000000000000000..4a36687d7f5c03289987f2b1468e793a1edeef74 --- /dev/null +++ b/tools/osxrelocator.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +# cerbero - a multi-platform build system for Open Source software +# Copyright (C) 2012 Andoni Morales Alastruey +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import os +import subprocess + + +INT_CMD = 'install_name_tool' +OTOOL_CMD = 'otool' + + +def shell_call(cmd, cmd_dir='.', fail=True): + print "call", cmd + try: + ret = subprocess.check_call( + cmd, cwd=cmd_dir, + env=os.environ.copy()) + except subprocess.CalledProcessError: + if fail: + raise SystemError("Error running command: {}".format(cmd)) + else: + ret = 0 + return ret + + +def shell_check_call(cmd): + print "ccall", cmd + try: + process = subprocess.Popen( + cmd, stdout=subprocess.PIPE) + output, _ = process.communicate() + except Exception: + raise SystemError("Error running command: {}".format(cmd)) + return output + + +class OSXRelocator(object): + ''' + Wrapper for OS X's install_name_tool and otool commands to help + relocating shared libraries. + It parses lib/ /libexec and bin/ directories, changes the prefix path of + the shared libraries that an object file uses and changes it's library + ID if the file is a shared library. + ''' + + def __init__(self, root, lib_prefix, new_lib_prefix, recursive): + self.root = root + self.lib_prefix = self._fix_path(lib_prefix) + self.new_lib_prefix = self._fix_path(new_lib_prefix) + self.recursive = recursive + + def relocate(self): + self.parse_dir(self.root, filters=['', '.dylib', '.so', '0']) + + def relocate_file(self, object_file, id=None): + self.change_libs_path(object_file) + self.change_id(object_file, id) + + def change_id(self, object_file, id=None): + id = id or object_file.replace(self.lib_prefix, self.new_lib_prefix) + filename = os.path.basename(object_file) + if not (filename.endswith('so') or filename.endswith('dylib')): + return + cmd = [INT_CMD, "-id", id, object_file] + shell_call(cmd, fail=False) + + def change_libs_path(self, object_file): + for lib in self.list_shared_libraries(object_file): + if self.lib_prefix in lib: + new_lib = lib.replace(self.lib_prefix, self.new_lib_prefix) + cmd = [INT_CMD, "-change", lib, new_lib, object_file] + shell_call(cmd) + + def parse_dir(self, dir_path, filters=None): + for dirpath, dirnames, filenames in os.walk(dir_path): + for f in filenames: + if filters is not None and \ + os.path.splitext(f)[1] not in filters: + continue + fn = os.path.join(dirpath, f) + if os.path.islink(fn): + continue + if not os.path.isfile(fn): + continue + self.relocate_file(fn) + if not self.recursive: + break + + @staticmethod + def list_shared_libraries(object_file): + cmd = [OTOOL_CMD, "-L", object_file] + res = shell_check_call(cmd).split('\n') + # We don't use the first line + libs = res[1:] + # Remove the first character tabulation + libs = [x[1:] for x in libs] + # Remove the version info + libs = [x.split(' ', 1)[0] for x in libs] + return libs + + @staticmethod + def library_id_name(object_file): + cmd = [OTOOL_CMD, "-D", object_file] + res = shell_check_call(cmd).split('\n')[0] + # the library name ends with ':' + lib_name = res[:-1] + return lib_name + + def _fix_path(self, path): + if path.endswith('/'): + return path[:-1] + return path + + +class Main(object): + + def run(self): + # We use OptionParser instead of ArgumentsParse because this script + # might be run in OS X 10.6 or older, which do not provide the argparse + # module + import optparse + usage = "usage: %prog [options] directory old_prefix new_prefix" + description = 'Rellocates object files changing the dependant '\ + ' dynamic libraries location path with a new one' + parser = optparse.OptionParser(usage=usage, description=description) + parser.add_option('-r', '--recursive', action='store_true', + default=False, dest='recursive', + help='Scan directories recursively') + + options, args = parser.parse_args() + if len(args) != 3: + parser.print_usage() + exit(1) + relocator = OSXRelocator(args[0], args[1], args[2], options.recursive) + relocator.relocate() + exit(0) + +def main(): + main = Main() + main.run() + +if __name__ == "__main__": + main() + diff --git a/tools/prepare_gstreamer_framework.sh b/tools/prepare_gstreamer_framework.sh new file mode 100755 index 0000000000000000000000000000000000000000..ec848955c3776b56e4999ddc9ab67165d06a8e3c --- /dev/null +++ b/tools/prepare_gstreamer_framework.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# +# Author: Gus Grubba +# +# Copies the regular distribution of the gstreamer framework +# into the libs directory (to be used for creating the Mac OS +# bundle). It also prunes unnecessary files and relocates the +# dynamic libraries. +# +# This script is run by the build process and should not be +# executed manually. +# +# Usage: $script [framework destination path] [app bundle path] [qgc executable name] +# +# destination is usually: ../libs/lib/Frameworks/ +# app bundle is something like: /path/qgroundcontrol.app +# executable name is usually: qgroundcontrol + +die () { + echo "$@" 1>&2 + exit 1 +} + +GST_VER=1.0 +GST_ROOT=/Library/Frameworks/GStreamer.framework +GST_BASE=$GST_ROOT/Versions/$GST_VER +RELOC=$(dirname $0)/osxrelocator.py + +echo "GST Installer" +[ "$#" -eq 3 ] || die "3 arguments required, $# provided" +[ -d "$1" ] || die "Could not find $1" +[ -d "$2" ] || die "Could not find $2" + +FMWORK_TARGET=$1 +BUNDLE_TARGET=$2 +GST_SOURCE=${1%/}/GStreamer.framework +GST_TARGET=$GST_SOURCE/Versions/$GST_VER +QGC_BINARY=$BUNDLE_TARGET/Contents/MacOS/$3 +[ -e "$QGC_BINARY" ] || die "Could not find $QGC_BINARY" + +process_framework() { + #-- Start looking for the source framework + [ -d "$GST_ROOT" ] || die "Could not find gstreamer framework in $GST_ROOT" + [ -d "$GST_BASE" ] || die "Wrong version of gstreamer framework found in $GST_ROOT" + [ -e $RELOC ] || die "Cannot find $RELOC" + echo "GST Installer: Copying $GST_ROOT to $FMWORK_TARGET" + rsync -a --delete "$GST_ROOT" "$FMWORK_TARGET" || die "Error copying $GST_ROOT to $FMWORK_TARGET" + #-- Prune unused stuff + rm -rf $GST_TARGET/bin/* + rm -rf $GST_TARGET/Headers/* + rm -rf $GST_TARGET/include/* + rm -rf $GST_TARGET/lib/*.a + rm -rf $GST_TARGET/lib/*.la + rm -rf $GST_TARGET/lib/gio/modules/static + rm -rf $GST_TARGET/lib/glib-2.0 + rm -rf $GST_TARGET/lib/gst-validate-launcher + rm -rf $GST_TARGET/lib/gstreamer-1.0/static + rm -rf $GST_TARGET/lib/libffi-3.0.13 + rm -rf $GST_TARGET/lib/pkgconfig + #-- Some dylibs are dupes instead of symlinks. + #-- This will do a minimum job in trying to clean those. + #-- Doesn't work. The stupid thing can't load a dlyb symlink. + #for f in $GST_TARGET/lib/*.dylib + #do + # foo=$(basename "$f") + # bar="${foo%.*}" + # for i in `seq 0 9`; + # do + # if [ -e $GST_TARGET/lib/$bar.$i.dylib ]; then + # DUPES="$DUPES + #rm -f $GST_TARGET/lib/$bar.$i.dylib" + # DUPES="$DUPES + #ln -s $f $GST_TARGET/lib/$bar.$i.dylib" + # fi + # done + #done + #IFS=$'\n' + #for c in $DUPES + #do + # eval $c + #done + #-- Now relocate the embeded paths + echo "GST Installer: Relocating" + python $RELOC -r $GST_TARGET/lib /Library/Frameworks/GStreamer.framework/ @executable_path/../Frameworks/GStreamer.framework/ > /dev/null || die "Error relocating binaries in $GST_TARGET/lib" + python $RELOC -r $GST_TARGET/libexec /Library/Frameworks/GStreamer.framework/ @executable_path/../Frameworks/GStreamer.framework/ > /dev/null || die "Error relocating binaries in $GST_TARGET/libexec" +} + +#-- Check and see if we've already processed the framework +echo "GST Installer: Checking $GST_TARGET" +[ -d $GST_TARGET ] || process_framework +#-- Now copy the framework to the app bundle +echo "GST Installer: Copying $GST_SOURCE to $BUNDLE_TARGET/Contents/Frameworks/" +rsync -a --delete $GST_SOURCE $BUNDLE_TARGET/Contents/Frameworks/ || die "Error copying framework into app bundle" +#-- Move this gst binary to MacOS +mv $BUNDLE_TARGET/Contents/Frameworks/GStreamer.framework/Versions/1.0/libexec/gstreamer-1.0/gst-plugin-scanner $BUNDLE_TARGET/Contents/MacOS/ || die "Error moving gst-plugin-scanner" +#-- Fix main binary +python $RELOC $QGC_BINARY /Library/Frameworks/GStreamer.framework/ @executable_path/../Frameworks/GStreamer.framework/ > /dev/null || die "Error relocating $QGC_BINARY" + +