mirror of https://github.com/Genymobile/scrcpy
Implement audio forwarding
Use Android Open Accessory 2 to redirect the device audio output to the computer, creating a new audio input source. Record this new source and play it to the default output. <https://source.android.com/devices/accessories/aoa2#audio-support>audio_old
parent
36a94137b5
commit
2bbf650758
@ -0,0 +1,206 @@
|
||||
#include "aoa.h"
|
||||
|
||||
#include "command.h" // must be first to include "winsock2.h" before "windows.h"
|
||||
#include <libusb-1.0/libusb.h>
|
||||
#include "log.h"
|
||||
|
||||
// <https://source.android.com/devices/accessories/aoa2>
|
||||
#define AOA_GET_PROTOCOL 51
|
||||
#define AOA_START_ACCESSORY 53
|
||||
#define AOA_SET_AUDIO_MODE 58
|
||||
|
||||
#define AUDIO_MODE_NO_AUDIO 0
|
||||
#define AUDIO_MODE_S16LSB_STEREO_44100HZ 1
|
||||
|
||||
#define DEFAULT_TIMEOUT 1000
|
||||
|
||||
typedef struct control_params {
|
||||
uint8_t request_type;
|
||||
uint8_t request;
|
||||
uint16_t value;
|
||||
uint16_t index;
|
||||
unsigned char *data;
|
||||
uint16_t length;
|
||||
unsigned int timeout;
|
||||
} control_params;
|
||||
|
||||
static void log_libusb_error(enum libusb_error errcode) {
|
||||
LOGE("%s", libusb_strerror(errcode));
|
||||
}
|
||||
|
||||
static SDL_bool control_transfer(libusb_device_handle *handle, control_params *params) {
|
||||
int r = libusb_control_transfer(handle,
|
||||
params->request_type,
|
||||
params->request,
|
||||
params->value,
|
||||
params->index,
|
||||
params->data,
|
||||
params->length,
|
||||
params->timeout);
|
||||
if (r < 0) {
|
||||
log_libusb_error(r);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
static SDL_bool get_serial(libusb_device *device, struct libusb_device_descriptor *desc, unsigned char *data, int length) {
|
||||
|
||||
libusb_device_handle *handle;
|
||||
int r;
|
||||
if ((r = libusb_open(device, &handle))) {
|
||||
// silently ignore
|
||||
LOGD("USB: cannot open device %04x:%04x (%s)", desc->idVendor, desc->idProduct, libusb_strerror(r));
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
if (!desc->iSerialNumber) {
|
||||
LOGD("USB: device %04x:%04x has no serial number available", desc->idVendor, desc->idProduct);
|
||||
libusb_close(handle);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
if ((r = libusb_get_string_descriptor_ascii(handle, desc->iSerialNumber, data, length)) <= 0) {
|
||||
// silently ignore
|
||||
LOGD("USB: cannot read serial of device %04x:%04x (%s)", desc->idVendor, desc->idProduct, libusb_strerror(r));
|
||||
libusb_close(handle);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
data[length - 1] = '\0'; // just in case
|
||||
|
||||
libusb_close(handle);
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
static libusb_device *find_device(const char *serial) {
|
||||
libusb_device **list;
|
||||
libusb_device *found = NULL;
|
||||
ssize_t cnt = libusb_get_device_list(NULL, &list);
|
||||
ssize_t i = 0;
|
||||
if (cnt < 0) {
|
||||
log_libusb_error(cnt);
|
||||
return NULL;
|
||||
}
|
||||
for (i = 0; i < cnt; ++i) {
|
||||
libusb_device *device = list[i];
|
||||
|
||||
struct libusb_device_descriptor desc;
|
||||
libusb_get_device_descriptor(device, &desc);
|
||||
|
||||
char usb_serial[128];
|
||||
if (get_serial(device, &desc, (unsigned char *) usb_serial, sizeof(usb_serial))) {
|
||||
if (!strncmp(serial, usb_serial, sizeof(usb_serial))) {
|
||||
libusb_ref_device(device);
|
||||
found = device;
|
||||
LOGD("USB device with serial %s found: %04x:%04x", serial, desc.idVendor, desc.idProduct);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
return found;
|
||||
}
|
||||
|
||||
static SDL_bool aoa_get_protocol(libusb_device_handle *handle, uint16_t *version) {
|
||||
unsigned char data[2];
|
||||
control_params params = {
|
||||
.request_type = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR,
|
||||
.request = AOA_GET_PROTOCOL,
|
||||
.value = 0,
|
||||
.index = 0,
|
||||
.data = data,
|
||||
.length = sizeof(data),
|
||||
.timeout = DEFAULT_TIMEOUT
|
||||
};
|
||||
if (control_transfer(handle, ¶ms)) {
|
||||
// little endian
|
||||
*version = (data[1] << 8) | data[0];
|
||||
return SDL_TRUE;
|
||||
}
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
static SDL_bool set_audio_mode(libusb_device_handle *handle, uint16_t mode) {
|
||||
control_params params = {
|
||||
.request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR,
|
||||
.request = AOA_SET_AUDIO_MODE,
|
||||
// <https://source.android.com/devices/accessories/aoa2.html#audio-support>
|
||||
.value = mode,
|
||||
.index = 0, // unused
|
||||
.data = NULL,
|
||||
.length = 0,
|
||||
.timeout = DEFAULT_TIMEOUT
|
||||
};
|
||||
return control_transfer(handle, ¶ms);
|
||||
}
|
||||
|
||||
static SDL_bool start_accessory(libusb_device_handle *handle) {
|
||||
control_params params = {
|
||||
.request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR,
|
||||
.request = AOA_START_ACCESSORY,
|
||||
.value = 0, // unused
|
||||
.index = 0, // unused
|
||||
.data = NULL,
|
||||
.length = 0,
|
||||
.timeout = DEFAULT_TIMEOUT
|
||||
};
|
||||
return control_transfer(handle, ¶ms);
|
||||
}
|
||||
|
||||
SDL_bool aoa_init(void) {
|
||||
return !libusb_init(NULL);
|
||||
}
|
||||
|
||||
void aoa_exit(void) {
|
||||
libusb_exit(NULL);
|
||||
}
|
||||
|
||||
SDL_bool aoa_forward_audio(const char *serial, SDL_bool forward) {
|
||||
LOGD("%s audio accessory...", forward ? "Enabling" : "Disabling");
|
||||
libusb_device *device = find_device(serial);
|
||||
if (!device) {
|
||||
LOGE("Cannot find USB device having serial %s", serial);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
SDL_bool ret = SDL_FALSE;
|
||||
|
||||
libusb_device_handle *handle;
|
||||
int r = libusb_open(device, &handle);
|
||||
if (r) {
|
||||
log_libusb_error(r);
|
||||
goto finally_unref_device;
|
||||
}
|
||||
|
||||
uint16_t version;
|
||||
if (!aoa_get_protocol(handle, &version)) {
|
||||
LOGE("Cannot get AOA protocol version");
|
||||
goto finally_close_handle;
|
||||
}
|
||||
|
||||
LOGD("Device AOA version: %" PRIu16 "\n", version);
|
||||
if (version < 2) {
|
||||
LOGE("Device does not support AOA 2: %" PRIu16, version);
|
||||
goto finally_close_handle;
|
||||
}
|
||||
|
||||
uint16_t mode = forward ? AUDIO_MODE_S16LSB_STEREO_44100HZ : AUDIO_MODE_NO_AUDIO;
|
||||
if (!set_audio_mode(handle, mode)) {
|
||||
LOGE("Cannot set audio mode: %" PRIu16, mode);
|
||||
goto finally_close_handle;
|
||||
}
|
||||
|
||||
if (!start_accessory(handle)) {
|
||||
LOGE("Cannot start accessory");
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
ret = SDL_TRUE;
|
||||
|
||||
finally_close_handle:
|
||||
libusb_close(handle);
|
||||
finally_unref_device:
|
||||
libusb_unref_device(device);
|
||||
|
||||
return ret;
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
#ifndef AOA_H
|
||||
#define AOA_H
|
||||
|
||||
#include <SDL2/SDL_stdinc.h>
|
||||
|
||||
#define AUDIO_MODE_NO_AUDIO 0
|
||||
#define AUDIO_MODE_S16LSB_STEREO_44100HZ 1
|
||||
|
||||
SDL_bool aoa_init(void);
|
||||
void aoa_exit(void);
|
||||
|
||||
// serial must not be NULL
|
||||
SDL_bool aoa_forward_audio(const char *serial, SDL_bool forward);
|
||||
|
||||
#endif
|
@ -0,0 +1,205 @@
|
||||
#include "audio.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include "aoa.h"
|
||||
#include "command.h"
|
||||
#include "log.h"
|
||||
|
||||
SDL_bool sdl_audio_init(void) {
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||
LOGC("Could not initialize SDL audio: %s", SDL_GetError());
|
||||
return SDL_FALSE;
|
||||
}
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
static void init_audio_spec(SDL_AudioSpec *spec) {
|
||||
SDL_zero(*spec);
|
||||
spec->freq = 44100;
|
||||
spec->format = AUDIO_S16LSB;
|
||||
spec->channels = 2;
|
||||
spec->samples = 1024;
|
||||
}
|
||||
|
||||
SDL_bool audio_player_init(struct audio_player *player, const char *serial) {
|
||||
player->serial = SDL_strdup(serial);
|
||||
return !!player->serial;
|
||||
}
|
||||
|
||||
void audio_player_destroy(struct audio_player *player) {
|
||||
SDL_free((void *) player->serial);
|
||||
}
|
||||
|
||||
static void audio_input_callback(void *userdata, Uint8 *stream, int len) {
|
||||
struct audio_player *player = userdata;
|
||||
if (SDL_QueueAudio(player->output_device, stream, len)) {
|
||||
LOGE("Cannot queue audio: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
static int get_matching_audio_device(const char *serial, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
LOGD("Audio input #%d: %s", i, SDL_GetAudioDeviceName(i, 1));
|
||||
}
|
||||
|
||||
char model[128];
|
||||
int r = adb_read_model(serial, model, sizeof(model));
|
||||
if (r <= 0) {
|
||||
LOGE("Cannot read Android device model");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOGD("Device model is: %s", model);
|
||||
|
||||
// iterate backwards since the matching device is probably the last one
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
// model is a NUL-terminated string
|
||||
const char *name = SDL_GetAudioDeviceName(i, 1);
|
||||
if (strstr(name, model)) {
|
||||
// the device name contains the device model, we found it!
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static SDL_AudioDeviceID open_accessory_audio_input(struct audio_player *player) {
|
||||
int count = SDL_GetNumAudioDevices(1);
|
||||
if (!count) {
|
||||
LOGE("No audio input source found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int selected = get_matching_audio_device(player->serial, count);
|
||||
if (selected == -1) {
|
||||
LOGE("Cannot find the Android accessory audio input source");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *selected_name = SDL_GetAudioDeviceName(selected, 1);
|
||||
LOGI("Selecting audio input source: %s", selected_name);
|
||||
|
||||
SDL_AudioSpec spec;
|
||||
init_audio_spec(&spec);
|
||||
spec.callback = audio_input_callback;
|
||||
spec.userdata = player;
|
||||
|
||||
int id = SDL_OpenAudioDevice(selected_name, 1, &spec, NULL, 0);
|
||||
if (!id) {
|
||||
LOGE("Cannot open audio input: %s", SDL_GetError());
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
static SDL_AudioDeviceID open_default_audio_output() {
|
||||
SDL_AudioSpec spec;
|
||||
init_audio_spec(&spec);
|
||||
int id = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0);
|
||||
if (!id) {
|
||||
LOGE("Cannot open audio output: %s", SDL_GetError());
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
SDL_bool audio_player_open(struct audio_player *player) {
|
||||
player->output_device = open_default_audio_output();
|
||||
if (!player->output_device) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
player->input_device = open_accessory_audio_input(player);
|
||||
if (!player->input_device) {
|
||||
SDL_CloseAudioDevice(player->output_device);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
static void audio_player_set_paused(struct audio_player *player, SDL_bool paused) {
|
||||
SDL_PauseAudioDevice(player->input_device, paused);
|
||||
SDL_PauseAudioDevice(player->output_device, paused);
|
||||
}
|
||||
|
||||
void audio_player_play(struct audio_player *player) {
|
||||
audio_player_set_paused(player, SDL_FALSE);
|
||||
}
|
||||
|
||||
void audio_player_pause(struct audio_player *player) {
|
||||
audio_player_set_paused(player, SDL_TRUE);
|
||||
}
|
||||
|
||||
void audio_player_close(struct audio_player *player) {
|
||||
SDL_CloseAudioDevice(player->input_device);
|
||||
SDL_CloseAudioDevice(player->output_device);
|
||||
}
|
||||
|
||||
SDL_bool audio_forwarding_start(struct audio_player *player, const char *serial) {
|
||||
if (!aoa_init()) {
|
||||
LOGE("Cannot initialize AOA");
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
char serialno[128];
|
||||
if (!serial) {
|
||||
LOGD("No serial provided, request it to the device");
|
||||
int r = adb_read_serialno(NULL, serialno, sizeof(serialno));
|
||||
if (r <= 0) {
|
||||
LOGE("Cannot read serial from the device");
|
||||
goto error_aoa_exit;
|
||||
}
|
||||
LOGD("Device serial is %s", serialno);
|
||||
serial = serialno;
|
||||
}
|
||||
|
||||
if (!audio_player_init(player, serial)) {
|
||||
LOGE("Cannot initialize audio player");
|
||||
goto error_aoa_exit;
|
||||
}
|
||||
|
||||
// adb connection will be reset!
|
||||
if (!aoa_forward_audio(player->serial, SDL_TRUE)) {
|
||||
LOGE("AOA audio forwarding failed");
|
||||
goto error_destroy_player;
|
||||
}
|
||||
|
||||
LOGI("Audio accessory enabled");
|
||||
|
||||
if (!sdl_audio_init()) {
|
||||
goto error_disable_audio_forwarding;
|
||||
}
|
||||
|
||||
LOGI("Waiting 2s for USB reconfiguration...");
|
||||
SDL_Delay(2000);
|
||||
|
||||
if (!audio_player_open(player)) {
|
||||
goto error_disable_audio_forwarding;
|
||||
}
|
||||
|
||||
audio_player_play(player);
|
||||
return SDL_TRUE;
|
||||
|
||||
error_disable_audio_forwarding:
|
||||
if (!aoa_forward_audio(serial, SDL_FALSE)) {
|
||||
LOGW("Cannot disable audio forwarding");
|
||||
}
|
||||
error_destroy_player:
|
||||
audio_player_destroy(player);
|
||||
error_aoa_exit:
|
||||
aoa_exit();
|
||||
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
void audio_forwarding_stop(struct audio_player *player) {
|
||||
audio_player_close(player);
|
||||
|
||||
if (aoa_forward_audio(player->serial, SDL_FALSE)) {
|
||||
LOGI("Audio forwarding disabled");
|
||||
} else {
|
||||
LOGW("Cannot disable audio forwarding");
|
||||
}
|
||||
aoa_exit();
|
||||
|
||||
audio_player_destroy(player);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
#ifndef AUDIO_H
|
||||
#define AUDIO_H
|
||||
|
||||
#include <SDL2/SDL_audio.h>
|
||||
#include <SDL2/SDL_stdinc.h>
|
||||
|
||||
struct audio_player {
|
||||
const char *serial;
|
||||
SDL_AudioDeviceID input_device;
|
||||
SDL_AudioDeviceID output_device;
|
||||
};
|
||||
|
||||
SDL_bool sdl_audio_init(void);
|
||||
|
||||
// serial must not be NULL
|
||||
SDL_bool audio_player_init(struct audio_player *player, const char *serial);
|
||||
void audio_player_destroy(struct audio_player *player);
|
||||
|
||||
SDL_bool audio_player_open(struct audio_player *player);
|
||||
void audio_player_close(struct audio_player *player);
|
||||
|
||||
void audio_player_play(struct audio_player *player);
|
||||
void audio_player_pause(struct audio_player *player);
|
||||
|
||||
// for convenience, these functions handle everything
|
||||
SDL_bool audio_forwarding_start(struct audio_player *player, const char *serial);
|
||||
void audio_forwarding_stop(struct audio_player *player);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue