From 0e1d5006549214d936a461d2809276c7dfddb7b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Thu, 27 Jun 2024 20:37:14 +0200 Subject: [PATCH] video/external: Move OpenH264 related stuff into the `openh264` decoder module --- desktop/src/player.rs | 6 +- tests/framework/src/options.rs | 6 +- video/external/src/backend.rs | 136 +---------------------- video/external/src/decoder/openh264.rs | 142 ++++++++++++++++++++++++- 4 files changed, 151 insertions(+), 139 deletions(-) diff --git a/desktop/src/player.rs b/desktop/src/player.rs index 5599986a5..da3384cbf 100644 --- a/desktop/src/player.rs +++ b/desktop/src/player.rs @@ -228,9 +228,11 @@ impl ActivePlayer { if cfg!(feature = "external_video") && preferences.openh264_enabled() { #[cfg(feature = "external_video")] { - use ruffle_video_external::backend::ExternalVideoBackend; + use ruffle_video_external::{ + backend::ExternalVideoBackend, decoder::openh264::OpenH264Codec, + }; let openh264 = tokio::task::block_in_place(|| { - ExternalVideoBackend::load_openh264(&opt.cache_directory.join("video")) + OpenH264Codec::load(&opt.cache_directory.join("video")) }); let openh264 = match openh264 { Ok(codec) => Some(codec), diff --git a/tests/framework/src/options.rs b/tests/framework/src/options.rs index 7258a75f7..0880ad415 100644 --- a/tests/framework/src/options.rs +++ b/tests/framework/src/options.rs @@ -180,8 +180,10 @@ impl PlayerOptions { let current_exe = std::env::current_exe()?; let directory = current_exe.parent().expect("Executable parent dir"); - use ruffle_video_external::backend::ExternalVideoBackend; - let openh264 = ExternalVideoBackend::load_openh264(directory) + use ruffle_video_external::{ + backend::ExternalVideoBackend, decoder::openh264::OpenH264Codec, + }; + let openh264 = OpenH264Codec::load(directory) .map_err(|e| anyhow!("Couldn't load OpenH264: {}", e))?; player_builder = diff --git a/video/external/src/backend.rs b/video/external/src/backend.rs index 7751ebe1a..7908ad9c5 100644 --- a/video/external/src/backend.rs +++ b/video/external/src/backend.rs @@ -1,6 +1,6 @@ use crate::decoder::openh264::OpenH264Codec; use crate::decoder::VideoDecoder; -use bzip2::read::BzDecoder; + use ruffle_render::backend::RenderBackend; use ruffle_render::bitmap::{BitmapHandle, BitmapInfo, PixelRegion}; use ruffle_video::backend::VideoBackend; @@ -8,11 +8,8 @@ use ruffle_video::error::Error; use ruffle_video::frame::{EncodedFrame, FrameDependency}; use ruffle_video::VideoStreamHandle; use ruffle_video_software::backend::SoftwareVideoBackend; -use sha2::{Digest, Sha256}; use slotmap::SlotMap; -use std::fs::File; -use std::io::copy; -use std::path::{Path, PathBuf}; + use swf::{VideoCodec, VideoDeblocking}; enum ProxyOrStream { @@ -25,12 +22,6 @@ 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 { @@ -46,129 +37,6 @@ impl Default for ExternalVideoBackend { } impl ExternalVideoBackend { - 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 - let (download_filename, download_sha256) = match (OS, ARCH) { - ("linux", "x86") => ( - "libopenh264-2.4.1-linux32.7.so", - "b7cf0e407f99056d90cbf62787a34820a7595b2129b165319d50766e00a66704", - ), - ("linux", "x86_64") => ( - "libopenh264-2.4.1-linux64.7.so", - "1392d21466bc638e68151b716d5b2086d54cd812afd43253f1adb5b6e0185f51", - ), - ("linux", "arm") => ( - "libopenh264-2.4.1-linux-arm.7.so", - "fd1dfb27d30bb72e903c9d2b4c650104a4369d2e7ffe8a4a533e8db2e7e9b19e", - ), - ("linux", "aarch64") => ( - "libopenh264-2.4.1-linux-arm64.7.so", - "e8ea7e42855ceb4a90e7bd0b3abeba0c58b5f97166e8b0a30eefd58e099557a4", - ), - ("macos", "x86_64") => ( - "libopenh264-2.4.1-mac-x64.dylib", - "cc0ba518a63791c37571f3c851f0aa03a4fbda5410acc214ecd4f24f8d1c478e", - ), - ("macos", "aarch64") => ( - "libopenh264-2.4.1-mac-arm64.dylib", - "213ff93831cfa3dd6d7ad0c3a3403a6ceedf4ac1341e1278b5b869d42fefb496", - ), - ("windows", "x86") => ( - "openh264-2.4.1-win32.dll", - "83270149640469c994a62cc32a6d8c0413cd7b802b7f1f2f532159f5bdc1cedd", - ), - ("windows", "x86_64") => ( - "openh264-2.4.1-win64.dll", - "081b0c081480d177cbfddfbc90b1613640e702f875897b30d8de195cde73dd34", - ), - (os, arch) => return Err(format!("Unsupported OS/arch: {}/{}", os, arch).into()), - }; - - Ok(OpenH264Data { - local_filenames, - download_filename, - download_sha256, - }) - } - - fn download_openh264( - openh264_data: &OpenH264Data, - directory: &Path, - ) -> 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) = ( - openh264_data.download_filename, - openh264_data.download_sha256, - ); - - std::fs::create_dir_all(directory)?; - let filepath = directory.join(filename); - - // If the binary doesn't exist in the expected location, download it. - if !filepath.is_file() { - let url = format!("{}{}{}", URL_BASE, filename, URL_SUFFIX); - let response = reqwest::blocking::get(url)?; - let mut bzip2_reader = BzDecoder::new(response); - - let mut tempfile = tempfile::NamedTempFile::with_prefix_in(filename, directory)?; - copy(&mut bzip2_reader, &mut tempfile)?; - // Let's assume that if this fails, it's because another process has already put it there - // and loaded it, therefore it can't be overwritten (on Windows at least), but in the end, - // all's fine - the hash will still be checked before attempting to load the library. - let _ = tempfile.persist(&filepath); - } - - // Regardless of whether the library was already there, or we just downloaded it, let's check the MD5 hash. - let mut sha256 = Sha256::new(); - copy(&mut File::open(filepath.clone())?, &mut sha256)?; - let sha256digest = sha256.finalize(); - let result: [u8; 32] = sha256digest.into(); - - if result[..] != hex::decode(sha256sum)?[..] { - let size = filepath.metadata().map(|f| f.len()).unwrap_or_default(); - return Err(format!( - "SHA256 checksum mismatch for {filename}; expected {sha256sum}, found {sha256digest:x} (with a size of {size} bytes)", - ) - .into()); - } - - Ok(filepath) - } - - pub fn load_openh264(directory: &Path) -> 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, directory)?; - tracing::info!("Using OpenH264 at {:?}", filename); - Ok(OpenH264Codec::new(&filename)?) - } - pub fn new(openh264_codec: Option) -> Self { Self { streams: SlotMap::with_key(), diff --git a/video/external/src/decoder/openh264.rs b/video/external/src/decoder/openh264.rs index 3f06fdbc5..5091c6262 100644 --- a/video/external/src/decoder/openh264.rs +++ b/video/external/src/decoder/openh264.rs @@ -1,6 +1,9 @@ use core::slice; use std::ffi::{c_int, c_uchar}; use std::fmt::Display; +use std::fs::File; +use std::io::copy; +use std::path::{Path, PathBuf}; use std::ptr; use std::sync::Arc; @@ -10,6 +13,9 @@ use crate::decoder::VideoDecoder; use ruffle_render::bitmap::BitmapFormat; use ruffle_video::error::Error; use ruffle_video::frame::{DecodedFrame, EncodedFrame, FrameDependency}; + +use bzip2::read::BzDecoder; +use sha2::{Digest, Sha256}; use thiserror::Error; #[derive(Debug, PartialEq, Eq)] @@ -39,7 +45,111 @@ pub struct OpenH264Codec { impl OpenH264Codec { const VERSION: OpenH264Version = OpenH264Version(2, 4, 1); - pub fn new

(filename: P) -> Result + /// Returns the OpenH264 library data for the current platform. + fn get_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 + let (download_filename, download_sha256) = match (OS, ARCH) { + ("linux", "x86") => ( + "libopenh264-2.4.1-linux32.7.so", + "b7cf0e407f99056d90cbf62787a34820a7595b2129b165319d50766e00a66704", + ), + ("linux", "x86_64") => ( + "libopenh264-2.4.1-linux64.7.so", + "1392d21466bc638e68151b716d5b2086d54cd812afd43253f1adb5b6e0185f51", + ), + ("linux", "arm") => ( + "libopenh264-2.4.1-linux-arm.7.so", + "fd1dfb27d30bb72e903c9d2b4c650104a4369d2e7ffe8a4a533e8db2e7e9b19e", + ), + ("linux", "aarch64") => ( + "libopenh264-2.4.1-linux-arm64.7.so", + "e8ea7e42855ceb4a90e7bd0b3abeba0c58b5f97166e8b0a30eefd58e099557a4", + ), + ("macos", "x86_64") => ( + "libopenh264-2.4.1-mac-x64.dylib", + "cc0ba518a63791c37571f3c851f0aa03a4fbda5410acc214ecd4f24f8d1c478e", + ), + ("macos", "aarch64") => ( + "libopenh264-2.4.1-mac-arm64.dylib", + "213ff93831cfa3dd6d7ad0c3a3403a6ceedf4ac1341e1278b5b869d42fefb496", + ), + ("windows", "x86") => ( + "openh264-2.4.1-win32.dll", + "83270149640469c994a62cc32a6d8c0413cd7b802b7f1f2f532159f5bdc1cedd", + ), + ("windows", "x86_64") => ( + "openh264-2.4.1-win64.dll", + "081b0c081480d177cbfddfbc90b1613640e702f875897b30d8de195cde73dd34", + ), + (os, arch) => return Err(format!("Unsupported OS/arch: {}/{}", os, arch).into()), + }; + + Ok(OpenH264Data { + local_filenames, + download_filename, + download_sha256, + }) + } + + /// Downloads the OpenH264 library if it doesn't exist yet, and verifies its SHA256 hash. + fn fetch_and_verify( + openh264_data: &OpenH264Data, + directory: &Path, + ) -> 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) = ( + openh264_data.download_filename, + openh264_data.download_sha256, + ); + + std::fs::create_dir_all(directory)?; + let filepath = directory.join(filename); + + // If the binary doesn't exist in the expected location, download it. + if !filepath.is_file() { + let url = format!("{}{}{}", URL_BASE, filename, URL_SUFFIX); + let response = reqwest::blocking::get(url)?; + let mut bzip2_reader = BzDecoder::new(response); + + let mut tempfile = tempfile::NamedTempFile::with_prefix_in(filename, directory)?; + copy(&mut bzip2_reader, &mut tempfile)?; + // Let's assume that if this fails, it's because another process has already put it there + // and loaded it, therefore it can't be overwritten (on Windows at least), but in the end, + // all's fine - the hash will still be checked before attempting to load the library. + let _ = tempfile.persist(&filepath); + } + + // Regardless of whether the library was already there, or we just downloaded it, let's check the MD5 hash. + let mut sha256 = Sha256::new(); + copy(&mut File::open(filepath.clone())?, &mut sha256)?; + let sha256digest = sha256.finalize(); + let result: [u8; 32] = sha256digest.into(); + + if result[..] != hex::decode(sha256sum)?[..] { + let size = filepath.metadata().map(|f| f.len()).unwrap_or_default(); + return Err(format!( + "SHA256 checksum mismatch for {filename}; expected {sha256sum}, found {sha256digest:x} (with a size of {size} bytes)", + ) + .into()); + } + + Ok(filepath) + } + + /// Loads an existing OpenH264 library from the given path. + fn load_existing

(filename: P) -> Result where P: AsRef<::std::ffi::OsStr>, { @@ -56,6 +166,30 @@ impl OpenH264Codec { openh264: Arc::new(openh264), }) } + + /// Loads the OpenH264 library - first trying one installed on the system (on supported platforms), + /// then falling back to a local file in `directory`, downloading it into there if necessary. + pub fn load(directory: &Path) -> Result> { + let openh264_data = Self::get_data()?; + + for filename in &openh264_data.local_filenames { + match OpenH264Codec::load_existing(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::fetch_and_verify(&openh264_data, directory)?; + tracing::info!("Using OpenH264 at {:?}", filename); + Ok(OpenH264Codec::load_existing(&filename)?) + } } /// H264 video decoder. @@ -67,6 +201,12 @@ pub struct H264Decoder { decoder: *mut ISVCDecoder, } +struct OpenH264Data { + local_filenames: Vec<&'static str>, + download_filename: &'static str, + download_sha256: &'static str, +} + 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.