1 /* GStreamer NVENC plugin
2 * Copyright (C) 2015 Centricular Ltd
3 * Copyright (C) 2018 Seungha Yang <pudding8757@gmail.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include "gstnvh265enc.h"
26
27 #include <gst/pbutils/codec-utils.h>
28
29 #include <string.h>
30
31 GST_DEBUG_CATEGORY_STATIC (gst_nv_h265_enc_debug);
32 #define GST_CAT_DEFAULT gst_nv_h265_enc_debug
33
34 #if HAVE_NVENC_GST_GL
35 #include <cuda.h>
36 #include <cuda_runtime_api.h>
37 #include <cuda_gl_interop.h>
38 #include <gst/gl/gl.h>
39 #endif
40
41 #define parent_class gst_nv_h265_enc_parent_class
42 G_DEFINE_TYPE (GstNvH265Enc, gst_nv_h265_enc, GST_TYPE_NV_BASE_ENC);
43
44 #if HAVE_NVENC_GST_GL
45 #define GL_CAPS_STR \
46 ";" \
47 "video/x-raw(memory:GLMemory), " \
48 "format = (string) { NV12, Y444 }, " \
49 "width = (int) [ 16, 4096 ], height = (int) [ 16, 4096 ], " \
50 "framerate = (fraction) [0, MAX] "
51 #else
52 #define GL_CAPS_STR ""
53 #endif
54
55 /* *INDENT-OFF* */
56 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
57 GST_PAD_SINK,
58 GST_PAD_ALWAYS,
59 GST_STATIC_CAPS ("video/x-raw, " "format = (string) { NV12, I420 }, " // TODO: YV12, Y444 support
60 "width = (int) [ 16, 4096 ], height = (int) [ 16, 4096 ], "
61 "framerate = (fraction) [0, MAX] "
62 GL_CAPS_STR
63 ));
64
65 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
66 GST_PAD_SRC,
67 GST_PAD_ALWAYS,
68 GST_STATIC_CAPS ("video/x-h265, "
69 "width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ], "
70 "framerate = (fraction) [0/1, MAX], "
71 "stream-format = (string) byte-stream, "
72 "alignment = (string) au, "
73 "profile = (string) { main }") // TODO: a couple of others
74 );
75 /* *INDENT-ON* */
76
77 static gboolean gst_nv_h265_enc_open (GstVideoEncoder * enc);
78 static gboolean gst_nv_h265_enc_close (GstVideoEncoder * enc);
79 static GstCaps *gst_nv_h265_enc_getcaps (GstVideoEncoder * enc,
80 GstCaps * filter);
81 static gboolean gst_nv_h265_enc_set_src_caps (GstNvBaseEnc * nvenc,
82 GstVideoCodecState * state);
83 static gboolean gst_nv_h265_enc_set_encoder_config (GstNvBaseEnc * nvenc,
84 GstVideoCodecState * state, NV_ENC_CONFIG * config);
85 static gboolean gst_nv_h265_enc_set_pic_params (GstNvBaseEnc * nvenc,
86 GstVideoCodecFrame * frame, NV_ENC_PIC_PARAMS * pic_params);
87 static void gst_nv_h265_enc_set_property (GObject * object, guint prop_id,
88 const GValue * value, GParamSpec * pspec);
89 static void gst_nv_h265_enc_get_property (GObject * object, guint prop_id,
90 GValue * value, GParamSpec * pspec);
91 static void gst_nv_h265_enc_finalize (GObject * obj);
92
93 static void
gst_nv_h265_enc_class_init(GstNvH265EncClass * klass)94 gst_nv_h265_enc_class_init (GstNvH265EncClass * klass)
95 {
96 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
97 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
98 GstVideoEncoderClass *videoenc_class = GST_VIDEO_ENCODER_CLASS (klass);
99 GstNvBaseEncClass *nvenc_class = GST_NV_BASE_ENC_CLASS (klass);
100
101 gobject_class->set_property = gst_nv_h265_enc_set_property;
102 gobject_class->get_property = gst_nv_h265_enc_get_property;
103 gobject_class->finalize = gst_nv_h265_enc_finalize;
104
105 videoenc_class->open = GST_DEBUG_FUNCPTR (gst_nv_h265_enc_open);
106 videoenc_class->close = GST_DEBUG_FUNCPTR (gst_nv_h265_enc_close);
107
108 videoenc_class->getcaps = GST_DEBUG_FUNCPTR (gst_nv_h265_enc_getcaps);
109
110 nvenc_class->codec_id = NV_ENC_CODEC_HEVC_GUID;
111 nvenc_class->set_encoder_config = gst_nv_h265_enc_set_encoder_config;
112 nvenc_class->set_src_caps = gst_nv_h265_enc_set_src_caps;
113 nvenc_class->set_pic_params = gst_nv_h265_enc_set_pic_params;
114
115 gst_element_class_add_static_pad_template (element_class, &sink_factory);
116 gst_element_class_add_static_pad_template (element_class, &src_factory);
117
118 gst_element_class_set_static_metadata (element_class,
119 "NVENC HEVC Video Encoder",
120 "Codec/Encoder/Video/Hardware",
121 "Encode HEVC video streams using NVIDIA's hardware-accelerated NVENC encoder API",
122 "Tim-Philipp Müller <tim@centricular.com>, "
123 "Matthew Waters <matthew@centricular.com>, "
124 "Seungha Yang <pudding8757@gmail.com>");
125
126 GST_DEBUG_CATEGORY_INIT (gst_nv_h265_enc_debug,
127 "nvh265enc", 0, "Nvidia HEVC encoder");
128 }
129
130 static void
gst_nv_h265_enc_init(GstNvH265Enc * nvenc)131 gst_nv_h265_enc_init (GstNvH265Enc * nvenc)
132 {
133 }
134
135 static void
gst_nv_h265_enc_finalize(GObject * obj)136 gst_nv_h265_enc_finalize (GObject * obj)
137 {
138 G_OBJECT_CLASS (gst_nv_h265_enc_parent_class)->finalize (obj);
139 }
140
141 static gboolean
_get_supported_profiles(GstNvH265Enc * nvenc)142 _get_supported_profiles (GstNvH265Enc * nvenc)
143 {
144 NVENCSTATUS nv_ret;
145 GUID profile_guids[64];
146 GValue list = G_VALUE_INIT;
147 GValue val = G_VALUE_INIT;
148 guint i, n, n_profiles;
149
150 if (nvenc->supported_profiles)
151 return TRUE;
152
153 nv_ret =
154 NvEncGetEncodeProfileGUIDCount (GST_NV_BASE_ENC (nvenc)->encoder,
155 NV_ENC_CODEC_HEVC_GUID, &n);
156 if (nv_ret != NV_ENC_SUCCESS)
157 return FALSE;
158
159 nv_ret =
160 NvEncGetEncodeProfileGUIDs (GST_NV_BASE_ENC (nvenc)->encoder,
161 NV_ENC_CODEC_HEVC_GUID, profile_guids, G_N_ELEMENTS (profile_guids), &n);
162 if (nv_ret != NV_ENC_SUCCESS)
163 return FALSE;
164
165 n_profiles = 0;
166 g_value_init (&list, GST_TYPE_LIST);
167 for (i = 0; i < n; i++) {
168 g_value_init (&val, G_TYPE_STRING);
169
170 if (gst_nvenc_cmp_guid (profile_guids[i], NV_ENC_HEVC_PROFILE_MAIN_GUID)) {
171 g_value_set_static_string (&val, "main");
172 gst_value_list_append_value (&list, &val);
173 n_profiles++;
174 }
175 /* TODO: map MAIN10, FREXT */
176
177 g_value_unset (&val);
178 }
179
180 if (n_profiles == 0)
181 return FALSE;
182
183 GST_OBJECT_LOCK (nvenc);
184 nvenc->supported_profiles = g_new0 (GValue, 1);
185 *nvenc->supported_profiles = list;
186 GST_OBJECT_UNLOCK (nvenc);
187
188 return TRUE;
189 }
190
191 static gboolean
gst_nv_h265_enc_open(GstVideoEncoder * enc)192 gst_nv_h265_enc_open (GstVideoEncoder * enc)
193 {
194 GstNvH265Enc *nvenc = GST_NV_H265_ENC (enc);
195
196 if (!GST_VIDEO_ENCODER_CLASS (gst_nv_h265_enc_parent_class)->open (enc))
197 return FALSE;
198
199 /* Check if HEVC is supported */
200 {
201 uint32_t i, num = 0;
202 GUID guids[16];
203
204 NvEncGetEncodeGUIDs (GST_NV_BASE_ENC (nvenc)->encoder, guids,
205 G_N_ELEMENTS (guids), &num);
206
207 for (i = 0; i < num; ++i) {
208 if (gst_nvenc_cmp_guid (guids[i], NV_ENC_CODEC_HEVC_GUID))
209 break;
210 }
211 GST_INFO_OBJECT (enc, "HEVC encoding %ssupported", (i == num) ? "un" : "");
212 if (i == num) {
213 gst_nv_h265_enc_close (enc);
214 return FALSE;
215 }
216 }
217
218 /* query supported input formats */
219 if (!_get_supported_profiles (nvenc)) {
220 GST_WARNING_OBJECT (nvenc, "No supported encoding profiles");
221 gst_nv_h265_enc_close (enc);
222 return FALSE;
223 }
224
225 return TRUE;
226 }
227
228 static gboolean
gst_nv_h265_enc_close(GstVideoEncoder * enc)229 gst_nv_h265_enc_close (GstVideoEncoder * enc)
230 {
231 GstNvH265Enc *nvenc = GST_NV_H265_ENC (enc);
232
233 GST_OBJECT_LOCK (nvenc);
234 if (nvenc->supported_profiles)
235 g_value_unset (nvenc->supported_profiles);
236 g_free (nvenc->supported_profiles);
237 nvenc->supported_profiles = NULL;
238 GST_OBJECT_UNLOCK (nvenc);
239
240 return GST_VIDEO_ENCODER_CLASS (gst_nv_h265_enc_parent_class)->close (enc);
241 }
242
243 static GstCaps *
gst_nv_h265_enc_getcaps(GstVideoEncoder * enc,GstCaps * filter)244 gst_nv_h265_enc_getcaps (GstVideoEncoder * enc, GstCaps * filter)
245 {
246 GstNvH265Enc *nvenc = GST_NV_H265_ENC (enc);
247 GstCaps *supported_incaps = NULL;
248 GstCaps *template_caps, *caps;
249 GValue *input_formats = GST_NV_BASE_ENC (enc)->input_formats;
250
251 GST_OBJECT_LOCK (nvenc);
252
253 if (input_formats != NULL) {
254 template_caps = gst_pad_get_pad_template_caps (enc->sinkpad);
255 supported_incaps = gst_caps_copy (template_caps);
256 gst_caps_set_value (supported_incaps, "format", input_formats);
257
258 GST_LOG_OBJECT (enc, "codec input caps %" GST_PTR_FORMAT, supported_incaps);
259 GST_LOG_OBJECT (enc, " template caps %" GST_PTR_FORMAT, template_caps);
260 caps = gst_caps_intersect (template_caps, supported_incaps);
261 gst_caps_unref (template_caps);
262 gst_caps_unref (supported_incaps);
263 supported_incaps = caps;
264 GST_LOG_OBJECT (enc, " supported caps %" GST_PTR_FORMAT, supported_incaps);
265 }
266
267 GST_OBJECT_UNLOCK (nvenc);
268
269 caps = gst_video_encoder_proxy_getcaps (enc, supported_incaps, filter);
270
271 if (supported_incaps)
272 gst_caps_unref (supported_incaps);
273
274 GST_DEBUG_OBJECT (nvenc, " returning caps %" GST_PTR_FORMAT, caps);
275
276 return caps;
277 }
278
279 static gboolean
gst_nv_h265_enc_set_level_tier_and_profile(GstNvH265Enc * nvenc,GstCaps * caps)280 gst_nv_h265_enc_set_level_tier_and_profile (GstNvH265Enc * nvenc,
281 GstCaps * caps)
282 {
283 #define N_BYTES_VPS 128
284 guint8 vps[N_BYTES_VPS];
285 NV_ENC_SEQUENCE_PARAM_PAYLOAD spp = { 0, };
286 NVENCSTATUS nv_ret;
287 guint32 seq_size;
288
289 spp.version = NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER;
290 spp.inBufferSize = N_BYTES_VPS;
291 spp.spsId = 0;
292 spp.ppsId = 0;
293 spp.spsppsBuffer = &vps;
294 spp.outSPSPPSPayloadSize = &seq_size;
295 nv_ret = NvEncGetSequenceParams (GST_NV_BASE_ENC (nvenc)->encoder, &spp);
296 if (nv_ret != NV_ENC_SUCCESS) {
297 GST_ELEMENT_ERROR (nvenc, STREAM, ENCODE, ("Encode header failed."),
298 ("NvEncGetSequenceParams return code=%d", nv_ret));
299 return FALSE;
300 }
301
302 if (seq_size < 8) {
303 GST_ELEMENT_ERROR (nvenc, STREAM, ENCODE, ("Encode header failed."),
304 ("NvEncGetSequenceParams returned incomplete data"));
305 return FALSE;
306 }
307
308 GST_MEMDUMP ("Header", spp.spsppsBuffer, seq_size);
309
310 /* skip nal header and identifier */
311 gst_codec_utils_h265_caps_set_level_tier_and_profile (caps,
312 &vps[6], seq_size - 6);
313
314 return TRUE;
315
316 #undef N_BYTES_VPS
317 }
318
319 static gboolean
gst_nv_h265_enc_set_src_caps(GstNvBaseEnc * nvenc,GstVideoCodecState * state)320 gst_nv_h265_enc_set_src_caps (GstNvBaseEnc * nvenc, GstVideoCodecState * state)
321 {
322 GstNvH265Enc *h265enc = GST_NV_H265_ENC (nvenc);
323 GstVideoCodecState *out_state;
324 GstStructure *s;
325 GstCaps *out_caps;
326
327 out_caps = gst_caps_new_empty_simple ("video/x-h265");
328 s = gst_caps_get_structure (out_caps, 0);
329
330 /* TODO: add support for hvc1,hev1 format as well */
331 gst_structure_set (s, "stream-format", G_TYPE_STRING, "byte-stream",
332 "alignment", G_TYPE_STRING, "au", NULL);
333
334 if (!gst_nv_h265_enc_set_level_tier_and_profile (h265enc, out_caps)) {
335 gst_caps_unref (out_caps);
336 return FALSE;
337 }
338
339 out_state = gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (nvenc),
340 out_caps, state);
341
342 GST_INFO_OBJECT (nvenc, "output caps: %" GST_PTR_FORMAT, out_state->caps);
343
344 /* encoder will keep it around for us */
345 gst_video_codec_state_unref (out_state);
346
347 /* TODO: would be nice to also send some tags with the codec name */
348 return TRUE;
349 }
350
351 static gboolean
gst_nv_h265_enc_set_encoder_config(GstNvBaseEnc * nvenc,GstVideoCodecState * state,NV_ENC_CONFIG * config)352 gst_nv_h265_enc_set_encoder_config (GstNvBaseEnc * nvenc,
353 GstVideoCodecState * state, NV_ENC_CONFIG * config)
354 {
355 GstNvH265Enc *h265enc = GST_NV_H265_ENC (nvenc);
356 GstCaps *allowed_caps, *template_caps;
357 GUID selected_profile = NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID;
358 int level_idc = NV_ENC_LEVEL_AUTOSELECT;
359
360 template_caps = gst_static_pad_template_get_caps (&src_factory);
361 allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (h265enc));
362
363 if (template_caps == allowed_caps) {
364 GST_INFO_OBJECT (h265enc, "downstream has ANY caps");
365 } else if (allowed_caps) {
366 GstStructure *s;
367 const gchar *profile;
368 const gchar *level;
369
370 if (gst_caps_is_empty (allowed_caps)) {
371 gst_caps_unref (allowed_caps);
372 gst_caps_unref (template_caps);
373 return FALSE;
374 }
375
376 allowed_caps = gst_caps_make_writable (allowed_caps);
377 allowed_caps = gst_caps_fixate (allowed_caps);
378 s = gst_caps_get_structure (allowed_caps, 0);
379
380 profile = gst_structure_get_string (s, "profile");
381 /* FIXME: only support main profile only for now */
382 if (profile) {
383 if (!strcmp (profile, "main")) {
384 selected_profile = NV_ENC_HEVC_PROFILE_MAIN_GUID;
385 } else {
386 g_assert_not_reached ();
387 }
388 }
389
390 level = gst_structure_get_string (s, "level");
391 if (level)
392 /* matches values stored in NV_ENC_LEVEL */
393 level_idc = gst_codec_utils_h265_get_level_idc (level);
394
395 gst_caps_unref (allowed_caps);
396 }
397 gst_caps_unref (template_caps);
398
399 /* override some defaults */
400 GST_LOG_OBJECT (h265enc, "setting parameters");
401 config->profileGUID = selected_profile;
402 config->encodeCodecConfig.hevcConfig.level = level_idc;
403 config->encodeCodecConfig.hevcConfig.idrPeriod = config->gopLength;
404
405 /* FIXME: make property */
406 config->encodeCodecConfig.hevcConfig.outputAUD = 1;
407
408 return TRUE;
409 }
410
411 static gboolean
gst_nv_h265_enc_set_pic_params(GstNvBaseEnc * enc,GstVideoCodecFrame * frame,NV_ENC_PIC_PARAMS * pic_params)412 gst_nv_h265_enc_set_pic_params (GstNvBaseEnc * enc, GstVideoCodecFrame * frame,
413 NV_ENC_PIC_PARAMS * pic_params)
414 {
415 /* encode whole picture in one single slice */
416 pic_params->codecPicParams.hevcPicParams.sliceMode = 0;
417 pic_params->codecPicParams.hevcPicParams.sliceModeData = 0;
418
419 return TRUE;
420 }
421
422 static void
gst_nv_h265_enc_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)423 gst_nv_h265_enc_set_property (GObject * object, guint prop_id,
424 const GValue * value, GParamSpec * pspec)
425 {
426 switch (prop_id) {
427 default:
428 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
429 break;
430 }
431 }
432
433 static void
gst_nv_h265_enc_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)434 gst_nv_h265_enc_get_property (GObject * object, guint prop_id, GValue * value,
435 GParamSpec * pspec)
436 {
437 switch (prop_id) {
438 default:
439 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
440 break;
441 }
442 }
443