Skip to content

Conversation

@faha223
Copy link
Contributor

@faha223 faha223 commented Jan 24, 2026

Changed the usb-xblc driver to use the SDL3 audio backend

To test this: add an xblc to your controller using the following command in the qemu monitor:
device_add usb-xblc,port=1.3.2,id=xblc1

You can also remove the xblc from your controller using the following command in the qemu monitor:
device_del xblc1

This will add the virtual XBLC device to expansion slot A of the controller in port 1

@gemini-code-assist
Copy link

Summary of Changes

Hello @faha223, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly updates the USB Xbox Live Communicator (XBLC) driver by transitioning its audio processing from QEMU's legacy audio backend to SDL3. This migration modernizes the audio stack, leveraging SDL3's capabilities for more robust and potentially more performant audio input and output, and streamlines the device's audio stream management.

Highlights

  • SDL3 Migration: The USB Xbox Live Communicator (XBLC) driver has been migrated from QEMU's internal AudioBackend to use the SDL3 audio backend for both input and output streams.
  • Audio Stream Management: Refactored audio stream initialization and management, introducing a new XBLCStream struct to encapsulate SDL3 audio device and stream details, and a helper function xblc_audio_channel_init.
  • Callback Updates: The output_callback and input_callback functions have been rewritten to align with SDL3's audio stream callback signatures and utilize SDL_PutAudioStreamData and SDL_GetAudioStreamData respectively.
  • Underrun Prevention: Improved handling for audio underrun by ensuring that output packets are always filled, even with silence, to prevent audio glitches.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully migrates the xblc driver to the SDL3 audio backend, refactoring the audio stream handling and updating the callbacks accordingly. The changes are generally good, but I've found a few critical issues related to potential null pointer dereferences when handling audio streams, which could lead to crashes. I've also identified a logic issue that could cause unnecessary re-initialization of audio streams and a minor performance issue in one of the new audio callbacks. Please address these points to ensure the stability and correctness of the driver.

hw/xbox/xblc.c Outdated
Comment on lines 249 to 254
channel->voice = SDL_OpenAudioDeviceStream(device_id,
&channel->spec,
capture ? input_callback : output_callback,
(void*)s);

SDL_ResumeAudioStreamDevice(channel->voice);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

SDL_OpenAudioDeviceStream can return NULL on failure, but its return value is not checked. This will cause a crash in the following call to SDL_ResumeAudioStreamDevice. Please add a NULL check to handle potential failures gracefully.

    channel->voice = SDL_OpenAudioDeviceStream(device_id,
                                               &channel->spec, 
                                               capture ? input_callback : output_callback,
                                               (void*)s);
    if (channel->voice == NULL) {
        DPRINTF("[xblc] Failed to open audio device stream: %s\n", SDL_GetError());
        return;
    }
    
    SDL_ResumeAudioStreamDevice(channel->voice);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented the suggested change

hw/xbox/xblc.c Outdated
Comment on lines 358 to 367
SDL_PauseAudioStreamDevice(s->in.voice);
SDL_PauseAudioStreamDevice(s->out.voice);
fifo8_destroy(&s->out.fifo);
fifo8_destroy(&s->in.fifo);

AUD_close_out(s->audio_be, s->out.voice);
AUD_close_in(s->audio_be, s->in.voice);
SDL_DestroyAudioStream(s->in.voice);
s->in.voice = 0;
SDL_DestroyAudioStream(s->out.voice);
s->out.voice = 0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The functions SDL_PauseAudioStreamDevice and SDL_DestroyAudioStream are called without checking if s->in.voice and s->out.voice are NULL. If a stream was not created successfully or has already been destroyed, this will result in a crash. Please add NULL checks before calling these functions.

    if (s->in.voice) {
        SDL_PauseAudioStreamDevice(s->in.voice);
    }
    if (s->out.voice) {
        SDL_PauseAudioStreamDevice(s->out.voice);
    }
    
    fifo8_destroy(&s->in.fifo);
    fifo8_destroy(&s->out.fifo);

    if (s->in.voice) {
        SDL_DestroyAudioStream(s->in.voice);
        s->in.voice = 0;
    }
    if (s->out.voice) {
        SDL_DestroyAudioStream(s->out.voice);
        s->out.voice = 0;
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented the suggested change

hw/xbox/xblc.c Outdated
Comment on lines 274 to 277
if(init_input_stream || init_output_stream) {
xblc_audio_channel_init(s, true);
xblc_audio_channel_init(s, false);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Both input and output audio streams are re-initialized if either one needs it. This can lead to unnecessary re-initialization and potential audio glitches on the stream that was already configured correctly. It's better to initialize them separately based on their respective flags.

    if (init_input_stream) {
        xblc_audio_channel_init(s, true);
    }
    if (init_output_stream) {
        xblc_audio_channel_init(s, false);
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented the suggested change

hw/xbox/xblc.c Outdated
uint32_t total_bytes_read = 0;
uint32_t max_len = MIN(total_amount, fifo8_num_free(&s->in.fifo));
if (max_len > 0) {
memset(buffer, 0, max_len);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The call to memset to zero out the buffer before reading audio data with SDL_GetAudioStreamData is unnecessary, as SDL_GetAudioStreamData will overwrite the buffer's contents. This line can be removed to improve performance in this audio callback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented the suggested change

…improve stability. Removed an unnecessary memset in the input_callback.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the USB Xbox Live Communicator (XBLC) driver from the custom QEMU audio backend to SDL3's audio API. The migration replaces the previous AudioBackend/SWVoice system with SDL3's SDL_AudioStream and SDL_AudioDeviceStream APIs.

Changes:

  • Replaced custom audio backend structures with SDL3 audio streams
  • Refactored audio initialization to use SDL3 device streams
  • Updated audio callbacks to SDL3's callback signature and buffering approach
  • Simplified resource management with SDL3's lifecycle APIs

hw/xbox/xblc.c Outdated
Comment on lines 166 to 177
if(s->in.voice != 0)
SDL_LockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_LockAudioStream(s->out.voice);

fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);

if(s->in.voice != 0)
SDL_UnlockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_UnlockAudioStream(s->out.voice);
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing braces around single-line if statement. According to QEMU coding style (docs/devel/style.rst:168), every indented statement must be braced even if the block contains just one statement.

Suggested change
if(s->in.voice != 0)
SDL_LockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_LockAudioStream(s->out.voice);
fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);
if(s->in.voice != 0)
SDL_UnlockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_UnlockAudioStream(s->out.voice);
if(s->in.voice != 0) {
SDL_LockAudioStream(s->in.voice);
}
if(s->out.voice != 0) {
SDL_LockAudioStream(s->out.voice);
}
fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);
if(s->in.voice != 0) {
SDL_UnlockAudioStream(s->in.voice);
}
if(s->out.voice != 0) {
SDL_UnlockAudioStream(s->out.voice);
}

Copilot uses AI. Check for mistakes.
hw/xbox/xblc.c Outdated
Comment on lines 272 to 275
if(s->in.voice == 0)
init_input_stream = true;
if(s->out.voice == 0)
init_output_stream = true;
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing braces around single-line if statement. According to QEMU coding style (docs/devel/style.rst:168), every indented statement must be braced even if the block contains just one statement.

Suggested change
if(s->in.voice == 0)
init_input_stream = true;
if(s->out.voice == 0)
init_output_stream = true;
if (s->in.voice == 0) {
init_input_stream = true;
}
if (s->out.voice == 0) {
init_output_stream = true;
}

Copilot uses AI. Check for mistakes.
hw/xbox/xblc.c Outdated
Comment on lines 274 to 275
if(s->out.voice == 0)
init_output_stream = true;
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing braces around single-line if statement. According to QEMU coding style (docs/devel/style.rst:168), every indented statement must be braced even if the block contains just one statement.

Copilot uses AI. Check for mistakes.
hw/xbox/xblc.c Outdated

// Clear out the remainder of the input buffer
int bytes_read = 1;
while(bytes_read > 0 && total_bytes_read < total_amount) {
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after while keyword. The QEMU coding style requires a space between control flow keywords and the opening parenthesis.

Suggested change
while(bytes_read > 0 && total_bytes_read < total_amount) {
while (bytes_read > 0 && total_bytes_read < total_amount) {

Copilot uses AI. Check for mistakes.
hw/xbox/xblc.c Outdated
static void input_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount)
{
USBXBLCState *s = (USBXBLCState *)userdata;
static uint8_t buffer[XBLC_FIFO_SIZE];
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static buffer declared here makes the input_callback function non-reentrant, which could be problematic if SDL3 calls this callback from multiple threads or if multiple XBLC devices exist. Consider making this buffer part of the XBLCStream structure or using a per-device buffer allocated on the heap.

Suggested change
static uint8_t buffer[XBLC_FIFO_SIZE];
uint8_t buffer[XBLC_FIFO_SIZE];

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this buffer into the USBXBLCState structure because the output stream doesn't need it

hw/xbox/xblc.c Outdated
Comment on lines 166 to 177
if(s->in.voice != 0)
SDL_LockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_LockAudioStream(s->out.voice);

fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);

if(s->in.voice != 0)
SDL_UnlockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_UnlockAudioStream(s->out.voice);
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing braces around single-line if statement. According to QEMU coding style (docs/devel/style.rst:168), every indented statement must be braced even if the block contains just one statement.

Suggested change
if(s->in.voice != 0)
SDL_LockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_LockAudioStream(s->out.voice);
fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);
if(s->in.voice != 0)
SDL_UnlockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_UnlockAudioStream(s->out.voice);
if (s->in.voice != 0) {
SDL_LockAudioStream(s->in.voice);
}
if (s->out.voice != 0) {
SDL_LockAudioStream(s->out.voice);
}
fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);
if (s->in.voice != 0) {
SDL_UnlockAudioStream(s->in.voice);
}
if (s->out.voice != 0) {
SDL_UnlockAudioStream(s->out.voice);
}

Copilot uses AI. Check for mistakes.
hw/xbox/xblc.c Outdated
Comment on lines 222 to 223
if(bytes_read > 0)
total_bytes_read += bytes_read;
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing braces around single-line if statement. According to QEMU coding style (docs/devel/style.rst:168), every indented statement must be braced even if the block contains just one statement.

Suggested change
if(bytes_read > 0)
total_bytes_read += bytes_read;
if(bytes_read > 0) {
total_bytes_read += bytes_read;
}

Copilot uses AI. Check for mistakes.
hw/xbox/xblc.c Outdated
Comment on lines 234 to 235
if(channel->voice != 0) {
SDL_PauseAudioStreamDevice(channel->voice);
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing braces around single-line if statement. According to QEMU coding style (docs/devel/style.rst:168), every indented statement must be braced even if the block contains just one statement.

Copilot uses AI. Check for mistakes.
hw/xbox/xblc.c Outdated

s->out.voice = AUD_open_out(s->audio_be, s->out.voice, TYPE_USB_XBLC "-speaker",
s, output_callback, &s->as);
if(s->sample_rate != sample_rate) {
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after if keyword. The QEMU coding style requires a space between control flow keywords and the opening parenthesis, similar to other if statements in the codebase (e.g., hw/usb/bus.c:58, hw/usb/core.c:86).

Suggested change
if(s->sample_rate != sample_rate) {
if (s->sample_rate != sample_rate) {

Copilot uses AI. Check for mistakes.
hw/xbox/xblc.c Outdated
Comment on lines 166 to 177
if(s->in.voice != 0)
SDL_LockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_LockAudioStream(s->out.voice);

fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);

if(s->in.voice != 0)
SDL_UnlockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_UnlockAudioStream(s->out.voice);
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing braces around single-line if statement. According to QEMU coding style (docs/devel/style.rst:168), every indented statement must be braced even if the block contains just one statement.

Suggested change
if(s->in.voice != 0)
SDL_LockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_LockAudioStream(s->out.voice);
fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);
if(s->in.voice != 0)
SDL_UnlockAudioStream(s->in.voice);
if(s->out.voice != 0)
SDL_UnlockAudioStream(s->out.voice);
if (s->in.voice != 0) {
SDL_LockAudioStream(s->in.voice);
}
if (s->out.voice != 0) {
SDL_LockAudioStream(s->out.voice);
}
fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);
if (s->in.voice != 0) {
SDL_UnlockAudioStream(s->in.voice);
}
if (s->out.voice != 0) {
SDL_UnlockAudioStream(s->out.voice);
}

Copilot uses AI. Check for mistakes.
…ad in the USBBLCState structure. This will prevent multiple devices from interfering with each other if the input callback is called on multiple threads simultaneously

Fixed some inconsistencies with the QEMU code style guidelines.
hw/xbox/xblc.c Outdated

DPRINTF("[XBLC] Reset\n");

if (s->in.voice != 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Since this is a pointer, prefer checking against NULL instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were a lot of instances where that was being compared or assigned 0 instead of NULL. I went through and fixed them

hw/xbox/xblc.c Outdated
* QEMU USB Xbox Live Communicator (XBLC) Device
*
* Copyright (c) 2022 Ryan Wendland
* Copyright (c) 2025 faha223
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you add your name here, prefer to use your real name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to my real name

hw/xbox/xblc.c Outdated
processed = AUD_write(s->out.voice, (void *)data, max_len);
avail -= processed;
if (processed < max_len) return;
static void input_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SDL3 enables us to simplify this code and drop these extra fifos and callbacks. Let's refactor this code to use the direct stream approach instead of callbacks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored the xblc to remove the fifo queues and callbacks, and I'm not directly calling SDL_PutAudioStreamData and SDL_GetAudioStreamData inside the usb_xblc_handle_data function. It appears that it still works. There's slightly less latency now when testing with N.U.D.E.

hw/xbox/xblc.c Outdated
// Clear out the remainder of the input buffer
int bytes_read = 1;
while (bytes_read > 0 && total_bytes_read < total_amount) {
bytes_read = SDL_GetAudioStreamData(stream, s->input_buffer, MIN(XBLC_FIFO_SIZE, total_amount - total_bytes_read));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If SDL_GetAudioStreamData is returning <0, something is wrong. We should not hammer on it here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If SDL_GetAudioStreamData returns <0 then it exits the loop. I'm not sure what else to do when that happens.

hw/xbox/xblc.c Outdated
if (s->sample_rate != sample_rate) {
init_input_stream = true;
init_output_stream = true;
s->sample_rate = sample_rate;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to recreate the stream to change the sample rate. SDL3 offers an API for this SDL_SetAudioStreamFormat

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this function so that it uses SDL_SetAudioStreamFormat if the streams are not NULL instead of calling xblc_audio_channel_init

… NULL.

Changed the xblc_audio_stream_init function to call SDL_SetAudioStreamFormat instead of calling xblc_audio_channel_init when the device is already initialized but the sample rate is being changed.

Refactored the XBLCStream to get rid of the input_buffer and the fifo queues and callbacks and just call SDL_GetAudioStreamData and SDL_PutAudioStreamData directly in the usb_xblc_handle_data function
hw/xbox/xblc.c Outdated
while(to_process) {
chunk_len = MIN(p->iov.size, sizeof(s->out.packet));
usb_packet_copy(p, (void *)s->out.packet, chunk_len);
SDL_PutAudioStreamData(s->out.voice, s->out.packet, chunk_len);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Is it possible that s->out.voice is not initialized here? E.g. if xblc_audio_stream_init is never called
  • SDL_PutAudioStreamData may fail
  • Why do we need this intermediate buffer s->out.packet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I'm not sure
  • That's true. I'm not sure what to do when it fails though
  • It turns out we don't. I can just call SDL_PutAudioStreamData using p->iov.iov->iov_base directly

Copy link
Contributor Author

@faha223 faha223 Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose if it fails I should log the error and assert(false);

hw/xbox/xblc.c Outdated
while (to_process) {
const uint8_t *packet = fifo8_pop_bufptr(&s->in.fifo, to_process, &chunk_len);
usb_packet_copy(p, (void *)packet, chunk_len);
chunk_len = SDL_GetAudioStreamData(s->in.voice, s->in.packet, MIN(to_process, p->iov.size));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Is it possible that s->in.voice is not initialized here? E.g. if xblc_audio_stream_init is never called
  • to_process will always be <= p->iov.size

hw/xbox/xblc.c Outdated
const uint8_t *packet = fifo8_pop_bufptr(&s->in.fifo, to_process, &chunk_len);
usb_packet_copy(p, (void *)packet, chunk_len);
chunk_len = SDL_GetAudioStreamData(s->in.voice, s->in.packet, MIN(to_process, p->iov.size));
if(chunk_len < 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Needs braces

hw/xbox/xblc.c Outdated
fifo8_push_all(&s->out.fifo, s->out.packet, to_process);

// Process the desired amount of data
to_process = p->iov.size;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment is redundant

hw/xbox/xblc.c Outdated
// Process the desired amount of data
to_process = p->iov.size;

// In case the out packet is smallerthan the iov size, do it in chunks
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment

hw/xbox/xblc.c Outdated
to_process = p->iov.size;

// In case the out packet is smallerthan the iov size, do it in chunks
while(to_process) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Spaces. In this case feel free to just run clang-format on this file to normalize whitespace, clang-format config in root dir

Added some error checking
Combined some if statements to reduce the code size.
Removed some redundant comments
Added return value checks to some functions and added some error logging and asserts
Added a call to xblc_audio_stream_ini to usb_xbox_communicator_realize to ensure the audio streams are initialized from the moment the device is connected
Added a call to SDL_ClearAudioStream at the end of xblc_audio_stream_init to ensure that the stream is empty after setting the sample rate. This resolves the latency problem I've been seeing in N.U.D.E. where it turns the mic on and off but the audio stream keeps filling up while the mic is off. Not sure if this applies to other games.
hw/xbox/xblc.c Outdated
} while (avail > 0 && processed >= XBLC_MAX_PACKET);
return;
if (s->in.voice != NULL) {
SDL_LockAudioStream(s->in.voice);
Copy link
Member

@mborgerson mborgerson Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it necessary to lock the audio streams?

According to the docs for SDL_ClearAudioStream:

It is safe to call this function from any thread.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover from SDL2 and from when we were using callbacks. I've removed these locks

hw/xbox/xblc.c Outdated
SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;

if (channel->voice != NULL) {
SDL_PauseAudioStreamDevice(channel->voice);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we pausing the stream before destruction? I think SDL_DestroyAudioStream should implicitly pause a stream

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was leftover from when we were using the callbacks and managing the queued data ourselves. This isn't necessary so I removed it.

hw/xbox/xblc.c Outdated
if (s->in.voice == NULL) {
DPRINTF("[XBLC] Tried to get data from the input audio tream but "
"the audio stream is not initialized");
assert(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why assert? xblc emulation should still work, even if we failed to bring up audio streams

hw/xbox/xblc.c Outdated

to_process = MIN(p->iov.size, available);

// Read data from the stream
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment

hw/xbox/xblc.c Outdated
assert(false);
}

int available = SDL_GetAudioStreamAvailable(s->in.voice);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check if there are data available? What if we just attempt to directly SDL_GetAudioStreamData instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to check. It's just useful info to have when debugging. I'll remove it

hw/xbox/xblc.c Outdated
if (s->out.voice == NULL) {
DPRINTF("[XBLC] Tried to put data into the speaker audio stream "
"but the audio stream is not initialized");
assert(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above. We should still be able to emulate an XBLC even if SDL3 streams aren't available

hw/xbox/xblc.c Outdated
p->iov.size)) {
DPRINTF("[XBLC] Error putting data into output stream: %s\n",
SDL_GetError());
assert(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above

hw/xbox/xblc.c Outdated
processed = AUD_write(s->out.voice, (void *)data, max_len);
avail -= processed;
if (processed < max_len) return;
SDL_ClearAudioStream(s->in.voice);
Copy link
Member

@mborgerson mborgerson Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Let's check that the stream != NULL before calling SDL_ClearAudioStream
  • Should we be clearing input here? See comment below Actually clearing here probably makes sense to discard queued data that might increase latency

hw/xbox/xblc.c Outdated

s->in.voice = AUD_open_in(s->audio_be, s->in.voice, TYPE_USB_XBLC "-microphone",
s, input_callback, &s->as);
SDL_ClearAudioStream(s->in.voice);
Copy link
Member

@mborgerson mborgerson Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Should only clear a stream if it exists. If we initialized a new one, we probably don't need to clear it?
  • According to https://wiki.libsdl.org/SDL3/SDL_SetAudioStreamFormat it appears you do not need to clear streams when the format changes, so maybe we can just drop the clear

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were 2 reasons why I added this:

  • When calling SDL_SetAudiostreamFormat, any samples already in the queue will be in the previous format
  • This solves a problem I was seeing in N.U.D.E. where the queue was filling up with data while the mic was "off" (not being read, but still active). I noticed that every time the mic starts to be used a command is sent to set the sample rate, so I use that as a cue to clear the queue

Copy link
Member

@mborgerson mborgerson Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When calling SDL_SetAudiostreamFormat, any samples already in the queue will be in the previous format

According to SDL_SetAudioStreamFormat docs:

Future calls to and SDL_GetAudioStreamAvailable and SDL_GetAudioStreamData will reflect the new format, and future calls to SDL_PutAudioStreamData must provide data in the new input formats.

So I don't think we need to worry about old format of samples--SDL should handle this for us.

his solves a problem I was seeing in N.U.D.E. where the queue was filling up with data while the mic was "off" (not being read, but still active)

This makes sense. But isn't this going to be done in usb_xblc_handle_reset?

hw/xbox/xblc.c Outdated
AUD_close_out(s->audio_be, s->out.voice);
AUD_close_in(s->audio_be, s->in.voice);
if (s->in.voice) {
SDL_PauseAudioStreamDevice(s->in.voice);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above. Do we really need to pause before destroying a stream?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that we don't, so I removed the call to SDL_PauseAudioStreamDevice

…oStream, and SDL_UnlockAudioStream, and SDL_GetAudioStreamAvailable.

Removed a redundant comment

Removed some asserts from usb_xblc_handle_data error conditions because the error conditions don't degrade the emulator. The errors are still being logged
…stead, I added some code to usb_xblc_handle_data to call SDL_ClearAudioStream on the mic stream if the number of available bytes exceeds an amount of data equivalent to 100 ms at the current sample rate.
usb_packet_copy(p, (void *)packet, chunk_len);
chunk_len = SDL_GetAudioStreamData(s->in.voice, s->in.packet,
MIN(sizeof(s->in.packet), to_process));
if (chunk_len < 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • If we break out here, should we be providing silence to the guest as before?
  • If SDL_GetAudioStreamData doesn't have enough data to provide, it will spin here and block emulation. Previous behavior was to pull as much as possible from the FIFO and provide silence for the rest of the packet. Blocking might be better to introduce some latency in order to avoid underruns, but totally blocking emulation here is not ideal. A simple solution to both underruns and blocking could be to just provide silence for a while until the stream has buffered at least a full packets worth of data plus some headroom on the input stream.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't see any harm in filling the buffer with silence if we don't have enough data to fill the buffer. We definitely don't want to wait for new data if we can avoid it

hw/xbox/xblc.c Outdated
SDL_AudioDeviceID device_id;
SDL_AudioStream *voice;
SDL_AudioSpec spec;
uint8_t packet[XBLC_MAX_PACKET];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the packet field anymore? Looks like it's only used usb_xblc_handle_data USB_TOKEN_IN case, so we can probably replace with a local buffer there

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only need it for the USB_TOKEN_IN case, so it can be replaced with a local buffer. It's only 48 bytes

hw/xbox/xblc.c Outdated

fifo8_create(&s->in.fifo, XBLC_FIFO_SIZE);
fifo8_create(&s->out.fifo, XBLC_FIFO_SIZE);
// What sample rate does the physical XBLC default to?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a TODO or FIXME or something else? If so, we should indicate as such. Any reason for picking the sample rate at index 1, or was this an arbitrary choice? Why not highest or lowest?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was a FIXME, but I went looking and, according to the source code for Ryzee119's HAWK XBLC replacement, the XBLC appears to default to 16 KHz, so I changed it to 16KHz here, and added a comment explaining where I got that number.

hw/xbox/xblc.c Outdated
24000 };

typedef struct XBLCStream {
SDL_AudioDeviceID device_id;
Copy link
Member

@mborgerson mborgerson Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the device_id field needed? It is set, but never read, from xblc_audio_channel_init

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll be needed for the UI changes, but those aren't in this PR so I'll remove it

…ta when there isn't enough data available to fill the request. Added code to fill the rest of the buffer with silence.

Changed the default sample rate of the XBLC to 16 KHz, and updated the comment to point to where I got that number from.

Removed the device_id and packet fields from the XBLCStream struct. Replaced the packet field with a local variable in the usb_xblc_handle_data function
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants