صوت. من الاهتزازات الميكانيكية إلى طبقة ALSA SoC





نحن في SberDevices نصنع الأجهزة التي يمكنك من خلالها الاستماع إلى الموسيقى ومشاهدة فيلم وغير ذلك الكثير. كما يمكنك أن تتخيل ، بدون صوت ، هذا كله لا يهم. دعونا نلقي نظرة على ما يحدث للصوت في الجهاز ، من فيزياء المدرسة إلى نظام ALSA الفرعي في Linux.



ما هو الصوت الذي نسمعه؟ للتبسيط تمامًا ، هذه اهتزازات من جزيئات الهواء التي تصل إلى طبلة الأذن. ثم يترجم دماغهم ، بالطبع ، إلى موسيقى ممتعة أو إلى صوت سائق دراجة نارية يمر خارج النافذة ، لكن دعونا نتحدث عن الاهتزازات في الوقت الحالي.



في القرن التاسع عشر ، أدرك الناس أنه يمكنك محاولة تسجيل الاهتزازات الصوتية ثم إعادة إنتاجها.



أولاً ، دعنا نلقي نظرة على كيفية عمل أحد أجهزة التسجيل الأولى.





الفونوغراف ومخترعه توماس إديسون

مصدر الصورة



كل شيء بسيط هنا. أخذوا اسطوانة ولفوها بورق قصدير. ثم أخذوا شيئًا مدببًا (لجعله أعلى) مع وجود غشاء في النهاية. يتم توصيل إبرة صغيرة بالغشاء. تم ثني الإبرة على الرقاقة. ثم قام شخص مدرب بشكل خاص بلف الاسطوانة وقال شيئًا ما في الرنان. إبرة ، مدفوعة بغشاء ، جعلت المسافات البادئة في الرقاقة. إذا كان كافيًا لف الأسطوانة بالتساوي ، فإن اعتماد سعة تذبذب الغشاء على "الجرح" على الأسطوانة سينتهي.







لتشغيل الإشارة ، كان عليك فقط قلب الأسطوانة مرة أخرى من البداية - ستسقط الإبرة في التجاويف وتنقل الاهتزازات المسجلة إلى الغشاء ، وذلك إلى الرنان. لذلك نسمع التسجيل. يمكنك بسهولة العثور على مشاركات مثيرة للاهتمام من قبل المتحمسين على YouTube.



الانتقال إلى الكهرباء



الآن دعونا نلقي نظرة على شيء أكثر حداثة ، ولكن ليس معقدًا للغاية. على سبيل المثال ، ميكروفون بكرة. تغير تذبذبات الهواء الآن موضع المغناطيس داخل الملف ، وبفضل الحث الكهرومغناطيسي ، نحصل على اعتماد سعة تذبذبات المغناطيس (ومن ثم الغشاء) في الوقت المحدد. الآن فقط يتم التعبير عن هذا الاعتماد ليس من خلال انخفاضات على الرقاقة ، ولكن من خلال اعتماد الجهد الكهربائي عند خرج الميكروفون في الوقت المحدد.







من أجل تخزين مثل هذا التمثيل للتقلبات في ذاكرة الكمبيوتر ، يجب تحديدها. يتم ذلك عن طريق قطعة خاصة من الأجهزة - محول تناظري إلى رقمي (ADC). يمكن لـ ADC حفظ قيمة الجهد (حتى دقة حساب العدد الصحيح ADC) عند الإدخال عدة مرات في ثانية واحدة وكتابتها في الذاكرة. يسمى عدد هذه العينات في الثانية بمعدل العينة. القيم النموذجية هي 8000 هرتز - 96000 هرتز.



لن ندخل في تفاصيل ADC ، لأنها تستحق سلسلة منفصلة من المقالات. دعنا ننتقل إلى الشيء الرئيسي - كل الصوت الذي تعمل به برامج تشغيل Linux وجميع أنواع الأجهزة يتم تمثيله بدقة في شكل سعة مقابل اعتماد على الوقت. يسمى تنسيق التسجيل هذا PCM (تعديل رمز النبض). لكل شريحة زمنية بمدة 1 / sample_rate ، يشار إلى قيمة سعة الصوت. من PCM تتكون ملفات .wav.



مثال على تصور PCM لملف .wav مع الموسيقى ، حيث يكون المحور الأفقي هو الوقت ، والمحور الرأسي هو سعة الإشارة:







نظرًا لأن لوحتنا بها مخرج استريو للسماعات ، فأنت بحاجة إلى معرفة كيفية تخزين صوت الاستريو في ملف واحد بتنسيق .wav: القنوات اليسرى واليمنى. كل شيء بسيط هنا - سوف تتناوب العينات على النحو التالي:







تسمى طريقة تخزين البيانات هذه بالتشذير. هناك طرق أخرى ، لكننا لن نفكر فيها الآن.



الآن دعنا نتعرف على الإشارات الكهربائية التي نحتاجها لتنظيم نقل البيانات بين الأجهزة. وليس هناك حاجة إلى الكثير:



  1. Bit Clock (BCLK) هي إشارة ساعة (أو ساعة) يحدد من خلالها الجهاز وقت إرسال البت التالي.
  2. ساعة الإطار (FCLK أو تسمى أيضًا LRCLK) هي إشارة توقيت يفهم من خلالها الجهاز عندما يكون من الضروري البدء في إرسال قناة أخرى.
  3. البيانات هي البيانات نفسها.






على سبيل المثال ، لدينا ملف بالخصائص التالية:

  • عرض العينة = 16 بت ؛
  • معدل أخذ العينات = 48000 هرتز ؛
  • القنوات = 2.


ثم نحتاج إلى ضبط قيم التردد التالية:

  • FCLK = 48000 هرتز ؛
  • BCLK = 48000 * 16 * 2 هرتز.


لنقل المزيد من القنوات ، يتم استخدام بروتوكول TDM ، والذي يختلف عن I2S في أن FCLK لم يعد مطلوبًا لدورة عمل بنسبة 50 ٪ ، ولا تحدد الحافة الصاعدة سوى بداية حزمة من العينات التي تنتمي إلى قنوات مختلفة.



المخطط العام



في متناول اليد كانت لوحة amlogic s400 ، والتي يمكنك توصيل مكبر صوت بها. تم تثبيت نواة لينكس المنبع. سنعمل على هذا المثال.



يتكون مجلس الإدارة الخاص بنا من SoC (amlogic A113x) الذي يتصل به TAS5707PHPR DAC . والمخطط العام يبدو كما يلي:



ما يمكن أن تفعله شركة نفط الجنوب:

  • يحتوي SoC على 3 دبابيس: BCLK و LRCLK و DATA ؛
  • يمكنك تكوين دبابيس CLK من خلال السجلات الخاصة بشركة نفط الجنوب بحيث يكون لديهم الترددات الصحيحة ؛
  • يمكنك أيضًا أن تقول لـ SoC هذه: "هذا عنوان في الذاكرة. يحتوي على بيانات PCM. أرسل هذه البيانات شيئًا فشيئًا عبر خط البيانات ". ستسمى منطقة الذاكرة هذه hwbuf.


لتشغيل الصوت ، يخبر برنامج تشغيل Linux شركة نفط الجنوب بالترددات التي يجب ضبطها على خطوط BCLK و LRCLK. بالإضافة إلى ذلك ، يخبرك برنامج تشغيل Linux بمكان hwbuf. ثم يستقبل DAC (TAS5707) البيانات عبر خط البيانات ويحولها إلى إشارتين كهربائيتين تناظريتين. ثم يتم إرسال هذه الإشارات عبر زوج من الأسلاك {analog +؛ analog-} في مكبرات صوت.



ننتقل إلى Linux



نحن على استعداد للانتقال إلى شكل هذه الدائرة في Linux. أولاً ، هناك "مكتبة" للعمل مع الصوت في Linux ، وهي منتشرة بين kernel و userspace. يطلق عليه ALSA ، وسننظر في اسمه. يتمثل جوهر ALSA في أن "توافق" مساحة المستخدمين والنواة على الواجهة للعمل مع أجهزة الصوت.



تتفاعل مكتبة ALSA المخصصة مع النواة باستخدام واجهة ioctl. يتم استخدام أجهزة pcmC {x} D {y} {c، p} التي تم إنشاؤها في الدليل / dev / snd /. يتم إنشاء هذه الأجهزة بواسطة برنامج تشغيل يجب كتابته بواسطة بائع SoC. على سبيل المثال ، ها هي محتويات هذا المجلد على amlogic s400:



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


باسم pcmC {x} D {y} {c، p}:

X - رقم بطاقة الصوت (قد يكون هناك العديد منها) ؛

Y هو رقم الواجهة على البطاقة (على سبيل المثال ، يمكن أن يكون pcmC0D0p مسؤولاً عن التشغيل على مكبرات الصوت عبر واجهة tdm ، و pcmC0D1c ​​- لتسجيل الصوت من الميكروفونات عبر واجهة جهاز مختلفة) ؛

ص - يقول أن الجهاز لتشغيل الصوت (التشغيل) ؛

ج- يقول أن الجهاز لتسجيل الصوت (التقاط).



في حالتنا ، يتوافق جهاز pcmC0D0p تمامًا مع واجهة التشغيل I2S. D1 هو spdif و D2 عبارة عن ميكروفونات pdm ، لكننا لن نتحدث عنها.



شجرة الجهاز



يبدأ وصف بطاقة الصوت بـ 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";
};


هنا نرى تلك الأجهزة الثلاثة التي ستظهر بعد ذلك في / dev / snd: tdmif_c ، spdif ، pdm.



يُطلق على الجهاز الذي سينتقل الصوت من خلاله اسم dai-link-6. ستعمل تحت سيطرة مشغل TDM. السؤال الذي يطرح نفسه: كنا نتحدث عن كيفية نقل الصوت عبر I2S ، ثم فجأة ، TDM. من السهل شرح ذلك: كما كتبت أعلاه ، لا تزال I2S هي نفس TDM ، ولكن مع متطلبات واضحة لدورة عمل LRCLK وعدد القنوات - يجب أن يكون هناك اثنان منهم. سيقرأ محرك TDM بعد ذلك الحقل dai-format = "i2s" ؛ وسيفهم أنه يحتاج إلى العمل في وضع I2S.



يشير ما يلي إلى أي DAC (داخل Linux يشار إليها باسم "برنامج ترميز") تم تثبيته على اللوحة باستخدام هيكل speaker_amp1. لاحظ أنه يشار على الفور إلى أي خط I2C (يجب عدم الخلط بينه وبين I2S!) لدينا TAS5707 DAC متصل. على طول هذا الخط ، سيتم تشغيل مكبر الصوت وضبطه من السائق.



يصف هيكل tdmif_c أي دبابيس SoC ستعمل كواجهة I2S.



طبقة ALSA SoC



بالنسبة إلى SoCs التي تحتوي على دعم صوتي في الداخل ، يحتوي Linux على طبقة ALSA SoC. يتيح لك وصف برامج الترميز (تذكر أن هذا هو ما يسمى أي DAC في مصطلحات ALSA) ، ويسمح لك بتحديد كيفية توصيل برامج الترميز هذه.



تسمى برامج الترميز في مصطلحات Linux kernel DAI (واجهة الصوت الرقمية). واجهة TDM / I2S نفسها ، الموجودة في SoC ، تسمى أيضًا DAI ، ويتم العمل معها بطريقة مماثلة.



يصف السائق برنامج الترميز باستخدام Struct snd_soc_dai. الجزء الأكثر إثارة للاهتمام في وصف برنامج الترميز هو عملية تعيين معلمات الإرسال TDM. وهي موجودة هنا: Struct snd_soc_dai -> Struct snd_soc_dai_driver -> Struct snd_soc_dai_ops. لنفكر في أهم مجالات الفهم (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);
    ...
الوظائف ذاتها التي تتعرض بها ساعات TDM. عادةً ما يتم تنفيذ هذه الوظائف بواسطة بائع SoC.



...
int (*hw_params)(struct snd_pcm_substream *,
    struct snd_pcm_hw_params *, struct snd_soc_dai *);
...
الوظيفة الأكثر إثارة للاهتمام هي hw_params ().

إنه ضروري لتكوين جميع أجهزة SoC وفقًا لمعايير ملف PCM الذي نحاول تشغيله. هي التي ستستدعي لاحقًا الوظائف من المجموعة أعلاه لتثبيت ساعات TDM.



...
int (*trigger)(struct snd_pcm_substream *, int,
    struct snd_soc_dai *);
...
وتأخذ هذه الوظيفة الخطوة الأخيرة بعد تكوين برنامج الترميز - فهي تضع برنامج الترميز في الوضع النشط.



يتم وصف DAC التي ستخرج الصوت التناظري إلى السماعة بنفس البنية تمامًا. سيقوم snd_soc_dai_ops في هذه الحالة بتكوين DAC لتلقي البيانات بالتنسيق الصحيح. يتم إعداد DAC هذا عادةً عبر واجهة I2C.



جميع برامج الترميز المحددة في شجرة الجهاز في الهيكل ،

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


- ويمكن أن يكون هناك الكثير منها ، تتم إضافتها إلى قائمة واحدة وإرفاقها بالجهاز / dev / snd / pcm *. يعد هذا ضروريًا بحيث عند تشغيل الصوت ، يمكن للنواة تجاوز جميع برامج تشغيل الترميز الضرورية وتكوينها / تمكينها.



يجب أن يخبرك كل برنامج ترميز بمعلمات PCM التي يدعمها. يفعل هذا بهيكل:

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 */
};


إذا كان أي من برامج الترميز في السلسلة لا يدعم معلمات محددة ، فسينتهي كل شيء بخطأ.



يمكن عرض تنفيذ مشغل TDM المقابل لـ amlogic s400 في sound / soc / meson / axg-tdm-interface.c . ويتم تنفيذ برنامج تشغيل برنامج الترميز TAS5707 في sound / soc / codecs / tas571x.c



جزء المستخدم



لنرى الآن ما يحدث عندما يريد المستخدم تشغيل صوت. مثال سهل التعلم لتطبيق ALSA مخصص هو tinyalsa . يمكن الاطلاع على الكود المصدري لكل ما يلي هناك.

يتضمن الأداة المساعدة الصغيرة. لتشغيل الصوت الذي تحتاجه للتشغيل:



bash$ tinyplay ./music.wav -D 0 -d 0
(يشير الخياران -D و -d إلى أنه يجب تشغيل الصوت من خلال / dev / snd / pcmC0D0p).



ماذا يحدث؟

إليك مخطط كتلة قصير ، متبوعًا بتفسيرات:







  1. [مساحة المستخدمين] تحليل رأس .wav لمعرفة معلمات PCM (معدل العينة ، عرض البت ، القنوات) للملف الجاري تشغيله. نضيف جميع المعلمات إلى البنية snd_pcm_hw_params.
  2. [مساحة المستخدمين] افتح الجهاز / 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] انسخ المستخدم إلى hwbuf (انظر المخطط العام) باستخدام copy_from_user ().
  10. [مساحة المستخدمين] الانتقال 6.


يمكن مشاهدة تنفيذ جزء النواة من ioctl بالبحث عن كلمة SNDRV_PCM_IOCTL_ *



خاتمة



لدينا الآن فكرة عن مكان انتقال الصوت في نواة Linux. في المقالات التالية ، سيكون هناك تحليل لكيفية تشغيل الصوت من تطبيقات Android ، ولهذا فإن الطريق طويل لنقطعه.



All Articles