shithub: m8c

Download patch

ref: 03b9b306d48df8ecf170f8597f8bd0a79a90d341
parent: afef4abe4d35daf2ea4f14b9a5cd76e3450018cc
author: Maido Käära <maido@producement.com>
date: Thu Mar 9 13:22:12 EST 2023

Add support for audio over USB

- Add possibility to choose an audio output device
- Use async libusb API so that it works better with audio

--- a/.gitignore
+++ b/.gitignore
@@ -11,5 +11,5 @@
 compile_commands.json
 .clangd
 result*
-cmake-build-debug
+cmake-build-*
 .idea
--- a/audio.c
+++ b/audio.c
@@ -1,6 +1,6 @@
 // Copyright 2021 Jonne Kokkonen
 // Released under the MIT licence, https://opensource.org/licenses/MIT
-
+#ifndef USE_LIBUSB
 #include "audio.h"
 #include <SDL.h>
 #include <stdint.h>
@@ -12,7 +12,7 @@
   SDL_QueueAudio(devid_out, stream, len);
 }
 
-int audio_init(int audio_buffer_size) {
+int audio_init(int audio_buffer_size, const char* output_device_name) {
 
   int i = 0;
   int m8_device_id = -1;
@@ -46,8 +46,7 @@
     want_out.format = AUDIO_S16;
     want_out.channels = 2;
     want_out.samples = audio_buffer_size;
-    // Opening device NULL specifies that we want to use a reasonable system default device
-    devid_out = SDL_OpenAudioDevice(NULL, 0, &want_out, &have_out,
+    devid_out = SDL_OpenAudioDevice(output_device_name, 0, &want_out, &have_out,
                                     SDL_AUDIO_ALLOW_ANY_CHANGE);
     if (devid_out == 0) {
       SDL_Log("Failed to open output: %s", SDL_GetError());
@@ -84,3 +83,4 @@
   SDL_CloseAudioDevice(devid_in);
   SDL_CloseAudioDevice(devid_out);
 }
+#endif
--- a/audio.h
+++ b/audio.h
@@ -1,13 +1,9 @@
 // Copyright 2021 Jonne Kokkonen
 // Released under the MIT licence, https://opensource.org/licenses/MIT
-
 #ifndef AUDIO_H
 #define AUDIO_H
 
-#include <stdint.h>
-
-void audio_cb_in(void *userdata, uint8_t *stream, int len);
-int audio_init(int audio_buffer_size);
+int audio_init(int audio_buffer_size, const char *output_device_name);
 void audio_destroy();
 
 #endif
--- a/config.c
+++ b/config.c
@@ -29,9 +29,10 @@
   c.idle_ms = 10;        // default to high performance
   c.wait_for_device = 1; // default to exit if device disconnected
   c.wait_packets = 1024; // default zero-byte attempts to disconnect (about 2
-                         // sec for default idle_ms)
+  // sec for default idle_ms)
   c.audio_enabled = 0;   // route M8 audio to default output
   c.audio_buffer_size = 1024; // requested audio buffer size in samples
+  c.audio_device_name = NULL; // Use this device, leave NULL to use the default output device
 
   c.key_up = SDL_SCANCODE_UP;
   c.key_left = SDL_SCANCODE_LEFT;
@@ -82,7 +83,7 @@
 
   SDL_Log("Writing config file to %s", config_path);
 
-  const unsigned int INI_LINE_COUNT = 43;
+  const unsigned int INI_LINE_COUNT = 44;
   const unsigned int LINELEN = 50;
 
   // Entries for the config file
@@ -103,6 +104,8 @@
            conf->audio_enabled ? "true" : "false");
   snprintf(ini_values[initPointer++], LINELEN, "audio_buffer_size=%d\n",
            conf->audio_buffer_size);
+  snprintf(ini_values[initPointer++], LINELEN, "audio_device_name=%s\n",
+           conf->audio_device_name ? conf->audio_device_name : "Default");
   snprintf(ini_values[initPointer++], LINELEN, "[keyboard]\n");
   snprintf(ini_values[initPointer++], LINELEN, "key_up=%d\n", conf->key_up);
   snprintf(ini_values[initPointer++], LINELEN, "key_left=%d\n", conf->key_left);
@@ -216,7 +219,9 @@
 void read_audio_config(ini_t *ini, config_params_s *conf) {
   const char *param_audio_enabled = ini_get(ini, "audio", "audio_enabled");
   const char *param_audio_buffer_size =
-      ini_get(ini, "audio", "audio_buffer_size");
+          ini_get(ini, "audio", "audio_buffer_size");
+  const char *param_audio_device_name =
+          ini_get(ini, "audio", "audio_device_name");
 
   if (param_audio_enabled != NULL) {
     if (strcmpci(param_audio_enabled, "true") == 0) {
@@ -226,6 +231,10 @@
     }
   }
 
+  if (param_audio_device_name != NULL && SDL_strcmp(param_audio_device_name, "Default") != 0) {
+    conf->audio_device_name = SDL_strdup(param_audio_device_name);
+  }
+
   if (param_audio_buffer_size != NULL) {
     conf->audio_buffer_size = SDL_atoi(param_audio_buffer_size);
   }
@@ -326,21 +335,21 @@
   const char *gamepad_quit = ini_get(ini, "gamepad", "gamepad_quit");
   const char *gamepad_reset = ini_get(ini, "gamepad", "gamepad_reset");
   const char *gamepad_analog_threshold =
-      ini_get(ini, "gamepad", "gamepad_analog_threshold");
+          ini_get(ini, "gamepad", "gamepad_analog_threshold");
   const char *gamepad_analog_invert =
-      ini_get(ini, "gamepad", "gamepad_analog_invert");
+          ini_get(ini, "gamepad", "gamepad_analog_invert");
   const char *gamepad_analog_axis_updown =
-      ini_get(ini, "gamepad", "gamepad_analog_axis_updown");
+          ini_get(ini, "gamepad", "gamepad_analog_axis_updown");
   const char *gamepad_analog_axis_leftright =
-      ini_get(ini, "gamepad", "gamepad_analog_axis_leftright");
+          ini_get(ini, "gamepad", "gamepad_analog_axis_leftright");
   const char *gamepad_analog_axis_select =
-      ini_get(ini, "gamepad", "gamepad_analog_axis_select");
+          ini_get(ini, "gamepad", "gamepad_analog_axis_select");
   const char *gamepad_analog_axis_start =
-      ini_get(ini, "gamepad", "gamepad_analog_axis_start");
+          ini_get(ini, "gamepad", "gamepad_analog_axis_start");
   const char *gamepad_analog_axis_opt =
-      ini_get(ini, "gamepad", "gamepad_analog_axis_opt");
+          ini_get(ini, "gamepad", "gamepad_analog_axis_opt");
   const char *gamepad_analog_axis_edit =
-      ini_get(ini, "gamepad", "gamepad_analog_axis_edit");
+          ini_get(ini, "gamepad", "gamepad_analog_axis_edit");
 
   if (gamepad_up)
     conf->gamepad_up = SDL_atoi(gamepad_up);
@@ -374,7 +383,7 @@
     conf->gamepad_analog_axis_updown = SDL_atoi(gamepad_analog_axis_updown);
   if (gamepad_analog_axis_leftright)
     conf->gamepad_analog_axis_leftright =
-        SDL_atoi(gamepad_analog_axis_leftright);
+            SDL_atoi(gamepad_analog_axis_leftright);
   if (gamepad_analog_axis_select)
     conf->gamepad_analog_axis_select = SDL_atoi(gamepad_analog_axis_select);
   if (gamepad_analog_axis_start)
--- a/config.h
+++ b/config.h
@@ -15,6 +15,7 @@
   int wait_packets;
   int audio_enabled;
   int audio_buffer_size;
+  const char *audio_device_name;
 
   int key_up;
   int key_left;
--- a/main.c
+++ b/main.c
@@ -91,7 +91,7 @@
     if (port_inited == 1 && enable_and_reset_display() == 1) {
       // if audio routing is enabled, try to initialize audio devices
       if (conf.audio_enabled == 1) {
-        audio_init(conf.audio_buffer_size);
+        audio_init(conf.audio_buffer_size, conf.audio_device_name);
       }
       run = RUN;
     } else {
@@ -133,7 +133,7 @@
           if (run == WAIT_FOR_DEVICE && init_serial(0) == 1) {
 
             if (conf.audio_enabled == 1) {
-              if (audio_init(conf.audio_buffer_size) == 0) {
+              if (audio_init(conf.audio_buffer_size, conf.audio_device_name) == 0) {
                 SDL_Log("Cannot initialize audio, exiting.");
                 run = QUIT;
               }
--- /dev/null
+++ b/ringbuffer.c
@@ -1,0 +1,62 @@
+#include "ringbuffer.h"
+#include <stdlib.h>
+#include <SDL.h>
+
+RingBuffer *ring_buffer_create(uint32_t size) {
+  RingBuffer *rb = SDL_malloc(sizeof(*rb));
+  rb->buffer = SDL_malloc(sizeof(*(rb->buffer)) * size);
+  rb->head = 0;
+  rb->tail = 0;
+  rb->max_size = size;
+  rb->size = 0;
+  return rb;
+}
+
+void ring_buffer_free(RingBuffer *rb) {
+  free(rb->buffer);
+  free(rb);
+}
+
+uint32_t ring_buffer_empty(RingBuffer *rb) {
+  return (rb->size == 0);
+}
+
+uint32_t ring_buffer_full(RingBuffer *rb) {
+  return (rb->size == rb->max_size);
+}
+
+uint32_t ring_buffer_push(RingBuffer *rb, const uint8_t *data, uint32_t length) {
+  if (ring_buffer_full(rb)) {
+    return -1; // buffer full, push fails
+  } else {
+    uint32_t space1 = rb->max_size - rb->tail;
+    uint32_t n = (length <= rb->max_size - rb->size) ? length : (rb->max_size - rb->size);
+    if (n <= space1) {
+      SDL_memcpy(rb->buffer + rb->tail, data, n);
+    } else {
+      SDL_memcpy(rb->buffer + rb->tail, data, space1);
+      SDL_memcpy(rb->buffer, data + space1, n - space1);
+    }
+    rb->tail = (rb->tail + n) % rb->max_size;
+    rb->size += n;
+    return n; // push successful, returns number of bytes pushed
+  }
+}
+
+uint32_t ring_buffer_pop(RingBuffer *rb, uint8_t *data, uint32_t length) {
+  if (ring_buffer_empty(rb)) {
+    return -1; // buffer empty, pop fails
+  } else {
+    uint32_t space1 = rb->max_size - rb->head;
+    uint32_t n = (length <= rb->size) ? length : rb->size;
+    if (n <= space1) {
+      SDL_memcpy(data, rb->buffer + rb->head, n);
+    } else {
+      SDL_memcpy(data, rb->buffer + rb->head, space1);
+      SDL_memcpy(data + space1, rb->buffer, n - space1);
+    }
+    rb->head = (rb->head + n) % rb->max_size;
+    rb->size -= n;
+    return n; // pop successful, returns number of bytes popped
+  }
+}
--- /dev/null
+++ b/ringbuffer.h
@@ -1,0 +1,24 @@
+#ifndef M8C_RINGBUFFER_H
+#define M8C_RINGBUFFER_H
+
+#include <stdlib.h>
+
+typedef struct {
+    uint8_t *buffer;
+    uint32_t head;
+    uint32_t tail;
+    uint32_t max_size;
+    uint32_t size;
+} RingBuffer;
+
+RingBuffer *ring_buffer_create(uint32_t size);
+
+uint32_t ring_buffer_empty(RingBuffer *rb);
+
+uint32_t ring_buffer_pop(RingBuffer *rb, uint8_t *data, uint32_t length);
+
+uint32_t ring_buffer_push(RingBuffer *rb, const uint8_t *data, uint32_t length);
+
+void ring_buffer_free(RingBuffer *rb);
+
+#endif //M8C_RINGBUFFER_H
--- a/serial.h
+++ b/serial.h
@@ -5,12 +5,8 @@
 #define _SERIAL_H_
 
 #ifdef USE_LIBUSB
-// Not sure about this value but it seems to work
+// Max packet length of the USB endpoint
 #define serial_read_size 512
-#include <libusb.h>
-typedef int (*usb_callback_t)(libusb_device_handle *devh);
-void set_usb_init_callback(usb_callback_t callback);
-void set_usb_destroy_callback(usb_callback_t callback);
 int init_serial_with_file_descriptor(int file_descriptor);
 #else
 // maximum amount of bytes to read from the serial in one read()
--- a/usb.c
+++ b/usb.c
@@ -6,15 +6,12 @@
 #ifdef USE_LIBUSB
 
 #include <SDL.h>
-#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
 #include <libusb.h>
 
-#include "serial.h"
+#include "usb.h"
 
-
 static int ep_out_addr = 0x03;
 static int ep_in_addr = 0x83;
 
@@ -21,47 +18,74 @@
 #define ACM_CTRL_DTR   0x01
 #define ACM_CTRL_RTS   0x02
 
-usb_callback_t init_callback = NULL;
-usb_callback_t destroy_callback = NULL;
+libusb_context *ctx = NULL;
 libusb_device_handle *devh = NULL;
 
-void set_usb_init_callback(usb_callback_t callback) {
-  init_callback = callback;
+static int do_exit = 0;
+
+int usb_loop(void *data) {
+  SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL);
+  while (!do_exit) {
+    int rc = libusb_handle_events(ctx);
+    if (rc != LIBUSB_SUCCESS) {
+      SDL_Log("Audio loop error: %s\n", libusb_error_name(rc));
+      break;
+    }
+  }
+  return 0;
 }
 
-void set_usb_destroy_callback(usb_callback_t callback) {
-  destroy_callback = callback;
+static SDL_Thread *usb_thread;
+
+static void LIBUSB_CALL xfr_cb_in(struct libusb_transfer *transfer) {
+  int *completed = transfer->user_data;
+  *completed = 1;
 }
 
-int blocking_write(void *buf,
- int count, unsigned int timeout_ms) {
-  int actual_length;
-  int rc;
-  rc = libusb_bulk_transfer(devh, ep_out_addr, buf, count,
-   &actual_length, timeout_ms);
-  if (rc < 0) {
-    SDL_Log("Error while sending char: %s", libusb_error_name(rc));
-    return -1;
+int bulk_transfer(int endpoint, uint8_t *serial_buf, int count, unsigned int timeout_ms) {
+  int completed = 0;
+
+  struct libusb_transfer *transfer;
+  transfer = libusb_alloc_transfer(0);
+  libusb_fill_bulk_transfer(transfer, devh, endpoint, serial_buf, count,
+                            xfr_cb_in, &completed, timeout_ms);
+  int r = libusb_submit_transfer(transfer);
+
+  if (r < 0) {
+    SDL_Log("Error");
+    libusb_free_transfer(transfer);
+    return r;
   }
-  return actual_length;
-}
 
-int serial_read(uint8_t *serial_buf, int count) {
-  int actual_length;
-  int rc = libusb_bulk_transfer(devh, ep_in_addr, serial_buf, count, &actual_length,
-    10);
-  if (rc == LIBUSB_ERROR_TIMEOUT) {
-    return 0;
-  } else if (rc < 0) {
-    SDL_Log("Error while waiting for char: %s", libusb_error_name(rc));
-    return -1;
+  retry:
+  libusb_lock_event_waiters(ctx);
+  while (!completed) {
+    if (!libusb_event_handler_active(ctx)) {
+      libusb_unlock_event_waiters(ctx);
+      goto retry;
+    }
+    libusb_wait_for_event(ctx, NULL);
   }
+  libusb_unlock_event_waiters(ctx);
 
+  int actual_length = transfer->actual_length;
+
+  libusb_free_transfer(transfer);
+
   return actual_length;
 }
 
+int blocking_write(void *buf,
+                   int count, unsigned int timeout_ms) {
+  return bulk_transfer(ep_out_addr, buf, count, timeout_ms);
+}
+
+int serial_read(uint8_t *serial_buf, int count) {
+  return bulk_transfer(ep_in_addr, serial_buf, count, 1);
+}
+
 int check_serial_port() {
-    // Reading will fail anyway when the device is not present anymore
+  // Reading will fail anyway when the device is not present anymore
   return 1;
 }
 
@@ -86,29 +110,31 @@
     }
   }
 
-    /* Start configuring the device:
-     * - set line state
-     */
+  /* Start configuring the device:
+   * - set line state
+   */
   SDL_Log("Setting line state");
   rc = libusb_control_transfer(devh, 0x21, 0x22, ACM_CTRL_DTR | ACM_CTRL_RTS,
-   0, NULL, 0, 0);
+                               0, NULL, 0, 0);
   if (rc < 0) {
     SDL_Log("Error during control transfer: %s", libusb_error_name(rc));
     return 0;
   }
 
-    /* - set line encoding: here 115200 8N1
-     * 115200 = 0x01C200 ~> 0x00, 0xC2, 0x01, 0x00 in little endian
-     */
+  /* - set line encoding: here 115200 8N1
+   * 115200 = 0x01C200 ~> 0x00, 0xC2, 0x01, 0x00 in little endian
+   */
   SDL_Log("Set line encoding");
   unsigned char encoding[] = {0x00, 0xC2, 0x01, 0x00, 0x00, 0x00, 0x08};
   rc = libusb_control_transfer(devh, 0x21, 0x20, 0, 0, encoding,
-   sizeof(encoding), 0);
+                               sizeof(encoding), 0);
   if (rc < 0) {
     SDL_Log("Error during control transfer: %s", libusb_error_name(rc));
     return 0;
   }
 
+  usb_thread = SDL_CreateThread(&usb_loop, "USB", NULL);
+
   return 1;
 }
 
@@ -126,12 +152,12 @@
     SDL_Log("libusb_set_option failed: %s", libusb_error_name(r));
     return 0;
   }
-  r = libusb_init(NULL);
+  r = libusb_init(&ctx);
   if (r < 0) {
     SDL_Log("libusb_init failed: %s", libusb_error_name(r));
     return 0;
   }
-  r = libusb_wrap_sys_device(NULL, (intptr_t) file_descriptor, &devh);
+  r = libusb_wrap_sys_device(ctx, (intptr_t) file_descriptor, &devh);
   if (r < 0) {
     SDL_Log("libusb_wrap_sys_device failed: %s", libusb_error_name(r));
     return 0;
@@ -141,15 +167,6 @@
   }
   SDL_Log("USB device init success");
 
-  if (init_callback != NULL) {
-    r = init_callback(devh);
-
-    if (r < 0) {
-      SDL_Log("Init callback failed: %d", r);
-      return 0;
-    }
-  }
-
   return init_interface();
 }
 
@@ -160,12 +177,12 @@
   }
 
   int r;
-  r = libusb_init(NULL);
+  r = libusb_init(&ctx);
   if (r < 0) {
     SDL_Log("libusb_init failed: %s", libusb_error_name(r));
     return 0;
   }
-  devh = libusb_open_device_with_vid_pid(NULL, 0x16c0, 0x048a);
+  devh = libusb_open_device_with_vid_pid(ctx, 0x16c0, 0x048a);
   if (devh == NULL) {
     SDL_Log("libusb_open_device_with_vid_pid returned invalid handle");
     return 0;
@@ -172,15 +189,6 @@
   }
   SDL_Log("USB device init success");
 
-  if (init_callback != NULL) {
-    r = init_callback(devh);
-
-    if (r < 0) {
-      SDL_Log("Init callback failed: %d", r);
-      return 0;
-    }
-  }
-
   return init_interface();
 }
 
@@ -194,7 +202,7 @@
   result = blocking_write(buf, 1, 5);
   if (result != 1) {
     SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error resetting M8 display, code %d",
-     result);
+                 result);
     return 0;
   }
   return 1;
@@ -223,21 +231,12 @@
   char buf[1] = {'D'};
   int result;
 
-  if (destroy_callback != NULL) {
-    result = destroy_callback(devh);
-
-    if (result < 0) {
-      SDL_Log("Destroy callback failed: %d", result);
-      return result;
-    }
-  }
-
   SDL_Log("Disconnecting M8\n");
 
   result = blocking_write(buf, 1, 5);
   if (result != 1) {
     SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending disconnect, code %d",
-     result);
+                 result);
     return -1;
   }
 
@@ -251,12 +250,16 @@
     }
   }
 
+  do_exit = 1;
+
   if (devh != NULL) {
     libusb_close(devh);
   }
 
-  libusb_exit(NULL);
+  SDL_WaitThread(usb_thread, NULL);
 
+  libusb_exit(ctx);
+
   return 1;
 }
 
@@ -267,7 +270,7 @@
   result = blocking_write(buf, nbytes, 5);
   if (result != nbytes) {
     SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending input, code %d",
-     result);
+                 result);
     return -1;
   }
   return 1;
@@ -282,7 +285,7 @@
   result = blocking_write(buf, nbytes, 5);
   if (result != nbytes) {
     SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending keyjazz, code %d",
-     result);
+                 result);
     return -1;
   }
 
--- /dev/null
+++ b/usb.h
@@ -1,0 +1,9 @@
+#ifdef USE_LIBUSB
+#ifndef M8C_USB_H
+#define M8C_USB_H
+
+#include <libusb.h>
+extern libusb_device_handle *devh;
+
+#endif //M8C_USB_H
+#endif
--- /dev/null
+++ b/usb_audio.c
@@ -1,0 +1,194 @@
+#ifdef USE_LIBUSB
+
+#include <libusb.h>
+#include <errno.h>
+#include <SDL.h>
+#include "ringbuffer.h"
+#include "usb.h"
+
+#define EP_ISO_IN 0x85
+#define IFACE_NUM 4
+
+#define NUM_TRANSFERS 10
+#define PACKET_SIZE 180
+#define NUM_PACKETS 64
+
+SDL_AudioDeviceID sdl_audio_device_id = 0;
+RingBuffer *audio_buffer = NULL;
+
+static void audio_callback(void *userdata, Uint8 *stream,
+                           int len) {
+  uint32_t read_len = ring_buffer_pop(audio_buffer, stream, len);
+
+  if (read_len == -1) {
+    SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,"Buffer underflow!");
+  }
+
+  // If we didn't read the full len bytes, fill the rest with zeros
+  if (read_len < len) {
+    SDL_memset(&stream[read_len], 0, len - read_len);
+  }
+}
+
+static void cb_xfr(struct libusb_transfer *xfr) {
+  unsigned int i;
+
+  for (i = 0; i < xfr->num_iso_packets; i++) {
+    struct libusb_iso_packet_descriptor *pack = &xfr->iso_packet_desc[i];
+
+    if (pack->status != LIBUSB_TRANSFER_COMPLETED) {
+      SDL_Log("XFR callback error (status %d: %s)", pack->status,
+              libusb_error_name(pack->status));
+      /* This doesn't happen, so bail out if it does. */
+      return;
+    }
+
+    const uint8_t *data = libusb_get_iso_packet_buffer_simple(xfr, i);
+    if (sdl_audio_device_id != 0) {
+      uint32_t actual = ring_buffer_push(audio_buffer, data, pack->actual_length);
+      if (actual == -1) {
+        SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Buffer overflow!");
+      }
+    }
+  }
+
+  if (libusb_submit_transfer(xfr) < 0) {
+    SDL_Log("error re-submitting URB\n");
+  }
+}
+
+static struct libusb_transfer *xfr[NUM_TRANSFERS];
+
+static int benchmark_in(uint8_t ep) {
+  static uint8_t buf[PACKET_SIZE * NUM_PACKETS];
+  int num_iso_pack = NUM_PACKETS;
+  int i;
+
+  for (i = 0; i < NUM_TRANSFERS; i++) {
+    xfr[i] = libusb_alloc_transfer(num_iso_pack);
+    if (!xfr[i]) {
+      SDL_Log("Could not allocate transfer");
+      return -ENOMEM;
+    }
+
+    libusb_fill_iso_transfer(xfr[i], devh, ep, buf,
+                             sizeof(buf), num_iso_pack, cb_xfr, NULL, 1000);
+    libusb_set_iso_packet_lengths(xfr[i], sizeof(buf) / num_iso_pack);
+
+    libusb_submit_transfer(xfr[i]);
+  }
+
+  return 1;
+}
+
+int audio_init(int audio_buffer_size, const char *output_device_name) {
+  SDL_Log("USB audio setup");
+
+  int rc;
+
+  rc = libusb_kernel_driver_active(devh, IFACE_NUM);
+  if (rc == 1) {
+    SDL_Log("Detaching kernel driver");
+    rc = libusb_detach_kernel_driver(devh, IFACE_NUM);
+    if (rc < 0) {
+      SDL_Log("Could not detach kernel driver: %s\n",
+              libusb_error_name(rc));
+      return rc;
+    }
+  }
+
+  rc = libusb_claim_interface(devh, IFACE_NUM);
+  if (rc < 0) {
+    SDL_Log("Error claiming interface: %s\n", libusb_error_name(rc));
+    return rc;
+  }
+
+  rc = libusb_set_interface_alt_setting(devh, IFACE_NUM, 1);
+  if (rc < 0) {
+    SDL_Log("Error setting alt setting: %s\n", libusb_error_name(rc));
+    return rc;
+  }
+
+  if (!SDL_WasInit(SDL_INIT_AUDIO)) {
+    if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
+      SDL_Log("Init audio failed %s", SDL_GetError());
+      return -1;
+    }
+  } else {
+    SDL_Log("Audio was already initialised");
+  }
+
+  static SDL_AudioSpec audio_spec;
+  audio_spec.format = AUDIO_S16;
+  audio_spec.channels = 2;
+  audio_spec.freq = 44100;
+  audio_spec.samples = audio_buffer_size;
+  audio_spec.callback = audio_callback;
+
+  SDL_AudioSpec _obtained;
+  SDL_zero(_obtained);
+
+  SDL_Log("Current audio driver is %s and device %s", SDL_GetCurrentAudioDriver(),
+          output_device_name);
+
+  if (SDL_strcasecmp(SDL_GetCurrentAudioDriver(), "openslES") == 0 || output_device_name == NULL) {
+    SDL_Log("Using default audio device");
+    sdl_audio_device_id = SDL_OpenAudioDevice(NULL, 0, &audio_spec, &_obtained, 0);
+  } else {
+    sdl_audio_device_id = SDL_OpenAudioDevice(output_device_name, 0, &audio_spec, &_obtained, 0);
+  }
+
+  audio_buffer = ring_buffer_create(4 * _obtained.size);
+
+  SDL_Log("Obtained audio spec. Sample rate: %d, channels: %d, samples: %d, size: %d",
+          _obtained.freq,
+          _obtained.channels,
+          _obtained.samples, +_obtained.size);
+
+  SDL_PauseAudioDevice(sdl_audio_device_id, 0);
+
+  // Good to go
+  SDL_Log("Starting capture");
+  if ((rc = benchmark_in(EP_ISO_IN)) < 0) {
+    SDL_Log("Capture failed to start: %d", rc);
+    return rc;
+  }
+
+  SDL_Log("Successful init");
+  return 1;
+}
+
+int audio_destroy() {
+  SDL_Log("Closing audio");
+
+  int i, rc;
+
+  for (i = 0; i < NUM_TRANSFERS; i++) {
+    rc = libusb_cancel_transfer(xfr[i]);
+    if (rc < 0) {
+      SDL_Log("Error cancelling transfer: %s\n", libusb_error_name(rc));
+    }
+  }
+
+  SDL_Log("Freeing interface %d", IFACE_NUM);
+
+  rc = libusb_release_interface(devh, IFACE_NUM);
+  if (rc < 0) {
+    SDL_Log("Error releasing interface: %s\n", libusb_error_name(rc));
+    return rc;
+  }
+
+  if (sdl_audio_device_id != 0) {
+    SDL_Log("Closing audio device %d", sdl_audio_device_id);
+    SDL_AudioDeviceID device = sdl_audio_device_id;
+    sdl_audio_device_id = 0;
+    SDL_CloseAudioDevice(device);
+  }
+
+  SDL_Log("Audio closed");
+
+  ring_buffer_free(audio_buffer);
+  return 1;
+}
+
+#endif
--