Skip to content

Commit

Permalink
Linux support fixes #10
Browse files Browse the repository at this point in the history
- Removed PulseAudio code
- Separate Tauri conf file
- Fix for dev environment on Ubuntu
- Unfortunately there is no support for WebRTC on webkit2gtk yet, so the oscilloscope doesn't work
  • Loading branch information
basharovV committed Aug 2, 2024
1 parent 2907673 commit bdc1df8
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 145 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.8.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --host",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json",
Expand Down
144 changes: 0 additions & 144 deletions src-tauri/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,144 +40,6 @@ pub enum AudioOutputError {

pub type Result<T> = result::Result<T, AudioOutputError>;

#[cfg(target_os = "linux")]
mod pulseaudio {
use super::{AudioOutput, AudioOutputError, Result};

use symphonia::core::audio::*;
use symphonia::core::units::Duration;

use libpulse_binding as pulse;
use libpulse_simple_binding as psimple;

use log::{error, warn};

pub struct PulseAudioOutput {
pa: psimple::Simple,
sample_buf: RawSampleBuffer<f32>,
}

impl PulseAudioOutput {
pub fn try_open(spec: SignalSpec, duration: Duration) -> Result<Box<dyn AudioOutput>> {
// An interleaved buffer is required to send data to PulseAudio. Use a SampleBuffer to
// move data between Symphonia AudioBuffers and the byte buffers required by PulseAudio.
let sample_buf = RawSampleBuffer::<f32>::new(duration, spec);

// Create a PulseAudio stream specification.
let pa_spec = pulse::sample::Spec {
format: pulse::sample::Format::FLOAT32NE,
channels: spec.channels.count() as u8,
rate: spec.rate,
};

assert!(pa_spec.is_valid());

let pa_ch_map = map_channels_to_pa_channelmap(spec.channels);

// PulseAudio seems to not play very short audio buffers, use these custom buffer
// attributes for very short audio streams.
//
// let pa_buf_attr = pulse::def::BufferAttr {
// maxlength: std::u32::MAX,
// tlength: 1024,
// prebuf: std::u32::MAX,
// minreq: std::u32::MAX,
// fragsize: std::u32::MAX,
// };

// Create a PulseAudio connection.
let pa_result = psimple::Simple::new(
None, // Use default server
"Symphonia Player", // Application name
pulse::stream::Direction::Playback, // Playback stream
None, // Default playback device
"Music", // Description of the stream
&pa_spec, // Signal specification
pa_ch_map.as_ref(), // Channel map
None, // Custom buffering attributes
);

match pa_result {
Ok(pa) => Ok(Box::new(PulseAudioOutput { pa, sample_buf })),
Err(err) => {
error!("audio output stream open error: {}", err);

Err(AudioOutputError::OpenStreamError)
}
}
}
}

impl AudioOutput for PulseAudioOutput {
fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()> {
// Do nothing if there are no audio frames.
if decoded.frames() == 0 {
return Ok(());
}

// Interleave samples from the audio buffer into the sample buffer.
self.sample_buf.copy_interleaved_ref(decoded);

// Write interleaved samples to PulseAudio.
match self.pa.write(self.sample_buf.as_bytes()) {
Err(err) => {
error!("audio output stream write error: {}", err);

Err(AudioOutputError::StreamClosedError)
}
_ => Ok(()),
}
}

fn flush(&mut self) {
// Flush is best-effort, ignore the returned result.
let _ = self.pa.drain();
}
}

/// Maps a set of Symphonia `Channels` to a PulseAudio channel map.
fn map_channels_to_pa_channelmap(channels: Channels) -> Option<pulse::channelmap::Map> {
let mut map: pulse::channelmap::Map = Default::default();
map.init();
map.set_len(channels.count() as u8);

let is_mono = channels.count() == 1;

for (i, channel) in channels.iter().enumerate() {
map.get_mut()[i] = match channel {
Channels::FRONT_LEFT if is_mono => pulse::channelmap::Position::Mono,
Channels::FRONT_LEFT => pulse::channelmap::Position::FrontLeft,
Channels::FRONT_RIGHT => pulse::channelmap::Position::FrontRight,
Channels::FRONT_CENTRE => pulse::channelmap::Position::FrontCenter,
Channels::REAR_LEFT => pulse::channelmap::Position::RearLeft,
Channels::REAR_CENTRE => pulse::channelmap::Position::RearCenter,
Channels::REAR_RIGHT => pulse::channelmap::Position::RearRight,
Channels::LFE1 => pulse::channelmap::Position::Lfe,
Channels::FRONT_LEFT_CENTRE => pulse::channelmap::Position::FrontLeftOfCenter,
Channels::FRONT_RIGHT_CENTRE => pulse::channelmap::Position::FrontRightOfCenter,
Channels::SIDE_LEFT => pulse::channelmap::Position::SideLeft,
Channels::SIDE_RIGHT => pulse::channelmap::Position::SideRight,
Channels::TOP_CENTRE => pulse::channelmap::Position::TopCenter,
Channels::TOP_FRONT_LEFT => pulse::channelmap::Position::TopFrontLeft,
Channels::TOP_FRONT_CENTRE => pulse::channelmap::Position::TopFrontCenter,
Channels::TOP_FRONT_RIGHT => pulse::channelmap::Position::TopFrontRight,
Channels::TOP_REAR_LEFT => pulse::channelmap::Position::TopRearLeft,
Channels::TOP_REAR_CENTRE => pulse::channelmap::Position::TopRearCenter,
Channels::TOP_REAR_RIGHT => pulse::channelmap::Position::TopRearRight,
_ => {
// If a Symphonia channel cannot map to a PulseAudio position then return None
// because PulseAudio will not be able to open a stream with invalid channels.
warn!("failed to map channel {:?} to output", channel);
return None;
}
}
}

Some(map)
}
}

#[cfg(not(target_os = "linux"))]
mod cpal {
use std::sync::mpsc::Receiver;
use std::sync::{Arc, RwLock};
Expand Down Expand Up @@ -725,12 +587,6 @@ mod cpal {
}
}

#[cfg(target_os = "linux")]
pub fn try_open(spec: SignalSpec, duration: Duration) -> Result<Box<dyn AudioOutput>> {
pulseaudio::PulseAudioOutput::try_open(spec, duration)
}

#[cfg(not(target_os = "linux"))]
pub fn try_open(
spec: SignalSpec,
volume_control_receiver: Arc<
Expand Down
106 changes: 106 additions & 0 deletions src-tauri/tauri.linux.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:5173",
"distDir": "../dist",
"withGlobalTauri": false
},
"package": {
"productName": "Musicat",
"version": "0.8.0"
},
"tauri": {
"macOSPrivateApi": true,
"allowlist": {
"all": true,
"fs": {
"all": true,
"scope": {
"allow": ["**"],
"requireLiteralLeadingDot": false
}
},
"globalShortcut": {
"all": true
},
"path": {
"all": true
},
"protocol": {
"all": true,
"asset": true,
"assetScope": ["**"]
},
"http": {
"all": true,
"request": true,
"scope": [
"http://**",
"https://**",
"asset://**",
"stream://**"
]
}
},
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "com.vbapps.musicat",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"security": {
"csp": "default-src 'self' https://api.iconify.design https://api.simplesvg.com https://api.unisvg.com https://www.wikidata.org https://en.wikipedia.org https://api.openai.com https://api.genius.com *.archive.org https://archive.org asset: https://asset.localhost stream: http://stream.localhost; img-src 'self' https://archive.org asset: https://asset.localhost data:; style-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' https://*.archive.org archive.org"
},
"updater": {
"active": false
},
"windows": [
{
"theme": "Dark",
"fullscreen": false,
"height": 780,
"resizable": true,
"title": "Musicat",
"width": 1200,
"minHeight": 210,
"acceptFirstMouse": true,
"minWidth": 210,
"transparent": false,
"visible": true,
"fileDropEnabled": true,
"hiddenTitle": true,
"decorations": true,
"titleBarStyle": "Overlay"
}
]
}
}

0 comments on commit bdc1df8

Please sign in to comment.