In einem neu übersetzten Artikel diskutieren wir, wie ein Piepser auf verschiedenen Plattformen erstellt wird.
Audio I / O ist ein kniffliges Thema, das viele Musiker, die programmieren, und Programmierer, die sich für Musik interessieren, erschreckt. Versuchen wir das herauszufinden! In diesem Artikel werden wir diskutieren, wie Sound auf jedem modernen Betriebssystem (Desktop-Version) funktioniert.
Unser heutiger Fall wird am Beispiel eines einfachen Piepsers betrachtet. Erinnern Sie sich an das nervige Ding in Ihrer PC-Box, das ein unangenehmes Summen erzeugt? Jetzt ist es nur noch eine Erinnerung! Ich schlage vor, eine Bibliothek zu erstellen, die auf allen Betriebssystemen ähnliche Sounds wiedergibt.
Das Endergebnis finden Sie unter diesem Link .
FENSTER
Wir haben Glück mit Windows: In <utilapiset.h> gibt es bereits eine Beep-Funktion (Frequenz, Dauer) . Wir können es benutzen. Diese Funktion hat eine sehr lange und komplexe Geschichte . Es wurde eingeführt, um Audiosignale über einen Hardware-Piepser mit dem programmierbaren Timer 8245 abzuspielen. Da immer mehr Computer ohne Piepser freigegeben wurden, wurde diese Funktion im Laufe der Zeit veraltet. In Windows 7 wurde es jedoch neu geschrieben, um Audiosignale mithilfe der Soundkarten-API abzuspielen. Die scheinbare Einfachheit dieser Funktion verbirgt jedoch die Komplexität aller Windows-Sound-APIs. MME wurde 1991 veröffentlicht
... Es wird standardmäßig für Audio verwendet, da es eine gute Unterstützung bietet.
MME hat bekanntermaßen eine hohe Wiedergabelatenz und ist wahrscheinlich für die meisten Audioanwendungen nicht geeignet. Ebenfalls 2007 wurde WASAPI veröffentlicht . Es hat eine geringere Latenz, insbesondere wenn es im exklusiven Modus verwendet wird (ein Modus, in dem der Benutzer Spotify oder eine andere Anwendung nicht hören kann, während Ihre Anwendung ausgeführt wird). WASAPI ist eine gute Wahl für Audioanwendungen. Achten Sie jedoch auf DirectSound , einen WASAPI-Wrapper für die Interaktion mit DirectX.
Wenn Sie sich nicht sicher sind, verwenden Sie WASAPI.
LINUX
Audio ist einer der wenigen Bereiche, in denen die Linux-API so cool ist wie andere Plattformen. Zunächst sollte über ALSA gesprochen werden, das Teil des Kerns selbst ist.
ALSA kommuniziert direkt mit der Hardware. Wenn Ihre Anwendung ausschließlich mit Sound arbeiten soll, kann ALSA ein guter Kompromiss zwischen Komplexität und Leistung sein. Wenn Sie einen Synthesizer oder Sampler für den Raspberry Pi bauen, ist ALSA eine gute Wahl.
Darüber hinaus gibt es PulseAudio, eine Audio-Abstraktionsschicht, die auf ALSA aufbaut. Es leitet Audio von verschiedenen Anwendungen weiter und versucht, Audio-Streams zu mischen, damit kritische Anwendungen nicht unter Latenzproblemen leiden. Während PulseAudio viele Funktionen bietet, die mit ALSA nicht möglich wären (z. B. das Weiterleiten von Audio über das Internet), wird es von den meisten Musikanwendungen nicht verwendet.
Viele Leute benutzen das JACK Audio Connection Kit... JACK wurde für professionelle Musiker entwickelt. Es sorgt für die Echtzeitwiedergabe, während PulseAudio für Gelegenheitsbenutzer entwickelt wurde, bei denen beim Abspielen von YouTube-Videos möglicherweise Verzögerungen auftreten. JACK verbindet Audioanwendungen mit minimaler Latenz, aber denken Sie daran, dass sie immer noch auf ALSA ausgeführt werden. Wenn Ihre Anwendung also die einzige Audioanwendung ist, die ausgeführt wird (z. B. wenn Sie einen Drumcomputer aus einem alten Raspberry Pi erstellen), ist ALSA viel einfacher zu bedienen und auch bessere Leistung.
Es ist eigentlich nicht so schwierig, mit ALSA eine Piepserfunktion zu erstellen. Wir müssen das Standard-Audiogerät öffnen, es so konfigurieren, dass es eine gut unterstützte Abtastrate und ein gut unterstütztes Abtastformat verwendet, und Daten darauf schreiben. Die Audiodaten können eine Sägezahnwelle sein, wie in meinem vorherigen Artikel beschrieben .
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;
}
Hier verwenden wir eine synchrone API und suchen nicht nach Fehlern, um die Funktion kurz und einfach zu halten. Synchrones Blockieren von E / A ist wahrscheinlich nicht die beste Option für seriöse Audioanwendungen, und zum Glück bietet ALSA verschiedene Übertragungsmethoden und Betriebsmodi: Link . Für unser einfaches Experiment ist dies jedoch völlig ausreichend. Verwenden Sie im Zweifelsfall ALSA. Wenn Sie mit anderen Audioanwendungen interagieren müssen, verwenden Sie JACK.
MAC OS
Bei MacOS ist alles recht einfach, aber nicht ganz elementar.
MacOS verfügt über ein CoreAudio- Framework für Audiofunktionen auf dem Desktop und iOS. CoreAudio selbst ist eine Low-Level-API, die eng in das Betriebssystem integriert ist, um Latenz und Leistung zu optimieren. Um Audio mit CoreAudio abzuspielen, müssen Sie eine AudioUnit (Audio-Plugin) erstellen. Die AudioUnit-API ist etwas lang, aber leicht zu verstehen. So erstellen Sie eine neue 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);
Dieser Code erstellt und startet nur eine neue AudioUnit. Die eigentliche Klangerzeugung erfolgt asynchron im Rückruf:
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;
}
Dieser Rückruf generiert Audio auf die gleiche Weise wie bei ALSA, wird jedoch asynchron aufgerufen, wenn CoreAudio der Ansicht ist, dass der Audiopuffer fast leer ist und mit neuen Audio-Samples gefüllt werden muss.
Dieser asynchrone Ansatz zur Klangerzeugung ist weit verbreitet und wird von fast jeder modernen Audiobibliothek unterstützt. Wenn Sie eine Musikanwendung erstellen möchten, sollten Sie sie unter Berücksichtigung der asynchronen Wiedergabe entwerfen.
Verwenden Sie im Zweifelsfall CoreAudio.
Klingt kompliziert, oder?
Wenn Sie eine Musikanwendung erstellen, können Sie demselben Pfad folgen, indem Sie ein Audio-Backend für WASAPI, ALSA und CoreAudio implementieren. In der Tat ist es nicht so schwierig. Sie können den vollständigen Quellcode des Piepser sehen . Dies sind ungefähr 100 Codezeilen für alle drei Plattformen.
Es gibt jedoch eine Reihe guter plattformübergreifender Bibliotheken wie:
- RtAudio + RtMidi (sehr einfach zu bedienen, eine .cpp- und .h-Datei);
- PortAudio + PortMidi (in C geschrieben und etwas größer) hat viele verschiedene Backends;
- SoundIO ist eine wundervolle kleine Bibliothek vom Schöpfer von Zig.
Einige Leute bevorzugen JUCE für plattformübergreifende Audioanwendungen, aber es hat seine Grenzen.
All dies mag wie eine entmutigende Aufgabe erscheinen, aber es gibt viele Implementierungen, und die meisten davon sind gut. Also versuche es weiter!
Ich hoffe, Ihnen hat dieser Artikel gefallen. Sie können Nachrichten und Projekte auf Github , Twitter verfolgen oder per RSS abonnieren .