В этой статье мы познакомимся с эффектом Доплера, но не так, как об этом рассказывается в школьном учебнике физики, а посредством своих любопытных глазок и ушек. Поможет нам в этом специально разработанное мной приложение для Андроид-смартфона.

Что внутри приложения?

Внутри программа использует оптимизированный для быстрого вычисления вариант дискретного преобразования Фурье - быстрое преобразование Фурье (БПФ).

В предыдущей статье "Дискретное преобразование Фурье в живых картинках для девятиклассников" я рассказал о том, как можно представить себе механику действия дискретного преобразования Фурье и предоставил пример на Python, декодирующий из аудиофайла символы тонального телефонного набора DTMF.

Нам не нужно погружаться в детали этого преобразования, необходимые сведения будут даны по мере изложения.

Что такое эффект Доплера

Представим себе некую среду, в которой могут распространяться волны. Обычно представляют поверхность воды, но это может быть даже воздух, если вашего воображения хватает, чтобы представить распространение волны, состоящей из чередования зон повышенного и пониженного давления, которые для краткости называют звуковой волной.

Пусть в середине нашего воображаемого пространства (или поверхности воды) находится источник волн. На воде это можете быть вы сами, с надетым на вас надувным кругом. В воздухе пусть будет вертолёт, создающий звуковые волны.

И, конечно же, будучи параноиком, я поставил позади и впереди вас, простите, источника волн, пару своих агентов-наблюдателей. Первого зовут A (пусть будет Алиса), а второго - B (Воbа :). Агенты расположены на равных расстояниях от источника и остаются на своих местах.

И вот, наш источник волн активировался, зашлёпал ручками по воде, завис в воздухе на одном месте и привлекает к себе внимание.

На анимации ниже эта ситуация соответствует первым секундам симуляции (навеяно вот этим текстом, но я слишком люблю насекомых и плохой художник):

Мои Алиса и Boba заметят ваш сигнал далеко не сразу, а только к 8-й секунде (кадр 234) активного излучения волн, так как любая среда характеризуется свойственной ей скоростью распространения колебаний определённого типа.

Пара примеров скоростей распространения волн

Например, скорость звука в воздухе при 20 градусах Цельсия составляет примерно 343 м/с, а скорость света в вакууме почти 300 миллионов м/с. В воздухе скорость света не намного, но всё же меньше.

Как только гребень (или зона повышенного давления) волны на симуляции достигает одного из агентов, он сразу зеленеет и распухает, но потом быстро приходит в норму. Наблюдая за поведением Алисы и Bobы, мы легко сможем понять, как на них влияет эффект Доплера.

С 8-й по 11-ю секунды (кадры 234..335) наших наблюдателей колбасит с одинаковой периодичностью и, что немаловажно, синхронно, ведь волны от источника, преодолевая одно и то же расстояние, накрывают наших героев будучи в одной фазе.

А вот дальше вы поплыли... или полетели. Точнее, источник волн начал движение в сторону Bobы. С 11-й секунды (кадр 337) генерируемая источником волна каждый раз испускается немного правее предыдущей. Это приводит к тому, что расстояние между гребнями волны справа уменьшается, а слева - увеличивается. На 18-й секунде (кадр 550) до Воbы доходит новая ситуация и его начинает подбрасывать и накрывать значительно чаще, чем ранее. В это же время Алиса скучает в ожидании запаздывающего развлечения.

Эффе́кт До́плера — изменение частоты и, соответственно, длины волны излучения, воспринимаемое наблюдателем, вследствие движения источника излучения относительно наблюдателя. Эффект назван в честь австрийского физика Кристиана Доплера.

На 30-й секунде (кадр 904) источник волн останавливается недалеко от Воbы и вскоре тот ощущает привычную частоту приходящих к нему волн. Алисе же придётся поскучать ещё целых 14 секунд (до кадра 1333) пока до неё начнут доходить волны с изначальной частотой. Следует заметить, что из-за разного теперь расстояния между каждым из наблюдателей и источником, приходящие к ним волны не совпадают по фазе. Поэтому агентов подбрасывает и накрывает не синхронно, хотя и с одинаковой частотой.

Формулы, куда же без них

(1) Изменение длины волны для неподвижного наблюдателя и движущегося источника волн (как на анимации):

\lambda'=\lambda\cdot\left(1-\frac{v}{c}\right)

Где:
\lambda'- длина волны, воспринимаемая приёмником,
\lambda- длина волны, посылаемой источником,
v- скорость источника относительно среды распространения волн (при движении к приёмнику v положительная, при удалении - отрицательная),
c - скорость распространения волн в среде.

(2) Частота волны:

f=\frac{c}{\lambda}

Где:
f - частота волны, Гц,
c - скорость распространения волн в среде,
\lambda - длина волны.

(3) Изменение частоты волны для неподвижного наблюдателя и движущегося источника волн (как на анимации):

f'=f\cdot\left(\frac{c}{c-v}\right)

Где:
f' - частота волны, воспринимаемая приёмником,
f - частота волны, генерируемой источником,
c - скорость распространения волн в среде,
v - скорость источника относительно среды распространения волн (при движении к приёмнику v положительная, при удалении - отрицательная).

(4) Вариант, используемый в программе для Android. Эффект Доплера для отражённого сигнала от движущегося отражателя (двойной Доплер): источник и приёмник находятся рядом и неподвижны, волна отражается от движущегося предмета (отражателя):

f'=f\cdot\left(\frac{c+v}{c-v}\right)

Где:
f' - частота волны, воспринимаемая приёмником,
f - частота волны, генерируемой источником,
c - скорость распространения волн в среде,
v - скорость отражателя относительно среды распространения волн (при движении отражателя к приёмнику (и источнику, который рядом с приёмником) v положительная, при удалении - отрицательная).

Я знаю, что вы знаете, но так, на всякий случай...

Длина волны - это расстояние между соседними гребнями, либо между соседними впадинами волны. Для продольных волн, к которым относятся звуковые волны в воздухе, длина волны - это расстояние между соседними зонами повышенного давления или между соседними зонами пониженного давления.

Эффект Доплера характерен для обоих типов волн.

Приложение для Android

Приложение использует C++ реализацию БПФ (быстрое преобразование Фурье, FFT - fast Fourier transform). Связь между Java кодом и C++ осуществляется посредством JNI (Java Native Interface). Использование более модного Kotlin не спасло бы меня от JNI поэтому я решил писать на том, что мне привычнее, да простят меня читатели.

Интерфейс предельно прост и в виде вертикальных палочек отображает частоты гармоник принимаемого микрофоном сигнала. Поддерживается два режима работы: с отдельным источником опорного звукового сигнала (я его иногда называю "центральная частота") и с генерацией этого сигнала самим приложением. В обоих случаях из гармоник выделяется доплеровский сдвиг - та самая частота f', но для варианта с отдельным источником сигнала используется формула (3), а для варианта с отражением - формула (4).

Скриншотики

Рис. 1. Приложение генерирует опорный звуковой сигнал ~20000 Гц (опция Play) и принимает отражённый сигнал ~20026 Гц. Жёлтые вертикальные палочки показывают условную мощность гармоники, её частоту и расчётную скорость отражателя.
Рис. 1. Приложение генерирует опорный звуковой сигнал ~20000 Гц (опция Play) и принимает отражённый сигнал ~20026 Гц. Жёлтые вертикальные палочки показывают условную мощность гармоники, её частоту и расчётную скорость отражателя.
Рис. 2. Приложение генерирует опорный звуковой сигнал ~20000 Гц (опция Play) и принимает отражённый сигнал ~19977 Гц. Жёлтые вертикальные палочки показывают условную мощность гармоники, её частоту и расчётную скорость отражателя.
Рис. 2. Приложение генерирует опорный звуковой сигнал ~20000 Гц (опция Play) и принимает отражённый сигнал ~19977 Гц. Жёлтые вертикальные палочки показывают условную мощность гармоники, её частоту и расчётную скорость отражателя.

На рис. 1 и 2 показан интерфейс приложения, работающего в режиме генерации (опция Play включена) опорного сигнала частотой около 20 кГц. На рис. 1 отражённый сигнал имеет более высокую частоту, чем опорный. Следовательно, отражатель приближается к смартфону. На рис. 2 ситуация прямо обратная.

А почему "около 20 кГц"?

А потому что реальная опорная частота подгоняется под частоту ближайшей гармоники. Это позволяет избежать растекания принимаемой микрофоном опорной частоты на соседние гармоники и повысить чувствительность приложения.

Частотный шаг гармоник вычисляется по формуле:

\Delta{f}=\frac{SampleRate}{BlockSize}

По-умолчанию в приложении настроены SampleRate=44100 семплов/с, BlockSize=8192 сэмплов. Отсюда:

\Delta{f}=\frac{44100}{8192}\approx{5.3833} (Hz)

Ближайшая к 20000 Гц гармоника имеет порядковый номер 3715. Следовательно, опорная частота равна:

{20000}\cdot{5.3833}\approx19998.96(Hz)

Вертикальная серая линия прямо посередине экрана приложения обозначает положение частоты опорного сигнала (она же "центральная частота").

Множество гармоник возникает в силу нескольких причин:
а) Растекание на соседние гармоники частоты, не совпадающей точно ни с одной гармоникой.
б) Неравномерное движение отражателя в процессе оцифровки блока данных размером (по-умолчанию) 8192 сэмпла. Это количество сэмплов набирается в течение

t=\frac{BlockSize}{FrameRate}

секунд, что с дефолтными настройками составляет около 0.2 секунд.

Как это выглядит в работе

Весьма забавно выглядит. Прошу прощения у читателей за съёмку с рук. Приложение обнаруживает самые незначительные движения в небольшой комнате и нужно практически замереть полностью, чтобы на UI исчезли все признаки движения.

Полагаю, что даже такое видео объяснит поведение этой штуковины лучше моих слов.

Настройки

Настроек не много, но они все важные. Сразу скажу, что никакой защиты "от дурака" там нет, приложение сырое, работает 90% времени на моём смартфоне.

Рис. 3. Настройки приложения
Рис. 3. Настройки приложения

Central freq, Hz

Частота опорного сигнала. Она же "центральная частота". Если включена опция "Play", приложение генерирует звук с такой частотой (во всяком случае, оно пытается это делать). Все расчёты ведутся исходя из того, что источник сигнала излучает волны этой частоты. Теоретически максимальное, что здесь может быть, - это число, равное половине частоты дискретизации Rate (будет описано ниже). Фактически, я полагаю, далеко не все смартфоны способны пищать и слышать даже на 20 кГц. Поэтому, данным параметром можно играться и экспериментировать как мы любим.

Sound speed, m/s

Скорость звука. Зависит от температуры. 343 м/с при 20 градусах Цельсия. Тут онлайн калькулятор. Эта величина нужна для расчёта скорости предмета (отражателя) или источника звука.

Rate, samples/s

Частота дискретизации (скорость сэмплирования) звука с микрофона. Можно задать что-нибудь своё. Наверняка поддерживается 44100 сэмплов/с, много устройств могут 48000 и некоторые 96000. Играемся, экспериментируем.

FFT wnd, samples

Размер блока данных (количество сэмплов в блоке) для быстрого преобразования Фурье. Должно быть строго степенью двойки: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 и так далее. Такое требование связано с реализацией алгоритма БПФ. Чем больше это значение, тем выше точность определения частот, но при этом возрастает задержка на оцифровку такого объёма данных и нагрузка на процессор. Играемся, экспериментируем.

Play

Включаем, если хотим, чтобы смартфон сам издавал звук опорной частоты. В таком случае приложение предполагает, что имеет дело с отражённым от подвижного предмета сигналом. Если опция отключена, то нужно задействовать отдельный источник звука с частотой опорного сигнала и тогда он и будет перемещающимся источником, а приложение будет вычислять скорость по формулам для движущегося источника.

Да, не забудьте про стандартный регулятор громкости, который в виде кнопочек где-то сбоку: если его "выкрутить" на максимум, то праздник может стать веселее :)

В качестве отдельного источника можно использовать второй экземпляр моего приложения, запущенный на отдельном устройстве с включенной опцией Play, либо любой софт-генератор синусоидального сигнала опорной частоты. После включения генератора частоту на нём надо подстроить на несколько герц выше или ниже так, чтобы максимально уменьшить эффект растекания спектра на соседние гармоники.

Кнопка Start

Запускает весь цирк. Чтобы применить новые значения опций, нужно остановить обработку (если она запущена) этой кнопкой, а потом запустить уже с новыми параметрами.

Внутренности программы

Ну, я не настоящий андроидер, поэтому как получилось, так и получилось. Из важного:

В классе RecordAudioThread для настройки AudioRecord используется параметр источника звука MediaRecorder.AudioSource.UNPROCESSED для предотвращения возможных системных обработок, эквалайзеров и прочего. Документация напоминает, что UNPROCESSED может поддерживаться не везде. Возможно, вам придётся пересобрать с другим параметром - MediaRecorder.AudioSource.VOICE_RECOGNITION который, как обещают, тоже должен выдавать необработанный цифровой звук. Вот этот кусок:

final AudioRecord audioRecord = new AudioRecord.Builder()
        .setAudioSource(MediaRecorder.AudioSource.UNPROCESSED)
        .setAudioFormat(af)
        .setBufferSizeInBytes(_params.getRecordBufferSize())
        .build();

В MySurfaceView есть метод speed() для расчёта скорости перемещения источника или отражателя. Проверьте формулы, если это важно. Писал ночью, мог накосячить:

private float speed(int harmonic) {
    float freq1 = (float) _sampleRate * harmonic / _fftChunkSize;
    float freq = (float) _sampleRate * _centralHarmonic / _fftChunkSize;
    if (_isReflectedSound) {
        // f' = f * (c + v_отражателя) / (c - v_отражателя)
        // f' * (c - v_отражателя) = f * (c + v_отражателя)
        // f' * c - f' * v_отражателя = f * c + f * v_отражателя
        // f' * c - f * c  = f * v_отражателя + f' * v_отражателя
        // f' * c - f * c  = v_отражателя * (f + f')
        // c * (f' - f) / (f + f') = v_отражателя
        return _soundSpeed * (freq1 - freq) / (freq1 + freq);
    } else {
        // f' = f * (c + v_наблюдателя) / c
        // c * f' = f * (c + v_наблюдателя)
        // c * f' / f = c + v_наблюдателя
        // c * f' / f - c = v_наблюдателя
        return _soundSpeed * freq1 / freq - _soundSpeed;
    }
}

Ну вот, как-то так. Старался, жду откликов и комментариев!

Искренне ваш, А. Галилов

PS Вот тут моя поделка на схожую тему, но из более интересных комплектующих:

Ссылки

  1. LibreTexts Physics The Doppler effect

  2. KISS FFT Library (original)

  3. KISS FFT (для Android)

  4. Программа для Android, исходники

  5. Программа для Android, APK

  6. FFT fast Fourier transform

  7. Java Native Interface (JNI)

  8. JNI tips (Android)

  9. Speed of Sound Calculator

Комментарии (11)


  1. Mnemone
    24.06.2025 01:57

    Прикольно. Проверил на своем телефоне (Poco M5). Видимо 20кГц микрофон или динамик не тянет, какая-то мешанина в сигнале, а если 18 и ниже - все четко. Примерно на расстоянии до двух c половиной метров определяет и показывает скорость.


    1. AGalilov Автор
      24.06.2025 01:57

      Да, это несколько нестандартное использование смартфона. Рад, что у вас работает :)


  1. VT100
    24.06.2025 01:57

    Поправка. Не "частота волны", а "частота колебаний".


    1. AGalilov Автор
      24.06.2025 01:57

      "Частота волны" - вполне общеупотребительный термин.

      https://www.google.com/search?q="частота+волны"+учебник

      "Так же, как и у гармонических колебаний, частота волны измеряется в герцах." - https://de.ifmo.ru/bk_netra/page.php?tutindex=12&index=8&layer=1

      "круговая частота волны" - http://library.mephi.ru/pdftunnel.php?PATH=book-mephi%2FErmolaeva_Fizika_razdely_Kolebaniya_i_volny_Optika_2015.pdf&Z21FAMILY=Набиева&Z21ID=2012092426

      и другие


  1. jar_ohty
    24.06.2025 01:57

    А еще можно излучать двухчастотный сигнал. Одна частота постоянная, другая линейно изменяется со временем. И мы знаем не только скорость объекта, но и дальность до него.


    1. AGalilov Автор
      24.06.2025 01:57

      Да, можно и так заморочиться :) Только хорошо подумать там есть над чем. Едва ли из штатного оборудования смартфона получится что-то на самом деле интересное.

      Меня удивило, что сработала вот та идея, о которой я написал. Я раньше экспериментировал с профессиональными аудиокартами с АЦП на 192 кГц, разрядностью 32 бит и адекватной аналоговой частью. Вот там всякие интересные вещи получались типа этой:

      https://youtu.be/y1wk9jut_OU?t=312


  1. alcotel
    24.06.2025 01:57

    Интересный эксперимент. Вот тут не так давно тоже про сонар на смартфоне читал.

    Поскольку интересны частоты в очень узком диапазоне, то традиционно делают перенос частоты в 0 Гц, фильтрацию сверху и снизу, и понижение частоты дискретизации. Тогда размер FFT получается гораздо меньше. И можно применить окно, чтобы спектр не растекался.


    1. AGalilov Автор
      24.06.2025 01:57

      Думал о таком подходе, но в итоге просто сделал "в лоб" чисто для демонстрации самого эффекта. Ну и отлаживать в итоге меньше :)


      1. alcotel
        24.06.2025 01:57

        В общем логично. Раз производительности хватает, почему бы и нет)


  1. SergeyAstanin
    24.06.2025 01:57

    Тоже использую смартфон для работы, он вполне до 20КГц слышит и приемлемо излучает, хорошо смотрятся анимации при обработки сигнала, даже простые, вот к примеру из старого:

    Прямое и обратное дискретное...
    Прямое и обратное дискретное...

    Полезно и интересно, отплюсовал...


    1. AGalilov Автор
      24.06.2025 01:57

      Спасибо.