Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Программирование Sound Blaster в защищенном режиме процессора

Оформил: DeeCo

Немного истории

Когда разработчики компании Intel создавали первый 16-разрядный процессор I8086, они, скорее всего, рассматривали его как переходную модель от 8- к 16-разрядным процессорам и не могли даже предположить, что он станет образцом, совместимость с которым будут вынуждены поддерживать как сама компания Intel в дальнейших разработках, так и большинство ее конкурентов. В отличие от своего 8-разрядного предшественника I8080, у которого была 16-разрядная шина адреса, новый процессор имел два режима адресации: 16-разрядный - для обеспечения совместимости с программами, написанными для I8080, и 32-разрядный. Однако последний был не линейным, а сегментированным, в результате чего полный физический адрес состоял только из 20 разрядов.

Переход от 64-Кбайт к 1-Мбайт адресному пространству казался тогда огромным шагом вперед. Возможно даже, этот процессор несколько опередил свое время, по крайней мере вскоре Intel вынуждена была сделать маленький шаг назад и выпустить модификацию этого процессора, продолжающую оставаться 16-разрядной внутри, но имеющую 8-разрядную внешнюю шину. Новинка, получившая название I8088, была выбрана фирмой IBM, до того момента выпускавшей только большие ЭВМ, для ее первого ПК. Немаловажную роль в выборе именно этого процессора сыграло наличие 8-разрядной шины, позволявшей использовать в конструкции компьютера дешевые 8-разрядные устройства, разработанные для I8080. Применение контроллера прямого доступа к памяти (DMAC - Direct Memory Access Controller), относящегося к этой категории устройств, сразу ограничило максимальный размер передаваемого им единого блока информации 64 Кбайт. Кроме того, фирма IBM решила сэкономить на специальном дешифраторе адреса для DMAC и, отказавшись от применения сегментированной модели памяти для этого устройства, подключила его адресные выходы к младшим разрядам 20-разрядной адресной шины напрямую, а для задания старшей части адреса использовала 4-разрядный внешний порт, в результате чего оказалась невозможной пересылка данных через границу сегмента размером 64Кбайт. Компромиссные решения, принятые Intel и IBM, неожиданно стали промышленным стандартом, а со всеми недостатками когда-то выбранной архитектуры сегодня приходится бороться программистам.

Использование DMA для записи и воспроизведения звука

Первоначально при разработке DMAC предполагалось, что одним из основных его назначений будет более быстрая, чем у процессора, скорость пересылки данных из одной области памяти в другую. В случае с процессором I8080 так и было, но у семейства I8086/88 появились новые команды работы со строками, в результате чего такое использование DMAC оказалось нецелесообразным. С этого времени контроллер прямого доступа к памяти обычно применяется только для передачи данных между памятью и некоторыми периферийными устройствами. К стандартным устройствам такого типа можно отнести контроллер гибких дисков, а к нестандартным (не поддерживаемым на уровне BIOS) - звуковые платы, сканеры, стримеры etc. Причем если программы для сканеров и стримеров пишут в основном разработчики этих устройств, то программировать звуковые платы сегодня вынуждены многие программисты.

Стандартом de facto среди звуковых плат для ПК стала наиболее распространенная - Sound Blaster фирмы Creative Labs. На ее примере мы и рассмотрим способы программирования звуковых плат. Считывать звуковые данные с платы или воспроизводить их можно и без применения DMAC, работая только с eе портами, но при этом процессор будет полностью загружен и не сможет выполнять другую работу. Кроме того, объем записываемых или воспроизводимых данных ограничен объемом доступной процессору оперативной памяти, поэтому такой способ не получил широкого распространения. Обычно компьютер одновременно с воспроизведением звука должен выполнять и другую работу, зачастую более ресурсоемкую. Тогда без DMAC не обойтись.

В реальном режиме работы процессора для вывода звука через контроллер прямого доступа к памяти на звуковую плату необходимо выделить память для звуковых данных, запрограммировать эту плату и DMAC, а также переопределить прерывание. Процессор после этого может заниматься другой работой до наступления аппаратного прерывания, генерируемого звуковой платой по окончании звуковой последовательности. Затем процессор должен снабдить пару DMAC-звуковая плата очередной порцией данных. При этом программисту необходимо позаботиться о том, чтобы выделенный буфер, содержащий звуковую последовательность, не пересекал границу сегмента размером 64 Кбайт, а также требуется преобразовать 32-разрядный сегментированный адрес в 20-разрядный несегментированный.

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

Формат WAV-файла

В нашем журнале (Д. В. Солдатенков. Программируем Sound Blaster. "Мир ПК", #9/94) уже были опубликованы сведения об основах программирования Sound Blaster и формате VOC-файла (стандарт Creative), однако сегодня более распространен формат WAV (стандарт Microsoft Windows). Каждый из форматов допускает различные варианты представления заголовка. Упрощенная структура двух наиболее часто встречающихся вариантов для формата WAV приведена в таблице.

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

Смещение от начала файла

Длина

Описание

0h

4h

Идентификатор формата ('RIFF')

4h

4h

Длина блока данных (длина файла - 8h)

8h

4h

Идентификатор блока звуковых данных ('WAVE')

0ch

4h

Идентификатор подблока заголовка ('fmt" - с пробелом в конце)

10h

4h

000ch/0010h - длина подблока заголовка

14h

2h

01h - тип формата представления данных

16h

2h

Число каналов (1 - моно, 2 - стерео)

18h

2h/4h

Частота дискретизации, Гц

1ah/1ch

2h/4h

Скорость передачи данных, байт/с (произведение числа каналов, частоты дискретизации и разрядности в байтах)

1ch/20h

2h

Числов байт для представления одного отсчета (1 - 8 бит моно, 1 - 16 бит стерео)

1eh/22h

2h

Разрядность, бит (8, 16)

20h/24h

4h

Идентификатор подблока данных ('data')

24h/28h

4h

Длина звуковых данных

28h/2ch

 

Звуковые данные

[an error occurred while processing this directive] Пример программы

Простейшая программа, демонстрирующая работу со звуковой платой в защищенном режиме процессора, приведена в листинге 1. В примере решено было ограничиться лишь воспроизведением 8-битного звука, что обусловлено двумя причинами. Первая из них заключается в том, что все необходимые регистры и команды DSP (Digital Signal Processor - цифровой сигнальный процессор) звуковой платы были описаны ранее в статье Д.В. Солдатенкова, и это позволяет сократить объем журнальной публикации. Вторая причина связана с тем, что подавляющее большинство выпускаемых сегодня звуковых плат являются 16-разрядными и совместимы с Sound Blaster лишь на уровне 8-битного звука, т.е. подавляющее большинство звуковых плат, выпускаемых не Creative Labs, не совместимы с Sound Blaster 16, и соответственно 16-битный звук в них реализуется по-другому. Следовательно, стандарта, даже de facto, для воспроизведения 16-битного звука не существует.

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

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

Процедуры выделения и освобождения нижней памяти называются GetSBMem и FreeSBMem соответственно. Для облегчения переноса программы на другой компилятор или в другой язык программирования в листинге 2 приведены ассемблерные варианты этих процедур.

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

Листинг 1. Воспроизведение звука из WAV-файла через звуковую плату Sound Blaster

program playwavp; {воспроизведение 8-битного звука}
{в защищенном режиме процессора}
{$D+,L+,I+,R+,S+,V+}
uses dos, winapi;
const
  BlasterPort: word = $220; {номер порта Sound Blaster}
  BlasterIRQ: word = 5; {номер прерывания Sound Blaster}
  BlasterDMAl: word = 1; {номер канала DMA Sound Blaster}
  maxlen = 35000; {длина звукового буфера}
  {   Seg0040 = $40;}
  SouBlas: Boolean = False; {было ли прерывание от SB }

function clock: longint; {функция определения времени}
begin
  clock := MemL[Seg0040: $6C];
end;

var
  ExitSave: Pointer; {адрес старой программы выхода в DOS}
  SBold: pointer; {адрес обработчика прерывания Sound Blaster'a}
  SBIRQ: byte; { номер прерывания Sound Blaster'a }

{$F+}

procedure SBint;
interrupt; { обработчик прерывания SB }
begin
  SouBlas := True;
  Port[$20] := $20;
end;

procedure MyExit; {дополнительная процедура при выходе в DOS}
begin
  ExitProc := ExitSave;
  SetIntVec(SBIRQ + 8, SBold);
end;
{$F-}

procedure IntInit(sbi: byte); {установка нового вектора} {прерывания и т.д.}
begin { sbi - номер аппаратного прерывания SB }
  if sbi < 8 then
    SBIRQ := sbi
  else
    SBIRQ := 2;
  GetIntVec(SBIRQ + 8, SBold);
  SetIntVec(SBIRQ + 8, @SBint); {переопределение} {прерывания SB}
  ExitSave := ExitProc; {переопределение} {процедуры выхода}
  ExitProc := @MyExit;
end;

procedure WriteCommand(Comm: byte); { процедура записи} {команды в регистр SB }
begin
  while (Port[BlasterPort + $0C] and $80) <> 0 do
    ;
  Port[BlasterPort + $0C] := Comm;
end;

procedure NotSupport; { вывод на экран сообщения }
begin
  writeln('Format not supported');
  halt;
end;

var
  longi: longint; {рабочая ячейка}
  arr1: pointer; {для выравнивающего массива}
  pusto: word; {длина выравнивающего массива}
  sndp: pointer; {для массива звуковых отсчетов}
  DMAPage: byte; {номер 64K сегмента для записи} {в регистр DMA)
  DMAOfs :word;     {смещение звуковой последовательности}{в нижней памяти}

procedure GetSBMem; {выделение буфера } {нижней памяти для SB/DMA}
begin
  longi := GlobalDosAlloc(16);
  arr1 := ptr(longi and $0FFFF, 0); {селектор нижней памяти}
  longi := $FFFF - (((longi shr 16) shl 4) and $FFFF) + 1;
  pusto := longi; {столько осталось до начала} {следующего 64k сегмента}
  GlobalDosFree(seg(arr1^));
  longi := GlobalDosAlloc(pusto);
  arr1 := ptr(longi and $0FFFF, 0); {селектор нижней} {памяти (выравн.масс.)}
  longi := GlobalDosAlloc(maxlen);
  sndp := ptr(longi and $0FFFF, 0); {селектор нижней} {памяти (для звука)}
  DMAPage := longi shr 28;
  DMAofs := 0;
end;

procedure FreeSBMem; {возвращение нижней памяти в систему}
begin
  GlobalDosFree(seg(sndp^));
  GlobalDosFree(seg(arr1^));
end;

const
  dmap: array[0..3] of byte = ($87, $83, $81, $82); {номера} {регистров DMA}
var
  i: word; {рабочая ячейка}
  t, friq: word; {временной параметр/частота дискретизации}
  lenfil: word; {длина считываемой части звуковых данных}
  snd: file; {звуковой файл}
  riff: array[0..15] of char; {массив для чтения} {неиспольз. частей заголовка}

begin {the main program}
  if paramcount <> 1 then
  begin
    writeln;
    writeln("Usage: playwave filename.wav");
    writeln;
    halt;
  end;
  IntInit(BlasterIRQ); { переустанавливаем прерывания }
  Assign(snd, paramstr(1));
  Reset(snd, 1);
  BlockRead(snd, riff, 16); { $00}
  BlockRead(snd, longi, 4); { $10 длина заголовка}
  BlockRead(snd, i, 2); { $14}
  BlockRead(snd, i, 2); { $16 число каналов}
  if i <> 1 then
    notsupport;
  BlockRead(snd, friq, 2); { $18 частота дискретизации}
  Seek(snd, longi + $12); { $1A пропускаем заголовок} {до предпосл.слова}
  BlockRead(snd, i, 2); {     разрядность}
  if i <> 8 then
    notsupport;
  GetSBMem; {запрашиваем нижнюю память для звука}
  BlockRead(snd, longi, 4); {  'data'}
  BlockRead(snd, longi, 4); { длина данных}
  if longi > maxlen then
    lenfil := maxlen
  else
    lenfil := longi;
  BlockRead(snd, sndp^, lenfil); {звуковые данные}
  close(snd);
  WriteCommand($D3); {включаем звук}
  t := 256 - 1000000 div friq;
  WriteCommand($40); {задаем частоту дискретизации}
  WriteCommand(t);
  Port[$21] := Port[$21] and not (1 shl BlasterIRQ); {разрешаем прерывание}
  Port[$A] := BlasterDMAl + 4; {маскируем DMA }
  Port[$C] := 0; {сбрасываем триггер}
  Port[$B] := BlasterDMAl + $48; {задаем режим передачи }
  Port[$2] := lo(DMAOfs); {задаем адрес буфера}
  Port[$2] := hi(DMAOfs);
  Port[dmap[BlasterDMAl]] := DMAPage; {задаем 64k страницу}
  Port[BlasterDMAl * 2 + 1] := lo(lenfil - 1); {длина звуковой}
    {последовательности}
  Port[BlasterDMAl * 2 + 1] := hi(lenfil - 1);
  Port[$A] := BlasterDMAl; {размаскируем DMA }
  WriteCommand($14); {начинаем воспроизведение звука}
  WriteCommand(lo(lenfil - 1));
  WriteCommand(hi(lenfil - 1));
  longi := clock; {на всякий случай ограничим по времени}
  while (SouBlas = false) and (longi + 50 > clock) do
    ;
  if not SouBlas then
    Port[$20] := $20; {сбрасываем DMA }
  i := Port[BlasterPort + $0E]; {сбрасываем Sound Blaster}
  WriteCommand($D1); {выключаем звук}
  Port[$21] := Port[$21] or (1 shl BlasterIRQ); {запрещаем прерывание}
  FreeSBMem; {возвращаем память в систему}
end.
Листинг 2. Процедуры выделения и освобождения нижней памяти для звукового буфера
var
  m1, m2, m3: word; { флаги процедур выделения памяти: }
    { 4 - успешно, 5 - ошибка, 2 - не выделялась }

procedure GetSBMem; {выделение буфера нижней памяти} {для SB/DMA}
var
  j: word;
begin
  j := (longint(maxlen) + 15) div 16; {длина блока} {в параграфах}
  m1 := 2;
  m2 := 2;
  m3 := 2;
  asm
      mov ax,$0100
      mov bx,j
      int $31      {запрашиваем память}
      rcl m1,1     {запоминаем CF}


      mov bx,$1000
      mov cx,ax
      and cx,$fff
      sub bx,bc      {вычисляем размер до конца сегмента}
      cmp j,bx
      jb @l1       {если достаточно места, уходим}

      push bx
      mov ax,$0101
      int $31      {возвращаем память}

      pop bx
      mov ax,$0100
      int $31      {забираем память до конца 64k-сегмента}
      rcl m2,1     {запоминаем CF}
      mov word ptr [arr1+2],dx  {сохраняем селектор}

      mov ax,$0100
      mov bx,j
      int $31    {запрашиваем память для звукового буфера}
      rcl m3,1   {запоминаем CF}
     mov bx,ax
      mov ax,0
      adc ax,ax
      mov m3,ax   {запоминаем CF}
 @l1:
      mov word ptr [sndp+2],dx    {сохраняем селектор}
      xor dx,dx
      mov word ptr [sndp],dx      {сохраняем смещение}
      mov ax,bx
      shl ax,4
      mov DMAOfs,ax
      shr bx,12
      mov DMAPage,bl
  end;
  if ((m1 and 1) or (m2 and 1) or (m3 and 1)) <> 0 then
    halt;
end;

procedure FreeSBMem; {возвращение нижней памяти в систему}
begin
  asm
      mov ax,$0101
      mov dx,word ptr[sndp+2]
      int $31
  end;
  if m2 = 4 then
    asm
      mov ax,$0101
      mov dx,word ptr[arr1+2]
      int $31
    end;
end;
Проект Delphi World © Выпуск 2002 - 2024
Автор проекта: USU Software
Вы можете выкупить этот проект.