From cc792692a1f76c300f552ffe4c92ff4628788b91 Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Sun, 17 May 2015 14:06:33 +0200 Subject: [PATCH 01/10] Add SDL/FFmpeg video output --- CMakeLists.txt | 14 ++++ src/video.c | 4 ++ src/video.h | 3 + src/video/ffmpeg.c | 160 +++++++++++++++++++++++++++++++++++++++++++++ src/video/ffmpeg.h | 39 +++++++++++ src/video/sdl.c | 116 ++++++++++++++++++++++++++++++++ 6 files changed, 336 insertions(+) create mode 100644 src/video/ffmpeg.c create mode 100644 src/video/ffmpeg.h create mode 100644 src/video/sdl.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 877ef0f..74da1be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,10 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(EVDEV REQUIRED libevdev) pkg_check_modules(AVAHI REQUIRED avahi-client) pkg_check_modules(UDEV REQUIRED libudev) +pkg_check_modules(SDL sdl) +pkg_check_modules(AVCODEC libavcodec) +pkg_check_modules(AVUTIL libavutil) +pkg_check_modules(SWSCALE libswscale) if(BROADCOM_FOUND) aux_source_directory(./ilclient SRC_LIST) @@ -39,6 +43,11 @@ if(FREESCALE_FOUND) list(APPEND MOONLIGHT_DEFINITIONS HAVE_IMX) endif() +if (AVCODEC_FOUND AND AVUTIL_FOUND AND SWSCALE_FOUND AND SDL_FOUND) + list(APPEND SRC_LIST ./src/video/ffmpeg.c ./src/video/sdl.c) + list(APPEND MOONLIGHT_DEFINITIONS HAVE_SDL) +endif() + add_executable(moonlight ${SRC_LIST}) set_property(TARGET moonlight PROPERTY C_STANDARD 11) @@ -53,6 +62,11 @@ if(FREESCALE_FOUND) target_link_libraries (moonlight PUBLIC ${FREESCALE_LIBRARIES}) endif() +if (AVCODEC_FOUND AND AVUTIL_FOUND AND SWSCALE_FOUND AND SDL_FOUND) + include_directories(${SDL_INCLUDE_DIRS} ${AVCODEC_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS} ${SWSCALE_INCLUDE_DIRS}) + target_link_libraries(moonlight PUBLIC ${SDL_LIBRARIES} ${AVCODEC_LIBRARIES} ${AVUTIL_LIBRARIES} ${SWSCALE_LIBRARIES}) +endif() + set_property(TARGET moonlight PROPERTY COMPILE_DEFINITIONS ${MOONLIGHT_DEFINITIONS}) include_directories(./moonlight-common-c ${OPUS_INCLUDE_DIRS} ${EVDEV_INCLUDE_DIRS} ${AVAHI_INCLUDE_DIRS} ${UDEV_INCLUDE_DIRS}) target_link_libraries (moonlight PUBLIC ${CMAKE_THREAD_LIBS_INIT} ${CURL_LIBRARIES} ${OPENSSL_LIBRARIES} ${EXPAT_LIBRARIES} ${EVDEV_LIBRARIES} ${ALSA_LIBRARY} ${OPUS_LIBRARY} ${AVAHI_LIBRARIES} ${UDEV_LIBRARIES} ${CMAKE_DL_LIBS}) diff --git a/src/video.c b/src/video.c index 530ebf1..857a945 100644 --- a/src/video.c +++ b/src/video.c @@ -32,7 +32,11 @@ char* decoder_output_name; static int decoder_level; void video_init() { + #ifdef HAVE_SDL + decoder_callbacks = &decoder_callbacks_sdl; + #else decoder_callbacks = &decoder_callbacks_fake; + #endif #ifdef HAVE_OMX if (dlsym(RTLD_DEFAULT, "bcm_host_init") != NULL) { decoder_callbacks = &decoder_callbacks_omx; diff --git a/src/video.h b/src/video.h index bd6ca69..169aa45 100644 --- a/src/video.h +++ b/src/video.h @@ -24,6 +24,9 @@ extern DECODER_RENDERER_CALLBACKS *decoder_callbacks; void video_init(); extern DECODER_RENDERER_CALLBACKS decoder_callbacks_fake; +#ifdef HAVE_SDL +extern DECODER_RENDERER_CALLBACKS decoder_callbacks_sdl; +#endif #ifdef HAVE_OMX extern DECODER_RENDERER_CALLBACKS decoder_callbacks_omx; #endif diff --git a/src/video/ffmpeg.c b/src/video/ffmpeg.c new file mode 100644 index 0000000..29087f5 --- /dev/null +++ b/src/video/ffmpeg.c @@ -0,0 +1,160 @@ +/* + * This file is part of Moonlight Embedded. + * + * Based on Moonlight Pc implementation + * + * Moonlight 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. + * + * Moonlight 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 Moonlight; if not, see . + */ + +#include "ffmpeg.h" + +#include +#include +#include +#include + +// General decoder and renderer state +static AVPacket pkt; +static AVCodec* decoder; +static AVCodecContext* decoder_ctx; +static AVFrame* dec_frame; +static struct SwsContext* scaler_ctx; + +#define BYTES_PER_PIXEL 4 + +// This function must be called before +// any other decoding functions +int ffmpeg_init(int width, int height, int perf_lvl, int thread_count) { + // Initialize the avcodec library and register codecs + av_log_set_level(AV_LOG_QUIET); + avcodec_register_all(); + + av_init_packet(&pkt); + + decoder = avcodec_find_decoder(AV_CODEC_ID_H264); + if (decoder == NULL) { + printf("Couldn't find H264 decoder"); + return -1; + } + + decoder_ctx = avcodec_alloc_context3(decoder); + if (decoder_ctx == NULL) { + printf("Couldn't allocate context"); + return -1; + } + + if (perf_lvl & DISABLE_LOOP_FILTER) + // Skip the loop filter for performance reasons + decoder_ctx->skip_loop_filter = AVDISCARD_ALL; + + if (perf_lvl & LOW_LATENCY_DECODE) + // Use low delay single threaded encoding + decoder_ctx->flags |= CODEC_FLAG_LOW_DELAY; + + if (perf_lvl & SLICE_THREADING) + decoder_ctx->thread_type = FF_THREAD_SLICE; + else + decoder_ctx->thread_type = FF_THREAD_FRAME; + + decoder_ctx->thread_count = thread_count; + + decoder_ctx->width = width; + decoder_ctx->height = height; + decoder_ctx->pix_fmt = PIX_FMT_YUV420P; + + int err = avcodec_open2(decoder_ctx, decoder, NULL); + if (err < 0) { + printf("Couldn't open codec"); + return err; + } + + dec_frame = av_frame_alloc(); + if (dec_frame == NULL) { + printf("Couldn't allocate frame"); + return -1; + } + + int filtering; + if (perf_lvl & FAST_BILINEAR_FILTERING) + filtering = SWS_FAST_BILINEAR; + else if (perf_lvl & BILINEAR_FILTERING) + filtering = SWS_BILINEAR; + else + filtering = SWS_BICUBIC; + + scaler_ctx = sws_getContext(decoder_ctx->width, decoder_ctx->height, decoder_ctx->pix_fmt, decoder_ctx->width, decoder_ctx->height, PIX_FMT_YUV420P, filtering, NULL, NULL, NULL); + if (scaler_ctx == NULL) { + printf("Couldn't get scaler context"); + return -1; + } + + return 0; +} + +// This function must be called after +// decoding is finished +void ffmpeg_destroy(void) { + if (decoder_ctx) { + avcodec_close(decoder_ctx); + av_free(decoder_ctx); + decoder_ctx = NULL; + } + if (scaler_ctx) { + sws_freeContext(scaler_ctx); + scaler_ctx = NULL; + } + if (dec_frame) { + av_frame_free(&dec_frame); + dec_frame = NULL; + } +} + +int ffmpeg_draw_frame(AVPicture pict) { + int err = sws_scale(scaler_ctx, (const uint8_t* const*) dec_frame->data, dec_frame->linesize, 0, decoder_ctx->height, pict.data, pict.linesize); + + if (err != decoder_ctx->height) { + printf("Scaling failed"); + return 0; + } + + return 1; +} + +// packets must be decoded in order +// indata must be inlen + FF_INPUT_BUFFER_PADDING_SIZE in length +int ffmpeg_decode(unsigned char* indata, int inlen) { + int err; + int got_pic = 0; + + pkt.data = indata; + pkt.size = inlen; + + while (pkt.size > 0) { + got_pic = 0; + err = avcodec_decode_video2(decoder_ctx, dec_frame, &got_pic, &pkt); + if (err < 0) { + printf("Decode failed"); + got_pic = 0; + break; + } + + pkt.size -= err; + pkt.data += err; + } + + if (got_pic) + return 1; + + return err < 0 ? err : 0; +} diff --git a/src/video/ffmpeg.h b/src/video/ffmpeg.h new file mode 100644 index 0000000..160eab1 --- /dev/null +++ b/src/video/ffmpeg.h @@ -0,0 +1,39 @@ +/* + * This file is part of Moonlight Embedded. + * + * Based on Moonlight Pc implementation + * + * Moonlight 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. + * + * Moonlight 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 Moonlight; if not, see . + */ + +#include + +// Disables the deblocking filter at the cost of image quality +#define DISABLE_LOOP_FILTER 0x1 +// Uses the low latency decode flag (disables multithreading) +#define LOW_LATENCY_DECODE 0x2 +// Threads process each slice, rather than each frame +#define SLICE_THREADING 0x4 +// Uses nonstandard speedup tricks +#define FAST_DECODE 0x8 +// Uses bilinear filtering instead of bicubic +#define BILINEAR_FILTERING 0x10 +// Uses a faster bilinear filtering with lower image quality +#define FAST_BILINEAR_FILTERING 0x20 + +int ffmpeg_init(int width, int height, int perf_lvl, int thread_count); +void ffmpeg_destroy(void); + +int ffmpeg_draw_frame(AVPicture pict); +int ffmpeg_decode(unsigned char* indata, int inlen); diff --git a/src/video/sdl.c b/src/video/sdl.c new file mode 100644 index 0000000..73df010 --- /dev/null +++ b/src/video/sdl.c @@ -0,0 +1,116 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2015 Iwan Timmer + * + * Moonlight 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. + * + * Moonlight 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 Moonlight; if not, see . + */ + +#include "ffmpeg.h" + +#include "limelight-common/Limelight.h" + +#include +#include + +#define DECODER_BUFFER_SIZE 92*1024 + +static SDL_Surface *screen; +static SDL_Overlay *bmp = NULL; +static int screen_width, screen_height; +static char* ffmpeg_buffer; + +static void sdl_setup(int width, int height, int redrawRate, void* context, int drFlags) { + if(SDL_Init(SDL_INIT_VIDEO)) { + fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError()); + exit(1); + } + + screen = SDL_SetVideoMode(width, height, 0, 0); + if(!screen) { + fprintf(stderr, "SDL: could not set video mode - exiting\n"); + exit(1); + } + + bmp = SDL_CreateYUVOverlay(width, height, SDL_YV12_OVERLAY, screen); + + int avc_flags = FAST_BILINEAR_FILTERING; + if (ffmpeg_init(width, height, 2, avc_flags) < 0) { + fprintf(stderr, "Couldn't initialize video decoding\n"); + exit(1); + } + + ffmpeg_buffer = malloc(DECODER_BUFFER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE); + if (ffmpeg_buffer == NULL) { + fprintf(stderr, "Not enough memory\n"); + exit(1); + } + + screen_width = width; + screen_height = height; +} + +static void sdl_release() { + ffmpeg_destroy(); +} + +static int sdl_submit_decode_unit(PDECODE_UNIT decodeUnit) { + if (decodeUnit->fullLength < DECODER_BUFFER_SIZE) { + PLENTRY entry = decodeUnit->bufferList; + int length = 0; + while (entry != NULL) { + memcpy(ffmpeg_buffer+length, entry->data, entry->length); + length += entry->length; + entry = entry->next; + } + + int ret = ffmpeg_decode(ffmpeg_buffer, length); + if (ret == 1) { + SDL_LockYUVOverlay(bmp); + + AVPicture pict; + pict.data[0] = bmp->pixels[0]; + pict.data[1] = bmp->pixels[2]; + pict.data[2] = bmp->pixels[1]; + + pict.linesize[0] = bmp->pitches[0]; + pict.linesize[1] = bmp->pitches[2]; + pict.linesize[2] = bmp->pitches[1]; + + ffmpeg_draw_frame(pict); + + SDL_UnlockYUVOverlay(bmp); + + SDL_Rect rect; + rect.x = 0; + rect.y = 0; + rect.w = screen_width; + rect.h = screen_height; + SDL_DisplayYUVOverlay(bmp, &rect); + } + } else { + fprintf(stderr, "Video decode buffer too small"); + exit(1); + } + + return DR_OK; +} + +DECODER_RENDERER_CALLBACKS decoder_callbacks_sdl = { + .setup = sdl_setup, + .start = NULL, + .stop = NULL, + .release = sdl_release, + .submitDecodeUnit = sdl_submit_decode_unit, +}; From 7ee58c820983045478fbe9539d707cec2a5a39b2 Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Sat, 27 Jun 2015 13:42:14 +0200 Subject: [PATCH 02/10] Convert to SDL2 --- CMakeLists.txt | 2 +- src/video/ffmpeg.c | 15 ++++++---- src/video/ffmpeg.h | 3 +- src/video/sdl.c | 71 +++++++++++++++++++++++----------------------- 4 files changed, 49 insertions(+), 42 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dee9db7..b8f4993 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(EVDEV REQUIRED libevdev) pkg_check_modules(AVAHI REQUIRED avahi-client) pkg_check_modules(UDEV REQUIRED libudev) -pkg_check_modules(SDL sdl) +pkg_check_modules(SDL sdl2) pkg_check_modules(AVCODEC libavcodec) pkg_check_modules(AVUTIL libavutil) pkg_check_modules(SWSCALE libswscale) diff --git a/src/video/ffmpeg.c b/src/video/ffmpeg.c index 29087f5..cfd6f35 100644 --- a/src/video/ffmpeg.c +++ b/src/video/ffmpeg.c @@ -120,17 +120,21 @@ void ffmpeg_destroy(void) { } } -int ffmpeg_draw_frame(AVPicture pict) { - int err = sws_scale(scaler_ctx, (const uint8_t* const*) dec_frame->data, dec_frame->linesize, 0, decoder_ctx->height, pict.data, pict.linesize); +int ffmpeg_draw_frame(AVFrame *pict) { + int err = sws_scale(scaler_ctx, (const uint8_t* const*) dec_frame->data, dec_frame->linesize, 0, decoder_ctx->height, pict->data, pict->linesize); if (err != decoder_ctx->height) { - printf("Scaling failed"); + fprintf(stderr, "Scaling failed\n"); return 0; } return 1; } +AVFrame* ffmpeg_get_frame() { + return dec_frame; +} + // packets must be decoded in order // indata must be inlen + FF_INPUT_BUFFER_PADDING_SIZE in length int ffmpeg_decode(unsigned char* indata, int inlen) { @@ -144,7 +148,7 @@ int ffmpeg_decode(unsigned char* indata, int inlen) { got_pic = 0; err = avcodec_decode_video2(decoder_ctx, dec_frame, &got_pic, &pkt); if (err < 0) { - printf("Decode failed"); + fprintf(stderr, "Decode failed\n"); got_pic = 0; break; } @@ -153,8 +157,9 @@ int ffmpeg_decode(unsigned char* indata, int inlen) { pkt.data += err; } - if (got_pic) + if (got_pic) { return 1; + } return err < 0 ? err : 0; } diff --git a/src/video/ffmpeg.h b/src/video/ffmpeg.h index 160eab1..0afefca 100644 --- a/src/video/ffmpeg.h +++ b/src/video/ffmpeg.h @@ -35,5 +35,6 @@ int ffmpeg_init(int width, int height, int perf_lvl, int thread_count); void ffmpeg_destroy(void); -int ffmpeg_draw_frame(AVPicture pict); +int ffmpeg_draw_frame(AVFrame *pict); +AVFrame* ffmpeg_get_frame(); int ffmpeg_decode(unsigned char* indata, int inlen); diff --git a/src/video/sdl.c b/src/video/sdl.c index c00308e..b33b711 100644 --- a/src/video/sdl.c +++ b/src/video/sdl.c @@ -26,25 +26,13 @@ #define DECODER_BUFFER_SIZE 92*1024 -static SDL_Surface *screen; -static SDL_Overlay *bmp = NULL; +static SDL_Window *window; +static SDL_Renderer *renderer; +static SDL_Texture *bmp = NULL; static int screen_width, screen_height; static char* ffmpeg_buffer; static void sdl_setup(int width, int height, int redrawRate, void* context, int drFlags) { - if(SDL_Init(SDL_INIT_VIDEO)) { - fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError()); - exit(1); - } - - screen = SDL_SetVideoMode(width, height, 0, 0); - if(!screen) { - fprintf(stderr, "SDL: could not set video mode - exiting\n"); - exit(1); - } - - bmp = SDL_CreateYUVOverlay(width, height, SDL_YV12_OVERLAY, screen); - int avc_flags = FAST_BILINEAR_FILTERING; if (ffmpeg_init(width, height, 2, avc_flags) < 0) { fprintf(stderr, "Couldn't initialize video decoding\n"); @@ -66,6 +54,31 @@ static void sdl_cleanup() { } static int sdl_submit_decode_unit(PDECODE_UNIT decodeUnit) { + if (window == NULL) { + if(SDL_Init(SDL_INIT_VIDEO)) { + fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError()); + exit(1); + } + + window = SDL_CreateWindow("Moonlight", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_width, screen_height, 0); + if(!window) { + fprintf(stderr, "SDL: could not create window - exiting\n"); + exit(1); + } + + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_TARGETTEXTURE); + if (!renderer) { + fprintf(stderr, "SDL: could not create renderer - exiting\n"); + exit(1); + } + + bmp = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_TARGET, screen_width, screen_height); + if (!bmp) { + fprintf(stderr, "SDL: could not create texture - exiting\n"); + exit(1); + } + } + if (decodeUnit->fullLength < DECODER_BUFFER_SIZE) { PLENTRY entry = decodeUnit->bufferList; int length = 0; @@ -77,32 +90,20 @@ static int sdl_submit_decode_unit(PDECODE_UNIT decodeUnit) { int ret = ffmpeg_decode(ffmpeg_buffer, length); if (ret == 1) { - SDL_LockYUVOverlay(bmp); + AVFrame* frame = ffmpeg_get_frame(); - AVPicture pict; - pict.data[0] = bmp->pixels[0]; - pict.data[1] = bmp->pixels[2]; - pict.data[2] = bmp->pixels[1]; - - pict.linesize[0] = bmp->pitches[0]; - pict.linesize[1] = bmp->pitches[2]; - pict.linesize[2] = bmp->pitches[1]; - - ffmpeg_draw_frame(pict); - - SDL_UnlockYUVOverlay(bmp); - - SDL_Rect rect; - rect.x = 0; - rect.y = 0; - rect.w = screen_width; - rect.h = screen_height; - SDL_DisplayYUVOverlay(bmp, &rect); + SDL_UpdateYUVTexture(bmp, NULL, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, bmp, NULL, NULL); + SDL_RenderPresent(renderer); } } else { fprintf(stderr, "Video decode buffer too small"); exit(1); } + + SDL_Event event; + SDL_PollEvent(&event); return DR_OK; } From 82d1ee1984eb3b60ebf5405ff1b52196e21b04b3 Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Sun, 12 Jul 2015 12:12:49 +0200 Subject: [PATCH 03/10] Initialize inputs only when necessary --- src/main.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index fd5225e..c4c6123 100644 --- a/src/main.c +++ b/src/main.c @@ -46,6 +46,13 @@ #define MOONLIGHT_PATH "/moonlight/" #define USER_PATHS ":~/.moonlight:./" +#define MAX_INPUTS 6 + +struct input_config { + char* path; + char* mapping; +}; + static void applist(const char* address) { struct app_list* list = client_applist(address); for (int i = 1;list != NULL;i++) { @@ -173,6 +180,8 @@ int main(int argc, char* argv[]) { {0, 0, 0, 0}, }; + struct input_config inputs[MAX_INPUTS]; + int inputsCount = 0; char* app = "Steam"; char* action = NULL; char* address = NULL; @@ -214,7 +223,13 @@ int main(int argc, char* argv[]) { app = optarg; break; case 'j': - evdev_create(optarg, mapping); + if (inputsCount >= MAX_INPUTS) { + perror("Too many inputs specified"); + exit(-1); + } + inputs[inputsCount].path = optarg; + inputs[inputsCount].mapping = optarg; + inputsCount++; autoadd = false; break; case 'k': @@ -249,6 +264,9 @@ int main(int argc, char* argv[]) { exit(-1); } udev_init(autoadd, mapping); + for (int i=0;i Date: Sun, 12 Jul 2015 13:26:05 +0200 Subject: [PATCH 04/10] Platform can be specified as argument --- src/main.c | 24 +++++++++---- src/platform.c | 70 +++++++++++++++++++++++++++++++++++++ src/{video.c => platform.h} | 30 ++-------------- src/video.h | 6 ---- 4 files changed, 91 insertions(+), 39 deletions(-) create mode 100644 src/platform.c rename src/{video.c => platform.h} (56%) diff --git a/src/main.c b/src/main.c index c4c6123..912978e 100644 --- a/src/main.c +++ b/src/main.c @@ -23,6 +23,7 @@ #include "video.h" #include "audio.h" #include "discover.h" +#include "platform.h" #include "input/evdev.h" #include "input/udev.h" @@ -61,7 +62,7 @@ static void applist(const char* address) { } } -static void stream(STREAM_CONFIGURATION* config, const char* address, const char* app, bool sops, bool localaudio) { +static void stream(STREAM_CONFIGURATION* config, const char* address, const char* app, bool sops, bool localaudio, enum platform system) { int appId = client_get_app_id(address, app); if (appId<0) { fprintf(stderr, "Can't find app %s\n", app); @@ -70,13 +71,12 @@ static void stream(STREAM_CONFIGURATION* config, const char* address, const char client_start_app(config, address, appId, sops, localaudio); - video_init(); evdev_init(); #ifdef HAVE_LIBCEC cec_init(); #endif /* HAVE_LIBCEC */ - LiStartConnection(address, config, &connection_callbacks, decoder_callbacks, &audio_callbacks, NULL, NULL, 0, client_get_server_version()); + LiStartConnection(address, config, &connection_callbacks, platform_get_video(system), &audio_callbacks, NULL, NULL, 0, client_get_server_version()); loop_main(); @@ -177,11 +177,13 @@ int main(int argc, char* argv[]) { {"nosops", no_argument, 0, 'l'}, {"audio", required_argument, 0, 'm'}, {"localaudio", no_argument, 0, 'n'}, + {"platform", required_argument, 0, 'o'}, {0, 0, 0, 0}, }; struct input_config inputs[MAX_INPUTS]; int inputsCount = 0; + char* platform = "default"; char* app = "Steam"; char* action = NULL; char* address = NULL; @@ -191,7 +193,7 @@ int main(int argc, char* argv[]) { bool localaudio = false; bool autoadd = true; int c; - while ((c = getopt_long_only(argc, argv, "-abc:d:efg:h:i:j:k:lm:n", long_options, &option_index)) != -1) { + while ((c = getopt_long_only(argc, argv, "-abc:d:efg:h:i:j:k:lm:no:", long_options, &option_index)) != -1) { switch (c) { case 'a': config.width = 1280; @@ -244,6 +246,9 @@ int main(int argc, char* argv[]) { case 'n': localaudio = true; break; + case 'o': + platform = optarg; + break; case 1: if (action == NULL) action = optarg; @@ -258,7 +263,14 @@ int main(int argc, char* argv[]) { if (action == NULL || strcmp("help", action) == 0) help(); - else if (strcmp("map", action) == 0) { + + enum platform system = platform_check(platform); + if (system != 0) { + fprintf(stderr, "Platform '%s' not found\n", platform); + exit(-1); + } + + if (strcmp("map", action) == 0) { if (address == NULL) { perror("No filename for mapping"); exit(-1); @@ -296,7 +308,7 @@ int main(int argc, char* argv[]) { for (int i=0;i. + */ + +#define _GNU_SOURCE + +#include "platform.h" +#include "video.h" + +#include +#include +#include +#include + +enum platform platform_check(char* name) { + bool std = strcmp(name, "default") == 0; + #ifdef HAVE_SDL + if (std || strcmp(name, "sdl") == 0) + return SDL; + #endif + #ifdef HAVE_IMX + if (std || strcmp(name, "imx") == 0) { + if (dlsym(RTLD_DEFAULT, "vpu_Init") != NULL && video_imx_init()) + return IMX; + } + #endif + #ifdef HAVE_OMX + if (std || strcmp(name, "omx") == 0) { + if (dlsym(RTLD_DEFAULT, "bcm_host_init") != NULL) + return OMX; + } + #endif + if (std || strcmp(name, "fake") == 0) + return FAKE; +} + +DECODER_RENDERER_CALLBACKS* platform_get_video(enum platform system) { + switch (system) { + #ifdef HAVE_SDL + case SDL: + return &decoder_callbacks_sdl; + #endif + #ifdef HAVE_IMX + case IMX: + return &decoder_callbacks_imx; + #endif + #ifdef HAVE_OMX + case OMX: + return &decoder_callbacks_omx; + #endif + case FAKE: + return &decoder_callbacks_fake; + } + return NULL; +} diff --git a/src/video.c b/src/platform.h similarity index 56% rename from src/video.c rename to src/platform.h index 44e6f31..f152118 100644 --- a/src/video.c +++ b/src/platform.h @@ -17,33 +17,9 @@ * along with Moonlight; if not, see . */ -#define _GNU_SOURCE - -#include "video.h" - #include "limelight-common/Limelight.h" -#include -#include +enum platform { SDL, OMX, IMX, FAKE }; -DECODER_RENDERER_CALLBACKS *decoder_callbacks; - -static int decoder_level; - -void video_init() { - #ifdef HAVE_SDL - decoder_callbacks = &decoder_callbacks_sdl; - #else - decoder_callbacks = &decoder_callbacks_fake; - #endif - #ifdef HAVE_IMX - if (dlsym(RTLD_DEFAULT, "vpu_Init") != NULL && video_imx_init()) { - decoder_callbacks = &decoder_callbacks_imx; - } - #endif - #ifdef HAVE_OMX - if (dlsym(RTLD_DEFAULT, "bcm_host_init") != NULL) { - decoder_callbacks = &decoder_callbacks_omx; - } - #endif -} +enum platform platform_check(char*); +DECODER_RENDERER_CALLBACKS* platform_get_video(enum platform system); diff --git a/src/video.h b/src/video.h index b55aecf..89dac68 100644 --- a/src/video.h +++ b/src/video.h @@ -19,12 +19,6 @@ #include "limelight-common/Limelight.h" -#include - -extern DECODER_RENDERER_CALLBACKS *decoder_callbacks; - -void video_init(); - extern DECODER_RENDERER_CALLBACKS decoder_callbacks_fake; #ifdef HAVE_SDL extern DECODER_RENDERER_CALLBACKS decoder_callbacks_sdl; From 676f39e1971cad6d3885d318a874bf53c1d6663c Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Sun, 12 Jul 2015 13:43:01 +0200 Subject: [PATCH 05/10] Seperate main loop for SDL --- src/main.c | 26 +++++++++++++++++--------- src/platform.h | 6 ++++++ src/sdl.c | 38 ++++++++++++++++++++++++++++++++++++++ src/sdl.h | 24 ++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 src/sdl.c create mode 100644 src/sdl.h diff --git a/src/main.c b/src/main.c index 912978e..0dd46ff 100644 --- a/src/main.c +++ b/src/main.c @@ -24,6 +24,7 @@ #include "audio.h" #include "discover.h" #include "platform.h" +#include "sdl.h" #include "input/evdev.h" #include "input/udev.h" @@ -71,14 +72,14 @@ static void stream(STREAM_CONFIGURATION* config, const char* address, const char client_start_app(config, address, appId, sops, localaudio); - evdev_init(); - #ifdef HAVE_LIBCEC - cec_init(); - #endif /* HAVE_LIBCEC */ - LiStartConnection(address, config, &connection_callbacks, platform_get_video(system), &audio_callbacks, NULL, NULL, 0, client_get_server_version()); - loop_main(); + if (IS_EMBEDDED(system)) + loop_main(); + #ifdef HAVE_SDL + else if (system == SDL) + sdl_loop(); + #endif LiStopConnection(); } @@ -303,10 +304,17 @@ int main(int argc, char* argv[]) { pair_check(); applist(address); } else if (strcmp("stream", action) == 0) { - udev_init(autoadd, mapping); pair_check(); - for (int i=0;i. + */ + +#ifdef HAVE_SDL + +#include +#include + +static bool done; + +void sdl_loop() { + SDL_Event event; + + while(!done && SDL_WaitEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + done = true; + } + } +} + +#endif /* HAVE_SDL */ diff --git a/src/sdl.h b/src/sdl.h new file mode 100644 index 0000000..477bacf --- /dev/null +++ b/src/sdl.h @@ -0,0 +1,24 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2015 Iwan Timmer + * + * Moonlight 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. + * + * Moonlight 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 Moonlight; if not, see . + */ + +#ifdef HAVE_SDL + +void sdl_loop(); + +#endif /* HAVE_SDL */ From a92d215151bb6b88b29a70aa5da38d3009da198e Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Sun, 12 Jul 2015 14:04:10 +0200 Subject: [PATCH 06/10] Mouse support for SDL --- src/sdl.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/sdl.c b/src/sdl.c index 47f0b0b..606eb49 100644 --- a/src/sdl.c +++ b/src/sdl.c @@ -19,16 +19,46 @@ #ifdef HAVE_SDL +#include "limelight-common/Limelight.h" + #include #include static bool done; void sdl_loop() { + SDL_SetRelativeMouseMode(SDL_TRUE); + SDL_ShowCursor(SDL_DISABLE); + SDL_Event event; while(!done && SDL_WaitEvent(&event)) { + int button = 0; switch (event.type) { + case SDL_MOUSEMOTION: + LiSendMouseMoveEvent(event.motion.xrel, event.motion.yrel); + break; + case SDL_MOUSEWHEEL: + LiSendScrollEvent(event.wheel.y); + break; + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEBUTTONDOWN: + switch (event.button.button) { + case SDL_BUTTON_LEFT: + button = BUTTON_LEFT; + break; + case SDL_BUTTON_MIDDLE: + button = BUTTON_MIDDLE; + break; + case SDL_BUTTON_RIGHT: + button = BUTTON_RIGHT; + break; + } + + if (button != 0) + LiSendMouseButtonEvent(event.button.state==SDL_PRESSED?BUTTON_ACTION_PRESS:BUTTON_ACTION_RELEASE, button); + + break; case SDL_QUIT: done = true; } From be831308207bfa056f900a094a9ce71e976e5b3d Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Sun, 12 Jul 2015 18:22:30 +0200 Subject: [PATCH 07/10] Simple keyboard support for SDL --- src/sdl.c | 14 +++++++++++++- src/sdl.h | 28 ++++++++++++++++++++++++++++ src/video/sdl.c | 3 --- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/sdl.c b/src/sdl.c index 606eb49..390e243 100644 --- a/src/sdl.c +++ b/src/sdl.c @@ -19,6 +19,8 @@ #ifdef HAVE_SDL +#include "sdl.h" + #include "limelight-common/Limelight.h" #include @@ -56,9 +58,19 @@ void sdl_loop() { } if (button != 0) - LiSendMouseButtonEvent(event.button.state==SDL_PRESSED?BUTTON_ACTION_PRESS:BUTTON_ACTION_RELEASE, button); + LiSendMouseButtonEvent(event.type==SDL_MOUSEBUTTONDOWN?BUTTON_ACTION_PRESS:BUTTON_ACTION_RELEASE, button); break; + case SDL_KEYDOWN: + case SDL_KEYUP: + button = event.key.keysym.sym; + if (button >= (0x40000000 + 0x39) && button < (0x40000000 + sizeof(keyCodes))) + button = keyCodes[button - 0x40000039]; + if (button >= 0x61) + button -= 0x20; + + LiSendKeyboardEvent(0x80 << 8 | button, event.type==SDL_KEYDOWN?KEY_ACTION_DOWN:KEY_ACTION_UP, 0); + break; case SDL_QUIT: done = true; } diff --git a/src/sdl.h b/src/sdl.h index 477bacf..010a750 100644 --- a/src/sdl.h +++ b/src/sdl.h @@ -19,6 +19,34 @@ #ifdef HAVE_SDL +static const short keyCodes[] = { + 0x14, //SDLK_CAPSLOCK + 0x70, //SDLK_F1 + 0x71, //SDLK_F2 + 0x72, //SDLK_F3 + 0x73, //SDLK_F4 + 0x74, //SDLK_F5 + 0x75, //SDLK_F6 + 0x76, //SDLK_F7 + 0x77, //SDLK_F8 + 0x78, //SDLK_F9 + 0x79, //SDLK_F10 + 0x7A, //SDLK_F11 + 0x7B, //SDLK_F12 + 0, //SDLK_PRINTSCREEN + 0x91, //SDLK_SCROLLLOCK + 0x13, //SDLK_PAUSE + 0x9B, //SDLK_INSERT + 0x24, //SDLK_HOME + 0x21, //SDLK_PAGEUP + 0x23, //SDLK_END + 0x22, //SDLK_PAGEDOWN + 0x27, //SDLK_RIGHT + 0x25, //SDLK_LEFT + 0x28, //SDLK_DOWN + 0x26, //SDLK_UP +}; + void sdl_loop(); #endif /* HAVE_SDL */ diff --git a/src/video/sdl.c b/src/video/sdl.c index b33b711..d2c82d5 100644 --- a/src/video/sdl.c +++ b/src/video/sdl.c @@ -101,9 +101,6 @@ static int sdl_submit_decode_unit(PDECODE_UNIT decodeUnit) { fprintf(stderr, "Video decode buffer too small"); exit(1); } - - SDL_Event event; - SDL_PollEvent(&event); return DR_OK; } From f167c6febbdfa0e66dcb0432f21182641f27ded4 Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Sun, 12 Jul 2015 21:04:02 +0200 Subject: [PATCH 08/10] Support single gamepad in SDL --- src/sdl.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/sdl.c b/src/sdl.c index 390e243..dc43a5f 100644 --- a/src/sdl.c +++ b/src/sdl.c @@ -28,10 +28,25 @@ static bool done; +char leftTrigger, rightTrigger; +short leftStickX, leftStickY; +short rightStickX, rightStickY; +int buttons; + void sdl_loop() { SDL_SetRelativeMouseMode(SDL_TRUE); + SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); + SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt"); SDL_ShowCursor(SDL_DISABLE); + for (int i = 0; i < SDL_NumJoysticks(); ++i) { + if (SDL_IsGameController(i)) { + if (!SDL_GameControllerOpen(i)) { + fprintf(stderr, "Could not open gamecontroller %i: %s\n", i, SDL_GetError()); + } + } + } + SDL_Event event; while(!done && SDL_WaitEvent(&event)) { @@ -71,6 +86,89 @@ void sdl_loop() { LiSendKeyboardEvent(0x80 << 8 | button, event.type==SDL_KEYDOWN?KEY_ACTION_DOWN:KEY_ACTION_UP, 0); break; + case SDL_CONTROLLERAXISMOTION: + switch (event.caxis.axis) { + case SDL_CONTROLLER_AXIS_LEFTX: + leftStickX = event.caxis.value; + break; + case SDL_CONTROLLER_AXIS_LEFTY: + leftStickY = -event.caxis.value - 1; + break; + case SDL_CONTROLLER_AXIS_RIGHTX: + rightStickX = event.caxis.value; + break; + case SDL_CONTROLLER_AXIS_RIGHTY: + rightStickY = -event.caxis.value - 1; + break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + leftTrigger = (event.caxis.value >> 8) + 127; + break; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + rightTrigger = (event.caxis.value >> 8) + 127; + break; + default: + continue; + } + LiSendControllerEvent(0, leftTrigger, rightTrigger, leftStickX, leftStickY, rightStickX, rightStickY); + break; + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + switch (event.cbutton.button) { + case SDL_CONTROLLER_BUTTON_A: + button = A_FLAG; + break; + case SDL_CONTROLLER_BUTTON_B: + button = B_FLAG; + break; + case SDL_CONTROLLER_BUTTON_Y: + button = Y_FLAG; + break; + case SDL_CONTROLLER_BUTTON_X: + button = X_FLAG; + break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + button = UP_FLAG; + break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + button = DOWN_FLAG; + break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + button = RIGHT_FLAG; + break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + button = LEFT_FLAG; + break; + case SDL_CONTROLLER_BUTTON_BACK: + button = BACK_FLAG; + break; + case SDL_CONTROLLER_BUTTON_START: + button = PLAY_FLAG; + break; + case SDL_CONTROLLER_BUTTON_GUIDE: + button = SPECIAL_FLAG; + break; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + button = LS_CLK_FLAG; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + button = RS_CLK_FLAG; + break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + button = LB_FLAG; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + button = RB_FLAG; + break; + default: + continue; + } + if (event.type == SDL_CONTROLLERBUTTONDOWN) + buttons |= button; + else + buttons &= ~button; + + LiSendControllerEvent(buttons, leftTrigger, rightTrigger, leftStickX, leftStickY, rightStickX, rightStickY); + break; case SDL_QUIT: done = true; } From 2ba484056994998eaba7d76eb521d81b475fecd7 Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Sun, 12 Jul 2015 21:31:53 +0200 Subject: [PATCH 09/10] Move audio code to seperate directory --- CMakeLists.txt | 2 ++ src/audio.h | 2 +- src/{audio.c => audio/alsa.c} | 16 ++++++++-------- src/main.c | 2 +- src/platform.c | 8 ++++++++ 5 files changed, 20 insertions(+), 10 deletions(-) rename src/{audio.c => audio/alsa.c} (90%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce8d55b..afdfccc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,8 @@ if (AVCODEC_FOUND AND AVUTIL_FOUND AND SWSCALE_FOUND AND SDL_FOUND) list(APPEND MOONLIGHT_DEFINITIONS HAVE_SDL) endif() +list(APPEND SRC_LIST ./src/audio/alsa.c) + add_subdirectory(libgamestream) add_executable(moonlight ${SRC_LIST}) diff --git a/src/audio.h b/src/audio.h index 476910a..421068a 100644 --- a/src/audio.h +++ b/src/audio.h @@ -21,4 +21,4 @@ extern const char* audio_device; -extern AUDIO_RENDERER_CALLBACKS audio_callbacks; +extern AUDIO_RENDERER_CALLBACKS audio_callbacks_alsa; diff --git a/src/audio.c b/src/audio/alsa.c similarity index 90% rename from src/audio.c rename to src/audio/alsa.c index eaddf26..192d3b6 100644 --- a/src/audio.c +++ b/src/audio/alsa.c @@ -17,7 +17,7 @@ * along with Moonlight; if not, see . */ -#include "audio.h" +#include "../audio.h" #include #include @@ -35,7 +35,7 @@ static snd_pcm_t *handle; static OpusDecoder* decoder; static short pcmBuffer[FRAME_SIZE * CHANNEL_COUNT]; -static void audio_renderer_init() { +static void alsa_renderer_init() { int rc; decoder = opus_decoder_create(SAMPLE_RATE, CHANNEL_COUNT, &rc); @@ -71,7 +71,7 @@ static void audio_renderer_init() { CHECK_RETURN(snd_pcm_prepare(handle)); } -static void audio_renderer_cleanup() { +static void alsa_renderer_cleanup() { if (decoder != NULL) opus_decoder_destroy(decoder); @@ -81,7 +81,7 @@ static void audio_renderer_cleanup() { } } -static void audio_renderer_decode_and_play_sample(char* data, int length) { +static void alsa_renderer_decode_and_play_sample(char* data, int length) { int decodeLen = opus_decode(decoder, data, length, pcmBuffer, FRAME_SIZE, 0); if (decodeLen > 0) { int rc = snd_pcm_writei(handle, pcmBuffer, decodeLen); @@ -97,8 +97,8 @@ static void audio_renderer_decode_and_play_sample(char* data, int length) { } } -AUDIO_RENDERER_CALLBACKS audio_callbacks = { - .init = audio_renderer_init, - .cleanup = audio_renderer_cleanup, - .decodeAndPlaySample = audio_renderer_decode_and_play_sample, +AUDIO_RENDERER_CALLBACKS audio_callbacks_alsa = { + .init = alsa_renderer_init, + .cleanup = alsa_renderer_cleanup, + .decodeAndPlaySample = alsa_renderer_decode_and_play_sample, }; diff --git a/src/main.c b/src/main.c index 0dd46ff..28fe32e 100644 --- a/src/main.c +++ b/src/main.c @@ -72,7 +72,7 @@ static void stream(STREAM_CONFIGURATION* config, const char* address, const char client_start_app(config, address, appId, sops, localaudio); - LiStartConnection(address, config, &connection_callbacks, platform_get_video(system), &audio_callbacks, NULL, NULL, 0, client_get_server_version()); + LiStartConnection(address, config, &connection_callbacks, platform_get_video(system), platform_get_audio(system), NULL, NULL, 0, client_get_server_version()); if (IS_EMBEDDED(system)) loop_main(); diff --git a/src/platform.c b/src/platform.c index 65479cd..0de9e08 100644 --- a/src/platform.c +++ b/src/platform.c @@ -21,6 +21,7 @@ #include "platform.h" #include "video.h" +#include "audio.h" #include #include @@ -68,3 +69,10 @@ DECODER_RENDERER_CALLBACKS* platform_get_video(enum platform system) { } return NULL; } + +AUDIO_RENDERER_CALLBACKS* platform_get_audio(enum platform system) { + switch (system) { + default: + return &audio_callbacks_alsa; + } +} From 299a4a461f37343a24999ccdb345bb316f31a5ad Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Tue, 28 Jul 2015 11:49:29 +0200 Subject: [PATCH 10/10] Provide alternative SDL audio output --- CMakeLists.txt | 2 +- src/audio.h | 3 ++ src/audio/sdl.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ src/platform.c | 4 +++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/audio/sdl.c diff --git a/CMakeLists.txt b/CMakeLists.txt index afdfccc..ece9b6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ if(FREESCALE_FOUND) endif() if (AVCODEC_FOUND AND AVUTIL_FOUND AND SWSCALE_FOUND AND SDL_FOUND) - list(APPEND SRC_LIST ./src/video/ffmpeg.c ./src/video/sdl.c) + list(APPEND SRC_LIST ./src/video/ffmpeg.c ./src/video/sdl.c ./src/audio/sdl.c) list(APPEND MOONLIGHT_DEFINITIONS HAVE_SDL) endif() diff --git a/src/audio.h b/src/audio.h index 421068a..e41a738 100644 --- a/src/audio.h +++ b/src/audio.h @@ -22,3 +22,6 @@ extern const char* audio_device; extern AUDIO_RENDERER_CALLBACKS audio_callbacks_alsa; +#ifdef HAVE_SDL +extern AUDIO_RENDERER_CALLBACKS audio_callbacks_sdl; +#endif \ No newline at end of file diff --git a/src/audio/sdl.c b/src/audio/sdl.c new file mode 100644 index 0000000..5590718 --- /dev/null +++ b/src/audio/sdl.c @@ -0,0 +1,79 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2015 Iwan Timmer + * + * Moonlight 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. + * + * Moonlight 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 Moonlight; if not, see . + */ + +#include "../audio.h" + +#include +#include + +#include +#include + +#define SAMPLE_RATE 48000 +#define CHANNEL_COUNT 2 +#define FRAME_SIZE 240 + +static OpusDecoder* decoder; +static short pcmBuffer[FRAME_SIZE * CHANNEL_COUNT]; +static SDL_AudioDeviceID dev; + +static void sdl_renderer_init() { + int rc; + decoder = opus_decoder_create(SAMPLE_RATE, CHANNEL_COUNT, &rc); + + SDL_InitSubSystem(SDL_INIT_AUDIO); + + SDL_AudioSpec want, have; + SDL_zero(want); + want.freq = SAMPLE_RATE; + want.format = AUDIO_S16LSB; + want.channels = CHANNEL_COUNT; + want.samples = 4096; + + dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_FORMAT_CHANGE); + if (dev == 0) { + printf("Failed to open audio: %s\n", SDL_GetError()); + } else { + if (have.format != want.format) // we let this one thing change. + printf("We didn't get requested audio format.\n"); + SDL_PauseAudioDevice(dev, 0); // start audio playing. + } +} + +static void sdl_renderer_cleanup() { + if (decoder != NULL) + opus_decoder_destroy(decoder); + + SDL_CloseAudioDevice(dev); +} + +static void sdl_renderer_decode_and_play_sample(char* data, int length) { + int decodeLen = opus_decode(decoder, data, length, pcmBuffer, FRAME_SIZE, 0); + if (decodeLen > 0) { + SDL_QueueAudio(dev, pcmBuffer, decodeLen * CHANNEL_COUNT * sizeof(short)); + } else { + printf("Opus error from decode: %d\n", decodeLen); + } +} + +AUDIO_RENDERER_CALLBACKS audio_callbacks_sdl = { + .init = sdl_renderer_init, + .cleanup = sdl_renderer_cleanup, + .decodeAndPlaySample = sdl_renderer_decode_and_play_sample, +}; diff --git a/src/platform.c b/src/platform.c index 0de9e08..8c4e0bb 100644 --- a/src/platform.c +++ b/src/platform.c @@ -72,6 +72,10 @@ DECODER_RENDERER_CALLBACKS* platform_get_video(enum platform system) { AUDIO_RENDERER_CALLBACKS* platform_get_audio(enum platform system) { switch (system) { + #ifdef HAVE_SDL + case SDL: + return &audio_callbacks_sdl; + #endif default: return &audio_callbacks_alsa; }