Como reduzir o volume de um fluxo de música de fundo quando uma fonte de áudio diferente está sendo reproduzida?

7

Com o PulseAudio, é possível gerenciar o volume em uma base de aplicativo, mas acho pouco útil fazê-lo manualmente. O que eu prefiro ter é o seguinte: Eu geralmente estou ouvindo música, mas às vezes eu quero assistir a um vídeo do YouTube - então eu tenho que pausar ou reduzir manualmente o volume da música, muitas vezes eu esqueço de ligá-la quando o vídeo acabou.

O que eu preciso fazer para reduzir automaticamente o volume de um fluxo de áudio (a música de fundo) quando outro aplicativo reproduz o som?

    
por pantalaimon 06.03.2014 / 23:42

2 respostas

1

Não é muito de uma resposta, mas posso descrever os problemas / possibilidades que encontrei.

Não vejo como gravar um script de shell para isso. Não há nenhuma maneira (documentada) de alterar os volumes por aplicativo usando pactl / pacmd . Nem consigo ver uma maneira de saber quando um novo cliente é adicionado sem consultar repetidamente algo como:

pactl list short clients

O pulso de áudio pode ser configurado para logar através de syslog , então uma possibilidade é ter um script chamado via rsyslog (se a distro o tiver). Veja minha resposta aqui para uma indicação de como fazer isso. Isso obviamente depende de pulseaudio informações de registro sobre novos clientes.

Isso é definitivamente factível, o programa pavucontrol é um excelente exemplo desse tipo de coisa sendo feita. No entanto, atualmente parece que não há nenhuma CLI para fazer o mesmo material, portanto, provavelmente será necessária uma interface mais direta para a API pulseaudio .

Atualizar

Analisando o link do @derobert , o módulo de ducking de funções seria bastante fácil de ativar, mas requer a especificação de propriedades media.role . Eu não consigo encontrar de qualquer maneira para ver o que são estes! É provável que eles não estejam definidos para muitos fluxos (muitos programas ainda acham que estão usando o ALSA). Se houver alguma maneira listar estes e talvez configurá-los para ser atribuído (talvez com base no nome do processo), esta seria a maneira mais fácil.

Atualização 2

media.role pode ser definido por meio da variável ambiental PULSE_PROP . Por exemplo:

PULSE_PROP='media.role=music' play some_music.mp3 &
pactl list clients | grep -C 10 'media.role = "music"'

Isso pode ser definido para aplicativos diferentes editando .desktop arquivos e / ou criando scripts de wrapper, mas isso não parece ser uma maneira muito boa.

    
por 07.03.2014 / 02:25
1

As dicas nos comentários de @derobert e @ Graeme são úteis, mas o formato de saída é inconveniente, então escrevi um analisador para transformá-lo em 0 Name 0.0 , que é mais fácil de analisar. Ao fazer isso, também adicionei um ajudante para alterar o volume com facilidade.

É possível inserir isso em um loop e fazer o que você quiser. Isso está na linguagem Nim .

import osproc, strutils, pegs, os, parseopt2, sequtils

type Application = object
  id: int
  name: string
  volume: float

var verbose = false

proc tryExec(cmd): auto =
  if verbose: echo("executing $1" % [cmd])
  let output = execCmdEx(cmd)
  if output.exitCode != 0:
    raise newException(EBase, "Failed to execute '$1' with error code $2\nOutput:\n===\n$3\n===" %
                              [cmd, $output.exitCode, output.output])
  return output.output

iterator getPlayingApps(): auto =
  let paOutput = tryExec("pactl list sink-inputs")

  let sinkStrings = paOutput.split("\l\l")

  for str in sinkStrings:
    var num: int
    var volume: float
    var applicationName: string

    var matches = ["", ""]

    if str.find(peg"'Sink Input #'{\d+}", matches) != -1:
      num = parseInt(matches[0])
    else:
      raise newException(EBase, "Could not find sink input number in \n===\n$1" % [str])

    if str.find(peg"'Volume'@'/'\s*{\d+}'%'", matches) != -1:
      volume = parseFloat(matches[0])
    else:
      raise newException(EBase, "Could not find application volume in \n===\n$1" % [str])

    if str.find(peg"'application.name = ""'{[^""]+}'""'", matches) != -1:
      applicationName = matches[0]
    else:
      raise newException(EBase, "Could not find application name in \n===\n$1" % [str])

    yield Application(id: num, name: applicationName, volume: volume)

proc adjustAppVolume(app: Application, percent: float) =
  let nextVol = min((app.volume + (app.volume * (percent - 100) / 100.0)).int, 100)
  discard tryExec("pactl set-sink-input-volume $1 $2%" % [$app.id, $nextVol])

proc adjustName(name: string, percent: float) =
  for app in getPlayingApps():
    if app.name.contains(peg(name)):
      adjustAppVolume(app, percent)

proc printInfo() =
  for app in getPlayingApps():
    echo("$1\t$2\t$3" % [$app.id, app.name, $app.volume])

var params: seq[string] = @[]
for kind, key, val in getopt():
  case kind
  of cmdArgument:
    params.add(key)
  of cmdLongOption, cmdShortOption:
    case key
    of "help", "h":
      echo "usage: parsepa [-h|--help] [-v|--verbose] [<name expression> percent (-a|--adjust)] [-p|--print]"
      quit(QuitSuccess)
    of "verbose", "v":
      verbose = true
    of "adjust", "a":
      adjustName(params[0], parseFloat(params[1]))
      quit(QuitSuccess)
    of "print", "p":
      printInfo()
      quit(QuitSuccess)
    else:
      echo "Invalid Argument"
      quit(QuitFailure)
  else:
    echo "Invalid Argument"
    quit(QuitFailure)

x64 binário nesta essência, para o preguiçoso: link

    
por 02.11.2014 / 21:59

Tags