diff --git a/configure.ac b/configure.ac index 028915158e..f082b8c520 100644 --- a/configure.ac +++ b/configure.ac @@ -1197,6 +1197,13 @@ if test "x$use_dbus" != "xno"; then else AC_MSG_NOTICE($dbus_disabled) fi + #case "$host_vendor" != "apple"; determine availability of SDL or SDL2 + PKG_CHECK_MODULES([SDL2], [sdl2], + [AC_DEFINE([HAVE_SDL_VERSION],[2],["SDL major version"]) + AC_DEFINE([HAVE_SDL],[1],["Define to 1 if using sdl"]) + INCLUDES="$INCLUDES $SDL2_CFLAGS"; LIBS="$LIBS $SDL2_LIBS"], + AC_MSG_RESULT("SDL enabled") + ) fi XB_FIND_SONAME([ASS], [ass]) @@ -1225,6 +1232,16 @@ else AC_MSG_RESULT($alsa_disabled) fi +# Steam Link +AC_CHECK_HEADERS([SLVideo.h SLAudio.h], steamlink_found=yes,) +if test "$steamlink_found" = "yes"; then + USE_STEAMLINK=1 + use_pulse=no + AC_DEFINE([HAS_STEAMLINK], [], [Define if we are compiling with the Steam Link SDK]) + LIBS="$LIBS -lSLVideo -lSLAudio" +fi +AC_SUBST(USE_STEAMLINK) + # PulseAudio if test "x$use_pulse" != "xno"; then if test "$host_vendor" = "apple" ; then @@ -1615,7 +1632,7 @@ fi if test "${USE_STATIC_FFMPEG}" = "1"; then # get the libdir for static linking - FFMPEG_LIBDIR=${pkg_cfg_prefix}$(PKG_CONFIG_SYSROOT_DIR="" ${PKG_CONFIG} --static --variable=libdir libavcodec) + FFMPEG_LIBDIR=$(${PKG_CONFIG} --static --variable=libdir libavcodec) GNUTLS_ALL_LIBS=$(${PKG_CONFIG} --static --libs-only-l --silence-errors gnutls) # check if static libs are available diff --git a/system/addon-manifest.xml b/system/addon-manifest.xml index 04cce30856..dff21e9eb9 100644 --- a/system/addon-manifest.xml +++ b/system/addon-manifest.xml @@ -28,7 +28,6 @@ screensaver.xbmc.builtin.black screensaver.xbmc.builtin.dim script.module.pil - service.xbmc.versioncheck skin.estuary skin.estouchy webinterface.default diff --git a/tools/depends/.gitignore b/tools/depends/.gitignore index c6c4d2515b..339a459ed4 100644 --- a/tools/depends/.gitignore +++ b/tools/depends/.gitignore @@ -18,6 +18,7 @@ /target/*/arm-linux-gnueabihf/* /target/*/arm-linux-androideabi-*/* /target/*/arm-linux-gnueabi/* +/target/*/armv7a-cros-linux-gnueabi/* /target/*/macosx*.*_x86_64-target/ /target/*/macosx*.*_x86_64-target/* /target/*/macosx*.*_i386-target/ diff --git a/tools/depends/target/Makefile b/tools/depends/target/Makefile index f0f0bf4f26..9378623af3 100644 --- a/tools/depends/target/Makefile +++ b/tools/depends/target/Makefile @@ -130,41 +130,41 @@ distclean:: for d in $(DEPENDS); do $(MAKE) -C $$d distclean; done linux-system-libs-egl: - [ -f $(PREFIX)/lib/pkgconfig/egl.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/egl.pc $(PREFIX)/lib/pkgconfig/egl.pc - [ -f $(PREFIX)/lib/pkgconfig/damageproto.pc ] || ln -sf /usr/share/pkgconfig/damageproto.pc $(PREFIX)/lib/pkgconfig/damageproto.pc - [ -f $(PREFIX)/lib/pkgconfig/fixesproto.pc ] || ln -sf /usr/share/pkgconfig/fixesproto.pc $(PREFIX)/lib/pkgconfig/fixesproto.pc - [ -f $(PREFIX)/lib/pkgconfig/x11-xcb.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/x11-xcb.pc $(PREFIX)/lib/pkgconfig/x11-xcb.pc - [ -f $(PREFIX)/lib/pkgconfig/xcb-dri2.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-dri2.pc $(PREFIX)/lib/pkgconfig/xcb-dri2.pc - [ -f $(PREFIX)/lib/pkgconfig/xcb-dri3.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-dri3.pc $(PREFIX)/lib/pkgconfig/xcb-dri3.pc - [ -f $(PREFIX)/lib/pkgconfig/xcb-glx.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-glx.pc $(PREFIX)/lib/pkgconfig/xcb-glx.pc - [ -f $(PREFIX)/lib/pkgconfig/xcb-xfixes.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-xfixes.pc $(PREFIX)/lib/pkgconfig/xcb-xfixes.pc - [ -f $(PREFIX)/lib/pkgconfig/xcb-present.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-present.pc $(PREFIX)/lib/pkgconfig/xcb-present.pc - [ -f $(PREFIX)/lib/pkgconfig/xcb-randr.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-randr.pc $(PREFIX)/lib/pkgconfig/xcb-randr.pc - [ -f $(PREFIX)/lib/pkgconfig/xcb-render.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-render.pc $(PREFIX)/lib/pkgconfig/xcb-render.pc - [ -f $(PREFIX)/lib/pkgconfig/xcb-shape.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-shape.pc $(PREFIX)/lib/pkgconfig/xcb-shape.pc - [ -f $(PREFIX)/lib/pkgconfig/xcb-sync.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-sync.pc $(PREFIX)/lib/pkgconfig/xcb-sync.pc - [ -f $(PREFIX)/lib/pkgconfig/xdamage.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xdamage.pc $(PREFIX)/lib/pkgconfig/xdamage.pc - [ -f $(PREFIX)/lib/pkgconfig/xf86vidmodeproto.pc ] || ln -sf /usr/share/pkgconfig/xf86vidmodeproto.pc $(PREFIX)/lib/pkgconfig/xf86vidmodeproto.pc - [ -f $(PREFIX)/lib/pkgconfig/xfixes.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xfixes.pc $(PREFIX)/lib/pkgconfig/xfixes.pc - [ -f $(PREFIX)/lib/pkgconfig/xshmfence.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xshmfence.pc $(PREFIX)/lib/pkgconfig/xshmfence.pc - [ -f $(PREFIX)/lib/pkgconfig/xxf86vm.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xxf86vm.pc $(PREFIX)/lib/pkgconfig/xxf86vm.pc + [ -L $(PREFIX)/lib/pkgconfig/egl.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/egl.pc $(PREFIX)/lib/pkgconfig/egl.pc + [ -L $(PREFIX)/lib/pkgconfig/damageproto.pc ] || ln -sf /usr/share/pkgconfig/damageproto.pc $(PREFIX)/lib/pkgconfig/damageproto.pc + [ -L $(PREFIX)/lib/pkgconfig/fixesproto.pc ] || ln -sf /usr/share/pkgconfig/fixesproto.pc $(PREFIX)/lib/pkgconfig/fixesproto.pc + [ -L $(PREFIX)/lib/pkgconfig/x11-xcb.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/x11-xcb.pc $(PREFIX)/lib/pkgconfig/x11-xcb.pc + [ -L $(PREFIX)/lib/pkgconfig/xcb-dri2.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-dri2.pc $(PREFIX)/lib/pkgconfig/xcb-dri2.pc + [ -L $(PREFIX)/lib/pkgconfig/xcb-dri3.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-dri3.pc $(PREFIX)/lib/pkgconfig/xcb-dri3.pc + [ -L $(PREFIX)/lib/pkgconfig/xcb-glx.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-glx.pc $(PREFIX)/lib/pkgconfig/xcb-glx.pc + [ -L $(PREFIX)/lib/pkgconfig/xcb-xfixes.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-xfixes.pc $(PREFIX)/lib/pkgconfig/xcb-xfixes.pc + [ -L $(PREFIX)/lib/pkgconfig/xcb-present.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-present.pc $(PREFIX)/lib/pkgconfig/xcb-present.pc + [ -L $(PREFIX)/lib/pkgconfig/xcb-randr.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-randr.pc $(PREFIX)/lib/pkgconfig/xcb-randr.pc + [ -L $(PREFIX)/lib/pkgconfig/xcb-render.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-render.pc $(PREFIX)/lib/pkgconfig/xcb-render.pc + [ -L $(PREFIX)/lib/pkgconfig/xcb-shape.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-shape.pc $(PREFIX)/lib/pkgconfig/xcb-shape.pc + [ -L $(PREFIX)/lib/pkgconfig/xcb-sync.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb-sync.pc $(PREFIX)/lib/pkgconfig/xcb-sync.pc + [ -L $(PREFIX)/lib/pkgconfig/xdamage.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xdamage.pc $(PREFIX)/lib/pkgconfig/xdamage.pc + [ -L $(PREFIX)/lib/pkgconfig/xf86vidmodeproto.pc ] || ln -sf /usr/share/pkgconfig/xf86vidmodeproto.pc $(PREFIX)/lib/pkgconfig/xf86vidmodeproto.pc + [ -L $(PREFIX)/lib/pkgconfig/xfixes.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xfixes.pc $(PREFIX)/lib/pkgconfig/xfixes.pc + [ -L $(PREFIX)/lib/pkgconfig/xshmfence.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xshmfence.pc $(PREFIX)/lib/pkgconfig/xshmfence.pc + [ -L $(PREFIX)/lib/pkgconfig/xxf86vm.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xxf86vm.pc $(PREFIX)/lib/pkgconfig/xxf86vm.pc linux-system-libs: linux-system-libs-egl - [ -f $(PREFIX)/lib/pkgconfig/x11.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/x11.pc $(PREFIX)/lib/pkgconfig/x11.pc - [ -f $(PREFIX)/lib/pkgconfig/xproto.pc ] || ln -sf /usr/share/pkgconfig/xproto.pc $(PREFIX)/lib/pkgconfig/xproto.pc - [ -f $(PREFIX)/lib/pkgconfig/kbproto.pc ] || ln -sf /usr/share/pkgconfig/kbproto.pc $(PREFIX)/lib/pkgconfig/kbproto.pc - [ -f $(PREFIX)/lib/pkgconfig/xcb.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb.pc $(PREFIX)/lib/pkgconfig/xcb.pc - [ -f $(PREFIX)/lib/pkgconfig/pthread-stubs.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/pthread-stubs.pc $(PREFIX)/lib/pkgconfig/pthread-stubs.pc - [ -f $(PREFIX)/lib/pkgconfig/xau.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xau.pc $(PREFIX)/lib/pkgconfig/xau.pc - [ -f $(PREFIX)/lib/pkgconfig/xdmcp.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xdmcp.pc $(PREFIX)/lib/pkgconfig/xdmcp.pc - [ -f $(PREFIX)/lib/pkgconfig/xext.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xext.pc $(PREFIX)/lib/pkgconfig/xext.pc - [ -f $(PREFIX)/lib/pkgconfig/xextproto.pc ] || ln -sf /usr/share/pkgconfig/xextproto.pc $(PREFIX)/lib/pkgconfig/xextproto.pc - [ -f $(PREFIX)/lib/pkgconfig/xrandr.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xrandr.pc $(PREFIX)/lib/pkgconfig/xrandr.pc - [ -f $(PREFIX)/lib/pkgconfig/xrender.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xrender.pc $(PREFIX)/lib/pkgconfig/xrender.pc - [ -f $(PREFIX)/lib/pkgconfig/randrproto.pc ] || ln -sf /usr/share/pkgconfig/randrproto.pc $(PREFIX)/lib/pkgconfig/randrproto.pc - [ -f $(PREFIX)/lib/pkgconfig/renderproto.pc ] || ln -sf /usr/share/pkgconfig/renderproto.pc $(PREFIX)/lib/pkgconfig/renderproto.pc - [ -f $(PREFIX)/lib/pkgconfig/xt.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xt.pc $(PREFIX)/lib/pkgconfig/xt.pc - [ -f $(PREFIX)/lib/pkgconfig/ice.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/ice.pc $(PREFIX)/lib/pkgconfig/ice.pc - [ -f $(PREFIX)/lib/pkgconfig/sm.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/sm.pc $(PREFIX)/lib/pkgconfig/sm.pc - [ -f $(PREFIX)/lib/pkgconfig/xmu.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xmu.pc $(PREFIX)/lib/pkgconfig/xmu.pc - [ -f $(PREFIX)/lib/pkgconfig/libdrm.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/libdrm.pc $(PREFIX)/lib/pkgconfig/libdrm.pc + [ -L $(PREFIX)/lib/pkgconfig/x11.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/x11.pc $(PREFIX)/lib/pkgconfig/x11.pc + [ -L $(PREFIX)/lib/pkgconfig/xproto.pc ] || ln -sf /usr/share/pkgconfig/xproto.pc $(PREFIX)/lib/pkgconfig/xproto.pc + [ -L $(PREFIX)/lib/pkgconfig/kbproto.pc ] || ln -sf /usr/share/pkgconfig/kbproto.pc $(PREFIX)/lib/pkgconfig/kbproto.pc + [ -L $(PREFIX)/lib/pkgconfig/xcb.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xcb.pc $(PREFIX)/lib/pkgconfig/xcb.pc + [ -L $(PREFIX)/lib/pkgconfig/pthread-stubs.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/pthread-stubs.pc $(PREFIX)/lib/pkgconfig/pthread-stubs.pc + [ -L $(PREFIX)/lib/pkgconfig/xau.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xau.pc $(PREFIX)/lib/pkgconfig/xau.pc + [ -L $(PREFIX)/lib/pkgconfig/xdmcp.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xdmcp.pc $(PREFIX)/lib/pkgconfig/xdmcp.pc + [ -L $(PREFIX)/lib/pkgconfig/xext.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xext.pc $(PREFIX)/lib/pkgconfig/xext.pc + [ -L $(PREFIX)/lib/pkgconfig/xextproto.pc ] || ln -sf /usr/share/pkgconfig/xextproto.pc $(PREFIX)/lib/pkgconfig/xextproto.pc + [ -L $(PREFIX)/lib/pkgconfig/xrandr.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xrandr.pc $(PREFIX)/lib/pkgconfig/xrandr.pc + [ -L $(PREFIX)/lib/pkgconfig/xrender.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xrender.pc $(PREFIX)/lib/pkgconfig/xrender.pc + [ -L $(PREFIX)/lib/pkgconfig/randrproto.pc ] || ln -sf /usr/share/pkgconfig/randrproto.pc $(PREFIX)/lib/pkgconfig/randrproto.pc + [ -L $(PREFIX)/lib/pkgconfig/renderproto.pc ] || ln -sf /usr/share/pkgconfig/renderproto.pc $(PREFIX)/lib/pkgconfig/renderproto.pc + [ -L $(PREFIX)/lib/pkgconfig/xt.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xt.pc $(PREFIX)/lib/pkgconfig/xt.pc + [ -L $(PREFIX)/lib/pkgconfig/ice.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/ice.pc $(PREFIX)/lib/pkgconfig/ice.pc + [ -L $(PREFIX)/lib/pkgconfig/sm.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/sm.pc $(PREFIX)/lib/pkgconfig/sm.pc + [ -L $(PREFIX)/lib/pkgconfig/xmu.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/xmu.pc $(PREFIX)/lib/pkgconfig/xmu.pc + [ -L $(PREFIX)/lib/pkgconfig/libdrm.pc ] || ln -sf /usr/lib/$(HOST)/pkgconfig/libdrm.pc $(PREFIX)/lib/pkgconfig/libdrm.pc diff --git a/tools/depends/target/ffmpeg/Makefile b/tools/depends/target/ffmpeg/Makefile index eaf9fc4931..8dfbb48b3d 100644 --- a/tools/depends/target/ffmpeg/Makefile +++ b/tools/depends/target/ffmpeg/Makefile @@ -8,7 +8,7 @@ APPLY_PATCHES=no # configuration settings ffmpg_config = --prefix=$(PREFIX) --extra-version="kodi-$(VERSION)" -ffmpg_config += --cc=$(CC) --cxx=$(CXX) --ar=$(AR) --ranlib=$(RANLIB) +ffmpg_config += --cc="$(CC)" --cxx="$(CXX)" --ar=$(AR) --ranlib=$(RANLIB) ffmpg_config += --disable-devices --disable-doc ffmpg_config += --disable-ffplay --disable-ffmpeg --disable-sdl ffmpg_config += --disable-ffprobe --disable-ffserver diff --git a/tools/depends/target/libsdl2/Makefile b/tools/depends/target/libsdl2/Makefile index 6c6eb5a26a..4c88e60e05 100644 --- a/tools/depends/target/libsdl2/Makefile +++ b/tools/depends/target/libsdl2/Makefile @@ -8,7 +8,7 @@ SOURCE=$(LIBNAME)-$(VERSION) ARCHIVE=$(SOURCE).tar.gz # configuration settings -CONFIGURE=./configure --prefix=$(PREFIX) --disable-video-directfb +CONFIGURE=./configure --prefix=$(PREFIX) --disable-video-directfb --without-x --disable-video-x11 ifneq ($(OS),linux) CONFIGURE += --without-x --disable-video-x11 endif diff --git a/tools/depends/target/openssl/Makefile b/tools/depends/target/openssl/Makefile index def05255ed..dcae452bcf 100644 --- a/tools/depends/target/openssl/Makefile +++ b/tools/depends/target/openssl/Makefile @@ -33,7 +33,7 @@ $(TARBALLS_LOCATION)/$(ARCHIVE): $(PLATFORM): $(TARBALLS_LOCATION)/$(ARCHIVE) $(DEPS) rm -rf $(PLATFORM); mkdir -p $(PLATFORM) cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) - cd $(PLATFORM); AR="$(AR)" CFLAGS="$(CFLAGS)" CC=$(CC) RANLIB=$(RANLIB) $(CONFIGURE) + cd $(PLATFORM); AR="$(AR)" CFLAGS="$(CFLAGS)" CC="$(CC)" RANLIB=$(RANLIB) $(CONFIGURE) if test "$(OS)" = "osx"; then \ sed -ie "s|CC= /usr/bin/gcc-4.2|CC= $(CC)|" "$(PLATFORM)/Makefile"; \ sed -ie "s|CFLAG= |CFLAG=$(CFLAGS) |" "$(PLATFORM)/Makefile"; \ diff --git a/tools/depends/target/zlib/Makefile b/tools/depends/target/zlib/Makefile index 551301ca57..2145116dea 100644 --- a/tools/depends/target/zlib/Makefile +++ b/tools/depends/target/zlib/Makefile @@ -7,7 +7,7 @@ VERSION=1.2.7 SOURCE=$(LIBNAME)-$(VERSION) ARCHIVE=$(SOURCE).tar.gz # configuration settings -CONFIGURE= CC="$(CC)" CFLAGS="$(CFLAGS)" ./configure --prefix=$(PREFIX) --static +CONFIGURE= CC="$(CC)" CFLAGS="$(CFLAGS) -fPIC" ./configure --prefix=$(PREFIX) --static LIBDYLIB=$(PLATFORM)/$(LIBNAME).a diff --git a/xbmc/Application.cpp b/xbmc/Application.cpp index b8ff91b427..1b8a8c2e93 100644 --- a/xbmc/Application.cpp +++ b/xbmc/Application.cpp @@ -2238,7 +2238,7 @@ bool CApplication::OnAction(const CAction &action) m_pPlayer->SetPlaySpeed(1); return true; } - if (!m_pPlayer->IsPaused()) + if (!m_pPlayer->IsPaused() && m_pPlayer->CanFFRW()) { if (action.GetID() == ACTION_PLAYER_FORWARD || action.GetID() == ACTION_PLAYER_REWIND) { diff --git a/xbmc/ApplicationPlayer.cpp b/xbmc/ApplicationPlayer.cpp index 9163bae34b..13e6b79d7b 100644 --- a/xbmc/ApplicationPlayer.cpp +++ b/xbmc/ApplicationPlayer.cpp @@ -405,6 +405,12 @@ bool CApplicationPlayer::CanPause() return (player && player->CanPause()); } +bool CApplicationPlayer::CanFFRW() +{ + std::shared_ptr player = GetInternal(); + return (player && player->CanFFRW()); +} + bool CApplicationPlayer::IsRecording() const { std::shared_ptr player = GetInternal(); diff --git a/xbmc/ApplicationPlayer.h b/xbmc/ApplicationPlayer.h index a0567a6727..7d55035921 100644 --- a/xbmc/ApplicationPlayer.h +++ b/xbmc/ApplicationPlayer.h @@ -106,6 +106,7 @@ class CApplicationPlayer bool CanPause(); bool CanRecord(); bool CanSeek(); + bool CanFFRW(); void DoAudioWork(); void GetAudioCapabilities(std::vector &audioCaps); int GetAudioStream(); diff --git a/xbmc/cores/AudioEngine/AESinkFactory.cpp b/xbmc/cores/AudioEngine/AESinkFactory.cpp index 952461d29b..4ebed1fcbe 100644 --- a/xbmc/cores/AudioEngine/AESinkFactory.cpp +++ b/xbmc/cores/AudioEngine/AESinkFactory.cpp @@ -42,6 +42,9 @@ #if defined(HAS_PULSEAUDIO) #include "Sinks/AESinkPULSE.h" #endif + #if defined(HAS_STEAMLINK) + #include "Sinks/AESinkSteamLink.h" + #endif #else #pragma message("NOTICE: No audio sink for target platform. Audio output will not be available.") #endif @@ -83,6 +86,9 @@ void CAESinkFactory::ParseDevice(std::string &device, std::string &driver) #if defined(HAS_PULSEAUDIO) driver == "PULSE" || #endif + #if defined(HAS_STEAMLINK) + driver == "STEAMLINK" || + #endif #endif driver == "PROFILER" || driver == "NULL") @@ -133,6 +139,10 @@ IAESink *CAESinkFactory::TrySink(std::string &driver, std::string &device, AEAud if (driver == "OSS") sink = new CAESinkOSS(); #endif + #if defined(HAS_STEAMLINK) + if (driver == "STEAMLINK") + sink = new STEAMLINK::CAESinkSteamLink(); + #endif #endif } @@ -250,6 +260,10 @@ void CAESinkFactory::EnumerateEx(AESinkInfoList &list, bool force) if (envSink == "OSS") CAESinkOSS::EnumerateDevicesEx(info.m_deviceInfoList, force); #endif + #if defined(HAS_STEAMLINK) + if (envSink == "STEAMLINK") + STEAMLINK::CAESinkSteamLink::EnumerateDevicesEx(info.m_deviceInfoList, force); + #endif if(!info.m_deviceInfoList.empty()) { @@ -290,6 +304,17 @@ void CAESinkFactory::EnumerateEx(AESinkInfoList &list, bool force) list.push_back(info); #endif + #if defined(HAS_STEAMLINK) + info.m_deviceInfoList.clear(); + info.m_sinkName = "STEAMLINK"; + STEAMLINK::CAESinkSteamLink::EnumerateDevicesEx(info.m_deviceInfoList, force); + if(!info.m_deviceInfoList.empty()) + { + list.push_back(info); + return; + } + #endif + #endif } diff --git a/xbmc/cores/AudioEngine/Makefile.in b/xbmc/cores/AudioEngine/Makefile.in index edb6b110bf..7004f46aed 100644 --- a/xbmc/cores/AudioEngine/Makefile.in +++ b/xbmc/cores/AudioEngine/Makefile.in @@ -63,6 +63,11 @@ SRCS += Sinks/AESinkOSS.cpp ifeq (@USE_PULSE@,1) SRCS += Sinks/AESinkPULSE.cpp endif +ifeq (@USE_STEAMLINK@,1) +SRCS += Sinks/AESinkSteamLink.cpp +SRCS += Sinks/AESinkSteamLinkStream.cpp +SRCS += Sinks/AESinkSteamLinkTranslator.cpp +endif endif SRCS += Engines/ActiveAE/AudioDSPAddons/ActiveAEDSP.cpp diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkSteamLink.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLink.cpp new file mode 100644 index 0000000000..63fc7d376d --- /dev/null +++ b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLink.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ + +#include "AESinkSteamLink.h" +#include "AESinkSteamLinkStream.h" +#include "AESinkSteamLinkTranslator.h" +#include "cores/AudioEngine/Utils/AEUtil.h" +#include "utils/log.h" +#include "utils/TimeUtils.h" + +// Steam Link audio API +#include "SLAudio.h" + +#include +#include + +#define SL_SAMPLE_RATE 48000 +#define SINK_FEED_MS 50 // Steam Link game streaming uses 10ms +#define CACHE_TOTAL_MS 200 +#define INITIAL_ATTENUATION_TIME_SECS 6.0 + +using namespace STEAMLINK; + +namespace +{ + void LogFunction(void *pContext, ESLAudioLog eLogLevel, const char *pszMessage) + { + int level = CAESinkSteamLinkTranslator::TranslateLogLevel(eLogLevel); + CLog::Log(level, "%s", pszMessage); + } +} + +CAESinkSteamLink::CAESinkSteamLink() : + m_context(nullptr), + m_startTimeSecs(0.0), + m_framesSinceStart(0) +{ + SLAudio_SetLogLevel(k_ESLAudioLogDebug); + SLAudio_SetLogFunction(LogFunction, nullptr); +} + +CAESinkSteamLink::~CAESinkSteamLink() +{ + Deinitialize(); + SLAudio_SetLogFunction(nullptr, nullptr); +} + +bool CAESinkSteamLink::Initialize(AEAudioFormat &format, std::string &device) +{ + bool bSuccess = false; + + Deinitialize(); + + m_startTimeSecs = 0.0; // Set in first call to AddPackets() + m_framesSinceStart = 0; + + format.m_dataFormat = AE_FMT_S16NE; + format.m_sampleRate = SL_SAMPLE_RATE; + format.m_frames = format.m_sampleRate * SINK_FEED_MS / 1000; + format.m_frameSize = format.m_channelLayout.Count() * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3); + + if (format.m_channelLayout.Count() == 0 || format.m_frameSize == 0) + return false; + + CSLAudioContext* context = SLAudio_CreateContext(); + if (!context) + { + CLog::Log(LOGERROR, "SteamLinkAudio: Failed to create context"); + } + else + { + std::unique_ptr stream(new CAESinkSteamLinkStream(context, format.m_sampleRate, format.m_channelLayout.Count(), format.m_frames * format.m_frameSize)); + if (stream->Open()) + { + m_format = format; + m_context = context; + m_stream = std::move(stream); + bSuccess = true; + } + else + { + SLAudio_FreeContext(context); + } + } + + return bSuccess; +} + +void CAESinkSteamLink::Deinitialize() +{ + m_stream.reset(); + + if (m_context) + { + SLAudio_FreeContext(m_context); + m_context = nullptr; + } +} + +double CAESinkSteamLink::GetCacheTotal() +{ + return CACHE_TOTAL_MS / 1000.0; +} + +unsigned int CAESinkSteamLink::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) +{ + if (offset >= frames) + return 0; + + if (!m_stream) + return 0; + + // Calculate frame count from given parameters + const unsigned int frameCount = frames - offset; + + // Calculate start time and present time + const double nowSecs = static_cast(CurrentHostCounter()) / CurrentHostFrequency(); + + if (m_startTimeSecs == 0.0) + m_startTimeSecs = nowSecs; + + double presentTimeSecs = m_startTimeSecs + static_cast(m_framesSinceStart) / m_format.m_sampleRate; + + // Detect underrun + if (presentTimeSecs < nowSecs) + { + CLog::Log(LOGDEBUG, "SteamLinkAudio: Buffer underrun detected"); + presentTimeSecs = m_startTimeSecs = nowSecs; + m_framesSinceStart = 0; + } + + // Ensure space in the buffer + const double delaySecs = presentTimeSecs - nowSecs; + + const double availableSecs = GetCacheTotal() - delaySecs; + + const int sleepTimeUs = static_cast((SINK_FEED_MS - availableSecs * 1000.0) * 1000); + + if (sleepTimeUs > 0) + usleep(sleepTimeUs); + + // Create buffer and copy data + const size_t packetSize = frameCount * m_format.m_frameSize; + std::unique_ptr buffer(new uint8_t[packetSize]); + + std::memcpy(buffer.get(), *data + offset * m_format.m_frameSize, packetSize); + + // Attenuate if necessary + const double elapsedSinceStartSecs = (presentTimeSecs - m_startTimeSecs); + const bool bAttenuate = (elapsedSinceStartSecs < INITIAL_ATTENUATION_TIME_SECS); + if (bAttenuate) + { + double flVolume = elapsedSinceStartSecs / INITIAL_ATTENUATION_TIME_SECS; + AttenuateChunk(buffer.get(), packetSize, flVolume * flVolume); + } + + // Add packet + if (!m_stream->AddPacket(std::move(buffer), packetSize, presentTimeSecs)) + return 0; + + m_framesSinceStart += frameCount; + + return frameCount; +} + +void CAESinkSteamLink::GetDelay(AEDelayStatus &status) +{ + double delaySecs = 0.0; + + if (m_startTimeSecs != 0.0) + { + const double nowSecs = static_cast(CurrentHostCounter()) / CurrentHostFrequency(); + + double nextPresentTimeSecs = m_startTimeSecs + static_cast(m_framesSinceStart) / m_format.m_sampleRate; + + if (nextPresentTimeSecs > nowSecs) + delaySecs = nextPresentTimeSecs - nowSecs; + } + + status.SetDelay(delaySecs); +} + +void CAESinkSteamLink::Drain() +{ + if (m_stream) + { + if (!m_stream->Flush()) + m_stream.reset(); + } + + m_startTimeSecs = 0.0; + m_framesSinceStart = 0; +} + +void CAESinkSteamLink::AttenuateChunk(uint8_t *pChunk, unsigned int size, double flVolume) +{ + int16_t *pData = reinterpret_cast(pChunk); + int nCount = size / sizeof(*pData); + while (nCount--) + { + *pData = static_cast(*pData * flVolume); + ++pData; + } +} + +void CAESinkSteamLink::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force /* = false */) +{ + CAEDeviceInfo info; + + info.m_deviceType = AE_DEVTYPE_PCM; + info.m_deviceName = "SteamLink"; + info.m_displayName = "Steam Link Low Latency Audio"; + info.m_displayNameExtra = ""; + info.m_channels += AE_CH_FL; + info.m_channels += AE_CH_FR; + info.m_sampleRates.push_back(SL_SAMPLE_RATE); + info.m_dataFormats.push_back(AE_FMT_S16NE); + + deviceInfoList.push_back(info); +} diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkSteamLink.h b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLink.h new file mode 100644 index 0000000000..ffe053d2a3 --- /dev/null +++ b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLink.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ +#pragma once + +#include "cores/AudioEngine/Interfaces/AESink.h" +#include "cores/AudioEngine/Utils/AEAudioFormat.h" +#include "cores/AudioEngine/Utils/AEDeviceInfo.h" + +#include +#include + +#define STEAM_LINK_SINK_NAME "SteamLinkAudio" + +struct CSLAudioContext; + +namespace STEAMLINK +{ +class CAESinkSteamLinkStream; + +class CAESinkSteamLink : public IAESink +{ +public: + virtual const char* GetName() override { return STEAM_LINK_SINK_NAME; } + + CAESinkSteamLink(); + virtual ~CAESinkSteamLink(); + + // implementation of IAESink + virtual bool Initialize(AEAudioFormat &format, std::string &device) override; + virtual void Deinitialize() override; + virtual double GetCacheTotal() override; + virtual unsigned int AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) override; + virtual void GetDelay(AEDelayStatus &status) override; + virtual void Drain() override; + + static void EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force = false); + +private: + void AttenuateChunk(uint8_t *pChunk, unsigned int size, double flVolume); + + // Steam Link stuff + CSLAudioContext *m_context; + std::unique_ptr m_stream; + + // AE stuff + AEAudioFormat m_format; + double m_startTimeSecs; + uint64_t m_framesSinceStart; +}; + +} diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkStream.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkStream.cpp new file mode 100644 index 0000000000..a213bd3c6d --- /dev/null +++ b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkStream.cpp @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ + +#include "AESinkSteamLinkStream.h" +#include "AESinkSteamLink.h" +#include "cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideo.h" +#include "interfaces/AnnouncementManager.h" +#include "threads/SingleLock.h" +//#include "threads/SystemClock.h" +#include "utils/log.h" +#include "utils/TimeUtils.h" + +// Steam Link audio API +#include "SLAudio.h" + +#include +#include +#include + +using namespace STEAMLINK; + +#define MAX_AUDIO_DELAY_MS 100 // Skip packets if audio delay exceeds this value +#define SL_INTRINSIC_DELAY_MS 250 // Observed audio delay while playing video + +CAESinkSteamLinkStream::CAESinkSteamLinkStream(CSLAudioContext* context, unsigned int sampleRateHz, unsigned int channels, unsigned int packetSize) : + CThread("SteamLinkAudio"), + m_context(context), + m_sampleRateHz(sampleRateHz), + m_channels(channels), + m_packetSize(packetSize), + m_stream(nullptr), + m_steamLinkBuffer(nullptr), + m_remainingBytes(0) +{ + ANNOUNCEMENT::CAnnouncementManager::GetInstance().AddAnnouncer(this); +} + +CAESinkSteamLinkStream::~CAESinkSteamLinkStream() +{ + ANNOUNCEMENT::CAnnouncementManager::GetInstance().RemoveAnnouncer(this); + + Close(); +} + +bool CAESinkSteamLinkStream::Open() +{ + Close(); + + CSingleLock lock(m_streamMutex); + + bool bSuccess = false; + + m_stream = SLAudio_CreateStream(m_context, m_sampleRateHz, m_channels, m_packetSize, true); + + if (m_stream) + { + bSuccess = true; + lock.Leave(); + Create(false); + } + else + { + CLog::Log(LOGERROR, "SteamLinkAudio: Failed to create stream"); + } + + return bSuccess; +} + +void CAESinkSteamLinkStream::Close() +{ + StopThread(); + + CSingleLock lock(m_streamMutex); + + if (m_stream) + { + SLAudio_FreeStream(m_stream); + m_stream = nullptr; + } +} + +bool CAESinkSteamLinkStream::Flush() +{ + bool bOpen = false; + + // Causes AddPacket() to return immediately + { + CSingleLock lock(m_streamMutex); + if (m_stream) + { + bOpen = true; + SLAudio_FreeStream(m_stream); + m_stream = nullptr; + } + } + + // Clear queue + { + CSingleLock lock(m_queueMutex); + m_queue.clear(); + } + + // Reopen stream + bool bSuccess = true; + if (bOpen) + { + CSingleLock lock(m_streamMutex); + m_stream = SLAudio_CreateStream(m_context, m_sampleRateHz, m_channels, m_packetSize, true); + bSuccess = (m_stream != nullptr); + } + + return bSuccess; +} + +bool CAESinkSteamLinkStream::AddPacket(std::unique_ptr data, unsigned int size, double presentTimeSecs) +{ + { + CSingleLock lock(m_streamMutex); + if (m_stream == nullptr) + return true; // This might have been called during a Flush() + } + + int delayMs = CSteamLinkVideo::GetDelayMs(); + + delayMs -= SL_INTRINSIC_DELAY_MS; + + if (delayMs > 0) + presentTimeSecs += delayMs / 1000.0; + + { + CSingleLock lock(m_queueMutex); + m_queue.emplace_back(std::move(data), size, presentTimeSecs); + } + + // Notify thread that a packet is ready + m_queueEvent.Set(); + + return true; +} + +void CAESinkSteamLinkStream::Process() +{ + AudioPacket packet; + + while (!m_bStop) + { + if (packet.buffer || GetNextPacket(packet)) + { + // Sleep until we're ready to show the frame + WaitUntilReady(packet.presentTimeSecs); + + if (m_bStop) + break; + + SendPacket(std::move(packet)); + + if (GetNextPacket(packet)) + continue; + } + + AbortableWait(m_queueEvent); + } + + // Make sure we haven't left a Steam Link packet open + if (m_steamLinkBuffer != nullptr) + EndPacket(); +} + +void CAESinkSteamLinkStream::Announce(ANNOUNCEMENT::AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data) +{ + if (flag == ANNOUNCEMENT::Player && strcmp(sender, "xbmc") == 0) + { + if (strcmp(message, "OnPlay") == 0 || + strcmp(message, "OnPause") == 0 || + strcmp(message, "OnStop") == 0 || + strcmp(message, "OnSpeedChanged") == 0 || + strcmp(message, "OnSeek") == 0) + { + Flush(); + } + else + { + CLog::Log(LOGDEBUG, "CAESinkSteamLinkStream: Unknown player announcement \"%s\"", message); + } + } +} + +bool CAESinkSteamLinkStream::GetNextPacket(AudioPacket& packet) +{ + CSingleLock lock(m_queueMutex); + + if (!m_queue.empty()) + { + packet = std::move(m_queue.front()); + m_queue.pop_front(); + return true; + } + + return false; +} + +void CAESinkSteamLinkStream::WaitUntilReady(double targetTimeSecs) +{ + const double nowSecs = static_cast(CurrentHostCounter()) / CurrentHostFrequency(); + + const int sleepTimeMs = static_cast((targetTimeSecs - nowSecs) * 1000.0); + + if (sleepTimeMs > 0) + Sleep(sleepTimeMs); +} + +bool CAESinkSteamLinkStream::HasLatePacket(double nextPresentTimeSecs) const +{ + return std::find_if(m_queue.begin(), m_queue.end(), + [nextPresentTimeSecs](const AudioPacket& packet) + { + return packet.presentTimeSecs >= nextPresentTimeSecs; + }) != m_queue.end(); +} + +void CAESinkSteamLinkStream::ClearLatePackets(double nextPresentTimeSecs) +{ + while (HasLatePacket(nextPresentTimeSecs)) + m_queue.pop_front(); +} + +void CAESinkSteamLinkStream::BeginPacket() +{ + m_steamLinkBuffer = static_cast(SLAudio_BeginFrame(m_stream)); + m_remainingBytes = m_packetSize; +} + +void CAESinkSteamLinkStream::EndPacket() +{ + SLAudio_SubmitFrame(m_stream); + m_steamLinkBuffer = nullptr; + m_remainingBytes = 0; +} + +void CAESinkSteamLinkStream::SendPacket(AudioPacket packet) +{ + CSingleLock lock(m_streamMutex); + + if (m_stream == nullptr) + return; + + if (GetSLDelaySecs() > MAX_AUDIO_DELAY_MS) + { + // Flush() grabs the queue mutex, so don't hold the stream mutex + CSingleExit exit(m_streamMutex); + if (!Flush()) + return; + } + + if (m_stream == nullptr) + return; + + unsigned int bytesWritten = 0; + + // Loop until all bytes have been written + while (bytesWritten < packet.size) + { + if (m_steamLinkBuffer == nullptr) + BeginPacket(); + + const unsigned int bytesToWrite = std::min(m_remainingBytes, packet.size - bytesWritten); + + // Sanity check (shouldn't happen) + if (bytesToWrite == 0 || m_remainingBytes == 0) + break; + + const unsigned int bufferOffset = m_packetSize - m_remainingBytes; + std::memcpy(m_steamLinkBuffer + bufferOffset, packet.buffer.get() + bytesWritten, bytesToWrite); + + m_remainingBytes -= bytesToWrite; + bytesWritten += bytesToWrite; + + if (m_remainingBytes == 0) + EndPacket(); + } +} + +double CAESinkSteamLinkStream::GetSLDelaySecs() +{ + uint32_t queuedFrames = 0; + + if (m_stream) + queuedFrames = SLAudio_GetQueuedAudioSamples(m_stream) / m_channels; + + return static_cast(queuedFrames) / m_sampleRateHz; +} diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkStream.h b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkStream.h new file mode 100644 index 0000000000..93a344d778 --- /dev/null +++ b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkStream.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ +#pragma once + +#include "interfaces/IAnnouncer.h" +#include "threads/CriticalSection.h" +#include "threads/SystemClock.h" +#include "threads/Thread.h" + +#include +#include +#include + +struct CSLAudioContext; +struct CSLAudioStream; + +namespace STEAMLINK +{ + +class CAESinkSteamLinkStream : public CThread, + public ANNOUNCEMENT::IAnnouncer +{ +public: + CAESinkSteamLinkStream(CSLAudioContext* context, unsigned int sampleRateHz, unsigned int channels, unsigned int packetSize); + virtual ~CAESinkSteamLinkStream(); + + bool Open(); + void Close(); + bool Flush(); + bool AddPacket(std::unique_ptr data, unsigned int size, double presentTimeSecs); + + // implementation of IAnnouncer + virtual void Announce(ANNOUNCEMENT::AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data) override; + +protected: + // implementation of CThread + virtual void Process() override; + +private: + struct AudioPacket + { + AudioPacket() : + size(0), + presentTimeSecs(0.0) + { + } + + AudioPacket(std::unique_ptr buffer, unsigned int size, double presentTimeSecs) : + buffer(std::move(buffer)), + size(size), + presentTimeSecs(presentTimeSecs) + { + } + + std::unique_ptr buffer; + unsigned int size; + double presentTimeSecs; + }; + + bool GetNextPacket(AudioPacket& packet); + + void WaitUntilReady(double targetTimeSecs); + + bool HasLatePacket(double nextPresentTimeSecs) const; + + void ClearLatePackets(double nextPresentTimeSecs); + + void BeginPacket(); + void EndPacket(); + + void SendPacket(AudioPacket packet); + + double GetSLDelaySecs(); + + // Construction parameters + CSLAudioContext* const m_context; + const unsigned int m_sampleRateHz; + const unsigned int m_channels; + const unsigned int m_packetSize; + + // Stream parameters + CSLAudioStream* m_stream; + CCriticalSection m_streamMutex; + std::deque m_queue; + CCriticalSection m_queueMutex; + CEvent m_queueEvent; + uint8_t* m_steamLinkBuffer; + unsigned int m_remainingBytes; // Bytes remaining in the Steam Link audio buffer + XbmcThreads::EndTime m_videoDelay; +}; + +} diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkTranslator.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkTranslator.cpp new file mode 100644 index 0000000000..77270c62f1 --- /dev/null +++ b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkTranslator.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * http://kodi.tv + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ + +#include "AESinkSteamLinkTranslator.h" +#include "commons/ilog.h" + +using namespace STEAMLINK; + +int CAESinkSteamLinkTranslator::TranslateLogLevel(ESLAudioLog logLevel) +{ + switch (logLevel) + { + case k_ESLAudioLogDebug: return LOGDEBUG; + case k_ESLAudioLogInfo: return LOGINFO; + case k_ESLAudioLogWarning: return LOGWARNING; + case k_ESLAudioLogError: return LOGERROR; + break; + default: + break; + } + + return LOGDEBUG; +} diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkTranslator.h b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkTranslator.h new file mode 100644 index 0000000000..b999858380 --- /dev/null +++ b/xbmc/cores/AudioEngine/Sinks/AESinkSteamLinkTranslator.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * http://kodi.tv + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ +#pragma once + +#include "SLAudio.h" + +namespace STEAMLINK +{ + +class CAESinkSteamLinkTranslator +{ +public: + static int TranslateLogLevel(ESLAudioLog logLevel); +}; + +} diff --git a/xbmc/cores/IPlayer.h b/xbmc/cores/IPlayer.h index a1ea99f2e1..32a5fc9c9a 100644 --- a/xbmc/cores/IPlayer.h +++ b/xbmc/cores/IPlayer.h @@ -246,6 +246,7 @@ class IPlayer virtual bool HasRDS() const { return false; } virtual bool IsPassthrough() const { return false;} virtual bool CanSeek() {return true;} + virtual bool CanFFRW() { return true; } virtual void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false) = 0; virtual bool SeekScene(bool bPlus = true) {return false;} virtual void SeekPercentage(float fPercent = 0){} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.cpp index 9717412256..cade549d5b 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.cpp @@ -41,6 +41,9 @@ #include "Video/DVDVideoCodecAndroidMediaCodec.h" #include "platform/android/activity/AndroidFeatures.h" #endif +#if defined(HAS_STEAMLINK) +#include "Video/SteamLinkVideo.h" +#endif #include "Audio/DVDAudioCodecFFmpeg.h" #include "Audio/DVDAudioCodecPassthrough.h" #include "Overlay/DVDOverlayCodecSSA.h" @@ -141,7 +144,9 @@ CDVDVideoCodec* CDVDFactoryCodec::CreateVideoCodec(CDVDStreamInfo &hint, CProces return pCodec; #endif -#if defined(HAS_IMXVPU) +#if defined(HAS_STEAMLINK) + pCodec = OpenCodec(new STEAMLINK::CSteamLinkVideo(processInfo), hint, options); +#elif defined(HAS_IMXVPU) pCodec = OpenCodec(new CDVDVideoCodecIMX(processInfo), hint, options); #elif defined(TARGET_ANDROID) pCodec = OpenCodec(new CDVDVideoCodecAndroidMediaCodec(processInfo), hint, options); diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h index a2da9de437..118ea83c5a 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h @@ -51,6 +51,7 @@ class CDVDMediaCodecInfo; class CDVDVideoCodecIMXBuffer; class CMMALBuffer; class CDVDAmlogicInfo; +namespace STEAMLINK { class CSteamLinkBuffer; } // should be entirely filled by all codecs @@ -100,6 +101,10 @@ struct DVDVideoPicture CDVDAmlogicInfo *amlcodec; }; + struct { + STEAMLINK::CSteamLinkBuffer *steamlink; + }; + }; unsigned int iFlags; diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/Makefile.in b/xbmc/cores/VideoPlayer/DVDCodecs/Video/Makefile.in index 62d44cea58..574bf6c633 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/Makefile.in +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/Makefile.in @@ -34,6 +34,13 @@ ifeq (@USE_MMAL@,1) SRCS += MMALCodec.cpp MMALFFmpeg.cpp endif +ifeq (@USE_STEAMLINK@,1) +SRCS += SteamLinkTranslator.cpp +SRCS += SteamLinkVideo.cpp +SRCS += SteamLinkVideoBuffer.cpp +SRCS += SteamLinkVideoStream.cpp +endif + LIB=Video.a include @abs_top_srcdir@/Makefile.include diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkTranslator.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkTranslator.cpp new file mode 100644 index 0000000000..9a800b130a --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkTranslator.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * http://kodi.tv + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ + +#include "SteamLinkTranslator.h" +#include "commons/ilog.h" + +using namespace STEAMLINK; + +bool CSteamLinkTranslator::TranslateFormat(AVCodecID format, ESLVideoFormat& slFormat) +{ + switch (format) + { + case AV_CODEC_ID_H264: + slFormat = k_ESLVideoFormatH264; + break; + default: + return false; + } + + return true; +} + +const char* CSteamLinkTranslator::TranslateFormatToString(ESLVideoFormat format) +{ + switch (format) + { + case k_ESLVideoFormatH264: return "h264"; + default: + break; + } + + return ""; +} + +int CSteamLinkTranslator::TranslateLogLevel(ESLVideoLog logLevel) +{ + switch (logLevel) + { + case k_ESLVideoLogDebug: return LOGDEBUG; + case k_ESLVideoLogInfo: return LOGINFO; + case k_ESLVideoLogWarning: return LOGWARNING; + case k_ESLVideoLogError: return LOGERROR; + break; + default: + break; + } + + return LOGDEBUG; +} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkTranslator.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkTranslator.h new file mode 100644 index 0000000000..1a0d5b68c5 --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkTranslator.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * http://kodi.tv + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ +#pragma once + +#include "SLVideo.h" +#include "libavcodec/avcodec.h" + +namespace STEAMLINK +{ + +class CSteamLinkTranslator +{ +public: + static bool TranslateFormat(AVCodecID format, ESLVideoFormat& slFormat); + + static const char* TranslateFormatToString(ESLVideoFormat format); + + static int TranslateLogLevel(ESLVideoLog logLevel); +}; + +} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideo.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideo.cpp new file mode 100644 index 0000000000..4776983a07 --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideo.cpp @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * http://kodi.tv + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ + +#include "SteamLinkVideo.h" +#include "SteamLinkTranslator.h" +#include "SteamLinkVideoStream.h" +#include "cores/VideoPlayer/DVDClock.h" +#include "cores/VideoPlayer/DVDStreamInfo.h" +#include "cores/VideoPlayer/VideoRenderers/RenderFormats.h" +#include "settings/AdvancedSettings.h" +#include "threads/SingleLock.h" +#include "utils/log.h" + +// Steam Link video API +#include "SLVideo.h" + +#include +#include +#include "SteamLinkTranslator.h" + +using namespace STEAMLINK; + +namespace +{ + void LogFunction(void *pContext, ESLVideoLog eLogLevel, const char *pszMessage) + { + int level = CSteamLinkTranslator::TranslateLogLevel(eLogLevel); + CLog::Log(level, "%s", pszMessage); + } +} + +CSteamLinkVideo* CSteamLinkVideo::m_globalVideo = nullptr; +unsigned int CSteamLinkVideo::m_globalInstances = 0; +CCriticalSection CSteamLinkVideo::m_globalLock; + +CSteamLinkVideo::CSteamLinkVideo(CProcessInfo& processInfo) : + CDVDVideoCodec(processInfo), + m_context(nullptr), + m_stream(nullptr), + m_bufferSize(0), + m_dts(0.0), + m_sps_pps_size(0), + m_convert_bitstream(false) +{ + // Set global parameters + { + CSingleLock lock(m_globalLock); + + if (m_globalVideo == nullptr) + m_globalVideo = this; + else + CLog::Log(LOGERROR, "%s: Already a global video instance!!!", GetName()); + + if (m_globalInstances++ == 0) + { + SLVideo_SetLogLevel(g_advancedSettings.CanLogComponent(LOGVIDEO) ? k_ESLVideoLogDebug : k_ESLVideoLogError); + SLVideo_SetLogFunction(LogFunction, nullptr); + } + } +} + +CSteamLinkVideo::~CSteamLinkVideo() +{ + Dispose(); + + // Unset global parameters + { + CSingleLock lock(m_globalLock); + + if (m_globalVideo == this) + m_globalVideo = nullptr; + + if (m_globalInstances > 0 && --m_globalInstances == 0) + SLVideo_SetLogFunction(nullptr, nullptr); + } +} + +bool CSteamLinkVideo::Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) +{ + if (hints.software) + return false; + + bool bSuccess = false; + + Dispose(); + + CSLVideoContext* context = SLVideo_CreateContext(); + if (!context) + { + CLog::Log(LOGERROR, "%s: Failed to create context", GetName()); + } + else + { + std::unique_ptr stream; + + ESLVideoFormat slFormat; + if (!CSteamLinkTranslator::TranslateFormat(hints.codec, slFormat)) + { + if (g_advancedSettings.CanLogComponent(LOGVIDEO)) + CLog::Log(LOGDEBUG, "%s: Codec not supported", GetName()); + } + else + { + if (hints.extrasize < 7 || hints.extradata == nullptr) + { + if (g_advancedSettings.CanLogComponent(LOGVIDEO)) + CLog::Log(LOGNOTICE, "%s: avcC data too small or missing", GetName()); + } + else + { + // valid avcC data (bitstream) always starts with the value 1 (version) + if (*static_cast(hints.extradata) == 1) + m_convert_bitstream = bitstream_convert_init(hints.extradata, hints.extrasize); + + if (hints.fpsrate > 0 && hints.fpsscale > 0) + stream.reset(new CSteamLinkVideoStream(context, k_ESLVideoFormatH264, hints.fpsrate, hints.fpsscale)); + else + stream.reset(new CSteamLinkVideoStream(context, k_ESLVideoFormatH264)); + + if (!stream->Open()) + stream.reset(); + } + } + + if (!stream) + { + if (g_advancedSettings.CanLogComponent(LOGVIDEO)) + CLog::Log(LOGNOTICE, "%s: Failed to create stream", GetName()); + SLVideo_FreeContext(context); + } + else + { + // Success + m_context = context; + { + CSingleLock lock(m_streamLock); + m_stream = std::move(stream); + } + bSuccess = true; + + // Initialize buffer for DVDVideoPicture + memset(&m_videoPictureBuffer, 0, sizeof(m_videoPictureBuffer)); + m_videoPictureBuffer.dts = DVD_NOPTS_VALUE; + m_videoPictureBuffer.pts = DVD_NOPTS_VALUE; + m_videoPictureBuffer.format = RENDER_FMT_STEAMLINK; + m_videoPictureBuffer.color_range = 0; + m_videoPictureBuffer.color_matrix = 4; // FCC + m_videoPictureBuffer.iFlags = DVP_FLAG_ALLOCATED; + m_videoPictureBuffer.iWidth = hints.width; + m_videoPictureBuffer.iHeight = hints.height; + m_videoPictureBuffer.steamlink = nullptr; + + m_videoPictureBuffer.iDisplayWidth = m_videoPictureBuffer.iWidth; + m_videoPictureBuffer.iDisplayHeight = m_videoPictureBuffer.iHeight; + + m_processInfo.SetVideoDecoderName(GetName(), true); + m_processInfo.SetVideoPixelFormat(CSteamLinkTranslator::TranslateFormatToString(slFormat)); + m_processInfo.SetVideoDimensions(hints.width, hints.height); + m_processInfo.SetVideoFps(static_cast(hints.fpsrate) / static_cast(hints.fpsscale)); + m_processInfo.SetVideoDAR(hints.aspect); + + if (g_advancedSettings.CanLogComponent(LOGVIDEO)) + { + CLog::Log(LOGINFO, "%s: Opened codec %s", GetName(), CSteamLinkTranslator::TranslateFormatToString(slFormat)); + + int width = 0; + int height = 0; + SLVideo_GetDisplayResolution(m_context, &width, &height); + + CLog::Log(LOGDEBUG, "%s: Display resolution = %d x %d", GetName(), width, height); + } + } + } + + return bSuccess; +} + +void CSteamLinkVideo::Dispose() +{ + if (m_stream) + { + m_stream->Close(); + { + CSingleLock lock(m_streamLock); + m_stream.reset(); + } + } + + if (m_context) + { + SLVideo_FreeContext(m_context); + m_context = nullptr; + } + + if (m_convert_bitstream) + { + if (m_sps_pps_context.sps_pps_data) + { + free(m_sps_pps_context.sps_pps_data); + m_sps_pps_context.sps_pps_data = NULL; + } + m_convert_bitstream = false; + } +} + +int CSteamLinkVideo::Decode(uint8_t *pData, int iSize, double dts, double pts) +{ + if (pData == nullptr || iSize <= 0) + return VC_BUFFER; + + if (!m_stream) + return VC_ERROR; + + m_buffer.reset(); + m_bufferSize = 0; + m_dts = dts; + + if (m_convert_bitstream) + { + // convert demuxer packet from bitstream to bytestream (AnnexB) + uint8_t *bytestream_buff = nullptr; + int bytestream_size = 0; + + bitstream_convert(pData, iSize, &bytestream_buff, &bytestream_size); + if (bytestream_buff != nullptr && (bytestream_size > 0)) + { + m_buffer.reset(bytestream_buff); + m_bufferSize = bytestream_size; + } + } + else + { + uint8_t *bytestream_buff = static_cast(malloc(iSize)); + if (bytestream_buff) + { + memcpy(bytestream_buff, pData, iSize); + m_buffer.reset(bytestream_buff); + m_bufferSize = iSize; + } + } + + return VC_PICTURE; +} + +void CSteamLinkVideo::Reset(void) +{ + if (!m_stream) + return; + + if (!m_stream->Flush()) + { + CSingleLock lock(m_streamLock); + m_stream.reset(); + if (g_advancedSettings.CanLogComponent(LOGVIDEO)) + CLog::Log(LOGERROR, "%s: Failed to flush stream", GetName()); + } +} + +bool CSteamLinkVideo::GetPicture(DVDVideoPicture *pDvdVideoPicture) +{ + if (!m_stream) + return false; + + *pDvdVideoPicture = m_videoPictureBuffer; + + // Steam link video accepts decode packet, so use dts for timing + pDvdVideoPicture->pts = m_dts; + pDvdVideoPicture->steamlink = new CSteamLinkBuffer(std::move(m_buffer), m_bufferSize, m_stream); + + return true; +} + +bool CSteamLinkVideo::ClearPicture(DVDVideoPicture* pDvdVideoPicture) +{ + delete pDvdVideoPicture->steamlink; + pDvdVideoPicture->steamlink = nullptr; + + return true; +} + +std::shared_ptr CSteamLinkVideo::GetStream() +{ + std::shared_ptr stream; + + { + CSingleLock lock(m_streamLock); + stream = m_stream; + } + + return stream; +} + +bool CSteamLinkVideo::IsPlayingVideo() +{ + std::shared_ptr stream; + + { + CSingleLock lock(m_globalLock); + + if (m_globalVideo != nullptr) + stream = m_globalVideo->GetStream(); + } + + return stream.get() != nullptr; +} + +unsigned int CSteamLinkVideo::GetDelayMs() +{ + std::shared_ptr stream; + + { + CSingleLock lock(m_globalLock); + + if (m_globalVideo != nullptr) + stream = m_globalVideo->GetStream(); + } + + return stream ? stream->GetDelayMs() : 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +bool CSteamLinkVideo::bitstream_convert_init(void *in_extradata, int in_extrasize) +{ + // based on h264_mp4toannexb_bsf.c (ffmpeg) + // which is Copyright (c) 2007 Benoit Fouet + // and Licensed GPL 2.1 or greater + + m_sps_pps_size = 0; + m_sps_pps_context.sps_pps_data = NULL; + + // nothing to filter + if (!in_extradata || in_extrasize < 6) + return false; + + uint16_t unit_size; + uint32_t total_size = 0; + uint8_t *out = NULL, unit_nb, sps_done = 0; + const uint8_t *extradata = (uint8_t*)in_extradata + 4; + static const uint8_t nalu_header[4] = {0, 0, 0, 1}; + + // retrieve length coded size + m_sps_pps_context.length_size = (*extradata++ & 0x3) + 1; + if (m_sps_pps_context.length_size == 3) + return false; + + // retrieve sps and pps unit(s) + unit_nb = *extradata++ & 0x1f; // number of sps unit(s) + if (!unit_nb) + { + unit_nb = *extradata++; // number of pps unit(s) + sps_done++; + } + while (unit_nb--) + { + unit_size = extradata[0] << 8 | extradata[1]; + total_size += unit_size + 4; + if ( (extradata + 2 + unit_size) > ((uint8_t*)in_extradata + in_extrasize) ) + { + free(out); + return false; + } + uint8_t* new_out = (uint8_t*)realloc(out, total_size); + if (new_out) + { + out = new_out; + } + else + { + CLog::Log(LOGERROR, "bitstream_convert_init failed - %s : could not realloc the buffer out", __FUNCTION__); + free(out); + return false; + } + + memcpy(out + total_size - unit_size - 4, nalu_header, 4); + memcpy(out + total_size - unit_size, extradata + 2, unit_size); + extradata += 2 + unit_size; + + if (!unit_nb && !sps_done++) + unit_nb = *extradata++; // number of pps unit(s) + } + + m_sps_pps_context.sps_pps_data = out; + m_sps_pps_context.size = total_size; + m_sps_pps_context.first_idr = 1; + + return true; +} + +bool CSteamLinkVideo::bitstream_convert(uint8_t* pData, int iSize, uint8_t **poutbuf, int *poutbuf_size) +{ + // based on h264_mp4toannexb_bsf.c (ffmpeg) + // which is Copyright (c) 2007 Benoit Fouet + // and Licensed GPL 2.1 or greater + + uint8_t *buf = pData; + uint32_t buf_size = iSize; + uint8_t unit_type; + int32_t nal_size; + uint32_t cumul_size = 0; + const uint8_t *buf_end = buf + buf_size; + + do + { + if (buf + m_sps_pps_context.length_size > buf_end) + goto fail; + + if (m_sps_pps_context.length_size == 1) + nal_size = buf[0]; + else if (m_sps_pps_context.length_size == 2) + nal_size = buf[0] << 8 | buf[1]; + else + nal_size = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; + + buf += m_sps_pps_context.length_size; + unit_type = *buf & 0x1f; + + if (buf + nal_size > buf_end || nal_size < 0) + goto fail; + + // prepend only to the first type 5 NAL unit of an IDR picture + if (m_sps_pps_context.first_idr && unit_type == 5) + { + bitstream_alloc_and_copy(poutbuf, poutbuf_size, + m_sps_pps_context.sps_pps_data, m_sps_pps_context.size, buf, nal_size); + m_sps_pps_context.first_idr = 0; + } + else + { + bitstream_alloc_and_copy(poutbuf, poutbuf_size, NULL, 0, buf, nal_size); + if (!m_sps_pps_context.first_idr && unit_type == 1) + m_sps_pps_context.first_idr = 1; + } + + if (!*poutbuf) + goto fail; + + buf += nal_size; + cumul_size += nal_size + m_sps_pps_context.length_size; + } while (cumul_size < buf_size); + + return true; + +fail: + free(*poutbuf); + *poutbuf = NULL; + *poutbuf_size = 0; + return false; +} + +void CSteamLinkVideo::bitstream_alloc_and_copy( + uint8_t **poutbuf, int *poutbuf_size, + const uint8_t *sps_pps, uint32_t sps_pps_size, + const uint8_t *in, uint32_t in_size) +{ + // based on h264_mp4toannexb_bsf.c (ffmpeg) + // which is Copyright (c) 2007 Benoit Fouet + // and Licensed GPL 2.1 or greater + + #define CHD_WB32(p, d) { \ + ((uint8_t*)(p))[3] = (d); \ + ((uint8_t*)(p))[2] = (d) >> 8; \ + ((uint8_t*)(p))[1] = (d) >> 16; \ + ((uint8_t*)(p))[0] = (d) >> 24; } + + uint32_t offset = *poutbuf_size; + uint8_t nal_header_size = 4;//offset ? 3 : 4; + + *poutbuf_size += sps_pps_size + nal_header_size + in_size; + *poutbuf = static_cast(realloc(*poutbuf, *poutbuf_size)); + + if (!*poutbuf) + return; + + if (sps_pps) + memcpy(*poutbuf + offset, sps_pps, sps_pps_size); + + memcpy(*poutbuf + offset + sps_pps_size + nal_header_size, in, in_size); + if (true || !offset) + { + CHD_WB32(*poutbuf + offset + sps_pps_size, 1); + } + else + { + (*poutbuf + offset + sps_pps_size)[0] = 0; + (*poutbuf + offset + sps_pps_size)[1] = 0; + (*poutbuf + offset + sps_pps_size)[2] = 1; + } +} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideo.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideo.h new file mode 100644 index 0000000000..ebe42c795a --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideo.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * http://kodi.tv + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ +#pragma once + +#include "DVDVideoCodec.h" +#include "SteamLinkVideoBuffer.h" +#include "threads/CriticalSection.h" + +#include +#include +#include + +#define STEAMLINK_VIDEO_CODEC_NAME "SteamLinkVideo" + +struct CSLVideoContext; +struct CSLVideoStream; + +namespace STEAMLINK +{ + +class CSteamLinkVideoStream; + +class CSteamLinkVideo : public CDVDVideoCodec +{ +public: + CSteamLinkVideo(CProcessInfo& processInfo); + virtual ~CSteamLinkVideo(); + + // implementation of CDVDVideoCodec + virtual bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) override; + virtual int Decode(uint8_t* pData, int iSize, double dts, double pts) override; + virtual void Reset() override; + virtual bool GetPicture(DVDVideoPicture* pDvdVideoPicture) override; + virtual bool ClearPicture(DVDVideoPicture* pDvdVideoPicture) override; + virtual void SetDropState(bool bDrop) override { } + virtual const char* GetName() override { return STEAMLINK_VIDEO_CODEC_NAME; } + + // Access global stream instance + static bool IsPlayingVideo(); + static unsigned int GetDelayMs(); + +private: + void Dispose(); + + std::shared_ptr GetStream(); + + // Steam Link data + CSLVideoContext* m_context; + std::shared_ptr m_stream; + CCriticalSection m_streamLock; + + // VideoPlayer data + DVDVideoPicture m_videoPictureBuffer; + SteamLinkUniqueBuffer m_buffer; + size_t m_bufferSize; + float m_dts; + + // bitstream to bytestream (Annex B) conversion support. + bool bitstream_convert_init(void *in_extradata, int in_extrasize); + bool bitstream_convert(uint8_t* pData, int iSize, uint8_t **poutbuf, int *poutbuf_size); + static void bitstream_alloc_and_copy(uint8_t **poutbuf, int *poutbuf_size, + const uint8_t *sps_pps, uint32_t sps_pps_size, const uint8_t *in, uint32_t in_size); + + typedef struct bitstream_ctx + { + uint8_t length_size; + uint8_t first_idr; + uint8_t *sps_pps_data; + uint32_t size; + + bitstream_ctx() + { + length_size = 0; + first_idr = 0; + sps_pps_data = NULL; + size = 0; + } + + } bitstream_ctx; + + uint32_t m_sps_pps_size; + bitstream_ctx m_sps_pps_context; + bool m_convert_bitstream; + + // Global instance + static CSteamLinkVideo* m_globalVideo; + static unsigned int m_globalInstances; + static CCriticalSection m_globalLock; +}; + +} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoBuffer.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoBuffer.cpp new file mode 100644 index 0000000000..08fa22feaf --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoBuffer.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * http://kodi.tv + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ + +#include "SteamLinkVideoBuffer.h" + +#include + +using namespace STEAMLINK; + +// --- SteamLinkBufferFree ----------------------------------------------------- + +void SteamLinkBufferFree::operator()(void* x) +{ + free(x); +} + +// --- CSteamLinkBuffer -------------------------------------------------------- + +CSteamLinkBuffer::CSteamLinkBuffer(SteamLinkUniqueBuffer buffer, size_t size, std::shared_ptr stream) : + buffer(std::move(buffer)), + size(size), + stream(std::move(stream)) +{ +} + +CSteamLinkBuffer::CSteamLinkBuffer(CSteamLinkBuffer&& other) : + buffer(std::move(other.buffer)), + size(other.size), + stream(std::move(other.stream)) +{ + other.size = 0; +} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoBuffer.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoBuffer.h new file mode 100644 index 0000000000..bb1fa33bff --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoBuffer.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * http://kodi.tv + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ +#pragma once + +#include +#include +#include + +namespace STEAMLINK +{ + +class CSteamLinkVideoStream; + +struct SteamLinkBufferFree +{ + void operator()(void* x); +}; + +typedef std::unique_ptr SteamLinkUniqueBuffer; + +class CSteamLinkBuffer +{ +public: + CSteamLinkBuffer(SteamLinkUniqueBuffer buffer, size_t size, std::shared_ptr stream); + + CSteamLinkBuffer(CSteamLinkBuffer&& other); + + SteamLinkUniqueBuffer buffer; + size_t size; + std::shared_ptr stream; +}; + +} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoStream.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoStream.cpp new file mode 100644 index 0000000000..d140db41ee --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoStream.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * http://kodi.tv + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ + +#include "SteamLinkVideoStream.h" +#include "threads/SingleLock.h" +#include "utils/log.h" + +using namespace STEAMLINK; + +CSteamLinkVideoStream::CSteamLinkVideoStream(CSLVideoContext *pContext, + ESLVideoFormat eFormat, + unsigned int fpsRate /* = 0 */, + unsigned int fpsScale /* = 0 */) : + m_pContext(pContext), + m_eFormat(eFormat), + m_fpsRate(fpsRate), + m_fpsScale(fpsScale), + m_stream(nullptr) +{ +} + +bool CSteamLinkVideoStream::Open() +{ + CSingleLock lock(m_streamMutex); + + m_stream = SLVideo_CreateStream(m_pContext, m_eFormat, false); + + if (m_stream != nullptr) + { + if (m_fpsRate > 0 && m_fpsScale > 0) + SLVideo_SetStreamTargetFramerate(m_stream, m_fpsRate, m_fpsScale); + + return true; + } + + return false; +} + +void CSteamLinkVideoStream::Close() +{ + CSingleLock lock(m_streamMutex); + + if (m_stream) + { + SLVideo_FreeStream(m_stream); + m_stream = nullptr; + } + + m_delay.SetExpired(); +} + +bool CSteamLinkVideoStream::Flush() +{ + CSingleLock lock(m_streamMutex); + + Close(); + + return Open(); +} + +bool CSteamLinkVideoStream::WriteData(const uint8_t* data, size_t size) +{ + uint32_t delayMs = 0; + + { + CSingleLock lock(m_streamMutex); + + if (m_stream) + { + if (SLVideo_BeginFrame(m_stream, size) != 0) + { + CLog::Log(LOGERROR, "SteamLinkVideo: Failed to begin frame of size %u", size); + return false; + } + + if (SLVideo_WriteFrameData(m_stream, const_cast(data), size) != 0) + { + CLog::Log(LOGERROR, "SteamLinkVideo: Error writing data of size %u", size); + return false; + } + + if (SLVideo_SubmitFrame(m_stream) != 0) + { + CLog::Log(LOGERROR, "SteamLinkVideo: Error submitting frame of size %u", size); + return false; + } + + delayMs = SLVideo_GetQueuedVideoMS(m_stream); + } + } + + { + CSingleLock lock(m_delayMutex); + + if (delayMs > 0) + { + //CLog::Log(LOGERROR, "CSteamLinkVideoStream - Delay = %u ms", delayMs); + m_delay.Set(delayMs); + } + else + ;//CLog::Log(LOGERROR, "CSteamLinkVideoStream - No delay!!!"); //! @todo + } + + return true; +} + +void CSteamLinkVideoStream::SetSpeed(float fps) +{ + CSingleLock lock(m_streamMutex); + + //! @todo + CLog::Log(LOGERROR, "CSteamLinkVideoStream - SetSpeed() not implemented!"); +} + +unsigned int CSteamLinkVideoStream::GetDelayMs() +{ + CSingleLock lock(m_delayMutex); + + return m_delay.MillisLeft(); +} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoStream.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoStream.h new file mode 100644 index 0000000000..af5b3249bb --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoStream.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * http://kodi.tv + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ +#pragma once + +#include "threads/CriticalSection.h" +#include "threads/SystemClock.h" + +// Steam Link video API +#include "SLVideo.h" + +#include + +namespace STEAMLINK +{ + +class CSteamLinkVideoStream +{ +public: + CSteamLinkVideoStream(CSLVideoContext *pContext, + ESLVideoFormat format, + unsigned int fpsRate = 0, + unsigned int fpsScale = 0); + + ~CSteamLinkVideoStream() { Close(); } + + bool Open(); + void Close(); + + bool Flush(); + + bool WriteData(const uint8_t* data, size_t size); + + void SetSpeed(float speed); + + unsigned int GetDelayMs(); + +private: + // Construction parameters + CSLVideoContext * const m_pContext; + const ESLVideoFormat m_eFormat; + const unsigned int m_fpsRate; + const unsigned int m_fpsScale; + + // Stream parameters + CSLVideoStream *m_stream; + CCriticalSection m_streamMutex; + XbmcThreads::EndTime m_delay;; + CCriticalSection m_delayMutex; +}; + +} diff --git a/xbmc/cores/VideoPlayer/IVideoPlayer.h b/xbmc/cores/VideoPlayer/IVideoPlayer.h index 0b676c9b61..520131f9cf 100644 --- a/xbmc/cores/VideoPlayer/IVideoPlayer.h +++ b/xbmc/cores/VideoPlayer/IVideoPlayer.h @@ -111,6 +111,7 @@ class IDVDStreamPlayerVideo : public IDVDStreamPlayer virtual int GetDecoderBufferSize() { return 0; } virtual int GetDecoderFreeSpace() = 0; virtual bool IsEOS() { return false; }; + virtual bool CanFFRW() { return true; } }; class CDVDAudioCodec; diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index bd11cd8a76..e779fc857c 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -3141,6 +3141,12 @@ bool CVideoPlayer::CanSeek() return m_State.canseek; } +bool CVideoPlayer::CanFFRW() +{ + CSingleLock lock(m_StateSection); + return m_State.canffrw; +} + void CVideoPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride) { if (!m_State.canseek) @@ -5046,6 +5052,9 @@ void CVideoPlayer::UpdatePlayState(double timeout) state.canseek = m_pInputStream->CanSeek(); } + state.canffrw = m_VideoPlayerVideo->CanFFRW(); + + if (m_Edl.HasCut()) { state.time = (double) m_Edl.RemoveCutTime(llrint(state.time)); diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.h b/xbmc/cores/VideoPlayer/VideoPlayer.h index 41ff6b4c86..497100e6e2 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.h +++ b/xbmc/cores/VideoPlayer/VideoPlayer.h @@ -131,6 +131,7 @@ struct SPlayerState bool recording; // are we currently recording bool canpause; // pvr: can pause the current playing item bool canseek; // pvr: can seek in the current playing item + bool canffrw; bool caching; int64_t cache_bytes; // number of bytes current's cached @@ -302,6 +303,7 @@ class CVideoPlayer : public IPlayer, public CThread, public IVideoPlayer, public virtual bool HasRDS() const; virtual bool IsPassthrough() const; virtual bool CanSeek(); + virtual bool CanFFRW() override; virtual void Seek(bool bPlus, bool bLargeStep, bool bChapterOverride); virtual bool SeekScene(bool bPlus = true); virtual void SeekPercentage(float iPercent); diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp index 89db27cce0..73315f6100 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp @@ -654,6 +654,11 @@ void CVideoPlayerVideo::SetSpeed(int speed) m_speed = speed; } +bool CVideoPlayerVideo::CanFFRW() +{ + return m_renderManager.CanFFRW(); +} + void CVideoPlayerVideo::Flush(bool sync) { /* flush using message as this get's called from VideoPlayer thread */ diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.h b/xbmc/cores/VideoPlayer/VideoPlayerVideo.h index 0d4100e58e..c77c9f8ea3 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerVideo.h +++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.h @@ -91,6 +91,7 @@ class CVideoPlayerVideo : public CThread, public IDVDStreamPlayerVideo int GetVideoBitrate(); std::string GetStereoMode(); void SetSpeed(int iSpeed); + virtual bool CanFFRW() override; // classes CDVDOverlayContainer* m_pOverlayContainer; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp index f9b3bfb4eb..09df778fde 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp @@ -389,7 +389,7 @@ void CBaseRenderer::ManageRenderArea() else if(stereo_view == RENDER_STEREO_VIEW_RIGHT) stereo_view = RENDER_STEREO_VIEW_LEFT; } - if (m_format != RENDER_FMT_BYPASS) + if (IsGuiLayer()) { switch(stereo_mode) { diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h index c7294cea37..ef8830b84f 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h @@ -92,6 +92,7 @@ class CBaseRenderer virtual void ReleaseBuffer(int idx) { } virtual bool NeedBuffer(int idx) { return false; } virtual bool IsGuiLayer() { return true; } + virtual bool CanFFRW() { return true; } // Render info, can be called before configure virtual CRenderInfo GetRenderInfo() { return CRenderInfo(); } virtual void Update() = 0; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/Makefile.in b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/Makefile.in index 2f916de794..82067204f3 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/Makefile.in +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/Makefile.in @@ -26,6 +26,10 @@ ifeq ($(findstring osx,@ARCH@),osx) SRCS += RendererVTBGL.cpp endif +ifeq (@USE_STEAMLINK@,1) +SRCS += RendererSteamLink.cpp +endif + LIB=HwDecRender.a include ../../../../../Makefile.include diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererSteamLink.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererSteamLink.cpp new file mode 100644 index 0000000000..8d17599bca --- /dev/null +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererSteamLink.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ + +#include "RendererSteamLink.h" +#include "cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoBuffer.h" +#include "cores/VideoPlayer/DVDCodecs/Video/SteamLinkVideoStream.h" +#include "utils/log.h" + +// Steam Link video API +#include "SLVideo.h" + +#include + +#include + +using namespace STEAMLINK; + +void CRendererSteamLink::AddVideoPictureHW(DVDVideoPicture &picture, int index) +{ + if (picture.steamlink) + { + YUVBUFFER &buf = m_buffers[index]; + buf.hwDec = new CSteamLinkBuffer(std::move(*picture.steamlink)); + } +} + +void CRendererSteamLink::PreInit() +{ + CLinuxRendererGLES::PreInit(); + + m_formats.clear(); + m_formats.push_back(RENDER_FMT_STEAMLINK); +} + +void CRendererSteamLink::ReleaseBuffer(int idx) +{ + YUVBUFFER &buf = m_buffers[idx]; + + delete static_cast(buf.hwDec); + buf.hwDec = nullptr; +} + +bool CRendererSteamLink::LoadShadersHook() +{ + CLog::Log(LOGNOTICE, "CRendererSteamLink: Using STEAMLINK render method"); + m_textureTarget = GL_TEXTURE_2D; + m_renderMethod = RENDER_BYPASS; + return true; +} + +bool CRendererSteamLink::RenderUpdateVideoHook(bool clear, DWORD flags /* = 0 */, DWORD alpha /* = 255 */) +{ + ManageRenderArea(); + + CSteamLinkBuffer* pBuffer = static_cast(m_buffers[m_iYV12RenderBuffer].hwDec); + + if (pBuffer && pBuffer->buffer) + { + CSteamLinkBuffer buffer(std::move(*pBuffer)); + + buffer.stream->WriteData(buffer.buffer.get(), buffer.size); + } + + return true; +} + +int CRendererSteamLink::GetImageHook(YV12Image *image, int source /* = AUTOSOURCE */, bool readonly /* = false */) +{ + return source; +} diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererSteamLink.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererSteamLink.h new file mode 100644 index 0000000000..58bd18d9e0 --- /dev/null +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererSteamLink.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ +#pragma once + +#include "system.h" // for HAS_GLES, needed for LinuxRendererGLES.h + +#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h" + +namespace STEAMLINK +{ + +class CRendererSteamLink : public CLinuxRendererGLES +{ +public: + CRendererSteamLink() = default; + virtual ~CRendererSteamLink() = default; + + // implementation of CBaseRenderer via CLinuxRendererGLES + virtual void AddVideoPictureHW(DVDVideoPicture &picture, int index) override; + virtual bool IsPictureHW(DVDVideoPicture &picture) override { return true; } + virtual void PreInit() override; + virtual void ReleaseBuffer(int idx) override; + virtual bool IsGuiLayer() override { return false; } + virtual bool CanFFRW() override { return false; } + virtual bool SupportsMultiPassRendering() override { return false; } + virtual bool Supports(ERENDERFEATURE feature) override { return false; } + virtual bool Supports(ESCALINGMETHOD method) override { return false; } + +protected: + // implementation of CLinuxRendererGLES + virtual bool LoadShadersHook() override; + virtual bool RenderUpdateVideoHook(bool clear, DWORD flags = 0, DWORD alpha = 255) override; + virtual int GetImageHook(YV12Image *image, int source = -1, bool readonly = false) override; +}; + +} diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp index f478fb0c02..98a39c39ce 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp @@ -710,7 +710,7 @@ inline void CLinuxRendererGLES::ReorderDrawPoints() bool CLinuxRendererGLES::CreateTexture(int index) { - if (m_format == RENDER_FMT_BYPASS) + if (!IsGuiLayer()) { CreateBYPASSTexture(index); return true; @@ -732,7 +732,7 @@ bool CLinuxRendererGLES::CreateTexture(int index) void CLinuxRendererGLES::DeleteTexture(int index) { - if (m_format == RENDER_FMT_BYPASS) + if (!IsGuiLayer()) { DeleteBYPASSTexture(index); } @@ -750,7 +750,7 @@ void CLinuxRendererGLES::DeleteTexture(int index) bool CLinuxRendererGLES::UploadTexture(int index) { // Now that we now the render method, setup texture function handlers - if (m_format == RENDER_FMT_BYPASS) + if (!IsGuiLayer()) { UploadBYPASSTexture(index); return true; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderFormats.h b/xbmc/cores/VideoPlayer/VideoRenderers/RenderFormats.h index 39f62fa616..6d2c2d0df0 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/RenderFormats.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderFormats.h @@ -45,6 +45,7 @@ enum ERenderFormat { RENDER_FMT_IMXMAP, RENDER_FMT_MMAL, RENDER_FMT_AML, + RENDER_FMT_STEAMLINK, }; struct CRenderInfo diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp index 420b5b5d8e..0acc08eecb 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp @@ -69,6 +69,10 @@ #include "HwDecRender/RendererMediaCodecSurface.h" #endif +#if defined(HAS_STEAMLINK) + #include "HwDecRender/RendererSteamLink.h" +#endif + #if defined(TARGET_POSIX) #include "linux/XTimeUtils.h" #endif @@ -111,6 +115,7 @@ static std::string GetRenderFormatName(ERenderFormat format) case RENDER_FMT_IMXMAP: return "IMXMAP"; case RENDER_FMT_MMAL: return "MMAL"; case RENDER_FMT_AML: return "AMLCODEC"; + case RENDER_FMT_STEAMLINK: return "STEAMLINK"; case RENDER_FMT_NONE: return "NONE"; } return "UNKNOWN"; @@ -580,6 +585,12 @@ void CRenderManager::CreateRenderer() { #if defined(HAS_LIBAMCODEC) m_pRenderer = new CRendererAML; +#endif + } + else if (m_format == RENDER_FMT_STEAMLINK) + { +#if defined(HAS_STEAMLINK) + m_pRenderer = new STEAMLINK::CRendererSteamLink; #endif } else if (m_format != RENDER_FMT_NONE) @@ -1358,6 +1369,14 @@ void CRenderManager::DiscardBuffer() m_presentevent.notifyAll(); } +bool CRenderManager::CanFFRW() +{ + if (m_pRenderer) + return m_pRenderer->CanFFRW(); + + return true; +} + bool CRenderManager::GetStats(int &lateframes, double &pts, int &queued, int &discard) { CSingleLock lock(m_presentlock); diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.h b/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.h index d5340877bc..d1e2117360 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.h @@ -156,6 +156,8 @@ class CRenderManager void SetDelay(int delay) { m_videoDelay = delay; }; int GetDelay() { return m_videoDelay; }; + bool CanFFRW(); + protected: void PresentSingle(bool clear, DWORD flags, DWORD alpha); diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/SteamLinkRenderer.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/SteamLinkRenderer.cpp new file mode 100644 index 0000000000..d623fac060 --- /dev/null +++ b/xbmc/cores/VideoPlayer/VideoRenderers/SteamLinkRenderer.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ + +#include "SteamLinkRenderer.h" +#include "utils/log.h" + +#include + +using namespace STEAMLINK; + +bool CSteamLinkRenderer::LoadShadersHook() +{ + CLog::Log(LOGNOTICE, "CSteamLinkRenderer: Using STEAMLINK render method"); + m_textureTarget = GL_TEXTURE_2D; + m_renderMethod = RENDER_BYPASS; + return false; +} + +int CSteamLinkRenderer::GetImageHook(YV12Image *image, int source /* = AUTOSOURCE */, bool readonly /* = false */) +{ + return source; +} diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/SteamLinkRenderer.h b/xbmc/cores/VideoPlayer/VideoRenderers/SteamLinkRenderer.h new file mode 100644 index 0000000000..9f609b9800 --- /dev/null +++ b/xbmc/cores/VideoPlayer/VideoRenderers/SteamLinkRenderer.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 Team Kodi + * Copyright (C) 2016 Valve Corporation + * + * 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, 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; see the file COPYING. If not, see + * . + * + */ +#pragma once + +#include "system.h" // for HAS_GLES, needed for LinuxRendererGLES.h + +#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h" + +namespace STEAMLINK +{ + +class CSteamLinkRenderer : public CLinuxRendererGLES +{ +public: + CSteamLinkRenderer() { } + virtual ~CSteamLinkRenderer() { } + + // implementation of CBaseRenderer via CLinuxRendererGLES + virtual bool IsGuiLayer() override { return false; } + virtual bool SupportsMultiPassRendering() override { return false; } + virtual bool Supports(ERENDERFEATURE feature) override { return false; } + virtual bool Supports(ESCALINGMETHOD method) override { return false; } + +protected: + // implementation of CLinuxRendererGLES + bool LoadShadersHook(); + bool RenderHook(int index) { return true; } + bool RenderUpdateVideoHook(bool clear, DWORD flags = 0, DWORD alpha = 255) { return true; } + int GetImageHook(YV12Image *image, int source = -1, bool readonly = false); +}; + +} diff --git a/xbmc/windowing/WinEventsSDL.cpp b/xbmc/windowing/WinEventsSDL.cpp index b0658d1ff3..22ea6cad6c 100644 --- a/xbmc/windowing/WinEventsSDL.cpp +++ b/xbmc/windowing/WinEventsSDL.cpp @@ -36,7 +36,7 @@ #include "platform/darwin/osx/CocoaInterface.h" #endif -#if defined(TARGET_POSIX) && !defined(TARGET_DARWIN) && !defined(TARGET_ANDROID) +#ifdef HAVE_X11 #include #include #include "input/XBMC_keysym.h" @@ -45,7 +45,7 @@ using namespace KODI::MESSAGING; -#if defined(TARGET_POSIX) && !defined(TARGET_DARWIN) +#ifdef HAVE_X11 // The following chunk of code is Linux specific. For keys that have // with keysym.sym set to zero it checks the scan code, and sets the sym // for some known scan codes. This is mostly the multimedia keys. @@ -215,6 +215,276 @@ static uint16_t SymFromScancode(uint16_t scancode) } #endif // End of checks for keysym.sym == 0 +#if HAVE_SDL_VERSION == 2 +// The keysyms changed from SDL 1 to SDL 2, so create a mapping +static std::map s_keysymTable; + +static void InitializeKeysymLookup() +{ + s_keysymTable[SDLK_RETURN] = XBMCK_RETURN; + s_keysymTable[SDLK_ESCAPE] = XBMCK_ESCAPE; + s_keysymTable[SDLK_BACKSPACE] = XBMCK_BACKSPACE; + s_keysymTable[SDLK_TAB] = XBMCK_TAB; + s_keysymTable[SDLK_SPACE] = XBMCK_SPACE; + s_keysymTable[SDLK_EXCLAIM] = XBMCK_EXCLAIM; + s_keysymTable[SDLK_QUOTEDBL] = XBMCK_QUOTEDBL; + s_keysymTable[SDLK_HASH] = XBMCK_HASH; + s_keysymTable[SDLK_PERCENT] = XBMCK_PERCENT; + s_keysymTable[SDLK_DOLLAR] = XBMCK_DOLLAR; + s_keysymTable[SDLK_AMPERSAND] = XBMCK_AMPERSAND; + s_keysymTable[SDLK_QUOTE] = XBMCK_QUOTE; + s_keysymTable[SDLK_LEFTPAREN] = XBMCK_LEFTPAREN; + s_keysymTable[SDLK_RIGHTPAREN] = XBMCK_RIGHTPAREN; + s_keysymTable[SDLK_ASTERISK] = XBMCK_ASTERISK; + s_keysymTable[SDLK_PLUS] = XBMCK_PLUS; + s_keysymTable[SDLK_COMMA] = XBMCK_COMMA; + s_keysymTable[SDLK_MINUS] = XBMCK_MINUS; + s_keysymTable[SDLK_PERIOD] = XBMCK_PERIOD; + s_keysymTable[SDLK_SLASH] = XBMCK_SLASH; + s_keysymTable[SDLK_0] = XBMCK_0; + s_keysymTable[SDLK_1] = XBMCK_1; + s_keysymTable[SDLK_2] = XBMCK_2; + s_keysymTable[SDLK_3] = XBMCK_3; + s_keysymTable[SDLK_4] = XBMCK_4; + s_keysymTable[SDLK_5] = XBMCK_5; + s_keysymTable[SDLK_6] = XBMCK_6; + s_keysymTable[SDLK_7] = XBMCK_7; + s_keysymTable[SDLK_8] = XBMCK_8; + s_keysymTable[SDLK_9] = XBMCK_9; + s_keysymTable[SDLK_COLON] = XBMCK_COLON; + s_keysymTable[SDLK_SEMICOLON] = XBMCK_SEMICOLON; + s_keysymTable[SDLK_LESS] = XBMCK_LESS; + s_keysymTable[SDLK_EQUALS] = XBMCK_EQUALS; + s_keysymTable[SDLK_GREATER] = XBMCK_GREATER; + s_keysymTable[SDLK_QUESTION] = XBMCK_QUESTION; + s_keysymTable[SDLK_AT] = XBMCK_AT; + s_keysymTable[SDLK_LEFTBRACKET] = XBMCK_LEFTBRACKET; + s_keysymTable[SDLK_BACKSLASH] = XBMCK_BACKSLASH; + s_keysymTable[SDLK_RIGHTBRACKET] = XBMCK_RIGHTBRACKET; + s_keysymTable[SDLK_CARET] = XBMCK_CARET; + s_keysymTable[SDLK_UNDERSCORE] = XBMCK_UNDERSCORE; + s_keysymTable[SDLK_BACKQUOTE] = XBMCK_BACKQUOTE; + s_keysymTable[SDLK_a] = XBMCK_a; + s_keysymTable[SDLK_b] = XBMCK_b; + s_keysymTable[SDLK_c] = XBMCK_c; + s_keysymTable[SDLK_d] = XBMCK_d; + s_keysymTable[SDLK_e] = XBMCK_e; + s_keysymTable[SDLK_f] = XBMCK_f; + s_keysymTable[SDLK_g] = XBMCK_g; + s_keysymTable[SDLK_h] = XBMCK_h; + s_keysymTable[SDLK_i] = XBMCK_i; + s_keysymTable[SDLK_j] = XBMCK_j; + s_keysymTable[SDLK_k] = XBMCK_k; + s_keysymTable[SDLK_l] = XBMCK_l; + s_keysymTable[SDLK_m] = XBMCK_m; + s_keysymTable[SDLK_n] = XBMCK_n; + s_keysymTable[SDLK_o] = XBMCK_o; + s_keysymTable[SDLK_p] = XBMCK_p; + s_keysymTable[SDLK_q] = XBMCK_q; + s_keysymTable[SDLK_r] = XBMCK_r; + s_keysymTable[SDLK_s] = XBMCK_s; + s_keysymTable[SDLK_t] = XBMCK_t; + s_keysymTable[SDLK_u] = XBMCK_u; + s_keysymTable[SDLK_v] = XBMCK_v; + s_keysymTable[SDLK_w] = XBMCK_w; + s_keysymTable[SDLK_x] = XBMCK_x; + s_keysymTable[SDLK_y] = XBMCK_y; + s_keysymTable[SDLK_z] = XBMCK_z; + s_keysymTable[SDLK_CAPSLOCK] = XBMCK_CAPSLOCK; + s_keysymTable[SDLK_F1] = XBMCK_F1; + s_keysymTable[SDLK_F2] = XBMCK_F2; + s_keysymTable[SDLK_F3] = XBMCK_F3; + s_keysymTable[SDLK_F4] = XBMCK_F4; + s_keysymTable[SDLK_F5] = XBMCK_F5; + s_keysymTable[SDLK_F6] = XBMCK_F6; + s_keysymTable[SDLK_F7] = XBMCK_F7; + s_keysymTable[SDLK_F8] = XBMCK_F8; + s_keysymTable[SDLK_F9] = XBMCK_F9; + s_keysymTable[SDLK_F10] = XBMCK_F10; + s_keysymTable[SDLK_F11] = XBMCK_F11; + s_keysymTable[SDLK_F12] = XBMCK_F12; + s_keysymTable[SDLK_PRINTSCREEN] = XBMCK_PRINT; + s_keysymTable[SDLK_SCROLLLOCK] = XBMCK_SCROLLOCK; + s_keysymTable[SDLK_PAUSE] = XBMCK_PAUSE; + s_keysymTable[SDLK_INSERT] = XBMCK_INSERT; + s_keysymTable[SDLK_HOME] = XBMCK_HOME; + s_keysymTable[SDLK_PAGEUP] = XBMCK_PAGEUP; + s_keysymTable[SDLK_DELETE] = XBMCK_DELETE; + s_keysymTable[SDLK_END] = XBMCK_END; + s_keysymTable[SDLK_PAGEDOWN] = XBMCK_PAGEDOWN; + s_keysymTable[SDLK_RIGHT] = XBMCK_RIGHT; + s_keysymTable[SDLK_LEFT] = XBMCK_LEFT; + s_keysymTable[SDLK_DOWN] = XBMCK_DOWN; + s_keysymTable[SDLK_UP] = XBMCK_UP; + s_keysymTable[SDLK_NUMLOCKCLEAR] = XBMCK_NUMLOCK; + s_keysymTable[SDLK_KP_DIVIDE] = XBMCK_KP_DIVIDE; + s_keysymTable[SDLK_KP_MULTIPLY] = XBMCK_KP_MULTIPLY; + s_keysymTable[SDLK_KP_MINUS] = XBMCK_KP_MINUS; + s_keysymTable[SDLK_KP_PLUS] = XBMCK_KP_PLUS; + s_keysymTable[SDLK_KP_ENTER] = XBMCK_KP_ENTER; + s_keysymTable[SDLK_KP_1] = XBMCK_KP1; + s_keysymTable[SDLK_KP_2] = XBMCK_KP2; + s_keysymTable[SDLK_KP_3] = XBMCK_KP3; + s_keysymTable[SDLK_KP_4] = XBMCK_KP4; + s_keysymTable[SDLK_KP_5] = XBMCK_KP5; + s_keysymTable[SDLK_KP_6] = XBMCK_KP6; + s_keysymTable[SDLK_KP_7] = XBMCK_KP7; + s_keysymTable[SDLK_KP_8] = XBMCK_KP8; + s_keysymTable[SDLK_KP_9] = XBMCK_KP9; + s_keysymTable[SDLK_KP_0] = XBMCK_KP0; + s_keysymTable[SDLK_KP_PERIOD] = XBMCK_KP_PERIOD; + s_keysymTable[SDLK_APPLICATION] = XBMCK_LAUNCH_APP1; + s_keysymTable[SDLK_POWER] = XBMCK_POWER; + s_keysymTable[SDLK_KP_EQUALS] = XBMCK_KP_EQUALS; + s_keysymTable[SDLK_F13] = XBMCK_F13; + s_keysymTable[SDLK_F14] = XBMCK_F14; + s_keysymTable[SDLK_F15] = XBMCK_F15; +/* + s_keysymTable[SDLK_F16] = XBMCK_F16; + s_keysymTable[SDLK_F17] = XBMCK_F17; + s_keysymTable[SDLK_F18] = XBMCK_F18; + s_keysymTable[SDLK_F19] = XBMCK_F19; + s_keysymTable[SDLK_F20] = XBMCK_F20; + s_keysymTable[SDLK_F21] = XBMCK_F21; + s_keysymTable[SDLK_F22] = XBMCK_F22; + s_keysymTable[SDLK_F23] = XBMCK_F23; + s_keysymTable[SDLK_F24] = XBMCK_F24; + s_keysymTable[SDLK_EXECUTE] = XBMCK_EXECUTE; +*/ + s_keysymTable[SDLK_HELP] = XBMCK_HELP; + s_keysymTable[SDLK_MENU] = XBMCK_MENU; +/* + s_keysymTable[SDLK_SELECT] = XBMCK_SELECT; + s_keysymTable[SDLK_STOP] = XBMCK_STOP; + s_keysymTable[SDLK_AGAIN] = XBMCK_AGAIN; + s_keysymTable[SDLK_UNDO] = XBMCK_UNDO; + s_keysymTable[SDLK_CUT] = XBMCK_CUT; + s_keysymTable[SDLK_COPY] = XBMCK_COPY; + s_keysymTable[SDLK_PASTE] = XBMCK_PASTE; + s_keysymTable[SDLK_FIND] = XBMCK_FIND; +*/ + s_keysymTable[SDLK_MUTE] = XBMCK_VOLUME_MUTE; + s_keysymTable[SDLK_VOLUMEUP] = XBMCK_VOLUME_UP; + s_keysymTable[SDLK_VOLUMEDOWN] = XBMCK_VOLUME_DOWN; +/* + s_keysymTable[SDLK_KP_COMMA] = XBMCK_KP_COMMA; + s_keysymTable[SDLK_KP_EQUALSAS400] = XBMCK_KP_EQUALSAS400; + s_keysymTable[SDLK_ALTERASE] = XBMCK_ALTERASE; +*/ + s_keysymTable[SDLK_SYSREQ] = XBMCK_SYSREQ; +/* + s_keysymTable[SDLK_CANCEL] = XBMCK_CANCEL; +*/ + s_keysymTable[SDLK_CLEAR] = XBMCK_CLEAR; +/* + s_keysymTable[SDLK_PRIOR] = XBMCK_PRIOR; + s_keysymTable[SDLK_RETURN2] = XBMCK_RETURN2; + s_keysymTable[SDLK_SEPARATOR] = XBMCK_SEPARATOR; + s_keysymTable[SDLK_OUT] = XBMCK_OUT; + s_keysymTable[SDLK_OPER] = XBMCK_OPER; + s_keysymTable[SDLK_CLEARAGAIN] = XBMCK_CLEARAGAIN; + s_keysymTable[SDLK_CRSEL] = XBMCK_CRSEL; + s_keysymTable[SDLK_EXSEL] = XBMCK_EXSEL; + s_keysymTable[SDLK_KP_00] = XBMCK_KP_00; + s_keysymTable[SDLK_KP_000] = XBMCK_KP_000; + s_keysymTable[SDLK_THOUSANDSSEPARATOR] = XBMCK_THOUSANDSSEPARATOR; + s_keysymTable[SDLK_DECIMALSEPARATOR] = XBMCK_DECIMALSEPARATOR; + s_keysymTable[SDLK_CURRENCYUNIT] = XBMCK_CURRENCYUNIT; + s_keysymTable[SDLK_CURRENCYSUBUNIT] = XBMCK_CURRENCYSUBUNIT; + s_keysymTable[SDLK_KP_LEFTPAREN] = XBMCK_KP_LEFTPAREN; + s_keysymTable[SDLK_KP_RIGHTPAREN] = XBMCK_KP_RIGHTPAREN; + s_keysymTable[SDLK_KP_LEFTBRACE] = XBMCK_KP_LEFTBRACE; + s_keysymTable[SDLK_KP_RIGHTBRACE] = XBMCK_KP_RIGHTBRACE; + s_keysymTable[SDLK_KP_TAB] = XBMCK_KP_TAB; + s_keysymTable[SDLK_KP_BACKSPACE] = XBMCK_KP_BACKSPACE; + s_keysymTable[SDLK_KP_A] = XBMCK_KP_A; + s_keysymTable[SDLK_KP_B] = XBMCK_KP_B; + s_keysymTable[SDLK_KP_C] = XBMCK_KP_C; + s_keysymTable[SDLK_KP_D] = XBMCK_KP_D; + s_keysymTable[SDLK_KP_E] = XBMCK_KP_E; + s_keysymTable[SDLK_KP_F] = XBMCK_KP_F; + s_keysymTable[SDLK_KP_XOR] = XBMCK_KP_XOR; + s_keysymTable[SDLK_KP_POWER] = XBMCK_KP_POWER; + s_keysymTable[SDLK_KP_PERCENT] = XBMCK_KP_PERCENT; + s_keysymTable[SDLK_KP_LESS] = XBMCK_KP_LESS; + s_keysymTable[SDLK_KP_GREATER] = XBMCK_KP_GREATER; + s_keysymTable[SDLK_KP_AMPERSAND] = XBMCK_KP_AMPERSAND; + s_keysymTable[SDLK_KP_DBLAMPERSAND] = XBMCK_KP_DBLAMPERSAND; + s_keysymTable[SDLK_KP_VERTICALBAR] = XBMCK_KP_VERTICALBAR; + s_keysymTable[SDLK_KP_DBLVERTICALBAR] = XBMCK_KP_DBLVERTICALBAR; + s_keysymTable[SDLK_KP_COLON] = XBMCK_KP_COLON; + s_keysymTable[SDLK_KP_HASH] = XBMCK_KP_HASH; + s_keysymTable[SDLK_KP_SPACE] = XBMCK_KP_SPACE; + s_keysymTable[SDLK_KP_AT] = XBMCK_KP_AT; + s_keysymTable[SDLK_KP_EXCLAM] = XBMCK_KP_EXCLAM; + s_keysymTable[SDLK_KP_MEMSTORE] = XBMCK_KP_MEMSTORE; + s_keysymTable[SDLK_KP_MEMRECALL] = XBMCK_KP_MEMRECALL; + s_keysymTable[SDLK_KP_MEMCLEAR] = XBMCK_KP_MEMCLEAR; + s_keysymTable[SDLK_KP_MEMADD] = XBMCK_KP_MEMADD; + s_keysymTable[SDLK_KP_MEMSUBTRACT] = XBMCK_KP_MEMSUBTRACT; + s_keysymTable[SDLK_KP_MEMMULTIPLY] = XBMCK_KP_MEMMULTIPLY; + s_keysymTable[SDLK_KP_MEMDIVIDE] = XBMCK_KP_MEMDIVIDE; + s_keysymTable[SDLK_KP_PLUSMINUS] = XBMCK_KP_PLUSMINUS; + s_keysymTable[SDLK_KP_CLEAR] = XBMCK_KP_CLEAR; + s_keysymTable[SDLK_KP_CLEARENTRY] = XBMCK_KP_CLEARENTRY; + s_keysymTable[SDLK_KP_BINARY] = XBMCK_KP_BINARY; + s_keysymTable[SDLK_KP_OCTAL] = XBMCK_KP_OCTAL; + s_keysymTable[SDLK_KP_DECIMAL] = XBMCK_KP_DECIMAL; + s_keysymTable[SDLK_KP_HEXADECIMAL] = XBMCK_KP_HEXADECIMAL; +*/ + s_keysymTable[SDLK_LCTRL] = XBMCK_LCTRL; + s_keysymTable[SDLK_LSHIFT] = XBMCK_LSHIFT; + s_keysymTable[SDLK_LALT] = XBMCK_LALT; + s_keysymTable[SDLK_LGUI] = XBMCK_LSUPER; + s_keysymTable[SDLK_RCTRL] = XBMCK_RCTRL; + s_keysymTable[SDLK_RSHIFT] = XBMCK_RSHIFT; + s_keysymTable[SDLK_RALT] = XBMCK_RALT; + s_keysymTable[SDLK_RGUI] = XBMCK_RSUPER; + s_keysymTable[SDLK_MODE] = XBMCK_MODE; + s_keysymTable[SDLK_AUDIONEXT] = XBMCK_MEDIA_NEXT_TRACK; + s_keysymTable[SDLK_AUDIOPREV] = XBMCK_MEDIA_PREV_TRACK; + s_keysymTable[SDLK_AUDIOSTOP] = XBMCK_MEDIA_STOP; + s_keysymTable[SDLK_AUDIOPLAY] = XBMCK_MEDIA_PLAY_PAUSE; + s_keysymTable[SDLK_AUDIOMUTE] = XBMCK_VOLUME_MUTE; + s_keysymTable[SDLK_MEDIASELECT] = XBMCK_LAUNCH_MEDIA_SELECT; + s_keysymTable[SDLK_WWW] = XBMCK_BROWSER_HOME; + s_keysymTable[SDLK_MAIL] = XBMCK_LAUNCH_MAIL; + s_keysymTable[SDLK_CALCULATOR] = XBMCK_LAUNCH_APP1; + s_keysymTable[SDLK_COMPUTER] = XBMCK_LAUNCH_FILE_BROWSER; + s_keysymTable[SDLK_AC_SEARCH] = XBMCK_BROWSER_SEARCH; + s_keysymTable[SDLK_AC_HOME] = XBMCK_BROWSER_HOME; + s_keysymTable[SDLK_AC_BACK] = XBMCK_BROWSER_BACK; + s_keysymTable[SDLK_AC_FORWARD] = XBMCK_BROWSER_FORWARD; + s_keysymTable[SDLK_AC_STOP] = XBMCK_BROWSER_STOP; + s_keysymTable[SDLK_AC_REFRESH] = XBMCK_BROWSER_REFRESH; + s_keysymTable[SDLK_AC_BOOKMARKS] = XBMCK_BROWSER_FAVORITES; +/* + s_keysymTable[SDLK_BRIGHTNESSDOWN] = XBMCK_BRIGHTNESSDOWN; + s_keysymTable[SDLK_BRIGHTNESSUP] = XBMCK_BRIGHTNESSUP; + s_keysymTable[SDLK_DISPLAYSWITCH] = XBMCK_DISPLAYSWITCH; + s_keysymTable[SDLK_KBDILLUMTOGGLE] = XBMCK_KBDILLUMTOGGLE; + s_keysymTable[SDLK_KBDILLUMDOWN] = XBMCK_KBDILLUMDOWN; + s_keysymTable[SDLK_KBDILLUMUP] = XBMCK_KBDILLUMUP; +*/ + s_keysymTable[SDLK_EJECT] = XBMCK_EJECT; + s_keysymTable[SDLK_SLEEP] = XBMCK_SLEEP; +} + +static XBMCKey LookupKeysym(SDL_Keycode keycode) +{ + if (s_keysymTable.size() == 0) { + InitializeKeysymLookup(); + } + + std::map::iterator it = s_keysymTable.find(keycode); + if (it != s_keysymTable.end()) { + return it->second; + } + return XBMCK_UNKNOWN; +} + +#endif // SDL 2.0 + bool CWinEventsSDL::MessagePump() { SDL_Event event; @@ -229,6 +499,7 @@ bool CWinEventsSDL::MessagePump() CApplicationMessenger::GetInstance().PostMsg(TMSG_QUIT); break; +#if HAVE_SDL_VERSION == 1 case SDL_ACTIVEEVENT: //If the window was inconified or restored if( event.active.state & SDL_APPACTIVE ) @@ -270,7 +541,7 @@ bool CWinEventsSDL::MessagePump() mod |= XBMCKMOD_LSUPER; newEvent.key.keysym.mod = (XBMCMod) mod; -#if defined(TARGET_POSIX) && !defined(TARGET_DARWIN) +#ifdef HAVE_X11 // If the keysym.sym is zero try to get it from the scan code if (newEvent.key.keysym.sym == 0) newEvent.key.keysym.sym = (XBMCKey) SymFromScancode(newEvent.key.keysym.scancode); @@ -353,6 +624,7 @@ bool CWinEventsSDL::MessagePump() ret |= g_application.OnEvent(newEvent); break; } + case SDL_VIDEORESIZE: { // Under newer osx versions sdl is so fucked up that it even fires resize events @@ -372,6 +644,161 @@ bool CWinEventsSDL::MessagePump() g_windowManager.MarkDirty(); break; } + + case SDL_VIDEOEXPOSE: + { + g_windowManager.MarkDirty(); + break; + } +#endif // SDL 1.2 + +#if HAVE_SDL_VERSION == 2 + case SDL_WINDOWEVENT: + { + switch( event.window.event ) + { + case SDL_WINDOWEVENT_MINIMIZED: + if (g_application.GetRenderGUI()) + { + g_application.SetRenderGUI(false); + g_Windowing.NotifyAppActiveChange(g_application.GetRenderGUI()); + } + break; + case SDL_WINDOWEVENT_RESTORED: + if (!g_application.GetRenderGUI()) + { + g_application.SetRenderGUI(true); + g_Windowing.NotifyAppActiveChange(g_application.GetRenderGUI()); + } + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + g_application.m_AppFocused = true; + g_Windowing.NotifyAppFocusChange(g_application.m_AppFocused); + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + g_application.m_AppFocused = false; + g_Windowing.NotifyAppFocusChange(g_application.m_AppFocused); + break; + } + break; + } + + case SDL_KEYDOWN: + { + // process any platform specific shortcuts before handing off to XBMC +#ifdef TARGET_DARWIN_OSX + if (ProcessOSXShortcuts(event)) + { + ret = true; + break; + } +#endif + + // See if there's a text event associated with this keypress + // This won't catch multi-character composed input, but it's better than nothing + uint16_t unicode = 0; + SDL_Event next_event; + if (SDL_PeepEvents(&next_event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) > 0 && + next_event.type == SDL_TEXTINPUT) + { + SDL_PeepEvents(&next_event, 1, SDL_GETEVENT, SDL_TEXTINPUT, SDL_TEXTINPUT); + if (next_event.text.text[0] < 0xC0) + unicode = next_event.text.text[0]; + else + { + Uint16 *text16 = SDL_iconv_utf8_ucs2(next_event.text.text); + if (text16) + { + unicode = text16[0]; + SDL_free(text16); + } + } + } + + XBMC_Event newEvent; + newEvent.type = XBMC_KEYDOWN; + newEvent.key.keysym.scancode = event.key.keysym.scancode; + newEvent.key.keysym.sym = LookupKeysym(event.key.keysym.sym); + newEvent.key.keysym.unicode = unicode; + newEvent.key.state = event.key.state; + + // Check if the Windows keys are down because SDL doesn't flag this. + uint16_t mod = event.key.keysym.mod; + const uint8_t* keystate = SDL_GetKeyboardState(NULL); + if (keystate[SDL_SCANCODE_LGUI] || keystate[SDL_SCANCODE_RGUI]) + mod |= XBMCKMOD_LSUPER; + newEvent.key.keysym.mod = (XBMCMod) mod; + +#ifdef HAVE_X11 + // If the keysym.sym is zero try to get it from the scan code + if (newEvent.key.keysym.sym == 0) + newEvent.key.keysym.sym = (XBMCKey) SymFromScancode(newEvent.key.keysym.scancode); +#endif + + // don't handle any more messages in the queue until we've handled keydown, + // if a keyup is in the queue it will reset the keypress before it is handled. + ret |= g_application.OnEvent(newEvent); + break; + } + + case SDL_KEYUP: + { + XBMC_Event newEvent; + newEvent.type = XBMC_KEYUP; + newEvent.key.keysym.scancode = event.key.keysym.scancode; + newEvent.key.keysym.sym = LookupKeysym(event.key.keysym.sym); + newEvent.key.keysym.mod =(XBMCMod) event.key.keysym.mod; + newEvent.key.keysym.unicode = 0; + newEvent.key.state = event.key.state; + + ret |= g_application.OnEvent(newEvent); + break; + } + + case SDL_MOUSEBUTTONDOWN: + { + XBMC_Event newEvent; + newEvent.type = XBMC_MOUSEBUTTONDOWN; + newEvent.button.button = event.button.button; + newEvent.button.state = event.button.state; + newEvent.button.which = event.button.which; + newEvent.button.x = event.button.x; + newEvent.button.y = event.button.y; + + ret |= g_application.OnEvent(newEvent); + break; + } + + case SDL_MOUSEBUTTONUP: + { + XBMC_Event newEvent; + newEvent.type = XBMC_MOUSEBUTTONUP; + newEvent.button.button = event.button.button; + newEvent.button.state = event.button.state; + newEvent.button.which = event.button.which; + newEvent.button.x = event.button.x; + newEvent.button.y = event.button.y; + + ret |= g_application.OnEvent(newEvent); + break; + } + + case SDL_MOUSEMOTION: + { + XBMC_Event newEvent; + newEvent.type = XBMC_MOUSEMOTION; + newEvent.motion.xrel = event.motion.xrel; + newEvent.motion.yrel = event.motion.yrel; + newEvent.motion.state = event.motion.state; + newEvent.motion.which = event.motion.which; + newEvent.motion.x = event.motion.x; + newEvent.motion.y = event.motion.y; + + ret |= g_application.OnEvent(newEvent); + break; + } +#endif // SDL 2.0 + case SDL_USEREVENT: { XBMC_Event newEvent; @@ -380,9 +807,6 @@ bool CWinEventsSDL::MessagePump() ret |= g_application.OnEvent(newEvent); break; } - case SDL_VIDEOEXPOSE: - g_windowManager.MarkDirty(); - break; } memset(&event, 0, sizeof(SDL_Event)); } @@ -392,11 +816,16 @@ bool CWinEventsSDL::MessagePump() size_t CWinEventsSDL::GetQueueSize() { - int ret; - SDL_Event event; + int ret = 0; - if (-1 == (ret = SDL_PeepEvents(&event, 0, SDL_PEEKEVENT, ~0))) +#if HAVE_SDL_VERSION == 1 + if (-1 == (ret = SDL_PeepEvents(NULL, 0, SDL_PEEKEVENT, ~0))) + ret = 0; +#endif +#if HAVE_SDL_VERSION == 2 + if (-1 == (ret = SDL_PeepEvents(NULL, 0, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT))) ret = 0; +#endif return ret; } diff --git a/xbmc/windowing/WinEventsSDL.h b/xbmc/windowing/WinEventsSDL.h index 6a23658e5a..154e588c29 100644 --- a/xbmc/windowing/WinEventsSDL.h +++ b/xbmc/windowing/WinEventsSDL.h @@ -28,7 +28,7 @@ #if HAVE_SDL_VERSION == 1 #include #elif HAVE_SDL_VERSION == 2 -#include +#include #endif #include "WinEvents.h" diff --git a/xbmc/windowing/egl/EGLNativeTypeSDL.cpp b/xbmc/windowing/egl/EGLNativeTypeSDL.cpp new file mode 100644 index 0000000000..d3523cb93a --- /dev/null +++ b/xbmc/windowing/egl/EGLNativeTypeSDL.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2011-2013 Team XBMC + * http://xbmc.org + * + * 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, 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 XBMC; see the file COPYING. If not, see + * . + * + */ +#include "system.h" + +#include "EGLNativeTypeSDL.h" +#include "guilib/gui3d.h" +#include "utils/log.h" +#include "utils/StringUtils.h" + +#if HAVE_SDL_VERSION == 2 + +CEGLNativeTypeSDL::CEGLNativeTypeSDL() +{ + m_window = NULL; + + if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) { + return; + } + + SDL_DisplayMode mode; + SDL_GetDesktopDisplayMode(0, &mode); + + m_window = SDL_CreateWindow("SDL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, mode.w, mode.h, SDL_WINDOW_FULLSCREEN|SDL_WINDOW_HIDDEN); + if (!m_window) { + return; + } + + SDL_VERSION(&m_windowInfo.version); + if (SDL_GetWindowWMInfo(m_window, &m_windowInfo) < 0 || m_windowInfo.subsystem != SDL_SYSWM_VIVANTE) { + SDL_DestroyWindow(m_window); + m_window = NULL; + } + + // XBMC draws its own cursor + SDL_ShowCursor(0); +} + +CEGLNativeTypeSDL::~CEGLNativeTypeSDL() +{ + if (m_window) { + SDL_DestroyWindow(m_window); + } + SDL_QuitSubSystem(SDL_INIT_VIDEO); +} + +bool CEGLNativeTypeSDL::CheckCompatibility() +{ + return (m_window != NULL); +} + +void CEGLNativeTypeSDL::Initialize() +{ +} + +void CEGLNativeTypeSDL::Destroy() +{ +} + +bool CEGLNativeTypeSDL::CreateNativeDisplay() +{ + return true; +} + +bool CEGLNativeTypeSDL::CreateNativeWindow() +{ + SDL_ShowWindow(m_window); + return true; +} + +bool CEGLNativeTypeSDL::GetNativeDisplay(XBNativeDisplayType **nativeDisplay) const +{ + if (!nativeDisplay) + return false; + *nativeDisplay = (XBNativeDisplayType*) &m_windowInfo.info.vivante.display; + return true; +} + +bool CEGLNativeTypeSDL::GetNativeWindow(XBNativeDisplayType **nativeWindow) const +{ + if (!nativeWindow) + return false; + *nativeWindow = (XBNativeWindowType*) &m_windowInfo.info.vivante.window; + return true; +} + +bool CEGLNativeTypeSDL::DestroyNativeDisplay() +{ + return true; +} + +bool CEGLNativeTypeSDL::DestroyNativeWindow() +{ + SDL_HideWindow(m_window); + return true; +} + +bool CEGLNativeTypeSDL::GetNativeResolution(RESOLUTION_INFO *res) const +{ + SDL_GetWindowSize(m_window, &res->iWidth, &res->iHeight); + res->fRefreshRate = 59.94f; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + res->iScreen = 0; + res->bFullScreen = true; + res->iSubtitles = static_cast(0.965 * res->iHeight); + res->fPixelRatio = 1.0f; + res->iScreenWidth = res->iWidth; + res->iScreenHeight = res->iHeight; + res->strMode = StringUtils::Format("%dx%d @ %.2fp", + res->iScreenWidth, + res->iScreenHeight, + res->fRefreshRate); + return true; +} + +bool CEGLNativeTypeSDL::SetNativeResolution(const RESOLUTION_INFO &res) +{ + return true; +} + +bool CEGLNativeTypeSDL::ProbeResolutions(std::vector &resolutions) +{ + RESOLUTION_INFO res; + if (!GetNativeResolution(&res)) + return false; + + resolutions.push_back(res); + return true; +} + +bool CEGLNativeTypeSDL::GetPreferredResolution(RESOLUTION_INFO *res) const +{ + return GetNativeResolution(res); +} + +bool CEGLNativeTypeSDL::ShowWindow(bool show) +{ + if (show) + SDL_ShowWindow(m_window); + else + SDL_HideWindow(m_window); + return true; +} + +#endif // SDL 2.0 diff --git a/xbmc/windowing/egl/EGLNativeTypeSDL.h b/xbmc/windowing/egl/EGLNativeTypeSDL.h new file mode 100644 index 0000000000..387a8485e8 --- /dev/null +++ b/xbmc/windowing/egl/EGLNativeTypeSDL.h @@ -0,0 +1,60 @@ +#pragma once + +/* + * Copyright (C) 2011-2013 Team XBMC + * http://xbmc.org + * + * 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, 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 XBMC; see the file COPYING. If not, see + * . + * + */ + +#if HAVE_SDL_VERSION == 2 + +#include "EGLNativeType.h" +#include "SDL2/SDL.h" +#include "SDL2/SDL_syswm.h" + +class CEGLNativeTypeSDL : public CEGLNativeType +{ +public: + CEGLNativeTypeSDL(); + virtual ~CEGLNativeTypeSDL(); + virtual std::string GetNativeName() const { return "SDL"; }; + virtual bool CheckCompatibility(); + virtual void Initialize(); + virtual void Destroy(); + virtual int GetQuirks() { return EGL_QUIRK_NONE; }; + + virtual bool CreateNativeDisplay(); + virtual bool CreateNativeWindow(); + virtual bool GetNativeDisplay(XBNativeDisplayType **nativeDisplay) const; + virtual bool GetNativeWindow(XBNativeWindowType **nativeWindow) const; + + virtual bool DestroyNativeWindow(); + virtual bool DestroyNativeDisplay(); + + virtual bool GetNativeResolution(RESOLUTION_INFO *res) const; + virtual bool SetNativeResolution(const RESOLUTION_INFO &res); + virtual bool ProbeResolutions(std::vector &resolutions); + virtual bool GetPreferredResolution(RESOLUTION_INFO *res) const; + + virtual bool ShowWindow(bool show); + +private: + SDL_Window *m_window; + SDL_SysWMinfo m_windowInfo; +}; + +#endif // SDL 2.0 diff --git a/xbmc/windowing/egl/EGLWrapper.cpp b/xbmc/windowing/egl/EGLWrapper.cpp index 30f57577d6..6422bfbc5b 100644 --- a/xbmc/windowing/egl/EGLWrapper.cpp +++ b/xbmc/windowing/egl/EGLWrapper.cpp @@ -37,6 +37,9 @@ #if defined(TARGET_LINUX) && defined(HAS_LIBAMCODEC) #include "EGLNativeTypeAmlogic.h" #endif +#if HAVE_SDL_VERSION == 2 +#include "EGLNativeTypeSDL.h" +#endif #include "EGLWrapper.h" #define CheckError() m_result = eglGetError(); if(m_result != EGL_SUCCESS) CLog::Log(LOGERROR, "EGL error in %s: %x",__FUNCTION__, m_result); @@ -101,6 +104,8 @@ bool CEGLWrapper::Initialize(const std::string &implementation) (nativeGuess = CreateEGLNativeType(implementation)) #elif defined(TARGET_LINUX) && defined(HAS_LIBAMCODEC) (nativeGuess = CreateEGLNativeType(implementation)) +#elif HAVE_SDL_VERSION == 2 + (nativeGuess = CreateEGLNativeType(implementation)) #endif ) { diff --git a/xbmc/windowing/egl/Makefile.in b/xbmc/windowing/egl/Makefile.in index 68f7862dfe..02a3ba5485 100644 --- a/xbmc/windowing/egl/Makefile.in +++ b/xbmc/windowing/egl/Makefile.in @@ -1,6 +1,7 @@ INCLUDES=-I. SRCS = WinSystemEGL.cpp +SRCS+= EGLNativeTypeSDL.cpp SRCS+= EGLNativeTypeAmlogic.cpp ifeq (@USE_ANDROID@,1) SRCS+= EGLNativeTypeAndroid.cpp