Controlar amplitude com o VolumeShaper

É possível usar um VolumeShaper em um app de áudio para realizar fade-ins, fade-outs, cross fades, redução de áudio e outras transições de volume curtas automatizadas. A classe VolumeShaper está disponível no Android 8.0 (API de nível 26) e versões posteriores.

Você cria um VolumeShaper chamando createVolumeShaper() em uma instância de AudioTrack ou MediaPlayer. O VolumeShaper só atua no áudio produzido pelo AudioTrack ou MediaPlayer que o criou.

VolumeShaper.Configuration

O comportamento de um VolumeShaper é definido pela VolumeShaper.Configuration dele. A configuração especifica *uma curva de volume, um tipo de interpolador e uma duração.*

Curva de volume

A curva de volume representa a mudança de amplitude ao longo do tempo. Ela é definida por um par de matrizes flutuantes, x[] e y[], que definem uma série de pontos de controle. Cada par (x, y) representa o tempo e o volume, respectivamente. As matrizes precisam ter o mesmo comprimento e conter no mínimo 2 e no máximo 16 valores. O comprimento máximo da curva é definido em getMaximumCurvePoints().

As coordenadas de tempo são fornecidas no intervalo [0.0, 1.0]. O primeiro ponto de tempo precisa ser 0.0, o último precisa ser 1.0 e os tempos precisam ser aumentados de forma monotônica.

As coordenadas do volume são especificadas em escala linear no intervalo [0.0, 1.0].

Tipo de interpolador

A curva de volume sempre passa pelos pontos de controle especificados. Os valores entre os pontos de controle são derivados por spline, de acordo com o tipo de interpolador da configuração. Há quatro constantes para os tipos de interpoladores VolumeShaper disponíveis:

  • VolumeShaper.Configuration.INTERPOLATOR_TYPE_STEP
  • VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR
  • VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC
  • VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC_MONOTONIC

Duração

As coordenadas de tempo especificadas no intervalo [0.0, 1.0] são dimensionadas para uma duração que você especifica em milissegundos. Isso determina a duração real no tempo da curva de volume quando o shaper está em execução e aplicando a curva à saída de áudio.

Usar um VolumeShaper

Criar uma configuração

Antes de criar uma VolumeShaper, você precisa criar uma instância de VolumeShaper.Configuration. Faça isso usando um VolumeShaper.Configuration.Builder():

Kotlin

    val config: VolumeShaper.Configuration = VolumeShaper.Configuration.Builder()
            .setDuration(3000)
            .setCurve(floatArrayOf(0f, 1f), floatArrayOf(0f, 1f))
            .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
            .build()
    

Java

    VolumeShaper.Configuration config =
      new VolumeShaper.Configuration.Builder()
          .setDuration(3000)
          .setCurve(new float[] {0.f, 1.f}, new float[] {0.f, 1.f})
          .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
          .build();
    

Sem argumentos, o construtor VolumeShaper.Configuration.Builder retorna um builder que cria uma configuração com definições padrão: INTERPOLATOR_TYPE_CUBIC, uma duração de um segundo e nenhuma curva. Adicione uma curva ao builder antes de chamar build().

O framework fornece constantes para configurações com curvas predefinidas, cada uma delas com duração de um segundo:

  • VolumeShaper.Configuration.LINEAR_RAMP
  • VolumeShaper.Configuration.CUBIC_RAMP
  • VolumeShaper.Configuration.SINE_RAMP
  • VolumeShaper.Configuration.SCURVE_RAMP

Como criar um VolumeShaper

Para criar um VolumeShaper, chame createVolumeShaper() em uma instância da classe apropriada, transmitindo um VolumeShaper.Configuration:

Kotlin

    volumeShaper = myMediaPlayer.createVolumeShaper(config)
    volumeShaper = myAudioTrack.createVolumeShaper(config)
    

Java

    volumeShaper = myMediaPlayer.createVolumeShaper(config);
    volumeShaper = myAudioTrack.createVolumeShaper(config);
    

Uma única faixa ou player de mídia pode ter muitos shapers anexados, e você pode controlar cada shaper individualmente. As saídas de todos os shapers em uma faixa ou player são multiplicadas em conjunto. Um VolumeShaper não pode ser compartilhado entre AudioTracks ou MediaPlayers, mas é possível usar a mesma configuração em chamadas para createVolumeShaper para criar shapers idênticos em vários AudioTracks ou MediaPlayers.

Quando você cria o shaper, o primeiro ponto de controle dele (em t = 0) é aplicado ao streaming de áudio. Se o volume inicial não for de 1.0 e seu app estiver tocando alguma mídia no momento da criação, o áudio poderá ter uma mudança abrupta no volume. O recomendado é começar a tocar áudio a partir do silêncio e usar um VolumeShaper para implementar um fade-in quando a reprodução começar. Crie um VolumeShaper que comece no volume 0 e aumente aos poucos. Exemplo:

setCurve(new float[] {0.f, 1.f}, new float[] {0.f, 1.f})
    

Inicie a reprodução e o shaper ao mesmo tempo. Isso garante que a reprodução comece a partir do silêncio e o volume suba para o máximo. Isso é explicado na próxima seção.

Executar um VolumeShaper

Embora o nível de volume do ponto de controle seja aplicado ao caminho do áudio assim que o shaper é criado, o shaper não seguirá com a curva até que você chame o método apply() com VolumeShaper.Operation.PLAY. Depois de criar o shaper, a primeira invocação de apply() precisa especificar a operação PLAY para iniciá-lo. Isso executa a curva do primeiro ponto de controle ao último:

Kotlin

    shaper.apply(VolumeShaper.Operation.PLAY)
    

Java

    shaper.apply(VolumeShaper.Operation.PLAY);
    

Enquanto o shaper estiver em execução, você poderá emitir chamadas apply() alternantes para especificar as operações REVERSE e PLAY. Isso muda a direção da leitura dos pontos de controle a cada vez.

O shaper ajusta o volume continuamente e passa por todos os pontos de controle até expirar. Isso acontece quando o shaper chega ao último (para a operação PLAY) ou ao primeiro (para a operação REVERSE) ponto de controle na curva.

Quando o shaper expira, o volume permanece na última configuração, que pode ser o primeiro ou o último ponto de controle. Você pode chamar VolumeShaper.getVolume() para o nível de volume atual a qualquer momento.

Depois que o shaper expirar, você pode emitir outra chamada apply() para executar a curva na direção oposta. Por exemplo, se o shaper expirou durante a execução de PLAY, o próximo apply() tem que ser REVERSE. Chamar PLAY depois que PLAY tiver expirado ou REVERSE depois que REVERSE tiver expirado não terá efeito.

Você precisa alternar as operações PLAY e REVERSE. Não é possível reproduzir uma curva do primeiro ao último ponto de controle e, em seguida, reiniciá-la do primeiro ponto de controle. Você pode usar o método replace(), descrito na seção a seguir, para substituir a curva por uma cópia de si mesma. Isso redefine o shaper, exigindo a operação PLAY para iniciá-lo novamente.

Mudar a curva

Use o método replace() para mudar a curva de um VolumeShaper. Esse método usa uma configuração, uma operação e um parâmetro de mesclagem. Você pode chamar o método replace() a qualquer momento, enquanto o shaper estiver em execução ou depois que ele expirar:

Kotlin

    val newConfig = VolumeShaper.Configuration.Builder()
            .setDuration(1000)
            .setCurve(floatArrayOf(0f, 0.5f), floatArrayOf(0f, 1f))
            .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
            .build()
    val join = true
    shaper.replace(newConfig, VolumeShaper.Operation.PLAY, join)
    

Java

    VolumeShaper.Configuration newConfig =
      new VolumeShaper.Configuration.Builder()
        .setDuration(1000)
        .setCurve(new float[] {0.f, 0.5f}, new float[] {0.f, 1.f})
        .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
        .build();
    boolean join = true;
    shaper.replace(newConfig, VolumeShaper.Operation.PLAY, join);
    

Quando você chama replace() enquanto o shaper está em execução, ele para de mudar o volume e mantém o valor atual. Depois disso, o shaper tenta iniciar a nova curva a partir do primeiro ponto de controle. Isso quer dizer que o argumento de operação controla se o shaper é ou não executado depois da chamada. Especifique PLAY para começar a nova curva imediatamente, especifique REVERSE para deixar o shaper pausado no volume do primeiro ponto de controle na nova curva. Você pode iniciar o shaper depois disso com apply(VolumeShaper.Operation.PLAY).

Quando você chama replace() com join = false, o shaper inicia a curva em um nível especificado pelo primeiro ponto de controle. Isso pode causar uma descontinuidade no volume. Você pode evitar isso chamando replace() com join = true. Isso define o primeiro ponto de controle da nova curva para o nível atual do shaper e dimensiona o volume de todos os pontos de controle entre o primeiro e o último para manter o formato relativo da nova curva (o último ponto de controle permanece inalterado). A operação de dimensionamento muda permanentemente os pontos de controle na nova curva do shaper.

Remover um VolumeShaper

O sistema fecha e coleta um VolumeShaper quando o AudioTrack ou MediaPlayer é liberado ou não está mais em uso. Você pode chamar o método close() em um shaper para destruí-lo imediatamente. O sistema remove o shaper do pipeline de áudio em cerca de 20 ms. Tenha cuidado ao fechar um VolumeShaper enquanto o áudio está tocando. Se o shaper tiver um volume inferior a 1.0 quando você chamar close(), a escala de volume do shaper mudará para 1.0. Isso pode causar um aumento repentino no volume.