Implement microphone support (#167)

* microphone initial work

* implement dumping functionality

Allows us to dump packets from the console for replay later on

* implement dump playback functionality

* pipe: when using playback, don't require wireless adapter

* move dump functionality to pipe

* started caramel library for interfacing with real gamepad

* finalized microphone implementation

* cleanup

* cleanup 2

* cleanup 3

* cleanup 4

* fixed more issues

* use initializers so mutex and cond are always valid

* rewrite audio output system to improve latency

* fix indent

* provide capability for audio to be enabled/disabled based on state

* fix android builds
This commit is contained in:
MattKC authored and GitHub committed 2025-05-23 12:28:32 -07:00
1 parent 745dae9b96
commit 00f41e0546
19 files changed
+660 -240

No files matched your search

+1
View File
@@ -1,4 +1,5 @@
#!/bin/sh
set -e
cd /vanilla/android
./gradlew -PbuildDir=/build assemble
zipalign -v -p 4 /build/outputs/apk/release/app-release-unsigned.apk /build/outputs/apk/release/app-release-unsigned-aligned.apk
+1
View File
@@ -32,6 +32,7 @@ if (VANILLA_BUILD_TESTS)
target_include_directories(${TEST_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
endfunction()
add_test(audioheader "test/audioheader.c")
add_test(bittest "test/bittest.c")
add_test(reversebittest "test/reversebit.c")
add_test(reversebitstresstest "test/reversebitstresstest.c")
+132 -20
View File
@@ -8,32 +8,125 @@
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include "gamepad.h"
#include "vanilla.h"
#include "util.h"
#pragma pack(push, 1)
typedef struct {
unsigned format : 3;
unsigned mono : 1;
unsigned vibrate : 1;
unsigned type : 1;
unsigned seq_id : 10;
unsigned payload_size : 16;
unsigned timestamp : 32;
unsigned char payload[2048];
} AudioPacket;
const static unsigned int TYPE_AUDIO = 0;
const static unsigned int TYPE_VIDEO = 1;
#pragma pack(pop)
static unsigned char queued_audio[8192];
static size_t queued_audio_start = 0;
static size_t queued_audio_end = 0;
static pthread_mutex_t queued_audio_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t queued_audio_cond = PTHREAD_COND_INITIALIZER;
typedef struct {
uint32_t timestamp;
uint32_t unknown_freq_0[2];
uint32_t unknown_freq_1[2];
uint32_t video_format;
} AudioPacketVideoFormat;
int send_audio_packet(const void *data, size_t len)
{
pthread_mutex_lock(&queued_audio_mutex);
for (size_t i = 0; i < len; ) {
size_t phys = queued_audio_end % sizeof(queued_audio);
size_t max_write = MIN(sizeof(queued_audio) - phys, len - i);
memcpy(queued_audio + phys, ((const unsigned char *) data) + i, max_write);
i += max_write;
queued_audio_end += max_write;
// Skip start ahead if necessary
if (queued_audio_end > queued_audio_start + sizeof(queued_audio)) {
queued_audio_start = queued_audio_end - sizeof(queued_audio);
}
}
pthread_cond_broadcast(&queued_audio_cond);
pthread_mutex_unlock(&queued_audio_mutex);
return VANILLA_SUCCESS;
}
static void *handle_queued_audio(void *data)
{
// Mic only ever sends 512 bytes at a time
const size_t MIC_PAYLOAD_SIZE = 512;
gamepad_context_t *ctx = (gamepad_context_t *) data;
pthread_mutex_lock(&queued_audio_mutex);
while (!is_interrupted()) {
while (!is_interrupted() && queued_audio_end < (queued_audio_start + MIC_PAYLOAD_SIZE)) {
// Wait for more data
pthread_cond_wait(&queued_audio_cond, &queued_audio_mutex);
}
if (is_interrupted()) {
break;
}
AudioPacket ap;
// Copy data into our packet
for (size_t i = 0; i < MIC_PAYLOAD_SIZE; ) {
size_t phys = queued_audio_start % sizeof(queued_audio);
size_t max_write = MIN(sizeof(queued_audio) - phys, MIC_PAYLOAD_SIZE - i);
memcpy(ap.payload + i, queued_audio + phys, max_write);
i += max_write;
queued_audio_start += max_write;
}
// Don't need access to the buffer while we set up the packet
pthread_mutex_unlock(&queued_audio_mutex);
// Set up remaining default parameters
ap.format = 6;
ap.mono = 1;
ap.vibrate = 0;
ap.type = TYPE_AUDIO; // Audio data
ap.timestamp = 0; // Gamepad actually sends no timestamp
ap.payload_size = MIC_PAYLOAD_SIZE;
static unsigned int seq_id = 0;
ap.seq_id = seq_id++;
// Reverse bits on params
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);//ntohs(ap.payload_size);
// ap.timestamp = reverse_bits(ap.timestamp, 32); // Not necessary because timestamp is 0
// Further reverse bits
unsigned char *bytes = (unsigned char *) &ap;
const size_t header_sz = sizeof(AudioPacket) - sizeof(ap.payload);
for (int i = 0; i < 4; i++) { // 4 instead of 8 because ap.timestamp == 0
bytes[i] = (unsigned char) reverse_bits(bytes[i], 8);
}
// Console expects 512 bytes every 16 ms so make sure we achieve that interval
static struct timeval last;
struct timeval now;
gettimeofday(&now, 0);
long diff = (now.tv_sec - last.tv_sec) * 1000000 + (now.tv_usec - last.tv_usec);
static const long target_delta = 16000;
if (diff < target_delta) {
usleep(target_delta - diff);
}
gettimeofday(&last, 0);
// Send packet to console
send_to_console(ctx->socket_aud, &ap, header_sz + MIC_PAYLOAD_SIZE, PORT_AUD);
pthread_mutex_lock(&queued_audio_mutex);
}
pthread_mutex_unlock(&queued_audio_mutex);
return 0;
}
void handle_audio_packet(gamepad_context_t *ctx, unsigned char *data, size_t len)
{
@@ -76,6 +169,17 @@ void *listen_audio(void *x)
gamepad_context_t *info = (gamepad_context_t *) x;
unsigned char data[2048];
ssize_t size;
queued_audio_start = 0;
queued_audio_end = 0;
pthread_t mic_thread;
int mic_thread_created = 1;
if (pthread_create(&mic_thread, 0, handle_queued_audio, info) != 0) {
vanilla_log("Failed to create mic thread");
mic_thread_created = 0;
}
do {
size = recv(info->socket_aud, data, sizeof(data), 0);
if (size > 0) {
@@ -83,6 +187,14 @@ void *listen_audio(void *x)
}
} while (!is_interrupted());
if (mic_thread_created) {
// Tell thread to exit
pthread_mutex_lock(&queued_audio_mutex);
pthread_cond_broadcast(&queued_audio_cond);
pthread_mutex_unlock(&queued_audio_mutex);
pthread_join(mic_thread, 0);
}
pthread_exit(NULL);
return NULL;
+27
View File
@@ -1,6 +1,33 @@
#ifndef GAMEPAD_AUDIO_H
#define GAMEPAD_AUDIO_H
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#pragma pack(push, 1)
typedef struct {
unsigned format : 3;
unsigned mono : 1;
unsigned vibrate : 1;
unsigned type : 1;
unsigned seq_id : 10;
unsigned payload_size : 16;
unsigned timestamp : 32;
unsigned char payload[2048];
} AudioPacket;
const static unsigned int TYPE_AUDIO = 0;
const static unsigned int TYPE_VIDEO = 1;
#pragma pack(pop)
typedef struct {
uint32_t timestamp;
uint32_t unknown_freq_0[2];
uint32_t unknown_freq_1[2];
uint32_t video_format;
} AudioPacketVideoFormat;
void *listen_audio(void *x);
int send_audio_packet(const void *data, size_t len);
#endif // GAMEPAD_AUDIO_H
+43 -206
View File
@@ -10,100 +10,13 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "gamepad.h"
#include "vanilla.h"
#include "util.h"
typedef struct
{
// Little endian
uint16_t packet_type;
uint16_t query_type;
uint16_t payload_size;
uint16_t seq_id;
} CmdHeader;
static_assert(sizeof(CmdHeader) == 0x8);
enum PacketType
{
PACKET_TYPE_REQUEST,
PACKET_TYPE_REQUEST_ACK,
PACKET_TYPE_RESPONSE,
PACKET_TYPE_RESPONSE_ACK
};
#pragma pack(push, 1)
typedef struct
{
uint8_t magic_0x7E;
uint8_t version;
uint8_t ids[3];
uint8_t flags;
uint8_t service_id;
uint8_t method_id;
uint16_t error_code; // Big endian
uint16_t payload_size; // Big endian
} GenericCmdHeader;
static_assert(sizeof(GenericCmdHeader) == 0xC);
typedef struct
{
CmdHeader cmd_header;
GenericCmdHeader generic_cmd_header;
uint8_t payload[1544];
} GenericPacket;
static_assert(offsetof(GenericPacket, generic_cmd_header) == 0x8);
static_assert(sizeof(GenericPacket) == 0x61C);
struct UvcUacCommand {
uint8_t f1;
uint16_t unknown_0;
uint8_t f3;
uint8_t mic_enable;
uint8_t mic_mute;
int16_t mic_volume;
int16_t mic_volume_2;
uint8_t unknown_A;
uint8_t unknown_B;
uint16_t mic_freq;
uint8_t cam_enable;
uint8_t cam_power;
uint8_t cam_power_freq;
uint8_t cam_auto_expo;
uint32_t cam_expo_absolute;
uint16_t cam_brightness;
uint16_t cam_contrast;
uint16_t cam_gain;
uint16_t cam_hue;
uint16_t cam_saturation;
uint16_t cam_sharpness;
uint16_t cam_gamma;
uint8_t cam_key_frame;
uint8_t cam_white_balance_auto;
uint32_t cam_white_balance;
uint16_t cam_multiplier;
uint16_t cam_multiplier_limit;
};
struct TimeCommand {
uint16_t days_counter;
uint16_t padding;
uint32_t seconds_counter;
};
typedef struct
{
CmdHeader cmd_header;
struct UvcUacCommand uac_uvc;
} UvcUacPacket;
typedef struct
{
CmdHeader cmd_header;
struct TimeCommand time;
} TimePacket;
const char *uic_firmware_version = "\x28\x00\x00\x58";
const char *eeprom_bytes = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x80\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x51\x32\x00\x02\x06\xd3\x36\x21\x31\x60\x52\x50\x64\xcb\xe7\x47\x0c\xca\x8a\x7e\x79\xf3\xb4\x70\xea\x34\xaf\x2c\xa0\x4b\xc6\x70\x49\x01\x0e\x1e\x24\xa1\x68\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x1c\x3d\x8b\x5c\x35\x01\x0e\x1e\x15\xab\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb8\xf0\x06\xff\x10\xab\xbc\xff\x20\x00\x11\xff\x6f\x1f\x61\x1f\x55\x1e\x60\x52\xc5\x00\x00\xf0\xff\xff\x09\x00\x00\x3a\x5c\x02\x32\x57\x02\xd0\x5b\x02\xc8\xd2\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x35\x00\x1e\x00\x22\x03\xc3\x01\x53\x01\x5d\x0e\xa9\x0e\x9b\x01\x66\xae\xe4\x17\x03\xa0\xb5\x43\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8b\x5c\x35\x01\x0e\x1e\x15\xab\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb8\xf0\x06\xff\x10\xab\xbc\xff\x20\x00\x11\xff\x6f\x1f\x61\x1f\x55\x1e\x60\x52\xc5\x00\x00\xf0\xff\xff\x09\x00\x00\x3a\x5c\x02\x32\x57\x02\xd0\x5b\x02\xc8\xd2\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x35\x00\x1e\x00\x22\x03\xc3\x01\x53\x01\x5d\x0e\xa9\x0e\x9b\x01\x66\xae\xe4\x17\x03\xa0\xb5\x43\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x2a\x58\x00\x87\x0f\x00\x87\x0f\x01\x0e\x1e\x00\x00\x00\x00\x00\x19\x00\x16\x1d\x6f\xbc\xff\x20\x00\x11\xff\x6f\x1f\x61\x1f\x55\x1e\x60\x52\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5d\x00\x78\x00\xf8\x02\x82\x01\xfc\x01\x92\x0b\xf1\x0d\x6c\x03\x48\x1a\x01\x00\x02\x17\x14\x48\x00\x00\x00\x03\xba\x31\xe4\x17\x03\xa0\xb5\x43\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x87\x0f\x15\x01\x2d\x01\x4d\x01\x7a\x01\xb7\x01\xff\x01\x03\x26\x8c\x04\xa3\x49\x54\x31\x39\x36\x33\x53\x31\x33\x37\x37\x78\x01\x00\x87\x0f\x00\x87\x0f\x01\x0e\x1e\xff\xff\x00\x00\x87\x0f\x00\x87\x0f\x02\x00\x08\xc3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\xa3\x49";
int current_region = VANILLA_REGION_AMERICA;
void set_region(int region)
@@ -111,125 +24,19 @@ void set_region(int region)
current_region = region;
}
typedef struct
CmdHeader create_ack_packet(CmdHeader *pkt)
{
uint16_t new_min_x;
uint16_t new_min_y;
uint16_t new_max_x;
uint16_t new_max_y;
uint16_t old_min_x;
uint16_t old_min_y;
uint16_t old_max_x;
uint16_t old_max_y;
uint16_t crc;
} TouchPadCalibration;
typedef struct
{
uint8_t unk00[0x103];
uint8_t region;
uint16_t region_crc;
uint8_t unk11e[0x4d];
TouchPadCalibration factory_touchpad_calibration;
uint8_t unk19e[0x6e];
TouchPadCalibration second_factory_touchpad_calibration;
uint8_t unk200[0x5f];
TouchPadCalibration touchpad_calibration;
uint8_t unk256[0xaa];
} EEPROM;
static_assert(offsetof(EEPROM, region) == 0x103);
static_assert(offsetof(EEPROM, factory_touchpad_calibration) == 0x153);
static_assert(offsetof(EEPROM, second_factory_touchpad_calibration) == 0x1D3);
static_assert(offsetof(EEPROM, touchpad_calibration) == 0x244);
static_assert(sizeof(EEPROM) == 0x300);
enum BoardVersion
{
BOARD_VERSION_DK1,
BOARD_VERSION_DK1_EP_DK2,
BOARD_VERSION_DP1,
BOARD_VERSION_DP2,
BOARD_VERSION_DK3,
BOARD_VERSION_DK4,
BOARD_VERSION_PreDP3_DP3,
BOARD_VERSION_DK5,
BOARD_VERSION_DP4,
BOARD_VERSION_DKMP,
BOARD_VERSION_DP5,
BOARD_VERSION_MASS,
BOARD_VERSION_DKMP2,
BOARD_VERSION_DRC_I,
BOARD_VERSION_DKTVMP,
};
enum ChipVersion
{
CHIP_VERSION_TS = 0x10,
CHIP_VERSION_ES1 = 0x20,
CHIP_VERSION_ES2 = 0x30,
CHIP_VERSION_ES3 = 0x40,
CHIP_VERSION_MS01 = 0x41,
};
typedef struct
{
uint32_t board_ver;
uint32_t chip_ver;
uint32_t run_ver;
uint32_t uic_ver;
uint32_t unk10;
uint32_t sd_cis;
uint32_t spl_id;
} SystemInfo;
static_assert(sizeof(SystemInfo) == 0x1C);
#pragma pack(pop)
const char *uic_firmware_version = "\x28\x00\x00\x58";
const char *eeprom_bytes = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x80\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x51\x32\x00\x02\x06\xd3\x36\x21\x31\x60\x52\x50\x64\xcb\xe7\x47\x0c\xca\x8a\x7e\x79\xf3\xb4\x70\xea\x34\xaf\x2c\xa0\x4b\xc6\x70\x49\x01\x0e\x1e\x24\xa1\x68\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x1c\x3d\x8b\x5c\x35\x01\x0e\x1e\x15\xab\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb8\xf0\x06\xff\x10\xab\xbc\xff\x20\x00\x11\xff\x6f\x1f\x61\x1f\x55\x1e\x60\x52\xc5\x00\x00\xf0\xff\xff\x09\x00\x00\x3a\x5c\x02\x32\x57\x02\xd0\x5b\x02\xc8\xd2\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x35\x00\x1e\x00\x22\x03\xc3\x01\x53\x01\x5d\x0e\xa9\x0e\x9b\x01\x66\xae\xe4\x17\x03\xa0\xb5\x43\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8b\x5c\x35\x01\x0e\x1e\x15\xab\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb8\xf0\x06\xff\x10\xab\xbc\xff\x20\x00\x11\xff\x6f\x1f\x61\x1f\x55\x1e\x60\x52\xc5\x00\x00\xf0\xff\xff\x09\x00\x00\x3a\x5c\x02\x32\x57\x02\xd0\x5b\x02\xc8\xd2\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x35\x00\x1e\x00\x22\x03\xc3\x01\x53\x01\x5d\x0e\xa9\x0e\x9b\x01\x66\xae\xe4\x17\x03\xa0\xb5\x43\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x2a\x58\x00\x87\x0f\x00\x87\x0f\x01\x0e\x1e\x00\x00\x00\x00\x00\x19\x00\x16\x1d\x6f\xbc\xff\x20\x00\x11\xff\x6f\x1f\x61\x1f\x55\x1e\x60\x52\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5d\x00\x78\x00\xf8\x02\x82\x01\xfc\x01\x92\x0b\xf1\x0d\x6c\x03\x48\x1a\x01\x00\x02\x17\x14\x48\x00\x00\x00\x03\xba\x31\xe4\x17\x03\xa0\xb5\x43\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x87\x0f\x15\x01\x2d\x01\x4d\x01\x7a\x01\xb7\x01\xff\x01\x03\x26\x8c\x04\xa3\x49\x54\x31\x39\x36\x33\x53\x31\x33\x37\x37\x78\x01\x00\x87\x0f\x00\x87\x0f\x01\x0e\x1e\xff\xff\x00\x00\x87\x0f\x00\x87\x0f\x02\x00\x08\xc3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\xa3\x49";
enum CommandType
{
CMD_GENERIC,
CMD_UVC_UAC,
CMD_TIME
};
enum ServiceID
{
SERVICE_ID_SOFTWARE,
SERVICE_ID_CONFIG,
SERVICE_ID_ADMIN,
SERVICE_ID_METRICS,
SERVICE_ID_SYSTEM,
SERVICE_ID_PERIPHERAL
};
enum MethodIDSoftware
{
METHOD_ID_SOFTWARE_GET_VERSION = 0x0,
METHOD_ID_SOFTWARE_GET_EXT_ID = 0xa,
};
enum MethodIDSystem
{
METHOD_ID_SYSTEM_GET_INFO = 0x4,
METHOD_ID_SYSTEM_POWER = 0x1A,
};
enum MethodIDPeripheral
{
METHOD_ID_PERIPHERAL_EEPROM = 0x6,
METHOD_ID_PERIPHERAL_UPDATE_EEPROM = 0xC,
METHOD_ID_PERIPHERAL_SET_REMOCON = 0x18,
};
void send_ack_packet(int skt, CmdHeader *pkt)
{
CmdHeader ack;
CmdHeader ack;
ack.packet_type = pkt->packet_type == PACKET_TYPE_REQUEST ? PACKET_TYPE_REQUEST_ACK : PACKET_TYPE_RESPONSE_ACK;
ack.query_type = pkt->query_type;
ack.payload_size = 0;
ack.seq_id = pkt->seq_id;
return ack;
}
void send_ack_packet(int skt, CmdHeader *pkt)
{
CmdHeader ack = create_ack_packet(pkt);
send_to_console(skt, &ack, sizeof(ack), PORT_CMD);
}
@@ -325,6 +132,14 @@ void handle_generic_packet(gamepad_context_t *info, int skt, GenericPacket *requ
}
break;
}
case 10:
response.generic_cmd_header.error_code = 0;
break;
case 11:
response.payload[0] = 0;
response.generic_cmd_header.payload_size = htons(1);
response.generic_cmd_header.error_code = 0;
break;
}
break;
case SERVICE_ID_PERIPHERAL:
@@ -376,11 +191,31 @@ void handle_generic_packet(gamepad_context_t *info, int skt, GenericPacket *requ
send_generic_response(skt, (CmdHeader *) &response);
}
void handle_uac_uvc_packet(int skt, UvcUacPacket *request)
void handle_uac_uvc_packet(gamepad_context_t *info, int skt, UvcUacPacket *request)
{
vanilla_log("uac/uvc - mic_enable: %u, mic_freq: %u, mic_mute: %u, mic_volume: %i, mic_volume2: %i", request->uac_uvc.mic_enable, request->uac_uvc.mic_freq, request->uac_uvc.mic_mute, request->uac_uvc.mic_volume, request->uac_uvc.mic_volume_2);
// send_quick_response(skt, &request->cmd_header);
uint8_t mic_enabled = request->uac_uvc.mic_enable;
push_event(info->event_loop, VANILLA_EVENT_MIC, &mic_enabled, sizeof(mic_enabled));
print_hex(&request->uac_uvc, sizeof(request->uac_uvc));
vanilla_log_no_newline("\n");
// TODO: Create real response instead of using canned data
static const size_t uvc_resp_size = 16;
const char *uvc_resp = "\x00\x80\x00\x80\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00";
const char *uvc_resp2 = "\x00\x16\x00\x19\x9E\x00\x00\x00\x80\x00\x40\x00\x00\x00\x00\xFF";
char buf[sizeof(CmdHeader) + uvc_resp_size];
CmdHeader *response = (CmdHeader *) buf;
response->packet_type = PACKET_TYPE_RESPONSE;
response->payload_size = uvc_resp_size;
response->query_type = request->cmd_header.query_type;
response->seq_id = request->cmd_header.seq_id;
memcpy(buf + sizeof(CmdHeader), uvc_resp, uvc_resp_size);
send_to_console(skt, buf, sizeof(buf), PORT_CMD);
}
void handle_time_packet(int skt, TimePacket *request)
@@ -392,6 +227,7 @@ void handle_time_packet(int skt, TimePacket *request)
void handle_command_packet(gamepad_context_t *info, int skt, CmdHeader *request)
{
vanilla_log("packet_type: %u, query_type: %u, payload_size: 0x%X", request->packet_type, request->query_type, request->payload_size);
switch (request->packet_type)
{
case PACKET_TYPE_REQUEST:
@@ -405,7 +241,7 @@ void handle_command_packet(gamepad_context_t *info, int skt, CmdHeader *request)
}
case CMD_UVC_UAC:
{
handle_uac_uvc_packet(skt, (UvcUacPacket *)request);
handle_uac_uvc_packet(info, skt, (UvcUacPacket *)request);
break;
}
case CMD_TIME:
@@ -429,6 +265,7 @@ void handle_command_packet(gamepad_context_t *info, int skt, CmdHeader *request)
case PACKET_TYPE_REQUEST_ACK:
case PACKET_TYPE_RESPONSE_ACK:
// NOTE: Should we do something if we don't get this? e.g. attempt re-sending request/response after some time has passed
// Real gamepad attempts 5 times in 33ms intervals if ACK is not received
break;
default:
vanilla_log("Unhandled command packet type: %u", request->packet_type);
+205
View File
@@ -1,8 +1,213 @@
#ifndef GAMEPAD_COMMAND_H
#define GAMEPAD_COMMAND_H
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
typedef struct
{
// Little endian
uint16_t packet_type;
uint16_t query_type;
uint16_t payload_size;
uint16_t seq_id;
} CmdHeader;
static_assert(sizeof(CmdHeader) == 0x8);
enum PacketType
{
PACKET_TYPE_REQUEST,
PACKET_TYPE_REQUEST_ACK,
PACKET_TYPE_RESPONSE,
PACKET_TYPE_RESPONSE_ACK
};
#pragma pack(push, 1)
typedef struct
{
uint8_t magic_0x7E;
uint8_t version;
uint8_t ids[3];
uint8_t flags;
uint8_t service_id;
uint8_t method_id;
uint16_t error_code; // Big endian
uint16_t payload_size; // Big endian
} GenericCmdHeader;
static_assert(sizeof(GenericCmdHeader) == 0xC);
typedef struct
{
CmdHeader cmd_header;
GenericCmdHeader generic_cmd_header;
uint8_t payload[1544];
} GenericPacket;
static_assert(offsetof(GenericPacket, generic_cmd_header) == 0x8);
static_assert(sizeof(GenericPacket) == 0x61C);
struct UvcUacCommand {
uint8_t f1;
uint16_t unknown_0;
uint8_t f3;
uint8_t mic_enable;
uint8_t mic_mute;
int16_t mic_volume;
int16_t mic_volume_2;
uint8_t unknown_A;
uint8_t unknown_B;
uint16_t mic_freq;
uint8_t cam_enable;
uint8_t cam_power;
uint8_t cam_power_freq;
uint8_t cam_auto_expo;
uint32_t cam_expo_absolute;
uint16_t cam_brightness;
uint16_t cam_contrast;
uint16_t cam_gain;
uint16_t cam_hue;
uint16_t cam_saturation;
uint16_t cam_sharpness;
uint16_t cam_gamma;
uint8_t cam_key_frame;
uint8_t cam_white_balance_auto;
uint32_t cam_white_balance;
uint16_t cam_multiplier;
uint16_t cam_multiplier_limit;
};
struct TimeCommand {
uint16_t days_counter;
uint16_t padding;
uint32_t seconds_counter;
};
typedef struct
{
CmdHeader cmd_header;
struct UvcUacCommand uac_uvc;
} UvcUacPacket;
typedef struct
{
CmdHeader cmd_header;
struct TimeCommand time;
} TimePacket;
typedef struct
{
uint16_t new_min_x;
uint16_t new_min_y;
uint16_t new_max_x;
uint16_t new_max_y;
uint16_t old_min_x;
uint16_t old_min_y;
uint16_t old_max_x;
uint16_t old_max_y;
uint16_t crc;
} TouchPadCalibration;
typedef struct
{
uint8_t unk00[0x103];
uint8_t region;
uint16_t region_crc;
uint8_t unk11e[0x4d];
TouchPadCalibration factory_touchpad_calibration;
uint8_t unk19e[0x6e];
TouchPadCalibration second_factory_touchpad_calibration;
uint8_t unk200[0x5f];
TouchPadCalibration touchpad_calibration;
uint8_t unk256[0xaa];
} EEPROM;
static_assert(offsetof(EEPROM, region) == 0x103);
static_assert(offsetof(EEPROM, factory_touchpad_calibration) == 0x153);
static_assert(offsetof(EEPROM, second_factory_touchpad_calibration) == 0x1D3);
static_assert(offsetof(EEPROM, touchpad_calibration) == 0x244);
static_assert(sizeof(EEPROM) == 0x300);
enum BoardVersion
{
BOARD_VERSION_DK1,
BOARD_VERSION_DK1_EP_DK2,
BOARD_VERSION_DP1,
BOARD_VERSION_DP2,
BOARD_VERSION_DK3,
BOARD_VERSION_DK4,
BOARD_VERSION_PreDP3_DP3,
BOARD_VERSION_DK5,
BOARD_VERSION_DP4,
BOARD_VERSION_DKMP,
BOARD_VERSION_DP5,
BOARD_VERSION_MASS,
BOARD_VERSION_DKMP2,
BOARD_VERSION_DRC_I,
BOARD_VERSION_DKTVMP,
};
enum ChipVersion
{
CHIP_VERSION_TS = 0x10,
CHIP_VERSION_ES1 = 0x20,
CHIP_VERSION_ES2 = 0x30,
CHIP_VERSION_ES3 = 0x40,
CHIP_VERSION_MS01 = 0x41,
};
typedef struct
{
uint32_t board_ver;
uint32_t chip_ver;
uint32_t run_ver;
uint32_t uic_ver;
uint32_t unk10;
uint32_t sd_cis;
uint32_t spl_id;
} SystemInfo;
static_assert(sizeof(SystemInfo) == 0x1C);
#pragma pack(pop)
enum CommandType
{
CMD_GENERIC,
CMD_UVC_UAC,
CMD_TIME
};
enum ServiceID
{
SERVICE_ID_SOFTWARE,
SERVICE_ID_CONFIG,
SERVICE_ID_ADMIN,
SERVICE_ID_METRICS,
SERVICE_ID_SYSTEM,
SERVICE_ID_PERIPHERAL
};
enum MethodIDSoftware
{
METHOD_ID_SOFTWARE_GET_VERSION = 0x0,
METHOD_ID_SOFTWARE_GET_EXT_ID = 0xa,
};
enum MethodIDSystem
{
METHOD_ID_SYSTEM_GET_INFO = 0x4,
METHOD_ID_SYSTEM_POWER = 0x1A,
};
enum MethodIDPeripheral
{
METHOD_ID_PERIPHERAL_EEPROM = 0x6,
METHOD_ID_PERIPHERAL_UPDATE_EEPROM = 0xC,
METHOD_ID_PERIPHERAL_SET_REMOCON = 0x18,
};
void *listen_command(void *x);
void set_region(int region);
CmdHeader create_ack_packet(CmdHeader *pkt);
void send_ack_packet(int skt, CmdHeader *pkt);
#endif // GAMEPAD_COMMAND_H
+4 -1
View File
@@ -101,7 +101,10 @@ void send_to_console(int fd, const void *data, size_t data_size, uint16_t port)
ssize_t sent = sendto(fd, data, data_size, 0, (const struct sockaddr *) &addr, addr_size);
if (sent == -1) {
vanilla_log("Failed to send to Wii U socket: fd: %d, port: %d, errno: %i", fd, console_port, skterr());
int err = skterr();
if (err != 111) { // 111 is connection refused, occurs if we lose connection, but we'll already know that for other reasons so we don't need to spam the console with this error
vanilla_log("Failed to send to Wii U socket: fd: %d, port: %d, errno: %i", fd, console_port, skterr());
}
}
}
+49
View File
@@ -0,0 +1,49 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "gamepad/audio.h"
#include "util.h"
int main(int argc, const char **argv)
{
if (argc != 2) {
printf("Usage: %s <hex-string>\n", argv[0]);
return 1;
}
const char *s = argv[1];
uint8_t bytes[8];
if (strlen(s) != sizeof(bytes) * 2) {
printf("ERROR: Invalid hex data\n");
return 1;
}
char tmp_hex[3];
tmp_hex[2] = 0;
for (size_t i = 0; i < sizeof(bytes); i++) {
memcpy(tmp_hex, s + i * 2, 2);
bytes[i] = strtol(tmp_hex, 0, 16);
}
for (int i = 0; i < sizeof(bytes); i++) {
bytes[i] = (unsigned char) reverse_bits(bytes[i], 8);
}
AudioPacket *ap = (AudioPacket *) bytes;
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);//ntohs(ap->payload_size);
ap->timestamp = reverse_bits(ap->timestamp, 32);
printf("Format: %u\n", ap->format);
printf("Mono: %u\n", ap->mono);
printf("Vibrate: %u\n", ap->vibrate);
printf("Type: %u\n", ap->type);
printf("Seq ID: %u\n", ap->seq_id);
printf("Payload Size: %u\n", ap->payload_size);
printf("Timestamp: %u\n", ap->timestamp);
return 0;
}
+8
View File
@@ -98,3 +98,11 @@ uint32_t reverse_bits(uint32_t b, int bit_count)
b >>= 32 - bit_count;
return b;
}
void print_hex(const void *data, size_t len)
{
const unsigned char *c = (const unsigned char *) data;
for (size_t i = 0; i < len; i++) {
vanilla_log_no_newline("%02X", c[i]);
}
}
+2
View File
@@ -24,4 +24,6 @@ unsigned int reverse_bits(unsigned int b, int bit_count);
uint16_t crc16(const void* data, size_t len);
void print_hex(const void *data, size_t len);
#endif // VANILLA_UTIL_H
+6
View File
@@ -12,6 +12,7 @@
#include <string.h>
#include <unistd.h>
#include "gamepad/audio.h"
#include "gamepad/command.h"
#include "gamepad/gamepad.h"
#include "gamepad/input.h"
@@ -232,3 +233,8 @@ size_t vanilla_generate_h264_header(void *data, size_t size)
{
return generate_h264_header(data, size);
}
void vanilla_send_audio(const void *data, size_t size)
{
send_audio_packet(data, size);
}
+7 -1
View File
@@ -74,7 +74,8 @@ enum VanillaEvent
VANILLA_EVENT_AUDIO,
VANILLA_EVENT_VIBRATE,
VANILLA_EVENT_SYNC,
VANILLA_EVENT_ERROR
VANILLA_EVENT_ERROR,
VANILLA_EVENT_MIC
};
enum VanillaRegion
@@ -193,6 +194,11 @@ void vanilla_set_battery_status(int battery_status);
*/
size_t vanilla_generate_h264_header(void *data, size_t size);
/**
* Send microphone audio
*/
void vanilla_send_audio(const void *data, size_t size);
#if defined(__cplusplus)
}
#endif
+3
View File
@@ -73,6 +73,9 @@ void *vpi_event_handler(void *data)
case VANILLA_EVENT_ERROR:
vpi_game_set_error(*(int *)event.data);
break;
case VANILLA_EVENT_MIC:
vui_mic_enabled_set(vui, event.data[0]);
break;
}
vanilla_free_event(&event);
+8
View File
@@ -13,8 +13,16 @@
#include "menu_main.h"
#include "platform.h"
void vpi_mic_callback(void *userdata, const uint8_t *data, size_t len)
{
vanilla_send_audio(data, len);
}
void vpi_menu_init(vui_context_t *vui)
{
// Set microphone callback
vui_mic_callback_set(vui, vpi_mic_callback, 0);
// Start with main menu
vpi_menu_main(vui, 0);
}
+2
View File
@@ -100,12 +100,14 @@ void vpi_display_update(vui_context_t *vui, int64_t time, void *v)
pthread_mutex_lock(&vpi_decode_loop_mutex);
if (vpi_present_frame && vpi_present_frame->data[0]) {
vui_game_mode_set(vui, 1);
vui_audio_set_enabled(vui, 1);
vpi_game_set_error(VANILLA_SUCCESS);
}
pthread_mutex_unlock(&vpi_decode_loop_mutex);
break;
case VANILLA_ERR_DISCONNECTED:
vui_label_update_text(vui, status_lbl, lang(VPI_LANG_DISCONNECTED));
vui_audio_set_enabled(vui, 0);
vui_game_mode_set(vui, 0);
break;
case VANILLA_ERR_SHUTDOWN:
+23
View File
@@ -25,6 +25,9 @@ vui_context_t *vui_alloc(int width, int height)
vui->font_height_handler = 0;
vui->text_open_handler = 0;
vui->power_state_handler = 0;
vui->mic_callback = 0;
vui->mic_enabled_handler = 0;
vui->audio_enabled_handler = 0;
vui->quit = 0;
vui_reset(vui);
return vui;
@@ -52,6 +55,7 @@ void vui_reset(vui_context_t *ctx)
ctx->selected_button = -1;
ctx->cancel_button = -1;
ctx->active_textedit = -1;
vui_audio_set_enabled(ctx, 0);
if (ctx->text_open_handler)
ctx->text_open_handler(ctx, -1, 0, ctx->text_open_handler_data);
}
@@ -517,6 +521,12 @@ void vui_audio_push(vui_context_t *ctx, const void *data, size_t size)
}
}
void vui_audio_set_enabled(vui_context_t *ctx, int enabled)
{
if (ctx->audio_enabled_handler)
ctx->audio_enabled_handler(ctx, enabled, ctx->audio_enabled_handler_data);
}
void vui_vibrate_set(vui_context_t *ctx, uint8_t val)
{
if (ctx->vibrate_handler) {
@@ -907,3 +917,16 @@ vui_power_state_t vui_power_state_get(vui_context_t *ctx, int *percent)
return VUI_POWERSTATE_UNKNOWN;
}
}
void vui_mic_enabled_set(vui_context_t *ctx, int enabled)
{
if (ctx->mic_enabled_handler) {
ctx->mic_enabled_handler(ctx, enabled, ctx->mic_enabled_handler_data);
}
}
void vui_mic_callback_set(vui_context_t *ctx, vui_mic_callback_t callback, void *userdata)
{
ctx->mic_callback = callback;
ctx->mic_callback_data = userdata;
}
+8
View File
@@ -45,6 +45,7 @@ typedef struct vui_context_t vui_context_t;
typedef void (*vui_callback_t)(vui_context_t *ctx, void *userdata);
typedef void (*vui_button_callback_t)(vui_context_t *ctx, int button, void *userdata);
typedef void (*vui_anim_step_callback_t)(vui_context_t *ctx, int64_t time, void *userdata);
typedef void (*vui_mic_callback_t)(void *userdata, const uint8_t *stream, size_t len);
typedef enum {
VUI_DIR_LEFT,
@@ -69,6 +70,7 @@ void vui_quit(vui_context_t *ctx);
* Audio-related functions
*/
void vui_audio_push(vui_context_t *ctx, const void *data, size_t size);
void vui_audio_set_enabled(vui_context_t *ctx, int enabled);
/**
* Screen-related functions
@@ -160,4 +162,10 @@ void vui_vibrate_set(vui_context_t *ctx, uint8_t val);
*/
vui_power_state_t vui_power_state_get(vui_context_t *ctx, int *percent);
/**
* Microphone-related functions
*/
void vui_mic_enabled_set(vui_context_t *ctx, int enabled);
void vui_mic_callback_set(vui_context_t *ctx, vui_mic_callback_t callback, void *userdata);
#endif // VANILLA_PI_UI_H
+7
View File
@@ -99,6 +99,7 @@ typedef void (*vui_vibrate_handler_t)(uint8_t vibrate, void *userdata);
typedef int (*vui_font_height_handler_t)(vui_font_size_t size, void *userdata);
typedef void (*vui_text_open_handler_t)(vui_context_t *ctx, int textedit, int open, void *userdata);
typedef vui_power_state_t (*vui_power_state_handler_t)(int *percent);
typedef void (*vui_audio_enabled_handler_t)(vui_context_t *ctx, int enabled, void *userdata);
typedef struct vui_context_t {
void *platform_data;
@@ -138,6 +139,12 @@ typedef struct vui_context_t {
struct timeval active_textedit_time;
int quit;
vui_power_state_handler_t power_state_handler;
vui_mic_callback_t mic_callback;
void *mic_callback_data;
vui_audio_enabled_handler_t mic_enabled_handler;
void *mic_enabled_handler_data;
vui_audio_enabled_handler_t audio_enabled_handler;
void *audio_enabled_handler_data;
} vui_context_t;
#endif // VANILLA_PI_UI_PRIV_H
+124 -12
View File
@@ -15,6 +15,8 @@
#include "ui_priv.h"
#include "ui_util.h"
#define MIN(a,b) (((a)<(b))?(a):(b))
typedef struct {
SDL_Texture *texture;
int w;
@@ -38,12 +40,19 @@ typedef struct {
TTF_Font *sysfont_small;
TTF_Font *sysfont_tiny;
SDL_AudioDeviceID audio;
SDL_AudioDeviceID mic;
SDL_GameController *controller;
SDL_Texture *game_tex;
int last_shown_toast;
SDL_Texture *toast_tex;
struct timeval toast_expiry;
AVFrame *frame;
uint8_t *audio_buffer;
size_t audio_buffer_size;
size_t audio_buffer_start;
size_t audio_buffer_end;
pthread_mutex_t audio_buffer_mutex;
} vui_sdl_context_t;
static int button_map[SDL_CONTROLLER_BUTTON_MAX];
@@ -137,11 +146,30 @@ SDL_GameController *find_valid_controller()
void vui_sdl_audio_handler(const void *data, size_t size, void *userdata)
{
vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) userdata;
vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) userdata;
if (SDL_QueueAudio(sdl_ctx->audio, data, size) < 0) {
vpilog("Failed to queue audio (data: %p, size: %zu)\n", data, size);
}
if (!sdl_ctx->audio) {
// Buffers will not have been initialized, so return before we try using them
return;
}
pthread_mutex_lock(&sdl_ctx->audio_buffer_mutex);
for (size_t i = 0; i < size; ) {
size_t phys = sdl_ctx->audio_buffer_end % sdl_ctx->audio_buffer_size;
size_t max_write = MIN(sdl_ctx->audio_buffer_size - phys, size - i);
memcpy(sdl_ctx->audio_buffer + phys, ((const unsigned char *) data) + i, max_write);
i += max_write;
sdl_ctx->audio_buffer_end += max_write;
if (sdl_ctx->audio_buffer_end > sdl_ctx->audio_buffer_start + sdl_ctx->audio_buffer_size) {
vpilog("SKIPPED %zu AUDIO BYTES\n", (sdl_ctx->audio_buffer_end - sdl_ctx->audio_buffer_size) - sdl_ctx->audio_buffer_start);
sdl_ctx->audio_buffer_start = sdl_ctx->audio_buffer_end - sdl_ctx->audio_buffer_size;
}
}
pthread_mutex_unlock(&sdl_ctx->audio_buffer_mutex);
}
void vui_sdl_vibrate_handler(uint8_t vibrate, void *userdata)
@@ -198,6 +226,49 @@ vui_power_state_t vui_sdl_power_state_handler(int *percent)
return (vui_power_state_t) SDL_GetPowerInfo(NULL, percent);
}
void vui_sdl_audio_set_enabled(vui_context_t *ctx, int enabled, void *userdata)
{
vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) userdata;
SDL_PauseAudioDevice(sdl_ctx->audio, !enabled);
}
void vui_sdl_mic_set_enabled(vui_context_t *ctx, int enabled, void *userdata)
{
vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) userdata;
SDL_PauseAudioDevice(sdl_ctx->mic, !enabled);
}
void audio_callback(void *userdata, Uint8 *stream, int len)
{
vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) userdata;
pthread_mutex_lock(&sdl_ctx->audio_buffer_mutex);
if ((sdl_ctx->audio_buffer_end - sdl_ctx->audio_buffer_start) >= len) {
for (size_t i = 0; i < len; ) {
size_t phys = sdl_ctx->audio_buffer_start % sdl_ctx->audio_buffer_size;
size_t max_write = MIN(sdl_ctx->audio_buffer_size - phys, len - i);
memcpy(stream + i, sdl_ctx->audio_buffer + phys, max_write);
i += max_write;
sdl_ctx->audio_buffer_start += max_write;
}
} else {
// Return silence
memset(stream, 0, len);
}
pthread_mutex_unlock(&sdl_ctx->audio_buffer_mutex);
}
void mic_callback(void *userdata, Uint8 *stream, int len)
{
vui_context_t *ctx = (vui_context_t *) userdata;
if (ctx->mic_callback)
ctx->mic_callback(ctx->mic_callback_data, stream, len);
}
int vui_init_sdl(vui_context_t *ctx, int fullscreen)
{
// Initialize SDL
@@ -233,19 +304,49 @@ int vui_init_sdl(vui_context_t *ctx, int fullscreen)
return -1;
}
SDL_AudioSpec desired = {0};
desired.freq = 48000;
desired.format = AUDIO_S16LSB;
desired.channels = 2;
// Open audio output device
SDL_AudioSpec desired = {0}, obtained;
desired.freq = 48000;
desired.format = AUDIO_S16LSB;
desired.channels = 2;
desired.callback = audio_callback;
desired.userdata = sdl_ctx;
sdl_ctx->audio = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, 0);
if (sdl_ctx->audio) {
SDL_PauseAudioDevice(sdl_ctx->audio, 0);
sdl_ctx->audio = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
if (sdl_ctx->audio) {
// Set up buffers
size_t buffer_size = obtained.samples * 4; // * 2 for 16-bit, * 2 for stereo
buffer_size += 2048; // Pad buffer for maximum size of Wii U packet
sdl_ctx->audio_buffer_size = buffer_size;
sdl_ctx->audio_buffer = malloc(buffer_size);
sdl_ctx->audio_buffer_start = 0;
sdl_ctx->audio_buffer_end = 0;
pthread_mutex_init(&sdl_ctx->audio_buffer_mutex, 0);
// Set up handler for audio submitted from VPI to VUI
ctx->audio_handler = vui_sdl_audio_handler;
ctx->audio_handler_data = sdl_ctx;
// Set up handler for enabling audio
ctx->audio_enabled_handler = vui_sdl_audio_set_enabled;
ctx->audio_enabled_handler_data = sdl_ctx;
} else {
vpilog("Failed to open audio device\n");
}
// Open audio input device
SDL_AudioSpec mic_desired = {0};
mic_desired.freq = 16000;
mic_desired.format = AUDIO_S16LSB;
mic_desired.callback = mic_callback;
mic_desired.userdata = ctx;
mic_desired.channels = 1;
sdl_ctx->mic = SDL_OpenAudioDevice(NULL, 1, &mic_desired, NULL, 0);
if (sdl_ctx->mic) {
ctx->mic_enabled_handler = vui_sdl_mic_set_enabled;
ctx->mic_enabled_handler_data = sdl_ctx;
} else {
vpilog("Failed to open audio device\n");
vpilog("Failed to open microphone device\n");
}
ctx->vibrate_handler = vui_sdl_vibrate_handler;
@@ -324,6 +425,17 @@ void vui_close_sdl(vui_context_t *ctx)
SDL_GameControllerClose(sdl_ctx->controller);
}
if (sdl_ctx->audio) {
SDL_CloseAudioDevice(sdl_ctx->audio);
free(sdl_ctx->audio_buffer);
pthread_mutex_destroy(&sdl_ctx->audio_buffer_mutex);
}
if (sdl_ctx->mic) {
SDL_CloseAudioDevice(sdl_ctx->mic);
}
SDL_DestroyTexture(sdl_ctx->toast_tex);
SDL_DestroyTexture(sdl_ctx->game_tex);
SDL_DestroyTexture(sdl_ctx->background);