mirror of
https://github.com/Maschell/StreamingPluginWiiU.git
synced 2025-02-17 13:36:22 +01:00
- Use the new Java Client instead of MJPEG via HTTP. See https://github.com/Maschell/StreamingPluginClient for more information.
- Add an option to choose which screen to stream.
This commit is contained in:
parent
993504ed15
commit
5c1733f55c
21
README.md
21
README.md
@ -2,8 +2,8 @@
|
||||
|
||||
## Still an early PROOF OF CONCEPT. DON'T EXPECT MAGIC.
|
||||
|
||||
This is just a simple plugin that allows you to stream the content of the DRC to any browser.
|
||||
Currently no configuration without recompiling is supported. It streams in a resolution of 428x240 and tries to achieve 20 fps. These numbers might improve in the future.
|
||||
This is just a simple plugin that allows you to stream the content of the Gamepad or TV screen to your Computer. With default settings streams in a resolution of 428x240 with selft adjusting quality and tries to achieve as much fps as possible.
|
||||
It's possible to adjust the resolution via the config menu (Press **L, DPAD DOWN and MINUS** on your Wii U Gamepad whenever using the home menu is allowed).
|
||||
|
||||
But general notes:
|
||||
- This is still an early PoC.
|
||||
@ -13,14 +13,18 @@ But general notes:
|
||||
- No streaming of the home menu.
|
||||
- Probably unstable.
|
||||
- Some games might be too dark, some might be too bright, some doesn't work at all.
|
||||
- Currently streaming is achieved via "MJPEG via HTTP", this might change in the future to improve performance.
|
||||
- Currently streaming is achieved via a custom Java client.
|
||||
|
||||
## Configuration
|
||||
While the plugin is running, is possible to configure certain parameters. To open the config menu press **L, DPAD DOWN and MINUS** on your Wii U Gamepad. For more information check the [Wii U Plugin System](https://github.com/Maschell/WiiUPluginSystem).
|
||||
Currently the following options are available:
|
||||
- Change the resolution, possible options: 240p, 360p and 480p
|
||||
- Choose the screen to stream, possible options: Gamepad, TV.
|
||||
|
||||
|
||||
# Usage
|
||||
Simply load the plugin with the plugin loader. When the system menu is loaded, you can open `http://<ip of your ip>:8080` on your browser and should see the stream. Whenever you switch the application (e.g. load a game), you need to refresh the site in your browser.
|
||||
Example when the IP of your Wii U is 192.168.0.44.
|
||||
```
|
||||
http:/192.168.0.44:8080
|
||||
```
|
||||
Simply load the plugin with the plugin loader, after that start the [StreamingPluginClient](https://github.com/Maschell/StreamingPluginClient). The StreamingPluginClient requires a computer with Java 8. Double click on the `.jar` and enter the IP address of your Wii U console.
|
||||
|
||||
If you don't know the IP of your Wii U, you can start for example [ftpiiu](https://github.com/dimok789/ftpiiu) which shows the IP when running.
|
||||
|
||||
## Wii U Plugin System
|
||||
@ -29,6 +33,7 @@ This is a plugin for the [Wii U Plugin System (WUPS)](https://github.com/Maschel
|
||||
```
|
||||
sd:/wiiu/plugins
|
||||
```
|
||||
|
||||
When the file is placed on the SDCard you can load it with [plugin loader](https://github.com/Maschell/WiiUPluginSystem/).
|
||||
|
||||
## Building
|
||||
|
@ -17,20 +17,22 @@
|
||||
|
||||
#include <vector>
|
||||
#include "EncodingHelper.h"
|
||||
#include "MJPEGStreamServer.hpp"
|
||||
#include "MJPEGStreamServerUDP.hpp"
|
||||
#include "stream_utils.h"
|
||||
#include "JpegInformation.h"
|
||||
#include <gx2/event.h>
|
||||
#include <gx2/surface.h>
|
||||
#include <gx2/mem.h>
|
||||
|
||||
#include "retain_vars.hpp"
|
||||
|
||||
EncodingHelper *EncodingHelper::instance = NULL;
|
||||
|
||||
OSMessageQueue encodeQueue __attribute__((section(".data")));
|
||||
OSMessage encodeQueueMessages[ENCODE_QUEUE_MESSAGE_COUNT] __attribute__((section(".data")));
|
||||
|
||||
void EncodingHelper::StartAsyncThread() {
|
||||
int32_t priority = 31;
|
||||
int32_t priority = gEncodePriority;
|
||||
this->pThread = CThread::create(DoAsyncThread, this, CThread::eAttributeAffCore0 |CThread::eAttributeAffCore2 , priority,0x40000);
|
||||
this->pThread->resumeThread();
|
||||
}
|
||||
@ -115,7 +117,9 @@ void EncodingHelper::DoAsyncThreadInternal(CThread *thread) {
|
||||
JpegInformation * info = convertToJpeg((uint8_t*) colorBuffer->surface.image,colorBuffer->surface.width,colorBuffer->surface.height,colorBuffer->surface.pitch,colorBuffer->surface.format, curQuality);
|
||||
|
||||
if(info != NULL ) {
|
||||
MJPEGStreamServer::getInstance()->streamJPEG(info);
|
||||
if(mjpegServer == NULL || !mjpegServer->streamJPEG(info)){
|
||||
delete info;
|
||||
}
|
||||
}
|
||||
|
||||
//DEBUG_FUNCTION_LINE("We can now kill the colorBuffer\n",colorBuffer);
|
||||
|
@ -22,10 +22,10 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include <system/CThread.h>
|
||||
#include <coreinit/cache.h>
|
||||
#include <coreinit/messagequeue.h>
|
||||
#include <utils/logger.h>
|
||||
#include <MJPEGStreamServer.hpp>
|
||||
|
||||
#include "MJPEGStreamServerUDP.hpp"
|
||||
|
||||
#define ENCODE_QUEUE_MESSAGE_COUNT 1
|
||||
|
||||
@ -53,7 +53,6 @@ public:
|
||||
delete instance;
|
||||
instance = NULL;
|
||||
}
|
||||
MJPEGStreamServer::destroyInstance();
|
||||
}
|
||||
|
||||
static bool addFSQueueMSG(OSMessage message) {
|
||||
@ -72,6 +71,16 @@ public:
|
||||
DCFlushRange((void*) &shouldExit,sizeof(shouldExit));
|
||||
}
|
||||
|
||||
void setMJPEGStreamServer(MJPEGStreamServer * server){
|
||||
this->mjpegServer = server;
|
||||
}
|
||||
|
||||
void setThreadPriority(int32_t priority){
|
||||
if(pThread != NULL){
|
||||
pThread->setThreadPriority(priority);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EncodingHelper() {
|
||||
OSInitMessageQueue(&encodeQueue, encodeQueueMessages, ENCODE_QUEUE_MESSAGE_COUNT);
|
||||
@ -85,6 +94,8 @@ private:
|
||||
|
||||
CThread *pThread;
|
||||
|
||||
MJPEGStreamServer * mjpegServer = NULL;
|
||||
|
||||
volatile bool serverRunning = false;
|
||||
|
||||
volatile bool shouldExit = false;
|
||||
|
96
src/HeartBeatServer.cpp
Normal file
96
src/HeartBeatServer.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
/****************************************************************************
|
||||
* Copyright (C) 2018 Maschell
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
****************************************************************************/
|
||||
#include "HeartBeatServer.hpp"
|
||||
#include "MJPEGStreamServerUDP.hpp"
|
||||
#include "EncodingHelper.h"
|
||||
#include "turbojpeg.h"
|
||||
#include <malloc.h>
|
||||
#include <network/net.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <utils/logger.h>
|
||||
#include <utils/StringTools.h>
|
||||
#include <coreinit/thread.h>
|
||||
#include <coreinit/time.h>
|
||||
|
||||
|
||||
HeartBeatServer * HeartBeatServer::instance = NULL;
|
||||
|
||||
HeartBeatServer::HeartBeatServer(int32_t port): TCPServer(port,HeartBeatServer::getPriority()) {
|
||||
|
||||
}
|
||||
|
||||
HeartBeatServer::~HeartBeatServer() {
|
||||
if(mjpegStreamServer != NULL) {
|
||||
delete mjpegStreamServer;
|
||||
mjpegStreamServer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL HeartBeatServer::whileLoop() {
|
||||
int32_t ret;
|
||||
int32_t clientfd = getClientFD();
|
||||
|
||||
while (1) {
|
||||
if(shouldExit()) {
|
||||
break;
|
||||
}
|
||||
ret = checkbyte(clientfd);
|
||||
if (ret < 0) {
|
||||
if(socketlasterr() != 6) {
|
||||
return false;
|
||||
}
|
||||
OSSleepTicks(OSMillisecondsToTicks(1000));
|
||||
continue;
|
||||
}
|
||||
if(ret == 0x15) {
|
||||
sendbyte(clientfd,0x16);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOL HeartBeatServer::acceptConnection() {
|
||||
int32_t clientfd = getClientFD();
|
||||
struct sockaddr_in sockaddr = getSockAddr();
|
||||
|
||||
int32_t connectedIP = sockaddr.sin_addr.s_addr;
|
||||
|
||||
EncodingHelper::getInstance()->setMJPEGStreamServer(NULL);
|
||||
|
||||
if(mjpegStreamServer != NULL) {
|
||||
delete mjpegStreamServer;
|
||||
mjpegStreamServer = NULL;
|
||||
}
|
||||
|
||||
mjpegStreamServer = MJPEGStreamServerUDP::createInstance(connectedIP, DEFAULT_UDP_CLIENT_PORT);
|
||||
EncodingHelper::getInstance()->setMJPEGStreamServer(mjpegStreamServer);
|
||||
|
||||
DEBUG_FUNCTION_LINE("Handshake done! Success!\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void HeartBeatServer::onConnectionClosed() {
|
||||
DEBUG_FUNCTION_LINE("disconnected\n");
|
||||
EncodingHelper::getInstance()->setMJPEGStreamServer(NULL);
|
||||
if(mjpegStreamServer != NULL) {
|
||||
delete mjpegStreamServer;
|
||||
mjpegStreamServer = NULL;
|
||||
}
|
||||
}
|
76
src/HeartBeatServer.hpp
Normal file
76
src/HeartBeatServer.hpp
Normal file
@ -0,0 +1,76 @@
|
||||
/****************************************************************************
|
||||
* Copyright (C) 2018 Maschell
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
****************************************************************************/
|
||||
#ifndef _HEARTBEAT_SERVER_H_
|
||||
#define _HEARTBEAT_SERVER_H_
|
||||
|
||||
#include <utils/TCPServer.hpp>
|
||||
#include "MJPEGStreamServerUDP.hpp"
|
||||
|
||||
#define DEFAULT_TCP_PORT 8092
|
||||
|
||||
class HeartBeatServer: TCPServer {
|
||||
|
||||
public:
|
||||
static HeartBeatServer *getInstance() {
|
||||
if(!instance) {
|
||||
instance = new HeartBeatServer(DEFAULT_TCP_PORT);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void destroyInstance() {
|
||||
if(instance) {
|
||||
delete instance;
|
||||
instance = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t getPriority() {
|
||||
return 31;
|
||||
}
|
||||
|
||||
static volatile bool isInstanceConnected() {
|
||||
if(instance) {
|
||||
return instance->isConnected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
volatile void setMJPEGServerThreadPriority(int32_t priority) {
|
||||
if(mjpegStreamServer != NULL) {
|
||||
mjpegStreamServer->setThreadPriority(priority);
|
||||
}
|
||||
}
|
||||
|
||||
HeartBeatServer(int32_t port);
|
||||
virtual ~HeartBeatServer();
|
||||
|
||||
MJPEGStreamServer * getMJPEGServer(){
|
||||
return this->mjpegStreamServer;
|
||||
}
|
||||
|
||||
virtual BOOL whileLoop();
|
||||
|
||||
virtual BOOL acceptConnection();
|
||||
|
||||
virtual void onConnectionClosed();
|
||||
|
||||
static HeartBeatServer * instance;
|
||||
MJPEGStreamServerUDP * mjpegStreamServer = NULL;
|
||||
};
|
||||
|
||||
#endif //_MJPEG_STREAM_SERVER_H_
|
@ -1,128 +0,0 @@
|
||||
/****************************************************************************
|
||||
* Copyright (C) 2018 Maschell
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
****************************************************************************/
|
||||
#include "MJPEGStreamServer.hpp"
|
||||
#include "turbojpeg.h"
|
||||
#include <malloc.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <utils/logger.h>
|
||||
#include <utils/StringTools.h>
|
||||
#include <coreinit/thread.h>
|
||||
#include <coreinit/time.h>
|
||||
|
||||
OSMessageQueue streamSendQueue __attribute__((section(".data")));
|
||||
OSMessage streamSendQueueMessages[STREAM_SEND_QUEUE_MESSAGE_COUNT] __attribute__((section(".data")));
|
||||
|
||||
MJPEGStreamServer * MJPEGStreamServer::instance = NULL;
|
||||
|
||||
MJPEGStreamServer::MJPEGStreamServer(int32_t port): TCPServer(port,MJPEGStreamServer::getPriority()) {
|
||||
OSInitMessageQueue(&streamSendQueue, streamSendQueueMessages, STREAM_SEND_QUEUE_MESSAGE_COUNT);
|
||||
}
|
||||
|
||||
MJPEGStreamServer::~MJPEGStreamServer() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
The sendwait from <network/net.h> is reaaally slow.
|
||||
**/
|
||||
int32_t mysendwait(int32_t sock, const void *buffer, int32_t len) {
|
||||
int32_t ret;
|
||||
while (len > 0) {
|
||||
ret = send(sock, buffer, len, 0);
|
||||
if(ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
len -= ret;
|
||||
buffer = (void *)(((char *) buffer) + ret);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MJPEGStreamServer::sendJPEG(uint8_t * buffer, uint64_t size) {
|
||||
int32_t clientfd = getClientFD();
|
||||
|
||||
char str[90];
|
||||
snprintf(str, 90, "\r\n--boundary\r\nContent-Type: image/jpeg\r\nContent-Length: %llu \r\n\r\n", size);
|
||||
|
||||
|
||||
mysendwait(clientfd, str, strlen(str));
|
||||
mysendwait(clientfd, buffer, size);
|
||||
|
||||
//DEBUG_FUNCTION_LINE("Send frame\n");
|
||||
}
|
||||
|
||||
|
||||
BOOL MJPEGStreamServer::whileLoop() {
|
||||
int32_t ret;
|
||||
int32_t clientfd = getClientFD();
|
||||
|
||||
OSMessage message;
|
||||
|
||||
while (1) {
|
||||
ret = checkbyte(clientfd);
|
||||
if (ret < 0) {
|
||||
if(socketlasterr() != 6) {
|
||||
// Ending Server on error.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//DEBUG_FUNCTION_LINE("Waiting\n",message.message,message.data1);
|
||||
while(!OSReceiveMessage(&streamSendQueue,&message,OS_MESSAGE_FLAGS_NONE)) {
|
||||
if(shouldExit()) {
|
||||
break;
|
||||
}
|
||||
OSSleepTicks(OSMicrosecondsToTicks(500));
|
||||
}
|
||||
|
||||
if((uint32_t) message.message == 0xDEADBEEF) {
|
||||
DEBUG_FUNCTION_LINE("We should exit\n");
|
||||
break;
|
||||
}
|
||||
|
||||
DCFlushRange(&message,sizeof(OSMessage));
|
||||
|
||||
JpegInformation * info = (JpegInformation *) message.args[0];
|
||||
if(info != NULL) {
|
||||
DCFlushRange(info,sizeof(JpegInformation));
|
||||
sendJPEG(info->getBuffer(),info->getSize());
|
||||
delete info;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const char * headerHTTP = "HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=--boundary\r\n";
|
||||
|
||||
BOOL MJPEGStreamServer::acceptConnection() {
|
||||
int32_t clientfd = getClientFD();
|
||||
DEBUG_FUNCTION_LINE("TCP Connection accepted! \n");
|
||||
|
||||
mysendwait(clientfd, headerHTTP, strlen(headerHTTP));
|
||||
|
||||
// Consume the first response of the browser.
|
||||
while(checkbyte(clientfd) > 0);
|
||||
|
||||
DEBUG_FUNCTION_LINE("Handshake done! Success!\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
void MJPEGStreamServer::onConnectionClosed() {
|
||||
DEBUG_FUNCTION_LINE("disconnected\n");
|
||||
}
|
@ -14,93 +14,19 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
****************************************************************************/
|
||||
#ifndef _MJPEG_STREAM_SERVER_H_
|
||||
#define _MJPEG_STREAM_SERVER_H_
|
||||
#ifndef _MJPEG_STREAM_SERVER_WINDOW_H_
|
||||
#define _MJPEG_STREAM_SERVER_WINDOW_H_
|
||||
|
||||
#include <utils/TCPServer.hpp>
|
||||
#include <network/net.h>
|
||||
#include <coreinit/messagequeue.h>
|
||||
#include "turbojpeg.h"
|
||||
#include "JpegInformation.h"
|
||||
|
||||
#define STREAM_SEND_QUEUE_MESSAGE_COUNT 1
|
||||
|
||||
extern OSMessageQueue streamSendQueue;
|
||||
extern OSMessage streamSendQueueMessages[STREAM_SEND_QUEUE_MESSAGE_COUNT];
|
||||
|
||||
extern uint32_t frame_counter_skipped;
|
||||
|
||||
class MJPEGStreamServer: TCPServer {
|
||||
|
||||
class MJPEGStreamServer {
|
||||
public:
|
||||
static MJPEGStreamServer *getInstance() {
|
||||
if(!instance) {
|
||||
instance = new MJPEGStreamServer(8080);
|
||||
}
|
||||
return instance;
|
||||
MJPEGStreamServer(){
|
||||
}
|
||||
virtual ~MJPEGStreamServer(){
|
||||
}
|
||||
|
||||
static void destroyInstance() {
|
||||
if(instance) {
|
||||
instance->StopAsyncThread();
|
||||
while(instance->isConnected()) {
|
||||
OSSleepTicks(OSMicrosecondsToTicks(1000));
|
||||
}
|
||||
OSSleepTicks(OSMillisecondsToTicks(500));
|
||||
delete instance;
|
||||
instance = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void StopAsyncThread() {
|
||||
DEBUG_FUNCTION_LINE("StopAsyncThread\n");
|
||||
OSMessage message;
|
||||
message.message = (void *)0xDEADBEEF;
|
||||
OSSendMessage(&streamSendQueue,&message,OS_MESSAGE_FLAGS_BLOCKING);
|
||||
}
|
||||
|
||||
|
||||
static int32_t getPriority() {
|
||||
return 31;
|
||||
}
|
||||
|
||||
static volatile bool isInstanceConnected() {
|
||||
if(instance) {
|
||||
return instance->isConnected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MJPEGStreamServer(int32_t port);
|
||||
|
||||
void sendJPEG(uint8_t * buffer, uint64_t size);
|
||||
|
||||
virtual bool streamJPEG(JpegInformation * info) {
|
||||
if(this->isConnected()) {
|
||||
OSMessage message;
|
||||
message.message = (void *) 0x11111;
|
||||
message.args[0] = (uint32_t) info;
|
||||
if(!OSSendMessage(&streamSendQueue,&message,OS_MESSAGE_FLAGS_NONE)) {
|
||||
frame_counter_skipped++;
|
||||
//DEBUG_FUNCTION_LINE("Dropping frame\n");
|
||||
delete info;
|
||||
return false;
|
||||
};
|
||||
} else {
|
||||
delete info;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual ~MJPEGStreamServer();
|
||||
|
||||
virtual BOOL whileLoop();
|
||||
|
||||
virtual BOOL acceptConnection();
|
||||
|
||||
virtual void onConnectionClosed();
|
||||
|
||||
static MJPEGStreamServer * instance;
|
||||
virtual bool streamJPEG(JpegInformation * info) = 0;
|
||||
};
|
||||
|
||||
#endif //_MJPEG_STREAM_SERVER_H_
|
||||
#endif //_MJPEG_STREAM_SERVER_WINDOW_H_
|
||||
|
164
src/MJPEGStreamServerUDP.cpp
Normal file
164
src/MJPEGStreamServerUDP.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
/****************************************************************************
|
||||
* Copyright (C) 2016,2017 Maschell
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
****************************************************************************/
|
||||
#include "MJPEGStreamServerUDP.hpp"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "retain_vars.hpp"
|
||||
#include <coreinit/thread.h>
|
||||
#include <utils/logger.h>
|
||||
#include <coreinit/cache.h>
|
||||
#include <nsysnet/socket.h>
|
||||
#include "crc32.h"
|
||||
|
||||
#define MAX_UDP_SIZE 0x578
|
||||
|
||||
extern int frame_counter_skipped;
|
||||
|
||||
MJPEGStreamServerUDP::MJPEGStreamServerUDP(uint32_t ip, int32_t port) {
|
||||
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sockfd < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_in connect_addr;
|
||||
memset(&connect_addr, 0, sizeof(struct sockaddr_in));
|
||||
connect_addr.sin_family = AF_INET;
|
||||
connect_addr.sin_port = port;
|
||||
connect_addr.sin_addr.s_addr = ip;
|
||||
|
||||
if(connect(sockfd, (struct sockaddr*)&connect_addr, sizeof(connect_addr)) < 0) {
|
||||
socketclose(sockfd);
|
||||
sockfd = -1;
|
||||
}
|
||||
|
||||
crc32_init(&crc32Buffer);
|
||||
|
||||
OSInitMessageQueue(&dataQueue, dataQueueMessages, DATA_SEND_QUEUE_MESSAGE_COUNT);
|
||||
|
||||
//StartAsyncThread();
|
||||
}
|
||||
|
||||
void MJPEGStreamServerUDP::StartAsyncThread() {
|
||||
int32_t priority = 31;
|
||||
this->pThread = CThread::create(DoAsyncThread, this, CThread::eAttributeAffCore0 |CThread::eAttributeAffCore2, priority,0x80000);
|
||||
this->pThread->resumeThread();
|
||||
}
|
||||
|
||||
void MJPEGStreamServerUDP::DoAsyncThread(CThread *thread, void *arg) {
|
||||
MJPEGStreamServerUDP * arg_instance = (MJPEGStreamServerUDP *) arg;
|
||||
return arg_instance->DoAsyncThreadInternal(thread);
|
||||
}
|
||||
|
||||
MJPEGStreamServerUDP::~MJPEGStreamServerUDP() {
|
||||
StopAsyncThread();
|
||||
|
||||
OSSleepTicks(OSMillisecondsToTicks(100));
|
||||
|
||||
if(pThread != NULL) {
|
||||
delete pThread;
|
||||
pThread = NULL;
|
||||
}
|
||||
|
||||
if (this->sockfd != -1) {
|
||||
socketclose(sockfd);
|
||||
}
|
||||
DEBUG_FUNCTION_LINE("Thread has been closed\n");
|
||||
}
|
||||
|
||||
void MJPEGStreamServerUDP::DoAsyncThreadInternal(CThread *thread) {
|
||||
OSMessage message;
|
||||
bool breakOut = false;
|
||||
while (1) {
|
||||
if(breakOut) {
|
||||
break;
|
||||
}
|
||||
while(!OSReceiveMessage(&dataQueue,&message,OS_MESSAGE_FLAGS_NONE)) {
|
||||
if(shouldExit) {
|
||||
breakOut = true;
|
||||
break;
|
||||
}
|
||||
OSSleepTicks(OSMicrosecondsToTicks(500));
|
||||
}
|
||||
|
||||
if(breakOut) {
|
||||
break;
|
||||
}
|
||||
|
||||
DCFlushRange(&message,sizeof(OSMessage));
|
||||
|
||||
JpegInformation * info = (JpegInformation *) message.args[0];
|
||||
if(info != NULL) {
|
||||
//DEBUG_FUNCTION_LINE("GOT FRAME INFO! %08X\n",info);
|
||||
DCFlushRange(info,sizeof(JpegInformation));
|
||||
sendJPEG(info->getBuffer(),info->getSize());
|
||||
delete info;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool MJPEGStreamServerUDP::streamJPEGThreaded(JpegInformation * info) {
|
||||
OSMessage message;
|
||||
message.message = (void *) 0x11111;
|
||||
message.args[0] = (uint32_t) info;
|
||||
if(!OSSendMessage(&dataQueue,&message,OS_MESSAGE_FLAGS_NONE)) {
|
||||
frame_counter_skipped++;
|
||||
//DEBUG_FUNCTION_LINE("Dropping frame\n");
|
||||
delete info;
|
||||
return false;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MJPEGStreamServerUDP::streamJPEG(JpegInformation * info) {
|
||||
if(info != NULL) {
|
||||
//return streamJPEGThreaded(info);
|
||||
|
||||
DCFlushRange(info,sizeof(JpegInformation));
|
||||
sendJPEG(info->getBuffer(),info->getSize());
|
||||
delete info;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MJPEGStreamServerUDP::sendJPEG(uint8_t * buffer, uint64_t size) {
|
||||
uint32_t crcValue = crc32_crc(&crc32Buffer,buffer, size);
|
||||
|
||||
sendData((uint8_t*)&crcValue, sizeof(crcValue));
|
||||
sendData((uint8_t*)&size, sizeof(size));
|
||||
sendData((uint8_t*)buffer, size);
|
||||
}
|
||||
|
||||
bool MJPEGStreamServerUDP::sendData(uint8_t * data,int32_t length) {
|
||||
int len = length;
|
||||
int ret = -1;
|
||||
while (len > 0) {
|
||||
int block = len < MAX_UDP_SIZE ? len : MAX_UDP_SIZE; // take max 508 bytes per UDP packet
|
||||
ret = send(sockfd, data, block, 0);
|
||||
|
||||
if(ret < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
len -= ret;
|
||||
data += ret;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
86
src/MJPEGStreamServerUDP.hpp
Normal file
86
src/MJPEGStreamServerUDP.hpp
Normal file
@ -0,0 +1,86 @@
|
||||
/****************************************************************************
|
||||
* Copyright (C) 2016,2017 Maschell
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
****************************************************************************/
|
||||
#ifndef _UDPCLIENT_WINDOW_H_
|
||||
#define _UDPCLIENT_WINDOW_H_
|
||||
|
||||
#define DEFAULT_UDP_CLIENT_PORT 9445
|
||||
|
||||
#include <coreinit/messagequeue.h>
|
||||
#include <coreinit/cache.h>
|
||||
#include <utils/logger.h>
|
||||
#include <system/CThread.h>
|
||||
#include <nsysnet/socket.h>
|
||||
#include "crc32.h"
|
||||
#include "JpegInformation.h"
|
||||
#include "MJPEGStreamServer.hpp"
|
||||
|
||||
#define DATA_SEND_QUEUE_MESSAGE_COUNT 1
|
||||
|
||||
class MJPEGStreamServerUDP : public MJPEGStreamServer {
|
||||
public:
|
||||
~MJPEGStreamServerUDP();
|
||||
|
||||
static MJPEGStreamServerUDP *createInstance(int32_t ip, int32_t port) {
|
||||
return new MJPEGStreamServerUDP(ip, port);
|
||||
}
|
||||
|
||||
void StartAsyncThread();
|
||||
|
||||
static void DoAsyncThread(CThread *thread, void *arg);
|
||||
|
||||
void DoAsyncThreadInternal(CThread *thread);
|
||||
|
||||
void StopAsyncThread() {
|
||||
DEBUG_FUNCTION_LINE("StopAsyncThread\n");
|
||||
shouldExit = true;
|
||||
DCFlushRange((void*) &shouldExit,sizeof(shouldExit));
|
||||
}
|
||||
|
||||
void setThreadPriority(int priority) {
|
||||
if(pThread != NULL) {
|
||||
pThread->setThreadPriority(priority);
|
||||
}
|
||||
}
|
||||
|
||||
void proccessData(CThread *thread, void *arg);
|
||||
|
||||
bool streamJPEG(JpegInformation * info);
|
||||
|
||||
bool streamJPEGThreaded(JpegInformation * info);
|
||||
|
||||
void sendJPEG(uint8_t * buffer, uint64_t size);
|
||||
|
||||
bool sendData(uint8_t * data,int32_t length);
|
||||
|
||||
volatile int32_t sockfd = -1;
|
||||
|
||||
static MJPEGStreamServerUDP *instance;
|
||||
|
||||
crc32_t crc32Buffer;
|
||||
|
||||
private:
|
||||
MJPEGStreamServerUDP(uint32_t ip,int32_t port);
|
||||
|
||||
|
||||
bool shouldExit = false;
|
||||
CThread * pThread = NULL;
|
||||
|
||||
OSMessageQueue dataQueue;
|
||||
OSMessage dataQueueMessages[DATA_SEND_QUEUE_MESSAGE_COUNT];
|
||||
};
|
||||
|
||||
#endif //_UDPClient_WINDOW_H_
|
37
src/crc32.cpp
Normal file
37
src/crc32.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include "crc32.h"
|
||||
|
||||
void crc32_init(crc32_t* crc) {
|
||||
uint32_t v;
|
||||
|
||||
for(int i = 0; i < 256; ++i) {
|
||||
v = i;
|
||||
|
||||
for(int j = 0; j < 8; ++j) {
|
||||
v = (v & 1) ? (CRC32_INITIAL ^ (v >> 1)) : (v >> 1);
|
||||
}
|
||||
crc->table[i] = v;
|
||||
}
|
||||
}
|
||||
|
||||
void crc32_update(crc32_t* c, uint8_t* buf, size_t len) {
|
||||
for(size_t i = 0; i < len; ++i) {
|
||||
c->value = c->table[(c->value ^ buf[i]) & 0xFF] ^ (c->value >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
void crc32_start(crc32_t* c) {
|
||||
c->value = 0xfffffffful;
|
||||
}
|
||||
|
||||
uint32_t crc32_finalize(crc32_t* c) {
|
||||
return c->value ^ 0xfffffffful;
|
||||
}
|
||||
|
||||
uint32_t crc32_crc(crc32_t* c, uint8_t* buf, size_t len) {
|
||||
crc32_start(c);
|
||||
crc32_update(c, buf, len);
|
||||
return crc32_finalize(c);
|
||||
}
|
18
src/crc32.h
Normal file
18
src/crc32.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef __CRC32_H_
|
||||
#define __CRC32_H_
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <dirent.h>
|
||||
|
||||
typedef struct crc32 {
|
||||
uint32_t table[256];
|
||||
uint32_t value;
|
||||
} crc32_t;
|
||||
|
||||
#define CRC32_INITIAL 0xedb88320
|
||||
|
||||
void crc32_init(crc32_t* crc);
|
||||
|
||||
uint32_t crc32_crc(crc32_t* c, uint8_t* buf, size_t len);
|
||||
|
||||
#endif
|
@ -10,8 +10,11 @@ uint32_t count = 0;
|
||||
|
||||
DECL_FUNCTION(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer *colorBuffer, int32_t scan_target) {
|
||||
if(gAppStatus == WUPS_APP_STATUS_FOREGROUND) {
|
||||
|
||||
if(scan_target == 4 /*&& (count++ % 4 == 0)*/ && colorBuffer != NULL ) {
|
||||
int32_t use_scan_target = 4;
|
||||
if(gScreen == WUPS_STREAMING_SCREEN_TV){
|
||||
use_scan_target = 1;
|
||||
}
|
||||
if(scan_target == use_scan_target /*&& (count++ % 4 == 0)*/ && colorBuffer != NULL ) {
|
||||
count = 0;
|
||||
streamVideo((GX2ColorBuffer *)colorBuffer);
|
||||
}
|
||||
|
29
src/main.cpp
29
src/main.cpp
@ -7,7 +7,8 @@
|
||||
#include <utils/logger.h>
|
||||
#include "retain_vars.hpp"
|
||||
#include "EncodingHelper.h"
|
||||
#include "MJPEGStreamServer.hpp"
|
||||
#include "MJPEGStreamServerUDP.hpp"
|
||||
#include "HeartBeatServer.hpp"
|
||||
|
||||
// Mandatory plugin information.
|
||||
WUPS_PLUGIN_NAME("Gamepad streaming tool.");
|
||||
@ -19,18 +20,25 @@ WUPS_PLUGIN_LICENSE("GPL");
|
||||
// Something is using "write"...
|
||||
WUPS_FS_ACCESS()
|
||||
|
||||
void resolutionChanged(int32_t newResolution) {
|
||||
void resolutionChanged(WUPSConfigItemMultipleValues* configItem, int32_t newResolution) {
|
||||
DEBUG_FUNCTION_LINE("Resolution changed %d \n",newResolution);
|
||||
gResolution = newResolution;
|
||||
|
||||
// Restart server.
|
||||
EncodingHelper::destroyInstance();
|
||||
MJPEGStreamServer::destroyInstance();
|
||||
|
||||
EncodingHelper::getInstance()->StartAsyncThread();
|
||||
MJPEGStreamServer::getInstance();
|
||||
EncodingHelper::getInstance()->setMJPEGStreamServer(HeartBeatServer::getInstance()->getMJPEGServer());
|
||||
}
|
||||
|
||||
void screenChanged(WUPSConfigItemMultipleValues* configItem, int32_t newScreen) {
|
||||
DEBUG_FUNCTION_LINE("Screen changed %d \n",newScreen);
|
||||
gScreen = newScreen;
|
||||
|
||||
// Restart server.
|
||||
EncodingHelper::destroyInstance();
|
||||
EncodingHelper::getInstance()->StartAsyncThread();
|
||||
EncodingHelper::getInstance()->setMJPEGStreamServer(HeartBeatServer::getInstance()->getMJPEGServer());
|
||||
}
|
||||
|
||||
WUPS_GET_CONFIG() {
|
||||
WUPSConfig* config = new WUPSConfig("Streaming Plugin");
|
||||
@ -41,7 +49,12 @@ WUPS_GET_CONFIG() {
|
||||
resolutionValues[WUPS_STREAMING_RESOLUTION_360P] = "360p";
|
||||
resolutionValues[WUPS_STREAMING_RESOLUTION_480P] = "480p";
|
||||
|
||||
std::map<int32_t,std::string> screenValues;
|
||||
screenValues[WUPS_STREAMING_SCREEN_DRC] = "Gamepad";
|
||||
screenValues[WUPS_STREAMING_SCREEN_TV] = "TV";
|
||||
|
||||
// item Type config id displayed name default value onChangeCallback.
|
||||
catOther->addItem(new WUPSConfigItemMultipleValues("screen", "Screen", gScreen, screenValues, screenChanged));
|
||||
catOther->addItem(new WUPSConfigItemMultipleValues("resolution", "Streaming resolution", gResolution, resolutionValues, resolutionChanged));
|
||||
|
||||
return config;
|
||||
@ -61,16 +74,18 @@ ON_APPLICATION_START(my_args) {
|
||||
|
||||
gAppStatus = WUPS_APP_STATUS_FOREGROUND;
|
||||
|
||||
EncodingHelper::destroyInstance();
|
||||
EncodingHelper::getInstance()->StartAsyncThread();
|
||||
MJPEGStreamServer::getInstance();
|
||||
EncodingHelper::getInstance()->setMJPEGStreamServer(HeartBeatServer::getInstance()->getMJPEGServer());
|
||||
|
||||
log_init();
|
||||
}
|
||||
|
||||
ON_APP_STATUS_CHANGED(status) {
|
||||
gAppStatus = status;
|
||||
|
||||
if(status == WUPS_APP_STATUS_CLOSED) {
|
||||
EncodingHelper::destroyInstance();
|
||||
MJPEGStreamServer::destroyInstance();
|
||||
HeartBeatServer::destroyInstance();
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
#include "retain_vars.hpp"
|
||||
wups_loader_app_status_t gAppStatus __attribute__((section(".data"))) = WUPS_APP_STATUS_UNKNOWN;
|
||||
int32_t gResolution __attribute__((section(".data"))) = WUPS_STREAMING_RESOLUTION_240P;
|
||||
int32_t gScreen __attribute__((section(".data"))) = WUPS_STREAMING_SCREEN_DRC;
|
||||
int32_t gSendPriority __attribute__((section(".data"))) = 31;
|
||||
int32_t gEncodePriority __attribute__((section(".data"))) = 31;
|
||||
|
@ -3,11 +3,18 @@
|
||||
|
||||
#include <wups.h>
|
||||
|
||||
#define WUPS_STREAMING_SCREEN_DRC 0
|
||||
#define WUPS_STREAMING_SCREEN_TV 1
|
||||
|
||||
|
||||
#define WUPS_STREAMING_RESOLUTION_240P 1
|
||||
#define WUPS_STREAMING_RESOLUTION_360P 2
|
||||
#define WUPS_STREAMING_RESOLUTION_480P 3
|
||||
|
||||
extern wups_loader_app_status_t gAppStatus;
|
||||
extern int32_t gScreen;
|
||||
extern int32_t gResolution;
|
||||
extern int32_t gSendPriority;
|
||||
extern int32_t gEncodePriority;
|
||||
|
||||
#endif // _RETAINS_VARS_H_
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "stream_utils.h"
|
||||
#include "retain_vars.hpp"
|
||||
#include "EncodingHelper.h"
|
||||
#include "MJPEGStreamServer.hpp"
|
||||
#include "HeartBeatServer.hpp"
|
||||
#include <fs/FSUtils.h>
|
||||
#include <malloc.h>
|
||||
|
||||
@ -106,7 +106,7 @@ bool streamVideo(GX2ColorBuffer *srcBuffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!MJPEGStreamServer::isInstanceConnected()) {
|
||||
if(!HeartBeatServer::isInstanceConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user