nvidia_video_codec_sdk/safe/
encoder.rs

1//! The [`Encoder`] is the main entrypoint for the Encoder API.
2//!
3//! The [`Encoder`] provides a slightly higher-level abstraction over the
4//! encoder API. This module also defines builders for some of the parameter
5//! structs used by the interface.
6
7use std::{ffi::c_void, ptr, sync::Arc};
8
9use cudarc::driver::CudaContext;
10
11use super::{api::ENCODE_API, result::EncodeError, session::Session};
12use crate::sys::nvEncodeAPI::{
13    GUID,
14    NVENCAPI_VERSION,
15    NV_ENC_BUFFER_FORMAT,
16    NV_ENC_CONFIG,
17    NV_ENC_CONFIG_VER,
18    NV_ENC_DEVICE_TYPE,
19    NV_ENC_INITIALIZE_PARAMS,
20    NV_ENC_INITIALIZE_PARAMS_VER,
21    NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS,
22    NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER,
23    NV_ENC_PRESET_CONFIG,
24    NV_ENC_PRESET_CONFIG_VER,
25    NV_ENC_TUNING_INFO,
26};
27
28/// Entrypoint for the Encoder API.
29///
30/// The general usage follows these steps:
31/// 1. Initialize the encoder.
32/// 2. Set up the desired encoding parameters.
33/// 3. Allocate or register input and output buffers.
34/// 4. Copy frames to input buffers, encode, and read out of output bitstream.
35/// 5. Close the encoding session and clean up.
36///
37/// With this wrapper cleanup is performed automatically.
38/// To do the other steps this struct provides associated functions
39/// such as [`Encoder::get_encode_guids`] or
40/// [`Encoder::get_supported_input_formats`].
41///
42/// Once the configuration is completed, a session should be initialized with
43/// [`Encoder::start_session`] to get a [`Session`].
44/// This type has further function to create input and output buffers
45/// and encode pictures.
46///
47/// See [NVIDIA Video Codec SDK - Video Encoder API Programming Guide](https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/nvenc-video-encoder-api-prog-guide/index.html).
48#[derive(Debug)]
49pub struct Encoder {
50    pub(crate) ptr: *mut c_void,
51    // Used to fetch the device pointer for an externally allocated buffer
52    pub(crate) ctx: Arc<CudaContext>,
53}
54
55/// The client must flush the encoder before freeing any resources.
56/// Do this by sending an EOS encode frame.
57/// (This is also done automatically when [`Session`] is dropped.).
58///
59/// Sending an EOS frame might still generate data, so if you care
60/// about this you should send an EOS frame yourself.
61///
62/// The client must free all the input and output resources before
63/// destroying the encoder.
64/// If using events, they must also be unregistered.
65impl Drop for Encoder {
66    fn drop(&mut self) {
67        unsafe { (ENCODE_API.destroy_encoder)(self.ptr) }
68            .result(self)
69            .expect("The encoder pointer should be valid.");
70    }
71}
72
73impl Encoder {
74    /// Create an [`Encoder`] with CUDA as the encode device.
75    ///
76    /// See [NVIDIA docs](https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/nvenc-video-encoder-api-prog-guide/index.html#cuda).
77    ///
78    /// # Errors
79    ///
80    /// Could error if there was no encode capable device detected
81    /// or if the encode device was invalid.
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// # use cudarc::driver::CudaContext;
87    /// # use nvidia_video_codec_sdk::Encoder;
88    /// let cuda_ctx = CudaContext::new(0).unwrap();
89    /// let encoder = Encoder::initialize_with_cuda(cuda_ctx).unwrap();
90    /// ```
91    pub fn initialize_with_cuda(cuda_ctx: Arc<CudaContext>) -> Result<Self, EncodeError> {
92        let mut encoder = ptr::null_mut();
93        let mut session_params = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS {
94            version: NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER,
95            deviceType: NV_ENC_DEVICE_TYPE::NV_ENC_DEVICE_TYPE_CUDA,
96            apiVersion: NVENCAPI_VERSION,
97            // Pass the CUDA Context as the device.
98            // valid casting since CUcontext is a *mut
99            device: cuda_ctx.cu_ctx().cast::<c_void>(),
100            ..Default::default()
101        };
102
103        if let err @ Err(_) =
104            unsafe { (ENCODE_API.open_encode_session_ex)(&mut session_params, &mut encoder) }
105                .result_without_string()
106        {
107            // We are required to destroy the encoder if there was an error.
108            unsafe { (ENCODE_API.destroy_encoder)(encoder) }.result_without_string()?;
109            err?;
110        }
111
112        Ok(Self {
113            ptr: encoder,
114            ctx: cuda_ctx,
115        })
116    }
117
118    // TODO:
119    // - Make Encoder generic in Device.
120    // - Add functions to create Encoder from other encode devices.
121
122    /// Get the encode GUIDs which the encoder supports.
123    ///
124    /// You should use this function to check whether your
125    /// machine supports the video compression standard
126    /// that you with to use.
127    ///
128    /// See [NVIDIA docs](https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/nvenc-video-encoder-api-prog-guide/index.html#selecting-encoder-codec-guid).
129    ///
130    /// # Errors
131    ///
132    /// Could error if we run out of memory.
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// # use cudarc::driver::CudaContext;
138    /// # use nvidia_video_codec_sdk::{sys::nvEncodeAPI::NV_ENC_CODEC_H264_GUID, Encoder};
139    /// # let cuda_ctx = CudaContext::new(0).unwrap();
140    /// let encoder = Encoder::initialize_with_cuda(cuda_ctx).unwrap();
141    /// let encode_guids = encoder.get_encode_guids().unwrap();
142    /// // Confirm that this machine support encoding to H.264.
143    /// assert!(encode_guids.contains(&NV_ENC_CODEC_H264_GUID));
144    /// ```
145    pub fn get_encode_guids(&self) -> Result<Vec<GUID>, EncodeError> {
146        // Query number of supported encoder codec GUIDs.
147        let mut supported_count = 0;
148        unsafe { (ENCODE_API.get_encode_guid_count)(self.ptr, &mut supported_count) }
149            .result(self)?;
150        // Get the supported GUIDs.
151        let mut encode_guids = vec![GUID::default(); supported_count as usize];
152        let mut actual_count = 0;
153        unsafe {
154            (ENCODE_API.get_encode_guids)(
155                self.ptr,
156                encode_guids.as_mut_ptr(),
157                supported_count,
158                &mut actual_count,
159            )
160        }
161        .result(self)?;
162        encode_guids.truncate(actual_count as usize);
163        Ok(encode_guids)
164    }
165
166    /// Get the encode preset GUIDs which the encoder supports
167    /// for the given codec GUID.
168    ///
169    /// You should use this function to check whether your
170    /// machine supports the encode preset that you wish to use.
171    ///
172    /// See [NVIDIA docs](https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/nvenc-video-encoder-api-prog-guide/index.html#selecting-encoder-preset-configuration).
173    ///
174    /// # Errors
175    ///
176    /// Could error if the encode GUID is invalid
177    /// or we run out of memory.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// # use cudarc::driver::CudaContext;
183    /// # use nvidia_video_codec_sdk::{
184    /// #     sys::nvEncodeAPI::{NV_ENC_CODEC_H264_GUID, NV_ENC_PRESET_P1_GUID},
185    /// #     Encoder,
186    /// # };
187    /// # let cuda_ctx = CudaContext::new(0).unwrap();
188    /// let encoder = Encoder::initialize_with_cuda(cuda_ctx).unwrap();
189    ///
190    /// //* Check if H.264 encoding is supported. *//
191    /// # let encode_guids = encoder.get_encode_guids().unwrap();
192    /// # assert!(encode_guids.contains(&NV_ENC_CODEC_H264_GUID));
193    ///
194    /// let preset_guids = encoder.get_preset_guids(NV_ENC_CODEC_H264_GUID).unwrap();
195    /// // Confirm that H.264 supports the P1 preset (high performance, low quality) on this machine.
196    /// assert!(preset_guids.contains(&NV_ENC_PRESET_P1_GUID));
197    /// ```
198    pub fn get_preset_guids(&self, encode_guid: GUID) -> Result<Vec<GUID>, EncodeError> {
199        // Query the number of preset GUIDS.
200        let mut preset_count = 0;
201        unsafe { (ENCODE_API.get_encode_preset_count)(self.ptr, encode_guid, &mut preset_count) }
202            .result(self)?;
203        // Get the preset GUIDs.
204        let mut actual_count = 0;
205        let mut preset_guids = vec![GUID::default(); preset_count as usize];
206        unsafe {
207            (ENCODE_API.get_encode_preset_guids)(
208                self.ptr,
209                encode_guid,
210                preset_guids.as_mut_ptr(),
211                preset_count,
212                &mut actual_count,
213            )
214        }
215        .result(self)?;
216        preset_guids.truncate(actual_count as usize);
217        Ok(preset_guids)
218    }
219
220    /// Get the encode profile GUIDs which the encoder supports
221    /// for the given codec GUID.
222    ///
223    /// You should use this function to check whether your
224    /// machine supports the encode profile that you wish to use.
225    ///
226    /// See [NVIDIA docs](https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/nvenc-video-encoder-api-prog-guide/index.html#selecting-an-encoder-profile).
227    ///
228    /// # Errors
229    ///
230    /// Could error if the encode GUID is invalid
231    /// or we run out of memory.
232    ///
233    /// # Examples
234    ///
235    /// ```
236    /// # use cudarc::driver::CudaContext;
237    /// # use nvidia_video_codec_sdk::{
238    /// #     sys::nvEncodeAPI::{NV_ENC_CODEC_H264_GUID, NV_ENC_H264_PROFILE_HIGH_GUID},
239    /// #     Encoder,
240    /// # };
241    /// # let cuda_ctx = CudaContext::new(0).unwrap();
242    /// let encoder = Encoder::initialize_with_cuda(cuda_ctx).unwrap();
243    ///
244    /// //* Check if H.264 encoding is supported. *//
245    /// # let encode_guids = encoder.get_encode_guids().unwrap();
246    /// # assert!(encode_guids.contains(&NV_ENC_CODEC_H264_GUID));
247    ///
248    /// let profile_guids = encoder.get_profile_guids(NV_ENC_CODEC_H264_GUID).unwrap();
249    /// // Confirm that H.264 supports the HIGH profile on this machine.
250    /// assert!(profile_guids.contains(&NV_ENC_H264_PROFILE_HIGH_GUID));
251    /// ```
252    pub fn get_profile_guids(&self, encode_guid: GUID) -> Result<Vec<GUID>, EncodeError> {
253        // Query the number of profile GUIDs.
254        let mut profile_count = 0;
255        unsafe {
256            (ENCODE_API.get_encode_profile_guid_count)(self.ptr, encode_guid, &mut profile_count)
257        }
258        .result(self)?;
259        // Get the profile GUIDs.
260        let mut profile_guids = vec![GUID::default(); profile_count as usize];
261        let mut actual_count = 0;
262        unsafe {
263            (ENCODE_API.get_encode_profile_guids)(
264                self.ptr,
265                encode_guid,
266                profile_guids.as_mut_ptr(),
267                profile_count,
268                &mut actual_count,
269            )
270        }
271        .result(self)?;
272        profile_guids.truncate(actual_count as usize);
273        Ok(profile_guids)
274    }
275
276    /// Get the buffer formats which the encoder supports
277    /// for the given codec GUID.
278    ///
279    /// You should use this function to check whether your
280    /// machine supports the buffer format that you wish to use.
281    ///
282    /// See [NVIDIA docs](https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/nvenc-video-encoder-api-prog-guide/index.html#getting-supported-list-of-input-formats).
283    ///
284    /// # Errors
285    ///
286    /// Could error if the encode GUID is invalid
287    /// or we run out of memory.
288    ///
289    /// # Examples
290    ///
291    /// ```
292    /// # use cudarc::driver::CudaContext;
293    /// # use nvidia_video_codec_sdk::{
294    /// #     sys::nvEncodeAPI::{NV_ENC_BUFFER_FORMAT, NV_ENC_CODEC_H264_GUID},
295    /// #     Encoder,
296    /// # };
297    /// # let cuda_ctx = CudaContext::new(0).unwrap();
298    /// let encoder = Encoder::initialize_with_cuda(cuda_ctx).unwrap();
299    ///
300    /// //* Check if H.264 encoding is supported. *//
301    /// # let encode_guids = encoder.get_encode_guids().unwrap();
302    /// # assert!(encode_guids.contains(&NV_ENC_CODEC_H264_GUID));
303    ///
304    /// let input_guids = encoder
305    ///     .get_supported_input_formats(NV_ENC_CODEC_H264_GUID)
306    ///     .unwrap();
307    /// // Confirm that H.264 supports the `ARGB10` format on this machine.
308    /// assert!(input_guids.contains(&NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ARGB10));
309    /// ```
310    pub fn get_supported_input_formats(
311        &self,
312        encode_guid: GUID,
313    ) -> Result<Vec<NV_ENC_BUFFER_FORMAT>, EncodeError> {
314        // Query the number of supported input formats.
315        let mut format_count = 0;
316        unsafe { (ENCODE_API.get_input_format_count)(self.ptr, encode_guid, &mut format_count) }
317            .result(self)?;
318        // Get the supported input formats.
319        let mut supported_input_formats =
320            vec![NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_UNDEFINED; format_count as usize];
321        let mut actual_count = 0;
322        unsafe {
323            (ENCODE_API.get_input_formats)(
324                self.ptr,
325                encode_guid,
326                supported_input_formats.as_mut_ptr(),
327                format_count,
328                &mut actual_count,
329            )
330        }
331        .result(self)?;
332        supported_input_formats.truncate(actual_count as usize);
333        Ok(supported_input_formats)
334    }
335
336    /// Get the preset config struct from the given codec GUID, preset GUID,
337    /// and tuning info.
338    ///
339    /// You should use this function to generate a preset config for the
340    /// encoder session if you want to modify the preset further.
341    ///
342    /// See [NVIDIA docs](https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/nvenc-video-encoder-api-prog-guide/index.html#selecting-encoder-preset-configuration)
343    ///
344    /// # Errors
345    ///
346    /// Could error if `encode_guid` or `preset_guid` is invalid,
347    /// if `tuning_info` is set to
348    /// [`NV_ENC_TUNING_INFO::NV_ENC_TUNING_INFO_UNDEFINED`] or
349    /// [`NV_ENC_TUNING_INFO::NV_ENC_TUNING_INFO_COUNT`],
350    /// or if we run out of memory.
351    ///
352    /// # Examples
353    ///
354    /// ```
355    /// # use cudarc::driver::CudaContext;
356    /// # use nvidia_video_codec_sdk::{
357    /// #     sys::nvEncodeAPI::{
358    /// #         NV_ENC_CODEC_H264_GUID,
359    /// #         NV_ENC_PRESET_P1_GUID,
360    /// #         NV_ENC_TUNING_INFO,
361    /// #     },
362    /// #     Encoder,
363    /// # };
364    /// # let cuda_ctx = CudaContext::new(0).unwrap();
365    /// let encoder = Encoder::initialize_with_cuda(cuda_ctx).unwrap();
366    ///
367    /// //* Check if H.264 encoding and the P1 preset (highest performance) are supported. *//
368    /// # let encode_guids = encoder.get_encode_guids().unwrap();
369    /// # assert!(encode_guids.contains(&NV_ENC_CODEC_H264_GUID));
370    /// # let preset_guids = encoder.get_preset_guids(NV_ENC_CODEC_H264_GUID).unwrap();
371    /// # assert!(preset_guids.contains(&NV_ENC_PRESET_P1_GUID));
372    ///
373    /// // Create the preset config.
374    /// let _preset_config = encoder
375    ///     .get_preset_config(
376    ///         NV_ENC_CODEC_H264_GUID,
377    ///         NV_ENC_PRESET_P1_GUID,
378    ///         NV_ENC_TUNING_INFO::NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY,
379    ///     )
380    ///     .unwrap();
381    /// ```
382    pub fn get_preset_config(
383        &self,
384        encode_guid: GUID,
385        preset_guid: GUID,
386        tuning_info: NV_ENC_TUNING_INFO,
387    ) -> Result<NV_ENC_PRESET_CONFIG, EncodeError> {
388        let mut preset_config = NV_ENC_PRESET_CONFIG {
389            version: NV_ENC_PRESET_CONFIG_VER,
390            presetCfg: NV_ENC_CONFIG {
391                version: NV_ENC_CONFIG_VER,
392                ..Default::default()
393            },
394            ..Default::default()
395        };
396        unsafe {
397            (ENCODE_API.get_encode_preset_config_ex)(
398                self.ptr,
399                encode_guid,
400                preset_guid,
401                tuning_info,
402                &mut preset_config,
403            )
404        }
405        .result(self)?;
406        Ok(preset_config)
407    }
408
409    /// Initialize an encoder session with the given configuration.
410    ///
411    /// You must do this before you can encode a picture.
412    /// You should use the [`NV_ENC_INITIALIZE_PARAMS`] builder
413    /// via [`NV_ENC_INITIALIZE_PARAMS::new`].
414    ///
415    /// See [NVIDIA docs](https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/nvenc-video-encoder-api-prog-guide/index.html#initializing-the-hardware-encoder-session).
416    ///
417    /// # Errors
418    ///
419    /// Could error if the `initialize_params` are invalid
420    /// or if we run out of memory.
421    ///
422    /// # Examples
423    ///
424    /// ```
425    /// # use cudarc::driver::CudaContext;
426    /// # use nvidia_video_codec_sdk::{
427    /// #     sys::nvEncodeAPI::{
428    /// #         NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ARGB,
429    /// #         NV_ENC_CODEC_H264_GUID,
430    /// #     },
431    /// #     Encoder, EncoderInitParams
432    /// # };
433    /// # let cuda_ctx = CudaContext::new(0).unwrap();
434    /// let encoder = Encoder::initialize_with_cuda(cuda_ctx).unwrap();
435    ///
436    /// //* Check if `NV_ENC_CODEC_H264_GUID` is supported. *//
437    /// # let encode_guids = encoder.get_encode_guids().unwrap();
438    /// # assert!(encode_guids.contains(&NV_ENC_CODEC_H264_GUID));
439    ///
440    /// // Initialize the encoder session.
441    /// let _session = encoder
442    ///     .start_session(
443    ///         NV_ENC_BUFFER_FORMAT_ARGB,
444    ///         EncoderInitParams::new(NV_ENC_CODEC_H264_GUID, 1920, 1080),
445    ///     )
446    ///     .unwrap();
447    /// ```
448    pub fn start_session(
449        self,
450        buffer_format: NV_ENC_BUFFER_FORMAT,
451        mut initialize_params: EncoderInitParams<'_>,
452    ) -> Result<Session, EncodeError> {
453        let initialize_params = &mut initialize_params.param;
454        let width = initialize_params.encodeWidth;
455        let height = initialize_params.encodeHeight;
456        unsafe { (ENCODE_API.initialize_encoder)(self.ptr, initialize_params) }.result(&self)?;
457        Ok(Session {
458            encoder: self,
459            width,
460            height,
461            buffer_format,
462            encode_guid: initialize_params.encodeGUID,
463        })
464    }
465}
466
467/// A safe wrapper for [`NV_ENC_INITIALIZE_PARAMS`], which is the encoder
468/// initialize parameter.
469#[derive(Debug)]
470pub struct EncoderInitParams<'a> {
471    param: NV_ENC_INITIALIZE_PARAMS,
472    marker: std::marker::PhantomData<&'a mut NV_ENC_CONFIG>,
473}
474
475impl<'a> EncoderInitParams<'a> {
476    /// Create a new builder for [`EncoderInitParams`], which is a wrapper for
477    /// [`NV_ENC_INITIALIZE_PARAMS`].
478    #[must_use]
479    pub fn new(encode_guid: GUID, width: u32, height: u32) -> Self {
480        let param = NV_ENC_INITIALIZE_PARAMS {
481            version: NV_ENC_INITIALIZE_PARAMS_VER,
482            encodeGUID: encode_guid,
483            encodeWidth: width,
484            encodeHeight: height,
485            ..Default::default()
486        };
487        Self {
488            param,
489            marker: std::marker::PhantomData,
490        }
491    }
492
493    /// Specifies the preset for encoding. If the preset GUID is set then
494    /// the preset configuration will be applied before any other parameter.
495    pub fn preset_guid(&mut self, preset_guid: GUID) -> &mut Self {
496        self.param.presetGUID = preset_guid;
497        self
498    }
499
500    /// Tuning Info of NVENC encoding(`tuning_info` is not applicable to H264
501    /// and HEVC meonly mode).
502    pub fn tuning_info(&mut self, tuning_info: NV_ENC_TUNING_INFO) -> &mut Self {
503        self.param.tuningInfo = tuning_info;
504        self
505    }
506
507    /// Specifies the advanced codec specific structure. If client has sent a
508    /// valid codec config structure, it will override parameters set by the
509    /// [`EncoderInitParams::preset_guid`].
510    ///
511    /// The client can query the interface for codec-specific parameters
512    /// using [`Encoder::get_preset_config`](super::encoder::Encoder::get_preset_config).
513    /// It can then modify (if required) some of the codec config parameters and
514    /// send down a custom config structure using this method. Even in this
515    /// case the client is recommended to pass the same preset GUID it has
516    /// used to get the config.
517    pub fn encode_config(&mut self, encode_config: &'a mut NV_ENC_CONFIG) -> &mut Self {
518        self.param.encodeConfig = encode_config;
519        self
520    }
521
522    /// Specifies the display aspect ratio (H264/HEVC) or the render
523    /// width/height (AV1).
524    pub fn display_aspect_ratio(&mut self, width: u32, height: u32) -> &mut Self {
525        self.param.darWidth = width;
526        self.param.darHeight = height;
527        self
528    }
529
530    /// Specifies the framerate in frames per second as a fraction
531    /// `numerator / denominator`.
532    pub fn framerate(&mut self, numerator: u32, denominator: u32) -> &mut Self {
533        self.param.frameRateNum = numerator;
534        self.param.frameRateDen = denominator;
535        self
536    }
537
538    /// Enable the Picture Type Decision to be taken by the
539    /// `NvEncodeAPI` interface.
540    pub fn enable_picture_type_decision(&mut self) -> &mut Self {
541        self.param.enablePTD = 1;
542        self
543    }
544}