267 lines
12 KiB
C
267 lines
12 KiB
C
// MIT License Copyright (c) 2025 serifpersia
|
|
//
|
|
// Simple, non-synchronized playback driver.
|
|
// This version is a clean base, modified only to provide a ~1.0ms
|
|
// transfer buffer size for 48kHz and 96kHz sample rates.
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <libusb-1.0/libusb.h>
|
|
#include <stdbool.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <limits.h>
|
|
|
|
// --- Device and Endpoint Configuration ---
|
|
#define TASCAM_VID 0x0644
|
|
#define TASCAM_PID 0x8020
|
|
#define EP_AUDIO_OUT 0x02
|
|
#define EP_PLAYBACK_FEEDBACK 0x81
|
|
|
|
// --- USB Request Types ---
|
|
#define RT_H2D_CLASS_EP 0x22
|
|
#define RT_H2D_VENDOR_DEV 0x40
|
|
#define RT_D2H_VENDOR_DEV 0xc0
|
|
|
|
// --- UAC / Vendor Requests ---
|
|
#define UAC_SET_CUR 0x01
|
|
#define UAC_SAMPLING_FREQ_CONTROL 0x0100
|
|
#define VENDOR_REQ_REGISTER_WRITE 65
|
|
#define VENDOR_REQ_MODE_CONTROL 73
|
|
|
|
// --- Streaming Configuration ---
|
|
#define BYTES_PER_SAMPLE 3
|
|
#define DEVICE_CHANNELS 4
|
|
#define DEVICE_FRAME_SIZE (DEVICE_CHANNELS * BYTES_PER_SAMPLE) // 12 bytes
|
|
#define PIPE_CHANNELS 2
|
|
#define PIPE_FRAME_SIZE (PIPE_CHANNELS * BYTES_PER_SAMPLE) // 6 bytes
|
|
#define NUM_ISO_TRANSFERS 8
|
|
#define USB_TIMEOUT 1000
|
|
|
|
// --- Global State ---
|
|
static volatile bool is_running = true;
|
|
struct stream_state {
|
|
int playback_fifo_fd;
|
|
};
|
|
|
|
// --- Function Prototypes ---
|
|
void print_usage(const char *prog_name);
|
|
int perform_initialization_sequence(libusb_device_handle *handle, int rate);
|
|
static void LIBUSB_CALL iso_playback_callback(struct libusb_transfer *transfer);
|
|
static void LIBUSB_CALL feedback_callback(struct libusb_transfer *transfer);
|
|
|
|
void sigint_handler(int signum) {
|
|
if (is_running) {
|
|
printf("\nCtrl+C detected, stopping...\n");
|
|
is_running = false;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
libusb_device_handle *handle = NULL;
|
|
struct libusb_transfer *playback_transfers[NUM_ISO_TRANSFERS] = {0};
|
|
struct libusb_transfer *feedback_transfers[NUM_ISO_TRANSFERS] = {0};
|
|
struct stream_state state = {.playback_fifo_fd = -1};
|
|
bool kernel_driver_was_active[2] = {false, false};
|
|
int r = 0;
|
|
|
|
int sample_rate = 0;
|
|
const char *playback_pipe_path = NULL;
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "-r") == 0 && i + 1 < argc) { sample_rate = atoi(argv[++i]); }
|
|
else if (strcmp(argv[i], "--playback-pipe") == 0 && i + 1 < argc) { playback_pipe_path = argv[++i]; }
|
|
else { print_usage(argv[0]); return 1; }
|
|
}
|
|
|
|
if (sample_rate == 0 || !playback_pipe_path) {
|
|
fprintf(stderr, "Error: Missing required arguments.\n");
|
|
print_usage(argv[0]); return 1;
|
|
}
|
|
if (sample_rate != 48000 && sample_rate != 96000) {
|
|
fprintf(stderr, "Error: Sample rate %d is not supported. Use 48000 or 96000.\n", sample_rate); return 1;
|
|
}
|
|
|
|
// --- DYNAMIC BUFFER CALCULATION FOR 1.0ms LATENCY ---
|
|
const int frames_per_transfer = sample_rate / 1000; // 48 for 48k, 96 for 96k
|
|
const int playback_transfer_size = frames_per_transfer * DEVICE_FRAME_SIZE;
|
|
const int num_iso_packets = 8; // A reasonable default
|
|
const int playback_packet_size = playback_transfer_size / num_iso_packets;
|
|
|
|
// The feedback endpoint size is fixed and small
|
|
const int feedback_packet_size = 3;
|
|
const int feedback_transfer_size = feedback_packet_size * num_iso_packets;
|
|
|
|
printf("Initializing TASCAM US-144 MKII for %d Hz...\n", sample_rate);
|
|
printf("Using transfer size of %d bytes (%d frames) for ~1.0ms latency.\n", playback_transfer_size, frames_per_transfer);
|
|
|
|
signal(SIGINT, sigint_handler);
|
|
if (libusb_init(NULL) < 0) return 1;
|
|
handle = libusb_open_device_with_vid_pid(NULL, TASCAM_VID, TASCAM_PID);
|
|
if (!handle) { fprintf(stderr, "Device not found\n"); r = 1; goto cleanup; }
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
if (libusb_kernel_driver_active(handle, i)) {
|
|
kernel_driver_was_active[i] = true;
|
|
if ((r = libusb_detach_kernel_driver(handle, i)) != 0) {
|
|
fprintf(stderr, "Could not detach kernel driver for interface %d: %s\n", i, libusb_error_name(r));
|
|
r = 1; goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (perform_initialization_sequence(handle, sample_rate) != 0) { fprintf(stderr, "Device configuration failed.\n"); r = 1; goto cleanup; }
|
|
|
|
state.playback_fifo_fd = open(playback_pipe_path, O_RDONLY | O_NONBLOCK);
|
|
if (state.playback_fifo_fd < 0) { perror("Opening playback FIFO failed"); r = 1; goto cleanup; }
|
|
char drain_buf[1024]; while (read(state.playback_fifo_fd, drain_buf, sizeof(drain_buf)) > 0);
|
|
|
|
printf("Starting playback and feedback streams...\n");
|
|
for (int i = 0; i < NUM_ISO_TRANSFERS; i++) {
|
|
playback_transfers[i] = libusb_alloc_transfer(num_iso_packets);
|
|
unsigned char *buf_p = malloc(playback_transfer_size);
|
|
memset(buf_p, 0, playback_transfer_size);
|
|
libusb_fill_iso_transfer(playback_transfers[i], handle, EP_AUDIO_OUT, buf_p, playback_transfer_size, num_iso_packets, iso_playback_callback, &state, USB_TIMEOUT);
|
|
libusb_set_iso_packet_lengths(playback_transfers[i], playback_packet_size);
|
|
libusb_submit_transfer(playback_transfers[i]);
|
|
|
|
feedback_transfers[i] = libusb_alloc_transfer(num_iso_packets);
|
|
unsigned char *buf_f = malloc(feedback_transfer_size);
|
|
libusb_fill_iso_transfer(feedback_transfers[i], handle, EP_PLAYBACK_FEEDBACK, buf_f, feedback_transfer_size, num_iso_packets, feedback_callback, NULL, USB_TIMEOUT);
|
|
libusb_set_iso_packet_lengths(feedback_transfers[i], feedback_packet_size);
|
|
libusb_submit_transfer(feedback_transfers[i]);
|
|
}
|
|
|
|
printf("\n--- Playback active. Press Ctrl+C to stop. ---\n");
|
|
while (is_running) {
|
|
libusb_handle_events_timeout_completed(NULL, &(struct timeval){0, 100000}, NULL);
|
|
}
|
|
|
|
cleanup:
|
|
is_running = false;
|
|
printf("\nCleaning up...\n");
|
|
for (int i = 0; i < NUM_ISO_TRANSFERS; i++) {
|
|
if (playback_transfers[i]) libusb_cancel_transfer(playback_transfers[i]);
|
|
if (feedback_transfers[i]) libusb_cancel_transfer(feedback_transfers[i]);
|
|
}
|
|
if (handle) {
|
|
struct timeval tv = {0, 100000};
|
|
libusb_handle_events_timeout_completed(NULL, &tv, NULL);
|
|
}
|
|
if (handle) {
|
|
libusb_release_interface(handle, 1); libusb_release_interface(handle, 0);
|
|
for(int i = 0; i < 2; i++) if (kernel_driver_was_active[i]) libusb_attach_kernel_driver(handle, i);
|
|
libusb_close(handle);
|
|
handle = NULL;
|
|
}
|
|
for (int i = 0; i < NUM_ISO_TRANSFERS; i++) {
|
|
if (playback_transfers[i]) { if (playback_transfers[i]->buffer) free(playback_transfers[i]->buffer); libusb_free_transfer(playback_transfers[i]); }
|
|
if (feedback_transfers[i]) { if (feedback_transfers[i]->buffer) free(feedback_transfers[i]->buffer); libusb_free_transfer(feedback_transfers[i]); }
|
|
}
|
|
if (state.playback_fifo_fd >= 0) close(state.playback_fifo_fd);
|
|
libusb_exit(NULL);
|
|
printf("Cleanup complete.\n");
|
|
return r;
|
|
}
|
|
|
|
void print_usage(const char *prog_name) {
|
|
fprintf(stderr, "Usage: %s -r <rate> --playback-pipe <path>\n", prog_name);
|
|
fprintf(stderr, " -r <rate> : Set sample rate. Supported: 48000, 96000.\n");
|
|
fprintf(stderr, " --playback-pipe <path>: Path to the named pipe for audio playback.\n");
|
|
}
|
|
|
|
static void LIBUSB_CALL feedback_callback(struct libusb_transfer *transfer) {
|
|
if (!is_running) return;
|
|
if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {
|
|
if (is_running) {
|
|
libusb_submit_transfer(transfer);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LIBUSB_CALL iso_playback_callback(struct libusb_transfer *transfer) {
|
|
if (!is_running) return;
|
|
if (transfer->status != LIBUSB_TRANSFER_COMPLETED && transfer->status != LIBUSB_TRANSFER_CANCELLED) {
|
|
fprintf(stderr, "Playback callback error (EP 0x%02x): %s\n", transfer->endpoint, libusb_error_name(transfer->status));
|
|
is_running = false;
|
|
return;
|
|
}
|
|
|
|
struct stream_state *state = transfer->user_data;
|
|
int total_frames = transfer->length / DEVICE_FRAME_SIZE;
|
|
int bytes_to_read_from_fifo = total_frames * PIPE_FRAME_SIZE;
|
|
|
|
// Use a static buffer to avoid malloc/free in the callback
|
|
static unsigned char source_buf[8192];
|
|
if (bytes_to_read_from_fifo > sizeof(source_buf)) {
|
|
bytes_to_read_from_fifo = sizeof(source_buf);
|
|
}
|
|
|
|
ssize_t n = read(state->playback_fifo_fd, source_buf, bytes_to_read_from_fifo);
|
|
|
|
unsigned char *device_buf = transfer->buffer;
|
|
memset(device_buf, 0, transfer->length);
|
|
|
|
if (n > 0) {
|
|
int frames_read = n / PIPE_FRAME_SIZE;
|
|
unsigned char *src = source_buf;
|
|
for (int i = 0; i < frames_read; i++) {
|
|
memcpy(device_buf, src, PIPE_FRAME_SIZE);
|
|
device_buf += DEVICE_FRAME_SIZE;
|
|
src += PIPE_FRAME_SIZE;
|
|
}
|
|
}
|
|
|
|
if (is_running) {
|
|
libusb_submit_transfer(transfer);
|
|
}
|
|
}
|
|
|
|
int perform_initialization_sequence(libusb_device_handle *handle, int rate) {
|
|
unsigned char buf[64]; int r;
|
|
unsigned char rate_data[3];
|
|
uint16_t rate_vendor_wValue;
|
|
|
|
switch(rate) {
|
|
case 48000: memcpy(rate_data, (unsigned char[]){0x80, 0xbb, 0x00}, 3); rate_vendor_wValue = 0x1002; break;
|
|
case 96000: memcpy(rate_data, (unsigned char[]){0x00, 0x77, 0x01}, 3); rate_vendor_wValue = 0x100a; break;
|
|
default: fprintf(stderr, "Invalid sample rate for initialization.\n"); return -1;
|
|
}
|
|
|
|
printf("\n--- STARTING DEVICE CONFIGURATION ---\n");
|
|
|
|
#define CHECK(desc, call) \
|
|
r = (call); \
|
|
if (r < 0) { fprintf(stderr, " [FAIL] %s: %s\n", desc, libusb_error_name(r)); return -1; } \
|
|
else { printf(" [OK] %s (returned %d)\n", desc, r); }
|
|
|
|
r = libusb_set_configuration(handle, 1);
|
|
if (r < 0 && r != LIBUSB_ERROR_BUSY) { fprintf(stderr, " [FAIL] Set Configuration 1: %s\n", libusb_error_name(r)); return -1; }
|
|
for (int i=0; i<=1; i++) {
|
|
r = libusb_claim_interface(handle, i);
|
|
if (r < 0) { fprintf(stderr, " [FAIL] Claim Interface %d: %s\n", i, libusb_error_name(r)); return -1; }
|
|
r = libusb_set_interface_alt_setting(handle, i, 1);
|
|
if (r < 0) { fprintf(stderr, " [FAIL] Set Alt Setting on Intf %d: %s\n", i, libusb_error_name(r)); return -1; }
|
|
}
|
|
printf(" [OK] Interfaces set and claimed.\n");
|
|
|
|
CHECK("Status Check", libusb_control_transfer(handle, RT_D2H_VENDOR_DEV, VENDOR_REQ_MODE_CONTROL, 0x0000, 0x0000, buf, 1, USB_TIMEOUT));
|
|
CHECK("Set Initial Mode", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_MODE_CONTROL, 0x0010, 0x0000, NULL, 0, USB_TIMEOUT));
|
|
CHECK("Set Rate on Capture EP (0x86)", libusb_control_transfer(handle, RT_H2D_CLASS_EP, UAC_SET_CUR, UAC_SAMPLING_FREQ_CONTROL, 0x86, rate_data, 3, USB_TIMEOUT));
|
|
CHECK("Set Rate on Playback EP (0x02)", libusb_control_transfer(handle, RT_H2D_CLASS_EP, UAC_SET_CUR, UAC_SAMPLING_FREQ_CONTROL, EP_AUDIO_OUT, rate_data, 3, USB_TIMEOUT));
|
|
CHECK("Reg Write 1 (0x0d04)", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_REGISTER_WRITE, 0x0d04, 0x0101, NULL, 0, USB_TIMEOUT));
|
|
CHECK("Reg Write 2 (0x0e00)", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_REGISTER_WRITE, 0x0e00, 0x0101, NULL, 0, USB_TIMEOUT));
|
|
CHECK("Reg Write 3 (0x0f00)", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_REGISTER_WRITE, 0x0f00, 0x0101, NULL, 0, USB_TIMEOUT));
|
|
CHECK("Reg Write 4 (Rate-Dep)", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_REGISTER_WRITE, rate_vendor_wValue, 0x0101, NULL, 0, USB_TIMEOUT));
|
|
CHECK("Reg Write 5 (0x110b)", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_REGISTER_WRITE, 0x110b, 0x0101, NULL, 0, USB_TIMEOUT));
|
|
CHECK("Enable Streaming", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_MODE_CONTROL, 0x0030, 0x0000, NULL, 0, USB_TIMEOUT));
|
|
|
|
printf("\n--- CONFIGURATION COMPLETE ---\n\n");
|
|
return 0;
|
|
}
|