Commit 960366cf authored by Karsten Keil's avatar Karsten Keil

Add mISDN DSP

Enable support for digital audio processing capability.
This module may be used for special applications that require
cross connecting of bchannels, conferencing, dtmf decoding
echo cancelation, tone generation, and Blowfish encryption and
decryption.
It may use hardware features if available.
Signed-off-by: default avatarKarsten Keil <kkeil@suse.de>
parent 1b2b03f8
......@@ -7,3 +7,21 @@ menuconfig MISDN
help
Enable support for the modular ISDN driver.
if MISDN != n
config MISDN_DSP
tristate "Digital Audio Processing of transparent data"
depends on MISDN
help
Enable support for digital audio processing capability.
This module may be used for special applications that require
cross connecting of bchannels, conferencing, dtmf decoding
echo cancelation, tone generation, and Blowfish encryption and
decryption.
It may use hardware features if available.
E.g. it is required for PBX4Linux. Go to http://isdn.eversberg.eu
and get more informations about this module and it's usage.
If unsure, say 'N'.
source "drivers/isdn/hardware/mISDN/Kconfig"
endif #MISDN
......@@ -3,7 +3,9 @@
#
obj-$(CONFIG_MISDN) += mISDN_core.o
obj-$(CONFIG_MISDN_DSP) += mISDN_dsp.o
# multi objects
mISDN_core-objs := core.o fsm.o socket.o hwchannel.o stack.o layer1.o layer2.o tei.o timerdev.o
mISDN_dsp-objs := dsp_core.o dsp_cmx.o dsp_tones.o dsp_dtmf.o dsp_audio.o dsp_blowfish.o dsp_pipeline.o dsp_hwec.o
/*
* Audio support data for ISDN4Linux.
*
* Copyright 2002/2003 by Andreas Eversberg (jolly@eversberg.eu)
*
* This software may be used and distributed according to the terms
* of the GNU General Public License, incorporated herein by reference.
*
*/
#define DEBUG_DSP_CTRL 0x0001
#define DEBUG_DSP_CORE 0x0002
#define DEBUG_DSP_DTMF 0x0004
#define DEBUG_DSP_CMX 0x0010
#define DEBUG_DSP_TONE 0x0020
#define DEBUG_DSP_BLOWFISH 0x0040
#define DEBUG_DSP_DELAY 0x0100
#define DEBUG_DSP_DTMFCOEFF 0x8000 /* heavy output */
/* options may be:
*
* bit 0 = use ulaw instead of alaw
* bit 1 = enable hfc hardware accelleration for all channels
*
*/
#define DSP_OPT_ULAW (1<<0)
#define DSP_OPT_NOHARDWARE (1<<1)
#include <linux/timer.h>
#include <linux/workqueue.h>
#include "dsp_ecdis.h"
extern int dsp_options;
extern int dsp_debug;
extern int dsp_poll;
extern int dsp_tics;
extern spinlock_t dsp_lock;
extern struct work_struct dsp_workq;
extern u32 dsp_poll_diff; /* calculated fix-comma corrected poll value */
/***************
* audio stuff *
***************/
extern s32 dsp_audio_alaw_to_s32[256];
extern s32 dsp_audio_ulaw_to_s32[256];
extern s32 *dsp_audio_law_to_s32;
extern u8 dsp_audio_s16_to_law[65536];
extern u8 dsp_audio_alaw_to_ulaw[256];
extern u8 dsp_audio_mix_law[65536];
extern u8 dsp_audio_seven2law[128];
extern u8 dsp_audio_law2seven[256];
extern void dsp_audio_generate_law_tables(void);
extern void dsp_audio_generate_s2law_table(void);
extern void dsp_audio_generate_seven(void);
extern void dsp_audio_generate_mix_table(void);
extern void dsp_audio_generate_ulaw_samples(void);
extern void dsp_audio_generate_volume_changes(void);
extern u8 dsp_silence;
/*************
* cmx stuff *
*************/
#define MAX_POLL 256 /* maximum number of send-chunks */
#define CMX_BUFF_SIZE 0x8000 /* must be 2**n (0x1000 about 1/2 second) */
#define CMX_BUFF_HALF 0x4000 /* CMX_BUFF_SIZE / 2 */
#define CMX_BUFF_MASK 0x7fff /* CMX_BUFF_SIZE - 1 */
/* how many seconds will we check the lowest delay until the jitter buffer
is reduced by that delay */
#define MAX_SECONDS_JITTER_CHECK 5
extern struct timer_list dsp_spl_tl;
extern u32 dsp_spl_jiffies;
/* the structure of conferences:
*
* each conference has a unique number, given by user space.
* the conferences are linked in a chain.
* each conference has members linked in a chain.
* each dsplayer points to a member, each member points to a dsplayer.
*/
/* all members within a conference (this is linked 1:1 with the dsp) */
struct dsp;
struct dsp_conf_member {
struct list_head list;
struct dsp *dsp;
};
/* the list of all conferences */
struct dsp_conf {
struct list_head list;
u32 id;
/* all cmx stacks with the same ID are
connected */
struct list_head mlist;
int software; /* conf is processed by software */
int hardware; /* conf is processed by hardware */
/* note: if both unset, has only one member */
};
/**************
* DTMF stuff *
**************/
#define DSP_DTMF_NPOINTS 102
#define ECHOCAN_BUFLEN (4*128)
struct dsp_dtmf {
int treshold; /* above this is dtmf (square of) */
int software; /* dtmf uses software decoding */
int hardware; /* dtmf uses hardware decoding */
int size; /* number of bytes in buffer */
signed short buffer[DSP_DTMF_NPOINTS];
/* buffers one full dtmf frame */
u8 lastwhat, lastdigit;
int count;
u8 digits[16]; /* just the dtmf result */
};
/******************
* pipeline stuff *
******************/
struct dsp_pipeline {
rwlock_t lock;
struct list_head list;
int inuse;
};
/***************
* tones stuff *
***************/
struct dsp_tone {
int software; /* tones are generated by software */
int hardware; /* tones are generated by hardware */
int tone;
void *pattern;
int count;
int index;
struct timer_list tl;
};
/*****************
* general stuff *
*****************/
struct dsp {
struct list_head list;
struct mISDNchannel ch;
struct mISDNchannel *up;
unsigned char name[64];
int b_active;
int echo; /* echo is enabled */
int rx_disabled; /* what the user wants */
int rx_is_off; /* what the card is */
int tx_mix;
struct dsp_tone tone;
struct dsp_dtmf dtmf;
int tx_volume, rx_volume;
/* queue for sending frames */
struct work_struct workq;
struct sk_buff_head sendq;
int hdlc; /* if mode is hdlc */
int data_pending; /* currently an unconfirmed frame */
/* conference stuff */
u32 conf_id;
struct dsp_conf *conf;
struct dsp_conf_member
*member;
/* buffer stuff */
int rx_W; /* current write pos for data without timestamp */
int rx_R; /* current read pos for transmit clock */
int rx_init; /* if set, pointers will be adjusted first */
int tx_W; /* current write pos for transmit data */
int tx_R; /* current read pos for transmit clock */
int rx_delay[MAX_SECONDS_JITTER_CHECK];
int tx_delay[MAX_SECONDS_JITTER_CHECK];
u8 tx_buff[CMX_BUFF_SIZE];
u8 rx_buff[CMX_BUFF_SIZE];
int last_tx; /* if set, we transmitted last poll interval */
int cmx_delay; /* initial delay of buffers,
or 0 for dynamic jitter buffer */
int tx_dejitter; /* if set, dejitter tx buffer */
int tx_data; /* enables tx-data of CMX to upper layer */
/* hardware stuff */
struct dsp_features features;
int features_rx_off; /* set if rx_off is featured */
int pcm_slot_rx; /* current PCM slot (or -1) */
int pcm_bank_rx;
int pcm_slot_tx;
int pcm_bank_tx;
int hfc_conf; /* unique id of current conference (or -1) */
/* encryption stuff */
int bf_enable;
u32 bf_p[18];
u32 bf_s[1024];
int bf_crypt_pos;
u8 bf_data_in[9];
u8 bf_crypt_out[9];
int bf_decrypt_in_pos;
int bf_decrypt_out_pos;
u8 bf_crypt_inring[16];
u8 bf_data_out[9];
int bf_sync;
struct dsp_pipeline
pipeline;
};
/* functions */
extern void dsp_change_volume(struct sk_buff *skb, int volume);
extern struct list_head dsp_ilist;
extern struct list_head conf_ilist;
extern void dsp_cmx_debug(struct dsp *dsp);
extern void dsp_cmx_hardware(struct dsp_conf *conf, struct dsp *dsp);
extern int dsp_cmx_conf(struct dsp *dsp, u32 conf_id);
extern void dsp_cmx_receive(struct dsp *dsp, struct sk_buff *skb);
extern void dsp_cmx_hdlc(struct dsp *dsp, struct sk_buff *skb);
extern void dsp_cmx_send(void *arg);
extern void dsp_cmx_transmit(struct dsp *dsp, struct sk_buff *skb);
extern int dsp_cmx_del_conf_member(struct dsp *dsp);
extern int dsp_cmx_del_conf(struct dsp_conf *conf);
extern void dsp_dtmf_goertzel_init(struct dsp *dsp);
extern void dsp_dtmf_hardware(struct dsp *dsp);
extern u8 *dsp_dtmf_goertzel_decode(struct dsp *dsp, u8 *data, int len,
int fmt);
extern int dsp_tone(struct dsp *dsp, int tone);
extern void dsp_tone_copy(struct dsp *dsp, u8 *data, int len);
extern void dsp_tone_timeout(void *arg);
extern void dsp_bf_encrypt(struct dsp *dsp, u8 *data, int len);
extern void dsp_bf_decrypt(struct dsp *dsp, u8 *data, int len);
extern int dsp_bf_init(struct dsp *dsp, const u8 *key, unsigned int keylen);
extern void dsp_bf_cleanup(struct dsp *dsp);
extern int dsp_pipeline_module_init(void);
extern void dsp_pipeline_module_exit(void);
extern int dsp_pipeline_init(struct dsp_pipeline *pipeline);
extern void dsp_pipeline_destroy(struct dsp_pipeline *pipeline);
extern int dsp_pipeline_build(struct dsp_pipeline *pipeline, const char *cfg);
extern void dsp_pipeline_process_tx(struct dsp_pipeline *pipeline, u8 *data,
int len);
extern void dsp_pipeline_process_rx(struct dsp_pipeline *pipeline, u8 *data,
int len);
This diff is collapsed.
/*
* SpanDSP - a series of DSP components for telephony
*
* biquad.h - General telephony bi-quad section routines (currently this just
* handles canonic/type 2 form)
*
* Written by Steve Underwood <steveu@coppice.org>
*
* Copyright (C) 2001 Steve Underwood
*
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
struct biquad2_state {
int32_t gain;
int32_t a1;
int32_t a2;
int32_t b1;
int32_t b2;
int32_t z1;
int32_t z2;
};
static inline void biquad2_init(struct biquad2_state *bq,
int32_t gain, int32_t a1, int32_t a2, int32_t b1, int32_t b2)
{
bq->gain = gain;
bq->a1 = a1;
bq->a2 = a2;
bq->b1 = b1;
bq->b2 = b2;
bq->z1 = 0;
bq->z2 = 0;
}
static inline int16_t biquad2(struct biquad2_state *bq, int16_t sample)
{
int32_t y;
int32_t z0;
z0 = sample*bq->gain + bq->z1*bq->a1 + bq->z2*bq->a2;
y = z0 + bq->z1*bq->b1 + bq->z2*bq->b2;
bq->z2 = bq->z1;
bq->z1 = z0 >> 15;
y >>= 15;
return y;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*
* DTMF decoder.
*
* Copyright by Andreas Eversberg (jolly@eversberg.eu)
* based on different decoders such as ISDN4Linux
*
* This software may be used and distributed according to the terms
* of the GNU General Public License, incorporated herein by reference.
*
*/
#include <linux/mISDNif.h>
#include <linux/mISDNdsp.h>
#include "core.h"
#include "dsp.h"
#define NCOEFF 8 /* number of frequencies to be analyzed */
/* For DTMF recognition:
* 2 * cos(2 * PI * k / N) precalculated for all k
*/
static u64 cos2pik[NCOEFF] =
{
/* k << 15 (source: hfc-4s/8s documentation (www.colognechip.de)) */
55960, 53912, 51402, 48438, 38146, 32650, 26170, 18630
};
/* digit matrix */
static char dtmf_matrix[4][4] =
{
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
/* dtmf detection using goertzel algorithm
* init function
*/
void dsp_dtmf_goertzel_init(struct dsp *dsp)
{
dsp->dtmf.size = 0;
dsp->dtmf.lastwhat = '\0';
dsp->dtmf.lastdigit = '\0';
dsp->dtmf.count = 0;
}
/* check for hardware or software features
*/
void dsp_dtmf_hardware(struct dsp *dsp)
{
int hardware = 1;
if (!dsp->features.hfc_dtmf)
hardware = 0;
/* check for volume change */
if (dsp->tx_volume) {
if (dsp_debug & DEBUG_DSP_DTMF)
printk(KERN_DEBUG "%s dsp %s cannot do hardware DTMF, "
"because tx_volume is changed\n",
__func__, dsp->name);
hardware = 0;
}
if (dsp->rx_volume) {
if (dsp_debug & DEBUG_DSP_DTMF)
printk(KERN_DEBUG "%s dsp %s cannot do hardware DTMF, "
"because rx_volume is changed\n",
__func__, dsp->name);
hardware = 0;
}
/* check if encryption is enabled */
if (dsp->bf_enable) {
if (dsp_debug & DEBUG_DSP_DTMF)
printk(KERN_DEBUG "%s dsp %s cannot do hardware DTMF, "
"because encryption is enabled\n",
__func__, dsp->name);
hardware = 0;
}
/* check if pipeline exists */
if (dsp->pipeline.inuse) {
if (dsp_debug & DEBUG_DSP_DTMF)
printk(KERN_DEBUG "%s dsp %s cannot do hardware DTMF, "
"because pipeline exists.\n",
__func__, dsp->name);
hardware = 0;
}
dsp->dtmf.hardware = hardware;
dsp->dtmf.software = !hardware;
}
/*************************************************************
* calculate the coefficients of the given sample and decode *
*************************************************************/
/* the given sample is decoded. if the sample is not long enough for a
* complete frame, the decoding is finished and continued with the next
* call of this function.
*
* the algorithm is very good for detection with a minimum of errors. i
* tested it allot. it even works with very short tones (40ms). the only
* disadvantage is, that it doesn't work good with different volumes of both
* tones. this will happen, if accoustically coupled dialers are used.
* it sometimes detects tones during speach, which is normal for decoders.
* use sequences to given commands during calls.
*
* dtmf - points to a structure of the current dtmf state
* spl and len - the sample
* fmt - 0 = alaw, 1 = ulaw, 2 = coefficients from HFC DTMF hw-decoder
*/
u8
*dsp_dtmf_goertzel_decode(struct dsp *dsp, u8 *data, int len, int fmt)
{
u8 what;
int size;
signed short *buf;
s32 sk, sk1, sk2;
int k, n, i;
s32 *hfccoeff;
s32 result[NCOEFF], tresh, treshl;
int lowgroup, highgroup;
s64 cos2pik_;
dsp->dtmf.digits[0] = '\0';
/* Note: The function will loop until the buffer has not enough samples
* left to decode a full frame.
*/
again:
/* convert samples */
size = dsp->dtmf.size;
buf = dsp->dtmf.buffer;
switch (fmt) {
case 0: /* alaw */
case 1: /* ulaw */
while (size < DSP_DTMF_NPOINTS && len) {
buf[size++] = dsp_audio_law_to_s32[*data++];
len--;
}
break;
case 2: /* HFC coefficients */
default:
if (len < 64) {
if (len > 0)
printk(KERN_ERR "%s: coefficients have invalid "
"size. (is=%d < must=%d)\n",
__func__, len, 64);
return dsp->dtmf.digits;
}
hfccoeff = (s32 *)data;
for (k = 0; k < NCOEFF; k++) {
sk2 = (*hfccoeff++)>>4;
sk = (*hfccoeff++)>>4;
if (sk > 32767 || sk < -32767 || sk2 > 32767
|| sk2 < -32767)
printk(KERN_WARNING
"DTMF-Detection overflow\n");
/* compute |X(k)|**2 */
result[k] =
(sk * sk) -
(((cos2pik[k] * sk) >> 15) * sk2) +
(sk2 * sk2);
}
data += 64;
len -= 64;
goto coefficients;
break;
}
dsp->dtmf.size = size;
if (size < DSP_DTMF_NPOINTS)
return dsp->dtmf.digits;
dsp->dtmf.size = 0;
/* now we have a full buffer of signed long samples - we do goertzel */
for (k = 0; k < NCOEFF; k++) {
sk = 0;
sk1 = 0;
sk2 = 0;
buf = dsp->dtmf.buffer;
cos2pik_ = cos2pik[k];
for (n = 0; n < DSP_DTMF_NPOINTS; n++) {
sk = ((cos2pik_*sk1)>>15) - sk2 + (*buf++);
sk2 = sk1;
sk1 = sk;
}
sk >>= 8;
sk2 >>= 8;
if (sk > 32767 || sk < -32767 || sk2 > 32767 || sk2 < -32767)
printk(KERN_WARNING "DTMF-Detection overflow\n");
/* compute |X(k)|**2 */
result[k] =
(sk * sk) -
(((cos2pik[k] * sk) >> 15) * sk2) +
(sk2 * sk2);
}
/* our (squared) coefficients have been calculated, we need to process
* them.
*/
coefficients:
tresh = 0;
for (i = 0; i < NCOEFF; i++) {
if (result[i] < 0)
result[i] = 0;
if (result[i] > dsp->dtmf.treshold) {
if (result[i] > tresh)
tresh = result[i];
}
}
if (tresh == 0) {
what = 0;
goto storedigit;
}
if (dsp_debug & DEBUG_DSP_DTMFCOEFF)
printk(KERN_DEBUG "a %3d %3d %3d %3d %3d %3d %3d %3d"
" tr:%3d r %3d %3d %3d %3d %3d %3d %3d %3d\n",
result[0]/10000, result[1]/10000, result[2]/10000,
result[3]/10000, result[4]/10000, result[5]/10000,
result[6]/10000, result[7]/10000, tresh/10000,
result[0]/(tresh/100), result[1]/(tresh/100),
result[2]/(tresh/100), result[3]/(tresh/100),
result[4]/(tresh/100), result[5]/(tresh/100),
result[6]/(tresh/100), result[7]/(tresh/100));
/* calc digit (lowgroup/highgroup) */
lowgroup = -1;
highgroup = -1;
treshl = tresh >> 3; /* tones which are not on, must be below 9 dB */
tresh = tresh >> 2; /* touchtones must match within 6 dB */
for (i = 0; i < NCOEFF; i++) {
if (result[i] < treshl)
continue; /* ignore */
if (result[i] < tresh) {
lowgroup = -1;
highgroup = -1;
break; /* noise inbetween */
}
/* good level found. This is allowed only one time per group */
if (i < NCOEFF/2) {
/* lowgroup */
if (lowgroup >= 0) {
/* Bad. Another tone found. */
lowgroup = -1;
break;
} else
lowgroup = i;
} else {
/* higroup */
if (highgroup >= 0) {
/* Bad. Another tone found. */
highgroup = -1;
break;
} else
highgroup = i-(NCOEFF/2);
}
}
/* get digit or null */
what = 0;
if (lowgroup >= 0 && highgroup >= 0)
what = dtmf_matrix[lowgroup][highgroup];
storedigit:
if (what && (dsp_debug & DEBUG_DSP_DTMF))
printk(KERN_DEBUG "DTMF what: %c\n", what);
if (dsp->dtmf.lastwhat != what)
dsp->dtmf.count = 0;
/* the tone (or no tone) must remain 3 times without change */
if (dsp->dtmf.count == 2) {
if (dsp->dtmf.lastdigit != what) {
dsp->dtmf.lastdigit = what;
if (what) {
if (dsp_debug & DEBUG_DSP_DTMF)
printk(KERN_DEBUG "DTMF digit: %c\n",
what);
if ((strlen(dsp->dtmf.digits)+1)
< sizeof(dsp->dtmf.digits)) {
dsp->dtmf.digits[strlen(
dsp->dtmf.digits)+1] = '\0';
dsp->dtmf.digits[strlen(
dsp->dtmf.digits)] = what;
}
}
}
} else
dsp->dtmf.count++;
dsp->dtmf.lastwhat = what;
goto again;
}
/*
* SpanDSP - a series of DSP components for telephony
*
* ec_disable_detector.h - A detector which should eventually meet the
* G.164/G.165 requirements for detecting the
* 2100Hz echo cancellor disable tone.
*
* Written by Steve Underwood <steveu@coppice.org>
*
* Copyright (C) 2001 Steve Underwood
*
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include "dsp_biquad.h"
struct ec_disable_detector_state {
struct biquad2_state notch;
int notch_level;
int channel_level;
int tone_present;
int tone_cycle_duration;
int good_cycles;
int hit;
};
#define FALSE 0
#define TRUE (!FALSE)
static inline void
echo_can_disable_detector_init(struct ec_disable_detector_state *det)
{
/* Elliptic notch */
/* This is actually centred at 2095Hz, but gets the balance we want, due
to the asymmetric walls of the notch */
biquad2_init(&det->notch,
(int32_t) (-0.7600000*32768.0),
(int32_t) (-0.1183852*32768.0),
(int32_t) (-0.5104039*32768.0),
(int32_t) (0.1567596*32768.0),
(int32_t) (1.0000000*32768.0));
det->channel_level = 0;
det->notch_level = 0;
det->tone_present = FALSE;
det->tone_cycle_duration = 0;
det->good_cycles = 0;
det->hit = 0;
}
/*- End of function --------------------------------------------------------*/
static inline int
echo_can_disable_detector_update(struct ec_disable_detector_state *det,
int16_t amp)
{
int16_t notched;
notched = biquad2(&det->notch, amp);
/* Estimate the overall energy in the channel, and the energy in
the notch (i.e. overall channel energy - tone energy => noise).
Use abs instead of multiply for speed (is it really faster?).
Damp the overall energy a little more for a stable result.
Damp the notch energy a little less, so we don't damp out the
blip every time the phase reverses */
det->channel_level += ((abs(amp) - det->channel_level) >> 5);
det->notch_level += ((abs(notched) - det->notch_level) >> 4);
if (det->channel_level > 280) {
/* There is adequate energy in the channel.
Is it mostly at 2100Hz? */
if (det->notch_level*6 < det->channel_level) {
/* The notch says yes, so we have the tone. */
if (!det->tone_present) {
/* Do we get a kick every 450+-25ms? */
if (det->tone_cycle_duration >= 425*8
&& det->tone_cycle_duration <= 475*8) {
det->good_cycles++;
if (det->good_cycles > 2)
det->hit = TRUE;
}
det->tone_cycle_duration = 0;
}
det->tone_present = TRUE;
} else
det->tone_present = FALSE;
det->tone_cycle_duration++;
} else {
det->tone_present = FALSE;
det->tone_cycle_duration = 0;
det->good_cycles = 0;
}
return det->hit;
}
/*- End of function --------------------------------------------------------*/
/*- End of file ------------------------------------------------------------*/
This diff is collapsed.
/*
* dsp_hwec.h
*/
extern struct mISDN_dsp_element *dsp_hwec;
extern void dsp_hwec_enable(struct dsp *dsp, const char *arg);
extern void dsp_hwec_disable(struct dsp *dsp);
extern int dsp_hwec_init(void);
extern void dsp_hwec_exit(void);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment