Di SberDevices kami membuat perangkat tempat Anda dapat mendengarkan musik, menonton film, dan banyak lagi. Seperti yang bisa Anda bayangkan, tanpa suara, semua ini tidak menarik. Mari kita lihat apa yang terjadi pada suara di perangkat, dari fisika sekolah hingga subsistem ALSA di Linux.
Suara apa yang kita dengar? Untuk menyederhanakan sepenuhnya, ini adalah getaran partikel udara yang mencapai gendang telinga kita. Otak mereka, tentu saja, kemudian diterjemahkan menjadi musik yang menyenangkan atau menjadi suara pengendara sepeda motor yang lewat di luar jendela, tapi mari kita pikirkan getarannya untuk saat ini.
Kembali ke abad ke-19, orang menyadari bahwa Anda dapat mencoba merekam getaran suara dan mereproduksinya.
Pertama, mari kita lihat bagaimana salah satu alat perekam suara pertama bekerja.
Fonograf dan penemunya Sumber Foto Thomas Edison
Semuanya sederhana di sini. Mereka mengambil silinder dan membungkusnya dengan kertas timah. Kemudian mereka mengambil sesuatu yang meruncing (agar lebih keras) dengan selaput di ujungnya. Jarum kecil dipasang pada membran. Jarumnya disandarkan ke foil. Kemudian orang yang terlatih khusus memutar silinder dan mengatakan sesuatu ke resonator. Sebuah jarum, digerakkan oleh membran, membuat lekukan di foil. Jika dirasa cukup untuk memutar silinder secara merata, maka ketergantungan amplitudo osilasi membran pada waktu "luka" pada silinder akan keluar.
Untuk memainkan sinyal, Anda hanya perlu memutar silinder lagi dari awal - jarum akan jatuh ke dalam alur dan mengirimkan getaran yang terekam ke membran, dan ke resonator. Jadi kami mendengar rekamannya. Anda dapat dengan mudah menemukan postingan menarik dari para penggemar di YouTube.
Transisi ke listrik
Sekarang mari kita lihat sesuatu yang lebih modern, tetapi tidak terlalu rumit. Misalnya, mikrofon reel. Osilasi udara sekarang mengubah posisi magnet di dalam kumparan, dan berkat induksi elektromagnetik, kita mendapatkan keluaran ketergantungan amplitudo osilasi magnet (dan karenanya membran) tepat waktu. Hanya sekarang ketergantungan ini diekspresikan bukan oleh tekanan pada foil, tetapi oleh ketergantungan tegangan listrik pada output mikrofon tepat waktu.
Untuk dapat menyimpan representasi fluktuasi seperti itu dalam memori komputer, mereka harus diskrit. Ini dilakukan oleh perangkat keras khusus - konverter analog-ke-digital (ADC). ADC mampu menghafal nilai tegangan (hingga resolusi aritmatika integer ADC) di input berkali-kali dalam satu detik dan menuliskannya ke memori. Jumlah sampel tersebut per detik disebut laju sampel. Nilai tipikal adalah 8000 Hz - 96000 Hz.
Kami tidak akan membahas detail ADC, karena ADC berhak atas serangkaian artikel terpisah. Mari beralih ke hal utama - semua suara yang digunakan driver Linux dan semua jenis perangkat diwakili secara tepat dalam bentuk amplitudo versus ketergantungan waktu. Format perekaman ini disebut PCM (Pulse-code modulation). Untuk setiap irisan waktu dengan durasi 1 / sample_rate, nilai amplitudo suara ditunjukkan. File .Wav terdiri dari PCM.
Contoh visualisasi PCM untuk file .wav dengan musik, di mana sumbu horizontal adalah waktu, dan sumbu vertikal adalah amplitudo sinyal:
Karena papan kami memiliki output stereo untuk speaker, Anda perlu mempelajari cara menyimpan suara stereo dalam satu file .wav: saluran kiri dan kanan. Semuanya sederhana di sini - sampelnya akan bergantian seperti ini:
Cara menyimpan data ini disebut interleaved. Ada cara lain, tetapi kami tidak akan mempertimbangkannya sekarang.
Sekarang mari kita cari tahu sinyal listrik apa yang kita butuhkan untuk mengatur transfer data antar perangkat. Dan tidak banyak yang dibutuhkan:
- Bit Clock (BCLK) adalah sinyal clock (atau clock) yang digunakan perangkat keras untuk menentukan kapan harus mengirim bit berikutnya.
- Frame Clock (FCLK atau disebut juga LRCLK) adalah sinyal pengaturan waktu yang digunakan peralatan untuk memahami kapan perlu mulai mentransmisikan saluran lain.
- Data adalah data itu sendiri.
Misalnya, kami memiliki file dengan karakteristik berikut:
- lebar sampel = 16 bit;
- tingkat sampling = 48000 Hz;
- saluran = 2.
Maka kita perlu mengatur nilai frekuensi berikut:
- FCLK = 48000 Hz;
- BCLK = 48000 * 16 * 2 Hz.
Untuk mengirimkan lebih banyak saluran, protokol TDM digunakan, yang berbeda dari I2S karena FCLK tidak lagi diharuskan memiliki siklus kerja 50%, dan tepi naik hanya menetapkan awal paket sampel yang termasuk ke saluran yang berbeda.
Skema umum
Tepat di tangan adalah papan amlogic s400, tempat Anda dapat menghubungkan speaker. Ini memiliki kernel Linux hulu yang diinstal. Kami akan mengerjakan contoh ini.
Papan kami terdiri dari SoC (amlogic A113x) yang terhubung dengan TAS5707PHPR DAC . Dan skema umumnya terlihat seperti ini:
Apa yang bisa dilakukan SoC:
- SoC memiliki 3 pin: BCLK, LRCLK, DATA;
- Anda dapat mengkonfigurasi pin CLK melalui register khusus SoC sehingga mereka memiliki frekuensi yang benar;
- Anda juga dapat mengatakan ke SoC ini: βIni alamat di memori. Ada data PCM. Kirim data ini sedikit demi sedikit melalui jalur DATA. " Area memori ini akan disebut hwbuf.
Untuk memutar suara, driver Linux memberi tahu SoC frekuensi mana yang harus disetel pada jalur BCLK dan LRCLK. Selain itu, driver Linux memberi tahu Anda di mana hwbuf berada. DAC (TAS5707) kemudian menerima data melalui jalur DATA dan mengubahnya menjadi dua sinyal listrik analog. Sinyal-sinyal ini kemudian dikirim melalui sepasang kabel {analog +; analog-} menjadi dua speaker.
Pindah ke Linux
Kami siap untuk beralih ke tampilan sirkuit ini di Linux. Pertama, ada "perpustakaan" untuk bekerja dengan suara di Linux, yang tersebar di antara kernel dan ruang pengguna. Ini disebut ALSA, dan kami akan mempertimbangkan namanya. Inti dari ALSA adalah untuk userspace dan kernel untuk "menyetujui" antarmuka untuk bekerja dengan perangkat suara.
Pustaka ALSA khusus berinteraksi dengan kernel menggunakan antarmuka ioctl. Perangkat pcmC {x} D {y} {c, p} yang dibuat di direktori / dev / snd / digunakan. Perangkat ini dibuat oleh driver yang harus ditulis oleh vendor SoC. Misalnya, berikut adalah konten folder ini di amlogic s400:
# ls /dev/snd/
controlC0 pcmC0D0p pcmC0D0 pcmC0D1c pcmC0D1p pcmC0D2c
Atas nama pcmC {x} D {y} {c, p}:
X - nomor kartu suara (mungkin ada beberapa);
Y - jumlah antarmuka pada kartu (misalnya, pcmC0D0p dapat bertanggung jawab untuk memutar ulang ke speaker melalui antarmuka tdm, dan pcmC0D1c ββuntuk merekam suara dari mikrofon melalui antarmuka perangkat keras yang berbeda);
p - mengatakan bahwa perangkat untuk memutar suara (pemutaran);
c - mengatakan bahwa perangkat untuk merekam suara (menangkap).
Dalam kasus kami, perangkat pcmC0D0p sama persis dengan antarmuka I2S pemutaran. D1 adalah spdif dan D2 adalah mikrofon pdm, tetapi kami tidak akan membicarakannya.
Pohon perangkat
Deskripsi soundcard dimulai dengan 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";
};
Di sini kita melihat 3 perangkat yang kemudian akan muncul di / dev / snd: tdmif_c, spdif, pdm.
Perangkat yang digunakan untuk mengeluarkan suara disebut dai-link-6. Ini akan bekerja di bawah kendali driver TDM. Muncul pertanyaan: kami berbicara tentang bagaimana mengirimkan suara melalui I2S, dan kemudian, tiba-tiba, TDM. Ini mudah dijelaskan: seperti yang saya tulis di atas, I2S masih TDM yang sama, tetapi dengan persyaratan yang jelas untuk siklus tugas LRCLK dan jumlah saluran - harus ada dua di antaranya. Driver TDM kemudian akan membaca bidang dai-format = "i2s"; dan akan mengerti bahwa dia perlu bekerja dalam mode I2S.
Selanjutnya, ditunjukkan DAC mana (di dalam Linux disebut sebagai "codec") yang diinstal pada papan menggunakan struktur speaker_amp1. Perhatikan bahwa itu segera ditunjukkan ke jalur I2C mana (jangan bingung dengan I2S!) DAC TAS5707 kami terhubung. Di sepanjang garis inilah amplifier kemudian akan dihidupkan dan disetel dari driver.
Struktur tdmif_c menjelaskan pin SoC mana yang akan bertindak sebagai antarmuka I2S.
Lapisan SoC ALSA
Untuk SoC yang memiliki dukungan audio di dalamnya, Linux memiliki lapisan ALSA SoC. Ini memungkinkan Anda untuk mendeskripsikan codec (ingat bahwa inilah yang disebut DAC dalam istilah ALSA), memungkinkan Anda untuk menentukan bagaimana codec ini terhubung.
Codec dalam istilah kernel Linux disebut DAI (Digital Audio Interface). Antarmuka TDM / I2S itu sendiri, yang ada di SoC, juga disebut DAI, dan bekerja dengannya dilakukan dengan cara yang serupa.
Pengemudi menjelaskan codec menggunakan struct snd_soc_dai. Bagian yang paling menarik dalam deskripsi codec adalah operasi untuk mengatur parameter transmisi TDM. Mereka berada di sini: struct snd_soc_dai -> struct snd_soc_dai_driver -> struct snd_soc_dai_ops. Mari pertimbangkan bidang terpenting untuk pemahaman (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);
...
Inilah fungsi-fungsi yang dengannya jam TDM diekspos. Fungsi-fungsi ini biasanya diimplementasikan oleh vendor SoC.
...
int (*hw_params)(struct snd_pcm_substream *,
struct snd_pcm_hw_params *, struct snd_soc_dai *);
...
Fungsi yang paling menarik adalah hw_params ().
Diperlukan untuk mengkonfigurasi semua perangkat keras SoC sesuai dengan parameter file PCM yang coba kita mainkan. Dialah yang nantinya akan memanggil fungsi dari grup di atas untuk menginstal jam TDM.
...
int (*trigger)(struct snd_pcm_substream *, int,
struct snd_soc_dai *);
...
Dan fungsi ini mengambil langkah terakhir setelah mengonfigurasi codec - ini menempatkan codec ke mode aktif.
DAC yang akan mengeluarkan suara analog ke speaker dijelaskan dengan struktur yang persis sama. snd_soc_dai_ops dalam hal ini akan mengkonfigurasi DAC untuk menerima data dalam format yang benar. Pengaturan DAC ini biasanya dilakukan melalui antarmuka I2C.
Semua codec yang ditentukan di pohon perangkat dalam struktur,
dai-link-6 {
...
codec-1 {
sound-dai = <&speaker_amp1>;
};
};
- dan ada banyak dari mereka, ditambahkan ke satu daftar dan dilampirkan ke perangkat / dev / snd / pcm *. Ini diperlukan agar saat memutar suara, kernel dapat melewati semua driver codec yang diperlukan dan mengkonfigurasi / mengaktifkannya.
Setiap codec harus memberi tahu Anda parameter PCM mana yang didukungnya. Ini dilakukan dengan 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 */
};
Jika salah satu codec dalam rantai tidak mendukung parameter tertentu, semuanya akan berakhir dengan kesalahan.
Implementasi driver TDM yang sesuai untuk amlogic s400 dapat dilihat di sound / soc / meson / axg-tdm-interface.c . Dan implementasi driver codec TAS5707 ada di sound / soc / codecs / tas571x.c
Bagian pengguna
Sekarang mari kita lihat apa yang terjadi saat pengguna ingin memutar suara. Contoh implementasi ALSA kustom yang mudah dipelajari adalah tinyalsa . Kode sumber untuk semua yang berikut ini dapat dilihat di sana.
Termasuk utilitas tinyplay. Untuk memainkan suara yang Anda butuhkan untuk menjalankan:
bash$ tinyplay ./music.wav -D 0 -d 0(Opsi -D dan -d memberi tahu Anda untuk memutar suara melalui / dev / snd / pcmC0D0p).
Apa yang terjadi?
Berikut diagram blok pendek, diikuti penjelasannya:
- [userspace] Parsing header .wav untuk mengetahui parameter PCM (laju sampel, lebar bit, saluran) dari file yang sedang diputar. Kami menambahkan semua parameter ke struct snd_pcm_hw_params.
- [userspace] Buka perangkat / dev / snd / pcmC0D0p.
- [userspace] ioctl(β¦, SNDRV_PCM_IOCTL_HW_PARAMS ,β¦), PCM- .
- [kernel] PCM-, . :
- ;
- .
- , /dev/snd/pcmC0D0p ( ), .
- [userspace] , PCM-.
- [userspace] ioctl(β¦, SNDRV_PCM_IOCTL_WRITEI_FRAMES, β¦). I WRITEI , PCM- interleaved-.
- [kernelspace] , /dev/snd/pcmC0D0p , .
- [kernelspace] salin buf pengguna ke hwbuf (lihat Skema Umum) menggunakan copy_from_user ().
- [userspace] ke 6.
Implementasi bagian kernel ioctl dapat dilihat dengan mencari kata SNDRV_PCM_IOCTL_ *
Kesimpulan
Kami sekarang memiliki ide di mana suara masuk di kernel Linux. Dalam artikel berikutnya, akan ada analisis tentang bagaimana suara dimainkan dari aplikasi Android, dan untuk ini jalannya masih panjang.