PulseAudio e reamostragem dos fluxos de áudio

Apesar de constantemente difamado, achincalhado, (injustamente na maioria das vezes) o PulseAudio foi um passo importante para modernizar o áudio no Linux. Ainda existem arestas a aparar, é claro, porém o mais importante podemos considerar pronto.

De forma simplificada, o que o PulseAudio faz é reunir todos os fluxos de áudio das aplicações, uniformizar a especificação das amostras (caso sejam diferentes) e enviar ao ALSA para ele fazer o som tocar. O que é usado do ALSA são os drivers (kernel) e a alsa-lib. O resto, os programas do pacote alsa-utils, não tem mais papel nenhum — pelo menos não quando tudo está funcionando bem. A detecção do hardware de som é toda automática por parte do udev, o que acabou com vários hacks que existiam antigamente.

Pois bem. Quando o ALSA "abre" o hardware de áudio numa detrminada especificação (resolução, taxa de amostragem), o hardware fica travado nesta especificação. Ou seja, se você envia um fluxo de áudio 16-bit, 44,1kHz para ele, você não pode enviar outro, ao mesmo tempo, que seja 24-bit, 48kHz, por exemplo. PS: em hardware de áudio profissional geralmente pode, mas estamos falando de hardware dos pobres mortais aqui.

Então dá para perceber que alguém precisa juntar tudo que o usuário estiver tocando, transformar na mesma especificação e depois enviar ao ALSA. Nos Tempos Antigos, quem fazia isso era a própria alsa-lib. E basicamente só.

Com o PulseAudio, ele passou a ser responsável por esta tarefa, ao mesmo tempo que trouxe vários outros recursos, entre eles podemos destacar dois: controle independente de volume por aplicativo; capacidade de mudar de hardware on-the-fly (trocar da entrada de microfone analógica da placa de som para um microfone USB, por exemplo, sem interromper os aplicativos que estejam usando-o).

Por padrão o PulseAudio tenta configurar o hardware de som na especificação do CD, ou seja, resolução de 16-bit e taxa de amostragem de 44,1kHz. Quando o hardware não suporta alguma dessas características, o PA configura o que for mais próximo — na verdade, o PA conversa com a alsa-lib nesse processo.

Definida a especificação da amostra, todos os fluxos de áudio que o PulseAudio receber que não baterem com a taxa de amostragem configurada serão reamostrados (resample). Reamostrar demanda processamento. O PulseAudio suporta vários modos, desde códigos bem simples (e com resultado bem ruim) até códigos que beiram a precisão matemática (e que usam um caminhão de processamento).

Veja os modos suportados com:

$ pulseaudio --dump-resample-methods
src-sinc-best-quality
src-sinc-medium-quality
src-sinc-fastest
src-zero-order-hold
src-linear
trivial
speex-float-0
speex-float-1
speex-float-2
speex-float-3
speex-float-4
speex-float-5
speex-float-6
speex-float-7
speex-float-8
speex-float-9
speex-float-10
speex-fixed-0
speex-fixed-1
speex-fixed-2
speex-fixed-3
speex-fixed-4
speex-fixed-5
speex-fixed-6
speex-fixed-7
speex-fixed-8
speex-fixed-9
speex-fixed-10
ffmpeg
auto
copy
peaks

Se no arquivo /etc/pulse/daemon.conf a opção resample-method estiver comentada, é usado por padrão speex-float-3. As distribuições Linux costumam customizar (o Ubuntu usa speex-float-1). Os modos "speex" usam código da libspeex, que são equilibrados: qualidade decente sem moer o processador. "float" indica que necessitam de um processador com FPU (co-processador aritmético) e "fixed" não. Em desktops não faz muito sentido essa diferenciação, afinal temos FPU nos processadores desde o i486DX, porém em dispositivos embarcados é importante. Quanto menor o número dos modos "speex", menor o uso de processamento e menor a qualidade.

Para quem quer a melhor qualidade possível, existem os modos "src-", que usam a libsamplerate. Em ordem decrescente de qualidade (e de uso de processamento): src-sinc-best-quality, src-sinc-medium-quality, src-sinc-fastest, src-zero-order-hold, src-linear

src-sinc-best-quality é uma draga de processamento. Não se espante em ver o processo do PulseAudio consumindo 20% do seu Core i5 quando estiver fazendo reamostragem num par de fluxos de áudio. Das opções "src-" eu fico com src-sinc-fastest ou no máximo src-sinc-medium-quality.

Mudando a especificação da amostra

Em /etc/pulse/daemon.conf

default-sample-format = s16le
default-sample-rate = 44100

(caso estejam comentadas essas linhas, o padrão s16le 44100Hz é usado, ou o que o ALSA disponibilizar para o PA)

Formatos possíveis: u8, s16le, s16be, s24le, s24be, s24-32le, s24-32be, s32le, s32be float32le, float32be, ulaw, alaw
Taxas de amostragem comuns: 44100, 48000, 96000, 192000

Porém precisamos saber o que o hardware suporta, ou melhor, o que o driver suporta. Infelizmente não achei nada em /proc ou /sys que seja consistente para obter essa informação. Na lista de discussão do ALSA, foi postado tempos atrás um pequeno programa em C que serve para este fim: http://article.gmane.org/gmane.linux.alsa.user/32525

$ gcc -o hw_params hw_params.c -lasound

Requer, além do gcc, do pacote de desenvolvimento da alsa-lib. No Debian e derivados é o pacote libasound2-dev; no Fedora e derivados é o alsa-lib-devel.

Saída na minha SoundBlaster Live! 5.1:

$ ./hw_params
Device: hw (type: HW)
Access types: MMAP_INTERLEAVED RW_INTERLEAVED
Formats: U8 S16_LE
Channels: 1 2
Sample rates: 4000-96000
Interrupt interval: 166-16384000 us
Buffer size: 666-16384000 us

Saída do áudio integrado da placa-mãe Gigabyte GA-8VM800M:

$ ./hw_params
Device: hw (type: HW)
Access types: MMAP_INTERLEAVED RW_INTERLEAVED
Formats: U8 S16_LE
Channels: 1 2
Sample rates: 48000
Interrupt interval: 166-682667 us
Buffer size: 333-1365334 us

A diferença que salta aos olhos é que a taxa de amostargem na Live! é configurável entre 4kHz e 96kHz, enquanto no áudio integrado da Gigabyte é fixa em 48kHz.

Levando em conta que boa parte do que se toca num PC hoje em dia são fluxos de áudio de 44,1kHz (MP3 ripadas de CDs, maioria dos vídeos do YouTube, etc.), ter o hardware de áudio fixo em 48kHz faz o PulseAudio ter que reamostrar todos os fluxos de áudio. Por outro lado, vídeos ripados de DVDs (ou os DVDs em si) costumam ter áudio a 48kHz, que não precisariam de reamostragem.

Consultando o PulseAudio

http://www.pulseaudio.org/wiki/CLI

Áudio integrado da placa-mãe Gigabyte GA-8VM800M:

$ pacmd list-sinks
Welcome to PulseAudio! Use "help" for usage information.
>>> 1 sink(s) available.
  * index: 0
    name: <alsa_output.pci-0000_00_11.5.analog-stereo>
    driver: <module-alsa-card.c>
    flags: HARDWARE HW_MUTE_CTRL HW_VOLUME_CTRL DECIBEL_VOLUME LATENCY DYNAMIC_LATENCY
    state: RUNNING
    suspend cause:
    priority: 9959
    volume: 0:  41% 1:  41%
            0: -23,32 dB 1: -23,32 dB
            balance 0,00
    base volume:  63%
                 -12,00 dB
    volume steps: 65537
    muted: no
    current latency: 19,58 ms
    max request: 3 KiB
    max rewind: 64 KiB
    monitor source: 0
    sample spec: s16le 2ch 48000Hz
    channel map: front-left,front-right
                 Stereo
    used by: 1
    linked by: 1
    configured latency: 20,00 ms; range is 0,50 .. 341,33 ms
    card: 0 <alsa_card.pci-0000_00_11.5>
    module: 4
    properties:
        alsa.resolution_bits = "16"
        device.api = "alsa"
        device.class = "sound"
        alsa.class = "generic"
        alsa.subclass = "generic-mix"
        alsa.name = "VIA 8237"
        alsa.id = "VIA 8237"
        alsa.subdevice = "0"
        alsa.subdevice_name = "subdevice #0"
        alsa.device = "0"
        alsa.card = "0"
        alsa.card_name = "VIA 8237"
        alsa.long_card_name = "VIA 8237 with ALC655 at 0xbc00, irq 22"
        alsa.driver_name = "snd_via82xx"
        device.bus_path = "pci-0000:00:11.5"
        sysfs.path = "/devices/pci0000:00/0000:00:11.5/sound/card0"
        device.bus = "pci"
        device.vendor.id = "1106"
        device.vendor.name = "VIA Technologies, Inc."
        device.product.id = "3059"
        device.product.name = "VT8233/A/8235/8237 AC97 Audio Controller"
        device.form_factor = "internal"
        device.string = "front:0"
        device.buffering.buffer_size = "65536"
        device.buffering.fragment_size = "32768"
        device.access_mode = "mmap+timer"
        device.profile.name = "analog-stereo"
        device.profile.description = "Estéreo analógico"
        device.description = "Áudio interno Estéreo analógico"
        alsa.mixer_name = "Realtek ALC655 rev 1"
        alsa.components = "AC97a:414c4761"
        module-udev-detect.discovered = "1"
        device.icon_name = "audio-card-pci"
    ports:
        analog-output;output-amplifier-on: Saída analógica / Amplificador (priority 9910)
        analog-output;output-amplifier-off: Saída analógica / Sem amplificador (priority 9900)
        analog-output-mono;output-amplifier-on: Saída analógica monofônica / Amplificador (priority 5010)
        analog-output-mono;output-amplifier-off: Saída analógica monofônica / Sem amplificador (priority 5000)
        analog-output-lfe-on-mono;output-amplifier-on: Saída analógica (LFE) / Amplificador (priority 4010)
        analog-output-lfe-on-mono;output-amplifier-off: Saída analógica (LFE) / Sem amplificador (priority 4000)
    active port: <analog-output;output-amplifier-on>
>>>

sample spec: s16le 2ch 48000Hz é a forma como o PA configurou o ALSA.

$ pacmd list-sink-inputs
Welcome to PulseAudio! Use "help" for usage information.
>>> 1 sink input(s) available.
    index: 2
    driver: <protocol-native.c>
    flags:
    state: RUNNING
    sink: 0 <alsa_output.pci-0000_00_11.5.analog-stereo>
    volume: 0: 100% 1: 100%
            0: 0,00 dB 1: 0,00 dB
            balance 0,00
    muted: no
    current latency: 482,99 ms
    requested latency: 20,00 ms
    sample spec: s16le 2ch 44100Hz
    channel map: front-left,front-right
                 Stereo
    resample method: src-sinc-fastest
    module: 8
    client: 8 <ALSA plug-in [chromium]>
    properties:
        media.name = "ALSA Playback"
        application.name = "ALSA plug-in [chromium]"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "16"
        application.process.id = "793"
        application.process.user = "marcos"
        application.process.host = "PENTIUM4"
        application.process.binary = "chromium"
        window.x11.display = ":0.0"
        application.language = "pt_BR.UTF-8"
        application.process.machine_id = "a9cc6e94ec58167d02a07e8500000206"
        application.process.session_id = "a9cc6e94ec58167d02a07e8500000206-1307821349.641491-1084107650"
        application.icon_name = "chromium"
        module-stream-restore.id = "sink-input-by-application-name:ALSA plug-in [chromium]"
>>>

Aqui um cliente conectado ao PA (Flash 10.3 no Chromium) via plugin pulse de emulação do ALSA pelo PA. Como o áudio é 44,1kHz (sample spec: s16le 2ch 44100Hz) e o ALSA está usando 48kHz (a única taxa de amostragem suportada pelo driver), o PA está reamostrando via libsamplerate "fastest" (resample method: src-sinc-fastest), que eu configurei no /etc/pulse/daemon.conf com a opção resample-method. Para baixar o uso de processamento, posso escolher um modo de reamostragem mais simples, como os da libspeex.

SoundBlaster Live! 5.1:

$ pacmd list-sinks
Welcome to PulseAudio! Use "help" for usage information.
>>> 1 sink(s) available.
  * index: 0
    name: <alsa_output.pci-0000_03_05.0.analog-stereo>
    driver: <module-alsa-card.c>
    flags: HARDWARE HW_MUTE_CTRL HW_VOLUME_CTRL DECIBEL_VOLUME LATENCY DYNAMIC_LATENCY
    state: RUNNING
    suspend cause:
    priority: 9059
    volume: 0:  22% 1:  22%
            0: -39,67 dB 1: -39,67 dB
            balance 0,00
    base volume:  63%
                 -12,00 dB
    volume steps: 65537
    muted: no
    current latency: 89,64 ms
    max request: 15 KiB
    max rewind: 64 KiB
    monitor source: 0
    sample spec: s16le 2ch 44100Hz
    channel map: front-left,front-right
                 Stereo
    used by: 1
    linked by: 1
    configured latency: 90,00 ms; range is 0,50 .. 371,52 ms
    card: 0 <alsa_card.pci-0000_03_05.0>
    module: 4
    properties:
        alsa.resolution_bits = "16"
        device.api = "alsa"
        device.class = "sound"
        alsa.class = "generic"
        alsa.subclass = "generic-mix"
        alsa.name = "ADC Capture/Standard PCM Playback"
        alsa.id = "emu10k1"
        alsa.subdevice = "0"
        alsa.subdevice_name = "subdevice #0"
        alsa.device = "0"
        alsa.card = "0"
        alsa.card_name = "SB Live! 5.1 [SB0220]"
        alsa.long_card_name = "SB Live! 5.1 [SB0220] (rev.10, serial:0x80651102) at 0xe800, irq 20"
        alsa.driver_name = "snd_emu10k1"
        device.bus_path = "pci-0000:03:05.0"
        sysfs.path = "/devices/pci0000:00/0000:00:14.4/0000:03:05.0/sound/card0"
        device.bus = "pci"
        device.vendor.id = "1102"
        device.vendor.name = "Creative Labs"
        device.product.id = "0002"
        device.product.name = "SB Live! EMU10k1"
        device.string = "front:0"
        device.buffering.buffer_size = "65536"
        device.buffering.fragment_size = "65536"
        device.access_mode = "mmap+timer"
        device.profile.name = "analog-stereo"
        device.profile.description = "Estéreo analógico"
        device.description = "SB Live! EMU10k1 Estéreo analógico"
        alsa.mixer_name = "SigmaTel STAC9708,11"
        alsa.components = "AC97a:83847608"
        module-udev-detect.discovered = "1"
        device.icon_name = "audio-card-pci"
    ports:
        output-amplifier-on: Amplificador (priority 10)
        output-amplifier-off: Sem amplificador (priority 0)
    active port: <output-amplifier-on>
>>>

Aqui o PA configurou o ALSA em 44,1kHz (sample spec: s16le 2ch 44100Hz), que é suportado pelo driver.

$ pacmd list-sink-inputs
Welcome to PulseAudio! Use "help" for usage information.
>>> 1 sink input(s) available.
    index: 59
    driver: <protocol-native.c>
    flags: START_CORKED
    state: RUNNING
    sink: 0 <alsa_output.pci-0000_03_05.0.analog-stereo>
    volume: 0: 100% 1: 100%
            0: 0,00 dB 1: 0,00 dB
            balance 0,00
    muted: no
    current latency: 104,74 ms
    requested latency: 90,00 ms
    sample spec: float32le 2ch 44100Hz
    channel map: front-left,front-right
                 Stereo
    resample method: copy
    module: 8
    client: 85 <Banshee>
    properties:
        media.name = "\"The Best\" por \"Tina Turner\""
        application.name = "Banshee"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "16"
        application.process.id = "4170"
        application.process.user = "marcos"
        application.process.host = "ATHLON"
        application.process.binary = "mono"
        application.icon_name = "media-player-banshee"
        window.x11.display = ":0.0"
        application.language = "pt_BR.UTF-8"
        application.process.machine_id = "f9d0817e2c946b346ff241e30000000a"
        application.process.session_id = "f9d0817e2c946b346ff241e30000000a-1307733263.86396-659648757"
        module-stream-restore.id = "sink-input-by-application-name:Banshee"
        media.title = "The Best"
        media.artist = "Tina Turner"
>>>

Banshee tocando um arquivo MP3 de 44,1kHz. Essa é a mesma taxa de amostragem usada pelo ALSA; portanto, não é necessário reamostrar (resample method: copy).

Vale notar que o GStreamer (framework usado pelo Banshee) se comunica com o PA com resolução 32-bit float (sample spec: float32le 2ch 44100Hz), que previne, em teoria, clipping entre o tocador e o PA: http://xiph.org/video/vid1.shtml (14:13).

Para reiniciar o PulseAudio, você pode logar novamente, reiniciar a máquina, ou executar pulseaudio -k como usuário normal. Isso encerrará o programa, mas o PA será iniciado novamente em seguida automaticamente, relendo o arquivo de configuração.

Comentários