Сообщений 0    Оценка 136        Оценить  
Система Orphus

Обработка звуковых файлов в Windows

Автор: Евгений Музыченко
Источник: Журнал Компьютер Пресс #8-2000
Опубликовано: 13.07.2003
Исправлено: 10.12.2016
Версия текста: 1.0

Введение
Основные принципы мультимедийной файловой подсистемы
Файлы типа RIFF
Разделы RIFF–файлов
Код FOURCC
Типы главных разделов
Ключи мультимедийных файлов
Информационная структура файла
Буферизация файлового обмена
Скрытая и явная буферизация
Текущая позиция в буфере
Файлы в оперативной памяти
Процедуры обмена данными
Установленные и явные процедуры обмена
Глобальные и локальные процедуры обмена
Структура звуковых файлов RIFF/WAVE
Программирование MMIO
Типовые схемы применения средств MMIO
Открытие/закрытие файла
Чтение/запись без использования явного буфера
Чтение/запись с явной буферизацией
Формирование RIFF–файла
Чтение RIFF–файла
Использование собственных процедур обмена
Структуры, используемые в MMIO
MMCKINFO - описатель раздела файла
MMIOINFO - информационная структура файла
Функции MMIO
Классы функций
Перечень базовых функций:
Перечень функций управления буферизацией:
Перечень функций работы с форматом RIFF:
Перечень остальных функций интерфейса:
Возвращаемые значения
Базовые функции
MAKEFOURCC и mmioFOURCC - формирование кода FOURCC
StringToFOURCC - преобразование строки в код FOURCC
Open - открытие файла
Close - закрытие файла
Rename - переименование файла
Read - чтение из файла
Write - запись в файл
Flush - принудительная запись буфера на диск
Seek - позиционирование в файле
Функции управления буферизацией
GetInfo - запрос копии информационной структуры файла
SetInfo - установка системной информационной структуры файла
SetBuffer - установка буфера файла
Advance - продвижение по файлу в режиме явной буферизации
Функции работы с файлами RIFF
CreateChunk - создание раздела
Ascend - выход из раздела
Descend - вход в существующий раздел
Прочие функции
SendMessage – посылка произвольного сообщения
InstallIOProc – установка локальной процедуры обмена
IOProc - прототип функции для процедуры обмена
Недостатки интерфейса MMIO
Пример программы, использующей MMIO

Демонстрационная программа ACMPlay

Введение

Наряду с базовой подсистемой файлового ввода/вывода, представленной функциями CreateFile, ReadFile, WriteFile и т.п., в Windows существует подсистема мультимедийного (звукового, видео и им подобного) файлового ввода/вывода MMIO (Multimedia Input/Output). Подсистема была введена еще в Windows 3.x, и позволяет удобно и эффективно работать со звуковыми и видеофайлами, а также упрощает работу с файлами, размещенными в оперативной памяти (memory files).

Эта статья посвящена работе со звуковыми файлами с использованием программного интерфейса MMIO.

Основные принципы мультимедийной файловой подсистемы

Файлы типа RIFF

Подсистема может оперировать с двоичными файлами любого типа, однако она содержит стандартные средства для работы с файлами типа RIFF (Resource Interchange File Format - формат файла обмена ресурсами). Фирмы Microsoft и IBM предложили этот формат в качестве универсального средства организации и хранения данных с иерархическо-последовательной структурой в системах Windows и OS/2.

Структуру RIFF имеют файлы типа WAV (поток оцифрованного звука), IDF (описатель MIDI–инструмента), AVI (поток оцифрованного изображения и звука), ANI (описатель "оживленного" (анимированного) курсора мыши), RMI (один из видов MIDI–партитуры) и многие другие. Creative Labs использует формат RIFF в файлах типа CSP (программы для специализированного звукового процессора ASP звуковых адаптеров Sound Blaster) и SBK/SF2 (загружаемых банков инструментов для звуковых адаптеров классов Sound Blaster AWE и Live!). Универсальный формат загружаемых банков инструментов DLS (DownLoadable Sounds), принятый в качестве стандарта ассоциацией производителей MIDI–систем (MMA - MIDI Manufacturers Association), построен на основе RIFF. Фирма Aureal применяет этот формат для построения загружаемых банков инструментов для своих звуковых чипов.

Структура RIFF–файлов допускает непосредственную запись со звукового или видеоустройства в файл, а также непосредственное воспроизведение материала из файла.

Разделы RIFF–файлов

Файлы типа RIFF состоят из разделов (chunk - кусок). Раздел содержит набор данных определенного типа. Большая часть разделов непосредственно представляет наборы данных, однако некоторые разделы содержат внутри себя подразделы (subchunks), которые представляют собственно поток данных, его параметры, формат и т.п. Охватывающий раздел в этом случае называется главным, или родительским (parent chunk).

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

Поскольку раздел и подраздел в RIFF–файле имеют одинаковый вид, я буду в основном пользоваться термином "раздел", уточняя его лишь в том случае, когда имеет значение отношение подчиненности.

Каждый раздел состоит из заголовка (chunk header), содержащего код типа раздела и его размер, и из области данных (chunk data), представляющей собственно содержимое раздела. Поле размера в заголовке содержит размер только области данных; заголовок всегда состоит из двух двойных слов (DWORD) и имеет размер 8 байтов.

Каждый раздел располагается на границе слова (имеет четное смещение относительно начала файла). Общий размер раздела также должен быть четным; если содержащийся в нем блок данных имеет нечетный размер - он дополняется нулевым байтом. При этом поле размера блока данных в заголовке раздела содержит реальный размер блока; байт-заполнитель не учитывается.

Для каждого типа данных в файле есть обязательные и факультативные (optional) разделы; последние включаются в файл только при необходимости.

Код FOURCC

Для обозначения типов разделов, а также в некоторых других целях, используется специальный четырехсимвольный код FOURCC (Four-Character Code). Этот код представляет собой строку из четырех или менее алфавитно-цифровых символов, сформированную из текстового названия объекта и дополненную справа до полной длины пробелами. Регистр букв в строке кода является значимым, все сравнения выполняются буквально, регистрозависимым методом.

Фактически, числовое значение четырехсимвольного кода представляет собой четырехбайтовое, 32–разрядное значение типа DWORD, которое образует в памяти заданная строка. Например, код раздела "fmt" в памяти записывается тремя байтами - 0x66, 0x6D, 0x74, 0x20 (четвертый символ - пробел). Числовое значение слова, образованного этими четырьмя байтами, будет равно 0x20746D66, поскольку в процессорах Intel принято расположение в памяти байтов от младшего к старшему. По этой причине четырехсимвольные коды нельзя представлять обычными символьными константами языка C - числовое значение константы 'fmt ' будет равно 0x666D7420. Для формирования четырехсимвольных кодов в Windows имеются функции-макросы MAKEFOURCC и mmioFOURCC.

Для кодов главных разделов определены константы FOURCC_RIFF и FOURCC_LIST.

Типы главных разделов

Главные разделы типа RIFF и LIST бывают различных типов, образуя разного рода формы (RIFF form) и списки (list). Форма или список представляет собой раздел RIFF или LIST, первое двойное слово (DWORD) области данных которого содержит код формы/списка в формате FOURCC. Остальное содержимое области данных представляет собой собственно данные формы или списка, и обычно состоит из одного или нескольких подразделов.

Каждый RIFF–файл представляет собой одну RIFF–форму, которая содержит набор данных определенного типа - оцифрованный звук, изображение, видеоролик, MIDI–партитура и т.п. Таким образом, каждый RIFF–файл имеет структуру:

Смещение в файле Содержимое
0 Заголовок раздела RIFF (два двойных слова)
8 Тип формы (одно двойное слово)
12 Область подразделов (переменная длина)

Поле размера раздела RIFF в его заголовке содержит суммарный размер всех подразделов плюс размер двойного слова, содержащего тип формы (четыре байта). В правильно оформленном RIFF-файле поле размера раздела RIFF равно общему размеру файла минус размер RIFF–заголовка (восемь байтов).

В этой статье описывается только структура файлов типа WAVE, код типа формы для которого также представляется строкой "WAVE".

Ключи мультимедийных файлов

Как и в случае обычных файлов Windows, для доступа к мультимедийному файлу используется его ключ - числовое значение, возвращаемое функцией mmioOpen. Фактически, ключ файла представляет собой указатель системного описателя файла, расположенного в памяти подсистемы MMIO.

Ключи обычных и мультимедийных файлов несовместимы между собой, однако уже открытый обычный файл можно повторно открыть в подсистеме MMIO, передав его ключ (типа HANDLE) функции mmioOpen.

Информационная структура файла

Для каждого открытого файла подсистема MMIO поддерживает информационную структуру, описывающую режимы работы с файлом и его параметры. Базовая информационная структура находится внутри самой подсистемы MMIO, приложение может поддерживать собственную локальную копию типа MMIOINFO в своей собственной области памяти. Некоторые функции MMIO требуют от приложения указания локальной структуры MMIOINFO, и управляют полями структур таким образом, чтобы отразить операции, выполненные как приложением, так и самой подсистемой MMIO. Приложение также может непосредственно запросить копию системной структуры функцией mmioGetInfo, и модифицировать системную структуру функцией mmioSetInfo.

Буферизация файлового обмена

Обмен информацией с файлом может быть непосредственным или буферизованным.

Непосредственный обмен подразумевает прямое чтение информации с диска и запись ее обратно на диск, и эффективен в том случае, когда обмен с файлом идет большими (несколько килобайт или больше) блоками данных, размеры которых кратны размеру сектора диска (обычно 512 байтов), и сами блоки начинаются также на границе сектора. При обмене мелкими (несколько десятков или сотен байтов), или некратными сектору блоками резко возрастают накладные расходы.

При буферизованном обмене между приложением и файлом находится так называемый буфер файла. При чтении из файла вначале неявно считывается полный буфер, а последующие функции чтения выбирают нужные порции данных из буфера, не обращаясь при этом к диску. При записи все работает наоборот: функции записи вначале заполняют буфер, а затем полностью записанный буфер неявно переносится в файл одной операцией записи.

Строго говоря, в MMIO даже непосредственный обмен с файлом включает простую скрытую буферизацию в самой подсистеме, так как минимальной единицей обмена данными с диском является сектор.

Скрытая и явная буферизация

Буферизация файлов в MMIO может быть скрытой и явной.

Скрытая буферизация работает так же, как и в базовом файловом обмене: файловая подсистема создает буфер, через который проходят все операции чтения/записи, выполняемые приложением. Работа буфера в этом случае приложению не видна, однако возможны накладные расходы при пересылке данных между буферами приложения и файлов.

При явной буферизации приложение имеет непосредственный доступ к буферу для чтения и записи. В этом случае оно самостоятельно заносит данные в буфер при записи и извлекает их при чтении. Для уведомления файловой подсистемы об изменении состояния буфера служит функция продвижения по файлу, которая корректирует рабочие указатели буфера и при необходимости выполняет непосредственно обмен буфера с файлом.

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

Текущая позиция в буфере

При работе с буферизованным файлом используется понятие указателя текущей позиции буфера. В начале работы указатель устанавливается на начало буфера, в процессе считывания или записи данных он движется в сторону конца буфера. При достижении конца буфера или специального ограничителя содержимое буфера записывается в файл либо обновляется из него, и указатель текущей позиции снова возвращается в начало буфера.

Вместе с указателем текущей позицией файловая подсистема использует два специальных ограничителя буфера - ограничитель чтения и ограничитель записи, которые являются "барьерами" для указателя текущей позиции. Фактически, оба ограничителя представляют собой указатели, ссылающиеся на байты сразу за концом области буфера, доступной для чтения и записи соответственно. Таким образом, последним доступным для чтения или записи является адрес, на единицу меньший значения соответствующего ограничителя. Размер доступной для чтения или записи области определяется разностью соответствующего ограничителя и указателя текущей позиции.

Текущая позиция буфера не имеет никакого отношения к текущей позиции самого файла. Позиция в файле изменяется только при операциях непосредственного обращения к файлу, позиция буфера - при работе с буфером. Текущие позиции буфера и ограничителей находятся в информационной структуре файла.

Файлы в оперативной памяти

Вместо внешнего устройства файл может находиться в оперативной памяти. Механизм виртуальной памяти Windows позволяет таким файлам иметь практически любой размер, ограниченный лишь объемом доступного на диске пространства для файла подкачки (swapfile).

Размещение файла в памяти позволяет максимально быстро работать с данными, используя обычный файловый интерфейс, что в ряде случаев может быть удобнее, чем использование стандартных средств управления памятью Windows.

Файл, размещаемый в памяти, на самом деле состоит из одного лишь буфера, размер которого увеличивается по мере записи в файл. Стандартная процедура обмена для файлов в памяти управляет указателем текущей позиции, ограничителями чтения/записи и размером буфера, что с точки зрения приложения неотличимо от работы с реальным дисковым файлом.

Процедуры обмена данными

Файловые функции MMIO на самом деле являются промежуточными. Весь фактический обмен с файлом выполняют так называемые процедуры обмена (I/O Procedures). Стандартные процедуры обмена обслуживают файлы на дисковых устройствах и в оперативной памяти; пользовательские процедуры могут обслуживать файлы на любом носителе, или даже полностью виртуальные файлы, не имеющие физического представления.

Обращения приложения к файловым функциям MMIO транслируются подсистемой в сообщения к соответствующей процедуре обмена, которая выполняет запрос и возвращает соответствующий код результата.

Можно сказать, что процедура обмена, как оконечный интерфейс, определяет среду хранения (storage system) файла.

Установленные и явные процедуры обмена

Процедуры обмена могут быть установленными и явными. Установленная процедура вносится во внутренний список подсистемы MMIO и снабжается собственным уникальным символьным кодом, по которому она может быть найдена и подключена к открываемому файлу. Явная процедура задается непосредственно своим указателем при открытии файла, и не имеет собственного кода.

В подсистеме MMIO имеются две стандартные процедуры обмена: DOS - интерфейс с дисковыми устройствами, и MEM - интерфейс с оперативной памятью для файлов в памяти. Их коды определяются константами FOURCC_DOS и FOURCC_MEM.

Глобальные и локальные процедуры обмена

Установленные процедуры обмена делятся на глобальные и локальные. Глобальные процедуры доступны всем процессам системы, локальные - только установившему их процессу. Поиск процедуры по коду начинается со списка локальных процедур, которые в этом случае имеют приоритет.

Разделение процедур обмена между процессами возможно только в Win16; в Win32 это дает непредсказуемые результаты.

Структура звуковых файлов RIFF/WAVE

Файлы типа RIFF/WAVE служат для хранения оцифрованных звуковых потоков в различных звуковых форматах - PCM, ADPCM, a-Law, GSM, Audio MPEG и т.п. Стандартное расширение для файлов этого типа - WAV.

Минимальный состав WAVE–формы включает два подраздела: формата и данных. Раздел формата имеет код "fmt", и содержит описатель формата звуковых данных в виде расширенной структуры WAVEFORMATEX. Раздел данных состоит либо из одного подраздела "data", содержащего единый поток звуковых данных в цифровом виде, либо из подраздела-списка "wavl", содержащего последовательность из подразделов "data" и "slnt" (silent - тихий). Каждый подраздел "data" задает отдельный фрагмент звучания, подраздел "slnt" - фрагмент тишины (паузу) заданной длительности.

Для форматов, отличных от PCM, и в случае использования списка "wavl" после раздела "fmt" вставляется дополнительный раздел "fact". Первое двойное слово (DWORD) области данных раздела "fact" содержит общее количество звуковых отсчетов (samples) в файле. При помощи этого параметра можно определить время воспроизведения файла, поделив количество отсчетов на значение поля nSamplesPerSec в описателе формата, или вычислить объем, который поток займет после восстановления в PCM - умножив количество отсчетов на значение поля nBlockAlign в описателе выбранного для восстановления формата PCM.

В настоящее время область данных раздела "fact" включает только описанное поле, однако в будущем она может быть расширена добавлением дополнительных полей. Это необходимо иметь в виду, ориентируясь на размер области данных раздела, указанный в его заголовке. По размеру области данных можно узнать о наличии в разделе полей расширения.

В качестве необязательных элементов звукового файла перед разделом данных могут присутствовать разделы "cue" (список "флажков", или "закладок" внутри звукового потока - cue points), "plst" (порядок воспроизведения фрагментов - playlist), "adtl" (раздел типа "LIST", разная дополнительная информация о файле - associated data list) и т.п. Полное и подробное описание возможной структуры звукового файла выходит за рамки данной статьи.

Программирование MMIO

Для программирования ACM необходим любой стандартный SDK (Win16 или Win32), содержащий файлы заголовка MMSYSTEM.H и библиотеки WINMM.LIB.

Типовые схемы применения средств MMIO

Открытие/закрытие файла

При открытии файла функцией mmioOpen указываются необходимые виды доступа, режимы совместного использования и буферизации. Функция от крывания дискового файла в MMIO в конечном счете раскрывается в базовые функции открывания файла Windows, поэтому подробности о режимах открытия и совместного использования файлов можно получить из описания функции CreateFile.

Возможно использование в подсистеме MMIO уже открытого базового файла Windows; для этого предусмотрен частный случай использования функции mmioOpen.

При открытии файла, либо впоследствии, для него может быть установлен режим буферизации функцией mmioSetBuffer. В зависимости от набора функций, используемых для чтения/записи, буферизация может быть скрытой или явной.

После завершения работы с файлом он должен быть закрыт функцией mmioClose. Если файл был принят для обработки в MMIO посредством передачи ключа открытого базового файла Windows – базовый файл также будет закрыт, если в функции mmioClose не указан соответствующий флаг.

Чтение/запись без использования явного буфера

В этом режиме работа с файлом происходит точно так же, как в базовой файловой подсистеме Windows или встроенных средствах поддержки файлов языка C. Сразу после открытия файла для чтения/записи доступны функции mmioRead/mmioWrite, для смены позиции – функции mmioSeek. Для того, чтобы гарантированно записать в файл содержимое внутренних буферов, применяется функция mmioFlush.

Чтение/запись с явной буферизацией

Обработка файла с явной буферизацией может начинаться в любое время – сразу после открытия, либо после частичной обработки в обычных режимах. Перед началом обработки с явной буферизацией приложение должно любым из способов, предусмотренных в MMIO, задать буфер для файла. Затем приложение запрашивает копию информационной структуры файла функцией mmioGetInfo, после чего приступает к работе с буфером, используя указатели позиций буфера в полях pchNext, pchEndRead, pchEndWrite.

Если данные в буфере изменяются при записи или модификации буфера – приложение должно установить флаг DIRTY в слове флагов информационной структуры. При достижении конца буфера вызывается функция mmioAdvance, продвигающая буфер по файлу и соответствующим образом изменяющая указатели позиций.

Сеанс обработки файла в буферизованном режиме завершается вызовом функции mmioSetInfo, окончательно фиксирующей текущее состояние буфера в файле. После этого обработка файла может быть продолжена в обычном режиме.

Функция mmioSetInfo вызывается также в том случае, когда необходима фактическая запись на диск еще не заполненного буфера; при этом должен быть установлен флаг DIRTY.

Перед использованием функций работы с разделами RIFF файл должен находиться в обычном режиме. Например, существующий файл открывается в обычном режиме, затем функциями mmioDescend в нем находятся нужные разделы, после чего файл переводится в режим явной буферизации и считывается в этом режиме.

Формирование RIFF–файла

В созданном или открытом для перезаписи файле при помощи функции mmioCreateChunk создаются необходимые разделы и подразделы, заполняются данными в любом из режимов буферизации, после чего разделы закрываются функцией mmioAscend.

Чтение RIFF–файла

Для открытого файла вызывается функция mmioDescend, выполняющая поиск нужного раздела или подраздела, затем данные найденных разделов считываются и обрабатываются в любом из режимов буферизации.

Использование собственных процедур обмена

Если приложение поддерживает собственные устройства и методы хранения данных, и хочет использовать для них функции MMIO – оно может установить собственные процедуры обмена для этих файлов функцией mmioInstallIOProc, после чего работать с файлами посредством любых функций MMIO. Например, таким образом удобно работать с RIFF–файлами на нестандартных носителях информации.

Структуры, используемые в MMIO

MMCKINFO - описатель раздела файла

Описывает раздел файла типа RIFF. Первые три поля структуры представляют собой заголовок раздела в том же виде, в котором он присутствует в файле; поле fccType присутствует только в заголовках главных разделов.

  FOURCC  ckid;
  DWORD  cksize;
  FOURCC  fccType;
  DWORD  dwDataOffset;
  DWORD  dwFlags;

MMIOINFO - информационная структура файла

Описывает состояние открытого файла, его буфера и процедуры обмена данными.

  DWORD  dwFlags;
  FOURCC  fccIOProc;
  LPMMIOPROC  pIOProc;
  UINT  wErrorRet;
  HTASK  hTask;
  LONG  cchBuffer;
  HPSTR  pchBuffer;
  HPSTR  pchNext;
  HPSTR  pchEndRead;
  HPSTR  pchEndWrite;
  LONG  lBufOffset;
  LONG  lDiskOffset;
  DWORD  adwInfo [4];
  DWORD  dwReserved1;
  DWORD  dwReserved2;
  HMMIO  hmmio;
DIRTY Флаг, показывающий, что содержимое буфера было изменено и требует записи на диск. Устанавливается либо MMIO при скрытой буферизации, либо приложением - при явной буферизации. Сбрасывается MMIO после фактической записи буфера на диск.
RWMODE Битовая маска, включающая все флаги вида доступа к файлу.
SHAREMODE Битовая маска, включающая все флаги режимов совместного доступа к файлу.

Функции MMIO

Классы функций

Средства мультимедийной файловой подсистемы включают три основных класса функций:

Все функции интерфейса имеют имена с префиксом mmio. В заголовке описания каждой функции этот префикс опущен; полное имя каждой функции приведено в ее прототипе.

Большая часть функций получает параметром ключ открытого файла. Такие функции имеют в прототипе параметр File типа HMMIO. В целях экономии места этот параметр не описывается в каждом из описаний функций.

Перечень базовых функций:

mmioStringToFOURCC Преобразование строки ASCIZ в код FOURCC
mmioOpen Открытие или опрос файла
mmioClose Закрытие файла
mmioRename Переименование файла
mmioRead Чтение из файла
mmioWrite Запись в файл
mmioSeek Позиционирование по файлу

Перечень функций управления буферизацией:

mmioGetInfo Запрос информационной структуры файла
mmioSetInfo Модификация информационной структуры файла
mmioSetBuffer Установка буфера для файла
mmioAdvance Продвижение по файлу
mmioFlush Принудительная запись буфера в файл

Перечень функций работы с форматом RIFF:

mmioCreateChunk Создание раздела
mmioAscend Выход из раздела
mmioDescend Вход в раздел

Перечень остальных функций интерфейса:

mmioSendMessage Посылка произвольного сообщения процедуре обмена
mmioInstallIOProc Установка процедуры обмена
IOProc Прототип процедуры обмена

Возвращаемые значения

Для функций, возвращающих значения типа MMRESULT, а также для ряд других, определены константы кодов завершения с префиксами MMSYSERR_ и MMIOERR_. Константы первой группы были описаны в статье "Низкоуровневое программирование звука в Windows"), константы второй группы перечислены в таблице:

FILENOTFOUND Файл не найден
OUTOFMEMORY Недостаточно памяти
CANNOTOPEN Невозможно открыть файл
CANNOTCLOSE Невозможно закрыть файл
CANNOTREAD Невозможно прочитать из файла
CANNOTWRITE Невозможно записать в файл
CANNOTSEEK Невозможно позиционировать файл
CANNOTEXPAND Невозможно расширить файл
CHUNKNOTFOUND Раздел не найден
UNBUFFERED Файл открыт для непосредственного доступа
PATHNOTFOUND Недопустимый путь (устройство и/или каталог)
ACCESSDENIED Доступ к файлу запрещен
SHARINGVIOLATION Нарушение условий совместного доступа
NETWORKERROR Ошибка сетевой подсистемы
TOOMANYOPENFILES Нет свободных описателей ключей для нового файла
INVALIDFILE Общая ошибка, неудача по неизвестной причине

Базовые функции

MAKEFOURCC и mmioFOURCC - формирование кода FOURCC

Эти два макроса формируют 32-разрядное значение типа FOURCC (DWORD) из четырех заданных символов (значений типа char):

MAKEFOURCC (ch0, ch1, ch2, ch3)
mmioFOURCC (ch0, ch1, ch2, ch3)

Оба макроса полностью идентичны – mmioFOURCC определяется через MAKEFOURCC. Результатом вызова любого из макросов является "склейка" четырех символьных значений в одно 32-разрядное:

mmioFOURCC ('c', 'u', 'e', ' ') преобразуется в 0x20657563

StringToFOURCC - преобразование строки в код FOURCC

Преобразует заданную строку ASCIZ в код FOURCC.

FOURCC mmioStringToFOURCC (
  LPCSTR Str,
  UINT Flags
);

Функция формирует из заданной строки соответствующий четырехсимвольный код, при необходимости дополняя строку пробелами либо отбрасывая лишние символы. Например, результатом преобразования строки "fmt" будет числовое значение 0x20746D66.

Open - открытие файла

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

HMMIO mmioOpen (
  LPSTR FileName,
  MMIOINFO *Info,
  DWORD Mode
);

Флаги режимов открытия:

CREATE Запрашивает создание нового файла, либо усечение существующеего файла до нулевого размера.
DELETE Запрашивает удаление существующего файла. При успешном завершении возвращается TRUE, в противном случае - FALSE. Этот режим перекрывает все остальные режимы открытия файла.
ALLOCBUF Запрашивает автоматическое выделение буфера для файла. Если указатель информационной структуры не задан, либо ее поле cchBuffer имеет нулевое значение, выделяется буфер размером MMIO_DEFAULTBUFFER (8 кб), в противном случае поле cchBuffer задает размер выделяемого буфера.

Коды режимов опроса, без фактического открытия файла:

PARSE Запрашивает возврат полного имени (пути) для заданного имени файла. Существование самого файла не проверяется. При успешном завершении функция возвращает значение TRUE; в противном случае возвращается значение FALSE.
EXIST Аналогично PARSE, но проверяется существование заданного файла.
GETTEMP Запрашивает формирование уникального имени для временного файла. Временное имя дописывается к исходной строке имени. Функция возвращает нулевое значение при успешном завершении, и значение MMIOERR_FILENOTFOUND при неудаче. Этот флаг перекрывает все остальные флаги режима опроса.

При использовании этих трех режимов сформированное имя файла возвращается в область памяти по указателю FileName. С константами этой группы не допускается указание констант из других групп.

Флаги вида доступа (access mode):

READ Файл открывается только для чтения.
WRITE Файл открывается только для записи.
READWRITE Файл открывается для чтения и записи одновременно.

Флаги режимов разделяемого доступа (sharing mode):

COMPAT Совместимый режим (compatibility mode), не накладывающий ограничений на одновременное открытие файла другими процессами в таком же совместимом режиме.
DENYNONE Другим процессам разрешаются все операции с этим файлом.
DENYREAD Другим процессам запрещаются операции чтения с этим файлом.
DENYWRITE Другим процессам запрещаются операции записи с этим файлом.
EXCLUSIVE Запрещается любое повторное открытие этого файла, даже в текущем процессе.
ПРИМЕЧАНИЕ

Режимы разделяемого доступа работают "в обе стороны" - не позволяя другим процессам открывать файл в запрещенных режимах после успешного завершения функции, и не позволяя функции завершиться успешно, если файл уже открыт другим процессом в одном из запрещенных режимов. Одновременное открытие файла в совместимом и в одном из разделяемых режимов также недопустимо.

В случае задания локальной информационной структуры файла она должна быть заполнена в соответствии со следующими правилами:

Если было запрошено открытие файла, функция при успешном завершении возвращает ключ открытого файла, который впоследствии используется в большинстве остальных функций работы с файлами. При неудачном завершении возвращается нулевое значение, а код ошибки заносится в поле wErrorRet локальной информационной структуры, если она указана.

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

Локальная информационная структура файла, передаваемая параметром Info, служит только для передачи параметров в функцию, которая устанавливает значения внутренней информационной структуры MMIO. Никакие поля локальной структуры, включая указатель текущей позиции и ограничители чтения/записи, не заполняются функцией. Для получения копии текущего значения структуры необходимо использовать функцию mmioGetInfo.

Close - закрытие файла

Закрывает файл, открытый функцией mmioOpen, либо завершает работу со стандартным файлом Windows, повторно открытым функцией mmioOpen.

MMRESULT mmioClose (
  HMMIO File,
  UINT Flags
);

Если буфер файла помечен, как требующий дозаписи на диск, функция неявно вызывает mmioFlush. Если в процессе работы mmioFlush возникает ошибка (например, переполнение диска) - код ошибки будет возвращен функцией mmioClose.

После успешного завершения mmioClose ключ File становится недоступным и обращение к нему приведет к ошибке.

Rename - переименование файла

Переименует заданный файл.

MMRESULT mmioRename (
  LPCSTR Name,
  LPCSTR NewName,
  const MMIOINFO *Info,
  DWORD Reserved
);

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

Read - чтение из файла

Считывает из файла указанное количество байтов.

LONG mmioRead (
  HMMIO File,
  char *Buffer,
  LONG Size
);

Функция возвращает размер реально считанной и занесенной в буфер порции данных. Если данных в файле больше нет (достигнут конец файла) - возвращается нулевое значение. При ошибке чтения возвращается -1.

Write - запись в файл

Записывает в файл указанное количество байтов.

LONG mmioWrite (
  HMMIO File,
  const char *Data,
  LONG Size
);

Возвращается количество реально записанных в файл байтов, либо -1 в случае ошибки.

Успешное завершение функции означает, что данные успешно перенесены в скрытый внутренний буфер файла, поддерживаемый MMIO. Непосредственно в файл записываются только полные буферы, остаток данных остается в буфере до вызова функций mmioFlush или mmioClose.

Flush - принудительная запись буфера на диск

Служит для принудительной записи скрытого системного буфера файла на диск.

MMRESULT mmioFlush (
  HMMIO File,
  UINT Flags
);

Функция используется, когда приложение хочет быть уверенным, что все данные, записанные ранее функцией mmioWrite, на самом деле записаны в файл, а не остались в скрытом промежуточным буфере. При успешном завершении mmioFlush содержимое буфера записывается в файл.

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

Seek - позиционирование в файле

Перемещает текущую позицию файла в заданное место.

LONG mmioSeek (
  HMMIO File,
  LONG Offset,
  int Origin
);

Offset - смещение в байтах относительно заданной точки. Может быть положительным, нулевым, либо отрицательным.

Origin - точка, относительно которой выполняется позиционирование. Возможно три варианта задания исходной точки:

Функция возвращает новую текущую позицию относительно начала файла, либо -1 в случае ошибки. При установке текущей позиции за концом файла функция может выполниться успешно, однако последующие операции чтения/записи могут привести к ошибке.

Функция часто используется для получения длины файла - путем позиционирования на 0 байтов относительно конца файла; возвращаемое значение при этом представляет длину файла в байтах.

Функции управления буферизацией

GetInfo - запрос копии информационной структуры файла

Заполняет указанную область данными системной информационной структуры файла, которая поддерживается для каждого открытого файла внутри подсистемы MMIO.

MMRESULT mmioGetInfo (
  HMMIO File,
  MMIOINFO *Info,
  UINT Reserved
);

Функция создает в указанной области памяти локальную копию текущего состояния информационной структуры файла, и обязательно должна быть вызвана перед началом работы с файлом в режиме явной буферизации. После завершения работы в этом режиме системная информационная структура MMIO должна быть обновлена текущими значениями из локальной копии при помощи функции mmioSetInfo.

SetInfo - установка системной информационной структуры файла

Передает данные из локальной копии информационной структуры файла, поддерживаемой приложением, в системную структуру MMIO.

MMRESULT mmioSetInfo (
  HMMIO File,
  const MMIOINFO *Info,
  UINT Reserved
);

Функция используется для завершения обработки файла в режиме явной буферизации. Состояние буфера, в котором его оставило приложение, передается MMIO, и с этого момента приложение не должно пользоваться функциями явной буферизации, работая с файлом обычными методами до повторного переключения в режим явной буферизации.

Если содержимое буфера было изменено приложением, перед вызовом функции необходимо установить флаг DIRTY. Фактическая запись буфера на диск выполняется только при наличии этого флага.

SetBuffer - установка буфера файла

Устанавливает или отменяет буферизацию файла.

MMRESULT mmioSetBuffer (
  HMMIO File,
  LPSTR Buffer,
  LONG Size,
  UINT Reserved
);

Функция выполняет затребованную смену режима буферизации для указанного файла. Если в данный момент для файла используется внутренний буфер, а функция вызывается в режиме задания размера внутреннего буфера - MMIO изменяет размер буфера функцией GlobalRealloc.

Advance - продвижение по файлу в режиме явной буферизации

Считывает в явный буфер очередную порцию данных из файла, либо записывает содержимое буфера в файл.

MMRESULT mmioAdvance (
  HMMIO File,
  MMIOINFO *Info,
  UINT Mode
);

Для того, чтобы в режиме записи буфер был действительно записан в файл, необходимо наличие в локальной копии информационной структуры флага DIRTY. Если этот флаг не установлен, операция MMIO_WRITE лишь сдвигает текущую позицию файла, не выполняя фактической записи данных из буфера.

После успешного выполнения чтения или записи функция обновляет значения полей pchNext, pchEndRead, pchEndWrite, lDiskOffset локальной информационной структуры в соответствии с новым состоянием буфера и файла.

При достижении конца файла в режиме чтения функция всегда завершается успешно, а реальный объем считанных в буфер данных определяется разностью указателей pchEndRead и pchNext.

Функции работы с файлами RIFF

CreateChunk - создание раздела

Создает новый раздел в файле.

MMRESULT mmioCreateChunk (
  HMMIO File,
  MMCKINFO *Chunk,
  UINT Type
);

Chunk - указатель описателя создаваемого раздела, типа MMCKINFO:

Type - тип создаваемого раздела:

Функция создает с текущей позиции файла заголовок раздела заданного типа. Раздел создается путем простой записи заголовка с текущей позиции, средства MMIO не выполняют вставки новых разделов между существующими.

При успешном завершении функции текущая позиция файла устанавливается на начало его области данных, а для режимов CREATERIFF / CREATELIST - сразу за двойным словом типа раздела (12 байтов от начала заголовка). Функция также устанавливает флаг DIRTY в описателе раздела, чтобы вызванная впоследствии функция mmioAscend могла проверить и скорректировать фактический размер области данных раздела.

Ascend - выход из раздела

Выполняет выход из раздела, который был создан функцией mmioCreateChunk, либо в который был выполнен вход функцией mmioDescend.

MMRESULT mmioAscend (
  HMMIO File,
  MMCKINFO *Chunk,
  UINT Reserved
);

Смысл выхода из раздела состоит в позиционировании файла сразу за концом раздела, которым является конец области данных, автоматически дополненный при необходимости для четности нулевым выравнивающим байтом. Выход из раздела выполняется двумя способами, в зависимости от наличия в описателе раздела флага DIRTY.

Если флаг не установлен - функция сразу выполняет позиционирование за конец раздела в соответствии со значениями полей dwDataOffset и cksize описателя, полагая, что размер раздела не был изменен после входа в него функцией mmioDescend.

Если флаг установлен - функция трактует текущую позицию файла, как конец области данных сформированного или измененного раздела, при необходимости дописывает выравнивающий байт, вычисляет фактический размер области данных и сравнивает его со значением поля cksize. Если значения отличаются - функция корректирует значение поля cksize в заголовке раздела, отражая там его фактический размер. После этого выполняется позиционирование за конец раздела, как и в первом случае.

Функция вызывается сразу после завершения формирования нового раздела, длина области данных которого заранее неизвестна, либо после изменения длины существующего раздела.

Descend - вход в существующий раздел

Выполняет поиск указанного раздела и вход в него.

MMRESULT mmioDescend (
  HMMIO File,
  MMCKINFO *Chunk,
  const MMCKINFO *Parent,
  UINT Mode
);

Chunk - указатель описателя искомого раздела, типа MMCKINFO. В поле ckid должен быть указан код искомого раздела, кроме случаев явного поиска разделов RIFF/LIST путем задания значения параметра Mode.

Parent - указатель описателя главного раздела, типа MMCKINFO. Если задан - требуемый раздел ищется внутри указанного главного раздела, вход в который был ранее выполнен функцией mmioDescend.

Mode - вид выполняемой операции:

Функция ищет в файле раздел, код которого задан Если ищется раздел RIFF или LIST - функция автоматически заносит в это поле соответствующее значение. В этом случае в поле fccType должен быть указан тип искомого главного раздела.

Если требуется найти любой из главных разделов, независимо от типа - в поле ckid описателя искомого раздела необходимо занести соответствующий код, а в параметре Mode указать значение MMIO_FINDCHUNK.

Текущая позиция файла перед вызовом функции должна быть либо в начале какого-либо раздела, либо внутри указанного главного раздела. Поиск начинается с текущей позиции и продолжается до конца файла или указанного главного раздела.

При обнаружении нужного раздела функция считывает его заголовок в заданный описатель, заносит в поле dwDataOffset позицию начала области данных в файле, и устанавливет текущую позицию на начало области данных раздела. Для режимов FINDRIFF / FINDLIST позиция устанавливается сразу за двойным словом типа раздела (12 байтов от начала заголовка).

Флаг DIRTY в описателе раздела сбрасывается. Если требуется расширить, усечь или перезаписать область данных раздела - после этого необходимо установить флаг DIRTY и выполнить выход из раздела функцией mmioAscend для коррекции поля длины в заголовке. Для разделов, длина которых не изменяется, выход выполнять не требуется.

Если искомый раздел не найден - текущая позиция файла не определена.

Прочие функции

SendMessage – посылка произвольного сообщения

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

LRESULT mmioSendMessage (
  HMMIO File,
  UINT Msg,
  LPARAM Param1,
  LPARAM Param2
);

Фактически почти все вызываемые приложением функции MMIO преобразуются в сообщения с кодами MMIOM_xxx, передаваемые процедуре обмена. Если процедура обмена поддерживает какие-либо дополнительные сообщения – например, средства расширенного управления - функция mmioSendMessage позволяет передать такое сообщение с нужными параметрами. Функция возвращает значение, возвращенное процедурой обмена в ответ на переданное ей сообщение.

Поскольку стандартные сообщения MMIOM_OPEN, MMIOM_READ и т.п. генерируются подсистемой MMIO в ответ на обращение приложения к стандартным функциям MMIO, формально не допускается прямая передача таких сообщений процедуре обмена посредством функции mmioSendMessage. Однако, при хорошем понимании внутреннего устройства MMIO и принципа взаимодействия с процедурой обмена, возможно непосредственное общение приложения с процедурой обмена, если при этом соблюдаются все внутренние соглашения Windows и MMIO.

InstallIOProc – установка локальной процедуры обмена

Устанавливает собственную процедуру обмена внутри приложения и делает ее доступной для подключения к открываемым файлам. Позволяет также найти или удалить нужную установленную процедуру.

LPMMIOPROC mmioInstallIOProc (
  FOURCC ProcCode,
  LPMMIOPROC ProcAddr,
  DWORD Flags
);

ProcCode – код процедуры обмена, над которой выполняется требуемая операция. Код должен быть составлен только из символов верхнего регистра (заглавные буквы).

ProcAddr – адрес внутренней функции для устанавливаемой процедуры обмена. Функция должна быть оформлена в соответствии с заданным прототипом. Для случаев поиска или удаления процедуры этот параметр должен быть нулевым.

Flags – код выполняемой операции и дополнительные флаги:

Функция возвращает адрес установленной, удаленной или найденной процедуры обмена, либо нулевое значение в случае ошибки.

Новые процедуры обмена заносятся в начало внутреннего списка MMIO, и при установке процедуры для уже имеющегося кода новая процедура будет использоваться вместо старой. При поиске/удалении список просматривается от начала к концу, и найдена/удалена будет последняя из установленных процедур с подходящим кодом. Таким образом, удаление процедур должно происходить в порядке, обратном порядку их установки.

В Win32 глобально установленные процедуры становятся "видимыми" из других процессов – другой процесс может получить адрес установленной процедуры, удалить ее, а также попытаться подключить ее к открываемому файлу посредством указания структурного имени в функции mmioOpen. Однако, из-за использования различными процессами разных адресных пространств, обращение к "чужой" процедуре обмена в общем случае приведет к ошибке.

IOProc - прототип функции для процедуры обмена

Задает правила оформления внутренней программной функции, выступающей в роли процедуры обмена. Установка функции в качестве процедуры обмена выполняется сервисной функцией mmioInstallIOProc.

LRESULT CALLBACK IOProc (
  LPSTR Info,
  UINT Msg,
  LONG Param1,
  LONG Param2
);

Процедура обмена отвечает за поддержание правильного значения поля lDiskOffset в информационной структуре файла. Для линейных последовательных файлов, на диске или в памяти, это поле отражает текущее смещение внутри файла.

Процедура обмена вызывается обычным обращением к подпрограмме из той же задачи (thread), которая запросила файловую операцию посредством одной из функций MMIO. Отдельной задачи для процедуры обмена не создается.

Для хранения своих служебных данных процедура обмена может пользоваться массивом adwInfo в информационной структуре. Прочие поля структуры управляются только MMIO; процедура обмена не должна изменять их значений.

В ответ на стандартное сообщение функция должна возвратить значение, определяемое типом обрабатываемого сообщения. В том случае, если возвращаемое значение имеет тип MMRESULT, при успешном выполнении операции должно возвращаться значение MMSYSERR_NOERROR (нуль), а при неудаче - код ошибки. При возникновении типовых ошибок (файл не найден, недостаточно памяти и т.п.) должны возвращаться стандартные коды ошибок. Введение дополнительных кодов ошибок для стандартных сообщений допускается лишь в тех случаях, когда среди стандартных кодов нет подходящего.

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

Константы для кодов стандартных сообщений, передаваемых подсистемой MMIO, имеют префиксы MMIOM_:

OPEN Открытие файла. В параметре Param1 передается указатель строки имени открываемого файла. Поле dwFlags информационной структуры содержит коды режимов открытия файла и дополнительные флаги. Поле lDiskOffset в этот момент имеет нулевое значение. Возвращается значение типа MMRESULT.
CLOSE Закрытие файла. В параметре Param1 передается значение параметра Flags функции mmioClose. Возвращается значение типа MMRESULT.
RENAME Переименование файла. В параметре Param1 передается указатель строки имени исходного файла, в параметре Param2 – указатель строки нового имени. Возвращается значение типа MMRESULT.
READ Чтение из файла. В параметре Param1 передается указатель буфера, в который считываются данные, в параметре Param2 – размер считываемого фрагмента. Возвращается реальное количество считанных байтов, либо –1 в случае ошибки.
WRITE Запись в файл. В параметре Param1 передается указатель буфера, содержащего записываемые данные, в параметре Param2 – размер записываемого фрагмента. Возвращается реальное количество записанных байтов, либо –1 в случае ошибки.
WRITEFLUSH Аналогично WRITE, требует фактической записи на диск внутренних буферов файла после выполнения операции.
SEEK Смена текущей позиции файла. В параметре Param1 передается величина смещения в байтах, в параметре Param2 – код исходной точки для позиционирования, как в функции mmioSeek. Возвращается новая позиция относительно начала файла, либо –1 в случае ошибки.

Недостатки интерфейса MMIO

Механизм глобальных процедур обмена работает нормально только в Win16 благодаря общей системе адресации памяти. В Win32, несмотря на отсутствие формального запрета на использование глобальных процедур, их разделение между процессами невозможно, так как вызов процедуры обмена выполняется обычным образом, по адресу, без переключения процессов или задач. При попытке обращения к процедуре обмена, установленной другим процессом, MMIO пытается выполнить вызов локальной функции с адресом, по которому процедура находится в установившем ее процессе. В вызывающем же процессе по этому адресу может находиться все, что угодно – в результате возникает неустранимая ошибка.

Разделение процедур обмена в Windows 95/98 принципиально возможно путем помещения процедуры обмена в область общих адресов (начиная с 0x80000000), где она будет "видна" остальным процессам. Однако ее вызов в любом случае будет выполняться локально, и вызываемая процедура будет работать от имени вызывающего процесса и пользоваться его ресурсами.

Пример программы, использующей MMIO

В качестве иллюстрации схемы обработки звуковых файлов приведена программа ACMPlay. Программа переделана из опубликованной в прошлом номере программы RTComp, предназначенной для сжатия и записи звука в файл в реальном времени. Фактически, ACMPlay является "обратно" к RTComp – она открывает существующий WAV–файл, находит в нем требуемые разделы, после чего восстанавливает данные в формат PCM и воспроизводит на заданном Wave–устройстве.


Для воспроизведения файла необходимо выбрать звуковое устройство, после чего выбрать звуковой файл кнопкой "Файл". Фильтр типов файлов в диалоге выбора пропускает только файлы с расширением WAV. После успешного выбора воспроизведение запускается автоматически. В процессе воспроизведения кнопка переименовывается в "Стоп" для досрочной остановки воспроизведения.

Для поиска разделов RIFF/WAVE, fmt, data используются функции mmioDescend. Файл обрабатывается в режиме явной буферизации, причем буфер файла используется как исходный буфер при преобразовании порций данных в потоке ACM. Размер буфера округляется до 4096 байтов, чтобы он мог вместить полный блок в форматах MS ADPCM и IMA ADPCM.

После открытия устройство вывода устанавливается в режим паузы, затем в его очереди накапливаются пустые звуковые буферы, после чего выполняется сброс функцией waveOutReset, в результате чего все буферы сразу же передаются задаче уведомления. Такой подход позволил сосредоточить всю работу с буферами звукового устройства только внутри его задачи уведомления, которая считывает исходные данные из файла, восстанавливает их в формат PCM посредством подсистемы ACM, заполняет восстановленными данными возвращенные звуковым драйвером буферы, и передает их обратно для проигрывания.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 0    Оценка 136        Оценить