Compare commits

...

4 commits

Author SHA1 Message Date
Ryzee119 01867ee635
Merge afad59ad02 into 5a144a3fd3 2024-05-08 14:19:16 -04:00
Mason Thompson 5a144a3fd3
ci: Update actions to silence Node 16 warnings 2024-05-06 16:00:55 -07:00
Ryan Wendland afad59ad02 ui: Re-enable audiodev in windows 2024-03-15 11:45:21 -07:00
Ryzee119 0d24ddcc91 input: Add xbox live communicator support 2024-03-15 11:43:22 -07:00
5 changed files with 428 additions and 31 deletions

View file

@ -21,10 +21,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone tree
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Extract image metadata (tags, labels)
id: meta
uses: docker/metadata-action@v4.3.0
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
labels: |
@ -35,16 +35,16 @@ jobs:
type=ref,event=branch
type=sha
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.2.1
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v5
with:
context: ubuntu-win64-cross
push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}

View file

@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone tree
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
# On push to master, increment patch version and create a new tag on release
@ -48,7 +48,7 @@ jobs:
./scripts/archive-source.sh src.tar
gzip -1 src.tar
- name: Upload source package artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: src.tar.gz
path: src.tar.gz
@ -71,14 +71,14 @@ jobs:
steps:
- name: Download source package
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: src.tar.gz
- name: Extract source package
run: tar xf src.tar.gz
- name: Initialize compiler cache
id: cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: /tmp/xemu-ccache
key: cache-wincross-${{ runner.os }}-${{ matrix.configuration }}-${{ github.sha }}
@ -97,7 +97,7 @@ jobs:
$DOCKER_IMAGE_NAME \
bash -c "ccache -z; ./build.sh -p win64-cross ${{ matrix.build_param }} && ccache -s"
- name: Upload build artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: dist
@ -118,7 +118,7 @@ jobs:
artifact_name: xemu-win-release
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.artifact_name }}
@ -132,7 +132,7 @@ jobs:
7z a -tzip ../dist/${{ matrix.artifact_name }}.zip * "-xr!*.pdb"
7z a -tzip ../dist/${{ matrix.artifact_name }}-pdb.zip "-ir!*.pdb"
- name: Upload build artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}-pdb
path: dist
@ -155,13 +155,13 @@ jobs:
steps:
- name: Initialize compiler cache
id: cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: /tmp/xemu-ccache
key: cache-${{ runner.os }}-${{ matrix.configuration }}-${{ github.sha }}
restore-keys: cache-${{ runner.os }}-${{ matrix.configuration }}-
- name: Download source package
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: src.tar.gz
- name: Extract source package
@ -169,7 +169,7 @@ jobs:
mkdir src
tar -C src -xf src.tar.gz
- name: Clone Debian packaging
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: deb
path: debian-tmp
@ -232,7 +232,7 @@ jobs:
run: |
tar -czvf ${{ matrix.artifact_filename }} --transform "s#^dist#xemu#" dist
- name: Upload build artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.artifact_filename }}
@ -266,7 +266,7 @@ jobs:
artifact_filename: xemu-macos-arm64-release.zip
steps:
- name: Download source package
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: src.tar.gz
- name: Extract source package
@ -284,7 +284,7 @@ jobs:
python3 -m pip install pyyaml requests
- name: Initialize compiler, library cache
id: cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
xemu-ccache
@ -304,7 +304,7 @@ jobs:
zip -r ../${{ matrix.artifact_filename }} *
popd
- name: Upload build artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.artifact_filename }}
@ -318,12 +318,12 @@ jobs:
configuration: ["debug", "release"]
steps:
- name: Download x86_64 build
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: xemu-macos-x86_64-${{ matrix.configuration }}
path: xemu-macos-x86_64-${{ matrix.configuration }}
- name: Download arm64 build
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: xemu-macos-arm64-${{ matrix.configuration }}
path: xemu-macos-arm64-${{ matrix.configuration }}
@ -347,7 +347,7 @@ jobs:
zip -r ../xemu-macos-universal-${{ matrix.configuration }}.zip *
popd
- name: Upload build artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: xemu-macos-universal-${{ matrix.configuration }}
path: xemu-macos-universal-${{ matrix.configuration }}.zip
@ -358,7 +358,7 @@ jobs:
needs: [Ubuntu, macOSUniversal, WindowsPdb]
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
path: dist
- name: Extract source package
@ -411,7 +411,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download source package
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: src.tar.gz
- name: Extract source package
@ -419,7 +419,7 @@ jobs:
mkdir src
tar -C src -xf src.tar.gz
- name: Clone Debian packaging
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: deb
path: debian-tmp

View file

@ -15,6 +15,7 @@ specific_ss.add(files(
'xbox.c',
'xbox_pci.c',
'xid.c',
'xblc.c',
))
subdir('nv2a')
subdir('mcpx')

402
hw/xbox/xblc.c Normal file
View file

@ -0,0 +1,402 @@
/*
* QEMU USB Xbox Live Communicator (XBLC) Device
*
* Copyright (c) 2022 Ryan Wendland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "hw/qdev-properties.h"
#include "migration/vmstate.h"
#include "sysemu/sysemu.h"
#include "hw/hw.h"
#include "ui/console.h"
#include "hw/usb.h"
#include "hw/usb/desc.h"
#include "ui/xemu-input.h"
#include "audio/audio.h"
#include "qemu/fifo8.h"
//#define DEBUG_XBLC
#ifdef DEBUG_XBLC
#define DPRINTF printf
#else
#define DPRINTF(...)
#endif
#define TYPE_USB_XBLC "usb-xblc"
#define USB_XBLC(obj) OBJECT_CHECK(USBXBLCState, (obj), TYPE_USB_XBLC)
#define XBLC_STR "Microsoft Xbox Live Communicator"
#define XBLC_INTERFACE_CLASS 0x78
#define XBLC_INTERFACE_SUBCLASS 0x00
#define XBLC_EP_OUT 0x04
#define XBLC_EP_IN 0x05
#define XBLC_SET_SAMPLE_RATE 0x00
#define XBLC_SET_AGC 0x01
#define XBLC_MAX_PACKET 48
#define XBLC_FIFO_SIZE (XBLC_MAX_PACKET * 100) //~100 ms worth of audio at 16bit 24kHz
static const uint8_t silence[256] = {0};
static const uint16_t xblc_sample_rates[5] = {
8000, 11025, 16000, 22050, 24000
};
typedef struct USBXBLCState {
USBDevice dev;
uint8_t device_index;
uint8_t auto_gain_control;
uint16_t sample_rate;
QEMUSoundCard card;
struct audsettings as;
struct {
SWVoiceOut* voice;
uint8_t packet[XBLC_MAX_PACKET];
Fifo8 fifo;
} out;
struct {
SWVoiceIn *voice;
uint8_t packet[XBLC_MAX_PACKET];
Fifo8 fifo;
} in;
} USBXBLCState;
enum {
STR_MANUFACTURER = 1,
STR_PRODUCT,
STR_SERIALNUMBER,
};
static const USBDescStrings desc_strings = {
[STR_MANUFACTURER] = "xemu",
[STR_PRODUCT] = XBLC_STR,
[STR_SERIALNUMBER] = "1",
};
static const USBDescIface desc_iface[]= {
{
.bInterfaceNumber = 0,
.bNumEndpoints = 1,
.bInterfaceClass = XBLC_INTERFACE_CLASS,
.bInterfaceSubClass = XBLC_INTERFACE_SUBCLASS,
.bInterfaceProtocol = 0x00,
.eps = (USBDescEndpoint[]) {
{
.bEndpointAddress = USB_DIR_OUT | XBLC_EP_OUT,
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
.wMaxPacketSize = XBLC_MAX_PACKET,
.is_audio = 1,
.bInterval = 1,
.bRefresh = 0,
.bSynchAddress = 0,
}
},
},
{
.bInterfaceNumber = 1,
.bNumEndpoints = 1,
.bInterfaceClass = XBLC_INTERFACE_CLASS,
.bInterfaceSubClass = XBLC_INTERFACE_SUBCLASS,
.bInterfaceProtocol = 0x00,
.eps = (USBDescEndpoint[]) {
{
.bEndpointAddress = USB_DIR_IN | XBLC_EP_IN,
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
.wMaxPacketSize = XBLC_MAX_PACKET,
.is_audio = 1,
.bInterval = 1,
.bRefresh = 0,
.bSynchAddress = 0,
}
},
}
};
static const USBDescDevice desc_device = {
.bcdUSB = 0x0110,
.bMaxPacketSize0 = 8,
.bNumConfigurations = 1,
.confs = (USBDescConfig[]) {
{
.bNumInterfaces = 2,
.bConfigurationValue = 1,
.bmAttributes = USB_CFG_ATT_ONE,
.bMaxPower = 100,
.nif = ARRAY_SIZE(desc_iface),
.ifs = desc_iface,
},
},
};
static const USBDesc desc_xblc = {
.id = {
.idVendor = 0x045e,
.idProduct = 0x0283,
.bcdDevice = 0x0110,
.iManufacturer = STR_MANUFACTURER,
.iProduct = STR_PRODUCT,
.iSerialNumber = STR_SERIALNUMBER,
},
.full = &desc_device,
.str = desc_strings,
};
static void usb_xblc_handle_reset(USBDevice *dev)
{
USBXBLCState *s = (USBXBLCState *)dev;
DPRINTF("[XBLC] Reset\n");
fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);
}
static void output_callback(void *opaque, int avail)
{
USBXBLCState *s = (USBXBLCState *)opaque;
const uint8_t *data;
uint32_t processed, max_len;
// Not enough data to send, wait a bit longer, fill with silence for now
if (fifo8_num_used(&s->out.fifo) < XBLC_MAX_PACKET) {
do {
processed = AUD_write(s->out.voice, (void *)silence, ARRAY_SIZE(silence));
avail -= processed;
} while (avail > 0 && processed >= XBLC_MAX_PACKET);
return;
}
// Write speaker data into audio backend
while (avail > 0 && !fifo8_is_empty(&s->out.fifo)) {
max_len = MIN(fifo8_num_used(&s->out.fifo), avail);
data = fifo8_pop_buf(&s->out.fifo, max_len, &max_len);
processed = AUD_write(s->out.voice, (void *)data, max_len);
avail -= processed;
if (processed < max_len) return;
}
}
static void input_callback(void *opaque, int avail)
{
USBXBLCState *s = (USBXBLCState *)opaque;
uint32_t processed, max_len;
// Get microphone data from audio backend
while (avail > 0 && !fifo8_is_full(&s->in.fifo)) {
max_len = MIN(sizeof(s->in.packet), fifo8_num_free(&s->in.fifo));
processed = AUD_read(s->in.voice, s->in.packet, max_len);
avail -= processed;
fifo8_push_all(&s->in.fifo, s->in.packet, processed);
if (processed < max_len) return;
}
// Flush excess/old data - this can happen if the user program stops the iso transfers after it
// has setup the xblc.
while (avail > 0)
{
processed = AUD_read(s->in.voice, s->in.packet, XBLC_MAX_PACKET);
avail -= processed;
if (processed == 0) break;
}
}
static void xblc_audio_stream_init(USBDevice *dev, uint16_t sample_rate)
{
USBXBLCState *s = (USBXBLCState *)dev;
AUD_set_active_out(s->out.voice, FALSE);
AUD_set_active_in(s->in.voice, FALSE);
fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);
s->as.freq = sample_rate;
s->as.nchannels = 1;
s->as.fmt = AUDIO_FORMAT_S16;
s->as.endianness = 0;
s->out.voice = AUD_open_out(&s->card, s->out.voice, TYPE_USB_XBLC "-speaker",
s, output_callback, &s->as);
s->in.voice = AUD_open_in(&s->card, s->in.voice, TYPE_USB_XBLC "-microphone",
s, input_callback, &s->as);
AUD_set_active_out(s->out.voice, TRUE);
AUD_set_active_in(s->in.voice, TRUE);
DPRINTF("[XBLC] Init audio streams at %d Hz\n", sample_rate);
}
static void usb_xblc_handle_control(USBDevice *dev, USBPacket *p,
int request, int value, int index, int length, uint8_t *data)
{
USBXBLCState *s = (USBXBLCState *)dev;
if (usb_desc_handle_control(dev, p, request, value, index, length, data) >= 0) {
DPRINTF("[XBLC] USB Control request handled by usb_desc_handle_control\n");
return;
}
switch (request) {
case VendorInterfaceOutRequest | USB_REQ_SET_FEATURE:
if (index == XBLC_SET_SAMPLE_RATE)
{
uint8_t rate = value & 0xFF;
assert(rate < ARRAY_SIZE(xblc_sample_rates));
DPRINTF("[XBLC] Set Sample Rate to %04x\n", rate);
s->sample_rate = xblc_sample_rates[rate];
xblc_audio_stream_init(dev, s->sample_rate);
break;
}
else if (index == XBLC_SET_AGC)
{
DPRINTF("[XBLC] Set Auto Gain Control to %d\n", value);
s->auto_gain_control = (value) ? 1 : 0;
break;
}
// Fallthrough
default:
DPRINTF("[XBLC] USB stalled on request 0x%x value 0x%x\n", request, value);
p->status = USB_RET_STALL;
assert(false);
return;
}
}
static void usb_xblc_handle_data(USBDevice *dev, USBPacket *p)
{
USBXBLCState *s = (USBXBLCState *)dev;
uint32_t to_process, chunk_len;
switch (p->pid) {
case USB_TOKEN_IN:
// Microphone Data - Get data from fifo and copy into usb packet
assert(p->ep->nr == XBLC_EP_IN);
to_process = MIN(fifo8_num_used(&s->in.fifo), p->iov.size);
chunk_len = 0;
// fifo may not give us a contiguous packet, so may need multiple calls
while (to_process) {
const uint8_t *packet = fifo8_pop_buf(&s->in.fifo, to_process, &chunk_len);
usb_packet_copy(p, (void *)packet, chunk_len);
to_process -= chunk_len;
}
break;
case USB_TOKEN_OUT:
// Speaker data - get data from usb packet then push to fifo.
assert(p->ep->nr == XBLC_EP_OUT);
to_process = MIN(fifo8_num_free(&s->out.fifo), p->iov.size);
usb_packet_copy(p, s->out.packet, to_process);
fifo8_push_all(&s->out.fifo, s->out.packet, to_process);
break;
default:
//Iso cannot report STALL/HALT, but we shouldn't be here anyway.
assert(false);
break;
}
// Ensure we fill the entire packet regardless of if we have audio data so we don't
// cause an underrun error.
if (p->actual_length < p->iov.size)
usb_packet_copy(p, (void *)silence, p->iov.size - p->actual_length);
}
static void usb_xbox_communicator_unrealize(USBDevice *dev)
{
USBXBLCState *s = USB_XBLC(dev);
AUD_set_active_out(s->out.voice, false);
AUD_set_active_in(s->in.voice, false);
fifo8_destroy(&s->out.fifo);
fifo8_destroy(&s->in.fifo);
AUD_close_out(&s->card, s->out.voice);
AUD_close_in(&s->card, s->in.voice);
AUD_remove_card(&s->card);
}
static void usb_xblc_class_initfn(ObjectClass *klass, void *data)
{
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
uc->handle_reset = usb_xblc_handle_reset;
uc->handle_control = usb_xblc_handle_control;
uc->handle_data = usb_xblc_handle_data;
uc->handle_attach = usb_desc_attach;
}
static void usb_xbox_communicator_realize(USBDevice *dev, Error **errp)
{
USBXBLCState *s = USB_XBLC(dev);
usb_desc_create_serial(dev);
usb_desc_init(dev);
AUD_register_card(TYPE_USB_XBLC, &s->card);
fifo8_create(&s->in.fifo, XBLC_FIFO_SIZE);
fifo8_create(&s->out.fifo, XBLC_FIFO_SIZE);
}
static Property xblc_properties[] = {
DEFINE_PROP_UINT8("index", USBXBLCState, device_index, 0),
DEFINE_PROP_END_OF_LIST(),
};
static const VMStateDescription usb_xblc_vmstate = {
.name = TYPE_USB_XBLC,
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_USB_DEVICE(dev, USBXBLCState),
// FIXME
VMSTATE_END_OF_LIST()
},
};
static void usb_xbox_communicator_class_initfn(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
uc->product_desc = XBLC_STR;
uc->usb_desc = &desc_xblc;
uc->realize = usb_xbox_communicator_realize;
uc->unrealize = usb_xbox_communicator_unrealize;
usb_xblc_class_initfn(klass, data);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
dc->vmsd = &usb_xblc_vmstate;
device_class_set_props(dc, xblc_properties);
dc->desc = XBLC_STR;
}
static const TypeInfo info_xblc = {
.name = TYPE_USB_XBLC,
.parent = TYPE_USB_DEVICE,
.instance_size = sizeof(USBXBLCState),
.class_init = usb_xbox_communicator_class_initfn,
};
static void usb_xblc_register_types(void)
{
type_register_static(&info_xblc);
}
type_init(usb_xblc_register_types)

View file

@ -2876,12 +2876,6 @@ void qemu_init(int argc, char **argv)
fake_argv[fake_argc++] = strdup("-device");
fake_argv[fake_argc++] = strdup("usb-hub,port=1,ports=4");
#ifdef _WIN32
// FIXME: Create this dummy device to prevent logspam
fake_argv[fake_argc++] = strdup("-audiodev");
fake_argv[fake_argc++] = strdup("none,id=snd0");
#endif
for (int i = 1; i < argc; i++) {
if (argv[i] != NULL) {
fake_argv[fake_argc++] = argv[i];