Merge branch 'rpi-eventloop'

This commit is contained in:
MattKC 2024-11-13 22:14:37 -08:00
commit e9bd0fa30f
24 changed files with 1651 additions and 150 deletions

3
.gitignore vendored
View file

@ -4,3 +4,6 @@ build/
.cache/
*.AppImage
squashfs-root/
buildpi/
rpi-ffmpeg/

View file

@ -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()
add_subdirectory(app)
if (VANILLA_BUILD_DESKTOP)
add_subdirectory(app)
endif()
if (VANILLA_BUILD_RPI)
add_subdirectory(rpi)
endif()

View file

@ -14,20 +14,22 @@
#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) {
case VANILLA_EVENT_VIDEO:
emit backend->videoAvailable(QByteArray(data, dataLength));
break;
case VANILLA_EVENT_AUDIO:
emit backend->audioAvailable(QByteArray(data, dataLength));
break;
case VANILLA_EVENT_VIBRATE:
emit backend->vibrate(*data);
break;
while (vanilla_wait_event(&event)) {
switch (event.type) {
case VANILLA_EVENT_VIDEO:
emit videoAvailable(QByteArray((const char *) event.data, event.size));
break;
case VANILLA_EVENT_AUDIO:
emit audioAvailable(QByteArray((const char *) event.data, event.size));
break;
case VANILLA_EVENT_VIBRATE:
emit vibrate(*event.data);
break;
}
}
}
@ -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());
}

View file

@ -75,6 +75,8 @@ protected:
private slots:
void syncFutureCompleted();
void vanillaEventHandler();
};
class BackendViaInternalPipe : public Backend

7
docker/rpi/Dockerfile Normal file
View 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" ]

View file

@ -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()

View file

@ -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());

View file

@ -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;

View file

@ -1,3 +1,5 @@
#define _GNU_SOURCE
#include "gamepad.h"
#include <arpa/inet.h>
@ -29,30 +31,19 @@ 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;
address.sin_family = AF_INET;
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,14 +144,15 @@ 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));
if (ret != VANILLA_SUCCESS) {
goto exit;
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;
while (1) {
@ -172,13 +172,14 @@ int sync_internal(uint16_t code, uint32_t server_address)
}
exit_pipe:
close(skt);
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,16 +200,17 @@ 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;
ret = connect_to_backend(&pipe_cc_skt, VANILLA_PIPE_CC_CONNECT);
if (ret != VANILLA_SUCCESS) {
goto exit;
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
@ -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);
send_pipe_cc(pipe_cc_skt, VANILLA_PIPE_CC_UNBIND, 0);
if (server_address != 0) {
send_pipe_cc(pipe_cc_skt, VANILLA_PIPE_CC_UNBIND, 0);
}
ret = VANILLA_SUCCESS;
@ -261,7 +272,8 @@ exit_vid:
close(info.socket_vid);
exit_pipe:
close(pipe_cc_skt);
if (pipe_cc_skt)
close(pipe_cc_skt);
exit:
return ret;
@ -270,4 +282,58 @@ exit:
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;
}

View file

@ -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

View file

@ -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);

View file

@ -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,26 +56,25 @@ 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;
for (int i = 0; i < sizeof(vp->extended_header); i++) {
@ -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) {
*nals_current = 3;
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++;
}
*nals_current = video_packet[byte];
nals_current++;
if (current_index == video_packet_seq_end) {
break;
}
byte = 0;
current_index = (current_index + 1) % VIDEO_PACKET_CACHE_MAX;
}
event_handler(context, VANILLA_EVENT_VIDEO, nals, (nals_current - nals));
free(nals);
// 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;
}

View file

@ -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
View 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;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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()
@ -116,4 +160,14 @@ void vanilla_set_battery_status(int battery_status)
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);
}

View file

@ -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
*

View file

@ -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
)

View file

@ -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
View 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
View 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
View 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
View 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;
}