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

Add basic C-API #78

Merged
merged 1 commit into from
May 29, 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.

22 changes: 21 additions & 1 deletion hdr10plus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,34 @@ repository = "https://github.com/quietvoid/hdr10plus_tool/tree/master/hdr10plus"

[dependencies]
bitvec_helpers = { version = "3.1.4", default-features = false, features = ["bitstream-io"] }
hevc_parser = { version = "0.6.2", optional = true }

anyhow = "1.0.86"
serde = { version = "1.0.203", features = ["derive"], optional = true }
serde_json = { version = "1.0.117", features = ["preserve_order"], optional = true }
hevc_parser = { version = "0.6.1", optional = true }

libc = { version = "0.2", optional = true }

[features]
hevc = ["hevc_parser"]
json = ["serde", "serde_json"]
capi = ["libc", "json"]

[package.metadata.docs.rs]
all-features = true

[package.metadata.capi.header]
subdirectory = "libhdr10plus-rs"

[package.metadata.capi.pkg_config]
strip_include_path_components = 1
subdirectory = false
name = "hdr10plus-rs"
filename = "hdr10plus-rs"

[package.metadata.capi.library]
rustflags = "-Cpanic=abort"
name = "hdr10plus-rs"

[lib]
doctest = false
15 changes: 15 additions & 0 deletions hdr10plus/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
header = "// SPDX-License-Identifier: MIT"
sys_includes = ["stddef.h", "stdint.h", "stdlib.h", "stdbool.h"]
no_includes = true
include_guard = "HDR10PLUS_RS_H"
tab_width = 4
style = "Type"
language = "C"
cpp_compat = true

[parse]
parse_deps = false

[export]
item_types = ["constants", "enums", "structs", "unions", "typedefs", "opaque", "functions"]
prefix = "Hdr10PlusRs"
29 changes: 29 additions & 0 deletions hdr10plus/examples/capi_json_file.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

#include <libhdr10plus-rs/hdr10plus.h>

int main(void) {
char *path = "../../assets/hevc_tests/regular_metadata.json";
int ret;

Hdr10PlusRsJsonOpaque *hdr10plus_json = hdr10plus_rs_parse_json(path);
const char *error = hdr10plus_rs_json_get_error(hdr10plus_json);
if (error) {
printf("%s\n", error);

hdr10plus_rs_json_free(hdr10plus_json);
return 1;
}

const Hdr10PlusRsData *payload = hdr10plus_rs_write_av1_metadata_obu_t35_complete(hdr10plus_json, 0);
if (payload) {
assert(payload->len == 49);

hdr10plus_rs_data_free(payload);
}

hdr10plus_rs_json_free(hdr10plus_json);
}
42 changes: 42 additions & 0 deletions hdr10plus/src/c_structs/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use std::ffi::CString;

use libc::size_t;

use crate::metadata_json::MetadataJsonRoot;

/// Opaque HDR10+ JSON file handle
///
/// Use `hdr10plus_rs_json_free` to free.
/// It should be freed regardless of whether or not an error occurred.
pub struct JsonOpaque {
/// Optional parsed JSON, present when parsing is successful.
pub metadata_root: Option<MetadataJsonRoot>,

pub error: Option<CString>,
}

/// Struct representing a data buffer
#[repr(C)]
pub struct Data {
/// Pointer to the data buffer
pub data: *const u8,
/// Data buffer size
pub len: size_t,
}

impl Data {
/// # Safety
/// The pointers should all be valid.
pub unsafe fn free(&self) {
Vec::from_raw_parts(self.data as *mut u8, self.len, self.len);
}
}

impl From<Vec<u8>> for Data {
fn from(buf: Vec<u8>) -> Self {
Data {
len: buf.len(),
data: Box::into_raw(buf.into_boxed_slice()) as *const u8,
}
}
}
141 changes: 141 additions & 0 deletions hdr10plus/src/capi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#![deny(missing_docs)]

use anyhow::anyhow;
use libc::{c_char, size_t};
use std::{
ffi::{CStr, CString},
path::PathBuf,
ptr::{null, null_mut},
};

use crate::{
metadata::{Hdr10PlusMetadata, Hdr10PlusMetadataEncOpts},
metadata_json::MetadataJsonRoot,
};

use super::c_structs::*;

/// # Safety
/// The pointer to the data must be valid.
///
/// Parse a HDR10+ JSON file from file path.
/// Adds an error if the parsing fails.
#[no_mangle]
pub unsafe extern "C" fn hdr10plus_rs_parse_json(path: *const c_char) -> *mut JsonOpaque {
if path.is_null() {
return null_mut();
}

let mut opaque = JsonOpaque {
metadata_root: None,
error: None,
};
let mut error = None;

if let Ok(str) = CStr::from_ptr(path).to_str() {
let path = PathBuf::from(str);
match MetadataJsonRoot::from_file(path) {
Ok(metadata) => opaque.metadata_root = Some(metadata),
Err(e) => {
error = Some(format!(
"hdr10plus_rs_parse_json: Errored while parsing: {e}"
));
}
};
} else {
error =
Some("hdr10plus_rs_parse_json: Failed parsing the input path as a string".to_string());
}

if let Some(err) = error {
opaque.error = CString::new(err).ok();
}

Box::into_raw(Box::new(opaque))
}

/// # Safety
/// The pointer to the opaque struct must be valid.
///
/// Get the last logged error for the JsonOpaque operations.
///
/// On invalid parsing, an error is added.
/// The user should manually verify if there is an error, as the parsing does not return an error code.
#[no_mangle]
pub unsafe extern "C" fn hdr10plus_rs_json_get_error(ptr: *const JsonOpaque) -> *const c_char {
if ptr.is_null() {
return null();
}

let opaque = &*ptr;

match &opaque.error {
Some(s) => s.as_ptr(),
None => null(),
}
}

/// # Safety
/// The pointer to the opaque struct must be valid.
///
/// Free the Hdr10PlusJsonOpaque
#[no_mangle]
pub unsafe extern "C" fn hdr10plus_rs_json_free(ptr: *mut JsonOpaque) {
if !ptr.is_null() {
drop(Box::from_raw(ptr));
}
}

/// # Safety
/// The struct pointer must be valid.
///
/// Writes the encoded HDR10+ payload as a byte buffer, including country code
/// If an error occurs in the writing, returns null
#[no_mangle]
pub unsafe extern "C" fn hdr10plus_rs_write_av1_metadata_obu_t35_complete(
ptr: *mut JsonOpaque,
frame_number: size_t,
) -> *const Data {
if ptr.is_null() {
return null();
}

let opaque = &mut *ptr;
let frame_metadata = opaque
.metadata_root
.as_ref()
.and_then(|root| root.scene_info.get(frame_number))
.ok_or(anyhow!("No metadata for frame {frame_number}"))
.and_then(|jm| {
let enc_opts = Hdr10PlusMetadataEncOpts {
with_country_code: true,
..Default::default()
};

Hdr10PlusMetadata::try_from(jm)
.and_then(|metadata| metadata.encode_with_opts(&enc_opts))
});

match frame_metadata {
Ok(buf) => Box::into_raw(Box::new(Data::from(buf))),
Err(e) => {
opaque
.error
.replace(CString::new(format!("Failed writing byte buffer: {e}")).unwrap());

null()
}
}
}

/// # Safety
/// The data pointer should exist, and be allocated by Rust.
///
/// Free a Data buffer
#[no_mangle]
pub unsafe extern "C" fn hdr10plus_rs_data_free(data: *const Data) {
if !data.is_null() {
let data = Box::from_raw(data as *mut Data);
data.free();
}
}
8 changes: 8 additions & 0 deletions hdr10plus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ pub mod metadata_json;

#[cfg(feature = "hevc")]
pub mod hevc;

/// C API module
#[cfg(any(cargo_c, feature = "capi"))]
pub mod capi;

/// Structs used and exposed in the C API
#[cfg(any(cargo_c, feature = "capi"))]
pub mod c_structs;
Loading