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

capi: Expose frame buffer API #94

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ members = [
opt-level = 1

[replace]
"av-metrics:0.5.0" = { path = "av_metrics" }
"av-metrics:0.6.0" = { path = "av_metrics" }
2 changes: 1 addition & 1 deletion av_metrics/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "av-metrics"
version = "0.5.0"
version = "0.6.0"
authors = ["Josh Holmer <[email protected]>"]
edition = "2018"
description = "A collection of algorithms for measuring audio/video metrics"
Expand Down
9 changes: 9 additions & 0 deletions av_metrics/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,14 @@ tab_width = 4
style = "Type"
language = "C"

[parse]
parse_deps = true
include = ['av_metrics', 'v_frame']

[export]
prefix = "AVM"
item_types = ["enums", "structs", "unions", "typedefs", "opaque", "functions"]

[enum]
rename_variants = "ScreamingSnakeCase"
prefix_with_name = true
328 changes: 328 additions & 0 deletions av_metrics/src/capi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@
extern crate libc;

use libc::c_char;
use libc::ptrdiff_t;
use std::ffi::CStr;
use std::fs::File;
use std::os::raw::c_int;
use std::path::{Path, PathBuf};
use std::ptr::null;
use std::slice;

use crate::video as vid;
use crate::video::*;

type ChromaSamplePosition = vid::ChromaSamplePosition;
type ChromaSampling = vid::ChromaSampling;
type Rational = vid::Rational;

#[derive(Debug, Clone, Copy)]
enum InputType {
Video(VideoContainer),
Expand Down Expand Up @@ -436,6 +443,327 @@ pub unsafe extern fn avm_calculate_frame_ciede(
value
}

fn calculate_frame_buf_tmpl<T: Pixel>(
frame1: [&[u8]; 3],
frame1_strides: [ptrdiff_t; 3],
frame2: [&[u8]; 3],
frame2_strides: [ptrdiff_t; 3],
width: u32,
height: u32,
bitdepth: u8,
_chroma_pos: ChromaSamplePosition,
subsampling: ChromaSampling,
_pixel_aspect_ratio: Rational,
metric: &str,
) -> (*const Context, f64) {
let (xdec, ydec) = subsampling.get_decimation().unwrap_or((1, 1));
let planes = if subsampling == ChromaSampling::Cs400 {
1
} else {
3
};
let bw = if bitdepth == 8 { 1 } else { 2 };

let mut fi1 = FrameInfo {
planes: [
Plane::<T>::new(width as usize, height as usize, 0, 0, 0, 0),
Plane::<T>::new(
(width as usize) >> xdec,
(height as usize) >> ydec,
xdec,
ydec,
0,
0,
),
Plane::<T>::new(
(width as usize) >> xdec,
(height as usize) >> ydec,
xdec,
ydec,
0,
0,
),
],
bit_depth: bitdepth as usize,
chroma_sampling: subsampling,
};
let mut fi2 = FrameInfo {
planes: [
Plane::<T>::new(width as usize, height as usize, 0, 0, 0, 0),
Plane::<T>::new(
(width as usize) >> xdec,
(height as usize) >> ydec,
xdec,
ydec,
0,
0,
),
Plane::<T>::new(
(width as usize) >> xdec,
(height as usize) >> ydec,
xdec,
ydec,
0,
0,
),
],
bit_depth: bitdepth as usize,
chroma_sampling: subsampling,
};

for p in 0..planes {
fi1.planes[p].copy_from_raw_u8(frame1[p], frame1_strides[p] as usize, bw);
fi2.planes[p].copy_from_raw_u8(frame2[p], frame2_strides[p] as usize, bw);
}

if metric == "ciede" {
if let Ok(val) = ciede::calculate_frame_ciede(&fi1, &fi2) {
return (null(), val);
}
}

let val = match metric {
"psnr" => psnr::calculate_frame_psnr(&fi1, &fi2),
"psnr_hvs" => psnr_hvs::calculate_frame_psnr_hvs(&fi1, &fi2),
"ssim" => ssim::calculate_frame_ssim(&fi1, &fi2),
"msssim" => ssim::calculate_frame_msssim(&fi1, &fi2),
_ => unimplemented!("unknown metric"),
};

if let Ok(metrics) = val {
let ctx = Context {
y: metrics.y,
u: metrics.u,
v: metrics.v,
avg: metrics.avg,
};
let boxed = Box::new(ctx);
return (Box::into_raw(boxed), 0.0);
}

(null(), -1.0)
}

unsafe fn calculate_frame_buf_internal(
frame1: [*const u8; 3],
frame1_strides: [ptrdiff_t; 3],
frame2: [*const u8; 3],
frame2_strides: [ptrdiff_t; 3],
width: u32,
height: u32,
bitdepth: u8,
chroma_pos: ChromaSamplePosition,
subsampling: ChromaSampling,
pixel_aspect_ratio: Rational,
metric: &str,
) -> (*const Context, f64) {
let (_cw, ch) = subsampling.get_chroma_dimensions(width as usize, height as usize);

let luma_len1 = (frame1_strides[0] as usize) * (height as usize);
let luma_slice1 = slice::from_raw_parts(frame1[0], luma_len1);
let chroma_u_len1 = (frame1_strides[1] as usize) * (ch as usize);
let chroma_u_slice1 = slice::from_raw_parts(frame1[1], chroma_u_len1);
let chroma_v_len1 = (frame1_strides[2] as usize) * (ch as usize);
let chroma_v_slice1 = slice::from_raw_parts(frame1[2], chroma_v_len1);

let luma_len2 = (frame2_strides[0] as usize) * (height as usize);
let luma_slice2 = slice::from_raw_parts(frame2[0], luma_len2);
let chroma_u_len2 = (frame2_strides[1] as usize) * (ch as usize);
let chroma_u_slice2 = slice::from_raw_parts(frame2[1], chroma_u_len2);
let chroma_v_len2 = (frame2_strides[2] as usize) * (ch as usize);
let chroma_v_slice2 = slice::from_raw_parts(frame2[2], chroma_v_len2);

if bitdepth == 8 {
calculate_frame_buf_tmpl::<u8>(
[&luma_slice1, &chroma_u_slice1, &chroma_v_slice1],
frame1_strides,
[&luma_slice2, &chroma_u_slice2, &chroma_v_slice2],
frame2_strides,
width,
height,
bitdepth,
chroma_pos,
subsampling,
pixel_aspect_ratio,
metric,
)
} else {
calculate_frame_buf_tmpl::<u16>(
[&luma_slice1, &chroma_u_slice1, &chroma_v_slice1],
frame1_strides,
[&luma_slice2, &chroma_u_slice2, &chroma_v_slice2],
frame2_strides,
width,
height,
bitdepth,
chroma_pos,
subsampling,
pixel_aspect_ratio,
metric,
)
}
}

/// Calculate the `ciede` metric between two frame buffers
///
/// Returns the correct `ciede` value or `-1` on errors
#[no_mangle]
pub unsafe extern fn avm_calculate_frame_buf_ciede(
frame1: [*const u8; 3],
frame1_strides: [ptrdiff_t; 3],
frame2: [*const u8; 3],
frame2_strides: [ptrdiff_t; 3],
width: u32,
height: u32,
bitdepth: u8,
chroma_pos: ChromaSamplePosition,
subsampling: ChromaSampling,
pixel_aspect_ratio: Rational,
) -> f64 {
let (_ctx, val) = calculate_frame_buf_internal(
frame1,
frame1_strides,
frame2,
frame2_strides,
width,
height,
bitdepth,
chroma_pos,
subsampling,
pixel_aspect_ratio,
"ciede",
);
val
}

/// Calculate the `ssim` metric between two frame buffers
///
/// Returns the correct `ssim` value or `NULL` on errors
#[no_mangle]
pub unsafe extern fn avm_calculate_frame_buf_ssim(
frame1: [*const u8; 3],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What cbindgen produces for those?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It produces exactly the API I wanted:

/**
 * Calculate the `msssim` metric between two frame buffers
 *
 * Returns the correct `msssim` value or `NULL` on errors
 */
const AVMContext *avm_calculate_frame_buf_msssim(const uint8_t *frame1[3],
                                                 ptrdiff_t frame1_strides[3],
                                                 const uint8_t *frame2[3],
                                                 ptrdiff_t frame2_strides[3],
                                                 uint32_t width,
                                                 uint32_t height,
                                                 uint8_t bitdepth,
                                                 AVMChromaSamplePosition chroma_pos,
                                                 AVMChromaSampling subsampling,
                                                 AVMRational pixel_aspect_ratio);

But it doesn't matter, since Rust can't generate a confirming C ABI for it, as discussed on IRC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bug in cbindgen. ptrdiff_t frame2_strides[3] is *mut frame2_strides.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the correct way to write the rust declaration to generate ptrdiff_t frame2_strides[3]? And should we file a cbindgen bug?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me poke upstream to rustc confirm, but from what you are telling me it seems so and a good idea to tell cbindgen upstream.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

improper_ctypes is on by default only in extern blocks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

improper_ctypes is on by default only in extern blocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean... not's not really a good answer. Why wouldn't it be on for all extern "C" functions? It makes it almost useless.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I had been told today :) I hadn't tested it yet btw.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

upstream cbindgen would welcome a patch to prevent this and one to add annotations to use the style you'd use here.

frame1_strides: [ptrdiff_t; 3],
frame2: [*const u8; 3],
frame2_strides: [ptrdiff_t; 3],
width: u32,
height: u32,
bitdepth: u8,
chroma_pos: ChromaSamplePosition,
subsampling: ChromaSampling,
pixel_aspect_ratio: Rational,
) -> *const Context {
let (ctx, _val) = calculate_frame_buf_internal(
frame1,
frame1_strides,
frame2,
frame2_strides,
width,
height,
bitdepth,
chroma_pos,
subsampling,
pixel_aspect_ratio,
"ssim",
);
ctx
}

/// Calculate the `msssim` metric between two frame buffers
///
/// Returns the correct `msssim` value or `NULL` on errors
#[no_mangle]
pub unsafe extern fn avm_calculate_frame_buf_msssim(
frame1: [*const u8; 3],
frame1_strides: [ptrdiff_t; 3],
frame2: [*const u8; 3],
frame2_strides: [ptrdiff_t; 3],
width: u32,
height: u32,
bitdepth: u8,
chroma_pos: ChromaSamplePosition,
subsampling: ChromaSampling,
pixel_aspect_ratio: Rational,
) -> *const Context {
let (ctx, _val) = calculate_frame_buf_internal(
frame1,
frame1_strides,
frame2,
frame2_strides,
width,
height,
bitdepth,
chroma_pos,
subsampling,
pixel_aspect_ratio,
"msssim",
);
ctx
}

/// Calculate the `psnr` metric between two frame buffers
///
/// Returns the correct `psnr` value or `NULL` on errors
#[no_mangle]
pub unsafe extern fn avm_calculate_frame_buf_psnr(
frame1: [*const u8; 3],
frame1_strides: [ptrdiff_t; 3],
frame2: [*const u8; 3],
frame2_strides: [ptrdiff_t; 3],
width: u32,
height: u32,
bitdepth: u8,
chroma_pos: ChromaSamplePosition,
subsampling: ChromaSampling,
pixel_aspect_ratio: Rational,
) -> *const Context {
let (ctx, _val) = calculate_frame_buf_internal(
frame1,
frame1_strides,
frame2,
frame2_strides,
width,
height,
bitdepth,
chroma_pos,
subsampling,
pixel_aspect_ratio,
"psnr",
);
ctx
}

/// Calculate the `psnr_hvs` metric between two frame buffers
///
/// Returns the correct `psnr_hvs` value or `NULL` on errors
#[no_mangle]
pub unsafe extern fn avm_calculate_frame_buf_psnr_hvs(
frame1: [*const u8; 3],
frame1_strides: [ptrdiff_t; 3],
frame2: [*const u8; 3],
frame2_strides: [ptrdiff_t; 3],
width: u32,
height: u32,
bitdepth: u8,
chroma_pos: ChromaSamplePosition,
subsampling: ChromaSampling,
pixel_aspect_ratio: Rational,
) -> *const Context {
let (ctx, _val) = calculate_frame_buf_internal(
frame1,
frame1_strides,
frame2,
frame2_strides,
width,
height,
bitdepth,
chroma_pos,
subsampling,
pixel_aspect_ratio,
"psnr_hvs",
);
ctx
}

/// Drop the metric context
///
/// This function drops the context and free the memory
Expand Down
1 change: 1 addition & 0 deletions av_metrics/src/video/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ impl ChromaWeight for ChromaSampling {

/// Sample position for subsampled chroma
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C)]
pub enum ChromaSamplePosition {
/// The source video transfer function is not signaled. This crate will assume
/// no transformation needs to be done on this data, but there is a risk of metric
Expand Down