1、主通道vdagent能力协商时会同步录音和播放声音,下面以播放声音为例
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: //vdagent能力协商
{
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;
}
...
}
2、spice_main_get_audio获取核心音频处理类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);
}
3、建立异步调用获取播放音量和静音的任务
static void spice_gstaudio_get_playback_volume_info_async(SpiceAudio *audio,
GCancellable *cancellable,
SpiceMainChannel *main_channel,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task = g_task_new(audio, cancellable, callback, user_data);
g_task_return_boolean(task, TRUE);
g_object_unref(task);
}
4、异步调用audio_playback_volume_info_cb,从而会,获取完成后将结果发送
VD_AGENT_AUDIO_VOLUME_SYNC消息给spice服务器
static void audio_playback_volume_info_cb(GObject *object, GAsyncResult *res, gpointer user_data)
{
SpiceMainChannel *main_channel = user_data;
SpiceAudio *audio = spice_main_get_audio(main_channel);
VDAgentAudioVolumeSync *avs;
guint16 *volume;
guint8 nchannels;
gboolean mute, ret;
gsize array_size;
GError *error = NULL;
ret = spice_audio_get_playback_volume_info_finish(audio, res, &mute, &nchannels, &volume, &error);
if (ret == FALSE || volume == NULL || nchannels == 0) {
if (error != NULL) {
SPICE_DEBUG("Failed to get playback async volume info: %s", error->message);
g_error_free(error);
} else {
SPICE_DEBUG("Failed to get playback async volume info");
}
main_channel->priv->agent_volume_playback_sync = FALSE;
return;
}
array_size = sizeof(uint16_t) * nchannels;
avs = g_malloc0(sizeof(VDAgentAudioVolumeSync) + array_size);
avs->is_playback = TRUE;
avs->mute = mute;
avs->nchannels = nchannels;
memcpy(avs->volume, volume, array_size);
SPICE_DEBUG("%s mute=%s nchannels=%u volume[0]=%u", __func__, spice_yes_no(mute), nchannels, volume[0]);
g_free(volume);
agent_msg_queue(main_channel, VD_AGENT_AUDIO_VOLUME_SYNC, sizeof(VDAgentAudioVolumeSync) + array_size, avs);
g_free (avs);
}
5、调用SpiceAudio的spice_audio_get_playback_volume_info_finish获取音量和静音,从而调用到SpiceGstAudio的spice_gstaudio_get_playback_volume_info_finish
gboolean spice_audio_get_playback_volume_info_finish(SpiceAudio *audio,
GAsyncResult *res,
gboolean *mute,
guint8 *nchannels,
guint16 **volume,
GError **error)
{
g_return_val_if_fail(audio != NULL, FALSE);
return SPICE_AUDIO_GET_CLASS(audio)->get_playback_volume_info_finish(audio,
res, mute, nchannels, volume, error);
}
6、SpiceGstAudio获取音量的具体实现
static gboolean spice_gstaudio_get_playback_volume_info_finish(SpiceAudio *audio,
GAsyncResult *res,
gboolean *mute,
guint8 *nchannels,
guint16 **volume,
GError **error)
{
SpiceGstaudioPrivate *p = SPICE_GSTAUDIO(audio)->priv;
GstElement *e = NULL;
gboolean lmute;
gdouble vol;
GTask *task = G_TASK(res);
g_return_val_if_fail(g_task_is_valid(task, audio), FALSE);
if (g_task_had_error(task)) {
/* set out args that should have new alloc'ed memory to NULL */
if (volume != NULL) {
*volume = NULL;
}
return g_task_propagate_boolean(task, error);
}
if (p->playback.sink == NULL || p->playback.channels == 0) {
SPICE_DEBUG("PlaybackChannel not created yet, force start");
p->playback.fake = TRUE;
/* In order to get system volume, we start the pipeline */
playback_start(NULL, SPICE_AUDIO_FMT_S16, 2, 48000, audio);
}
if (GST_IS_BIN(p->playback.sink))
e = gst_bin_get_by_interface(GST_BIN(p->playback.sink), GST_TYPE_STREAM_VOLUME);
if (!e)
e = g_object_ref(p->playback.sink);
if (GST_IS_STREAM_VOLUME(e)) {
vol = gst_stream_volume_get_volume(GST_STREAM_VOLUME(e), GST_STREAM_VOLUME_FORMAT_CUBIC);
lmute = gst_stream_volume_get_mute(GST_STREAM_VOLUME(e));
} else {
g_object_get(e, "volume", &vol, "mute", &lmute, NULL);
}
g_object_unref(e);
if (p->playback.fake) {
SPICE_DEBUG("Stop faked PlaybackChannel");
playback_stop(SPICE_GSTAUDIO(audio));
p->playback.fake = FALSE;
}
if (mute != NULL) {
*mute = lmute;
}
if (nchannels != NULL) {
*nchannels = p->playback.channels;
}
if (volume != NULL) {
gint i;
*volume = g_new(guint16, p->playback.channels);
for (i = 0; i < p->playback.channels; i++) {
(*volume)[i] = (guint16) (vol * VOLUME_NORMAL);
SPICE_DEBUG("(playback) volume at %d is %u (%0.2f%%)", i, (*volume)[i], 100*vol);
}
}
return g_task_propagate_boolean(task, error);
}