1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
//!
//! [![Latest Version]][crates.io]
//! [![docs]][docs.rs]
//! ![BSD-2]
//! [![Rust](https://img.shields.io/badge/rust-1.65%2B-blue.svg?maxAge=3600)](https://github.com/ralfbiedert/openh264-rust)
//!
//!
//! This low-level crate used by [openh264](https://crates.io/crates/openh264)
//! contains
//!
//! - a fully self-contained version of OpenH264
//! - alternatively, a libloading wrapper around precompiled OpenH264 binaries
//! - `unsafe` Rust bindings
//! - build logic that should work "out of the box" on most platforms (sans bugs)
//!
//! [Latest Version]: https://img.shields.io/crates/v/openh264-sys2.svg
//! [crates.io]: https://crates.io/crates/openh264-sys2
//! [BSD-2]: https://img.shields.io/badge/license-BSD2-blue.svg
//! [docs]: https://docs.rs/openh264-sys2/badge.svg
//! [docs.rs]: https://docs.rs/openh264-sys2/

#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]

mod error;

/// Generated bindings for OpenH264.
mod generated {
    pub mod consts;
    #[cfg(feature = "libloading")]
    pub mod fns_libloading;
    #[cfg(feature = "source")]
    pub mod fns_source;
    pub mod types;
}

pub use self::generated::consts::*;
pub use self::generated::types::*;
pub use error::Error;
use std::os::raw::{c_int, c_long};

/// Abstraction over `source` or `libloading` APIs.
#[rustfmt::skip]
pub trait API {
    unsafe fn WelsCreateSVCEncoder(&self, ppEncoder: *mut *mut ISVCEncoder) -> ::std::os::raw::c_int;
    unsafe fn WelsDestroySVCEncoder(&self, pEncoder: *mut ISVCEncoder);
    unsafe fn WelsGetDecoderCapability(&self, pDecCapability: *mut SDecoderCapability) -> ::std::os::raw::c_int;
    unsafe fn WelsCreateDecoder(&self, ppDecoder: *mut *mut ISVCDecoder) -> ::std::os::raw::c_long;
    unsafe fn WelsDestroyDecoder(&self, pDecoder: *mut ISVCDecoder);
    unsafe fn WelsGetCodecVersion(&self) -> OpenH264Version;
    unsafe fn WelsGetCodecVersionEx(&self, pVersion: *mut OpenH264Version);
}

/// API surface via libloading.
///
/// 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/).
/// 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):
///
/// - **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?**
///
///     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.
///
/// In addition, note that this might not cover _all_ possible license claims:
///
/// - **Q: Is Cisco guaranteeing that it will pay other licensing fees for H.264, should additional patent holders assert claims in the future?**
///
///     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.
#[cfg(feature = "libloading")]
pub mod libloading {
    pub use crate::generated::fns_libloading::*;
    use crate::{ISVCDecoder, ISVCEncoder, OpenH264Version, SDecoderCapability};
    use std::os::raw::{c_int, c_long};

    #[rustfmt::skip]
    impl super::API for APILoader {
        unsafe fn WelsCreateSVCEncoder(&self, ppEncoder: *mut *mut ISVCEncoder) -> c_int { APILoader::WelsCreateSVCEncoder(self, ppEncoder) }
        unsafe fn WelsDestroySVCEncoder(&self, pEncoder: *mut ISVCEncoder) { APILoader::WelsDestroySVCEncoder(self, pEncoder) }
        unsafe fn WelsGetDecoderCapability(&self, pDecCapability: *mut SDecoderCapability) -> c_int { APILoader::WelsGetDecoderCapability(self, pDecCapability) }
        unsafe fn WelsCreateDecoder(&self, ppDecoder: *mut *mut ISVCDecoder) -> c_long { APILoader::WelsCreateDecoder(self, ppDecoder) }
        unsafe fn WelsDestroyDecoder(&self, pDecoder: *mut ISVCDecoder) { APILoader::WelsDestroyDecoder(self, pDecoder) }
        unsafe fn WelsGetCodecVersion(&self) -> OpenH264Version { APILoader::WelsGetCodecVersion(self) }
        unsafe fn WelsGetCodecVersionEx(&self, pVersion: *mut OpenH264Version) {APILoader::WelsGetCodecVersionEx(self, pVersion) }
    }
}

/// API surface using built-in source.
///
/// This API surface should _just work_ once compiled. Depending on your commercial, legal and geographic situation, and the H.264 features you use,
/// this might or might not come with an elevated patent risk.
#[cfg(feature = "source")]
pub mod source {
    use crate::{ISVCDecoder, ISVCEncoder, OpenH264Version, SDecoderCapability};
    use std::os::raw::{c_int, c_long};

    #[derive(Debug)]
    pub struct APILoader {}

    #[rustfmt::skip]
    impl APILoader {
        pub fn new() -> Self { Self {} }
        pub unsafe fn WelsCreateSVCEncoder(&self, ppEncoder: *mut *mut ISVCEncoder) -> ::std::os::raw::c_int { crate::generated::fns_source::WelsCreateSVCEncoder(ppEncoder) }
        pub unsafe fn WelsDestroySVCEncoder(&self, pEncoder: *mut ISVCEncoder) { crate::generated::fns_source::WelsDestroySVCEncoder(pEncoder) }
        pub unsafe fn WelsGetDecoderCapability(&self, pDecCapability: *mut SDecoderCapability) -> ::std::os::raw::c_int { crate::generated::fns_source::WelsGetDecoderCapability(pDecCapability) }
        pub unsafe fn WelsCreateDecoder(&self, ppDecoder: *mut *mut ISVCDecoder) -> ::std::os::raw::c_long { crate::generated::fns_source::WelsCreateDecoder(ppDecoder) }
        pub unsafe fn WelsDestroyDecoder(&self, pDecoder: *mut ISVCDecoder) { crate::generated::fns_source::WelsDestroyDecoder(pDecoder) }
        pub unsafe fn WelsGetCodecVersion(&self) -> OpenH264Version { crate::generated::fns_source::WelsGetCodecVersion() }
        pub unsafe fn WelsGetCodecVersionEx(&self, pVersion: *mut OpenH264Version) { crate::generated::fns_source::WelsGetCodecVersionEx(pVersion) }
    }

    #[rustfmt::skip]
    impl super::API for APILoader {
        unsafe fn WelsCreateSVCEncoder(&self, ppEncoder: *mut *mut ISVCEncoder) -> c_int { APILoader::WelsCreateSVCEncoder(self, ppEncoder) }
        unsafe fn WelsDestroySVCEncoder(&self, pEncoder: *mut ISVCEncoder) { APILoader::WelsDestroySVCEncoder(self, pEncoder) }
        unsafe fn WelsGetDecoderCapability(&self, pDecCapability: *mut SDecoderCapability) -> c_int { APILoader::WelsGetDecoderCapability(self, pDecCapability) }
        unsafe fn WelsCreateDecoder(&self, ppDecoder: *mut *mut ISVCDecoder) -> c_long { APILoader::WelsCreateDecoder(self, ppDecoder) }
        unsafe fn WelsDestroyDecoder(&self, pDecoder: *mut ISVCDecoder) { APILoader::WelsDestroyDecoder(self, pDecoder) }
        unsafe fn WelsGetCodecVersion(&self) -> OpenH264Version { APILoader::WelsGetCodecVersion(self) }
        unsafe fn WelsGetCodecVersionEx(&self, pVersion: *mut OpenH264Version) { APILoader::WelsGetCodecVersionEx(self, pVersion) }
    }
}

/// Convenience wrapper around `libloading` and `source` API surfaces.
///
/// This type mainly exists to prevent infecting the rest of the OpenH264 crate with generics. The dispatch overhead
/// in contrast to H.264 computation is absolutely negligible.
pub enum DynamicAPI {
    #[cfg(feature = "source")]
    Source(source::APILoader),

    #[cfg(feature = "libloading")]
    Libloading(libloading::APILoader),
}

impl DynamicAPI {
    /// Creates an OpenH264 API using the built-in source if available.
    #[cfg(feature = "source")]
    pub fn from_source() -> Self {
        let api = crate::source::APILoader::new();
        Self::Source(api)
    }

    /// Creates an OpenH264 API via the provided shared library.
    ///
    /// In order for this to have any (legal) use, you should download the library from
    /// Cisco [**during installation**](https://www.openh264.org/faq.html), and then
    /// pass the file-system path in here.
    ///
    /// # Errors
    ///
    /// Can fail if the library could not be loaded, e.g., it does not exist.
    ///
    /// # Safety
    ///
    /// Will cause UB if the provided path does not match the current platform and version.
    #[cfg(feature = "libloading")]
    pub unsafe fn from_blob_path_unchecked(path: impl AsRef<std::ffi::OsStr>) -> Result<Self, Error> {
        let api = unsafe { libloading::APILoader::new(path)? };
        Ok(Self::Libloading(api))
    }

    /// Creates an OpenH264 API via the provided shared library if the library is well-known.
    ///
    /// In order for this to have any (legal) use, you should download the library from
    /// Cisco [**during installation**](https://www.openh264.org/faq.html), and then
    /// pass the file-system path in here.
    ///
    /// This function also checks the file's SHA against a list of well-known libraries we can load.
    ///
    /// # Errors
    ///
    /// 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
    /// not match against a list of well-known versions we can load.
    #[cfg(feature = "libloading")]
    pub fn from_blob_path(path: impl AsRef<std::ffi::OsStr>) -> Result<Self, Error> {
        use sha2::Digest;

        let bytes = std::fs::read(path.as_ref())?;

        // Get SHA of blob at given path.
        let sha256 = sha2::Sha256::digest(bytes)
            .iter()
            .map(|byte| format!("{:02x}", byte))
            .collect::<String>();

        // Check all known hashes if we should load this library.
        // TODO: We might also want to verify this matches our architecture, but then again libloading should catch that.
        let hash_is_well_known = include_str!("blobs/hashes.txt")
            .lines()
            .filter_map(|line| line.split_whitespace().next())
            .any(|x| x == sha256);

        if !hash_is_well_known {
            return Err(Error::InvalidHash(sha256));
        }

        unsafe { Self::from_blob_path_unchecked(path) }
    }
}

#[allow(unreachable_patterns)]
#[allow(unused)]
impl API for DynamicAPI {
    unsafe fn WelsCreateSVCEncoder(&self, ppEncoder: *mut *mut ISVCEncoder) -> c_int {
        match self {
            #[cfg(feature = "source")]
            DynamicAPI::Source(api) => api.WelsCreateSVCEncoder(ppEncoder),
            #[cfg(feature = "libloading")]
            DynamicAPI::Libloading(api) => api.WelsCreateSVCEncoder(ppEncoder),
            _ => panic!("No API enabled"),
        }
    }

    unsafe fn WelsDestroySVCEncoder(&self, pEncoder: *mut ISVCEncoder) {
        match self {
            #[cfg(feature = "source")]
            DynamicAPI::Source(api) => api.WelsDestroySVCEncoder(pEncoder),
            #[cfg(feature = "libloading")]
            DynamicAPI::Libloading(api) => api.WelsDestroySVCEncoder(pEncoder),
            _ => panic!("No API enabled"),
        }
    }

    unsafe fn WelsGetDecoderCapability(&self, pDecCapability: *mut SDecoderCapability) -> c_int {
        match self {
            #[cfg(feature = "source")]
            DynamicAPI::Source(api) => api.WelsGetDecoderCapability(pDecCapability),
            #[cfg(feature = "libloading")]
            DynamicAPI::Libloading(api) => api.WelsGetDecoderCapability(pDecCapability),
            _ => panic!("No API enabled"),
        }
    }

    unsafe fn WelsCreateDecoder(&self, ppDecoder: *mut *mut ISVCDecoder) -> c_long {
        match self {
            #[cfg(feature = "source")]
            DynamicAPI::Source(api) => api.WelsCreateDecoder(ppDecoder),
            #[cfg(feature = "libloading")]
            DynamicAPI::Libloading(api) => api.WelsCreateDecoder(ppDecoder),
            _ => panic!("No API enabled"),
        }
    }

    unsafe fn WelsDestroyDecoder(&self, pDecoder: *mut ISVCDecoder) {
        match self {
            #[cfg(feature = "source")]
            DynamicAPI::Source(api) => api.WelsDestroyDecoder(pDecoder),
            #[cfg(feature = "libloading")]
            DynamicAPI::Libloading(api) => api.WelsDestroyDecoder(pDecoder),
            _ => panic!("No API enabled"),
        }
    }

    unsafe fn WelsGetCodecVersion(&self) -> OpenH264Version {
        match self {
            #[cfg(feature = "source")]
            DynamicAPI::Source(api) => api.WelsGetCodecVersion(),
            #[cfg(feature = "libloading")]
            DynamicAPI::Libloading(api) => api.WelsGetCodecVersion(),
            _ => panic!("No API enabled"),
        }
    }

    unsafe fn WelsGetCodecVersionEx(&self, pVersion: *mut OpenH264Version) {
        match self {
            #[cfg(feature = "source")]
            DynamicAPI::Source(api) => api.WelsGetCodecVersionEx(pVersion),
            #[cfg(feature = "libloading")]
            DynamicAPI::Libloading(api) => api.WelsGetCodecVersionEx(pVersion),
            _ => panic!("No API enabled"),
        }
    }
}

#[cfg(test)]
mod tests {
    #[test]
    #[ignore]
    #[cfg(feature = "libloading")]
    fn it_works() -> Result<(), crate::Error> {
        _ = super::DynamicAPI::from_blob_path(r"C:\Users\rb\Downloads\openh264-2.4.0-win64.dll\openh264-2.4.0-win64.dll")?;

        Ok(())
    }
}