video/external: Move OpenH264 related stuff into the `openh264` decoder module

This commit is contained in:
TÖRÖK Attila 2024-06-27 20:37:14 +02:00
parent 35f1fda4b2
commit 0e1d500654
4 changed files with 151 additions and 139 deletions

View File

@ -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),

View File

@ -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 =

View File

@ -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<OpenH264Data, Box<dyn std::error::Error>> {
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<PathBuf, Box<dyn std::error::Error>> {
// 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<OpenH264Codec, Box<dyn std::error::Error>> {
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<OpenH264Codec>) -> Self {
Self {
streams: SlotMap::with_key(),

View File

@ -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<P>(filename: P) -> Result<Self, OpenH264Error>
/// Returns the OpenH264 library data for the current platform.
fn get_data() -> Result<OpenH264Data, Box<dyn std::error::Error>> {
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<PathBuf, Box<dyn std::error::Error>> {
// 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<P>(filename: P) -> Result<Self, OpenH264Error>
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<Self, Box<dyn std::error::Error>> {
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.