博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
8086汇编语言 调用声卡播放wav文件(sound blaster)
阅读量:5966 次
发布时间:2019-06-19

本文共 10590 字,大约阅读时间需要 35 分钟。

开更

大概最后做了一个能播放无损音乐(无压缩、不需解码)的播放器

原理是基于dosbox的模拟声卡,通过硬件之间的相互通讯做到的

关于详细内容接下来再讲。

 

一、从dosbox入手

  我们知道cpu可以直接输出到蜂鸣器的端口,然后让蜂鸣器发声。但是蜂鸣器的局限性很大,大多数蜂鸣器只支持两种电压,也就只能发出非常单一的声音。所以,从播放音乐角度来讲,调用蜂鸣器是比较简单但局限性很大的。所以这里不会采用调用蜂鸣器的做法。

  要用8086发出复杂的声音,最简单的想法就是调用声卡,但在dos环境下,想调用windows的声卡是不可能的,一是windows的声卡驱动不兼容,第二是也没有提供可用的输出方式(驱动封装性好)。于是我就查阅了dosbox的sound方面的资料,发现了dosbox是支持模拟声卡的,最简单的就是PC speaker(蜂鸣器),还有disney声卡、midi声卡等等,不过在96年最普及的一款声卡是sound blaster 16。它同样也可以被dosbox模拟,查阅dosbox的document,我们会找到dosbox的模拟端口位置

  有了这个,我们就只需要查阅sound blaster的document,就可以知道如何使用sound blaster了

 

二、Sound Blaster 简要说明

  sound blaster的document网址:http://homepages.cae.wisc.edu/~brodskye/sb16doc/sb16doc.html

  非常推荐先读一遍这篇document

  这篇文章介绍了sound blaster每个端口的作用和位置,以及如何配置sound blaster。

  1、安装sound blaster中断

  2、编写DMA,用于音频流载入

  3、设定一个采样的速率

  4、编写DSP的读写I/O操作

  5、向DSP写入转换模式操作(转换到sound blaster模式)

  6、向DSP写入音频流的大小和播放设定

  那么整体的一个流程其实是这样

  向DSP写入转换模式操作(转换到soundblaster)->利用DSP向声卡发出播放命令->声卡发现DMA中没有数据,引发中断->中断更新DMA中的数据->声卡获取数据开始播放

  在这个过程中,一旦声卡没有数据了,就会引发中断获取新的数据,实现播放音乐的功能。

  下面分步说明

 

三、替换ISR(就是安装新的中断)

  这里说法可能有些跳跃,ISR实际上是PIC的一部分

  关于PIC的一些知识,可以看链接 http://wiki.osdev.org/8259_PIC

  实际上它有15条IRQ lines,对应的就是15个中断,我们需要替换其中的一个中断,作为声卡的中断

  那么这样的话,自己要编写新的中断,内容要包括(文档里有写)

  我的实现里没有double-buffering操作,所以就不需要复制了(代价就是块的size大的时候,音乐会有明显的跳跃间断)

  我们想要播放16位单声道音乐,所以要向2xF口读写信息

  这里我配置里是放到IRQ0~7里的,所以向20h写20h即可。

  代码如下

_DT segment para public 'DATA'    sbISR dw offset sb16ISR          dw _CODE    blockmask dw 0_DT ends_CODE segment    assume cs:_CODE, ds:_DT, es:_DT    swappointers:        ; swap si and di pointer        push bx        mov bx, word ptr [si]        xchg word ptr es:[di], bx        mov word ptr [si], bx        mov bx, word ptr [si+2]        xchg word ptr es:[di+2], bx        mov word ptr [si+2], bx        pop bx        ret    installISR:        push es        push si        push di        push dx        push ax        cli ; clear int        mov si, offset sbISR        sub di, di        mov es, di        mov di, ISR_VECTOR        call swappointers        sti ; set int        ; set mask        mov dx, PIC_DATA        in al, dx        xor al, PIC_MASK        out dx, al        pop ax        pop dx        pop di        pop si        pop es        ret    sb16ISR:        push ax        push dx        push ds        push es        ;Acknowledge the interrupt with the SB by reading from port 2xF for 16-bit sound.        mov dx, REG_DSP_ACK_16        in al, dx        ;Acknowledge the end of interrupt with the PIC by writing 20h to port 20h.        mov al, 20h        out 20h, al        ;maintain buffer        mov ax, _DT        mov ds, ax        mov bx, word ptr [BlockMask]        call maintainbuffer        not bx        mov word ptr [BlockMask], bx        pop es        pop ds        pop dx        pop ax        iret_CODE ends

 

四、编写DMA,载入音频流

  DMA是什么呢?引用百科里的解释,DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于CPU 的大量中断负载。

  为什么要编写DMA,实际上是因为sound blaster 是ISA(外部硬件),CPU是不能直接向其端口输入值的,需要DMA进行中介,也就是说

  CPU向DMA输送音频流,sound blaster从DMA获取音频流来播放,设置DMA,一是设置它的模式,二是设置它的端口对应,通知sound blaster这一段内存地址存放DMA的数据信息

  文档说明如下

  

  那么代码如下

.286_CODE segment    assume cs:_CODE    setDMA:        push ax        push bx        push cx        push dx        push si        ;重新设置,禁用通道        mov dx, REG_DMA_MASK        mov al, 4 + SB16_HDMA MOD 4        out dx, al        ;清零操作        mov dx, REG_DMA_CLEAR_FF        out dx, al        ;重新设置模式        mov dx, REG_DMA_MODE        mov al, 58h + SB16_HDMA MOD 4        out dx, al        ;设置DMA地址        ;ES = buffer segment        ;SI = buffer offset        ;DI = block size        mov bx, es        shr bx, 13        mov cx, es        shl cx, 3        shr si, 1        add cx, si        adc bx, 0        ;输出地址        mov dx, REG_DMA_ADDRESS        mov al, cl        out dx, al        mov al, ch        out dx, al        mov dx, REG_DMA_PAGE        mov al, bl        out dx, al        ;设置size        mov ax, di        shr ax, 1        mov dx, REG_DMA_COUNT        out dx, al        mov al, ah        out dx, al        ;启用频道        mov dx, REG_DMA_MASK        mov al, SB16_HDMA mod 4        out dx, al        pop si        pop dx        pop cx        pop bx        pop ax        ret_CODE ends

 

六、编写data,用于音频读入

  这个文档里没有提到,但也是必须要写的。大体工作就是从文件中读入信息,放入到buffer里,然后更新DMA

  没有什么额外的地方,代码如下

_DT segment para public 'DATA'    myfile db 'mymusic.wav', 0    filehandle dw 0    samplerate dw 0_DT ends_CODE segment    assume cs:_CODE, ds:_DT, es:_DT    maintainbuffer:        push es        push di        push bx        push ax        push si        push cx        push dx        mov di, word ptr [buffersegment]        mov es, di        mov di, BLOCK_SIZE        and di, bx        add di, word ptr [bufferoffset]        push ds        ;从文件中读入一段音频流        mov ax, es        mov ds, ax        mov dx, di        mov ah, 3fh        mov bx, word ptr [filehandle]        mov cx, BLOCK_SIZE        int 21h        pop ds        cmp ax, BLOCK_SIZE        je mydateret        ;循环播放        mov ax, 4200h        mov bx, word ptr [filehandle]        sub cx, cx        sub dx, dx        int 21h        mydateret:        pop dx        pop cx        pop si        pop ax        pop bx        pop di        pop es        ret    initbuffer:        push ax        push bx        push cx        push dx        ;打开文件        mov ax, 3d00h        mov dx, offset myfile        int 21h        mov word ptr [filehandle], ax        mov bx, ax        mov ax, 4200h        sub cx, cx        sub dx, dx        int 21h        mov ah, 3fh        mov bx, WORD PTR [fileHandle]        mov cx, 12        mov dx, OFFSET sampleRate        int 21h        mov ax, 4200h        mov bx, WORD PTR [fileHandle]        xor cx, cx        sub dx, dx        int 21h        pop dx        pop cx        pop bx        pop ax        ret_CODE ends

 

七、编写DSP

  DSP就是数字信号处理器,用于数字信号处理,cpu向它发出信号,就可以借助它向声卡做一些简单的指令操作。

  DSP的相关端口信息文档里也有说,对DSP的读写操作如下所述

  

  还有一些对DSP的指令来控制声卡模式,这些文档里都有,我就不再粘贴了

  代码如下

FORMAT_MONO     EQU 00h    FORMAT_STEREO   EQU 20h    FORMAT_SIGNED   EQU 10h    FORMAT_UNSIGNED EQU 00h_CODE segment    assume cs:_CODE    resetDSP:        push ax        push dx        ;设置DSP        mov dx, REG_DSP_RESET        mov al, 01h        out dx, al        sub al, al        out dx, al        mov dx, REG_DSP_READ_BS        ;等待sb16响应        DSPwait1:            in al, dx            test al, 80h            jz DSPwait1        mov dx, REG_DSP_READ        DSPwait2:            in al, dx            cmp al, 0aah            jne DSPwait2        pop dx        pop ax        ret    writeDSP:        push dx        push ax        mov dx, REG_DSP_WRITE_BS        DSPwait3:            in al, dx            test al, 80h            jz DSPwait3        pop ax        mov dx, REG_DSP_WRITE_DATA        out dx, al        pop dx        ret    readDSP:        push dx        mov dx, REG_DSP_READ_BS        dspwait4:            in al, dx            test al, 80h            jz dspwait4        pop ax        mov dx, REG_DSP_READ        in al, dx        pop dx        ret    setsample:        push dx        xchg al, ah        push ax        mov al, DSP_SET_SAMPLING_OUTPUT        call writeDSP        pop ax        call writeDSP        mov al, ah        call writeDSP        pop dx        ret    ;AX = Sampling    ;BL = Mode    ;CX = Size    startplay:        call setsample        mov al, 00b6h        call writeDSP        mov al, bl        call writeDSP        mov al, cl        call writeDSP        mov al, ch        call writeDSP        ret    pauseplay:        push ax        mov al, 00d5H        call WriteDSP        pop ax        ret    continueplay:        push ax        mov al, 00d6H        call WriteDSP        pop ax        ret_CODE ends

 

八、流的设置,常见端口的配置

  这些都是一些常量配置,就不在多叙述了,具体端口位置文档里也有提到

BLOCK_SIZE EQU 1024    BUFFER_SIZE EQU 1024assume ds:_DT, es:_DT_DT segment para public 'DATA'    buffer db BUFFER_SIZE DUP(0)    bufferoffset db offset buffer    buffersegment dw _DT_DT ends
;These are the only configurable constants ;IO Base SB16_BASE   EQU 220h  ;16-bit DMA channel (must be between 5-7) SB16_HDMA   EQU 5 ;IRQ Number SB16_IRQ    EQU 7 ;These a computed values, don't touch them if you don't know what ;you are doing ;REGISTER NAMES REG_DSP_RESET      EQU SB16_BASE + 6 REG_DSP_READ       EQU SB16_BASE + 0ah REG_DSP_WRITE_BS   EQU SB16_BASE + 0ch REG_DSP_WRITE_CMD  EQU SB16_BASE + 0ch REG_DSP_WRITE_DATA EQU SB16_BASE + 0ch REG_DSP_READ_BS    EQU SB16_BASE + 0eh REG_DSP_ACK        EQU SB16_BASE + 0eh REG_DSP_ACK_16     EQU SB16_BASE + 0fh ;DSP COMMANDS DSP_SET_SAMPLING_OUTPUT   EQU 41h DSP_DMA_16_OUTPUT_AUTO    EQU 0b6h DSP_STOP_DMA_16           EQU 0d5h ;DMA REGISTERS REG_DMA_ADDRESS    EQU 0c0h + (SB16_HDMA - 4) * 4 REG_DMA_COUNT      EQU REG_DMA_ADDRESS + 02h  REG_DMA_MASK       EQU 0d4h REG_DMA_MODE       EQU 0d6h REG_DMA_CLEAR_FF   EQU 0d8h  IF SB16_HDMA - 5    REG_DMA_PAGE       EQU 8bh       ELSE    IF SB16_HDMA - 6       REG_DMA_PAGE       EQU 89h    ELSE       REG_DMA_PAGE       EQU 8ah    ENDIF ENDIF ;ISR vector ISR_VECTOR            EQU ((SB16_IRQ SHR 3) * (70h - 08h) + (SB16_IRQ AND 7) + 08h) * 4 PIC_DATA        EQU (SB16_IRQ AND 8) + 21h PIC_MASK               EQU 1 SHL (SB16_IRQ AND 7)

 

九、主程序编写

  在各个部分都完成以后,主程序就比较好写了

  按照步骤顺序来就可以

  不要忘了最后把ISR再交换回来,让dos系统能正常运行

INCLUDE cfg.asmINCLUDE mybuffer.asmINCLUDE mydata.asmINCLUDE myisr.asmINCLUDE mydsp.asmINCLUDE mydma.asm_CODE segment     assume cs:_CODEstart:    mov ax, _DT    mov ds, ax    call installISR    call initbuffer    mov si, word ptr [buffersegment]    mov es, si    mov si, word ptr [bufferoffset]    mov di, BLOCK_SIZE * 2    call setDMA    call resetDSP    mov ax, word ptr [samplerate]    mov bx, FORMAT_MONO or FORMAT_SIGNED    mov cx, BLOCK_SIZE    call startplay    mov ah, 0    int 16h    call pauseplay    call installISR    mov ah, 4ch    int 21h_CODE endsend start

 

十、后记

  最后总算是完成了,幸运的是期间的debug比较顺利,感觉这个过程学到了很多。

  在查阅资料时,主要看了stackoverflow的有关问题,慢慢有所启发,去查阅dosbox的模拟声卡,最后发现了sound blaster这款声卡,查阅了很多文档

  也参考了github上的开源项目 https://github.com/margaretbloom/sb16-wav  

  非常感谢这个项目对我的启发

  不过这个项目还是存在问题的,它并没有实现double-buffering(可能只是我不会调用吧)

  

  关于音频格式的问题,以及为什么它可以播放wav格式,这里就简单说明一下

  wav是一种无损的格式,它包括一个头段和数据段,头段包含了对wav的说明

  而最关键的数据段,实际上是没有经过任何压缩的,也就是完整记录了每个声道的频率

  所以你把这些信息直接传递给声卡就是可以播放的

  你也可以把头段信息删除(这样普通播放器就无法播放了)。直接给这个程序,也是可以播放的

  然后还有一点是,其实它是16位单声道的播放模式,所以说如果你给它一个32位的wav,或者是双声道的wav,它就会变成1/2速度播放

  这个道理也很显然,就是它把32位当成2个16位,当然就会变成1/2速度了

  更重要的一点是,它的播放是有频率的,由于我们是直接往DMA里面输入,所以播放频率就是输入的速率,大概是10000HZ左右,所以只要转换音乐到这个频率附近,就可以正常播放了,如果太小或太大,则播放速率会有明显的加快或变慢的特点。

  

  

  关于编译环境,在dosbox环境下,我这边tasm和masm都可以编译和链接成功和正常运行

 

  orz 大概就是这样了,如果有什么问题,欢迎指出

 

转载于:https://www.cnblogs.com/Saurus/p/7124064.html

你可能感兴趣的文章
理解思科IPS系统的traffic flow notifications
查看>>
【博客话题】技术人生之三界修炼
查看>>
Ext JS 6开发实例(三) :主界面设计
查看>>
Hyper-V 3中虚拟机CPU竞争机制
查看>>
【原创】Oracle RAC原理和安装
查看>>
东哥读书小记 之 《MacTalk人生元编程》
查看>>
《随机出题软件》&《随机分队软件》源码(Windows API)
查看>>
python 文件及文件夹操作
查看>>
Android自定义ListView的Item无法响应OnItemClick的解决办法
查看>>
Building Apps for Windows Phone 8.1教程下载地址整理
查看>>
移动Web—CSS为Retina屏幕替换更高质量的图片
查看>>
[Linux 性能检测工具]SAR
查看>>
JS 运行、复制、另存为 代码。
查看>>
一个经典编程面试题的“隐退”
查看>>
阿里公共DNS 正式发布了
查看>>
Java抓取网页数据(原网页+Javascript返回数据)
查看>>
[转载] 推荐的C++书籍以及阅读顺序
查看>>
EasyUI基础入门之Pagination(分页)
查看>>
ORACLE中CONSTRAINT的四对属性
查看>>
python 迭代器 生成器
查看>>