2 * Copyright (C) 2011, Hewlett-Packard Development Company, L.P.
3 * Copyright (C) 2017 Xilinx, Inc.
4 * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, Collabora Ltd.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation
9 * version 2.1 of the License.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
28 #include "gstomxh265enc.h"
29 #include "gstomxh265utils.h"
31 GST_DEBUG_CATEGORY_STATIC (gst_omx_h265_enc_debug_category);
32 #define GST_CAT_DEFAULT gst_omx_h265_enc_debug_category
35 static gboolean gst_omx_h265_enc_set_format (GstOMXVideoEnc * enc,
36 GstOMXPort * port, GstVideoCodecState * state);
37 static GstCaps *gst_omx_h265_enc_get_caps (GstOMXVideoEnc * enc,
38 GstOMXPort * port, GstVideoCodecState * state);
39 static void gst_omx_h265_enc_set_property (GObject * object, guint prop_id,
40 const GValue * value, GParamSpec * pspec);
41 static void gst_omx_h265_enc_get_property (GObject * object, guint prop_id,
42 GValue * value, GParamSpec * pspec);
47 PROP_PERIODICITYOFIDRFRAMES,
48 PROP_INTERVALOFCODINGINTRAFRAMES,
50 PROP_CONSTRAINED_INTRA_PREDICTION,
51 PROP_LOOP_FILTER_MODE,
54 #define GST_OMX_H265_VIDEO_ENC_PERIODICITY_OF_IDR_FRAMES_DEFAULT (0xffffffff)
55 #define GST_OMX_H265_VIDEO_ENC_INTERVAL_OF_CODING_INTRA_FRAMES_DEFAULT (0xffffffff)
56 #define GST_OMX_H265_VIDEO_ENC_B_FRAMES_DEFAULT (0xffffffff)
57 #define GST_OMX_H265_VIDEO_ENC_CONSTRAINED_INTRA_PREDICTION_DEFAULT (FALSE)
58 #define GST_OMX_H265_VIDEO_ENC_LOOP_FILTER_MODE_DEFAULT (0xffffffff)
60 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
61 /* zynqultrascaleplus's OMX uses a param struct different of Android's one */
62 #define INDEX_PARAM_VIDEO_HEVC OMX_ALG_IndexParamVideoHevc
64 #define INDEX_PARAM_VIDEO_HEVC OMX_IndexParamVideoHevc
67 /* class initialization */
70 GST_DEBUG_CATEGORY_INIT (gst_omx_h265_enc_debug_category, "omxh265enc", 0, \
71 "debug category for gst-omx H265 video encoder");
73 #define parent_class gst_omx_h265_enc_parent_class
74 G_DEFINE_TYPE_WITH_CODE (GstOMXH265Enc, gst_omx_h265_enc,
75 GST_TYPE_OMX_VIDEO_ENC, DEBUG_INIT);
77 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
78 #define GST_TYPE_OMX_H265_ENC_LOOP_FILTER_MODE (gst_omx_h265_enc_loop_filter_mode_get_type ())
80 gst_omx_h265_enc_loop_filter_mode_get_type (void)
82 static GType qtype = 0;
85 static const GEnumValue values[] = {
86 {OMX_ALG_VIDEO_HEVCLoopFilterEnable, "Enable deblocking filter",
88 {OMX_ALG_VIDEO_HEVCLoopFilterDisable, "Disable deblocking filter",
90 {OMX_ALG_VIDEO_HEVCLoopFilterDisableCrossSlice,
91 "Disable deblocking filter on slice boundary", "disable-cross-slice"},
92 {OMX_ALG_VIDEO_HEVCLoopFilterDisableCrossTile,
93 "Disable deblocking filter on tile boundary", "disable-cross-tile"},
94 {OMX_ALG_VIDEO_HEVCLoopFilterDisableCrossSliceAndTile,
95 "Disable deblocking filter on slice and tile boundary",
96 "disable-slice-and-tile"},
97 {0xffffffff, "Component Default", "default"},
101 qtype = g_enum_register_static ("GstOMXH265EncLoopFilter", values);
108 gst_omx_h265_enc_class_init (GstOMXH265EncClass * klass)
110 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
111 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
112 GstOMXVideoEncClass *videoenc_class = GST_OMX_VIDEO_ENC_CLASS (klass);
114 videoenc_class->set_format = GST_DEBUG_FUNCPTR (gst_omx_h265_enc_set_format);
115 videoenc_class->get_caps = GST_DEBUG_FUNCPTR (gst_omx_h265_enc_get_caps);
117 gobject_class->set_property = gst_omx_h265_enc_set_property;
118 gobject_class->get_property = gst_omx_h265_enc_get_property;
120 g_object_class_install_property (gobject_class,
121 PROP_INTERVALOFCODINGINTRAFRAMES,
122 g_param_spec_uint ("interval-intraframes",
123 "Interval of coding Intra frames",
124 "Interval of coding Intra frames (0xffffffff=component default)", 0,
126 GST_OMX_H265_VIDEO_ENC_INTERVAL_OF_CODING_INTRA_FRAMES_DEFAULT,
127 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
128 GST_PARAM_MUTABLE_READY));
130 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
131 g_object_class_install_property (gobject_class, PROP_PERIODICITYOFIDRFRAMES,
132 g_param_spec_uint ("periodicity-idr", "IDR periodicity",
133 "Periodicity of IDR frames (0xffffffff=component default)",
135 GST_OMX_H265_VIDEO_ENC_PERIODICITY_OF_IDR_FRAMES_DEFAULT,
136 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
137 GST_PARAM_MUTABLE_READY));
139 g_object_class_install_property (gobject_class, PROP_B_FRAMES,
140 g_param_spec_uint ("b-frames", "Number of B-frames",
141 "Number of B-frames between two consecutive I-frames (0xffffffff=component default)",
142 0, G_MAXUINT, GST_OMX_H265_VIDEO_ENC_B_FRAMES_DEFAULT,
143 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
144 GST_PARAM_MUTABLE_READY));
146 g_object_class_install_property (gobject_class,
147 PROP_CONSTRAINED_INTRA_PREDICTION,
148 g_param_spec_boolean ("constrained-intra-prediction",
149 "Constrained Intra Prediction",
150 "If enabled, prediction only uses residual data and decoded samples "
151 "from neighbouring coding blocks coded using intra prediction modes",
152 GST_OMX_H265_VIDEO_ENC_CONSTRAINED_INTRA_PREDICTION_DEFAULT,
153 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
154 GST_PARAM_MUTABLE_READY));
156 g_object_class_install_property (gobject_class, PROP_LOOP_FILTER_MODE,
157 g_param_spec_enum ("loop-filter-mode", "Loop Filter mode",
158 "Enable or disable the deblocking filter (0xffffffff=component default)",
159 GST_TYPE_OMX_H265_ENC_LOOP_FILTER_MODE,
160 GST_OMX_H265_VIDEO_ENC_LOOP_FILTER_MODE_DEFAULT,
161 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
162 GST_PARAM_MUTABLE_READY));
165 videoenc_class->cdata.default_src_template_caps = "video/x-h265, "
166 "width=(int) [ 1, MAX ], " "height=(int) [ 1, MAX ], "
167 "framerate = (fraction) [0, MAX], "
168 "stream-format=(string) byte-stream, alignment=(string) au ";
170 gst_element_class_set_static_metadata (element_class,
171 "OpenMAX H.265 Video Encoder",
172 "Codec/Encoder/Video/Hardware",
173 "Encode H.265 video streams",
174 "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
176 gst_omx_set_default_role (&videoenc_class->cdata, "video_encoder.hevc");
180 gst_omx_h265_enc_set_property (GObject * object, guint prop_id,
181 const GValue * value, GParamSpec * pspec)
183 GstOMXH265Enc *self = GST_OMX_H265_ENC (object);
186 case PROP_INTERVALOFCODINGINTRAFRAMES:
187 self->interval_intraframes = g_value_get_uint (value);
189 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
190 case PROP_PERIODICITYOFIDRFRAMES:
191 self->periodicity_idr = g_value_get_uint (value);
194 self->b_frames = g_value_get_uint (value);
196 case PROP_CONSTRAINED_INTRA_PREDICTION:
197 self->constrained_intra_prediction = g_value_get_boolean (value);
199 case PROP_LOOP_FILTER_MODE:
200 self->loop_filter_mode = g_value_get_enum (value);
204 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
210 gst_omx_h265_enc_get_property (GObject * object, guint prop_id, GValue * value,
213 GstOMXH265Enc *self = GST_OMX_H265_ENC (object);
216 case PROP_INTERVALOFCODINGINTRAFRAMES:
217 g_value_set_uint (value, self->interval_intraframes);
219 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
220 case PROP_PERIODICITYOFIDRFRAMES:
221 g_value_set_uint (value, self->periodicity_idr);
224 g_value_set_uint (value, self->b_frames);
226 case PROP_CONSTRAINED_INTRA_PREDICTION:
227 g_value_set_boolean (value, self->constrained_intra_prediction);
229 case PROP_LOOP_FILTER_MODE:
230 g_value_set_enum (value, self->loop_filter_mode);
234 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
240 gst_omx_h265_enc_init (GstOMXH265Enc * self)
242 self->interval_intraframes =
243 GST_OMX_H265_VIDEO_ENC_INTERVAL_OF_CODING_INTRA_FRAMES_DEFAULT;
244 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
245 self->periodicity_idr =
246 GST_OMX_H265_VIDEO_ENC_PERIODICITY_OF_IDR_FRAMES_DEFAULT;
247 self->b_frames = GST_OMX_H265_VIDEO_ENC_B_FRAMES_DEFAULT;
248 self->constrained_intra_prediction =
249 GST_OMX_H265_VIDEO_ENC_CONSTRAINED_INTRA_PREDICTION_DEFAULT;
250 self->loop_filter_mode = GST_OMX_H265_VIDEO_ENC_LOOP_FILTER_MODE_DEFAULT;
254 /* Update OMX_VIDEO_PARAM_PROFILELEVELTYPE.{eProfile,eLevel}
256 * Returns TRUE if succeeded or if not supported, FALSE if failed */
258 update_param_profile_level (GstOMXH265Enc * self,
259 OMX_VIDEO_HEVCPROFILETYPE profile, OMX_VIDEO_HEVCLEVELTYPE level)
261 OMX_VIDEO_PARAM_PROFILELEVELTYPE param;
264 GST_OMX_INIT_STRUCT (¶m);
265 param.nPortIndex = GST_OMX_VIDEO_ENC (self)->enc_out_port->index;
268 gst_omx_component_get_parameter (GST_OMX_VIDEO_ENC (self)->enc,
269 OMX_IndexParamVideoProfileLevelCurrent, ¶m);
270 if (err != OMX_ErrorNone) {
271 GST_WARNING_OBJECT (self,
272 "Getting OMX_IndexParamVideoProfileLevelCurrent not supported by component");
276 if (profile != OMX_VIDEO_HEVCProfileUnknown)
277 param.eProfile = profile;
278 if (level != OMX_VIDEO_HEVCLevelUnknown)
279 param.eLevel = level;
282 gst_omx_component_set_parameter (GST_OMX_VIDEO_ENC (self)->enc,
283 OMX_IndexParamVideoProfileLevelCurrent, ¶m);
284 if (err == OMX_ErrorUnsupportedIndex) {
285 GST_WARNING_OBJECT (self,
286 "Setting OMX_IndexParamVideoProfileLevelCurrent not supported by component");
288 } else if (err != OMX_ErrorNone) {
289 GST_ERROR_OBJECT (self,
290 "Error setting profile %u and level %u: %s (0x%08x)",
291 (guint) param.eProfile, (guint) param.eLevel,
292 gst_omx_error_to_string (err), err);
299 /* Update OMX_ALG_VIDEO_PARAM_HEVCTYPE
301 * Returns TRUE if succeeded or if not supported, FALSE if failed */
303 update_param_hevc (GstOMXH265Enc * self,
304 OMX_VIDEO_HEVCPROFILETYPE profile, OMX_VIDEO_HEVCLEVELTYPE level)
306 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
307 OMX_ALG_VIDEO_PARAM_HEVCTYPE param;
309 OMX_VIDEO_PARAM_HEVCTYPE param;
313 GST_OMX_INIT_STRUCT (¶m);
314 param.nPortIndex = GST_OMX_VIDEO_ENC (self)->enc_out_port->index;
316 /* On Android the param struct is initialized manually with default
317 * settings rather than using GetParameter() to retrieve them.
318 * We should probably do the same when we'll add Android as target.
319 * See bgo#783862 for details. */
321 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
322 param.bConstIpred = self->constrained_intra_prediction;
324 if (self->loop_filter_mode != GST_OMX_H265_VIDEO_ENC_LOOP_FILTER_MODE_DEFAULT)
325 param.eLoopFilterMode = self->loop_filter_mode;
328 gst_omx_component_get_parameter (GST_OMX_VIDEO_ENC (self)->enc,
329 (OMX_INDEXTYPE) OMX_ALG_IndexParamVideoHevc, ¶m);
332 gst_omx_component_get_parameter (GST_OMX_VIDEO_ENC (self)->enc,
333 (OMX_INDEXTYPE) OMX_IndexParamVideoHevc, ¶m);
336 if (err != OMX_ErrorNone) {
337 GST_WARNING_OBJECT (self,
338 "Getting OMX_ALG_IndexParamVideoHevc not supported by component");
342 if (profile != OMX_VIDEO_HEVCProfileUnknown)
343 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
344 param.eProfile = (OMX_ALG_VIDEO_HEVCPROFILETYPE) profile;
346 param.eProfile = profile;
349 if (level != OMX_VIDEO_HEVCLevelUnknown)
350 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
351 param.eLevel = (OMX_ALG_VIDEO_HEVCLEVELTYPE) level;
353 param.eLevel = level;
357 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
358 /* The zynqultrascaleplus uses another PARAM_HEVCTYPE API allowing users to
359 * define the number of P and B frames while Android's API only expose the
361 if (self->interval_intraframes !=
362 GST_OMX_H265_VIDEO_ENC_INTERVAL_OF_CODING_INTRA_FRAMES_DEFAULT) {
363 param.nPFrames = self->interval_intraframes;
365 /* If user specified a specific number of B-frames, reduce the number of
366 * P-frames by this amount. If not ensure there is no B-frame to have the
367 * requested GOP length. */
368 if (self->b_frames != GST_OMX_H265_VIDEO_ENC_B_FRAMES_DEFAULT) {
369 if (self->b_frames > self->interval_intraframes) {
370 GST_ERROR_OBJECT (self,
371 "The interval_intraframes perdiod (%u) needs to be higher than the number of B-frames (%u)",
372 self->interval_intraframes, self->b_frames);
375 param.nPFrames -= self->b_frames;
381 if (self->b_frames != GST_OMX_H265_VIDEO_ENC_B_FRAMES_DEFAULT)
382 param.nBFrames = self->b_frames;
384 if (self->interval_intraframes !=
385 GST_OMX_H265_VIDEO_ENC_INTERVAL_OF_CODING_INTRA_FRAMES_DEFAULT)
386 param.nKeyFrameInterval = self->interval_intraframes;
390 gst_omx_component_set_parameter (GST_OMX_VIDEO_ENC (self)->enc,
391 (OMX_INDEXTYPE) INDEX_PARAM_VIDEO_HEVC, ¶m);
393 if (err == OMX_ErrorUnsupportedIndex) {
394 GST_WARNING_OBJECT (self,
395 "Setting IndexParamVideoHevc not supported by component");
397 } else if (err != OMX_ErrorNone) {
398 GST_ERROR_OBJECT (self,
399 "Error setting HEVC settings (profile %u and level %u): %s (0x%08x)",
400 (guint) param.eProfile, (guint) param.eLevel,
401 gst_omx_error_to_string (err), err);
408 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
410 set_intra_period (GstOMXH265Enc * self)
412 OMX_ALG_VIDEO_PARAM_INSTANTANEOUS_DECODING_REFRESH config_idr;
415 GST_OMX_INIT_STRUCT (&config_idr);
416 config_idr.nPortIndex = GST_OMX_VIDEO_ENC (self)->enc_out_port->index;
418 GST_DEBUG_OBJECT (self, "nIDRPeriod:%u",
419 (guint) config_idr.nInstantaneousDecodingRefreshFrequency);
421 config_idr.nInstantaneousDecodingRefreshFrequency = self->periodicity_idr;
424 gst_omx_component_set_parameter (GST_OMX_VIDEO_ENC (self)->enc,
425 (OMX_INDEXTYPE) OMX_ALG_IndexParamVideoInstantaneousDecodingRefresh,
427 if (err != OMX_ErrorNone) {
428 GST_ERROR_OBJECT (self,
429 "can't set OMX_IndexConfigVideoAVCIntraPeriod %s (0x%08x)",
430 gst_omx_error_to_string (err), err);
439 gst_omx_h265_enc_set_format (GstOMXVideoEnc * enc, GstOMXPort * port,
440 GstVideoCodecState * state)
442 GstOMXH265Enc *self = GST_OMX_H265_ENC (enc);
444 OMX_PARAM_PORTDEFINITIONTYPE port_def;
446 const gchar *profile_string, *level_string, *tier_string;
447 OMX_VIDEO_HEVCPROFILETYPE profile = OMX_VIDEO_HEVCProfileUnknown;
448 OMX_VIDEO_HEVCLEVELTYPE level = OMX_VIDEO_HEVCLevelUnknown;
450 #ifdef USE_OMX_TARGET_ZYNQ_USCALE_PLUS
451 if (self->periodicity_idr !=
452 GST_OMX_H265_VIDEO_ENC_PERIODICITY_OF_IDR_FRAMES_DEFAULT)
453 set_intra_period (self);
456 gst_omx_port_get_port_definition (GST_OMX_VIDEO_ENC (self)->enc_out_port,
458 port_def.format.video.eCompressionFormat =
459 (OMX_VIDEO_CODINGTYPE) OMX_VIDEO_CodingHEVC;
461 gst_omx_port_update_port_definition (GST_OMX_VIDEO_ENC
462 (self)->enc_out_port, &port_def);
463 if (err != OMX_ErrorNone)
466 /* Set profile and level */
467 peercaps = gst_pad_peer_query_caps (GST_VIDEO_ENCODER_SRC_PAD (enc),
468 gst_pad_get_pad_template_caps (GST_VIDEO_ENCODER_SRC_PAD (enc)));
472 if (gst_caps_is_empty (peercaps)) {
473 gst_caps_unref (peercaps);
474 GST_ERROR_OBJECT (self, "Empty caps");
478 s = gst_caps_get_structure (peercaps, 0);
479 profile_string = gst_structure_get_string (s, "profile");
480 if (profile_string) {
481 profile = gst_omx_h265_utils_get_profile_from_str (profile_string);
482 if (profile == OMX_VIDEO_HEVCProfileUnknown)
483 goto unsupported_profile;
486 level_string = gst_structure_get_string (s, "level");
487 tier_string = gst_structure_get_string (s, "tier");
488 if (level_string && tier_string) {
489 level = gst_omx_h265_utils_get_level_from_str (level_string, tier_string);
490 if (level == OMX_VIDEO_HEVCLevelUnknown)
491 goto unsupported_level;
494 gst_caps_unref (peercaps);
497 if (profile != OMX_VIDEO_HEVCProfileUnknown
498 || level != OMX_VIDEO_HEVCLevelUnknown) {
499 /* OMX provides 2 API to set the profile and level. We try using the
500 * generic one here and the H265 specific when calling
501 * update_param_hevc() */
502 if (!update_param_profile_level (self, profile, level))
506 if (!update_param_hevc (self, profile, level))
512 GST_ERROR_OBJECT (self, "Unsupported profile %s", profile_string);
513 gst_caps_unref (peercaps);
517 GST_ERROR_OBJECT (self, "Unsupported level %s", level_string);
518 gst_caps_unref (peercaps);
523 gst_omx_h265_enc_get_caps (GstOMXVideoEnc * enc, GstOMXPort * port,
524 GstVideoCodecState * state)
526 GstOMXH265Enc *self = GST_OMX_H265_ENC (enc);
529 OMX_VIDEO_PARAM_PROFILELEVELTYPE param;
530 const gchar *profile, *level, *tier;
532 GST_OMX_INIT_STRUCT (¶m);
533 param.nPortIndex = GST_OMX_VIDEO_ENC (self)->enc_out_port->index;
536 gst_omx_component_get_parameter (GST_OMX_VIDEO_ENC (self)->enc,
537 OMX_IndexParamVideoProfileLevelCurrent, ¶m);
538 if (err != OMX_ErrorNone && err != OMX_ErrorUnsupportedIndex)
541 caps = gst_caps_new_simple ("video/x-h265",
542 "stream-format", G_TYPE_STRING, "byte-stream",
543 "alignment", G_TYPE_STRING, "au", NULL);
545 if (err == OMX_ErrorNone) {
546 profile = gst_omx_h265_utils_get_profile_from_enum (param.eProfile);
548 g_assert_not_reached ();
549 gst_caps_unref (caps);
553 switch (param.eLevel) {
554 case OMX_VIDEO_HEVCMainTierLevel1:
558 case OMX_VIDEO_HEVCMainTierLevel2:
562 case OMX_VIDEO_HEVCMainTierLevel21:
566 case OMX_VIDEO_HEVCMainTierLevel3:
570 case OMX_VIDEO_HEVCMainTierLevel31:
574 case OMX_VIDEO_HEVCMainTierLevel4:
578 case OMX_VIDEO_HEVCMainTierLevel41:
582 case OMX_VIDEO_HEVCMainTierLevel5:
586 case OMX_VIDEO_HEVCMainTierLevel51:
590 case OMX_VIDEO_HEVCMainTierLevel52:
594 case OMX_VIDEO_HEVCMainTierLevel6:
598 case OMX_VIDEO_HEVCMainTierLevel61:
602 case OMX_VIDEO_HEVCMainTierLevel62:
606 case OMX_VIDEO_HEVCHighTierLevel4:
610 case OMX_VIDEO_HEVCHighTierLevel41:
614 case OMX_VIDEO_HEVCHighTierLevel5:
618 case OMX_VIDEO_HEVCHighTierLevel51:
622 case OMX_VIDEO_HEVCHighTierLevel52:
626 case OMX_VIDEO_HEVCHighTierLevel6:
630 case OMX_VIDEO_HEVCHighTierLevel61:
634 case OMX_VIDEO_HEVCHighTierLevel62:
639 g_assert_not_reached ();
640 gst_caps_unref (caps);
644 gst_caps_set_simple (caps,
645 "profile", G_TYPE_STRING, profile, "level", G_TYPE_STRING, level,
646 "tier", G_TYPE_STRING, tier, NULL);