Страницы

Поиск по вопросам

среда, 22 января 2020 г.

Wav-формат. Почему размер данных не кратен размеру сэмпла?

#c_sharp #аудио #wav


Пытаюсь прочитать Wav-файл. Считываю заголовок. 

Так получается, что размер данных (Subchunk2Size, для примера, он равен 178) всегда
не кратен размеру блока (BlockAlign, равен 4). Количество каналов - 2. Если мы будем
по очереди считывать по 2 байта из данных, то в конце концов у правого канала будет
на 1 значение меньше. Почему так? И как правильно в таком случае разделить данные между
двумя каналами?

Класс, который читает Wav-файл (переделанный пример, взятый отсюда):

public class Parser
{
    public string FilePath { get; private set; } 

    public Parser(string filePath)
    {
        FilePath = filePath;
    }

    public ParsingResult Parse()
    {
        var header = new WavHeader();
        byte[] data;
        using (var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read))
        {
            using (var reader = new BinaryReader(fileStream))
            {
                header.ChunkId = reader.ReadInt32();
                header.ChunkSize = reader.ReadInt32();
                header.Format = reader.ReadInt32();
                header.Subchunk1Id = reader.ReadInt32();
                header.Subchunk1Size = reader.ReadInt32();
                header.AudioFormat = reader.ReadInt16();
                header.NumChannels = reader.ReadInt16();
                header.SampleRate = reader.ReadInt32();
                header.ByteRate = reader.ReadInt32();
                header.BlockAlign = reader.ReadInt16();
                header.BitsPerSample = reader.ReadInt16();

                if (header.Subchunk1Size == 18)
                {
                    // Read extra values (Сюда при чтении моего файла не заходит)
                    header.FmtExtraSize = reader.ReadInt16();
                    reader.ReadBytes(header.FmtExtraSize);
                }

                header.Subchunk2Id = reader.ReadInt32();
                header.Subchunk2Size = reader.ReadInt32();

                data = reader.ReadBytes(header.Subchunk2Size);
            }
        }

        var result = new ParsingResult(header, data);

        return result;
    }
}


Класс WavHeader:

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

    // Содержит символы "RIFF" в ASCII кодировке
    // (0x52494646 в big-endian представлении)
    public int ChunkId { get; set; }

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

    // Содержит символы "WAVE"
    // (0x57415645 в big-endian представлении)
    public int Format { get; set; }

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

    // Содержит символы "fmt "
    // (0x666d7420 в big-endian представлении)
    public int Subchunk1Id { get; set; }

    // 16 для формата PCM.
    // Это оставшийся размер подцепочки, начиная с этой позиции.
    public int Subchunk1Size { get; set; }

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

    // Количество каналов. Моно = 1, Стерео = 2 и т.д.
    public short NumChannels { get; set; }

    // Частота дискретизации. 8000 Гц, 44100 Гц и т.д.
    public int SampleRate { get; set; }

    // sampleRate * numChannels * bitsPerSample/8
    public int ByteRate { get; set; }

    // numChannels * bitsPerSample/8
    // Количество байт для одного сэмпла, включая все каналы.
    public short BlockAlign { get; set; }

    // Так называемая "глубиная" или точность звучания. 8 бит, 16 бит и т.д.
    public short BitsPerSample { get; set; }

    public int FmtExtraSize { get; set; }

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

    // Содержит символы "data"
    // (0x64617461 в big-endian представлении)
    public int Subchunk2Id { get; set; }

    // numSamples * numChannels * bitsPerSample/8
    // Количество байт в области данных.
    public int Subchunk2Size { get; set; }
}

    


Ответы

Ответ 1



Итак, по совету пользователя KoVadim я заглянул в hex-редактор, и к моему удивлению, содержимое wav-файла отличалось от того, чего я ожидал. Привожу содержимое wav-файла: Если верить этому источнику и взглянуть на наш файл, то мы увидим, что в поле AudioFormat(на картинке отмечено синим) занесено значение 1, т.е. это PCM (формат без сжатия). Читаем дальше. После того, как прочитали поле BitsPerSample (на картинке зеленым) ожидается, что следующие 4 байта будут data (потому что в PCM, если верить источнику, не должно быть никаких дополнительных данных между BitsPerSample и data), но мы видим, что до поля data (подчеркнуто красным) идет дополнительная информация о треке, чего быть не должно. Не знаю, с чем именно связано это. Буду очень признателен, если мне объяснят, как правильно прочитать эти дополнительные данные и почему они лежат в данном файле. В итоге я решил проблему следующим способом: с помощью популярного аудио-редактора Audacity я перекодировал данный Wav-файл в следующий формат: После чего дополнительные данные исчезли из этого файла и я смог успешно прочитать все данные.

Ответ 2



потому что в PCM, если верить источнику, не должно быть никаких дополнительных данных между BitsPerSample и data Вероятно, слишком поздно, но источник врет. Во-первых, PCM - это способ кодирования, а не формат файла (формат называется RIFF). Во-вторых, согласно спецификации данного формата, он может расширяться в последующих версиях добавлением новых блоков данных (в любом месте между старыми блоками), поэтому приложения для его считывания должны пропускать неизвестные им блоки, а не падать с ошибкой при их появлении. В данном случае, между заголовком файла и блоком DATA содержится блок INFO, который содержит текстовые сведения о композиции (отлично заметные кракозябры в HEX-редакторе). Таким образом, правильный алгоритм считывания блоков RIFF-файла выглядит как-то так: Считать SubchunkID и SubchunkSize Если SubchunkID - не то, что ожидалось, пропустить SubchunkSize байтов и вернуться на шаг 1 Иначе, считать и обработать массив байтов, равный SubchunkSize Информацию о формате и ссылки на нормальную документацию можно найти здесь: Audio File Format Specifications

Комментариев нет:

Отправить комментарий