Code Monkey home page Code Monkey logo

audio's Introduction

balena audio block

Provides an easy way to work with audio applications in a containerized environment. The audio block is a docker image that runs a PulseAudio server optimized for balenaOS.

Features

  • PulseAudio v15 audio server
  • Configuration optimized for balenaOS, extendable via PA config files
  • Supports both TCP and UNIX socket communication
  • Bluetooth and ALSA support out of the box
  • Companion library to send PA commands and handle events using JavaScript

Usage

Prebuilt images

We maintain images for this block on balenaHub Container Registry. The images can be accessed using:

bh.cr/balenalabs/audio-<arch> or bhcr.io/balenalabs/audio-<arch> where <arch> is one of: rpi, armv7hf, aarch64 or amd64.

For details on how to select a specific version or commit version of the image see our documentation.

docker-compose file

To use this image, create a container in your docker-compose.yml file as shown below:

version: '2'

volumes:
  pulse:                          # Only required if using PA over UNIX socket

services:

  audio:
    image: bh.cr/balenalabs/audio-<arch> # where <arch> is one of rpi, armv7hf, aarch64 or amd64
    privileged: true
    labels:
      io.balena.features.dbus: 1  # Only required for bluetooth support
    ports:
      - 4317:4317                 # Only required if using PA over TCP socket
    volumes:
      - 'pulse:/run/pulse'        # Only required if using PA over UNIX socket

  my-audio-app:
    build: ./my-audio-app
    volumes:
      - 'pulse:/run/pulse'        # Only required if using PA over UNIX socket

Send/receive audio

In order to route audio through the audio block there are a few environment variables you'll need to set. Note that they must be set on your client container, where your audio application is running and not on the block itself. We recommend setting them in the Dockerfile.

Environment variable Description
PULSE_SERVER Required Address of the PulseAudio server which you want to connect to. Depending on the communication protocol you want to use it can be:
- UNIX socket: PULSE_SERVER=unix:/run/pulse/pulseaudio.socket
- TCP socket: PULSE_SERVER=tcp:audio:4317
PULSE_SINK Optional The PulseAudio sink your application will send audio to. If not set, the block will use the PulseAudio default sink. Unless you are building a complex audio application we don't recommend setting this variable. If you want to select which output to use, for example HDMI or audio jack for a Raspberry Pi use the AUDIO_OUTPUT env var on the block to select the output device.
PULSE_SOURCE Optional The PulseAudio source your application will get audio from.

Setting these environment variables will instruct your application to route audio to the PulseAudio server on the audio container. For this to work your application must have built-in support for PulseAudio as an audio backend. Most applications do, though some might require installing or configuring additional packages. If your application does not have native support for the PulseAudio backend you'll need to use your container's ALSA backend to bridge over to PulseAudio.

Read on for details on both alternatives. We've also included some examples in the examples folder (along with the docker-compose.yml file) so be sure to check that as well for implementation details.

PulseAudio backend

For applications with PulseAudio support, the audio is routed as follows:

[client-container] audio-app --> [audio] PulseAudio --> [audio] ALSA --> Audio Hardware

Here is a non-exhaustive list of applications with PulseAudio backend that have been tested to work, feel free to PR more:

  • SoX: PA backend distributed via libsox-fmt-pulse package
  • MPlayer: Native PA backend
  • FFmpeg: Native PA backend

ALSA bridge

For audio applications that don't have built-in PulseAudio support you can use ALSA to brige the gap:

[client-container] audio-app --> [client-container] ALSA --> [audio] PulseAudio --> [audio] ALSA --> Audio Hardware

Setting up the ALSA bridge requires extra configuration steps on your containers so we created a few bash scripts to simplify the process:

Before making use of audio capabilities you should run this script. An easy way to do so is by including the following instruction in your Dockerfile:

RUN curl -skL https://raw.githubusercontent.com/balena-labs-projects/audio/master/scripts/alsa-bridge/debian-setup.sh| sh

Customization

Extend image configuration

You can extend the audio block to include custom configuration as you would with any other Dockerfile. Just make sure you don't override the ENTRYPOINT as it contains important system configuration.

Here are some of the most common extension cases:

  • Pass a flag to the PulseAudio server:
FROM bh.cr/balenalabs/audio-aarch64

CMD [ "--disallow-module-loading" ]
  • Add custom configuration files:
FROM bh.cr/balenalabs/audio-aarch64

COPY custom.pa /usr/src/custom.pa
CMD [ "pulseaudio", "--file /usr/src/custom.pa" ]
  • Start PulseAudio from your own bash script:
FROM bh.cr/balenalabs/audio-aarch64

COPY custom.pa /usr/src/custom.pa
COPY start.sh /usr/src/start.sh
CMD [ "/bin/bash", "/usr/src/start.sh" ]

Environment variables

The following environment variables allow some degree of configuration:

Environment variable Description Default Options
AUDIO_LOG_LEVEL PulseAudio log level. WARN ERROR, WARN, NOTICE, INFO, DEBUG.
AUDIO_OUTPUT Select the default audio output device.
Can also be changed at runtime by using the companion library
AUTO For all device types:
- AUTO: Let PulseAudio decide. Priority is USB > DAC > HEADPHONES > HDMI
- DAC: Force default output to be an attached GPIO based DAC
- <PULSE_SINK_NAME>: If you know the sink name you can force set it too. Note that you can't use this to set custom sinks as default, in that case use set-default-sink on your custom pa script.

For Raspberry Pi devices:
- RPI_AUTO: BCM2835 automatic audio switching as described here. Deprecated for devices running Linux kernel 5.4 or newer.
- RPI_HEADPHONES: 3.5mm audio jack
- RPI_HDMI0: Main HDMI port
- RPI_HDMI1: Secondary HDMI port (only Raspberry Pi 4)

For Intel NUC:
- NUCs have automatic output detection and switching. If you plug both the HDMI and the 3.5mm audio jack it will use the latter.
AUDIO_VOLUME Initial volume level for the default audio output device. 75% Any value between 0-100%.

Companion library

If you need to manipulate the block's behavior at runtime you can connect to the PulseAudio server, send commands and receive data or events from it. You should be able to use any existing library that implements the PA client protocol over TCP/UNIX sockets (some examples: Python, Rust, JavaScript), or you could even write your own. Libraries that manipulate PA over dbus won't work because we don't run the pulse dbus daemon.

On this note, we built a companion javascript library that exposes the most common use cases with an easy to use interface, these include changing the volume, listening to play/stop events, etc. You can install it with npm install balena-audio; see https://github.com/balena-io-modules/balena-audio for usage and examples.

Bluetooth

Bluetooth support for PulseAudio is enabled out of the box. Note that this only provides the backend that routes bluetooth packets over to PulseAudio, this does not include the Bluetooth agent that's required for initiating a connection and pairing devices. Check out our Bluetooth block for an easy to use Bluetooth agent.

Supported devices

The audio block has been tested to work on the following devices:

Device Type Supported interface (driver)
Raspberry Pi (v1 / Zero / Zero W)  - Audio jack (bcm2835): ✔
- HDMI (bcm2835): ✔
- I2S DAC (snd-rpi-simple): ✔
- USB (snd-usb-audio): ✔
Raspberry Pi 2  - Audio jack (bcm2835): ✔
- HDMI (bcm2835): ✔
- I2S DAC (snd-rpi-simple): ✔
- USB (snd-usb-audio): ✔
Raspberry Pi 3  - Audio jack (bcm2835): ✔
- HDMI (bcm2835): ✔
- I2S DAC (snd-rpi-simple): ✔
- USB (snd-usb-audio): ✔
Raspberry Pi 4  - Audio jack (bcm2835): ✔
- HDMI (bcm2835): ✔
- I2S DAC (snd-rpi-simple): ✔
- USB (snd-usb-audio): ✔
Intel NUC  - Audio jack (snd_hda_intel): ✔
- HDMI (snd_hda_intel): ✔
- USB (snd-usb-audio): ✔
 Jetson Nano 1 - HDMI (tegrahda): ✘
- I2S DAC (tegrasndt210ref): ?2
- USB (snd-usb-audio): ✔
BeagleBone Black - USB (snd-usb-audio): ✔

1: Audio block crashes if no USB/DAC present. See: #35 2: Not tested. PR's welcome.

audio's People

Contributors

balena-ci avatar nucleardreamer avatar rahul-thakoor avatar shawaj avatar tmigone avatar zwhitchcox avatar

Stargazers

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

Watchers

 avatar

audio's Issues

[Jetson Nano] audio block crashes with HDMI output

If there are no additional outputs (USB or DAC) plugged in, the block crashes.

Soundcard:

root@f539fa0399cc:/usr/src# cat /proc/asound/cards 
 0 [tegrahda       ]: tegra-hda - tegra-hda
                      tegra-hda at 0x70038000 irq 83

Error:

E: [pulseaudio] module-alsa-card.c: Failed to find a working profile.
 audio  E: [pulseaudio] module.c: Failed to load module "module-alsa-card" (argument: "device_id="0" name="0" card_name="alsa_card.0" namereg_fail=false tsched=yes fixed_latency_range=no ignore_dB=no deferred_volume=yes use_ucm=yes avoid_resampling=yes card_properties="module-udev-detect.discovered=1""): initialization failed.

Amixer output:

root@f539fa0399cc:/usr/src# amixer --card tegrahda
Simple mixer control 'd HDA Comfort Noise',0
  Capabilities: pswitch pswitch-joined
  Playback channels: Mono
  Mono: Playback [off]
Simple mixer control 'd HDA Decode Capability',0
  Capabilities: volume volume-joined
  Playback channels: Mono
  Capture channels: Mono
  Limits: 0 - 4294967295
  Mono: 0 [0%]
Simple mixer control 'd HDA Maximum PCM Channels',0
  Capabilities: volume volume-joined
  Playback channels: Mono
  Capture channels: Mono
  Limits: 0 - 4294967295
  Mono: 2 [0%]
Simple mixer control 'd IEC958',0
  Capabilities: pswitch pswitch-joined
  Playback channels: Mono
  Mono: Playback [on]

bluetooth: audio stutters and lags when switching bluetooth device that is streaming

Description
Choppy audio and it lags when starting playback. Same symptoms as #17.

Affected devices
All.

Steps to reproduce
Doesn't happen every time, but:

  1. Connect device A via bluetooth (Android phone)
  2. Stream music --> sounds good
  3. Disconnect device A
  4. Connect device B via bluetooth
  5. Stream music --> audio stutters and lags behind

After a while it solves itself (or after stopping and playing a few times). Looks to be related to how PA handles buffered audio in high latency scenarios.

lib: add retries to the connect method

The audio block takes a few seconds to startup so adding a few retries to the connect method would allow it enough time to initialise for applications that rely on it during startup. Example: connect requires /run/pulse/pulseaudio.cookie file to extist.

Error building nodejs example

the pulseaudio2 library from the nodjes example does not compile, but gives the error:

[Build]   > [email protected] install /usr/src/node_modules/pulseaudio2
[Build]   > node-gyp configure build
[Build]   [nodejs] make: Entering directory '/usr/src/node_modules/pulseaudio2/build'
[Build]   [nodejs]   CXX(target) Release/obj.target/pulse/src/context.o
[Build]   [nodejs] In file included from ../src/common.hh:28,
[Build]                    from ../src/context.hh:23,
[Build]                    from ../src/context.cc:20:
[Build]   ../../nan/nan.h: In function ‘void Nan::AsyncQueueWorker(Nan::AsyncWorker*)’:
[Build]   ../../nan/nan.h:2294:62: warning: cast between incompatible function types from ‘void (*)(uv_work_t*)’ {aka ‘void (*)(uv_work_s*)’} to ‘uv_after_work_cb’ {aka ‘void (*)(uv_work_s*, int)’} [-Wcast-function-type]
[Build]        , reinterpret_cast<uv_after_work_cb>(AsyncExecuteComplete)
[Build]                                                                 ^
[Build]   
[Build]   [nodejs] In file included from ../src/common.hh:27,
[Build]                    from ../src/context.hh:23,
[Build]                    from ../src/context.cc:20:
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h: In instantiation of ‘void v8::PersistentBase<T>::SetWeak(P*, typename v8::WeakCallbackInfo<P>::Callback, v8::WeakCallbackType) [with P = node::ObjectWrap; T = v8::Object; typename v8::WeakCallbackInfo<P>::Callback = void (*)(const v8::WeakCallbackInfo<node::ObjectWrap>&)]’:
[Build]   /root/.cache/node-gyp/10.22.0/include/node/node_object_wrap.h:84:78:   required from here
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h:9502:16: warning: cast between incompatible function types from ‘v8::WeakCallbackInfo<node::ObjectWrap>::Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<node::ObjectWrap>&)’} to ‘Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<void>&)’} [-Wcast-function-type]
[Build]                   reinterpret_cast<Callback>(callback), type);
[Build]                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[Build]   
[Build]   [nodejs] /root/.cache/node-gyp/10.22.0/include/node/v8.h: In instantiation of ‘void v8::PersistentBase<T>::SetWeak(P*, typename v8::WeakCallbackInfo<P>::Callback, v8::WeakCallbackType) [with P = Nan::ObjectWrap; T = v8::Object; typename v8::WeakCallbackInfo<P>::Callback = void (*)(const v8::WeakCallbackInfo<Nan::ObjectWrap>&)]’:
[Build]   ../../nan/nan_object_wrap.h:65:61:   required from here
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h:9502:16: warning: cast between incompatible function types from ‘v8::WeakCallbackInfo<Nan::ObjectWrap>::Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<Nan::ObjectWrap>&)’} to ‘Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<void>&)’} [-Wcast-function-type]
[Build]   
[Build]   [nodejs]   CXX(target) Release/obj.target/pulse/src/stream.o
[Build]   [nodejs] In file included from ../src/common.hh:28,
[Build]                    from ../src/stream.hh:23,
[Build]                    from ../src/stream.cc:20:
[Build]   ../../nan/nan.h: In function ‘void Nan::AsyncQueueWorker(Nan::AsyncWorker*)’:
[Build]   ../../nan/nan.h:2294:62: warning: cast between incompatible function types from ‘void (*)(uv_work_t*)’ {aka ‘void (*)(uv_work_s*)’} to ‘uv_after_work_cb’ {aka ‘void (*)(uv_work_s*, int)’} [-Wcast-function-type]
[Build]        , reinterpret_cast<uv_after_work_cb>(AsyncExecuteComplete)
[Build]                                                                 ^
[Build]   
[Build]   [nodejs] In file included from ../src/common.hh:27,
[Build]                    from ../src/stream.hh:23,
[Build]                    from ../src/stream.cc:20:
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h: In instantiation of ‘void v8::PersistentBase<T>::SetWeak(P*, typename v8::WeakCallbackInfo<P>::Callback, v8::WeakCallbackType) [with P = node::ObjectWrap; T = v8::Object; typename v8::WeakCallbackInfo<P>::Callback = void (*)(const v8::WeakCallbackInfo<node::ObjectWrap>&)]’:
[Build]   /root/.cache/node-gyp/10.22.0/include/node/node_object_wrap.h:84:78:   required from here
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h:9502:16: warning: cast between incompatible function types from ‘v8::WeakCallbackInfo<node::ObjectWrap>::Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<node::ObjectWrap>&)’} to ‘Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<void>&)’} [-Wcast-function-type]
[Build]                   reinterpret_cast<Callback>(callback), type);
[Build]                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[Build]   
[Build]   [nodejs] /root/.cache/node-gyp/10.22.0/include/node/v8.h: In instantiation of ‘void v8::PersistentBase<T>::SetWeak(P*, typename v8::WeakCallbackInfo<P>::Callback, v8::WeakCallbackType) [with P = Nan::ObjectWrap; T = v8::Object; typename v8::WeakCallbackInfo<P>::Callback = void (*)(const v8::WeakCallbackInfo<Nan::ObjectWrap>&)]’:
[Build]   ../../nan/nan_object_wrap.h:65:61:   required from here
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h:9502:16: warning: cast between incompatible function types from ‘v8::WeakCallbackInfo<Nan::ObjectWrap>::Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<Nan::ObjectWrap>&)’} to ‘Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<void>&)’} [-Wcast-function-type]
[Build]   
[Build]   [nodejs]   CXX(target) Release/obj.target/pulse/src/uv-mainloop.o
[Build]   [nodejs] In file included from ../src/common.hh:28,
[Build]                    from ../src/context.hh:23,
[Build]                    from ../src/uv-mainloop.cc:20:
[Build]   ../../nan/nan.h: In function ‘void Nan::AsyncQueueWorker(Nan::AsyncWorker*)’:
[Build]   ../../nan/nan.h:2294:62: warning: cast between incompatible function types from ‘void (*)(uv_work_t*)’ {aka ‘void (*)(uv_work_s*)’} to ‘uv_after_work_cb’ {aka ‘void (*)(uv_work_s*, int)’} [-Wcast-function-type]
[Build]        , reinterpret_cast<uv_after_work_cb>(AsyncExecuteComplete)
[Build]                                                                 ^
[Build]   
[Build]   [nodejs] In file included from ../src/common.hh:27,
[Build]                    from ../src/context.hh:23,
[Build]                    from ../src/uv-mainloop.cc:20:
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h: In instantiation of ‘void v8::PersistentBase<T>::SetWeak(P*, typename v8::WeakCallbackInfo<P>::Callback, v8::WeakCallbackType) [with P = node::ObjectWrap; T = v8::Object; typename v8::WeakCallbackInfo<P>::Callback = void (*)(const v8::WeakCallbackInfo<node::ObjectWrap>&)]’:
[Build]   /root/.cache/node-gyp/10.22.0/include/node/node_object_wrap.h:84:78:   required from here
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h:9502:16: warning: cast between incompatible function types from ‘v8::WeakCallbackInfo<node::ObjectWrap>::Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<node::ObjectWrap>&)’} to ‘Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<void>&)’} [-Wcast-function-type]
[Build]                   reinterpret_cast<Callback>(callback), type);
[Build]                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[Build]   
[Build]   [nodejs] /root/.cache/node-gyp/10.22.0/include/node/v8.h: In instantiation of ‘void v8::PersistentBase<T>::SetWeak(P*, typename v8::WeakCallbackInfo<P>::Callback, v8::WeakCallbackType) [with P = Nan::ObjectWrap; T = v8::Object; typename v8::WeakCallbackInfo<P>::Callback = void (*)(const v8::WeakCallbackInfo<Nan::ObjectWrap>&)]’:
[Build]   ../../nan/nan_object_wrap.h:65:61:   required from here
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h:9502:16: warning: cast between incompatible function types from ‘v8::WeakCallbackInfo<Nan::ObjectWrap>::Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<Nan::ObjectWrap>&)’} to ‘Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<void>&)’} [-Wcast-function-type]
[Build]   
[Build]   [nodejs]   CXX(target) Release/obj.target/pulse/src/addon.o
[Build]   [nodejs] In file included from ../src/common.hh:28,
[Build]                    from ../src/addon.cc:20:
[Build]   ../../nan/nan.h: In function ‘void Nan::AsyncQueueWorker(Nan::AsyncWorker*)’:
[Build]   ../../nan/nan.h:2294:62: warning: cast between incompatible function types from ‘void (*)(uv_work_t*)’ {aka ‘void (*)(uv_work_s*)’} to ‘uv_after_work_cb’ {aka ‘void (*)(uv_work_s*, int)’} [-Wcast-function-type]
[Build]        , reinterpret_cast<uv_after_work_cb>(AsyncExecuteComplete)
[Build]                                                                 ^
[Build]   
[Build]   [nodejs] In file included from ../../nan/nan.h:56,
[Build]                    from ../src/common.hh:28,
[Build]                    from ../src/addon.cc:20:
[Build]   ../src/addon.cc: At global scope:
[Build]   /root/.cache/node-gyp/10.22.0/include/node/node.h:573:43: warning: cast between incompatible function types from ‘void (*)(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE)’ {aka ‘void (*)(v8::Local<v8::Object>)’} to ‘node::addon_register_func’ {aka ‘void (*)(v8::Local<v8::Object>, v8::Local<v8::Value>, void*)’} [-Wcast-function-type]
[Build]          (node::addon_register_func) (regfunc),                          \
[Build]                                              ^
[Build]   /root/.cache/node-gyp/10.22.0/include/node/node.h:607:3: note: in expansion of macro ‘NODE_MODULE_X’
[Build]      NODE_MODULE_X(modname, regfunc, NULL, 0)  // NOLINT (readability/null_usage)
[Build]      ^~~~~~~~~~~~~
[Build]   ../src/addon.cc:30:1: note: in expansion of macro ‘NODE_MODULE’
[Build]    NODE_MODULE(NODE_GYP_MODULE_NAME, node_pulseaudio_init)
[Build]    ^~~~~~~~~~~
[Build]   
[Build]   [nodejs] In file included from ../src/common.hh:27,
[Build]                    from ../src/addon.cc:20:
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h: In instantiation of ‘void v8::PersistentBase<T>::SetWeak(P*, typename v8::WeakCallbackInfo<P>::Callback, v8::WeakCallbackType) [with P = node::ObjectWrap; T = v8::Object; typename v8::WeakCallbackInfo<P>::Callback = void (*)(const v8::WeakCallbackInfo<node::ObjectWrap>&)]’:
[Build]   /root/.cache/node-gyp/10.22.0/include/node/node_object_wrap.h:84:78:   required from here
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h:9502:16: warning: cast between incompatible function types from ‘v8::WeakCallbackInfo<node::ObjectWrap>::Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<node::ObjectWrap>&)’} to ‘Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<void>&)’} [-Wcast-function-type]
[Build]                   reinterpret_cast<Callback>(callback), type);
[Build]                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[Build]   
[Build]   [nodejs] /root/.cache/node-gyp/10.22.0/include/node/v8.h: In instantiation of ‘void v8::PersistentBase<T>::SetWeak(P*, typename v8::WeakCallbackInfo<P>::Callback, v8::WeakCallbackType) [with P = Nan::ObjectWrap; T = v8::Object; typename v8::WeakCallbackInfo<P>::Callback = void (*)(const v8::WeakCallbackInfo<Nan::ObjectWrap>&)]’:
[Build]   ../../nan/nan_object_wrap.h:65:61:   required from here
[Build]   /root/.cache/node-gyp/10.22.0/include/node/v8.h:9502:16: warning: cast between incompatible function types from ‘v8::WeakCallbackInfo<Nan::ObjectWrap>::Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<Nan::ObjectWrap>&)’} to ‘Callback’ {aka ‘void (*)(const v8::WeakCallbackInfo<void>&)’} [-Wcast-function-type]
[Build]   
[Build]   [nodejs]   SOLINK_MODULE(target) Release/obj.target/pulse.node
[Build]   [nodejs]   COPY Release/pulse.node
[Build]   [nodejs] make: Leaving directory '/usr/src/node_modules/pulseaudio2/build'
[Build]   [nodejs] npm notice
[Build]   [nodejs]  created a lockfile as package-lock.json. You should commit this file.
[Build]   
[Build]   [nodejs] npm WARN
[Build]   [nodejs]  [email protected] No repository field.
[Build]   
[Build]   [nodejs] 
[Build]   
[Build]   [nodejs] updated 17 packages and audited 17 packages in 30.861s
[Build]   [nodejs] found 0 vulnerabilities
[Build]   [nodejs] Removing intermediate container e5c2898dd987
[Build]   [nodejs]  ---> 2a30e1f8fe2a
[Build]   [nodejs] Step 8/11 : RUN curl https://www.kozco.com/tech/LRMonoPhase4.wav --silent --output test.wav
[Build]   [nodejs]  ---> Running in 73e09e54b1cb
[Build]   [nodejs] Removing intermediate container 73e09e54b1cb
[Build]   [nodejs]  ---> 91a0804abef8
[Build]   [nodejs] Step 9/11 : CMD [ "balena-idle" ]
[Build]   [nodejs]  ---> Running in 164f59e18237
[Build]   [nodejs] Removing intermediate container 164f59e18237
[Build]   [nodejs]  ---> 5ae28e96056a
[Build]   [nodejs] Step 10/11 : LABEL io.resin.local.image=1
[Build]   [nodejs]  ---> Running in 9951b344c36c
[Build]   [nodejs] Removing intermediate container 9951b344c36c
[Build]   [nodejs]  ---> bb766cd1cffc
[Build]   [nodejs] Step 11/11 : LABEL io.resin.local.service=nodejs
[Build]   [nodejs]  ---> Running in 38ec633f7378
[Build]   [nodejs] Removing intermediate container 38ec633f7378
[Build]   [nodejs]  ---> e971a503f64a
[Build]   [nodejs] Successfully built e971a503f64a
[Build]   [nodejs] Successfully tagged local_image_nodejs:latest

[Live]    Waiting for device state to settle...
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
Error: No native build was found for platform=linux arch=x64 runtime=node abi=64 uv=1 libc=glibc node=10.21.0
    loaded from: /usr/bin/ref-napi

    at Function.load.path (/snapshot/versioned-source/node_modules/node-gyp-build/index.js:62:9)
    at load (/snapshot/versioned-source/node_modules/node-gyp-build/index.js:21:30)
    at Object.<anonymous> (/snapshot/versioned-source/node_modules/ref-napi/lib/ref.js:8:53)
    at Module._compile (pkg/prelude/bootstrap.js:1325:22)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10)
    at Module.load (internal/modules/cjs/loader.js:651:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:591:12)
    at Function.Module._load (internal/modules/cjs/loader.js:583:3)
    at Module.require (internal/modules/cjs/loader.js:690:17)
    at Module.require (pkg/prelude/bootstrap.js:1230:31)
    at require (internal/modules/cjs/helpers.js:25:18)
    at Object.<anonymous> (/snapshot/versioned-source/node_modules/net-keepalive/lib/index.js:24:9)
    at Module._compile (pkg/prelude/bootstrap.js:1325:22)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10)
    at Module.load (internal/modules/cjs/loader.js:651:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:591:12)

Have tried downgrading to node version 10 (from 12), downgrading pulseaudio to 0.3.1 (from 0.3.3), and tested on ARM 32-bit and 64-bit on the raspberry pi.

Cannot find card '0'

I'm trying to run the examples on Rpi 4 but I get Cannot find card '0'.

To reproduce:

  1. I start the audio service:
    balena-engine run --privileged -d -p 4317:4317 -v pulse:/run/pulse balenablocks/audio

  2. On my dev machine, I cd into the respective example directory and execute balena push <local-address>

  3. I start the bash to play the audio in a container:
    balena-engine exec -it <uuid> bash

root@d3f4184:/usr/src# mplayer test.wav
Creating config file: /root/.mplayer/config
MPlayer 1.4 (Debian), built with gcc-10 (C) 2000-2019 MPlayer Team
do_connect: could not connect to socket
connect: No such file or directory
Failed to open LIRC support. You will not be able to use your remote control.

Playing test.wav.
libavformat version 58.45.100 (external)
Audio only file format detected.
Load subtitles in ./
==========================================================================
Opening audio decoder: [pcm] Uncompressed PCM audio decoder
AUDIO: 48000 Hz, 2 ch, s16le, 1536.0 kbit/100.00% (ratio: 192000->192000)
Selected audio codec: [pcm] afm: pcm (Uncompressed PCM)
==========================================================================
AO: [pulse] Init failed: Connection refused
Failed to initialize audio driver 'pulse'
[AO_ALSA] alsa-lib: confmisc.c:767:(parse_card) cannot find card '0'
[AO_ALSA] alsa-lib: conf.c:4745:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
[AO_ALSA] alsa-lib: confmisc.c:392:(snd_func_concat) error evaluating strings
[AO_ALSA] alsa-lib: conf.c:4745:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
[AO_ALSA] alsa-lib: confmisc.c:1246:(snd_func_refer) error evaluating name
[AO_ALSA] alsa-lib: conf.c:4745:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
[AO_ALSA] alsa-lib: conf.c:5233:(snd_config_expand) Evaluate error: No such file or directory
[AO_ALSA] alsa-lib: pcm.c:2660:(snd_pcm_open_noupdate) Unknown PCM default
[AO_ALSA] Playback open error: No such file or directory
Failed to initialize audio driver 'alsa'
[AO SDL] Samplerate: 48000Hz Channels: Stereo Format s16le
[AO SDL] using aalib audio driver.
[AO SDL] Unable to open audio: No available audio device
Failed to initialize audio driver 'sdl:aalib'
Could not open/initialize audio device -> no sound.
Audio: no sound
Video: no video
root@d3f4184:/usr/src# python play.py
ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
ALSA lib confmisc.c:1246:(snd_func_refer) error evaluating name
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM default
Traceback (most recent call last):
  File "play.py", line 4, in <module>
    play_obj = wave_obj.play()
  File "/usr/local/lib/python3.5/site-packages/simpleaudio/shiny.py", line 20, in play
    self.bytes_per_sample, self.sample_rate)
  File "/usr/local/lib/python3.5/site-packages/simpleaudio/shiny.py", line 61, in play_buffer
    sample_rate)
_simpleaudio.SimpleaudioError: Error opening PCM device. -- CODE: -2 -- MSG: No such file or directory
root@d3f4184:/usr/src# node play.js
Running PLAY script with the following configuration:
PULSE_SERVER: unix:/run/pulse/pulseaudio.socket
PULSE_SINK: undefined
This script will grab a wav file and output audio to PULSE_SINK (if undefined sink will be default for PULSE_SERVER).
context: connecting
context: failed
events.js:291
      ^

Error: Access denied
    at /usr/src/node_modules/pulseaudio2/lib/pulse.js:121:21
    at processTicksAndRejections (internal/process/task_queues.js:79:11)
Emitted 'error' event on Context instance at:
    at /usr/src/node_modules/pulseaudio2/lib/pulse.js:123:22
    at processTicksAndRejections (internal/process/task_queues.js:79:11)
root@d3f4184:/usr/src# play test.wav
play FAIL sox: Sorry, there is no default audio device configured

After adding libsox-fmt-all to install_packages in examples/sox/Dockerfile.template

root@d3f4184:/usr/src# play test.wav
ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
ALSA lib conf.c:4745:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings
ALSA lib conf.c:4745:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
ALSA lib confmisc.c:1246:(snd_func_refer) error evaluating name
ALSA lib conf.c:4745:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5233:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2660:(snd_pcm_open_noupdate) Unknown PCM default
play FAIL ao: Could not open device: error 5
play FAIL sox: Sorry, there is no default audio device configured

Clarify docs

Make it clear how to use the block and what differs between alsa bridge, regular usage, etc.

pulseaudio daemon sometimes quits when running pactl commands

When running for example pactl list cards in pulsetest container the daemon gets shutdown with:

[pulseaudio] core.c: We are idle, quitting...
[pulseaudio] main.c: Daemon shutdown initiated.

As per this bug report, adding the flag --exit-idle-time=-1 prevents this from happening.

Issues with I2S Adafruit Amp

I'm trying to get this image working with the Adafruit I2S amp -> https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp

Supposedly it is supported as mentioned here: https://github.com/balenalabs/balena-sound/blob/master/docs/05-audio-interfaces.md#dtoverlay-values

I've managed to get it working on the host machine. (I'm using RaspbianLite and not balenaOS btw)

When I run this image for raspberrypi3 (docker run --privileged -d -p 4317:4317 balenablocks/audio:raspberrypi3) and exec inside it I can run the alsa debug commands:

aplay -l:

**** List of PLAYBACK Hardware Devices ****
card 0: dac [snd_rpi_hifiberry_dac], device 0: HifiBerry DAC HiFi pcm5102a-hifi-0 [HifiBerry DAC HiFi pcm5102a-hifi-0]
  Subdevices: 0/1
  Subdevice #0: subdevice #0

So it seems to recognize the speaker. However running the speaker-test command produces no sound (although the command runs successfully). The command I use is speaker-test -c2 --test=wav -w /usr/share/sounds/alsa/Front_Center.wav. The same command works (e.g. produces sound) if I run it outside the container (although I have to run it with sudo).

What can I try to make this work? Do I need to use the balenaOS host image?

Can't pull images?

These endpoints don't seem to work currently?

bh.cr/balenablocks/audio-<arch> or bhcr.io/balenablocks/audio-<arch> where <arch> is one of: rpi, armv7hf, aarch64 or amd64.
Building for machine name raspberrypi4-64, platform linux/arm64, pushing to dynamicdevices/balenablock-darkice
docker buildx build --no-cache -t dynamicdevices/balenablock-darkice:raspberrypi4-64 --load --platform linux/arm64 --file Dockerfile.raspberrypi4-64 .
[+] Building 1.1s (3/3) FINISHED
 => [internal] load build definition from Dockerfile.raspberrypi4-64                                                                                                                                              0.0s
 => => transferring dockerfile: 165B                                                                                                                                                                              0.0s
 => [internal] load .dockerignore                                                                                                                                                                                 0.0s
 => => transferring context: 2B                                                                                                                                                                                   0.0s
 => ERROR [internal] load metadata for bh.cr/balenablocks/audio-aarch64:latest                                                                                                                                    1.1s
------
 > [internal] load metadata for bh.cr/balenablocks/audio-aarch64:latest:
------
error: failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to create LLB definition: unexpected status code [manifests latest]: 403 Forbidden

Event hooks

Expose events such as “audio started/stopped playing” to allow other containers to react

Events:

  • start: A pulseaudio source started streaming
  • stop: A pulseaudio source stopped streaming

PulseAudio doesn't detect USB hot-plugging if no USB device was connected at startup

To replicate

  1. Start audio block
  2. Connect a USB sound card to your device

Running udev monitor or aplay -l will show that both udev and ALSA are picking up the sound card. However running pactl list sinks will yield no result. PulseAudio is ignoring the udev events it seems.

If you invert the order of operations however hotplugging works as intended:

  1. Connect a USB sound card to your device
  2. Start audio block

This might be related to how systemd ALSA rules are handled, and how PulseAudio interacts with that (seems a bit sketchy?). Some references here for further investigation:

No audio without "alsactl restore"

I am working on a flutter app "block" using balenalib/intel-nuc-ubuntu-node:14-focal-run and I have been unable to get ANY audio to play via alsa or pulse without first running alsactl restore.

Adding the following to my flutter container's Dockerfile seems to have fixed the audio and allows the audio block to work:

...
# Reload sound card drivers
RUN alsactl restore
...
CMD ["bash", "/usr/src/app/docker/start.sh"]

FYI, here's the whole working Dockerfile to running a native flutter app (MUCH better performance than using a web build with the browser block)...

Dockerfile.flutter

# BUILD STEP

FROM balenalib/intel-nuc-ubuntu-node:14-focal-build as build

ARG FLUTTER_LOCATION=/usr/src/flutter

# Move to app dir
WORKDIR /usr/src/app

# Install build dependencies
RUN install_packages \
  # Flutter
  git \
  unzip \
  clang \
  cmake \
  ninja-build \
  pkg-config \
  libgtk-3-dev \
  libblkid-dev \
  liblzma-dev \
  # Flutter pub dependencies
  vlc \
  libvlc-dev 

RUN git clone https://github.com/flutter/flutter.git -b stable ${FLUTTER_LOCATION}

ENV PATH="${PATH}:${FLUTTER_LOCATION}/bin"

RUN flutter config --enable-linux-desktop
RUN flutter doctor --verbose

COPY . .

RUN flutter build linux

# RUN STEP

FROM balenalib/intel-nuc-ubuntu-node:14-focal-run

# Move to app dir
WORKDIR /usr/src/app

# Install run dependencies
RUN install_packages \
  # Flutter
  libgtk-3-dev \
  vlc \
  libvlc-dev \
  # X Window System
  xserver-xorg \
  xserver-xorg-video-intel \
  x11-xserver-utils \
  xinit \
  # Audio Utilities
  alsa-utils \
  # Touchscreen
  xinput \
  # VNC Server
  x11vnc

# Reload sound card drivers
RUN alsactl restore

COPY --from=build /usr/src/app/build/linux build/linux
COPY --from=build /usr/src/app/docker docker

CMD ["bash", "/usr/src/app/docker/start.sh"]

DBus and X11 error

19.06.21 09:40:40 (+0000)  audio  W: [pulseaudio] server-lookup.c: Unable to contact D-Bus: org.freedesktop.DBus.Error.NotSupported: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
19.06.21 09:40:40 (+0000)  audio  W: [pulseaudio] main.c: Unable to contact D-Bus: org.freedesktop.DBus.Error.NotSupported: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11

We should suppress this error if it's not relevant (which I presume since it's about X11 and this is an audio block). It's probably alarming for users trying to root cause "no audio" issues.

Add support for Nano Pi Air

A user has asked about adding support for this container on the nano pi air in the docker registry for the balena sound project

Bluetooth support

  • Enable bluetooth chips by default for pulseaudio usage.
  • Provide a way to disable bluetooth
  • Test in smartphones

bluetooth: audio takes a few seconds to start

Description

  • Audio takes a few seconds to start, not playing.
  • bluetooth disconnects
  • bluetooth automatically reconnects and audio only starts to play

Affected devices
This is likely related to headset_audio_gateway profiles getting in the way, or at least I haven't seen this problem with a2dp only devices.

Steps to reproduce
It happens occasionally when starting playback, specially after at least 5 seconds of not streaming audio (5 seconds is the default time where PA sources go from RUNNING to IDLE).

Logs

[pulseaudio] backend-native.c: Lost RFCOMM connection.
[pulseaudio] bluez5-util.c: Transport /org/bluez/hci0/dev_40_4E_36_42_9A_36/fd30 state: idle -> disconnected
[pulseaudio] card.c: Setting card bluez_card.40_4E_36_42_9A_36 profile headset_audio_gateway to availability status no
[pulseaudio] device-port.c: Setting port phone-output to status no
[pulseaudio] core-subscribe.c: Dropped redundant event due to change event.
[pulseaudio] bluez5-util.c: Properties changed in device /org/bluez/hci0/dev_40_4E_36_42_9A_36
[pulseaudio] bluez5-util.c: dbus: path=/MediaEndpoint/A2DPSink/sbc, interface=org.bluez.MediaEndpoint1, member=ClearConfiguration
[pulseaudio] bluez5-util.c: Clearing transport /org/bluez/hci0/dev_40_4E_36_42_9A_36/fd153 profile a2dp_source
[pulseaudio] bluez5-util.c: Transport /org/bluez/hci0/dev_40_4E_36_42_9A_36/fd153 state: idle -> disconnected
[pulseaudio] card.c: Setting card bluez_card.40_4E_36_42_9A_36 profile a2dp_source to availability status no
[pulseaudio] device-port.c: Setting port phone-input to status no
[pulseaudio] core-subscribe.c: Dropped redundant event due to change event.
[pulseaudio] module-bluez5-discover.c: Unregistering module for /org/bluez/hci0/dev_40_4E_36_42_9A_36
[pulseaudio] module-bluez5-device.c: Unloading module for device /org/bluez/hci0/dev_40_4E_36_42_9A_36
[pulseaudio] card.c: Freed 2 "bluez_card.40_4E_36_42_9A_36"
[pulseaudio] core-subscribe.c: Dropped redundant event due to remove event.
[pulseaudio] module.c: Unloaded "module-bluez5-device" (index: #28).
[pulseaudio] bluez5-util.c: Properties changed in device /org/bluez/hci0/dev_40_4E_36_42_9A_36
[pulseaudio] bluez5-util.c: dbus: path=/MediaEndpoint/A2DPSink/sbc, interface=org.bluez.MediaEndpoint1, member=SelectConfiguration
[pulseaudio] bluez5-util.c: Unknown interface org.freedesktop.DBus.Introspectable found, skipping
[pulseaudio] bluez5-util.c: Unknown interface org.bluez.MediaTransport1 found, skipping
[pulseaudio] bluez5-util.c: Unknown interface org.freedesktop.DBus.Properties found, skipping
[pulseaudio] bluez5-util.c: dbus: path=/MediaEndpoint/A2DPSink/sbc, interface=org.bluez.MediaEndpoint1, member=SetConfiguration
[pulseaudio] bluez5-util.c: Transport /org/bluez/hci0/dev_40_4E_36_42_9A_36/fd154 state: disconnected -> idle

rpi4: AUDIO_OUTPUT selection not working

Setting AUDIO_OUTPUT to any RPI_ option like RPI_HEADPHONES produces the following error:

02.11.20 16:50:26 (-0800)  audio  Invalid card number.
02.11.20 16:50:26 (-0800)  audio  Usage: amixer <options> [command]
02.11.20 16:50:26 (-0800)  audio 

This happens on a Pi4 running balenaOS 2.58.6+rev1

Audio mixer

Expose means to manipulate volume. Could be via an API or GUI.

  • setVolume

  • getVolume

bluetooth: choppy/stuttery audio when starting playback

Description

  • Audio starts to play but it's a bit choppy, and it lags behind noticeably.

Steps to reproduce
Could not isolate a reproducible scenario but encountered this a few times with my Android phone. I suspect it's related to one of the following:

  • shitty bluetooth radio on my phone
  • additional pulseaudio card profile headset_head_unit: Headset Head Unit (HSP/HFP) present in this phone but not in other devices

Dockerfile

Create a minimal dockerfile capable of running pulseaudio daemon

  • Debian based dockerfile
  • Alpine based dockerfile; to reduce image size. (see #18)
  • Allow extending pulseaudio configuration

Disable console-kit and ofono modules

They are not necessary and produce errors below since we don't have pulseaudio dbus interface in balenaOS.

console-kit:

02.06.20 16:35:21 (-0300)  pulseaudio  E: [pulseaudio] module-console-kit.c: GetSessionsForUnixUser() call failed: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.ConsoleKit was not provided by any .service files
02.06.20 16:35:21 (-0300)  pulseaudio  E: [pulseaudio] module.c: Failed to load module "module-console-kit" (argument: ""): initialization failed.

ofono:

02.06.20 16:35:21 (-0300)  pulseaudio  E: [pulseaudio] backend-ofono.c: Failed to register as a handsfree audio agent with ofono: org.freedesktop.DBus.Error.ServiceUnknown: The name org.ofono was not provided by any .service files

ALSA support

Device sound cards will be preconfigured and exposed to pulseaudio for supported devices with no extra configuration needed.

Raspberry Pi 3:

  • Audio jack
  • HDMI

Raspberry Pi 4:

  • Audio jack
  • HDMI0
  • HDMI1

Investigate pulseaudio control via code

Investigate DBUS API

DBus communication with PulseAudio won’t work as it’s not currently supported in the hostOS. We could spin a local dbus instance so that other containers can connect to it via DBUS.

Consider using alpine based images

An alpine based image would need a few packages to be installed so before committing to migrating to alpine we need to make sure the image size gains are worth it.

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.