Структура WAV файла

Главная » Статьи » Структура WAV файла

Итак, рассмотрим самый обычный WAV файл (Windows PCM). Он представляет собой две, четко делящиеся, области. Одна из них – заголовок файла, другая – область данных. В заголовке файла хранится информация о:

  • Размере файла.
  • Количестве каналов.
  • Частоте дискретизации.
  • Количестве бит в сэмпле (эту величину еще называют глубиной звучания).

Но для большего понимания смысла величин в заголовке следует еще рассказать об области данных и оцифровке звука. Звук состоит из колебаний, которые при оцифровке приобретают ступенчатый вид. Этот вид обусловлен тем, что компьютер может воспроизводить в любой короткий промежуток времени звук определенной амплитуды (громкости) и этот короткий момент далеко не бесконечно короткий. Продолжительность этого промежутка и определяет частота дискретизации. Например, у нас файл с частотой дискретизации 44.1 kHz, это значит, что тот короткий промежуток времени равен 1/44100 секунды (следует из размерности величины Гц = 1/с). Современные звуковые карты поддерживают частоту дискретизации до 192 kHz. Так, со временем разобрались.

Теперь, что касается амплитуды (громкости звука в коротком промежутке времени). От нее, я бы сказал, зависит точность звука. Амплитуда выражается числом, занимаемым в памяти (файле) 8, 16, 24, 32 бит (теоретически можно и больше). Как известно, 8 бит = 1 байту, следовательно, какая-то одна амплитуда в какой-то короткий промежуток времени в памяти (файле) может занимать 1, 2, 3, 4 байта соответственно. Таким образом, чем больше число занимает места в памяти (файле), тем больше диапазон значений для этого числа, а значит и для амплитуды.

  • 1 байт – 0..255
  • 2 байта – 0..65 535
  • 3 байта – 0..16 777 216
  • 4 байта – 0..4 294 967 296

В моно варианте значения амплитуды расположены последовательно. В стерео же, например, сначала идет значение амплитуды для левого канала, затем для правого, затем снова для левого и так далее.

Совокупность амплитуды и короткого промежутка времени носит название сэмпл.

Теперь таблица, наглядно показывающая структуру WAV файла.

Местоположение Поле Описание
0..3 (4 байта) chunkId Содержит символы "RIFF" в ASCII кодировке (0x52494646 в big-endian представлении). Является началом RIFF-цепочки.
4..7 (4 байта) chunkSize Это оставшийся размер цепочки, начиная с этой позиции. Иначе говоря, это размер файла - 8, то есть, исключены поля chunkId и chunkSize.
8..11 (4 байта) format Содержит символы "WAVE" (0x57415645 в big-endian представлении)
12..15 (4 байта) subchunk1Id Содержит символы "fmt " (0x666d7420 в big-endian представлении)
16..19 (4 байта) subchunk1Size 16 для формата PCM. Это оставшийся размер подцепочки, начиная с этой позиции.
20..21 (2 байта) audioFormat Аудио формат, полный список можно получить здесь. Для PCM = 1 (то есть, Линейное квантование). Значения, отличающиеся от 1, обозначают некоторый формат сжатия.
22..23 (2 байта) numChannels Количество каналов. Моно = 1, Стерео = 2 и т.д.
24..27 (4 байта) sampleRate Частота дискретизации. 8000 Гц, 44100 Гц и т.д.
28..31 (4 байта) byteRate Количество байт, переданных за секунду воспроизведения.
32..33 (2 байта) blockAlign Количество байт для одного сэмпла, включая все каналы.
34..35 (2 байта) bitsPerSample Количество бит в сэмпле. Так называемая "глубина" или точность звучания. 8 бит, 16 бит и т.д.
36..39 (4 байта) subchunk2Id Содержит символы "data" (0x64617461 в big-endian представлении)
40..43 (4 байта) subchunk2Size Количество байт в области данных.
44.. data Непосредственно WAV-данные.

Вот и весь заголовок, длина которого составляет 44 байта, далее следует блок данных о котором я уже рассказал выше.

На самом деле, понятно, что использованные типы данных можно и менять. Например, в Си (MSVS) вместе массива char[4] можно использовать __int32 или DWORD, но тогда сравнение с какой-либо строковой константой, к примеру может оказаться не очень удобным. Также хотелось бы предостеречь Вас в связи с влияением новой 64-битной моды на программные средства. А именно: всегда стоит помнить, в языке Си тип переменной int в 64-битной системе будет иметь длину 8 байт, а в 32-битной – 4 байта. В таких случаях можно воспользоваться вышеупомянутым типом переменной __int32 или __int64, в зависимости от того, какой размер переменной в памяти Вам необходим. Существуют типы __int8, __int16, __int32 и __int64, они доступны только для MSVC++ компилятора как минимум 7-й версии (Microsoft Visual Studio 2003.NET), но зато Вы не ошибетесь с выбором размера типа данных.

Примеры реализации

На языке C++

#include <stdio.h>
#include <tchar.h>
#include <conio.h>
#include <math.h>

// Структура, описывающая заголовок WAV файла.
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 данные.
};

int _tmain(int argc, _TCHAR* argv[])
{
    FILE *file;
    errno_t err;
    err = fopen_s(&file, "Slipknot - Three Nil.wav", "rb");
    if (err)
    {
        printf_s("Failed open file, error %d");
        return 0;
    }

    WAVHEADER header;

    fread_s(&header, sizeof(WAVHEADER), sizeof(WAVHEADER), 1, file);

    // Выводим полученные данные
    printf_s("Sample rate: %d\n", header.sampleRate);
    printf_s("Channels: %d\n", header.numChannels);
    printf_s("Bits per sample: %d\n", header.bitsPerSample);

    // Посчитаем длительность воспроизведения в секундах
    float fDurationSeconds = 1.f * header.subchunk2Size / (header.bitsPerSample / 8) / header.numChannels / header.sampleRate;
    int iDurationMinutes = (int)floor(fDurationSeconds) / 60;
    fDurationSeconds = fDurationSeconds - (iDurationMinutes * 60);
    printf_s("Duration: %02d:%02.f\n", iDurationMinutes, fDurationSeconds);

    fclose(file);

    _getch();
    return 0;
}

На языке C#

using System;
using System.IO;
using System.Runtime.InteropServices;

namespace WavFormatCSharp
{
	[StructLayout(LayoutKind.Sequential)]
	// Структура, описывающая заголовок WAV файла.
	internal class WavHeader
	{
		// WAV-формат начинается с RIFF-заголовка:

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

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

		// Содержит символы "WAVE"
		// (0x57415645 в big-endian представлении)
		public UInt32 Format;
	
		// Формат "WAVE" состоит из двух подцепочек: "fmt " и "data":
		// Подцепочка "fmt " описывает формат звуковых данных:
	
		// Содержит символы "fmt "
		// (0x666d7420 в big-endian представлении)
		public UInt32 Subchunk1Id;
		
		// 16 для формата PCM.
		// Это оставшийся размер подцепочки, начиная с этой позиции.
		public UInt32 Subchunk1Size;

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

		// Частота дискретизации. 8000 Гц, 44100 Гц и т.д.
		public UInt32 SampleRate;

		// sampleRate * numChannels * bitsPerSample/8
		public UInt32 ByteRate;

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

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

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

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

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

		// Далее следуют непосредственно Wav данные.
	}

	class Program
	{
		static void Main(string[] args)
		{
			var header = new WavHeader();
			// Размер заголовка
			var headerSize = Marshal.SizeOf(header);

			var fileStream = new FileStream("Slipknot - Three Nil.wav", FileMode.Open, FileAccess.Read);
			var buffer = new byte[headerSize];
			fileStream.Read(buffer, 0, headerSize);
			
			// Чтобы не считывать каждое значение заголовка по отдельности,
			// воспользуемся выделением unmanaged блока памяти
			var headerPtr = Marshal.AllocHGlobal(headerSize);
			// Копируем считанные байты из файла в выделенный блок памяти
			Marshal.Copy(buffer, 0, headerPtr, headerSize);
			// Преобразовываем указатель на блок памяти к нашей структуре
			Marshal.PtrToStructure(headerPtr, header);

			// Выводим полученные данные
			Console.WriteLine("Sample rate: {0}", header.SampleRate);
			Console.WriteLine("Channels: {0}", header.NumChannels);
			Console.WriteLine("Bits per sample: {0}", header.BitsPerSample);

			// Посчитаем длительность воспроизведения в секундах
			var durationSeconds = 1.0 * header.Subchunk2Size / (header.BitsPerSample / 8.0) / header.NumChannels / header.SampleRate;
			var durationMinutes = (int)Math.Floor(durationSeconds / 60);
			durationSeconds = durationSeconds - (durationMinutes * 60);
			Console.WriteLine("Duration: {0:00}:{1:00}", durationMinutes, durationSeconds);

			Console.ReadKey();
		}
	}
}