nvidia_video_codec_sdk/safe/
result.rs

1//! Defines a wrapper around
2//! [`NVENCSTATUS`](crate::sys::nvEncodeAPI::NVENCSTATUS) to provide ergonomic
3//! error handling.
4
5use std::{error::Error, ffi::CStr, fmt};
6
7use super::{api::ENCODE_API, encoder::Encoder};
8use crate::sys::nvEncodeAPI::NVENCSTATUS;
9
10/// Wrapper enum around [`NVENCSTATUS`].
11#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
12pub enum ErrorKind {
13    /// No encode capable devices were detected.
14    NoEncodeDevice = 1,
15    /// The device passed by the client is not supported.
16    UnsupportedDevice = 2,
17    /// The encoder device supplied by the client is not valid.
18    InvalidEncoderDevice = 3,
19    /// The device passed to the API call is invalid.
20    InvalidDevice = 4,
21    /// The device passed to the API call is no longer available
22    /// and needs to be reinitialized. The clients need to destroy the
23    /// current encoder session by freeing the allocated input output
24    /// buffers and destroying the device and create a new encoding session.
25    DeviceNotExist = 5,
26    /// One or more of the pointers passed to the API call is invalid.
27    InvalidPtr = 6,
28    /// The completion event passed in the [`EncodeAPI.encode_picture`]
29    /// call is invalid.
30    InvalidEvent = 7,
31    /// One or more of the parameter passed to the API call is invalid.
32    InvalidParam = 8,
33    /// An API call was made in wrong sequence or order.
34    InvalidCall = 9,
35    /// the API call failed because it was unable to allocate enough memory
36    /// to perform the requested operation.
37    OutOfMemory = 10,
38    /// The encoder has not been initialized with
39    /// [`EncodeAPI.initialize_encoder`] or that initialization has failed.
40    /// The client cannot allocate input or output buffers or do any encoding
41    /// related operation before successfully initializing the encoder.
42    EncoderNotInitialized = 11,
43    /// An unsupported parameter was passed by the client.
44    UnsupportedParam = 12,
45    /// The [`EncodeAPI.lock_bitstream`] failed to lock the output
46    /// buffer. This happens when the client makes a non-blocking lock call
47    /// to access the output bitstream by passing the `doNotWait` flag.
48    /// This is not a fatal error and client should retry the same operation
49    /// after few milliseconds.
50    LockBusy = 13,
51    /// The size of the user buffer passed by the client is insufficient for
52    /// the requested operation.
53    NotEnoughBuffer = 14,
54    /// An invalid struct version was used by the client.
55    InvalidVersion = 15,
56    /// [`EncodeAPI.map_input_resource`] failed to map the client provided
57    /// input resource.
58    MapFailed = 16,
59    /// The encode driver requires more input buffers to produce an output
60    /// bitstream. If this error is returned from [`EncodeAPI.encode_picture`],
61    /// this is not a fatal error. If the client is encoding with B frames
62    /// then, [`EncodeAPI.encode_picture`] might be buffering the input
63    /// frame for re-ordering.
64    ///
65    /// A client operating in synchronous mode cannot call
66    /// [`EncodeAPI.lock_bitstream`] on the output bitstream buffer if
67    /// [`EncodeAPI.encode_picture`] returned this variant. The client must
68    /// continue providing input frames until encode driver returns
69    /// successfully. After a success the client
70    /// can call [`EncodeAPI.lock_bitstream`] on the output buffers in the
71    /// same order in which it has called [`EncodeAPI.encode_picture`].
72    NeedMoreInput = 17,
73    /// The hardware encoder is busy encoding and is unable to encode
74    /// the input. The client should call [`EncodeAPI.encode_picture`] again
75    /// after few milliseconds.
76    EncoderBusy = 18,
77    /// The completion event passed in [`EncodeAPI.encode_picture`]
78    /// has not been registered with encoder driver using
79    /// [`EncodeAPI.register_async_event`].
80    EventNotRegistered = 19,
81    /// An unknown internal error has occurred.
82    Generic = 20,
83    /// The client is attempting to use a feature
84    /// that is not available for the license type for the current system.
85    IncompatibleClientKey = 21,
86    /// the client is attempting to use a feature
87    /// that is not implemented for the current version.
88    Unimplemented = 22,
89    /// [`EncodeAPI.register_resource`] failed to register the resource.
90    ResourceRegisterFailed = 23,
91    /// The client is attempting to unregister a resource
92    /// that has not been successfully registered.
93    ResourceNotRegistered = 24,
94    /// The client is attempting to unmap a resource
95    /// that has not been successfully mapped.
96    ResourceNotMapped = 25,
97    /// The encode driver requires more output buffers to write an
98    /// output bitstream. If this error is returned from
99    /// [`EncodeAPI.restore_encoder_state`], this is not a fatal error. If the
100    /// client is encoding with B frames then,
101    /// [`EncodeAPI.restore_encoder_state`] API might be requiring the extra
102    /// output buffer for accommodating overlay frame output in a separate
103    /// buffer, for AV1 codec. In this case, the client must call
104    /// [`EncodeAPI.restore_encoder_state`] API again with
105    /// an output bitstream as input along with the parameters in the previous
106    /// call. When operating in asynchronous mode of encoding, client must
107    /// also specify the completion event.
108    NeedMoreOutput = 26,
109}
110
111/// Wrapper struct around [`NVENCSTATUS`].
112///
113/// This struct also contains a string with additional info
114/// when it is relevant and available.
115#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
116pub struct EncodeError {
117    kind: ErrorKind,
118    string: Option<String>,
119}
120
121impl EncodeError {
122    /// Getter for the error kind.
123    #[must_use]
124    pub fn kind(&self) -> ErrorKind {
125        self.kind
126    }
127
128    /// Getter for the error string.
129    #[must_use]
130    pub fn string(&self) -> Option<&str> {
131        self.string.as_deref()
132    }
133}
134
135impl fmt::Display for EncodeError {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        match &self.string {
138            Some(s) => write!(f, "{:?}: {s}", self.kind),
139            None => write!(f, "{:?}", self.kind),
140        }
141    }
142}
143
144impl Error for EncodeError {}
145
146impl From<NVENCSTATUS> for ErrorKind {
147    fn from(status: NVENCSTATUS) -> Self {
148        match status {
149            NVENCSTATUS::NV_ENC_SUCCESS => {
150                unreachable!("Success should not be converted to an error.")
151            }
152            NVENCSTATUS::NV_ENC_ERR_NO_ENCODE_DEVICE => Self::NoEncodeDevice,
153            NVENCSTATUS::NV_ENC_ERR_UNSUPPORTED_DEVICE => Self::UnsupportedDevice,
154            NVENCSTATUS::NV_ENC_ERR_INVALID_ENCODERDEVICE => Self::InvalidEncoderDevice,
155            NVENCSTATUS::NV_ENC_ERR_INVALID_DEVICE => Self::InvalidDevice,
156            NVENCSTATUS::NV_ENC_ERR_DEVICE_NOT_EXIST => Self::DeviceNotExist,
157            NVENCSTATUS::NV_ENC_ERR_INVALID_PTR => Self::InvalidPtr,
158            NVENCSTATUS::NV_ENC_ERR_INVALID_EVENT => Self::InvalidEvent,
159            NVENCSTATUS::NV_ENC_ERR_INVALID_PARAM => Self::InvalidParam,
160            NVENCSTATUS::NV_ENC_ERR_INVALID_CALL => Self::InvalidCall,
161            NVENCSTATUS::NV_ENC_ERR_OUT_OF_MEMORY => Self::OutOfMemory,
162            NVENCSTATUS::NV_ENC_ERR_ENCODER_NOT_INITIALIZED => Self::EncoderNotInitialized,
163            NVENCSTATUS::NV_ENC_ERR_UNSUPPORTED_PARAM => Self::UnsupportedParam,
164            NVENCSTATUS::NV_ENC_ERR_LOCK_BUSY => Self::LockBusy,
165            NVENCSTATUS::NV_ENC_ERR_NOT_ENOUGH_BUFFER => Self::NotEnoughBuffer,
166            NVENCSTATUS::NV_ENC_ERR_INVALID_VERSION => Self::InvalidVersion,
167            NVENCSTATUS::NV_ENC_ERR_MAP_FAILED => Self::MapFailed,
168            NVENCSTATUS::NV_ENC_ERR_NEED_MORE_INPUT => Self::NeedMoreInput,
169            NVENCSTATUS::NV_ENC_ERR_ENCODER_BUSY => Self::EncoderBusy,
170            NVENCSTATUS::NV_ENC_ERR_EVENT_NOT_REGISTERD => Self::EventNotRegistered,
171            NVENCSTATUS::NV_ENC_ERR_GENERIC => Self::Generic,
172            NVENCSTATUS::NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY => Self::IncompatibleClientKey,
173            NVENCSTATUS::NV_ENC_ERR_UNIMPLEMENTED => Self::Unimplemented,
174            NVENCSTATUS::NV_ENC_ERR_RESOURCE_REGISTER_FAILED => Self::ResourceRegisterFailed,
175            NVENCSTATUS::NV_ENC_ERR_RESOURCE_NOT_REGISTERED => Self::ResourceNotRegistered,
176            NVENCSTATUS::NV_ENC_ERR_RESOURCE_NOT_MAPPED => Self::ResourceNotMapped,
177            NVENCSTATUS::NV_ENC_ERR_NEED_MORE_OUTPUT => Self::NeedMoreOutput,
178        }
179    }
180}
181
182impl NVENCSTATUS {
183    /// Convert an [`NVENCSTATUS`] to a [`Result`].
184    ///
185    /// [`NVENCSTATUS::NV_ENC_SUCCESS`] is converted to `Ok(())`,
186    /// and all other variants are mapped to the corresponding variant
187    /// in [`ErrorKind`]. The error type is [`EncodeError`] which has
188    /// a kind and an optional `String` which might contain additional
189    /// information about the error.
190    ///
191    /// # Errors
192    ///
193    /// Returns an error whenever the status is not
194    /// [`NVENCSTATUS::NV_ENC_SUCCESS`].
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// # use cudarc::driver::CudaContext;
200    /// # use nvidia_video_codec_sdk::{sys::nvEncodeAPI::GUID, EncodeError, Encoder, ErrorKind};
201    /// # let cuda_ctx = CudaContext::new(0).unwrap();
202    /// let encoder = Encoder::initialize_with_cuda(cuda_ctx).unwrap();
203    /// // Cause an error by passing in an invalid GUID.
204    /// // `Encoder::get_supported_input_formats()` uses `.result()` internally
205    /// let error = encoder
206    ///     .get_supported_input_formats(GUID::default())
207    ///     .unwrap_err();
208    /// // Get the kind.
209    /// assert_eq!(error.kind(), ErrorKind::InvalidParam);
210    /// // Get the error message.
211    /// // Unfortunately, it's not always helpful.
212    /// assert_eq!(error.string(), Some("EncodeAPI Internal Error."));
213    /// ```
214    pub fn result(self, encoder: &Encoder) -> Result<(), EncodeError> {
215        self.result_without_string().map_err(|mut err| {
216            err.string = match err.kind {
217                // Avoid getting the string if it is not needed.
218                ErrorKind::LockBusy
219                | ErrorKind::EncoderBusy
220                | ErrorKind::NeedMoreInput
221                | ErrorKind::OutOfMemory => None,
222                // Otherwise allocate an owned `String` with the error.
223                _ => Some(
224                    unsafe { CStr::from_ptr((ENCODE_API.get_last_error_string)(encoder.ptr)) }
225                        .to_string_lossy()
226                        .to_string(),
227                ),
228            }
229            .and_then(|s| if s.is_empty() { None } else { Some(s) });
230            err
231        })
232    }
233
234    /// Convert an [`NVENCSTATUS`] to a [`Result`] without
235    /// using an [`Encoder`].
236    ///
237    /// This function is the same as [`NVENCSTATUS::result`] except
238    /// it does not get the error string because it does not have access
239    /// to an [`Encoder`]. This is only useful if you do not have an [`Encoder`]
240    /// yet, for example when initializing the API.
241    ///
242    /// You should always prefer to use [`NVENCSTATUS::result`] when possible.
243    ///
244    /// # Errors
245    ///
246    /// Returns an error whenever the status is not
247    /// [`NVENCSTATUS::NV_ENC_SUCCESS`].
248    pub fn result_without_string(self) -> Result<(), EncodeError> {
249        match self {
250            Self::NV_ENC_SUCCESS => Ok(()),
251            err => Err(EncodeError {
252                kind: err.into(),
253                string: None,
254            }),
255        }
256    }
257}