Spice如何开启H.264编码
太长不看:
- 保证服务端和客户端均正确安装GStreamer套件,才能启用H.264的编码和解码
- 修改SPICE源码中的codecs优先级
// reds.cpp
// 把默认codecs中的第一个改为gstreamer:h264(前置条件是客户端和服务端都正确安装gstreamer)
static const char default_video_codecs[] = "gstreamer:h264;" GSTREAMER_CODECS;
关于流数据编码的设置,入口代码在客户端连接上SPICE-SERVER的回调函数中,即 DisplayChannel::on_connect()
函数中,具体是其中调用的dcc_start()方法。关键的函数调用链路梳理:
细节可自行查看源码中上述函数的实现。
dcc_get_preferred_video_codecs_for_encoding
的源码:
GArray *dcc_get_preferred_video_codecs_for_encoding(DisplayChannelClient *dcc)
{
if (dcc->priv->preferred_video_codecs != nullptr) {
return dcc->priv->preferred_video_codecs;
}
return display_channel_get_video_codecs(DCC_TO_DC(dcc));
}
我们发现,这里依赖了 preferred_video_codecs
字段,这个值有接口动态设置,在初始化过程中没有作用。所以我们主要关注 display_channel_get_video_codecs
接口的实现:
GArray *display_channel_get_video_codecs(DisplayChannel *display)
{
spice_return_val_if_fail(display, NULL);
return display->priv->video_codecs;
}
所以现在的问题是:video_codecs
字段的值从哪儿来?
在 spice_server_new
接口中,执行了video_codecs的初始化逻辑,关键代码是 spice_server_init()
,该接口由qemu初始化spice-server时调用,接口实现里使用默认的codecs初始化video_codecs。
关于default_video_codecs有一点需要注意,SPICE的H.264编码基于GStreamer,因此需要正确安装GStreamer。
#if defined(HAVE_GSTREAMER_1_0) || defined(HAVE_GSTREAMER_0_10)
#define GSTREAMER_CODECS "gstreamer:mjpeg;gstreamer:h264;gstreamer:vp8;gstreamer:vp9;"
#else
#define GSTREAMER_CODECS ""
#endif
static const char default_video_codecs[] = "spice:mjpeg;" GSTREAMER_CODECS;
// 开启h264
// static const char default_video_codecs[] = "gstreamer:h264;" GSTREAMER_CODECS;
codecs的顺序代表优先级,要开启h264只需要调整mjpeg和h264的顺序即可。
客户端同样需要确保有GStreamer组件。SPICE-SERVER会检查客户端的支持情况,如果客户端不支持H.264则回退到MJPEG编码。相关代码:
// video-stream.cpp
static VideoEncoder* dcc_create_video_encoder(DisplayChannelClient *dcc,
uint64_t starting_bit_rate,
VideoEncoderRateControlCbs *cbs)
{
bool client_has_multi_codec = dcc->test_remote_cap(SPICE_DISPLAY_CAP_MULTI_CODEC);
int i;
GArray *video_codecs;
video_codecs = dcc_get_preferred_video_codecs_for_encoding(dcc);
for (i = 0; i < video_codecs->len; i++) {
RedVideoCodec* video_codec = &g_array_index (video_codecs, RedVideoCodec, i);
if (!client_has_multi_codec &&
video_codec->type != SPICE_VIDEO_CODEC_TYPE_MJPEG) {
/* Old clients only support MJPEG */
spice_debug("[*lsf*]client does not support multi-codec");
continue;
}
if (client_has_multi_codec &&
!dcc->test_remote_cap(video_codec->cap)) {
spice_debug("[*lsf*]client does not support this codec");
/* The client is recent but does not support this codec */
continue;
}
VideoEncoder* video_encoder = video_codec->create(video_codec->type, starting_bit_rate, cbs, bitmap_ref, bitmap_unref);
if (video_encoder) {
return video_encoder;
}
}
/* Try to use the builtin MJPEG video encoder as a fallback */
if (!client_has_multi_codec || dcc->test_remote_cap(SPICE_DISPLAY_CAP_CODEC_MJPEG)) {
return mjpeg_encoder_new(SPICE_VIDEO_CODEC_TYPE_MJPEG, starting_bit_rate, cbs, bitmap_ref, bitmap_unref);
}
return nullptr;
}