Skip to content
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
25 changes: 25 additions & 0 deletions Cargo.lock

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

11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,13 @@ panic = "abort"
opt-level = 3

[profile.dev.package.h263-rs-yuv]
opt-level = 3
opt-level = 3

[profile.dev.package.nihav_core]
opt-level = 3

[profile.dev.package.nihav_codec_support]
opt-level = 3

[profile.dev.package.nihav_duck]
opt-level = 3
1 change: 1 addition & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ Ruffle depends on third-party libraries with compatible licenses.
| [ndk-sys](https://github.com/rust-windowing/android-ndk-rs) | [Apache-2.0](#Apache-20)/[MIT](#MIT) | Copyright (c) The Rust Windowing contributors |
| nellymoser-rs | [MIT](#MIT) | Copyright (c) 2021 relrelb Copyright (c) 2007 a840bda5870ba11f19698ff6eb9581dfb0f95fa5, 539459aeb7d425140b62a3ec7dbf6dc8e408a306, and 520e17cd55896441042b14df2566a6eb610ed444 Copyright (c) 2007 Loic Minier, Benjamin Larsson. Derived from MIT licensed source in the nelly2pcm and FFmpeg projects. |
| [net2](https://github.com/deprecrated/net2-rs) | [Apache-2.0](#Apache-20)/[MIT](#MIT) | Copyright (c) 2014 The Rust Project Developers |
| [nihav-vp6](https://github.com/ruffle-rs/nihav-vp6) | [MIT](#MIT) | Copyright (c) 2021 Kostya Shishkov |
| [nix](https://github.com/nix-rust/nix) | [MIT](#MIT) | Copyright (c) 2015 Carl Lerche + nix-rust Authors |
| [nom](https://github.com/Geal/nom) | [MIT](#MIT) | Copyright (c) 2014-2019 Geoffroy Couprie |
| [num-complex](https://github.com/rust-num/num-complex) | [Apache-2.0](#Apache-20)/[MIT](#MIT) | Copyright (c) 2014 The Rust Project Developers |
Expand Down
4 changes: 4 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ json = "0.12.4"
lzma-rs = {version = "0.2.0", optional = true }
dasp = { git = "https://github.com/RustAudio/dasp", rev = "f05a703", features = ["interpolate", "interpolate-linear", "signal"] }
symphonia = { version = "0.4.0", default-features = false, features = ["mp3"], optional = true }
nihav_core = { git = "https://github.com/ruffle-rs/nihav-vp6", rev = "9416fcc9fc8aab8f4681aa9093b42922214abbd3", optional = true }
nihav_codec_support = { git = "https://github.com/ruffle-rs/nihav-vp6", rev = "9416fcc9fc8aab8f4681aa9093b42922214abbd3", optional = true }
nihav_duck = { git = "https://github.com/ruffle-rs/nihav-vp6", rev = "9416fcc9fc8aab8f4681aa9093b42922214abbd3", optional = true }

[dependencies.jpeg-decoder]
version = "0.1.22"
Expand All @@ -54,6 +57,7 @@ approx = "0.5.0"
[features]
default = ["minimp3", "serde"]
h263 = ["h263-rs", "h263-rs-yuv"]
vp6 = ["nihav_core", "nihav_codec_support", "nihav_duck", "h263-rs-yuv"]
lzma = ["lzma-rs", "swf/lzma"]
wasm-bindgen = [ "instant/wasm-bindgen" ]
avm_debug = []
216 changes: 215 additions & 1 deletion core/src/backend/video/software.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,17 @@ impl VideoBackend for SoftwareVideoBackend {
fn register_video_stream(
&mut self,
_num_frames: u32,
_size: (u16, u16),
size: (u16, u16),
codec: VideoCodec,
_filter: VideoDeblocking,
) -> Result<VideoStreamHandle, Error> {
let decoder: Box<dyn VideoDecoder> = match codec {
#[cfg(feature = "h263")]
VideoCodec::H263 => Box::new(h263::H263Decoder::new()),
#[cfg(feature = "vp6")]
VideoCodec::Vp6 => Box::new(vp6::Vp6Decoder::new(false, size)),
#[cfg(feature = "vp6")]
VideoCodec::Vp6WithAlpha => Box::new(vp6::Vp6Decoder::new(true, size)),
_ => return Err(format!("Unsupported video codec type {:?}", codec).into()),
};
let stream = VideoStream::new(decoder);
Expand Down Expand Up @@ -198,3 +202,213 @@ mod h263 {
}
}
}

#[cfg(feature = "vp6")]
mod vp6 {
use crate::backend::video::software::VideoDecoder;
use crate::backend::video::{DecodedFrame, EncodedFrame, Error, FrameDependency};

use h263_rs_yuv::bt601::yuv420_to_rgba;

use nihav_codec_support::codecs::NAVideoInfo;
use nihav_codec_support::codecs::{NABufferType::Video, YUV420_FORMAT};
use nihav_core::codecs::NADecoderSupport;
use nihav_duck::codecs::vp6::{VP56Decoder, VP56Parser, VP6BR};
use nihav_duck::codecs::vpcommon::{BoolCoder, VP_YUVA420_FORMAT};

/// VP6 video decoder.
pub struct Vp6Decoder {
with_alpha: bool,
bounds: (u16, u16),
decoder: VP56Decoder,
support: NADecoderSupport,
bitreader: VP6BR,
init_called: bool,
}

impl Vp6Decoder {
pub fn new(with_alpha: bool, bounds: (u16, u16)) -> Self {
// Unfortunately, `init()` cannot be called on the decoder
// just yet, because `bounds` is only the declared size of
// the video, to which it will be cropped.
// This can be (rarely) even much smaller than the actual
// encoded size of the frames.
// `VP56Decoder::init()` needs the full encoded frame size,
// so it can allocate its internal buffers accordingly.
// The encoded frame size will be parsed from the header of
// the first encoded frame passed to `Self::decode_frame()`.

Self {
with_alpha,
bounds,
decoder: VP56Decoder::new(6, with_alpha, true),
support: NADecoderSupport::new(),
bitreader: VP6BR::new(),
init_called: false,
}
}
}

impl VideoDecoder for Vp6Decoder {
fn preload_frame(
&mut self,
encoded_frame: EncodedFrame<'_>,
) -> Result<FrameDependency, Error> {
// Luckily the very first bit of the encoded frames is exactly this flag,
// so we don't have to bother asking any "proper" decoder or parser.
Ok(
if !encoded_frame.data.is_empty() && (encoded_frame.data[0] & 0b_1000_0000) == 0 {
FrameDependency::None
} else {
FrameDependency::Past
},
)
}

fn decode_frame(&mut self, encoded_frame: EncodedFrame<'_>) -> Result<DecodedFrame, Error> {
// If this is the first frame, the decoder needs to be initialized.

if !self.init_called {
let mut bool_coder = BoolCoder::new(if self.with_alpha {
// The 24 bits alpha offset needs to be skipped first in this case
&encoded_frame.data[3..]
} else {
encoded_frame.data
})
.map_err(|error| {
Error::from(format!("Error constructing VP6 bool coder: {:?}", error))
})?;

let header = self
.bitreader
.parse_header(&mut bool_coder)
.map_err(|error| {
Error::from(format!("Error parsing VP6 frame header: {:?}", error))
})?;

let video_info = NAVideoInfo::new(
header.disp_w as usize * 16,
header.disp_h as usize * 16,
true,
if self.with_alpha {
VP_YUVA420_FORMAT
} else {
YUV420_FORMAT
},
);

self.decoder
.init(&mut self.support, video_info)
.map_err(|error| {
Error::from(format!("Error initializing VP6 decoder: {:?}", error))
})?;

self.init_called = true;
}

// Actually decoding the frame and extracting the buffer it is stored in.

let decoded = self
.decoder
.decode_frame(&mut self.support, encoded_frame.data, &mut self.bitreader)
.map_err(|error| Error::from(format!("VP6 decoder error: {:?}", error)))?;

let frame = match decoded {
(Video(buffer), _) => Ok(buffer),
_ => Err(Error::from(
"Unexpected buffer type after decoding a VP6 frame",
)),
}?;

// Converting it from YUV420 to RGBA.

let yuv = frame.get_data();

let (mut width, mut height) = frame.get_dimensions(0);
let (chroma_width, chroma_height) = frame.get_dimensions(1);

// We assume that there is no padding between rows
debug_assert!(frame.get_stride(0) == frame.get_dimensions(0).0);
debug_assert!(frame.get_stride(1) == frame.get_dimensions(1).0);
debug_assert!(frame.get_stride(2) == frame.get_dimensions(2).0);

// Where each plane starts in the buffer
let offsets = (
frame.get_offset(0),
frame.get_offset(1),
frame.get_offset(2),
);

let mut rgba = yuv420_to_rgba(
&yuv[offsets.0..offsets.0 + width * height],
&yuv[offsets.1..offsets.1 + chroma_width * chroma_height],
&yuv[offsets.2..offsets.2 + chroma_width * chroma_height],
width,
chroma_width,
);

// Adding in the alpha component, if present.

if self.with_alpha {
debug_assert!(frame.get_stride(3) == frame.get_dimensions(3).0);
let alpha_offset = frame.get_offset(3);
let alpha = &yuv[alpha_offset..alpha_offset + width * height];
for (alpha, rgba) in alpha.iter().zip(rgba.chunks_mut(4)) {
// The SWF spec mandates the `min` to avoid any accidental "invalid"
// premultiplied colors, which would cause strange results after blending.
// And the alpha data is encoded in full range (0-255), unlike the Y
// component of the main color data, so no remapping is needed.
rgba.copy_from_slice(&[
u8::min(rgba[0], *alpha),
u8::min(rgba[1], *alpha),
u8::min(rgba[2], *alpha),
*alpha,
]);
}
}

// Cropping the encoded frame (containing whole macroblocks) to the
// size requested by the the bounds attribute.

let &bounds = &self.bounds;

if width < bounds.0 as usize || height < bounds.1 as usize {
log::warn!("A VP6 video frame is smaller than the bounds of the stream it belongs in. This is not supported.");
// Flash Player just produces a black image in this case!
}

if width > bounds.0 as usize {
// Removing the unwanted pixels on the right edge (most commonly: unused pieces of macroblocks)
// by squishing all the rows tightly next to each other.
// Bitmap at the moment does not allow these gaps, so we need to remove them.
let new_width = bounds.0 as usize;
let new_height = usize::min(height, bounds.1 as usize);
// no need to move the first row, nor any rows on the bottom that will end up being cropped entirely
for row in 1..new_height {
rgba.copy_within(
row * width * 4..(row * width + new_width) * 4,
row * new_width * 4,
);
}
width = new_width;
height = new_height;
}

// Cropping the unwanted rows on the bottom, also dropping any unused space at the end left by the squish above
height = usize::min(height, bounds.1 as usize);
rgba.truncate(width * height * 4);

Ok(DecodedFrame {
width: width as u16,
height: height as u16,
rgba,
})
}
}

impl Default for Vp6Decoder {
fn default() -> Self {
Self::new(false, (0, 0))
}
}
}
3 changes: 2 additions & 1 deletion desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ winapi = "0.3.9"
embed-resource = "1"

[features]
default = ["h263"]
default = ["h263", "vp6"]

# core features
avm_debug = ["ruffle_core/avm_debug"]
h263 = ["ruffle_core/h263"]
lzma = ["ruffle_core/lzma"]
vp6 = ["ruffle_core/vp6"]

# wgpu features
render_debug_labels = ["ruffle_render_wgpu/render_debug_labels"]
Expand Down
2 changes: 1 addition & 1 deletion web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ base64 = "0.13.0"
[dependencies.ruffle_core]
path = "../core"
default-features = false
features = ["h263", "serde", "wasm-bindgen"]
features = ["h263", "vp6", "serde", "wasm-bindgen"]

[dependencies.web-sys]
version = "0.3.50"
Expand Down