Various optimizations and refactoring (#259)

* optimizations

* Update menu_game.h

* try new code

* use cached EGLImages

* try drm cache on pi

* nani?

* huh?

* require opengl egl

* find EGL

* find EGL separately

* logging

* add support for versions of ffmpeg less than 6.0

* disable drm cache for now

* Update ui_sdl.c

* use old code for now

* disable avcc hdr for now

* restore error message

* try disabling format modifiers?

* tell me what the layer is

* pi decoder actually produces frames in yuv420p

* print more debug info

* try this

* switch to faster decode path

* switch to old code

* Update ui_sdl.c

* add more timing profiling

* more printing

* enable some optimizations

* draw menus when not in game mode

* call render present

* Update ui_sdl.c

* Update ui_sdl.c

* remove unused code

* Update CMakeLists.txt

* incorporate direct DRM code

* Update CMakeLists.txt

* free drm context and test old decoding code

* move SDL event loop to separate thread so controls are not limited by display rate

* add a wait to reduce CPU thrashing

* fix double free

* separate video into recv and consume threads

* Update ui_sdl_drm.c

* Update ui_sdl_drm.c

* Update gamepad.c

* further optimizations

* anti-alias buttons more

* fix some compiler errors

* only include DRM on linux

* fix windows compile issues

* fix includes

* Update CMakeLists.txt

* add htobe32 define for macOS

* Update video.c

* Update gamepad.c
This commit is contained in:
MattKC
2025-11-30 20:56:52 -08:00
committed by GitHub
parent 6a82715263
commit b0bbc31002
29 changed files with 2123 additions and 2255 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
root = true
[*]
indent_style = tab
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
+92
View File
@@ -0,0 +1,92 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
find_package(PkgConfig QUIET)
function(qt_internal_multimedia_set_va_outputs component include_dir lib_path)
if ("${component}" STREQUAL "VA")
set(VAAPI_INCLUDE_DIR "${include_dir}" CACHE INTERNAL "")
get_filename_component(lib_realpath "${lib_path}" REALPATH)
string(REGEX MATCH "[0-9]+(\\.[0-9]+)*$" VAAPI_SUFFIX "${lib_realpath}")
set(VAAPI_SUFFIX "${VAAPI_SUFFIX}" CACHE INTERNAL "")
mark_as_advanced(VAAPI_SUFFIX VAAPI_INCLUDE_DIR)
endif()
endfunction()
function(find_component component prefix header library)
if(NOT TARGET VAAPI::${component})
string(TOUPPER ${component} upper)
pkg_search_module(PC_VAAPI_${upper} ${prefix} IMPORTED_TARGET)
if(TARGET PkgConfig::PC_VAAPI_${upper})
add_library(VAAPI::${component} INTERFACE IMPORTED)
target_link_libraries(VAAPI::${component} INTERFACE PkgConfig::PC_VAAPI_${upper})
if (NOT PC_VAAPI_${upper}_LINK_LIBRARIES)
get_target_property(PC_VAAPI_${upper}_LINK_LIBRARIES PkgConfig::PC_VAAPI_${upper} INTERFACE_LINK_LIBRARIES)
message(STATUS "PC_VAAPI_${upper}_LINK_LIBRARIES is not defined by PkgConfig; "
"Get the value from target properties: ${PC_VAAPI_${upper}_LINK_LIBRARIES}")
endif()
foreach (lib_path ${PC_VAAPI_${upper}_LINK_LIBRARIES})
get_filename_component(lib_name "${lib_path}" NAME_WLE)
if (${lib_name} STREQUAL ${prefix})
qt_internal_multimedia_set_va_outputs(${component}
"${PC_VAAPI_${upper}_INCLUDEDIR}" "${lib_path}")
break()
endif()
endforeach()
else()
find_path(VAAPI_${component}_INCLUDE_DIR
NAMES ${header}
PATH_SUFFIXES VAAPI-1.0
)
find_library(VAAPI_${component}_LIBRARY
NAMES ${library}
)
if(VAAPI_${component}_LIBRARY AND VAAPI_${component}_INCLUDE_DIR)
add_library(VAAPI::${component} INTERFACE IMPORTED)
target_include_directories(VAAPI::${component} INTERFACE ${VAAPI_${component}_INCLUDE_DIR})
target_link_libraries(VAAPI::${component} INTERFACE ${VAAPI_${component}_LIBRARY})
endif()
mark_as_advanced(VAAPI_${component}_INCLUDE_DIR VAAPI_${component}_LIBRARY)
qt_internal_multimedia_set_va_outputs(${component}
"${VAAPI_${component}_INCLUDE_DIR}" "${VAAPI_${component}_LIBRARY}")
endif()
endif()
if(TARGET VAAPI::${component})
set(VAAPI_${component}_FOUND TRUE PARENT_SCOPE)
endif()
endfunction()
find_component(VA libva va/va.h libva)
find_component(DRM libva-drm va/va-drm.h libva-drm)
if(TARGET VAAPI::VA)
target_link_libraries(VAAPI::VA)
endif()
if(TARGET VAAPI::VA AND TARGET VAAPI::DRM)
target_link_libraries(VAAPI::DRM INTERFACE VAAPI::VA)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(VAAPI
REQUIRED_VARS
VAAPI_VA_FOUND
VAAPI_DRM_FOUND
VAAPI_INCLUDE_DIR
VAAPI_SUFFIX
HANDLE_COMPONENTS
)
if(VAAPI_FOUND AND NOT TARGET VAAPI::VAAPI)
add_library(VAAPI::VAAPI INTERFACE IMPORTED)
target_link_libraries(VAAPI::VAAPI INTERFACE
VAAPI::VA
VAAPI::DRM
)
endif()
+31 -19
View File
@@ -247,8 +247,8 @@ list(APPEND VANILLA_GUI_SRC
# drm.c
lang.c
#game/game_decode_ffmpeg.c
game/game_decode.c
game/game_main.c
# game/game_decode.c
# game/game_main.c
main.c
menu/menu.c
menu/menu_common.c
@@ -271,6 +271,18 @@ list(APPEND VANILLA_GUI_SRC
ui/ui_util.h
)
if (LINUX)
# Link with DRM if available
find_package(DRM)
if (DRM_FOUND)
list(APPEND VANILLA_GUI_SRC
ui/ui_sdl_drm.c
)
endif()
find_package(VAAPI)
endif()
if (VANILLA_BUILD_PIPE)
OPTION(VANILLA_BUILD_USE_POLKIT "Enable Polkit-based authentication" ON)
endif()
@@ -346,30 +358,30 @@ endif()
if (VANILLA_BUILD_USE_POLKIT)
# Find Polkit and GLIB libraries
find_package(PkgConfig)
find_package(PkgConfig REQUIRED)
pkg_check_modules(PC_POLKIT polkit-agent-1 REQUIRED IMPORTED_TARGET)
target_include_directories(vanilla PRIVATE ${PC_POLKIT_INCLUDE_DIRS})
target_link_libraries(vanilla PRIVATE ${PC_POLKIT_LIBRARIES})
target_compile_definitions(vanilla PRIVATE VANILLA_POLKIT_AVAILABLE)
endif()
if (LINUX)
# Link with DRM if available
find_package(DRM)
if (DRM_FOUND)
target_link_libraries(vanilla PRIVATE ${DRM_LIBRARIES})
target_include_directories(vanilla PRIVATE ${DRM_INCLUDE_DIRS})
endif()
if (VAAPI_FOUND)
target_link_libraries(vanilla PRIVATE VAAPI::VAAPI VAAPI::VA VAAPI::DRM va-x11 X11)
endif()
# TODO: A more comprehensive "Find" for libva
OPTION(VANILLA_BUILD_USE_VAAPI "Enable VAAPI hardware decoding" ON)
if (VANILLA_BUILD_USE_VAAPI)
find_package(OpenGL COMPONENTS EGL)
if (OpenGL_EGL_FOUND)
target_link_libraries(vanilla PRIVATE OpenGL::EGL va va-drm va-x11 X11)
target_compile_definitions(vanilla PRIVATE VANILLA_HAS_EGL)
endif()
endif()
if (DRM_FOUND)
target_link_libraries(vanilla PRIVATE ${DRM_LIBRARIES})
target_compile_definitions(vanilla PRIVATE VANILLA_DRM_AVAILABLE)
target_include_directories(vanilla PRIVATE ${DRM_INCLUDE_DIRS})
endif()
if (VAAPI_FOUND OR DRM_FOUND)
find_package(PkgConfig REQUIRED)
pkg_check_modules(EGL REQUIRED egl)
target_include_directories(vanilla PRIVATE ${EGL_INCLUDE_DIRS})
target_link_directories(vanilla PRIVATE ${EGL_LIBRARY_DIRS})
target_link_libraries(vanilla PRIVATE ${EGL_LIBRARIES})
target_compile_definitions(vanilla PRIVATE VANILLA_HAS_EGL)
endif()
# Find libxml2 (used for storing/parsing user config)
-26
View File
@@ -1,26 +0,0 @@
#ifndef VANILLA_PI_DRM_H
#define VANILLA_PI_DRM_H
#include <libavutil/frame.h>
#include <libavutil/hwcontext_drm.h>
#include <linux/kd.h>
#include <stdint.h>
typedef struct {
int fd;
uint32_t crtc;
int crtc_index;
uint32_t plane_id;
int got_plane;
uint32_t fb_id;
int got_fb;
uint32_t handles[AV_DRM_MAX_PLANES];
int got_handles;
} vanilla_drm_ctx_t;
int set_tty(int mode);
int initialize_drm(vanilla_drm_ctx_t *ctx);
int free_drm(vanilla_drm_ctx_t *ctx);
int display_drm(vanilla_drm_ctx_t *ctx, AVFrame *frame);
#endif // VANILLA_PI_DRM_H
-615
View File
@@ -1,615 +0,0 @@
#include "game_decode.h"
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
#include <sys/time.h>
#include <vanilla.h>
#include "def.h"
#include "game_main.h"
#include "lang.h"
#include "platform.h"
#include "ui/ui_util.h"
pthread_mutex_t vpi_decoding_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t decoding_wait_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t vpi_decode_loop_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t vpi_decode_loop_cond = PTHREAD_COND_INITIALIZER;
int vpi_decode_loop_running = 0;
uint8_t vpi_decode_ready = 0;
uint8_t vpi_decode_data[65536];
size_t vpi_decode_size = 0;
AVFrame *vpi_present_frame = 0;
static AVFrame *decoding_frame;
static AVCodecContext *video_codec_ctx;
static AVPacket *video_packet;
static pthread_mutex_t screenshot_mutex = PTHREAD_MUTEX_INITIALIZER;
static char screenshot_buf[4096] = {0};
static pthread_mutex_t recording_mutex = PTHREAD_MUTEX_INITIALIZER;
static AVFormatContext *recording_fmt_ctx = 0;
static AVStream *recording_vstr;
static AVStream *recording_astr;
static struct timeval recording_start;
static const int VIDEO_STREAM_INDEX = 0;
static const int AUDIO_STREAM_INDEX = 1;
enum AVPixelFormat get_format(AVCodecContext* ctx, const enum AVPixelFormat *pix_fmt)
{
while (*pix_fmt != AV_PIX_FMT_NONE) {
if (*pix_fmt == AV_PIX_FMT_DRM_PRIME) {
return AV_PIX_FMT_DRM_PRIME;
}
pix_fmt++;
}
return AV_PIX_FMT_NONE;
}
int dump_frame_to_file(const AVFrame *frame, const char *filename)
{
AVFormatContext *ofmt;
int ret = avformat_alloc_output_context2(&ofmt, 0, 0, filename);
if (ret < 0) {
vpilog("Failed to allocate new AVFormatContext for screenshot\n");
goto exit;
}
enum AVCodecID codec_id = AV_CODEC_ID_PNG;
const AVCodec *codec = avcodec_find_encoder(codec_id);
if (!codec) {
vpilog("Failed to find encoder for screenshot\n");
ret = AVERROR_ENCODER_NOT_FOUND;
goto exit_and_free_avformatcontext;
}
AVStream *s = avformat_new_stream(ofmt, 0);
if (!s) {
vpilog("Failed to create new AVStream for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_avformatcontext;
}
s->id = 0;
AVCodecContext *ocodec_ctx = avcodec_alloc_context3(codec);
if (!ocodec_ctx) {
vpilog("Failed to create new AVCodecContext for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_avformatcontext;
}
enum AVPixelFormat opix_fmt = AV_PIX_FMT_RGB24;
ocodec_ctx->width = frame->width;
ocodec_ctx->height = frame->height;
ocodec_ctx->pix_fmt = opix_fmt;
ocodec_ctx->codec_id = codec_id;
ocodec_ctx->time_base = (AVRational){1, 1};
ocodec_ctx->frame_num = 1;
if (ofmt->flags & AVFMT_GLOBALHEADER) {
ocodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
AVPacket *opkt = av_packet_alloc();
if (!opkt) {
vpilog("Failed to create new AVPacket for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_avcodeccontext;
}
ret = avcodec_open2(ocodec_ctx, codec, 0);
if (ret < 0) {
vpilog("Failed to open encoder for screenshot\n");
goto exit_and_free_avpacket;
}
ret = avcodec_parameters_from_context(s->codecpar, ocodec_ctx);
if (ret < 0) {
vpilog("Failed to copy params from context for screenshot\n");
goto exit_and_free_avpacket;
}
ret = avformat_write_header(ofmt, 0);
if (ret < 0) {
vpilog("Failed to write header for screenshot\n");
goto exit_and_free_avpacket;
}
AVFrame *swframe = 0;
if (frame->format != AV_PIX_FMT_YUV420P) {
// Assume this frame is hardware accelerated and we need to download it
// to CPU memory
swframe = av_frame_alloc();
if (!swframe) {
vpilog("Failed to allocate CPU frame for screenshot\n");
ret = AVERROR(EINVAL);
goto exit_and_free_avpacket;
}
if (av_hwframe_transfer_data(swframe, frame, 0) < 0) {
vpilog("Failed to transfer data from hardware for screenshot\n");
ret = AVERROR(EINVAL);
goto exit_and_free_swframe;
}
// Use this CPU frame instead from now on
frame = swframe;
}
struct SwsContext *osws_ctx = sws_getContext(
frame->width, frame->height, frame->format,
frame->width, frame->height, opix_fmt,
0, 0, 0, 0);
if (!osws_ctx) {
vpilog("Failed to get SwsContext for screenshot\n");
ret = AVERROR(EINVAL);
goto exit_and_free_avpacket;
}
AVFrame *oframe = av_frame_alloc();
if (!oframe) {
vpilog("Failed to allocate AVFrame for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_swscontext;
}
oframe->width = frame->width;
oframe->height = frame->height;
oframe->format = opix_fmt;
ret = av_frame_get_buffer(oframe, 0);
if (ret < 0) {
vpilog("Failed to get AVFrame buffer for screenshot\n");
goto exit_and_free_frame;
}
ret = sws_scale(osws_ctx, (const uint8_t **) frame->data, (const int *) frame->linesize, 0, frame->height, oframe->data, oframe->linesize);
if (ret < 0) {
vpilog("Failed to do swscale for screenshot\n");
goto exit_and_free_frame;
}
ret = avcodec_send_frame(ocodec_ctx, oframe);
if (ret < 0) {
vpilog("Failed to send frame to encoder for screenshot\n");
goto exit_and_free_frame;
}
ret = avcodec_receive_packet(ocodec_ctx, opkt);
if (ret < 0) {
vpilog("Failed to receive packet from encoder for screenshot\n");
goto exit_and_free_frame;
}
ret = av_write_frame(ofmt, opkt);
if (ret < 0) {
vpilog("Failed to write frame for screenshot\n");
goto exit_and_free_frame;
}
ret = av_write_trailer(ofmt);
if (ret < 0) {
vpilog("Failed to write trailer for screenshot\n");
goto exit_and_free_frame;
}
exit_and_free_frame:
av_frame_free(&oframe);
exit_and_free_swscontext:
sws_freeContext(osws_ctx);
exit_and_free_swframe:
if (swframe) {
av_frame_free(&swframe);
}
exit_and_free_avpacket:
av_packet_free(&opkt);
exit_and_free_avcodeccontext:
avcodec_free_context(&ocodec_ctx);
exit_and_free_avformatcontext:
avformat_free_context(ofmt);
exit:
if (ret >= 0) {
vpilog("Saved screenshot to \"%s\"\n", filename);
char buf[VPI_TOAST_MAX_LEN];
snprintf(buf, sizeof(buf), lang(VPI_LANG_SCREENSHOT_SUCCESS), filename);
vpi_show_toast(buf);
} else {
char buf[VPI_TOAST_MAX_LEN];
snprintf(buf, sizeof(buf), lang(VPI_LANG_SCREENSHOT_ERROR), ret);
vpi_show_toast(buf);
}
return ret;
}
int64_t get_recording_timestamp(AVRational timebase)
{
struct timeval tv;
gettimeofday(&tv, 0);
int64_t us = (tv.tv_sec - recording_start.tv_sec) * 1000000 + (tv.tv_usec - recording_start.tv_usec);
return av_rescale_q(us, (AVRational) {1, 1000000}, timebase);
}
void vpi_decode_send_audio(const void *data, size_t size)
{
pthread_mutex_lock(&recording_mutex);
if (recording_fmt_ctx) {
int ret;
AVPacket *pkt = av_packet_alloc();
pkt->data = (uint8_t *) data;
pkt->size = size;
int64_t ts = get_recording_timestamp(recording_astr->time_base);
pkt->stream_index = AUDIO_STREAM_INDEX;
pkt->dts = ts;
pkt->pts = ts;
av_interleaved_write_frame(recording_fmt_ctx, pkt);
av_packet_free(&pkt);
}
pthread_mutex_unlock(&recording_mutex);
}
int vpi_decode_is_recording()
{
int i;
pthread_mutex_lock(&recording_mutex);
i = (recording_fmt_ctx != 0);
pthread_mutex_unlock(&recording_mutex);
return i;
}
int vpi_decode_record(const char *filename)
{
pthread_mutex_lock(&recording_mutex);
int r = avformat_alloc_output_context2(&recording_fmt_ctx, 0, 0, filename);
if (r < 0) {
vpilog("Failed to allocate output context for recording\n");
goto exit;
}
recording_vstr = avformat_new_stream(recording_fmt_ctx, 0);
if (!recording_vstr) {
vpilog("Failed to allocate video stream for recording\n");
goto exit_and_free_context;
}
recording_vstr->id = VIDEO_STREAM_INDEX;
recording_vstr->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
recording_vstr->codecpar->width = 854;
recording_vstr->codecpar->height = 480;
recording_vstr->codecpar->format = AV_PIX_FMT_YUV420P;
recording_vstr->time_base = (AVRational) {1, 60};
recording_vstr->codecpar->codec_id = AV_CODEC_ID_H264;
recording_vstr->codecpar->extradata = av_malloc(200);
recording_vstr->codecpar->extradata_size = vanilla_generate_h264_header(recording_vstr->codecpar->extradata, 200);
recording_astr = avformat_new_stream(recording_fmt_ctx, 0);
if (!recording_astr) {
vpilog("Failed to allocate audio stream for recording\n");
goto exit_and_free_context;
}
recording_astr->id = AUDIO_STREAM_INDEX;
recording_astr->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
recording_astr->codecpar->sample_rate = 48000;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57,24,100)
recording_astr->codecpar->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO;
#else
recording_astr->codecpar->channel_layout = AV_CH_LAYOUT_STEREO;
#endif
recording_astr->codecpar->format = AV_SAMPLE_FMT_S16;
recording_astr->time_base = (AVRational) {1, 48000};
recording_astr->codecpar->codec_id = AV_CODEC_ID_PCM_S16LE;
r = avio_open2(&recording_fmt_ctx->pb, filename, AVIO_FLAG_WRITE, 0, 0);
if (r < 0) {
vpilog("Failed to open AVIO for recording\n");
goto exit_and_free_context;
}
r = avformat_write_header(recording_fmt_ctx, 0);
if (r < 0) {
vpilog("Failed to write header for recording\n");
goto exit_and_free_context;
}
vanilla_request_idr();
gettimeofday(&recording_start, 0);
char buf[VPI_TOAST_MAX_LEN];
snprintf(buf, sizeof(buf), lang(VPI_LANG_RECORDING_START), filename);
vpi_show_toast(buf);
vpilog("Started recording to \"%s\"\n", filename);
goto exit;
exit_and_free_context:
avformat_free_context(recording_fmt_ctx);
recording_fmt_ctx = 0;
exit:
pthread_mutex_unlock(&recording_mutex);
return r;
}
void vpi_decode_record_stop()
{
pthread_mutex_lock(&recording_mutex);
if (recording_fmt_ctx) {
int r = av_write_trailer(recording_fmt_ctx);
if (r < 0) {
vpilog("Failed to write trailer for recording\n");
}
avio_closep(&recording_fmt_ctx->pb);
avformat_free_context(recording_fmt_ctx);
recording_fmt_ctx = 0;
vpilog("Finished recording\n");
vpi_show_toast(lang(VPI_LANG_RECORDING_FINISH));
}
pthread_mutex_unlock(&recording_mutex);
}
int decode()
{
int err;
int ret = 1;
// Retrieve frame from decoder
while (1) {
err = avcodec_receive_frame(video_codec_ctx, decoding_frame);
if (err == AVERROR(EAGAIN)) {
// Decoder wants another packet before it can output a frame. Silently exit.
break;
} else if (err < 0) {
vpilog("Failed to receive frame from decoder: %i\n", err);
ret = 0;
break;
} else {
pthread_mutex_lock(&vpi_decoding_mutex);
// Swap refs from decoding_frame to present_frame
av_frame_unref(vpi_present_frame);
av_frame_move_ref(vpi_present_frame, decoding_frame);
pthread_mutex_lock(&screenshot_mutex);
if (screenshot_buf[0] != 0) {
// Dump this frame into file
dump_frame_to_file(vpi_present_frame, screenshot_buf);
screenshot_buf[0] = 0;
}
pthread_mutex_unlock(&screenshot_mutex);
pthread_cond_broadcast(&decoding_wait_cond);
pthread_mutex_unlock(&vpi_decoding_mutex);
}
}
return ret;
}
void vpi_decode_screenshot(const char *filename)
{
// Grab copy of filename
pthread_mutex_lock(&screenshot_mutex);
vui_strncpy(screenshot_buf, filename, sizeof(screenshot_buf));
pthread_mutex_unlock(&screenshot_mutex);
}
static enum AVPixelFormat vaapi_get_format(struct AVCodecContext *s, const enum AVPixelFormat *fmt)
{
while (*fmt != AV_PIX_FMT_NONE) {
if (*fmt == AV_PIX_FMT_VAAPI) {
return *fmt;
}
fmt++;
}
return AV_PIX_FMT_NONE;
}
static enum AVPixelFormat drm_get_format(struct AVCodecContext *s, const enum AVPixelFormat *fmt)
{
while (*fmt != AV_PIX_FMT_NONE) {
if (*fmt == AV_PIX_FMT_DRM_PRIME) {
return *fmt;
}
fmt++;
}
return AV_PIX_FMT_NONE;
}
void *vpi_decode_loop(void *)
{
int ret = VANILLA_PI_ERR_DECODER;
int ffmpeg_err;
// Initialize FFmpeg
const AVCodec *codec = 0;
enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
// Test for VAAPI
AVBufferRef *hw_device_ctx = 0;
if ((ffmpeg_err = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, 0, 0, 0)) >= 0) {
vpilog("Decoding: VAAPI\n");
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
get_format = vaapi_get_format;
} else if ((ffmpeg_err = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_DRM, "/dev/dri/card0", 0, 0)) >= 0) {
vpilog("Decoding: DRM\n");
codec = avcodec_find_decoder_by_name("h264_v4l2m2m");
get_format = drm_get_format;
}
// If we couldn't find a decoder (regardless of if we found hwaccel), fallback to software
if (!codec) {
if (hw_device_ctx) {
av_buffer_unref(&hw_device_ctx);
}
vpilog("Decoding: Software\n");
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
get_format = 0;
}
// If we couldn't find a hardware or software decoder, fail
if (!codec) {
vpilog("No decoder was available\n");
goto free_hwctx;
}
video_codec_ctx = avcodec_alloc_context3(codec);
if (!video_codec_ctx) {
vpilog("Failed to allocate codec context\n");
goto free_context;
}
if (hw_device_ctx) {
video_codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
}
if (get_format) {
video_codec_ctx->get_format = get_format;
}
ffmpeg_err = avcodec_open2(video_codec_ctx, codec, NULL);
if (ffmpeg_err < 0) {
vpilog("Failed to open decoder: %i\n", ffmpeg_err);
goto free_context;
}
decoding_frame = av_frame_alloc();
if (!decoding_frame) {
vpilog("Failed to allocate AVFrame\n");
goto free_context;
}
vpi_present_frame = av_frame_alloc();
if (!vpi_present_frame) {
vpilog("Failed to allocate AVFrame\n");
goto free_decode_frame;
}
video_packet = av_packet_alloc();
if (!video_packet) {
vpilog("Failed to allocate AVPacket\n");
goto free_present_frame;
}
AVFrame *frame = av_frame_alloc();
pthread_mutex_lock(&vpi_decode_loop_mutex);
while (vpi_decode_loop_running) {
while (vpi_decode_loop_running && !vpi_decode_ready) {
pthread_cond_wait(&vpi_decode_loop_cond, &vpi_decode_loop_mutex);
}
if (!vpi_decode_loop_running) {
break;
}
// Send packet to decoder
video_packet->data = vpi_decode_data;
video_packet->size = vpi_decode_size;
vpi_decode_ready = 0;
pthread_mutex_lock(&recording_mutex);
if (recording_fmt_ctx) {
video_packet->stream_index = VIDEO_STREAM_INDEX;
int64_t ts = get_recording_timestamp(recording_vstr->time_base);
video_packet->dts = ts;
video_packet->pts = ts;
av_interleaved_write_frame(recording_fmt_ctx, video_packet);
// av_interleaved_write_frame() eventually calls av_packet_unref(),
// so we must put the references back in
video_packet->data = vpi_decode_data;
video_packet->size = vpi_decode_size;
}
pthread_mutex_unlock(&recording_mutex);
int err = avcodec_send_packet(video_codec_ctx, video_packet);
pthread_mutex_unlock(&vpi_decode_loop_mutex);
if (err < 0) {
vpilog("Failed to send packet to decoder: %s (%i)\n", av_err2str(err), err);
// return 0;
vanilla_request_idr();
} else {
decode();
}
pthread_mutex_lock(&vpi_decode_loop_mutex);
}
pthread_mutex_unlock(&vpi_decode_loop_mutex);
ret = VANILLA_SUCCESS;
// TEMP: Signals to DRM thread
// pthread_mutex_lock(&decoding_mutex);
// running = 0;
// pthread_cond_broadcast(&decoding_wait_cond);
// pthread_mutex_unlock(&decoding_mutex);
av_frame_free(&frame);
free_packet:
av_packet_free(&video_packet);
free_present_frame:
av_frame_free(&vpi_present_frame);
vpi_present_frame = 0;
free_decode_frame:
av_frame_free(&decoding_frame);
free_context:
avcodec_free_context(&video_codec_ctx);
free_hwctx:
if (hw_device_ctx) {
av_buffer_unref(&hw_device_ctx);
}
exit:
if (ret != VANILLA_SUCCESS) {
vpi_game_set_error(ret);
}
return 0;
}
-25
View File
@@ -1,25 +0,0 @@
#ifndef VPI_GAME_DECODE_H
#define VPI_GAME_DECODE_H
#include <libavutil/frame.h>
#include <pthread.h>
extern pthread_mutex_t vpi_decode_loop_mutex;
extern pthread_cond_t vpi_decode_loop_cond;
extern int vpi_decode_loop_running;
extern uint8_t vpi_decode_data[65536];
extern uint8_t vpi_decode_ready;
extern size_t vpi_decode_size;
extern AVFrame *vpi_present_frame;
extern pthread_mutex_t vpi_decoding_mutex;
extern pthread_cond_t decoding_wait_cond;
void *vpi_decode_loop(void *);
void vpi_decode_screenshot(const char *filename);
int vpi_decode_is_recording();
int vpi_decode_record(const char *filename);
void vpi_decode_record_stop();
void vpi_decode_send_audio(const void *data, size_t size);
#endif // VPI_GAME_DECODE_H
-132
View File
@@ -1,132 +0,0 @@
#include "game_main.h"
#include <pthread.h>
#include <string.h>
#include <vanilla.h>
#include "game_decode.h"
#include "platform.h"
#include "ui/ui.h"
#include "ui/ui_util.h"
static pthread_t vpi_event_handler_thread;
static int vpi_game_error_value;
static pthread_mutex_t vpi_error_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t vpi_toast_mutex = PTHREAD_MUTEX_INITIALIZER;
static char vpi_toast_string[VPI_TOAST_MAX_LEN];
static struct timeval vpi_toast_expiry;
static int vpi_toast_number = 0;
int vpi_game_error()
{
int r;
pthread_mutex_lock(&vpi_error_mutex);
r = vpi_game_error_value;
pthread_mutex_unlock(&vpi_error_mutex);
return r;
}
void vpi_game_set_error(int r)
{
pthread_mutex_lock(&vpi_error_mutex);
vpi_game_error_value = r;
pthread_mutex_unlock(&vpi_error_mutex);
}
void *vpi_event_handler(void *data)
{
vanilla_event_t event;
vpi_decode_loop_running = 1;
pthread_t decode_thread;
pthread_create(&decode_thread, 0, vpi_decode_loop, 0);
vui_context_t *vui = (vui_context_t *) data;
while (vanilla_wait_event(&event)) {
int stop = 0;
switch (event.type) {
case VANILLA_EVENT_VIDEO:
pthread_mutex_lock(&vpi_decode_loop_mutex);
vpi_decode_size = event.size;
memcpy(vpi_decode_data, event.data, event.size);
vpi_decode_ready = 1;
pthread_cond_broadcast(&vpi_decode_loop_cond);
pthread_mutex_unlock(&vpi_decode_loop_mutex);
break;
case VANILLA_EVENT_AUDIO:
vui_audio_push(vui, event.data, event.size);
// We send audio to vpi_decode, but not actually for decoding since
// the audio is already uncompressed. We send it in case vpi_decode
// is recording, so the audio can be written to the file.
vpi_decode_send_audio(event.data, event.size);
break;
case VANILLA_EVENT_VIBRATE:
vui_vibrate_set(vui, event.data[0]);
break;
case VANILLA_EVENT_ERROR:
vpi_game_set_error(*(int *)event.data);
break;
case VANILLA_EVENT_MIC:
vui_mic_enabled_set(vui, event.data[0]);
break;
}
vanilla_free_event(&event);
}
pthread_mutex_lock(&vpi_decode_loop_mutex);
vpi_decode_loop_running = 0;
pthread_cond_broadcast(&vpi_decode_loop_cond);
pthread_mutex_unlock(&vpi_decode_loop_mutex);
pthread_join(decode_thread, 0);
return 0;
}
void vpi_game_start(vui_context_t *vui)
{
vpi_game_error_value = VANILLA_SUCCESS;
pthread_create(&vpi_event_handler_thread, 0, vpi_event_handler, vui);
}
void vpi_show_toast(const char *message)
{
pthread_mutex_lock(&vpi_toast_mutex);
vui_strncpy(vpi_toast_string, message, sizeof(vpi_toast_string));
gettimeofday(&vpi_toast_expiry, 0);
// Toast lasts for 2 seconds
vpi_toast_expiry.tv_sec += 2;
vpi_toast_number++;
pthread_mutex_unlock(&vpi_toast_mutex);
}
void vpi_get_toast(int *number, char *output, size_t output_size, struct timeval *expiry_time)
{
pthread_mutex_lock(&vpi_toast_mutex);
if (number)
*number = vpi_toast_number;
if (output && output_size) {
vui_strncpy(output, vpi_toast_string, output_size);
}
if (expiry_time) {
*expiry_time = vpi_toast_expiry;
}
pthread_mutex_unlock(&vpi_toast_mutex);
}
-17
View File
@@ -1,17 +0,0 @@
#ifndef VPI_GAME_MAIN_H
#define VPI_GAME_MAIN_H
#include <sys/time.h>
#include "ui/ui.h"
#define VPI_TOAST_MAX_LEN 1024
void vpi_game_start(vui_context_t *vui);
int vpi_game_error();
void vpi_game_set_error(int r);
void vpi_get_toast(int *number, char *output, size_t output_size, struct timeval *expiry_time);
void vpi_show_toast(const char *message);
#endif // VPI_GAME_MAIN_H
-2
View File
@@ -1,2 +0,0 @@
#include "game_record.h"
-8
View File
@@ -1,8 +0,0 @@
#ifndef VPI_GAME_RECORD_H
#define VPI_GAME_RECORD_H
void game_take_screenshot(const char *filename);
void game_start_recording(const char *filename);
void game_stop_recording();
#endif // VPI_GAME_RECORD_H
-1
View File
@@ -15,7 +15,6 @@
#define SDL_main main
#endif
#include "game/game_main.h"
#include "pipemgmt.h"
void display_cli_help(const char **argv);
-564
View File
@@ -1,564 +0,0 @@
#define SCREEN_WIDTH 854
#define SCREEN_HEIGHT 480
#include <arpa/inet.h>
#include <libavcodec/avcodec.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <stdio.h>
#include <SDL2/SDL.h>
#include <vanilla.h>
#include <unistd.h>
#include "drm.h"
#include "ui.h"
AVFrame *present_frame;
AVFrame *decoding_frame;
pthread_mutex_t decoding_mutex;
pthread_cond_t decoding_wait_cond;
AVCodecContext *video_codec_ctx;
AVPacket *video_packet;
int running = 0;
static int button_map[SDL_CONTROLLER_BUTTON_MAX] = {-1};
static int axis_map[SDL_CONTROLLER_AXIS_MAX] = {-1};
static int vibrate = 0;
// HACK: Easy macro to test between desktop and RPi (even though ARM doesn't necessarily mean RPi)
#ifdef __arm__
#define RASPBERRY_PI
#endif
enum AVPixelFormat get_format(AVCodecContext* ctx, const enum AVPixelFormat *pix_fmt)
{
while (*pix_fmt != AV_PIX_FMT_NONE) {
if (*pix_fmt == AV_PIX_FMT_DRM_PRIME) {
return AV_PIX_FMT_DRM_PRIME;
}
pix_fmt++;
}
return AV_PIX_FMT_NONE;
}
int decode(void *data, size_t size)
{
int err;
// Parse this data for packets
err = av_packet_from_data(video_packet, (uint8_t *) data, size);
if (err < 0) {
fprintf(stderr, "Failed to initialize AVPacket from data: %s (%i)\n", av_err2str(err), err);
av_free(data);
return 0;
}
// Send packet to decoder
err = avcodec_send_packet(video_codec_ctx, video_packet);
av_packet_unref(video_packet);
if (err < 0) {
fprintf(stderr, "Failed to send packet to decoder: %s (%i)\n", av_err2str(err), err);
return 0;
}
int ret = 1;
// Retrieve frame from decoder
while (1) {
err = avcodec_receive_frame(video_codec_ctx, decoding_frame);
if (err == AVERROR(EAGAIN)) {
// Decoder wants another packet before it can output a frame. Silently exit.
break;
} else if (err < 0) {
fprintf(stderr, "Failed to receive frame from decoder: %i\n", err);
ret = 0;
break;
} else if (!decoding_frame->data[0]) {
fprintf(stderr, "WE GOT A NULL DATA[0] STRAIGHT FROM THE DECODER?????\n");
abort();
} else if ((decoding_frame->flags & AV_FRAME_FLAG_CORRUPT) != 0) {
fprintf(stderr, "GOT A CORRUPT FRAME??????\n");
abort();
} else {
pthread_mutex_lock(&decoding_mutex);
// Swap refs from decoding_frame to present_frame
av_frame_unref(present_frame);
av_frame_move_ref(present_frame, decoding_frame);
pthread_cond_broadcast(&decoding_wait_cond);
pthread_mutex_unlock(&decoding_mutex);
}
}
return ret;
}
static SDL_mutex *decode_loop_mutex;
static SDL_cond *decode_loop_cond;
static int decode_loop_running = 0;
static uint8_t *decode_data = NULL;
static size_t decode_size = 0;
int decode_loop(void *)
{
SDL_LockMutex(decode_loop_mutex);
while (decode_loop_running) {
while (decode_loop_running && !decode_data) {
SDL_CondWait(decode_loop_cond, decode_loop_mutex);
}
if (!decode_loop_running) {
break;
}
// Copy into av_malloc buffer
uint8_t *our_data = decode_data;
size_t our_size = decode_size;
decode_data = NULL;
decode_size = 0;
SDL_UnlockMutex(decode_loop_mutex);
decode(our_data, our_size);
SDL_LockMutex(decode_loop_mutex);
}
SDL_UnlockMutex(decode_loop_mutex);
}
int run_backend(void *data)
{
SDL_AudioSpec desired = {0};
desired.freq = 48000;
desired.format = AUDIO_S16LSB;
desired.channels = 2;
SDL_AudioDeviceID audio = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, 0);
if (audio) {
SDL_PauseAudioDevice(audio, 0);
} else {
printf("Failed to open audio device\n");
}
vanilla_event_t event;
decode_loop_mutex = SDL_CreateMutex();
decode_loop_cond = SDL_CreateCond();
decode_loop_running = 1;
SDL_Thread *decode_thread = SDL_CreateThread(decode_loop, "vanilla-decode", NULL);
while (vanilla_wait_event(&event)) {
if (event.type == VANILLA_EVENT_VIDEO) {
SDL_LockMutex(decode_loop_mutex);
if (decode_data) {
av_free(decode_data);
decode_data = NULL;
}
decode_size = event.size;
decode_data = av_malloc(event.size);
memcpy(decode_data, event.data, event.size);
SDL_CondBroadcast(decode_loop_cond);
SDL_UnlockMutex(decode_loop_mutex);
} else if (event.type == VANILLA_EVENT_AUDIO) {
if (audio) {
if (SDL_QueueAudio(audio, event.data, event.size) < 0) {
printf("Failed to queue audio\n", event.size);
}
}
} else if (event.type == VANILLA_EVENT_VIBRATE) {
vibrate = event.data[0];
}
vanilla_free_event(&event);
}
SDL_LockMutex(decode_loop_mutex);
decode_loop_running = 0;
SDL_CondBroadcast(decode_loop_cond);
SDL_UnlockMutex(decode_loop_mutex);
SDL_WaitThread(decode_thread, NULL);
SDL_DestroyCond(decode_loop_cond);
SDL_DestroyMutex(decode_loop_mutex);
SDL_CloseAudioDevice(audio);
return 0;
}
void logger(const char *s, va_list args)
{
vfprintf(stderr, s, args);
}
int display_loop(void *data)
{
vanilla_drm_ctx_t *drm_ctx = (vanilla_drm_ctx_t *) data;
Uint32 start_ticks = SDL_GetTicks();
#define TICK_MAX 30
Uint32 tick_deltas[TICK_MAX];
size_t tick_delta_index = 0;
AVFrame *frame = av_frame_alloc();
pthread_mutex_lock(&decoding_mutex);
while (running) {
while (running && !present_frame->data[0]) {
pthread_cond_wait(&decoding_wait_cond, &decoding_mutex);
}
if (!running) {
break;
}
// If a frame is available, present it here
av_frame_unref(frame);
av_frame_move_ref(frame, present_frame);
pthread_mutex_unlock(&decoding_mutex);
display_drm(drm_ctx, frame);
// FPS counter stuff
Uint32 now = SDL_GetTicks();
tick_deltas[tick_delta_index] = (now - start_ticks);
start_ticks = now;
tick_delta_index++;
if (tick_delta_index == TICK_MAX) {
Uint32 total = 0;
for (int i = 0; i < TICK_MAX; i++) {
total += tick_deltas[i];
}
fprintf(stderr, "AVERAGE FPS: %.2f\n", 1000.0f / (total / (float) TICK_MAX));
tick_delta_index = 0;
}
pthread_mutex_lock(&decoding_mutex);
}
pthread_mutex_unlock(&decoding_mutex);
av_frame_free(&frame);
return 0;
}
SDL_GameController *find_valid_controller()
{
for (int i = 0; i < SDL_NumJoysticks(); i++) {
SDL_GameController *c = SDL_GameControllerOpen(i);
if (c) {
return c;
}
}
return NULL;
}
void sigint_handler(int signum)
{
printf("Received SIGINT...\n");
running = 0;
SDL_QuitEvent ev;
ev.type = SDL_QUIT;
ev.timestamp = SDL_GetTicks();
SDL_PushEvent((SDL_Event *) &ev);
}
void init_gamepad()
{
vibrate = 0;
button_map[SDL_CONTROLLER_BUTTON_A] = VANILLA_BTN_A;
button_map[SDL_CONTROLLER_BUTTON_B] = VANILLA_BTN_B;
button_map[SDL_CONTROLLER_BUTTON_X] = VANILLA_BTN_X;
button_map[SDL_CONTROLLER_BUTTON_Y] = VANILLA_BTN_Y;
button_map[SDL_CONTROLLER_BUTTON_BACK] = VANILLA_BTN_MINUS;
button_map[SDL_CONTROLLER_BUTTON_GUIDE] = VANILLA_BTN_HOME;
button_map[SDL_CONTROLLER_BUTTON_MISC1] = VANILLA_BTN_TV;
button_map[SDL_CONTROLLER_BUTTON_START] = VANILLA_BTN_PLUS;
button_map[SDL_CONTROLLER_BUTTON_LEFTSTICK] = VANILLA_BTN_L3;
button_map[SDL_CONTROLLER_BUTTON_RIGHTSTICK] = VANILLA_BTN_R3;
button_map[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = VANILLA_BTN_L;
button_map[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = VANILLA_BTN_R;
button_map[SDL_CONTROLLER_BUTTON_DPAD_UP] = VANILLA_BTN_UP;
button_map[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = VANILLA_BTN_DOWN;
button_map[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = VANILLA_BTN_LEFT;
button_map[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = VANILLA_BTN_RIGHT;
axis_map[SDL_CONTROLLER_AXIS_LEFTX] = VANILLA_AXIS_L_X;
axis_map[SDL_CONTROLLER_AXIS_LEFTY] = VANILLA_AXIS_L_Y;
axis_map[SDL_CONTROLLER_AXIS_RIGHTX] = VANILLA_AXIS_R_X;
axis_map[SDL_CONTROLLER_AXIS_RIGHTY] = VANILLA_AXIS_R_Y;
axis_map[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = VANILLA_BTN_ZL;
axis_map[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = VANILLA_BTN_ZR;
}
int32_t pack_float(float f)
{
int32_t x;
memcpy(&x, &f, sizeof(int32_t));
return x;
}
int main(int argc, const char **argv)
{
vanilla_drm_ctx_t drm_ctx;
Uint32 sdl_flags = SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER;
#ifdef RASPBERRY_PI
if (!initialize_drm(&drm_ctx)) {
fprintf(stderr, "Failed to find DRM output\n");
//return 1;
}
#else
sdl_flags |= SDL_INIT_VIDEO;
#endif
// Initialize SDL2 for audio and input
if (SDL_Init(sdl_flags) < 0) {
fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError());
return 1;
}
// Initialize FFmpeg
#ifdef RASPBERRY_PI
const AVCodec *codec = avcodec_find_decoder_by_name("h264_v4l2m2m");
#else
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
#endif
if (!codec) {
fprintf(stderr, "No decoder was available\n");
return 1;
}
video_codec_ctx = avcodec_alloc_context3(codec);
if (!video_codec_ctx) {
fprintf(stderr, "Failed to allocate codec context\n");
return 1;
}
int ffmpeg_err;
#ifdef RASPBERRY_PI
ffmpeg_err = av_hwdevice_ctx_create(&video_codec_ctx->hw_device_ctx, AV_HWDEVICE_TYPE_DRM, "/dev/dri/card0", NULL, 0);
if (ffmpeg_err < 0) {
fprintf(stderr, "Failed to create hwdevice context: %s (%i)\n", av_err2str(ffmpeg_err), ffmpeg_err);
return 1;
}
// MAKE SURE WE GET DRM PRIME FRAMES BY OVERRIDING THE GET_FORMAT FUNCTION
video_codec_ctx->get_format = get_format;
#endif
ffmpeg_err = avcodec_open2(video_codec_ctx, codec, NULL);
if (ffmpeg_err < 0) {
fprintf(stderr, "Failed to open decoder: %i\n", ffmpeg_err);
return 1;
}
decoding_frame = av_frame_alloc();
present_frame = av_frame_alloc();
if (!decoding_frame || !present_frame) {
fprintf(stderr, "Failed to allocate AVFrame\n");
return 1;
}
video_packet = av_packet_alloc();
if (!video_packet) {
fprintf(stderr, "Failed to create renderer: %s\n", SDL_GetError());
return 1;
}
// Install logging debugger
vanilla_install_logger(logger);
// Start Vanilla
#ifdef RASPBERRY_PI
vanilla_start(0);
#else
vanilla_start(ntohl(inet_addr("127.0.0.1")));
#endif
// Initialize gamepad lookup tables
init_gamepad();
// Create variable for background threads to run
running = 1;
signal(SIGINT, sigint_handler);
pthread_mutex_init(&decoding_mutex, NULL);
pthread_cond_init(&decoding_wait_cond, NULL);
// Start background threads
SDL_Thread *backend_thread = SDL_CreateThread(run_backend, "vanilla-pi-backend", NULL);
#ifdef RASPBERRY_PI
SDL_Thread *display_thread = SDL_CreateThread(display_loop, "vanilla-pi-display", &drm_ctx);
#else
SDL_Window *window = SDL_CreateWindow("vanilla-pi", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_RESIZABLE);
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT);
AVFrame *frame = av_frame_alloc();
#endif
SDL_GameController *controller = find_valid_controller();
vui_context_t *vui = vui_alloc();
Uint32 ticks = SDL_GetTicks();
while (running) {
vui_reset(vui);
int sync_btn = vui_draw_button(vui, 100, 100, 100, 100, "Sync");
int connect_btn = vui_draw_button(vui, 300, 100, 100, 100, "Connect");
SDL_Event event;
#ifdef RASPBERRY_PI
if (SDL_WaitEvent(&event)) {
#else
while (SDL_PollEvent(&event)) {
#endif
switch (event.type) {
case SDL_QUIT:
// Exit loop
running = 0;
break;
case SDL_CONTROLLERDEVICEADDED:
// Attempt to find controller if one doesn't exist already
if (!controller) {
controller = find_valid_controller();
}
break;
case SDL_CONTROLLERDEVICEREMOVED:
// Handle current controller being removed
if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) {
SDL_GameControllerClose(controller);
controller = find_valid_controller();
}
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) {
int vanilla_btn = button_map[event.cbutton.button];
if (vanilla_btn != -1) {
vanilla_set_button(vanilla_btn, event.type == SDL_CONTROLLERBUTTONDOWN ? INT16_MAX : 0);
}
}
break;
case SDL_CONTROLLERAXISMOTION:
if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) {
int vanilla_axis = axis_map[event.caxis.axis];
Sint16 axis_value = event.caxis.value;
if (vanilla_axis != -1) {
vanilla_set_button(vanilla_axis, axis_value);
}
}
break;
case SDL_CONTROLLERSENSORUPDATE:
if (event.csensor.sensor == SDL_SENSOR_ACCEL) {
vanilla_set_button(VANILLA_SENSOR_ACCEL_X, pack_float(event.csensor.data[0]));
vanilla_set_button(VANILLA_SENSOR_ACCEL_Y, pack_float(event.csensor.data[1]));
vanilla_set_button(VANILLA_SENSOR_ACCEL_Z, pack_float(event.csensor.data[2]));
} else if (event.csensor.sensor == SDL_SENSOR_GYRO) {
vanilla_set_button(VANILLA_SENSOR_GYRO_PITCH, pack_float(event.csensor.data[0]));
vanilla_set_button(VANILLA_SENSOR_GYRO_YAW, pack_float(event.csensor.data[1]));
vanilla_set_button(VANILLA_SENSOR_GYRO_ROLL, pack_float(event.csensor.data[2]));
}
break;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_ESCAPE) {
running = 0;
}
break;
case SDL_MOUSEBUTTONDOWN:
{
int btn = vui_process_click(vui, event.button.x, event.button.y);
if (btn != -1) {
if (btn == sync_btn) {
// Go to sync menu
} else if (btn == connect_btn) {
// Go to connect menu
}
}
break;
}
}
if (controller) {
uint16_t amount = vibrate ? 0xFFFF : 0;
SDL_GameControllerRumble(controller, amount, amount, 0);
}
}
#ifndef RASPBERRY_PI
pthread_mutex_lock(&decoding_mutex);
if (present_frame->data[0]) {
av_frame_move_ref(frame, present_frame);
}
pthread_mutex_unlock(&decoding_mutex);
if (frame->data[0]) {
SDL_UpdateYUVTexture(texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
av_frame_unref(frame);
SDL_RenderCopy(renderer, texture, NULL, NULL);
}
SDL_RenderPresent(renderer);
#endif
}
vui_free(vui);
// Terminate background threads and wait for them to end gracefully
vanilla_stop();
pthread_mutex_lock(&decoding_mutex);
running = 0;
pthread_cond_broadcast(&decoding_wait_cond);
pthread_mutex_unlock(&decoding_mutex);
SDL_WaitThread(backend_thread, NULL);
#ifdef RASPBERRY_PI
SDL_WaitThread(display_thread, NULL);
#else
av_frame_free(&frame);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
#endif
pthread_cond_destroy(&decoding_wait_cond);
pthread_mutex_destroy(&decoding_mutex);
av_frame_free(&present_frame);
av_frame_free(&decoding_frame);
av_packet_free(&video_packet);
avcodec_free_context(&video_codec_ctx);
SDL_Quit();
free_drm(&drm_ctx);
return 0;
}
+3 -4
View File
@@ -7,9 +7,8 @@
#include <unistd.h>
#include <vanilla.h>
#include "game/game_decode.h"
#include "game/game_main.h"
#include "menu_common.h"
#include "menu_game.h"
#include "menu_main.h"
#include "platform.h"
@@ -63,8 +62,8 @@ void vpi_menu_action(vui_context_t *vui, vpi_extra_action_t action)
case VPI_ACTION_DISCONNECT:
{
if (vui_game_mode_get(vui)) {
// Simulate shutdown
vpi_game_set_error(VANILLA_ERR_SHUTDOWN);
// Send shutdown signal
vpi_game_shutdown();
} else {
// Quit Vanilla entirely
vpi_menu_quit_vanilla(vui);
+750 -30
View File
@@ -1,21 +1,46 @@
#include "menu_game.h"
#include <assert.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
#include <stdatomic.h>
#include <stdio.h>
#include <sys/time.h>
#include <vanilla.h>
#include "config.h"
#include "lang.h"
#include "game/game_decode.h"
#include "game/game_main.h"
#include "menu_common.h"
#include "menu_main.h"
#include "ui/ui_anim.h"
#include "ui/ui_util.h"
static char vpi_toast_string[VPI_TOAST_MAX_LEN];
static struct timeval vpi_toast_expiry;
static int vpi_toast_number = 0;
AVFrame *vpi_present_frame = 0;
pthread_mutex_t vpi_present_frame_mutex = PTHREAD_MUTEX_INITIALIZER;
static AVFormatContext *recording_fmt_ctx = 0;
static AVStream *recording_vstr;
static AVStream *recording_astr;
static struct timeval recording_start;
static char screenshot_buf[4096] = {0};
static const int VIDEO_STREAM_INDEX = 0;
static const int AUDIO_STREAM_INDEX = 1;
static int status_lbl;
static struct {
int64_t last_power_time;
} menu_game_ctx;
static _Atomic int vpi_game_queued_error = VANILLA_SUCCESS;
void back_to_main_menu(vui_context_t *vui, int button, void *v)
{
int layer = (intptr_t) v;
@@ -90,36 +115,579 @@ static void update_battery_information(vui_context_t *vui, int64_t time)
}
}
static inline void be16(uint8_t *p, uint16_t v) { p[0] = (uint8_t)(v >> 8); p[1] = (uint8_t)v; }
size_t build_avcc(uint8_t *dst, size_t cap,
const uint8_t * const *sps_list, const uint16_t *sps_len, int sps_count,
const uint8_t * const *pps_list, const uint16_t *pps_len, int pps_count,
int nal_length_size /* 1,2,4 */)
{
assert(nal_length_size==1 || nal_length_size==2 || nal_length_size==4);
// Derive profile/compat/level from the first SPS (bytes after NAL header)
// SPS layout starts with: [nal_header=0x67][profile_idc][profile_compat][level_idc]...
if (sps_count < 1 || sps_len[0] < 4) return 0;
const uint8_t *sps0 = sps_list[0];
uint8_t AVCProfileIndication = sps0[1];
uint8_t profile_compatibility = sps0[2];
uint8_t AVCLevelIndication = sps0[3];
uint8_t *p = dst;
uint8_t *e = dst + cap;
// configurationVersion
if (p+1 > e) return 0; *p++ = 1;
// profile/compat/level
if (p+3 > e) return 0;
*p++ = AVCProfileIndication;
*p++ = profile_compatibility;
*p++ = AVCLevelIndication;
// lengthSizeMinusOne (low 2 bits), upper 6 bits all 1s
if (p+1 > e) return 0;
uint8_t lengthSizeMinusOne = (uint8_t)(nal_length_size - 1); // 0→1B,1→2B,3→4B
*p++ = (uint8_t)(0xFC | (lengthSizeMinusOne & 0x03));
// numOfSPS (low 5 bits), upper 3 bits all 1s
if (sps_count > 31) return 0;
if (p+1 > e) return 0;
*p++ = (uint8_t)(0xE0 | (sps_count & 0x1F));
// Write each SPS: [2B len][bytes...]
for (int i = 0; i < sps_count; ++i) {
if (p+2+sps_len[i] > e) return 0;
be16(p, sps_len[i]); p += 2;
memcpy(p, sps_list[i], sps_len[i]); p += sps_len[i];
}
// numOfPPS
if (pps_count > 255) return 0;
if (p+1 > e) return 0;
*p++ = (uint8_t)pps_count;
// Write each PPS: [2B len][bytes...]
for (int i = 0; i < pps_count; ++i) {
if (p+2+pps_len[i] > e) return 0;
be16(p, pps_len[i]); p += 2;
memcpy(p, pps_list[i], pps_len[i]); p += pps_len[i];
}
return (size_t)(p - dst);
}
static enum AVPixelFormat vaapi_get_format(struct AVCodecContext *s, const enum AVPixelFormat *fmt)
{
while (*fmt != AV_PIX_FMT_NONE) {
if (*fmt == AV_PIX_FMT_VAAPI) {
return *fmt;
}
fmt++;
}
return AV_PIX_FMT_NONE;
}
static enum AVPixelFormat drm_get_format(struct AVCodecContext *s, const enum AVPixelFormat *fmt)
{
while (*fmt != AV_PIX_FMT_NONE) {
if (*fmt == AV_PIX_FMT_DRM_PRIME) {
return *fmt;
}
fmt++;
}
return AV_PIX_FMT_NONE;
}
typedef struct {
AVCodecContext *codec_ctx;
AVPacket *pkt;
AVFrame *frame;
AVBufferRef *hw_device_ctx;
} vpi_decode_state_t;
int vpi_decode_init(vpi_decode_state_t *s)
{
int ffmpeg_err;
// Initialize FFmpeg
const AVCodec *codec = 0;
enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
// Test for VAAPI
if ((ffmpeg_err = av_hwdevice_ctx_create(&s->hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, 0, 0, 0)) >= 0) {
vpilog("Decoding: VAAPI\n");
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
get_format = vaapi_get_format;
} else if ((ffmpeg_err = av_hwdevice_ctx_create(&s->hw_device_ctx, AV_HWDEVICE_TYPE_DRM, "/dev/dri/card0", 0, 0)) >= 0) {
vpilog("Decoding: DRM\n");
codec = avcodec_find_decoder_by_name("h264_v4l2m2m");
get_format = drm_get_format;
}
// If we couldn't find a decoder (regardless of if we found hwaccel), fallback to software
if (!codec) {
if (s->hw_device_ctx) {
av_buffer_unref(&s->hw_device_ctx);
}
vpilog("Decoding: Software\n");
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
get_format = 0;
}
// If we couldn't find a hardware or software decoder, fail
if (!codec) {
vpilog("No decoder was available\n");
return VANILLA_ERR_GENERIC;
}
s->codec_ctx = avcodec_alloc_context3(codec);
if (!s->codec_ctx) {
vpilog("Failed to allocate codec context\n");
return VANILLA_ERR_GENERIC;
}
if (s->hw_device_ctx) {
s->codec_ctx->hw_device_ctx = av_buffer_ref(s->hw_device_ctx);
}
if (get_format) {
s->codec_ctx->get_format = get_format;
}
const int BUILD_AVCC = 0;
if (BUILD_AVCC) {
uint8_t sps[100], pps[100];
uint16_t sps_size = vanilla_generate_sps_params(sps, sizeof(sps));
uint16_t pps_size = vanilla_generate_pps_params(pps, sizeof(pps));
const uint8_t *p_sps = sps;
const uint8_t *p_pps = pps;
s->codec_ctx->extradata = av_malloc(1024);
s->codec_ctx->extradata_size = build_avcc(
s->codec_ctx->extradata, 1024,
&p_sps, &sps_size, 1,
&p_pps, &pps_size, 1,
4
);
}
ffmpeg_err = avcodec_open2(s->codec_ctx, codec, NULL);
if (ffmpeg_err < 0) {
vpilog("Failed to open decoder: %i\n", ffmpeg_err);
return VANILLA_ERR_GENERIC;
}
AVFrame *decoding_frame = av_frame_alloc();
if (!decoding_frame) {
vpilog("Failed to allocate AVFrame\n");
return VANILLA_ERR_GENERIC;
}
vpi_present_frame = av_frame_alloc();
if (!vpi_present_frame) {
vpilog("Failed to allocate AVFrame\n");
return VANILLA_ERR_GENERIC;
}
s->pkt = av_packet_alloc();
if (!s->pkt) {
vpilog("Failed to allocate AVPacket\n");
return VANILLA_ERR_GENERIC;
}
s->frame = av_frame_alloc();
vpilog("initialized state!\n");
return VANILLA_SUCCESS;
}
void vpi_decode_exit(vpi_decode_state_t *s)
{
if (s->frame)
av_frame_free(&s->frame);
if (s->pkt)
av_packet_free(&s->pkt);
if (vpi_present_frame)
av_frame_free(&vpi_present_frame);
if (s->codec_ctx)
avcodec_free_context(&s->codec_ctx);
if (s->hw_device_ctx)
av_buffer_unref(&s->hw_device_ctx);
}
int64_t get_recording_timestamp(AVRational timebase)
{
struct timeval tv;
gettimeofday(&tv, 0);
int64_t us = (tv.tv_sec - recording_start.tv_sec) * 1000000 + (tv.tv_usec - recording_start.tv_usec);
return av_rescale_q(us, (AVRational) {1, 1000000}, timebase);
}
int dump_frame_to_file(const AVFrame *frame, const char *filename)
{
AVFormatContext *ofmt;
int ret = avformat_alloc_output_context2(&ofmt, 0, 0, filename);
if (ret < 0) {
vpilog("Failed to allocate new AVFormatContext for screenshot\n");
goto exit;
}
enum AVCodecID codec_id = AV_CODEC_ID_PNG;
const AVCodec *codec = avcodec_find_encoder(codec_id);
if (!codec) {
vpilog("Failed to find encoder for screenshot\n");
ret = AVERROR_ENCODER_NOT_FOUND;
goto exit_and_free_avformatcontext;
}
AVStream *s = avformat_new_stream(ofmt, 0);
if (!s) {
vpilog("Failed to create new AVStream for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_avformatcontext;
}
s->id = 0;
AVCodecContext *ocodec_ctx = avcodec_alloc_context3(codec);
if (!ocodec_ctx) {
vpilog("Failed to create new AVCodecContext for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_avformatcontext;
}
enum AVPixelFormat opix_fmt = AV_PIX_FMT_RGB24;
ocodec_ctx->width = frame->width;
ocodec_ctx->height = frame->height;
ocodec_ctx->pix_fmt = opix_fmt;
ocodec_ctx->codec_id = codec_id;
ocodec_ctx->time_base = (AVRational){1, 1};
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 2, 100)
ocodec_ctx->frame_num = 1;
#else
ocodec_ctx->frame_number = 1;
#endif
if (ofmt->flags & AVFMT_GLOBALHEADER) {
ocodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
AVPacket *opkt = av_packet_alloc();
if (!opkt) {
vpilog("Failed to create new AVPacket for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_avcodeccontext;
}
ret = avcodec_open2(ocodec_ctx, codec, 0);
if (ret < 0) {
vpilog("Failed to open encoder for screenshot\n");
goto exit_and_free_avpacket;
}
ret = avcodec_parameters_from_context(s->codecpar, ocodec_ctx);
if (ret < 0) {
vpilog("Failed to copy params from context for screenshot\n");
goto exit_and_free_avpacket;
}
ret = avformat_write_header(ofmt, 0);
if (ret < 0) {
vpilog("Failed to write header for screenshot\n");
goto exit_and_free_avpacket;
}
AVFrame *swframe = 0;
if (frame->format != AV_PIX_FMT_YUV420P) {
// Assume this frame is hardware accelerated and we need to download it
// to CPU memory
swframe = av_frame_alloc();
if (!swframe) {
vpilog("Failed to allocate CPU frame for screenshot\n");
ret = AVERROR(EINVAL);
goto exit_and_free_avpacket;
}
if (av_hwframe_transfer_data(swframe, frame, 0) < 0) {
vpilog("Failed to transfer data from hardware for screenshot\n");
ret = AVERROR(EINVAL);
goto exit_and_free_swframe;
}
// Use this CPU frame instead from now on
frame = swframe;
}
struct SwsContext *osws_ctx = sws_getContext(
frame->width, frame->height, frame->format,
frame->width, frame->height, opix_fmt,
0, 0, 0, 0);
if (!osws_ctx) {
vpilog("Failed to get SwsContext for screenshot\n");
ret = AVERROR(EINVAL);
goto exit_and_free_avpacket;
}
AVFrame *oframe = av_frame_alloc();
if (!oframe) {
vpilog("Failed to allocate AVFrame for screenshot\n");
ret = AVERROR(ENOMEM);
goto exit_and_free_swscontext;
}
oframe->width = frame->width;
oframe->height = frame->height;
oframe->format = opix_fmt;
ret = av_frame_get_buffer(oframe, 0);
if (ret < 0) {
vpilog("Failed to get AVFrame buffer for screenshot\n");
goto exit_and_free_frame;
}
ret = sws_scale(osws_ctx, (const uint8_t **) frame->data, (const int *) frame->linesize, 0, frame->height, oframe->data, oframe->linesize);
if (ret < 0) {
vpilog("Failed to do swscale for screenshot\n");
goto exit_and_free_frame;
}
ret = avcodec_send_frame(ocodec_ctx, oframe);
if (ret < 0) {
vpilog("Failed to send frame to encoder for screenshot\n");
goto exit_and_free_frame;
}
ret = avcodec_receive_packet(ocodec_ctx, opkt);
if (ret < 0) {
vpilog("Failed to receive packet from encoder for screenshot\n");
goto exit_and_free_frame;
}
ret = av_write_frame(ofmt, opkt);
if (ret < 0) {
vpilog("Failed to write frame for screenshot\n");
goto exit_and_free_frame;
}
ret = av_write_trailer(ofmt);
if (ret < 0) {
vpilog("Failed to write trailer for screenshot\n");
goto exit_and_free_frame;
}
exit_and_free_frame:
av_frame_free(&oframe);
exit_and_free_swscontext:
sws_freeContext(osws_ctx);
exit_and_free_swframe:
if (swframe) {
av_frame_free(&swframe);
}
exit_and_free_avpacket:
av_packet_free(&opkt);
exit_and_free_avcodeccontext:
avcodec_free_context(&ocodec_ctx);
exit_and_free_avformatcontext:
avformat_free_context(ofmt);
exit:
if (ret >= 0) {
vpilog("Saved screenshot to \"%s\"\n", filename);
char buf[VPI_TOAST_MAX_LEN];
snprintf(buf, sizeof(buf), lang(VPI_LANG_SCREENSHOT_SUCCESS), filename);
vpi_show_toast(buf);
} else {
char buf[VPI_TOAST_MAX_LEN];
snprintf(buf, sizeof(buf), lang(VPI_LANG_SCREENSHOT_ERROR), ret);
vpi_show_toast(buf);
}
return ret;
}
void vpi_game_shutdown()
{
vpi_game_queued_error = VANILLA_ERR_SHUTDOWN;
}
static pthread_t vpi_event_thread;
void *vpi_event_loop(void *arg)
{
vui_context_t *vui = (vui_context_t *) arg;
static int vpi_decode_alloc = 0;
static vpi_decode_state_t s;
vanilla_event_t event;
while (vpi_game_queued_error == VANILLA_SUCCESS && vanilla_wait_event(&event)) {
int stop = 0;
switch (event.type) {
case VANILLA_EVENT_VIDEO:
if (!vpi_decode_alloc) {
int ret = vpi_decode_init(&s);
if (ret >= 0) {
vpi_decode_alloc = 1;
}
}
if (vpi_decode_alloc) {
AVPacket *pkt = s.pkt;
// Send packet to decoder
pkt->data = event.data;
pkt->size = event.size;
if (recording_fmt_ctx) {
pkt->stream_index = VIDEO_STREAM_INDEX;
int64_t ts = get_recording_timestamp(recording_vstr->time_base);
pkt->dts = ts;
pkt->pts = ts;
av_interleaved_write_frame(recording_fmt_ctx, pkt);
// av_interleaved_write_frame() eventually calls av_packet_unref(),
// so we must put the references back in
pkt->data = event.data;
pkt->size = event.size;
}
int err = avcodec_send_packet(s.codec_ctx, pkt);
if (err < 0) {
vpilog("Failed to send packet to decoder: %s (%i)\n", av_err2str(err), err);
// return 0;
vanilla_request_idr();
} else {
int err;
int ret = 1;
// Retrieve frame from decoder
while (1) {
err = avcodec_receive_frame(s.codec_ctx, s.frame);
if (err == AVERROR(EAGAIN)) {
// Decoder wants another packet before it can output a frame. Silently exit.
break;
} else if (err < 0) {
vpilog("Failed to receive frame from decoder: %i\n", err);
ret = 0;
break;
} else {
pthread_mutex_lock(&vpi_present_frame_mutex);
// Swap refs from decoding_frame to present_frame
av_frame_unref(vpi_present_frame);
av_frame_move_ref(vpi_present_frame, s.frame);
vpi_present_frame->pts = s.frame->pts;
if (screenshot_buf[0] != 0) {
// Dump this frame into file
dump_frame_to_file(vpi_present_frame, screenshot_buf);
screenshot_buf[0] = 0;
}
pthread_mutex_unlock(&vpi_present_frame_mutex);
// Not thread safe?
if (!vui_game_mode_get(vui)) {
vui_game_mode_set(vui, 1);
vui_audio_set_enabled(vui, 1);
}
}
}
// return ret;
}
}
break;
case VANILLA_EVENT_AUDIO:
vui_audio_push(vui, event.data, event.size);
// We send audio to vpi_decode, but not actually for decoding since
// the audio is already uncompressed. We send it in case vpi_decode
// is recording, so the audio can be written to the file.
vpi_decode_send_audio(event.data, event.size);
break;
case VANILLA_EVENT_VIBRATE:
vui_vibrate_set(vui, event.data[0]);
break;
case VANILLA_EVENT_ERROR:
{
int backend_err = *(int *)event.data;
switch (backend_err) {
case VANILLA_SUCCESS:
case VANILLA_ERR_CONNECTED:
// Do nothing
break;
case VANILLA_ERR_DISCONNECTED:
case VANILLA_ERR_SHUTDOWN:
vpi_game_queued_error = VANILLA_ERR_SHUTDOWN;
break;
default:
vpi_game_queued_error = backend_err;
};
break;
}
case VANILLA_EVENT_MIC:
vui_mic_enabled_set(vui, event.data[0]);
break;
}
vanilla_free_event(&event);
}
if (vpi_decode_alloc) {
vpi_decode_exit(&s);
vpi_decode_alloc = 0;
}
return NULL;
}
void vpi_display_update(vui_context_t *vui, int64_t time, void *v)
{
int backend_err = vpi_game_error();
if (backend_err != VANILLA_SUCCESS) {
switch (backend_err) {
case VANILLA_ERR_CONNECTED:
// Wait for frame before enabling "game mode"
pthread_mutex_lock(&vpi_decode_loop_mutex);
if (vpi_present_frame && vpi_present_frame->format != -1) {
vui_game_mode_set(vui, 1);
vui_audio_set_enabled(vui, 1);
vpi_game_set_error(VANILLA_SUCCESS);
}
pthread_mutex_unlock(&vpi_decode_loop_mutex);
break;
case VANILLA_ERR_DISCONNECTED:
vui_label_update_text(vui, status_lbl, lang(VPI_LANG_DISCONNECTED));
vui_audio_set_enabled(vui, 0);
vui_game_mode_set(vui, 0);
break;
case VANILLA_ERR_SHUTDOWN:
vanilla_stop();
update_battery_information(vui, time);
switch ((int)vpi_game_queued_error) {
case VANILLA_SUCCESS:
// All good, do nothing
break;
case VANILLA_ERR_DISCONNECTED:
// Tell user connection has terminated and try to re-establish
vui_label_update_text(vui, status_lbl, lang(VPI_LANG_DISCONNECTED));
vui_audio_set_enabled(vui, 0);
vui_game_mode_set(vui, 0);
break;
default:
// Something went wrong, assume we must fail and report to user
pthread_join(vpi_event_thread, 0);
vanilla_stop();
if (vpi_game_queued_error != VANILLA_ERR_SHUTDOWN) {
show_error(vui, (void*)(intptr_t) vpi_game_queued_error);
} else {
vpi_menu_main(vui, 0);
break;
default:
vanilla_stop();
show_error(vui, (void*)(intptr_t) backend_err);
};
} else {
update_battery_information(vui, time);
}
vpi_game_queued_error = VANILLA_SUCCESS;
}
}
@@ -150,12 +718,14 @@ void vpi_menu_game_start(vui_context_t *vui, void *v)
vui_get_screen_size(vui, &scrw, &scrh);
status_lbl = vui_label_create(vui, 0, scrh * 2 / 5, scrw, scrh, buf, vui_color_create(0.5f,0.5f,0.5f,1), VUI_FONT_SIZE_NORMAL, fglayer);
// TODO: `state` does not get freed if
const int cancel_btn_w = BTN_SZ*3;
int cancel_btn = vui_button_create(vui, scrw/2 - cancel_btn_w/2, scrh * 3 / 5, cancel_btn_w, BTN_SZ, lang(VPI_LANG_CANCEL_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, cancel_connect, (void *) (intptr_t) fglayer);
vui_transition_fade_layer_in(vui, fglayer, 0, 0);
vpi_game_start(vui);
vpi_game_queued_error = VANILLA_SUCCESS;
pthread_create(&vpi_event_thread, 0, vpi_event_loop, vui);
vui_start_passive_animation(vui, vpi_display_update, 0);
}
@@ -165,3 +735,153 @@ void vpi_menu_game(vui_context_t *vui, void *v)
{
vpi_menu_start_pipe(vui, 0, vpi_menu_game_start, v, 0, 0);
}
void vpi_show_toast(const char *message)
{
vui_strncpy(vpi_toast_string, message, sizeof(vpi_toast_string));
gettimeofday(&vpi_toast_expiry, 0);
// Toast lasts for 2 seconds
vpi_toast_expiry.tv_sec += 2;
vpi_toast_number++;
}
void vpi_get_toast(int *number, char *output, size_t output_size, struct timeval *expiry_time)
{
if (number)
*number = vpi_toast_number;
if (output && output_size) {
vui_strncpy(output, vpi_toast_string, output_size);
}
if (expiry_time) {
*expiry_time = vpi_toast_expiry;
}
}
void vpi_decode_send_audio(const void *data, size_t size)
{
if (recording_fmt_ctx) {
int ret;
AVPacket *pkt = av_packet_alloc();
pkt->data = (uint8_t *) data;
pkt->size = size;
int64_t ts = get_recording_timestamp(recording_astr->time_base);
pkt->stream_index = AUDIO_STREAM_INDEX;
pkt->dts = ts;
pkt->pts = ts;
av_interleaved_write_frame(recording_fmt_ctx, pkt);
av_packet_free(&pkt);
}
}
int vpi_decode_is_recording()
{
return (recording_fmt_ctx != 0);
}
int vpi_decode_record(const char *filename)
{
int r = avformat_alloc_output_context2(&recording_fmt_ctx, 0, 0, filename);
if (r < 0) {
vpilog("Failed to allocate output context for recording\n");
goto exit;
}
recording_vstr = avformat_new_stream(recording_fmt_ctx, 0);
if (!recording_vstr) {
vpilog("Failed to allocate video stream for recording\n");
goto exit_and_free_context;
}
recording_vstr->id = VIDEO_STREAM_INDEX;
recording_vstr->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
recording_vstr->codecpar->width = 854;
recording_vstr->codecpar->height = 480;
recording_vstr->codecpar->format = AV_PIX_FMT_YUV420P;
recording_vstr->time_base = (AVRational) {1, 60};
recording_vstr->codecpar->codec_id = AV_CODEC_ID_H264;
recording_vstr->codecpar->extradata = av_malloc(200);
recording_vstr->codecpar->extradata_size = vanilla_generate_h264_header(recording_vstr->codecpar->extradata, 200);
recording_astr = avformat_new_stream(recording_fmt_ctx, 0);
if (!recording_astr) {
vpilog("Failed to allocate audio stream for recording\n");
goto exit_and_free_context;
}
recording_astr->id = AUDIO_STREAM_INDEX;
recording_astr->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
recording_astr->codecpar->sample_rate = 48000;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57,24,100)
recording_astr->codecpar->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO;
#else
recording_astr->codecpar->channel_layout = AV_CH_LAYOUT_STEREO;
#endif
recording_astr->codecpar->format = AV_SAMPLE_FMT_S16;
recording_astr->time_base = (AVRational) {1, 48000};
recording_astr->codecpar->codec_id = AV_CODEC_ID_PCM_S16LE;
r = avio_open2(&recording_fmt_ctx->pb, filename, AVIO_FLAG_WRITE, 0, 0);
if (r < 0) {
vpilog("Failed to open AVIO for recording\n");
goto exit_and_free_context;
}
r = avformat_write_header(recording_fmt_ctx, 0);
if (r < 0) {
vpilog("Failed to write header for recording\n");
goto exit_and_free_context;
}
vanilla_request_idr();
gettimeofday(&recording_start, 0);
char buf[VPI_TOAST_MAX_LEN];
snprintf(buf, sizeof(buf), lang(VPI_LANG_RECORDING_START), filename);
vpi_show_toast(buf);
vpilog("Started recording to \"%s\"\n", filename);
goto exit;
exit_and_free_context:
avformat_free_context(recording_fmt_ctx);
recording_fmt_ctx = 0;
exit:
return r;
}
void vpi_decode_record_stop()
{
if (recording_fmt_ctx) {
int r = av_write_trailer(recording_fmt_ctx);
if (r < 0) {
vpilog("Failed to write trailer for recording\n");
}
avio_closep(&recording_fmt_ctx->pb);
avformat_free_context(recording_fmt_ctx);
recording_fmt_ctx = 0;
vpilog("Finished recording\n");
vpi_show_toast(lang(VPI_LANG_RECORDING_FINISH));
}
}
void vpi_decode_screenshot(const char *filename)
{
// Grab copy of filename
vui_strncpy(screenshot_buf, filename, sizeof(screenshot_buf));
}
+21 -1
View File
@@ -1,8 +1,28 @@
#ifndef VANILLA_PI_MENU_GAME_H
#define VANILLA_PI_MENU_GAME_H
#include <libavutil/frame.h>
#include <pthread.h>
#include <sys/time.h>
#include "ui/ui.h"
#define VPI_TOAST_MAX_LEN 1024
extern AVFrame *vpi_present_frame;
extern pthread_mutex_t vpi_present_frame_mutex;
void vpi_menu_game(vui_context_t *vui, void *v);
#endif // VANILLA_PI_MENU_GAME_H
void vpi_game_shutdown();
void vpi_get_toast(int *number, char *output, size_t output_size, struct timeval *expiry_time);
void vpi_show_toast(const char *message);
void vpi_decode_screenshot(const char *filename);
int vpi_decode_is_recording();
int vpi_decode_record(const char *filename);
void vpi_decode_record_stop();
void vpi_decode_send_audio(const void *data, size_t size);
#endif // VANILLA_PI_MENU_GAME_H
+3
View File
@@ -468,6 +468,9 @@ int vpi_start_pipe()
}
#endif
// Tell lib our wireless interface
vanilla_set_wireless_interface(vpi_config.wireless_interface);
return ret;
}
}
+1 -1
View File
@@ -923,7 +923,7 @@ void vui_process_keyup(vui_context_t *ctx, int button)
vui_power_state_t vui_power_state_get(vui_context_t *ctx, int *percent)
{
if (ctx->power_state_handler) {
return ctx->power_state_handler(percent);
return ctx->power_state_handler(ctx, percent);
} else {
if (percent) {
*percent = -1;
+1 -1
View File
@@ -99,7 +99,7 @@ typedef void (*vui_audio_handler_t)(const void *data, size_t size, void *userdat
typedef void (*vui_vibrate_handler_t)(uint8_t vibrate, void *userdata);
typedef int (*vui_font_height_handler_t)(vui_font_size_t size, void *userdata);
typedef void (*vui_text_open_handler_t)(vui_context_t *ctx, int textedit, int open, void *userdata);
typedef vui_power_state_t (*vui_power_state_handler_t)(int *percent);
typedef vui_power_state_t (*vui_power_state_handler_t)(vui_context_t *ctx, int *percent);
typedef void (*vui_audio_enabled_handler_t)(vui_context_t *ctx, int enabled, void *userdata);
typedef struct vui_context_t {
+473 -355
View File
File diff suppressed because it is too large Load Diff
+199 -186
View File
@@ -1,23 +1,184 @@
#include "drm.h"
#include "ui_sdl_drm.h"
#include <drm_fourcc.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <libavutil/hwcontext_drm.h>
#include <stdint.h>
#include <SDL2/SDL_syswm.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_mode.h>
#include <drm_fourcc.h>
#include <errno.h>
#include <string.h>
#define ALIGN(x, a) ((x) + (a - 1)) & (~(a - 1))
#include "platform.h"
int initialize_drm(vanilla_drm_ctx_t *ctx)
#define MAX_HANDLE_CACHE 32
typedef struct {
int fd;
uint32_t handle;
} vanilla_drm_handle_t;
typedef struct vanilla_drm_ctx_t {
int fd;
uint32_t crtc;
int crtc_index;
uint32_t plane_id;
int got_plane;
uint32_t fb_id;
int got_fb;
vanilla_drm_handle_t handle_cache[MAX_HANDLE_CACHE];
size_t handle_cache_count;
} vanilla_drm_ctx_t;
static int find_plane(const int drmfd, const int crtcidx, const uint32_t format, uint32_t *const pplane_id)
{
// Open DRM
ctx->fd = drmOpen("vc4", 0);
if (ctx->fd == -1) {
return 0;
}
drmModePlaneResPtr planes;
drmModePlanePtr plane;
unsigned int i;
unsigned int j;
int ret = 0;
planes = drmModeGetPlaneResources(drmfd);
if (!planes) {
vpilog("drmModeGetPlaneResources failed: %s\n", strerror(errno));
return -1;
}
for (i = 0; i < planes->count_planes; ++i) {
plane = drmModeGetPlane(drmfd, planes->planes[i]);
if (!plane) {
vpilog("drmModeGetPlane failed: %s\n", strerror(errno));
break;
}
if (!(plane->possible_crtcs & (1 << crtcidx))) {
drmModeFreePlane(plane);
continue;
}
for (j = 0; j < plane->count_formats; ++j) {
if (plane->formats[j] == format) break;
}
if (j == plane->count_formats) {
drmModeFreePlane(plane);
continue;
}
*pplane_id = plane->plane_id;
drmModeFreePlane(plane);
break;
}
if (i == planes->count_planes) ret = -1;
drmModeFreePlaneResources(planes);
return ret;
}
int vui_sdl_drm_present(vanilla_drm_ctx_t *ctx, AVFrame *frame)
{
const AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *) frame->data[0];
const uint32_t format = desc->layers[0].format;
if (!ctx->got_plane) {
if (find_plane(ctx->fd, ctx->crtc_index, format, &ctx->plane_id) < 0) {
vpilog("Failed to find plane for format: %x\n", format);
return 0;
} else {
ctx->got_plane = 1;
}
}
uint32_t pitches[AV_DRM_MAX_PLANES] = {0};
uint32_t offsets[AV_DRM_MAX_PLANES] = {0};
uint32_t bo_handles[AV_DRM_MAX_PLANES] = {0};
uint64_t modifiers[AV_DRM_MAX_PLANES] = {0};
uint32_t handles[AV_DRM_MAX_PLANES];
for (int i = 0; i < desc->nb_objects; i++) {
int fd = desc->objects[i].fd;
uint32_t handle = 0;
for (size_t i = 0; i < ctx->handle_cache_count; i++) {
vanilla_drm_handle_t *h = &ctx->handle_cache[i];
if (h->fd == fd) {
handle = h->handle;
break;
}
}
if (!handle) {
vpilog("(re)opening DRM handle because fd 0x%x was not found\n", fd);
vanilla_drm_handle_t *h = &ctx->handle_cache[ctx->handle_cache_count];
if (drmPrimeFDToHandle(ctx->fd, fd, &handle) != 0) {
vpilog("Failed to get handle from file descriptor: %s\n", strerror(errno));
return 0;
}
h->handle = handle;
h->fd = fd;
ctx->handle_cache_count++;
}
handles[i] = handle;
}
int n = 0;
for (int i = 0; i < desc->nb_layers; i++) {
const AVDRMLayerDescriptor *layer = &desc->layers[i];
for (int j = 0; j < layer->nb_planes; j++) {
const AVDRMPlaneDescriptor *plane = &layer->planes[j];
const AVDRMObjectDescriptor *obj = &desc->objects[plane->object_index];
pitches[n] = plane->pitch;
offsets[n] = plane->offset;
modifiers[n] = obj->format_modifier;
bo_handles[n] = handles[plane->object_index];
n++;
}
}
uint32_t new_fb;
if (drmModeAddFB2WithModifiers(ctx->fd,
frame->width, frame->height, desc->layers[0].format,
bo_handles, pitches, offsets, modifiers,
&new_fb, DRM_MODE_FB_MODIFIERS) != 0) {
vpilog("Failed to create framebuffer: %s\n", strerror(errno));
return 0;
}
if (drmModeSetPlane(ctx->fd, ctx->plane_id, ctx->crtc, new_fb, 0,
0, 0, frame->width, frame->height,
0, 0, frame->width << 16, frame->height << 16) != 0) {
vpilog("Failed to set plane: %s\n", strerror(errno));
return 0;
}
// Free old framebuffer
if (ctx->got_fb) {
drmModeRmFB(ctx->fd, ctx->fb_id);
}
ctx->fb_id = new_fb;
ctx->got_fb = 1;
return 1;
}
int vui_sdl_drm_initialize(vanilla_drm_ctx_t **c, SDL_Window *window)
{
vanilla_drm_ctx_t *ctx = (vanilla_drm_ctx_t *) malloc(sizeof(vanilla_drm_ctx_t));
*c = ctx;
memset(ctx, 0, sizeof(vanilla_drm_ctx_t));
SDL_SysWMinfo wmi;
SDL_VERSION(&wmi.version);
if (!SDL_GetWindowWMInfo(window, &wmi)) return 0;
if (wmi.subsystem != SDL_SYSWM_KMSDRM) return 0;
ctx->fd = wmi.info.kmsdrm.drm_fd;
int ret = 0;
@@ -49,194 +210,46 @@ int initialize_drm(vanilla_drm_ctx_t *ctx)
}
drmModeFreeConnector(c);
}
// Free DRM resources
drmModeFreeResources(res);
ctx->got_plane = 0;
ctx->got_fb = 0;
ctx->got_handles = 0;
memset(ctx->handles, 0, sizeof(ctx->handles));
return ret;
}
int free_drm(vanilla_drm_ctx_t *ctx)
{
if (ctx->got_fb) {
drmModeRmFB(ctx->fd, ctx->fb_id);
for (size_t i = 0; i < MAX_HANDLE_CACHE; i++) {
vanilla_drm_handle_t *h = &ctx->handle_cache[i];
h->fd = -1;
h->handle = 0;
}
// Close DRM
drmClose(ctx->fd);
}
static int find_plane(const int drmfd, const int crtcidx, const uint32_t format,
uint32_t *const pplane_id)
{
drmModePlaneResPtr planes;
drmModePlanePtr plane;
unsigned int i;
unsigned int j;
int ret = 0;
planes = drmModeGetPlaneResources(drmfd);
if (!planes) {
fprintf(stderr, "drmModeGetPlaneResources failed: %s\n", strerror(errno));
return -1;
}
for (i = 0; i < planes->count_planes; ++i) {
plane = drmModeGetPlane(drmfd, planes->planes[i]);
if (!plane) {
fprintf(stderr, "drmModeGetPlane failed: %s\n", strerror(errno));
break;
}
if (!(plane->possible_crtcs & (1 << crtcidx))) {
drmModeFreePlane(plane);
continue;
}
for (j = 0; j < plane->count_formats; ++j) {
if (plane->formats[j] == format) break;
}
if (j == plane->count_formats) {
drmModeFreePlane(plane);
continue;
}
*pplane_id = plane->plane_id;
drmModeFreePlane(plane);
break;
}
if (i == planes->count_planes) ret = -1;
drmModeFreePlaneResources(planes);
return ret;
}
extern int running;
int display_drm(vanilla_drm_ctx_t *ctx, AVFrame *frame)
int vui_sdl_drm_free(vanilla_drm_ctx_t **c)
{
const AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *) frame->data[0];
const uint32_t format = desc->layers[0].format;
vanilla_drm_ctx_t *ctx = *c;
*c = NULL;
if (!ctx->got_plane) {
if (find_plane(ctx->fd, ctx->crtc_index, format, &ctx->plane_id) < 0) {
fprintf(stderr, "Failed to find plane for format: %x\n", format, DRM_FORMAT_YUV420, frame->format);
return 0;
} else {
ctx->got_plane = 1;
}
struct drm_gem_close gem_close = {0};
for (size_t i = 0; i < ctx->handle_cache_count; i++) {
vanilla_drm_handle_t *h = &ctx->handle_cache[i];
gem_close.handle = h->handle;
drmIoctl(ctx->fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
h->handle = 0;
h->fd = -1;
}
/*{
drmVBlank vbl;
vbl.request.type = DRM_VBLANK_RELATIVE;
vbl.request.sequence = 0;
while (running && drmWaitVBlank(ctx->fd, &vbl)) {
// Not sure what this does, stole it from hello_drmprime
if (errno != EINTR) {
break;
}
}
}
if (!running) {
return 1;
}*/
uint32_t pitches[AV_DRM_MAX_PLANES] = {0};
uint32_t offsets[AV_DRM_MAX_PLANES] = {0};
uint32_t bo_handles[AV_DRM_MAX_PLANES] = {0};
uint64_t modifiers[AV_DRM_MAX_PLANES] = {0};
uint32_t new_handles[AV_DRM_MAX_PLANES] = {0};
for (int i = 0; i < desc->nb_objects; i++) {
if (drmPrimeFDToHandle(ctx->fd, desc->objects[i].fd, &new_handles[i]) != 0) {
fprintf(stderr, "Failed to get handle from file descriptor: %s\n", strerror(errno));
return 0;
}
}
int n = 0;
for (int i = 0; i < desc->nb_layers; i++) {
const AVDRMLayerDescriptor *layer = &desc->layers[i];
for (int j = 0; j < layer->nb_planes; j++) {
const AVDRMPlaneDescriptor *plane = &layer->planes[j];
const AVDRMObjectDescriptor *obj = &desc->objects[plane->object_index];
pitches[n] = plane->pitch;
offsets[n] = plane->offset;
modifiers[n] = obj->format_modifier;
bo_handles[n] = new_handles[plane->object_index];
n++;
}
}
/*fprintf(stderr, "%dx%d, fmt: %x, boh=%d,%d,%d,%d, pitch=%d,%d,%d,%d,"
" offset=%d,%d,%d,%d, mod=%llx,%llx,%llx,%llx\n",
frame->width,
frame->height,
desc->layers[0].format,
bo_handles[0],
bo_handles[1],
bo_handles[2],
bo_handles[3],
pitches[0],
pitches[1],
pitches[2],
pitches[3],
offsets[0],
offsets[1],
offsets[2],
offsets[3],
(long long)modifiers[0],
(long long)modifiers[1],
(long long)modifiers[2],
(long long)modifiers[3]
);*/
uint32_t new_fb;
if (drmModeAddFB2WithModifiers(ctx->fd,
frame->width, frame->height, desc->layers[0].format,
bo_handles, pitches, offsets, modifiers,
&new_fb, DRM_MODE_FB_MODIFIERS) != 0) {
fprintf(stderr, "Failed to create framebuffer: %s\n", strerror(errno));
return 0;
}
if (drmModeSetPlane(ctx->fd, ctx->plane_id, ctx->crtc, new_fb, 0,
0, 0, frame->width, frame->height,
0, 0, frame->width << 16, frame->height << 16) != 0) {
fprintf(stderr, "Failed to set plane: %s\n", strerror(errno));
return 0;
}
// Free old framebuffer
if (ctx->got_fb) {
drmModeRmFB(ctx->fd, ctx->fb_id);
ctx->got_fb = 0;
}
ctx->fb_id = new_fb;
ctx->got_fb = 1;
if (ctx->got_handles) {
struct drm_gem_close gem_close = {0};
for (int i = 0; i < desc->nb_objects; i++) {
if (ctx->handles[i]) {
gem_close.handle = ctx->handles[i];
drmIoctl(ctx->fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
ctx->handles[i] = 0;
}
}
}
memcpy(ctx->handles, new_handles, sizeof(ctx->handles));
ctx->got_handles = 1;
ctx->got_plane = 0;
return 1;
}
// Close DRM (now owned by SDL so don't do this)
// drmClose(ctx->fd);
free(ctx);
return 0;
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef VPI_UI_DRM_H
#define VPI_UI_DRM_H
#include <SDL2/SDL.h>
#include <libavutil/frame.h>
typedef struct vanilla_drm_ctx_t vanilla_drm_ctx_t;
int vui_sdl_drm_initialize(vanilla_drm_ctx_t **ctx, SDL_Window *window);
int vui_sdl_drm_present(vanilla_drm_ctx_t *ctx, AVFrame *frame);
int vui_sdl_drm_free(vanilla_drm_ctx_t **ctx);
#endif // VPI_UI_DRM_H
+131 -112
View File
@@ -2,16 +2,6 @@
#include "gamepad.h"
#ifdef _WIN32
#include <winsock2.h>
typedef uint32_t in_addr_t;
typedef uint16_t in_port_t;
#else
#include <arpa/inet.h>
#include <errno.h>
#include <sys/un.h>
#endif
#include <assert.h>
#include <pthread.h>
#include <signal.h>
@@ -31,18 +21,13 @@ typedef uint16_t in_port_t;
static uint32_t SERVER_ADDRESS = 0;
static const int MAX_PIPE_RETRY = 5;
char wireless_interface[128];
#define EVENT_BUFFER_SIZE 65536
#define EVENT_BUFFER_ARENA_SIZE VANILLA_MAX_EVENT_COUNT * 2
uint8_t *EVENT_BUFFER_ARENA[EVENT_BUFFER_ARENA_SIZE] = {0};
pthread_mutex_t event_buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
typedef union {
struct sockaddr_in in;
#ifndef _WIN32
struct sockaddr_un un;
#endif
} sockaddr_u;
static inline int skterr()
{
#ifdef _WIN32
@@ -52,24 +37,10 @@ static inline int skterr()
#endif
}
in_addr_t get_real_server_address()
{
if (SERVER_ADDRESS == VANILLA_ADDRESS_DIRECT) {
// The Wii U always places itself at this address
return inet_addr("192.168.1.10");
} else if (SERVER_ADDRESS != VANILLA_ADDRESS_LOCAL) {
// If there's a remote pipe, we send to that
return SERVER_ADDRESS;
}
// Must be local, in which case we don't care
return INADDR_ANY;
}
void create_sockaddr(sockaddr_u *addr, size_t *size, in_addr_t inaddr, uint16_t port, int delete)
void create_sockaddr(sockaddr_u *addr, size_t *size, in_addr_t inaddr, uint16_t port, int local, int delete)
{
#ifndef _WIN32
if (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) {
if (local) {
memset(&addr->un, 0, sizeof(addr->un));
addr->un.sun_family = AF_UNIX;
snprintf(addr->un.sun_path, sizeof(addr->un.sun_path) - 1, VANILLA_PIPE_LOCAL_SOCKET, port);
@@ -90,6 +61,24 @@ void create_sockaddr(sockaddr_u *addr, size_t *size, in_addr_t inaddr, uint16_t
#endif
}
void create_server_sockaddr(sockaddr_u *addr, size_t *size, uint16_t port, int delete)
{
// The Wii U always places itself at this address
in_addr_t ip = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) ? inet_addr("192.168.1.10") : SERVER_ADDRESS;
return create_sockaddr(addr, size, ip, port, 0, delete);
}
void send_to_sockaddr(int fd, const void *data, size_t data_size, const sockaddr_u *sockaddr, size_t sockaddr_size)
{
ssize_t sent = sendto(fd, data, data_size, 0, (const struct sockaddr *) sockaddr, sockaddr_size);
if (sent == -1) {
int err = skterr();
if (err != 111) { // 111 is connection refused, occurs if we lose connection, but we'll already know that for other reasons so we don't need to spam the console with this error
vanilla_log("Failed to send to Wii U socket: fd: %d, errno: %i", fd, skterr());
}
}
}
void send_to_console(int fd, const void *data, size_t data_size, uint16_t port)
{
sockaddr_u addr;
@@ -97,15 +86,9 @@ void send_to_console(int fd, const void *data, size_t data_size, uint16_t port)
in_port_t console_port = port - 100;
create_sockaddr(&addr, &addr_size, get_real_server_address(), console_port, 0);
create_server_sockaddr(&addr, &addr_size, console_port, 0);
ssize_t sent = sendto(fd, data, data_size, 0, (const struct sockaddr *) &addr, addr_size);
if (sent == -1) {
int err = skterr();
if (err != 111) { // 111 is connection refused, occurs if we lose connection, but we'll already know that for other reasons so we don't need to spam the console with this error
vanilla_log("Failed to send to Wii U socket: fd: %d, port: %d, errno: %i", fd, console_port, skterr());
}
}
send_to_sockaddr(fd, data, data_size, &addr, addr_size);
}
void set_socket_rcvtimeo(int skt, uint64_t microseconds)
@@ -121,14 +104,15 @@ void set_socket_rcvtimeo(int skt, uint64_t microseconds)
#endif // _WIN32
}
int create_socket(int *socket_out, in_port_t port)
int create_socket(int *socket_out, in_port_t port, int pipe)
{
sockaddr_u addr;
size_t addr_size;
create_sockaddr(&addr, &addr_size, INADDR_ANY, port, 1);
int is_pipe_and_local = (pipe && SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL);
int domain = is_pipe_and_local ? AF_UNIX : AF_INET;
int domain = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) ? AF_UNIX : AF_INET;
create_sockaddr(&addr, &addr_size, INADDR_ANY, port, is_pipe_and_local, 1);
int skt = socket(domain, SOCK_DGRAM, 0);
if (skt == -1) {
@@ -148,6 +132,22 @@ int create_socket(int *socket_out, in_port_t port)
set_socket_rcvtimeo(skt, 250000);
// int buf_sz = 16777216;
// setsockopt(skt, SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz));
// setsockopt(skt, SOL_SOCKET, SO_SNDBUF, &buf_sz, sizeof(buf_sz));
#if !defined(_WIN32) && !defined(__APPLE__)
if (!pipe && SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) {
// Bind to wireless device
setsockopt(skt, SOL_SOCKET, SO_BINDTODEVICE, wireless_interface, strlen(wireless_interface));
}
#endif
// int val = 1000; // microseconds to busy-poll per recv() before sleeping
// if (setsockopt(skt, SOL_SOCKET, SO_BUSY_POLL, &val, sizeof(val)) < 0) {
// perror("setsockopt SO_BUSY_POLL");
// }
return VANILLA_SUCCESS;
}
@@ -156,7 +156,10 @@ int send_pipe_cc(int skt, vanilla_pipe_command_t *cmd, size_t cmd_size, int wait
sockaddr_u addr;
size_t addr_size;
create_sockaddr(&addr, &addr_size, get_real_server_address(), VANILLA_PIPE_CMD_SERVER_PORT, 0);
int pipe_is_local = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL);
in_addr_t pipe_addr = pipe_is_local ? INADDR_ANY : SERVER_ADDRESS;
create_sockaddr(&addr, &addr_size, pipe_addr, VANILLA_PIPE_CMD_SERVER_PORT, pipe_is_local, 0);
ssize_t read_size;
uint8_t recv_cc;
@@ -195,7 +198,7 @@ int connect_to_backend(int *socket, vanilla_pipe_command_t *cmd, size_t cmd_size
{
// Try to bind with backend
int pipe_cc_skt = -1;
int ret = create_socket(&pipe_cc_skt, VANILLA_PIPE_CMD_CLIENT_PORT);
int ret = create_socket(&pipe_cc_skt, VANILLA_PIPE_CMD_CLIENT_PORT, 1);
if (ret != VANILLA_SUCCESS) {
return ret;
}
@@ -288,16 +291,14 @@ int install_polkit_internal(thread_data_t *data, int install)
int ret = VANILLA_ERR_GENERIC;
int pipe_cc_skt = -1;
if (SERVER_ADDRESS != VANILLA_ADDRESS_DIRECT) {
vanilla_pipe_command_t cmd;
cmd.control_code = install ? VANILLA_PIPE_CC_INSTALL_POLKIT : VANILLA_PIPE_CC_UNINSTALL_POLKIT;
vanilla_pipe_command_t cmd;
cmd.control_code = install ? VANILLA_PIPE_CC_INSTALL_POLKIT : VANILLA_PIPE_CC_UNINSTALL_POLKIT;
// Connect to backend pipe
ret = connect_to_backend(&pipe_cc_skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection));
// Connect to backend pipe
ret = connect_to_backend(&pipe_cc_skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection));
// No interrupt is required here because VANILLA_PIPE_CC_INSTALL_POLKIT
// does not start a thread in the pipe
}
// No interrupt is required here because VANILLA_PIPE_CC_INSTALL_POLKIT
// does not start a thread in the pipe
if (pipe_cc_skt != -1) {
// Disconnect from pipe if necessary
@@ -320,47 +321,45 @@ void connect_as_gamepad_internal(thread_data_t *data)
int ret = VANILLA_SUCCESS;
// Open all required sockets
if (create_socket(&info.socket_vid, PORT_VID) != VANILLA_SUCCESS) goto exit_pipe;
if (create_socket(&info.socket_msg, PORT_MSG) != VANILLA_SUCCESS) goto exit_vid;
if (create_socket(&info.socket_hid, PORT_HID) != VANILLA_SUCCESS) goto exit_msg;
if (create_socket(&info.socket_aud, PORT_AUD) != VANILLA_SUCCESS) goto exit_hid;
if (create_socket(&info.socket_cmd, PORT_CMD) != VANILLA_SUCCESS) goto exit_aud;
if (create_socket(&info.socket_vid, PORT_VID, 0) != VANILLA_SUCCESS) goto exit_pipe;
if (create_socket(&info.socket_msg, PORT_MSG, 0) != VANILLA_SUCCESS) goto exit_vid;
if (create_socket(&info.socket_hid, PORT_HID, 0) != VANILLA_SUCCESS) goto exit_msg;
if (create_socket(&info.socket_aud, PORT_AUD, 0) != VANILLA_SUCCESS) goto exit_hid;
if (create_socket(&info.socket_cmd, PORT_CMD, 0) != VANILLA_SUCCESS) goto exit_aud;
int pipe_cc_skt = -1;
if (SERVER_ADDRESS != VANILLA_ADDRESS_DIRECT) {
vanilla_pipe_command_t cmd;
cmd.control_code = VANILLA_PIPE_CC_CONNECT;
vanilla_pipe_command_t cmd;
cmd.control_code = VANILLA_PIPE_CC_CONNECT;
cmd.connection.bssid = data->bssid;
cmd.connection.psk = data->psk;
cmd.connection.bssid = data->bssid;
cmd.connection.psk = data->psk;
// Connect to backend pipe
ret = connect_to_backend(&pipe_cc_skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection));
if (ret == VANILLA_SUCCESS) {
// Wait for backend to be available
vanilla_pipe_command_t connected_state;
ret = VANILLA_ERR_NO_CONNECTION;
while (!is_interrupted()) {
ssize_t read_size = recv(pipe_cc_skt, (char *) &connected_state, sizeof(connected_state), 0);
if (read_size < 0) {
int r = skterr();
// Connect to backend pipe
ret = connect_to_backend(&pipe_cc_skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection));
if (ret == VANILLA_SUCCESS) {
// Wait for backend to be available
vanilla_pipe_command_t connected_state;
ret = VANILLA_ERR_NO_CONNECTION;
while (!is_interrupted()) {
ssize_t read_size = recv(pipe_cc_skt, (char *) &connected_state, sizeof(connected_state), 0);
if (read_size < 0) {
int r = skterr();
#ifdef _WIN32
if (r != WSAETIMEDOUT) {
if (r != WSAETIMEDOUT) {
#else
if (r != EAGAIN) {
if (r != EAGAIN) {
#endif
vanilla_log("FAILED TO GET CONNECTED STATE: %i", r);
ret = VANILLA_ERR_PIPE_UNRESPONSIVE;
break;
}
} else if (connected_state.control_code == VANILLA_PIPE_CC_CONNECTED) {
ret = VANILLA_SUCCESS;
sleep(1);
vanilla_log("FAILED TO GET CONNECTED STATE: %i", r);
ret = VANILLA_ERR_PIPE_UNRESPONSIVE;
break;
}
vanilla_log("STILL WAITING FOR CONNECTED STATE");
} else if (connected_state.control_code == VANILLA_PIPE_CC_CONNECTED) {
ret = VANILLA_SUCCESS;
sleep(1);
break;
}
vanilla_log("STILL WAITING FOR CONNECTED STATE");
}
}
@@ -439,47 +438,67 @@ exit:
wait_for_interrupt();
}
int push_event(event_loop_t *loop, int type, const void *data, size_t size)
int acquire_event(event_loop_t *loop, vanilla_event_t **event)
{
int ret = VANILLA_SUCCESS;
int ret = VANILLA_SUCCESS;
pthread_mutex_lock(&loop->mutex);
if (size <= EVENT_BUFFER_SIZE) {
// Prevent rollover by skipping oldest event if necessary
if (loop->new_index == loop->used_index + VANILLA_MAX_EVENT_COUNT) {
vanilla_free_event(&loop->events[loop->used_index % VANILLA_MAX_EVENT_COUNT]);
vanilla_log("SKIPPED EVENT TO PREVENT ROLLOVER (%lu > %lu + %lu)", loop->new_index, loop->used_index, VANILLA_MAX_EVENT_COUNT);
loop->used_index++;
}
// Prevent rollover by skipping oldest event if necessary
if (loop->new_index == loop->used_index + VANILLA_MAX_EVENT_COUNT) {
vanilla_free_event(&loop->events[loop->used_index % VANILLA_MAX_EVENT_COUNT]);
vanilla_log("SKIPPED EVENT TO PREVENT ROLLOVER (%lu > %lu + %lu)", loop->new_index, loop->used_index, VANILLA_MAX_EVENT_COUNT);
loop->used_index++;
}
vanilla_event_t *ev = &loop->events[loop->new_index % VANILLA_MAX_EVENT_COUNT];
vanilla_event_t *ev = &loop->events[loop->new_index % VANILLA_MAX_EVENT_COUNT];
assert(!ev->data);
assert(!ev->data);
ev->data = get_event_buffer();
if (!ev->data) {
vanilla_log("OUT OF MEMORY FOR NEW EVENTS");
ret = VANILLA_ERR_OUT_OF_MEMORY;
goto exit;
}
ev->data = get_event_buffer();
// if (!ev->data) {
// vanilla_log("OUT OF MEMORY FOR NEW EVENTS");
// ret = VANILLA_ERR_OUT_OF_MEMORY;
// }
ev->type = type;
memcpy(ev->data, data, size);
ev->size = size;
*event = ev;
loop->new_index++;
return ret;
}
pthread_cond_broadcast(&loop->waitcond);
} else {
vanilla_log("FAILED TO PUSH EVENT: wanted %lu, only had %lu. This is a bug, please report to developers.", size, EVENT_BUFFER_SIZE);
ret = VANILLA_ERR_INVALID_ARGUMENT;
}
int release_event(event_loop_t *loop)
{
loop->new_index++;
pthread_cond_broadcast(&loop->waitcond);
// } else {
// vanilla_log("FAILED TO PUSH EVENT: wanted %lu, only had %lu. This is a bug, please report to developers.", size, EVENT_BUFFER_SIZE);
// ret = VANILLA_ERR_INVALID_ARGUMENT;
// }
exit:
pthread_mutex_unlock(&loop->mutex);
return ret;
return VANILLA_SUCCESS;
}
int push_event(event_loop_t *loop, int type, const void *data, size_t size)
{
vanilla_event_t *ev;
int ret = acquire_event(loop, &ev);
if (ret != VANILLA_SUCCESS) {
return ret;
}
ev->type = type;
memcpy(ev->data, data, size);
ev->size = size;
ret = release_event(loop);
return ret;
}
int get_event(event_loop_t *loop, vanilla_event_t *event, int wait)
+26 -3
View File
@@ -1,16 +1,28 @@
#ifndef VANILLA_GAMEPAD_H
#define VANILLA_GAMEPAD_H
#include "vanilla.h"
#include <pthread.h>
#include <stdint.h>
#ifdef _WIN32
#include <winsock2.h>
typedef uint32_t in_addr_t;
typedef uint16_t in_port_t;
#else
#include <arpa/inet.h>
#include <errno.h>
#include <sys/un.h>
#endif
#include "vanilla.h"
#include "../pipe/ports.h"
struct wpa_ctrl;
#define VANILLA_MAX_EVENT_COUNT 20
extern char wireless_interface[];
#define VANILLA_MAX_EVENT_COUNT 100
typedef struct
{
vanilla_event_t events[VANILLA_MAX_EVENT_COUNT];
@@ -43,12 +55,23 @@ typedef struct thread_data_t
vanilla_psk_t psk;
} thread_data_t;
typedef union {
struct sockaddr_in in;
#ifndef _WIN32
struct sockaddr_un un;
#endif
} sockaddr_u;
void sync_internal(thread_data_t *data);
void connect_as_gamepad_internal(thread_data_t *data);
int install_polkit_internal(thread_data_t *data, int install);
void create_server_sockaddr(sockaddr_u *addr, size_t *size, uint16_t port, int delete);
void send_to_sockaddr(int fd, const void *data, size_t data_size, const sockaddr_u *sockaddr, size_t sockaddr_size);
void send_to_console(int fd, const void *data, size_t data_size, uint16_t port);
int push_event(event_loop_t *loop, int type, const void *data, size_t size);
int get_event(event_loop_t *loop, vanilla_event_t *event, int wait);
int acquire_event(event_loop_t *loop, vanilla_event_t **event);
int release_event(event_loop_t *loop);
void init_event_buffer_arena();
void free_event_buffer_arena();
+12 -8
View File
@@ -112,7 +112,7 @@ uint16_t resolve_axis_value(float axis, float neg, float pos, int flip)
if (flip) {
val = -val;
}
return ((int) (val * 1024)) + 2048;
}
@@ -160,7 +160,7 @@ void set_battery_status(int status)
pthread_mutex_unlock(&button_mtx);
}
void send_input(int socket_hid)
void send_input(int socket_hid, const sockaddr_u *addr, size_t addr_size)
{
InputPacket ip;
memset(&ip, 0, sizeof(ip));
@@ -213,7 +213,7 @@ void send_input(int socket_hid)
ip.buttons = htons(button_mask);
button_mask = 0;
if (current_buttons[VANILLA_BTN_L3]) button_mask |= 0x80;
if (current_buttons[VANILLA_BTN_R3]) button_mask |= 0x40;
if (current_buttons[VANILLA_BTN_TV]) button_mask |= 0x20;
@@ -242,7 +242,7 @@ void send_input(int socket_hid)
ip.fw_version_neg = 215;
send_to_console(socket_hid, &ip, sizeof(ip), PORT_HID);
send_to_sockaddr(socket_hid, &ip, sizeof(ip), addr, addr_size);
}
void *listen_input(void *x)
@@ -251,14 +251,18 @@ void *listen_input(void *x)
pthread_mutex_init(&button_mtx, NULL);
sockaddr_u addr;
size_t addr_size;
create_server_sockaddr(&addr, &addr_size, PORT_HID - 100, 0);
do {
send_input(info->socket_hid);
usleep(5 * 1000); // Produces 200Hz input, probably no need to go higher for the Wii U (supposedly the real gamepad is 180Hz)
send_input(info->socket_hid, &addr, addr_size);
usleep(5555); // Roughly 180Hz, same as the original gamepad
} while (!is_interrupted());
pthread_mutex_destroy(&button_mtx);
pthread_exit(NULL);
return NULL;
}
}
+322 -115
View File
@@ -20,6 +20,12 @@
#include "vanilla.h"
#include "util.h"
#ifdef __APPLE__
// macOS has no htobe32
#include <libkern/OSByteOrder.h>
#define htobe32(x) OSSwapHostToBigInt32(x)
#endif // __APPLE__
typedef struct
{
unsigned magic : 4;
@@ -36,15 +42,15 @@ typedef struct
uint8_t payload[2048];
} VideoPacket;
static pthread_mutex_t video_mutex;
static pthread_mutex_t idr_mutex;
static int idr_is_queued = 0;
#define VIDEO_PACKET_CACHE_MAX 1024
static VideoPacket video_packet_cache[VIDEO_PACKET_CACHE_MAX];
static size_t video_packet_cache_index = 0;
static uint8_t video_packet[100000];
static size_t generated_sps_params_size = 0;
#define VIDEO_PACKET_QUEUE_MAX 1024
static VideoPacket video_packet_queue[VIDEO_PACKET_QUEUE_MAX];
static size_t video_packet_min = 0;
static size_t video_packet_max = 0;
static pthread_mutex_t video_packet_mutex;
static pthread_cond_t video_packet_cond;
static const uint8_t VANILLA_PPS_PARAMS[] = {
0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x06, 0x0c, 0xe8
@@ -52,9 +58,9 @@ static const uint8_t VANILLA_PPS_PARAMS[] = {
void request_idr()
{
pthread_mutex_lock(&video_mutex);
pthread_mutex_lock(&idr_mutex);
idr_is_queued = 1;
pthread_mutex_unlock(&video_mutex);
pthread_mutex_unlock(&idr_mutex);
}
void send_idr_request_to_console(int socket_msg)
@@ -65,7 +71,80 @@ void send_idr_request_to_console(int socket_msg)
send_to_console(socket_msg, idr_request, sizeof(idr_request), PORT_MSG);
}
void handle_video_packet(gamepad_context_t *ctx, VideoPacket *vp, size_t size, int socket_msg)
static uint8_t *write_slice_nal(int is_idr, int frame_decode_num, uint8_t *out)
{
uint32_t slice_header = is_idr ? 0x25b804ff : (0x21e003ff | ((frame_decode_num & 0xff) << 13));
*((uint32_t *)out) = htobe32(slice_header);
return out + sizeof(uint32_t);
static uint8_t idr_num = 0;
uint8_t slice_nal[200];
size_t bit_index = 0;
// forbidden_zero_bit
write_bits(slice_nal, sizeof(slice_nal), &bit_index, 0, 1);
// nal_ref_idc
write_bits(slice_nal, sizeof(slice_nal), &bit_index, is_idr ? 3 : 1, 2);
// nal_unit_type
write_bits(slice_nal, sizeof(slice_nal), &bit_index, is_idr ? 5 : 1, 5);
// first_mb_in_slice
write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0);
// slice_type
write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, is_idr ? 2 : 0);
// pic_parameter_set_id
write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0);
// frame_num
const int fn_bits = 8;
write_bits(slice_nal, sizeof(slice_nal), &bit_index, frame_decode_num, fn_bits);
if (is_idr) {
// idr_pic_id
write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, idr_num);
idr_num++;
// dec_ref_pic_marking() for IDR:
write_bits(slice_nal, sizeof(slice_nal), &bit_index, 0, 1); // no_output_of_prior_pics_flag
write_bits(slice_nal, sizeof(slice_nal), &bit_index, 0, 1); // long_term_reference_flag
} else {
// num_ref_idx_active_override_flag
write_bits(slice_nal, sizeof(slice_nal), &bit_index, 1, 1);
// num_ref_idx_l0_active_minus1
write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0);
// ref_pic_list_modification_flag_l0 = 0 (no modifications)
write_bits(slice_nal, sizeof(slice_nal), &bit_index, 0, 1);
// dec_ref_pic_marking() for non-IDR:
write_bits(slice_nal, sizeof(slice_nal), &bit_index, 0, 1); // adaptive_ref_pic_marking_mode_flag = 0
// CABAC: entropy_coding_mode_flag==1 and P-slice → must write cabac_init_idc
write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); // cabac_init_idc = 0 (good default)
}
// slice_qp_delta
write_signed_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0);
// deblocking_filter_control_present_flag == 1 → must write
write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); // disable_deblocking_filter_idc = 0
write_signed_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); // slice_alpha_c0_offset_div2
write_signed_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); // slice_beta_offset_div2
size_t bytes = (bit_index + 7) >> 3;
memcpy(out, slice_nal, bytes);
out += bytes;
return out;
}
void handle_video_packet(gamepad_context_t *ctx, VideoPacket *vp)
{
//
// === IMPORTANT NOTE! ===
@@ -88,123 +167,211 @@ void handle_video_packet(gamepad_context_t *ctx, VideoPacket *vp, size_t size, i
int is_idr = 0;
for (int i = 0; i < sizeof(vp->extended_header); i++) {
if (vp->extended_header[i] == 0x80) {
// vanilla_log("GOT IDR");
is_idr = 1;
// pthread_mutex_lock(&video_mutex);
// if (idr_is_queued) {
// idr_is_queued = 0;
// }
// pthread_mutex_unlock(&video_mutex);
break;
}
}
// Check seq ID
static int seq_id_expected = -1;
int seq_matched = 1;
if (seq_id_expected == -1) {
seq_id_expected = vp->seq_id;
} else if (seq_id_expected != vp->seq_id) {
seq_matched = 0;
}
seq_id_expected = (vp->seq_id + 1) & 0x3ff; // 10 bit number
static int is_streaming = 0;
if (!seq_matched) {
is_streaming = 0;
}
// Check if this is the beginning of the packet
static VideoPacket *video_segments[1024];
static VideoPacket *video_segments[VIDEO_PACKET_QUEUE_MAX];
static int video_packet_seq = -1;
static int video_packet_seq_end = -1;
static int video_complete_frame = 0;
static uint8_t frame_decode_num = 0;
if (vp->frame_begin) {
video_packet_seq = vp->seq_id;
video_packet_seq_end = -1;
if (!is_streaming) {
if (is_idr) {
is_streaming = 1;
} else {
send_idr_request_to_console(socket_msg);
return;
}
memset(video_segments, 0, sizeof(video_segments));
frame_decode_num++;
if (!video_complete_frame && !is_idr) {
send_idr_request_to_console(ctx->socket_msg);
return;
}
video_complete_frame = 0;
}
pthread_mutex_lock(&video_mutex);
pthread_mutex_lock(&idr_mutex);
if (idr_is_queued) {
send_idr_request_to_console(socket_msg);
send_idr_request_to_console(ctx->socket_msg);
idr_is_queued = 0;
}
pthread_mutex_unlock(&video_mutex);
pthread_mutex_unlock(&idr_mutex);
// vanilla_log("set seq_id %i = %p", vp->seq_id, vp);
video_segments[vp->seq_id] = vp;
if (vp->frame_end) {
if (vp->frame_end)
video_packet_seq_end = vp->seq_id;
}
if (video_packet_seq_end != -1) {
// int complete_frame = 1;
if (is_streaming) {
// Encapsulate packet data into NAL unit
static int frame_decode_num = 0;
uint8_t *nals_current = video_packet + generated_sps_params_size;
int slice_header = is_idr ? 0x25b804ff : (0x21e003ff | ((frame_decode_num & 0xff) << 13));
// begin slice nalu
uint8_t slice[] = {0x00, 0x00, 0x00, 0x01,
(uint8_t) ((slice_header >> 24) & 0xff),
(uint8_t) ((slice_header >> 16) & 0xff),
(uint8_t) ((slice_header >> 8) & 0xff),
(uint8_t) (slice_header & 0xff)
};
memcpy(nals_current, slice, sizeof(slice));
nals_current += sizeof(slice);
// Get pointer to first packet's payload
if (video_packet_seq != -1 && video_packet_seq_end != -1) {
int complete_frame = 1;
{
int current_index = video_packet_seq;
uint8_t *from = video_segments[current_index]->payload;
memcpy(nals_current, from, 2);
nals_current += 2;
// Escape codes
int byte = 2;
while (1) {
uint8_t *data = video_segments[current_index]->payload;
size_t pkt_size = video_segments[current_index]->payload_size;
while (byte < pkt_size) {
if (data[byte] <= 3 && *(nals_current - 2) == 0 && *(nals_current - 1) == 0) {
*nals_current = 3;
nals_current++;
}
*nals_current = data[byte];
nals_current++;
byte++;
if (!video_segments[current_index]) {
complete_frame = 0;
vanilla_log("damn, incomplete frame (missing %i)", current_index);
break;
}
if (current_index == video_packet_seq_end) {
break;
}
byte = 0;
current_index = (current_index + 1) % VIDEO_PACKET_CACHE_MAX;
current_index = (current_index + 1) % VIDEO_PACKET_QUEUE_MAX;
}
}
// Skip IDR parameters if not an IDR frame
uint8_t *nals = (is_idr) ? video_packet : (video_packet + generated_sps_params_size);
push_event(ctx->event_loop, VANILLA_EVENT_VIDEO, nals, (nals_current - nals));
if (complete_frame) {
video_complete_frame = 1;
// Encapsulate packet data into NAL unit
vanilla_event_t *event;
int ret = acquire_event(ctx->event_loop, &event);
event->type = VANILLA_EVENT_VIDEO;
uint8_t *video_packet = event->data;
const int OLD_CODE = 1;
if (OLD_CODE) {
uint8_t *nals_current = video_packet;
static const char *frame_start_word = "\x00\x00\x00\x01";
if (is_idr) {
uint8_t sps[200], pps[200];
size_t sps_size = generate_sps_params(sps, sizeof(sps));
size_t pps_size = generate_pps_params(pps, sizeof(pps));
memcpy(nals_current, frame_start_word, 4);
nals_current += 4;
memcpy(nals_current, sps, sps_size);
nals_current += sps_size;
memcpy(nals_current, pps, pps_size);
nals_current += pps_size;
}
memcpy(nals_current, frame_start_word, 4);
nals_current += 4;
nals_current = write_slice_nal(is_idr, frame_decode_num, nals_current);
// Get pointer to first packet's payload
int current_index = video_packet_seq;
uint8_t *from = video_segments[current_index]->payload;
memcpy(nals_current, from, 2);
nals_current += 2;
// Escape codes
int byte = 2;
while (1) {
uint8_t *data = video_segments[current_index]->payload;
size_t pkt_size = video_segments[current_index]->payload_size;
while (byte < pkt_size) {
if (data[byte] <= 3 && *(nals_current - 2) == 0 && *(nals_current - 1) == 0) {
*nals_current = 3;
nals_current++;
}
*nals_current = data[byte];
nals_current++;
byte++;
}
if (current_index == video_packet_seq_end) {
break;
}
byte = 0;
current_index = (current_index + 1) % VIDEO_PACKET_QUEUE_MAX;
}
event->size = (nals_current - video_packet);
} else {
uint8_t *out = video_packet;
out += 4; // Make room for size
out = write_slice_nal(is_idr, frame_decode_num, out);
// vanilla_log_no_newline("generated slice NAL header:");
// for (size_t i = 0; i < bytes; i++) {
// vanilla_log_no_newline(" %02x", slice_nal[i] & 0xFF);
// }
// vanilla_log_no_newline("\n");
// uint32_t slice_header = is_idr ? 0x25b804ff : (0x21e003ff | ((0 & 0xff) << 13));
// *(uint32_t*)out = htobe32(slice_header);
// out += sizeof(uint32_t);
int i = video_packet_seq;
size_t offset = 0;
while (1) {
uint8_t *data = video_segments[i]->payload;
size_t sz = video_segments[i]->payload_size - offset;
memcpy(out, data + offset, sz);
out += sz;
offset = 0;
if (i == video_packet_seq_end) {
break;
}
i = (i + 1) % VIDEO_PACKET_QUEUE_MAX;
}
// Go back and write size
*((uint32_t *)video_packet) = htobe32(out - (video_packet + 4));
event->size = (out - video_packet);
}
// vanilla_log_no_newline("a few bytes from the packet:");
// for (size_t i = 0; i < 64; i++) {
// // vanilla_log_no_newline(" %02x", video_segments[video_packet_seq]->payload[i] & 0xFF);
// vanilla_log_no_newline(" %02x", video_packet[i] & 0xFF);
// }
// vanilla_log_no_newline("\n");
release_event(ctx->event_loop);
} else {
// We didn't receive the complete frame so we'll skip it here
}
}
}
void *consume_video_packets(void *data)
{
gamepad_context_t *ctx = (gamepad_context_t *) data;
pthread_mutex_lock(&video_packet_mutex);
while (!is_interrupted()) {
while (video_packet_min < video_packet_max) {
VideoPacket *vp = &video_packet_queue[video_packet_min % VIDEO_PACKET_QUEUE_MAX];
pthread_mutex_unlock(&video_packet_mutex);
handle_video_packet(ctx, vp);
pthread_mutex_lock(&video_packet_mutex);
video_packet_min++;
}
pthread_cond_wait(&video_packet_cond, &video_packet_mutex);
}
pthread_mutex_unlock(&video_packet_mutex);
}
size_t generate_h264_header(void *data, size_t size)
{
uint8_t *output = (uint8_t *) data;
@@ -228,36 +395,33 @@ void *listen_video(void *x)
gamepad_context_t *info = (gamepad_context_t *) x;
ssize_t size;
// Set up IDR nals on video_packet
uint8_t *nals_current = video_packet;
pthread_mutex_init(&idr_mutex, NULL);
pthread_mutex_init(&video_packet_mutex, NULL);
pthread_cond_init(&video_packet_cond, NULL);
static const char *frame_start_word = "\x00\x00\x00\x01";
memcpy(nals_current, frame_start_word, 4);
nals_current += 4;
generated_sps_params_size += 4;
size_t generated_sps = generate_sps_params(nals_current, sizeof(video_packet));
nals_current += generated_sps;
generated_sps_params_size += generated_sps;
memcpy(nals_current, VANILLA_PPS_PARAMS, sizeof(VANILLA_PPS_PARAMS));
nals_current += sizeof(VANILLA_PPS_PARAMS);
generated_sps_params_size += sizeof(VANILLA_PPS_PARAMS);
pthread_mutex_init(&video_mutex, NULL);
pthread_t video_consumer_thread;
pthread_create(&video_consumer_thread, 0, consume_video_packets, info);
do {
VideoPacket *vp = &video_packet_cache[video_packet_cache_index % VIDEO_PACKET_CACHE_MAX];
video_packet_cache_index++;
void *data = vp;
size = recv(info->socket_vid, data, sizeof(VideoPacket), 0);
VideoPacket *vp = &video_packet_queue[video_packet_max % VIDEO_PACKET_QUEUE_MAX];
size = recv(info->socket_vid, (void *) vp, sizeof(VideoPacket), 0);
if (size > 0) {
handle_video_packet(info, vp, size, info->socket_msg);
pthread_mutex_lock(&video_packet_mutex);
video_packet_max++;
if (video_packet_max == video_packet_min + VIDEO_PACKET_QUEUE_MAX) {
vanilla_log("WARNING: ROLLED OVER VIDEO PACKET QUEUE");
}
pthread_cond_broadcast(&video_packet_cond);
pthread_mutex_unlock(&video_packet_mutex);
// (info, vp, size, info->socket_msg);
}
} while (!is_interrupted());
pthread_mutex_destroy(&video_mutex);
pthread_join(video_consumer_thread, 0);
pthread_cond_destroy(&video_packet_cond);
pthread_mutex_destroy(&video_packet_mutex);
pthread_mutex_destroy(&idr_mutex);
pthread_exit(NULL);
@@ -269,7 +433,7 @@ void write_bits(void *data, size_t buffer_size, size_t *bit_index, uint8_t value
const size_t size_of_byte = 8;
assert(bit_width <= size_of_byte);
// Calculate offsets
size_t offset = *bit_index;
uint8_t *bytes = (uint8_t *) data;
@@ -325,7 +489,7 @@ void write_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, uint64_
int bit_width = ((sizeof(value) * size_of_byte) - leading_zeros);
int exp_golomb_leading_zeros = bit_width - 1;
for (int i = 0; i < exp_golomb_leading_zeros; i += size_of_byte) {
write_bits(data, buffer_size, bit_index, 0, MIN(size_of_byte, exp_golomb_leading_zeros - i));
}
@@ -348,8 +512,31 @@ void write_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, uint64_
}
}
// void write_signed_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, int64_t value)
// {
// uint64_t codeNum = (value <= 0) ? (uint64_t)(-value*2) : (uint64_t)(value*2-1);
// write_exp_golomb(data, buffer_size, bit_index, codeNum-1);
// }
void write_signed_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, int64_t value)
{
uint64_t codeNum;
if (value > 0) {
codeNum = ((uint64_t)value << 1) - 1; // 2*v - 1
} else {
// value <= 0
codeNum = (uint64_t)(-value) << 1; // -2*v
}
write_exp_golomb(data, buffer_size, bit_index, codeNum);
}
size_t generate_sps_params(void *data, size_t size)
{
// memcpy(data, VANILLA_SPS_PARAMS, MIN(sizeof(VANILLA_SPS_PARAMS), size));
// return sizeof(VANILLA_SPS_PARAMS);
//
// Reference: https://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set
//
@@ -409,7 +596,7 @@ size_t generate_sps_params(void *data, size_t size)
// seq_scaling_matrix_present_flag
write_bits(data, size, &bit_index, 0, 1);
// log2_max_frame_num_minus4
write_exp_golomb(data, size, &bit_index, 4);
@@ -420,7 +607,7 @@ size_t generate_sps_params(void *data, size_t size)
write_exp_golomb(data, size, &bit_index, 1);
// gaps_in_frame_num_value_allowed_flag
write_bits(data, size, &bit_index, 0, 1);
write_bits(data, size, &bit_index, 1, 1);
// pic_width_in_mbs_minus1
write_exp_golomb(data, size, &bit_index, 53);
@@ -508,7 +695,7 @@ size_t generate_sps_params(void *data, size_t size)
// RBSP trailing stop bit
write_bits(data, size, &bit_index, 1, 1);
// Alignment
const int align = 8;
if (bit_index % align != 0) {
@@ -522,4 +709,24 @@ size_t generate_pps_params(void *data, size_t size)
{
memcpy(data, VANILLA_PPS_PARAMS, MIN(sizeof(VANILLA_PPS_PARAMS), size));
return sizeof(VANILLA_PPS_PARAMS);
// size_t bit_index = 0;
// // forbidden_zero_bit
// write_bits(data, size, &bit_index, 0, 1);
// // nal_ref_idc = 3 (important)
// write_bits(data, size, &bit_index, 3, 2);
// // nal_unit_type = 8 (PPS)
// write_bits(data, size, &bit_index, 7, 5);
// // pic_parameter_set_id
// write_exp_golomb(data, size, &bit_index, 0);
// // seq_parameter_set_id
// write_exp_golomb(data, size, &bit_index, 0);
// // entropy_coding_mode_flag
// write_exp_golomb(data, size, &bit_index, 0);
}
+2 -1
View File
@@ -11,5 +11,6 @@ size_t generate_pps_params(void *data, size_t size);
size_t generate_h264_header(void *data, size_t size);
void write_bits(void *data, size_t size, size_t *bit_index, uint8_t value, size_t bit_width);
void write_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, uint64_t value);
void write_signed_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, int64_t value);
#endif // GAMEPAD_VIDEO_H
#endif // GAMEPAD_VIDEO_H
+5
View File
@@ -255,3 +255,8 @@ void vanilla_send_audio(const void *data, size_t size)
{
send_audio_packet(data, size);
}
void vanilla_set_wireless_interface(const char *intf)
{
strcpy(wireless_interface, intf);
}
+4 -3
View File
@@ -23,7 +23,6 @@ extern "C" {
#define VANILLA_ERR_DISCONNECTED -11
static const uint32_t VANILLA_ADDRESS_LOCAL = 0xFFFFFFFF;
static const uint32_t VANILLA_ADDRESS_DIRECT = 0x0;
enum VanillaGamepadButtons
{
@@ -125,6 +124,8 @@ typedef struct {
int vanilla_start(uint32_t server_address, vanilla_bssid_t bssid, vanilla_psk_t psk);
int vanilla_sync(uint16_t code, uint32_t server_address);
void vanilla_set_wireless_interface(const char *intf);
int vanilla_install_polkit(uint32_t server_address);
int vanilla_uninstall_polkit(uint32_t server_address);
@@ -134,8 +135,6 @@ int vanilla_free_event(vanilla_event_t *event);
/**
* Attempt to stop the current action
*
* This can be called from another thread to safely exit a blocking call to vanilla_start().
*/
void vanilla_stop();
@@ -195,6 +194,8 @@ void vanilla_set_battery_status(int battery_status);
/**
* Retrieve SPS/PPS parameters for H.264 packeting
*/
size_t vanilla_generate_sps_params(void *data, size_t data_size);
size_t vanilla_generate_pps_params(void *data, size_t data_size);
size_t vanilla_generate_h264_header(void *data, size_t size);
/**
+33 -25
View File
@@ -666,28 +666,31 @@ close:
void create_all_relays(struct sync_args *args)
{
pthread_t vid_thread, aud_thread, msg_thread, cmd_thread, hid_thread;
struct relay_info vid_info, aud_info, msg_info, cmd_info, hid_info;
// Set common info for all
vid_info.wireless_interface = aud_info.wireless_interface = msg_info.wireless_interface = cmd_info.wireless_interface = hid_info.wireless_interface = args->wireless_interface;
vid_info.local = aud_info.local = msg_info.local = cmd_info.local = hid_info.local = args->local;
vid_info.client = aud_info.client = msg_info.client = cmd_info.client = hid_info.client = args->client;
vid_info.client_size = aud_info.client_size = msg_info.client_size = cmd_info.client_size = hid_info.client_size = args->client_size;
if (!args->local) {
struct relay_info vid_info, aud_info, msg_info, cmd_info, hid_info;
vid_info.port = PORT_VID;
aud_info.port = PORT_AUD;
msg_info.port = PORT_MSG;
cmd_info.port = PORT_CMD;
hid_info.port = PORT_HID;
// Set common info for all
vid_info.wireless_interface = aud_info.wireless_interface = msg_info.wireless_interface = cmd_info.wireless_interface = hid_info.wireless_interface = args->wireless_interface;
vid_info.local = aud_info.local = msg_info.local = cmd_info.local = hid_info.local = args->local;
vid_info.client = aud_info.client = msg_info.client = cmd_info.client = hid_info.client = args->client;
vid_info.client_size = aud_info.client_size = msg_info.client_size = cmd_info.client_size = hid_info.client_size = args->client_size;
// Enable relays
relay_running = 1;
vid_info.port = PORT_VID;
aud_info.port = PORT_AUD;
msg_info.port = PORT_MSG;
cmd_info.port = PORT_CMD;
hid_info.port = PORT_HID;
pthread_create(&vid_thread, NULL, open_relay, &vid_info);
pthread_create(&aud_thread, NULL, open_relay, &aud_info);
pthread_create(&msg_thread, NULL, open_relay, &msg_info);
pthread_create(&cmd_thread, NULL, open_relay, &cmd_info);
pthread_create(&hid_thread, NULL, open_relay, &hid_info);
// Enable relays
relay_running = 1;
pthread_create(&vid_thread, NULL, open_relay, &vid_info);
pthread_create(&aud_thread, NULL, open_relay, &aud_info);
pthread_create(&msg_thread, NULL, open_relay, &msg_info);
pthread_create(&cmd_thread, NULL, open_relay, &cmd_info);
pthread_create(&hid_thread, NULL, open_relay, &hid_info);
}
// Notify client that we are connected
vanilla_pipe_command_t cmd;
@@ -708,16 +711,21 @@ void create_all_relays(struct sync_args *args)
break;
}
}
// wpa_ctrl_recv is non-blocking, so reduce thrashing by sleeping here
sleep(1);
}
// Stop all relays
interrupt_relays();
if (!args->local) {
// Stop all relays
interrupt_relays();
pthread_join(vid_thread, NULL);
pthread_join(aud_thread, NULL);
pthread_join(msg_thread, NULL);
pthread_join(cmd_thread, NULL);
pthread_join(hid_thread, NULL);
pthread_join(vid_thread, NULL);
pthread_join(aud_thread, NULL);
pthread_join(msg_thread, NULL);
pthread_join(cmd_thread, NULL);
pthread_join(hid_thread, NULL);
}
}
void *thread_handler(void *data)