mirror of
https://github.com/vanilla-wiiu/vanilla.git
synced 2025-01-22 08:11:47 -05:00
Merge branch 'rpi-eventloop'
This commit is contained in:
commit
e9bd0fa30f
24 changed files with 1651 additions and 150 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,3 +4,6 @@ build/
|
|||
.cache/
|
||||
*.AppImage
|
||||
squashfs-root/
|
||||
|
||||
buildpi/
|
||||
rpi-ffmpeg/
|
||||
|
|
|
@ -8,11 +8,19 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
|||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
set(CMAKE_C_STANDARD 23)
|
||||
|
||||
OPTION(VANILLA_BUILD_DESKTOP "Build Qt app for Linux desktop" ON)
|
||||
OPTION(VANILLA_BUILD_RPI "Build SDL2 app for Raspberry Pi" OFF)
|
||||
OPTION(VANILLA_BUILD_TESTS "Build unit tests for Vanilla" OFF)
|
||||
|
||||
add_subdirectory(lib)
|
||||
|
||||
if (NOT ANDROID)
|
||||
add_subdirectory(pipe)
|
||||
endif()
|
||||
|
||||
if (VANILLA_BUILD_DESKTOP)
|
||||
add_subdirectory(app)
|
||||
endif()
|
||||
if (VANILLA_BUILD_RPI)
|
||||
add_subdirectory(rpi)
|
||||
endif()
|
||||
|
|
|
@ -14,22 +14,24 @@
|
|||
#include <unistd.h>
|
||||
#include <vanilla.h>
|
||||
|
||||
void vanillaEventHandler(void *context, int type, const char *data, size_t dataLength)
|
||||
void Backend::vanillaEventHandler()
|
||||
{
|
||||
Backend *backend = static_cast<Backend*>(context);
|
||||
vanilla_event_t event;
|
||||
|
||||
switch (type) {
|
||||
while (vanilla_wait_event(&event)) {
|
||||
switch (event.type) {
|
||||
case VANILLA_EVENT_VIDEO:
|
||||
emit backend->videoAvailable(QByteArray(data, dataLength));
|
||||
emit videoAvailable(QByteArray((const char *) event.data, event.size));
|
||||
break;
|
||||
case VANILLA_EVENT_AUDIO:
|
||||
emit backend->audioAvailable(QByteArray(data, dataLength));
|
||||
emit audioAvailable(QByteArray((const char *) event.data, event.size));
|
||||
break;
|
||||
case VANILLA_EVENT_VIBRATE:
|
||||
emit backend->vibrate(*data);
|
||||
emit vibrate(*event.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Backend::Backend(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
@ -70,12 +72,13 @@ int Backend::syncInternal(uint16_t code)
|
|||
|
||||
void Backend::connectToConsole()
|
||||
{
|
||||
QtConcurrent::run(&Backend::connectInternal, this);
|
||||
connectInternal();
|
||||
QtConcurrent::run(&Backend::vanillaEventHandler, this);
|
||||
}
|
||||
|
||||
int Backend::connectInternal()
|
||||
{
|
||||
return vanilla_start(vanillaEventHandler, this);
|
||||
return vanilla_start(0);
|
||||
}
|
||||
|
||||
void Backend::updateTouch(int x, int y)
|
||||
|
@ -175,7 +178,7 @@ int BackendViaInternalPipe::syncInternal(uint16_t code)
|
|||
|
||||
int BackendViaInternalPipe::connectInternal()
|
||||
{
|
||||
return vanilla_start_udp(vanillaEventHandler, this, QHostAddress(QHostAddress::LocalHost).toIPv4Address());
|
||||
return vanilla_start(QHostAddress(QHostAddress::LocalHost).toIPv4Address());
|
||||
}
|
||||
|
||||
BackendViaExternalPipe::BackendViaExternalPipe(const QHostAddress &udpServer, QObject *parent) : Backend(parent)
|
||||
|
@ -190,5 +193,5 @@ int BackendViaExternalPipe::syncInternal(uint16_t code)
|
|||
|
||||
int BackendViaExternalPipe::connectInternal()
|
||||
{
|
||||
return vanilla_start_udp(vanillaEventHandler, this, m_serverAddress.toIPv4Address());
|
||||
return vanilla_start(m_serverAddress.toIPv4Address());
|
||||
}
|
|
@ -75,6 +75,8 @@ protected:
|
|||
private slots:
|
||||
void syncFutureCompleted();
|
||||
|
||||
void vanillaEventHandler();
|
||||
|
||||
};
|
||||
|
||||
class BackendViaInternalPipe : public Backend
|
||||
|
|
7
docker/rpi/Dockerfile
Normal file
7
docker/rpi/Dockerfile
Normal file
|
@ -0,0 +1,7 @@
|
|||
FROM --platform=linux/arm/v6 arm32v6/alpine
|
||||
|
||||
RUN apk update
|
||||
RUN apk upgrade
|
||||
RUN apk add build-base cmake libnl3-dev openssl-dev sdl2-dev linux-headers ffmpeg-dev eudev-dev
|
||||
|
||||
CMD [ "make", "-j32", "-C", "/data/buildpi" ]
|
|
@ -20,3 +20,12 @@ if (NOT ANDROID)
|
|||
endif()
|
||||
|
||||
install(TARGETS vanilla)
|
||||
|
||||
if (VANILLA_BUILD_TESTS)
|
||||
add_executable(bittest
|
||||
test/bittest.c
|
||||
)
|
||||
|
||||
target_link_libraries(bittest PRIVATE vanilla m)
|
||||
target_include_directories(bittest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
endif()
|
|
@ -31,21 +31,24 @@ typedef struct {
|
|||
uint32_t video_format;
|
||||
} AudioPacketVideoFormat;
|
||||
|
||||
void handle_audio_packet(vanilla_event_handler_t event_handler, void *context, char *data, size_t len)
|
||||
void handle_audio_packet(gamepad_context_t *ctx, unsigned char *data, size_t len)
|
||||
{
|
||||
for (int byte = 0; byte < len; byte++) {
|
||||
//
|
||||
// === IMPORTANT NOTE! ===
|
||||
//
|
||||
// This for loop skips ap->format, ap->seq_id, and ap->timestamp to save processing.
|
||||
// If you want those, you'll have to adjust this loop.
|
||||
//
|
||||
for (int byte = 0; byte < 4; byte++) {
|
||||
data[byte] = (unsigned char) reverse_bits(data[byte], 8);
|
||||
}
|
||||
|
||||
AudioPacket *ap = (AudioPacket *) data;
|
||||
|
||||
ap->format = reverse_bits(ap->format, 3);
|
||||
ap->seq_id = reverse_bits(ap->seq_id, 10);
|
||||
// ap->format = reverse_bits(ap->format, 3);
|
||||
// ap->seq_id = reverse_bits(ap->seq_id, 10);
|
||||
ap->payload_size = reverse_bits(ap->payload_size, 16);
|
||||
ap->timestamp = reverse_bits(ap->timestamp, 32);
|
||||
for (int byte = 0; byte < ap->payload_size; ++byte) {
|
||||
ap->payload[byte] = (unsigned char) reverse_bits(ap->payload[byte], 8);
|
||||
}
|
||||
// ap->timestamp = reverse_bits(ap->timestamp, 32);
|
||||
|
||||
if (ap->type == TYPE_VIDEO) {
|
||||
AudioPacketVideoFormat *avp = (AudioPacketVideoFormat *) ap->payload;
|
||||
|
@ -56,22 +59,24 @@ void handle_audio_packet(vanilla_event_handler_t event_handler, void *context, c
|
|||
return;
|
||||
}
|
||||
|
||||
event_handler(context, VANILLA_EVENT_AUDIO, ap->payload, ap->payload_size);
|
||||
if (ap->payload_size) {
|
||||
push_event(ctx->event_loop, VANILLA_EVENT_AUDIO, ap->payload, ap->payload_size);
|
||||
}
|
||||
|
||||
uint8_t vibrate_val = ap->vibrate;
|
||||
event_handler(context, VANILLA_EVENT_VIBRATE, &vibrate_val, sizeof(vibrate_val));
|
||||
push_event(ctx->event_loop, VANILLA_EVENT_VIBRATE, &vibrate_val, sizeof(vibrate_val));
|
||||
}
|
||||
|
||||
void *listen_audio(void *x)
|
||||
{
|
||||
struct gamepad_thread_context *info = (struct gamepad_thread_context *) x;
|
||||
gamepad_context_t *info = (gamepad_context_t *) x;
|
||||
unsigned char data[2048];
|
||||
ssize_t size;
|
||||
do {
|
||||
size = recv(info->socket_aud, data, sizeof(data), 0);
|
||||
if (size > 0) {
|
||||
if (is_stop_code(data, size)) break;
|
||||
handle_audio_packet(info->event_handler, info->context, data, size);
|
||||
handle_audio_packet(info, data, size);
|
||||
}
|
||||
} while (!is_interrupted());
|
||||
|
||||
|
|
|
@ -422,7 +422,7 @@ void handle_command_packet(int skt, CmdHeader *request)
|
|||
|
||||
void *listen_command(void *x)
|
||||
{
|
||||
struct gamepad_thread_context *info = (struct gamepad_thread_context *)x;
|
||||
gamepad_context_t *info = (gamepad_context_t *)x;
|
||||
|
||||
unsigned char data[sizeof(CmdHeader) + 2048];
|
||||
ssize_t size;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#define _GNU_SOURCE
|
||||
|
||||
#include "gamepad.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
@ -29,17 +31,6 @@ uint16_t PORT_AUD;
|
|||
uint16_t PORT_HID;
|
||||
uint16_t PORT_CMD;
|
||||
|
||||
unsigned int reverse_bits(unsigned int b, int bit_count)
|
||||
{
|
||||
unsigned int result = 0;
|
||||
|
||||
for (int i = 0; i < bit_count; i++) {
|
||||
result |= ((b >> i) & 1) << (bit_count - 1 -i );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void send_to_console(int fd, const void *data, size_t data_size, int port)
|
||||
{
|
||||
struct sockaddr_in address;
|
||||
|
@ -47,12 +38,12 @@ void send_to_console(int fd, const void *data, size_t data_size, int port)
|
|||
address.sin_addr.s_addr = SERVER_ADDRESS;
|
||||
address.sin_port = htons((uint16_t) (port - 100));
|
||||
|
||||
char ip[20];
|
||||
inet_ntop(AF_INET, &address.sin_addr, ip, sizeof(ip));
|
||||
char ip[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &address.sin_addr, ip, INET_ADDRSTRLEN);
|
||||
|
||||
ssize_t sent = sendto(fd, data, data_size, 0, (const struct sockaddr *) &address, sizeof(address));
|
||||
if (sent == -1) {
|
||||
print_info("Failed to send to Wii U socket: fd - %d; port - %d", fd, port - 100);
|
||||
print_info("Failed to send to Wii U socket: address: %s, fd: %d, port: %d, errno: %i", ip, fd, port - 100, errno);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,13 +53,21 @@ int create_socket(int *socket_out, uint16_t port)
|
|||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
address.sin_port = htons(port);
|
||||
(*socket_out) = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
|
||||
int skt = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (skt == -1) {
|
||||
print_info("FAILED TO CREATE SOCKET FOR PORT %i", port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
(*socket_out) = skt;
|
||||
|
||||
if (bind((*socket_out), (const struct sockaddr *) &address, sizeof(address)) == -1) {
|
||||
print_info("FAILED TO BIND PORT %u: %i", port, errno);
|
||||
return 0;
|
||||
}
|
||||
|
||||
print_info("SUCCESSFULLY BOUND SOCKET %i on PORT %i", skt, port);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -145,13 +144,14 @@ int sync_internal(uint16_t code, uint32_t server_address)
|
|||
SERVER_ADDRESS = htonl(server_address);
|
||||
}
|
||||
|
||||
int skt;
|
||||
int ret = connect_to_backend(&skt, VANILLA_PIPE_SYNC_CODE(code));
|
||||
int skt = 0;
|
||||
int ret = VANILLA_ERROR;
|
||||
if (server_address != 0) {
|
||||
ret = connect_to_backend(&skt, VANILLA_PIPE_SYNC_CODE(code));
|
||||
if (ret != VANILLA_SUCCESS) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ret = VANILLA_ERROR;
|
||||
}
|
||||
|
||||
// Wait for sync result from pipe
|
||||
uint32_t recv_cc;
|
||||
|
@ -172,13 +172,14 @@ int sync_internal(uint16_t code, uint32_t server_address)
|
|||
}
|
||||
|
||||
exit_pipe:
|
||||
if (skt)
|
||||
close(skt);
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int connect_as_gamepad_internal(vanilla_event_handler_t event_handler, void *context, uint32_t server_address)
|
||||
int connect_as_gamepad_internal(event_loop_t *event_loop, uint32_t server_address)
|
||||
{
|
||||
clear_interrupt();
|
||||
|
||||
|
@ -199,17 +200,18 @@ int connect_as_gamepad_internal(vanilla_event_handler_t event_handler, void *con
|
|||
PORT_CMD += 200;
|
||||
}
|
||||
|
||||
struct gamepad_thread_context info;
|
||||
info.event_handler = event_handler;
|
||||
info.context = context;
|
||||
gamepad_context_t info;
|
||||
info.event_loop = event_loop;
|
||||
|
||||
int ret = VANILLA_ERROR;
|
||||
|
||||
int pipe_cc_skt;
|
||||
int pipe_cc_skt = 0;
|
||||
if (server_address != 0) {
|
||||
ret = connect_to_backend(&pipe_cc_skt, VANILLA_PIPE_CC_CONNECT);
|
||||
if (ret != VANILLA_SUCCESS) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Open all required sockets
|
||||
if (!create_socket(&info.socket_vid, PORT_VID)) goto exit_pipe;
|
||||
|
@ -221,9 +223,16 @@ int connect_as_gamepad_internal(vanilla_event_handler_t event_handler, void *con
|
|||
pthread_t video_thread, audio_thread, input_thread, msg_thread, cmd_thread;
|
||||
|
||||
pthread_create(&video_thread, NULL, listen_video, &info);
|
||||
pthread_setname_np(video_thread, "vanilla-video");
|
||||
|
||||
pthread_create(&audio_thread, NULL, listen_audio, &info);
|
||||
pthread_setname_np(audio_thread, "vanilla-audio");
|
||||
|
||||
pthread_create(&input_thread, NULL, listen_input, &info);
|
||||
pthread_setname_np(input_thread, "vanilla-input");
|
||||
|
||||
pthread_create(&cmd_thread, NULL, listen_command, &info);
|
||||
pthread_setname_np(cmd_thread, "vanilla-cmd");
|
||||
|
||||
while (1) {
|
||||
usleep(250 * 1000);
|
||||
|
@ -241,7 +250,9 @@ int connect_as_gamepad_internal(vanilla_event_handler_t event_handler, void *con
|
|||
pthread_join(input_thread, NULL);
|
||||
pthread_join(cmd_thread, NULL);
|
||||
|
||||
if (server_address != 0) {
|
||||
send_pipe_cc(pipe_cc_skt, VANILLA_PIPE_CC_UNBIND, 0);
|
||||
}
|
||||
|
||||
ret = VANILLA_SUCCESS;
|
||||
|
||||
|
@ -261,6 +272,7 @@ exit_vid:
|
|||
close(info.socket_vid);
|
||||
|
||||
exit_pipe:
|
||||
if (pipe_cc_skt)
|
||||
close(pipe_cc_skt);
|
||||
|
||||
exit:
|
||||
|
@ -271,3 +283,57 @@ int is_stop_code(const char *data, size_t data_length)
|
|||
{
|
||||
return (data_length == sizeof(STOP_CODE) && !memcmp(data, &STOP_CODE, sizeof(STOP_CODE)));
|
||||
}
|
||||
|
||||
int push_event(event_loop_t *loop, int type, const void *data, size_t size)
|
||||
{
|
||||
|
||||
pthread_mutex_lock(&loop->mutex);
|
||||
|
||||
vanilla_event_t *ev = &loop->events[loop->new_index % VANILLA_MAX_EVENT_COUNT];
|
||||
|
||||
if (size <= sizeof(ev->data)) {
|
||||
ev->type = type;
|
||||
memcpy(ev->data, data, size);
|
||||
ev->size = size;
|
||||
|
||||
loop->new_index++;
|
||||
|
||||
// Prevent rollover by skipping oldest event if necessary
|
||||
if (loop->new_index > loop->used_index + VANILLA_MAX_EVENT_COUNT) {
|
||||
print_info("SKIPPED EVENT TO PREVENT ROLLOVER (%lu > %lu + %lu)", loop->new_index, loop->used_index, VANILLA_MAX_EVENT_COUNT);
|
||||
loop->used_index++;
|
||||
}
|
||||
|
||||
pthread_cond_broadcast(&loop->waitcond);
|
||||
} else {
|
||||
print_info("FAILED TO PUSH EVENT: wanted %lu, only had %lu. This is a bug, please report to developers.\n", size, sizeof(ev->data));
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&loop->mutex);
|
||||
}
|
||||
|
||||
int get_event(event_loop_t *loop, vanilla_event_t *event, int wait)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
pthread_mutex_lock(&loop->mutex);
|
||||
|
||||
if (loop->active) {
|
||||
if (wait) {
|
||||
while (loop->active && loop->used_index == loop->new_index) {
|
||||
pthread_cond_wait(&loop->waitcond, &loop->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
if (loop->active && loop->used_index < loop->new_index) {
|
||||
// Output data to pointer
|
||||
*event = loop->events[loop->used_index % VANILLA_MAX_EVENT_COUNT];
|
||||
loop->used_index++;
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&loop->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "vanilla.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern uint16_t PORT_MSG;
|
||||
|
@ -13,22 +14,32 @@ extern uint16_t PORT_CMD;
|
|||
|
||||
struct wpa_ctrl;
|
||||
|
||||
struct gamepad_thread_context
|
||||
#define VANILLA_MAX_EVENT_COUNT 20
|
||||
typedef struct
|
||||
{
|
||||
vanilla_event_handler_t event_handler;
|
||||
void *context;
|
||||
vanilla_event_t events[VANILLA_MAX_EVENT_COUNT];
|
||||
size_t new_index;
|
||||
size_t used_index;
|
||||
int active;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t waitcond;
|
||||
} event_loop_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
event_loop_t *event_loop;
|
||||
int socket_vid;
|
||||
int socket_aud;
|
||||
int socket_hid;
|
||||
int socket_msg;
|
||||
int socket_cmd;
|
||||
};
|
||||
} gamepad_context_t;
|
||||
|
||||
int sync_internal(uint16_t code, uint32_t server_address);
|
||||
int connect_as_gamepad_internal(vanilla_event_handler_t event_handler, void *context, uint32_t server_address);
|
||||
unsigned int reverse_bits(unsigned int b, int bit_count);
|
||||
int connect_as_gamepad_internal(event_loop_t *ctx, uint32_t server_address);
|
||||
void send_to_console(int fd, const void *data, size_t data_size, int port);
|
||||
int is_stop_code(const char *data, size_t data_length);
|
||||
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);
|
||||
|
||||
#endif // VANILLA_GAMEPAD_H
|
|
@ -243,7 +243,7 @@ void send_input(int socket_hid)
|
|||
|
||||
void *listen_input(void *x)
|
||||
{
|
||||
struct gamepad_thread_context *info = (struct gamepad_thread_context *) x;
|
||||
gamepad_context_t *info = (gamepad_context_t *) x;
|
||||
|
||||
pthread_mutex_init(&button_mtx, NULL);
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
#include "video.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gamepad.h"
|
||||
|
@ -29,8 +32,15 @@ typedef struct
|
|||
uint8_t payload[2048];
|
||||
} VideoPacket;
|
||||
|
||||
pthread_mutex_t video_mutex;
|
||||
int idr_is_queued = 0;
|
||||
static pthread_mutex_t video_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;
|
||||
|
||||
void request_idr()
|
||||
{
|
||||
|
@ -46,25 +56,24 @@ 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(vanilla_event_handler_t event_handler, void *context, unsigned char *data, size_t size, int socket_msg)
|
||||
void handle_video_packet(gamepad_context_t *ctx, VideoPacket *vp, size_t size, int socket_msg)
|
||||
{
|
||||
// TODO: This is all really weird. Copied from drc-sim-c but I feel like there's probably a better way.
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
//
|
||||
// === IMPORTANT NOTE! ===
|
||||
//
|
||||
// This for loop skips vp->magic, vp->packet_type, and vp->timestamp to save processing.
|
||||
// If you want those, you'll have to adjust this loop.
|
||||
//
|
||||
uint8_t *data = (uint8_t *) vp;
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
data[i] = reverse_bits(data[i], 8);
|
||||
}
|
||||
|
||||
VideoPacket *vp = (VideoPacket *) data;
|
||||
|
||||
vp->magic = reverse_bits(vp->magic, 4);
|
||||
vp->packet_type = reverse_bits(vp->packet_type, 2);
|
||||
// vp->magic = reverse_bits(vp->magic, 4);
|
||||
// vp->packet_type = reverse_bits(vp->packet_type, 2);
|
||||
// vp->timestamp = reverse_bits(vp->timestamp, 32);
|
||||
vp->seq_id = reverse_bits(vp->seq_id, 10);
|
||||
vp->payload_size = reverse_bits(vp->payload_size, 11);
|
||||
vp->timestamp = reverse_bits(vp->timestamp, 32);
|
||||
for (int byte = 0; byte < sizeof(vp->extended_header); ++byte)
|
||||
vp->extended_header[byte] = (unsigned char) reverse_bits(vp->extended_header[byte], 8);
|
||||
for (int byte = 0; byte < vp->payload_size; ++byte)
|
||||
vp->payload[byte] = (unsigned char) reverse_bits(vp->payload[byte], 8);
|
||||
|
||||
// Check if packet is IDR (instantaneous decoder refresh)
|
||||
int is_idr = 0;
|
||||
|
@ -90,8 +99,7 @@ void handle_video_packet(vanilla_event_handler_t event_handler, void *context, u
|
|||
}
|
||||
|
||||
// Check if this is the beginning of the packet
|
||||
static char video_segments[1024][2048];
|
||||
static size_t video_segment_size[1024];
|
||||
static VideoPacket *video_segments[1024];
|
||||
static int video_packet_seq = -1;
|
||||
static int video_packet_seq_end = -1;
|
||||
|
||||
|
@ -116,8 +124,7 @@ void handle_video_packet(vanilla_event_handler_t event_handler, void *context, u
|
|||
}
|
||||
pthread_mutex_unlock(&video_mutex);
|
||||
|
||||
memcpy(video_segments[vp->seq_id], vp->payload, vp->payload_size);
|
||||
video_segment_size[vp->seq_id] = vp->payload_size;
|
||||
video_segments[vp->seq_id] = vp;
|
||||
|
||||
if (vp->frame_end) {
|
||||
video_packet_seq_end = vp->seq_id;
|
||||
|
@ -126,32 +133,13 @@ void handle_video_packet(vanilla_event_handler_t event_handler, void *context, u
|
|||
if (video_packet_seq_end != -1) {
|
||||
// int complete_frame = 1;
|
||||
if (is_streaming) {
|
||||
// Combine segments
|
||||
char video_packet[100000];
|
||||
size_t video_packet_size = 0;
|
||||
for (int i = video_packet_seq; ; i = (i + 1) % 1024) {
|
||||
memcpy(video_packet + video_packet_size, video_segments[i], video_segment_size[i]);
|
||||
video_packet_size += video_segment_size[i];
|
||||
if (i == video_packet_seq_end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Encapsulate packet data into NAL unit
|
||||
static int frame_decode_num = 0;
|
||||
size_t nals_sz = video_packet_size * 2;
|
||||
uint8_t *nals = malloc(nals_sz);
|
||||
uint8_t *nals_current = nals;
|
||||
uint8_t *nals_current = video_packet + generated_sps_params_size + sizeof(VANILLA_PPS_PARAMS);
|
||||
|
||||
int slice_header = is_idr ? 0x25b804ff : (0x21e003ff | ((frame_decode_num & 0xff) << 13));
|
||||
frame_decode_num++;
|
||||
|
||||
if (is_idr) {
|
||||
memcpy(nals_current, VANILLA_SPS_PARAMS, sizeof(VANILLA_SPS_PARAMS));
|
||||
nals_current += sizeof(VANILLA_SPS_PARAMS);
|
||||
memcpy(nals_current, VANILLA_PPS_PARAMS, sizeof(VANILLA_PPS_PARAMS));
|
||||
nals_current += sizeof(VANILLA_PPS_PARAMS);
|
||||
}
|
||||
|
||||
// begin slice nalu
|
||||
uint8_t slice[] = {0x00, 0x00, 0x00, 0x01,
|
||||
(uint8_t) ((slice_header >> 24) & 0xff),
|
||||
|
@ -162,23 +150,39 @@ void handle_video_packet(vanilla_event_handler_t event_handler, void *context, u
|
|||
memcpy(nals_current, slice, sizeof(slice));
|
||||
nals_current += sizeof(slice);
|
||||
|
||||
// Frame
|
||||
memcpy(nals_current, video_packet, 2);
|
||||
// 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
|
||||
for (int byte = 2; byte < video_packet_size; ++byte) {
|
||||
if (video_packet[byte] <= 3 && *(nals_current - 2) == 0 && *(nals_current - 1) == 0) {
|
||||
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 = video_packet[byte];
|
||||
*nals_current = data[byte];
|
||||
nals_current++;
|
||||
byte++;
|
||||
}
|
||||
|
||||
event_handler(context, VANILLA_EVENT_VIDEO, nals, (nals_current - nals));
|
||||
if (current_index == video_packet_seq_end) {
|
||||
break;
|
||||
}
|
||||
|
||||
free(nals);
|
||||
byte = 0;
|
||||
current_index = (current_index + 1) % VIDEO_PACKET_CACHE_MAX;
|
||||
}
|
||||
|
||||
// Skip IDR parameters if not an IDR frame
|
||||
uint8_t *nals = (is_idr) ? video_packet : (video_packet + generated_sps_params_size + sizeof(VANILLA_PPS_PARAMS));
|
||||
push_event(ctx->event_loop, VANILLA_EVENT_VIDEO, nals, (nals_current - nals));
|
||||
} else {
|
||||
// We didn't receive the complete frame so we'll skip it here
|
||||
}
|
||||
|
@ -188,17 +192,35 @@ void handle_video_packet(vanilla_event_handler_t event_handler, void *context, u
|
|||
void *listen_video(void *x)
|
||||
{
|
||||
// Receive video
|
||||
struct gamepad_thread_context *info = (struct gamepad_thread_context *) x;
|
||||
unsigned char data[2048];
|
||||
gamepad_context_t *info = (gamepad_context_t *) x;
|
||||
ssize_t size;
|
||||
|
||||
// Set up IDR nals on video_packet
|
||||
uint8_t *nals_current = video_packet;
|
||||
|
||||
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);
|
||||
|
||||
pthread_mutex_init(&video_mutex, NULL);
|
||||
|
||||
do {
|
||||
size = recv(info->socket_vid, data, sizeof(data), 0);
|
||||
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);
|
||||
if (size > 0) {
|
||||
if (is_stop_code(data, size)) break;
|
||||
handle_video_packet(info->event_handler, info->context, data, size, info->socket_msg);
|
||||
handle_video_packet(info, vp, size, info->socket_msg);
|
||||
}
|
||||
} while (!is_interrupted());
|
||||
|
||||
|
@ -208,3 +230,257 @@ void *listen_video(void *x)
|
|||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void write_bits(void *data, size_t buffer_size, size_t *bit_index, uint8_t value, size_t bit_width)
|
||||
{
|
||||
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;
|
||||
size_t byte_offset = offset / size_of_byte;
|
||||
|
||||
// NOTE: If this was big endian, the value would need to be shifted to the upper most 8 bits manually
|
||||
|
||||
// Shift to the right for placing into buffer
|
||||
size_t local_bit_offset = offset - (byte_offset * size_of_byte);
|
||||
size_t remainder = size_of_byte - local_bit_offset;
|
||||
|
||||
// Put bits into the right place in the uint16
|
||||
// Promote value to a 16-bit buffer (in case it crosses an 8-bit alignment boundary)
|
||||
uint16_t shifted = value << (size_of_byte - bit_width) >> local_bit_offset;
|
||||
|
||||
// Clear any non-zero bits if necessary
|
||||
bytes[byte_offset] = (bytes[byte_offset] >> remainder) << remainder;
|
||||
|
||||
// Put bits into buffer
|
||||
if (buffer_size - byte_offset > 1) {
|
||||
bytes[byte_offset + 1] = 0;
|
||||
uint16_t *words = (uint16_t *) (&bytes[byte_offset]);
|
||||
*words |= shifted;
|
||||
} else {
|
||||
uint8_t shifted_byte = shifted;
|
||||
// NOTE: If this was big endian, we'd need to get the upper 8 bits manually
|
||||
bytes[byte_offset] |= shifted_byte;
|
||||
}
|
||||
|
||||
// Increment bit counter by bits
|
||||
offset += bit_width;
|
||||
*bit_index = offset;
|
||||
}
|
||||
|
||||
void write_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, uint64_t value)
|
||||
{
|
||||
const size_t size_of_byte = 8;
|
||||
|
||||
// x + 1
|
||||
uint64_t exp_golomb_value = value + 1;
|
||||
|
||||
// Count how many bits are in this byte
|
||||
int leading_zeros = 0;
|
||||
const size_t num_bits = sizeof(value)*size_of_byte;
|
||||
for (size_t i = 0; i < num_bits; i++) {
|
||||
if (exp_golomb_value & (1ULL << (num_bits - i - 1))) {
|
||||
break;
|
||||
} else {
|
||||
leading_zeros++;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
int total_bytes = (bit_width / size_of_byte);
|
||||
if (bit_width % size_of_byte != 0) {
|
||||
total_bytes++;
|
||||
}
|
||||
for (int i = 0; i < total_bytes; i++) {
|
||||
int write_count;
|
||||
if (i == 0 && bit_width % size_of_byte != 0) {
|
||||
write_count = bit_width % size_of_byte;
|
||||
} else {
|
||||
write_count = MIN(bit_width, size_of_byte);
|
||||
}
|
||||
|
||||
uint8_t byte = exp_golomb_value >> ((total_bytes - 1 - i) * size_of_byte);
|
||||
|
||||
write_bits(data, buffer_size, bit_index, byte, write_count);
|
||||
}
|
||||
}
|
||||
|
||||
size_t generate_sps_params(void *data, size_t size)
|
||||
{
|
||||
//
|
||||
// Reference: https://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set
|
||||
//
|
||||
|
||||
size_t bit_index = 0;
|
||||
|
||||
// forbidden_zero_bit
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// nal_ref_idc = 3 (important/SPS)
|
||||
write_bits(data, size, &bit_index, 3, 2);
|
||||
|
||||
// nal_unit_type = 7 (SPS)
|
||||
write_bits(data, size, &bit_index, 7, 5);
|
||||
|
||||
// profile_idc = 100 (not sure if this is correct, seems to work)
|
||||
write_bits(data, size, &bit_index, 100, 8);
|
||||
|
||||
// constraint_set0_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// constraint_set1_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// constraint_set2_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// constraint_set3_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// constraint_set4_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// constraint_set5_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// reserved_zero_2bits
|
||||
write_bits(data, size, &bit_index, 0, 2);
|
||||
|
||||
// level_idc (not sure if this is correct, seems to work)
|
||||
write_bits(data, size, &bit_index, 0x20, 8);
|
||||
|
||||
// seq_parameter_set_id
|
||||
write_exp_golomb(data, size, &bit_index, 0);
|
||||
|
||||
// chroma_format_idc
|
||||
write_exp_golomb(data, size, &bit_index, 1);
|
||||
|
||||
// bit_depth_luma_minus8
|
||||
write_exp_golomb(data, size, &bit_index, 0);
|
||||
|
||||
// bit_depth_chroma_minus8
|
||||
write_exp_golomb(data, size, &bit_index, 0);
|
||||
|
||||
// qpprime_y_zero_transform_bypass_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// 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);
|
||||
|
||||
// pic_order_cnt_type
|
||||
write_exp_golomb(data, size, &bit_index, 2);
|
||||
|
||||
// max_num_ref_frames
|
||||
write_exp_golomb(data, size, &bit_index, 1);
|
||||
|
||||
// gaps_in_frame_num_value_allowed_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// pic_width_in_mbs_minus1
|
||||
write_exp_golomb(data, size, &bit_index, 53);
|
||||
|
||||
// pic_height_in_map_units_minus1
|
||||
write_exp_golomb(data, size, &bit_index, 29);
|
||||
|
||||
// frame_mbs_only_flag
|
||||
write_bits(data, size, &bit_index, 1, 1);
|
||||
|
||||
// direct_8x8_inference_flag
|
||||
write_bits(data, size, &bit_index, 1, 1);
|
||||
|
||||
// frame_cropping_flag
|
||||
write_bits(data, size, &bit_index, 1, 1);
|
||||
|
||||
// frame_crop_left_offset
|
||||
write_exp_golomb(data, size, &bit_index, 0);
|
||||
|
||||
// frame_crop_right_offset
|
||||
write_exp_golomb(data, size, &bit_index, 5);
|
||||
|
||||
// frame_crop_top_offset
|
||||
write_exp_golomb(data, size, &bit_index, 0);
|
||||
|
||||
// frame_crop_bottom_offset
|
||||
write_exp_golomb(data, size, &bit_index, 0);
|
||||
|
||||
// vui_parameters_present_flag
|
||||
int enable_vui = 1;
|
||||
write_bits(data, size, &bit_index, enable_vui, 1);
|
||||
|
||||
if (enable_vui) {
|
||||
// aspect_ratio_info_present_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// overscan_info_present_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// video_signal_type_present_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// chroma_loc_info_present_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// timing_info_present_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// nal_hrd_parameters_present_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// vcl_hrd_parameters_present_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// pic_struct_present_flag
|
||||
write_bits(data, size, &bit_index, 0, 1);
|
||||
|
||||
// bitstream_restriction_flag
|
||||
write_bits(data, size, &bit_index, 1, 1);
|
||||
|
||||
// bitstream_restriction:
|
||||
{
|
||||
// motion_vectors_over_pic_boundaries_flag
|
||||
write_bits(data, size, &bit_index, 1, 1);
|
||||
|
||||
// max_bytes_per_pic_denom
|
||||
write_exp_golomb(data, size, &bit_index, 2);
|
||||
|
||||
// max_bits_per_mb_denom
|
||||
write_exp_golomb(data, size, &bit_index, 1);
|
||||
|
||||
// log2_max_mv_length_horizontal
|
||||
write_exp_golomb(data, size, &bit_index, 16);
|
||||
|
||||
// log2_max_mv_length_vertical
|
||||
write_exp_golomb(data, size, &bit_index, 16);
|
||||
|
||||
// max_num_reorder_frames
|
||||
write_exp_golomb(data, size, &bit_index, 0);
|
||||
|
||||
// max_dec_frame_buffering
|
||||
write_exp_golomb(data, size, &bit_index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// RBSP trailing stop bit
|
||||
write_bits(data, size, &bit_index, 1, 1);
|
||||
|
||||
// Alignment
|
||||
const int align = 8;
|
||||
if (bit_index % align != 0) {
|
||||
write_bits(data, size, &bit_index, 0, align - (bit_index % align));
|
||||
}
|
||||
|
||||
return bit_index / align;
|
||||
}
|
|
@ -2,8 +2,12 @@
|
|||
#define GAMEPAD_VIDEO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void *listen_video(void *x);
|
||||
void request_idr();
|
||||
size_t generate_sps_params(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);
|
||||
|
||||
#endif // GAMEPAD_VIDEO_H
|
195
lib/test/bittest.c
Normal file
195
lib/test/bittest.c
Normal file
|
@ -0,0 +1,195 @@
|
|||
/**
|
||||
* Small unit test for ensuring our SPS/PPS bit writing functions are accurate
|
||||
*/
|
||||
|
||||
#include <byteswap.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "gamepad/video.h"
|
||||
|
||||
int simple()
|
||||
{
|
||||
uint8_t test = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
write_bits(&test, sizeof(test), &offset, 1, 1);
|
||||
|
||||
if (test == 128) {
|
||||
printf("SUCCESS\n");
|
||||
return 0;
|
||||
} else {
|
||||
printf("FAIL (got %i)\n", test);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int simple_double()
|
||||
{
|
||||
uint8_t test = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
write_bits(&test, sizeof(test), &offset, 1, 1);
|
||||
|
||||
offset = 7;
|
||||
write_bits(&test, sizeof(test), &offset, 1, 1);
|
||||
|
||||
if (test != 129) {
|
||||
printf("FAIL (got %i)\n", test);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("SUCCESS\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int simple_exp_golomb()
|
||||
{
|
||||
uint8_t test = 0;
|
||||
size_t index = 0;
|
||||
|
||||
write_exp_golomb(&test, sizeof(test), &index, 1);
|
||||
|
||||
if (test != 64) {
|
||||
printf("FAIL (got %i)\n", test);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("SUCCESS\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int complex_exp_golomb()
|
||||
{
|
||||
// 00000110110
|
||||
uint16_t test = 0;
|
||||
size_t index = 5;
|
||||
|
||||
write_exp_golomb(&test, sizeof(test), &index, 53);
|
||||
|
||||
test = __bswap_16(test);
|
||||
|
||||
if (test != 0b110110) {
|
||||
printf("FAIL (got %x)\n", test);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("SUCCESS\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int complex_bit_test()
|
||||
{
|
||||
const char *expected = "\x67\x64\x00\x20";
|
||||
uint8_t data[4];
|
||||
size_t bit_index = 0;
|
||||
|
||||
// forbidden_zero_bit
|
||||
write_bits(data, sizeof(data), &bit_index, 0, 1);
|
||||
|
||||
// nal_ref_idc = 3 (important/SPS)
|
||||
write_bits(data, sizeof(data), &bit_index, 3, 2);
|
||||
|
||||
// nal_unit_type = 7 (SPS)
|
||||
write_bits(data, sizeof(data), &bit_index, 7, 5);
|
||||
|
||||
// profile_idc = 100 (not sure if this is correct, seems to work)
|
||||
write_bits(data, sizeof(data), &bit_index, 100, 8);
|
||||
|
||||
// constraint_set0_flag
|
||||
write_bits(data, sizeof(data), &bit_index, 0, 1);
|
||||
|
||||
// constraint_set1_flag
|
||||
write_bits(data, sizeof(data), &bit_index, 0, 1);
|
||||
|
||||
// constraint_set2_flag
|
||||
write_bits(data, sizeof(data), &bit_index, 0, 1);
|
||||
|
||||
// constraint_set3_flag
|
||||
write_bits(data, sizeof(data), &bit_index, 0, 1);
|
||||
|
||||
// constraint_set4_flag
|
||||
write_bits(data, sizeof(data), &bit_index, 0, 1);
|
||||
|
||||
// constraint_set5_flag
|
||||
write_bits(data, sizeof(data), &bit_index, 0, 1);
|
||||
|
||||
// reserved_zero_2bits
|
||||
write_bits(data, sizeof(data), &bit_index, 0, 2);
|
||||
|
||||
// level_idc (not sure if this is correct, seems to work)
|
||||
write_bits(data, sizeof(data), &bit_index, 0x20, 8);
|
||||
|
||||
for (int i = 0; i < sizeof(data); i++) {
|
||||
if (i > 0) {
|
||||
printf(" ");
|
||||
}
|
||||
printf("%X", data[i] & 0xFF);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
if (!memcmp(expected, data, sizeof(data))) {
|
||||
printf("SUCCESS\n");
|
||||
return 0;
|
||||
} else {
|
||||
printf("FAIL\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int full_test()
|
||||
{
|
||||
const char *expected = "\x67\x64\x00\x20\xAC\x2B\x40\x6C\x1E\xF3\x68";
|
||||
|
||||
//uint8_t buffer[0xB];
|
||||
uint8_t buffer[0x100];
|
||||
size_t size = generate_sps_params(buffer, sizeof(buffer));
|
||||
|
||||
for (int i = 0; i < sizeof(buffer); i++) {
|
||||
if (i > 0) {
|
||||
printf(" ");
|
||||
}
|
||||
printf("%02X", buffer[i] & 0xFF);
|
||||
}
|
||||
printf("\n");
|
||||
printf("Size: %zu\n", size);
|
||||
|
||||
/*if (!memcmp(expected, buffer, sizeof(buffer))) {
|
||||
printf("SUCCESS\n");
|
||||
return 0;
|
||||
} else {
|
||||
printf("FAIL\n");
|
||||
return 1;
|
||||
}*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
if (simple()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (simple_double()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (complex_bit_test()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (simple_exp_golomb()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (complex_exp_golomb()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (full_test()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
31
lib/util.c
31
lib/util.c
|
@ -1,8 +1,10 @@
|
|||
#include "util.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "status.h"
|
||||
|
@ -62,3 +64,32 @@ uint16_t crc16(const void *data, size_t len)
|
|||
|
||||
return crc;
|
||||
}
|
||||
|
||||
size_t get_millis()
|
||||
{
|
||||
size_t ms; // Milliseconds
|
||||
size_t s; // Seconds
|
||||
struct timespec spec;
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &spec);
|
||||
|
||||
s = spec.tv_sec;
|
||||
ms = round(spec.tv_nsec / 1.0e6); // Convert nanoseconds to milliseconds
|
||||
if (ms > 999) {
|
||||
s++;
|
||||
ms = 0;
|
||||
}
|
||||
|
||||
return (s * 1000) + ms;
|
||||
}
|
||||
|
||||
unsigned int reverse_bits(unsigned int b, int bit_count)
|
||||
{
|
||||
unsigned int result = 0;
|
||||
|
||||
for (int i = 0; i < bit_count; i++) {
|
||||
result |= ((b >> i) & 1) << (bit_count - 1 -i );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -19,6 +19,8 @@ int is_interrupted();
|
|||
void force_interrupt();
|
||||
void install_interrupt_handler();
|
||||
void uninstall_interrupt_handler();
|
||||
size_t get_millis();
|
||||
unsigned int reverse_bits(unsigned int b, int bit_count);
|
||||
|
||||
uint16_t crc16(const void* data, size_t len);
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
#define _GNU_SOURCE
|
||||
|
||||
#include "vanilla.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
@ -14,27 +17,68 @@
|
|||
#include "vanilla.h"
|
||||
|
||||
pthread_mutex_t main_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_t gamepad_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
event_loop_t event_loop = {{0}, 0, 0, 0, PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER};
|
||||
|
||||
int vanilla_start(vanilla_event_handler_t event_handler, void *context)
|
||||
struct gamepad_data_t
|
||||
{
|
||||
uint32_t server_address;
|
||||
};
|
||||
|
||||
void *start_gamepad(void *arg)
|
||||
{
|
||||
struct gamepad_data_t *data = (struct gamepad_data_t *) arg;
|
||||
|
||||
pthread_mutex_lock(&event_loop.mutex);
|
||||
event_loop.active = 1;
|
||||
event_loop.new_index = 0;
|
||||
event_loop.used_index = 0;
|
||||
pthread_cond_broadcast(&event_loop.waitcond);
|
||||
pthread_mutex_unlock(&event_loop.mutex);
|
||||
|
||||
connect_as_gamepad_internal(&event_loop, data->server_address);
|
||||
|
||||
free(data);
|
||||
|
||||
pthread_mutex_lock(&event_loop.mutex);
|
||||
event_loop.active = 0;
|
||||
pthread_cond_broadcast(&event_loop.waitcond);
|
||||
pthread_mutex_unlock(&event_loop.mutex);
|
||||
|
||||
pthread_mutex_unlock(&main_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vanilla_start_internal(uint32_t server_address)
|
||||
{
|
||||
if (pthread_mutex_trylock(&main_mutex) == 0) {
|
||||
int r = connect_as_gamepad_internal(event_handler, context, 0);
|
||||
pthread_mutex_unlock(&main_mutex);
|
||||
return r;
|
||||
pthread_t other;
|
||||
|
||||
struct gamepad_data_t *data = malloc(sizeof(struct gamepad_data_t));
|
||||
data->server_address = server_address;
|
||||
|
||||
// Lock event loop mutex so it can't be set to active until we're ready
|
||||
pthread_mutex_lock(&event_loop.mutex);
|
||||
|
||||
// Start other thread (which will set event loop to active)
|
||||
pthread_create(&other, NULL, start_gamepad, data);
|
||||
pthread_setname_np(other, "vanilla-gamepad");
|
||||
|
||||
// Wait for event loop to be set active before returning
|
||||
while (!event_loop.active) {
|
||||
pthread_cond_wait(&event_loop.waitcond, &event_loop.mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&event_loop.mutex);
|
||||
|
||||
return VANILLA_SUCCESS;
|
||||
} else {
|
||||
return VANILLA_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
int vanilla_start_udp(vanilla_event_handler_t event_handler, void *context, uint32_t server_address)
|
||||
int vanilla_start(uint32_t server_address)
|
||||
{
|
||||
if (pthread_mutex_trylock(&main_mutex) == 0) {
|
||||
int r = connect_as_gamepad_internal(event_handler, context, server_address);
|
||||
pthread_mutex_unlock(&main_mutex);
|
||||
return r;
|
||||
} else {
|
||||
return VANILLA_ERROR;
|
||||
}
|
||||
return vanilla_start_internal(server_address);
|
||||
}
|
||||
|
||||
void vanilla_stop()
|
||||
|
@ -117,3 +161,13 @@ int vanilla_sync(uint16_t code, uint32_t server_address)
|
|||
{
|
||||
return sync_internal(code, server_address);
|
||||
}
|
||||
|
||||
int vanilla_poll_event(vanilla_event_t *event)
|
||||
{
|
||||
return get_event(&event_loop, event, 0);
|
||||
}
|
||||
|
||||
int vanilla_wait_event(vanilla_event_t *event)
|
||||
{
|
||||
return get_event(&event_loop, event, 1);
|
||||
}
|
|
@ -60,6 +60,7 @@ enum VanillaGamepadButtons
|
|||
|
||||
enum VanillaEvent
|
||||
{
|
||||
VANILLA_EVENT_NONE,
|
||||
VANILLA_EVENT_VIDEO,
|
||||
VANILLA_EVENT_AUDIO,
|
||||
VANILLA_EVENT_VIBRATE
|
||||
|
@ -94,19 +95,22 @@ static const uint8_t VANILLA_PPS_PARAMS[] = {
|
|||
0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x06, 0x0c, 0xe8
|
||||
};
|
||||
|
||||
/**
|
||||
* Event handler used by caller to receive events
|
||||
*/
|
||||
typedef void (*vanilla_event_handler_t)(void *context, int event_type, const char *data, size_t data_size);
|
||||
typedef struct
|
||||
{
|
||||
int type;
|
||||
uint8_t data[65536];
|
||||
size_t size;
|
||||
} vanilla_event_t;
|
||||
|
||||
/**
|
||||
* Start listening for gamepad commands
|
||||
*/
|
||||
int vanilla_start(vanilla_event_handler_t event_handler, void *context);
|
||||
int vanilla_start_udp(vanilla_event_handler_t event_handler, void *context, uint32_t server_address);
|
||||
|
||||
int vanilla_start(uint32_t server_address);
|
||||
int vanilla_sync(uint16_t code, uint32_t server_address);
|
||||
|
||||
int vanilla_poll_event(vanilla_event_t *event);
|
||||
int vanilla_wait_event(vanilla_event_t *event);
|
||||
|
||||
/**
|
||||
* Attempt to stop the current action
|
||||
*
|
||||
|
|
|
@ -18,20 +18,20 @@ add_custom_target(
|
|||
)
|
||||
add_custom_target(
|
||||
wpa_supplicant_build
|
||||
COMMAND make -j$$(nproc) && cp wpa_supplicant wpa_supplicant_drc
|
||||
COMMAND make -j$$(nproc)
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/hostap/wpa_supplicant"
|
||||
BYPRODUCTS "${CMAKE_CURRENT_SOURCE_DIR}/hostap/wpa_supplicant/wpa_supplicant_drc"
|
||||
BYPRODUCTS "${CMAKE_CURRENT_SOURCE_DIR}/hostap/wpa_supplicant/wpa_supplicant"
|
||||
USES_TERMINAL
|
||||
DEPENDS wpa_supplicant_configure
|
||||
)
|
||||
add_executable(wpa_supplicant IMPORTED)
|
||||
add_dependencies(wpa_supplicant wpa_supplicant_build)
|
||||
set_target_properties(wpa_supplicant PROPERTIES
|
||||
IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/hostap/wpa_supplicant/wpa_supplicant_drc"
|
||||
IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/hostap/wpa_supplicant/wpa_supplicant"
|
||||
)
|
||||
add_custom_command(
|
||||
TARGET wpa_supplicant_build POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/hostap/wpa_supplicant/wpa_supplicant_drc" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/hostap/wpa_supplicant/wpa_supplicant" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/wpa_supplicant_drc"
|
||||
DEPENDS wpa_supplicant_build
|
||||
)
|
||||
|
||||
|
|
|
@ -981,6 +981,10 @@ void *vanilla_connect_to_console(void *data)
|
|||
void vanilla_listen(const char *wireless_interface)
|
||||
{
|
||||
int skt = open_socket(VANILLA_PIPE_CMD_SERVER_PORT);
|
||||
if (skt == -1) {
|
||||
pprint("Failed to open server socket\n");
|
||||
return;
|
||||
}
|
||||
uint32_t control_code;
|
||||
|
||||
struct sockaddr_in addr = {0};
|
||||
|
|
31
rpi/CMakeLists.txt
Normal file
31
rpi/CMakeLists.txt
Normal file
|
@ -0,0 +1,31 @@
|
|||
find_package(SDL2 REQUIRED)
|
||||
#find_package(SDL2_ttf REQUIRED)
|
||||
find_package(FFmpeg REQUIRED COMPONENTS avformat avcodec avutil avfilter swscale)
|
||||
|
||||
add_executable(vanilla-pi
|
||||
drm.c
|
||||
main.c
|
||||
)
|
||||
|
||||
install(TARGETS vanilla-pi)
|
||||
|
||||
target_include_directories(vanilla-pi PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/lib"
|
||||
|
||||
# xf86drm.h needs this
|
||||
/usr/include/libdrm
|
||||
)
|
||||
|
||||
target_link_libraries(vanilla-pi PRIVATE
|
||||
SDL2::SDL2
|
||||
# SDL2_ttf::SDL2_ttf
|
||||
vanilla
|
||||
FFmpeg::avfilter
|
||||
FFmpeg::avformat
|
||||
FFmpeg::avcodec
|
||||
FFmpeg::avutil
|
||||
FFmpeg::swscale
|
||||
|
||||
drm
|
||||
m
|
||||
)
|
269
rpi/drm.c
Normal file
269
rpi/drm.c
Normal file
|
@ -0,0 +1,269 @@
|
|||
#include "drm.h"
|
||||
|
||||
#include <drm_fourcc.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <xf86drm.h>
|
||||
#include <xf86drmMode.h>
|
||||
|
||||
#define ALIGN(x, a) ((x) + (a - 1)) & (~(a - 1))
|
||||
|
||||
int set_tty(int mode)
|
||||
{
|
||||
/*int tty_fd = open("/dev/tty0", O_RDWR);
|
||||
if (tty_fd == -1) {
|
||||
fprintf(stderr, "Failed to open /dev/tty\n");
|
||||
return 0;
|
||||
}
|
||||
if (ioctl(tty_fd, KDSETMODE, mode) < 0) {
|
||||
fprintf(stderr, "Failed to set KDSETMODE: %s (%i)\n", strerror(errno), errno);
|
||||
return 0;
|
||||
}
|
||||
close(tty_fd);*/
|
||||
|
||||
/*if (ioctl(STDIN_FILENO, KDSETMODE, mode) < 0) {
|
||||
fprintf(stderr, "Failed to set KDSETMODE: %s (%i)\n", strerror(errno), errno);
|
||||
return 0;
|
||||
}*/
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int initialize_drm(vanilla_drm_ctx_t *ctx)
|
||||
{
|
||||
// Open DRM
|
||||
ctx->fd = drmOpen("vc4", 0);
|
||||
if (ctx->fd == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!set_tty(KD_GRAPHICS)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
|
||||
// Find DRM output
|
||||
drmModeResPtr res = drmModeGetResources(ctx->fd);
|
||||
|
||||
for (int i = 0; i < res->count_connectors; i++) {
|
||||
drmModeConnectorPtr c = drmModeGetConnector(ctx->fd, res->connectors[i]);
|
||||
if (c->encoder_id) {
|
||||
drmModeEncoderPtr enc = drmModeGetEncoder(ctx->fd, c->encoder_id);
|
||||
if (enc->crtc_id) {
|
||||
drmModeCrtcPtr crtc = drmModeGetCrtc(ctx->fd, enc->crtc_id);
|
||||
|
||||
// Good! We can use this connector :)
|
||||
ctx->crtc = crtc->crtc_id;
|
||||
|
||||
for (int j = 0; j < res->count_crtcs; j++) {
|
||||
if (res->crtcs[j] == crtc->crtc_id) {
|
||||
ctx->crtc_index = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 1;
|
||||
|
||||
drmModeFreeCrtc(crtc);
|
||||
}
|
||||
drmModeFreeEncoder(enc);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
set_tty(KD_TEXT);
|
||||
|
||||
// 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)
|
||||
{
|
||||
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) {
|
||||
fprintf(stderr, "Failed to find plane for format: %x\n", format, DRM_FORMAT_YUV420, frame->format);
|
||||
return 0;
|
||||
} else {
|
||||
ctx->got_plane = 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->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;
|
||||
|
||||
return 1;
|
||||
}
|
26
rpi/drm.h
Normal file
26
rpi/drm.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
#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
|
491
rpi/main.c
Normal file
491
rpi/main.c
Normal file
|
@ -0,0 +1,491 @@
|
|||
#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"
|
||||
|
||||
AVFrame *present_frame;
|
||||
AVFrame *decoding_frame;
|
||||
pthread_mutex_t decoding_mutex;
|
||||
pthread_cond_t decoding_wait_cond;
|
||||
AVCodecContext *video_codec_ctx;
|
||||
AVCodecParserContext *video_parser;
|
||||
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(const 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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t ticks = SDL_GetTicks64();
|
||||
|
||||
// Send packet to decoder
|
||||
err = avcodec_send_packet(video_codec_ctx, 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 int decode_pkt_ready = 0;
|
||||
static vanilla_event_t decode_event;
|
||||
int decode_loop(void *)
|
||||
{
|
||||
vanilla_event_t our_pkt;
|
||||
|
||||
SDL_LockMutex(decode_loop_mutex);
|
||||
while (decode_loop_running) {
|
||||
while (!decode_pkt_ready) {
|
||||
SDL_CondWait(decode_loop_cond, decode_loop_mutex);
|
||||
}
|
||||
|
||||
our_pkt = decode_event;
|
||||
decode_pkt_ready = 0;
|
||||
|
||||
SDL_UnlockMutex(decode_loop_mutex);
|
||||
|
||||
decode(our_pkt.data, our_pkt.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);
|
||||
decode_event = event;
|
||||
decode_pkt_ready = 1;
|
||||
SDL_CondBroadcast(decode_loop_cond);
|
||||
SDL_UnlockMutex(decode_loop_mutex);
|
||||
// decode(event.data, event.size);
|
||||
} 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];
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
set_tty(KD_TEXT);
|
||||
|
||||
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;
|
||||
if (!initialize_drm(&drm_ctx)) {
|
||||
fprintf(stderr, "Failed to find DRM output\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize SDL2 for audio and input
|
||||
if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER) < 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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
video_parser = av_parser_init(codec->id);
|
||||
if (!video_parser) {
|
||||
fprintf(stderr, "Failed to create codec parser\n");
|
||||
exit(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);
|
||||
SDL_Thread *display_thread = SDL_CreateThread(display_loop, "vanilla-pi-display", &drm_ctx);
|
||||
|
||||
SDL_GameController *controller = find_valid_controller();
|
||||
|
||||
while (running) {
|
||||
SDL_Event event;
|
||||
if (SDL_WaitEvent(&event)) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (controller) {
|
||||
uint16_t amount = vibrate ? 0xFFFF : 0;
|
||||
SDL_GameControllerRumble(controller, amount, amount, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
SDL_WaitThread(display_thread, NULL);
|
||||
|
||||
pthread_cond_destroy(&decoding_wait_cond);
|
||||
pthread_mutex_destroy(&decoding_mutex);
|
||||
|
||||
av_parser_close(video_parser);
|
||||
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;
|
||||
}
|
Loading…
Reference in a new issue