Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

video: Prefer loading system OpenH264 library to downloading it #17799

Merged
merged 5 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions desktop/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
6 changes: 3 additions & 3 deletions tests/framework/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions video/external/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
105 changes: 76 additions & 29 deletions video/external/src/backend.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::decoder::openh264::OpenH264Codec;
use crate::decoder::VideoDecoder;
use bzip2::read::BzDecoder;
use ruffle_render::backend::RenderBackend;
Expand All @@ -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<VideoStreamHandle, ProxyOrStream>,
openh264_lib_filepath: Option<PathBuf>,
openh264_codec: Option<OpenH264Codec>,
software: SoftwareVideoBackend,
}

Expand All @@ -39,51 +46,71 @@ impl Default for ExternalVideoBackend {
}

impl ExternalVideoBackend {
fn get_openh264_data() -> Result<(&'static str, &'static str), Box<dyn std::error::Error>> {
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
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<PathBuf, Box<dyn std::error::Error>> {
fn download_openh264(
openh264_data: &OpenH264Data,
) -> 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) = 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
Expand Down Expand Up @@ -122,10 +149,32 @@ impl ExternalVideoBackend {
Ok(filepath)
}

pub fn new(openh264_lib_filepath: Option<PathBuf>) -> Self {
pub fn load_openh264() -> 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)?;
tracing::info!("Using OpenH264 at {:?}", filename);
Ok(OpenH264Codec::new(&filename)?)
}

pub fn new(openh264_codec: Option<OpenH264Codec>) -> Self {
Self {
streams: SlotMap::with_key(),
openh264_lib_filepath,
openh264_codec,
software: SoftwareVideoBackend::new(),
}
}
Expand All @@ -142,10 +191,8 @@ impl VideoBackend for ExternalVideoBackend {
filter: VideoDeblocking,
) -> Result<VideoStreamHandle, Error> {
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 {
Expand Down
64 changes: 52 additions & 12 deletions video/external/src/decoder/openh264.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,79 @@
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;

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<OpenH264>,
}

impl OpenH264Codec {
const VERSION: OpenH264Version = OpenH264Version(2, 4, 1);

pub fn new<P>(filename: P) -> Result<Self, OpenH264Error>
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<OpenH264>,
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();
Expand Down
4 changes: 2 additions & 2 deletions video/external/src/decoder/openh264_sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1359,11 +1359,11 @@ pub struct OpenH264 {
Result<unsafe extern "C" fn(pVersion: *mut OpenH264Version), ::libloading::Error>,
}
impl OpenH264 {
pub unsafe fn new<P>(path: P) -> Result<Self, ::libloading::Error>
pub unsafe fn new<P>(filename: P) -> Result<Self, ::libloading::Error>
where
P: AsRef<::std::ffi::OsStr>,
torokati44 marked this conversation as resolved.
Show resolved Hide resolved
{
let library = ::libloading::Library::new(path)?;
let library = ::libloading::Library::new(filename)?;
Self::from_library(library)
}
pub unsafe fn from_library<L>(library: L) -> Result<Self, ::libloading::Error>
Expand Down
Loading