video/external: Move OpenH264 related stuff into the `openh264` decoder module
This commit is contained in:
parent
35f1fda4b2
commit
0e1d500654
|
@ -228,9 +228,11 @@ impl ActivePlayer {
|
||||||
if cfg!(feature = "external_video") && preferences.openh264_enabled() {
|
if cfg!(feature = "external_video") && preferences.openh264_enabled() {
|
||||||
#[cfg(feature = "external_video")]
|
#[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(|| {
|
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 {
|
let openh264 = match openh264 {
|
||||||
Ok(codec) => Some(codec),
|
Ok(codec) => Some(codec),
|
||||||
|
|
|
@ -180,8 +180,10 @@ impl PlayerOptions {
|
||||||
let current_exe = std::env::current_exe()?;
|
let current_exe = std::env::current_exe()?;
|
||||||
let directory = current_exe.parent().expect("Executable parent dir");
|
let directory = current_exe.parent().expect("Executable parent dir");
|
||||||
|
|
||||||
use ruffle_video_external::backend::ExternalVideoBackend;
|
use ruffle_video_external::{
|
||||||
let openh264 = ExternalVideoBackend::load_openh264(directory)
|
backend::ExternalVideoBackend, decoder::openh264::OpenH264Codec,
|
||||||
|
};
|
||||||
|
let openh264 = OpenH264Codec::load(directory)
|
||||||
.map_err(|e| anyhow!("Couldn't load OpenH264: {}", e))?;
|
.map_err(|e| anyhow!("Couldn't load OpenH264: {}", e))?;
|
||||||
|
|
||||||
player_builder =
|
player_builder =
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::decoder::openh264::OpenH264Codec;
|
use crate::decoder::openh264::OpenH264Codec;
|
||||||
use crate::decoder::VideoDecoder;
|
use crate::decoder::VideoDecoder;
|
||||||
use bzip2::read::BzDecoder;
|
|
||||||
use ruffle_render::backend::RenderBackend;
|
use ruffle_render::backend::RenderBackend;
|
||||||
use ruffle_render::bitmap::{BitmapHandle, BitmapInfo, PixelRegion};
|
use ruffle_render::bitmap::{BitmapHandle, BitmapInfo, PixelRegion};
|
||||||
use ruffle_video::backend::VideoBackend;
|
use ruffle_video::backend::VideoBackend;
|
||||||
|
@ -8,11 +8,8 @@ use ruffle_video::error::Error;
|
||||||
use ruffle_video::frame::{EncodedFrame, FrameDependency};
|
use ruffle_video::frame::{EncodedFrame, FrameDependency};
|
||||||
use ruffle_video::VideoStreamHandle;
|
use ruffle_video::VideoStreamHandle;
|
||||||
use ruffle_video_software::backend::SoftwareVideoBackend;
|
use ruffle_video_software::backend::SoftwareVideoBackend;
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::copy;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use swf::{VideoCodec, VideoDeblocking};
|
use swf::{VideoCodec, VideoDeblocking};
|
||||||
|
|
||||||
enum ProxyOrStream {
|
enum ProxyOrStream {
|
||||||
|
@ -25,12 +22,6 @@ enum ProxyOrStream {
|
||||||
Owned(VideoStream),
|
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,
|
/// A video backend that falls back to the software backend for most codecs,
|
||||||
/// except for H.264, for which it uses an external decoder.
|
/// except for H.264, for which it uses an external decoder.
|
||||||
pub struct ExternalVideoBackend {
|
pub struct ExternalVideoBackend {
|
||||||
|
@ -46,129 +37,6 @@ impl Default for ExternalVideoBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub fn new(openh264_codec: Option<OpenH264Codec>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
streams: SlotMap::with_key(),
|
streams: SlotMap::with_key(),
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use core::slice;
|
use core::slice;
|
||||||
use std::ffi::{c_int, c_uchar};
|
use std::ffi::{c_int, c_uchar};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::copy;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -10,6 +13,9 @@ use crate::decoder::VideoDecoder;
|
||||||
use ruffle_render::bitmap::BitmapFormat;
|
use ruffle_render::bitmap::BitmapFormat;
|
||||||
use ruffle_video::error::Error;
|
use ruffle_video::error::Error;
|
||||||
use ruffle_video::frame::{DecodedFrame, EncodedFrame, FrameDependency};
|
use ruffle_video::frame::{DecodedFrame, EncodedFrame, FrameDependency};
|
||||||
|
|
||||||
|
use bzip2::read::BzDecoder;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
@ -39,7 +45,111 @@ pub struct OpenH264Codec {
|
||||||
impl OpenH264Codec {
|
impl OpenH264Codec {
|
||||||
const VERSION: OpenH264Version = OpenH264Version(2, 4, 1);
|
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
|
where
|
||||||
P: AsRef<::std::ffi::OsStr>,
|
P: AsRef<::std::ffi::OsStr>,
|
||||||
{
|
{
|
||||||
|
@ -56,6 +166,30 @@ impl OpenH264Codec {
|
||||||
openh264: Arc::new(openh264),
|
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.
|
/// H264 video decoder.
|
||||||
|
@ -67,6 +201,12 @@ pub struct H264Decoder {
|
||||||
decoder: *mut ISVCDecoder,
|
decoder: *mut ISVCDecoder,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct OpenH264Data {
|
||||||
|
local_filenames: Vec<&'static str>,
|
||||||
|
download_filename: &'static str,
|
||||||
|
download_sha256: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
impl H264Decoder {
|
impl H264Decoder {
|
||||||
/// `extradata` should hold "AVCC (MP4) format" decoder configuration, including PPS and SPS.
|
/// `extradata` should hold "AVCC (MP4) format" decoder configuration, including PPS and SPS.
|
||||||
/// Make sure it has any start code emulation prevention "three bytes" removed.
|
/// Make sure it has any start code emulation prevention "three bytes" removed.
|
||||||
|
|
Loading…
Reference in New Issue