Skip to content

Commit

Permalink
Log before JACK library calls commence. (#208)
Browse files Browse the repository at this point in the history
* Enable jack logging sooner.

- Make `log` crate an optional dependency.
- Write documentation for available features.

* Bump version.
  • Loading branch information
wmedrano authored Sep 12, 2024
1 parent 23ac4a5 commit 4d9c4f8
Show file tree
Hide file tree
Showing 15 changed files with 322 additions and 117 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/linting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt update && sudo apt install jackd libjack0 libjack-dev
run: sudo apt update && sudo apt install jackd2 libjack-jackd2-0 libjack-jackd2-dev
- name: Lint (No Features)
run: cargo clippy --all-targets --no-default-features -- -D clippy::all
run: cargo clippy --all-targets -- -D clippy::all
- name: Lint (metadata)
run: cargo clippy --all-targets --no-default-features --features metadata -- -D clippy::all
- name: Cargo Fmt
Expand Down
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ license = "MIT"
name = "jack"
readme = "README.md"
repository = "https://github.com/RustAudio/rust-jack"
version = "0.12.1"
version = "0.12.2"

[dependencies]
bitflags = "2"
jack-sys = {version = "0.5", path = "./jack-sys"}
lazy_static = "1.4"
libc = "0.2"
log = "0.4"
log = { version = "0.4", optional = true}

[dev-dependencies]
crossbeam-channel = "0.5"

[features]
default = ["dynamic_loading"]
metadata = []
dynamic_loading = ["jack-sys/dynamic_loading"]
default = ["dynamic_loading", "log"]
dynamic_loading = ["jack-sys/dynamic_loading"]
metadata = []
46 changes: 46 additions & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
layout: page
title: Features
permalink: /features
nav_order: 1
---

# Features

The Rust features for the `jack` crate are defined in
<https://github.com/RustAudio/rust-jack/blob/main/Cargo.toml>. To see the
documentation for Rust features in general, see the [Rust
Book](https://doc.rust-lang.org/cargo/reference/features.html).

## Disabling Default Features

The `jack` crate ships with a reasonable set of default features. To enable just
a subset of features, set `default-features` to false and select only the
desired features.

```toml
jack = { version = "..", default-features = false, features = ["log"] }
```

## `log`

Default: Yes

If the [`log` crate](https://crates.io/crates/log) should be used to handle JACK
logging. Requires setting up a logging implementation to make messages
available.

## `dynamic_loading`

Default: Yes

Load `libjack` at runtime as opposed to the standard dynamic linking. This is
preferred as it allows `pw-jack` to intercept the loading at runtime to provide
the Pipewire JACK server implementation.

## `metadata`

Default: No

Provides access to the metadata API. This is experimental. Details on the JACK
metadata API can be found at <https://jackaudio.org/metadata/>.
49 changes: 49 additions & 0 deletions docs/logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
layout: page
title: Logging
permalink: /logging
nav_order: 2
---

# Logging

JACK can communicate info and error messages. By default, the [log
crate](https://github.com/rust-lang/log) is hooked up to output
messages. However, other logging methods can be used with the
[`set_logger`](https://docs.rs/jack/latest/jack/fn.set_logger.html) function.

## No Logging

Logging from `jack` can be disabled entirely by setting the logger to `None`.

```rust
jack::set_logger(jack::LoggerType::None);
```

## Log Crate (default)

The log crate is the default logger if the `log` feature is enabled, which is
enabled by default. The `log` crate provides a *facade* for logging; it provides
macros to perform logging, but another mechanism or crate is required to
actually perform the logging.

In the example below, we use the [`env_logger` crate]() to display logging for
info and error severity level messages.

```rust
env_logger::builder().filter(None, log::LevelFilter::Info).init();

// JACK may log things to `info!` or `error!`.
let (client, _status) =
jack::Client::new("rust_jack_simple", jack::ClientOptions::NO_START_SERVER).unwrap();
```


## Stdio

If the `log` feature is not enabled, then `jack` will log info messages to
`stdout` and error messages to `stderr`. These usually show up in the terminal.

```rust
jack::set_logger(jack::LoggerType::Stdio);
```
1 change: 1 addition & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
layout: page
title: Quickstart
permalink: /
nav_order: 0
---

# Quickstart
Expand Down
17 changes: 13 additions & 4 deletions examples/playback_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@ use std::io;

fn main() {
// Create client
jack::set_logger(jack::LoggerType::Stdio);
let (client, _status) =
jack::Client::new("rust_jack_simple", jack::ClientOptions::NO_START_SERVER).unwrap();

// Register ports. They will be used in a callback that will be
// called when new data is available.
let in_a = client.register_port("rust_in_l", jack::AudioIn).unwrap();
let in_b = client.register_port("rust_in_r", jack::AudioIn).unwrap();
let mut out_a = client.register_port("rust_out_l", jack::AudioOut).unwrap();
let mut out_b = client.register_port("rust_out_r", jack::AudioOut).unwrap();
let in_a = client
.register_port("rust_in_l", jack::AudioIn::default())
.unwrap();
let in_b = client
.register_port("rust_in_r", jack::AudioIn::default())
.unwrap();
let mut out_a = client
.register_port("rust_out_l", jack::AudioOut::default())
.unwrap();
let mut out_b = client
.register_port("rust_out_r", jack::AudioOut::default())
.unwrap();
let process_callback = move |_: &jack::Client, ps: &jack::ProcessScope| -> jack::Control {
let out_a_p = out_a.as_mut_slice(ps);
let out_b_p = out_b.as_mut_slice(ps);
Expand Down
4 changes: 3 additions & 1 deletion examples/sine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ fn main() {
jack::Client::new("rust_jack_sine", jack::ClientOptions::NO_START_SERVER).unwrap();

// 2. register port
let mut out_port = client.register_port("sine_out", jack::AudioOut).unwrap();
let mut out_port = client
.register_port("sine_out", jack::AudioOut::default())
.unwrap();

// 3. define process callback handler
let mut frequency = 220.0;
Expand Down
4 changes: 2 additions & 2 deletions src/client/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ where
/// `notification_handler` and `process_handler` are consumed, but they are returned when
/// `Client::deactivate` is called.
pub fn new(client: Client, notification_handler: N, process_handler: P) -> Result<Self, Error> {
let _m = CREATE_OR_DESTROY_CLIENT_MUTEX.lock().unwrap();
let _m = CREATE_OR_DESTROY_CLIENT_MUTEX.lock().ok();
unsafe {
sleep_on_test();
let mut callback_context = Box::new(CallbackContext {
Expand Down Expand Up @@ -107,7 +107,7 @@ impl<N, P> AsyncClient<N, P> {
// Helper function for deactivating. Any function that calls this should
// have ownership of self and no longer use it after this call.
unsafe fn maybe_deactivate(&mut self) -> Result<Box<CallbackContext<N, P>>, Error> {
let _m = CREATE_OR_DESTROY_CLIENT_MUTEX.lock().unwrap();
let _m = CREATE_OR_DESTROY_CLIENT_MUTEX.lock().ok();
if self.callback.is_none() {
return Err(Error::ClientIsNoLongerAlive);
}
Expand Down
40 changes: 4 additions & 36 deletions src/client/client_impl.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use jack_sys as j;
use std::fmt::Debug;
use std::panic::catch_unwind;
use std::sync::Arc;
use std::{ffi, fmt, ptr};

Expand Down Expand Up @@ -51,7 +50,7 @@ impl Client {
/// Although the client may be successful in opening, there still may be some errors minor
/// errors when attempting to opening. To access these, check the returned `ClientStatus`.
pub fn new(client_name: &str, options: ClientOptions) -> Result<(Self, ClientStatus), Error> {
let _m = CREATE_OR_DESTROY_CLIENT_MUTEX.lock().unwrap();
let _m = CREATE_OR_DESTROY_CLIENT_MUTEX.lock().ok();

// All of the jack_sys functions below assume the client library is loaded and will panic if
// it is not
Expand All @@ -60,10 +59,7 @@ impl Client {
return Err(Error::LibraryError(err.to_string()));
}

unsafe {
jack_sys::jack_set_error_function(Some(silent_handler));
jack_sys::jack_set_info_function(Some(silent_handler));
}
crate::logging::maybe_init_logging();
sleep_on_test();
let mut status_bits = 0;
let client = unsafe {
Expand All @@ -75,10 +71,6 @@ impl Client {
if client.is_null() {
Err(Error::ClientError(status))
} else {
unsafe {
jack_sys::jack_set_error_function(Some(error_handler));
jack_sys::jack_set_info_function(Some(info_handler));
}
sleep_on_test();
Ok((Client(client, Arc::default(), None), status))
}
Expand Down Expand Up @@ -552,7 +544,7 @@ impl Client {
source_port: &Port<A>,
destination_port: &Port<B>,
) -> Result<(), Error> {
let _m = CREATE_OR_DESTROY_CLIENT_MUTEX.lock().unwrap();
let _m = CREATE_OR_DESTROY_CLIENT_MUTEX.lock().ok();
self.connect_ports_by_name(&source_port.name()?, &destination_port.name()?)
}

Expand Down Expand Up @@ -670,7 +662,7 @@ impl Client {
/// Close the client.
impl Drop for Client {
fn drop(&mut self) {
let _m = CREATE_OR_DESTROY_CLIENT_MUTEX.lock().unwrap();
let _m = CREATE_OR_DESTROY_CLIENT_MUTEX.lock().ok();
debug_assert!(!self.raw().is_null()); // Rep invariant
// Close the client
sleep_on_test();
Expand Down Expand Up @@ -790,27 +782,3 @@ pub struct CycleTimes {
pub next_usecs: Time,
pub period_usecs: libc::c_float,
}

unsafe extern "C" fn error_handler(msg: *const libc::c_char) {
let res = catch_unwind(|| match std::ffi::CStr::from_ptr(msg).to_str() {
Ok(msg) => log::error!("{}", msg),
Err(err) => log::error!("failed to log to JACK error: {:?}", err),
});
if let Err(err) = res {
eprintln!("{err:?}");
std::mem::forget(err);
}
}

unsafe extern "C" fn info_handler(msg: *const libc::c_char) {
let res = catch_unwind(|| match std::ffi::CStr::from_ptr(msg).to_str() {
Ok(msg) => log::info!("{}", msg),
Err(err) => log::error!("failed to log to JACK info: {:?}", err),
});
if let Err(err) = res {
eprintln!("{err:?}");
std::mem::forget(err);
}
}

unsafe extern "C" fn silent_handler(_msg: *const libc::c_char) {}
20 changes: 16 additions & 4 deletions src/client/test_callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,14 @@ fn client_cback_reports_xruns() {
#[test]
fn client_cback_calls_port_registered() {
let ac = active_test_client("client_cback_cpr");
let _pa = ac.as_client().register_port("pa", AudioIn).unwrap();
let _pb = ac.as_client().register_port("pb", AudioIn).unwrap();
let _pa = ac
.as_client()
.register_port("pa", AudioIn::default())
.unwrap();
let _pb = ac
.as_client()
.register_port("pb", AudioIn::default())
.unwrap();
let counter = ac.deactivate().unwrap().1;
assert_eq!(
counter.port_register_history.len(),
Expand All @@ -228,8 +234,14 @@ fn client_cback_calls_port_registered() {
#[test]
fn client_cback_calls_port_unregistered() {
let ac = active_test_client("client_cback_cpr");
let pa = ac.as_client().register_port("pa", AudioIn).unwrap();
let pb = ac.as_client().register_port("pb", AudioIn).unwrap();
let pa = ac
.as_client()
.register_port("pa", AudioIn::default())
.unwrap();
let pb = ac
.as_client()
.register_port("pb", AudioIn::default())
.unwrap();
ac.as_client().unregister_port(pa).unwrap();
ac.as_client().unregister_port(pb).unwrap();
let counter = ac.deactivate().unwrap().1;
Expand Down
24 changes: 6 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub use crate::client::{
InternalClientID, NotificationHandler, ProcessHandler, ProcessScope, CLIENT_NAME_SIZE,
};
pub use crate::jack_enums::{Control, Error, LatencyType};
pub use crate::logging::{set_logger, LoggerType};
pub use crate::port::{
AudioIn, AudioOut, MidiIn, MidiIter, MidiOut, MidiWriter, Port, PortFlags, PortSpec, RawMidi,
Unowned, PORT_NAME_SIZE, PORT_TYPE_SIZE,
Expand All @@ -49,36 +50,23 @@ pub use crate::transport::{
TransportStatePosition,
};

/// The underlying system bindings for JACK. Can be useful for using possibly
/// experimental stuff through `jack_sys::library()`.
/// The underlying system bindings for JACK. Can be useful for using possibly experimental stuff
/// through `jack_sys::library()`.
pub use jack_sys;

//only expose metadata if enabled
#[cfg(feature = "metadata")]
pub use crate::properties::*;

/// Create and manage client connections to a JACK server.
mod client;

/// Create and manage JACK ring buffers.
mod ringbuffer;

/// Enum types in jack.
mod jack_enums;

mod jack_utils;

/// Types for safely interacting with port data from JACK.
mod logging;
mod port;

/// Platform independent types.
mod primitive_types;

/// Transport.
mod transport;

/// Properties
mod properties;
mod ringbuffer;
mod transport;

static TIME_CLIENT: std::sync::LazyLock<Client> = std::sync::LazyLock::new(|| {
Client::new("deprecated_get_time", ClientOptions::NO_START_SERVER)
Expand Down
Loading

0 comments on commit 4d9c4f8

Please sign in to comment.