
Задача объединения двух wav-файлов кажется элементарной и для yнее существует большое количество утилит (ffmpeg например) и библиотек, особенно для языка Python (например pydub, pyaudio и даже wav встроенный в сам язык), но тем удивительнее результат. Несмотря на то, что библиотеки должны лишь объединить два файла не производя для них никаких конверсий, попробовав некоторые, типа pydub, pyaudio и др, я явно слышал ухудшение звучания. Возможно все же какие-то конверсии производились в недрах библиотек. Это совершенно неподходит для разрабатываемой программы Vinyl2CD, конвертирующей винил-рипы в CD-Audio формат, которой порой нужно слить две стороны пластинки в один файл, и поэтому было принято решение написать функцию объединения wav самостоятельно, минуя по возможности разные библиотеки, в которых могут быть подвохи. Для этого понадобилось описание формата WAV.
В сети множество описаний заголовка WAV, но они, по большей части, оказались неверны, указывали неправильные адреса, что я выяснил экспериментальным путем, начав писать программу и получая совсем не те данные, что ожидались. Так же выяснился факт, что в мире есть огромное количество популяррных программ, которые создают или создавали испорченные WAV-файлы, и другие программисты вынуждены писать код, чтобы корректно читать некорректное.
Кроме того, несмотря на то что считается, что WAV – это формат без сжатия, но это несовсем так. В Windows WAV работает через встроенный в ОС ACM менеджер, который может принимать и воспроизводить кодированные файлы, и в силу этого аудио в WAV может быть закондировано даже в MP3 и тд.
Так же WAV файл ограничен размером в 4 Гб, из за типа Unsigned Int (32 битного целого беззнакового числа), это примерно 6,8 часов в формате CD-Audio.
Ниже привожу верный заголовок WAV:
Позиции | Значение | Описание |
1-4 | “RIFF” | Помечает файл как riff-файл. Каждый символ имеет длину 1 байт. |
5-8 | Размер файла (целое число) | Размер всего файла минус 8 байт, в байтах (32-битное целое число). Как правило, вы заполняете это после создания. |
9-12 | “WAVE” | Заголовок типа файла. Для наших целей это всегда равно «WAVE». |
13-16 | “fmt” | Формат маркера фрагмента. Включает конечный нуль |
17-20 | 16 | Длина данных формата, как указано выше |
21-22 | 1 | Тип формата (1 — PCM) — 2-байтовое целое |
23-24 | 2 | Количество каналов — 2-байтовое целое число |
25-28 | 44100 | Частота дискретизации — 32-байтовое целое число. Общие значения: 44100 (CD), 48000 (DAT). Частота дискретизации = количество выборок в секунду или герц. |
29-32 | 176400 | (Частота дискретизации * Биты на выборку * Каналы) / 8. |
33-34 | 4 | (BitsPerSample * Channels) / 8. 1 — 8 бит моно, 2 — 8 бит стерео/16 бит моно, 4 — 16 бит стерео |
35-36 | 16 | Бит на выборку |
37-40 | “данные” | заголовок фрагмента “данные”. Отмечает начало раздела данных. |
41-44 | Размер файла (данные) | Размер раздела аудиоданных. |
44 и далее | само аудио |
В ходе создания функции для Vinyl2CD объединения было несколько сложностей, поэтому разберу этот фрагмент кода подробно.
Но прежде всего идея объединения двух файлов в один в моем представлении выглядела так.
В WAV файле есть заголовок с разной информацией, включая метаданные, после заголовка идут чистые аудиоданные. Соответственно необходимо узнать размер аудиоданных в первом и втором файле, а потом сложить их для получения размера аудиоданных совмещенного файла.
В итоговый файл имеет смысл полностью поместить первый файл, а затем из второго файла взять только выкушенный кусок аудиоданных и поместить его после аудиоданных из первого файла.
После этого в заголовок нового файла нужно внести изменения, а именно в поле по адрусу 41 внести 4 байта содержащих размер соединенных из двух файлов аудиоданных.
Если сделать только это, файлы сольются в один, но воспроизводится будет только аудиочасть первого файла. Потому что в загаловке есть еще одно поле, которое высчитывается по особой формуле: размер аудиодананных + 44 байта заголовка и минус 8 первых байт в которых лежит слово RIFF и то самое поле с адреса 5 , которое и нужно изменить.
Так как создавалась функция под программу Vinyl2CD, то было заранее известно, что будут объединяться файлы в формате WAV 44100 Hz 16 bit.
Теперь рассмотрим код:
Создаем функцию принимающую три аргумента на вход - путь к первому WAV файлу, путь ко второму WAV файлу и путь с названием выходного итогового файла. def comb(wav1, wav2, outwav): Открываем файл для чтения как битовый (rb - read byte), так как это WAV. with open(wav1,'rb') as f1, open(wav2,'rb') as f2: перейдем в заголовке к адресу хранящему размер аудиочасти файла, у 1 и 2 файлов f1.seek(40) f2.seek(40) Считаем размеры из обоих файлов, в инт из байтов sz1 = int.from_bytes(f1.read(4), byteorder='little') sz2 = int.from_bytes(f2.read(4), byteorder='little') Посчитаем сумму аудиоданных sz12 = sz1+sz2 прибавим к размеру объединенных аудиоданных заголовок за исключением первых 8 байт 44 (заголовок)-8 (которые пока не меняем) = 36 chunk = sz12+36 Сохраним в новый файл with open(wav1,'rb') as f1, open(wav2,'rb') as f2, open(outwav, 'wb') as out:#записать все 44 байта WAV заголовка из первого файла в выходной новый файл Считываем первый файл полностью out.write(f1.read()) #записал все из первого Переходим к 4 байту и записываем размер объединенных аудиоданных с +36 out.seek(4) out.write(chunk.to_bytes(4, byteorder='little')) переписать размер аудиоданных на новый - сумма двух для этого перейдем на начало этого раздела , на 40 байт, где начинаются данные о размере out.seek(40) и запишем туда новый размер в виде байтов, из инт в байты out.write(sz12.to_bytes(4, byteorder='little')) #поменял размер на сумму размеров Теперь пропустим первые 44 байта заголовка и считаем до конца все аудиоданные f2.seek(44) data2 = f2.read() # из второго считал аудио без заголовка Перейдем с конец файла исохраним туда аудиоданные из второго файла out.seek(0,2) out.write(data2) Пример использования: comb('1.wav', '2.wav', '3.wav') print("Выполнено слияние")
Отправить ответ
Для отправки комментария вам необходимо авторизоваться.