spice/server/sound.cpp
Frediano Ziglio 176970f3f1 red-channel-client: Remove GObject type
Make all RedChannelClient hierarchy a C++ class.
This allows to use virtual methods.
Added a normal contructor instead or properties and g_object_new.

As we remove GObject conversion macros I added a macro XXX_CAST
to create a function to replace the old macro.
They will be removed when more type safety is introduced.

There's a new SPICE_CXX_GLIB_ALLOCATOR macro in red-common.h.
This macro, added to a class define the class allocator allowing
to use, in this case, GLib for allocation. This to avoid C++ library
dependency and to initialize all structure to 0 (not all fields
are manually initialized, will be improved with more encapsulation).

Currently the methods are mainly public, access will be modified
when more encapsulation (all functions in method) are done.

Some classes are now defined in the header, C++ uses access to
limit accessibility but for efficiency and type safety/inline and
other features require types to be defined in the headers.

Some fields were moved from XxxPrivate structure to class, C++
has accessibility.

Many destructors are defined as protected to forbid the use of
stack, this as these objects uses internal reference counting
to have normal pointers. Maybe in the future pointers like
std::shared_ptr could be used instead.

Reference counting is now implemented very easily using atomic
operations.

Signed-off-by: Frediano Ziglio <fziglio@redhat.com>
2020-05-01 06:58:09 +01:00

1442 lines
43 KiB
C++

/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2009 Red Hat, Inc.
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.1 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 <config.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#ifndef _WIN32
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#endif
#include <common/generated_server_marshallers.h>
#include <common/snd_codec.h>
#include "spice.h"
#include "red-common.h"
#include "main-channel.h"
#include "reds.h"
#include "red-channel-client.h"
#include "red-client.h"
#include "sound.h"
#include "main-channel-client.h"
#define SND_RECEIVE_BUF_SIZE (16 * 1024 * 2)
#define RECORD_SAMPLES_SIZE (SND_RECEIVE_BUF_SIZE >> 2)
enum SndCommand {
SND_MIGRATE,
SND_CTRL,
SND_VOLUME,
SND_MUTE,
SND_END_COMMAND,
};
enum PlaybackCommand {
SND_PLAYBACK_MODE = SND_END_COMMAND,
SND_PLAYBACK_PCM,
SND_PLAYBACK_LATENCY,
};
#define SND_MIGRATE_MASK (1 << SND_MIGRATE)
#define SND_CTRL_MASK (1 << SND_CTRL)
#define SND_VOLUME_MASK (1 << SND_VOLUME)
#define SND_MUTE_MASK (1 << SND_MUTE)
#define SND_VOLUME_MUTE_MASK (SND_VOLUME_MASK|SND_MUTE_MASK)
#define SND_PLAYBACK_MODE_MASK (1 << SND_PLAYBACK_MODE)
#define SND_PLAYBACK_PCM_MASK (1 << SND_PLAYBACK_PCM)
#define SND_PLAYBACK_LATENCY_MASK ( 1 << SND_PLAYBACK_LATENCY)
typedef struct SndChannelClient SndChannelClient;
typedef struct SndChannel SndChannel;
typedef struct PlaybackChannelClient PlaybackChannelClient;
typedef struct RecordChannelClient RecordChannelClient;
typedef struct AudioFrame AudioFrame;
typedef struct AudioFrameContainer AudioFrameContainer;
typedef struct SpicePlaybackState PlaybackChannel;
typedef struct SpiceRecordState RecordChannel;
typedef void (*snd_channel_on_message_done_proc)(SndChannelClient *client);
struct PersistentPipeItem: public RedPipeItem
{
SndChannelClient *client;
};
/* Connects an audio client to a Spice client */
class SndChannelClient: public RedChannelClient
{
public:
using RedChannelClient::RedChannelClient;
bool active;
bool client_active;
uint32_t command;
/* we don't expect very big messages so don't allocate too much
* bytes, data will be cached in RecordChannelClient::samples */
uint8_t receive_buf[SND_CODEC_MAX_FRAME_BYTES + 64];
PersistentPipeItem persistent_pipe_item;
snd_channel_on_message_done_proc on_message_done;
virtual bool config_socket() override;
virtual uint8_t *alloc_recv_buf(uint16_t type, uint32_t size) override;
virtual void release_recv_buf(uint16_t type, uint32_t size, uint8_t *msg) override;
};
static void snd_playback_alloc_frames(PlaybackChannelClient *playback);
enum {
RED_PIPE_ITEM_PERSISTENT = RED_PIPE_ITEM_TYPE_CHANNEL_BASE,
};
struct AudioFrame {
uint32_t time;
uint32_t samples[SND_CODEC_MAX_FRAME_SIZE];
PlaybackChannelClient *client;
AudioFrame *next;
AudioFrameContainer *container;
bool allocated;
};
#define NUM_AUDIO_FRAMES 3
struct AudioFrameContainer
{
int refs;
AudioFrame items[NUM_AUDIO_FRAMES];
};
class PlaybackChannelClient final: public SndChannelClient
{
protected:
~PlaybackChannelClient();
public:
PlaybackChannelClient(RedChannel *channel,
RedClient *client,
RedStream *stream,
RedChannelCapabilities *caps);
virtual bool init() override;
AudioFrameContainer *frames = nullptr;
AudioFrame *free_frames = nullptr;
AudioFrame *in_progress = nullptr; /* Frame being sent to the client */
AudioFrame *pending_frame = nullptr; /* Next frame to send to the client */
uint32_t mode = SPICE_AUDIO_DATA_MODE_RAW;
uint32_t latency = 0;
SndCodec codec = nullptr;
uint8_t encode_buf[SND_CODEC_MAX_COMPRESSED_BYTES];
};
typedef struct SpiceVolumeState {
uint16_t *volume;
uint8_t volume_nchannels;
int mute;
} SpiceVolumeState;
#define TYPE_SND_CHANNEL snd_channel_get_type()
#define SND_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_SND_CHANNEL, SndChannel))
GType snd_channel_get_type(void) G_GNUC_CONST;
/* Base class for SpicePlaybackState and SpiceRecordState */
struct SndChannel: public RedChannel
{
bool active;
SpiceVolumeState volume;
uint32_t frequency;
};
typedef struct SndChannelClass {
RedChannelClass parent_class;
} SndChannelClass;
G_DEFINE_TYPE(SndChannel, snd_channel, RED_TYPE_CHANNEL)
#define TYPE_PLAYBACK_CHANNEL playback_channel_get_type()
#define PLAYBACK_CHANNEL(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_PLAYBACK_CHANNEL, PlaybackChannel))
GType playback_channel_get_type(void) G_GNUC_CONST;
struct SpicePlaybackState final: public SndChannel
{
};
typedef struct PlaybackChannelClass {
SndChannelClass parent_class;
} PlaybackChannelClass;
G_DEFINE_TYPE(PlaybackChannel, playback_channel, TYPE_SND_CHANNEL)
#define TYPE_RECORD_CHANNEL record_channel_get_type()
#define RECORD_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_RECORD_CHANNEL, RecordChannel))
GType record_channel_get_type(void) G_GNUC_CONST;
struct SpiceRecordState final: public SndChannel
{
};
typedef struct RecordChannelClass {
SndChannelClass parent_class;
} RecordChannelClass;
G_DEFINE_TYPE(RecordChannel, record_channel, TYPE_SND_CHANNEL)
class RecordChannelClient final: public SndChannelClient
{
protected:
~RecordChannelClient();
public:
using SndChannelClient::SndChannelClient;
virtual bool init() override;
uint32_t samples[RECORD_SAMPLES_SIZE];
uint32_t write_pos = 0;
uint32_t read_pos = 0;
uint32_t mode = SPICE_AUDIO_DATA_MODE_RAW;
uint32_t mode_time = 0;
uint32_t start_time = 0;
SndCodec codec = nullptr;
uint8_t decode_buf[SND_CODEC_MAX_FRAME_BYTES];
};
/* A list of all Spice{Playback,Record}State objects */
static GList *snd_channels;
static void snd_send(SndChannelClient * client);
/* sound channels only support a single client */
static SndChannelClient *snd_channel_get_client(SndChannel *channel)
{
GList *clients = channel->get_clients();
if (clients == NULL) {
return NULL;
}
return (SndChannelClient*) clients->data;
}
static RedsState* snd_channel_get_server(SndChannelClient *client)
{
g_return_val_if_fail(client != NULL, NULL);
return client->get_channel()->get_server();
}
static void snd_playback_free_frame(PlaybackChannelClient *playback_client, AudioFrame *frame)
{
frame->client = playback_client;
frame->next = playback_client->free_frames;
playback_client->free_frames = frame;
}
static void snd_playback_on_message_done(SndChannelClient *client)
{
PlaybackChannelClient *playback_client = (PlaybackChannelClient *)client;
if (playback_client->in_progress) {
snd_playback_free_frame(playback_client, playback_client->in_progress);
playback_client->in_progress = NULL;
if (playback_client->pending_frame) {
client->command |= SND_PLAYBACK_PCM_MASK;
snd_send(client);
}
}
}
static bool snd_record_handle_write(RecordChannelClient *record_client, size_t size, void *message)
{
SpiceMsgcRecordPacket *packet;
uint32_t write_pos;
uint8_t* data;
uint32_t len;
uint32_t now;
if (!record_client) {
return false;
}
packet = (SpiceMsgcRecordPacket *)message;
if (record_client->mode == SPICE_AUDIO_DATA_MODE_RAW) {
data = packet->data;
size = packet->data_size >> 2;
size = MIN(size, RECORD_SAMPLES_SIZE);
} else {
int decode_size;
decode_size = sizeof(record_client->decode_buf);
if (snd_codec_decode(record_client->codec, packet->data, packet->data_size,
record_client->decode_buf, &decode_size) != SND_CODEC_OK)
return false;
data = record_client->decode_buf;
size = decode_size >> 2;
}
write_pos = record_client->write_pos % RECORD_SAMPLES_SIZE;
record_client->write_pos += size;
len = RECORD_SAMPLES_SIZE - write_pos;
now = MIN(len, size);
size -= now;
memcpy(record_client->samples + write_pos, data, now << 2);
if (size) {
memcpy(record_client->samples, data + now, size << 2);
}
if (record_client->write_pos - record_client->read_pos > RECORD_SAMPLES_SIZE) {
record_client->read_pos = record_client->write_pos - RECORD_SAMPLES_SIZE;
}
return true;
}
static
const char* spice_audio_data_mode_to_string(gint mode)
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
static const char * const str[] = {
[ SPICE_AUDIO_DATA_MODE_INVALID ] = "invalid",
[ SPICE_AUDIO_DATA_MODE_RAW ] = "raw",
[ SPICE_AUDIO_DATA_MODE_CELT_0_5_1 ] = "celt",
[ SPICE_AUDIO_DATA_MODE_OPUS ] = "opus",
};
G_GNUC_END_IGNORE_DEPRECATIONS
if (mode >= 0 && mode < G_N_ELEMENTS(str)) {
return str[mode];
}
return "unknown audio codec";
}
XXX_CAST(RedChannelClient, RecordChannelClient, RECORD_CHANNEL_CLIENT)
static bool
record_channel_handle_message(RedChannelClient *rcc, uint16_t type, uint32_t size, void *message)
{
RecordChannelClient *record_client = RECORD_CHANNEL_CLIENT(rcc);
switch (type) {
case SPICE_MSGC_RECORD_DATA:
return snd_record_handle_write(record_client, size, message);
case SPICE_MSGC_RECORD_MODE: {
SpiceMsgcRecordMode *mode = (SpiceMsgcRecordMode *)message;
SndChannel *channel = SND_CHANNEL(rcc->get_channel());
record_client->mode_time = mode->time;
if (mode->mode != SPICE_AUDIO_DATA_MODE_RAW) {
if (snd_codec_is_capable((SpiceAudioDataMode) mode->mode, channel->frequency)) {
if (snd_codec_create(&record_client->codec, mode->mode, channel->frequency,
SND_CODEC_DECODE) == SND_CODEC_OK) {
record_client->mode = mode->mode;
} else {
red_channel_warning(rcc->get_channel(),
"create decoder failed");
return false;
}
}
else {
red_channel_warning(rcc->get_channel(),
"unsupported mode %d",
record_client->mode);
return false;
}
}
else
record_client->mode = mode->mode;
spice_debug("record client %p using mode %s", record_client,
spice_audio_data_mode_to_string(record_client->mode));
break;
}
case SPICE_MSGC_RECORD_START_MARK: {
SpiceMsgcRecordStartMark *mark = (SpiceMsgcRecordStartMark *)message;
record_client->start_time = mark->time;
break;
}
default:
return RedChannelClient::handle_message(rcc, type, size, message);
}
return true;
}
static bool snd_channel_send_migrate(SndChannelClient *client)
{
RedChannelClient *rcc = client;
SpiceMarshaller *m = rcc->get_marshaller();
SpiceMsgMigrate migrate;
rcc->init_send_data(SPICE_MSG_MIGRATE);
migrate.flags = 0;
spice_marshall_msg_migrate(m, &migrate);
rcc->begin_send_message();
return true;
}
static bool snd_playback_send_migrate(PlaybackChannelClient *client)
{
return snd_channel_send_migrate(client);
}
static bool snd_send_volume(SndChannelClient *client, uint32_t cap, int msg)
{
SpiceMsgAudioVolume *vol;
uint8_t c;
RedChannelClient *rcc = client;
SpiceMarshaller *m = rcc->get_marshaller();
SndChannel *channel = SND_CHANNEL(rcc->get_channel());
SpiceVolumeState *st = &channel->volume;
if (!rcc->test_remote_cap(cap)) {
return false;
}
vol = (SpiceMsgAudioVolume*) alloca(sizeof (SpiceMsgAudioVolume) +
st->volume_nchannels * sizeof (uint16_t));
rcc->init_send_data(msg);
vol->nchannels = st->volume_nchannels;
for (c = 0; c < st->volume_nchannels; ++c) {
vol->volume[c] = st->volume[c];
}
spice_marshall_SpiceMsgAudioVolume(m, vol);
rcc->begin_send_message();
return true;
}
static bool snd_playback_send_volume(PlaybackChannelClient *playback_client)
{
return snd_send_volume(playback_client, SPICE_PLAYBACK_CAP_VOLUME,
SPICE_MSG_PLAYBACK_VOLUME);
}
static bool snd_send_mute(SndChannelClient *client, uint32_t cap, int msg)
{
SpiceMsgAudioMute mute;
RedChannelClient *rcc = client;
SpiceMarshaller *m = rcc->get_marshaller();
SndChannel *channel = SND_CHANNEL(rcc->get_channel());
SpiceVolumeState *st = &channel->volume;
if (!rcc->test_remote_cap(cap)) {
return false;
}
rcc->init_send_data(msg);
mute.mute = st->mute;
spice_marshall_SpiceMsgAudioMute(m, &mute);
rcc->begin_send_message();
return true;
}
static bool snd_playback_send_mute(PlaybackChannelClient *playback_client)
{
return snd_send_mute(playback_client, SPICE_PLAYBACK_CAP_VOLUME,
SPICE_MSG_PLAYBACK_MUTE);
}
static bool snd_playback_send_latency(PlaybackChannelClient *playback_client)
{
RedChannelClient *rcc = playback_client;
SpiceMarshaller *m = rcc->get_marshaller();
SpiceMsgPlaybackLatency latency_msg;
spice_debug("latency %u", playback_client->latency);
rcc->init_send_data(SPICE_MSG_PLAYBACK_LATENCY);
latency_msg.latency_ms = playback_client->latency;
spice_marshall_msg_playback_latency(m, &latency_msg);
rcc->begin_send_message();
return true;
}
static bool snd_playback_send_start(PlaybackChannelClient *playback_client)
{
RedChannelClient *rcc = playback_client;
SpiceMarshaller *m = rcc->get_marshaller();
SpiceMsgPlaybackStart start;
rcc->init_send_data(SPICE_MSG_PLAYBACK_START);
start.channels = SPICE_INTERFACE_PLAYBACK_CHAN;
start.frequency = SND_CHANNEL(rcc->get_channel())->frequency;
spice_assert(SPICE_INTERFACE_PLAYBACK_FMT == SPICE_INTERFACE_AUDIO_FMT_S16);
start.format = SPICE_AUDIO_FMT_S16;
start.time = reds_get_mm_time();
spice_marshall_msg_playback_start(m, &start);
rcc->begin_send_message();
return true;
}
static bool snd_playback_send_stop(PlaybackChannelClient *playback_client)
{
RedChannelClient *rcc = playback_client;
rcc->init_send_data(SPICE_MSG_PLAYBACK_STOP);
rcc->begin_send_message();
return true;
}
static int snd_playback_send_ctl(PlaybackChannelClient *playback_client)
{
SndChannelClient *client = playback_client;
if ((client->client_active = client->active)) {
return snd_playback_send_start(playback_client);
} else {
return snd_playback_send_stop(playback_client);
}
}
static bool snd_record_send_start(RecordChannelClient *record_client)
{
RedChannelClient *rcc = record_client;
SpiceMarshaller *m = rcc->get_marshaller();
SpiceMsgRecordStart start;
rcc->init_send_data(SPICE_MSG_RECORD_START);
start.channels = SPICE_INTERFACE_RECORD_CHAN;
start.frequency = SND_CHANNEL(rcc->get_channel())->frequency;
spice_assert(SPICE_INTERFACE_RECORD_FMT == SPICE_INTERFACE_AUDIO_FMT_S16);
start.format = SPICE_AUDIO_FMT_S16;
spice_marshall_msg_record_start(m, &start);
rcc->begin_send_message();
return true;
}
static bool snd_record_send_stop(RecordChannelClient *record_client)
{
RedChannelClient *rcc = record_client;
rcc->init_send_data(SPICE_MSG_RECORD_STOP);
rcc->begin_send_message();
return true;
}
static int snd_record_send_ctl(RecordChannelClient *record_client)
{
SndChannelClient *client = record_client;
if ((client->client_active = client->active)) {
return snd_record_send_start(record_client);
} else {
return snd_record_send_stop(record_client);
}
}
static bool snd_record_send_volume(RecordChannelClient *record_client)
{
return snd_send_volume(record_client, SPICE_RECORD_CAP_VOLUME,
SPICE_MSG_RECORD_VOLUME);
}
static bool snd_record_send_mute(RecordChannelClient *record_client)
{
return snd_send_mute(record_client, SPICE_RECORD_CAP_VOLUME,
SPICE_MSG_RECORD_MUTE);
}
static bool snd_record_send_migrate(RecordChannelClient *record_client)
{
/* No need for migration data: if recording has started before migration,
* the client receives RECORD_STOP from the src before the migration completion
* notification (when the vm is stopped).
* Afterwards, when the vm starts on the dest, the client receives RECORD_START. */
return snd_channel_send_migrate(record_client);
}
static bool snd_playback_send_write(PlaybackChannelClient *playback_client)
{
RedChannelClient *rcc = playback_client;
SpiceMarshaller *m = rcc->get_marshaller();
AudioFrame *frame;
SpiceMsgPlaybackPacket msg;
RedPipeItem *pipe_item = &playback_client->persistent_pipe_item;
rcc->init_send_data(SPICE_MSG_PLAYBACK_DATA);
frame = playback_client->in_progress;
msg.time = frame->time;
spice_marshall_msg_playback_data(m, &msg);
if (playback_client->mode == SPICE_AUDIO_DATA_MODE_RAW) {
spice_marshaller_add_by_ref_full(m, (uint8_t *)frame->samples,
snd_codec_frame_size(playback_client->codec) *
sizeof(frame->samples[0]),
marshaller_unref_pipe_item, pipe_item);
}
else {
int n = sizeof(playback_client->encode_buf);
if (snd_codec_encode(playback_client->codec, (uint8_t *) frame->samples,
snd_codec_frame_size(playback_client->codec) * sizeof(frame->samples[0]),
playback_client->encode_buf, &n) != SND_CODEC_OK) {
red_channel_warning(rcc->get_channel(), "encode failed");
rcc->disconnect();
return false;
}
spice_marshaller_add_by_ref_full(m, playback_client->encode_buf, n,
marshaller_unref_pipe_item, pipe_item);
}
rcc->begin_send_message();
return true;
}
static bool playback_send_mode(PlaybackChannelClient *playback_client)
{
RedChannelClient *rcc = playback_client;
SpiceMarshaller *m = rcc->get_marshaller();
SpiceMsgPlaybackMode mode;
rcc->init_send_data(SPICE_MSG_PLAYBACK_MODE);
mode.time = reds_get_mm_time();
mode.mode = playback_client->mode;
spice_marshall_msg_playback_mode(m, &mode);
rcc->begin_send_message();
return true;
}
/* This function is called when the "persistent" item is removed from the
* queue. Note that there is not free call as the item is allocated into
* SndChannelClient.
* This is used to have a simple item in RedChannelClient queue but to send
* multiple messages in a row if possible.
* During realtime sound transmission you usually don't want to queue too
* much data or having retransmission preferring instead loosing some
* samples.
*/
static void snd_persistent_pipe_item_free(struct RedPipeItem *item)
{
SndChannelClient *client = static_cast<PersistentPipeItem*>(item)->client;
red_pipe_item_init_full(item, RED_PIPE_ITEM_PERSISTENT,
snd_persistent_pipe_item_free);
if (client->on_message_done) {
client->on_message_done(client);
}
}
static void snd_send(SndChannelClient * client)
{
if (!client->pipe_is_empty()|| !client->command) {
return;
}
// just append a dummy item and push!
red_pipe_item_init_full(&client->persistent_pipe_item, RED_PIPE_ITEM_PERSISTENT,
snd_persistent_pipe_item_free);
client->persistent_pipe_item.client = client;
client->pipe_add_push(&client->persistent_pipe_item);
}
XXX_CAST(RedChannelClient, PlaybackChannelClient, PLAYBACK_CHANNEL_CLIENT)
static void playback_channel_send_item(RedChannelClient *rcc, G_GNUC_UNUSED RedPipeItem *item)
{
PlaybackChannelClient *playback_client = PLAYBACK_CHANNEL_CLIENT(rcc);
SndChannelClient *client = playback_client;
client->command &= SND_PLAYBACK_MODE_MASK|SND_PLAYBACK_PCM_MASK|
SND_CTRL_MASK|SND_VOLUME_MUTE_MASK|
SND_MIGRATE_MASK|SND_PLAYBACK_LATENCY_MASK;
while (client->command) {
if (client->command & SND_PLAYBACK_MODE_MASK) {
client->command &= ~SND_PLAYBACK_MODE_MASK;
if (playback_send_mode(playback_client)) {
break;
}
}
if (client->command & SND_PLAYBACK_PCM_MASK) {
spice_assert(!playback_client->in_progress && playback_client->pending_frame);
playback_client->in_progress = playback_client->pending_frame;
playback_client->pending_frame = NULL;
client->command &= ~SND_PLAYBACK_PCM_MASK;
if (snd_playback_send_write(playback_client)) {
break;
}
red_channel_warning(rcc->get_channel(),
"snd_send_playback_write failed");
}
if (client->command & SND_CTRL_MASK) {
client->command &= ~SND_CTRL_MASK;
if (snd_playback_send_ctl(playback_client)) {
break;
}
}
if (client->command & SND_VOLUME_MASK) {
client->command &= ~SND_VOLUME_MASK;
if (snd_playback_send_volume(playback_client)) {
break;
}
}
if (client->command & SND_MUTE_MASK) {
client->command &= ~SND_MUTE_MASK;
if (snd_playback_send_mute(playback_client)) {
break;
}
}
if (client->command & SND_MIGRATE_MASK) {
client->command &= ~SND_MIGRATE_MASK;
if (snd_playback_send_migrate(playback_client)) {
break;
}
}
if (client->command & SND_PLAYBACK_LATENCY_MASK) {
client->command &= ~SND_PLAYBACK_LATENCY_MASK;
if (snd_playback_send_latency(playback_client)) {
break;
}
}
}
snd_send(client);
}
static void record_channel_send_item(RedChannelClient *rcc, G_GNUC_UNUSED RedPipeItem *item)
{
RecordChannelClient *record_client = RECORD_CHANNEL_CLIENT(rcc);
SndChannelClient *client = record_client;
client->command &= SND_CTRL_MASK|SND_VOLUME_MUTE_MASK|SND_MIGRATE_MASK;
while (client->command) {
if (client->command & SND_CTRL_MASK) {
client->command &= ~SND_CTRL_MASK;
if (snd_record_send_ctl(record_client)) {
break;
}
}
if (client->command & SND_VOLUME_MASK) {
client->command &= ~SND_VOLUME_MASK;
if (snd_record_send_volume(record_client)) {
break;
}
}
if (client->command & SND_MUTE_MASK) {
client->command &= ~SND_MUTE_MASK;
if (snd_record_send_mute(record_client)) {
break;
}
}
if (client->command & SND_MIGRATE_MASK) {
client->command &= ~SND_MIGRATE_MASK;
if (snd_record_send_migrate(record_client)) {
break;
}
}
}
snd_send(client);
}
bool SndChannelClient::config_socket()
{
RedStream *stream = get_stream();
RedClient *red_client = get_client();
MainChannelClient *mcc = red_client_get_main(red_client);
#ifdef SO_PRIORITY
int priority = 6;
if (setsockopt(stream->socket, SOL_SOCKET, SO_PRIORITY, (void*)&priority,
sizeof(priority)) == -1) {
if (errno != ENOTSUP) {
red_channel_warning(get_channel(),
"setsockopt failed, %s", strerror(errno));
}
}
#endif
#ifdef IPTOS_LOWDELAY
int tos = IPTOS_LOWDELAY;
if (setsockopt(stream->socket, IPPROTO_IP, IP_TOS, (void*)&tos, sizeof(tos)) == -1) {
if (errno != ENOTSUP) {
red_channel_warning(get_channel(),
"setsockopt failed, %s",
strerror(errno));
}
}
#endif
red_stream_set_no_delay(stream, !main_channel_client_is_low_bandwidth(mcc));
return true;
}
uint8_t*
SndChannelClient::alloc_recv_buf(uint16_t type, uint32_t size)
{
// If message is too big allocate one, this should never happen
if (size > sizeof(receive_buf)) {
return (uint8_t*) g_malloc(size);
}
return receive_buf;
}
void
SndChannelClient::release_recv_buf(uint16_t type, uint32_t size, uint8_t *msg)
{
if (msg != receive_buf) {
g_free(msg);
}
}
static void snd_set_command(SndChannelClient *client, uint32_t command)
{
if (!client) {
return;
}
client->command |= command;
}
static void snd_channel_set_volume(SndChannel *channel,
uint8_t nchannels, uint16_t *volume)
{
SpiceVolumeState *st = &channel->volume;
SndChannelClient *client = snd_channel_get_client(channel);
st->volume_nchannels = nchannels;
g_free(st->volume);
st->volume = (uint16_t*) g_memdup(volume, sizeof(uint16_t) * nchannels);
if (!client || nchannels == 0)
return;
snd_set_command(client, SND_VOLUME_MASK);
snd_send(client);
}
SPICE_GNUC_VISIBLE void spice_server_playback_set_volume(SpicePlaybackInstance *sin,
uint8_t nchannels,
uint16_t *volume)
{
snd_channel_set_volume(sin->st, nchannels, volume);
}
static void snd_channel_set_mute(SndChannel *channel, uint8_t mute)
{
SpiceVolumeState *st = &channel->volume;
SndChannelClient *client = snd_channel_get_client(channel);
st->mute = mute;
if (!client)
return;
snd_set_command(client, SND_MUTE_MASK);
snd_send(client);
}
SPICE_GNUC_VISIBLE void spice_server_playback_set_mute(SpicePlaybackInstance *sin, uint8_t mute)
{
snd_channel_set_mute(sin->st, mute);
}
static void snd_channel_client_start(SndChannelClient *client)
{
spice_assert(!client->active);
client->active = true;
if (!client->client_active) {
snd_set_command(client, SND_CTRL_MASK);
snd_send(client);
} else {
client->command &= ~SND_CTRL_MASK;
}
}
static void playback_channel_client_start(SndChannelClient *client)
{
if (!client) {
return;
}
reds_disable_mm_time(snd_channel_get_server(client));
snd_channel_client_start(client);
}
SPICE_GNUC_VISIBLE void spice_server_playback_start(SpicePlaybackInstance *sin)
{
SndChannel *channel = sin->st;
channel->active = true;
return playback_channel_client_start(snd_channel_get_client(channel));
}
SPICE_GNUC_VISIBLE void spice_server_playback_stop(SpicePlaybackInstance *sin)
{
SndChannelClient *client = snd_channel_get_client(sin->st);
sin->st->active = false;
if (!client)
return;
PlaybackChannelClient *playback_client = PLAYBACK_CHANNEL_CLIENT(client);
spice_assert(client->active);
reds_enable_mm_time(snd_channel_get_server(client));
client->active = false;
if (client->client_active) {
snd_set_command(client, SND_CTRL_MASK);
snd_send(client);
} else {
client->command &= ~SND_CTRL_MASK;
client->command &= ~SND_PLAYBACK_PCM_MASK;
if (playback_client->pending_frame) {
spice_assert(!playback_client->in_progress);
snd_playback_free_frame(playback_client,
playback_client->pending_frame);
playback_client->pending_frame = NULL;
}
}
}
SPICE_GNUC_VISIBLE void spice_server_playback_get_buffer(SpicePlaybackInstance *sin,
uint32_t **frame, uint32_t *num_samples)
{
SndChannelClient *client = snd_channel_get_client(sin->st);
*frame = NULL;
*num_samples = 0;
if (!client) {
return;
}
PlaybackChannelClient *playback_client = PLAYBACK_CHANNEL_CLIENT(client);
if (!playback_client->free_frames) {
return;
}
spice_assert(client->active);
if (!playback_client->free_frames->allocated) {
playback_client->free_frames->allocated = true;
++playback_client->frames->refs;
}
*frame = playback_client->free_frames->samples;
playback_client->free_frames = playback_client->free_frames->next;
*num_samples = snd_codec_frame_size(playback_client->codec);
}
SPICE_GNUC_VISIBLE void spice_server_playback_put_samples(SpicePlaybackInstance *sin, uint32_t *samples)
{
PlaybackChannelClient *playback_client;
AudioFrame *frame;
frame = SPICE_CONTAINEROF(samples, AudioFrame, samples[0]);
if (frame->allocated) {
frame->allocated = false;
if (--frame->container->refs == 0) {
g_free(frame->container);
return;
}
}
playback_client = frame->client;
if (!playback_client || snd_channel_get_client(sin->st) != playback_client) {
/* lost last reference, client has been destroyed previously */
spice_debug("audio samples belong to a disconnected client");
return;
}
spice_assert(playback_client->active);
if (playback_client->pending_frame) {
snd_playback_free_frame(playback_client, playback_client->pending_frame);
}
frame->time = reds_get_mm_time();
playback_client->pending_frame = frame;
snd_set_command(playback_client, SND_PLAYBACK_PCM_MASK);
snd_send(playback_client);
}
void snd_set_playback_latency(RedClient *client, uint32_t latency)
{
GList *l;
for (l = snd_channels; l != NULL; l = l->next) {
SndChannel *now = (SndChannel*) l->data;
SndChannelClient *scc = snd_channel_get_client(now);
uint32_t type;
g_object_get(now, "channel-type", &type, NULL);
if (type == SPICE_CHANNEL_PLAYBACK && scc &&
scc->get_client() == client) {
if (scc->test_remote_cap(SPICE_PLAYBACK_CAP_LATENCY)) {
PlaybackChannelClient* playback = (PlaybackChannelClient*)scc;
playback->latency = latency;
snd_set_command(scc, SND_PLAYBACK_LATENCY_MASK);
snd_send(scc);
} else {
spice_debug("client doesn't not support SPICE_PLAYBACK_CAP_LATENCY");
}
}
}
}
static int snd_desired_audio_mode(bool playback_compression, int frequency,
bool client_can_opus)
{
if (!playback_compression)
return SPICE_AUDIO_DATA_MODE_RAW;
if (client_can_opus && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency))
return SPICE_AUDIO_DATA_MODE_OPUS;
return SPICE_AUDIO_DATA_MODE_RAW;
}
PlaybackChannelClient::~PlaybackChannelClient()
{
int i;
// free frames, unref them
for (i = 0; i < NUM_AUDIO_FRAMES; ++i) {
frames->items[i].client = NULL;
}
if (--frames->refs == 0) {
g_free(frames);
}
if (active) {
reds_enable_mm_time(snd_channel_get_server(this));
}
snd_codec_destroy(&codec);
}
PlaybackChannelClient::PlaybackChannelClient(RedChannel *channel,
RedClient *client,
RedStream *stream,
RedChannelCapabilities *caps):
SndChannelClient(channel, client, stream, caps)
{
snd_playback_alloc_frames(this);
RedChannel *red_channel = get_channel();
SndChannel *snd_channel = SND_CHANNEL(red_channel);
on_message_done = snd_playback_on_message_done;
bool client_can_opus = test_remote_cap(SPICE_PLAYBACK_CAP_OPUS);
bool playback_compression =
reds_config_get_playback_compression(red_channel->get_server());
int desired_mode = snd_desired_audio_mode(playback_compression, snd_channel->frequency, client_can_opus);
if (desired_mode != SPICE_AUDIO_DATA_MODE_RAW) {
if (snd_codec_create(&codec, desired_mode, snd_channel->frequency,
SND_CODEC_ENCODE) == SND_CODEC_OK) {
mode = desired_mode;
} else {
red_channel_warning(red_channel, "create encoder failed");
}
}
spice_debug("playback client %p using mode %s", this,
spice_audio_data_mode_to_string(mode));
}
bool PlaybackChannelClient::init()
{
RedClient *red_client = get_client();
SndChannelClient *scc = this;
RedChannel *red_channel = get_channel();
SndChannel *channel = SND_CHANNEL(red_channel);
if (!SndChannelClient::init()) {
return false;
}
if (!red_client_during_migrate_at_target(red_client)) {
snd_set_command(scc, SND_PLAYBACK_MODE_MASK);
if (channel->volume.volume_nchannels) {
snd_set_command(scc, SND_VOLUME_MUTE_MASK);
}
}
if (channel->active) {
playback_channel_client_start(scc);
}
snd_send(scc);
return true;
}
static void snd_set_peer_common(RedChannel *red_channel)
{
SndChannel *channel = SND_CHANNEL(red_channel);
SndChannelClient *snd_client = snd_channel_get_client(channel);
/* sound channels currently only support a single client */
if (snd_client) {
snd_client->disconnect();
}
}
static void snd_set_playback_peer(RedChannel *red_channel, RedClient *client, RedStream *stream,
G_GNUC_UNUSED int migration,
RedChannelCapabilities *caps)
{
snd_set_peer_common(red_channel);
auto peer = new PlaybackChannelClient(red_channel, client, stream, caps);
if (!peer->init()) {
peer->unref();
}
}
XXX_CAST(RedChannelClient, SndChannelClient, SND_CHANNEL_CLIENT)
static void snd_migrate_channel_client(RedChannelClient *rcc)
{
snd_set_command(SND_CHANNEL_CLIENT(rcc), SND_MIGRATE_MASK);
snd_send(SND_CHANNEL_CLIENT(rcc));
}
SPICE_GNUC_VISIBLE void spice_server_record_set_volume(SpiceRecordInstance *sin,
uint8_t nchannels,
uint16_t *volume)
{
snd_channel_set_volume(sin->st, nchannels, volume);
}
SPICE_GNUC_VISIBLE void spice_server_record_set_mute(SpiceRecordInstance *sin, uint8_t mute)
{
snd_channel_set_mute(sin->st, mute);
}
static void record_channel_client_start(SndChannelClient *client)
{
if (!client) {
return;
}
RecordChannelClient *record_client = RECORD_CHANNEL_CLIENT(client);
record_client->read_pos = record_client->write_pos = 0; //todo: improve by
//stream generation
snd_channel_client_start(client);
}
SPICE_GNUC_VISIBLE void spice_server_record_start(SpiceRecordInstance *sin)
{
SndChannel *channel = sin->st;
channel->active = true;
record_channel_client_start(snd_channel_get_client(channel));
}
SPICE_GNUC_VISIBLE void spice_server_record_stop(SpiceRecordInstance *sin)
{
SndChannelClient *client = snd_channel_get_client(sin->st);
sin->st->active = false;
if (!client)
return;
spice_assert(client->active);
client->active = false;
if (client->client_active) {
snd_set_command(client, SND_CTRL_MASK);
snd_send(client);
} else {
client->command &= ~SND_CTRL_MASK;
}
}
SPICE_GNUC_VISIBLE uint32_t spice_server_record_get_samples(SpiceRecordInstance *sin,
uint32_t *samples, uint32_t bufsize)
{
SndChannelClient *client = snd_channel_get_client(sin->st);
uint32_t read_pos;
uint32_t now;
uint32_t len;
if (!client)
return 0;
RecordChannelClient *record_client = RECORD_CHANNEL_CLIENT(client);
spice_assert(client->active);
if (record_client->write_pos < RECORD_SAMPLES_SIZE / 2) {
return 0;
}
len = MIN(record_client->write_pos - record_client->read_pos, bufsize);
read_pos = record_client->read_pos % RECORD_SAMPLES_SIZE;
record_client->read_pos += len;
now = MIN(len, RECORD_SAMPLES_SIZE - read_pos);
memcpy(samples, &record_client->samples[read_pos], now * 4);
if (now < len) {
memcpy(samples + now, record_client->samples, (len - now) * 4);
}
return len;
}
static void snd_set_rate(SndChannel *channel, uint32_t frequency, uint32_t cap_opus)
{
channel->frequency = frequency;
if (channel && snd_codec_is_capable(SPICE_AUDIO_DATA_MODE_OPUS, frequency)) {
channel->set_cap(cap_opus);
}
}
SPICE_GNUC_VISIBLE uint32_t spice_server_get_best_playback_rate(SpicePlaybackInstance *sin)
{
return SND_CODEC_OPUS_PLAYBACK_FREQ;
}
SPICE_GNUC_VISIBLE void spice_server_set_playback_rate(SpicePlaybackInstance *sin, uint32_t frequency)
{
snd_set_rate(sin->st, frequency, SPICE_PLAYBACK_CAP_OPUS);
}
SPICE_GNUC_VISIBLE uint32_t spice_server_get_best_record_rate(SpiceRecordInstance *sin)
{
return SND_CODEC_OPUS_PLAYBACK_FREQ;
}
SPICE_GNUC_VISIBLE void spice_server_set_record_rate(SpiceRecordInstance *sin, uint32_t frequency)
{
snd_set_rate(sin->st, frequency, SPICE_RECORD_CAP_OPUS);
}
RecordChannelClient::~RecordChannelClient()
{
snd_codec_destroy(&codec);
}
bool RecordChannelClient::init()
{
RedChannel *red_channel = get_channel();
SndChannel *channel = SND_CHANNEL(red_channel);
if (!SndChannelClient::init()) {
return FALSE;
}
if (channel->volume.volume_nchannels) {
snd_set_command(this, SND_VOLUME_MUTE_MASK);
}
if (channel->active) {
record_channel_client_start(this);
}
snd_send(this);
return TRUE;
}
static void snd_set_record_peer(RedChannel *red_channel, RedClient *client, RedStream *stream,
G_GNUC_UNUSED int migration,
RedChannelCapabilities *caps)
{
snd_set_peer_common(red_channel);
auto peer = new RecordChannelClient(red_channel, client, stream, caps);
if (!peer->init()) {
peer->unref();
}
}
static void add_channel(SndChannel *channel)
{
snd_channels = g_list_prepend(snd_channels, channel);
}
static void remove_channel(SndChannel *channel)
{
snd_channels = g_list_remove(snd_channels, channel);
}
static void
snd_channel_init(SndChannel *self)
{
self->frequency = SND_CODEC_OPUS_PLAYBACK_FREQ;
}
static void
snd_channel_finalize(GObject *object)
{
SndChannel *channel = SND_CHANNEL(object);
remove_channel(channel);
g_free(channel->volume.volume);
channel->volume.volume = NULL;
G_OBJECT_CLASS(snd_channel_parent_class)->finalize(object);
}
static void
snd_channel_class_init(SndChannelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = snd_channel_finalize;
}
static void
playback_channel_init(PlaybackChannel *self)
{
}
static void
playback_channel_constructed(GObject *object)
{
SndChannel *self = SND_CHANNEL(object);
RedsState *reds = self->get_server();
G_OBJECT_CLASS(playback_channel_parent_class)->constructed(object);
self->set_cap(SPICE_PLAYBACK_CAP_VOLUME);
add_channel(self);
reds_register_channel(reds, self);
}
static void
playback_channel_class_init(PlaybackChannelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
RedChannelClass *channel_class = RED_CHANNEL_CLASS(klass);
object_class->constructed = playback_channel_constructed;
channel_class->parser = spice_get_client_channel_parser(SPICE_CHANNEL_PLAYBACK, NULL);
channel_class->handle_message = RedChannelClient::handle_message;
channel_class->send_item = playback_channel_send_item;
// client callbacks
channel_class->connect = snd_set_playback_peer;
channel_class->migrate = snd_migrate_channel_client;
}
void snd_attach_playback(RedsState *reds, SpicePlaybackInstance *sin)
{
sin->st = (SpicePlaybackState*) g_object_new(TYPE_PLAYBACK_CHANNEL,
"spice-server", reds,
"core-interface", reds_get_core_interface(reds),
"channel-type", SPICE_CHANNEL_PLAYBACK,
"id", 0,
NULL);
}
static void
record_channel_init(RecordChannel *self)
{
}
static void
record_channel_constructed(GObject *object)
{
SndChannel *self = SND_CHANNEL(object);
RedsState *reds = self->get_server();
G_OBJECT_CLASS(record_channel_parent_class)->constructed(object);
self->set_cap(SPICE_RECORD_CAP_VOLUME);
add_channel(self);
reds_register_channel(reds, self);
}
static void
record_channel_class_init(RecordChannelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
RedChannelClass *channel_class = RED_CHANNEL_CLASS(klass);
object_class->constructed = record_channel_constructed;
channel_class->parser = spice_get_client_channel_parser(SPICE_CHANNEL_RECORD, NULL);
channel_class->handle_message = record_channel_handle_message;
channel_class->send_item = record_channel_send_item;
// client callbacks
channel_class->connect = snd_set_record_peer;
channel_class->migrate = snd_migrate_channel_client;
}
void snd_attach_record(RedsState *reds, SpiceRecordInstance *sin)
{
sin->st = (SpiceRecordState*) g_object_new(TYPE_RECORD_CHANNEL,
"spice-server", reds,
"core-interface", reds_get_core_interface(reds),
"channel-type", SPICE_CHANNEL_RECORD,
"id", 0,
NULL);
}
static void snd_detach_common(SndChannel *channel)
{
if (!channel) {
return;
}
channel->destroy();
}
void snd_detach_playback(SpicePlaybackInstance *sin)
{
snd_detach_common(sin->st);
}
void snd_detach_record(SpiceRecordInstance *sin)
{
snd_detach_common(sin->st);
}
void snd_set_playback_compression(bool on)
{
GList *l;
for (l = snd_channels; l != NULL; l = l->next) {
SndChannel *now = (SndChannel*) l->data;
SndChannelClient *client = snd_channel_get_client(now);
uint32_t type;
g_object_get(now, "channel-type", &type, NULL);
if (type == SPICE_CHANNEL_PLAYBACK && client) {
PlaybackChannelClient* playback = PLAYBACK_CHANNEL_CLIENT(client);
RedChannelClient *rcc = playback;
bool client_can_opus = rcc->test_remote_cap(SPICE_PLAYBACK_CAP_OPUS);
int desired_mode = snd_desired_audio_mode(on, now->frequency, client_can_opus);
if (playback->mode != desired_mode) {
playback->mode = desired_mode;
snd_set_command(client, SND_PLAYBACK_MODE_MASK);
spice_debug("playback client %p using mode %s", playback,
spice_audio_data_mode_to_string(playback->mode));
}
}
}
}
static void snd_playback_alloc_frames(PlaybackChannelClient *playback)
{
int i;
playback->frames = g_new0(AudioFrameContainer, 1);
playback->frames->refs = 1;
for (i = 0; i < NUM_AUDIO_FRAMES; ++i) {
playback->frames->items[i].container = playback->frames;
snd_playback_free_frame(playback, &playback->frames->items[i]);
}
}