Giter VIP home page Giter VIP logo

audioipc's Introduction

cubeb audio remoting for Gecko

An IPC client/server providing inter-process remoting of the cubeb audio API.

A single server running in a parent or server process interfaces with cubeb on behalf of multiple client or child processes.

On the server side, the external interface is a small set of C FFI functions to initialize and destroy the server (audioipc_server_start/audioipc_server_stop) and create new server connections (audioipc_server_new_client). On the client side, the external interface consists of a single C FFI function (audioipc_client_init), replacing a call to cubeb_init and returning a cubeb context that transparently remotes the cubeb API to the server; aside from this the client uses the cubeb API and remoting is handled transparently. audioipc_server_new_client returns a handle that must be supplied to audioipc_client_init to establish the client-server connection. Transferring this handle from the server process to the client process is done externally, in Gecko code.

  • The client side remoting layer (located in the client crate) contains ClientContext and ClientStream, which adapts the cubeb context and cubeb stream APIs (respectively) to the RPC layer. This is driven by one "Client RPC" EventLoopThread per ClientContext and one "Client Callback" EventLoopThread per ClientStream.
  • The server side remoting layer (located in the server crate) contains CubebServer, which adapts both context and stream RPCs to native cubeb API calls. This is driven by a single trio of "Server RPC", "Server Callback", and "Server DeviceCollection RPC) EventLoopThreads servicing all CubebServer instances.
  • Core RPC and IPC functionality and support code is located in the audioipc crate.
    • The RPC interface provides a Client trait specifying message types the client implementation will use, and a Server trait specifying message types the server implementation will use and a process function that handles incoming messages and produces responses. Server and Client implementations work as pairs, with one on each side of the IPC connection. Server and Client implementations are driven by an EventLoopThread once bound to the event loop with a connection via bind_server or bind_client. Each Server implementation may have multiple associated Client implementations in one or more remote process.
    • The RPC interface also provides Proxy and ProxyResponse objects, providing a blocking thread-safe mechanism to send and receive RPC messages from arbitrary threads.
    • The IPC interface provides the core EventLoopThread object, responsible for driving inter-process communication on each end of previously bound Client and Server implementations (and their associated connections).
    • The IPC interface also provides a cloneable, sendable EventLoopHandle object associated with an EventLoopThread that allows arbitrary threads to bind Client and Server implementations and signal the EventLoopThread to shut down.

audioipc's People

Contributors

anarchpenguin avatar andreastt avatar chunminchang avatar dexterp37 avatar djg avatar eijebong avatar froydnj avatar glandium avatar kinetiknz avatar kmaglione avatar mbrubeck avatar nox avatar padenot avatar pehrsons avatar singingtree avatar staktrace avatar sylvestre avatar upsuper avatar yjugl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

audioipc's Issues

Build fails on Windows, appears to be due to sys/socket.h usage in c code

Attempting to build current master I get the following (trimmed) log:

error: failed to run custom build command for `audioipc v0.2.4 (C:\Projects\audioipc-2\audioipc)`
process didn't exit successfully: `C:\Projects\audioipc-2\target\debug\build\audioipc-84d347709dee7ffc\build-script-build` (exit code: 101)
--- stdout
TARGET = Some("x86_64-pc-windows-msvc")
OPT_LEVEL = Some("0")
TARGET = Some("x86_64-pc-windows-msvc")
HOST = Some("x86_64-pc-windows-msvc")
TARGET = Some("x86_64-pc-windows-msvc")
TARGET = Some("x86_64-pc-windows-msvc")
HOST = Some("x86_64-pc-windows-msvc")
CC_x86_64-pc-windows-msvc = None
CC_x86_64_pc_windows_msvc = None
HOST_CC = None
CC = None
TARGET = Some("x86_64-pc-windows-msvc")
HOST = Some("x86_64-pc-windows-msvc")
CFLAGS_x86_64-pc-windows-msvc = None
CFLAGS_x86_64_pc_windows_msvc = None
HOST_CFLAGS = None
CFLAGS = None
DEBUG = Some("true")
running: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Preview\\VC\\Tools\\MSVC\\14.20.27508\\bin\\HostX64\\x64\\cl.exe" "/nologo" "/MD" "/Z7" "/W4" "/FoC:\\Projects\\audioipc-2\\target\\debug\\build\\audioipc-f2f8c614171789dd\\out\\src\\cmsghdr.o" "/c" "src/cmsghdr.c"
cmsghdr.c
src/cmsghdr.c(4): fatal error C1083: Cannot open include file: 'sys/socket.h': No such file or directory
exit code: 2

--- stderr
thread 'main' panicked at '

It looks like we're failing due to looking for sys/socket.h rather than the Windows specific winsock2.h. Edit: as well as the other code changes that would be needed to accommodate Windows. @kinetiknz, I'm not super familiar with this, do you have any thoughts?

adapt for tokio-uds 0.2.0

cf https://bugzilla.mozilla.org/show_bug.cgi?id=1467872

right now with uds 0.2.0 it fails to build:

    Compiling audioipc v0.2.4 (file:///home/landry/src/audioipc-2/audioipc)
error[E0061]: this function takes 2 parameters but 1 parameter was supplied
  --> audioipc/src/async.rs:62:34
   |
62 |         if let Async::NotReady = <UnixStream>::poll_read(self) {
   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 2 parameters

error[E0308]: mismatched types
  --> audioipc/src/async.rs:62:16
   |
62 |         if let Async::NotReady = <UnixStream>::poll_read(self) {
   |                ^^^^^^^^^^^^^^^ expected enum `std::result::Result`, found enum `futures::Async`
  |
   = note: expected type `std::result::Result<futures::Async<usize>, std::io::Error>`
              found type `futures::Async<_>`

error[E0599]: no method named `need_read` found for type `&mut tokio_uds::UnixStream` in the current scope
   --> audioipc/src/async.rs:118:22
    |
118 |                 self.need_read();
    |                      ^^^^^^^^^

error[E0061]: this function takes 2 parameters but 1 parameter was supplied
   --> audioipc/src/async.rs:132:34
    |
132 |         if let Async::NotReady = <UnixStream>::poll_write(self) {
    |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 2 parameters

error[E0308]: mismatched types
   --> audioipc/src/async.rs:132:16
    |
132 |         if let Async::NotReady = <UnixStream>::poll_write(self) {
    |                ^^^^^^^^^^^^^^^ expected enum `std::result::Result`, found enum `futures::Async`
    |
    = note: expected type `std::result::Result<futures::Async<usize>, std::io::Error>`
               found type `futures::Async<_>`

error[E0599]: no method named `need_write` found for type `&mut tokio_uds::UnixStream` in the current scope
   --> audioipc/src/async.rs:153:22
    |
153 |                 self.need_write();
    |                      ^^^^^^^^^^

Make file descriptor passing more flexible

The current code expects exactly 3 file descriptors to be sent/received. This was based on StreamCreated being the only user and needing to pass exactly 3 file descriptors.

With #62, we have a need to pass a single file descriptor when initializing the device collection changed callback. Currently, there's a hack that passes two dummy file descriptors and discards them, which is quite ugly.

This would also help with addressing #45, where we initialize more shared memory than we might need for a given stream configuration because we're required to send 3 file descriptors.

Safer shared memory API

Following on from the discussion in #63, shm.rs and the exposed API used by callers can probably be improved to be safer. Right now, synchronization of the shared memory is completely external, making shm.rs unsafe despite being marked as safe.

New clippy issues about alignment size

error: casting from `*const u8` to a more-strictly-aligned pointer (`*const i32`) (1 < 4 bytes)
  --> audioipc/src/cmsg.rs:20:40
   |
20 |         unsafe { slice::from_raw_parts(self.fds.as_ptr() as *const _, n) }
   |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[deny(clippy::cast_ptr_alignment)]` on by default
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment

error: casting from `*const u8` to a more-strictly-aligned pointer (`*const libc::unix::bsd::cmsghdr`) (1 < 4 bytes)
  --> audioipc/src/cmsg.rs:55:45
   |
55 |             let cmsg: &cmsghdr = unsafe { &*(control.as_ptr() as *const _) };
   |                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment

error: casting from `*mut u8` to a more-strictly-aligned pointer (`*const libc::unix::bsd::cmsghdr`) (1 < 4 bytes)
   --> audioipc/src/cmsg.rs:128:53
    |
128 |                 let cmsg_data_ptr = libc::CMSG_DATA(cmsghdr_ptr as _);
    |                                                     ^^^^^^^^^^^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment

I don't know if this is problematic in practice.

Rework naming conventions and improve documentation

Per discussion here and elsewhere, rework the naming conventions for various overloaded terms in the code base. Terms such as "server"/"client" and "callback" are overloaded and make it harder to understand the message flow and overall architecture of the code.

musl patch from #43 doesn't work on big endian

From musl's <bits/socket.h>:

struct cmsghdr {
#if __BYTE_ORDER == __BIG_ENDIAN
        int __pad1;
        socklen_t cmsg_len;
#else
        socklen_t cmsg_len;
        int __pad1;
#endif
        int cmsg_level;
        int cmsg_type;
};

So, __pad1 needs to come before cmsg_len on BE targets.

Collection changed callback does not respect input or output as device type

Registering a callback for input or output collection change is supported by Linux backends (both C and rust). The following code should be supported:

  int rv = cubeb_register_device_collection_changed(GetCubebContext(),
       static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT),
       &mozilla::CubebDeviceEnumerator::AudioDeviceListChanged_s, this);

With cubeb remote on, the collection change callback is not triggered on plug or unplug. However, the method returns CUBEB_OK and does not give the opportunity to the client to workaround the missing callback.

File descriptor passing broken on FreeBSD

Something is wrong (non-portable) with the cmsg parsing: the slice passed to clone_into_array in the tests ends up being [0, 0, 1, 2].

(btw, why is the parsing fully custom? The libc crate provides CMSG_FIRSTHDR, CMSG_NXTHDR, CMSG_DATA, CMSG_LEN and the nix crate has a nice wrapper that uses them)

messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Serialize` is not implemented for `[u8; 80]`

13:48.78 error[E0277]: arrays only have std trait implementations for lengths 0..=32
13:48.78 --> media/audioipc/audioipc/src/messages.rs:214:29
13:48.78 |
13:48.78 214 | PromoteThreadToRealTime([u8; std::mem::size_of::()]),
13:48.78 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait std::array::LengthAtMost32 is not implemented for [u8; 80]
13:48.78 |
13:48.79 = note: required because of the requirements on the impl of std::fmt::Debug for [u8; 80]
13:48.79 = note: required because of the requirements on the impl of std::fmt::Debug for &[u8; 80]
13:48.79 = note: required for the cast to the object type dyn std::fmt::Debug
13:48.88 error[E0277]: the trait bound [u8; 80]: messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Serialize is not satisfied
13:48.89 --> media/audioipc/audioipc/src/messages.rs:214:29
13:48.89 |
13:48.89 214 | PromoteThreadToRealTime([u8; std::mem::size_of::()]),
13:48.89 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Serialize is not implemented for [u8; 80]
13:48.89 |
13:48.89 = help: the following implementations were found:
13:48.89 <[T; 0] as messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Serialize>
13:48.89 <[T; 10] as messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Serialize>
13:48.89 <[T; 11] as messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Serialize>
13:48.89 <[T; 12] as messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Serialize>
13:48.89 and 30 others
13:48.89 = note: required by messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Serializer::serialize_newtype_variant
13:48.93 error[E0277]: the trait bound [u8; 80]: messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Deserialize<'_> is not satisfied
13:48.93 --> media/audioipc/audioipc/src/messages.rs:214:29
13:48.93 |
13:48.93 214 | PromoteThreadToRealTime([u8; std::mem::size_of::()]),
13:48.93 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Deserialize<'_> is not implemented for [u8; 80]
13:48.93 |
13:48.93 = help: the following implementations were found:
13:48.93 <&'a [u8] as messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Deserialize<'de>>
13:48.93 <[T; 0] as messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Deserialize<'de>>
13:48.93 <[T; 10] as messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Deserialize<'de>>
13:48.94 <[T; 11] as messages::_IMPL_DESERIALIZE_FOR_Device::_serde::Deserialize<'de>>
13:48.94 and 30 others
13:48.94 = note: required by messages::_IMPL_DESERIALIZE_FOR_Device::_serde::de::VariantAccess::newtype_variant
13:49.44 error: aborting due to 3 previous errors
13:49.44 For more information about this error, try rustc --explain E0277.
13:49.51 error: Could not compile audioipc.

Query cubeb to calculate precise `shm_area_size` for a given stream configuration

PR #121 provides a way to specify a per-server shm_area_size via a new parameter to audioipc_server_new_client, allowing Gecko to specify a suitable value. This removes the previous hard-coded guess of 2MB, which is too large for most streams but potentially too small for streams with a large number of channels or very high latency request.

This could be improved by extending cubeb's API to provide a way to query the required shm_area_size on a per-stream basis during configuration. Requires a new API for cubeb with changes for each supported backend, and a new guarantee that the data_callback requests will never exceed the reported buffer size (this can happen in some backends in response to latency changes in the audio server, for example).

upgrade memmap requirement to 0.6

memmap 0.5.2 depends on winapi 0.2. I'm trying to add support to winapi for aarch64, and I'd much prefer to do that on the newer version of winapi, 0.3.x. If audioipc depended on a newer version of memmap, which in turn pulled in a newer version of winapi, that would make my job much easier.

Ensure audioipc's real-time thread are on the same work group on macOS

See https://developer.apple.com/documentation/audiotoolbox/workgroup_management/understanding_audio_workgroups?language=objc for a lot more context.

This is going to be a bit tricky. In addition to promoting the threads (that audio_thread_priority already does), another API should be called. It's not clear yet to me where it's best to file or put this issue, but clearly it has something to do with audioipc so I'm filing here.

From e.g. cubeb_stream_init (or elsewhere we can access the audio device that is going to be in use):

  os_workgroup_t workgroup;
  size = sizeof(os_workgroup_t);
  r = AudioUnitGetProperty(stm->output_unit, kAudioOutputUnitProperty_OSWorkgroup,
                           kAudioUnitScope_Output, AU_OUT_BUS, &workgroup,
                           &size);

the we stash this workgroup somewhere, and from the audioipc thread that transport audio data, when creating the thread:

  os_workgroup_join_token_s joinToken;
  const int result = os_workgroup_join(workgroup, &joinToken);
  if (result == 0) {
    printf("OK\n");
      // Success.
  }
  else if (result == EALREADY) {
    // The thread is already part of a workgroup that can't be
    // nested in the the specified workgroup.
    printf("Already joined\n");
  }
  else if (result == EINVAL) {
    printf("Already EINVAL\n");
    // The workgroup has been canceled.
  }

and before exiting the thread, leave the group:

os_workgroup_leave(workgroup, joinToken);

One of the issue here is that the workgroup is per audio device, and we only have a single audioipc thread that transports the audio data, regardless of the devices. For now, we generally always use the same audio output device (the default), so it's not terrible, but we might want to take this into consideration.

We might end up being able to implement something by snooping on the actual device in use via https://github.com/mozilla/cubeb/blob/master/include/cubeb/cubeb.h#L637, but this breaks the audioipc layering somewhat.

musl requires padding for liblibc

From: Jory A. Pratt [email protected]

Musl requires pad1 for libc

diff --git a/media/audioipc/audioipc/src/cmsg.rs b/media/audioipc/audioipc/src/cmsg.rs
--- a/media/audioipc/audioipc/src/cmsg.rs
+++ b/media/audioipc/audioipc/src/cmsg.rs
@@ -105,16 +105,18 @@ impl ControlMsgBuilder {
if cmsg.remaining_mut() < cmsg_len {
return Err(Error::NoSpace);
}

         let cmsghdr = cmsghdr {
             cmsg_len: cmsg_len as _,
             cmsg_level: level,
             cmsg_type: kind,
  •   #[cfg(target_env = "musl")]
    
  •   __pad1: 0,
           };
    
           let cmsghdr = unsafe {
               slice::from_raw_parts(&cmsghdr as *const _ as *const _, mem::size_of::<cmsghdr>())
           };
           cmsg.put_slice(cmsghdr);
           let mut cmsg = try!(align_buf(cmsg));
           cmsg.put_slice(msg);
    

upgrade futures-cpupool to a newer version

AudioIPC threads take up a lot of stack space by default, and there can be a lot of them. Newer versions of futures-cpupool support setting the stack size for thread pools. We should use a newer version of futures-cpupool so the stack size of AudioIPC threads can be limited somewhat.

Avoid configuring shared memory for unused stream directions.

As mentioned in issue #42: right now, we blindly configure shared memory for both input and output directions of every stream. It's common for streams to only use output (e.g. for simple playback), so configuring an extra 2MB shared memory region for input that will never be used during the lifetime of the stream is wasteful.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.