Using the audio device under Linux

by Karsten Scheibler

Remark (a):
Save this page as audio.html and use the following command to extract the source code with a sample Makefile:
sh -c "( mkdir audio && cd audio && awk '/^<!--eof/{d=0}{if(d)print>f}/^<!--sof/{f=\$2;d=1}' ) < audio.html"
Remark (b):
Some browsers try to be super smart and reformat the html code so you may have trouble to extract the files. This is the right time to learn more about wget, a tool to download files via http or ftp ;-)
Remark (c):
Some versions of nasm are known to generate broken code. I have used nasm-0.98 for this example which worked for me.

This is a short explanation how you can use the OSS interface of the kernel to output audio samples. If you find mistakes or have suggestions, mail me.

The example code below is very simple. It doesn't check if your soundcard supports the options required (44.1 kHz mono/stereo 16 Bits signed). This causes wrong output if your soundcard doesn't support this. Of course your kernel has to have loaded the correct driver for your soundcard in order to get this work.

short procedure:

  1. open the device via sys_open (usually /dev/dsp)
  2. set your audio options via sys_ioctl
  3. use sys_write to output the audio samples

Mostly all of the ioctl's used here are read/write this means the address given in edx must be writable, because the kernel writes the real parameter for your sound options back to this address (if you want to set 48 kHz but your soundcard only supports 44.1 kHz you will find this value in your variable addressed with edx). Therefore placing such a variable in section .text is a bad idea. Notice: if you look at the kernel sources (especially /usr/src/linux/include/linux/soundcard.h) you will find multiple names for the same ioctl (example: SNDCTL_DSP_CHANNELS is equal to SOUND_PCM_WRITE_CHANNELS).

If you want to use sound output in your program you should do something more than this sample code. Here some more interesting ioctl's:

It is always a good idea to write as much data as possible at once, because every syscall consumes extra cpu cycles.

The sample code below makes 2 outputs. First a simple sawtooth waveform and second it tries to open the file audio.raw and if it exists it is played. This file should be in the format 44.1 kHz, stereo, 16 Bit signed.


;****************************************************************************
;****************************************************************************
;*
;* USING THE AUDIO DEVICE UNDER LINUX
;*
;* written by Karsten Scheibler, 2003-DEC-08
;*
;****************************************************************************
;****************************************************************************





global audio_start



;****************************************************************************
;* some assign's
;****************************************************************************
%assign SYS_READ			3
%assign SYS_WRITE			4
%assign SYS_OPEN			5
%assign SYS_IOCTL			54

%assign O_RDONLY			0
%assign O_RDWR				2

%assign SNDCTL_DSP_RESET		0x00005000
%assign SNDCTL_DSP_SPEED		0xc0045002
%assign SNDCTL_DSP_SETFMT		0xc0045005
%assign		AFMT_S16_LE		0x00000010
%assign SNDCTL_DSP_CHANNELS		0xc0045006

%assign AUDIO_DATA_SIZE			0x100000	;1 MB



;****************************************************************************
;* data
;****************************************************************************
section .data
					align	4
audio_handle:				dd	0
ioctl_table:				dd	SNDCTL_DSP_RESET, 0
					dd	SNDCTL_DSP_SPEED, 44100
					dd	SNDCTL_DSP_CHANNELS, 1
					dd	SNDCTL_DSP_SETFMT, AFMT_S16_LE

section .bss
					alignb	4
audio_data:				resb	AUDIO_DATA_SIZE



;****************************************************************************
;* audio_start
;****************************************************************************
section .text
audio_start:
	;open the device

	mov	dword eax, SYS_OPEN
	mov	dword ebx, .device
	mov	dword ecx, O_RDWR
	int	byte  0x80
	test	dword eax, eax
	js	near  audio_error
	mov	dword [audio_handle], eax

	;set the options: 44.1 kHz, mono, 16 Bit signed

	xor	dword ebp, ebp
.do_ioctl:
	mov	dword eax, SYS_IOCTL
	mov	dword ebx, [audio_handle]
	mov	dword ecx, [ioctl_table + 8 * ebp]
	lea	dword edx, [ioctl_table + 8 * ebp + 4]
	int	byte  0x80
	test	dword eax, eax
	js	near  audio_error
	inc	dword ebp
	cmp	dword ebp, byte 4
	jb	short .do_ioctl

	;output something very simple (sawtooth waveform)
	;not recommended: write every single sample like here

	mov	dword esi, 0x40000
	xor	dword ebp, ebp
.loop:
	mov	word  [audio_data], bp
	mov	dword eax, SYS_WRITE
	mov	dword ebx, [audio_handle]
	mov	dword ecx, audio_data
	mov	dword edx, 2
	int	byte  0x80
	test	dword eax, eax
	js	near  audio_error
	add	dword ebp, 0x0100
	dec	dword esi
	jnz	short .loop

	;open a raw audio file
	;format should be 44.1 kHz, stereo, 16 Bit signed

	mov	dword eax, SYS_OPEN
	mov	dword ebx, .file
	mov	dword ecx, O_RDONLY
	int	byte  0x80
	mov	dword ebx, eax
	test	dword eax, eax
	js	short audio_error
	mov	dword eax, SYS_READ
	mov	dword ecx, audio_data
	mov	dword edx, AUDIO_DATA_SIZE
	int	byte  0x80
	test	dword eax, eax
	js	short audio_error

	;set output to stereo

	lea	dword ebp, [ioctl_table + 4 * 6]
	mov	dword eax, SYS_IOCTL
	mov	dword ebx, [audio_handle]
	mov	dword ecx, SNDCTL_DSP_CHANNELS
	mov	dword [ebp], 2
	mov	dword edx, ebp
	int	byte  0x80
	test	dword eax, eax
	js	short audio_error

	;dump it to the audio device

	mov	dword eax, SYS_WRITE
	mov	dword ebx, [audio_handle]
	mov	dword ecx, audio_data
	mov	dword edx, AUDIO_DATA_SIZE
	int	byte  0x80
	test	dword eax, eax
	js	short audio_error

	;exit nicely

	jmp	short audio_end

.device:				db	"/dev/dsp", 0
.file:					db	"audio.raw", 0



;****************************************************************************
;* audio_error
;****************************************************************************
section .text
audio_error:
	xor	dword eax, eax
	inc	dword eax
	mov	dword ebx, eax
	int	byte  0x80



;****************************************************************************
;* audio_end
;****************************************************************************
section .text
audio_end:
	xor	dword eax, eax
	xor	dword ebx, ebx
	inc	dword eax
	int	byte  0x80
;*********************************************** linuxassembly@unusedino.de *