linux 音频播放的系统层问题

1. 音频框架简单介绍

linux 的音频管理是比较繁杂,我们的音频框架上层应用是的 js 服务, 中间层 ffmpeg + pulseaudio,底层的 alsa。 整个链路比较长,如何快速的定位播放问题是个问题,我对常见的问题做了总结

2. 常见问题的定位

2.1 设备无声

a. 确认各播放相关的进程是否正常,在内存珍贵的开发板上发送内存不足,进程被 OOM 一点也不奇怪,查看 pulseaudio 等播放相关进程被 kill

b. ffplay 确认中间层 ffmpeg 的基本功能

c. paplay 播放 wav 文件确认 paulseaudio 是否播放正常

d. pactl list 确认设备是不是被静音

e. pactl list sinks 检查 sink 流有木有被切到蓝牙设备上

f. aplay 播放 wav 文件确认 alsa 是否播放正常

g. 查看设备节点 ls dev/snd/

h. 检查喇叭是否连接好

备注:
  1. pulseaudio 可以直接管理一些流之外,它可以自主的切换输出流,比如切到 蓝牙设备上,耳机,蓝牙耳机很快切换 sink
  2. 可以使用 pactl 查看和设置当前 pulseaudio 的状态和每个sink、sink-input、source、source-output 的状态。
pactl -h
pactl [options] stat
pactl [options] info
pactl [options] list [short] [TYPE]
pactl [options] exit
pactl [options] upload-sample FILENAME [NAME]
pactl [options] play-sample  NAME [SINK]
pactl [options] remove-sample  NAME
pactl [options] load-module  NAME [ARGS ...]
pactl [options] unload-module  NAME|#N
pactl [options] move-(sink-input|source-output) #N SINK|SOURCE
pactl [options] suspend-(sink|source) NAME|#N 1|0
pactl [options] set-card-profile  CARD PROFILE
pactl [options] set-default-(sink|source) NAME
pactl [options] set-(sink|source)-port NAME|#N PORT
pactl [options] set-(sink|source)-volume NAME|#N VOLUME [VOLUME ...]
pactl [options] set-(sink-input|source-output)-volume #N VOLUME [VOLUME ...]
pactl [options] set-(sink|source)-mute NAME|#N 1|0|toggle
pactl [options] set-(sink-input|source-output)-mute #N 1|0|toggle
pactl [options] set-sink-formats #N FORMATS
pactl [options] set-port-latency-offset CARD-NAME|CARD-#N PORT OFFSET
pactl [options] subscribe

The special names @DEFAULT_SINK@, @DEFAULT_SOURCE@ and @DEFAULT_MONITOR@
can be used to specify the default sink, source and monitor.

常用的 pactl list 实时打印 pulseaudio 流的信息,主要的信息有:sink-input id, sink id (0 是默认 alsa 声卡), 流的 format 的信息,各声道的音量,是否被 mute,音量的通道,进程 id 和进程名

下图为 sink-inputs 的结果

pactl list sink-inputs
 
Sink Input #0
  Driver: protocol-native.c
  Owner Module: 3
  Client: 2
  Sink: 0
  Sample Specification: s16le 1ch 16000Hz
  Channel Map: mono
  Format: pcm, format.sample_format = "\"s16le\"" 
  format.rate = "16000" format.channels = "1"  format.channel_map = "\"mono\"" 
  Corked: no
  Mute: no
  Volume: mono: 46656 /  71% / -8.85 dB
          balance 0.00
  Buffer Latency: 0 usec
  Sink Latency: 50535 usec
  Resample method: ffmpeg
  Properties:
    media.name = "system"
    application.name = "simpleplay"
    native-protocol.peer = "UNIX socket client"
    native-protocol.version = "32"
    application.process.id = "1786"
    application.process.user = "root"
    application.process.host = "OpenWrt"
    application.process.binary = "iotjs"
    application.language = "C"
    application.process.machine_id = "c7f625e9718357b9bd7ff09b5d0790b1"
    module-stream-restore.id = "sink-input-by-media-name:system"

2.2 播放失败

一般情况下,可以通过日志打印初步确认是否是 js 上层的调用逻辑问题

其他的播放失败问题就主要在 ffmpeg 和 pulseaudio

2.2.1 ffmpeg 层错误

网络流媒体播放失败原因主要有网络问题,音频源服务端问题等,服务端的问题主要通过 tcpdump 抓包查看

a. 网络不好读不到包,ping 查看网络环境(最好 ping 音乐源的服务器)

b. 音乐源代理服务器出问题连接断掉,导致播放器提前读到 eof。
之前有个酷狗音乐源的,音乐的最后一段没播放完成,解决到方法是每次设备播放完成在做一次重连策略

c. gethostbyname() 域名解析失败阻塞,导致播放失败且会达到 8秒以上的阻塞

d. 有遇到一个设备无法播放酷狗服务器的任何 url,原因:音乐源做压力测试 ,那个网络出口,请求太多了,服务器认为异常攻击,把它的ip请求给封了

e. 如果是 adb 命令行 paplay 播放,播放一半后断掉,首先检查 adb 连接是否正常,可能是 paplay 进程在前台播放被终止了,将paplay放到后台测试。

f. seek 操作后无法正确获取音频数据,可能是有的音频服务端的没有conten-lenght 字段,ffmpeg http服务无法 seek。

2.2.2 pulseaudio 层

a. 声卡未准备成功,导致 pulseaudio 无法正常播放

b. 声卡被独占

2.3. 设备声音不合适

比如最小音量过大的问题,可以设置合适的音量曲线

2.4 底噪爆破音问题

2.4.1 问题定位

底噪和爆破音的问题定位,主要是通过录制各层生成的数据,然后在 PC 上通过 AdobeAudition 查看录制的文件确认来确认噪音数据到底是那层产生的

ffmpeg 到 pulseaudio 的链路:

在 ffplayer 代码中在 sdl_audio_callback 中有个 test 的宏打开可以录制 ffmpeg 给 pulseaudio 的 pcm 数据

pulseaudio 到 alsa 的链路:

下面是捕捉 pulseaudio 给 alsa 数据的脚本(现在直接存的 pcm 数据,要是数据量很大的话,也可以在存储的时候把 pcm 数据进行编码,不过要编译对应的编码程序)

PCM="$1"
if [ -z "$PCM" ]; then
    echo "Usage: $0 OUTPUT.PCM" >&2
    exit 1
fi
rm -f "$PCM"
# Get sink monitor:
MONITOR=$(pactl list | egrep -A2 '^(\*\*\* )?Source #' | \
    grep 'Name: .*\.monitor$' | awk '{print $NF}' | tail -n1)
echo "set-source-mute ${MONITOR} false" | pacmd >/dev/null
# Record it raw
echo "Recording to $PCM ..."
echo "Close this window to stop"
parec -d "$MONITOR" --format=s16le --channels=2 >> "$PCM"

2.4.1 解决方法

a. 底层功放限制,做最高音量的限制

b. 应用层可以做播放的淡入淡出操作,具体可以是添加静音数据或音量阶梯式恢复

c. 设备启动爆音,可以在 pulseaudio 的音量数据库中设置好启动音量,避免突然大音量导致的爆破

备注:

设备启动时日志抓取

a. 通过串口读日志

b. 读 logread 的缓存日志,需要分析 pulseaudio 的日志就先开启 pulseaudio log 并重定向到logread 系统,pulseaudio 的debug会有大量日志信息,logread的缓存区太小,抓不到更多信息,可以启动脚本修改 logread 的缓存区大小,获取更多缓存日志

2.5 卡断的问题

a. 网络卡顿

b. 缓冲区太小

c. 供电不足

2.6 内存泄露爆增问题

2.6.1 内存问题

内存问题一般分为传统的内存泄露和大量内存被 still reachable

内存泄露

a. new 和delete ,malloc 和free 不匹配使用造成的泄露

b. 申请的动态内存没有释放

c. remalloc 泄露

glibc 等内存管理引起内存爆增

a. linux 底层内存管理的策略

b. 程序设计无限制的申请大量内存作为缓冲区

2.6.2 问题定位

难点

a. 内存管理引起的内存爆增,通常是要某种场景内存使用达到某种峰值使得进程向系统申请内存,不好复现

b. 我们的播放器功能多主要有播放,倍速播放和不同音效播放,使用了不同的库,测试定位不方便

c. 我们的设备内存小,好多内存检测的工具和带符号表的文件不好推

解决方法

a. 带符号表的库 /usr/lib 放不下问题,可以推到 data 目录下,然后修改环境变量 LD_LIBRARY_PATH 添加 data 到加载路径

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/data                

b. 使用内存检查工具 valgrind 的 mencheck,基本可以检查出内存是否为泄露还是被进程管理,其中still reachable 说明是被内存管理,未还给系统的

==8302==    definitely lost: 96 bytes in 6 blocks
==8302==    indirectly lost: 0 bytes in 0 blocks
==8302==      possibly lost: 46,616 bytes in 1,317 blocks     
==8302==    still reachable: 814,079 bytes in 2,321 blocks
==8302==         suppressed: 0 bytes in 0 blocks

c. valgrind运行会占用大量内存,如果跑不起来可以使用谷歌工具 gperftools,gpeftoos 轻量很适合开发板的内存检查

2.7. cpu 占用高的问题

定位进程 cpu 问题

1.查看进程中的哪个线程cpu的占⽤用率⾼,查看高 cpu 的线程id
top -Hp ${pid}

2. 使用 strace 查看 CPU 占用高的线程当前的系统调用 
//注:tid 是通过 1 查看对高 cpu 的线程 id
strace -p ${tid}

3. 如果通过 2 的系统调用不能确认上层的使⽤的函数,使用 adbserver attach 
到该进程查看 cpu 高的线程的堆栈调用
// 注: adbserver 主要有2 个优势:
// 1.可以 attach 到正在运行的进程上进行调试
// 2. 可以在 PC 机上外部分析栈等,解决设备内存小不好推带符号表的库的问题

备注

gdbserver 的使用

gdbserver的使用

1.在设备端

// 在设备端运⾏ gdbserver,并 attach 到调试进程(ip是设备ip)         
gdbserver IP:PORT --attach ${pid}

2. 在 pc 端运行 

// 运行 gdb
gdb

// 连接 gdbserver
target remote IP:PORT

// 加载进程
file ${proc}

// 加载带符号表的动态库
set sysroot ${solib}

3. 进⾏ gdb 调试

实际操作中可能会出现进程突然attch的时候挂掉

你可能感兴趣的:(linux,音频)