Skip to content

Commit 20eeba2

Browse files
committed
Add example for usage from C/C++
1 parent 648c697 commit 20eeba2

9 files changed

Lines changed: 359 additions & 1 deletion

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ hound = "3.5.1"
1818
clap = "4.5.30"
1919

2020
[lib]
21-
crate-type = ["cdylib", "rlib"]
21+
crate-type = ["cdylib", "staticlib", "rlib"]
2222

2323
[features]
2424
default = ["wasm-api"]

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ Options:
4848
Print help
4949
```
5050

51+
### C API
52+
53+
A C API is available for integrating SEA into C/C++ projects.
54+
See the [C API README](examples/c_api/README.md) for usage details and build instructions.
55+
5156
# SEA file specification
5257

5358
A SEA file consists of a file header followed by a series of chunks. Samples are stored as 16-bit signed integers in interleaved format. All values are stored in little-endian order.

examples/c_api/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build

examples/c_api/CMakeLists.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
project(sea_codec_c_example)
3+
4+
set(CMAKE_C_STANDARD 99)
5+
6+
# Include directories
7+
include_directories(../../include)
8+
9+
# Find the library
10+
if(CMAKE_BUILD_TYPE STREQUAL "Release")
11+
set(LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../target/release")
12+
else()
13+
set(LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../target/debug")
14+
endif()
15+
16+
message(STATUS "Looking for library in ${LIB_DIR}")
17+
18+
find_library(SEA_LIB NAMES sea_codec libsea_codec sea_codec.dll PATHS ${LIB_DIR} NO_DEFAULT_PATH)
19+
20+
if(NOT SEA_LIB)
21+
message(WARNING "sea-codec library not found in ${LIB_DIR}. Please build the rust project first (cargo build).")
22+
endif()
23+
24+
add_executable(c_example main.c)
25+
26+
if(SEA_LIB)
27+
target_link_libraries(c_example ${SEA_LIB})
28+
if(UNIX)
29+
target_link_libraries(c_example m pthread dl)
30+
endif()
31+
endif()

examples/c_api/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# C API Example for sea-codec
2+
3+
This directory contains an example of how to use the C API of `sea-codec` from a C application.
4+
5+
## Prerequisites
6+
7+
- Rust (cargo)
8+
- CMake
9+
- A C compiler (GCC, Clang, MSVC)
10+
11+
## Structure
12+
13+
- `main.c`: A simple C program that generates a sine wave, encodes it using `sea-codec`, and decodes it back.
14+
- `CMakeLists.txt`: A CMake build file demonstrating how to link against the `sea-codec` library.
15+
16+
## Building and Running
17+
18+
1. **Build the Rust library:**
19+
20+
From the root of the repository:
21+
22+
```bash
23+
cargo build --release
24+
```
25+
26+
This will generate the static and dynamic libraries in `target/release`.
27+
28+
2. **Build the C example:**
29+
30+
```bash
31+
cd examples/c_api
32+
mkdir build
33+
cd build
34+
cmake -DCMAKE_BUILD_TYPE=Release ..
35+
cmake --build .
36+
```
37+
38+
3. **Run the example:**
39+
40+
On Linux/macOS:
41+
```bash
42+
./c_example
43+
```
44+
45+
On Windows:
46+
```bash
47+
.\Release\c_example.exe
48+
```
49+
(Note: If linking dynamically, ensure `sea_codec.dll` is in the same directory or in PATH).
50+
51+
## Header File
52+
53+
The C header file is located at `include/sea_codec.h`. You should include this in your C/C++ projects.
54+
55+
## Cross-compilation
56+
57+
To build a static library for a different platform (e.g., ARM Cortex-M3), use `rustup` to add the target and `cargo` to build:
58+
59+
```sh
60+
rustup target add thumbv7m-none-eabi
61+
cargo build --release --target thumbv7m-none-eabi
62+
```

examples/c_api/main.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#include "../../include/sea_codec.h"
2+
#include <math.h>
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
6+
#ifndef M_PI
7+
#define M_PI 3.14159265358979323846
8+
#endif
9+
10+
void generate_sine_wave(int16_t* buffer, size_t length, uint32_t sample_rate, float frequency)
11+
{
12+
for (size_t i = 0; i < length; i++) {
13+
float t = (float)i / sample_rate;
14+
buffer[i] = (int16_t)(sinf(2.0f * (float)M_PI * frequency * t) * 32000.0f);
15+
}
16+
}
17+
18+
int main()
19+
{
20+
uint32_t sample_rate = 44100;
21+
uint32_t channels = 1;
22+
float duration = 1.0f;
23+
size_t num_samples = (size_t)(sample_rate * duration);
24+
25+
printf("Generating %zu samples of sine wave...\n", num_samples);
26+
int16_t* input_samples = (int16_t*)malloc(num_samples * sizeof(int16_t));
27+
if (!input_samples) {
28+
fprintf(stderr, "Memory allocation failed\n");
29+
return 1;
30+
}
31+
32+
generate_sine_wave(input_samples, num_samples, sample_rate, 440.0f);
33+
34+
printf("Encoding...\n");
35+
CSeaEncoderSettings settings = sea_encoder_default_settings();
36+
uint8_t* encoded_data = NULL;
37+
size_t encoded_length = 0;
38+
39+
int result = sea_encode(input_samples, num_samples, sample_rate, channels, &settings, &encoded_data, &encoded_length);
40+
41+
if (result != 0) {
42+
fprintf(stderr, "Encoding failed\n");
43+
free(input_samples);
44+
return 1;
45+
}
46+
47+
printf("Encoded size: %zu bytes\n", encoded_length);
48+
49+
printf("Decoding...\n");
50+
int16_t* decoded_samples = NULL;
51+
size_t decoded_sample_count = 0;
52+
uint32_t decoded_sample_rate = 0;
53+
uint32_t decoded_channels = 0;
54+
55+
result = sea_decode(encoded_data, encoded_length, &decoded_samples, &decoded_sample_count, &decoded_sample_rate, &decoded_channels);
56+
57+
if (result != 0) {
58+
fprintf(stderr, "Decoding failed\n");
59+
sea_free_packet(encoded_data, encoded_length);
60+
free(input_samples);
61+
return 1;
62+
}
63+
64+
printf("Decoded info: %zu samples, %u Hz, %u channels\n", decoded_sample_count, decoded_sample_rate, decoded_channels);
65+
66+
if (decoded_sample_count == num_samples) {
67+
printf("Sample count matches!\n");
68+
} else {
69+
printf("Sample count mismatch! Expected %zu, got %zu\n", num_samples, decoded_sample_count);
70+
}
71+
72+
sea_free_packet(encoded_data, encoded_length);
73+
sea_free_samples(decoded_samples, decoded_sample_count);
74+
free(input_samples);
75+
76+
return 0;
77+
}

include/sea_codec.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#ifndef SEA_CODEC_H
2+
#define SEA_CODEC_H
3+
4+
#include <stdint.h>
5+
#include <stddef.h>
6+
#include <stdbool.h>
7+
8+
#ifdef __cplusplus
9+
extern "C" {
10+
#endif
11+
12+
typedef struct {
13+
uint8_t scale_factor_bits;
14+
uint8_t scale_factor_frames;
15+
float residual_bits;
16+
uint16_t frames_per_chunk;
17+
bool vbr;
18+
} CSeaEncoderSettings;
19+
20+
// Helper to get default settings
21+
CSeaEncoderSettings sea_encoder_default_settings();
22+
23+
// Encode
24+
// Returns 0 on success, non-zero on error.
25+
// output_data is allocated by the function and must be freed by sea_free_packet.
26+
// input_length is the number of samples (total, across all channels)
27+
int sea_encode(
28+
const int16_t* input_samples,
29+
size_t input_length,
30+
uint32_t sample_rate,
31+
uint32_t channels,
32+
const CSeaEncoderSettings* settings,
33+
uint8_t** output_data,
34+
size_t* output_length
35+
);
36+
37+
// Decode
38+
// output_samples is allocated by the function and must be freed by sea_free_samples.
39+
int sea_decode(
40+
const uint8_t* encoded_data,
41+
size_t encoded_length,
42+
int16_t** output_samples,
43+
size_t* output_sample_count,
44+
uint32_t* output_sample_rate,
45+
uint32_t* output_channels
46+
);
47+
48+
// Memory management
49+
// length must match the size returned by the allocate functions
50+
void sea_free_packet(uint8_t* data, size_t length);
51+
void sea_free_samples(int16_t* samples, size_t length);
52+
53+
#ifdef __cplusplus
54+
}
55+
#endif
56+
57+
#endif // SEA_CODEC_H

src/c_api.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use crate::{
2+
encoder::EncoderSettings, sea_decode as rust_sea_decode, sea_encode as rust_sea_encode,
3+
};
4+
use alloc::vec::Vec;
5+
use core::ffi::c_float;
6+
use core::slice;
7+
8+
#[repr(C)]
9+
pub struct CSeaEncoderSettings {
10+
pub scale_factor_bits: u8,
11+
pub scale_factor_frames: u8,
12+
pub residual_bits: c_float,
13+
pub frames_per_chunk: u16,
14+
pub vbr: bool,
15+
}
16+
17+
impl From<&CSeaEncoderSettings> for EncoderSettings {
18+
fn from(c_settings: &CSeaEncoderSettings) -> Self {
19+
Self {
20+
scale_factor_bits: c_settings.scale_factor_bits,
21+
scale_factor_frames: c_settings.scale_factor_frames,
22+
residual_bits: c_settings.residual_bits,
23+
frames_per_chunk: c_settings.frames_per_chunk,
24+
vbr: c_settings.vbr,
25+
}
26+
}
27+
}
28+
29+
#[no_mangle]
30+
pub extern "C" fn sea_encoder_default_settings() -> CSeaEncoderSettings {
31+
let default = EncoderSettings::default();
32+
CSeaEncoderSettings {
33+
scale_factor_bits: default.scale_factor_bits,
34+
scale_factor_frames: default.scale_factor_frames,
35+
residual_bits: default.residual_bits,
36+
frames_per_chunk: default.frames_per_chunk,
37+
vbr: default.vbr,
38+
}
39+
}
40+
41+
#[no_mangle]
42+
pub unsafe extern "C" fn sea_encode(
43+
input_samples: *const i16,
44+
input_length: usize,
45+
sample_rate: u32,
46+
channels: u32,
47+
settings: *const CSeaEncoderSettings,
48+
output_data: *mut *mut u8,
49+
output_length: *mut usize,
50+
) -> i32 {
51+
if input_samples.is_null() || output_data.is_null() || output_length.is_null() {
52+
return -1;
53+
}
54+
55+
let input_slice = slice::from_raw_parts(input_samples, input_length);
56+
let encoder_settings = if settings.is_null() {
57+
EncoderSettings::default()
58+
} else {
59+
EncoderSettings::from(&*settings)
60+
};
61+
62+
let mut encoded = rust_sea_encode(input_slice, sample_rate, channels, encoder_settings);
63+
64+
encoded.shrink_to_fit();
65+
let ptr = encoded.as_mut_ptr();
66+
let len = encoded.len();
67+
core::mem::forget(encoded);
68+
69+
*output_data = ptr;
70+
*output_length = len;
71+
72+
0
73+
}
74+
75+
#[no_mangle]
76+
pub unsafe extern "C" fn sea_decode(
77+
encoded_data: *const u8,
78+
encoded_length: usize,
79+
output_samples: *mut *mut i16,
80+
output_sample_count: *mut usize,
81+
output_sample_rate: *mut u32,
82+
output_channels: *mut u32,
83+
) -> i32 {
84+
if encoded_data.is_null() || output_samples.is_null() || output_sample_count.is_null() {
85+
return -1;
86+
}
87+
88+
let encoded_slice = slice::from_raw_parts(encoded_data, encoded_length);
89+
90+
let decode_info = rust_sea_decode(encoded_slice);
91+
92+
let mut samples = decode_info.samples;
93+
samples.shrink_to_fit();
94+
let ptr = samples.as_mut_ptr();
95+
let len = samples.len();
96+
core::mem::forget(samples);
97+
98+
*output_samples = ptr;
99+
*output_sample_count = len;
100+
101+
if !output_sample_rate.is_null() {
102+
*output_sample_rate = decode_info.sample_rate;
103+
}
104+
if !output_channels.is_null() {
105+
*output_channels = decode_info.channels;
106+
}
107+
108+
0
109+
}
110+
111+
#[no_mangle]
112+
pub unsafe extern "C" fn sea_free_packet(data: *mut u8, length: usize) {
113+
if !data.is_null() {
114+
let _ = Vec::from_raw_parts(data, length, length);
115+
}
116+
}
117+
118+
#[no_mangle]
119+
pub unsafe extern "C" fn sea_free_samples(samples: *mut i16, length: usize) {
120+
if !samples.is_null() {
121+
let _ = Vec::from_raw_parts(samples, length, length);
122+
}
123+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub mod encoder;
1313
#[cfg(all(target_arch = "wasm32", feature = "wasm-api"))]
1414
pub mod wasm_api;
1515

16+
pub mod c_api;
17+
1618
pub fn sea_encode(
1719
input_samples: &[i16],
1820
sample_rate: u32,

0 commit comments

Comments
 (0)