mirror of
https://github.com/DevL0rd/SkyNX
synced 2024-11-21 18:43:03 +00:00
Oops unlinked my source
This commit is contained in:
parent
3bc3ba5e1f
commit
3e92241d5c
15 changed files with 3995 additions and 1 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -11,6 +11,6 @@ SkyNX-Streamer/SkyNXStreamer-win32-x64
|
|||
SkyNX-Streamer/SkyNXStreamer-win32-x64.zip
|
||||
SkyNX-Streamer/SkyNXStreamer-win32-ia32
|
||||
SkyNX-Streamer/SkyNXStreamer-win32-ia32.zip
|
||||
SkyNX/source
|
||||
SkyNX/build
|
||||
SkyNX/SkyNX.zip
|
||||
SkyNX-Streamer/settings.json
|
||||
|
|
2337
SkyNX/source/SDL_FontCache.c
Normal file
2337
SkyNX/source/SDL_FontCache.c
Normal file
File diff suppressed because it is too large
Load diff
268
SkyNX/source/SDL_FontCache.h
Normal file
268
SkyNX/source/SDL_FontCache.h
Normal file
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
SDL_FontCache v0.9.0: A font cache for SDL and SDL_ttf
|
||||
by Jonathan Dearborn
|
||||
Dedicated to the memory of Florian Hufsky
|
||||
|
||||
License:
|
||||
The short:
|
||||
Use it however you'd like, but keep the copyright and license notice
|
||||
whenever these files or parts of them are distributed in uncompiled form.
|
||||
|
||||
The long:
|
||||
Copyright (c) 2016 Jonathan Dearborn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef _SDL_FONTCACHE_H__
|
||||
#define _SDL_FONTCACHE_H__
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_ttf.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Let's pretend this exists...
|
||||
#define TTF_STYLE_OUTLINE 16
|
||||
|
||||
#define FC_Rect SDL_Rect
|
||||
#define FC_Target SDL_Renderer
|
||||
#define FC_Image SDL_Texture
|
||||
#define FC_Log SDL_Log
|
||||
|
||||
// SDL_FontCache types
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FC_ALIGN_LEFT,
|
||||
FC_ALIGN_CENTER,
|
||||
FC_ALIGN_RIGHT
|
||||
} FC_AlignEnum;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FC_FILTER_NEAREST,
|
||||
FC_FILTER_LINEAR
|
||||
} FC_FilterEnum;
|
||||
|
||||
typedef struct FC_Scale
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
|
||||
} FC_Scale;
|
||||
|
||||
typedef struct FC_Effect
|
||||
{
|
||||
FC_AlignEnum alignment;
|
||||
FC_Scale scale;
|
||||
SDL_Color color;
|
||||
|
||||
} FC_Effect;
|
||||
|
||||
// Opaque type
|
||||
typedef struct FC_Font FC_Font;
|
||||
|
||||
typedef struct FC_GlyphData
|
||||
{
|
||||
SDL_Rect rect;
|
||||
int cache_level;
|
||||
|
||||
} FC_GlyphData;
|
||||
|
||||
// Object creation
|
||||
|
||||
FC_Rect FC_MakeRect(float x, float y, float w, float h);
|
||||
|
||||
FC_Scale FC_MakeScale(float x, float y);
|
||||
|
||||
SDL_Color FC_MakeColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a);
|
||||
|
||||
FC_Effect FC_MakeEffect(FC_AlignEnum alignment, FC_Scale scale, SDL_Color color);
|
||||
|
||||
FC_GlyphData FC_MakeGlyphData(int cache_level, Sint16 x, Sint16 y, Uint16 w, Uint16 h);
|
||||
|
||||
// Font object
|
||||
|
||||
FC_Font* FC_CreateFont(void);
|
||||
|
||||
Uint8 FC_LoadFontFromTTF(FC_Font* font, SDL_Renderer* renderer, TTF_Font* ttf, TTF_Font* ext, SDL_Color color);
|
||||
|
||||
Uint8 FC_LoadFont_RW(FC_Font* font, SDL_Renderer* renderer, SDL_RWops* file_rwops_ttf, SDL_RWops* file_rwops_ext, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style);
|
||||
|
||||
void FC_ClearFont(FC_Font* font);
|
||||
|
||||
void FC_FreeFont(FC_Font* font);
|
||||
|
||||
// Built-in loading strings
|
||||
|
||||
char* FC_GetStringASCII(void);
|
||||
|
||||
|
||||
// UTF-8 to SDL_FontCache codepoint conversion
|
||||
|
||||
/*!
|
||||
Returns the Uint32 codepoint (not UTF-32) parsed from the given UTF-8 string.
|
||||
\param c A pointer to a string of proper UTF-8 character values.
|
||||
\param advance_pointer If true, the source pointer will be incremented to skip the extra bytes from multibyte codepoints.
|
||||
*/
|
||||
Uint32 FC_GetCodepointFromUTF8(const char** c, Uint8 advance_pointer);
|
||||
|
||||
/*!
|
||||
Parses the given codepoint and stores the UTF-8 bytes in 'result'. The result is NULL terminated.
|
||||
\param result A memory buffer for the UTF-8 values. Must be at least 5 bytes long.
|
||||
\param codepoint The Uint32 codepoint to parse (not UTF-32).
|
||||
*/
|
||||
void FC_GetUTF8FromCodepoint(char* result, Uint32 codepoint);
|
||||
|
||||
|
||||
// UTF-8 string operations
|
||||
|
||||
/*! Allocates a new string of 'size' bytes that is already NULL-terminated. The NULL byte counts toward the size limit, as usual. Returns NULL if size is 0. */
|
||||
char* U8_alloc(unsigned int size);
|
||||
|
||||
/*! Deallocates the given string. */
|
||||
void U8_free(char* string);
|
||||
|
||||
/*! Allocates a copy of the given string. */
|
||||
char* U8_strdup(const char* string);
|
||||
|
||||
/*! Returns the number of UTF-8 characters in the given string. */
|
||||
int U8_strlen(const char* string);
|
||||
|
||||
/*! Returns the number of bytes in the UTF-8 multibyte character pointed at by 'character'. */
|
||||
int U8_charsize(const char* character);
|
||||
|
||||
/*! Copies the source multibyte character into the given buffer without overrunning it. Returns 0 on failure. */
|
||||
int U8_charcpy(char* buffer, const char* source, int buffer_size);
|
||||
|
||||
/*! Returns a pointer to the next UTF-8 character. */
|
||||
const char* U8_next(const char* string);
|
||||
|
||||
/*! Inserts a UTF-8 string into 'string' at the given position. Use a position of -1 to append. Returns 0 when unable to insert the string. */
|
||||
int U8_strinsert(char* string, int position, const char* source, int max_bytes);
|
||||
|
||||
/*! Erases the UTF-8 character at the given position, moving the subsequent characters down. */
|
||||
void U8_strdel(char* string, int position);
|
||||
|
||||
|
||||
// Internal settings
|
||||
|
||||
/*! Sets the string from which to load the initial glyphs. Use this if you need upfront loading for any reason (such as lack of render-target support). */
|
||||
void FC_SetLoadingString(FC_Font* font, const char* string);
|
||||
|
||||
/*! Returns the size of the internal buffer which is used for unpacking variadic text data. This buffer is shared by all FC_Fonts. */
|
||||
unsigned int FC_GetBufferSize(void);
|
||||
|
||||
/*! Changes the size of the internal buffer which is used for unpacking variadic text data. This buffer is shared by all FC_Fonts. */
|
||||
void FC_SetBufferSize(unsigned int size);
|
||||
|
||||
void FC_SetRenderCallback(FC_Rect (*callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale));
|
||||
|
||||
FC_Rect FC_DefaultRenderCallback(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale);
|
||||
|
||||
|
||||
// Custom caching
|
||||
|
||||
/*! Returns the number of cache levels that are active. */
|
||||
int FC_GetNumCacheLevels(FC_Font* font);
|
||||
|
||||
/*! Returns the cache source texture at the given cache level. */
|
||||
FC_Image* FC_GetGlyphCacheLevel(FC_Font* font, int cache_level);
|
||||
|
||||
// TODO: Specify ownership of the texture (should be shareable)
|
||||
/*! Sets a cache source texture for rendering. New cache levels must be sequential. */
|
||||
Uint8 FC_SetGlyphCacheLevel(FC_Font* font, int cache_level, FC_Image* cache_texture);
|
||||
|
||||
/*! Copies the given surface to the given cache level as a texture. New cache levels must be sequential. */
|
||||
Uint8 FC_UploadGlyphCache(FC_Font* font, int cache_level, SDL_Surface* data_surface);
|
||||
|
||||
|
||||
/*! Returns the number of codepoints that are stored in the font's glyph data map. */
|
||||
unsigned int FC_GetNumCodepoints(FC_Font* font);
|
||||
|
||||
/*! Copies the stored codepoints into the given array. */
|
||||
void FC_GetCodepoints(FC_Font* font, Uint32* result);
|
||||
|
||||
/*! Stores the glyph data for the given codepoint in 'result'. Returns 0 if the codepoint was not found in the cache. */
|
||||
Uint8 FC_GetGlyphData(FC_Font* font, FC_GlyphData* result, Uint32 codepoint);
|
||||
|
||||
/*! Sets the glyph data for the given codepoint. Duplicates are not checked. Returns a pointer to the stored data. */
|
||||
FC_GlyphData* FC_SetGlyphData(FC_Font* font, Uint32 codepoint, FC_GlyphData glyph_data);
|
||||
|
||||
|
||||
// Rendering
|
||||
|
||||
FC_Rect FC_Draw(FC_Font* font, FC_Target* dest, float x, float y, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawAlign(FC_Font* font, FC_Target* dest, float x, float y, FC_AlignEnum align, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawScale(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawColor(FC_Font* font, FC_Target* dest, float x, float y, SDL_Color color, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawEffect(FC_Font* font, FC_Target* dest, float x, float y, FC_Effect effect, const char* formatted_text, ...);
|
||||
|
||||
FC_Rect FC_DrawBox(FC_Font* font, FC_Target* dest, FC_Rect box, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawBoxAlign(FC_Font* font, FC_Target* dest, FC_Rect box, FC_AlignEnum align, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawBoxScale(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Scale scale, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawBoxColor(FC_Font* font, FC_Target* dest, FC_Rect box, SDL_Color color, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawBoxEffect(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Effect effect, const char* formatted_text, ...);
|
||||
|
||||
FC_Rect FC_DrawColumn(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawColumnAlign(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_AlignEnum align, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawColumnScale(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Scale scale, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawColumnColor(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, SDL_Color color, const char* formatted_text, ...);
|
||||
FC_Rect FC_DrawColumnEffect(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Effect effect, const char* formatted_text, ...);
|
||||
|
||||
// Getters
|
||||
|
||||
FC_FilterEnum FC_GetFilterMode(FC_Font* font);
|
||||
Uint16 FC_GetLineHeight(FC_Font* font);
|
||||
Uint16 FC_GetHeight(FC_Font* font, const char* formatted_text, ...);
|
||||
Uint16 FC_GetWidth(FC_Font* font, const char* formatted_text, ...);
|
||||
|
||||
// Returns a 1-pixel wide box in front of the character in the given position (index)
|
||||
FC_Rect FC_GetCharacterOffset(FC_Font* font, Uint16 position_index, int column_width, const char* formatted_text, ...);
|
||||
Uint16 FC_GetColumnHeight(FC_Font* font, Uint16 width, const char* formatted_text, ...);
|
||||
|
||||
int FC_GetAscent(FC_Font* font, const char* formatted_text, ...);
|
||||
int FC_GetDescent(FC_Font* font, const char* formatted_text, ...);
|
||||
int FC_GetBaseline(FC_Font* font);
|
||||
int FC_GetSpacing(FC_Font* font);
|
||||
int FC_GetLineSpacing(FC_Font* font);
|
||||
Uint16 FC_GetMaxWidth(FC_Font* font);
|
||||
SDL_Color FC_GetDefaultColor(FC_Font* font);
|
||||
|
||||
Uint8 FC_InRect(float x, float y, FC_Rect input_rect);
|
||||
// Given an offset (x,y) from the text draw position (the upper-left corner), returns the character position (UTF-8 index)
|
||||
Uint16 FC_GetPositionFromOffset(FC_Font* font, float x, float y, int column_width, FC_AlignEnum align, const char* formatted_text, ...);
|
||||
|
||||
// Setters
|
||||
|
||||
void FC_SetFilterMode(FC_Font* font, FC_FilterEnum filter);
|
||||
void FC_SetSpacing(FC_Font* font, int LetterSpacing);
|
||||
void FC_SetLineSpacing(FC_Font* font, int LineSpacing);
|
||||
void FC_SetDefaultColor(FC_Font* font, SDL_Color color);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
144
SkyNX/source/audio.c
Normal file
144
SkyNX/source/audio.c
Normal file
|
@ -0,0 +1,144 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include <inttypes.h>
|
||||
#include <switch.h>
|
||||
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define SAMPLERATE 48000
|
||||
#define CHANNELCOUNT 2
|
||||
#define FRAMERATE (1000 / 30)
|
||||
#define SAMPLECOUNT (SAMPLERATE / FRAMERATE)
|
||||
#define BYTESPERSAMPLE 2
|
||||
|
||||
void diep(char *s)
|
||||
{
|
||||
perror(s);
|
||||
while (1)
|
||||
;
|
||||
}
|
||||
|
||||
int setup_socket()
|
||||
{
|
||||
struct sockaddr_in si_me;
|
||||
int s;
|
||||
|
||||
if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
|
||||
diep("socket");
|
||||
|
||||
memset((char *)&si_me, 0, sizeof(si_me));
|
||||
si_me.sin_family = AF_INET;
|
||||
si_me.sin_port = htons(2224);
|
||||
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (bind(s, &si_me, sizeof(si_me)) == -1)
|
||||
diep("bind");
|
||||
return s;
|
||||
}
|
||||
|
||||
#define BUF_COUNT 5
|
||||
AudioOutBuffer audiobuf[BUF_COUNT];
|
||||
u8 *buf_data[BUF_COUNT];
|
||||
int curBuf = 0;
|
||||
#define swapbuf (curBuf = (curBuf + 1) % (BUF_COUNT))
|
||||
|
||||
AudioOutBuffer *audout_released_buf;
|
||||
int audout_filled = 0;
|
||||
void play_buf(int buffer_size, int data_size)
|
||||
{
|
||||
|
||||
if (audout_filled >= BUF_COUNT)
|
||||
{
|
||||
u32 released_count;
|
||||
//audoutPlayBuffer(&audout_released_buf, &released_count);
|
||||
audoutWaitPlayFinish(&audout_released_buf, &released_count, UINT64_MAX);
|
||||
}
|
||||
|
||||
audiobuf[curBuf].next = 0;
|
||||
audiobuf[curBuf].buffer = buf_data[curBuf];
|
||||
audiobuf[curBuf].buffer_size = buffer_size;
|
||||
audiobuf[curBuf].data_size = data_size;
|
||||
audiobuf[curBuf].data_offset = 0;
|
||||
audoutAppendAudioOutBuffer(&audiobuf[curBuf]);
|
||||
|
||||
audout_filled++;
|
||||
|
||||
swapbuf;
|
||||
}
|
||||
|
||||
// out_buf has to be in_buf_size*fact*2 big
|
||||
short lastAbove0Value = 0;
|
||||
void resample(unsigned short *in_buf, int in_buf_size, unsigned short *out_buf, short fact)
|
||||
{
|
||||
int channels = 2;
|
||||
//channels are right next to each other
|
||||
int dataLength = in_buf_size / sizeof(unsigned short);
|
||||
for (int i = 0; i < dataLength; i += channels) //skip through audio data based on channel count
|
||||
{
|
||||
int out_base = i * fact; //get next starting point in upsampled audio buffer
|
||||
|
||||
for (int j = 0; j < fact; j++) //do this thing 3 times to fill missing audio
|
||||
{
|
||||
for (int chan = 0; chan < channels; chan++) //For both channels
|
||||
{
|
||||
out_buf[out_base++] = in_buf[i + chan]; //Smoothing here?? maybe..
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define DATA_SIZE 1920
|
||||
#define IN_RATE 160000 // Bitrate.
|
||||
#define OUT_RATE 480000 // Bitrate.
|
||||
#define FACT (OUT_RATE / IN_RATE)
|
||||
#define IN_BUFSIZE (DATA_SIZE / FACT)
|
||||
|
||||
void audioHandlerLoop()
|
||||
{
|
||||
char in_buf[IN_BUFSIZE] = {0};
|
||||
|
||||
u32 buffer_size = (DATA_SIZE + 0xfff) & ~0xfff;
|
||||
|
||||
for (int curBuf = 0; curBuf < BUF_COUNT; curBuf++)
|
||||
{
|
||||
buf_data[curBuf] = memalign(0x1000, buffer_size);
|
||||
}
|
||||
|
||||
int sock = setup_socket();
|
||||
printf("%d\n", sock);
|
||||
int played = 0;
|
||||
struct sockaddr si_other;
|
||||
socklen_t slen = sizeof(si_other);
|
||||
while (appletMainLoop())
|
||||
{
|
||||
int ret = recvfrom(sock, in_buf, sizeof(in_buf), 0, &si_other, &slen);
|
||||
if (ret < 0)
|
||||
{
|
||||
perror("recv failed:");
|
||||
continue;
|
||||
}
|
||||
if (ret != sizeof(in_buf))
|
||||
{
|
||||
printf("Bad input %d\n", ret);
|
||||
|
||||
continue;
|
||||
}
|
||||
resample((unsigned short *)in_buf, sizeof(in_buf), (unsigned short *)buf_data[curBuf], FACT);
|
||||
play_buf(buffer_size, DATA_SIZE);
|
||||
played++;
|
||||
// svcSleepThread(16666665); //Nano sleep to keep at 60fps
|
||||
}
|
||||
|
||||
for (int curBuf = 0; curBuf < BUF_COUNT; curBuf++)
|
||||
{
|
||||
free(buf_data[curBuf]);
|
||||
}
|
||||
}
|
3
SkyNX/source/audio.h
Normal file
3
SkyNX/source/audio.h
Normal file
|
@ -0,0 +1,3 @@
|
|||
// TODO: Proper context stuff. Pretty hacky right now.
|
||||
|
||||
void audioHandlerLoop();
|
58
SkyNX/source/context.h
Normal file
58
SkyNX/source/context.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
#ifndef _CONTEXT_H
|
||||
#define _CONTEXT_H
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
#include <switch.h>
|
||||
#include <SDL.h>
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include "SDL_FontCache.h"
|
||||
|
||||
#define RESX 1280
|
||||
#define RESY 720
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *yuv_text;
|
||||
|
||||
SDL_Rect rect;
|
||||
SDL_Texture *logoTexture;
|
||||
|
||||
Mutex texture_mut;
|
||||
|
||||
u8 YPlane[RESX * RESY];
|
||||
u8 UPlane[RESX * RESY / 4];
|
||||
u8 VPlane[RESX * RESY / 4];
|
||||
|
||||
bool frame_avail;
|
||||
Mutex frame_avail_mut;
|
||||
FC_Font *font;
|
||||
|
||||
bool video_active;
|
||||
Mutex video_active_mut;
|
||||
|
||||
} RenderContext;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
AVFormatContext *fmt_ctx;
|
||||
AVCodecContext *video_dec_ctx; //, *audio_dec_ctx;
|
||||
AVStream *video_stream; //, *audio_stream = NULL;
|
||||
int video_stream_idx; //, audio_stream_idx = -1;
|
||||
AVFrame *rgbframe;
|
||||
AVFrame *frame;
|
||||
uint8_t *video_dst_data[4];
|
||||
int video_dst_linesize[4];
|
||||
int video_frame_count;
|
||||
RenderContext *renderContext;
|
||||
//static int audio_frame_count = 0;
|
||||
} VideoContext;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int lissock;
|
||||
int sock;
|
||||
} JoyConSocket;
|
||||
|
||||
#endif
|
45
SkyNX/source/input.c
Normal file
45
SkyNX/source/input.c
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include <switch.h>
|
||||
|
||||
#include "context.h"
|
||||
#include "input.h"
|
||||
#include "network.h"
|
||||
|
||||
void gamePadSend(JoyConSocket *connection)
|
||||
{
|
||||
JoystickPosition lJoy;
|
||||
JoystickPosition rJoy;
|
||||
touchPosition touch;
|
||||
JoyPkg pkg;
|
||||
|
||||
/* Recieve switch input and generate the package */
|
||||
hidScanInput();
|
||||
pkg.heldKeys = hidKeysHeld(CONTROLLER_P1_AUTO);
|
||||
hidJoystickRead(&lJoy, CONTROLLER_P1_AUTO, JOYSTICK_LEFT);
|
||||
hidJoystickRead(&rJoy, CONTROLLER_P1_AUTO, JOYSTICK_RIGHT);
|
||||
pkg.lJoyX = lJoy.dx;
|
||||
pkg.lJoyY = lJoy.dy;
|
||||
pkg.rJoyX = rJoy.dx;
|
||||
pkg.rJoyY = rJoy.dy;
|
||||
hidTouchRead(&touch, 0);
|
||||
pkg.touchX = touch.px;
|
||||
pkg.touchY = touch.py;
|
||||
sendJoyConInput(connection, &pkg);
|
||||
}
|
||||
|
||||
void handleInput(JoyConSocket *connection)
|
||||
{
|
||||
if (connectJoyConSocket(connection, 2223))
|
||||
gamePadSend(connection);
|
||||
}
|
||||
|
||||
void inputHandlerLoop(void *dummy)
|
||||
{
|
||||
JoyConSocket *connection = createJoyConSocket();
|
||||
while (appletMainLoop())
|
||||
{
|
||||
handleInput(connection);
|
||||
svcSleepThread(23333333);
|
||||
}
|
||||
|
||||
freeJoyConSocket(connection);
|
||||
}
|
7
SkyNX/source/input.h
Normal file
7
SkyNX/source/input.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#ifndef INPUT_H
|
||||
#define INPUT_H
|
||||
|
||||
/* Loop to handle joycon inputs and send theme to the server */
|
||||
void inputHandlerLoop(void* dummy);
|
||||
|
||||
#endif
|
145
SkyNX/source/main.c
Normal file
145
SkyNX/source/main.c
Normal file
|
@ -0,0 +1,145 @@
|
|||
// The following ffmpeg code is inspired by an official ffmpeg example, so here is its Copyright notice:
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012 Stefano Sabatini
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
|
||||
|
||||
*
|
||||
* @file
|
||||
* Demuxing and decoding example.
|
||||
*
|
||||
* Show how to use the libavformat and libavcodec API to demux and
|
||||
* decode audio and video data.
|
||||
* @example demuxing_decoding.c
|
||||
|
||||
*/
|
||||
|
||||
#include <libavutil/samplefmt.h>
|
||||
|
||||
#include <switch.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#include "context.h"
|
||||
#include "input.h"
|
||||
#include "video.h"
|
||||
#include "network.h"
|
||||
#include "renderer.h"
|
||||
#include "audio.h"
|
||||
static const SocketInitConfig socketInitConf = {
|
||||
.bsdsockets_version = 1,
|
||||
|
||||
.tcp_tx_buf_size = 0x80000,
|
||||
.tcp_rx_buf_size = 0x100000,
|
||||
.tcp_tx_buf_max_size = 0x400000,
|
||||
.tcp_rx_buf_max_size = 0x400000,
|
||||
|
||||
.udp_tx_buf_size = 0x1400,
|
||||
.udp_rx_buf_size = 0x2500,
|
||||
|
||||
.sb_efficiency = 2,
|
||||
};
|
||||
|
||||
void switchInit()
|
||||
{
|
||||
plInitialize();
|
||||
pcvInitialize();
|
||||
|
||||
romfsInit();
|
||||
networkInit(&socketInitConf);
|
||||
|
||||
audoutInitialize();
|
||||
audoutStartAudioOut();
|
||||
}
|
||||
|
||||
void switchDestroy()
|
||||
{
|
||||
audoutStopAudioOut();
|
||||
audoutExit();
|
||||
networkDestroy();
|
||||
pcvExit();
|
||||
plExit();
|
||||
}
|
||||
|
||||
void startInput()
|
||||
{
|
||||
static Thread inputHandlerThread;
|
||||
threadCreate(&inputHandlerThread, inputHandlerLoop, NULL, NULL, 0x1000, 0x2b, 0);
|
||||
threadStart(&inputHandlerThread);
|
||||
}
|
||||
|
||||
void startAudio()
|
||||
{
|
||||
static Thread audioHandlerThread;
|
||||
// On same thread as input and preemptive
|
||||
threadCreate(&audioHandlerThread, audioHandlerLoop, NULL, NULL, 0x10000, 0x20, 1);
|
||||
threadStart(&audioHandlerThread);
|
||||
}
|
||||
|
||||
void startRender(VideoContext *videoContext)
|
||||
{
|
||||
static Thread renderThread;
|
||||
threadCreate(&renderThread, videoLoop, videoContext, NULL, 0x800000, 0x2b, 2);
|
||||
threadStart(&renderThread);
|
||||
}
|
||||
|
||||
RenderContext *renderContext = NULL;
|
||||
VideoContext *videoContext = NULL;
|
||||
void init()
|
||||
{
|
||||
/* Init all switch required systems */
|
||||
switchInit();
|
||||
pcvSetClockRate(PcvModule_CpuBus, 1785000000); //Overclock CPU
|
||||
|
||||
renderContext = createRenderer();
|
||||
videoContext = createVideoContext();
|
||||
videoContext->renderContext = renderContext;
|
||||
/* Run audio handling in background */
|
||||
startAudio();
|
||||
/* Run input handling in background */
|
||||
startInput();
|
||||
/* Run input handling in background */
|
||||
startRender(videoContext);
|
||||
}
|
||||
void unInit()
|
||||
{
|
||||
freeRenderer(renderContext);
|
||||
freeVideoContext(videoContext);
|
||||
pcvSetClockRate(PcvModule_CpuBus, 1020000000); //Reset CPU clock to default
|
||||
}
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
init();
|
||||
|
||||
while (appletMainLoop())
|
||||
{
|
||||
if (isVideoActive(renderContext))
|
||||
{
|
||||
displayFrame(renderContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
drawSplash(renderContext);
|
||||
}
|
||||
svcSleepThread(12500000); //Nano sleep to keep at 80fps to handle drop frames without stutter
|
||||
}
|
||||
/* Deinitialize all used systems */
|
||||
unInit();
|
||||
}
|
88
SkyNX/source/network.c
Normal file
88
SkyNX/source/network.c
Normal file
|
@ -0,0 +1,88 @@
|
|||
#include "network.h"
|
||||
#include <libavutil/timestamp.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
void networkInit(const SocketInitConfig *conf)
|
||||
{
|
||||
socketInitialize(conf);
|
||||
nxlinkStdio();
|
||||
avformat_network_init();
|
||||
}
|
||||
|
||||
void networkDestroy()
|
||||
{
|
||||
avformat_network_deinit();
|
||||
socketExit();
|
||||
}
|
||||
|
||||
JoyConSocket *createJoyConSocket()
|
||||
{
|
||||
JoyConSocket *socket = (JoyConSocket *)malloc(sizeof(JoyConSocket));
|
||||
socket->sock = -1;
|
||||
socket->lissock = -1;
|
||||
return socket;
|
||||
}
|
||||
|
||||
void freeJoyConSocket(JoyConSocket *connection)
|
||||
{
|
||||
free(connection);
|
||||
}
|
||||
|
||||
int connectJoyConSocket(JoyConSocket *connection, int port)
|
||||
{
|
||||
if (connection->lissock == -1)
|
||||
{
|
||||
if (connection->sock != -1)
|
||||
{
|
||||
close(connection->sock);
|
||||
connection->sock = -1;
|
||||
}
|
||||
|
||||
struct sockaddr_in server;
|
||||
|
||||
connection->lissock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_addr.s_addr = INADDR_ANY;
|
||||
server.sin_port = htons(port);
|
||||
|
||||
if (bind(connection->lissock, (struct sockaddr *)&server, sizeof(server)) < 0)
|
||||
{
|
||||
close(connection->lissock);
|
||||
connection->lissock = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
listen(connection->lissock, 1);
|
||||
}
|
||||
|
||||
if (connection->sock == -1)
|
||||
{
|
||||
// TODO: We might want to be able to not block if no client is connected
|
||||
struct sockaddr_in client;
|
||||
int c = sizeof(struct sockaddr_in);
|
||||
connection->sock = accept(connection->lissock, (struct sockaddr *)&client, (socklen_t *)&c);
|
||||
if (connection->sock < 0)
|
||||
{
|
||||
close(connection->lissock);
|
||||
connection->sock = -1;
|
||||
connection->lissock = -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void sendJoyConInput(JoyConSocket *connection, const JoyPkg *pkg)
|
||||
{
|
||||
if (send(connection->sock, pkg, sizeof(JoyPkg), 0) != sizeof(JoyPkg))
|
||||
{
|
||||
connection->sock = -1;
|
||||
}
|
||||
}
|
43
SkyNX/source/network.h
Normal file
43
SkyNX/source/network.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#ifndef _NETWORK_H
|
||||
#define _NETWORK_H
|
||||
|
||||
#include <switch.h>
|
||||
#include "context.h"
|
||||
|
||||
#define URL "tcp://0.0.0.0:2222"
|
||||
//#define TCP_RECV_BUFFER "500000"
|
||||
|
||||
/* Data to send to server */
|
||||
typedef struct
|
||||
{
|
||||
unsigned long heldKeys;
|
||||
short lJoyX;
|
||||
short lJoyY;
|
||||
short rJoyX;
|
||||
short rJoyY;
|
||||
short touchX;
|
||||
short touchY;
|
||||
} JoyPkg;
|
||||
|
||||
/* Init nx network and av network */
|
||||
void networkInit(const SocketInitConfig *conf);
|
||||
|
||||
/* Deinitialize nx network and av network*/
|
||||
void networkDestroy();
|
||||
|
||||
/* Creates the context for sending joycon inputs */
|
||||
JoyConSocket *createJoyConSocket();
|
||||
|
||||
/* Deallocate from memory the constext used to sent joycon inputs */
|
||||
void freeJoyConSocket(JoyConSocket *connection);
|
||||
|
||||
/* Send joycon input over the network */
|
||||
void sendJoyConInput(JoyConSocket *connection, const JoyPkg *pkg);
|
||||
|
||||
/*
|
||||
* Binds, listens and accepts connection with the server
|
||||
* If the connection was previously opened reuses it
|
||||
*/
|
||||
int connectJoyConSocket(JoyConSocket *connection, int port);
|
||||
|
||||
#endif
|
520
SkyNX/source/renderer.c
Normal file
520
SkyNX/source/renderer.c
Normal file
|
@ -0,0 +1,520 @@
|
|||
#include "renderer.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <unistd.h>
|
||||
#include "video.h"
|
||||
#include <time.h>
|
||||
float timeThen = 0;
|
||||
float timeNow = 1;
|
||||
float delta = 1;
|
||||
void initDelta()
|
||||
{
|
||||
timeThen = svcGetSystemTick();
|
||||
timeNow = svcGetSystemTick();
|
||||
delta = (timeNow - timeThen) / 10000000;
|
||||
}
|
||||
void loopStart()
|
||||
{
|
||||
timeNow = svcGetSystemTick();
|
||||
delta = (timeNow - timeThen) / 10000000;
|
||||
if (delta > 1)
|
||||
{
|
||||
//to much lag. set to 1
|
||||
delta = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void loopEnd()
|
||||
{
|
||||
timeThen = timeNow;
|
||||
}
|
||||
|
||||
int getRandomInt(int minimum_number, int max_number)
|
||||
{
|
||||
return rand() % (max_number + 1 - minimum_number) + minimum_number;
|
||||
}
|
||||
typedef struct
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float r;
|
||||
float vx;
|
||||
float vy;
|
||||
SDL_Color color;
|
||||
} bubble;
|
||||
typedef struct
|
||||
{
|
||||
bubble a;
|
||||
bubble aIndex;
|
||||
bubble b;
|
||||
bubble bIndex;
|
||||
} bubblePair;
|
||||
typedef struct
|
||||
{
|
||||
float vx;
|
||||
float vy;
|
||||
} vector;
|
||||
vector globalForce = {0, -30};
|
||||
|
||||
int bubblesLength = 0;
|
||||
int maxBubbles = 20;
|
||||
bubble bubbles[20];
|
||||
long getDistance(long ax, long ay, long bx, long by)
|
||||
{
|
||||
long a = ax - bx;
|
||||
long b = ay - by;
|
||||
return sqrt(a * a + b * b);
|
||||
}
|
||||
bubble resolveBubble(bubble b1, bubble b2)
|
||||
{ //Fix colliding circles
|
||||
long distance_x = b1.x - b2.x;
|
||||
long distance_y = b1.y - b2.y;
|
||||
long radii_sum = b1.r + b2.r;
|
||||
long distance = getDistance(b1.x, b1.y, b2.x, b2.y);
|
||||
long unit_x = distance_x / distance;
|
||||
long unit_y = distance_y / distance;
|
||||
|
||||
b1.x = b2.x + (radii_sum)*unit_x; //Uncollide
|
||||
b1.y = b2.y + (radii_sum)*unit_y; //Uncollide
|
||||
//Conservation of momentum
|
||||
long newVelX1 = (b1.vx * (b1.r - b2.r) + (2 * b2.r * b2.vx)) / radii_sum;
|
||||
long newVelY1 = (b1.vy * (b1.r - b2.r) + (2 * b2.r * b2.vy)) / radii_sum;
|
||||
// long newVelX2 = (b2.vx * (b2.r - b1.r) + (2 * b1.r * b1.vx)) / radii_sum;
|
||||
// long newVelY2 = (b2.vy * (b2.r - b1.r) + (2 * b1.r * b1.vy)) / radii_sum;
|
||||
b1.vx = newVelX1;
|
||||
b1.vy = newVelY1;
|
||||
// b2.vx = newVelX2;
|
||||
// b2.vy = newVelY2;
|
||||
bubble newBubble = b1;
|
||||
return newBubble;
|
||||
}
|
||||
bool detectCircleToCircleCollision(bubble b1, bubble b2)
|
||||
{ //check for collision between circles
|
||||
long radii_sum = b1.r + b2.r;
|
||||
long distance = getDistance(b1.x, b1.y, b2.x, b2.y); //If distance is less than radius added together a collision is occuring
|
||||
if (distance < radii_sum)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool testForCollision(bubble b, int bI)
|
||||
{
|
||||
for (int i = 0; i < bubblesLength; i++)
|
||||
{
|
||||
if (i != bI)
|
||||
{
|
||||
if (detectCircleToCircleCollision(b, bubbles[i]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void resolveCollisions()
|
||||
{
|
||||
bubble newBubbles[20];
|
||||
bool aCollided = false;
|
||||
for (int a = 0; a < bubblesLength; a++)
|
||||
{
|
||||
for (int b = a + 1; b < bubblesLength; b++)
|
||||
{
|
||||
|
||||
if (detectCircleToCircleCollision(bubbles[a], bubbles[b]))
|
||||
{
|
||||
aCollided = true;
|
||||
newBubbles[a] = resolveBubble(bubbles[a], bubbles[b]);
|
||||
}
|
||||
}
|
||||
if (!aCollided)
|
||||
{ //If nothing collided keep the same data
|
||||
newBubbles[a] = bubbles[a];
|
||||
}
|
||||
aCollided = false;
|
||||
}
|
||||
for (int i = 0; i < bubblesLength; i++)
|
||||
{
|
||||
bubbles[i] = newBubbles[i];
|
||||
}
|
||||
}
|
||||
void initBubbles()
|
||||
{
|
||||
while (bubblesLength < maxBubbles)
|
||||
{
|
||||
SDL_Color bubbleColor = {126, 242, 213, 255};
|
||||
float randR = 15;
|
||||
if (bubblesLength < 8)
|
||||
{
|
||||
SDL_Color nbc = {145, 255, 249, 120};
|
||||
randR = getRandomInt(5, 20);
|
||||
bubbleColor = nbc;
|
||||
}
|
||||
else if (bubblesLength < 13)
|
||||
{
|
||||
SDL_Color nbc = {75, 219, 211, 180};
|
||||
randR = getRandomInt(25, 40);
|
||||
bubbleColor = nbc;
|
||||
}
|
||||
else if (bubblesLength < 17)
|
||||
{
|
||||
SDL_Color nbc = {24, 161, 153, 200};
|
||||
randR = getRandomInt(45, 60);
|
||||
bubbleColor = nbc;
|
||||
}
|
||||
else if (bubblesLength < 20)
|
||||
{
|
||||
SDL_Color nbc = {0, 102, 96, 230};
|
||||
randR = getRandomInt(65, 80);
|
||||
bubbleColor = nbc;
|
||||
}
|
||||
float randXv = 0;
|
||||
if (getRandomInt(0, 1) == 0)
|
||||
{
|
||||
randXv = getRandomInt(10, 25);
|
||||
}
|
||||
else
|
||||
{
|
||||
randXv = getRandomInt(10, 25) * -1;
|
||||
}
|
||||
float randYv = getRandomInt(20, 50) * -1;
|
||||
bool collides = true;
|
||||
while (collides)
|
||||
{
|
||||
int randX = getRandomInt(0, 1280);
|
||||
int randY = getRandomInt(0, 720);
|
||||
bubble nb = {randX, randY, randR, randXv, randYv, bubbleColor};
|
||||
if (!testForCollision(nb, -1)) //-1 is because bubble has not yet spawned
|
||||
{
|
||||
bubblesLength++;
|
||||
bubbles[bubblesLength] = nb;
|
||||
collides = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
SDL_Texture *logoTexture = NULL;
|
||||
RenderContext *createRenderer()
|
||||
{
|
||||
RenderContext *context = malloc(sizeof(RenderContext));
|
||||
|
||||
context->window = SDL_CreateWindow("sdl2_gles2", 0, 0, RESX, RESY, SDL_WINDOW_FULLSCREEN);
|
||||
if (context->window == NULL)
|
||||
{
|
||||
SDL_Log("SDL_CreateWindow: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
while (1)
|
||||
;
|
||||
}
|
||||
|
||||
context->renderer = SDL_CreateRenderer(context->window, 0, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
if (context->renderer == NULL)
|
||||
{
|
||||
SDL_Log("SDL_CreateRenderer: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
while (1)
|
||||
;
|
||||
}
|
||||
SDL_SetRenderDrawBlendMode(context->renderer, SDL_BLENDMODE_BLEND); //enable transparency
|
||||
|
||||
logoTexture = IMG_LoadTexture(context->renderer, "iconTransparent.png");
|
||||
|
||||
//Create font cache
|
||||
context->yuv_text = SDL_CreateTexture(context->renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, RESX, RESY);
|
||||
|
||||
context->rect.x = 0;
|
||||
context->rect.y = 0;
|
||||
context->rect.w = RESX;
|
||||
context->rect.h = RESY;
|
||||
|
||||
mutexInit(&context->texture_mut);
|
||||
mutexInit(&context->frame_avail_mut);
|
||||
mutexInit(&context->video_active_mut);
|
||||
context->frame_avail = false;
|
||||
context->video_active = false;
|
||||
|
||||
PlFontData fontData, fontExtData;
|
||||
plGetSharedFontByType(&fontData, PlSharedFontType_Standard);
|
||||
plGetSharedFontByType(&fontExtData, PlSharedFontType_NintendoExt);
|
||||
context->font = FC_CreateFont();
|
||||
FC_LoadFont_RW(context->font, context->renderer, SDL_RWFromMem((void *)fontData.address, fontData.size), SDL_RWFromMem((void *)fontExtData.address, fontExtData.size), 1, 40, FC_MakeColor(0, 0, 0, 255), TTF_STYLE_NORMAL);
|
||||
initDelta();
|
||||
initBubbles();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
void setFrameAvail(RenderContext *context)
|
||||
{
|
||||
mutexLock(&context->frame_avail_mut);
|
||||
context->frame_avail = true;
|
||||
mutexUnlock(&context->frame_avail_mut);
|
||||
}
|
||||
|
||||
bool checkFrameAvail(RenderContext *context)
|
||||
{
|
||||
bool ret;
|
||||
mutexLock(&context->frame_avail_mut);
|
||||
ret = context->frame_avail;
|
||||
context->frame_avail = false;
|
||||
mutexUnlock(&context->frame_avail_mut);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool isVideoActive(RenderContext *context)
|
||||
{
|
||||
bool ret;
|
||||
mutexLock(&context->video_active_mut);
|
||||
ret = context->video_active;
|
||||
mutexUnlock(&context->video_active_mut);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void setVideoActive(RenderContext *context, bool active)
|
||||
{
|
||||
mutexLock(&context->video_active_mut);
|
||||
context->video_active = active;
|
||||
mutexUnlock(&context->video_active_mut);
|
||||
}
|
||||
|
||||
void SDL_ClearScreen(RenderContext *context, SDL_Color colour)
|
||||
{
|
||||
SDL_SetRenderDrawColor(context->renderer, colour.r, colour.g, colour.b, colour.a);
|
||||
SDL_RenderClear(context->renderer);
|
||||
}
|
||||
|
||||
void SDL_DrawText(RenderContext *context, int x, int y, SDL_Color colour, const char *text)
|
||||
{
|
||||
FC_DrawColor(context->font, context->renderer, x, y, colour, text);
|
||||
}
|
||||
void draw_rect(RenderContext *context, int x, int y, int w, int h, SDL_Color colour)
|
||||
{
|
||||
SDL_SetRenderDrawColor(context->renderer, colour.r, colour.g, colour.b, colour.a);
|
||||
SDL_Rect r = {x, y, w, h};
|
||||
SDL_RenderFillRect(context->renderer, &r);
|
||||
}
|
||||
void strokeCircle(RenderContext *context, float centreX, float centreY, float radius, SDL_Color colour)
|
||||
{
|
||||
int diameter = (radius * 2);
|
||||
int x = (radius - 1);
|
||||
int y = 0;
|
||||
int tx = 1;
|
||||
int ty = 1;
|
||||
int error = (tx - diameter);
|
||||
SDL_SetRenderDrawColor(context->renderer, colour.r, colour.g, colour.b, colour.a);
|
||||
while (x >= y)
|
||||
{
|
||||
// Each of the following renders an octant of the circle
|
||||
SDL_RenderDrawPoint(context->renderer, centreX + x, centreY - y);
|
||||
SDL_RenderDrawPoint(context->renderer, centreX + x, centreY + y);
|
||||
SDL_RenderDrawPoint(context->renderer, centreX - x, centreY - y);
|
||||
SDL_RenderDrawPoint(context->renderer, centreX - x, centreY + y);
|
||||
SDL_RenderDrawPoint(context->renderer, centreX + y, centreY - x);
|
||||
SDL_RenderDrawPoint(context->renderer, centreX + y, centreY + x);
|
||||
SDL_RenderDrawPoint(context->renderer, centreX - y, centreY - x);
|
||||
SDL_RenderDrawPoint(context->renderer, centreX - y, centreY + x);
|
||||
|
||||
if (error <= 0)
|
||||
{
|
||||
++y;
|
||||
error += ty;
|
||||
ty += 2;
|
||||
}
|
||||
|
||||
if (error > 0)
|
||||
{
|
||||
--x;
|
||||
tx += 2;
|
||||
error += (tx - diameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
void drawCircle(RenderContext *context, float centreX, float centreY, float radius, int lineThicknes, SDL_Color colour)
|
||||
{
|
||||
for (int i = 0; i < lineThicknes; i++)
|
||||
{
|
||||
strokeCircle(context, centreX, centreY, radius - i, colour);
|
||||
}
|
||||
}
|
||||
void fillCircle(RenderContext *context, float centreX, float centreY, float radius, SDL_Color colour)
|
||||
{
|
||||
for (double dy = 1; dy <= radius; dy += 1.0)
|
||||
{
|
||||
double dx = floor(sqrt((2.0 * radius * dy) - (dy * dy)));
|
||||
SDL_SetRenderDrawColor(context->renderer, colour.r, colour.g, colour.b, colour.a);
|
||||
SDL_RenderDrawLine(context->renderer, centreX - dx, centreY + dy - radius, centreX + dx, centreY + dy - radius);
|
||||
SDL_RenderDrawLine(context->renderer, centreX - dx, (centreY + 1) - dy + radius, centreX + dx, (centreY + 1) - dy + radius);
|
||||
}
|
||||
}
|
||||
void drawGradient(RenderContext *context, int x, int y, int w, int h, SDL_Color colourStart, SDL_Color colourEnd, int direction)
|
||||
{
|
||||
int drawLines = 1;
|
||||
switch (direction)
|
||||
{
|
||||
case 1: //Top to bottom gradient
|
||||
drawLines = h;
|
||||
break;
|
||||
case 2: //Bottom to top gradient
|
||||
drawLines = h;
|
||||
break;
|
||||
case 3: //Left to right gradient
|
||||
drawLines = w;
|
||||
break;
|
||||
case 4: //Right to left gradient
|
||||
drawLines = w;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < drawLines; i++)
|
||||
{ //Top to bottom gradient
|
||||
float t = ((float)(i)) / ((float)(drawLines));
|
||||
int r = ((float)colourStart.r) * (1.0f - t) + ((float)colourEnd.r) * t;
|
||||
int g = ((float)colourStart.g) * (1.0f - t) + ((float)colourEnd.g) * t;
|
||||
int b = ((float)colourStart.b) * (1.0f - t) + ((float)colourEnd.b) * t;
|
||||
int a = ((float)colourStart.a) * (1.0f - t) + ((float)colourEnd.a) * t;
|
||||
SDL_Color glc = {r, g, b, a};
|
||||
switch (direction)
|
||||
{
|
||||
case 1:
|
||||
draw_rect(context, x, y + i, w, 1, glc);
|
||||
break;
|
||||
case 2:
|
||||
draw_rect(context, x, (y + h) - i, w, 1, glc);
|
||||
break;
|
||||
case 3:
|
||||
draw_rect(context, x + 1, y, 0, h, glc);
|
||||
break;
|
||||
case 4:
|
||||
draw_rect(context, (x + w) - i, y, 0, h, glc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void renderBubbles(RenderContext *context)
|
||||
{
|
||||
//Buggy right now. I need to understand C a bit better first.
|
||||
//resolveCollisions();
|
||||
for (int i = 0; i < bubblesLength; i++)
|
||||
{
|
||||
bubbles[i].vx += globalForce.vx * delta;
|
||||
bubbles[i].vy += globalForce.vy * delta;
|
||||
bubbles[i].x += bubbles[i].vx * delta;
|
||||
bubbles[i].y += bubbles[i].vy * delta;
|
||||
float negRadius = bubbles[i].r * -1;
|
||||
if (bubbles[i].y < negRadius || bubbles[i].y > 720 + bubbles[i].r + 1) //bubble off top or overflowed to bottom because large delta
|
||||
{
|
||||
|
||||
float randYv = getRandomInt(20, 50) * -1;
|
||||
bubbles[i].vy = randYv;
|
||||
bubbles[i].y = 720 + bubbles[i].r;
|
||||
bool isColliding = true;
|
||||
int attempts = 3;
|
||||
while (isColliding && attempts > 3)
|
||||
{
|
||||
bubbles[i].x = getRandomInt(0, 1280);
|
||||
if (!testForCollision(bubbles[i], i))
|
||||
{
|
||||
isColliding = false;
|
||||
}
|
||||
attempts--;
|
||||
}
|
||||
|
||||
float randXv = 0;
|
||||
if (getRandomInt(0, 1) == 0)
|
||||
{
|
||||
randXv = getRandomInt(10, 25);
|
||||
}
|
||||
else
|
||||
{
|
||||
randXv = getRandomInt(10, 25) * -1;
|
||||
}
|
||||
bubbles[i].vx = randXv;
|
||||
}
|
||||
fillCircle(context, bubbles[i].x, bubbles[i].y, bubbles[i].r, bubbles[i].color);
|
||||
}
|
||||
}
|
||||
void drawSplash(RenderContext *context)
|
||||
{
|
||||
loopStart();
|
||||
SDL_Color bg = {50, 50, 50, 255};
|
||||
SDL_ClearScreen(context, bg);
|
||||
|
||||
SDL_Color bgf = {0, 181, 178, 255};
|
||||
SDL_Color bgt = {0, 41, 40, 255};
|
||||
drawGradient(context, 0, 0, 1280, 720, bgf, bgt, 1);
|
||||
|
||||
renderBubbles(context);
|
||||
|
||||
SDL_Color gf = {0, 0, 0, 250};
|
||||
SDL_Color gt = {0, 0, 0, 0};
|
||||
drawGradient(context, 0, 0, 1280, 180, gf, gt, 1);
|
||||
drawGradient(context, 0, 720 - 100, 1280, 180, gf, gt, 2);
|
||||
|
||||
int imgW = 0;
|
||||
int imgH = 0;
|
||||
SDL_QueryTexture(logoTexture, NULL, NULL, &imgW, &imgH);
|
||||
SDL_Rect imgDest;
|
||||
imgDest.x = (1280 / 2) - (imgW / 2);
|
||||
imgDest.y = (720 / 2) - (imgH / 2);
|
||||
imgDest.w = imgW;
|
||||
imgDest.h = imgH;
|
||||
SDL_RenderCopy(context->renderer, logoTexture, NULL, &imgDest);
|
||||
|
||||
SDL_Color white = {230, 230, 230, 255};
|
||||
u32 ip = gethostid();
|
||||
char str_buf[300];
|
||||
snprintf(str_buf, 300, "IP-Address: %u.%u.%u.%u\n",
|
||||
ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF);
|
||||
|
||||
SDL_DrawText(context, 400, imgDest.y + imgH + 8, white, str_buf);
|
||||
|
||||
SDL_RenderPresent(context->renderer);
|
||||
loopEnd();
|
||||
}
|
||||
|
||||
u64 old_time = 0, new_time = 0;
|
||||
void handleFrame(RenderContext *renderContext, VideoContext *videoContext)
|
||||
{
|
||||
AVFrame *frame = videoContext->frame;
|
||||
|
||||
mutexLock(&renderContext->texture_mut);
|
||||
memcpy(renderContext->YPlane, frame->data[0], sizeof(renderContext->YPlane));
|
||||
memcpy(renderContext->UPlane, frame->data[1], sizeof(renderContext->UPlane));
|
||||
memcpy(renderContext->VPlane, frame->data[2], sizeof(renderContext->VPlane));
|
||||
mutexUnlock(&renderContext->texture_mut);
|
||||
setFrameAvail(renderContext);
|
||||
|
||||
if (++videoContext->video_frame_count % 60 == 0)
|
||||
{
|
||||
new_time = svcGetSystemTick();
|
||||
printf("Framerate: %f\n", 60.0 / ((new_time - old_time) / 19200000.0));
|
||||
old_time = new_time;
|
||||
}
|
||||
}
|
||||
|
||||
void displayFrame(RenderContext *renderContext)
|
||||
{
|
||||
// while (!checkFrameAvail(renderContext))
|
||||
// {
|
||||
// }
|
||||
if (checkFrameAvail(renderContext))
|
||||
{
|
||||
SDL_RenderClear(renderContext->renderer);
|
||||
|
||||
mutexLock(&renderContext->texture_mut);
|
||||
SDL_UpdateYUVTexture(renderContext->yuv_text, &renderContext->rect, renderContext->YPlane, RESX,
|
||||
renderContext->UPlane, RESX / 2,
|
||||
renderContext->VPlane, RESX / 2);
|
||||
mutexUnlock(&renderContext->texture_mut);
|
||||
|
||||
SDL_RenderCopy(renderContext->renderer, renderContext->yuv_text, NULL, NULL);
|
||||
SDL_RenderPresent(renderContext->renderer);
|
||||
}
|
||||
}
|
||||
|
||||
void freeRenderer(RenderContext *context)
|
||||
{
|
||||
free(context);
|
||||
}
|
37
SkyNX/source/renderer.h
Normal file
37
SkyNX/source/renderer.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef _RENDERER_H
|
||||
#define _RENDERER_H
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
|
||||
#include "context.h"
|
||||
|
||||
/* Allocates a render context */
|
||||
RenderContext* createRenderer(void);
|
||||
|
||||
/* Draws an image filling all screen */
|
||||
void drawSplash(RenderContext *context);
|
||||
|
||||
/* Handles a frame received from server */
|
||||
void handleFrame(RenderContext* context, VideoContext* videoContext);
|
||||
|
||||
/* Draws a frame */
|
||||
void displayFrame(RenderContext *renderContext);
|
||||
|
||||
/* Deallocates the render context */
|
||||
void freeRenderer(RenderContext* context);
|
||||
|
||||
/* Sets the variable that indicates that there's a frame ready to be drawn */
|
||||
void setFrameAvail(RenderContext* context);
|
||||
|
||||
/* Checks if a frame is ready to be drawn and sets that variable to false */
|
||||
bool checkFrameAvail(RenderContext* context);
|
||||
|
||||
/* Returns true if there is a video playing right now */
|
||||
bool isVideoActive(RenderContext *context);
|
||||
|
||||
/* Sets the video-playing status */
|
||||
void setVideoActive(RenderContext *context, bool active);
|
||||
|
||||
#endif
|
282
SkyNX/source/video.c
Normal file
282
SkyNX/source/video.c
Normal file
|
@ -0,0 +1,282 @@
|
|||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libavutil/timestamp.h>
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
#include "video.h"
|
||||
#include "network.h"
|
||||
#include "renderer.h"
|
||||
|
||||
VideoContext *createVideoContext()
|
||||
{
|
||||
VideoContext *context = (VideoContext *)malloc(sizeof(VideoContext));
|
||||
context->fmt_ctx = NULL;
|
||||
context->video_dec_ctx = NULL;
|
||||
context->video_stream = NULL;
|
||||
context->video_stream_idx = -1;
|
||||
context->rgbframe = NULL;
|
||||
context->video_frame_count = 0;
|
||||
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
context->video_dst_data[i] = NULL;
|
||||
|
||||
// Frame
|
||||
context->frame = av_frame_alloc();
|
||||
if (context->frame == NULL)
|
||||
{
|
||||
fprintf(stderr, "Could not allocate frame\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// RGBA Frame
|
||||
context->rgbframe = av_frame_alloc();
|
||||
context->rgbframe->width = RESX;
|
||||
context->rgbframe->height = RESY;
|
||||
context->rgbframe->format = AV_PIX_FMT_RGBA;
|
||||
av_image_alloc(context->rgbframe->data,
|
||||
context->rgbframe->linesize,
|
||||
context->rgbframe->width,
|
||||
context->rgbframe->height,
|
||||
context->rgbframe->format, 32);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
void freeVideoContext(VideoContext *context)
|
||||
{
|
||||
avcodec_free_context(&(context->video_dec_ctx));
|
||||
avformat_close_input(&(context->fmt_ctx));
|
||||
av_frame_free(&(context->frame));
|
||||
av_free(context->video_dst_data[0]);
|
||||
free(context);
|
||||
}
|
||||
|
||||
/* Decodes a single frame and returns 0 on success */
|
||||
int decode_frame(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt)
|
||||
{
|
||||
int ret;
|
||||
*got_frame = 0;
|
||||
|
||||
if (pkt)
|
||||
{
|
||||
ret = avcodec_send_packet(avctx, pkt);
|
||||
if (ret < 0)
|
||||
return ret == AVERROR_EOF ? 0 : ret;
|
||||
}
|
||||
|
||||
ret = avcodec_receive_frame(avctx, frame);
|
||||
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
|
||||
return ret;
|
||||
if (ret >= 0)
|
||||
*got_frame = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns 1 if frame format is the same as the AVCodecContext format */
|
||||
int expected_frame_format(AVCodecContext *avctx, AVFrame *frame)
|
||||
{
|
||||
int width = avctx->width;
|
||||
int height = avctx->height;
|
||||
enum AVPixelFormat pix_fmt = avctx->pix_fmt;
|
||||
|
||||
return frame->width == width || frame->height == height || frame->format == pix_fmt;
|
||||
}
|
||||
|
||||
static int decode_packet(VideoContext *context, int *got_frame, AVPacket *pkt)
|
||||
{
|
||||
if (pkt->stream_index == context->video_stream_idx &&
|
||||
decode_frame(context->video_dec_ctx, context->frame, got_frame, pkt) == 0)
|
||||
{
|
||||
if (!expected_frame_format(context->video_dec_ctx, context->frame))
|
||||
{
|
||||
fprintf(stderr, "Error: Width, height and pixel format have to be "
|
||||
"constant in a rawvideo file, but the width, height or "
|
||||
"pixel format of the input video changed:\n"
|
||||
"old: width = %d, height = %d, format = %s\n"
|
||||
"new: width = %d, height = %d, format = %s\n",
|
||||
context->video_dec_ctx->width,
|
||||
context->video_dec_ctx->height,
|
||||
av_get_pix_fmt_name(context->video_dec_ctx->pix_fmt),
|
||||
context->frame->width, context->frame->height,
|
||||
av_get_pix_fmt_name(context->frame->format));
|
||||
return -1;
|
||||
}
|
||||
|
||||
handleFrame(context->renderContext, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Error decoding video frame \n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return context->frame->pkt_size;
|
||||
}
|
||||
|
||||
static int open_codec_context(VideoContext *context, enum AVMediaType type)
|
||||
{
|
||||
int ret, stream_index;
|
||||
AVStream *st;
|
||||
AVCodec *dec = NULL;
|
||||
|
||||
ret = av_find_best_stream(context->fmt_ctx, type, -1, -1, NULL, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Could not find %s stream in input file \n",
|
||||
av_get_media_type_string(type));
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream_index = ret;
|
||||
st = context->fmt_ctx->streams[stream_index];
|
||||
// find decoder for the stream
|
||||
|
||||
dec = avcodec_find_decoder(st->codecpar->codec_id);
|
||||
if (dec == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to find %s codec\n",
|
||||
av_get_media_type_string(type));
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
// Allocate a codec context for the decoder
|
||||
context->video_dec_ctx = avcodec_alloc_context3(dec);
|
||||
if (context->video_dec_ctx == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to allocate the %s codec context\n",
|
||||
av_get_media_type_string(type));
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
/*
|
||||
Copy codec parameters from input stream to output codec context
|
||||
*/
|
||||
|
||||
if ((ret = avcodec_parameters_to_context(context->video_dec_ctx, st->codecpar)) < 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to copy %s codec parameters to decoder context\n",
|
||||
av_get_media_type_string(type));
|
||||
return ret;
|
||||
}
|
||||
//Init the decoders, without reference counting
|
||||
|
||||
if ((ret = avcodec_open2(context->video_dec_ctx, dec, NULL)) < 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to open %s codec\n",
|
||||
av_get_media_type_string(type));
|
||||
return ret;
|
||||
}
|
||||
context->video_stream_idx = stream_index;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void videoLoop(void *context_ptr)
|
||||
{
|
||||
VideoContext* context = (VideoContext*) context_ptr;
|
||||
while(appletMainLoop())
|
||||
handleVid(context);
|
||||
}
|
||||
|
||||
int handleVid(VideoContext *context)
|
||||
{
|
||||
int ret = 0;
|
||||
int got_frame = 0;
|
||||
AVFormatContext *fmt_ctx = NULL;
|
||||
AVPacket pkt;
|
||||
|
||||
// setting TCP input options
|
||||
AVDictionary *opts = 0;
|
||||
av_dict_set(&opts, "listen", "1", 0); // set option for listening
|
||||
av_dict_set(&opts, "probesize", "50000", 0);
|
||||
|
||||
//open input file, and allocate format context
|
||||
ret = avformat_open_input(&fmt_ctx, URL, 0, &opts);
|
||||
if (ret < 0)
|
||||
{
|
||||
char errbuf[100];
|
||||
av_strerror(ret, errbuf, 100);
|
||||
|
||||
fprintf(stderr, "Input Error %s\n", errbuf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
setVideoActive(context->renderContext, true);
|
||||
|
||||
context->fmt_ctx = fmt_ctx;
|
||||
|
||||
// Retrieve stream information
|
||||
if (avformat_find_stream_info(fmt_ctx, NULL) < 0)
|
||||
{
|
||||
fprintf(stderr, "Could not find stream information\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Context for the video
|
||||
if (open_codec_context(context, AVMEDIA_TYPE_VIDEO) >= 0)
|
||||
{
|
||||
context->video_stream = fmt_ctx->streams[context->video_stream_idx];
|
||||
|
||||
// Allocate image where the decoded image will be put
|
||||
ret = av_image_alloc(context->video_dst_data,
|
||||
context->video_dst_linesize,
|
||||
context->video_dec_ctx->width,
|
||||
context->video_dec_ctx->height,
|
||||
context->video_dec_ctx->pix_fmt, 1);
|
||||
if (ret < 0)
|
||||
{
|
||||
char errbuf[100];
|
||||
av_strerror(ret, errbuf, 100);
|
||||
fprintf(stderr, "Could not allocate raw video buffer %s\n", errbuf);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
//dump input information to stderr
|
||||
//av_dump_format(context->fmt_ctx, 0, URL, 0);
|
||||
|
||||
if (context->video_stream == NULL)
|
||||
{
|
||||
fprintf(stderr, "Could not find stream in the input, aborting\n");
|
||||
ret = 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
//initialize packet, set data to NULL, let the demuxer fill it
|
||||
|
||||
av_init_packet(&pkt);
|
||||
pkt.data = NULL;
|
||||
pkt.size = 0;
|
||||
|
||||
//read frames from the file
|
||||
while (av_read_frame(fmt_ctx, &pkt) >= 0)
|
||||
{
|
||||
AVPacket orig_pkt = pkt;
|
||||
do
|
||||
{
|
||||
ret = decode_packet(context, &got_frame, &pkt);
|
||||
if (ret < 0)
|
||||
break;
|
||||
pkt.data += ret;
|
||||
pkt.size -= ret;
|
||||
}
|
||||
while (pkt.size > 0);
|
||||
av_packet_unref(&orig_pkt);
|
||||
}
|
||||
|
||||
//flush cached frames
|
||||
pkt.data = NULL;
|
||||
pkt.size = 0;
|
||||
do
|
||||
{
|
||||
decode_packet(context, &got_frame, &pkt);
|
||||
}
|
||||
while (got_frame);
|
||||
|
||||
printf("Stream finished.\n");
|
||||
checkFrameAvail(context->renderContext);
|
||||
setVideoActive(context->renderContext, false);
|
||||
|
||||
return ret;
|
||||
}
|
17
SkyNX/source/video.h
Normal file
17
SkyNX/source/video.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#ifndef _VIDEO_H
|
||||
#define _VIDEO_H
|
||||
|
||||
#include "context.h"
|
||||
|
||||
/* Allocates a video context and all its av fields (frame, rgbaFrame ...) */
|
||||
VideoContext* createVideoContext(void);
|
||||
|
||||
/* Loop to handle video streaming with the server */
|
||||
int handleVid(VideoContext* context);
|
||||
|
||||
/* Deallocates a video context */
|
||||
void freeVideoContext(VideoContext* context);
|
||||
|
||||
/* Loop for receiving and decoding video */
|
||||
void videoLoop(void *context_ptr);
|
||||
#endif
|
Loading…
Reference in a new issue