1、获取SpiceAudio句柄,也就是音频播放和录音类对象
1.1、在主通道中获取SpiceAudio句柄
1.1.1、在channel-main.c的
main_agent_handle_msg函数中
能力协商(
VD_AGENT_ANNOUNCE_CAPABILITIES
)时调用agent同步音频播放获取SpiceAudio句柄
/* coroutine context */
static void main_agent_handle_msg(SpiceChannel *channel,
VDAgentMessage *msg, gpointer payload)
{
SpiceMainChannel *self = SPICE_MAIN_CHANNEL(channel);
SpiceMainChannelPrivate *c = self->priv;
guint8 selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
guint32 serial;
g_return_if_fail(msg->protocol == VD_AGENT_PROTOCOL);
switch (msg->type) {
case VD_AGENT_CLIPBOARD_RELEASE:
case VD_AGENT_CLIPBOARD_REQUEST:
case VD_AGENT_CLIPBOARD_GRAB:
case VD_AGENT_CLIPBOARD:
if (test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
selection = *((guint8*)payload);
payload = ((guint8*)payload) + 4;
msg->size -= 4;
}
break;
default:
break;
}
switch (msg->type) {
case VD_AGENT_ANNOUNCE_CAPABILITIES:
{
VDAgentAnnounceCapabilities *caps = payload;
int i, size;
size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(msg->size);
if (size > VD_AGENT_CAPS_SIZE)
size = VD_AGENT_CAPS_SIZE;
memset(c->agent_caps, 0, sizeof(c->agent_caps));
for (i = 0; i < size * 32; i++) {
if (!VD_AGENT_HAS_CAPABILITY(caps->caps, size, i))
continue;
SPICE_DEBUG("%s: cap: %d (%s)", __FUNCTION__,
i, NAME(agent_caps, i));
VD_AGENT_SET_CAPABILITY(c->agent_caps, i);
}
c->agent_caps_received = true;
g_coroutine_signal_emit(self, signals[SPICE_MAIN_AGENT_UPDATE], 0);
update_display_timer(SPICE_MAIN_CHANNEL(channel), 0);
if (caps->request)
agent_announce_caps(self);
if (test_agent_cap(self, VD_AGENT_CAP_DISPLAY_CONFIG) &&
!c->agent_display_config_sent) {
agent_display_config(self);
c->agent_display_config_sent = true;
}
agent_sync_audio_playback(self);
agent_sync_audio_record(self);
agent_max_clipboard(self);
agent_send_msg_queue(self);
break;
}
...
}
1.1.2、获取SpiceAudio句柄,连接音量控制函数
static void agent_sync_audio_playback(SpiceMainChannel *main_channel)
{
SpiceAudio *audio = spice_main_get_audio(main_channel);
SpiceMainChannelPrivate *c = main_channel->priv;
if (audio == NULL ||
!test_agent_cap(main_channel, VD_AGENT_CAP_AUDIO_VOLUME_SYNC) ||
c->agent_volume_playback_sync == TRUE) {
SPICE_DEBUG("%s - is not going to sync audio with guest", __func__);
return;
}
/* only one per connection */
g_cancellable_reset(c->cancellable_volume_info);
c->agent_volume_playback_sync = TRUE;
spice_audio_get_playback_volume_info_async(audio, c->cancellable_volume_info, main_channel,
audio_playback_volume_info_cb, main_channel);
}
1.1.3、最终调用
spice_audio_get获取SpiceAudio句柄
static SpiceAudio *spice_main_get_audio(const SpiceMainChannel *channel)
{
return spice_audio_get(spice_channel_get_session(SPICE_CHANNEL(channel)), NULL);
}
1.2、在外部new_channel信号的回调函数中启动
1.2.1、spicy.c源文件中
connection_new连接信号与回调函数
static spice_connection *connection_new(void)
{
spice_connection *conn;
SpiceUsbDeviceManager *manager;
conn = g_new0(spice_connection, 1);
conn->session = spice_session_new();
conn->gtk_session = spice_gtk_session_get(conn->session);
g_signal_connect(conn->session, "channel-new",G_CALLBACK(channel_new), conn);
g_signal_connect(conn->session, "channel-destroy",G_CALLBACK(channel_destroy), conn);
g_signal_connect(conn->session, "notify::migration-state",G_CALLBACK(migration_state), conn);
g_signal_connect(conn->session, "disconnected",G_CALLBACK(connection_destroy), conn);
manager = spice_usb_device_manager_get(conn->session, NULL);
if (manager) {
g_signal_connect(manager, "auto-connect-failed",G_CALLBACK(usb_connect_failed), NULL);
g_signal_connect(manager, "device-error",G_CALLBACK(usb_connect_failed), NULL);
}
conn->transfers = g_hash_table_new_full(g_direct_hash, g_direct_equal,g_object_unref,(GDestroyNotify)transfer_task_widgets_free);
connections++;
SPICE_DEBUG("%s (%d)", __FUNCTION__, connections);
return conn;
}
1.2.2、new_channel回调函数实现如下,
如果是音频通道,就获取SpiceAudio句柄
static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
{
...
if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
SPICE_DEBUG("new audio channel");
conn->audio = spice_audio_get(s, NULL);
}
...
}
2、连接播放的信号(如:playback-start,playback-stop和playback-data)和录音的信号(
如:record-start,
record
-stop和
record
-data
)
2.1、
在spice-session.c中获取音频句柄SpiceAudio
SpiceAudio *spice_audio_get(SpiceSession *session, GMainContext *context)
{
static GMutex mutex;
SpiceAudio *self;
g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
g_mutex_lock(&mutex);
self = session->priv->audio_manager;
if (self == NULL) {
self = spice_audio_new_priv(session, context, NULL);
session->priv->audio_manager = self;
}
g_mutex_unlock(&mutex);
return self;
}
2.2、SpiceAudio初始化信号与回调函数,并更新通道
G_GNUC_INTERNAL
SpiceAudio *spice_audio_new_priv(SpiceSession *session, GMainContext *context,const char *name)
{
SpiceAudio *self = NULL;
if (context == NULL)
context = g_main_context_default();
if (name == NULL)
name = g_get_application_name();
self = SPICE_AUDIO(spice_gstaudio_new(session, context, name));
if (self != NULL) {
spice_g_signal_connect_object(session, "notify::enable-audio", G_CALLBACK(session_enable_audio), self, 0);
spice_g_signal_connect_object(session, "channel-new", G_CALLBACK(channel_new), self, G_CONNECT_AFTER);
update_audio_channels(self, session);
}
return self;
}
2.3、channel-new信号的回调函数最终调用到connect_channel函数中
static void channel_new(SpiceSession *session, SpiceChannel *channel, SpiceAudio *self)
{
connect_channel(self, channel);
}
2.4、更新所有的音频通道
static void update_audio_channels(SpiceAudio *self, SpiceSession *session)
{
GList *list, *tmp;
if (!spice_session_get_audio_enabled(session)) {
SPICE_DEBUG("FIXME: disconnect audio channels");
return;
}
list = spice_session_get_channels(session);
for (tmp = g_list_first(list); tmp != NULL; tmp = g_list_next(tmp)) {
connect_channel(self, tmp->data);
}
g_list_free(list);
}
2.5、在spice-audio.c的connect_channel函数中调用connect_channel回调函数指针()
static void connect_channel(SpiceAudio *self, SpiceChannel *channel)
{
if (channel->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED)
return;
if (SPICE_AUDIO_GET_CLASS(self)->connect_channel(self, channel))
spice_channel_connect(channel);
}
2.6、SpiceGstAudio继承自SpiceAudio并在
spice_gstaudio_class_init中初始化
了connect_channel函数指针
static void spice_gstaudio_class_init(SpiceGstaudioClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
SpiceAudioClass *audio_class = SPICE_AUDIO_CLASS(klass);
audio_class->connect_channel = connect_channel;
audio_class->get_playback_volume_info_async = spice_gstaudio_get_playback_volume_info_async;
audio_class->get_playback_volume_info_finish = spice_gstaudio_get_playback_volume_info_finish;
audio_class->get_record_volume_info_async = spice_gstaudio_get_record_volume_info_async;
audio_class->get_record_volume_info_finish = spice_gstaudio_get_record_volume_info_finish;
gobject_class->dispose = spice_gstaudio_dispose;
}
2.7、最终调用到spice-gstaudio.c中的connect_channel函数,连接各种信号与回调函数
static gboolean connect_channel(SpiceAudio *audio, SpiceChannel *channel)
{
SpiceGstaudio *gstaudio = SPICE_GSTAUDIO(audio);
SpiceGstaudioPrivate *p = gstaudio->priv;
if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
g_return_val_if_fail(p->pchannel == NULL, FALSE);
p->pchannel = channel;
g_object_weak_ref(G_OBJECT(p->pchannel), channel_weak_notified, audio);
spice_g_signal_connect_object(channel, "playback-start",G_CALLBACK(playback_start), gstaudio, 0);
spice_g_signal_connect_object(channel, "playback-data",G_CALLBACK(playback_data), gstaudio, 0);
spice_g_signal_connect_object(channel, "playback-stop",G_CALLBACK(playback_stop), gstaudio, G_CONNECT_SWAPPED);
spice_g_signal_connect_object(channel, "notify::volume",G_CALLBACK(playback_volume_changed), gstaudio, 0);
spice_g_signal_connect_object(channel, "notify::mute",G_CALLBACK(playback_mute_changed), gstaudio, 0);
return TRUE;
}
if (SPICE_IS_RECORD_CHANNEL(channel)) {
g_return_val_if_fail(p->rchannel == NULL, FALSE);
p->rchannel = channel;
g_object_weak_ref(G_OBJECT(p->rchannel), channel_weak_notified, audio);
spice_g_signal_connect_object(channel, "record-start",G_CALLBACK(record_start), gstaudio, 0);
spice_g_signal_connect_object(channel, "record-stop",G_CALLBACK(record_stop), gstaudio, G_CONNECT_SWAPPED);
spice_g_signal_connect_object(channel, "notify::volume",G_CALLBACK(record_volume_changed), gstaudio, 0);
spice_g_signal_connect_object(channel, "notify::mute", G_CALLBACK(record_mute_changed), gstaudio, 0);
return TRUE;
}
return FALSE;
}
3、服务器控制音频开启,停止和数据传输
3.1、以音频播放
开启(
playback-start)为例,其他都同比类似
3.1.1、收到网络音频播放开启数据包
static void channel_set_handlers(SpiceChannelClass *klass)
{
static const spice_msg_handler handlers[] = {
...
[ SPICE_MSG_PLAYBACK_START ] = playback_handle_start,
...
};
spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
}
3.1.2、调用SpicePlaybackChannel的SPICE_PLAYBACK_START信号的回调函数,也就是前面spice_g_signal_connect_object中spice-gstaudio.c中的playback_start函数
static void playback_handle_start(SpiceChannel *channel, SpiceMsgIn *in)
{
SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
SpiceMsgPlaybackStart *start = spice_msg_in_parsed(in);
CHANNEL_DEBUG(channel, "%s: fmt %u channels %u freq %u time %u mode %s", __FUNCTION__,
start->format, start->channels, start->frequency, start->time,
spice_audio_data_mode_to_string(c->mode));
c->frame_count = 0;
c->last_time = start->time;
c->is_active = TRUE;
c->min_latency = SPICE_PLAYBACK_DEFAULT_LATENCY_MS;
snd_codec_destroy(&c->codec);
if (c->mode != SPICE_AUDIO_DATA_MODE_RAW) {
if (snd_codec_create(&c->codec, c->mode, start->frequency, SND_CODEC_DECODE) != SND_CODEC_OK) {
g_warning("create decoder failed");
return;
}
}
g_coroutine_signal_emit(channel, signals[SPICE_PLAYBACK_START], 0,
start->format, start->channels, start->frequency);
}
3.1.3、spice-gstaudio.c中playbakc-start回调函数实现如下,创建播放的pipeline
static void playback_start(SpicePlaybackChannel *channel, gint format, gint channels,
gint frequency, gpointer data)
{
SpiceGstaudio *gstaudio = data;
SpiceGstaudioPrivate *p = gstaudio->priv;
g_return_if_fail(p != NULL);
g_return_if_fail(format == SPICE_AUDIO_FMT_S16);
if (p->playback.pipe &&
(p->playback.rate != frequency ||
p->playback.channels != channels)) {
playback_stop(gstaudio);
g_clear_pointer(&p->playback.pipe, gst_object_unref);
}
if (!p->playback.pipe) {
GError *error = NULL;
gchar *audio_caps =
g_strdup_printf("audio/x-raw,format=\"S16LE\",channels=%d,rate=%d,"
"layout=interleaved", channels, frequency);
gchar *pipeline = g_strdup (g_getenv("SPICE_GST_AUDIOSINK"));
if (pipeline == NULL)
pipeline = g_strdup_printf("appsrc is-live=1 do-timestamp=0 format=time caps=\"%s\" name=\"appsrc\" ! queue ! "
"audioconvert ! audioresample ! autoaudiosink name=\"audiosink\"", audio_caps);
SPICE_DEBUG("audio pipeline: %s", pipeline);
p->playback.pipe = gst_parse_launch(pipeline, &error);
if (error != NULL) {
g_warning("Failed to create pipeline: %s", error->message);
goto cleanup;
}
p->playback.src = gst_bin_get_by_name(GST_BIN(p->playback.pipe), "appsrc");
p->playback.sink = gst_bin_get_by_name(GST_BIN(p->playback.pipe), "audiosink");
p->playback.rate = frequency;
p->playback.channels = channels;
cleanup:
if (error != NULL)
g_clear_pointer(&p->playback.pipe, gst_object_unref);
g_clear_error(&error);
g_free(audio_caps);
g_free(pipeline);
}
if (p->playback.pipe)
gst_element_set_state(p->playback.pipe, GST_STATE_PLAYING);
if (!p->playback.fake && p->mmtime_id == 0) {
update_mmtime_timeout_cb(gstaudio);
p->mmtime_id = g_timeout_add_seconds(1, update_mmtime_timeout_cb, gstaudio);
}
}