mirror of
https://github.com/moonlight-stream/moonlight-embedded.git
synced 2026-04-03 06:26:04 +00:00
Merge tag 'v2.2.0' into raspbian/jessie
This commit is contained in:
@@ -4,8 +4,8 @@ SET(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||
include(${CMAKE_ROOT}/Modules/GNUInstallDirs.cmake)
|
||||
|
||||
set(MOONLIGHT_MAJOR_VERSION 2)
|
||||
set(MOONLIGHT_MINOR_VERSION 1)
|
||||
set(MOONLIGHT_PATCH_VERSION 4)
|
||||
set(MOONLIGHT_MINOR_VERSION 2)
|
||||
set(MOONLIGHT_PATCH_VERSION 0)
|
||||
set(MOONLIGHT_VERSION ${MOONLIGHT_MAJOR_VERSION}.${MOONLIGHT_MINOR_VERSION}.${MOONLIGHT_PATCH_VERSION})
|
||||
|
||||
aux_source_directory(./src SRC_LIST)
|
||||
@@ -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)
|
||||
@@ -40,14 +41,14 @@ else()
|
||||
set(SOFTWARE_FOUND FALSE)
|
||||
endif()
|
||||
|
||||
SET(MOONLIGHT_COMMON_INCLUDE_DIR ./third_party/moonlight-common-c)
|
||||
SET(MOONLIGHT_COMMON_INCLUDE_DIR ./third_party/moonlight-common-c/src)
|
||||
SET(GAMESTREAM_INCLUDE_DIR ./libgamestream)
|
||||
|
||||
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()
|
||||
|
||||
@@ -62,7 +63,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,13 +91,23 @@ if (CEC_FOUND)
|
||||
target_link_libraries(moonlight ${CEC_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(AMLOGIC_FOUND)
|
||||
list(APPEND MOONLIGHT_DEFINITIONS HAVE_AML)
|
||||
list(APPEND MOONLIGHT_OPTIONS AML)
|
||||
add_library(moonlight-aml SHARED ./src/video/aml.c ${ILCLIENT_SRC_LIST})
|
||||
target_include_directories(moonlight-aml PRIVATE ${AMLOGIC_INCLUDE_DIRS} ${GAMESTREAM_INCLUDE_DIR} ${MOONLIGHT_COMMON_INCLUDE_DIR})
|
||||
target_link_libraries(moonlight-aml gamestream ${AMLOGIC_LIBRARIES})
|
||||
set_property(TARGET moonlight-aml PROPERTY COMPILE_DEFINITIONS ${AMLOGIC_DEFINITIONS})
|
||||
install(TARGETS moonlight-aml DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
endif()
|
||||
|
||||
if(BROADCOM_FOUND)
|
||||
list(APPEND MOONLIGHT_DEFINITIONS HAVE_PI)
|
||||
list(APPEND MOONLIGHT_OPTIONS PI)
|
||||
aux_source_directory(./third_party/ilclient ILCLIENT_SRC_LIST)
|
||||
add_library(moonlight-pi SHARED ./src/video/pi.c ${ILCLIENT_SRC_LIST})
|
||||
target_include_directories(moonlight-pi PRIVATE ./third_party/ilclient ${BROADCOM_INCLUDE_DIRS} ${GAMESTREAM_INCLUDE_DIR} ${MOONLIGHT_COMMON_INCLUDE_DIR})
|
||||
target_link_libraries(moonlight-pi gamestream ${BROADCOM_LIBRARIES})
|
||||
add_library(moonlight-pi SHARED ./src/video/pi.c ./src/audio/omx.c ${ILCLIENT_SRC_LIST})
|
||||
target_include_directories(moonlight-pi PRIVATE ./third_party/ilclient ${BROADCOM_INCLUDE_DIRS} ${GAMESTREAM_INCLUDE_DIR} ${MOONLIGHT_COMMON_INCLUDE_DIR} ${OPUS_INCLUDE_DIRS})
|
||||
target_link_libraries(moonlight-pi gamestream ${BROADCOM_LIBRARIES} ${OPUS_LIBRARY})
|
||||
set_property(TARGET moonlight-pi PROPERTY COMPILE_DEFINITIONS ${BROADCOM_DEFINITIONS})
|
||||
install(TARGETS moonlight-pi DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
endif()
|
||||
|
||||
32
CONTRIBUTING.md
Normal file
32
CONTRIBUTING.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Contribution Guide
|
||||
|
||||
## Got a Question or Problem?
|
||||
Please take a look at the [wiki](https://github.com/irtimmer/moonlight-embedded/wiki) to see if answers your questions about the usage of Moonlight Embedded.
|
||||
|
||||
If you still have questions about Moonlight Embedded, please use one of the different fora discussing Moonlight Embedded.
|
||||
|
||||
[XDA](http://forum.xda-developers.com/showthread.php?t=2505510) Moonlight in General
|
||||
[Raspberry Pi Forum](http://www.raspberrypi.org/forums/viewtopic.php?f=78&t=65878) Moonlight Embedded for Raspberry Pi
|
||||
[SolidRun Community](http://www.solid-run.com/community/viewtopic.php?f=13&t=1489&p=11173) Moonlight Embedded for Cubox-i and Hummingboard
|
||||
[ODROID Forum](http://forum.odroid.com/viewtopic.php?f=91&t=15456) Moonlight Embedded on ODROID
|
||||
|
||||
## Found an Issue?
|
||||
If you think you found a bug in Moonlight Embedded you can submit a issue. But please ensure first you have checked the following or otherwise we will mark the issue as invalid:
|
||||
- [ ] It's a bug in Moonlight Embedded and not in NVidia Geforce Experience or Steam as we otherwise can't fix them
|
||||
- [ ] It's not a misconfiguration of your own setup. Like firewall misconfiguration.
|
||||
- [ ] Their is no other bug report with the same issue. Check also the closed issues in case your bug is already solved in master.
|
||||
|
||||
Also provide as much information as possible about your setup and how to produce the issue so their are higher chances we can reproduce the issue and fix it. Even better you can submit a Pull Request
|
||||
with a fix.
|
||||
|
||||
## Feature request
|
||||
There are not much developers working on Moonlight Embedded. So it currently doesn't make much sense to use the issue tracker to submit feature request. Please try to implement it yourself and submit a pull request or discuss it on one of the fora to see if someone else is able to implement it.
|
||||
|
||||
## Submitting a Pull Request
|
||||
Have you created a cool new feature or fixed a few bugs you can submit a pull request. But before your request is merged you have to check the following.
|
||||
- [ ] Your branch is based on a recent commit and can be merge cleanly
|
||||
- [ ] Your code uses the same code style as the rest of the code
|
||||
- [ ] Your history is cleanup and you provide one or multiple commits
|
||||
- [ ] Your commits only changes the necessery lines and not accidently changes whitespace or add or remove empty lines.
|
||||
|
||||
If these guide lines are not met we maybe won't merge your pull request or take some to cleanup your pull request before merging. Depending on how bad we wan't your code.
|
||||
11
ISSUE_TEMPLATE.md
Normal file
11
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,11 @@
|
||||
Please provide the following info.
|
||||
|
||||
**_NVidia Geforce Experience version:_**
|
||||
**Moonlight Embedded version:**
|
||||
**Moonlight Embedded running on:** _Raspberry Pi/Cubox-i/Hummingboard/Other linux device/..._
|
||||
|
||||
**Output of Moonlight Embedded:**
|
||||
|
||||
**What is the expected result?**
|
||||
|
||||
**What happens instead of that?**
|
||||
3
PULL_REQUEST_TEMPLATE.md
Normal file
3
PULL_REQUEST_TEMPLATE.md
Normal file
@@ -0,0 +1,3 @@
|
||||
**Description**
|
||||
|
||||
**Purpose**
|
||||
18
README.md
18
README.md
@@ -3,19 +3,12 @@
|
||||
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 Raspberry Pi, CuBox-i and ODROID.
|
||||
|
||||
## Documentation
|
||||
|
||||
More information about installing and runnning Moonlight Embedded is available on the [wiki](https://github.com/irtimmer/moonlight-embedded/wiki).
|
||||
|
||||
## Features
|
||||
|
||||
* 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
|
||||
|
||||
## Requirements
|
||||
|
||||
* [GFE compatible](http://shield.nvidia.com/play-pc-games/) computer with GTX 600/700/900 series GPU (for the PC you're streaming from)
|
||||
@@ -33,6 +26,8 @@ More information about installing and runnning Moonlight Embedded is available o
|
||||
|
||||
## Bugs
|
||||
|
||||
Please check the fora, wiki and old bug reports before submitting a new bug report.
|
||||
|
||||
Bugs can be reported to the [issue tracker](https://github.com/irtimmer/moonlight-embedded/issues).
|
||||
|
||||
## See also
|
||||
@@ -44,9 +39,10 @@ different C implementations of Moonlight
|
||||
|
||||
## Discussion
|
||||
|
||||
[XDA](http://forum.xda-developers.com/showthread.php?t=2505510)
|
||||
[Raspberry Pi Forum](http://www.raspberrypi.org/forums/viewtopic.php?f=78&t=65878)
|
||||
[SolidRun Community](http://www.solid-run.com/community/viewtopic.php?f=13&t=1489&p=11173)
|
||||
[XDA](http://forum.xda-developers.com/showthread.php?t=2505510) Moonlight in General
|
||||
[Raspberry Pi Forum](http://www.raspberrypi.org/forums/viewtopic.php?f=78&t=65878) Moonlight Embedded for Raspberry Pi
|
||||
[SolidRun Community](http://www.solid-run.com/community/viewtopic.php?f=13&t=1489&p=11173) Moonlight Embedded for Cubox-i and Hummingboard
|
||||
[ODROID Forum](http://forum.odroid.com/viewtopic.php?f=91&t=15456) Moonlight Embedded on ODROID
|
||||
|
||||
## Contribute
|
||||
|
||||
|
||||
29
cmake/FindAmlogic.cmake
Normal file
29
cmake/FindAmlogic.cmake
Normal file
@@ -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})
|
||||
@@ -1,19 +0,0 @@
|
||||
# - Try to find CEC
|
||||
# Once done this will define
|
||||
#
|
||||
# CEC_FOUND - system has libcec
|
||||
# CEC_INCLUDE_DIRS - the libcec include directory
|
||||
# CEC_LIBRARIES - The libcec libraries
|
||||
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules (CEC libcec>=3.0.0)
|
||||
else()
|
||||
find_path(CEC_INCLUDE_DIRS libcec/cec.h)
|
||||
find_library(CEC_LIBRARIES cec)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(CEC DEFAULT_MSG CEC_INCLUDE_DIRS CEC_LIBRARIES)
|
||||
|
||||
list(APPEND CEC_DEFINITIONS -DHAVE_LIBCEC=1)
|
||||
mark_as_advanced(CEC_INCLUDE_DIRS CEC_LIBRARIES CEC_DEFINITIONS)
|
||||
@@ -19,6 +19,10 @@ Create a mapping file for a gamepad.
|
||||
|
||||
Pair this computer with the host.
|
||||
|
||||
=item B<unpair>
|
||||
|
||||
Unpair this computer with the host.
|
||||
|
||||
=item B<stream>
|
||||
|
||||
Stream game from host to this computer.
|
||||
@@ -95,6 +99,11 @@ Change the network packetsize to I<PACKETSIZE>.
|
||||
The packetsize should the smaller than the MTU of the network.
|
||||
By default a safe value of 1024 is used.
|
||||
|
||||
=item B<-forcehevc>
|
||||
|
||||
Request a h265/HEVC from the server.
|
||||
Will still use h264 if server doesn't support HEVC.
|
||||
|
||||
=item B<-remote>
|
||||
|
||||
Enable the optimizations for remote connections in GFE.
|
||||
@@ -132,7 +141,7 @@ To use a different gamepad mapping then the default the B<-mapping> should be sp
|
||||
=item B<-audio> [I<DEVICE>]
|
||||
|
||||
Use <DEVICE> as audio output device.
|
||||
The default value is 'sysdefault'
|
||||
The default value is 'sysdefault' for ALSA and 'hdmi' for OMX on the Raspberry Pi.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ find_package(OpenSSL REQUIRED)
|
||||
find_package(EXPAT REQUIRED)
|
||||
|
||||
pkg_check_modules(AVAHI REQUIRED avahi-client)
|
||||
pkg_check_modules(ENET REQUIRED libenet)
|
||||
|
||||
aux_source_directory(./ GAMESTREAM_SRC_LIST)
|
||||
aux_source_directory(../third_party/h264bitstream GAMESTREAM_SRC_LIST)
|
||||
|
||||
aux_source_directory(../third_party/moonlight-common-c/limelight-common MOONLIGHT_COMMON_SRC_LIST)
|
||||
aux_source_directory(../third_party/moonlight-common-c/limelight-common/OpenAES MOONLIGHT_COMMON_SRC_LIST)
|
||||
aux_source_directory(../third_party/moonlight-common-c/src MOONLIGHT_COMMON_SRC_LIST)
|
||||
|
||||
add_library(moonlight-common SHARED ${MOONLIGHT_COMMON_SRC_LIST})
|
||||
|
||||
@@ -21,8 +21,10 @@ target_link_libraries(gamestream moonlight-common)
|
||||
set_target_properties(gamestream PROPERTIES SOVERSION 0 VERSION ${MOONLIGHT_VERSION})
|
||||
set_target_properties(moonlight-common PROPERTIES SOVERSION 0 VERSION ${MOONLIGHT_VERSION})
|
||||
|
||||
target_include_directories(gamestream PRIVATE ../third_party/moonlight-common-c ../third_party/h264bitstream ${AVAHI_INCLUDE_DIRS} ${LIBUUID_INCLUDE_DIRS})
|
||||
target_include_directories(gamestream PRIVATE ../third_party/moonlight-common-c/src ../third_party/h264bitstream ${AVAHI_INCLUDE_DIRS} ${LIBUUID_INCLUDE_DIRS})
|
||||
target_include_directories(moonlight-common PRIVATE ${ENET_INCLUDE_DIRS})
|
||||
target_link_libraries(gamestream ${CURL_LIBRARIES} ${OPENSSL_LIBRARIES} ${EXPAT_LIBRARIES} ${AVAHI_LIBRARIES} ${LIBUUID_LIBRARIES})
|
||||
target_link_libraries(moonlight-common ${ENET_LIBRARIES})
|
||||
|
||||
target_link_libraries(gamestream ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015 Iwan Timmer
|
||||
* Copyright (C) 2015-2016 Iwan Timmer
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "client.h"
|
||||
#include "errors.h"
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <stdbool.h>
|
||||
@@ -166,85 +166,117 @@ static int load_cert(const char* keyDirectory) {
|
||||
}
|
||||
|
||||
static int load_server_status(PSERVER_DATA server) {
|
||||
char *pairedText = NULL;
|
||||
char *currentGameText = NULL;
|
||||
char *versionText = NULL;
|
||||
char *stateText = NULL;
|
||||
char *heightText = NULL;
|
||||
char *serverCodecModeSupportText = NULL;
|
||||
|
||||
uuid_t uuid;
|
||||
char uuid_str[37];
|
||||
|
||||
int ret = GS_INVALID;
|
||||
|
||||
int ret;
|
||||
char url[4096];
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
sprintf(url, "https://%s:47984/serverinfo?uniqueid=%s&uuid=%s", server->address, unique_id, uuid_str);
|
||||
int i;
|
||||
|
||||
PHTTP_DATA data = http_create_data();
|
||||
if (data == NULL) {
|
||||
ret = GS_OUT_OF_MEMORY;
|
||||
goto cleanup;
|
||||
i = 0;
|
||||
do {
|
||||
char *pairedText = NULL;
|
||||
char *currentGameText = NULL;
|
||||
char *versionText = NULL;
|
||||
char *stateText = NULL;
|
||||
char *heightText = NULL;
|
||||
char *serverCodecModeSupportText = NULL;
|
||||
|
||||
ret = GS_INVALID;
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
|
||||
// Modern GFE versions don't allow serverinfo to be fetched over HTTPS if the client
|
||||
// is not already paired. Since we can't pair without knowing the server version, we
|
||||
// make another request over HTTP if the HTTPS request fails. We can't just use HTTP
|
||||
// for everything because it doesn't accurately tell us if we're paired.
|
||||
sprintf(url, "%s://%s:%d/serverinfo?uniqueid=%s&uuid=%s",
|
||||
i == 0 ? "https" : "http", server->address, i == 0 ? 47984 : 47989, unique_id, uuid_str);
|
||||
|
||||
PHTTP_DATA data = http_create_data();
|
||||
if (data == NULL) {
|
||||
ret = GS_OUT_OF_MEMORY;
|
||||
goto cleanup;
|
||||
}
|
||||
if (http_request(url, data) != GS_OK) {
|
||||
ret = GS_IO_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (xml_search(data->memory, data->size, "currentgame", ¤tGameText) != GS_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (xml_search(data->memory, data->size, "PairStatus", &pairedText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "appversion", &versionText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "state", &stateText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "Height", &heightText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "ServerCodecModeSupport", &serverCodecModeSupportText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "gputype", &server->gpuType) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "GfeVersion", &server->gfeVersion) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
// These fields are present on all version of GFE that this client supports
|
||||
if (!strlen(currentGameText) || !strlen(pairedText) || !strlen(versionText) || !strlen(stateText))
|
||||
goto cleanup;
|
||||
|
||||
server->paired = pairedText != NULL && strcmp(pairedText, "1") == 0;
|
||||
server->currentGame = currentGameText == NULL ? 0 : atoi(currentGameText);
|
||||
server->supports4K = heightText != NULL && serverCodecModeSupportText != NULL && atoi(heightText) >= 2160;
|
||||
server->serverMajorVersion = atoi(versionText);
|
||||
if (strstr(stateText, "_SERVER_AVAILABLE")) {
|
||||
// After GFE 2.8, current game remains set even after streaming
|
||||
// has ended. We emulate the old behavior by forcing it to zero
|
||||
// if streaming is not active.
|
||||
server->currentGame = 0;
|
||||
}
|
||||
ret = GS_OK;
|
||||
|
||||
cleanup:
|
||||
if (data != NULL)
|
||||
http_free_data(data);
|
||||
|
||||
if (pairedText != NULL)
|
||||
free(pairedText);
|
||||
|
||||
if (currentGameText != NULL)
|
||||
free(currentGameText);
|
||||
|
||||
if (versionText != NULL)
|
||||
free(versionText);
|
||||
|
||||
if (heightText != NULL)
|
||||
free(heightText);
|
||||
|
||||
if (serverCodecModeSupportText != NULL)
|
||||
free(serverCodecModeSupportText);
|
||||
|
||||
i++;
|
||||
} while (ret != GS_OK && i < 2);
|
||||
|
||||
if (ret == GS_OK) {
|
||||
if (server->serverMajorVersion > MAX_SUPPORTED_GFE_VERSION) {
|
||||
gs_error = "Ensure you're running the latest version of Moonlight Embedded or downgrade GeForce Experience and try again";
|
||||
ret = GS_UNSUPPORTED_VERSION;
|
||||
} else if (server->serverMajorVersion < MIN_SUPPORTED_GFE_VERSION) {
|
||||
gs_error = "Moonlight Embedded requires a newer version of GeForce Experience. Please upgrade GFE on your PC and try again.";
|
||||
ret = GS_UNSUPPORTED_VERSION;
|
||||
}
|
||||
}
|
||||
if (http_request(url, data) != GS_OK) {
|
||||
ret = GS_IO_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (xml_search(data->memory, data->size, "currentgame", ¤tGameText) != GS_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (xml_search(data->memory, data->size, "PairStatus", &pairedText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "appversion", &versionText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "state", &stateText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "Height", &heightText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (xml_search(data->memory, data->size, "ServerCodecModeSupport", &serverCodecModeSupportText) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
server->paired = pairedText != NULL && strcmp(pairedText, "1") == 0;
|
||||
server->currentGame = currentGameText == NULL ? 0 : atoi(currentGameText);
|
||||
server->supports4K = heightText != NULL && serverCodecModeSupportText != NULL && atoi(heightText) >= 2160;
|
||||
char *versionSep = strstr(versionText, ".");
|
||||
if (versionSep != NULL) {
|
||||
*versionSep = 0;
|
||||
}
|
||||
server->serverMajorVersion = atoi(versionText);
|
||||
if (strstr(stateText, "_SERVER_AVAILABLE")) {
|
||||
// After GFE 2.8, current game remains set even after streaming
|
||||
// has ended. We emulate the old behavior by forcing it to zero
|
||||
// if streaming is not active.
|
||||
server->currentGame = 0;
|
||||
}
|
||||
ret = GS_OK;
|
||||
|
||||
cleanup:
|
||||
if (data != NULL)
|
||||
http_free_data(data);
|
||||
|
||||
if (pairedText != NULL)
|
||||
free(pairedText);
|
||||
|
||||
if (currentGameText != NULL)
|
||||
free(currentGameText);
|
||||
|
||||
if (versionText != NULL)
|
||||
free(versionText);
|
||||
|
||||
if (heightText != NULL)
|
||||
free(heightText);
|
||||
|
||||
if (serverCodecModeSupportText != NULL)
|
||||
free(serverCodecModeSupportText);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -305,8 +337,53 @@ static int sign_it(const char *msg, size_t mlen, unsigned char **sig, size_t *sl
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool verifySignature(const char *data, int dataLength, const char *signature, int signatureLength, const char *cert) {
|
||||
X509* x509;
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
BIO_puts(bio, cert);
|
||||
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
if (!x509) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_PKEY* pubKey = X509_get_pubkey(x509);
|
||||
EVP_MD_CTX *mdctx = NULL;
|
||||
mdctx = EVP_MD_CTX_create();
|
||||
EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pubKey);
|
||||
EVP_DigestVerifyUpdate(mdctx, data, dataLength);
|
||||
int result = EVP_DigestVerifyFinal(mdctx, signature, signatureLength);
|
||||
|
||||
X509_free(x509);
|
||||
EVP_PKEY_free(pubKey);
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
int gs_unpair(PSERVER_DATA server) {
|
||||
int ret = GS_OK;
|
||||
char url[4096];
|
||||
uuid_t uuid;
|
||||
char uuid_str[37];
|
||||
PHTTP_DATA data = http_create_data();
|
||||
if (data == NULL)
|
||||
return GS_OUT_OF_MEMORY;
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
sprintf(url, "http://%s:47989/unpair?uniqueid=%s&uuid=%s", server->address, unique_id, uuid_str);
|
||||
ret = http_request(url, data);
|
||||
|
||||
http_free_data(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
int ret = GS_OK;
|
||||
char* result = NULL;
|
||||
char url[4096];
|
||||
uuid_t uuid;
|
||||
char uuid_str[37];
|
||||
@@ -335,12 +412,45 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
else if ((ret = http_request(url, data)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if ((ret = xml_search(data->memory, data->size, "paired", &result)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (strcmp(result, "1") != 0) {
|
||||
gs_error = "Pairing failed";
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
free(result);
|
||||
result = NULL;
|
||||
if ((ret = xml_search(data->memory, data->size, "plaincert", &result)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (strlen(result)/2 > 8191) {
|
||||
gs_error = "Server certificate too big";
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char plaincert[8192];
|
||||
for (int count = 0; count < strlen(result); count += 2) {
|
||||
sscanf(&result[count], "%2hhx", &plaincert[count / 2]);
|
||||
}
|
||||
plaincert[strlen(result)/2] = '\0';
|
||||
printf("%d / %d\n", strlen(result)/2, strlen(plaincert));
|
||||
|
||||
unsigned char salt_pin[20];
|
||||
unsigned char aes_key_hash[20];
|
||||
unsigned char aes_key_hash[32];
|
||||
AES_KEY enc_key, dec_key;
|
||||
memcpy(salt_pin, salt_data, 16);
|
||||
memcpy(salt_pin+16, pin, 4);
|
||||
SHA1(salt_pin, 20, aes_key_hash);
|
||||
|
||||
int hash_length = server->serverMajorVersion >= 7 ? 32 : 20;
|
||||
if (server->serverMajorVersion >= 7)
|
||||
SHA256(salt_pin, 20, aes_key_hash);
|
||||
else
|
||||
SHA1(salt_pin, 20, aes_key_hash);
|
||||
|
||||
AES_set_encrypt_key((unsigned char *)aes_key_hash, 128, &enc_key);
|
||||
AES_set_decrypt_key((unsigned char *)aes_key_hash, 128, &dec_key);
|
||||
|
||||
@@ -357,7 +467,19 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
if ((ret = http_request(url, data)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
char *result;
|
||||
free(result);
|
||||
result = NULL;
|
||||
if ((ret = xml_search(data->memory, data->size, "paired", &result)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (strcmp(result, "1") != 0) {
|
||||
gs_error = "Pairing failed";
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
free(result);
|
||||
result = NULL;
|
||||
if (xml_search(data->memory, data->size, "challengeresponse", &result) != GS_OK) {
|
||||
ret = GS_INVALID;
|
||||
goto cleanup;
|
||||
@@ -368,7 +490,6 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
for (int count = 0; count < strlen(result); count += 2) {
|
||||
sscanf(&result[count], "%2hhx", &challenge_response_data_enc[count / 2]);
|
||||
}
|
||||
free(result);
|
||||
|
||||
for (int i = 0; i < 48; i += 16) {
|
||||
AES_decrypt(&challenge_response_data_enc[i], &challenge_response_data[i], &dec_key);
|
||||
@@ -381,10 +502,13 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
char challenge_response_hash[32];
|
||||
char challenge_response_hash_enc[32];
|
||||
char challenge_response_hex[65];
|
||||
memcpy(challenge_response, challenge_response_data + 20, 16);
|
||||
memcpy(challenge_response, challenge_response_data + hash_length, 16);
|
||||
memcpy(challenge_response + 16, cert->signature->data, 256);
|
||||
memcpy(challenge_response + 16 + 256, client_secret_data, 16);
|
||||
SHA1(challenge_response, 16 + 256 + 16, challenge_response_hash);
|
||||
if (server->serverMajorVersion >= 7)
|
||||
SHA256(challenge_response, 16 + 256 + 16, challenge_response_hash);
|
||||
else
|
||||
SHA1(challenge_response, 16 + 256 + 16, challenge_response_hash);
|
||||
|
||||
for (int i = 0; i < 32; i += 16) {
|
||||
AES_encrypt(&challenge_response_hash[i], &challenge_response_hash_enc[i], &enc_key);
|
||||
@@ -397,11 +521,35 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
if ((ret = http_request(url, data)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
free(result);
|
||||
result = NULL;
|
||||
if ((ret = xml_search(data->memory, data->size, "paired", &result)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (strcmp(result, "1") != 0) {
|
||||
gs_error = "Pairing failed";
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
free(result);
|
||||
result = NULL;
|
||||
if (xml_search(data->memory, data->size, "pairingsecret", &result) != GS_OK) {
|
||||
ret = GS_INVALID;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char pairing_secret[16 + 256];
|
||||
for (int count = 0; count < strlen(result); count += 2) {
|
||||
sscanf(&result[count], "%2hhx", &pairing_secret[count / 2]);
|
||||
}
|
||||
|
||||
if (!verifySignature(pairing_secret, 16, pairing_secret+16, 256, plaincert)) {
|
||||
gs_error = "MITM attack detected";
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
unsigned char *signature = NULL;
|
||||
size_t s_len;
|
||||
if (sign_it(client_secret_data, 16, &signature, &s_len, privateKey) != GS_OK) {
|
||||
@@ -422,15 +570,43 @@ int gs_pair(PSERVER_DATA server, char* pin) {
|
||||
if ((ret = http_request(url, data)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
free(result);
|
||||
result = NULL;
|
||||
if ((ret = xml_search(data->memory, data->size, "paired", &result)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (strcmp(result, "1") != 0) {
|
||||
gs_error = "Pairing failed";
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
sprintf(url, "https://%s:47984/pair?uniqueid=%s&uuid=%s&devicename=roth&updateState=1&phrase=pairchallenge", server->address, unique_id, uuid_str);
|
||||
if ((ret = http_request(url, data)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
free(result);
|
||||
result = NULL;
|
||||
if ((ret = xml_search(data->memory, data->size, "paired", &result)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (strcmp(result, "1") != 0) {
|
||||
gs_error = "Pairing failed";
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
server->paired = true;
|
||||
|
||||
cleanup:
|
||||
if (ret != GS_OK)
|
||||
gs_unpair(server);
|
||||
|
||||
if (result != NULL)
|
||||
free(result);
|
||||
|
||||
http_free_data(data);
|
||||
|
||||
return ret;
|
||||
@@ -458,7 +634,9 @@ int gs_applist(PSERVER_DATA server, PAPP_LIST *list) {
|
||||
}
|
||||
|
||||
int gs_start_app(PSERVER_DATA server, STREAM_CONFIGURATION *config, int appId, bool sops, bool localaudio) {
|
||||
int ret = GS_OK;
|
||||
uuid_t uuid;
|
||||
char* result = NULL;
|
||||
char uuid_str[37];
|
||||
|
||||
if (config->height >= 2160 && !server->supports4K)
|
||||
@@ -469,7 +647,7 @@ int gs_start_app(PSERVER_DATA server, STREAM_CONFIGURATION *config, int appId, b
|
||||
|
||||
srand(time(NULL));
|
||||
char url[4096];
|
||||
u_int32_t rikeyid = 1;
|
||||
u_int32_t rikeyid = 0;
|
||||
char rikey_hex[33];
|
||||
bytes_to_hex(config->remoteInputAesKey, rikey_hex, 16);
|
||||
|
||||
@@ -486,18 +664,33 @@ int gs_start_app(PSERVER_DATA server, STREAM_CONFIGURATION *config, int appId, b
|
||||
} else
|
||||
sprintf(url, "https://%s:47984/resume?uniqueid=%s&uuid=%s&rikey=%s&rikeyid=%d", server->address, unique_id, uuid_str, rikey_hex, rikeyid);
|
||||
|
||||
int ret = http_request(url, data);
|
||||
if (ret == GS_OK)
|
||||
if ((ret = http_request(url, data)) == GS_OK)
|
||||
server->currentGame = appId;
|
||||
else
|
||||
goto cleanup;
|
||||
|
||||
if ((ret = xml_search(data->memory, data->size, "gamesession", &result)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (!strcmp(result, "0")) {
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (result != NULL)
|
||||
free(result);
|
||||
|
||||
http_free_data(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gs_quit_app(PSERVER_DATA server) {
|
||||
int ret = GS_OK;
|
||||
char url[4096];
|
||||
uuid_t uuid;
|
||||
char uuid_str[37];
|
||||
char* result = NULL;
|
||||
PHTTP_DATA data = http_create_data();
|
||||
if (data == NULL)
|
||||
return GS_OUT_OF_MEMORY;
|
||||
@@ -505,7 +698,20 @@ int gs_quit_app(PSERVER_DATA server) {
|
||||
uuid_generate_random(uuid);
|
||||
uuid_unparse(uuid, uuid_str);
|
||||
sprintf(url, "https://%s:47984/cancel?uniqueid=%s&uuid=%s", server->address, unique_id, uuid_str);
|
||||
int ret = http_request(url, data);
|
||||
if ((ret = http_request(url, data)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if ((ret = xml_search(data->memory, data->size, "cancel", &result)) != GS_OK)
|
||||
goto cleanup;
|
||||
|
||||
if (strcmp(result, "0") == 0) {
|
||||
ret = GS_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (result != NULL)
|
||||
free(result);
|
||||
|
||||
http_free_data(data);
|
||||
return ret;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015 Iwan Timmer
|
||||
* Copyright (C) 2015, 2016 Iwan Timmer
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -21,12 +21,17 @@
|
||||
|
||||
#include "xml.h"
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MIN_SUPPORTED_GFE_VERSION 3
|
||||
#define MAX_SUPPORTED_GFE_VERSION 7
|
||||
|
||||
typedef struct _SERVER_DATA {
|
||||
const char* address;
|
||||
char* gpuType;
|
||||
char* gfeVersion;
|
||||
bool paired;
|
||||
bool supports4K;
|
||||
int currentGame;
|
||||
@@ -36,5 +41,6 @@ typedef struct _SERVER_DATA {
|
||||
int gs_init(PSERVER_DATA server, const char *keyDirectory);
|
||||
int gs_start_app(PSERVER_DATA server, PSTREAM_CONFIGURATION config, int appId, bool sops, bool localaudio);
|
||||
int gs_applist(PSERVER_DATA server, PAPP_LIST *app_list);
|
||||
int gs_unpair(PSERVER_DATA server);
|
||||
int gs_pair(PSERVER_DATA server, char* pin);
|
||||
int gs_quit_app(PSERVER_DATA server);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015 Iwan Timmer
|
||||
* Copyright (C) 2015, 2016 Iwan Timmer
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -26,5 +26,6 @@
|
||||
#define GS_WRONG_STATE -4
|
||||
#define GS_IO_ERROR -5
|
||||
#define GS_NOT_SUPPORTED_4K -6
|
||||
#define GS_UNSUPPORTED_VERSION -7
|
||||
|
||||
const char* gs_error;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#define GS_SPS_BITSTREAM_FIXUP 0x01
|
||||
#define GS_SPS_BASELINE_HACK 0x02
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
## Size of network packets should be lower than MTU
|
||||
#packetsize = 1024
|
||||
|
||||
## Use of h265/HEVC video codec
|
||||
#h265 = false
|
||||
|
||||
## Default started application on host
|
||||
#app = Steam
|
||||
|
||||
@@ -42,6 +45,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
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
extern const char* audio_device;
|
||||
|
||||
@@ -31,3 +31,6 @@ extern AUDIO_RENDERER_CALLBACKS audio_callbacks_sdl;
|
||||
extern AUDIO_RENDERER_CALLBACKS audio_callbacks_pulse;
|
||||
bool audio_pulse_init();
|
||||
#endif
|
||||
#ifdef HAVE_PI
|
||||
extern AUDIO_RENDERER_CALLBACKS audio_callbacks_omx;
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015 Iwan Timmer
|
||||
* Copyright (C) 2015, 2016 Iwan Timmer
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -28,8 +28,6 @@
|
||||
#define MAX_CHANNEL_COUNT 6
|
||||
#define FRAME_SIZE 240
|
||||
|
||||
const char* audio_device = "sysdefault";
|
||||
|
||||
static snd_pcm_t *handle;
|
||||
static OpusMSDecoder* decoder;
|
||||
static short pcmBuffer[FRAME_SIZE * MAX_CHANNEL_COUNT];
|
||||
@@ -64,6 +62,9 @@ static void alsa_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGU
|
||||
snd_pcm_uframes_t buffer_size = 12 * period_size;
|
||||
unsigned int sampleRate = opusConfig->sampleRate;
|
||||
|
||||
if (audio_device == NULL)
|
||||
audio_device = "sysdefault";
|
||||
|
||||
/* Open PCM device for playback. */
|
||||
CHECK_RETURN(snd_pcm_open(&handle, audio_device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK))
|
||||
|
||||
|
||||
208
src/audio/omx.c
Normal file
208
src/audio/omx.c
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015, 2016 Iwan Timmer
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../audio.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <opus_multistream.h>
|
||||
#include "bcm_host.h"
|
||||
#include "ilclient.h"
|
||||
|
||||
#define MAX_CHANNEL_COUNT 6
|
||||
#define FRAME_SIZE 240
|
||||
|
||||
static OpusMSDecoder* decoder;
|
||||
ILCLIENT_T* handle;
|
||||
COMPONENT_T* component;
|
||||
static OMX_BUFFERHEADERTYPE *buf;
|
||||
static short pcmBuffer[FRAME_SIZE * MAX_CHANNEL_COUNT];
|
||||
static int channelCount;
|
||||
|
||||
static void omx_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig) {
|
||||
int rc, error;
|
||||
OMX_ERRORTYPE err;
|
||||
unsigned char omxMapping[6];
|
||||
char* componentName = "audio_render";
|
||||
|
||||
channelCount = opusConfig->channelCount;
|
||||
/* The supplied mapping array has order: FL-FR-C-LFE-RL-RR
|
||||
* OMX expects the order: FL-FR-LFE-C-RL-RR
|
||||
* We need copy the mapping locally and swap the channels around.
|
||||
*/
|
||||
memcpy(omxMapping, opusConfig->mapping, sizeof(omxMapping));
|
||||
if (opusConfig->channelCount > 2) {
|
||||
omxMapping[2] = opusConfig->mapping[3];
|
||||
omxMapping[3] = opusConfig->mapping[2];
|
||||
}
|
||||
|
||||
decoder = opus_multistream_decoder_create(opusConfig->sampleRate,
|
||||
opusConfig->channelCount,
|
||||
opusConfig->streams,
|
||||
opusConfig->coupledStreams,
|
||||
omxMapping,
|
||||
&rc);
|
||||
|
||||
handle = ilclient_init();
|
||||
if (handle == NULL) {
|
||||
fprintf(stderr, "IL client init failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (ilclient_create_component(handle, &component, componentName, ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS) != 0) {
|
||||
fprintf(stderr, "Component create failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (ilclient_change_component_state(component, OMX_StateIdle)!= 0) {
|
||||
fprintf(stderr, "Couldn't change state to Idle\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// must be before we enable buffers
|
||||
OMX_AUDIO_PARAM_PORTFORMATTYPE audioPortFormat;
|
||||
memset(&audioPortFormat, 0, sizeof(OMX_AUDIO_PARAM_PORTFORMATTYPE));
|
||||
audioPortFormat.nSize = sizeof(OMX_AUDIO_PARAM_PORTFORMATTYPE);
|
||||
audioPortFormat.nVersion.nVersion = OMX_VERSION;
|
||||
|
||||
audioPortFormat.nPortIndex = 100;
|
||||
|
||||
OMX_GetParameter(ilclient_get_handle(component), OMX_IndexParamAudioPortFormat, &audioPortFormat);
|
||||
|
||||
audioPortFormat.eEncoding = OMX_AUDIO_CodingPCM;
|
||||
OMX_SetParameter(ilclient_get_handle(component), OMX_IndexParamAudioPortFormat, &audioPortFormat);
|
||||
|
||||
OMX_AUDIO_PARAM_PCMMODETYPE sPCMMode;
|
||||
|
||||
memset(&sPCMMode, 0, sizeof(OMX_AUDIO_PARAM_PCMMODETYPE));
|
||||
sPCMMode.nSize = sizeof(OMX_AUDIO_PARAM_PCMMODETYPE);
|
||||
sPCMMode.nVersion.nVersion = OMX_VERSION;
|
||||
sPCMMode.nPortIndex = 100;
|
||||
sPCMMode.nChannels = channelCount;
|
||||
sPCMMode.eNumData = OMX_NumericalDataSigned;
|
||||
sPCMMode.eEndian = OMX_EndianLittle;
|
||||
sPCMMode.nSamplingRate = opusConfig->sampleRate;
|
||||
sPCMMode.bInterleaved = OMX_TRUE;
|
||||
sPCMMode.nBitPerSample = 16;
|
||||
sPCMMode.ePCMMode = OMX_AUDIO_PCMModeLinear;
|
||||
|
||||
switch(channelCount) {
|
||||
case 1:
|
||||
sPCMMode.eChannelMapping[0] = OMX_AUDIO_ChannelCF;
|
||||
break;
|
||||
case 8:
|
||||
sPCMMode.eChannelMapping[7] = OMX_AUDIO_ChannelRS;
|
||||
case 7:
|
||||
sPCMMode.eChannelMapping[6] = OMX_AUDIO_ChannelLS;
|
||||
case 6:
|
||||
sPCMMode.eChannelMapping[5] = OMX_AUDIO_ChannelRR;
|
||||
case 5:
|
||||
sPCMMode.eChannelMapping[4] = OMX_AUDIO_ChannelLR;
|
||||
case 4:
|
||||
sPCMMode.eChannelMapping[3] = OMX_AUDIO_ChannelLFE;
|
||||
case 3:
|
||||
sPCMMode.eChannelMapping[2] = OMX_AUDIO_ChannelCF;
|
||||
case 2:
|
||||
sPCMMode.eChannelMapping[1] = OMX_AUDIO_ChannelRF;
|
||||
sPCMMode.eChannelMapping[0] = OMX_AUDIO_ChannelLF;
|
||||
break;
|
||||
}
|
||||
|
||||
err = OMX_SetParameter(ilclient_get_handle(component), OMX_IndexParamAudioPcm, &sPCMMode);
|
||||
if(err != OMX_ErrorNone){
|
||||
fprintf(stderr, "PCM mode unsupported\n");
|
||||
return;
|
||||
}
|
||||
OMX_CONFIG_BRCMAUDIODESTINATIONTYPE arDest;
|
||||
|
||||
if (audio_device == NULL)
|
||||
audio_device = "hdmi";
|
||||
|
||||
if (audio_device && strlen(audio_device) < sizeof(arDest.sName)) {
|
||||
memset(&arDest, 0, sizeof(OMX_CONFIG_BRCMAUDIODESTINATIONTYPE));
|
||||
arDest.nSize = sizeof(OMX_CONFIG_BRCMAUDIODESTINATIONTYPE);
|
||||
arDest.nVersion.nVersion = OMX_VERSION;
|
||||
|
||||
strcpy((char *)arDest.sName, audio_device);
|
||||
|
||||
err = OMX_SetParameter(ilclient_get_handle(component), OMX_IndexConfigBrcmAudioDestination, &arDest);
|
||||
if (err != OMX_ErrorNone) {
|
||||
fprintf(stderr, "Error on setting audio destination\nomx option must be set to hdmi or local\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// input port
|
||||
ilclient_enable_port_buffers(component, 100, NULL, NULL, NULL);
|
||||
ilclient_enable_port(component, 100);
|
||||
|
||||
err = ilclient_change_component_state(component, OMX_StateExecuting);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "Couldn't change state to Executing\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void omx_renderer_cleanup() {
|
||||
if (decoder != NULL)
|
||||
opus_multistream_decoder_destroy(decoder);
|
||||
if (handle != NULL) {
|
||||
if((buf = ilclient_get_input_buffer(component, 100, 1)) == NULL){
|
||||
fprintf(stderr, "Can't get audio buffer\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
buf->nFilledLen = 0;
|
||||
buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN | OMX_BUFFERFLAG_EOS;
|
||||
|
||||
if(OMX_EmptyThisBuffer(ILC_GET_HANDLE(component), buf) != OMX_ErrorNone){
|
||||
fprintf(stderr, "Can't empty audio buffer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ilclient_disable_port_buffers(component, 100, NULL, NULL, NULL);
|
||||
ilclient_change_component_state(component, OMX_StateIdle);
|
||||
ilclient_change_component_state(component, OMX_StateLoaded);
|
||||
}
|
||||
}
|
||||
|
||||
static void omx_renderer_decode_and_play_sample(char* data, int length) {
|
||||
int decodeLen = opus_multistream_decode(decoder, data, length, pcmBuffer, FRAME_SIZE, 0);
|
||||
if (decodeLen > 0) {
|
||||
buf = ilclient_get_input_buffer(component, 100, 1);
|
||||
buf->nOffset = 0;
|
||||
buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN;
|
||||
int bufLength = decodeLen * sizeof(short) * channelCount;
|
||||
memcpy(buf->pBuffer, pcmBuffer, bufLength);
|
||||
buf->nFilledLen = bufLength;
|
||||
int r = OMX_EmptyThisBuffer(ilclient_get_handle(component), buf);
|
||||
if (r != OMX_ErrorNone) {
|
||||
fprintf(stderr, "Empty buffer error\n");
|
||||
}
|
||||
} else {
|
||||
printf("Opus error from decode: %d\n", decodeLen);
|
||||
}
|
||||
}
|
||||
|
||||
AUDIO_RENDERER_CALLBACKS audio_callbacks_omx = {
|
||||
.init = omx_renderer_init,
|
||||
.cleanup = omx_renderer_cleanup,
|
||||
.decodeAndPlaySample = omx_renderer_decode_and_play_sample,
|
||||
.capabilities = CAPABILITY_DIRECT_SUBMIT,
|
||||
};
|
||||
16
src/config.c
16
src/config.c
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015 Iwan Timmer
|
||||
* Copyright (C) 2015, 2016 Iwan Timmer
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
bool inputAdded = false;
|
||||
static bool mapped = true;
|
||||
const char* audio_device = NULL;
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"720", no_argument, NULL, 'a'},
|
||||
@@ -63,6 +64,8 @@ static struct option long_options[] = {
|
||||
{"surround", no_argument, NULL, 'u'},
|
||||
{"fps", required_argument, NULL, 'v'},
|
||||
{"forcehw", no_argument, NULL, 'w'},
|
||||
{"forcehevc", no_argument, NULL, 'x'},
|
||||
{"unsupported", no_argument, NULL, 'y'},
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
|
||||
@@ -199,6 +202,13 @@ static void parse_argument(int c, char* value, PCONFIGURATION config) {
|
||||
break;
|
||||
case 'w':
|
||||
config->forcehw = true;
|
||||
break;
|
||||
case 'x':
|
||||
config->stream.supportsHevc = true;
|
||||
break;
|
||||
case 'y':
|
||||
config->unsupported_version = true;
|
||||
break;
|
||||
case 1:
|
||||
if (config->action == NULL)
|
||||
config->action = value;
|
||||
@@ -280,6 +290,7 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) {
|
||||
config->stream.packetSize = 1024;
|
||||
config->stream.streamingRemotely = 0;
|
||||
config->stream.audioConfiguration = AUDIO_CONFIGURATION_STEREO;
|
||||
config->stream.supportsHevc = false;
|
||||
|
||||
config->platform = "default";
|
||||
config->app = "Steam";
|
||||
@@ -289,6 +300,7 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) {
|
||||
config->sops = true;
|
||||
config->localaudio = false;
|
||||
config->fullscreen = true;
|
||||
config->unsupported_version = false;
|
||||
|
||||
config->inputsCount = 0;
|
||||
config->mapping = get_path("mappings/default.conf", getenv("XDG_DATA_DIRS"));
|
||||
@@ -306,7 +318,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:xy", long_options, &option_index)) != -1) {
|
||||
parse_argument(c, optarg, config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015 Iwan Timmer
|
||||
* Copyright (C) 2015, 2016 Iwan Timmer
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
@@ -41,6 +41,7 @@ typedef struct _CONFIGURATION {
|
||||
bool localaudio;
|
||||
bool fullscreen;
|
||||
bool forcehw;
|
||||
bool unsupported_version;
|
||||
struct input_config inputs[MAX_INPUTS];
|
||||
int inputsCount;
|
||||
} CONFIGURATION, *PCONFIGURATION;
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
extern CONNECTION_LISTENER_CALLBACKS connection_callbacks;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#ifdef HAVE_LIBCEC
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <ceccloader.h>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "mapping.h"
|
||||
|
||||
#include "libevdev/libevdev.h"
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <libudev.h>
|
||||
#include <stdio.h>
|
||||
@@ -106,9 +106,9 @@ static short evdev_convert_value(struct input_event *ev, struct input_device *de
|
||||
else if (ev->value < parms->min)
|
||||
return reverse?SHRT_MAX:SHRT_MIN;
|
||||
else if (reverse)
|
||||
return (parms->max - (ev->value<parms->avg?parms->flat*2:0) - ev->value) * (SHRT_MAX-SHRT_MIN) / (parms->max-parms->min-parms->flat*2) + SHRT_MIN;
|
||||
return (long long)(parms->max - (ev->value<parms->avg?parms->flat*2:0) - ev->value) * (SHRT_MAX-SHRT_MIN) / (parms->max-parms->min-parms->flat*2) + SHRT_MIN;
|
||||
else
|
||||
return (ev->value - (ev->value>parms->avg?parms->flat*2:0) - parms->min) * (SHRT_MAX-SHRT_MIN) / (parms->max-parms->min-parms->flat*2) + SHRT_MIN;
|
||||
return (long long)(ev->value - (ev->value>parms->avg?parms->flat*2:0) - parms->min) * (SHRT_MAX-SHRT_MIN) / (parms->max-parms->min-parms->flat*2) + SHRT_MIN;
|
||||
}
|
||||
|
||||
static char evdev_convert_value_byte(struct input_event *ev, struct input_device *dev, struct input_abs_parms *parms) {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "sdlinput.h"
|
||||
#include "../sdl.h"
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#define ACTION_MODIFIERS (MODIFIER_SHIFT|MODIFIER_ALT|MODIFIER_CTRL)
|
||||
#define QUIT_KEY SDLK_q
|
||||
|
||||
23
src/main.c
23
src/main.c
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015 Iwan Timmer
|
||||
* Copyright (C) 2015, 2016 Iwan Timmer
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -33,7 +33,7 @@
|
||||
#include "input/cec.h"
|
||||
#include "input/sdlinput.h"
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -121,6 +121,7 @@ static void help() {
|
||||
printf("\n Actions\n\n");
|
||||
printf("\tmap\t\t\tCreate mapping file for gamepad\n");
|
||||
printf("\tpair\t\t\tPair device with computer\n");
|
||||
printf("\tunpair\t\t\tUnpair device with computer\n");
|
||||
printf("\tstream\t\t\tStream computer to device\n");
|
||||
printf("\tlist\t\t\tList available games and applications\n");
|
||||
printf("\tquit\t\t\tQuit the application or game being streamed\n");
|
||||
@@ -137,6 +138,7 @@ static void help() {
|
||||
printf("\t-60fps\t\t\tUse 60fps [default]\n");
|
||||
printf("\t-bitrate <bitrate>\tSpecify the bitrate in Kbps\n");
|
||||
printf("\t-packetsize <size>\tSpecify the maximum packetsize in bytes\n");
|
||||
printf("\t-forcehevc\t\tUse high efficiency video decoding (HEVC)\n");
|
||||
printf("\t-remote\t\t\tEnable remote optimizations\n");
|
||||
printf("\t-app <app>\t\tName of app to stream\n");
|
||||
printf("\t-nosops\t\t\tDon't allow GFE to modify game settings\n");
|
||||
@@ -151,7 +153,7 @@ static void help() {
|
||||
printf("\n I/O options\n\n");
|
||||
printf("\t-mapping <file>\t\tUse <file> as gamepad mapping configuration file (use before -input)\n");
|
||||
printf("\t-input <device>\t\tUse <device> as input. Can be used multiple times\n");
|
||||
printf("\t-audio <device>\t\tUse <device> as ALSA audio output device (default sysdefault)\n");
|
||||
printf("\t-audio <device>\t\tUse <device> as audio output device\n");
|
||||
printf("\t-forcehw \t\tTry to use video hardware acceleration\n");
|
||||
#endif
|
||||
printf("\nUse Ctrl+Alt+Shift+Q to exit streaming session\n\n");
|
||||
@@ -179,6 +181,7 @@ int main(int argc, char* argv[]) {
|
||||
fprintf(stderr, "Platform '%s' not found\n", config.platform);
|
||||
exit(-1);
|
||||
}
|
||||
config.stream.supportsHevc = config.stream.supportsHevc || platform_supports_hevc(system);
|
||||
|
||||
if (strcmp("map", config.action) == 0) {
|
||||
if (config.address == NULL) {
|
||||
@@ -200,6 +203,7 @@ int main(int argc, char* argv[]) {
|
||||
exit(-1);
|
||||
}
|
||||
config.address[0] = 0;
|
||||
printf("Searching for server...\n");
|
||||
gs_discover_server(config.address);
|
||||
if (config.address[0] == 0) {
|
||||
fprintf(stderr, "Autodiscovery failed. Specify an IP address next time.\n");
|
||||
@@ -223,11 +227,18 @@ int main(int argc, char* argv[]) {
|
||||
} else if (ret == GS_INVALID) {
|
||||
fprintf(stderr, "Invalid data received from server: %s\n", config.address, gs_error);
|
||||
exit(-1);
|
||||
} else if (ret == GS_UNSUPPORTED_VERSION) {
|
||||
if (!config.unsupported_version) {
|
||||
fprintf(stderr, "Unsupported version: %s\n", gs_error);
|
||||
exit(-1);
|
||||
}
|
||||
} else if (ret != GS_OK) {
|
||||
fprintf(stderr, "Can't connect to server %s\n", config.address);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
printf("NVIDIA %s, GFE %s (protocol version %d)\n", server.gpuType, server.gfeVersion, server.serverMajorVersion);
|
||||
|
||||
if (strcmp("list", config.action) == 0) {
|
||||
pair_check(&server);
|
||||
applist(&server);
|
||||
@@ -260,6 +271,12 @@ int main(int argc, char* argv[]) {
|
||||
} else {
|
||||
printf("Succesfully paired\n");
|
||||
}
|
||||
} else if (strcmp("unpair", config.action) == 0) {
|
||||
if (gs_unpair(&server) != GS_OK) {
|
||||
fprintf(stderr, "Failed to unpair to server: %s\n", gs_error);
|
||||
} else {
|
||||
printf("Succesfully unpaired\n");
|
||||
}
|
||||
} else if (strcmp("quit", config.action) == 0) {
|
||||
pair_check(&server);
|
||||
gs_quit_app(&server);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015 Iwan Timmer
|
||||
* Copyright (C) 2015, 2016 Iwan Timmer
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -22,9 +22,9 @@
|
||||
#include "platform.h"
|
||||
#include "audio.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
typedef bool(*ImxInit)();
|
||||
@@ -48,6 +48,13 @@ enum platform platform_check(char* name) {
|
||||
return PI;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_AML
|
||||
if (std || strcmp(name, "aml") == 0) {
|
||||
void *handle = dlopen("libmoonlight-aml.so", RTLD_NOW | RTLD_GLOBAL);
|
||||
if (handle != NULL && access("/dev/amvideo", F_OK) != -1)
|
||||
return AML;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_SDL
|
||||
if (std || strcmp(name, "sdl") == 0)
|
||||
return SDL;
|
||||
@@ -73,6 +80,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 (PDECODER_RENDERER_CALLBACKS) dlsym(RTLD_DEFAULT, "decoder_callbacks_aml");
|
||||
#endif
|
||||
#ifdef HAVE_FAKE
|
||||
case FAKE:
|
||||
return &decoder_callbacks_fake;
|
||||
@@ -87,6 +98,11 @@ AUDIO_RENDERER_CALLBACKS* platform_get_audio(enum platform system) {
|
||||
case SDL:
|
||||
return &audio_callbacks_sdl;
|
||||
#endif
|
||||
#ifdef HAVE_PI
|
||||
case PI:
|
||||
if (audio_device == NULL || strcmp(audio_device, "local") == 0 || strcmp(audio_device, "hdmi") == 0)
|
||||
return (PAUDIO_RENDERER_CALLBACKS) dlsym(RTLD_DEFAULT, "audio_callbacks_omx");
|
||||
#endif
|
||||
default:
|
||||
#ifdef HAVE_PULSE
|
||||
if (audio_pulse_init())
|
||||
@@ -96,3 +112,11 @@ AUDIO_RENDERER_CALLBACKS* platform_get_audio(enum platform system) {
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool platform_supports_hevc(enum platform system) {
|
||||
switch (system) {
|
||||
case AML:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015 Iwan Timmer
|
||||
* Copyright (C) 2015, 2016 Iwan Timmer
|
||||
*
|
||||
* Moonlight is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -17,19 +17,21 @@
|
||||
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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);
|
||||
bool platform_supports_hevc(enum platform system);
|
||||
|
||||
#ifdef HAVE_FAKE
|
||||
extern DECODER_RENDERER_CALLBACKS decoder_callbacks_fake;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "sdl.h"
|
||||
#include "input/sdlinput.h"
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
static bool done;
|
||||
static int fullscreen_flags;
|
||||
|
||||
120
src/video/aml.c
Normal file
120
src/video/aml.c
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* This file is part of Moonlight Embedded.
|
||||
*
|
||||
* Copyright (C) 2015, 2016 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <amcodec/codec.h>
|
||||
|
||||
static codec_para_t codecParam = { 0 };
|
||||
static const size_t SYNC_OUTSIDE = (2);
|
||||
|
||||
static 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);
|
||||
int ret = write(fd,bcmd,strlen(bcmd));
|
||||
if (ret < 0) {
|
||||
printf("osd_blank error during write: %x\n", ret);
|
||||
}
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void aml_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
osd_blank("/sys/class/graphics/fb0/blank",1);
|
||||
osd_blank("/sys/class/graphics/fb1/blank",0);
|
||||
|
||||
codecParam.stream_type = STREAM_TYPE_ES_VIDEO;
|
||||
codecParam.has_video = 1;
|
||||
codecParam.noblock = 0;
|
||||
|
||||
switch (videoFormat) {
|
||||
case VIDEO_FORMAT_H264:
|
||||
if (width > 1920 || height > 1080) {
|
||||
codecParam.video_type = VFORMAT_H264_4K2K;
|
||||
codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_H264_4K2K;
|
||||
} else {
|
||||
codecParam.video_type = VFORMAT_H264;
|
||||
codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_H264;
|
||||
}
|
||||
break;
|
||||
case VIDEO_FORMAT_H265:
|
||||
codecParam.video_type = VFORMAT_HEVC;
|
||||
codecParam.am_sysinfo.format = VIDEO_DEC_FORMAT_HEVC;
|
||||
break;
|
||||
default:
|
||||
printf("Video format not supported\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
codecParam.am_sysinfo.width = width;
|
||||
codecParam.am_sysinfo.height = height;
|
||||
codecParam.am_sysinfo.rate = 96000 / redrawRate;
|
||||
codecParam.am_sysinfo.param = (void *)(SYNC_OUTSIDE);
|
||||
|
||||
int api = codec_init(&codecParam);
|
||||
if (api != 0) {
|
||||
fprintf(stderr, "codec_init error: %x\n", api);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void aml_cleanup() {
|
||||
int api = codec_close(&codecParam);
|
||||
osd_blank("/sys/class/graphics/fb0/blank",0);
|
||||
osd_blank("/sys/class/graphics/fb1/blank",0);
|
||||
}
|
||||
|
||||
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),
|
||||
};
|
||||
@@ -17,14 +17,14 @@
|
||||
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static FILE* fd;
|
||||
static const char* fileName = "fake.h264";
|
||||
|
||||
void decoder_renderer_setup(int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
void decoder_renderer_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
fd = fopen(fileName, "w");
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include "ffmpeg_vdpau.h"
|
||||
#endif
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <pthread.h>
|
||||
@@ -42,7 +44,7 @@ enum decoders decoder_system;
|
||||
|
||||
// This function must be called before
|
||||
// any other decoding functions
|
||||
int ffmpeg_init(int width, int height, int perf_lvl, int thread_count) {
|
||||
int ffmpeg_init(int videoFormat, int width, int height, int perf_lvl, int thread_count) {
|
||||
// Initialize the avcodec library and register codecs
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
avcodec_register_all();
|
||||
@@ -51,7 +53,15 @@ int ffmpeg_init(int width, int height, int perf_lvl, int thread_count) {
|
||||
|
||||
#ifdef HAVE_VDPAU
|
||||
if (perf_lvl & HARDWARE_ACCELERATION) {
|
||||
decoder = avcodec_find_decoder_by_name("h264_vdpau");
|
||||
switch (videoFormat) {
|
||||
case VIDEO_FORMAT_H264:
|
||||
decoder = avcodec_find_decoder_by_name("h264_vdpau");
|
||||
break;
|
||||
case VIDEO_FORMAT_H265:
|
||||
decoder = avcodec_find_decoder_by_name("hevc_vdpau");
|
||||
break;
|
||||
}
|
||||
|
||||
if (decoder != NULL)
|
||||
decoder_system = VDPAU;
|
||||
}
|
||||
@@ -59,7 +69,14 @@ int ffmpeg_init(int width, int height, int perf_lvl, int thread_count) {
|
||||
|
||||
if (decoder == NULL) {
|
||||
decoder_system = SOFTWARE;
|
||||
decoder = avcodec_find_decoder_by_name("h264");
|
||||
switch (videoFormat) {
|
||||
case VIDEO_FORMAT_H264:
|
||||
decoder = avcodec_find_decoder_by_name("h264");
|
||||
break;
|
||||
case VIDEO_FORMAT_H265:
|
||||
decoder = avcodec_find_decoder_by_name("hevc");
|
||||
break;
|
||||
}
|
||||
if (decoder == NULL) {
|
||||
printf("Couldn't find decoder\n");
|
||||
return -1;
|
||||
@@ -89,7 +106,7 @@ int ffmpeg_init(int width, int height, int perf_lvl, int thread_count) {
|
||||
|
||||
decoder_ctx->width = width;
|
||||
decoder_ctx->height = height;
|
||||
decoder_ctx->pix_fmt = PIX_FMT_YUV420P;
|
||||
decoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
|
||||
int err = avcodec_open2(decoder_ctx, decoder, NULL);
|
||||
if (err < 0) {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
// Uses hardware acceleration
|
||||
#define HARDWARE_ACCELERATION 0x40
|
||||
|
||||
int ffmpeg_init(int width, int height, int perf_lvl, int thread_count);
|
||||
int ffmpeg_init(int videoFormat, int width, int height, int perf_lvl, int thread_count);
|
||||
void ffmpeg_destroy(void);
|
||||
|
||||
int ffmpeg_draw_frame(AVFrame *pict);
|
||||
|
||||
@@ -64,23 +64,22 @@ struct vdpau_render_state* vdp_get_free_render_state() {
|
||||
return render_state;
|
||||
}
|
||||
|
||||
static int vdp_get_buffer(AVCodecContext* context, AVFrame* frame) {
|
||||
static void vdp_release_buffer(void* opaque, uint8_t *data) {
|
||||
struct vdpau_render_state *render_state = (struct vdpau_render_state *) data;
|
||||
render_state->state = 0;
|
||||
}
|
||||
|
||||
static int vdp_get_buffer(AVCodecContext* context, AVFrame* frame, int flags) {
|
||||
struct vdpau_render_state* pRenderState = vdp_get_free_render_state();
|
||||
frame->data[0] = (uint8_t*) pRenderState;
|
||||
frame->type = FF_BUFFER_TYPE_USER;
|
||||
frame->buf[0] = av_buffer_create(frame->data[0], 0, vdp_release_buffer, NULL, 0);
|
||||
|
||||
pRenderState->state |= FF_VDPAU_STATE_USED_FOR_RENDER;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vdp_release_buffer(AVCodecContext* context, AVFrame* frame) {
|
||||
struct vdpau_render_state *render_state = (struct vdpau_render_state *)frame->data[0];
|
||||
render_state->state = 0;
|
||||
frame->data[0] = 0;
|
||||
}
|
||||
|
||||
static enum AVPixelFormat vdp_get_format(AVCodecContext* context, const enum AVPixelFormat* pixel_format) {
|
||||
return PIX_FMT_VDPAU_H264;
|
||||
return AV_PIX_FMT_VDPAU_H264;
|
||||
}
|
||||
|
||||
static void vdp_draw_horiz_band(struct AVCodecContext* context, const AVFrame* frame, int offset[4], int y, int type, int height) {
|
||||
@@ -108,8 +107,7 @@ int vdpau_init(AVCodecContext* decoder_ctx, int width, int height) {
|
||||
vdp_get_proc_address(vdp_device, VDP_FUNC_ID_DECODER_CREATE, (void**)&vdp_decoder_create);
|
||||
vdp_get_proc_address(vdp_device, VDP_FUNC_ID_VIDEO_MIXER_CREATE, (void**)&vdp_video_mixer_create);
|
||||
|
||||
decoder_ctx->get_buffer = vdp_get_buffer;
|
||||
decoder_ctx->release_buffer = vdp_release_buffer;
|
||||
decoder_ctx->get_buffer2 = vdp_get_buffer;
|
||||
decoder_ctx->draw_horiz_band = vdp_draw_horiz_band;
|
||||
decoder_ctx->get_format = vdp_get_format;
|
||||
decoder_ctx->slice_flags = SLICE_FLAG_CODED_ORDER | SLICE_FLAG_ALLOW_FIELD;
|
||||
@@ -119,7 +117,7 @@ int vdpau_init(AVCodecContext* decoder_ctx, int width, int height) {
|
||||
printf("Couldn't allocate frame\n");
|
||||
return -1;
|
||||
}
|
||||
cpu_frame->format = PIX_FMT_YUV420P;
|
||||
cpu_frame->format = AV_PIX_FMT_YUV420P;
|
||||
cpu_frame->width = width;
|
||||
cpu_frame->height = height;
|
||||
av_frame_get_buffer(cpu_frame, 32);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with Moonlight; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
@@ -83,7 +83,12 @@ bool video_imx_init() {
|
||||
return vpu_Init(NULL) == RETCODE_SUCCESS;
|
||||
}
|
||||
|
||||
static void decoder_renderer_setup(int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
static void decoder_renderer_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
if (videoFormat != VIDEO_FORMAT_H264) {
|
||||
fprintf(stderr, "Video format not supported\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
struct mxcfb_gbl_alpha alpha;
|
||||
|
||||
dbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
||||
|
||||
@@ -30,7 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "sps.h"
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -53,7 +53,12 @@ static unsigned char *dest;
|
||||
static int port_settings_changed;
|
||||
static int first_packet;
|
||||
|
||||
static void decoder_renderer_setup(int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
static void decoder_renderer_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
if (videoFormat != VIDEO_FORMAT_H264) {
|
||||
fprintf(stderr, "Video format not supported\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
bcm_host_init();
|
||||
gs_sps_init(width, height);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#include "../sdl.h"
|
||||
#include "ffmpeg.h"
|
||||
|
||||
#include "limelight-common/Limelight.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_thread.h>
|
||||
@@ -32,9 +32,12 @@
|
||||
|
||||
static char* ffmpeg_buffer;
|
||||
|
||||
static void sdl_setup(int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
static void sdl_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
int avc_flags = SLICE_THREADING;
|
||||
if (ffmpeg_init(width, height, avc_flags, 2) < 0) {
|
||||
if (drFlags & FORCE_HARDWARE_ACCELERATION)
|
||||
avc_flags |= HARDWARE_ACCELERATION;
|
||||
|
||||
if (ffmpeg_init(videoFormat, width, height, avc_flags, 2) < 0) {
|
||||
fprintf(stderr, "Couldn't initialize video decoding\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
2
third_party/moonlight-common-c
vendored
2
third_party/moonlight-common-c
vendored
Submodule third_party/moonlight-common-c updated: fbd58c60ea...a6d9ab0664
Reference in New Issue
Block a user