openh264_sys2/
lib.rs

1//!
2//! [![Latest Version]][crates.io]
3//! [![docs]][docs.rs]
4//! ![BSD-2]
5//! [![Rust](https://img.shields.io/badge/rust-1.65%2B-blue.svg?maxAge=3600)](https://github.com/ralfbiedert/openh264-rust)
6//!
7//!
8//! This low-level crate used by [openh264](https://crates.io/crates/openh264)
9//! contains
10//!
11//! - a fully self-contained version of OpenH264
12//! - alternatively, a libloading wrapper around precompiled OpenH264 binaries
13//! - `unsafe` Rust bindings
14//! - build logic that should work "out of the box" on most platforms (sans bugs)
15//!
16//! [Latest Version]: https://img.shields.io/crates/v/openh264-sys2.svg
17//! [crates.io]: https://crates.io/crates/openh264-sys2
18//! [BSD-2]: https://img.shields.io/badge/license-BSD2-blue.svg
19//! [docs]: https://docs.rs/openh264-sys2/badge.svg
20//! [docs.rs]: https://docs.rs/openh264-sys2/
21
22#![allow(non_snake_case)]
23#![allow(non_camel_case_types)]
24#![allow(non_upper_case_globals)]
25#![allow(clippy::missing_safety_doc)]
26
27mod error;
28
29/// Generated bindings for OpenH264.
30mod generated {
31    pub mod consts;
32    #[cfg(feature = "libloading")]
33    pub mod fns_libloading;
34    #[cfg(feature = "source")]
35    pub mod fns_source;
36    pub mod types;
37}
38
39pub use self::generated::consts::*;
40pub use self::generated::types::*;
41pub use error::Error;
42use std::os::raw::{c_int, c_long};
43
44/// Abstraction over `source` or `libloading` APIs.
45#[rustfmt::skip]
46#[allow(clippy::missing_safety_doc)]
47pub trait API {
48    unsafe fn WelsCreateSVCEncoder(&self, ppEncoder: *mut *mut ISVCEncoder) -> ::std::os::raw::c_int;
49    unsafe fn WelsDestroySVCEncoder(&self, pEncoder: *mut ISVCEncoder);
50    unsafe fn WelsGetDecoderCapability(&self, pDecCapability: *mut SDecoderCapability) -> ::std::os::raw::c_int;
51    unsafe fn WelsCreateDecoder(&self, ppDecoder: *mut *mut ISVCDecoder) -> ::std::os::raw::c_long;
52    unsafe fn WelsDestroyDecoder(&self, pDecoder: *mut ISVCDecoder);
53    unsafe fn WelsGetCodecVersion(&self) -> OpenH264Version;
54    unsafe fn WelsGetCodecVersionEx(&self, pVersion: *mut OpenH264Version);
55}
56
57/// API surface via libloading.
58///
59/// While this is no legal advice, the idea is that using this API might be covered by Cisco's [promise to cover MPEG-LA license costs](https://www.openh264.org/).
60/// The big downside is you will have to download binary blobs from Cisco during installation. From [their FAQ](https://www.openh264.org/faq.html) (copied 2024-01-06, emphasis ours):
61///
62/// - **Q: If I use the source code in my product, and then distribute that product on my own, will Cisco cover the MPEG LA licensing fees which I'd otherwise have to pay?**
63///
64///     A: No. Cisco is only covering the licensing fees for its own binary module, and products or projects that utilize it **must download it at the time the product or project is installed on the user's computer or device**. Cisco will not be liable for any licensing fees incurred by other parties.
65///
66/// In addition, note that this might not cover _all_ possible license claims:
67///
68/// - **Q: Is Cisco guaranteeing that it will pay other licensing fees for H.264, should additional patent holders assert claims in the future?**
69///
70///     A: Cisco is providing no such guarantee. We are only covering the royalties that would apply to the binary module under MPEG LA's AVC/H.264 patent pool.
71#[cfg(feature = "libloading")]
72pub mod libloading {
73    pub use crate::generated::fns_libloading::*;
74    use crate::{ISVCDecoder, ISVCEncoder, OpenH264Version, SDecoderCapability};
75    use std::os::raw::{c_int, c_long};
76
77    #[rustfmt::skip]
78    impl super::API for APILoader {
79        unsafe fn WelsCreateSVCEncoder(&self, ppEncoder: *mut *mut ISVCEncoder) -> c_int { APILoader::WelsCreateSVCEncoder(self, ppEncoder) }
80        unsafe fn WelsDestroySVCEncoder(&self, pEncoder: *mut ISVCEncoder) { APILoader::WelsDestroySVCEncoder(self, pEncoder) }
81        unsafe fn WelsGetDecoderCapability(&self, pDecCapability: *mut SDecoderCapability) -> c_int { APILoader::WelsGetDecoderCapability(self, pDecCapability) }
82        unsafe fn WelsCreateDecoder(&self, ppDecoder: *mut *mut ISVCDecoder) -> c_long { APILoader::WelsCreateDecoder(self, ppDecoder) }
83        unsafe fn WelsDestroyDecoder(&self, pDecoder: *mut ISVCDecoder) { APILoader::WelsDestroyDecoder(self, pDecoder) }
84        unsafe fn WelsGetCodecVersion(&self) -> OpenH264Version { APILoader::WelsGetCodecVersion(self) }
85        unsafe fn WelsGetCodecVersionEx(&self, pVersion: *mut OpenH264Version) {APILoader::WelsGetCodecVersionEx(self, pVersion) }
86    }
87}
88
89/// API surface using built-in source.
90///
91/// This API surface should _just work_ once compiled. Depending on your commercial, legal and geographic situation, and the H.264 features you use,
92/// this might or might not come with an elevated patent risk.
93#[cfg(feature = "source")]
94pub mod source {
95    use crate::{ISVCDecoder, ISVCEncoder, OpenH264Version, SDecoderCapability};
96    use std::os::raw::{c_int, c_long};
97
98    #[derive(Debug, Default)]
99    pub struct APILoader {}
100
101    #[rustfmt::skip]
102    #[allow(clippy::missing_safety_doc)]
103    impl APILoader {
104        pub fn new() -> Self { Self {} }
105        pub unsafe fn WelsCreateSVCEncoder(&self, ppEncoder: *mut *mut ISVCEncoder) -> ::std::os::raw::c_int { crate::generated::fns_source::WelsCreateSVCEncoder(ppEncoder) }
106        pub unsafe fn WelsDestroySVCEncoder(&self, pEncoder: *mut ISVCEncoder) { crate::generated::fns_source::WelsDestroySVCEncoder(pEncoder) }
107        pub unsafe fn WelsGetDecoderCapability(&self, pDecCapability: *mut SDecoderCapability) -> ::std::os::raw::c_int { crate::generated::fns_source::WelsGetDecoderCapability(pDecCapability) }
108        pub unsafe fn WelsCreateDecoder(&self, ppDecoder: *mut *mut ISVCDecoder) -> ::std::os::raw::c_long { crate::generated::fns_source::WelsCreateDecoder(ppDecoder) }
109        pub unsafe fn WelsDestroyDecoder(&self, pDecoder: *mut ISVCDecoder) { crate::generated::fns_source::WelsDestroyDecoder(pDecoder) }
110        pub unsafe fn WelsGetCodecVersion(&self) -> OpenH264Version { crate::generated::fns_source::WelsGetCodecVersion() }
111        pub unsafe fn WelsGetCodecVersionEx(&self, pVersion: *mut OpenH264Version) { crate::generated::fns_source::WelsGetCodecVersionEx(pVersion) }
112    }
113
114    #[rustfmt::skip]
115    #[allow(clippy::missing_safety_doc)]
116    impl super::API for APILoader {
117        unsafe fn WelsCreateSVCEncoder(&self, ppEncoder: *mut *mut ISVCEncoder) -> c_int { APILoader::WelsCreateSVCEncoder(self, ppEncoder) }
118        unsafe fn WelsDestroySVCEncoder(&self, pEncoder: *mut ISVCEncoder) { APILoader::WelsDestroySVCEncoder(self, pEncoder) }
119        unsafe fn WelsGetDecoderCapability(&self, pDecCapability: *mut SDecoderCapability) -> c_int { APILoader::WelsGetDecoderCapability(self, pDecCapability) }
120        unsafe fn WelsCreateDecoder(&self, ppDecoder: *mut *mut ISVCDecoder) -> c_long { APILoader::WelsCreateDecoder(self, ppDecoder) }
121        unsafe fn WelsDestroyDecoder(&self, pDecoder: *mut ISVCDecoder) { APILoader::WelsDestroyDecoder(self, pDecoder) }
122        unsafe fn WelsGetCodecVersion(&self) -> OpenH264Version { APILoader::WelsGetCodecVersion(self) }
123        unsafe fn WelsGetCodecVersionEx(&self, pVersion: *mut OpenH264Version) { APILoader::WelsGetCodecVersionEx(self, pVersion) }
124    }
125}
126
127/// Convenience wrapper around `libloading` and `source` API surfaces.
128///
129/// This type mainly exists to prevent infecting the rest of the OpenH264 crate with generics. The dispatch overhead
130/// in contrast to H.264 computation is absolutely negligible.
131pub enum DynamicAPI {
132    #[cfg(feature = "source")]
133    Source(source::APILoader),
134
135    #[cfg(feature = "libloading")]
136    Libloading(libloading::APILoader),
137}
138
139impl DynamicAPI {
140    /// Creates an OpenH264 API using the built-in source if available.
141    #[cfg(feature = "source")]
142    pub fn from_source() -> Self {
143        let api = crate::source::APILoader::new();
144        Self::Source(api)
145    }
146
147    /// Creates an OpenH264 API via the provided shared library.
148    ///
149    /// In order for this to have any (legal) use, you should download the library from
150    /// Cisco [**during installation**](https://www.openh264.org/faq.html), and then
151    /// pass the file-system path in here.
152    ///
153    /// # Errors
154    ///
155    /// Can fail if the library could not be loaded, e.g., it does not exist.
156    ///
157    /// # Safety
158    ///
159    /// Will cause UB if the provided path does not match the current platform and version.
160    #[cfg(feature = "libloading")]
161    pub unsafe fn from_blob_path_unchecked(path: impl AsRef<std::ffi::OsStr>) -> Result<Self, Error> {
162        let api = unsafe { libloading::APILoader::new(path)? };
163        Ok(Self::Libloading(api))
164    }
165
166    /// Creates an OpenH264 API via the provided shared library if the library is well-known.
167    ///
168    /// In order for this to have any (legal) use, you should download the library from
169    /// Cisco [**during installation**](https://www.openh264.org/faq.html), and then
170    /// pass the file-system path in here.
171    ///
172    /// This function also checks the file's SHA against a list of well-known libraries we can load.
173    ///
174    /// # Errors
175    ///
176    /// Can fail if the library could not be loaded, e.g., it does not exist. It can also fail if the file's SHA does
177    /// not match against a list of well-known versions we can load.
178    #[cfg(feature = "libloading")]
179    pub fn from_blob_path(path: impl AsRef<std::ffi::OsStr>) -> Result<Self, Error> {
180        use sha2::Digest;
181        use std::fmt::Write;
182
183        let bytes = std::fs::read(path.as_ref())?;
184
185        // Get SHA of blob at given path.
186        let sha256 = sha2::Sha256::digest(bytes).iter().fold(String::new(), |mut acc, byte| {
187            write!(&mut acc, "{:02x}", byte).unwrap(); // Unless we're out of memory this should never panic.
188            acc
189        });
190
191        // Check all known hashes if we should load this library.
192        // TODO: We might also want to verify this matches our architecture, but then again libloading should catch that.
193        let hash_is_well_known = include_str!("blobs/hashes.txt")
194            .lines()
195            .filter_map(|line| line.split_whitespace().next())
196            .any(|x| x == sha256);
197
198        if !hash_is_well_known {
199            return Err(Error::InvalidHash(sha256));
200        }
201
202        unsafe { Self::from_blob_path_unchecked(path) }
203    }
204}
205
206#[allow(unreachable_patterns)]
207#[allow(unused)]
208impl API for DynamicAPI {
209    unsafe fn WelsCreateSVCEncoder(&self, ppEncoder: *mut *mut ISVCEncoder) -> c_int {
210        match self {
211            #[cfg(feature = "source")]
212            DynamicAPI::Source(api) => api.WelsCreateSVCEncoder(ppEncoder),
213            #[cfg(feature = "libloading")]
214            DynamicAPI::Libloading(api) => api.WelsCreateSVCEncoder(ppEncoder),
215            _ => panic!("No API enabled"),
216        }
217    }
218
219    unsafe fn WelsDestroySVCEncoder(&self, pEncoder: *mut ISVCEncoder) {
220        match self {
221            #[cfg(feature = "source")]
222            DynamicAPI::Source(api) => api.WelsDestroySVCEncoder(pEncoder),
223            #[cfg(feature = "libloading")]
224            DynamicAPI::Libloading(api) => api.WelsDestroySVCEncoder(pEncoder),
225            _ => panic!("No API enabled"),
226        }
227    }
228
229    unsafe fn WelsGetDecoderCapability(&self, pDecCapability: *mut SDecoderCapability) -> c_int {
230        match self {
231            #[cfg(feature = "source")]
232            DynamicAPI::Source(api) => api.WelsGetDecoderCapability(pDecCapability),
233            #[cfg(feature = "libloading")]
234            DynamicAPI::Libloading(api) => api.WelsGetDecoderCapability(pDecCapability),
235            _ => panic!("No API enabled"),
236        }
237    }
238
239    unsafe fn WelsCreateDecoder(&self, ppDecoder: *mut *mut ISVCDecoder) -> c_long {
240        match self {
241            #[cfg(feature = "source")]
242            DynamicAPI::Source(api) => api.WelsCreateDecoder(ppDecoder),
243            #[cfg(feature = "libloading")]
244            DynamicAPI::Libloading(api) => api.WelsCreateDecoder(ppDecoder),
245            _ => panic!("No API enabled"),
246        }
247    }
248
249    unsafe fn WelsDestroyDecoder(&self, pDecoder: *mut ISVCDecoder) {
250        match self {
251            #[cfg(feature = "source")]
252            DynamicAPI::Source(api) => api.WelsDestroyDecoder(pDecoder),
253            #[cfg(feature = "libloading")]
254            DynamicAPI::Libloading(api) => api.WelsDestroyDecoder(pDecoder),
255            _ => panic!("No API enabled"),
256        }
257    }
258
259    unsafe fn WelsGetCodecVersion(&self) -> OpenH264Version {
260        match self {
261            #[cfg(feature = "source")]
262            DynamicAPI::Source(api) => api.WelsGetCodecVersion(),
263            #[cfg(feature = "libloading")]
264            DynamicAPI::Libloading(api) => api.WelsGetCodecVersion(),
265            _ => panic!("No API enabled"),
266        }
267    }
268
269    unsafe fn WelsGetCodecVersionEx(&self, pVersion: *mut OpenH264Version) {
270        match self {
271            #[cfg(feature = "source")]
272            DynamicAPI::Source(api) => api.WelsGetCodecVersionEx(pVersion),
273            #[cfg(feature = "libloading")]
274            DynamicAPI::Libloading(api) => api.WelsGetCodecVersionEx(pVersion),
275            _ => panic!("No API enabled"),
276        }
277    }
278}
279
280/// Helper function that should always give the name of the latest supported and
281/// included DLL file, used by unit tests.
282#[doc(hidden)]
283#[cfg(all(target_os = "windows", target_arch = "x86_64", feature = "libloading"))]
284pub fn reference_dll_name() -> &'static str {
285    include_str!("../tests/reference/reference.txt")
286}