dimhold.by
← Статьи

Гул сети датировал 111 записей из 128 с точностью до 5 секунд. В одном эталонном файле один и тот же час повторяется 7 раз

У меня давно лежала заметка в списке идей: по гулу сети в записи можно сказать, когда она сделана. Когда генерация выше нагрузки, частота сети чуть поднимается. Когда ниже, чуть опускается. Этот дрейф доходит до каждой розетки сети одновременно. Микрофон рядом с проводкой или с гудящим прибором его записывает, поэтому гул работает как часы. Криминалистические статьи об этом есть с 2005 года. Я считал вопрос решённым и ни разу не проверял, слышит ли телефон этот гул на самом деле и какой длины должна быть запись.

NESO публикует частоту британской сети за каждую секунду, так что сначала я скачал август 2026 года. Самое низкое значение было 49,747 Гц 31 августа в 17:30 по часам файла, самое высокое 50,242 Гц в 22:01 того же дня. Только 40% секунд отклонялись от номинала не больше чем на 50 мГц.

Данные

Записи взяты из набора ENF-WHU, который собрали Гуан Хуа и соавторы, под лицензией MIT. Это 130 записей, сделанных в Уханьском университете и рядом с ним на смартфон и диктофон: в аудиториях, кабинетах, общежитиях и на дорожках кампуса. По моему счёту они длятся от 4,5 до 16 минут, 8 кГц, моно. К каждой записи прилагается сигнал сети за весь день, когда она сделана, снятый прямо с розетки с частотой дискретизации 400 Гц. Это 28 суточных файлов. Ещё в наборе есть 10 записей вообще без гула, их я оставил как контроль. В Китае, как и в Европе, сеть на 50 Гц. Вторым набором стал месяц NESO с округлением до 1 мГц. Поле поиска в нём в 31 раз больше суточного.

Метод я написал самый простой, какой смог. Python 3.12, numpy 2.5.3, scipy 1.18.1. Окно в 16 секунд сдвигается с шагом в 1 секунду. В каждом окне я нахожу пик спектра около частоты гула и уточняю его параболой, так получается одна частота на секунду. Эта кривая скользит по суткам. Выигрывает положение с самой высокой корреляцией Пирсона. Истинное положение даёт чистый эталон той же записи, как советует README набора. Попаданием считается ответ не дальше 5 секунд от него.

На 50 Гц гула почти нет

Сначала я поставил на сами 50 Гц. Там поиск нашёл правильное положение для 4 записей из 130.

Гул есть, но выше. На 100 Гц, второй гармонике, тот же поиск нашёл 116. Возможно, микрофон телефона плохо берёт 50 Гц, потому что для речи такие низкие частоты не нужны, но устройства я не проверял. Выбрать 100 Гц, уже увидев результат, значит жульничать. Поэтому программа берёт ту гармонику от 50 до 350 Гц, у которой пик сильнее всего выделяется над соседними частотами. Чаще всего она выбирала 100 Гц, но 41 раз взяла 200 Гц и 6 раз 300 Гц.

частота, Гц, запись 60 и эталон сети 49,96 49,97 49,98 49,99 50,00 50,01 50,02 50,03 0 1 2 3 4 5 6 7 8 9 мин эталон сети из записи, 100 Гц, делённые на 2
Запись выбрана правилом в figdata.py: попадание на 100 Гц, не меньше 580 секунд кривой, ошибка ближе всех к медиане попаданий. Корреляция 0,991, ошибка 3,4 мГц.

С этим правилом 113 записей из 130 попали не дальше 5 секунд от истинного времени. Автоматический выбор уступил фиксированным 100 Гц 3 записи, все на 200 Гц, где нашлись только 29 из 41. На попаданиях медианная ошибка кривой была 3,4 мГц, а сама сеть внутри типичной записи ходила примерно на 19 мГц.

Промах, который был не мой

В ранней версии скрипта самым странным промахом была запись 91. Корреляция 0,991, идеальное совпадение по всем моим числам, но ответ отстоял от истинного на 2 часа. Сначала я искал ошибку в своём поиске. Только потом сравнил суточный эталон сам с собой.

В 089-107_ref.wav час файла с 1:00 до 2:00 стоит 6 раз подряд, до 7:00. Отсчёты те же самые, только каждая копия сдвинута на 4 отсчёта позже предыдущей, то есть на 10 мс при 400 Гц. Сначала мне показалось, что это 2 часа, повторённые 3 раза, потому что сдвиг на 2 часа тоже совпадает. Вторая проверка нашла период в 1 час. Позже я прогнал по файлу поиск повторов с любым сдвигом. Тот же час нашёлся ещё раз, с 9:00 до 10:00, после 2 часов нормальных данных. В README сказано, что суточные файлы длятся 24 часа. Этот длится 23 часа, своих из них только около 17. Как так вышло, я сказать не могу. В остальных 27 суточных файлах повторов нет вообще.

суточный эталон 089-107, часы от начала файла один и тот же час ×7 0 2 4 6 8 10 12 14 16 18 20 22 запись 91 подходит везде, корреляция 0,9906…0,9907
Каждая копия совпадает отсчёт в отсчёт с часом, начинающимся в 1:00. В ряду каждая копия на 10 мс позже предыдущей. В те же часы попадает и запись 90.

Для записей из этих часов в эталоне есть 7 почти одинаково хороших положений. Истинное положение находится тем же поиском, значит это одна из тех же 7 копий. В итоговом прогоне запись 91 попала на ту же копию, что и её чистый эталон. Запись 90 тоже из этих часов и попала в 4 секундах от места своего эталона. Обе засчитаны как попадания, но только потому, что запись и чистый эталон обе выбирают самую раннюю копию, а от остальных она отличается только в пятом знаке после запятой. Проверить ни ту ни другую нельзя, поэтому я считаю 111 из 128.

2 промаха ошиблись на 7 и 8 секунд, это почти попадания. Остальные 15 ошиблись на час и больше. 12 из них пришли с записей, где программа выбрала 200 Гц. Запись 85 совпала с корреляцией 0,877 и ошиблась на 6 часов. 10 записей без гула дали максимум 0,506 с любыми из 28 суток, так что 0,877 выглядит убедительно.

Промах выдаёт отрыв от следующего лучшего места, которое лежит дальше минуты от первого. У этих 15 он был не больше 0,035, у попаданий медиана 0,18. Правило “верить ответу, только если отрыв больше 0,04” оставляет 103 попадания из 113 и отбрасывает записи 90 и 91. Оно пропускает 1 промах, тот, что на 8 секунд. Но 0,04 я выбрал, глядя на эти же 130 записей. Второго набора, на котором можно проверить порог, у меня нет.

Какой длины хватает

Потом я обрезал каждую запись до первых N секунд и искал снова. Гармоника по-прежнему выбиралась по всей записи. 30 секунд не нашли почти ничего, 16 из 130. С 2 минутами нашлось 89, с этого уже можно работать. 5 минут нашли 103 из 129 записей, которым на это хватило длины. Окно съедает 15 секунд, поэтому полные 10 минут кривой дают только 49 записей. Из них нашлись 42.

Дальше был месяц. На этот раз ошибка была в моём коде. Первый прогон показал, что чистый кусок сигнала сети длиной 30 секунд находит своё место только 53 раза из 200. Тут я увидел корреляции больше 1, до 2,67. Суммы значений около 50 по 2,7 млн секунд теряют точность, у тихих окон дисперсия выходила почти нулевой. Когда я стал центрировать эталон перед суммированием, все 200 чистых кусков нашли своё место.

Для честной проверки я добавил к случайным окнам куски реальной ошибки извлечения, то есть разницу между записью WHU и её эталоном. Здесь попадание строже, в пределах 2 секунд. С ошибками 10 минут нашли 160 из 200, а 1 минута около половины. Ещё я взял ошибки только с тех записей, что были попаданиями. Тогда 10 минут нашли 183.

доля кусков, найденных в пределах допуска 0% 25% 50% 75% 100% 30 60 120 300 600 секунд записи записи WHU, поиск в своих сутках британская сеть, поиск в месяце, без ошибки месяц британской сети плюс реальные ошибки всех 130 месяц британской сети плюс ошибки 113 попаданий
Допуск 5 секунд для WHU и 2 секунды для синтетической проверки на месяце. Кривую в 600 секунд дают только 49 записей WHU. Каждая точка поиска в месяце это 200 случайных окон.

На 5 и 10 минутах месяц вышел примерно на 6 процентных пунктов ниже суток, это в пределах шума при 200 окнах. На 30 секундах месяц был впереди, 80 из 200 против 16 из 130. Так что за большее поле поиска заметно платить не пришлось. Я думаю, что чистота гула решает больше, но здесь склеены 2 разные сети. Внутри 10 минут британская частота ходит сильнее, медианный разброс 26 мГц против 19. Насколько это помогает, я не мерил.

Чего я не проверял

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