/*
Calf Box, an open source musical instrument.
Copyright (C) 2010-2013 Krzysztof Foltman
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "config.h"
#if USE_LIBUSB
#include
#include
#include "usbio_impl.h"
#include
#include
#include
#define NUM_MULTIMIX_SYNC_PACKETS 10
#define MULTIMIX_EP_PLAYBACK 0x02
//#define MULTIMIX_EP_CAPTURE 0x86
#define MULTIMIX_EP_SYNC 0x81
#define NUM_CPUTIME_ENTRIES 100
static int register_cpu_time = 1;
static float real_time_registry[NUM_CPUTIME_ENTRIES];
static float cpu_time_registry[NUM_CPUTIME_ENTRIES];
static int cpu_time_write_ptr = 0;
static void usbio_play_buffer_adaptive(struct cbox_usb_io_impl *uii);
///////////////////////////////////////////////////////////////////////////////
static gboolean set_endpoint_sample_rate(struct libusb_device_handle *h, int sample_rate, int ep)
{
uint8_t freq_data[3];
freq_data[0] = sample_rate & 0xFF;
freq_data[1] = (sample_rate & 0xFF00) >> 8;
freq_data[2] = (sample_rate & 0xFF0000) >> 16;
if (libusb_control_transfer(h, 0x22, 0x01, 256, ep, freq_data, 3, USB_DEVICE_SETUP_TIMEOUT) != 3)
return FALSE;
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
gboolean usbio_open_audio_interface(struct cbox_usb_io_impl *uii, struct cbox_usb_audio_info *uainf, struct libusb_device_handle *handle, GError **error)
{
if (uii->output_resolution != 2 && uii->output_resolution != 3)
{
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Only 16-bit or 24-bit output resolution is supported.");
return FALSE;
}
if (!configure_usb_interface(handle, uainf->udi->bus, uainf->udi->devadr, uainf->intf, uainf->alt_setting, "audio (class driver)", error))
return FALSE;
if (!set_endpoint_sample_rate(handle, uii->sample_rate, uainf->epdesc.bEndpointAddress))
{
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Cannot set sample rate on class-compliant USB audio device.");
return FALSE;
}
uii->is_hispeed = FALSE;
uii->sync_protocol = uainf->sync_protocol;
uii->play_function = uainf->sync_protocol == USBAUDIOSYNC_PROTOCOL_CLASS ? usbio_play_buffer_asynchronous : usbio_play_buffer_adaptive;
uii->handle_audiodev = handle;
uii->audio_output_endpoint = uainf->epdesc.bEndpointAddress;
uii->audio_output_pktsize = uainf->epdesc.wMaxPacketSize; // 48 * 2 * uii->output_resolution;
uii->audio_sync_endpoint = uainf->sync_protocol == USBAUDIOSYNC_PROTOCOL_CLASS ? uainf->sync_epdesc.bEndpointAddress : 0;
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
static gboolean claim_multimix_interfaces(struct cbox_usb_io_impl *uii, struct libusb_device_handle *handle, int bus, int devadr, GError **error)
{
for (int ifno = 0; ifno < 2; ifno++)
{
if (!configure_usb_interface(handle, bus, devadr, ifno, 1, "audio (MultiMix driver)", error))
return FALSE;
}
return TRUE;
}
gboolean usbio_open_audio_interface_multimix(struct cbox_usb_io_impl *uii, int bus, int devadr, struct libusb_device_handle *handle, GError **error)
{
if (uii->output_resolution != 3)
{
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Only 24-bit output resolution is supported.");
return FALSE;
}
if (!claim_multimix_interfaces(uii, handle, bus, devadr, error))
return FALSE;
if (!set_endpoint_sample_rate(handle, uii->sample_rate, MULTIMIX_EP_PLAYBACK))
{
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Cannot set sample rate on Alesis Multimix.");
return FALSE;
}
uii->is_hispeed = TRUE;
uii->play_function = usbio_play_buffer_asynchronous;
uii->handle_audiodev = handle;
uii->sync_protocol = USBAUDIOSYNC_PROTOCOL_MULTIMIX8;
uii->audio_output_endpoint = MULTIMIX_EP_PLAYBACK;
uii->audio_output_pktsize = 156;
uii->audio_sync_endpoint = MULTIMIX_EP_SYNC;
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
static void calc_output_buffer(struct cbox_usb_io_impl *uii)
{
struct timespec tvs1, tve1, tvs2, tve2;
if (register_cpu_time)
{
clock_gettime(CLOCK_MONOTONIC_RAW, &tvs1);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tvs2);
}
struct cbox_io *io = uii->ioi.pio;
uint32_t buffer_size = io->io_env.buffer_size;
for (int b = 0; b < uii->output_channels; b++)
memset(io->output_buffers[b], 0, buffer_size * sizeof(float));
for (GList *p = uii->rt_midi_ports; p; p = p->next)
{
struct cbox_usb_midi_interface *umi = p->data;
if (umi->input_port->hdr.enable_appsink && umi->input_port && umi->input_port->hdr.buffer.count)
cbox_midi_appsink_supply(&umi->input_port->hdr.appsink, &umi->input_port->hdr.buffer, io->free_running_frame_counter);
}
io->cb->process(io->cb->user_data, io, buffer_size);
for (GList *p = uii->rt_midi_ports; p; p = p->next)
{
struct cbox_usb_midi_interface *umi = p->data;
if (umi->input_port)
cbox_midi_buffer_clear(&umi->input_port->hdr.buffer);
}
for (GSList *p = io->midi_outputs; p; p = p->next)
{
struct cbox_usb_midi_output *umo = p->data;
usbio_fill_midi_output_buffer(umo);
}
if (register_cpu_time)
{
clock_gettime(CLOCK_MONOTONIC_RAW, &tve1);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tve2);
float time1 = tve1.tv_sec - tvs1.tv_sec + (tve1.tv_nsec - tvs1.tv_nsec) / 1000000000.0;
float time2 = tve2.tv_sec - tvs2.tv_sec + (tve2.tv_nsec - tvs2.tv_nsec) / 1000000000.0;
real_time_registry[cpu_time_write_ptr] = time1;
cpu_time_registry[cpu_time_write_ptr] = time2;
cpu_time_write_ptr = (cpu_time_write_ptr + 1) % NUM_CPUTIME_ENTRIES;
if (time1 > 0.0008 || time2 > 0.0008)
g_warning("CPU time = %f ms, real time = %f ms", time2 * 1000, time1 * 1000);
}
io->free_running_frame_counter += buffer_size;
}
static void fill_playback_buffer(struct cbox_usb_io_impl *uii, struct libusb_transfer *transfer)
{
struct cbox_io *io = uii->ioi.pio;
uint32_t buffer_size = io->io_env.buffer_size;
uint8_t *data8 = (uint8_t*)transfer->buffer;
int16_t *data = (int16_t*)transfer->buffer;
int resolution = uii->output_resolution;
unsigned int oc = uii->output_channels;
uint32_t rptr = uii->read_ptr;
uint32_t nframes = transfer->length / (resolution * oc);
uint32_t i, b, j;
for (i = 0; i < nframes; )
{
if (rptr == buffer_size)
{
calc_output_buffer(uii);
rptr = 0;
}
unsigned int left1 = nframes - i;
unsigned int left2 = buffer_size - rptr;
if (left1 > left2)
left1 = left2;
for (b = 0; b < oc; b++)
{
float *obuf = io->output_buffers[b] + rptr;
if (resolution == 2)
{
int16_t *tbuf = data + oc * i + b;
for (j = 0; j < left1; j++)
{
float v = 32767 * obuf[j];
if (v < -32768)
v = -32768;
if (v > +32767)
v = +32767;
*tbuf = (int16_t)v;
tbuf += oc;
}
}
if (resolution == 3)
{
uint8_t *tbuf = data8 + (oc * i + b) * 3;
for (j = 0; j < left1; j++)
{
float v = 0x7FFFFF * obuf[j];
if (v < -0x800000)
v = -0x800000;
if (v > +0x7FFFFF)
v = +0x7FFFFF;
int vi = (int)v;
tbuf[0] = vi & 255;
tbuf[1] = (vi >> 8) & 255;
tbuf[2] = (vi >> 16) & 255;
tbuf += oc * 3;
}
}
}
i += left1;
rptr += left1;
}
uii->read_ptr = rptr;
}
static void play_callback_adaptive(struct libusb_transfer *transfer)
{
struct usbio_transfer *xf = transfer->user_data;
struct cbox_usb_io_impl *uii = xf->user_data;
xf->pending = FALSE;
if (transfer->status == LIBUSB_TRANSFER_CANCELLED)
{
xf->cancel_confirm = 1;
return;
}
if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE)
{
uii->device_removed++;
return;
}
int resolution = uii->output_resolution;
int oc = uii->output_channels;
gboolean init_finished = uii->playback_counter == uii->playback_buffers;
if (uii->playback_counter < uii->playback_buffers)
{
// send another URB for the next transfer before re-submitting
// this one
usbio_play_buffer_adaptive(uii);
}
// printf("Play Callback! %d %p status %d\n", (int)transfer->length, transfer->buffer, (int)transfer->status);
int tlen = 0, olen = 0;
for (int i = 0; i < transfer->num_iso_packets; i++)
{
tlen += transfer->iso_packet_desc[i].actual_length;
olen += transfer->iso_packet_desc[i].length;
if (transfer->iso_packet_desc[i].status)
printf("ISO error: index = %d i = %d status = %d\n", (int)xf->index, i, transfer->iso_packet_desc[i].status);
}
uii->samples_played += olen / (oc * resolution);
uint32_t nsamps = uii->sample_rate / 1000;
// If time elapsed is greater than
int lag = uii->desync / (1000 * transfer->num_iso_packets);
if (lag > 0 && nsamps < uii->audio_output_pktsize)
{
nsamps++;
lag--;
}
transfer->length = nsamps * transfer->num_iso_packets * oc * resolution;
libusb_set_iso_packet_lengths(transfer, nsamps * oc * resolution);
if (init_finished)
{
fill_playback_buffer(uii, transfer);
}
// desync value is expressed in milli-frames, i.e. desync of 1000 means 1 frame of lag
// It takes 1ms for each iso packet to be transmitted. Each transfer consists of
// num_iso_packets packets. So, this transfer took uii->sample_rate milli-frames.
uii->desync += transfer->num_iso_packets * uii->sample_rate;
// ... but during that time, tlen/4 samples == tlen/4*1000 millisamples have been
// transmitted.
uii->desync -= transfer->num_iso_packets * nsamps * 1000;
if (uii->no_resubmit)
return;
int err = usbio_transfer_submit(xf);
if (err)
{
if (err == LIBUSB_ERROR_NO_DEVICE)
{
uii->device_removed++;
transfer->status = LIBUSB_TRANSFER_NO_DEVICE;
}
g_warning("Cannot resubmit isochronous transfer, error = %s", libusb_error_name(err));
}
}
void usbio_play_buffer_adaptive(struct cbox_usb_io_impl *uii)
{
struct usbio_transfer *t;
int err;
int packets = uii->iso_packets;
t = usbio_transfer_new(uii->usbctx, "play", uii->playback_counter, packets, uii);
int tsize = uii->sample_rate * 2 * uii->output_resolution / 1000;
uint8_t *buf = (uint8_t *)calloc(packets, uii->audio_output_pktsize);
libusb_fill_iso_transfer(t->transfer, uii->handle_audiodev, uii->audio_output_endpoint, buf, tsize * packets, packets, play_callback_adaptive, t, 20000);
libusb_set_iso_packet_lengths(t->transfer, tsize);
uii->playback_transfers[uii->playback_counter++] = t;
err = usbio_transfer_submit(t);
if (!err)
return;
g_warning("Cannot resubmit isochronous transfer, error = %s", libusb_error_name(err));
uii->playback_counter--;
free(buf);
usbio_transfer_destroy(t);
uii->playback_transfers[uii->playback_counter] = NULL;
}
//////////////////////////////////////////////////////////////////////////////////////////
static int calc_packet_lengths(struct cbox_usb_io_impl *uii, struct libusb_transfer *t, int packets)
{
int packets_per_sec = uii->is_hispeed ? 8000 : 1000;
int tsize = 0;
int i;
// printf("sync_freq = %d\n", sync_freq);
for (i = 0; i < packets; i++)
{
int nsamps = (uii->samples_per_sec - uii->desync) / packets_per_sec;
// assert(nsamps > 0);
if ((uii->samples_per_sec - uii->desync) % packets_per_sec)
nsamps++;
//printf("%d sfreq=%d desync=%d nsamps=%d\n", i, uii->sync_freq, uii->desync, nsamps);
uii->desync = (uii->desync + nsamps * packets_per_sec) % uii->samples_per_sec;
int v = (nsamps) * 2 * uii->output_resolution;
t->iso_packet_desc[i].length = v;
tsize += v;
}
return tsize;
}
void play_callback_asynchronous(struct libusb_transfer *transfer)
{
struct usbio_transfer *xf = transfer->user_data;
struct cbox_usb_io_impl *uii = xf->user_data;
xf->pending = FALSE;
if (transfer->status == LIBUSB_TRANSFER_CANCELLED)
{
xf->cancel_confirm = TRUE;
return;
}
if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE)
{
xf->cancel_confirm = TRUE;
uii->device_removed++;
return;
}
gboolean init_finished = uii->playback_counter == uii->playback_buffers;
if (uii->playback_counter < uii->playback_buffers)
{
// send another URB for the next transfer before re-submitting
// this one
usbio_play_buffer_asynchronous(uii);
}
/*
printf("Play Callback! %d status %d\n", (int)transfer->length, (int)transfer->status);
for (i = 0; i < transfer->num_iso_packets; i++) {
if (transfer->iso_packet_desc[i].actual_length)
{
printf("%d: %d %d\n", i, transfer->iso_packet_desc[i].actual_length, transfer->iso_packet_desc[i].status);
}
}
*/
transfer->length = calc_packet_lengths(uii, transfer, transfer->num_iso_packets);
if (init_finished)
{
fill_playback_buffer(uii, transfer);
}
if (uii->no_resubmit)
return;
int err = usbio_transfer_submit(xf);
if (err)
{
if (err == LIBUSB_ERROR_NO_DEVICE)
{
transfer->status = LIBUSB_TRANSFER_NO_DEVICE;
uii->device_removed++;
}
g_warning("Cannot submit isochronous transfer, error = %s", libusb_error_name(err));
}
}
static struct usbio_transfer *sync_stuff_asynchronous(struct cbox_usb_io_impl *uii, int index);
void usbio_play_buffer_asynchronous(struct cbox_usb_io_impl *uii)
{
struct usbio_transfer *t;
int err;
int packets = uii->iso_packets_multimix;
t = usbio_transfer_new(uii->usbctx, "play", uii->playback_counter, packets, uii);
int tsize = calc_packet_lengths(uii, t->transfer, packets);
int bufsize = uii->audio_output_pktsize * packets;
uint8_t *buf = (uint8_t *)calloc(1, bufsize);
if (!uii->playback_counter)
{
for(uii->sync_counter = 0; uii->sync_counter < uii->sync_buffers; uii->sync_counter++)
uii->sync_transfers[uii->sync_counter] = sync_stuff_asynchronous(uii, uii->sync_counter);
}
libusb_fill_iso_transfer(t->transfer, uii->handle_audiodev, uii->audio_output_endpoint, buf, tsize, packets, play_callback_asynchronous, t, 20000);
uii->playback_transfers[uii->playback_counter++] = t;
err = usbio_transfer_submit(t);
if (err)
{
g_warning("Cannot submit playback urb: %s, error = %s (index = %d, tsize = %d)", libusb_error_name(err), strerror(errno), uii->playback_counter, tsize);
free(buf);
usbio_transfer_destroy(t);
uii->playback_transfers[--uii->playback_counter] = NULL;
}
}
//////////////////////////////////////////////////////////////////////////////////////////
/*
* The Multimix device controls the data rate of the playback stream using a
* device-to-host isochronous endpoint. The incoming packets consist of 3 bytes:
* a current value of sample rate (kHz) + 2 historical values. I'm only using
* the first byte, I haven't yet encountered a situation where using the
* second and third byte would be necessary. This seems to work for all
* sample rates supported by the Windows driver - 44100, 48000, 88200 and
* 96000. It is possible to set sample rate to 64000, but it doesn't work
* correctly, and isn't supported by the Windows driver either - it may
* require special handling or may be a half-implemented feature in hardware.
* The isochronous transfer using 10 packets seems to give acceptable resolution
* and latency to avoid over/underruns with supported sample rates.
*
* The non-integer multiples of 1 kHz (like 44.1) are passed as a sequence of
* values that average to a desired value (9 values of 44 and one value of 45).
*
* In order to compensate for clock rate difference
* between host clock and DAC clock, the sample rate values sent by the device
* are either larger (to increase data rate from the host) or smaller than
* the nominal frequency value - the driver uses that to adjust the sample frame
* counts of individual packets in an isochronous transfer.
*
* A similar mechanism is used with USB audio class asynchronous sinks: the
* device sends a 10.14 representation of number of audio samples (frames)
* per USB frame, and this is used to control packet sizes.
*/
static void use_audioclass_sync_feedback(const uint8_t *data, uint32_t actual_length, int *prate)
{
// 10.14 representation of 'samples' per ms value, see 5.12.4.2 of the USB spec
uint32_t value = data[0] + 256 * data[1] + 65536 * data[2];
// printf("Ff estimate = %f Hz\n", value * 1000 / 16384.0);
// only 1 packet
*prate = (1000 * value + 8192) >> 14;
}
static inline void use_multimix_sync_feedback(const uint8_t *data, uint32_t actual_length, int *prate, const uint8_t *prev_data)
{
// Those assertions never failed, so my understanding of the protocol was likely
// correct. They should probably be removed just to not crash the program when
// used with flaky devices.
assert(actual_length == 3);
// this is averaged across all packets in the transfer
*prate += 1000 * data[0];
if (prev_data)
assert(data[1] == prev_data[0]);
}
static void sync_callback(struct libusb_transfer *transfer)
{
struct usbio_transfer *xf = transfer->user_data;
struct cbox_usb_io_impl *uii = xf->user_data;
uint8_t *data = transfer->buffer;
int i, ofs, pkts;
int rate_est = 0;
xf->pending = FALSE;
if (transfer->status == LIBUSB_TRANSFER_CANCELLED)
{
xf->cancel_confirm = 1;
return;
}
if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE)
{
xf->cancel_confirm = 1;
return;
}
// XXXKF handle device disconnected error
if (uii->debug_sync)
printf("Sync callback! %p %d packets:", transfer, transfer->num_iso_packets);
ofs = 0;
pkts = 0;
rate_est = 0;
const uint8_t *prev_data = NULL;
for (i = 0; i < transfer->num_iso_packets; i++) {
if (transfer->iso_packet_desc[i].status)
{
printf("[%02d: actual length is %4d, status is %2d] ", i, transfer->iso_packet_desc[i].actual_length, transfer->iso_packet_desc[i].status);
continue;
}
else if (transfer->iso_packet_desc[i].actual_length)
{
if (uii->sync_protocol == USBAUDIOSYNC_PROTOCOL_MULTIMIX8)
use_multimix_sync_feedback(&data[ofs], transfer->iso_packet_desc[i].actual_length, &rate_est, prev_data);
else
use_audioclass_sync_feedback(&data[ofs], transfer->iso_packet_desc[i].actual_length, &rate_est);
prev_data = &data[ofs];
//printf("%d\n", (int)data[ofs]);
if (uii->debug_sync)
printf("%3d ", (int)data[ofs]);
pkts++;
}
else
if (uii->debug_sync)
printf("? ");
ofs += transfer->iso_packet_desc[i].length;
}
if (uii->debug_sync)
printf(" (%d of %d)", pkts, transfer->num_iso_packets);
if (pkts == transfer->num_iso_packets)
{
// Divide by the number of packets to compute the mean rate (in case
// of Multimix we don't have a proper fractional value, just an integer,
// so averaging is required to get more accuracy).
if (rate_est)
uii->samples_per_sec = rate_est / pkts;
if (uii->debug_sync)
printf("rate_est = %d sync_freq = %d\n", rate_est, uii->samples_per_sec);
}
if (uii->no_resubmit)
return;
int err = usbio_transfer_submit(xf);
if (err)
{
if (err == LIBUSB_ERROR_NO_DEVICE)
{
return;
}
}
if (uii->debug_sync)
printf("\n");
}
struct usbio_transfer *sync_stuff_asynchronous(struct cbox_usb_io_impl *uii, int index)
{
struct usbio_transfer *t;
int err;
int syncbufsize = uii->sync_protocol == USBAUDIOSYNC_PROTOCOL_MULTIMIX8 ? 64 : 3;
int syncbufcount = uii->sync_protocol == USBAUDIOSYNC_PROTOCOL_MULTIMIX8 ? NUM_MULTIMIX_SYNC_PACKETS : 1;
t = usbio_transfer_new(uii->usbctx, "sync", index, syncbufcount, uii);
uint8_t *sync_buf = (uint8_t *)calloc(syncbufcount, syncbufsize);
libusb_fill_iso_transfer(t->transfer, uii->handle_audiodev, uii->audio_sync_endpoint, sync_buf, syncbufsize * syncbufcount, syncbufcount, sync_callback, t, 20000);
libusb_set_iso_packet_lengths(t->transfer, syncbufsize);
err = libusb_submit_transfer(t->transfer);
if (err)
{
g_warning("Cannot submit sync urb: %s", libusb_error_name(err));
free(sync_buf);
usbio_transfer_destroy(t);
return NULL;
}
return t;
}
///////////////////////////////////////////////////////////////////////////
void cbox_usb_audio_info_init(struct cbox_usb_audio_info *uai, struct cbox_usb_device_info *udi)
{
uai->udi = udi;
uai->intf = -1;
uai->alt_setting = -1;
uai->epdesc.found = FALSE;
uai->sync_protocol = USBAUDIOSYNC_PROTOCOL_NONE;
uai->sync_epdesc.found = FALSE;
}
void usbio_start_audio_playback(struct cbox_usb_io_impl *uii)
{
uii->desync = 0;
uii->samples_played = 0;
uii->read_ptr = uii->ioi.pio->io_env.buffer_size;
uii->playback_transfers = malloc(sizeof(struct libusb_transfer *) * uii->playback_buffers);
uii->sync_transfers = malloc(sizeof(struct libusb_transfer *) * uii->sync_buffers);
uii->playback_counter = 0;
uii->device_removed = 0;
uii->samples_per_sec = uii->sample_rate;
uii->play_function(uii);
uii->setup_error = uii->playback_counter == 0;
if (!uii->setup_error)
{
while(uii->playback_counter < uii->playback_buffers && !uii->device_removed)
libusb_handle_events(uii->usbctx);
}
}
void usbio_stop_audio_playback(struct cbox_usb_io_impl *uii)
{
if (uii->device_removed)
{
// Wait until all the transfers pending are finished
while(uii->device_removed < uii->playback_counter)
libusb_handle_events(uii->usbctx);
}
if (uii->device_removed || uii->setup_error)
{
// Run the DSP code and send output to bit bucket until engine is stopped.
// This ensures that the command queue will still be processed.
// Otherwise the GUI thread would hang waiting for the command from
// the queue to be completed.
g_message("USB Audio output device has been disconnected - switching to null output.");
usbio_run_idle_loop(uii);
}
else
{
// Cancel all transfers pending, and wait until they get cancelled
for (unsigned int i = 0; i < uii->playback_counter; i++)
{
if (uii->playback_transfers[i])
usbio_transfer_shutdown(uii->playback_transfers[i]);
}
}
// Free the transfers for the buffers allocated so far. In case of setup
// failure, some buffers transfers might not have been created yet.
for (unsigned int i = 0; i < uii->playback_counter; i++)
{
if (uii->playback_transfers[i])
{
free(uii->playback_transfers[i]->transfer->buffer);
usbio_transfer_destroy(uii->playback_transfers[i]);
uii->playback_transfers[i] = NULL;
}
}
if (uii->playback_counter && uii->audio_sync_endpoint)
{
for (unsigned int i = 0; i < uii->sync_counter; i++)
{
if (uii->sync_transfers[i])
usbio_transfer_shutdown(uii->sync_transfers[i]);
}
for (unsigned int i = 0; i < uii->sync_counter; i++)
{
if (uii->sync_transfers[i])
{
free(uii->sync_transfers[i]->transfer->buffer);
usbio_transfer_destroy(uii->sync_transfers[i]);
uii->sync_transfers[i] = NULL;
}
}
}
free(uii->playback_transfers);
free(uii->sync_transfers);
}
#endif