ПрограммированиеФорумЗвук

Столкнулся с парсингом заголовка wav звука?

#0
19:54, 17 фев 2020

Юзаю такой код:

struct WAVHEADER
{
  // WAV-формат начинается с RIFF-заголовка:

  // Содержит символы "RIFF" в ASCII кодировке
  // (0x52494646 в big-endian представлении)
  char chunkId[4];

  // 36 + subchunk2Size, или более точно:
  // 4 + (8 + subchunk1Size) + (8 + subchunk2Size)
  // Это оставшийся размер цепочки, начиная с этой позиции.
  // Иначе говоря, это размер файла - 8, то есть,
  // исключены поля chunkId и chunkSize.
  unsigned long chunkSize;

  // Содержит символы "WAVE"
  // (0x57415645 в big-endian представлении)
  char format[4];

  // Формат "WAVE" состоит из двух подцепочек: "fmt " и "data":
  // Подцепочка "fmt " описывает формат звуковых данных:

  // Содержит символы "fmt "
  // (0x666d7420 в big-endian представлении)
  char subchunk1Id[4];

  // 16 для формата PCM.
  // Это оставшийся размер подцепочки, начиная с этой позиции.
  unsigned long subchunk1Size;

  // Аудио формат, полный список можно получить здесь http://audiocoding.ru/wav_formats.txt
  // Для PCM = 1 (то есть, Линейное квантование).
  // Значения, отличающиеся от 1, обозначают некоторый формат сжатия.
  unsigned short audioFormat;

  // Количество каналов. Моно = 1, Стерео = 2 и т.д.
  unsigned short numChannels;

  // Частота дискретизации. 8000 Гц, 44100 Гц и т.д.
  unsigned long sampleRate;

  // sampleRate * numChannels * bitsPerSample/8
  unsigned long byteRate;

  // numChannels * bitsPerSample/8
  // Количество байт для одного сэмпла, включая все каналы.
  unsigned short blockAlign;

  // Так называемая "глубиная" или точность звучания. 8 бит, 16 бит и т.д.
  unsigned short bitsPerSample;

  // Подцепочка "data" содержит аудио-данные и их размер.

  // Содержит символы "data"
  // (0x64617461 в big-endian представлении)
  char subchunk2Id[4];

  // numSamples * numChannels * bitsPerSample/8
  // Количество байт в области данных.
  unsigned long subchunk2Size;

  // Далее следуют непосредственно Wav данные.
};
  FILE* file_;
  errno_t err;
  err = fopen_s(&file_, "content//konga.wav", "rb");
  if (err)
  {
    printf_s("Failed open file, error %d", err);
    return 0;
  }

  WAVHEADER header;

  fread_s(&header, sizeof(WAVHEADER), sizeof(WAVHEADER), 1, file_);
  printf("audioformat:%d\n", header.audioFormat);
  printf("bitsPerSample:%d\n", header.bitsPerSample);
  printf("blockAlign:%d\n", header.blockAlign);
  printf("byteRate:%lu\n", header.byteRate);
  printf("chunkId:%s\n", header.chunkId);
  printf("chunkSize:%lu\n", header.chunkSize);
  printf("format:%s\n", header.format);
  printf("numCannels:%d\n", header.numChannels);
  printf("sampleRate:%lu\n", header.sampleRate);
  printf("subchunk1Id:%s\n", header.subchunk1Id);
  printf("subchunk1Size:%lu\n", header.subchunk1Size);
  printf("subchunk2Id:%s\n", header.subchunk2Id);
  printf("subchunk2Size:%lu\n", header.subchunk2Size);
  fclose(file_);

Выхлоп такой в случае хорошего файла https://ibb.co/Rv8hJKZ И в моем случае https://ibb.co/dgBg4Fh
сам звук тут https://wav-library.net/afrikanskij-konga-baraban-muzykalnaya-pod… zhka-zvuk-mp3 транслированный в wav на одном из сайтов https://audio.online-convert.com/ru/convert-to-wav

#1
20:45, 17 фев 2020

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

RIFF-файлы (частным случаем которых являются WAV-файлы) - это файлы не фиксированного формата, а наборы вложенных (в общем случае) chunk-ов, типа конвертов в конвертах. Все chunk-и имеют реально только два поля заголовка - chunkId (четырехбуквенный индентификатор, иначе называемый FOURCC) и поле длины chunk-а. Дальше с точки зрения этого chunk-а - произвольное содержимое, зависящее от типа chunk-а. Chunk-и типа RIFF (самый внешний chunk) и LIST фактически содержат в себе один или подряд несколько вложенных chunk-ов.

В твоей программе "хедер" фактически подразумевающий лишь внешний chunk "RIFF" и один вложенный в него chunk "WAVE", который в свою очередь содержит два вложенных chunk-а "fmt " и "data":

RIFF (
  WAVE (
    fmt (
      формат данных
    )
    data (
      сами данные
    )
  )
)

А судя по полученному тобой выводу, в случае имеющегося у тебя файла в нем вместо просто одного вложенного chunk-а data у тебя chunk LIST, в котором может лежать еще несколько chunk-в.

RIFF (
  WAVE (
    fmt (
      формат данных
    )
    LIST (
      тут хрен знает что вложено
    )
  )
)

Конкретное содержимое LIST надо разбирать путем последовательного прохода по всем чанкам внутри (т.к. тебе известна суммарная длина LIST в байтах и длины вложенных chunk-ов).

Если ты работаешь в Windows, то в WinAPI есть наборы специальных функций для работы с такой chunk-овой структурой riff-файлов, например, mmioDescend

#2
20:53, 17 фев 2020

Да, вот еще обратил внимание, что у тебя длина chunk-а RIFF офигенная, а длины chunk-ов fmt и LIST - маленькие, то есть после chunk-а LIST у тебя еще какие-то chunk-и вложены во внешний RIFF

RIFF (
  WAVE (
    fmt (
      формат данных
    )
    LIST (
      тут хрен знает что вложено
    )
    ЕЩЕК акие-то чанки
    ...
  )
)
#3
21:33, 17 фев 2020

Решил конвертированием файла такой командой

ffmpeg -i konga.mp3 -map_metadata -1 -fflags +bitexact konga.wav
#4
21:36, 17 фев 2020

На всякий случай освежил данные по RIFF, а то давно дело было. Оказалось, память меня немного подвела, неточно я указал выше. WAVE это сам по себе не chunk, это спецификация chunk-а-контейнера. (то есть у chunk-ов RIFF и LIST сразу после длины идет еще оддин FOURCC, который просто уточняет тип RIFF-а или LIST-а).

То есть правильно рассматривать вот так

RIFF(WAVE) {
  fmt {
    формат данных
  }
  LIST(смотри FOURCC после поля его длины) {
    тут хрен знает что вложено
  }
  ЕЩЕК акие-то чанки (скорее всего data)
  ...
}
#5
21:39, 17 фев 2020

Beginerok
> Решил конвертированием файла такой командой

В общем случае это не решение. В любой момент тебе может прилететь WAVE с отличающейся внутренней структурой. Правильно парсить WAV не твоим длинным фиксированным хедером, а спускаться по иерархии чанков, конкретно, если тебе интересны только формат и звуковые данные (а всякие тексты, субтитры, метаинфо неинтересны) - то тебе внутри chunk-а RIFF надо искать только chunk-и "fmt " и "data", и парсить именно их.

#6
22:32, 17 фев 2020

Если ты знаешь что там внутри mp3-упаковка, можно проще сделать - вообще наплевать на структуру, никакие заголовки не парсить, а сразу искать сигнатуру начала mp3. Все лишнее будет автоматически пропущено.

#7
20:39, 18 фев 2020

Dmitry_Milk
Так ты мне объясни нормально структуру его и я и пропущу или напишу нормальную структуру для его чтения, на всех сайтах бред, на википедии вообще объяснение хуже твоего

#8
20:54, 18 фев 2020

Beginerok
> fread_s(&header, sizeof(WAVHEADER), sizeof(WAVHEADER), 1, file_);

Сразу фейл.

#9
21:08, 18 фев 2020

Beginerok
https://fossies.org/linux/nas/lib/audio/wave.c

#10
22:07, 18 фев 2020

Beginerok
> Так ты мне объясни нормально структуру

Ок. Попытаюсь еще раз.

В wav-файле (как и других riff-файлах) НЕТ одного заголовка фиксированного формата. Его нельзя рассматривать как "в начале заголовок потом содержимое".

riff-файлы  состоят из так называемых чанков (chunk), часто вложенных друг в друга. Вот у каждого чанка ЕСТЬ фиксировнный заголовок, и этот заголовок состоит всего из двух полей - тип чанка (chunkID, 4 char-а) и длина содержимого чанка (32-битный int). После заголовка чанка следует его содержимое указанной длины.

Что именно будет представлять из себя содержимое конкретного чанка - определяется его chunkId.

Например, в чанке "fmt " находится структура

+ Показать

А в чанке "data" находятся сами аудиоданные

Особые типы чанков, RIFF и LIST, являются контейнерами для других чанков, и в своем содержимом содержат спецификатор подтипа (ниже обозначено listType, 4 char-а ) и подряд за ним один или несколько других чанков.

+==========================+
+ chunkId = "RIFF"         +
+--------------------------+
+ size                     +
+--------------------------+
+   + listType = "WAVE"    +
+   +======================+
+   + chunkId = "fmt "     +
+   +----------------------+
+   + size = 16            +
+   +----------------------+
+   +   + audioFormat      +
+   +   +------------------+
+   +   + numChannels      +
+   +   +------------------+
+   +   + sampleRate       +
+   +   +------------------+
+   +   + byteRate         +
+   +   +------------------+
+   +   + blockAlign       +
+   +   +------------------+
+   +   + bitsPerSample    +
+   +======================+
+   + chunkId = "LIST"     +
+   +----------------------+
+   + size                 +
+   +----------------------+
+   +   + listType         +
+   +   +==================+
+   +   + chunkId          +
+   +   +------------------+
+   +   + size             +
+   +   +------------------+
+   +   +   ...            +
+   +   +   ...            +
+   +   +==================+
+   +   + chunkId          +
+   +   +------------------+
+   +   + size             +
+   +   +------------------+
+   +   +   ...            +
+   +   +   ...            +
+   +   +==================+
+   +   +   ...            +
+   +======================+
+   + chunkId = "data"     +
+   +----------------------+
+   + size                 +
+   +----------------------+
+   +  аудио               +
+   +  данные              +
+   +======================+
+   + chunkId              +
+   +----------------------+
+   + size                 +
+   +----------------------+
+   +    ...               +
+   +======================+
+   + chunkId              +
+   +----------------------+
+   + size                 +
+   +----------------------+
+   +    ...               +
+   +======================+
+   +    ...               +
+==========================+

Вот я изобразил файл, в котором в корневом чанке RIFF лежат 5 или более вложенных чанков. Тебе надо среди них найти чанки fmt и data. При этом ты должен понимать, что последовательность этих чанков теоретически может быть любой. Скажем, в первом твоем файле чанки fmt и data шли подряд, а вот во втором - сначала шел чанк fmt а за ним чанк LIST. Какие чанки там лежали дальше мы не знаем, для этого ты должен идти по цепочке чанков, перескакивая их содержимое (а для этого тебе как раз и нужно поле size у каждого чанка).

#11
9:52, 19 фев 2020

Предлагаю бомжевание.

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

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

Но лучше скачать готовую библиотеку.

ПрограммированиеФорумЗвук

Тема в архиве.