Ferramentas do usuário

Ferramentas do site


prog2:processamento_de_audio

Processamento de áudio

Em um sistema digital, um som é normalmente codificado como um vetor de amostras (samples), onde cada amostra corresponde à amplitude do sinal sonoro em um instante de tempo. As figuras a seguir trazem um exemplos de sinais sonoros codificados dessa forma:

Trecho de áudio (duração: 45s)

 Trecho de música (45s)

Detalhe do trecho de áudio acima (duração: 0,2s):

 Detalhe (0,2s)

Detalhe do trecho de áudio acima (duração: 0,02s):

 Detalhe do detalhe (0,02s)

Visão conceitual da amostragem de áudio:

Amostragem de sinal analógico

Na última figura, podem ser identificados o sinal analógico, proveniente de um microfone ou outro dispositivo de captura, e as amostras, que correspondem ao valor do sinal medido em intervalos regulares de tempo. O sinal analógico acima seria então representado de forma aproximada pelo seguinte vetor de amostras:

[0, 10, 15, 9, -5, -9, -10, 0, 10, 14, 15, 5, 0, -5, ... ]

O padrão PCM (Pulse-Code Modulation) é um dos padrões mais simples para representar sinais analógicos em meios digitais. Duas informações caracterizam um sinal codificado em PCM:

  • Taxa de amostragem: é o número de amostras do sinal analógico feitas por segundo. Quanto maior a taxa de amostragem, melhor é a representação digital do som, sobretudo nas frequências mais elevadas (agudos). Por outro lado, taxas de amostragem elevadas implicam em arquivos maiores. Valores típicos de taxas de amostragem são 48 kHz em DVDs e 44,1 KHz em CDs de música (exemplos).
  • Resolução: é o número de bits usado para representar cada amostra. Quanto maior a resolução, mais precisa é a representação de cada amostra e menor a distorção do sinal. CDs de música usam 16 bits de resolução, enquanto sistemas de telefonia costumam usar 8 bits (exemplos).

No caso de sons com vários canais (estéreo ou surround), deve existir uma sequência de amostras para cada canal de som, geralmente todas usando a mesma taxa de amostragem e resolução.

O formato WAV

Para facilitar a leitura e escrita dos arquivos de áudio, neste projeto será adotado o formato WAV, reconhecido pela maioria dos softwares de geração e reprodução de áudio. Para este projeto será adotado o padrão WAV com codificação PCM de 16 bits com sinal. Neste formato, cada amostra é representada por um número inteiro de 16 bits com sinal (ou seja, do tipo int16_t, variando entre -32768 e +32767).

Um arquivo WAV típico é organizado nos seguintes chunks (pedaços) básicos:

Chunk ID tamanho (bytes) conteúdo
RIFF 8 cabeçalho RIFF, indica ser um arquivo de áudio
fmt 24 informações sobre o formato do áudio
data * amostras do(s) canal(is) de áudio

Em alguns arquivos WAV podem haver outros chunks, como LIST e INFO, que não precisam ser considerados neste projeto.

Esses chunks têm os seguintes campos internos:

nome tamanho tipo valor significado
ChunkID 4 char[4] “RIFF” constante, identifica o tipo de arquivo
ChunkSize 4 uint32_t filesize - 8 tamanho do arquivo em bytes, sem considerar este chunk
Format 4 char[4] “WAVE” constante, define o formato interno
SubChunk1ID 4 char[4] “fmt ” constante, cabeçalho do chunk
SubChunk1Size 4 uint32_t 16 (PCM) tamanho deste chunk
Audio format 2 uint16_t 1 (PCM) codificação utilizada
Number of channels 2 uint16_t 1, 2, … número de canais de áudio
Sample rate 4 uint32_t 44100, etc Taxa de amostragem (amostras/seg)
Byte rate 4 uint32_t varia taxa de bytes por segundo
Block align 2 uint16_t varia número de bytes por amostra (soma todos os canais)
Bits per sample 2 uint16_t 8, 16, 32, … bits por amostra, por canal
SubChunk2ID 4 char[4] “data” constante, cabeçalho do chunk
SubChunk2Size 4 uint32_t varia espaço ocupado pelas amostras, em bytes
Audio data varia varia varia amostras de áudio

Observe que os campos de tipo char[4] não são strings, pois strings precisam ser terminadas por \0, o que não ocorre aqui.

As amostras de áudio são armazenadas no chunk de dados (data) do arquivo, em sequência por canal e por tempo. Cada amostra tem um tamanho fixo em bytes, definido pelo campo bits per sample do cabeçalho. Considerando um áudio com dois canais (Left e Right), as amostras estarão dispostas da seguinte forma:

L[0], R[0], L[1], R[1], L[2], R[2], L[3], R[3], ...

Para ler facilmente o cabeçalho de um arquivo WAV, basta definir um struct com os mesmos campos, tamanhos e ordem do cabeçalho descrito acima, e então ler os primeiros bytes do arquivo para dentro desse struct, usando a função de leitura fread. Uma vez lido o struct, basta verificar se os campos constantes têm os valores esperados.

Documentação adicional sobre o formato WAV:

Alguns exemplos de arquivos de áudio WAV para usar no projeto:

Você pode gerar seus próprios arquivos WAV usando programas de processamento de áudio, como o “Audacity”, Sox, etc.

Filtros

Um filtro de áudio é um programa simples, que recebe como entrada um arquivo de áudio (que pode vir da entrada padrão stdin), realiza algum tipo de processamento de áudio e entrega na saída um arquivo de áudio (que pode ser stdout). Eventuais mensagens de erro devem ser enviadas para a saída de erro (stderr).

Lembre-se que o filtro deve atuar em todos os canais do áudio da entrada.

Informações

Este programa não é exatamente um filtro, pois produz como saída uma listagem das principais informações do áudio informado como entrada.

Forma de chamada:

wavinfo -i input

Exemplo de saída:

riff tag       : "RIFF"
riff size      : 8061776
wave tag       : "WAVE"
form tag       : "fmt "
fmt_size       : 16
audio_format   : 1
num_channels   : 2
sample_rate    : 44100
byte_rate      : 176400
block_align    : 4
bits_per_sample: 16
data tag       : "data"
data size      : 8061740
samples/channel: 2015435

Reversão

Este filtro produz como saída um áudio invertido, ou seja, “de trás para a frente” em relação ao áudio de entrada.

Forma de chamada:

wavrev -i input -o output

Ajuste de volume

O filtro de ajuste de volume permite aumentar ou diminuir o volume de áudio do arquivo, de acordo com um fator de ajuste V (0.0 ≤ V ≤ 10.0, com default em 1.0). Ele consiste basicamente em multiplicar o valor de cada amostra de áudio por V.

Forma de chamada (-l : level):

wavvol -l V -i input -o output

Exemplo de entrada e de saída (dobra o volume da entrada):

wavvol -l 2.0 -i musica1.wav -o musica2.wav

Ajuste automático de volume

Este filtro permite o ajuste automático de volume do arquivo de áudio. Para tal, é necessário encontrar o valor do maior pico no sinal de áudio (em todos os canais) e usá-lo para calcular um fator de ajuste, de modo que todas as amostras de todos os canais fiquem no intervalo de 16 bits com sinal [-32767 … +32767].

Forma de chamada:

wavautovol -i input -o output

Eco

Este filtro produz como saída um áudio com eco. O eco é controlado pelos parâmetros delay ( inteiro ≥ 0, default 1000 ms), que define o atraso do eco em milissegundos, e level (0.0 ≤ level ≤ 1.0, default 0.5), que define o nível do eco em relação ao sinal original.

O efeito de eco pode ser definido por esta equação: samplet = samplet + (level × samplet-delay)

Forma de chamada (-t : time, -l : level):

wavecho -t delay -l level -i input -o output

Experimente aplicar este filtro a um arquivo de voz, com um atraso bem pequeno (< 10 ms) =)

Estéreo ampliado

O filtro de estéreo ampliado permite aumentar a separação de canais em um sinal estéreo, gerando um som mais aberto e envolvente. Este filtro só pode ser aplicado a sinais estéreo (com 2 canais).

Sendo R(t) e L(t) as amostras de entrada do canal direito e esquerdo em um instante t, a saída R'(t), L'(t) é calculada da seguinte forma:

diff  = R(t) - L(t)
R'(t) = R(t) + k * diff
L'(t) = L(t) - k * diff

onde diff é o sinal de diferença entre os dois canais e k é o fator de ampliação do efeito estéreo (0.0 ≤ k ≤ 10.0, default 1.0).

Forma de chamada (-l : level):

wavwide -l k -i input -o output

Concatenação

O filtro de concatenação recebe como entrada um ou mais arquivos de áudio e gera uma saída contendo a concatenação das entradas na sequência indicada.

Forma de chamada:

wavcat arq1.wav arq2.wav arq3.wav ... -o output

A combinação de sinais de áudio com taxas de amostragem diferentes exige um procedimento chamado reamostragem (resampling), que pode ser complexo. Por isso, restrinja seu programa a arquivos com a mesma taxa de amostragem.

Mistura

O filtro de mistura (mixagem) recebe como entrada um ou mais arquivos de áudio e gera uma saída contendo a mistura (mixagem) das entradas.

Forma de chamada:

wavmix arq1.wav arq2.wav arq3.wav ... -o output

Ao somar as amostras, cuide para não saturar a saída, o que pode gerar distorção (clipping).

Atividade

  • Implementar os filtros acima definidos como arquivos e comandos separados. Exemplo: o filtro de volume deve ser implementado em um arquivo wavvol.c que gera um executável wavvol, e assim por diante.
  • Os filtros devem aceitar como entrada sons no formato WAV PCM 16 bits com sinal e devem gerar como saída sons nesse mesmo formato.
  • As rotinas comuns (leitura/escrita de arquivos, tratamento da linha de comando, etc) devem ser implementadas em arquivos separados, cujos cabeçalhos são incluídos nos arquivos de implementação dos filtros.
  • Sempre que possível, as informações do som necessárias às funções devem ser transferidas como parâmetros (por valor ou por referência, dependendo da situação). Minimizar o uso de variáveis globais.
  • Use alocação dinâmica de memória para os sons, para poder processar arquivos grandes. Só aloque a memória para as amostras após encontrar o número de amostras.
  • Construir um Makefile para o projeto:
    • Ao menos os alvos all (default), clean e purge.
    • CFLAGS = -Wall
    • Compilar e ligar separadamente (gerar arquivos .o intermediários)
  • O que deve ser entregue ao professor:
    • arquivos .c e .h
    • arquivo Makefile
    • não enviar os sons de teste

Para simplificar a implementação e evitar erros, sugere-o o uso dos tipos inteiros de tamanho fixo (int16_t, etc), acessíveis através do arquivo inttypes.h.

Linha de comando

  • A opção -i indica o nome do arquivo de entrada; se não for informado, deve-se usar a entrada padrão (stdin).
  • A opção -o indica o nome do arquivo de saída; se não for informado, deve-se usar a saída padrão (stdout).
  • Todas as mensagens de erro devem ser enviadas para a saída de erro (stderr).

Essas opções podem ser usadas em qualquer combinação, ou seja:

// entrada e saída em arquivos
wavvol -i inputfile.wav  -o outputfile.wav
wavvol -o outputfile.wav -i inputfile.wav

// entrada em arquivo, saída em stdout, vice-versa ou ambos
wavvol -i inputfile.wav  > outputfile.wav
wavvol -o outputfile.wav < inputfile.wav
wavvol  < inputfile.wav  > outputfile.wav

// as opções podem estar em qualquer ordem
wavvol -l 0.3 -i inputfile.wav -o outputfile.wav
wavvol -i inputfile.wav -l 0.3 -o outputfile.wav
wavvol -o outputfile.wav -i inputfile.wav -l 0.3

Para ler e tratar mais facilmente as opções da linha de comando, sugere-se usar funções já prontas para isso, como getopt ou arg_parse (link)

Como os filtros devem tratar a entrada e saída padrão, é possível combinar filtros usando pipes UNIX. Por exemplo, podemos usar pipes para construir o efeito Reverse Echo, muito apreciado por alguns grupos de Rock:

wavrev -i input.wav | wavecho -t 500 -l 0.5 | wavrev -o output.wav

Caso a adição de eco provoque clipping, pode-se atenuar o sinal antes de processá-lo:

wavvol -l 0.5 -i input.wav | wavrev | wavecho -t 500 -l 0.5 | wavrev | wavautovol -o output.wav

Para tocar facilmente os arquivos de áudio, pode-se usar os comandos play, aplay ou paplay no terminal do Linux, ou o preview de arquivos no gerenciador de arquivos do ambiente gráfico.

Estrutura do código-fonte

O código-fonte deve ser estruturado em diversos arquivos .c e .h que contenham as funcionalidades a serem implementadas. A figura abaixo traz uma uma sugestão de estrutura para o código-fonte (as setas correspondem a includes):

Estrutura do código-fonte

prog2/processamento_de_audio.txt · Última modificação: 2019/08/01 17:02 por maziero