mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-17 22:05:45 -04:00
dep/cubeb: Update to 1d66483
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2019 Nia Alarie
|
||||
* Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
@ -9,19 +9,14 @@
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb-internal.h"
|
||||
|
||||
#define BYTES_TO_FRAMES(bytes, channels) \
|
||||
(bytes / (channels * sizeof(int16_t)))
|
||||
|
||||
#define FRAMES_TO_BYTES(frames, channels) \
|
||||
(frames * (channels * sizeof(int16_t)))
|
||||
|
||||
/* Default to 4 + 1 for the default device. */
|
||||
#ifndef SUN_DEVICE_COUNT
|
||||
#define SUN_DEVICE_COUNT (5)
|
||||
@ -41,10 +36,6 @@
|
||||
#define SUN_DEFAULT_DEVICE "/dev/audio"
|
||||
#endif
|
||||
|
||||
#ifndef SUN_POLL_TIMEOUT
|
||||
#define SUN_POLL_TIMEOUT (1000)
|
||||
#endif
|
||||
|
||||
#ifndef SUN_BUFFER_FRAMES
|
||||
#define SUN_BUFFER_FRAMES (32)
|
||||
#endif
|
||||
@ -75,26 +66,26 @@ struct cubeb {
|
||||
struct cubeb_ops const * ops;
|
||||
};
|
||||
|
||||
struct sun_stream {
|
||||
char name[32];
|
||||
int fd;
|
||||
void * buf;
|
||||
struct audio_info info;
|
||||
unsigned frame_size; /* precision in bytes * channels */
|
||||
bool floating;
|
||||
};
|
||||
|
||||
struct cubeb_stream {
|
||||
struct cubeb * context;
|
||||
void * user_ptr;
|
||||
pthread_t thread;
|
||||
pthread_mutex_t mutex; /* protects running, volume, frames_written */
|
||||
int floating;
|
||||
int running;
|
||||
int play_fd;
|
||||
int record_fd;
|
||||
bool running;
|
||||
float volume;
|
||||
struct audio_info p_info; /* info for the play fd */
|
||||
struct audio_info r_info; /* info for the record fd */
|
||||
struct sun_stream play;
|
||||
struct sun_stream record;
|
||||
cubeb_data_callback data_cb;
|
||||
cubeb_state_callback state_cb;
|
||||
int16_t * play_buf;
|
||||
int16_t * record_buf;
|
||||
float * f_play_buf;
|
||||
float * f_record_buf;
|
||||
char input_name[32];
|
||||
char output_name[32];
|
||||
uint64_t frames_written;
|
||||
uint64_t blocks_written;
|
||||
};
|
||||
@ -312,18 +303,19 @@ sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
|
||||
{
|
||||
prinfo->channels = params->channels;
|
||||
prinfo->sample_rate = params->rate;
|
||||
prinfo->precision = 16;
|
||||
#ifdef AUDIO_ENCODING_SLINEAR_LE
|
||||
switch (params->format) {
|
||||
case CUBEB_SAMPLE_S16LE:
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
|
||||
prinfo->precision = 16;
|
||||
break;
|
||||
case CUBEB_SAMPLE_S16BE:
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
|
||||
prinfo->precision = 16;
|
||||
break;
|
||||
case CUBEB_SAMPLE_FLOAT32NE:
|
||||
stream->floating = 1;
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR;
|
||||
prinfo->precision = 32;
|
||||
break;
|
||||
default:
|
||||
LOG("Unsupported format");
|
||||
@ -333,10 +325,11 @@ sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
|
||||
switch (params->format) {
|
||||
case CUBEB_SAMPLE_S16NE:
|
||||
prinfo->encoding = AUDIO_ENCODING_LINEAR;
|
||||
prinfo->precision = 16;
|
||||
break;
|
||||
case CUBEB_SAMPLE_FLOAT32NE:
|
||||
stream->floating = 1;
|
||||
prinfo->encoding = AUDIO_ENCODING_LINEAR;
|
||||
prinfo->precision = 32;
|
||||
break;
|
||||
default:
|
||||
LOG("Unsupported format");
|
||||
@ -357,7 +350,7 @@ sun_stream_stop(cubeb_stream * s)
|
||||
{
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (s->running) {
|
||||
s->running = 0;
|
||||
s->running = false;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
pthread_join(s->thread, NULL);
|
||||
} else {
|
||||
@ -369,55 +362,52 @@ sun_stream_stop(cubeb_stream * s)
|
||||
static void
|
||||
sun_stream_destroy(cubeb_stream * s)
|
||||
{
|
||||
pthread_mutex_destroy(&s->mutex);
|
||||
sun_stream_stop(s);
|
||||
if (s->play_fd != -1) {
|
||||
close(s->play_fd);
|
||||
pthread_mutex_destroy(&s->mutex);
|
||||
if (s->play.fd != -1) {
|
||||
close(s->play.fd);
|
||||
}
|
||||
if (s->record_fd != -1) {
|
||||
close(s->record_fd);
|
||||
if (s->record.fd != -1) {
|
||||
close(s->record.fd);
|
||||
}
|
||||
free(s->f_play_buf);
|
||||
free(s->f_record_buf);
|
||||
free(s->play_buf);
|
||||
free(s->record_buf);
|
||||
free(s->play.buf);
|
||||
free(s->record.buf);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void
|
||||
sun_float_to_linear(float * in, int16_t * out,
|
||||
unsigned channels, long frames, float vol)
|
||||
sun_float_to_linear32(void * buf, unsigned sample_count, float vol)
|
||||
{
|
||||
unsigned i, sample_count = frames * channels;
|
||||
float multiplier = vol * 0x8000;
|
||||
float * in = buf;
|
||||
int32_t * out = buf;
|
||||
int32_t * tail = out + sample_count;
|
||||
|
||||
for (i = 0; i < sample_count; ++i) {
|
||||
int32_t sample = lrintf(in[i] * multiplier);
|
||||
if (sample < -0x8000) {
|
||||
out[i] = -0x8000;
|
||||
} else if (sample > 0x7fff) {
|
||||
out[i] = 0x7fff;
|
||||
} else {
|
||||
out[i] = sample;
|
||||
}
|
||||
while (out < tail) {
|
||||
float f = *(in++) * vol;
|
||||
if (f < -1.0)
|
||||
f = -1.0;
|
||||
else if (f > 1.0)
|
||||
f = 1.0;
|
||||
*(out++) = f * (float)INT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sun_linear_to_float(int16_t * in, float * out,
|
||||
unsigned channels, long frames)
|
||||
sun_linear32_to_float(void * buf, unsigned sample_count)
|
||||
{
|
||||
unsigned i, sample_count = frames * channels;
|
||||
int32_t * in = buf;
|
||||
float * out = buf;
|
||||
float * tail = out + sample_count;
|
||||
|
||||
for (i = 0; i < sample_count; ++i) {
|
||||
out[i] = (1.0 / 0x8000) * in[i];
|
||||
while (out < tail) {
|
||||
*(out++) = (1.0 / 0x80000000) * *(in++);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sun_linear_set_vol(int16_t * buf, unsigned channels, long frames, float vol)
|
||||
sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
|
||||
{
|
||||
unsigned i, sample_count = frames * channels;
|
||||
unsigned i;
|
||||
int32_t multiplier = vol * 0x8000;
|
||||
|
||||
for (i = 0; i < sample_count; ++i) {
|
||||
@ -445,41 +435,36 @@ sun_io_routine(void * arg)
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
if (s->floating) {
|
||||
if (s->record_fd != -1) {
|
||||
sun_linear_to_float(s->record_buf, s->f_record_buf,
|
||||
s->r_info.record.channels, SUN_BUFFER_FRAMES);
|
||||
}
|
||||
to_write = s->data_cb(s, s->user_ptr,
|
||||
s->f_record_buf, s->f_play_buf, SUN_BUFFER_FRAMES);
|
||||
if (to_write == CUBEB_ERROR) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
if (s->play_fd != -1) {
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
sun_float_to_linear(s->f_play_buf, s->play_buf,
|
||||
s->p_info.play.channels, to_write, s->volume);
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
}
|
||||
} else {
|
||||
to_write = s->data_cb(s, s->user_ptr,
|
||||
s->record_buf, s->play_buf, SUN_BUFFER_FRAMES);
|
||||
if (to_write == CUBEB_ERROR) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
if (s->play_fd != -1) {
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
sun_linear_set_vol(s->play_buf, s->p_info.play.channels, to_write, s->volume);
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
if (s->record.fd != -1 && s->record.floating) {
|
||||
sun_linear32_to_float(s->record.buf,
|
||||
s->record.info.record.channels * SUN_BUFFER_FRAMES);
|
||||
}
|
||||
to_write = s->data_cb(s, s->user_ptr,
|
||||
s->record.buf, s->play.buf, SUN_BUFFER_FRAMES);
|
||||
if (to_write == CUBEB_ERROR) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
if (s->play.fd != -1) {
|
||||
float vol;
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
vol = s->volume;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
if (s->play.floating) {
|
||||
sun_float_to_linear32(s->play.buf,
|
||||
s->play.info.play.channels * to_write, vol);
|
||||
} else {
|
||||
sun_linear16_set_vol(s->play.buf,
|
||||
s->play.info.play.channels * to_write, vol);
|
||||
}
|
||||
}
|
||||
if (to_write < SUN_BUFFER_FRAMES) {
|
||||
drain = 1;
|
||||
}
|
||||
to_write = s->play_fd != -1 ? to_write : 0;
|
||||
to_read = s->record_fd != -1 ? SUN_BUFFER_FRAMES : 0;
|
||||
to_write = s->play.fd != -1 ? to_write : 0;
|
||||
to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0;
|
||||
write_ofs = 0;
|
||||
read_ofs = 0;
|
||||
while (to_write > 0 || to_read > 0) {
|
||||
@ -487,27 +472,27 @@ sun_io_routine(void * arg)
|
||||
ssize_t n, frames;
|
||||
|
||||
if (to_write > 0) {
|
||||
bytes = FRAMES_TO_BYTES(to_write, s->p_info.play.channels);
|
||||
if ((n = write(s->play_fd, s->play_buf + write_ofs, bytes)) < 0) {
|
||||
bytes = to_write * s->play.frame_size;
|
||||
if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) < 0) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
frames = BYTES_TO_FRAMES(n, s->p_info.play.channels);
|
||||
frames = n / s->play.frame_size;
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
s->frames_written += frames;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
to_write -= frames;
|
||||
write_ofs += frames;
|
||||
write_ofs += n;
|
||||
}
|
||||
if (to_read > 0) {
|
||||
bytes = FRAMES_TO_BYTES(to_read, s->r_info.record.channels);
|
||||
if ((n = read(s->record_fd, s->record_buf + read_ofs, bytes)) < 0) {
|
||||
bytes = to_read * s->record.frame_size;
|
||||
if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, bytes)) < 0) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
frames = BYTES_TO_FRAMES(n, s->r_info.record.channels);
|
||||
frames = n / s->record.frame_size;
|
||||
to_read -= frames;
|
||||
read_ofs += frames;
|
||||
read_ofs += n;
|
||||
}
|
||||
}
|
||||
if (drain && state != CUBEB_STATE_ERROR) {
|
||||
@ -541,19 +526,19 @@ sun_stream_init(cubeb * context,
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
s->record_fd = -1;
|
||||
s->play_fd = -1;
|
||||
s->record.fd = -1;
|
||||
s->play.fd = -1;
|
||||
if (input_device != 0) {
|
||||
snprintf(s->input_name, sizeof(s->input_name),
|
||||
snprintf(s->record.name, sizeof(s->record.name),
|
||||
"/dev/audio%zu", (uintptr_t)input_device - 1);
|
||||
} else {
|
||||
snprintf(s->input_name, sizeof(s->input_name), "%s", SUN_DEFAULT_DEVICE);
|
||||
snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE);
|
||||
}
|
||||
if (output_device != 0) {
|
||||
snprintf(s->output_name, sizeof(s->output_name),
|
||||
snprintf(s->play.name, sizeof(s->play.name),
|
||||
"/dev/audio%zu", (uintptr_t)output_device - 1);
|
||||
} else {
|
||||
snprintf(s->output_name, sizeof(s->output_name), "%s", SUN_DEFAULT_DEVICE);
|
||||
snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE);
|
||||
}
|
||||
if (input_stream_params != NULL) {
|
||||
if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
@ -561,22 +546,23 @@ sun_stream_init(cubeb * context,
|
||||
ret = CUBEB_ERROR_NOT_SUPPORTED;
|
||||
goto error;
|
||||
}
|
||||
if (s->record_fd == -1) {
|
||||
if ((s->record_fd = open(s->input_name, O_RDONLY)) == -1) {
|
||||
LOG("Audio device cannot be opened as read-only");
|
||||
if (s->record.fd == -1) {
|
||||
if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
|
||||
LOG("Audio device could not be opened as read-only");
|
||||
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
AUDIO_INITINFO(&s->r_info);
|
||||
AUDIO_INITINFO(&s->record.info);
|
||||
#ifdef AUMODE_RECORD
|
||||
s->r_info.mode = AUMODE_RECORD;
|
||||
s->record.info.mode = AUMODE_RECORD;
|
||||
#endif
|
||||
if ((ret = sun_copy_params(s->record_fd, s, input_stream_params,
|
||||
&s->r_info, &s->r_info.record)) != CUBEB_OK) {
|
||||
if ((ret = sun_copy_params(s->record.fd, s, input_stream_params,
|
||||
&s->record.info, &s->record.info.record)) != CUBEB_OK) {
|
||||
LOG("Setting record params failed");
|
||||
goto error;
|
||||
}
|
||||
s->record.floating = (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
|
||||
}
|
||||
if (output_stream_params != NULL) {
|
||||
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
@ -584,22 +570,23 @@ sun_stream_init(cubeb * context,
|
||||
ret = CUBEB_ERROR_NOT_SUPPORTED;
|
||||
goto error;
|
||||
}
|
||||
if (s->play_fd == -1) {
|
||||
if ((s->play_fd = open(s->output_name, O_WRONLY)) == -1) {
|
||||
LOG("Audio device cannot be opened as write-only");
|
||||
if (s->play.fd == -1) {
|
||||
if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
|
||||
LOG("Audio device could not be opened as write-only");
|
||||
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
AUDIO_INITINFO(&s->p_info);
|
||||
AUDIO_INITINFO(&s->play.info);
|
||||
#ifdef AUMODE_PLAY
|
||||
s->p_info.mode = AUMODE_PLAY;
|
||||
s->play.info.mode = AUMODE_PLAY;
|
||||
#endif
|
||||
if ((ret = sun_copy_params(s->play_fd, s, output_stream_params,
|
||||
&s->p_info, &s->p_info.play)) != CUBEB_OK) {
|
||||
if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
|
||||
&s->play.info, &s->play.info.play)) != CUBEB_OK) {
|
||||
LOG("Setting play params failed");
|
||||
goto error;
|
||||
}
|
||||
s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
|
||||
}
|
||||
s->context = context;
|
||||
s->volume = 1.0;
|
||||
@ -610,28 +597,20 @@ sun_stream_init(cubeb * context,
|
||||
LOG("Failed to create mutex");
|
||||
goto error;
|
||||
}
|
||||
if (s->play_fd != -1 && (s->play_buf = calloc(SUN_BUFFER_FRAMES,
|
||||
s->p_info.play.channels * sizeof(int16_t))) == NULL) {
|
||||
s->play.frame_size = s->play.info.play.channels *
|
||||
(s->play.info.play.precision / 8);
|
||||
if (s->play.fd != -1 &&
|
||||
(s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
if (s->record_fd != -1 && (s->record_buf = calloc(SUN_BUFFER_FRAMES,
|
||||
s->r_info.record.channels * sizeof(int16_t))) == NULL) {
|
||||
s->record.frame_size = s->record.info.record.channels *
|
||||
(s->record.info.record.precision / 8);
|
||||
if (s->record.fd != -1 &&
|
||||
(s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
if (s->floating) {
|
||||
if (s->play_fd != -1 && (s->f_play_buf = calloc(SUN_BUFFER_FRAMES,
|
||||
s->p_info.play.channels * sizeof(float))) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
if (s->record_fd != -1 && (s->f_record_buf = calloc(SUN_BUFFER_FRAMES,
|
||||
s->r_info.record.channels * sizeof(float))) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
*stream = s;
|
||||
return CUBEB_OK;
|
||||
error:
|
||||
@ -644,7 +623,7 @@ error:
|
||||
static int
|
||||
sun_stream_start(cubeb_stream * s)
|
||||
{
|
||||
s->running = 1;
|
||||
s->running = true;
|
||||
if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
|
||||
LOG("Couldn't create thread");
|
||||
return CUBEB_ERROR;
|
||||
@ -658,12 +637,11 @@ sun_stream_get_position(cubeb_stream * s, uint64_t * position)
|
||||
#ifdef AUDIO_GETOOFFS
|
||||
struct audio_offset offset;
|
||||
|
||||
if (ioctl(s->play_fd, AUDIO_GETOOFFS, &offset) == -1) {
|
||||
if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
s->blocks_written += offset.deltablks;
|
||||
*position = BYTES_TO_FRAMES(s->blocks_written * s->p_info.blocksize,
|
||||
s->p_info.play.channels);
|
||||
*position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size;
|
||||
return CUBEB_OK;
|
||||
#else
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
@ -674,22 +652,21 @@ sun_stream_get_position(cubeb_stream * s, uint64_t * position)
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
|
||||
sun_stream_get_latency(cubeb_stream * s, uint32_t * latency)
|
||||
{
|
||||
#ifdef AUDIO_GETBUFINFO
|
||||
struct audio_info info;
|
||||
|
||||
if (ioctl(stream->play_fd, AUDIO_GETBUFINFO, &info) == -1) {
|
||||
if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
*latency = BYTES_TO_FRAMES(info.play.seek + info.blocksize,
|
||||
info.play.channels);
|
||||
*latency = (info.play.seek + info.blocksize) / s->play.frame_size;
|
||||
return CUBEB_OK;
|
||||
#else
|
||||
cubeb_stream_params params;
|
||||
|
||||
params.rate = stream->p_info.play.sample_rate;
|
||||
params.rate = s->play.info.play.sample_rate;
|
||||
|
||||
return sun_get_min_latency(NULL, params, latency);
|
||||
#endif
|
||||
@ -711,10 +688,10 @@ sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
|
||||
if (*device == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
(*device)->input_name = stream->record_fd != -1 ?
|
||||
strdup(stream->input_name) : NULL;
|
||||
(*device)->output_name = stream->play_fd != -1 ?
|
||||
strdup(stream->output_name) : NULL;
|
||||
(*device)->input_name = stream->record.fd != -1 ?
|
||||
strdup(stream->record.name) : NULL;
|
||||
(*device)->output_name = stream->play.fd != -1 ?
|
||||
strdup(stream->play.name) : NULL;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
@ -744,7 +721,9 @@ static struct cubeb_ops const sun_ops = {
|
||||
.stream_reset_default_device = NULL,
|
||||
.stream_get_position = sun_stream_get_position,
|
||||
.stream_get_latency = sun_stream_get_latency,
|
||||
.stream_get_input_latency = NULL,
|
||||
.stream_set_volume = sun_stream_set_volume,
|
||||
.stream_set_name = NULL,
|
||||
.stream_get_current_device = sun_get_current_device,
|
||||
.stream_device_destroy = sun_stream_device_destroy,
|
||||
.stream_register_device_changed_callback = NULL,
|
||||
|
Reference in New Issue
Block a user