Re: Lowest CPU utilization audio codec?
Jim Leonard <[email protected]>
| Newsgroups | gmane.comp.video.xine.codec.devel |
|---|---|
| Message-ID | <[email protected]> |
Mike Melanson wrote: > There is really no reason that ADPCM has to be transmitted as a > series of 4-bit samples. I have seen (or at least heard about) 5-bit, > 3-bit, and I think 2-bit ADPCM variants, of the IMA algorithm, no less. Yes, the original Sound Blaster actually has 3-bit and 2-bit ADPCM variations. But believe me when I tell you that they are simply terrible-sounding. The 2-bit in particular is a joke. "adaptive ADPCM" = "adaptive adaptive differential PCM"?...:) He made fun of it at the time as well :-) > Anyway, the 3:1 and 6:1 thing...sounds like it could be MACE compression-- > Macintosh Audio Compression and Expansion. It came in 3:1 and 6:1 flavors, > which were denoted by fourccs 'MAC3' and 'MAC6', respectively. ffmpeg/lavc > has code to decode both, but not compress it. I'm not sure about quality > as I have never worked much with it. No, that's not it at all. He wrote an article on it in the July 1992 issue of Dr. Dobbs' Journal (http://www.ddj.com/articles/1992/9207/), of which I seem to have misplaced :-( It was ADPCM, but it was frame-based and switched between different translation tables. It also had the usual special-case stuff (ie silence encoding). If Dr. Dobbs didn't CHARGE for access to the articles I would be able to tell you more (grumble)... ...okay, some creative searching shows that he called it "ACOMP" and I managed to track down the assmebler source for both compressor and decompressor, including tables. I've attached it. -- Jim Leonard ([email protected]) World's largest electronic gaming project: http://www.MobyGames.com/ A delicious slice of the demoscene: http://www.MindCandyDVD.com/ Various oldskool PC rants and ramblings: http://www.oldskool.org/
acomp.asc
(text/plain, 19.6 KB)
_AUDIO COMPRESSION_
by John W. Ratcliff
[LISTING ONE]
;; AC.ASM -> ACOMP assembly language compressor. Written by John W. Ratcliff,
;; 1991. Uses Turbo Assembler IDEAL mode and makes HEAVE use of macros. This
;; algorithm performs an exhaustive search for the best delta mod for each
;; section of the waveform. It is very CPU intensive, and the algorithm can
;; be a little difficult to follow in assembly language.
IDEAL ; Enter Turbo Assembler's IDEAL mode.
JUMPS ; Allow automatic jump sizing.
INCLUDE "prologue.mac" ; Include usefull assembly lanugage macro file.
SEGMENT _TEXT BYTE PUBLIC 'CODE'
ASSUME CS:_TEXT
;; This macro computes the amount of error in a frame of data, at this
;; bit resolution and this delta mod location.
Macro DoError BITS
LOCAL @@NO
mov bx,[COMP] ; Current delta mod value.
mov bh,BITS ; Current bit resolution.
push [MINERR] ; Pass minimum error so far.
push [PREV] ; Pass previous data point.
call ComputeError ; Compute error for frame.
add sp,4 ; Balance stack.
cmp dx,[MINERR] ; Less than previous minimum?
jge @@NO ; no, don't update.
mov [MINERR],dx ; Save as new miniume
mov [BESTPREV],ax ; Best previous location.
xor ah,ah ;
mov al,bl ; Get best delta modution.
mov [BEST1],ax ; save it.
mov al,bh ; Get best bits.
mov [BEST2],ax ; Save it.
@@NO:
endm
SQLCH equ 64 ; Squelch bit.
RESYNC equ 128 ; Resynchronization bit.
DELTAMOD equ 00110000b ; Bit mask for delta mod bits.
ONEBIT equ 00010000b ; Bit pattern for one bit delta mod.
TWOBIT equ 00100000b ; Bit pattern for two bit delta mod.
FOURBIT equ 00110000b ; Bit pattern for two bit delta mod.
;; This macro echos a message to the text screen, so that we can
;; monitor the progress of the compression algorithm.
Macro Note MSG
push ax
lea ax,[MSG]
push ax
call Notify
add sp,2
pop ax
endm
public _CompressAudio
;;This the the ACOMP compression procedure.
;;int far CompressAudio(unsigned char far *shand,
;; Address of audio data to compress.
;; unsigned char far *dhand,
;; Destination address of compressed data.
;; unsigned int slen, Length of audio data to compress.
;; int squelch, Squelch value allowed.
;; int freq, Playback frequency of audio data.
;; int frame, Frame size.
;; int maxerr); Maximum error allowed.
Proc _CompressAudio far
ARG SHAN:DWORD,DHAN:DWORD,SLEN:WORD,SQUELCH:WORD,FREQ:WORD,
FRAME:WORD,MAXERR:WORD
LOCAL PREV:WORD,COMP:WORD,MINERR:WORD,BEST1:WORD,BEST2:
WORD,BESTPREV:WORD = LocalSpace
PENTER LocalSpace
PushCREGS
lds si,[SHAN] ; Get source address.
les di,[DHAN] ; Get destination address.
mov cx,[SLEN] ; Get length of audio data.
mov ax,cx ; Get length of audio sample into AX
stosw ; Store it.
mov ax,[FREQ] ; Get frequency of recording.
stosw ; Store it.
mov ax,[FRAME] ; Get the frame size.
stosb ; Store it.
mov ax,[SQUELCH] ; Get squelch size
stosb ; Store it.
mov ax,[MAXERR] ; Get maximum error allowed.
stosw ; Save it.
xor ax,ax
lodsb ; Get first data sample
mov [PREV],ax
stosb ; Store first data sample.
dec cx ; Decrement sample size count.
jz @@DONE
@@SQU: mov ah,0Bh ; Test keyboard status.
int 21h
or al,al
jz @@NOK
mov ah,08h ; If a key was pressed get that key
int 21h ; value, and see if it was the
cmp al,27 ; escape key.
jne @@NOK
xor ax,ax ; If escape, return to caller, with abort.
jmp @@EXIT
@@NOK:
xor ax,ax
mov dx,[SQUELCH] ; Get squelch value.
push cx ; Save remaining data count.
push si ; Save si.
@@CK1: lodsb ; Get data byte.
sub ax,[PREV] ; Difference from previous data sample?
jns @@CK2 ; if positive leave it alone.
neg ax ; Make it positive.
@@CK2: cmp ax,dx ; Is it within the squelch range?
jg @@NOS ; yes, keep checking!
loop @@CK1 ; Keep going.
inc si ; Plus one, this last one counts.
@@NOS: pop ax ; Get back start SI
mov dx,si ; DX contains current address.
sub dx,ax ; Compute number of squelch bytes encountered.
dec dx ; Less, last non squelch byte.
cmp dx,3 ; At least three?
jle @@NOSQ ; no, don't squelch it.
@@SQS: cmp dx,63 ; Is it under 63?
jle @@SEND
mov ax,(63 + SQLCH) ; Send count.
sub dx,63 ; Less the 63 count we just sent.
stosb ; Write squelch byte out.
jmp short @@SQS ; Continue.
@@SEND: mov ax,dx ; Get remaining count.
or ax,SQLCH ; Or squelch bit on.
stosb ; Send squelch count out.
dec si ; Back up to last data point.
pop ax ; Pull CX off of stack, use current count.
Note msg0
jmp short @@NXT
@@NOSQ: mov si,ax ; Replace where source was.
pop cx ; Get back remaining data count.
@@NXT: jcxz @@DONE ; Exit if done.
cmp cx,[FRAME] ; Below current frame size?
jae @@GO ; no, go ahead.
@@FIN: lodsb ; Get raw sample.
shr al,1 ; Down to closest aproximated value.
or al,RESYNC ; Add resync bit to it.
stosb ; Store out.
loop @@FIN ; Keep sending final bytes.
jmp @@DONE ; exit, after sending final bytes.
@@GO: mov [MINERR],07FFFh
push cx
mov cx,[FRAME] ; Set CX to frame size.
mov [COMP],1
@@ALL1: DoError 1 ; Try one bit mode, +/-1.
inc [COMP]
cmp [COMP],17 ; Try delta comp values clean up to 16!!
jne @@ALL1
mov ax,[MINERR]
cmp ax,[MAXERR]
jle @@BCMP ; Not good enough...
mov [COMP],1
@@ALL2: DoError 2 ; Try two bit mode, +/-1.
inc [COMP]
cmp [COMP],17 ; Try delta comp values clean up to 16!!
jne @@ALL2
mov ax,[MINERR]
cmp ax,[MAXERR]
jle @@BCMP
mov [COMP],1
@@ALL4: DoError 8 ; Try four bit mode, +/-1.
inc [COMP]
cmp [COMP],17 ; Try delta comp values clean up to 16!!
jne @@ALL4
mov ax,[MINERR] ; Get what the minimum error was.
cmp ax,[MAXERR] ; Minimum error > maximum error?
jle @@BCMP ; no, then send frame.
pop cx ; Get back CX
lodsb ; Get data sample.
and al,(NOT 1) ; Strip off bottom bit.
xor ah,ah
mov [PREV],ax ; New previous.
shr al,1 ; /2
or al,RESYNC ; Or resync bit on.
stosb ; Store it out into data stream.
Note msg1
loop @@SQU ; Go check squelching.
jmp @@DONE ; Done, if this was last data sample.
@@BCMP: mov bx,[BEST1] ; Get best comp.
mov ax,[BEST2] ; Get best bit size.
mov bh,al ; Into BH
mov ax,32000
push ax
push [PREV] ; Pass prev.
call ComputeError ; Re-compute error term.
add sp,4
mov [PREV],ax ; New previous.
;; Now time to store results!
mov bx,[BEST1] ; Get best comp.
cmp [BEST2],1 ; 1 bit?
jne @@NXT1
call Fold1Bit ; Fold 1 bit data.
Note msg2
jmp short @@IN ; Reenter.
@@NXT1: cmp [BEST2],2 ; 2 bit data?
jne @@NXT2
call Fold2Bit
Note msg3
jmp short @@IN
@@NXT2:
call Fold4Bit
Note msg4
@@IN: mov ax,[FRAME]
pop cx ; Get back CX
add si,ax ; Advance source
sub cx,ax ; Decrement data count.
jnz @@SQU ; Continue, if not at end.
@@DONE:
mov ax,di ; Size of compressed file.
les di,[DHAN]
sub ax,di ; Difference.
@@EXIT:
PopCREGS
PLEAVE
ret
endp
;; Compute error: Registers on entry are:
;; DS:SI -> source data.
;; CX -> number of bytes to compute error term in.
;; DX -> total error incurred.
;; BL -> delta comp size.
;; BH -> maximum bit size value, positive or negative.
;; Exit: CX,DS:SI stay the same.
;; DX -> total error term.
;; AX -> new previous.
Proc ComputeError near
ARG PREV:WORD,MINERR:WORD
LOCAL CUR:WORD = LocalSpace
PENTER LocalSpace
push cx
push si
push di ; Save destination address.
xor dx,dx ; Initally no error.
@@CERR: lodsb ; Get a data byte.
xor ah,ah ; Zero high byte.
mov [CUR],ax ; Save as current sample.
sub ax,[PREV]
cmp bl,1
je @@ND
idiv bl ; Divided by delta mod size.
@@ND: or al,al
js @@DON ; Do negative side.
jnz @@CNT ; If not zero then continue.
inc al ; Can't represent a zero, make it one.
@@CNT: cmp al,bh ; > max representative size?
jle @@OK ; no, it fit as is.
mov al,bh ; Make it the max representative size.
jmp short @@OK ;
@@DON: neg al ; Make it positive.
cmp al,bh ; > max representative size?
jbe @@K2 ; no, use it.
mov al,bh ; Make it the max representative size.
@@K2: neg al ; Make it negative again.
@@OK:
stosb ; Store data value out.
imul bl ; Times delta comp value.
add ax,[PREV] ; Add to previous data point.
js @@CS ; Do signed case.
cmp ax,255 ; Did it over flow?
jle @@K3 ; No, then it fit byte sized.
mov ax,255 ; Make it byte sized.
jmp short @@K3 ; Re-enter
@@CS: xor ax,ax ; Close as we can get, underflow.
@@K3: mov [PREV],ax ; This is our new aproximated value.
sub ax,[CUR] ; Less actual value.
jns @@K4 ; if positive then fine.
neg ax ; Take absolute value.
@@K4: add dx,ax ; Add into total error.
cmp dx,[MINERR] ; Greater than minimum error allowed?
jg @@OUT
loop @@CERR
@@OUT: mov ax,[PREV] ; Current previous data point.
pop di ; Restore destination address.
pop si ; Reset SI back to start.
pop cx ; Reset CX back to start.
PLEAVE
ret
endp
Macro BuildByte
LOCAL @@HOP1,@@HOP2
lodsb
or al,al ; Is it signed?
jns @@HOP1
shl ah,1 ; Rotate.
jmp short @@HOP2
@@HOP1: stc
rcl ah,1
@@HOP2:
endm
;; Fold 1 bit data.
;; ES:DI -> points to data ready to fold out.
;; CX-> frame size.
;; BL-> contains delta size.
Proc Fold1Bit near
push ds
push si
push di ; Header byte address.
push es
pop ds ; DS=ES
mov si,di ; Source and dest.
inc di ; skip past header byte.
@@FOLD: xor ah,ah ; Dest byte to be built, zero it.
BuildByte
BuildByte
BuildByte
BuildByte
BuildByte
BuildByte
BuildByte
BuildByte
mov al,ah
stosb ; Store it out.
sub cx,8 ; Less the 8 samples just folded up.
jnz @@FOLD ; Continue.
pop si ; Get back header byte address.
mov al,bl ; Get delta comp size.
dec al ; Less one.
or al,ONEBIT ; Or the One Bit mode flag on.
mov [ds:si],al ; Store header byte.
pop si
pop ds
ret
endp
;; 2 Bit Format: 00 -> -2
;; 01 -> -1
;; 10 -> +1
;; 11 -> +2
Macro BByte
LOCAL @@HOP1,@@HOP2
lodsb
or al,al ; Is it signed?
jns @@HOP1
add al,2 ; Adjust it.
jmp short @@HOP2
@@HOP1: inc al ; Plus 1 to fit into format size.
@@HOP2: shl ah,1
shl ah,1
or ah,al ; Place bits into byte being built.
endm
;; Fold 2 bit data.
;; ES:DI -> points to data ready to fold out.
;; CX-> frame size.
;; BL-> contains delta size.
Proc Fold2Bit near
push ds
push si
@@F2:
push di ; Header byte address.
push es
pop ds ; DS=ES
mov si,di ; Source and dest.
inc di ; skip past header byte.
@@FOLD: xor ah,ah ; Dest byte to be built, zero it.
BByte
BByte
BByte
BByte
mov al,ah
stosb ; Store it out.
sub cx,4 ; Folded up 4 samples.
jnz @@FOLD ; Continue.
pop si ; Get back header byte address.
mov al,bl ; Get delta comp size.
dec al ; Less one.
or al,TWOBIT ; Or the One Bit mode flag on.
mov [ds:si],al ; Store header byte.
pop si
pop ds
ret
endp
;; Four bit format:
;; 0 -> -8
;; 1 -> -7
;; 2 -> -6
;; 3 -> -5
;; 4 -> -4
;; 5 -> -3
;; 6 -> -2
;; 7 -> -1
;; 8 -> +1
;; 9 -> +2
;;10 -> +3
;;11 -> +4
;;12 -> +5
;;13 -> +6
;;14 -> +7
;;15 -> +8
Macro Adjust4bit
LOCAL @@HOP1,@@HOP2
lodsb
or al,al
jns @@HOP1
add al,8 ; Adjust it.
jmp short @@HOP2
@@HOP1: add al,7 ; Adjust it.
@@HOP2:
endm
;; Fold 4 bit data.
;; ES:DI -> points to data ready to fold out.
;; CX-> frame size.
;; BL-> contains delta size.
Proc Fold4Bit near
push ds
push si
push di ; Header byte address.
push es
pop ds ; DS=ES
mov si,di ; Source and dest the same.
inc di ; skip past header byte.
@@FOLD: Adjust4bit ; Get first sample.
ShiftL al,4 ; Into high nibble.
mov ah,al ; Into AH
Adjust4bit ; Get next nibble.
or al,ah ; One whole byte.
stosb ; Store it out.
sub cx,2 ; Folded up 4 samples.
jnz @@FOLD ; Continue.
pop si ; Get back header byte address.
mov al,bl ; Get delta comp size.
dec al ; Less one.
or al,FOURBIT ; Or the One Bit mode flag on.
mov [ds:si],al ; Store header byte.
pop si
pop ds
ret
endp
msg0 db "SQUELCH"
msg1 db "RESYNC "
msg2 db "1 BIT "
msg3 db "2 BIT "
msg4 db "4 BIT "
Proc Notify near
ARG MSG:WORD
PENTER 0
PushAll
push cs
pop ds
mov ax,0B800h
mov es,ax
mov si,[MSG]
xor di,di
mov ah,1Fh
mov cx,7
@@SND: lodsb
stosw
loop @@SND
PopAll
PLEAVE
ret
endp
ENDS
END
[LISTING TWO]
;; UC.ASM -> Uncompress ACOMP compressed audio data.
;; Written by John W. Ratcliff, 1991.
;; Uses Turbo Assembler IDEAL mode.
IDEAL ; Enter Turbo Assembler IDEAL mode.
JUMPS ; Allow automatic jump sizing.
INCLUDE "prologue.mac" ; Include common useful assembly macros.
SMALL_MODEL equ 0 ;: true only if trying to generate near calls
SETUPSEGMENT ; Setup _TEXT segment.
Macro CPROC name
public _&name
IF SMALL_MODEL
Proc _&name near
ELSE
Proc _&name far
ENDIF
endm
SQLCH equ 64 ; Squelch byte flag
RESYNC equ 128 ; Resync byte flag.
DELTAMOD equ 00110000b ; Bit mask for delta mod bits.
ONEBIT equ 00010000b ; Bit pattern for one bit delta mod.
TWOBIT equ 00100000b ; Bit pattern for two bit delta mod.
FOURBIT equ 00110000b ; Bit pattern for two bit delta mod.
base dw ? ; Base address inside translate table.
TRANS db -8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8
db -16,-14,-12,-10,-8,-6,-4,-2,2,4,6,8,10,12,14,16
db -24,-21,-18,-15,-12,-9,-6,-3,3,6,9,12,15,18,21,24
db -32,-28,-24,-20,-16,-12,-8,-4,4,8,12,16,20,24,28,32
db -40,-35,-30,-25,-20,-15,-10,-5,5,10,15,20,25,30,35,40
db -48,-42,-36,-30,-24,-18,-12,-6,6,12,18,24,30,36,42,48
db -56,-49,-42,-35,-28,-21,-14,-7,7,14,21,28,35,42,49,56
db -64,-56,-48,-40,-32,-24,-16,-8,8,16,24,32,40,48,56,64
db -72,-63,-54,-45,-36,-27,-18,-9,9,18,27,36,45,54,63,72
db -80,-70,-60,-50,-40,-30,-20,-10,10,20,30,40,50,60,70,80
db -88,-77,-66,-55,-44,-33,-22,-11,11,22,33,44,55,66,77,88
db -96,-84,-72,-60,-48,-36,-24,-12,12,24,36,48,60,72,84,96
db -104,-91,-78,-65,-52,-39,-26,-13,13,26,39,52,65,78,91,104
db -112,-98,-84,-70,-56,-42,-28,-14,14,28,42,56,70,84,98,112
db -120,-105,-90,-75,-60,-45,-30,-15,15,30,45,60,75,90,105,120
db -128,-112,-96,-80,-64,-48,-32,-16,16,32,48,64,80,96,112,127
CPROC GetFreq ; Report playback frequency for an ACOMP file.
ARG SOURCE:DWORD
PENTER 0
push es
les bx,[SOURCE]
mov ax,[es:bx+2]
pop es
PLEAVE
ret
endp
;; DX contains PREVIOUS.
;; AH contains bit mask being rotated out.
;; BX up/down 1 bit value.
Macro Delta1
LOCAL @@UP,@@STORE
shl ah,1 ; Rotate bit mask out.
jc @@UP
sub dx,bx
jns @@STORE
xor dx,dx ; Zero it out.
jmp short @@STORE
@@UP: add dx,bx
or dh,dh
jz @@STORE
mov dx,255
@@STORE:mov al,dl ; Store result.
stosb
endm
;; BX-> base address of translate table.
;; DX-> previous.
;; AL-> index.
Macro DeModulate
LOCAL @@HIGH,@@OK
xlat [cs:bx] ; Translate into lookup table.
cbw ; Make it a signed word.
add dx,ax ; Do word sized add, into previous.
jns @@HIGH
xor dx,dx ; Underflowed.
@@HIGH: or dh,dh ; Did it overflow?
jz @@OK
mov dx,255 ; Maxed out.
@@OK: mov al,dl
stosb
endm
;;unsigned int far UnCompressAudio(unsigned char far *source,unsigned char far *dest);
;; UnCompressAudio will decompress data which was compressed using ACOMP
;; into the destination address provided. UnCompressAudio returns the
;; total size, in bytes, of the uncompressed audio data.
CPROC UnCompressAudio
ARG SHAN:DWORD,DHAN:DWORD
LOCAL SLEN:WORD,FREQ:WORD,FRAME:WORD,BITS:WORD = LocalSpace
PENTER LocalSpace
PushCREGS
lds si,[SHAN] ; Get source segment
les di,[DHAN] ; Get destination segment
lodsw ; Get length.
mov [SLEN],ax ; Save length.
mov cx,ax ; Into CX
lodsw ; Frequency.
mov [FREQ],ax ; Save frequency
lodsb ; Get frame size.
xor ah,ah ; Zero high byte
mov [FRAME],ax ; Save it.
lodsb ; Get squelch, and skip it.
lodsw ; Get maximum error, and skip it.
lodsb ; Get initial previous data point.
stosb ; Store it.
xor ah,ah ; zero high byte.
mov dx,ax ; Save into previous word.
dec cx ; Decrement total by one.
jz @@DONE ; Exit
mov ah,al ; AH, always the previous.
@@DCMP: lodsb ; Get sample.
test al,RESYNC ; Resync byte?
jz @@NOTR ; no, skip.
shl al,1 ; Times two.
mov dl,al ; Into previous.
xor dh,dh ; Zero high word.
stosb ; Store it.
loop @@DCMP ; Next one.
jmp @@DONE
@@NOTR: test al,SQLCH ; Squelch byte?
jz @@FRAM ; no, then it is a frame.
and al,00111111b ; Leave just the count.
push cx ; Save current countdown counter.
mov cl,al ; get repeat count
xor ch,ch ; zero high byte of CX
mov bx,cx ; Repeat count in DX
mov al,dl ; Repeat of previous.
rep stosb ; Repeat it.
pop cx ; Get back remaining count.
sub cx,bx ; Less.
jnz @@DCMP ; Keep going.
jmp @@DONE
@@FRAM:
mov bx,ax ; command byte into BX
and bx,0Fh ; Multiplier being used.
ShiftL bx,4 ; Times 16.
add bx,offset TRANS ; Plus address of translate table.
and al,DELTAMOD ; Leave just delta mod.
push cx
mov cx,[FRAME] ; Get frame size.
cmp al,ONEBIT ; In one bit delta mod?
jne @@NEXT1 ; no, try other.
ShiftR cx,3 ; /8
mov bl,[cs:bx+8] ; Get up amount
xor bh,bh ; Zero high byte.
@@GO: lodsb
xchg al,ah ; Place prev in AL, Bit mask in AH
Delta1
Delta1
Delta1
Delta1
Delta1
Delta1
Delta1
Delta1
mov ah,al
loop @@GO
jmp @@RENTER
@@NEXT1:cmp al,TWOBIT ; In two bit delta mod mode?
jne @@NEXT2
add bx,6 ; Point at +- 2 bit's in table.
shr cx,1
shr cx,1 ; 4 samples per byte.
@@GOGO: lodsb
ShiftR al,6
DeModulate
mov al,[ds:si-1]
ShiftR al,4
and al,3
DeModulate
mov al,[ds:si-1]
ShiftR al,2
and al,3
DeModulate
mov al,[ds:si-1]
and al,3
DeModulate
loop @@GOGO
jmp short @@RENTER
@@NEXT2:shr cx,1 ; Two samples per byte.
@@GO2: lodsb ; Get sample.
ShiftR al,4
DeModulate
mov al,[ds:si-1]
and al,0Fh
DeModulate
loop @@GO2
@@RENTER:
pop cx
sub cx,[FRAME]
jnz @@DCMP ; Continue decompress
@@DONE:
mov ax,[SLEN] ; Uncompressed length.
PopCREGS
PLEAVE
ret
endp
ENDS
END