mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-06-17 22:23:31 +00:00
Rewrite streaming code to C++ and forklift most of the setup out of the GUI code
This commit is contained in:
+8
-7
@@ -52,18 +52,18 @@ win32 {
|
|||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
streaming/audio.c \
|
|
||||||
streaming/input.c \
|
|
||||||
gui/mainwindow.cpp \
|
gui/mainwindow.cpp \
|
||||||
gui/popupmanager.cpp \
|
gui/popupmanager.cpp \
|
||||||
backend/identitymanager.cpp \
|
backend/identitymanager.cpp \
|
||||||
backend/nvhttp.cpp \
|
backend/nvhttp.cpp \
|
||||||
backend/nvpairingmanager.cpp \
|
backend/nvpairingmanager.cpp \
|
||||||
streaming/video.c \
|
|
||||||
streaming/connection.cpp \
|
|
||||||
backend/computermanager.cpp \
|
backend/computermanager.cpp \
|
||||||
backend/boxartmanager.cpp \
|
backend/boxartmanager.cpp \
|
||||||
settings/streamingpreferences.cpp
|
settings/streamingpreferences.cpp \
|
||||||
|
streaming/input.cpp \
|
||||||
|
streaming/session.cpp \
|
||||||
|
streaming/audio.cpp \
|
||||||
|
streaming/video.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
utils.h \
|
utils.h \
|
||||||
@@ -72,10 +72,11 @@ HEADERS += \
|
|||||||
backend/identitymanager.h \
|
backend/identitymanager.h \
|
||||||
backend/nvhttp.h \
|
backend/nvhttp.h \
|
||||||
backend/nvpairingmanager.h \
|
backend/nvpairingmanager.h \
|
||||||
streaming/streaming.h \
|
|
||||||
backend/computermanager.h \
|
backend/computermanager.h \
|
||||||
backend/boxartmanager.h \
|
backend/boxartmanager.h \
|
||||||
settings/streamingpreferences.h
|
settings/streamingpreferences.h \
|
||||||
|
streaming/input.hpp \
|
||||||
|
streaming/session.hpp
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
gui/mainwindow.ui
|
gui/mainwindow.ui
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ NvComputer::NvComputer(QSettings& settings)
|
|||||||
this->currentGameId = 0;
|
this->currentGameId = 0;
|
||||||
this->pairState = PS_UNKNOWN;
|
this->pairState = PS_UNKNOWN;
|
||||||
this->state = CS_UNKNOWN;
|
this->state = CS_UNKNOWN;
|
||||||
|
this->gfeVersion = nullptr;
|
||||||
|
this->appVersion = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -104,6 +106,8 @@ NvComputer::NvComputer(QString address, QString serverInfo)
|
|||||||
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
|
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
|
||||||
PS_PAIRED : PS_NOT_PAIRED;
|
PS_PAIRED : PS_NOT_PAIRED;
|
||||||
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
|
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
|
||||||
|
this->appVersion = NvHTTP::getXmlString(serverInfo, "appversion");
|
||||||
|
this->gfeVersion = NvHTTP::getXmlString(serverInfo, "GfeVersion");
|
||||||
this->activeAddress = address;
|
this->activeAddress = address;
|
||||||
this->state = NvComputer::CS_ONLINE;
|
this->state = NvComputer::CS_ONLINE;
|
||||||
}
|
}
|
||||||
@@ -142,6 +146,8 @@ bool NvComputer::update(NvComputer& that)
|
|||||||
ASSIGN_IF_CHANGED(currentGameId);
|
ASSIGN_IF_CHANGED(currentGameId);
|
||||||
ASSIGN_IF_CHANGED(activeAddress);
|
ASSIGN_IF_CHANGED(activeAddress);
|
||||||
ASSIGN_IF_CHANGED(state);
|
ASSIGN_IF_CHANGED(state);
|
||||||
|
ASSIGN_IF_CHANGED(gfeVersion);
|
||||||
|
ASSIGN_IF_CHANGED(appVersion);
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public:
|
|||||||
PairState pairState;
|
PairState pairState;
|
||||||
QString activeAddress;
|
QString activeAddress;
|
||||||
int currentGameId;
|
int currentGameId;
|
||||||
|
QString gfeVersion;
|
||||||
|
QString appVersion;
|
||||||
|
|
||||||
// Persisted traits
|
// Persisted traits
|
||||||
QString localAddress;
|
QString localAddress;
|
||||||
|
|||||||
+17
-83
@@ -3,13 +3,11 @@
|
|||||||
#include "gui/popupmanager.h"
|
#include "gui/popupmanager.h"
|
||||||
#include "backend/identitymanager.h"
|
#include "backend/identitymanager.h"
|
||||||
#include "backend/nvpairingmanager.h"
|
#include "backend/nvpairingmanager.h"
|
||||||
#include "streaming/streaming.h"
|
#include "streaming/session.hpp"
|
||||||
#include "backend/computermanager.h"
|
#include "backend/computermanager.h"
|
||||||
#include "backend/boxartmanager.h"
|
#include "backend/boxartmanager.h"
|
||||||
#include "settings/streamingpreferences.h"
|
#include "settings/streamingpreferences.h"
|
||||||
|
|
||||||
#include <QRandomGenerator>
|
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent) :
|
MainWindow::MainWindow(QWidget *parent) :
|
||||||
QMainWindow(parent),
|
QMainWindow(parent),
|
||||||
ui(new Ui::MainWindow),
|
ui(new Ui::MainWindow),
|
||||||
@@ -57,92 +55,28 @@ void MainWindow::computerStateChanged(NvComputer* computer)
|
|||||||
// Stop polling before launching a game
|
// Stop polling before launching a game
|
||||||
m_ComputerManager.stopPollingAsync();
|
m_ComputerManager.stopPollingAsync();
|
||||||
|
|
||||||
#if 1
|
Session session(computer, computer->appList.last());
|
||||||
STREAM_CONFIGURATION streamConfig;
|
QStringList warnings;
|
||||||
StreamingPreferences prefs;
|
QString errorMessage;
|
||||||
|
|
||||||
LiInitializeStreamConfiguration(&streamConfig);
|
// First check for a fatal configuration error
|
||||||
// TODO: Validate 4K and 4K60 based on serverinfo
|
errorMessage = session.checkForFatalValidationError();
|
||||||
streamConfig.width = prefs.width;
|
if (!errorMessage.isEmpty()) {
|
||||||
streamConfig.height = prefs.height;
|
// TODO: display error dialog
|
||||||
streamConfig.fps = prefs.fps;
|
goto AfterStreaming;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Validate HEVC support based on decoder caps
|
// Check for any informational messages to display
|
||||||
// TODO: Validate HDR support based on decoder caps, display, server caps, and app
|
warnings = session.checkForAdvisoryValidationError();
|
||||||
|
if (!warnings.isEmpty()) {
|
||||||
// Initialize the gamepad code with our preferences
|
// TODO: display toast or something before we start
|
||||||
// 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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SERVER_INFORMATION hostInfo;
|
// Run the streaming session until termination
|
||||||
QString serverInfo = http.getServerInfo();
|
session.exec();
|
||||||
|
|
||||||
QByteArray hostnameStr = computer->activeAddress.toLatin1();
|
AfterStreaming:
|
||||||
QByteArray siAppVersion = http.getXmlString(serverInfo, "appversion").toLatin1();
|
m_ComputerManager.startPolling();
|
||||||
QByteArray siGfeVersion = http.getXmlString(serverInfo, "GfeVersion").toLatin1();
|
|
||||||
|
|
||||||
hostInfo.address = hostnameStr.data();
|
|
||||||
hostInfo.serverInfoAppVersion = siAppVersion.data();
|
|
||||||
hostInfo.serverInfoGfeVersion = siGfeVersion.data();
|
|
||||||
|
|
||||||
StartConnection(&hostInfo, &streamConfig, box);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
|
#include "session.hpp"
|
||||||
|
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
#include <opus_multistream.h>
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
|
||||||
#define MAX_CHANNELS 6
|
#define MAX_CHANNELS 6
|
||||||
#define SAMPLES_PER_FRAME 240
|
#define SAMPLES_PER_FRAME 240
|
||||||
|
|
||||||
static SDL_AudioDeviceID g_AudioDevice;
|
SDL_AudioDeviceID Session::s_AudioDevice;
|
||||||
static OpusMSDecoder* g_OpusDecoder;
|
OpusMSDecoder* Session::s_OpusDecoder;
|
||||||
static short g_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
|
short Session::s_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
|
||||||
static OPUS_MULTISTREAM_CONFIGURATION g_OpusConfig;
|
int Session::s_ChannelCount;
|
||||||
|
|
||||||
int SdlDetermineAudioConfiguration(void)
|
int Session::sdlDetermineAudioConfiguration()
|
||||||
{
|
{
|
||||||
SDL_AudioSpec want, have;
|
SDL_AudioSpec want, have;
|
||||||
SDL_AudioDeviceID dev;
|
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;
|
SDL_AudioSpec want, have;
|
||||||
int error;
|
int error;
|
||||||
@@ -59,81 +62,72 @@ int SdlAudioInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusCon
|
|||||||
want.channels = opusConfig->channelCount;
|
want.channels = opusConfig->channelCount;
|
||||||
want.samples = 1024;
|
want.samples = 1024;
|
||||||
|
|
||||||
g_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
s_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||||
if (g_AudioDevice == 0) {
|
if (s_AudioDevice == 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Failed to open audio device: %s",
|
"Failed to open audio device: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_OpusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate,
|
s_OpusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate,
|
||||||
opusConfig->channelCount,
|
opusConfig->channelCount,
|
||||||
opusConfig->streams,
|
opusConfig->streams,
|
||||||
opusConfig->coupledStreams,
|
opusConfig->coupledStreams,
|
||||||
opusConfig->mapping,
|
opusConfig->mapping,
|
||||||
&error);
|
&error);
|
||||||
if (g_OpusDecoder == NULL) {
|
if (s_OpusDecoder == NULL) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Failed to create decoder: %d",
|
"Failed to create decoder: %d",
|
||||||
error);
|
error);
|
||||||
SDL_CloseAudioDevice(g_AudioDevice);
|
SDL_CloseAudioDevice(s_AudioDevice);
|
||||||
g_AudioDevice = 0;
|
s_AudioDevice = 0;
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_memcpy(&g_OpusConfig, opusConfig, sizeof(g_OpusConfig));
|
s_ChannelCount = opusConfig->channelCount;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlAudioStart(void)
|
void Session::sdlAudioStart()
|
||||||
{
|
{
|
||||||
// Unpause the audio device
|
// Unpause the audio device
|
||||||
SDL_PauseAudioDevice(g_AudioDevice, 0);
|
SDL_PauseAudioDevice(s_AudioDevice, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlAudioStop(void)
|
void Session::sdlAudioStop()
|
||||||
{
|
{
|
||||||
// Pause the audio device
|
// Pause the audio device
|
||||||
SDL_PauseAudioDevice(g_AudioDevice, 1);
|
SDL_PauseAudioDevice(s_AudioDevice, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlAudioCleanup(void)
|
void Session::sdlAudioCleanup()
|
||||||
{
|
{
|
||||||
SDL_CloseAudioDevice(g_AudioDevice);
|
SDL_CloseAudioDevice(s_AudioDevice);
|
||||||
g_AudioDevice = 0;
|
s_AudioDevice = 0;
|
||||||
|
|
||||||
opus_multistream_decoder_destroy(g_OpusDecoder);
|
opus_multistream_decoder_destroy(s_OpusDecoder);
|
||||||
g_OpusDecoder = NULL;
|
s_OpusDecoder = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength)
|
void Session::sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||||
{
|
{
|
||||||
int samplesDecoded;
|
int samplesDecoded;
|
||||||
|
|
||||||
samplesDecoded = opus_multistream_decode(g_OpusDecoder,
|
samplesDecoded = opus_multistream_decode(s_OpusDecoder,
|
||||||
(unsigned char*)sampleData,
|
(unsigned char*)sampleData,
|
||||||
sampleLength,
|
sampleLength,
|
||||||
g_OpusDecodeBuffer,
|
s_OpusDecodeBuffer,
|
||||||
SAMPLES_PER_FRAME,
|
SAMPLES_PER_FRAME,
|
||||||
0);
|
0);
|
||||||
if (samplesDecoded > 0) {
|
if (samplesDecoded > 0) {
|
||||||
if (SDL_QueueAudio(g_AudioDevice,
|
if (SDL_QueueAudio(s_AudioDevice,
|
||||||
g_OpusDecodeBuffer,
|
s_OpusDecodeBuffer,
|
||||||
sizeof(short) * samplesDecoded * g_OpusConfig.channelCount) < 0) {
|
sizeof(short) * samplesDecoded * s_ChannelCount) < 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Failed to queue audio sample: %s",
|
"Failed to queue audio sample: %s",
|
||||||
SDL_GetError());
|
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 <Limelight.h>
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <stdbool.h>
|
#include "input.hpp"
|
||||||
|
|
||||||
#define VK_0 0x30
|
#define VK_0 0x30
|
||||||
#define VK_A 0x41
|
#define VK_A 0x41
|
||||||
@@ -12,23 +12,7 @@
|
|||||||
#define VK_NUMPAD1 0x61
|
#define VK_NUMPAD1 0x61
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct _GAMEPAD_STATE {
|
const int SdlInputHandler::k_ButtonMap[] = {
|
||||||
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[] = {
|
|
||||||
A_FLAG, B_FLAG, X_FLAG, Y_FLAG,
|
A_FLAG, B_FLAG, X_FLAG, Y_FLAG,
|
||||||
BACK_FLAG, SPECIAL_FLAG, PLAY_FLAG,
|
BACK_FLAG, SPECIAL_FLAG, PLAY_FLAG,
|
||||||
LS_CLK_FLAG, RS_CLK_FLAG,
|
LS_CLK_FLAG, RS_CLK_FLAG,
|
||||||
@@ -36,7 +20,16 @@ static const short k_ButtonMap[] = {
|
|||||||
UP_FLAG, DOWN_FLAG, LEFT_FLAG, RIGHT_FLAG
|
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;
|
short keyCode;
|
||||||
char modifiers;
|
char modifiers;
|
||||||
@@ -257,7 +250,7 @@ void SdlHandleKeyEvent(SDL_KeyboardEvent* event)
|
|||||||
modifiers);
|
modifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlHandleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
||||||
{
|
{
|
||||||
int button;
|
int button;
|
||||||
|
|
||||||
@@ -289,7 +282,7 @@ void SdlHandleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
|||||||
button);
|
button);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlHandleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||||
{
|
{
|
||||||
if (event->xrel != 0 || event->yrel != 0) {
|
if (event->xrel != 0 || event->yrel != 0) {
|
||||||
LiSendMouseMoveEvent((unsigned short)event->xrel,
|
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) {
|
if (event->y != 0) {
|
||||||
LiSendScrollEvent((signed char)event->y);
|
LiSendScrollEvent((signed char)event->y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static PGAMEPAD_STATE FindStateForGamepad(SDL_JoystickID id)
|
GamepadState*
|
||||||
|
SdlInputHandler::findStateForGamepad(SDL_JoystickID id)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < MAX_GAMEPADS; i++) {
|
for (i = 0; i < MAX_GAMEPADS; i++) {
|
||||||
if (g_GamepadState[i].jsId == id) {
|
if (m_GamepadState[i].jsId == id) {
|
||||||
SDL_assert(!g_MultiController || g_GamepadState[i].index == i);
|
SDL_assert(!m_MultiController || m_GamepadState[i].index == i);
|
||||||
return &g_GamepadState[i];
|
return &m_GamepadState[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should only happen with > 4 gamepads
|
// This should only happen with > 4 gamepads
|
||||||
SDL_assert(SDL_NumJoysticks() > 4);
|
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,
|
LiSendMultiControllerEvent(state->index,
|
||||||
g_GamepadMask,
|
m_GamepadMask,
|
||||||
state->buttons,
|
state->buttons,
|
||||||
state->lt,
|
state->lt,
|
||||||
state->rt,
|
state->rt,
|
||||||
@@ -334,9 +328,9 @@ static void SendGamepadState(PGAMEPAD_STATE state)
|
|||||||
state->rsY);
|
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) {
|
if (state == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -368,12 +362,12 @@ void SdlHandleControllerAxisEvent(SDL_ControllerAxisEvent* event)
|
|||||||
return;
|
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) {
|
if (state == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -385,19 +379,19 @@ void SdlHandleControllerButtonEvent(SDL_ControllerButtonEvent* event)
|
|||||||
state->buttons &= ~k_ButtonMap[event->button];
|
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) {
|
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
||||||
int i;
|
int i;
|
||||||
const char* name;
|
const char* name;
|
||||||
|
|
||||||
for (i = 0; i < MAX_GAMEPADS; i++) {
|
for (i = 0; i < MAX_GAMEPADS; i++) {
|
||||||
if (g_GamepadState[i].controller == NULL) {
|
if (m_GamepadState[i].controller == NULL) {
|
||||||
// Found an empty slot
|
// Found an empty slot
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -409,8 +403,8 @@ void SdlHandleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = &g_GamepadState[i];
|
state = &m_GamepadState[i];
|
||||||
if (g_MultiController) {
|
if (m_MultiController) {
|
||||||
state->index = i;
|
state->index = i;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -435,29 +429,29 @@ void SdlHandleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
|
|||||||
name != NULL ? name : "<null>");
|
name != NULL ? name : "<null>");
|
||||||
|
|
||||||
// Add this gamepad to the gamepad mask
|
// Add this gamepad to the gamepad mask
|
||||||
if (g_MultiController) {
|
if (m_MultiController) {
|
||||||
SDL_assert(!(g_GamepadMask & (1 << state->index)));
|
SDL_assert(!(m_GamepadMask & (1 << state->index)));
|
||||||
g_GamepadMask |= (1 << state->index);
|
m_GamepadMask |= (1 << state->index);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SDL_assert(g_GamepadMask == 0x1);
|
SDL_assert(m_GamepadMask == 0x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send an empty event to tell the PC we've arrived
|
// Send an empty event to tell the PC we've arrived
|
||||||
SendGamepadState(state);
|
sendGamepadState(state);
|
||||||
}
|
}
|
||||||
else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||||
state = FindStateForGamepad(event->which);
|
state = findStateForGamepad(event->which);
|
||||||
if (state != NULL) {
|
if (state != NULL) {
|
||||||
SDL_GameControllerClose(state->controller);
|
SDL_GameControllerClose(state->controller);
|
||||||
|
|
||||||
// Remove this from the gamepad mask in MC-mode
|
// Remove this from the gamepad mask in MC-mode
|
||||||
if (g_MultiController) {
|
if (m_MultiController) {
|
||||||
SDL_assert(g_GamepadMask & (1 << state->index));
|
SDL_assert(m_GamepadMask & (1 << state->index));
|
||||||
g_GamepadMask &= ~(1 << state->index);
|
m_GamepadMask &= ~(1 << state->index);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SDL_assert(g_GamepadMask == 0x1);
|
SDL_assert(m_GamepadMask == 0x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -465,7 +459,7 @@ void SdlHandleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
|
|||||||
state->index);
|
state->index);
|
||||||
|
|
||||||
// Send a final event to let the PC know this gamepad is gone
|
// 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);
|
0, 0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
// Clear all remaining state from this slot
|
// 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 i;
|
||||||
int count;
|
int count;
|
||||||
int mask;
|
int mask;
|
||||||
|
|
||||||
if (!g_MultiController) {
|
if (!m_MultiController) {
|
||||||
// Player 1 is always present in non-MC mode
|
// Player 1 is always present in non-MC mode
|
||||||
return 0x1;
|
return 0x1;
|
||||||
}
|
}
|
||||||
@@ -494,12 +488,3 @@ int SdlGetAttachedGamepadMask(void)
|
|||||||
|
|
||||||
return mask;
|
return mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlInitializeGamepad(bool multiController)
|
|
||||||
{
|
|
||||||
g_MultiController = multiController;
|
|
||||||
if (!g_MultiController) {
|
|
||||||
// Player 1 is always present in non-MC mode
|
|
||||||
g_GamepadMask = 0x01;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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[];
|
||||||
|
};
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include <Limelight.h>
|
||||||
|
#include "session.hpp"
|
||||||
Reference in New Issue
Block a user