diff --git a/CMakeLists.txt b/CMakeLists.txt index 0beb0b7..4901a42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ find_package(ALSA REQUIRED) find_package(Opus REQUIRED) find_package(Broadcom) find_package(Freescale) +find_package(Amlogic) find_package(PkgConfig REQUIRED) pkg_check_modules(EVDEV REQUIRED libevdev) @@ -47,10 +48,17 @@ if(CMAKE_BUILD_TYPE MATCHES Debug) list(APPEND SRC_LIST ./src/video/fake.c) list(APPEND MOONLIGHT_DEFINITIONS HAVE_FAKE LC_DEBUG) list(APPEND MOONLIGHT_OPTIONS FAKE DEBUG) -elseif(NOT BROADCOM_FOUND AND NOT FREESCALE_FOUND AND NOT SOFTWARE_FOUND) +elseif(NOT AMLOGIC_FOUND AND NOT BROADCOM_FOUND AND NOT FREESCALE_FOUND AND NOT SOFTWARE_FOUND) message(FATAL_ERROR "No video output available") endif() +if (AMLOGIC_FOUND) + set(SOFTWARE_FOUND FALSE) + list(APPEND SRC_LIST ./src/video/aml.c) + list(APPEND MOONLIGHT_DEFINITIONS HAVE_AML) + list(APPEND MOONLIGHT_OPTIONS AML) +endif() + if (SOFTWARE_FOUND) list(APPEND SRC_LIST ./src/video/ffmpeg.c ./src/video/sdl.c ./src/audio/sdl.c) list(APPEND MOONLIGHT_DEFINITIONS HAVE_SDL) @@ -62,7 +70,7 @@ if (SOFTWARE_FOUND) endif() endif() -if (BROADCOM_FOUND OR FREESCALE_FOUND OR CMAKE_BUILD_TYPE MATCHES Debug) +if (AMLOGIC_FOUND OR BROADCOM_FOUND OR FREESCALE_FOUND OR CMAKE_BUILD_TYPE MATCHES Debug) list(APPEND MOONLIGHT_DEFINITIONS HAVE_EMBEDDED) list(APPEND MOONLIGHT_OPTIONS EMBEDDED) endif() @@ -90,6 +98,15 @@ if (CEC_FOUND) target_link_libraries(moonlight ${CEC_LIBRARIES}) endif() +if(AMLOGIC_FOUND) + list(APPEND MOONLIGHT_DEFINITIONS HAVE_AML) + list(APPEND MOONLIGHT_OPTIONS AML) + target_include_directories(moonlight PRIVATE ${AMLOGIC_INCLUDE_DIRS} ${GAMESTREAM_INCLUDE_DIR} ${MOONLIGHT_COMMON_INCLUDE_DIR}) + target_link_libraries(moonlight gamestream ${AMLOGIC_LIBRARIES}) + set_property(TARGET moonlight PROPERTY COMPILE_DEFINITIONS ${AMLOGIC_DEFINITIONS}) + install(TARGETS moonlight DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() + if(BROADCOM_FOUND) list(APPEND MOONLIGHT_DEFINITIONS HAVE_PI) list(APPEND MOONLIGHT_OPTIONS PI) diff --git a/README.md b/README.md index 7a741fc..313f1bc 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Moonlight Embedded is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built for Linux. Moonlight Embedded allows you to stream your full collection of Steam games from -your powerful Windows desktop to your (embedded) Linux system, like Raspberry Pi, CuBox-i and Hummingboard. +your powerful Windows desktop to your (embedded) Linux system, like ODROID, Raspberry Pi, CuBox-i and Hummingboard. ## Documentation @@ -14,7 +14,8 @@ More information about installing and runnning Moonlight Embedded is available o * Streams Steam and all of your games from your PC to your embedded system. * Use mDNS to scan for compatible GeForce Experience (GFE) machines on the network. * Qwerty Keyboard, Mouse and Gamepad support -* Support hardware video decoding on Raspberry Pi and i.MX 6 devices +* Support H264 hardware video decoding on ODROID, Raspberry Pi and i.MX 6 devices +* Support HEVC hardware video decoding on ODROID C1/C2 ## Requirements diff --git a/cmake/FindAmlogic.cmake b/cmake/FindAmlogic.cmake new file mode 100644 index 0000000..eb06031 --- /dev/null +++ b/cmake/FindAmlogic.cmake @@ -0,0 +1,29 @@ +find_path(AMLOGIC_INCLUDE_DIR + NAMES codec.h + DOC "Amlogic include directory" + PATHS /usr/local/include/amcodec /usr/include/amcodec) +mark_as_advanced(AMLOGIC_INCLUDE_DIR) + +find_library(AMAVUTILS_LIBRARY + NAMES libamavutils.so + DOC "Path to Amlogic Audio Video Utils Library" + PATHS /usr/lib/aml_libs /usr/local/lib /usr/lib) +mark_as_advanced(AMAVUTILS_LIBRARY) + +find_library(AMADEC_LIBRARY + NAMES libamadec.so + DOC "Path to Amlogic Audio Decoder Library" + PATHS /usr/lib/aml_libs /usr/local/lib /usr/lib) +mark_as_advanced(AMADEC_LIBRARY) + +find_library(AMCODEC_LIBRARY + NAMES libamcodec.so + DOC "Path to Amlogic Video Codec Library" + PATHS /usr/lib/aml_libs /usr/local/lib /usr/lib) +mark_as_advanced(AMCODEC_LIBRARY) + +include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Amlogic DEFAULT_MSG AMLOGIC_INCLUDE_DIR AMCODEC_LIBRARY AMADEC_LIBRARY AMAVUTILS_LIBRARY) + +set(AMLOGIC_LIBRARIES ${AMCODEC_LIBRARY} ${AMADEC_LIBRARY} ${AMAVUTILS_LIBRARY}) +set(AMLOGIC_INCLUDE_DIRS ${AMLOGIC_INCLUDE_DIR}) diff --git a/moonlight.conf b/moonlight.conf index 79ccdbb..0909648 100644 --- a/moonlight.conf +++ b/moonlight.conf @@ -42,6 +42,7 @@ ## Select the audio and video decoder to use ## default - autodetect +## aml - hardware video decoder for ODROID-C1/C2 ## omx - hardware video decoder for Raspberry Pi ## imx - hardware video decoder for i.MX6 devices ## sdl - software decoder diff --git a/src/config.c b/src/config.c index 52dae3b..615889f 100644 --- a/src/config.c +++ b/src/config.c @@ -63,6 +63,9 @@ static struct option long_options[] = { {"surround", no_argument, NULL, 'u'}, {"fps", required_argument, NULL, 'v'}, {"forcehw", no_argument, NULL, 'w'}, +#ifdef HAVE_AML + {"hevc", no_argument, NULL, 'x'}, +#endif {0, 0, 0, 0}, }; @@ -199,6 +202,12 @@ static void parse_argument(int c, char* value, PCONFIGURATION config) { break; case 'w': config->forcehw = true; + break; +#ifdef HAVE_AML + case 'x': + config->hevc = true; + break; +#endif case 1: if (config->action == NULL) config->action = value; @@ -289,6 +298,7 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) { config->sops = true; config->localaudio = false; config->fullscreen = true; + config->hevc = false; config->inputsCount = 0; config->mapping = get_path("mappings/default.conf", getenv("XDG_DATA_DIRS")); @@ -306,7 +316,7 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) { } else { int option_index = 0; int c; - while ((c = getopt_long_only(argc, argv, "-abc:d:efg:h:i:j:k:lm:no:p:q:r:stuv:w", long_options, &option_index)) != -1) { + while ((c = getopt_long_only(argc, argv, "-abc:d:efg:h:i:j:k:lm:no:p:q:r:stuv:w:x", long_options, &option_index)) != -1) { parse_argument(c, optarg, config); } } diff --git a/src/config.h b/src/config.h index 9b88b5c..77872b1 100644 --- a/src/config.h +++ b/src/config.h @@ -43,6 +43,9 @@ typedef struct _CONFIGURATION { bool forcehw; struct input_config inputs[MAX_INPUTS]; int inputsCount; +#ifdef HAVE_AML + bool hevc; +#endif } CONFIGURATION, *PCONFIGURATION; bool inputAdded; diff --git a/src/main.c b/src/main.c index 7459cf0..64c9421 100644 --- a/src/main.c +++ b/src/main.c @@ -83,6 +83,11 @@ static void stream(PSERVER_DATA server, PCONFIGURATION config, enum platform sys exit(-1); } + // h265 + if (config->hevc) { + config->stream.supportsHevc = 1; + } + int ret = gs_start_app(server, &config->stream, appId, config->sops, config->localaudio); if (ret < 0) { if (ret == GS_NOT_SUPPORTED_4K) @@ -154,6 +159,10 @@ static void help() { printf("\t-audio \t\tUse as ALSA audio output device (default sysdefault)\n"); printf("\t-forcehw \t\tTry to use video hardware acceleration\n"); #endif + #ifdef HAVE_AML + printf("\n Amlogic Codec options\n\n"); + printf("\t-hevc \t\tUse high efficiency video decoding (HEVC)\n"); + #endif printf("\nUse Ctrl+Alt+Shift+Q to exit streaming session\n\n"); exit(0); } diff --git a/src/platform.c b/src/platform.c index 3d4a81f..ba2d436 100644 --- a/src/platform.c +++ b/src/platform.c @@ -52,6 +52,10 @@ enum platform platform_check(char* name) { if (std || strcmp(name, "sdl") == 0) return SDL; #endif + #ifdef HAVE_AML + if (std || strcmp(name, "aml") == 0) + return AML; + #endif #ifdef HAVE_FAKE if (std || strcmp(name, "fake") == 0) return FAKE; @@ -73,6 +77,10 @@ DECODER_RENDERER_CALLBACKS* platform_get_video(enum platform system) { case PI: return (PDECODER_RENDERER_CALLBACKS) dlsym(RTLD_DEFAULT, "decoder_callbacks_pi"); #endif + #ifdef HAVE_AML + case AML: + return &decoder_callbacks_aml; + #endif #ifdef HAVE_FAKE case FAKE: return &decoder_callbacks_fake; diff --git a/src/platform.h b/src/platform.h index ac08228..e1cb86d 100644 --- a/src/platform.h +++ b/src/platform.h @@ -25,12 +25,15 @@ #define IS_EMBEDDED(SYSTEM) SYSTEM != SDL -enum platform { NONE, SDL, PI, IMX, FAKE }; +enum platform { NONE, SDL, PI, IMX, AML, FAKE }; enum platform platform_check(char*); PDECODER_RENDERER_CALLBACKS platform_get_video(enum platform system); PAUDIO_RENDERER_CALLBACKS platform_get_audio(enum platform system); +#ifdef HAVE_AML +extern DECODER_RENDERER_CALLBACKS decoder_callbacks_aml; +#endif #ifdef HAVE_FAKE extern DECODER_RENDERER_CALLBACKS decoder_callbacks_fake; #endif diff --git a/src/video/aml.c b/src/video/aml.c new file mode 100644 index 0000000..673747d --- /dev/null +++ b/src/video/aml.c @@ -0,0 +1,138 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2016 OtherCrashOverride, Daniel Mehrwald + * + * Moonlight 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 3 of the License, or + * (at your option) any later version. + * + * Moonlight 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 Moonlight; if not, see . + */ + +#include "limelight-common/Limelight.h" + +#include +#include +#include +#include +#include +#include +#include + +static codec_para_t codecParam = { 0 }; +const size_t EXTERNAL_PTS = (1); +const size_t SYNC_OUTSIDE = (2); + +int osd_blank(char *path,int cmd) { + int fd; + char bcmd[16]; + + fd = open(path, O_CREAT|O_RDWR | O_TRUNC, 0644); + + if(fd>=0) { + sprintf(bcmd,"%d",cmd); + if (write(fd,bcmd,strlen(bcmd)) < 0) { + printf("osd_blank error during write.\n"); + } + close(fd); + return 0; + } + + return -1; +} + +void init_display() { + osd_blank("/sys/class/graphics/fb0/blank",1); + osd_blank("/sys/class/graphics/fb1/blank",0); +} + +void restore_display() { + osd_blank("/sys/class/graphics/fb0/blank",0); + osd_blank("/sys/class/graphics/fb1/blank",0); +} + +void aml_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { + fprintf(stderr, "\nvideoFormat=%d nwidth=%d, height=%d, redrawRate=%d, context=%p, drFlags=%x\n", + videoFormat, width, height, redrawRate, context, drFlags); + + init_display(); + + codecParam.stream_type = STREAM_TYPE_ES_VIDEO; + codecParam.has_video = 1; + codecParam.noblock = 0; + + switch (videoFormat) { + case VIDEO_FORMAT_H264: // 1 + if (width > 1920 || height > 1080) { + codecParam.video_type = VFORMAT_H264_4K2K; + codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_H264_4K2K; ///< video format, such as H264, MPEG2... + } else { + codecParam.video_type = VFORMAT_H264; + codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_H264; ///< video format, such as H264, MPEG2... + } + + fprintf(stdout, "Decoding H264 video.\n"); + break; + case VIDEO_FORMAT_H265: // 2 + + codecParam.video_type = VFORMAT_HEVC; + codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_HEVC; ///< video format, such as H264, MPEG2... + + fprintf(stdout, "Decoding HEVC video.\n"); + break; + default: + printf("Unsupported video format.\n"); + exit(1); + } + + codecParam.am_sysinfo.width = width; //< video source width + codecParam.am_sysinfo.height = height; //< video source height + codecParam.am_sysinfo.rate = (96000 / (redrawRate)); //< video source frame duration + codecParam.am_sysinfo.param = (void *)(EXTERNAL_PTS | SYNC_OUTSIDE); //< other parameters for video decoder + + int api = codec_init(&codecParam); + fprintf(stdout, "codec_init=%x\n", api); + + if (api != 0) { + fprintf(stderr, "codec_init failed.\n"); + exit(1); + } +} + +void aml_cleanup() { + int api = codec_close(&codecParam); + restore_display(); +} + +int aml_submit_decode_unit(PDECODE_UNIT decodeUnit) { + int result = DR_OK; + PLENTRY entry = decodeUnit->bufferList; + while (entry != NULL) { + int api = codec_write(&codecParam, entry->data, entry->length); + if (api != entry->length) { + fprintf(stderr, "codec_write error: %x\n", api); + codec_reset(&codecParam); + result = DR_NEED_IDR; + break; + } + + entry = entry->next; + } + return result; +} + +DECODER_RENDERER_CALLBACKS decoder_callbacks_aml = { + .setup = aml_setup, + .cleanup = aml_cleanup, + .submitDecodeUnit = aml_submit_decode_unit, + .capabilities = CAPABILITY_DIRECT_SUBMIT | CAPABILITY_SLICES_PER_FRAME(8), +};