kramann.info
© Guido Kramann

Login: Passwort:










6.2 Ansteuerung einer Soundkarte mit Linux.

  • Eine Soundkarte faßt gleich eine ganze Palette an Hardwaremodulen zusammen (Devices):
  • Neben einem digitalen A/D und D/A-Wandler gibt es typischerweise einen Mixer für Lautstärke und Klangeinstellungen verschiedener Klangquellen.
  • Außerdem gibt es meistens einen Baustein zum Senden und Empfangen von MIDI-Daten und einen Synthesizer.
  • Hardwaremodule/Geräte/Devices werden unter dem Betriebssystem Linux als Dateien im Verzeichnis /dev/ abgebildet.
  • Dadurch ist es unter Linux möglich, über normale Dateioperationen auf Hardwarekomponenten zuzugreifen.
  • Für die Schnittstelle zum A/D- und D/A-Wandler findet sich unter /dev/ typischerweise der Eintrag /dev/audio
  • Alternativ kann auch /dev/dsp verwendet werden.
  • Sind mehrere Soundkarten eingebaut, werden zur Unterscheidung Nummern vergeben: dsp0, dsp1 usw.
  • Statt der von den Fileoperationen bekannten fopen(..) / fclose(..) - Befehlen werden für die Devices open(..) und close(..) aus der Library unistd.h verwendet.
  • Zur Konfiguration der Soundkarte wird die Funktion ioctl(..) verwendet.
  • Diese benötigt als Übergabeparameter diverse Konstanten, die beispielsweise das Audioformat festlegen.
  • Dies erfordert die Einbindung weiterer Libraries.
  • Das folgende Beispiel konfiguriert die Soundkarte für 44100Hz, signed 16Bit, "Low Byte zuerst" (Konstante AFMT_S16_LE).
  • Dann startet eine Audioaufnahme (read(..)). Hierzu muß ein Mikrofon angeschlossen, oder im PC integriert und aktiviert sein.
  • Im Anschluß werden die im Puffer-Array gespeicherten Werte wieder zu Gehör gebracht (write(...)). Hierzu muß der Lautsprecher vorhanden und aktiviert sein.
  • Mit close(..) wird die Soundkarte wieder freigegeben:
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/ioctl.h>
#include<linux/soundcard.h>
#define ANZAHL_BYTES 441000
char puffer[ANZAHL_BYTES];
int main()
{
    int audio_device = open("/dev/audio",O_RDWR);
    int format = AFMT_S16_LE;
    int parameter=1;
    ioctl(audio_device,SNDCTL_DSP_SETFMT,&format);
    ioctl(audio_device,SNDCTL_DSP_STEREO,¶meter);
    parameter=44100;
    ioctl(audio_device,SNDCTL_DSP_SPEED,¶meter);
    //Aufnahme durchführen:
    read(audio_device,puffer,ANZAHL_BYTES);        
    //Abspielen:
    write(audio_device,puffer,ANZAHL_BYTES);
    close(audio_device);
    return 0;
}

Code 6.2-1: Test der A/D- und D/A-Wandlung der Soundkarte unter Linux. Kompilierung mit: g++ -o filename filename.cpp

Echtzeit-Klangverarbeitung

  • In der Neuen Musik der 60er Jahre wurde gerne Livemusik mit Tonbandaufnahmen ergänzt.
  • Besonders interessant wurde es, wenn ein Tonbandgerät gleichzeitig aufnahm und zeitversetzt den Klang wieder abspielte.
  • Dies wurde über Tonbandschleifen erreicht, die über Umlenkrollen geführt und zuerst einen Aufnahme- und dann einen Wiedergabekopf passierten.
  • Damit kann man z.B. alleine einen Chanon singen.
  • Dies ist heute mit Hilfe digitaler Speicher viel einfacher realisierbar, setzt aber voraus, dass ein System gleichzeitig aufnehmen und abspielen kann.
  • Es gibt sogar s.g. Vocoder, die in Echtzeit FFT von einem hereinkommenden Tonsignal durchführen, im Frequenzbereich das Signal verarbeiten und den veränderten Klang wieder ausgeben.
  • Dadurch läßt sich beispielsweise die Tonhöhe einer Stimme ändern, ohne, dass eine Aufnahme schneller oder langsamer abgespielt werden müßte.
Echtzeit-Klangverarbeitung: Zeitversetzte Wiedergabe.

Bild 6.2-1: Echtzeit-Klangverarbeitung: Zeitversetzte Wiedergabe.

  • Läßt sich solch ein System mit Hilfe der oben dargestellten Technik realisieren?
  • Welche Voraussetzungen müßten dann erfüllt sein?
  • In obigem Programm wird ein Puffer für eine große Samplemenge komplett gelesen und dann komplett (hörbar) ausgegeben.
  • Mit jedem Sample wird der Ausgang des D/A-Wandlers auf einen bestimmten Wert gesetzt (Sample) und bis zum nächsten Sample festgehalten (hold).
  • Würde man mit der write(...)-Funktion nur einen einzigen Sample setzen (Puffergröße 4) und würde nach Verlassen der Funktion dieser Wert am Audioausgang festgehalten werden, so könnten man in der Zwischenzeit einen Wert am Audioeingang mit der read(..)-Funktion einlesen.
  • Diesen neu eingelesenen Wert müßte man in einem "Ringspeicher" merken und ihn nach Ablauf der Verzögerungszeit Δt auf den Ausgang schicken.
Prinzip der Realisierung der zeitversetzten Wiedergabe mit read(..) und write(..).

Bild 6.2-2: Prinzip der Realisierung der zeitversetzten Wiedergabe mit read(..) und write(..).

  • Bei dem Projekt wav_cp_linux wurde die Klasse Ton um folgende Methoden erweitert:
  • void oeffneAudio(void); - Konfiguriert die Soundkarte wie im Beispielprogramm weiter oben.
  • void schliesseAudio(void); - Freigabe der Soundkarte
  • void aufnehmen(void); - Aufnahme in einen Puffer
  • void abspielen(void); - Abspielen des Puffers
  • void abspielen_test(void); - s.u.
  • void lesePuffer(void); - Übertragen des Puffers auf die double-Arrays für den linken und rechten Kanal (Umwandlung Byte-Darstellung in double-Darstellung).
  • void schreibePuffer(void); - Übertragen der double-Array auf den Puffer (Umwandlung double-Darstellung in Byte-Darstellung)
wav_cp_linux.zip - Kombination der .wav-Bearbeitungsmethoden mit den Aufnahme- und Abspielmethoden.
  • abspielen_test(void) nimmt eine Sonderposition ein:
  • Hier werden der Funktion write(...) wiederholt immer nur 4 Byte des Puffers (ein 16-Bit-Stereo-Sample) übergeben.
  • Sollte dieses Vorgehen funktionieren, so erfüllt die Funktion write(..) zwei Bedingungen:
  • a) write(..) benötigt zur Abarbeitung weniger als eine Sampleperiode T=1/fs.
  • b) Wird write(..) wiederholt aufgerufen, so wird der neue Sample erst am Audioausgang gesetzt, wenn seit Setzen des letzten Samples T=1/fs vergangen ist.
  • Hier ist ein Testprogramm, das obiges Beispiel realisiert (Aufnehmen / Abspielen), aber auch synthetisch generierte Sinustöne abspielt, insbesondere auch unter Benutzung von abspielen_test(void).
#include<math.h>
#include<iostream>
#include<fstream>
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/ioctl.h>
#include<linux/soundcard.h>
using namespace std;
#include "Ton.h"
int main()
{
    long int anzahl_samples = 44100;
    Ton ton(anzahl_samples);
    Ton ton2(anzahl_samples);
    Ton ton3(anzahl_samples);
    double f1 = 441;
    double f2 = 882;
    double dt = 1.0/44100.0;
    double  t = 0.0;
    for(int i=0;i<anzahl_samples;i++)
    {
        ton.setLinks(i,0.0);
        ton.setRechts(i,0.0);
        t+=dt;
    }
    t = 0.0;
    for(int i=0;i<44100;i++)
    {
        ton.setLinks(i,0.9*sin(2.0*M_PI*f1*t));
        ton.setRechts(i,0.9*cos(2.0*M_PI*f1*t));
        ton3.setLinks(i,0.9*sin(2.0*M_PI*f2*t));
        ton3.setRechts(i,0.9*sin(2.0*M_PI*f2*t));
        t+=dt;
    }
    ton.speicher("sinus2.wav");   
    ton2.lade("sinus2.wav");   
    ton2.speicher("sinus3.wav");   
    //Ergänzungen für die Verwendung des Audio-Devices unter Linux:
    ton.oeffneAudio();
    cout<<"Sinuston mit 441Hz abspielen."<<endl;
    ton.schreibePuffer();
    ton.abspielen();
    //Signal testweise nur über 4-Byte-Puffer ausgeben:
    t = 0.0;
    for(int i=0;i<44100;i++)
    {
        ton.setLinks(i,0.9*sin(2.0*M_PI*f2*t));
        ton.setRechts(i,0.9*cos(2.0*M_PI*f2*t));
        t+=dt;
    }
    ton.schreibePuffer();
    ton.abspielen_test();
    ton.aufnehmen();
    ton.lesePuffer();
    ton.schreibePuffer();
    ton.abspielen();
    ton.schliesseAudio();    
}

Code 6.2-2: testton.cpp im Projekt wav_cp_linux.

Aufgabe
  • Prüfen Sie nach, ob abspielen_test(void) funktioniert.
  • Versuchen Sie auf Grundlage des Projektes wav_cp_linux unter Linux zeitversetzte Wiedergabe eines gleichzeitig aufgezeichneten Audiosignals zu realisieren.
  • Singen Sie einen Chanon.