mirror of
https://github.com/ApfelTeeSaft/vanilla.git
synced 2026-08-26 19:33:44 +00:00
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:
+1
-1
@@ -1,7 +1,7 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
#include "game_record.h"
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+199
-186
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user