В настоящее время я использую PyAudio для работы над облегченной утилитой записи, которая соответствует конкретным потребностям приложения, которое я планирую. Я работаю с аудиоинтерфейсом ASIO. Я пишу программу, чтобы воспроизводить wav-файл через интерфейс, одновременно записывая вывод из интерфейса. Интерфейс обрабатывает сигнал в реальном времени и изменяет звук. Поскольку я собираюсь импортировать этот визуализированный вывод в DAW, мне нужно, чтобы вывод был идеально синхронизирован с входным звуком. Используя DAW, я могу одновременно воспроизводить звук в своем интерфейсе и записывать результат. Когда я это делаю, он отлично синхронизируется в DAW. Цель моей утилиты - иметь возможность запускать это из скрипта Python.
Используя метод грубой силы, я нашел решение, которое работает, но теперь я застрял с магическим числом и не уверен, является ли это какой-то константой или чем-то, что я могу вычислить. Если это число, которое я могу вычислить, это было бы идеально, но я все же хотел бы понять, откуда оно взялось.
Мой обратный вызов выглядит следующим образом:
def testCallback(in_data, frame_count, time_info, status):
#read data from wave file
data = wave_file.readframes(frame_count)
#calculate number of latency frames for playback and recording
#1060 is my magic number
latencyCalc = math.ceil((stream.get_output_latency() + stream.get_input_latency()) * wave_file.getframerate()) + 1060
#no more data in playback file
if data == "":
#this is the number of times we must keep the loop alive to capture all playback
recordEndBuffer = latencyCalc / frame_count
if lastCt < recordEndBuffer:
#return 0-byte data to keep callback alive
data = b"0"*wave_file.getsampwidth()*frame_count
lastCt += 1
#we start recording before playback, so this accounts for the initial "pre-playback" data in the output file
if firstCt > (latencyCalc/frame_count):
wave_out.writeframes(in_data)
else:
firstCt += 1
return (data, pyaudio.paContinue)
Меня беспокоит функция:
latencyCalc = math.ceil((stream.get_output_latency() + stream.get_input_latency()) * wave_file.getframerate()) + 1060
Я объединил этот расчет, наблюдая смещение моего выходного файла по сравнению с исходным файлом воспроизведения. Произошли две вещи: мой выходной файл начинался позже, чем исходный файл при одновременном воспроизведении, и он также заканчивался раньше. Методом проб и ошибок я определил, что это было определенное количество кадров, лишних в начале и отсутствующих в конце. Это вычисляет это количество кадров. Я понимаю первую часть, это задержки ввода / вывода (предоставленные с точностью до секунды / субсекунды), преобразованные в кадры с использованием частоты дискретизации. Но я не совсем уверен, как ввести значение 1060, поскольку я не уверен, откуда оно взялось.
Я обнаружил, что, играя с настройками задержки в моем драйвере ASIO, мое приложение продолжает правильно синхронизировать записанный файл, даже если указанные выше задержки вывода / ввода изменяются из-за настройки (задержки ввода / вывода всегда одинаковы), так что 1060 кажется стабильным на моей машине. Однако я просто не знаю, можно ли рассчитать это значение. Или, если это конкретная константа, я не уверен, что именно она представляет.
Приветствуется любая помощь в лучшем понимании этих ценностей. Я рад, что моя утилита теперь работает правильно, но я хотел бы полностью понять, что здесь происходит, поскольку я подозреваю, что потенциально использование другого интерфейса, вероятно, больше не будет работать правильно (я хотел бы поддержать это в будущем по нескольким причинам ).
ИЗМЕНИТЬ 8 апреля 2014 г. в ответ Роберто: значение, которое я получаю для latencyCalc = math.ceil ((stream.get_output_latency () + stream.get_input_latency ()) * wave_file.getframerate ()) + 1060 равно 8576, а дополнительные 1060 увеличивают общую задержку до 9636 кадров. Вы правы в своем предположении, почему я добавил 1060 кадров. Я воспроизводю файл через внешний интерфейс ASIO, и обработка, которую я надеюсь зафиксировать в моем записанном файле, является результатом обработки, происходящей в интерфейсе (а не тем, что я закодировал). Чтобы сравнить выходные данные, я просто воспроизвел тестовый файл и записал выходные данные интерфейса без каких-либо эффектов обработки, задействованных в интерфейсе. Затем я исследовал две дорожки в Audacity и методом проб и ошибок определил, что 1060 - это самое близкое, что я мог бы выровнять. С тех пор я понял, что он все еще не совсем идеален, но он невероятно близок и не слышен при одновременном воспроизведении (что неверно, когда удалено смещение 1060, есть заметная задержка). Добавление / удаление дополнительного кадра тоже слишком большая компенсация по сравнению с 1060.
Я считаю, что вы правы, что дополнительная задержка связана с внешним интерфейсом. Сначала мне было интересно, можно ли это вычислить с помощью имеющейся у меня числовой информации, но я пришел к выводу, что это просто константа в интерфейсе. Я считаю, что это правда, поскольку я определил, что если я удалю 1060, смещение файла будет точно таким же, как при выполнении того же теста, но вручную в Reaper (это именно тот процесс, который я автоматизирую). У меня задержка намного лучше, чем у жнеца с моим новым смещением грубой силы, поэтому я собираюсь назвать это победой. В моем приложении цель состоит в том, чтобы полностью заменить исходный файл новым обработанным файлом, поэтому желательна абсолютная минимальная задержка между ними.
Отвечая на ваш вопрос об ASIO в PyAudio, к счастью, ответ положительный. Вы должны скомпилировать PortAudio с помощью ASIO SDK для PortAudio для работы с ASIO, а затем обновить настройку PyAudio для компиляции таким образом. К счастью, я работаю над Windows, http://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio со встроенной поддержкой ASIO, и тогда устройства будут доступны через ASIO.