Dieses Dokument behandelt gegenwärtig nur die Fragen rund um das Gebiet der gesampelten Sounds. Jedoch sind Beiträge zum Thema Synthesizer und Waveforms jederzeit willkommen.
Audioapplikationen sind tendenziell schwer zu portieren, da diese sich in einem nicht standardisierten Bereich befinden. Thematische Annäherungen variieren allerdings nicht erheblich zwischen den verschiedenen Betriebssystemen.
ossaudioossaudio-Emulation ist zwar möglicherweise die
einfachste Wahl, doch nicht immer machbar und für gewöhnlich auch
nicht die beste Wahl.
ioctl neu.
Soll ein ioctl-Aufruf im Portierungscode über
den reinen Audiobereich hinausgehen, dann
muss ein #undefioctl-Befehl gesetzt werden
und die reine Form _ossioctl Anwendung finden.
sys/audioio.h ist bereits überholt ist. Des Weiteren
tendieren viele Ports dazu, falsch codiert zu sein, und lassen sich
daher nur auf einen Maschinentyp anwenden. Einige Änderungen sind von
daher unumgänglich; siehe dazu den nächsten Abschnitt.
DU SOLLST KEINE VERMUTUNG DARÜBER ANSTELLEN,
WELCHE AUDIOHARDWARE BENUTZT WIRD.
Falscher Code ist Code, der lediglich das
a_info.play.precision-Feld auf 8 oder 16 Bits hin
überprüft, und Samples unter der Annahme einer
Soundblasterumgebung als signed oder unsigned interpretiert.
Der Sampletypus sollte ausdrücklich geprüft werden, und
demgemäß auch der Code selbst. Ein einfaches Beispiel:
AUDIO_INIT_INFO(&a_info);
a_info.play.encoding = AUDIO_ENCODING_SLINEAR;
a_info.play.precision = 16;
a_info.play.sample_rate = 22050;
error = ioctl(audio, AUDIO_SETINFO, &a_info);
if (error)
/* deal with it */
error = ioctl(audio, AUDIO_GETINFO, &a_info);
switch(a_info.play.encoding)
{
case AUDIO_ENCODING_ULINEAR_LE:
case AUDIO_ENCODING_ULINEAR_BE:
if (a_info.play.precision == 8)
/* ... */
else
/* ... */
break;
case ...
default:
/* don't forget to deal with what you don't know !!! For instance, */
fprintf(stderr,
"Unsupported audio format (%d), ask ports@ about that\n",
a_info.play.encoding);
}
/* now don't forget to check what sampling frequency you actually got */
Dieses kleinst mögliche Codefragment umfasst den größten Teil aller möglichen Fälle
AUDIO_ENCODING_SLINEAR), und man
findet eine Kodierung mit Endianness wieder (z. B.
AUDIO_ENCODING_SLINEAR_LE). Unter Berücksichtigung
der Tatsache, dass eine Soundkarte nicht dieselbe Endianness
benutzen muss wie die Plattform, auf der sie läuft, sollte man
gerade auf diesen Umstand vorbereitet sein. Der einfachste Weg
wäre gewiss die Einrichtung eines vollen Audiopuffers sowie die
Verwendung von swab(3), falls ein Endiannesswechsel
erforderlich ist. Der Umgang mit externen Samples läuft meistens
auf Folgendes hinaus:
Die Hardware kann einige eigenartige Begrenzungen aufweisen, so dass sie zwar nicht fähig ist, über 22050 Hz im Stereobereich hinaus zugehen, wohl aber im Monobereich die 44100-Grenze überschreiten kann. In solchen Fällen sollte man dem Benutzer die Chance geben, seine Präferenzen selbst anzugeben, um dann die bestmögliche Performance umzusetzen. So ist es zum Beispiel einfach nur unsinnig, den Bereich auf 22050 Hz zu limitieren, nur weil man Stereoausgabe erzielen will. Was ist, wenn der Benutzer kein Stereosoundsystem an seine Soundkarte angeschlossen hat?
Ebenso unsinnig ist es, soundblasterähnliche Begrenzungen in deinem Programm zu hardcoden. Man sollte sich darüber im Klaren sein und trotzdem versuchen, die 22050-Hz/Stereo-Barriere zu überwinden und die Ergebnisse anschließend zu überprüfen.
Samples schöpfen nicht immer den ihnen zukommenden Wertebereich komplett aus. Zunächst einmal sind Samples mit geringer Aufnahmeverstärkung auf der Maschine nicht besonders laut, so dass der Benutzer sich genötigt sieht, die Lautstärke zu erhöhen. Des Weiteren bedeutet eine leise Soundausgabe auf Maschinen mit schlecht isolierter Audiohardware, dass man eher den »Herzschlag« der Maschine hört als den erwarteten Sound. Letztendlich kann es passieren, dass man nach unbedarfter Umwandlung von 16 auf 8 Bit nur noch mit 4 Bits brauchbarer Soundausgabe dasteht, was eine wirklich miese Qualität bedeutet.
Die bestmögliche Lösung wäre es, den kompletten Stream, welchen
man abspielen möchte, frühst möglich zu scannen und
zu skalieren, sodass er den ganzen zur Verfügung stehenden
dynamischen Bereich ausfüllen kann. Sollte das nicht
funktionieren, dafür aber ein Teil von dem, was du abspielen
willst, einsehbar ist, kannst du die Klangverstärkung »on the
fly« vornehmen - du musst lediglich klarstellen, dass der
Verstärkungsfaktor auf einer niedrigeren Frequenz im Vergleich
zum Sound bleibt, der gespielt werden soll. Vermeide jede Art
von Überläufen - sie klingen immer viel schlechter als
die ursprünglich von dir angestrebte Verbesserung.
Da die Schallpegelwahrnehmung logarithmisch ist, genügt der
arithmetische Shiftgebrauch. Sind die Daten signed, dann sollte
der Shift ausdrücklich als Division gecodet werden, da der
C-Operator >> nicht auf Daten portiert
werden kann, die signed sind.
Sollte dies alles nicht greifen, muss dem Benutzer wenigstens die Option der Lautstärkeregelung an die Hand gegeben werden.
Hinsichtlich der Lowend-Applikationen gibt es nicht viel, um das man sich Gedanken machen müsste. Man sollte aber darauf achten, dass einige von uns OpenBSD auf einem Lowend-Level von 68030 verwenden - und dass eine Soundapplikation auf diesem Level laufen sollte, wenn sie es kann.
Vergiss das Benchmarking nicht. Theoretische Optimierungen sind nichts weiter als das: nämlich theoretisch. Es sollten schon genügend nüchterne Daten ermittelt werden, um entscheiden zu können, was eine wirkliche Verbesserung ist und was nicht.
Hinsichtlich der Highperformance-Audioapplikationen (wie z. B. mpegI-layer3) kommen folgende Punkte in Betracht:
write als Systemaufruf im Vergleich zur
internen Audioverarbeitung hohe Kosten nach sich zieht.
AUDIO_GETENC-ioctl sollte benutzt
werden, um alle Formate, welche das Audiodevice bietet,
wieder zu gewinnen. Insbesondere sollte man auf die Option
AUDIO_ENCODINGFLAG_EMULATED achten. Sollte
deine Applikation bereits in der Lage sein, jedes noch so
verworrene Format ausgeben zu können - und eben dafür auch
optimiert zu sein -, dann versuche jedoch um jeden Preis ein
ursprüngliches Format zu benutzen. Andererseits scheint der
Emulationscode im Audiodevice bereits optimal - ersetze ihn
nicht durch irgendeinen aus dem Hut gezauberten Code.
Modellcharakter für optimale Ergebnisse hat die Vorgehensweise, dass eingangs ein kleines Testprogramm kompiliert wird, welches zunächst eine spezifische Audiohardware abfragt, um dann dein Programm weiter so auszukonfigurieren, dass es mit der entsprechenden Hardware bestmöglich harmoniert. Es sollte dir schon klar sein, dass Leute, die eine gute Audioperformance erzielen möchten, deinen Port rekompilieren werden, um ihn auf andere Hardwaresysteme auszuweiten - insofern macht es einen Unterschied.
Trotz des Umstandes, dass OpenBSD kein Echtzeitbetriebssystem ist, möchtest du vermutlich eine Audioapplikation programmieren, die größtenteils im Echtzeitmodus läuft (z. B. für Spiele). In einem solchen Fall solltest du die Blockgröße verkleinern, so dass die erzielten Soundeffekte sich nicht asynchron zum laufenden Spiel verhalten. Problematisch wird es aber dann, wenn das Audiodevice verhungert: dies führt zu einem abscheulichen Resultat.
Sollte hingegen lediglich die Synchronisation auf der Ebene der
Audio/Grafik-Ausgabe erzielt werden und das Programmverhalten
vorhersehbar ist, dann ist die Synchronisation leichter zu
erzielen. Man spielt die Audiosamples einfach ab, befragt dann
das Audiodevice mit AUDIO_GETOOFFS, was gerade
gespielt wird, und benutzt schließlich diese Information zur
grafischen Postsynchronisation. So wird auf diesem Wege eine
sehr gute Synchronisation erzielt - vorausgesetzt, dass man
häufig genug fragt (sagen wir mal jede zehnte Sekunde) und über
genügend Rechenleistung verfügt, um die Anwendung laufen zu
lassen. Eventuell müssen die Werte durch ein konstantes Offset
optimiert werden, da es eine Differenz zwischen dem gibt, was
audiotechnisch im Augenblick gespielt wird, und der
verstrichenen Zeit, bis etwas im XWindow ausgegeben wird.
Im Falle der Audioapplikationen ist die Zusammenarbeit mit dem jeweiligen Programmurheber enorm wichtig. Sollte der Code z. B. in seiner Anwendung auf Soundblasterkarten begrenzt sein, so wird er aller Voraussicht nach auch auf andere Technologien übertragen.
Deine Arbeit ist wertlos, wenn du deine Kommentare nicht dem Programmierer zukommen lässt.
Es kann ja auch sein, dass der Autor die Probleme, mit denen du dich im Augenblick beschäftigst, bereits selbst erkannt hat und diese längst im Development-Tree adressiert hat. Kooperation ist ebenfalls eine hervorragende Idee, wenn die Patches, die du schreibst, sich über mehr als nur ein paar Zeilen erstrecken.