mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2025-07-03 00:06:09 +00:00
Rewrite streaming code to C++ and forklift most of the setup out of the GUI code
This commit is contained in:
parent
cad95f12f6
commit
0821c66d8c
15
app/app.pro
15
app/app.pro
@ -52,18 +52,18 @@ win32 {
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
streaming/audio.c \
|
||||
streaming/input.c \
|
||||
gui/mainwindow.cpp \
|
||||
gui/popupmanager.cpp \
|
||||
backend/identitymanager.cpp \
|
||||
backend/nvhttp.cpp \
|
||||
backend/nvpairingmanager.cpp \
|
||||
streaming/video.c \
|
||||
streaming/connection.cpp \
|
||||
backend/computermanager.cpp \
|
||||
backend/boxartmanager.cpp \
|
||||
settings/streamingpreferences.cpp
|
||||
settings/streamingpreferences.cpp \
|
||||
streaming/input.cpp \
|
||||
streaming/session.cpp \
|
||||
streaming/audio.cpp \
|
||||
streaming/video.cpp
|
||||
|
||||
HEADERS += \
|
||||
utils.h \
|
||||
@ -72,10 +72,11 @@ HEADERS += \
|
||||
backend/identitymanager.h \
|
||||
backend/nvhttp.h \
|
||||
backend/nvpairingmanager.h \
|
||||
streaming/streaming.h \
|
||||
backend/computermanager.h \
|
||||
backend/boxartmanager.h \
|
||||
settings/streamingpreferences.h
|
||||
settings/streamingpreferences.h \
|
||||
streaming/input.hpp \
|
||||
streaming/session.hpp
|
||||
|
||||
FORMS += \
|
||||
gui/mainwindow.ui
|
||||
|
@ -45,6 +45,8 @@ NvComputer::NvComputer(QSettings& settings)
|
||||
this->currentGameId = 0;
|
||||
this->pairState = PS_UNKNOWN;
|
||||
this->state = CS_UNKNOWN;
|
||||
this->gfeVersion = nullptr;
|
||||
this->appVersion = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
@ -104,6 +106,8 @@ NvComputer::NvComputer(QString address, QString serverInfo)
|
||||
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
|
||||
PS_PAIRED : PS_NOT_PAIRED;
|
||||
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
|
||||
this->appVersion = NvHTTP::getXmlString(serverInfo, "appversion");
|
||||
this->gfeVersion = NvHTTP::getXmlString(serverInfo, "GfeVersion");
|
||||
this->activeAddress = address;
|
||||
this->state = NvComputer::CS_ONLINE;
|
||||
}
|
||||
@ -142,6 +146,8 @@ bool NvComputer::update(NvComputer& that)
|
||||
ASSIGN_IF_CHANGED(currentGameId);
|
||||
ASSIGN_IF_CHANGED(activeAddress);
|
||||
ASSIGN_IF_CHANGED(state);
|
||||
ASSIGN_IF_CHANGED(gfeVersion);
|
||||
ASSIGN_IF_CHANGED(appVersion);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
|
||||
return changed;
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ public:
|
||||
PairState pairState;
|
||||
QString activeAddress;
|
||||
int currentGameId;
|
||||
QString gfeVersion;
|
||||
QString appVersion;
|
||||
|
||||
// Persisted traits
|
||||
QString localAddress;
|
||||
|
@ -3,13 +3,11 @@
|
||||
#include "gui/popupmanager.h"
|
||||
#include "backend/identitymanager.h"
|
||||
#include "backend/nvpairingmanager.h"
|
||||
#include "streaming/streaming.h"
|
||||
#include "streaming/session.hpp"
|
||||
#include "backend/computermanager.h"
|
||||
#include "backend/boxartmanager.h"
|
||||
#include "settings/streamingpreferences.h"
|
||||
|
||||
#include <QRandomGenerator>
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::MainWindow),
|
||||
@ -57,92 +55,28 @@ void MainWindow::computerStateChanged(NvComputer* computer)
|
||||
// Stop polling before launching a game
|
||||
m_ComputerManager.stopPollingAsync();
|
||||
|
||||
#if 1
|
||||
STREAM_CONFIGURATION streamConfig;
|
||||
StreamingPreferences prefs;
|
||||
Session session(computer, computer->appList.last());
|
||||
QStringList warnings;
|
||||
QString errorMessage;
|
||||
|
||||
LiInitializeStreamConfiguration(&streamConfig);
|
||||
// TODO: Validate 4K and 4K60 based on serverinfo
|
||||
streamConfig.width = prefs.width;
|
||||
streamConfig.height = prefs.height;
|
||||
streamConfig.fps = prefs.fps;
|
||||
streamConfig.bitrate = prefs.bitrateKbps;
|
||||
streamConfig.packetSize = 1024;
|
||||
streamConfig.hevcBitratePercentageMultiplier = 75;
|
||||
for (int i = 0; i < sizeof(streamConfig.remoteInputAesKey); i++) {
|
||||
streamConfig.remoteInputAesKey[i] =
|
||||
(char)(QRandomGenerator::global()->generate() % 256);
|
||||
}
|
||||
*(int*)streamConfig.remoteInputAesIv = qToBigEndian(QRandomGenerator::global()->generate());
|
||||
switch (prefs.audioConfig)
|
||||
{
|
||||
case StreamingPreferences::AC_AUTO:
|
||||
streamConfig.audioConfiguration = SdlDetermineAudioConfiguration();
|
||||
break;
|
||||
case StreamingPreferences::AC_FORCE_STEREO:
|
||||
streamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO;
|
||||
break;
|
||||
case StreamingPreferences::AC_FORCE_SURROUND:
|
||||
streamConfig.audioConfiguration = AUDIO_CONFIGURATION_51_SURROUND;
|
||||
break;
|
||||
}
|
||||
switch (prefs.videoCodecConfig)
|
||||
{
|
||||
case StreamingPreferences::VCC_AUTO:
|
||||
// TODO: Determine if HEVC is better depending on the decoder
|
||||
streamConfig.supportsHevc = true;
|
||||
streamConfig.enableHdr = false;
|
||||
break;
|
||||
case StreamingPreferences::VCC_FORCE_H264:
|
||||
streamConfig.supportsHevc = false;
|
||||
streamConfig.enableHdr = false;
|
||||
break;
|
||||
case StreamingPreferences::VCC_FORCE_HEVC:
|
||||
streamConfig.supportsHevc = true;
|
||||
streamConfig.enableHdr = false;
|
||||
break;
|
||||
case StreamingPreferences::VCC_FORCE_HEVC_HDR:
|
||||
streamConfig.supportsHevc = true;
|
||||
streamConfig.enableHdr = true;
|
||||
break;
|
||||
// First check for a fatal configuration error
|
||||
errorMessage = session.checkForFatalValidationError();
|
||||
if (!errorMessage.isEmpty()) {
|
||||
// TODO: display error dialog
|
||||
goto AfterStreaming;
|
||||
}
|
||||
|
||||
// TODO: Validate HEVC support based on decoder caps
|
||||
// TODO: Validate HDR support based on decoder caps, display, server caps, and app
|
||||
|
||||
// Initialize the gamepad code with our preferences
|
||||
// Note: must be done before SdlGetAttachedGamepadMask!
|
||||
SdlInitializeGamepad(prefs.multiController);
|
||||
|
||||
QMessageBox* box = new QMessageBox(nullptr);
|
||||
box->setAttribute(Qt::WA_DeleteOnClose); //makes sure the msgbox is deleted automatically when closed
|
||||
box->setStandardButtons(QMessageBox::Cancel);
|
||||
box->setText("Launching game...");
|
||||
box->open();
|
||||
|
||||
if (computer->currentGameId != 0) {
|
||||
http.resumeApp(&streamConfig);
|
||||
}
|
||||
else {
|
||||
http.launchApp(999999, &streamConfig,
|
||||
prefs.enableGameOptimizations,
|
||||
prefs.playAudioOnHost,
|
||||
SdlGetAttachedGamepadMask());
|
||||
// Check for any informational messages to display
|
||||
warnings = session.checkForAdvisoryValidationError();
|
||||
if (!warnings.isEmpty()) {
|
||||
// TODO: display toast or something before we start
|
||||
}
|
||||
|
||||
SERVER_INFORMATION hostInfo;
|
||||
QString serverInfo = http.getServerInfo();
|
||||
// Run the streaming session until termination
|
||||
session.exec();
|
||||
|
||||
QByteArray hostnameStr = computer->activeAddress.toLatin1();
|
||||
QByteArray siAppVersion = http.getXmlString(serverInfo, "appversion").toLatin1();
|
||||
QByteArray siGfeVersion = http.getXmlString(serverInfo, "GfeVersion").toLatin1();
|
||||
|
||||
hostInfo.address = hostnameStr.data();
|
||||
hostInfo.serverInfoAppVersion = siAppVersion.data();
|
||||
hostInfo.serverInfoGfeVersion = siGfeVersion.data();
|
||||
|
||||
StartConnection(&hostInfo, &streamConfig, box);
|
||||
#endif
|
||||
AfterStreaming:
|
||||
m_ComputerManager.startPolling();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,17 @@
|
||||
#include "session.hpp"
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <opus_multistream.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#define MAX_CHANNELS 6
|
||||
#define SAMPLES_PER_FRAME 240
|
||||
|
||||
static SDL_AudioDeviceID g_AudioDevice;
|
||||
static OpusMSDecoder* g_OpusDecoder;
|
||||
static short g_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
|
||||
static OPUS_MULTISTREAM_CONFIGURATION g_OpusConfig;
|
||||
SDL_AudioDeviceID Session::s_AudioDevice;
|
||||
OpusMSDecoder* Session::s_OpusDecoder;
|
||||
short Session::s_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
|
||||
int Session::s_ChannelCount;
|
||||
|
||||
int SdlDetermineAudioConfiguration(void)
|
||||
int Session::sdlDetermineAudioConfiguration()
|
||||
{
|
||||
SDL_AudioSpec want, have;
|
||||
SDL_AudioDeviceID dev;
|
||||
@ -48,7 +49,9 @@ int SdlDetermineAudioConfiguration(void)
|
||||
}
|
||||
}
|
||||
|
||||
int SdlAudioInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* arContext, int arFlags)
|
||||
int Session::sdlAudioInit(int audioConfiguration,
|
||||
POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||
void* arContext, int arFlags)
|
||||
{
|
||||
SDL_AudioSpec want, have;
|
||||
int error;
|
||||
@ -59,81 +62,72 @@ int SdlAudioInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusCon
|
||||
want.channels = opusConfig->channelCount;
|
||||
want.samples = 1024;
|
||||
|
||||
g_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (g_AudioDevice == 0) {
|
||||
s_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (s_AudioDevice == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to open audio device: %s",
|
||||
SDL_GetError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_OpusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate,
|
||||
s_OpusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate,
|
||||
opusConfig->channelCount,
|
||||
opusConfig->streams,
|
||||
opusConfig->coupledStreams,
|
||||
opusConfig->mapping,
|
||||
&error);
|
||||
if (g_OpusDecoder == NULL) {
|
||||
if (s_OpusDecoder == NULL) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create decoder: %d",
|
||||
error);
|
||||
SDL_CloseAudioDevice(g_AudioDevice);
|
||||
g_AudioDevice = 0;
|
||||
SDL_CloseAudioDevice(s_AudioDevice);
|
||||
s_AudioDevice = 0;
|
||||
return -2;
|
||||
}
|
||||
|
||||
SDL_memcpy(&g_OpusConfig, opusConfig, sizeof(g_OpusConfig));
|
||||
s_ChannelCount = opusConfig->channelCount;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SdlAudioStart(void)
|
||||
void Session::sdlAudioStart()
|
||||
{
|
||||
// Unpause the audio device
|
||||
SDL_PauseAudioDevice(g_AudioDevice, 0);
|
||||
SDL_PauseAudioDevice(s_AudioDevice, 0);
|
||||
}
|
||||
|
||||
void SdlAudioStop(void)
|
||||
void Session::sdlAudioStop()
|
||||
{
|
||||
// Pause the audio device
|
||||
SDL_PauseAudioDevice(g_AudioDevice, 1);
|
||||
SDL_PauseAudioDevice(s_AudioDevice, 1);
|
||||
}
|
||||
|
||||
void SdlAudioCleanup(void)
|
||||
void Session::sdlAudioCleanup()
|
||||
{
|
||||
SDL_CloseAudioDevice(g_AudioDevice);
|
||||
g_AudioDevice = 0;
|
||||
SDL_CloseAudioDevice(s_AudioDevice);
|
||||
s_AudioDevice = 0;
|
||||
|
||||
opus_multistream_decoder_destroy(g_OpusDecoder);
|
||||
g_OpusDecoder = NULL;
|
||||
opus_multistream_decoder_destroy(s_OpusDecoder);
|
||||
s_OpusDecoder = NULL;
|
||||
}
|
||||
|
||||
void SdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
void Session::sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
{
|
||||
int samplesDecoded;
|
||||
|
||||
samplesDecoded = opus_multistream_decode(g_OpusDecoder,
|
||||
samplesDecoded = opus_multistream_decode(s_OpusDecoder,
|
||||
(unsigned char*)sampleData,
|
||||
sampleLength,
|
||||
g_OpusDecodeBuffer,
|
||||
s_OpusDecodeBuffer,
|
||||
SAMPLES_PER_FRAME,
|
||||
0);
|
||||
if (samplesDecoded > 0) {
|
||||
if (SDL_QueueAudio(g_AudioDevice,
|
||||
g_OpusDecodeBuffer,
|
||||
sizeof(short) * samplesDecoded * g_OpusConfig.channelCount) < 0) {
|
||||
if (SDL_QueueAudio(s_AudioDevice,
|
||||
s_OpusDecodeBuffer,
|
||||
sizeof(short) * samplesDecoded * s_ChannelCount) < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to queue audio sample: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AUDIO_RENDERER_CALLBACKS k_AudioCallbacks = {
|
||||
SdlAudioInit,
|
||||
SdlAudioStart,
|
||||
SdlAudioStop,
|
||||
SdlAudioCleanup,
|
||||
SdlAudioDecodeAndPlaySample,
|
||||
CAPABILITY_DIRECT_SUBMIT
|
||||
};
|
@ -1,134 +0,0 @@
|
||||
#include "streaming.h"
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
bool g_StreamActive;
|
||||
QMessageBox* g_ProgressBox;
|
||||
|
||||
static
|
||||
void
|
||||
ClStageStarting(int stage)
|
||||
{
|
||||
char buffer[512];
|
||||
sprintf(buffer, "Starting %s...", LiGetStageName(stage));
|
||||
g_ProgressBox->setText(buffer);
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ClStageFailed(int stage, long errorCode)
|
||||
{
|
||||
char buffer[512];
|
||||
sprintf(buffer, "Failed %s with error: %ld",
|
||||
LiGetStageName(stage), errorCode);
|
||||
g_ProgressBox->setText(buffer);
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ClConnectionTerminated(long errorCode)
|
||||
{
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Connection terminated: %ld",
|
||||
errorCode);
|
||||
|
||||
// Push a quit event to the main loop
|
||||
SDL_Event event;
|
||||
event.type = SDL_QUIT;
|
||||
event.quit.timestamp = SDL_GetTicks();
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ClLogMessage(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION,
|
||||
SDL_LOG_PRIORITY_INFO,
|
||||
format,
|
||||
ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
int
|
||||
StartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION streamConfig, QMessageBox* progressBox)
|
||||
{
|
||||
CONNECTION_LISTENER_CALLBACKS listener;
|
||||
|
||||
// Hold onto this for our callbacks
|
||||
g_ProgressBox = progressBox;
|
||||
|
||||
|
||||
LiInitializeConnectionCallbacks(&listener);
|
||||
listener.stageStarting = ClStageStarting;
|
||||
listener.stageFailed = ClStageFailed;
|
||||
listener.connectionTerminated = ClConnectionTerminated;
|
||||
listener.logMessage = ClLogMessage;
|
||||
|
||||
int err = LiStartConnection(serverInfo, streamConfig, &listener,
|
||||
&k_VideoCallbacks, &k_AudioCallbacks,
|
||||
NULL, 0, NULL, 0);
|
||||
if (err != 0) {
|
||||
SDL_Quit();
|
||||
return err;
|
||||
}
|
||||
|
||||
// Before we get into our loop, close the message box used to
|
||||
// display progress
|
||||
Q_ASSERT(g_ProgressBox == progressBox);
|
||||
progressBox->close();
|
||||
delete progressBox;
|
||||
g_ProgressBox = nullptr;
|
||||
|
||||
// Hijack this thread to be the SDL main thread. We have to do this
|
||||
// because we want to suspend all Qt processing until the stream is over.
|
||||
SDL_Event event;
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Quit event received");
|
||||
goto Exit;
|
||||
case SDL_KEYUP:
|
||||
case SDL_KEYDOWN:
|
||||
SdlHandleKeyEvent(&event.key);
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
SdlHandleMouseButtonEvent(&event.button);
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
SdlHandleMouseMotionEvent(&event.motion);
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
SdlHandleMouseWheelEvent(&event.wheel);
|
||||
break;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
SdlHandleControllerAxisEvent(&event.caxis);
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
SdlHandleControllerButtonEvent(&event.cbutton);
|
||||
break;
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
SdlHandleControllerDeviceEvent(&event.cdevice);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_WaitEvent() failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
Exit:
|
||||
g_StreamActive = false;
|
||||
LiStopConnection();
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
#include <stdbool.h>
|
||||
#include "input.hpp"
|
||||
|
||||
#define VK_0 0x30
|
||||
#define VK_A 0x41
|
||||
@ -12,23 +12,7 @@
|
||||
#define VK_NUMPAD1 0x61
|
||||
#endif
|
||||
|
||||
typedef struct _GAMEPAD_STATE {
|
||||
SDL_GameController* controller;
|
||||
SDL_JoystickID jsId;
|
||||
short index;
|
||||
|
||||
short buttons;
|
||||
short lsX, lsY;
|
||||
short rsX, rsY;
|
||||
unsigned char lt, rt;
|
||||
} GAMEPAD_STATE, *PGAMEPAD_STATE;
|
||||
|
||||
#define MAX_GAMEPADS 4
|
||||
static GAMEPAD_STATE g_GamepadState[MAX_GAMEPADS];
|
||||
static unsigned short g_GamepadMask;
|
||||
static bool g_MultiController;
|
||||
|
||||
static const short k_ButtonMap[] = {
|
||||
const int SdlInputHandler::k_ButtonMap[] = {
|
||||
A_FLAG, B_FLAG, X_FLAG, Y_FLAG,
|
||||
BACK_FLAG, SPECIAL_FLAG, PLAY_FLAG,
|
||||
LS_CLK_FLAG, RS_CLK_FLAG,
|
||||
@ -36,7 +20,16 @@ static const short k_ButtonMap[] = {
|
||||
UP_FLAG, DOWN_FLAG, LEFT_FLAG, RIGHT_FLAG
|
||||
};
|
||||
|
||||
void SdlHandleKeyEvent(SDL_KeyboardEvent* event)
|
||||
SdlInputHandler::SdlInputHandler(bool multiController)
|
||||
: m_MultiController(multiController)
|
||||
{
|
||||
if (!m_MultiController) {
|
||||
// Player 1 is always present in non-MC mode
|
||||
m_GamepadMask = 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
{
|
||||
short keyCode;
|
||||
char modifiers;
|
||||
@ -257,7 +250,7 @@ void SdlHandleKeyEvent(SDL_KeyboardEvent* event)
|
||||
modifiers);
|
||||
}
|
||||
|
||||
void SdlHandleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
||||
void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
||||
{
|
||||
int button;
|
||||
|
||||
@ -289,7 +282,7 @@ void SdlHandleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
||||
button);
|
||||
}
|
||||
|
||||
void SdlHandleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||
void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||
{
|
||||
if (event->xrel != 0 || event->yrel != 0) {
|
||||
LiSendMouseMoveEvent((unsigned short)event->xrel,
|
||||
@ -297,34 +290,35 @@ void SdlHandleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||
}
|
||||
}
|
||||
|
||||
void SdlHandleMouseWheelEvent(SDL_MouseWheelEvent* event)
|
||||
void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
|
||||
{
|
||||
if (event->y != 0) {
|
||||
LiSendScrollEvent((signed char)event->y);
|
||||
}
|
||||
}
|
||||
|
||||
static PGAMEPAD_STATE FindStateForGamepad(SDL_JoystickID id)
|
||||
GamepadState*
|
||||
SdlInputHandler::findStateForGamepad(SDL_JoystickID id)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_GAMEPADS; i++) {
|
||||
if (g_GamepadState[i].jsId == id) {
|
||||
SDL_assert(!g_MultiController || g_GamepadState[i].index == i);
|
||||
return &g_GamepadState[i];
|
||||
if (m_GamepadState[i].jsId == id) {
|
||||
SDL_assert(!m_MultiController || m_GamepadState[i].index == i);
|
||||
return &m_GamepadState[i];
|
||||
}
|
||||
}
|
||||
|
||||
// This should only happen with > 4 gamepads
|
||||
SDL_assert(SDL_NumJoysticks() > 4);
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void SendGamepadState(PGAMEPAD_STATE state)
|
||||
void SdlInputHandler::sendGamepadState(GamepadState* state)
|
||||
{
|
||||
SDL_assert(g_GamepadMask == 0x1 || g_MultiController);
|
||||
SDL_assert(m_GamepadMask == 0x1 || m_MultiController);
|
||||
LiSendMultiControllerEvent(state->index,
|
||||
g_GamepadMask,
|
||||
m_GamepadMask,
|
||||
state->buttons,
|
||||
state->lt,
|
||||
state->rt,
|
||||
@ -334,9 +328,9 @@ static void SendGamepadState(PGAMEPAD_STATE state)
|
||||
state->rsY);
|
||||
}
|
||||
|
||||
void SdlHandleControllerAxisEvent(SDL_ControllerAxisEvent* event)
|
||||
void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
|
||||
{
|
||||
PGAMEPAD_STATE state = FindStateForGamepad(event->which);
|
||||
GamepadState* state = findStateForGamepad(event->which);
|
||||
if (state == NULL) {
|
||||
return;
|
||||
}
|
||||
@ -368,12 +362,12 @@ void SdlHandleControllerAxisEvent(SDL_ControllerAxisEvent* event)
|
||||
return;
|
||||
}
|
||||
|
||||
SendGamepadState(state);
|
||||
sendGamepadState(state);
|
||||
}
|
||||
|
||||
void SdlHandleControllerButtonEvent(SDL_ControllerButtonEvent* event)
|
||||
void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* event)
|
||||
{
|
||||
PGAMEPAD_STATE state = FindStateForGamepad(event->which);
|
||||
GamepadState* state = findStateForGamepad(event->which);
|
||||
if (state == NULL) {
|
||||
return;
|
||||
}
|
||||
@ -385,19 +379,19 @@ void SdlHandleControllerButtonEvent(SDL_ControllerButtonEvent* event)
|
||||
state->buttons &= ~k_ButtonMap[event->button];
|
||||
}
|
||||
|
||||
SendGamepadState(state);
|
||||
sendGamepadState(state);
|
||||
}
|
||||
|
||||
void SdlHandleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
|
||||
void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
|
||||
{
|
||||
PGAMEPAD_STATE state;
|
||||
GamepadState* state;
|
||||
|
||||
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
||||
int i;
|
||||
const char* name;
|
||||
|
||||
for (i = 0; i < MAX_GAMEPADS; i++) {
|
||||
if (g_GamepadState[i].controller == NULL) {
|
||||
if (m_GamepadState[i].controller == NULL) {
|
||||
// Found an empty slot
|
||||
break;
|
||||
}
|
||||
@ -409,8 +403,8 @@ void SdlHandleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
|
||||
return;
|
||||
}
|
||||
|
||||
state = &g_GamepadState[i];
|
||||
if (g_MultiController) {
|
||||
state = &m_GamepadState[i];
|
||||
if (m_MultiController) {
|
||||
state->index = i;
|
||||
}
|
||||
else {
|
||||
@ -435,29 +429,29 @@ void SdlHandleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
|
||||
name != NULL ? name : "<null>");
|
||||
|
||||
// Add this gamepad to the gamepad mask
|
||||
if (g_MultiController) {
|
||||
SDL_assert(!(g_GamepadMask & (1 << state->index)));
|
||||
g_GamepadMask |= (1 << state->index);
|
||||
if (m_MultiController) {
|
||||
SDL_assert(!(m_GamepadMask & (1 << state->index)));
|
||||
m_GamepadMask |= (1 << state->index);
|
||||
}
|
||||
else {
|
||||
SDL_assert(g_GamepadMask == 0x1);
|
||||
SDL_assert(m_GamepadMask == 0x1);
|
||||
}
|
||||
|
||||
// Send an empty event to tell the PC we've arrived
|
||||
SendGamepadState(state);
|
||||
sendGamepadState(state);
|
||||
}
|
||||
else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
state = FindStateForGamepad(event->which);
|
||||
state = findStateForGamepad(event->which);
|
||||
if (state != NULL) {
|
||||
SDL_GameControllerClose(state->controller);
|
||||
|
||||
// Remove this from the gamepad mask in MC-mode
|
||||
if (g_MultiController) {
|
||||
SDL_assert(g_GamepadMask & (1 << state->index));
|
||||
g_GamepadMask &= ~(1 << state->index);
|
||||
if (m_MultiController) {
|
||||
SDL_assert(m_GamepadMask & (1 << state->index));
|
||||
m_GamepadMask &= ~(1 << state->index);
|
||||
}
|
||||
else {
|
||||
SDL_assert(g_GamepadMask == 0x1);
|
||||
SDL_assert(m_GamepadMask == 0x1);
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@ -465,7 +459,7 @@ void SdlHandleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
|
||||
state->index);
|
||||
|
||||
// Send a final event to let the PC know this gamepad is gone
|
||||
LiSendMultiControllerEvent(state->index, g_GamepadMask,
|
||||
LiSendMultiControllerEvent(state->index, m_GamepadMask,
|
||||
0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
// Clear all remaining state from this slot
|
||||
@ -474,13 +468,13 @@ void SdlHandleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
|
||||
}
|
||||
}
|
||||
|
||||
int SdlGetAttachedGamepadMask(void)
|
||||
int SdlInputHandler::getAttachedGamepadMask()
|
||||
{
|
||||
int i;
|
||||
int count;
|
||||
int mask;
|
||||
|
||||
if (!g_MultiController) {
|
||||
if (!m_MultiController) {
|
||||
// Player 1 is always present in non-MC mode
|
||||
return 0x1;
|
||||
}
|
||||
@ -494,12 +488,3 @@ int SdlGetAttachedGamepadMask(void)
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
void SdlInitializeGamepad(bool multiController)
|
||||
{
|
||||
g_MultiController = multiController;
|
||||
if (!g_MultiController) {
|
||||
// Player 1 is always present in non-MC mode
|
||||
g_GamepadMask = 0x01;
|
||||
}
|
||||
}
|
50
app/streaming/input.hpp
Normal file
50
app/streaming/input.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
struct GamepadState {
|
||||
SDL_GameController* controller;
|
||||
SDL_JoystickID jsId;
|
||||
short index;
|
||||
|
||||
short buttons;
|
||||
short lsX, lsY;
|
||||
short rsX, rsY;
|
||||
unsigned char lt, rt;
|
||||
};
|
||||
|
||||
#define MAX_GAMEPADS 4
|
||||
|
||||
class SdlInputHandler
|
||||
{
|
||||
public:
|
||||
explicit SdlInputHandler(bool multiController);
|
||||
|
||||
void handleKeyEvent(SDL_KeyboardEvent* event);
|
||||
|
||||
void handleMouseButtonEvent(SDL_MouseButtonEvent* event);
|
||||
|
||||
void handleMouseMotionEvent(SDL_MouseMotionEvent* event);
|
||||
|
||||
void handleMouseWheelEvent(SDL_MouseWheelEvent* event);
|
||||
|
||||
void handleControllerAxisEvent(SDL_ControllerAxisEvent* event);
|
||||
|
||||
void handleControllerButtonEvent(SDL_ControllerButtonEvent* event);
|
||||
|
||||
void handleControllerDeviceEvent(SDL_ControllerDeviceEvent* event);
|
||||
|
||||
int getAttachedGamepadMask();
|
||||
|
||||
private:
|
||||
GamepadState*
|
||||
findStateForGamepad(SDL_JoystickID id);
|
||||
|
||||
void sendGamepadState(GamepadState* state);
|
||||
|
||||
bool m_MultiController;
|
||||
int m_GamepadMask;
|
||||
GamepadState m_GamepadState[MAX_GAMEPADS];
|
||||
|
||||
static const int k_ButtonMap[];
|
||||
};
|
298
app/streaming/session.cpp
Normal file
298
app/streaming/session.cpp
Normal file
@ -0,0 +1,298 @@
|
||||
#include "session.hpp"
|
||||
#include "settings/streamingpreferences.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#include <QRandomGenerator>
|
||||
#include <QMessageBox>
|
||||
#include <QtEndian>
|
||||
#include <QCoreApplication>
|
||||
|
||||
CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
|
||||
Session::clStageStarting,
|
||||
nullptr,
|
||||
Session::clStageFailed,
|
||||
nullptr,
|
||||
Session::clConnectionTerminated,
|
||||
nullptr,
|
||||
nullptr,
|
||||
Session::clLogMessage
|
||||
};
|
||||
|
||||
AUDIO_RENDERER_CALLBACKS Session::k_AudioCallbacks = {
|
||||
Session::sdlAudioInit,
|
||||
Session::sdlAudioStart,
|
||||
Session::sdlAudioStop,
|
||||
Session::sdlAudioCleanup,
|
||||
Session::sdlAudioDecodeAndPlaySample,
|
||||
CAPABILITY_DIRECT_SUBMIT
|
||||
};
|
||||
|
||||
DECODER_RENDERER_CALLBACKS Session::k_VideoCallbacks;
|
||||
|
||||
Session* Session::s_ActiveSession;
|
||||
|
||||
void Session::clStageStarting(int stage)
|
||||
{
|
||||
char buffer[512];
|
||||
sprintf(buffer, "Starting %s...", LiGetStageName(stage));
|
||||
|
||||
// We know this is called on the same thread as LiStartConnection()
|
||||
// which happens to be the main thread, so it's cool to interact
|
||||
// with the GUI in these callbacks.
|
||||
s_ActiveSession->m_ProgressBox.setText(buffer);
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
|
||||
void Session::clStageFailed(int stage, long errorCode)
|
||||
{
|
||||
s_ActiveSession->m_ProgressBox.close();
|
||||
|
||||
// TODO: Open error dialog
|
||||
char buffer[512];
|
||||
sprintf(buffer, "Failed %s with error: %ld",
|
||||
LiGetStageName(stage), errorCode);
|
||||
|
||||
// We know this is called on the same thread as LiStartConnection()
|
||||
// which happens to be the main thread, so it's cool to interact
|
||||
// with the GUI in these callbacks.
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
|
||||
void Session::clConnectionTerminated(long errorCode)
|
||||
{
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Connection terminated: %ld",
|
||||
errorCode);
|
||||
|
||||
// Push a quit event to the main loop
|
||||
SDL_Event event;
|
||||
event.type = SDL_QUIT;
|
||||
event.quit.timestamp = SDL_GetTicks();
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
void Session::clLogMessage(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION,
|
||||
SDL_LOG_PRIORITY_INFO,
|
||||
format,
|
||||
ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
Session::Session(NvComputer* computer, NvApp& app)
|
||||
: m_Computer(computer),
|
||||
m_App(app),
|
||||
m_ProgressBox(nullptr)
|
||||
{
|
||||
StreamingPreferences prefs;
|
||||
|
||||
LiInitializeStreamConfiguration(&m_StreamConfig);
|
||||
m_StreamConfig.width = prefs.width;
|
||||
m_StreamConfig.height = prefs.height;
|
||||
m_StreamConfig.fps = prefs.fps;
|
||||
m_StreamConfig.bitrate = prefs.bitrateKbps;
|
||||
m_StreamConfig.packetSize = 1024;
|
||||
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
|
||||
for (int i = 0; i < sizeof(m_StreamConfig.remoteInputAesKey); i++) {
|
||||
m_StreamConfig.remoteInputAesKey[i] =
|
||||
(char)(QRandomGenerator::global()->generate() % 256);
|
||||
}
|
||||
*(int*)m_StreamConfig.remoteInputAesIv = qToBigEndian(QRandomGenerator::global()->generate());
|
||||
switch (prefs.audioConfig)
|
||||
{
|
||||
case StreamingPreferences::AC_AUTO:
|
||||
m_StreamConfig.audioConfiguration = sdlDetermineAudioConfiguration();
|
||||
break;
|
||||
case StreamingPreferences::AC_FORCE_STEREO:
|
||||
m_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO;
|
||||
break;
|
||||
case StreamingPreferences::AC_FORCE_SURROUND:
|
||||
m_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_51_SURROUND;
|
||||
break;
|
||||
}
|
||||
switch (prefs.videoCodecConfig)
|
||||
{
|
||||
case StreamingPreferences::VCC_AUTO:
|
||||
// TODO: Determine if HEVC is better depending on the decoder
|
||||
m_StreamConfig.supportsHevc = true;
|
||||
m_StreamConfig.enableHdr = false;
|
||||
break;
|
||||
case StreamingPreferences::VCC_FORCE_H264:
|
||||
m_StreamConfig.supportsHevc = false;
|
||||
m_StreamConfig.enableHdr = false;
|
||||
break;
|
||||
case StreamingPreferences::VCC_FORCE_HEVC:
|
||||
m_StreamConfig.supportsHevc = true;
|
||||
m_StreamConfig.enableHdr = false;
|
||||
break;
|
||||
case StreamingPreferences::VCC_FORCE_HEVC_HDR:
|
||||
m_StreamConfig.supportsHevc = true;
|
||||
m_StreamConfig.enableHdr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QString Session::checkForFatalValidationError()
|
||||
{
|
||||
// Nothing here yet
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QStringList Session::checkForAdvisoryValidationError()
|
||||
{
|
||||
NvHTTP http(m_Computer->activeAddress);
|
||||
QStringList warningList;
|
||||
|
||||
if (m_StreamConfig.enableHdr) {
|
||||
// Turn HDR back off unless all criteria are met.
|
||||
m_StreamConfig.enableHdr = false;
|
||||
|
||||
// Check that the app supports HDR
|
||||
if (!m_App.hdrSupported) {
|
||||
warningList.append(m_App.name + " doesn't support HDR10.");
|
||||
}
|
||||
// Check that the server GPU supports HDR
|
||||
else if (!(m_Computer->serverCodecModeSupport & 0x200)) {
|
||||
warningList.append("Your host PC GPU doesn't support HDR streaming. "
|
||||
"A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.");
|
||||
}
|
||||
else {
|
||||
// TODO: Also validate client decoder and display capabilites
|
||||
|
||||
// Validation successful so HDR is good to go
|
||||
m_StreamConfig.enableHdr = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_StreamConfig.width >= 3840) {
|
||||
if (m_StreamConfig.fps >= 60) {
|
||||
// TODO: Validate 4K60 support based on serverinfo
|
||||
}
|
||||
else {
|
||||
// TODO: Validate 4K30 support based on serverinfo
|
||||
}
|
||||
}
|
||||
|
||||
if (m_StreamConfig.supportsHevc) {
|
||||
// TODO: Validate HEVC support based on decoder caps
|
||||
}
|
||||
|
||||
return warningList;
|
||||
}
|
||||
|
||||
void Session::exec()
|
||||
{
|
||||
// We're now active
|
||||
s_ActiveSession = this;
|
||||
|
||||
// Initialize the gamepad code with our preferences
|
||||
StreamingPreferences prefs;
|
||||
SdlInputHandler inputHandler(prefs.multiController);
|
||||
|
||||
m_ProgressBox.setStandardButtons(QMessageBox::Cancel);
|
||||
m_ProgressBox.setText("Launching "+m_App.name+"...");
|
||||
m_ProgressBox.open();
|
||||
|
||||
// Ensure the progress box is immediately visible
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
NvHTTP http(m_Computer->activeAddress);
|
||||
|
||||
// The UI should have ensured the old game was already quit
|
||||
// if we decide to stream a different game.
|
||||
Q_ASSERT(m_Computer->currentGameId == 0 ||
|
||||
m_Computer->currentGameId == m_App.id);
|
||||
|
||||
if (m_Computer->currentGameId != 0) {
|
||||
http.resumeApp(&m_StreamConfig);
|
||||
}
|
||||
else {
|
||||
http.launchApp(m_App.id, &m_StreamConfig,
|
||||
prefs.enableGameOptimizations,
|
||||
prefs.playAudioOnHost,
|
||||
inputHandler.getAttachedGamepadMask());
|
||||
}
|
||||
|
||||
QByteArray hostnameStr = m_Computer->activeAddress.toLatin1();
|
||||
QByteArray siAppVersion = m_Computer->appVersion.toLatin1();
|
||||
|
||||
SERVER_INFORMATION hostInfo;
|
||||
hostInfo.address = hostnameStr.data();
|
||||
hostInfo.serverInfoAppVersion = siAppVersion.data();
|
||||
|
||||
// Older GFE versions didn't have this field
|
||||
QByteArray siGfeVersion;
|
||||
if (!m_Computer->gfeVersion.isNull()) {
|
||||
siGfeVersion = m_Computer->gfeVersion.toLatin1();
|
||||
}
|
||||
if (!siGfeVersion.isNull()) {
|
||||
hostInfo.serverInfoGfeVersion = siGfeVersion.data();
|
||||
}
|
||||
|
||||
int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks,
|
||||
&k_VideoCallbacks, &k_AudioCallbacks,
|
||||
NULL, 0, NULL, 0);
|
||||
if (err != 0) {
|
||||
// We already displayed an error dialog in the stage failure
|
||||
// listener.
|
||||
return;
|
||||
}
|
||||
|
||||
// Before we get into our SDL loop, close the message box used to
|
||||
// display progress
|
||||
m_ProgressBox.close();
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
//SDL_CreateWindow("SDL", 0, 0, 1280, 720, SDL_WINDOW_INPUT_GRABBED);
|
||||
|
||||
// Hijack this thread to be the SDL main thread. We have to do this
|
||||
// because we want to suspend all Qt processing until the stream is over.
|
||||
SDL_Event event;
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Quit event received");
|
||||
goto Exit;
|
||||
case SDL_KEYUP:
|
||||
case SDL_KEYDOWN:
|
||||
inputHandler.handleKeyEvent(&event.key);
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
inputHandler.handleMouseButtonEvent(&event.button);
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
inputHandler.handleMouseMotionEvent(&event.motion);
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
inputHandler.handleMouseWheelEvent(&event.wheel);
|
||||
break;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
inputHandler.handleControllerAxisEvent(&event.caxis);
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
inputHandler.handleControllerButtonEvent(&event.cbutton);
|
||||
break;
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
inputHandler.handleControllerDeviceEvent(&event.cdevice);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_WaitEvent() failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
Exit:
|
||||
s_ActiveSession = nullptr;
|
||||
LiStopConnection();
|
||||
}
|
69
app/streaming/session.hpp
Normal file
69
app/streaming/session.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <opus_multistream.h>
|
||||
#include "backend/computermanager.h"
|
||||
#include "input.hpp"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
|
||||
class Session
|
||||
{
|
||||
public:
|
||||
explicit Session(NvComputer* computer, NvApp& app);
|
||||
|
||||
QString checkForFatalValidationError();
|
||||
|
||||
QStringList checkForAdvisoryValidationError();
|
||||
|
||||
void exec();
|
||||
|
||||
private:
|
||||
static
|
||||
void clStageStarting(int stage);
|
||||
|
||||
static
|
||||
void clStageFailed(int stage, long errorCode);
|
||||
|
||||
static
|
||||
void clConnectionTerminated(long errorCode);
|
||||
|
||||
static
|
||||
void clLogMessage(const char* format, ...);
|
||||
|
||||
static
|
||||
int sdlDetermineAudioConfiguration();
|
||||
|
||||
static
|
||||
int sdlAudioInit(int audioConfiguration,
|
||||
POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||
void* arContext, int arFlags);
|
||||
|
||||
static
|
||||
void sdlAudioStart();
|
||||
|
||||
static
|
||||
void sdlAudioStop();
|
||||
|
||||
static
|
||||
void sdlAudioCleanup();
|
||||
|
||||
static
|
||||
void sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength);
|
||||
|
||||
STREAM_CONFIGURATION m_StreamConfig;
|
||||
QMessageBox m_ProgressBox;
|
||||
NvComputer* m_Computer;
|
||||
NvApp m_App;
|
||||
|
||||
static SDL_AudioDeviceID s_AudioDevice;
|
||||
static OpusMSDecoder* s_OpusDecoder;
|
||||
static short s_OpusDecodeBuffer[];
|
||||
static int s_ChannelCount;
|
||||
|
||||
static AUDIO_RENDERER_CALLBACKS k_AudioCallbacks;
|
||||
static DECODER_RENDERER_CALLBACKS k_VideoCallbacks;
|
||||
static CONNECTION_LISTENER_CALLBACKS k_ConnCallbacks;
|
||||
static Session* s_ActiveSession;
|
||||
};
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
#include <QMessageBox>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern AUDIO_RENDERER_CALLBACKS k_AudioCallbacks;
|
||||
extern DECODER_RENDERER_CALLBACKS k_VideoCallbacks;
|
||||
|
||||
int SdlDetermineAudioConfiguration(void);
|
||||
|
||||
void SdlInitializeGamepad(bool multiController);
|
||||
int SdlGetAttachedGamepadMask(void);
|
||||
void SdlHandleControllerDeviceEvent(SDL_ControllerDeviceEvent* event);
|
||||
void SdlHandleControllerButtonEvent(SDL_ControllerButtonEvent* event);
|
||||
void SdlHandleControllerAxisEvent(SDL_ControllerAxisEvent* event);
|
||||
void SdlHandleMouseWheelEvent(SDL_MouseWheelEvent* event);
|
||||
void SdlHandleMouseMotionEvent(SDL_MouseMotionEvent* event);
|
||||
void SdlHandleMouseButtonEvent(SDL_MouseButtonEvent* event);
|
||||
void SdlHandleKeyEvent(SDL_KeyboardEvent* event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// This function uses C++ linkage
|
||||
int StartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION streamConfig, QMessageBox* progressBox);
|
@ -1,3 +0,0 @@
|
||||
#include <Limelight.h>
|
||||
|
||||
DECODER_RENDERER_CALLBACKS k_VideoCallbacks;
|
2
app/streaming/video.cpp
Normal file
2
app/streaming/video.cpp
Normal file
@ -0,0 +1,2 @@
|
||||
#include <Limelight.h>
|
||||
#include "session.hpp"
|
Loading…
x
Reference in New Issue
Block a user