Como controlar a amplitude com o VolumeShaper

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

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 uma VolumeShaper é definido pelo VolumeShaper.Configuration. A configuração especifica uma *curva de volume, tipo de interpolador e 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 tempo e volume, respectivamente. As matrizes precisam ter o mesmo comprimento e conter pelo menos 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 monotonicamente crescentes.

As coordenadas de 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 um spline de acordo com o tipo de interpolador da configuração. Há quatro constantes para os tipos de interpolador 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();

With no arguments the VolumeShaper.Configuration.Builder constructor returns a builder that creates a configuration with default settings: INTERPOLATOR_TYPE_CUBIC, a one second duration, and no curve. You must add a curve to the builder before calling build().

The framework provides constants for configurations with pre-built curves, each with one second duration:

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

Creating a VolumeShaper

To create a VolumeShaper, call createVolumeShaper() on an instance of the appropriate class, passing in a VolumeShaper.Configuration:

Kotlin

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

Java

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

A single track or media player can have many shapers attached to it, and you can control each shaper separately. The outputs of all the shapers on a track or player are multiplied together. A VolumeShaper cannot be shared between AudioTracks or MediaPlayers, but you can use the same configuration in calls to createVolumeShaper to build identical shapers on multiple AudioTracks or MediaPlayers.

When you create the shaper, its first control point (at t = 0) is applied to the audio stream. If the initial volume is not 1.0 and your app is playing material at create time, your audio might have an abrupt change in volume. Best practice is to start playing audio from silence and use a VolumeShaper to implement a fade-in when playback starts. Create a VolumeShaper that starts at 0 volume and fades up. For example:

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 que o volume seja carregado para o máximo. Isso é explicado na próxima seção.

Executar um VolumeShaper

Embora o nível de volume do primeiro ponto de controle seja aplicado ao caminho de áudio assim que o shaper é criado, o shaper não progride ao longo da 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 para o último ponto de controle:

Kotlin

shaper.apply(VolumeShaper.Operation.PLAY)

Java

shaper.apply(VolumeShaper.Operation.PLAY);

Enquanto o shaper estiver em execução, você poderá emitir chamadas apply() alternadas especificando 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) 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ê poderá 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() precisa ser REVERSE. Chamar PLAY depois da expiração de PLAY ou de REVERSE depois da expiração de REVERSE não tem 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 próxima seção, para substituir a curva por uma cópia dela. Isso redefine o shaper, exigindo que a operação PLAY o inicie 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 agrupamento. 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 = Config.replace, shaper.PLAY,

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_buildTYPE_LINEAR)
 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_buildTYPE_LINEAR)

Quando você chama replace() enquanto o shaper está em execução, ele para de mudar o volume e permanece no valor atual. Em seguida, o shaper tenta iniciar a nova curva no primeiro ponto de controle. Isso significa que o argumento de operação controla se o shaper é executado ou não após a chamada. Especifique PLAY para iniciar a nova curva imediatamente e REVERSE para deixar o shaper pausado no volume do primeiro ponto de controle na nova curva. Você pode iniciar o shaper mais tarde com apply(VolumeShaper.Operation.PLAY).

Quando você chama replace() com join = false, o shaper inicia a curva no 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 a forma relativa da nova curva (o último ponto de controle não é alterado). A operação de dimensionamento muda permanentemente os pontos de controle na nova curva do shaper.

Remover um VolumeShaper

O sistema é fechado, e o lixo coleta um VolumeShaper quando a AudioTrack ou MediaPlayer é liberada 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 menor que 1,0 quando você chamar close(), a escala de volume do shaper vai mudar para 1,0. Isso pode causar um aumento repentino no volume.