us144mkii/src & scripts/tascam_fifo_streamer.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;
}