Skip to content

Commit

Permalink
Merge branch 'working' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
AldaronLau committed Jan 17, 2021
2 parents 097878c + fcf47b1 commit a46e959
Show file tree
Hide file tree
Showing 8 changed files with 894 additions and 330 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to `wavy` will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://jeronlau.tk/semver/).

## [0.9.0] - 2021-01-17
### Changed
- Updated to fon 0.5

## [0.8.1] - 2021-01-08
### Fixed
- XRUNS on ALSA stopping playback indefinitely.
Expand Down
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

[package]
name = "wavy"
version = "0.8.1"
version = "0.9.0"
authors = ["Jeron Aldaron Lau <jeronlau@plopgrizzly.com>"]
license = "Apache-2.0 OR MIT OR BSL-1.0"

Expand All @@ -20,7 +20,7 @@ Asynchronous cross-platform real-time audio recording & playback.
repository = "https://github.com/libcala/wavy"
documentation = "https://docs.rs/wavy"
homepage = "https://github.com/libcala/wavy/blob/main/CHANGELOG.md"
include = ["Cargo.toml", "README.md", "src/*"]
include = ["Cargo.toml", "src/*"]
categories = [
"game-engines", "hardware-support", "multimedia", "multimedia::audio", "os"
]
Expand All @@ -30,7 +30,7 @@ edition = "2018"

# For all platforms
[dependencies.fon]
version = "0.4"
version = "0.5"

# For Linux and Android
[target.'cfg(all(not(target_arch = "wasm32"), any(target_os = "linux", target_os = "android")))'.dependencies]
Expand Down Expand Up @@ -62,4 +62,4 @@ version = "0.2"
# Examples
[dev-dependencies]
pasts = "0.7"
twang = "0.6"
twang = "0.7"
355 changes: 355 additions & 0 deletions src/ffi/macos/audio_queue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,355 @@
// Wavy
// Copyright © 2019-2021 Jeron Aldaron Lau.
//
// Licensed under any of:
// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
// - MIT License (https://mit-license.org/)
// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt)
// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt,
// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt).

#![allow(unsafe_code)]

use std::os::raw::c_void;
use std::sync::{Arc, Mutex};
use std::task::Waker;

use crate::consts::SAMPLE_RATE;

#[repr(C)]
struct AudioQueueBuffer {
audio_data_bytes_capacity: u32, // const
audio_data: *mut i16,
audio_data_byte_size: u32,
user_data: *mut c_void,

packet_description_capacity: u32, // const
packet_descriptions: *const c_void,
packet_description_count: u32,
}

#[repr(C)]
pub(crate) struct AudioStreamBasicDescription {
sample_rate: f64,
format_id: u32,
format_flags: u32,
bytes_per_packet: u32,
frames_per_packet: u32,
bytes_per_frame: u32,
channels_per_frame: u32,
bits_per_channel: u32,
reserved: u32,
}

impl AudioStreamBasicDescription {
pub(crate) const fn new(channels: u32) -> Self {
Self {
// Use wavy's target sample rate
sample_rate: SAMPLE_RATE as f64,
// Don't do any compression, use linear PCM (raw samples).
format_id: u32::from_ne_bytes(*b"lpcm"),
// Set flag for float (if unset, integer).
format_flags: 1,
// sizeof(f32) * channels
bytes_per_packet: 4 * channels,
// Always one
frames_per_packet: 1,
// Same as bytes_per_packet
bytes_per_frame: 4 * channels,
// Stereo (channels)
channels_per_frame: channels,
// sizeof(f32) * bits_in_a_byte
bits_per_channel: 4 * 8,
// Always zero
reserved: 0,
}
}
}

// MacOS RunLoop is thread-safe.
#[repr(transparent)]
#[derive(Copy, Clone)]
struct RunLoop(*mut c_void);

unsafe impl Send for RunLoop {}
unsafe impl Sync for RunLoop {}

type OSStatus = i32;

extern "C" {
fn CFRunLoopGetCurrent() -> RunLoop;
fn CFRunLoopRun();
fn AudioQueueNewOutput(
in_format: *const AudioStreamBasicDescription,
in_callback_proc: extern "C" fn(*mut c_void, *mut c_void, *mut AudioQueueBuffer),
in_user_data: *mut c_void,
in_callback_run_loop: RunLoop,
in_callback_run_loop_mode: *const c_void,
in_flags: u32,
out_aq: *mut *mut c_void,
) -> OSStatus;
fn AudioQueueNewInput(
in_format: *const AudioStreamBasicDescription,
in_callback_proc: extern "C" fn(
*mut c_void,
*mut c_void,
*mut AudioQueueBuffer,
*const c_void,
u32,
*const c_void,
),
in_user_data: *mut c_void,
in_callback_run_loop: RunLoop,
in_callback_run_loop_mode: *const c_void,
in_flags: u32,
out_aq: *mut *mut c_void,
) -> OSStatus;
fn AudioQueueEnqueueBuffer(
in_audio_queue: *mut c_void,
in_buffer: *mut AudioQueueBuffer,
in_num_packet_descs: u32,
in_packet_descs: *const c_void
) -> OSStatus;
fn AudioQueueStart(
in_audio_queue: *mut c_void,
in_start_timestamp: *const c_void,
) -> OSStatus;
fn AudioQueueAllocateBuffer(
in_audio_queue: *mut c_void,
in_buffer_byte_size: u32,
out_buffer: *mut *mut AudioQueueBuffer,
) -> OSStatus;
}

// Thread-local run loop for audio device (global state).
thread_local!(static RUN_LOOP: RunLoop = initialize());

/// Global initialization, may be called more than once.
fn initialize() -> RunLoop {
let pair = std::sync::Arc::new((std::sync::Mutex::new(None), std::sync::Condvar::new()));
let pair2 = Arc::clone(&pair);
std::thread::spawn(move || {
let (lock, cvar) = &*pair2;
let mut started = lock.lock().unwrap();
// Get the run loop associated with this thread.
let run_loop = unsafe { CFRunLoopGetCurrent() };
*started = Some(run_loop);
cvar.notify_one();
// Block this thread with the run loop until the process exits.
unsafe { CFRunLoopRun() }
});

// Wait to get the thread's Run Loop handle.
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
let run_loop = loop {
if let Some(run_loop) = started.take() {
break run_loop;
}
started = cvar.wait(started).unwrap();
};
run_loop
}

// User data associated with the speaker.
struct SpeakerContext {
// Buffer for the next period * channels.
buffer: Mutex<(Vec<f32>, Option<Waker>)>,
}

// AudioQueue callback for speaker.
extern "C" fn speaker_callback(user_data: *mut c_void, audio_queue: *mut c_void, audio_buffer: *mut AudioQueueBuffer) {
// Cast user data.
let user_data: *mut SpeakerContext = user_data.cast();

// Write into buffer.
let buffer: *mut f32 = unsafe { (*audio_buffer).audio_data.cast() };
// Lock the mutex
unsafe {
let mut locked = (*user_data).buffer.lock().unwrap();
// Copy the data
let inbuf = &mut (*locked).0;
for i in 0..((*audio_buffer).audio_data_byte_size / 4) {
*buffer.offset(i as isize) = inbuf.get(i as usize).cloned().unwrap_or(0.0);
}
// Clear the written samples
inbuf.clear();
// Wake the main thread, telling it produce samples while the current ones are processing.
if let Some(waker) = locked.1.take() {
waker.wake();
};
}

// Enqueue buffer to audio queue
let status = unsafe {
AudioQueueEnqueueBuffer(
audio_queue,
audio_buffer,
0,
std::ptr::null()
)
};
if status != 0 {
panic!("Failed enqueue {:?}", status);
}
}

// AudioQueue callback for microphone.
extern "C" fn microphone_callback(
user_data: *mut c_void,
queue: *mut c_void,
buffer: *mut c_void,
start_timestamp: *const c_void,
num_packet_descs: u32,
packet_descs: *const c_void,
) {

}

/// A speaker (output) or microphone (input).
pub(crate) struct AudioQueue {
audio_queue: *mut c_void,
speaker_cx: *mut SpeakerContext,
}

impl Drop for AudioQueue {
fn drop(&mut self) {
// FIXME: Stop and Free the AudioQueue and speaker_cx.
todo!()
}
}

// If the audio format is not supported, returns b"fmt?" if the number of channels
// is not supported.
pub(crate) fn speaker(channels: u8) -> Result<AudioQueue, [u8; 4]> {
RUN_LOOP.with(|run_loop| {
let speaker_cx = Box::into_raw(Box::new(SpeakerContext {
buffer: Mutex::new((Vec::new(), None)),
}));

let basic_desc = AudioStreamBasicDescription::new(channels.into());
let mut audio_queue = std::mem::MaybeUninit::uninit();
let status = unsafe {
AudioQueueNewOutput(
&basic_desc,
speaker_callback,
speaker_cx.cast(),
*run_loop,
std::ptr::null(),
0,
audio_queue.as_mut_ptr(),
)
};
if status != 0 {
return Err(status.to_ne_bytes());
}
let audio_queue = unsafe { audio_queue.assume_init() };

// Create an empty buffer and enqueue the data.
let mut audio_buffer = std::mem::MaybeUninit::uninit();
let buf_byte_count = basic_desc.bytes_per_frame * crate::consts::PERIOD as u32;
let status = unsafe {
AudioQueueAllocateBuffer(
audio_queue,
buf_byte_count,
audio_buffer.as_mut_ptr(),
)
};
if status != 0 {
panic!("Out of memory {}!", status);
}
let audio_buffer = unsafe { audio_buffer.assume_init() };
// Set buffer length.
unsafe {
(*audio_buffer).audio_data_byte_size = buf_byte_count;
}
// Initialize to zero (silence).
for i in 0..buf_byte_count {
unsafe {
let byte: *mut u8 = (*audio_buffer).audio_data.cast();
*byte.offset(i as _) = 0;
}
}

// Enqueue buffer to audio queue
let status = unsafe {
AudioQueueEnqueueBuffer(
audio_queue,
audio_buffer,
0,
std::ptr::null()
)
};
if status != 0 {
return Err(status.to_ne_bytes());
}

// Now that we've created the audio queue an enqueued a buffer, we can start it.
let status = unsafe {
AudioQueueStart(audio_queue, std::ptr::null())
};
if status != 0 {
return Err(status.to_ne_bytes());
}

Ok(AudioQueue {
audio_queue, speaker_cx,
})
})
}

/*
*
void callback (void *ptr, AudioQueueRef queue, AudioQueueBufferRef buf_ref)
{
OSStatus status;
PhaseBlah *p = ptr;
AudioQueueBuffer *buf = buf_ref;
int nsamp = buf->mAudioDataByteSize / 2;
short *samp = buf->mAudioData;
int ii;
printf ("Callback! nsamp: %d\n", nsamp);
for (ii = 0; ii < nsamp; ii++) {
samp[ii] = (int) (30000.0 * sin(p->phase));
p->phase += p->phase_inc;
//printf("phase: %.3f\n", p->phase);
}
p->count++;
status = AudioQueueEnqueueBuffer (queue, buf_ref, 0, NULL);
printf ("Enqueue status: %d\n", status);
}
int main (int argc, char *argv[])
{
AudioQueueRef queue;
PhaseBlah phase = { 0, 2 * 3.14159265359 * 450 / 44100 };
OSStatus status;
AudioStreamBasicDescription fmt = { 0 };
AudioQueueBufferRef buf_ref, buf_ref2;
fmt.mSampleRate = 44100;
fmt.mFormatID = kAudioFormatLinearPCM;
fmt.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
fmt.mFramesPerPacket = 1;
fmt.mChannelsPerFrame = 1; // 2 for stereo
fmt.mBytesPerPacket = fmt.mBytesPerFrame = 2; // x2 for stereo
fmt.mBitsPerChannel = 16;
status = AudioQueueNewOutput(&fmt, callback, &phase, CFRunLoopGetCurrent(),
kCFRunLoopCommonModes, 0, &queue);
if (status == kAudioFormatUnsupportedDataFormatError) puts ("oops!");
else printf("NewOutput status: %d\n", status);
//
status = AudioQueueAllocateBuffer (queue, 20000, &buf_ref);
printf ("Allocate status: %d\n", status);
AudioQueueBuffer *buf = buf_ref;
printf ("buf: %p, data: %p, len: %d\n", buf, buf->mAudioData, buf->mAudioDataByteSize);
buf->mAudioDataByteSize = 20000;
callback (&phase, queue, buf_ref);
*/
Loading

0 comments on commit a46e959

Please sign in to comment.