[Назад] [Далее]

5.10.8. Звуковые платы

Звуковые платы, совместимые с Sound Blaster, поддерживают стандартный интерфейс для общения с компьютером через порты 220h – 22Fh для воспроизведения оцифрованного звука. Кроме того, большинство звуковых плат поддерживает порты 0388h – 038Fh для совместимости с Adlib — одной из первых звуковых плат, в которой не было возможности вывода оцифрованного звука, а присутствовал только частотный синтез. Возможности частотного синтеза значительно расширились со времени появления Adlib, но речь не о ней, потому что средства MIDI-интерфейса позволяют получать более качественную музыку, a Sound Blaster — звук.

Базовый порт для звуковой платы может не быть равен 220h, в этом случае все следующие адреса надо изменить:

Программирование современных звуковых плат — весьма сложное занятие, поэтому в качестве примера рассмотрим одну часто применяемую операцию — воспроизведение оцифрованного звука. С этой целью потребуется программировать только DSP, для которого и команды, и данные (которые фактически являются аргументами команд) посылают в один и тот же порт 22Ch. Кроме того, стандартный DSP вызывает одно аппаратное прерывание (чаще всего IRQ5) и использует один канал DMA (чаще всего канал 1). DMA — конструкция, позволяющая внешним устройствам работать с памятью компьютера без вмешательства центрального процессора, рассмотрена в следующей главе, а здесь остановимся на основных командах DSP и попробуем воспроизвести звук без использования DMA и IRQ.

Команды DSP

E1h

Определить версию DSP. После посылки этой команды в 22Ch надо выполнить чтение двух байт из 22Ah. Первый байт — старший номер версии DSP:

1 — Sound Blaster

2 — Sound Blaster 2.0

3 — Sound Blaster Pro

4 — Sound Blaster 16


10h, N

8-битный непосредственный вывод байта N (число без знака) в DSP Команда впервые появилась на Sound Blaster.


14h, LL, LH

8-битный DMA-вывод блока байт без знака длиной L + 1 (LL — младший байт длины, LH — старший). По окончании вывода блока вызывается аппаратное прерывание. Команда впервые появилась на Sound Blaster.


1Ch

8-битный DMA-вывод с автоинициализацией. Размер блоков задается командой 48h, по окончании вывода каждого блока вызывается аппаратное прерывание. Чтобы остановить DMA-вывод с автоинициализацией, надо воспользоваться командами DAh или 14h. Команда впервые появилась на Sound Blaster 2.0.


90h

8-битный ускоренный DMA-вывод с автоинициализацией. По окончании каждого блока вызывается прерывание, но DSP не будет откликаться ни на какие другие команды. Чтобы выйти из этого режима, надо выполнить сброс и инициализацию DSP заново. Команда впервые появилась на Sound Blaster Pro.


D1h

Включить звук.

Команда впервые появилась на Sound Blaster и не действует, начиная с Sound Blaster 16 (за управление линиями ввода/вывода отвечает микшер).


D3h

Выключить звук.

Команда впервые появилась на Sound Blaster и не действует, начиная с Sound Blaster 16 (за управление линиями ввода/вывода отвечает микшер).


40h, ТС

Установить скорость передачи звука. ТС — старший байт величины 256 (1 000 000/rate), где rate — частота дискретизации (умножить на 2, если используется стереозвук).

Команда впервые появилась на Sound Blaster.


41h, RH, RL

Установить частоту дискретизации (RH — старший байт, RL — младший). Частоту не надо умножать на два в случае стереозвука (допустимые значения — от 5 000 до 45 000 Hz). Частота автоматически округляется до соответствующего целого ТС, как в команде 40h. Команда впервые появилась на Sound Blaster 16.


48h, SL, SH

Установить размер блока для DMA-вывода (SL — младший байт, SH — старший. SH:SL — число байт в блоке минус 1). По окончании каждого блока будет вызываться прерывание.

Команда впервые появилась на Sound Blaster 2.0.


D0h

Приостановить 8-битный DMA. Команда впервые появилась на Sound Blaster.


D4h

Продолжить 8-битный DMA после D0h. Команда впервые появилась на Sound Blaster.


DAh

Закончить 8-битный DMA (после окончания пересылки текущего блока). Команда впервые появилась на Sound Blaster 2.0.


B?h, BM, LL, LH

16-битный DMA-режим. Младшие четыре бита команды B?h выбирают тип режима:

бит 3: 1/0 — ввод/вывод

бит 2: 1/0 — обычный/с автоинициализацией

бит 1: 1/0 — FIFO включено/выключено

бит 0: 0

Команда ВМ выбирает вариант режима:

бит 5: 1/0 — стерео/моно

бит 4: 1/0 — данные рассматриваются как числа со знаком/без знака

LL — младший байт, LH — старший байт числа слов в блоке минус один.

Команда впервые появилась на Sound Blaster 16.


C?h, BM, LL, LH

8-битный DMA-режим.

Эти команды в точности совпадают с B?h, только они опиcывают 8-битную передачу данных и LH:LL — число байт, а не слов в блоке.

Команда впервые появилась на Sound Blaster 16.


D5h

Приостановить 16-битный DMA. Команда впервые появилась на Sound Blaster 16.


D6h

Продолжить 16-битный DMA после D5h. Команда впервые появилась на Sound Blaster 16


D9h

Закончить 16-битный DMA (по окончании пересылки текущего блока). Команда впервые появилась на Sound Blaster 16.


Итак, для вывода звука через звуковую плату может использоваться один из трех режимов. Прямой вывод (команда 10h), когда программа должна сама с нужной частотой посылать отдельные байты из оцифрованного звука в DSP; простой DMA-режим, когда выводится блок данных, после чего вызывается прерывание; и DMA с автоинициализацией, когда данные выводятся непрерывно и после вывода каждого блока вызывается прерывание. Именно в этом порядке увеличивается качество воспроизводимого звука. Так как мы пока не умеем работать с DMA, рассмотрим первый способ.

Чтобы вывести оцифрованные данные с нужной частотой в DSP, придется перепрограммировать канал 0 системного таймера на требуемую частоту и установить собственный обработчик прерывания 08h. При этом будет нарушена работа системных часов, хотя можно не выключать совсем старый обработчик, а передавать ему управление примерно 18,2 раза в секунду, то есть, в частности, при каждом 604-м вызове нашего обработчика на частоте 11 025 Hz. Покажем, как это сделать на примере простой программы, которая именно таким способом воспроизведет файл c:\windows\media\tada.wav (или c:\windows\tada.wav, если вы измените соответствующую директиву EQU в начале программы).

; wavdir.asm
; воспроизводит файл c:\windows\media\tada.wav, не используя DMA,
; нормально работает только под DOS в реальном режиме
; (то есть не в окне DOS (Windows) и не под EMM386, QEMM или другими
; подобными программами)

FILESPEC equ "c:\windows\media\tada.wav"   ; имя файла tada.wav с
                        ; полным путем (замените на c:\windows\tada.wav для
                        ; старых версий Windows)
SBPORT equ 220h         ; базовый порт звуковой платы (замените, если у вас он
                        ; отличается)
        .model     tiny
        .code
        .186                            ; для pusha/popa
        org        100h                 ; СОМ-программа
start:
        call       dsp_reset            ; сброс и инициализация DSP
        jc         no_blaster
        mov        bl,0D1h              ; команда DSP D1h
        call       dsp_write            ; включить звук
        call       open_file            ; открыть и прочитать tada.wav
        call       hook_int8            ; перехватить прерывание таймера
        mov        bx,5                 ; делитель таймера для частоты 22 050 Hz
                                        ; (на самом деле соответствует 23 867 Hz)
        call       reprogram_pit        ; перепрограммировать таймер

main_loop:                              ; основной цикл
        cmp        byte ptr finished_flag,0
        je         main_loop            ; выполняется, пока finished_flag
                                        ; равен нулю
        mov        bx,0FFFFh            ; делитель таймера для частоты 18,2 Hz
        call       reprogram_pit        ; перепрограммировать таймер
        call       restore_int8         ; восстановить IRQ0
no_blaster:
        ret

buffer_addr        dw    offset buffer  ; адрес текущего играемого байта
old_int08h         dd    ?              ; старый обработчик INT 08h (IRQ0)
finished_flag      db    0              ; флаг завершения
filename           db    FILESPEC,0     ; имя файла tada.wav с полным путем

; обработчик INT 08h (IRQ0)
; посылает байты из буфера в звуковую плату
int08h_handler     proc    far
        pusha                           ; сохранить регистры,
        cmp        byte ptr cs:finished_flag,1 ; если флаг уже 1,
        je         exit_handler                ; ничего не делать,
        mov        di,word ptr cs:buffer_addr  ; иначе: DI = адрес текущего байта
        mov        bl,10h               ; команда DSP 10h
        call       dsp_write            ; непосредственный 8-битный вывод
        mov        bl,byte ptr cs:[di]  ; BL = байт данных для вывода
        call       dsp_write
        inc        di                   ; DI = адрес следующего байта
        cmp        di,offset buffer+27459      ; 27 459 - длина звука в tada.wav,
        jb         not_finished                ; если весь буфер пройден,
        mov        byte ptr cs:finished_flag,1 ; установить флаг в 1,
not_finished:                                  ; иначе:
        mov        word ptr cs:buffer_addr,di  ; сохранить текущий адрес
exit_handler:
        mov        al,20h               ; завершить обработчик аппаратного
                                        ; прерывания,
        out        20h,al               ; послав неспецифичный EOI
                                        ; (см. гл. 5.10.10)
        рора                            ; восстановить регистры
        iret
int08h_handler     endp

; процедура dsp_reset
; сброс и инициализация DSP
dsp_reset          proc    near
        mov        dx,SBPORT+6          ; порт 226h - регистр сброса DSP
        mov        al,1                 ; запись единицы в него начинает
        out        dx,al                ; инициализацию
        mov        cx,40                ; небольшая пауза
dsploop:
        in         al,dx
        loop       dsploop
        mov        al,0                 ; запись нуля завершает инициализацию
        out        dx,al                ; теперь DSP готов к работе
                                        ; проверить, есть ли DSP вообще
        add        dx,8                 ; порт 22Eh - состояние буфера чтения DSP
        mov        cx,100
check_port:
        in         al,dx                ; прочитать состояние буфера
        and        al,80h               ; проверить бит 7
        jz         port_not_ready       ; если ноль - порт еще не готов,
        sub        dx,4                 ; иначе: порт 22Аh - чтение данных из DSP
        in         al,dx
        add        dx,4                 ; и снова порт 22Eh,
        cmp        al,0AAh              ; если причиталось число AAh -
                                        ; DSP присутствует и действительно
                                        ; готов к работе,
        je         good_reset
port_not_ready:
        loop       check_port           ; если нет - повторить проверку 100 раз
bad_reset:
        stc                             ; и сдаться
        ret                             ; выход с CF = 1
good_reset:
        clc                             ; если инициализация прошла успешно,
        ret                             ; выход с CF = 0
dsp_reset          endp

; процедура dsp_write
; посылает байт из BL в DSP
dsp_write          proc    near
        mov        dx,SBPORT+0Ch        ; порт 22Ch - ввод данных/команд DSP
write_loop:                             ; подождать готовности буфера записи DSP
        in         al,dx                ; прочитать порт 22Ch
        and        al,80h               ; и проверить бит 7,
        jnz        write_loop           ; если он не ноль - подождать еще,
        mov        al,bl                ; иначе:
        out        dx,al                ; послать данные
        ret
dsp_write          endp

; процедура reprogram_pit
; перепрограммирует канал 0 системного таймера на новую частоту
; Ввод: ВХ = делитель частоты
reprogram_pit      proc    near
        cli                         ; запретить прерывания
        mov        al,00110110b     ; канал 0, запись младшего и старшего байт
                                    ; режим работы 3, формат счетчика - двоичный
        out        43h,al           ; послать это в регистр команд первого таймера
        mov        al,bl            ; младший байт делителя -
        out        40h,al           ; в регистр данных канала 0
        mov        al,bh            ; и старший байт -
        out        40h,al           ; туда же
        sti                         ; теперь IRQO вызывается с частотой
                                    ; 1 193 180/ВХ Hz
        ret
reprogram_pit      endp

; процедура hook_int8
; перехватывает прерывание INT 08h (IRQ0)
hook_int8          proc    near
        mov        ax,3508h                 ; AH = 35h, AL = номер прерывания
        int        21h                      ; получить адрес старого обработчика
        mov        word ptr old_int08h,bx   ; сохранить его в old_int08h
        mov        word ptr old_int08h+2,es
        mov        ax,2508h                 ; AH = 25h, AL = номер прерывания
        mov        dx,offset int08h_handler ; DS:DX - адрес обработчика
        int        21h                      ; установить обработчик
        ret
hook_int8          endp

; процедура restore_int8
; восстанавливает прерывание INT 08h (IRQ0)
restore_int8       proc    near
        mov        ax,2508h                 ; AH = 25h, AL = номер прерывания
        lds        dx,dword ptr old_int08h  ; DS:DX - адрес обработчика
        int        21h                      ; установить старый обработчик
        ret
restore_int8       endp

; процедура open_file
; открывает файл filename и копирует звуковые данные из него,
; считая его файлом tada.wav, в буфер buffer
open_file          proc    near
        mov        ax,3D00h             ; AH = 3Dh, AL = 00
        mov        dx,offset filename   ; DS:DX - ASCIZ-имя файла с путем
        int        21h                  ; открыть файл для чтения,
        jc         error_exit           ; если не удалось открыть файл - выйти
        mov        bx,ax                ; идентификатор файла в ВХ
        mov        ax,4200h             ; АН = 42h, AL = 0
        mov        сх,0                 ; CX:DX - новое значение указателя
        mov        dx,38h               ; по этому адресу начинаются
                                        ; данные в tada.wav
        int        21h                  ; переместить файловый указатель
        mov        ah,3Fh               ; АН = 3Fh
        mov        cx,27459             ; это - длина звуковых данных
                                        ; в файле tada.wav
        mov        dx,offset buffer     ; DS:DX - адрес буфера
        int        21h                  ; чтение файла
        ret
error_exit:                             ; если не удалось открыть файл
        mov        ah,9                 ; АН = 09h
        mov        dx,offset notopenmsg ; DS:DX = сообщение об ошибке
        int        21h                  ; открыть файл для чтения
        int        20h                  ; конец программы
notopenmsg         db    "Ошибка при открытии файла",0Dh,0Ah,'$'
open_file          endp

buffer:                         ; здесь начинается буфер длиной 27 459 байт
        end        start

Если вы скомпилировали программу latency.asm из главы 5.10.5 и попробовали запустить ее в разных условиях, то могли заметить, что под Windows 95, а также под EMM386 и в некоторых других ситуациях пауза между реальным срабатыванием прерывания таймера и запуском обработчика может оказаться весьма значительной и варьироваться с течением времени, так что качество звука, выводимого нашей программой wavdir.asm, окажется под EMM386 очень плохим, а в DOS-задаче под Windows 95 вообще получится протяжный хрип. Чтобы этого избежать, а также чтобы указывать точную скорость оцифровки звука и выводить 16-битный звук, нам надо обратиться к программированию контроллера DMA (пример программы, выводящей звук при помощи DMA, см. в конце следующей главы).


п»ї
"target=_blank><\/a>") //-->