音频设备

更新时间:
2024-01-09
下载文档

音频设备

OSS(Open Sound System)是 Unix 平台上一个统一的音频接口标准。以前,每个 Unix 厂商都会提供一套专有的 API,用于处理音频。这就意味着为一种 Unix 平台编写的音频处理应用程序,在移植到另外一种 Unix 平台上时,必须要重写。不仅如此,在一种平台上具备的功能,可能在另外一种平台上无法实现。

但是,OSS 标准出现以后,情况就大不一样了。只要音频处理应用程序按照 OSS 标准的 API 来编写,那么在移植到另外一种平台时,只需要重新编译即可。因此 OSS 标准提供了源代码级的可移植性。

SylixOS 支持简易的 OSS 标准。

基础知识

数字音频设备(有时也称 codec,PCM,DSP,ADC/DAC 设备):播放或录制数字化的声音。它的指标主要有:采样速率(电话为 8KHz,DVD 为 96KHz)、channel 数目(单声道、立体声)、采样分辨率(8bit、16bit)。

  • mixer (混频器):用来控制多个输入、输出的音量,也控制输入(microphone,line-in,CD)之间的切换。
  • synthesizer (合成器):通过一些预先定义好的波形来合成声音,有时用在游戏中声音效果的产生。
  • MIDI 接口 :MIDI 接口是为了连接舞台上的 synthesizer、键盘、道具、灯光控制器的一种串行接口。

在 SylixOS 中,设备被抽象成文件,通过对文件的访问方式(首先 open,然后 read/write,同时可以使用 ioctl 读取/设置参数,最后 close)来访问设备。在 OSS 标准中,主要有以下几种设备文件:

  • /dev/mixer:访问声卡中内置的 mixer,调整音量大小,选择音源。
  • /dev/sndstat:测试声卡,执行 cat /dev/sndstat 会显示声卡驱动的信息。
  • /dev/dsp 、/dev/dspW、/dev/audio:读这个设备就相当于录音,写这个设备就相当于放音。/dev/dsp 与/dev/audio 之间的区别在于采样的编码不同,/dev/audio 使用 μ 律编码,/dev/dsp 使用 8bit(无符号)线性编码,/dev/dspW 使用 16bit(有符号)线形编码。/dev/audio 主要是为了与 SunOS 兼容,通常不建议使用。
  • /dev/sequencer:访问声卡内置的,或者连接在 MIDI 接口的 synthesizer。

在 SylixOS 中,实际具有哪些音频设备文件依赖于底层驱动程序的实现,一般只有 /dev/dsp 和 /dev/mixer 设备。

音频编程

下面我们分别讨论打开音频设备、放音、录音和参数调整。

头文件定义

#include <ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/soundcard.h>
#define BUF_SIZE 4096
int             audio_fd;
unsigned char   audio_buffer[BUF_SIZE];

打开音频设备

if ((audio_fd = open("/dev/dsp", open_mode, 0)) == -1) {
    perror("/dev/dsp");
    exit(1);
}

open_mode 有三种选择:O_RDONLY,O_WRONLY 和 O_RDWR,分别表示只读、只写和读写。OSS 标准建议尽量使用只读或只写,只有在全双工的情况下(即录音和放音同时)才使用读写模式。

录音

int len;
if ((len = read(audio_fd, audio_buffer, count)) == -1) {
    perror("audio read");
    exit(1);
}

count 为录音数据的字节个数(建议为 2 的指数),但不能超过 audio_buffer 的大小。从读字节的个数可以精确的测量时间,例如:8KHz 16bit stereo 的速率为 8000 * 2 * 2 = 32000 bytes/second,这是知道何时停止录音的唯一方法。

放音

放音实际上和录音很类似,只不过把 read 改成 write 即可,相应的 audio_buffer 中为音频数据,count 为数据的长度。

注意,用户始终要读/写一个完整的采样。例如:一个 16bit 的立体声模式下,每个采样有 4 个字节,所以应用程序每次必须读/写 4 的倍数个字节。

另外,由于 OSS 是一个跨平台的音频接口,所以用户在编程的时候,要考虑到可移植性的问题,其中一个重要的方面是读/写时的字节顺序。

设置采样格式

int format;
format = AFMT_S16_LE;
if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format) == -1) {
    perror("SNDCTL_DSP_SETFMT");
    exit(1);
}
if (format != AFMT_S16_LE) {
    /*
     *  本设备不支持选择的采样格式.
     */
}

在设置采样格式之前,可以先测试设备能够支持那些采样格式,方法如下:

int mask;
if (ioctl(audio_fd, SNDCTL_DSP_GETFMTS, &mask) == -1) {
perror("SNDCTL_DSP_GETFMTS");
exit(1);
}
if (mask & AFMT_MPEG) {
    /*
     *  本设备支持MPEG采样格式...
     */
}

设置通道数目

int channels = 2;                 /* 1=mono, 2=stereo              */
if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &channels) == -1) {
    perror("SNDCTL_DSP_CHANNELS");
    exit(1);
}
if (channels != 2){
    /*
     *  本设备不支持立体声模式...
     */
}

设置采样速率

int speed = 11025;
if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &speed)==-1) {
    perror("SNDCTL_DSP_SPEED");
    exit(1);
}
if ( /* 返回的速率(即硬件支持的速率)与需要的速率差别很大... */ ) {
     /*
     *  本设备不支持需要的速率...
     */
}

音频设备通过分频的方法产生需要的采样时钟,因此不可能产生所有的频率。驱动程序会计算出最接近要求的频率,用户程序要检查返回的速率值,如果误差较小,可以忽略。

Mixer 编程

对 Mixer 的控制包括调节音量(volume)、选择录音音源(microphone、line-in)、查询 mixer 的功能和状态,主要是通过 Mixer 设备 /dev/mixer 的 ioctl 接口。相应地,ioctl 接口提供的功能也分为三类:调节音量、查询 mixer 的能力、选择 mixer 的录音通道。下面分别介绍使用的方法:

下面的 mixer_fd 是对 mixer 设备执行 open 操作返回的文件描述符。

调节音量

应用程序通过 ioctl 的 MIXER_READ 和 MIXER_WIRTE 功能号来读取/设置音量。在 OSS 标准中,音量的大小范围在 0~100 之间。使用方法如下:

int vol;
if (ioctl(mixer_fd, MIXER_READ(SOUND_MIXER_MIC), &vol) == -1) {
    /*
     *  访问了没有定义的mixer通道...
     */
}

SOUND_MIXER_MIC 是通道参数,表示读 microphone 通道的音量,结果放置在 vol 中。如果通道是立体声,那么 vol 的最低有效字节为左声道的音量值,接着的字节为右声道的音量值,另外的两个字节不用。如果通道是单声道,vol 中左声道与右声道具有相同的值。

查询 mixer 的能力

int mask;
if (ioctl(mixer_fd, SOUND_MIXER_READ_xxxx, &mask) == -1) {
    /*
     *  Mixer没有此能力...
     */
}

SOUND_MIXER_READ_xxxx 中的 xxxx 代表具体要查询的内容:

  • 检查可用的 mixer 通道用 SOUND_MIXER_READ_DEVMASK。
  • 检查可用的录音设备,用 SOUND_MIXER_READ_RECMASK。
  • 检查单声道/立体声,用 SOUND_MIXER_READ_STEREODEVS。
  • 检查 mixer 的一般能力,用 SOUND_MIXER_READ_CAPS 等。

所有通道的查询的结果都放在 mask 中,所以要区分出特定通道的状况,使用 mask&(1 << channel_no)。

选择 mixer 的录音通道

首先可以通过 SOUND_MIXER_READ_RECMASK 检查可用的录音通道,然后通过 SOUND_MIXER_WRITE_RECSRC 选择录音通道。可以随时通过 SOUND_MIXER_READ_RECSRC 查询当前声卡中已经被选择的录音通道。

OSS 标准建议把 mixer 的用户控制功能单独出来形成一个通用的程序。但前提是,在使用 mixer 之前,首先需要通过 API 的查询功能检查声卡的能力。

以下程序展示了使用 OSS 标准操作音频设备播放音乐的方法。

#include "unistd.h"
#include "stdlib.h"
#include "sys/soundcard.h"
#define __OSS_TEST_BUFFER_LEN           10 * 1024
#define __OSS_TEST_WAV_FILE             "/apps/wo.wav"
#define __OSS_TEST_SAMPLE_RATE          11025
#define __OSS_TEST_CHANNELS             2
#define __OSS_TEST_SAMPLE_FORMAT        AFMT_S16_LE
int main (int argc, char *argv[])
{
CHAR            *pcBuffer;
CHAR            *pcPtr;
INT            iSampleFmt;
INT            iChannels;
INT            iSampleRate;
INT            iDspFd;
INT            iFileFd;
ssize_t        stLen;
ssize_t        stRet;
    pcBuffer = malloc(__OSS_TEST_BUFFER_LEN);
    if (!pcBuffer) {
        printf("failed to alloc buffer!\n");
        return  (-1);
    }
    iDspFd = open("/dev/dsp", O_WRONLY, 0666);
    if (iDspFd < 0) {
        printf("failed to open /dev/dsp device!\n");
        return  (-1);
    }
    iSampleFmt = __OSS_TEST_SAMPLE_FORMAT;
    stRet = ioctl(iDspFd, SNDCTL_DSP_SETFMT, &iSampleFmt);
    if (stRet < 0) {
        printf("failed to set sample format!\n");
        close(iDspFd);
        return  (-1);
    }
    iChannels = __OSS_TEST_CHANNELS;
    stRet = ioctl(iDspFd, SNDCTL_DSP_CHANNELS, &iChannels);
    if (stRet < 0) {
        printf("failed to set channels!\n");
        close(iDspFd);
        return  (-1);
    }
    iSampleRate = __OSS_TEST_SAMPLE_RATE;
    stRet = ioctl(iDspFd, SNDCTL_DSP_SPEED, &iSampleRate);
    if (stRet < 0) {
        printf("failed to set sample rate!\n");
        close(iDspFd);
        return  (-1);
    }
    iFileFd = open(__OSS_TEST_WAV_FILE, O_RDONLY, 0666);
    if (iFileFd < 0) {
        printf("failed to open test audio file %s!\n", 
__OSS_TEST_WAV_FILE);
        close(iDspFd);
        return  (-1);
    }
    read(iFileFd, pcBuffer, 0x2E * 2);
    while ((stLen = read(iFileFd, pcBuffer, 
__OSS_TEST_BUFFER_LEN)) > 0) {
        pcPtr = pcBuffer;
        while (stLen > 0) {
            stRet = write(iDspFd, pcPtr, stLen);
            if (stRet < 0) {
                break;
            }
            pcPtr += stRet;
            stLen -= stRet;
        }
    }
    sleep(3);
    close(iFileFd);
    close(iDspFd);
    free(pcBuffer);
    return  (0);
}
文档内容是否对您有所帮助?
有帮助
没帮助