PromyLOPh / pianobar

Console-based pandora.com player
http://6xq.net/pianobar/
Other
1.74k stars 321 forks source link

Pianobar in Termux: /!\ Cannot open audio device #664

Open tcaddy opened 6 years ago

tcaddy commented 6 years ago

Cannot open audio device

I was able to get pianobar to compile in the Termux app on a Chromebook. However, when I try to run the app I get console errors that say:

/!\ Cannot open audio device

Pulseaudio is running. There is a Termux package called play-audio. It will play a sample MP3 file from the Termux command line.

Your environment

Steps to reproduce

Getting pianobar to compile in the Termux environment was tricky and I didn't document it, so this is my best guess. LMK if I missed anything.

install packages

Use the pkg install command to install:

install libao

There is no libao package available so I had to build it from source.

edit Makefile

On line 20 of the Make file, change the else block to return this: CC:=gcc -std=c99

make pianobar

(I had to use make b/c gmake isn't listed as a package in Termux)

start pulseaudio

pulseaudio

run pianobar

pianobar

Expected behaviour

It should play audio after getting station and song.

Actual behaviour

It gives the error message and skips to the next track.

/!\ Cannot open audio device
tcaddy commented 6 years ago

This is the readme for the play-audio application:https://github.com/termux/play-audio/blob/master/README.md

It says it is using OpenSL ES: https://www.khronos.org/opensles/

tcaddy commented 6 years ago

I also followed this troubleshooting for pulseaudio, which worked for playing a .WAV file.

The output of the list-sinks from the pacmd shell might be of interest:

$ pacmd
Welcome to PulseAudio 12.0! Use "help" for usage information.
>>> load-sample test /data/data/com.termux/files/home/test.wav
>>> list-sinks
1 sink(s) available.
  * index: 1
        name: <OpenSL_ES_sink>
        driver: <module-sles-sink.c>
        flags: DECIBEL_VOLUME LATENCY FLAT_VOLUME DYNAMIC_LATENCY
        state: SUSPENDED
        suspend cause: IDLE
        priority: 1000
        volume: front-left: 65536 / 100% / 0.00 dB,   front-right: 65536 / 100% / 0.00 dB
                balance 0.00
        base volume: 65536 / 100% / 0.00 dB
        volume steps: 65537
        muted: no
        current latency: 0.00 ms
        max request: 344 KiB
        max rewind: 344 KiB
        monitor source: 1
        sample spec: s16le 2ch 44100Hz
        channel map: front-left,front-right
                     Stereo
        used by: 0
        linked by: 0
        configured latency: 0.00 ms; range is 0.50 .. 2000.00 ms
        module: 16
        properties:
                device.description = "OpenSL ES Output"
                device.class = "abstract"
                device.icon_name = "audio-card"
>>> play-sample test OpenSL_ES_sink
Playing on sink input #0
>>> exit
PromyLOPh commented 6 years ago

Can you recall which audio backends were enabled by libao’s ./configure? It might be helpful to switch on libao’s debug output by adding debug=1 to ~/.libao. You can force the pulseaudio driver by adding default_driver=pulse to the same file.

tcaddy commented 6 years ago

I think the libao was configured for pulseaudio and something that might have been called "null output".

My libao conf file has a different name. I will try to rename it and add the debug option

tcaddy commented 6 years ago

With the debug option, it shows this error on startup:

debug: Loading driver plugins from /data/data/com.termux/files/usr/lib/ao/plugins-4...
ERROR: Failed to load plugin /data/data/com.termux/files/usr/lib/ao/plugins-4/libpulse.so => dlopen() failed

Looks like the problem is with libao.

edward-p commented 6 years ago

@tcaddy I think we need to port it by using another audio playback library that suitable for Android just like the project "play-audio", rather than using libao and pulse audio.

PromyLOPh commented 6 years ago

@tcaddy: I assume libpulse.so does not exist? @edward-p: Writing a plugin for libao would be the better solution imo. It’s really not that difficult and every other projects using libao benefits. Edit: Also I probably won’t accept pull requests adding support for other audio libraries, unless they are universal and replace libao on all supported platforms (i.e. no Android-specific solution).

edward-p commented 6 years ago

@PromyLOPh @tcaddy

It seems that libpulseaudio on termux isn't fully functional now, which can be tracked here Integrating ALSA and Pulseaudio into Termux #821

tomty89 commented 6 years ago

It's just dlopen in Android sucks / is broken. Use LD_PRELOAD=$PREFIX/lib/libpulse.so or add -lpulse to LIBAO_LDFLAGS in the Makefile.

$ cat test.c
#include <dlfcn.h>
#include <stdio.h>

int main() {
        void* shared_handle = dlopen("/data/data/com.termux/files/usr/local/lib/ao/plugins-4/libpulse.so", RTLD_GLOBAL | RTLD_NOW);
        if (shared_handle == NULL) {
                printf("dlopen failed: %s\n", dlerror());
                return 1;
        }
}
$ cc test.c
$ ./a.out
dlopen failed: dlopen failed: cannot locate symbol "pa_threaded_mainloop_new" referenced by "/data/data/com.termux/files/usr64/lib/libpulse-simple.so"...
$ LD_PRELOAD=$PREFIX/lib/libpulse.so ./a.out
$ cc test.c -lpulse
$ ./a.out
$
tcaddy commented 6 years ago

So this runs without error, but there is no sound: LD_PRELOAD=$PREFIX/lib/libpulse.so pianboar

edward-p commented 6 years ago

@tcaddy I patched configure.ac with LIBAO_LA_LDFLAGS="-lpulse" for building libao , it works fine without LD_PRELOAD=$PREFIX/lib/libpulse.so. see here Add packages: pianobar, libao #2641

edward-p commented 6 years ago

@tcaddy However, it only works when starting pulseaudio manully. No sound if pulseaudio daemon started by libao.

I found that pulseaudio in termux is also started with lots of preloaded libraries.

$ cat $(which pulseaudio)     
#!/data/data/com.termux/files/usr/bin/sh
export LD_PRELOAD=/data/data/com.termux/files/usr/lib/libandroid-glob.so:/data/data/com.termux/files/usr/lib/libpulse.so:/data/data/com.termux/files/usr/lib/libpulsecommon-12.0.so:/data/data/com.termux/files/usr/lib/libpulsecore-12.0.so
LD_LIBRARY_PATH=/system/lib64:/system/vendor/lib64:/data/data/com.termux/files/usr/lib exec /data/data/com.termux/files/usr/libexec/pulseaudio $@

So we need a script just like this:

#!/data/data/com.termux/files/usr/bin/sh
export LD_PRELOAD=/data/data/com.termux/files/usr/lib/libandroid-glob.so:/data/data/com.termux/files/usr/lib/libpulse.so:/data/data/com.termux/files/usr/lib/libpulsecommon-12.0.so:/data/data/com.termux/files/usr/lib/libpulsecore-12.0.so
LD_LIBRARY_PATH=/system/lib64:/system/vendor/lib64:/data/data/com.termux/files/usr/lib exec /data/data/com.termux/files/usr/libexec/pianobar $@
tcaddy commented 6 years ago

Correct, it works for me when I manually start pulseaudio, too.

This is pretty cool. Thanks for your help!

edward-p commented 6 years ago

@tcaddy But there still is an buffer underrun issue with pulseaudio which makes the sound choppy randomly.

PromyLOPh commented 6 years ago

Actually, this could be pianobar’s fault. If you look at the player code you will find it decodes chunks of data, then passes them to libao and only after libao returned, decodes the next chunk. It should do this in parallel.

edward-p commented 6 years ago

@PromyLOPh I think you're right. I added usleep(20000) after the ao_play function, this issue could be reproduced on my computer. It seems that mobile devices can't do decoding as fast as computer. We need fix it by doing decoding and playing in parallel with a buffer.

edward-p commented 6 years ago

@PromyLOPh I tried putting ao_play function in another thread which created by BarPlayerThread . The BarPlayerThread prepare frames to play and add them into a STAILQ list which defined in <sys/queue.h>. The other thread access the head of the queue each time, play it then remove it from the queue head. Although with some new issues, the sound is fine on my computer. Howerver, I still get the random choppy sounds on termux. This could be pulseaudio-on-termux's fault now.

PromyLOPh commented 6 years ago

Can you share your code? Does the problem exist with other programs that use pulseaudio on termux and don’t use libao (paplay for example)?

edward-p commented 6 years ago

@PromyLOPh here is my changes. It looks not so good, I think. Because I'm not familiar with ffmpeg, I can't rewrite the whole player. I tried cmus on termux which doesn't use libao, it works perfectly.

PromyLOPh commented 6 years ago

Well, it’s not too bad. Copying av frames/packets is obviously not the most efficient thing to do, but that should’t be the problem. Also the new AoPlayThread spins until queue_element!=NULL, which can consume a lot of CPU time. Still, in theory it should fix the issue.

A different approach would be to offload playback to an external program entirely. I already tried that, see https://github.com/PromyLOPh/pianobar/tree/externalplay and – except for a few unsolved issue – it works quite well. Unfortunately that branch is very outdated and not easily portable to master.

edward-p commented 6 years ago

@PromyLOPh That's an ingenious idea to use external program and pipe. I've learned a lot from you, thanks! Now I think temux users can, somehow, enjoy pianobar on their Android devices. XD :+1: I'll still try my way.

edward-p commented 6 years ago

@PromyLOPh I tried to avoid copying av frames/packets, and adjust the time when AoPlayThread being created. When decoding is done, insert an "EOF" element to the end of the queue and wait for AoPlayThread to quit (when the thread get the "EOF" element from the queue or when shouldQuit(player) becomes true). Now it works quite well on my phone. See https://github.com/edward-p/pianobar Still, I have no idea how to deal with the CPU time consuming caused by the thread spinning until queue_element!=NULL. No matter how, the choppy sound issue is already gone :).

edward-p commented 6 years ago

Now I added force pause feature. Do force pause at start or queue is run out until the queue size get back to 256(may need adjustment) or the decoding is done(EOF element added at the end of the queue) . See https://github.com/edward-p/pianobar/commit/d21016537191e6b26376e0ea05f16700c7f7d527

This may help with CPU time consumming issue.

edward-p commented 5 years ago

Rebased and sent pull request pull/665.

edward-p commented 5 years ago

Another issue may lead to random choppy sound.

$ termux-audio-info 
{
  "PROPERTY_OUTPUT_SAMPLE_RATE": "48000",
  "PROPERTY_OUTPUT_FRAMES_PER_BUFFER": "192",
  "AUDIOTRACK_SAMPLE_RATE": 48000,
  "AUDIOTRACK_BUFFER_SIZE_IN_FRAMES": 3844,
  "AUDIOTRACK_SAMPLE_RATE_LOW_LATENCY": 48000,
  "AUDIOTRACK_BUFFER_SIZE_IN_FRAMES_LOW_LATENCY": 384,
  "BLUETOOTH_A2DP_IS_ON": false,
  "WIREDHEADSET_IS_CONNECTED": true
}

As you can see AUDIOTRACK_SAMPLE_RATE_LOW_LATENCY=48000, PROPERTY_OUTPUT_SAMPLE_RATE=48000.

But in pulseaudio

$ pulseaudio --dump-conf
...
enable-lfe-remixing = no
lfe-crossover-freq = 0
default-sample-format = s16le
default-sample-rate = 44100
alternate-sample-rate = 48000
default-sample-channels = 2
default-channel-map = front-left,front-right
default-fragments = 4
default-fragment-size-msec = 25
...

The default-sample-rate is set to 44100 by default after pulseaudio installed on Termux.

This cause the audio sample need to be converted to 48000 by the android system's audioserver before output, which waste a lot of CPU resources (I have monitored it by htop)

So it's important to modify the $PREFIX/etc/pulse/daemon.cfg to match the default-sample-rate to your device.

tomty89 commented 5 years ago

While setting pulse sink rate to the current optimum is mostly a good thing to do, it shouldn't cause any underrun even if it is on a non-optimal rate (at least not with my recent rewrite of the sink module).

So the sound shouldn't be choppy unless your device is really low end that it literally can't afford Android's (redundant) resampling. You can report back with a pulse log (e.g. pulseaudio -vvv --daemonize=no &> ~/pulse.log &) anyway.

Note that 48kHz is not some universal optimum. The value depends on your (current) audio device and even your Android configuration. So there isn't really an issue in Termux/pulse on the respect.

SLES in Android provides no means for getting the current optimal rate. AAudio does though, while it is only available in Oreo or later. Might port the SLES sink module to an (extra) AAudio one when I have time.

edward-p commented 5 years ago

I think this could be the problem of LineageOS 15.1 I'm using, which is in development and unofficial build for a sdm845 device. I get the random choppy sound mostly when I'm using my phone(e.g. screen rotated). Here is the log: pulse.log No this issue on my older phone (also LineageOS 15.1).

p.s. pianobar talks to libao instead of talking to pulseaudio directly.

tomty89 commented 5 years ago

Actually I think it's a libao issue. Its code for negotiating buffer/latency with pulse doesn't seem to be really proper, while at the same time it requests really little of that (base on a 20ms factor). Normally the configured latency of the sink would prevent that from happening, but with libao's code weird things seem to happen.

There is a workaround, which is to set buffer_time in libao.conf. However libao seems to be broken in parsing its conf as well, in which the option can at max be set to 44, otherwise the value would become enormous somehow.

The reason that the underruns happen more often when a non-optimal rate is used is, Android has a higher buffer/latency requirement with such track. It also gets way higher if you were on Bluetooth audio, where 44 is hardly enough.

Didn't test with pianobar, but could reproduce similar problem with cmus if it outputs to pulse via libao. No problem if it outputs to pulse via libpulse directly.

edward-p commented 5 years ago

Yeah, I think you're right. Setting buffer_time in libao.conf works. Have you changed the AO_SYSTEM_CONFIG to /data/data/com.termux/files/usr/etc/libao.conf which defined in include/ao/ao_private.h, line 54?

However, libao still may need maintenance for Bluetooth audio, since 44 is hardly enough.

tomty89 commented 5 years ago

Nah I was testing with cmus/libao/libpulse in an Arch proot with the help of this trick. Didn't bother to build them under Termux.

This is what happen with buffer_time=44:

I: [pulseaudio] sink-input.c: Created input 0 "libao[cmus] playback stream" on OpenSL_ES_sink with sample spec s16le 2ch 48000Hz and channel map front-left,front-right
I: [pulseaudio] sink-input.c:     media.name = "libao[cmus] playback stream"
I: [pulseaudio] sink-input.c:     application.name = "libao[cmus]"
I: [pulseaudio] sink-input.c:     native-protocol.peer = "UNIX socket client"
I: [pulseaudio] sink-input.c:     native-protocol.version = "32"
I: [pulseaudio] sink-input.c:     application.process.id = "8352"
I: [pulseaudio] sink-input.c:     application.process.user = "tom"
I: [pulseaudio] sink-input.c:     application.process.host = "localhost"
I: [pulseaudio] sink-input.c:     application.process.binary = "cmus"
I: [pulseaudio] sink-input.c:     application.language = "C"
I: [pulseaudio] sink-input.c:     application.process.machine_id = "1e448bfd85d542a4a1290e5ad1baf413"
I: [pulseaudio] sink-input.c:     module-stream-restore.id = "sink-input-by-application-name:libao[cmus]"
I: [pulseaudio] protocol-native.c: Requested tlength=44.00 ms, minreq=11.00 ms
D: [pulseaudio] protocol-native.c: Adjust latency mode enabled, configuring sink latency to half of overall latency.
D: [pulseaudio] protocol-native.c: Requested latency=11.00 ms, Received latency=125.00 ms
D: [pulseaudio] memblockq.c: memblockq requested: maxlength=10560, tlength=28224, base=4, prebuf=26116, minreq=2112 maxrewind=0
D: [pulseaudio] memblockq.c: memblockq sanitized: maxlength=10560, tlength=10560, base=4, prebuf=8452, minreq=2112 maxrewind=0
I: [pulseaudio] protocol-native.c: Final latency 180.00 ms = 33.00 ms + 2*11.00 ms + 125.00 ms

This is what happen with buffer_time=45:

I: [pulseaudio] sink-input.c: Created input 0 "libao[cmus] playback stream" on OpenSL_ES_sink with sample spec s16le 2ch 48000Hz and channel map front-left,front-right
I: [pulseaudio] sink-input.c:     media.name = "libao[cmus] playback stream"
I: [pulseaudio] sink-input.c:     application.name = "libao[cmus]"
I: [pulseaudio] sink-input.c:     native-protocol.peer = "UNIX socket client"
I: [pulseaudio] sink-input.c:     native-protocol.version = "32"
I: [pulseaudio] sink-input.c:     application.process.id = "8393"
I: [pulseaudio] sink-input.c:     application.process.user = "tom"
I: [pulseaudio] sink-input.c:     application.process.host = "localhost"
I: [pulseaudio] sink-input.c:     application.process.binary = "cmus"
I: [pulseaudio] sink-input.c:     application.language = "C"
I: [pulseaudio] sink-input.c:     application.process.machine_id = "1e448bfd85d542a4a1290e5ad1baf413"
I: [pulseaudio] sink-input.c:     module-stream-restore.id = "sink-input-by-application-name:libao[cmus]"
I: [pulseaudio] protocol-native.c: Requested tlength=5592394.23 ms, minreq=5592394.21 ms
D: [pulseaudio] protocol-native.c: Adjust latency mode enabled, configuring sink latency to half of overall latency.
D: [pulseaudio] protocol-native.c: Requested latency=0.00 ms, Received latency=125.00 ms
D: [pulseaudio] memblockq.c: memblockq requested: maxlength=4194304, tlength=2147503376, base=4, prebuf=1073763690, minreq=1073739690 maxrewind=0
D: [pulseaudio] memblockq.c: memblockq sanitized: maxlength=4194304, tlength=4194304, base=4, prebuf=4, minreq=4194304 maxrewind=0
I: [pulseaudio] protocol-native.c: Final latency 21970.33 ms = 22347776.00 ms + 2*21845.33 ms + 125.00 ms
edward-p commented 5 years ago

Only two underruns get each song at the start with buffer_time=44 I think this is acceptable.

I: [pulseaudio] sink-input.c: Created input 0 "libao[pianobar] playback stream" on OpenSL_ES_sink with sample spec s16le 2ch 44100Hz and channel map front-left,front-right
I: [pulseaudio] sink-input.c:     media.name = "libao[pianobar] playback stream"
I: [pulseaudio] sink-input.c:     application.name = "libao[pianobar]"
I: [pulseaudio] sink-input.c:     native-protocol.peer = "UNIX socket client"
I: [pulseaudio] sink-input.c:     native-protocol.version = "32"
I: [pulseaudio] sink-input.c:     application.process.id = "11359"
I: [pulseaudio] sink-input.c:     application.process.user = "u0_a118"
I: [pulseaudio] sink-input.c:     application.process.host = "localhost"
I: [pulseaudio] sink-input.c:     application.process.binary = "pianobar"
I: [pulseaudio] sink-input.c:     application.process.machine_id = "localhost"
I: [pulseaudio] sink-input.c:     module-stream-restore.id = "sink-input-by-application-name:libao[pianobar]"
I: [pulseaudio] protocol-native.c: Requested tlength=43.99 ms, minreq=11.00 ms
D: [pulseaudio] protocol-native.c: Adjust latency mode enabled, configuring sink latency to half of overall latency.
D: [pulseaudio] protocol-native.c: Requested latency=11.00 ms, Received latency=125.00 ms
D: [pulseaudio] memblockq.c: memblockq requested: maxlength=9700, tlength=25932, base=4, prebuf=23996, minreq=1940 maxrewind=0
D: [pulseaudio] memblockq.c: memblockq sanitized: maxlength=9700, tlength=9700, base=4, prebuf=7764, minreq=1940 maxrewind=0
I: [pulseaudio] protocol-native.c: Final latency 179.99 ms = 32.99 ms + 2*11.00 ms + 125.00 ms
D: [sles-sink] protocol-native.c: max_request changed, trying to update from 9700 to 25932.
D: [sles-sink] protocol-native.c: Failed to increase tlength
D: [pulseaudio] sink.c: OpenSL_ES_sink: state: IDLE -> RUNNING
D: [pulseaudio] core-subscribe.c: Dropped redundant event due to change event.
D: [sles-sink] protocol-native.c: Requesting rewind due to end of underrun.
D: [sles-sink] protocol-native.c: Requesting rewind due to end of underrun.

No underrun get during the song is playing. I haven't tested with Bluetooth audio yet.

tomty89 commented 5 years ago

@edward-p Okay I found and fixed problem. You can find the patches here: https://github.com/xiph/libao/pull/8

edward-p commented 5 years ago

@tomty89 That's great, thanks! I will apply the first when building libao for termux.

PromyLOPh commented 5 years ago

Can either of you check whether the latest version of patch #665 fixes the underrun issue reported here?

edward-p commented 5 years ago

I've tested it with xiph/libao#8, works well on both of my Nexus 6P and Oneplus 6 in termux. I also tested it in WSL with a pulseaudio windows port sinking on local tcp, works fine too:)

iamdarkmatter commented 5 years ago

Works pretty good. Sometimes you have to force stop the APP if it doesn't recognize the device and keeps playing. Keeps running during phone calls. Great work guys! Big fan promy. Pianobar has definitely enriched my life. I read the man pages for pianobar but I do not see where I can store my creds for the mobile version. It is amazing what you guys have done I am still learning about packages on mobile.

edward-p commented 5 years ago

@iamlord

Termux has its own HOME variable just like all Unix-Likes. So you just create a $HOME/.config/pianobar/config like this:

password = your-password
user = yourname@example.com

Then pianobar can parse it and login automatically.