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