Skip to content

Commit bed68bd

Browse files
committed
rust v2.0 changes
1 parent caf42d7 commit bed68bd

File tree

10 files changed

+154
-58
lines changed

10 files changed

+154
-58
lines changed

.github/workflows/rust-codestyle.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ jobs:
6464
profile: minimal
6565
toolchain: stable
6666
override: true
67+
68+
- name: Rust build binding
69+
run: bash copy.sh && cargo build --verbose
70+
working-directory: binding/rust
6771

6872
- name: Run clippy
6973
run: cargo clippy -- -D warnings
@@ -85,6 +89,10 @@ jobs:
8589
profile: minimal
8690
toolchain: stable
8791
override: true
92+
93+
- name: Rust build binding
94+
run: bash copy.sh && cargo build --verbose
95+
working-directory: binding/rust
8896

8997
- name: Run clippy
9098
run: cargo clippy -- -D warnings

.github/workflows/rust-demos.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ jobs:
4040
profile: minimal
4141
toolchain: stable
4242
override: true
43+
44+
- name: Rust build binding
45+
run: bash copy.sh && cargo build --verbose
46+
working-directory: binding/rust
4347

4448
- name: Rust build micdemo
4549
run: cargo build --verbose
@@ -72,6 +76,10 @@ jobs:
7276
with:
7377
toolchain: nightly
7478
override: true
79+
80+
- name: Rust build binding
81+
run: bash copy.sh && cargo build --verbose
82+
working-directory: binding/rust
7583

7684
- name: Rust build micdemo
7785
run: cargo build --verbose

binding/rust/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

binding/rust/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pv_cobra"
3-
version = "1.2.1"
3+
version = "2.0.0"
44
edition = "2018"
55
description = "The Rust bindings for Picovoice's Cobra library"
66
license = "Apache-2.0"

binding/rust/src/cobra.rs

Lines changed: 112 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ type PvCobraVersionFn = unsafe extern "C" fn() -> *mut c_char;
5454
type PvCobraProcessFn =
5555
unsafe extern "C" fn(object: *mut CCobra, pcm: *const i16, is_voiced: *mut f32) -> PvStatus;
5656
type PvCobraDeleteFn = unsafe extern "C" fn(object: *mut CCobra);
57+
type PvGetErrorStackFn =
58+
unsafe extern "C" fn(message_stack: *mut *mut *mut c_char, message_stack_depth: *mut i32);
59+
type PvFreeErrorStackFn = unsafe extern "C" fn(message_stack: *mut *mut c_char);
60+
type PvSetSdkFn = unsafe extern "C" fn(sdk: *const c_char);
5761

5862
#[derive(Clone, Debug)]
5963
pub enum CobraErrorStatus {
@@ -66,24 +70,44 @@ pub enum CobraErrorStatus {
6670
#[derive(Clone, Debug)]
6771
pub struct CobraError {
6872
pub status: CobraErrorStatus,
69-
pub message: Option<String>,
73+
pub message: String,
74+
pub message_stack: Vec<String>,
7075
}
7176

7277
impl CobraError {
7378
pub fn new(status: CobraErrorStatus, message: impl Into<String>) -> Self {
7479
Self {
7580
status,
76-
message: Some(message.into()),
81+
message: message.into(),
82+
message_stack: Vec::new()
83+
}
84+
}
85+
86+
pub fn new_with_stack(
87+
status: CobraErrorStatus,
88+
message: impl Into<String>,
89+
message_stack: impl Into<Vec<String>>
90+
) -> Self {
91+
Self {
92+
status,
93+
message: message.into(),
94+
message_stack: message_stack.into(),
7795
}
7896
}
7997
}
8098

8199
impl std::fmt::Display for CobraError {
82-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83-
match &self.message {
84-
Some(message) => write!(f, "{}: {:?}", message, self.status),
85-
None => write!(f, "Cobra error: {:?}", self.status),
100+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
101+
let mut message_string = String::new();
102+
message_string.push_str(&format!("{} with status '{:?}'", self.message, self.status));
103+
104+
if !self.message_stack.is_empty() {
105+
message_string.push(':');
106+
for x in 0..self.message_stack.len() {
107+
message_string.push_str(&format!(" [{}] {}\n", x, self.message_stack[x]))
108+
};
86109
}
110+
write!(f, "{}", message_string)
87111
}
88112
}
89113

@@ -145,44 +169,83 @@ unsafe fn load_library_fn<T>(
145169
})
146170
}
147171

148-
fn check_fn_call_status(status: PvStatus, function_name: &str) -> Result<(), CobraError> {
172+
fn check_fn_call_status(
173+
vtable: &CobraInnerVTable,
174+
status: PvStatus,
175+
function_name: &str
176+
) -> Result<(), CobraError> {
149177
match status {
150178
PvStatus::SUCCESS => Ok(()),
151-
_ => Err(CobraError::new(
152-
CobraErrorStatus::LibraryError(status),
153-
format!("Function '{}' in the cobra library failed", function_name),
154-
)),
179+
_ => unsafe {
180+
let mut message_stack_ptr: *mut c_char = std::ptr::null_mut();
181+
let mut message_stack_ptr_ptr = addr_of_mut!(message_stack_ptr);
182+
183+
let mut message_stack_depth: i32 = 0;
184+
(vtable.pv_get_error_stack)(
185+
addr_of_mut!(message_stack_ptr_ptr),
186+
addr_of_mut!(message_stack_depth),
187+
);
188+
189+
let mut message_stack = Vec::new();
190+
for i in 0..message_stack_depth as usize {
191+
let message = CStr::from_ptr(*message_stack_ptr_ptr.add(i));
192+
let message = message.to_string_lossy().into_owned();
193+
message_stack.push(message);
194+
}
195+
196+
(vtable.pv_free_error_stack)(message_stack_ptr_ptr);
197+
198+
Err(CobraError::new_with_stack(
199+
CobraErrorStatus::LibraryError(status),
200+
format!("'{function_name}' failed"),
201+
message_stack,
202+
))
203+
},
155204
}
156205
}
157206

158207
struct CobraInnerVTable {
208+
pv_cobra_init: RawSymbol<PvCobraInitFn>,
159209
pv_cobra_process: RawSymbol<PvCobraProcessFn>,
160210
pv_cobra_delete: RawSymbol<PvCobraDeleteFn>,
211+
pv_sample_rate: RawSymbol<PvSampleRateFn>,
212+
pv_cobra_frame_length: RawSymbol<PvCobraFrameLengthFn>,
213+
pv_cobra_version: RawSymbol<PvCobraVersionFn>,
214+
pv_get_error_stack: RawSymbol<PvGetErrorStackFn>,
215+
pv_free_error_stack: RawSymbol<PvFreeErrorStackFn>,
216+
pv_set_sdk: RawSymbol<PvSetSdkFn>,
161217

162218
_lib_guard: Library,
163219
}
164220

165-
struct CobraInner {
166-
ccobra: *mut CCobra,
167-
frame_length: i32,
168-
sample_rate: i32,
169-
version: String,
170-
vtable: CobraInnerVTable,
171-
}
172-
173221
impl CobraInnerVTable {
174222
pub fn new(lib: Library) -> Result<Self, CobraError> {
175223
unsafe {
176224
Ok(Self {
225+
pv_cobra_init: load_library_fn(&lib, b"pv_cobra_init")?,
177226
pv_cobra_process: load_library_fn::<PvCobraProcessFn>(&lib, b"pv_cobra_process")?,
178227
pv_cobra_delete: load_library_fn::<PvCobraDeleteFn>(&lib, b"pv_cobra_delete")?,
228+
pv_sample_rate: load_library_fn(&lib, b"pv_sample_rate")?,
229+
pv_cobra_frame_length: load_library_fn(&lib, b"pv_cobra_frame_length")?,
230+
pv_cobra_version: load_library_fn(&lib, b"pv_cobra_version")?,
231+
pv_get_error_stack: load_library_fn(&lib, b"pv_get_error_stack")?,
232+
pv_free_error_stack: load_library_fn(&lib, b"pv_free_error_stack")?,
233+
pv_set_sdk: load_library_fn(&lib, b"pv_set_sdk")?,
179234

180235
_lib_guard: lib,
181236
})
182237
}
183238
}
184239
}
185240

241+
struct CobraInner {
242+
ccobra: *mut CCobra,
243+
frame_length: i32,
244+
sample_rate: i32,
245+
version: String,
246+
vtable: CobraInnerVTable,
247+
}
248+
186249
impl CobraInner {
187250
pub fn init<S: Into<String>, P: Into<PathBuf>>(access_key: S, library_path: P) -> Result<Self, CobraError> {
188251

@@ -212,6 +275,17 @@ impl CobraInner {
212275
format!("Failed to load cobra dynamic library: {}", err),
213276
)
214277
})?;
278+
let vtable = CobraInnerVTable::new(lib)?;
279+
280+
let sdk_string = match CString::new("rust") {
281+
Ok(sdk_string) => sdk_string,
282+
Err(err) => {
283+
return Err(CobraError::new(
284+
CobraErrorStatus::ArgumentError,
285+
format!("sdk_string is not a valid C string {err}"),
286+
))
287+
}
288+
};
215289

216290
let pv_access_key = CString::new(access_key).map_err(|err| {
217291
CobraError::new(
@@ -220,37 +294,27 @@ impl CobraInner {
220294
)
221295
})?;
222296

223-
let (ccobra, sample_rate, frame_length, version) = unsafe {
224-
let pv_cobra_init = load_library_fn::<PvCobraInitFn>(&lib, b"pv_cobra_init")?;
225-
let pv_cobra_version = load_library_fn::<PvCobraVersionFn>(&lib, b"pv_cobra_version")?;
226-
let pv_sample_rate = load_library_fn::<PvSampleRateFn>(&lib, b"pv_sample_rate")?;
227-
let pv_cobra_frame_length =
228-
load_library_fn::<PvCobraFrameLengthFn>(&lib, b"pv_cobra_frame_length")?;
297+
let mut ccobra = std::ptr::null_mut();
229298

230-
let mut ccobra = std::ptr::null_mut();
299+
// SAFETY: most of the unsafe comes from the `load_library_fn` which is
300+
// safe, because we don't use the raw symbols after this function
301+
// anymore.
302+
let (sample_rate, frame_length, version) = unsafe {
303+
(vtable.pv_set_sdk)(sdk_string.as_ptr());
231304

232-
check_fn_call_status(
233-
pv_cobra_init(
234-
pv_access_key.as_ptr(),
235-
addr_of_mut!(ccobra),
236-
),
237-
"pv_cobra_init",
238-
)?;
239-
240-
let version = match CStr::from_ptr(pv_cobra_version()).to_str() {
241-
Ok(string) => string.to_string(),
242-
Err(err) => {
243-
return Err(CobraError::new(
244-
CobraErrorStatus::LibraryLoadError,
245-
format!("Failed to get version info from Cobra Library: {}", err),
246-
))
247-
}
248-
};
305+
let status = (vtable.pv_cobra_init)(
306+
pv_access_key.as_ptr(),
307+
addr_of_mut!(ccobra),
308+
);
309+
check_fn_call_status(&vtable, status, "pv_cobra_init")?;
310+
311+
let version = CStr::from_ptr((vtable.pv_cobra_version)())
312+
.to_string_lossy()
313+
.into_owned();
249314

250315
(
251-
ccobra,
252-
pv_sample_rate(),
253-
pv_cobra_frame_length(),
316+
(vtable.pv_sample_rate)(),
317+
(vtable.pv_cobra_frame_length)(),
254318
version,
255319
)
256320
};
@@ -260,7 +324,7 @@ impl CobraInner {
260324
sample_rate,
261325
frame_length,
262326
version,
263-
vtable: CobraInnerVTable::new(lib)?,
327+
vtable,
264328
})
265329
}
266330

@@ -280,7 +344,7 @@ impl CobraInner {
280344
let status = unsafe {
281345
(self.vtable.pv_cobra_process)(self.ccobra, pcm.as_ptr(), addr_of_mut!(result))
282346
};
283-
check_fn_call_status(status, "pv_cobra_process")?;
347+
check_fn_call_status(&self.vtable, status, "pv_cobra_process")?;
284348

285349
Ok(result)
286350
}

binding/rust/tests/cobra_tests.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,26 @@ mod tests {
5959
assert!(loss.abs() < 0.1);
6060
}
6161

62+
#[test]
63+
fn test_error_stack() {
64+
let mut error_stack = Vec::new();
65+
66+
let res = Cobra::new("invalid").init();
67+
if let Err(err) = res {
68+
error_stack = err.message_stack
69+
}
70+
71+
assert!(0 < error_stack.len() && error_stack.len() <= 8);
72+
73+
let res = Cobra::new("invalid").init();
74+
if let Err(err) = res {
75+
assert_eq!(error_stack.len(), err.message_stack.len());
76+
for i in 0..error_stack.len() {
77+
assert_eq!(error_stack[i], err.message_stack[i])
78+
}
79+
}
80+
}
81+
6282
#[test]
6383
fn test_version() {
6484
let access_key = env::var("PV_ACCESS_KEY")

demo/rust/filedemo/Cargo.lock

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/rust/filedemo/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ chrono = "0.4.23"
88
clap = "2.33.3"
99
hound = "3.5.0"
1010
itertools = "0.10.1"
11-
pv_cobra = "=1.2.1"
11+
pv_cobra = { path = "../../../binding/rust" }

demo/rust/micdemo/Cargo.lock

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/rust/micdemo/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ clap = "2.33.3"
99
ctrlc = "3.1.9"
1010
hound = "3.5.0"
1111
itertools = "0.10.1"
12-
pv_cobra = "=1.2.1"
12+
pv_cobra = { path = "../../../binding/rust" }
1313
pv_recorder = "=1.2.1"

0 commit comments

Comments
 (0)