Merge pull request #36 from UniversityRadioYork/mstratford/linux-audio

Linux updates for Studio1PC
This commit is contained in:
Matthew Stratford 2021-11-02 23:37:57 +00:00 committed by GitHub
commit 53b26e2366
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 113 additions and 21 deletions

2
.gitignore vendored
View file

@ -25,3 +25,5 @@ music-tmp/
presenter-build
node_modules/

View file

@ -7,7 +7,7 @@ build_branch="$(git branch --show-current)"
echo "BUILD: str = \"$build_commit\"" > ../build.py
echo "BRANCH: str = \"$build_branch\"" >> ../build.py
apt install libportaudio2
sudo apt install libportaudio2
python3 -m venv ../venv
source ../venv/bin/activate
@ -19,6 +19,8 @@ pip3 install -e ../
python3 ./generate-build-exe-config.py
chmod +x output/BAPSicle
python3 ./build-exe.py
bash ./build-exe-pyinstaller-command.sh

View file

@ -1,5 +1,5 @@
wheel
pygame==2.0.1
pygame==2.0.2
sanic==21.3.4
sanic-Cors==1.0.0
syncer==1.3.0

View file

@ -0,0 +1,24 @@
import os
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide"
os.putenv('SDL_AUDIODRIVER', 'pulseaudio')
import pygame._sdl2 as sdl2
import pygame
from pygame import mixer
pygame.init()
import time
mixer.init(44100, -16, 2, 1024)
is_capture = 0 # zero to request playback devices, non-zero to request recording devices
num = sdl2.get_num_audio_devices(is_capture)
names = [str(sdl2.get_audio_device_name(i, is_capture), encoding="utf-8") for i in range(num)]
mixer.quit()
for i in names:
print(i)
mixer.init(44100, -16, 2, 1024, devicename=i)
print(mixer.get_init())
mixer.music.load("/home/mstratford/Downloads/managed_play.mp3")
mixer.music.play()
# my_song = mixer.Sound("/home/mstratford/Downloads/managed_play.mp3")
# my_song.play()
time.sleep(5)
pygame.quit()

View file

@ -1,6 +1,13 @@
from typing import Any, Dict, List, Optional, Tuple
import sounddevice as sd
from helpers.os_environment import isLinux, isMacOS, isWindows
import os
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide"
if isLinux():
os.putenv('SDL_AUDIODRIVER', 'pulseaudio')
import pygame._sdl2 as sdl2
from pygame import mixer
import glob
if isWindows():
@ -20,10 +27,10 @@ class DeviceManager:
return host_api
@classmethod
def _getAudioDevices(cls) -> sd.DeviceList:
def _getSDAudioDevices(cls):
# To update the list of devices
# Sadly this doesn't work on MacOS.
if not isMacOS():
# Sadly this only works on Windows. Linux hangs, MacOS crashes.
if isWindows():
sd._terminate()
sd._initialize()
devices: sd.DeviceList = sd.query_devices()
@ -31,11 +38,13 @@ class DeviceManager:
@classmethod
def getAudioOutputs(cls) -> Tuple[List[Dict]]:
host_apis = sd.query_hostapis()
devices: sd.DeviceList = cls._getAudioDevices()
host_apis = list(sd.query_hostapis())
devices: sd.DeviceList = cls._getSDAudioDevices()
for host_api_id in range(len(host_apis)):
if isWindows() and host_apis[host_api_id]["name"] not in WINDOWS_APIS:
# Linux SDL uses PortAudio, which SoundDevice doesn't find. So mark all as unsable.
if (isWindows() and host_apis[host_api_id]["name"] not in WINDOWS_APIS) or (isLinux()):
host_apis[host_api_id]["usable"] = False
else:
host_apis[host_api_id]["usable"] = True
@ -51,6 +60,15 @@ class DeviceManager:
return host_apis
@classmethod
def getAudioDevices(cls) -> List[str]:
mixer.init(44100, -16, 2, 1024)
is_capture = 0 # zero to request playback devices, non-zero to request recording devices
num = sdl2.get_num_audio_devices(is_capture)
names = [str(sdl2.get_audio_device_name(i, is_capture), encoding="utf-8") for i in range(num)]
mixer.quit()
return names
@classmethod
def getSerialPorts(cls) -> List[Optional[str]]:
"""Lists serial port names

12
package-lock.json generated
View file

@ -1,5 +1,13 @@
{
"name": "bapsicle",
"version": "3.0.0",
"lockfileVersion": 1
"version": "3.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"yarn": {
"version": "1.22.15",
"resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.15.tgz",
"integrity": "sha512-AzoEDxj256BOS/jqDXA3pjyhmi4FRBBUMgYoTHI4EIt2EhREkvH0soPVEtnD+DQIJfU5R9bKhcZ1H9l8zPWeoA=="
}
}
}

View file

@ -24,5 +24,8 @@
"bugs": {
"url": "https://github.com/universityradioyork/bapsicle/issues"
},
"homepage": "https://github.com/universityradioyork/bapsicle#readme"
"homepage": "https://github.com/universityradioyork/bapsicle#readme",
"dependencies": {
"yarn": "^1.22.15"
}
}

View file

@ -21,8 +21,11 @@
# Stop the Pygame Hello message.
import os
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide"
from helpers.os_environment import isLinux
# It's the only one we could get to work.
if isLinux():
os.putenv('SDL_AUDIODRIVER', 'pulseaudio')
from queue import Empty
import multiprocessing

View file

@ -23,7 +23,7 @@ import json
from setproctitle import setproctitle
import psutil
from helpers.os_environment import isMacOS
from helpers.os_environment import isLinux, isMacOS
if not isMacOS():
# Rip, this doesn't like threading on MacOS.
@ -206,7 +206,9 @@ class BAPSicleServer:
time.sleep(1)
def startServer(self):
if isMacOS():
# On MacOS, the default causes something to keep creating new processes.
# On Linux, this is needed to make pulseaudio initiate properly.
if isMacOS() or isLinux():
multiprocessing.set_start_method("spawn", True)
process_title = "startServer"

View file

@ -33,6 +33,26 @@ Set for:
Default Audio Output
</code>
</p>
{% if data.sdl_direct %}
Linux (Pulse Audio)
<br>
<code>
{% for output in data.outputs %}
Set for:
{% for channel in data.channels %}
{% if not channel %}
Player {{loop.index0}}
{% elif channel.output == output %}
<strong>Player {{channel.channel}}</strong>
{% else %}
<a href="/player/{{channel.channel}}/output/{{output}}">Player {{channel.channel}}</a>
{% endif %}
/
{% endfor %}
{% if output %}{{output}}{% else %}System Default Output{% endif %}<br>
{% endfor %}
</code>
{% else %}
{% for host_api in data.outputs %}
{{host_api.name}}
<br>
@ -54,4 +74,5 @@ Default Audio Output
{% endfor %}
</code>
{% endfor %}
{% endif %}
{% endblock %}

View file

@ -17,6 +17,7 @@ import json
import os
from helpers.os_environment import (
isLinux,
resolve_external_file_path,
resolve_local_file_path,
)
@ -171,11 +172,16 @@ def ui_config_player(request):
for i in range(server_state.get()["num_channels"]):
channel_states.append(status(i))
outputs = DeviceManager.getAudioOutputs()
outputs = None
if isLinux():
outputs = DeviceManager.getAudioDevices()
else:
outputs = DeviceManager.getAudioOutputs()
data = {
"channels": channel_states,
"outputs": outputs,
"sdl_direct": isLinux(),
"ui_page": "config",
"ui_title": "Player Config",
}
@ -545,9 +551,12 @@ def WebServer(player_to: List[Queue], player_from: List[Queue], state: StateMana
)
except Exception:
break
loop = asyncio.get_event_loop()
if loop:
loop.close()
if app:
app.stop()
del app
try:
loop = asyncio.get_event_loop()
if loop:
loop.close()
if app:
app.stop()
del app
except Exception:
pass