mirror of
https://github.com/Maschell/StreamingPluginWiiU.git
synced 2024-06-11 02:38:47 +02:00
Compare commits
13 Commits
ScreenStre
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
2cc6b46107 | ||
|
fbefe2ad95 | ||
|
5c1733f55c | ||
|
993504ed15 | ||
|
3febae4867 | ||
|
eac6a6c0a6 | ||
|
d1286fa7a6 | ||
|
0790290bca | ||
|
327f1e08e5 | ||
|
364f45854c | ||
|
c7c7aefe1f | ||
|
2f00c98562 | ||
|
034f85b398 |
47
.travis.yml
47
.travis.yml
|
@ -1,52 +1,17 @@
|
|||
language: cpp
|
||||
os: linux
|
||||
sudo: false
|
||||
dist: trusty
|
||||
sudo: required
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
env:
|
||||
global:
|
||||
- DEVKITPRO=/opt/devkitpro
|
||||
- WUT_ROOT=/opt/devkitpro/wut
|
||||
- DEVKITPPC=/opt/devkitpro/devkitPPC
|
||||
- PORTLIBREPOS=$HOME/portlibrepos
|
||||
cache:
|
||||
directories:
|
||||
- "$HOME/.local"
|
||||
- "$DEVKITPRO"
|
||||
- master
|
||||
services:
|
||||
- docker
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- p7zip-full
|
||||
before_install:
|
||||
- mkdir -p "${PORTLIBREPOS}"
|
||||
- mkdir -p "${DEVKITPRO}"
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then wget https://github.com/devkitPro/pacman/releases/download/devkitpro-pacman-1.0.1/devkitpro-pacman.deb
|
||||
-O /tmp/devkitpro-pacman.deb; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo dpkg -i /tmp/devkitpro-pacman.deb;
|
||||
fi
|
||||
- yes | sudo dkp-pacman -Syu devkitPPC --needed
|
||||
- yes | sudo dkp-pacman -Syu general-tools --needed
|
||||
- wget https://github.com/decaf-emu/wut/releases/download/1.0.0-beta2/wut.linux64.7z
|
||||
install:
|
||||
- 7z x -y $(ls | grep "linux") -o${WUT_ROOT}
|
||||
- cd $PORTLIBREPOS
|
||||
- git clone https://github.com/Maschell/WiiUPluginSystem.git
|
||||
- git clone https://github.com/Maschell/libutils.git -b wut
|
||||
- cd WiiUPluginSystem
|
||||
- make && make install
|
||||
- cd $PORTLIBREPOS
|
||||
- cd libutils
|
||||
- mkdir build && cd build
|
||||
- cmake -DCMAKE_TOOLCHAIN_FILE=$WUT_ROOT/share/wut.toolchain.cmake -DCMAKE_INSTALL_PREFIX=$WUT_ROOT
|
||||
../
|
||||
- make install
|
||||
- cd $PORTLIBREPOS
|
||||
before_script:
|
||||
- cd $TRAVIS_BUILD_DIR/
|
||||
- docker build . -t screenstreamer-builder
|
||||
script:
|
||||
- make -j8
|
||||
- docker run -it --rm -v ${PWD}:/project screenstreamer-builder make
|
||||
before_deploy:
|
||||
- cd $TRAVIS_BUILD_DIR/
|
||||
- mkdir -p "wiiu/plugins"
|
||||
|
|
6
Dockerfile
Normal file
6
Dockerfile
Normal file
|
@ -0,0 +1,6 @@
|
|||
FROM wups/core-with-wut:0.1
|
||||
|
||||
# Get dependencies
|
||||
COPY --from=wiiuwut/libutils:0.1 /artifacts $WUT_ROOT
|
||||
|
||||
WORKDIR project
|
39
README.md
39
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,14 +33,15 @@ 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
|
||||
|
||||
For building you need:
|
||||
- [wups](https://github.com/Maschell/WiiUPluginSystem)
|
||||
- [dynamic_libs](https://github.com/Maschell/dynamic_libs/tree/lib) for access to the functions.
|
||||
- [libutils](https://github.com/Maschell/libutils) for common functions.
|
||||
- [wut](https://github.com/decaf-emu/wut)
|
||||
- [libutilswut](https://github.com/Maschell/libutils/tree/wut) (WUT version) for common functions.
|
||||
|
||||
Install them (in this order) according to their READMEs. Don't forget the dependencies of the libs itself.
|
||||
|
||||
|
@ -44,3 +49,17 @@ Other external libraries are already located in the `libs` folder.
|
|||
|
||||
- libjpeg
|
||||
- [libturbojpeg](https://libjpeg-turbo.org/)
|
||||
|
||||
### Building using the Dockerfile
|
||||
It's possible to use a docker image for building. This way you don't need anything installed on your host system.
|
||||
|
||||
```
|
||||
# Build docker image (only needed once
|
||||
docker build . -t screenstreamer-builder
|
||||
|
||||
# make
|
||||
docker run -it --rm -v ${PWD}:/project screenstreamer-builder make
|
||||
|
||||
# make clean
|
||||
docker run -it --rm -v ${PWD}:/project screenstreamer-builder make clean
|
||||
```
|
||||
|
|
|
@ -17,21 +17,23 @@
|
|||
|
||||
#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 = 17;
|
||||
this->pThread = CThread::create(DoAsyncThread, this, CThread::eAttributeAffCore0, priority,0x40000);
|
||||
int32_t priority = gEncodePriority;
|
||||
this->pThread = CThread::create(DoAsyncThread, this, CThread::eAttributeAffCore0 |CThread::eAttributeAffCore2 , priority,0x40000);
|
||||
this->pThread->resumeThread();
|
||||
}
|
||||
|
||||
|
@ -84,7 +86,7 @@ JpegInformation * convertToJpeg(uint8_t * sourceBuffer, uint32_t width, uint32_t
|
|||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern int32_t curQuality;
|
||||
void EncodingHelper::DoAsyncThreadInternal(CThread *thread) {
|
||||
serverRunning = true;
|
||||
|
||||
|
@ -99,7 +101,7 @@ void EncodingHelper::DoAsyncThreadInternal(CThread *thread) {
|
|||
DEBUG_FUNCTION_LINE("We should stop\n");
|
||||
break;
|
||||
}
|
||||
OSSleepTicks(OSMicrosecondsToTicks(5000));
|
||||
OSSleepTicks(OSMicrosecondsToTicks(500));
|
||||
continue;
|
||||
}
|
||||
DCFlushRange(&message,sizeof(OSMessage));
|
||||
|
@ -112,10 +114,12 @@ void EncodingHelper::DoAsyncThreadInternal(CThread *thread) {
|
|||
|
||||
colorBuffer = (GX2ColorBuffer *) message.args[1];
|
||||
|
||||
JpegInformation * info = convertToJpeg((uint8_t*) colorBuffer->surface.image,colorBuffer->surface.width,colorBuffer->surface.height,colorBuffer->surface.pitch,colorBuffer->surface.format,85);
|
||||
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,130 +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 == 0x1234) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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,91 +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];
|
||||
|
||||
|
||||
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 10;
|
||||
}
|
||||
|
||||
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_BLOCKING)) {
|
||||
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) {
|
||||
// Stream every 4th frame of the Gamepad (targetting 20fps)
|
||||
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);
|
||||
}
|
||||
|
|
54
src/main.cpp
54
src/main.cpp
|
@ -1,8 +1,14 @@
|
|||
#include <map>
|
||||
#include <string>
|
||||
#include <wups.h>
|
||||
#include <wups/config.h>
|
||||
#include <wups/config/WUPSConfig.h>
|
||||
#include <wups/config/WUPSConfigItemMultipleValues.h>
|
||||
#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.");
|
||||
|
@ -14,6 +20,46 @@ WUPS_PLUGIN_LICENSE("GPL");
|
|||
// Something is using "write"...
|
||||
WUPS_FS_ACCESS()
|
||||
|
||||
void resolutionChanged(WUPSConfigItemMultipleValues* configItem, int32_t newResolution) {
|
||||
DEBUG_FUNCTION_LINE("Resolution changed %d \n",newResolution);
|
||||
gResolution = newResolution;
|
||||
|
||||
// Restart server.
|
||||
EncodingHelper::destroyInstance();
|
||||
EncodingHelper::getInstance()->StartAsyncThread();
|
||||
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");
|
||||
WUPSConfigCategory* catOther = config->addCategory("Main");
|
||||
|
||||
std::map<int32_t,std::string> resolutionValues;
|
||||
resolutionValues[WUPS_STREAMING_RESOLUTION_240P] = "240p";
|
||||
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;
|
||||
}
|
||||
|
||||
// Gets called once the loader exists.
|
||||
INITIALIZE_PLUGIN() {
|
||||
socket_lib_init();
|
||||
|
@ -28,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,2 +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,6 +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,6 +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>
|
||||
|
||||
|
@ -31,6 +32,7 @@ bool copyBuffer(GX2ColorBuffer * sourceBuffer, GX2ColorBuffer * targetBuffer, ui
|
|||
targetBuffer->surface.depth = 1;
|
||||
targetBuffer->surface.mipLevels = 1;
|
||||
targetBuffer->surface.format = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8;
|
||||
//targetBuffer->surface.format = GX2_SURFACE_FORMAT_SRGB_R8_G8_B8_A8;
|
||||
targetBuffer->surface.aa = GX2_AA_MODE1X;
|
||||
targetBuffer->surface.tileMode = GX2_TILE_MODE_LINEAR_ALIGNED;
|
||||
targetBuffer->viewMip = 0;
|
||||
|
@ -91,13 +93,20 @@ bool copyBuffer(GX2ColorBuffer * sourceBuffer, GX2ColorBuffer * targetBuffer, ui
|
|||
|
||||
uint32_t frame_counter = 0;
|
||||
uint32_t frame_counter_skipped = 0;
|
||||
int32_t curQuality = 50;
|
||||
int32_t minQuality = 40;
|
||||
int32_t maxQuality = 85;
|
||||
int32_t stepQuality = 1;
|
||||
int32_t maxFrameDropsQuality = 20;
|
||||
int32_t minFrameDropsQuality = 95;
|
||||
|
||||
|
||||
bool streamVideo(GX2ColorBuffer *srcBuffer) {
|
||||
if(srcBuffer == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!MJPEGStreamServer::isInstanceConnected()) {
|
||||
if(!HeartBeatServer::isInstanceConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -110,12 +119,19 @@ bool streamVideo(GX2ColorBuffer *srcBuffer) {
|
|||
memset(colorBuffer,0,sizeof(GX2ColorBuffer));
|
||||
|
||||
// keep dimensions
|
||||
//uint32_t width = srcBuffer->surface.width;
|
||||
//uint32_t height = srcBuffer->surface.height;
|
||||
//uint32_t width = 640;
|
||||
//uint32_t height = 360;
|
||||
uint32_t width = 428;
|
||||
uint32_t height = 240;
|
||||
|
||||
uint32_t width = 640/2;
|
||||
uint32_t height = 360/2;
|
||||
|
||||
if(gResolution == WUPS_STREAMING_RESOLUTION_480P) {
|
||||
width = 854;
|
||||
height = 480;
|
||||
} else if(gResolution == WUPS_STREAMING_RESOLUTION_360P) {
|
||||
width = 640;
|
||||
height = 360;
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
bool valid = copyBuffer(srcBuffer,colorBuffer,width,height);
|
||||
if(!valid) {
|
||||
|
@ -163,8 +179,34 @@ bool streamVideo(GX2ColorBuffer *srcBuffer) {
|
|||
result = false;
|
||||
}
|
||||
|
||||
if(frame_counter % 120 == 0) {
|
||||
DEBUG_FUNCTION_LINE("Send %d frames, skipped %d. %.2f\n",frame_counter-frame_counter_skipped,frame_counter_skipped,100.f * (frame_counter_skipped*1.0f/frame_counter));
|
||||
if(frame_counter % 60 == 0) { // Print this every second.
|
||||
|
||||
int32_t curRatio = (int32_t)100.f*(frame_counter_skipped*1.0f/frame_counter);
|
||||
int32_t curQualityOld = curQuality;
|
||||
if(curRatio > maxFrameDropsQuality) { // Lower the quality if we drop more than [maxFrameDropsQuality]% of the frames.
|
||||
curQuality -= (curRatio - maxFrameDropsQuality);
|
||||
} else if(curRatio < minFrameDropsQuality) { // Increase the quality if we drop less than [minFrameDropsQuality]% of the frames.
|
||||
curQuality += stepQuality; // Increase the quality by [stepQuality]%
|
||||
}
|
||||
|
||||
// Make sure to set the quality to at least [minQuality]%
|
||||
if(curQuality < minQuality) {
|
||||
curQuality = minQuality;
|
||||
}
|
||||
|
||||
// Make sure to set the quality to at most [maxQuality]%
|
||||
if(curQuality >= maxQuality) {
|
||||
curQuality = maxQuality;
|
||||
}
|
||||
|
||||
DEBUG_FUNCTION_LINE("Streaming at %d fps\n",frame_counter-frame_counter_skipped);
|
||||
|
||||
frame_counter = 0;
|
||||
frame_counter_skipped = 0;
|
||||
|
||||
if(curQualityOld != curQuality) {
|
||||
DEBUG_FUNCTION_LINE("Quality is now at %d%%\n",curQuality);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
Loading…
Reference in New Issue
Block a user