Em um novo artigo traduzido, discutimos como criar um bip em diferentes plataformas.
E / S de áudio é um tópico complicado que assusta muitos músicos que estão programando e programadores que gostam de música. Vamos tentar descobrir isso! Neste artigo, discutiremos como o som funciona em cada sistema operacional moderno (versão para desktop).
Nosso caso de hoje será considerado usando um bipe simples como exemplo. Lembra daquela coisa irritante dentro da caixa do seu PC que faz um zumbido desagradável? Agora se tornou apenas uma memória! Eu sugiro fazer uma biblioteca que reproduz sons semelhantes em todos os sistemas operacionais.
O resultado final está disponível neste link .
JANELAS
Estamos com sorte com o Windows: já existe uma função Beep (frequência, duração) em <utilapiset.h> . Podemos usar isso.
Esse recurso tem uma história muito longa e complexa . Ele foi introduzido para reproduzir sinais de áudio por meio de um bipe de hardware usando o temporizador programável 8245. À medida que mais e mais computadores eram lançados sem um bipe, esse recurso se tornou obsoleto com o tempo. No entanto, no Windows 7, ele foi reescrito para reproduzir sinais de áudio usando a API da placa de som.
No entanto, a aparente simplicidade desse recurso oculta a complexidade de todas as APIs de som do Windows. MME foi lançado em 1991 ... É usado por padrão para áudio, pois tem um bom suporte.
O MME é conhecido por ter alta latência de reprodução e provavelmente não será adequado para a maioria das aplicações de áudio. Também em 2007, WASAPI foi lançado . Possui menor latência, especialmente quando usado em modo exclusivo (um modo em que o usuário não pode ouvir o Spotify ou qualquer outro aplicativo enquanto seu aplicativo está em execução). WASAPI é uma boa escolha para aplicativos de áudio, no entanto, procure DirectSound , que é um wrapper WASAPI para interagir com DirectX.
Se não tiver certeza, use WASAPI.
LINUX
O áudio é uma das poucas áreas em que a API do Linux é tão legal quanto outras plataformas. Antes de mais nada, é preciso falar do ALSA, que faz parte do próprio núcleo.
O ALSA interage diretamente com o hardware e, se você deseja que seu aplicativo funcione exclusivamente com som, o ALSA pode ser um bom meio-termo entre complexidade e desempenho. Se você estiver construindo um sintetizador ou sampler para o Raspberry Pi, o ALSA é uma boa escolha.
Além disso, existe o PulseAudio, uma camada de abstração de áudio construída em cima do ALSA. Ele roteia o áudio de vários aplicativos e tenta mixar fluxos de áudio para que os aplicativos críticos não sofram de problemas de latência. Embora o PulseAudio forneça muitos recursos que não seriam possíveis com o ALSA (como roteamento de áudio pela Internet), a maioria dos aplicativos de música não o utiliza.
Muitas pessoas usam o kit de conexão de áudio JACK... O JACK foi criado para músicos profissionais. Ele cuida da reprodução em tempo real, enquanto o PulseAudio foi criado para usuários casuais que podem sofrer algum atraso ao reproduzir vídeos do YouTube. O JACK conecta aplicativos de áudio com latência mínima, mas tenha em mente que ele ainda roda em cima do ALSA, então se seu aplicativo vai ser o único aplicativo de áudio em execução (por exemplo, se você está construindo uma bateria eletrônica de um Raspberry Pi antigo), então ALSA é muito mais fácil de usar e melhor desempenho também.
Fazer um beeper funcionar usando ALSA não é tão difícil. Precisamos abrir o dispositivo de áudio padrão, configurá-lo para usar uma taxa de amostragem e um formato de amostragem bem suportados e começar a gravar dados nele. Os dados de áudio podem ser uma onda dente de serra, conforme descrito no meu artigo anterior .
int beep(int freq, int ms) {
static void *pcm = NULL;
if (pcm == NULL) {
if (snd_pcm_open(&pcm, "default", 0, 0)) {
return -1;
}
snd_pcm_set_params(pcm, 1, 3, 1, 8000, 1, 20000);
}
unsigned char buf[2400];
long frames;
long phase;
for (int i = 0; i < ms / 50; i++) {
snd_pcm_prepare(pcm);
for (int j = 0; j < sizeof(buf); j++) {
buf[j] = freq > 0 ? (255 * j * freq / 8000) : 0;
}
int r = snd_pcm_writei(pcm, buf, sizeof(buf));
if (r < 0) {
snd_pcm_recover(pcm, r, 0);
}
}
return 0;
}
Aqui, usamos uma API síncrona e não verificamos erros para manter a função curta e simples. I / O de bloqueio síncrono provavelmente não é a melhor opção para aplicações sérias de áudio e, felizmente, o ALSA vem com diferentes métodos de transferência e modos de operação: link . Mas para nosso experimento simples, isso é o suficiente. Em caso de dúvida, use ALSA. Se você tiver que interagir com outros aplicativos de áudio, use o JACK.
MAC OS
No caso do MacOS, tudo é bastante simples, mas não tão elementar.
O MacOS possui uma estrutura CoreAudio para funções de áudio no desktop e iOS. O próprio CoreAudio é uma API de baixo nível totalmente integrada ao sistema operacional para otimizar a latência e o desempenho. Para reproduzir áudio usando CoreAudio, você precisa criar um AudioUnit (plug-in de áudio). A API AudioUnit é um pouco longa, mas fácil de entender. Veja como criar um novo AudioUnit:
AudioComponent output;
AudioUnit unit;
AudioComponentDescription descr;
AURenderCallbackStruct cb;
AudioStreamBasicDescription stream;
descr.componentType = kAudioUnitType_Output,
descr.componentSubType = kAudioUnitSubType_DefaultOutput,
descr.componentManufacturer = kAudioUnitManufacturer_Apple,
// Actual sound will be generated asynchronously in the callback tone_cb
cb.inputProc = tone_cb;
stream.mFormatID = kAudioFormatLinearPCM;
stream.mFormatFlags = 0;
stream.mSampleRate = 8000;
stream.mBitsPerChannel = 8;
stream.mChannelsPerFrame = 1;
stream.mFramesPerPacket = 1;
stream.mBytesPerFrame = 1;
stream.mBytesPerPacket = 1;
output = AudioComponentFindNext(NULL, &descr);
AudioComponentInstanceNew(output, &unit);
AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &cb, sizeof(cb));
AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0, &stream, sizeof(stream));
AudioUnitInitialize(unit);
AudioOutputUnitStart(unit);
Este código apenas cria e inicia um novo AudioUnit, a geração de som real acontecerá de forma assíncrona no retorno de chamada:
static OSStatus tone_cb(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
UInt32 inNumberFrames, AudioBufferList *ioData) {
unsigned int frame;
unsigned char *buf = ioData->mBuffers[0].mData;
unsigned long i = 0;
for (i = 0; i < inNumberFrames; i++) {
buf[i] = beep_freq > 0 ? (255 * theta * beep_freq / 8000) : 0;
theta++;
counter--;
}
return 0;
}
Este retorno de chamada gera áudio da mesma forma que fizemos com ALSA, mas é chamado de forma assíncrona quando o CoreAudio pensa que o buffer de áudio está quase vazio e precisa ser preenchido com novas amostras de áudio.
Esta abordagem assíncrona para geração de som é muito comum e quase todas as bibliotecas de áudio modernas a suportam. Se você deseja criar um aplicativo de música, deve projetá-lo tendo em mente a reprodução assíncrona.
Em caso de dúvida, use CoreAudio.
Parece complicado, certo?
Se estiver criando um aplicativo de música, você pode seguir o mesmo caminho implementando um back-end de áudio para WASAPI, ALSA e CoreAudio. Na verdade, não é tão difícil. Você pode ver o código-fonte completo do bip , cerca de 100 linhas de código para todas as três plataformas.
No entanto, há uma série de boas bibliotecas de plataforma cruzada, como:
- RtAudio + RtMidi (muito fácil de usar, um arquivo .cpp e .h);
- PortAudio + PortMidi (escrito em C e um pouco maior), tem muitos back-ends diferentes;
- SoundIO é uma pequena biblioteca maravilhosa do criador do Zig.
Algumas pessoas preferem usar JUCE para aplicativos de áudio de plataforma cruzada, mas tem suas limitações.
Todos os itens acima podem parecer uma tarefa difícil, mas existem muitas implementações, e a maioria delas são boas. Continue tentando!
Espero que tenha gostado deste artigo. Você pode acompanhar notícias e projetos no Github , Twitter ou se inscrever via RSS .