Klang. Von mechanischen Vibrationen bis zur ALSA SoC-Schicht





Wir bei SberDevices stellen Geräte her, auf denen Sie Musik hören, einen Film ansehen und vieles mehr können. Wie Sie sich vorstellen können, ist dies ohne Ton alles ohne Interesse. Schauen wir uns an, was mit dem Sound im Gerät passiert, von der Schulphysik bis zum ALSA-Subsystem unter Linux.



Was ist der Ton, den wir hören? Um dies vollständig zu vereinfachen, sind dies Schwingungen von Luftpartikeln, die unser Trommelfell erreichen. Ihr Gehirn übersetzt sich dann natürlich in angenehme Musik oder in den Klang eines Motorradfahrers, der vor dem Fenster vorbeifährt, aber lassen Sie uns zunächst auf Vibrationen eingehen.



Im 19. Jahrhundert wurde den Menschen klar, dass man versuchen kann, Schallschwingungen aufzunehmen und sie dann zu reproduzieren.



Lassen Sie uns zunächst sehen, wie eines der ersten Tonaufnahmegeräte funktioniert.





Der Phonograph und sein Erfinder Thomas Edison

Fotoquelle



Hier ist alles einfach. Sie nahmen einen Zylinder und wickelten ihn in Folie ein. Dann nahmen sie etwas, das sich verjüngt (um es lauter zu machen), mit einer Membran am Ende. Eine kleine Nadel ist an der Membran angebracht. Die Nadel wurde gegen die Folie gelehnt. Dann drehte eine speziell ausgebildete Person den Zylinder und sagte etwas in den Resonator. Eine von einer Membran angetriebene Nadel bohrte Vertiefungen in die Folie. Wenn es ausreicht, den Zylinder gleichmäßig zu verdrehen, stellt sich die Abhängigkeit der Membranoszillationsamplitude von der vom Zylinder "gewickelten" Zeit heraus.







Um das Signal abzuspielen, mussten Sie den Zylinder von Anfang an erneut drehen - die Nadel fällt in die Rillen und überträgt die aufgezeichneten Schwingungen auf die Membran und die auf den Resonator. Also hören wir die Aufnahme. Auf YouTube finden Sie leicht interessante Beiträge von Enthusiasten.



Übergang zu Elektrizität



Schauen wir uns nun etwas Moderneres an, das aber nicht sehr kompliziert ist. Zum Beispiel ein Rollenmikrofon. Luftschwingungen verändern nun die Position des Magneten innerhalb der Spule und dank elektromagnetischer Induktion erhalten wir am Ausgang die Abhängigkeit der Amplitude der Schwingungen des Magneten (und damit der Membran) von der Zeit. Erst jetzt drückt sich diese Abhängigkeit nicht durch Vertiefungen auf der Folie aus, sondern durch die Abhängigkeit der elektrischen Spannung am Ausgang des Mikrofons von der Zeit.







Um eine solche Darstellung von Schwankungen im Computerspeicher zu speichern, müssen diese diskretisiert werden. Dies geschieht durch eine spezielle Hardware - einen Analog-Digital-Wandler (ADC). Der ADC kann den Spannungswert (bis zur Auflösung der ADC-Ganzzahlarithmetik) am Eingang viele Male in einer Sekunde speichern und in den Speicher schreiben. Die Anzahl solcher Abtastungen pro Sekunde wird als Abtastrate bezeichnet. Typische Werte sind 8000 Hz - 96000 Hz.



Wir werden nicht auf die Details des ADC eingehen, da er eine separate Artikelserie verdient. Kommen wir zur Hauptsache: Der gesamte Sound, mit dem Linux-Treiber und alle Arten von Geräten arbeiten, wird genau in Form einer Abhängigkeit von Amplitude und Zeit dargestellt. Dieses Aufnahmeformat wird als PCM (Pulse-Code-Modulation) bezeichnet. Für jede Zeitscheibe mit einer Dauer von 1 / sample_rate wird der Wert der Schallamplitude angegeben. Aus PCM werden WAV-Dateien erstellt.



Ein Beispiel für die PCM-Visualisierung für eine WAV-Datei mit Musik, bei der die horizontale Achse die Zeit und die vertikale Achse die Signalamplitude ist:







Da unser Board über einen Stereoausgang für Lautsprecher verfügt, müssen Sie lernen, wie Sie Stereoton in einer WAV-Datei speichern: linker und rechter Kanal. Hier ist alles einfach - die Beispiele wechseln sich folgendermaßen ab:







Diese Art des Speicherns von Daten wird als Interleaved bezeichnet. Es gibt andere Möglichkeiten, aber wir werden sie jetzt nicht berücksichtigen.



Lassen Sie uns nun herausfinden, welche elektrischen Signale wir benötigen, um die Datenübertragung zwischen Geräten zu organisieren. Und es wird nicht viel benötigt:



  1. Bit Clock (BCLK) ist ein Taktsignal (oder Takt), mit dem die Hardware bestimmt, wann das nächste Bit gesendet werden soll.
  2. Frame Clock (FCLK oder auch LRCLK genannt) ist ein Zeitsignal, anhand dessen das Gerät versteht, wann die Übertragung eines anderen Kanals erforderlich ist.
  3. Daten sind die Daten selbst.






Zum Beispiel haben wir eine Datei mit den folgenden Eigenschaften:

  • Abtastbreite = 16 Bit;
  • Abtastrate = 48000 Hz;
  • Kanäle = 2.


Dann müssen wir die folgenden Frequenzwerte einstellen:

  • FCLK = 48000 Hz;
  • BCLK = 48000 * 16 * 2 Hz.


Um noch mehr Kanäle zu übertragen, wird das TDM-Protokoll verwendet, das sich von I2S dadurch unterscheidet, dass FCLK kein Tastverhältnis von 50% mehr benötigt und die ansteigende Flanke nur den Beginn eines Pakets von Abtastwerten festlegt, die zu verschiedenen Kanälen gehören.



Allgemeines Schema



Gleich zur Hand war die amlogic s400-Karte, an die Sie einen Lautsprecher anschließen können. Es ist der Upstream-Linux-Kernel installiert. Wir werden an diesem Beispiel arbeiten.



Unser Board besteht aus einem SoC (amlogic A113x), an den der TAS5707PHPR DAC angeschlossen ist. Und das allgemeine Schema sieht folgendermaßen aus:



Was SoC tun kann:

  • SoC hat 3 Pins: BCLK, LRCLK, DATA;
  • Sie können die CLK-Pins über die speziellen Register des SoC so konfigurieren, dass sie die richtigen Frequenzen haben.
  • Zu diesem SoC können Sie auch sagen: „Hier ist eine Adresse im Speicher. Es enthält PCM-Daten. Senden Sie diese Daten Stück für Stück über die DATA-Leitung. " Dieser Speicherbereich wird als hwbuf bezeichnet.


Um Sound abzuspielen, teilt der Linux-Treiber dem SoC mit, welche Frequenzen auf den BCLK- und LRCLK-Leitungen eingestellt werden sollen. Außerdem teilt Ihnen der Linux-Treiber mit, wo sich hwbuf befindet. Der DAC (TAS5707) empfängt die Daten dann über die DATA-Leitung und wandelt sie in zwei analoge elektrische Signale um. Diese Signale werden dann über ein Adernpaar übertragen {analog +; analog-} in zwei Lautsprecher.



Weiter zu Linux



Wir sind bereit, mit dem Aussehen dieser Schaltung unter Linux fortzufahren. Erstens gibt es eine "Bibliothek" für die Arbeit mit Sound unter Linux, die zwischen dem Kernel und dem Userspace verteilt ist. Es heißt ALSA und wir werden seinen Namen betrachten. Die Essenz von ALSA besteht darin, dass der Benutzerbereich und der Kernel sich auf die Schnittstelle für die Arbeit mit Audiogeräten "einigen".



Die benutzerdefinierte ALSA-Bibliothek interagiert mit dem Kernel über die ioctl-Schnittstelle. Es werden die im Verzeichnis / dev / snd / erstellten Geräte pcmC {x} D {y} {c, p} verwendet. Diese Geräte werden von einem Treiber erstellt, der vom SoC-Anbieter geschrieben werden muss. Zum Beispiel der Inhalt dieses Ordners auf amlogic s400:



# ls /dev/snd/
controlC0    pcmC0D0p   pcmC0D0   pcmC0D1c   pcmC0D1p   pcmC0D2c


Im Namen von pcmC {x} D {y} {c, p}:

X - Soundkartennummer (möglicherweise mehrere);

Y ist die Nummer der Schnittstelle auf der Karte (z. B. kann pcmC0D0p für die Wiedergabe zu Lautsprechern über die tdm-Schnittstelle und pcmC0D1c ​​für die Aufnahme von Ton von Mikrofonen über eine andere Hardwareschnittstelle verantwortlich sein).

p - sagt, dass das Gerät zum Abspielen von Ton (Wiedergabe);

c - sagt, dass das Gerät für die Tonaufnahme (Aufnahme).



In unserem Fall entspricht das pcmC0D0p-Gerät genau der I2S-Wiedergabeschnittstelle. D1 ist spdif und D2 ist pdm Mikrofone, aber wir werden nicht darüber sprechen.



Gerätebaum



Die Beschreibung der Soundkarte beginnt mit device_tree [arch / arm64 / boot / dts / amlogic / meson-axg-s400.dts]:



sound {
    compatible = "amlogic,axg-sound-card";
    model = "AXG-S400";
    audio-aux-devs = <&tdmin_a>, <&tdmin_b>,  <&tdmin_c>,
             <&tdmin_lb>, <&tdmout_c>;

    dai-link-6 {
        sound-dai = <&tdmif_c>;
        dai-format = "i2s";
        dai-tdm-slot-tx-mask-2 = <1 1>;
        dai-tdm-slot-rx-mask-1 = <1 1>;
        mclk-fs = <256>;
        codec-1 {
            sound-dai = <&speaker_amp1>;
        };
    };
    dai-link-7 {
        sound-dai = <&spdifout>;
        codec {
            sound-dai = <&spdif_dit>;
        };
    };
    dai-link-8 {
        sound-dai = <&spdifin>;
        codec {
            sound-dai = <&spdif_dir>;
        };
    };
    dai-link-9 {
        sound-dai = <&pdm>;
        codec {
            sound-dai = <&dmics>;
        };
    };
};


&i2c1 {
    speaker_amp1: audio-codec@1b {
        compatible = "ti,tas5707";
        reg = <0x1b>;
        reset-gpios = <&gpio_ao GPIOAO_4 GPIO_ACTIVE_LOW>;
        #sound-dai-cells = <0>;
    };
};
&tdmif_c {
    pinctrl-0 = <&tdmc_sclk_pins>, <&tdmc_fs_pins>,
            <&tdmc_din1_pins>, <&tdmc_dout2_pins>,
            <&mclk_c_pins>;
    pinctrl-names = "default";
    status = "okay";
};


Hier sehen wir die 3 Geräte, die dann in / dev / snd erscheinen: tdmif_c, spdif, pdm.



Das Gerät, über das der Ton ausgegeben wird, heißt dai-link-6. Es funktioniert unter der Kontrolle des TDM-Treibers. Es stellt sich die Frage: Wir haben darüber gesprochen, wie Ton über I2S und dann plötzlich über TDM übertragen werden kann. Dies ist leicht zu erklären: Wie ich oben geschrieben habe, ist I2S immer noch das gleiche TDM, aber mit klaren Anforderungen an den LRCLK-Arbeitszyklus und die Anzahl der Kanäle - es sollten zwei davon sein. Der TDM-Treiber liest dann das Feld dai-format = "i2s". und wird verstehen, dass er im I2S-Modus arbeiten muss.



Als nächstes wird angegeben, welcher DAC (unter Linux werden sie als "Codec" bezeichnet) unter Verwendung der Speaker_amp1-Struktur auf der Karte installiert wird. Beachten Sie, dass sofort angezeigt wird, an welche I2C-Leitung (nicht zu verwechseln mit I2S!) Unser TAS5707 DAC angeschlossen ist. In diesem Sinne wird der Verstärker dann vom Treiber aus eingeschaltet und abgestimmt.



Die Struktur tdmif_c beschreibt, welche SoC-Pins als I2S-Schnittstelle fungieren.



ALSA SoC-Schicht



Für SoCs mit Audio-Unterstützung verfügt Linux über eine ALSA-SoC-Schicht. Sie können Codecs beschreiben (denken Sie daran, dass jeder DAC in ALSA-Begriffen so genannt wird) und festlegen, wie diese Codecs verbunden sind.



Codecs in Linux-Kernel-Begriffen werden als DAI (Digital Audio Interface) bezeichnet. Die TDM / I2S-Schnittstelle selbst, die sich im SoC befindet, wird auch als DAI bezeichnet, und die Arbeit damit wird auf ähnliche Weise ausgeführt.



Der Treiber beschreibt den Codec mit struct snd_soc_dai. Der interessanteste Teil in der Beschreibung des Codecs ist die Operation zum Einstellen der TDM-Übertragungsparameter. Sie befinden sich hier: struct snd_soc_dai -> struct snd_soc_dai_driver -> struct snd_soc_dai_ops. Betrachten wir die wichtigsten Bereiche für das Verständnis (sound / soc / soc-dai.h):



struct snd_soc_dai_ops {
    /*
     * DAI clocking configuration.
     * Called by soc_card drivers, normally in their hw_params.
     */
    int (*set_sysclk)(struct snd_soc_dai *dai,
        int clk_id, unsigned int freq, int dir);
    int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
        unsigned int freq_in, unsigned int freq_out);
    int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
    int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);
    ...
Genau die Funktionen, mit denen TDM-Uhren belichtet werden. Diese Funktionen werden normalerweise vom SoC-Anbieter implementiert.



...
int (*hw_params)(struct snd_pcm_substream *,
    struct snd_pcm_hw_params *, struct snd_soc_dai *);
...
Die interessanteste Funktion ist hw_params ().

Es wird benötigt, um die gesamte SoC-Hardware gemäß den Parametern der PCM-Datei zu konfigurieren, die wir abspielen möchten. Sie wird später die Funktionen der obigen Gruppe aufrufen, um die TDM-Uhren zu installieren.



...
int (*trigger)(struct snd_pcm_substream *, int,
    struct snd_soc_dai *);
...
Und diese Funktion macht den allerletzten Schritt nach der Konfiguration des Codecs - sie versetzt den Codec in den aktiven Modus.



Der DAC, der analogen Ton an den Lautsprecher ausgibt, wird durch genau dieselbe Struktur beschrieben. snd_soc_dai_ops konfiguriert in diesem Fall den DAC so, dass Daten im richtigen Format empfangen werden. Diese DAC-Einrichtung erfolgt normalerweise über die I2C-Schnittstelle.



Alle Codecs, die im Gerätebaum in der Struktur angegeben sind,

dai-link-6 {
    ...
    codec-1 {
        sound-dai = <&speaker_amp1>;
    };
};


- und es kann viele davon geben, die zu einer Liste hinzugefügt und an das Gerät / dev / snd / pcm * angehängt werden. Dies ist erforderlich, damit der Kernel beim Abspielen von Sound alle erforderlichen Codec-Treiber umgehen und konfigurieren / aktivieren kann.



Jeder Codec muss Ihnen mitteilen, welche PCM-Parameter er unterstützt. Dies geschieht mit einer Struktur:

struct snd_soc_pcm_stream {
    const char *stream_name;
    u64 formats;            /* SNDRV_PCM_FMTBIT_* */
    unsigned int rates;     /* SNDRV_PCM_RATE_* */
    unsigned int rate_min;      /* min rate */
    unsigned int rate_max;      /* max rate */
    unsigned int channels_min;  /* min channels */
    unsigned int channels_max;  /* max channels */
    unsigned int sig_bits;      /* number of bits of content */
};


Wenn einer der Codecs in der Kette bestimmte Parameter nicht unterstützt, endet alles mit einem Fehler.



Die entsprechende TDM-Treiberimplementierung für amlogic s400 kann in sound / soc / meson / axg-tdm-interface.c angezeigt werden . Die Implementierung des TAS5707-Codec-Treibers erfolgt in sound / soc / codecs / tas571x.c



Benutzerteil



Nun wollen wir sehen, was passiert, wenn der Benutzer einen Sound abspielen möchte. Ein leicht zu erlernendes Beispiel für eine benutzerdefinierte ALSA-Implementierung ist tinyalsa . Dort kann der Quellcode für alle folgenden Elemente angezeigt werden.

Beinhaltet das Dienstprogramm tinyplay. Um den Sound abzuspielen, müssen Sie Folgendes ausführen:



bash$ tinyplay ./music.wav -D 0 -d 0
(Mit den Optionen -D und -d können Sie den Sound über / dev / snd / pcmC0D0p abspielen.)



Was ist los?

Hier ist ein kurzes Blockdiagramm, gefolgt von Erklärungen:







  1. [userspace] Analysieren Sie den WAV-Header, um die PCM-Parameter (Abtastrate, Bitbreite, Kanäle) der wiedergegebenen Datei zu ermitteln. Wir fügen alle Parameter zur Struktur snd_pcm_hw_params hinzu.
  2. [Userspace] Öffnen Sie das Gerät / dev / snd / pcmC0D0p.
  3. [userspace] ioctl(…, SNDRV_PCM_IOCTL_HW_PARAMS ,…), PCM- .
  4. [kernel] PCM-, . :

    • ;
    • .
  5. , /dev/snd/pcmC0D0p ( ), .
  6. [userspace] , PCM-.
  7. [userspace] ioctl(…, SNDRV_PCM_IOCTL_WRITEI_FRAMES, …). I WRITEI , PCM- interleaved-.
  8. [kernelspace] , /dev/snd/pcmC0D0p , .
  9. [kernelspace] Kopieren Sie den Benutzer buf mit copy_from_user () nach hwbuf (siehe Allgemeines Schema).
  10. [Userspace] gehe zu 6.


Die Implementierung des Kernel-Teils von ioctl kann durch Suchen nach dem Wort SNDRV_PCM_IOCTL_ * angezeigt werden



Fazit



Wir haben jetzt eine Vorstellung davon, wohin der Sound im Linux-Kernel geht. In den nächsten Artikeln wird analysiert, wie Sound von Android-Anwendungen abgespielt wird, und dafür ist es noch ein langer Weg.



All Articles