Skip to content

Commit

Permalink
Add basic C-API
Browse files Browse the repository at this point in the history
  • Loading branch information
quietvoid committed May 29, 2024
1 parent ee5322e commit b1d1de3
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 1 deletion.
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;

0 comments on commit b1d1de3

Please sign in to comment.