diff --git a/Cargo.lock b/Cargo.lock index 1c52ff074d54..f568c146a9ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4601,6 +4601,7 @@ dependencies = [ "slotmap", "swf", "tempfile", + "thiserror", "tracing", ] diff --git a/desktop/src/player.rs b/desktop/src/player.rs index dd32d6daf3e4..79f3210bc389 100644 --- a/desktop/src/player.rs +++ b/desktop/src/player.rs @@ -224,16 +224,16 @@ impl ActivePlayer { #[cfg(feature = "external_video")] { use ruffle_video_external::backend::ExternalVideoBackend; - let path = tokio::task::block_in_place(ExternalVideoBackend::get_openh264); - let openh264_path = match path { - Ok(path) => Some(path), + let openh264 = tokio::task::block_in_place(ExternalVideoBackend::load_openh264); + let openh264 = match openh264 { + Ok(codec) => Some(codec), Err(e) => { - tracing::error!("Couldn't get OpenH264: {}", e); + tracing::error!("Failed to load OpenH264: {}", e); None } }; - builder = builder.with_video(ExternalVideoBackend::new(openh264_path)); + builder = builder.with_video(ExternalVideoBackend::new(openh264)); } } else { #[cfg(feature = "software_video")] diff --git a/tests/framework/src/options.rs b/tests/framework/src/options.rs index 068abaf38825..b837c9f66640 100644 --- a/tests/framework/src/options.rs +++ b/tests/framework/src/options.rs @@ -178,11 +178,11 @@ impl PlayerOptions { #[cfg(feature = "ruffle_video_external")] { use ruffle_video_external::backend::ExternalVideoBackend; - let openh264_path = ExternalVideoBackend::get_openh264() - .map_err(|e| anyhow!("Couldn't get OpenH264: {}", e))?; + let openh264 = ExternalVideoBackend::load_openh264() + .map_err(|e| anyhow!("Couldn't load OpenH264: {}", e))?; player_builder = - player_builder.with_video(ExternalVideoBackend::new(Some(openh264_path))); + player_builder.with_video(ExternalVideoBackend::new(Some(openh264))); } #[cfg(all( diff --git a/video/external/Cargo.toml b/video/external/Cargo.toml index f254079a6d83..1af1d2866641 100644 --- a/video/external/Cargo.toml +++ b/video/external/Cargo.toml @@ -14,6 +14,7 @@ swf = { path = "../../swf" } slotmap = { workspace = true } tracing = { workspace = true } ruffle_video_software = { path = "../software" } +thiserror = { workspace = true } # Needed for OpenH264: libloading = "0.8.5" diff --git a/video/external/src/backend.rs b/video/external/src/backend.rs index 236a808d61af..d684294be6a1 100644 --- a/video/external/src/backend.rs +++ b/video/external/src/backend.rs @@ -1,3 +1,4 @@ +use crate::decoder::openh264::OpenH264Codec; use crate::decoder::VideoDecoder; use bzip2::read::BzDecoder; use ruffle_render::backend::RenderBackend; @@ -24,11 +25,17 @@ enum ProxyOrStream { Owned(VideoStream), } +struct OpenH264Data { + local_filenames: Vec<&'static str>, + download_filename: &'static str, + download_sha256: &'static str, +} + /// A video backend that falls back to the software backend for most codecs, /// except for H.264, for which it uses an external decoder. pub struct ExternalVideoBackend { streams: SlotMap, - openh264_lib_filepath: Option, + openh264_codec: Option, software: SoftwareVideoBackend, } @@ -39,51 +46,71 @@ impl Default for ExternalVideoBackend { } impl ExternalVideoBackend { - fn get_openh264_data() -> Result<(&'static str, &'static str), Box> { + fn get_openh264_data() -> Result> { + const OS: &str = std::env::consts::OS; + const ARCH: &str = std::env::consts::ARCH; + + let local_filenames = match OS { + "linux" => vec!["libopenh264.so.7", "libopenh264.so.2.4.1", "libopenh264.so"], + // TODO: investigate other OSes + _ => vec![], + }; + // Source: https://github.com/cisco/openh264/releases/tag/v2.4.1 - match (std::env::consts::OS, std::env::consts::ARCH) { - ("linux", "x86") => Ok(( + let (download_filename, download_sha256) = match (OS, ARCH) { + ("linux", "x86") => ( "libopenh264-2.4.1-linux32.7.so", "b7cf0e407f99056d90cbf62787a34820a7595b2129b165319d50766e00a66704", - )), - ("linux", "x86_64") => Ok(( + ), + ("linux", "x86_64") => ( "libopenh264-2.4.1-linux64.7.so", "1392d21466bc638e68151b716d5b2086d54cd812afd43253f1adb5b6e0185f51", - )), - ("linux", "arm") => Ok(( + ), + ("linux", "arm") => ( "libopenh264-2.4.1-linux-arm.7.so", "fd1dfb27d30bb72e903c9d2b4c650104a4369d2e7ffe8a4a533e8db2e7e9b19e", - )), - ("linux", "aarch64") => Ok(( + ), + ("linux", "aarch64") => ( "libopenh264-2.4.1-linux-arm64.7.so", "e8ea7e42855ceb4a90e7bd0b3abeba0c58b5f97166e8b0a30eefd58e099557a4", - )), - ("macos", "x86_64") => Ok(( + ), + ("macos", "x86_64") => ( "libopenh264-2.4.1-mac-x64.dylib", "cc0ba518a63791c37571f3c851f0aa03a4fbda5410acc214ecd4f24f8d1c478e", - )), - ("macos", "aarch64") => Ok(( + ), + ("macos", "aarch64") => ( "libopenh264-2.4.1-mac-arm64.dylib", "213ff93831cfa3dd6d7ad0c3a3403a6ceedf4ac1341e1278b5b869d42fefb496", - )), - ("windows", "x86") => Ok(( + ), + ("windows", "x86") => ( "openh264-2.4.1-win32.dll", "83270149640469c994a62cc32a6d8c0413cd7b802b7f1f2f532159f5bdc1cedd", - )), - ("windows", "x86_64") => Ok(( + ), + ("windows", "x86_64") => ( "openh264-2.4.1-win64.dll", "081b0c081480d177cbfddfbc90b1613640e702f875897b30d8de195cde73dd34", - )), - (os, arch) => Err(format!("Unsupported OS/ARCH: {} {}", os, arch).into()), - } + ), + (os, arch) => return Err(format!("Unsupported OS/arch: {}/{}", os, arch).into()), + }; + + Ok(OpenH264Data { + local_filenames, + download_filename, + download_sha256, + }) } - pub fn get_openh264() -> Result> { + fn download_openh264( + openh264_data: &OpenH264Data, + ) -> Result> { // See the license at: https://www.openh264.org/BINARY_LICENSE.txt const URL_BASE: &str = "http://ciscobinary.openh264.org/"; const URL_SUFFIX: &str = ".bz2"; - let (filename, sha256sum) = Self::get_openh264_data()?; + let (filename, sha256sum) = ( + openh264_data.download_filename, + openh264_data.download_sha256, + ); let current_exe = std::env::current_exe()?; let directory = current_exe @@ -122,10 +149,32 @@ impl ExternalVideoBackend { Ok(filepath) } - pub fn new(openh264_lib_filepath: Option) -> Self { + pub fn load_openh264() -> Result> { + let openh264_data = Self::get_openh264_data()?; + + for filename in &openh264_data.local_filenames { + match OpenH264Codec::new(filename) { + Ok(codec) => return Ok(codec), + Err(err) => { + tracing::warn!( + "Failed to load system OpenH264 library {}: {}", + filename, + err + ); + } + } + } + + tracing::info!("Downloading OpenH264 library"); + let filename = Self::download_openh264(&openh264_data)?; + tracing::info!("Using OpenH264 at {:?}", filename); + Ok(OpenH264Codec::new(&filename)?) + } + + pub fn new(openh264_codec: Option) -> Self { Self { streams: SlotMap::with_key(), - openh264_lib_filepath, + openh264_codec, software: SoftwareVideoBackend::new(), } } @@ -142,10 +191,8 @@ impl VideoBackend for ExternalVideoBackend { filter: VideoDeblocking, ) -> Result { let proxy_or_stream = if codec == VideoCodec::H264 { - let openh264 = &self.openh264_lib_filepath; - if let Some(openh264) = openh264 { - tracing::info!("Using OpenH264 at {:?}", openh264); - let decoder = Box::new(crate::decoder::openh264::H264Decoder::new(openh264)); + if let Some(h264_codec) = self.openh264_codec.as_ref() { + let decoder = Box::new(crate::decoder::openh264::H264Decoder::new(h264_codec)); let stream = VideoStream::new(decoder); ProxyOrStream::Owned(stream) } else { diff --git a/video/external/src/decoder/openh264.rs b/video/external/src/decoder/openh264.rs index ecf7965a6846..3f06fdbc5f27 100644 --- a/video/external/src/decoder/openh264.rs +++ b/video/external/src/decoder/openh264.rs @@ -1,6 +1,8 @@ use core::slice; use std::ffi::{c_int, c_uchar}; +use std::fmt::Display; use std::ptr; +use std::sync::Arc; use crate::decoder::openh264_sys::{self, videoFormatI420, ISVCDecoder, OpenH264}; use crate::decoder::VideoDecoder; @@ -8,32 +10,70 @@ use crate::decoder::VideoDecoder; use ruffle_render::bitmap::BitmapFormat; use ruffle_video::error::Error; use ruffle_video::frame::{DecodedFrame, EncodedFrame, FrameDependency}; +use thiserror::Error; + +#[derive(Debug, PartialEq, Eq)] +pub struct OpenH264Version(u32, u32, u32); + +impl Display for OpenH264Version { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}.{}", self.0, self.1, self.2)?; + Ok(()) + } +} + +#[derive(Debug, Error)] +pub enum OpenH264Error { + #[error("Error while loading OpenH264: {0}")] + LibraryLoadingError(#[from] ::libloading::Error), + + #[error("OpenH264 version mismatch, expected {0}, was {1}")] + VersionMismatchError(OpenH264Version, OpenH264Version), +} + +/// OpenH264 codec representation. +pub struct OpenH264Codec { + openh264: Arc, +} + +impl OpenH264Codec { + const VERSION: OpenH264Version = OpenH264Version(2, 4, 1); + + pub fn new

(filename: P) -> Result + where + P: AsRef<::std::ffi::OsStr>, + { + let openh264 = unsafe { OpenH264::new(filename)? }; + + let version = unsafe { openh264.WelsGetCodecVersion() }; + let version = OpenH264Version(version.uMajor, version.uMinor, version.uRevision); + + if Self::VERSION != version { + return Err(OpenH264Error::VersionMismatchError(Self::VERSION, version)); + } + + Ok(Self { + openh264: Arc::new(openh264), + }) + } +} /// H264 video decoder. pub struct H264Decoder { /// How many bytes are used to store the length of the NALU (1, 2, 3, or 4). length_size: u8, - openh264: OpenH264, + openh264: Arc, decoder: *mut ISVCDecoder, } impl H264Decoder { /// `extradata` should hold "AVCC (MP4) format" decoder configuration, including PPS and SPS. /// Make sure it has any start code emulation prevention "three bytes" removed. - pub fn new(openh264_lib_filename: &std::path::Path) -> Self { + pub fn new(h264: &OpenH264Codec) -> Self { + let openh264 = h264.openh264.clone(); let mut decoder: *mut ISVCDecoder = ptr::null_mut(); unsafe { - let openh264 = OpenH264::new(openh264_lib_filename).unwrap(); - - let version = openh264.WelsGetCodecVersion(); - - assert_eq!( - (version.uMajor, version.uMinor, version.uRevision), - (2, 4, 1), - "Unexpected OpenH264 version" - ); - openh264.WelsCreateDecoder(&mut decoder); let decoder_vtbl = (*decoder).as_ref().unwrap(); diff --git a/video/external/src/decoder/openh264_sys.rs b/video/external/src/decoder/openh264_sys.rs index e8713efb94d1..6246e2117732 100644 --- a/video/external/src/decoder/openh264_sys.rs +++ b/video/external/src/decoder/openh264_sys.rs @@ -1359,11 +1359,11 @@ pub struct OpenH264 { Result, } impl OpenH264 { - pub unsafe fn new

(path: P) -> Result + pub unsafe fn new

(filename: P) -> Result where P: AsRef<::std::ffi::OsStr>, { - let library = ::libloading::Library::new(path)?; + let library = ::libloading::Library::new(filename)?; Self::from_library(library) } pub unsafe fn from_library(library: L) -> Result