2025, Dec 11 06:01
Почему resample_poly на Raspberry Pi даёт нули и как это исправить
resample_poly в SciPy на Raspberry Pi при даунсемплинге аудио может вернуть нули. Причина — целочисленный dtype. Решение: приводите вход к float64. Стабильно.
При понижении частоты дискретизации аудио с помощью resample_poly, чтобы снизить нагрузку на CPU, можно столкнуться со странным, зависящим от платформы сбоем: фрагмент кода, который на ПК работает корректно, на Raspberry Pi 3B возвращает массив из одних нулей. Несоответствие особенно сбивает с толку, когда версии NumPy и SciPy совпадают. Ниже — минимальный пример, который воспроизводит проблему, и простой способ обойти её, не меняя логику обработки сигнала.
Как воспроизвести проблему
Следующий код понижает частоту дискретизации короткого целочисленного буфера с 16 кГц до 4 кГц. На ПК он выдаёт осмысленные значения, а на Raspberry Pi результат может быть сплошь нулями.
import numpy as np
from scipy.signal import resample_poly as rs_poly
buf = np.array([43, 58, 67, 88, 89, 99, 121, 113, 88, 69])
target_hz = 4000
source_hz = 16000
print(f"pre: {buf}")
buf = rs_poly(buf, target_hz, source_hz)
print(f"post: {buf}")
На ПК: После [28.66957128 103.77145182 80.49835032]
На Pi: После [0. 0. 0.]
Такое поведение уже описано и соответствует известной проблеме в SciPy. Коротко: целочисленные входные данные могут запустить неожиданный кодовый путь, который в некоторых окружениях приводит к нулевому результату.
Почему так происходит
Суть проблемы — в типе данных на входе. Если передать в resample_poly целочисленный массив, на затронутых версиях Raspberry Pi можно получить массив из одних нулей. Тот же код на ПК обрабатывает целые числа правильно, из‑за чего это выглядит как различие платформ, а не ловушка с типами. Несоответствие исчезает, если перед вызовом resample_poly преобразовать данные в числа с плавающей точкой.
Решение: приводите к числу с плавающей точкой перед ресемплингом
Если обновить SciPy нельзя, убедитесь, что к моменту ресемплинга данные имеют тип с плавающей точкой. Результат обработки сигнала останется тем же; меняется лишь тип входа.
Вариант 1: сразу создать массив в формате float64.
import numpy as np
from scipy.signal import resample_poly as rs_poly
buf = np.array([43, 58, 67, 88, 89, 99, 121, 113, 88, 69], dtype=np.float64)
target_hz = 4000
source_hz = 16000
print(f"pre: {buf}")
buf = rs_poly(buf, target_hz, source_hz)
print(f"post: {buf}")
Вариант 2: оставить исходный массив как есть и привести тип прямо в месте вызова.
import numpy as np
from scipy.signal import resample_poly as rs_poly
buf = np.array([43, 58, 67, 88, 89, 99, 121, 113, 88, 69])
target_hz = 4000
source_hz = 16000
print(f"pre: {buf}")
buf = rs_poly(buf.astype(np.float64), target_hz, source_hz)
print(f"post: {buf}")
Оба подхода сохраняют логику DSP и параметры дискретизации, но предотвращают «нулевой» результат на Raspberry Pi.
Почему это важно
Аудиопотоки часто начинаются с целочисленного PCM, особенно при чтении из файлов или устройств. Если в вашем окружении функция неявно рассчитывает на вычисления с плавающей точкой, передача целых чисел может тихо сломать цепочку и вернуть нули без ошибок. Явное соответствие dtype ожидаемому вычислительному пути делает результаты одинаковыми на разных машинах и помогает избежать скрытых платформенных ловушек.
Выводы
Если resample_poly на Raspberry Pi возвращает нули, а на ПК работает, сначала проверьте dtype. Преобразование буфера в float64 перед ресемплингом — минимальный и надёжный обходной путь, когда обновление SciPy невозможно. Остальную часть конвейера можно не трогать; достаточно явно задать тип на границе ресемплинга, чтобы получать стабильные, ненулевые результаты.