1060 lines
28 KiB
C
1060 lines
28 KiB
C
/* $Id$
|
|
*
|
|
* Linux ISDN subsystem, audio cmx (hardware level).
|
|
*
|
|
* Copyright 2002 by Andreas Eversberg (jolly@jolly.de)
|
|
*
|
|
* This software may be used and distributed according to the terms
|
|
* of the GNU General Public License, incorporated herein by reference.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* The process of adding and removing parties to/from a conference:
|
|
*
|
|
* There is a chain of conference_t which has one or more members in a chain
|
|
* of conf_member_t.
|
|
*
|
|
* After a party is added, the conference is checked for hardware capability.
|
|
* Also if a party is removed, the conference is checked again.
|
|
*
|
|
* There are 3 different solutions: -1 = software, 0 = hardware-crossconnect
|
|
* 1-n = hardware-conference. The n will give the conference number.
|
|
*
|
|
* Depending on the change after removal or insertion of a party, hardware
|
|
* commands are given.
|
|
*
|
|
* The current solution is stored within the conference_t entry.
|
|
*/
|
|
|
|
/* HOW THE CMX WORKS:
|
|
*
|
|
* There are 3 types of interaction: One member is alone, in this case only
|
|
* data flow is done.
|
|
* Two members will also exchange their data so they are crossconnected.
|
|
* Three or more members will be added in a conference and will hear each
|
|
* other but will not receive their own speech (echo) if not enabled.
|
|
*
|
|
* Features of CMX are:
|
|
* - Crossconnecting or even conference, if more than two members are together.
|
|
* - Force mixing of transmit data with other crossconnect/conference members.
|
|
* - Echo generation to benchmark the delay of audio processing.
|
|
* - Use hardware to minimize cpu load, disable FIFO load and minimize delay.
|
|
*
|
|
* There are 3 buffers:
|
|
*
|
|
* The conference buffer
|
|
*
|
|
* R-3 R-2 R-1 W-2 W-1 W-3
|
|
* | | | | | |
|
|
* --+-------+-----+------+------+---+---------------
|
|
* | |
|
|
* W-min W-max
|
|
*
|
|
* The conference buffer is a ring buffer used to mix all data from all members.
|
|
* To compensate the echo, data of individual member will later be substracted
|
|
* before it is sent to that member. Each conference has a W-min and a W-max
|
|
* pointer. Each individual member has a write pointer (W-x) and a read pointer
|
|
* (R-x). W-min shows the lowest value of all W-x. The W-max shows the highest
|
|
* value of all W-x. Whenever data is written, it is mixed by adding to the
|
|
* existing sample value in the buffer. If W-max would increase, the additional
|
|
* range is cleared so old data will be erased in the ring buffer.
|
|
*
|
|
*
|
|
* RX-Buffer
|
|
* R-1 W-1
|
|
* | |
|
|
* ----------------+-------------+-------------------
|
|
*
|
|
* The rx-buffer is a ring buffer used to store the received data for each
|
|
* individual member. To compensate echo, this data will later be substracted
|
|
* from the conference's data before it is sent to that member. If only two
|
|
* members are in one conference, this data is used to get the queued data from
|
|
* the other member.
|
|
*
|
|
*
|
|
* TX-Buffer
|
|
* R W
|
|
* | |
|
|
* -----------------+--------+-----------------------
|
|
*
|
|
* The tx-buffer is a ring buffer to queue the transmit data from user space
|
|
* until it will be mixed or sent. There are two pointers, R and W. If the write
|
|
* pointer W would reach or overrun R, the buffer would overrun. In this case
|
|
* (some) data is dropped so that it will not overrun.
|
|
*
|
|
*
|
|
* If a member joins a conference:
|
|
*
|
|
* - If a member joins, its rx_buff is set to silence.
|
|
* - If the conference reaches three members, the conf-buffer is cleared.
|
|
* - When a member is joined, it will set its write and read pointer to W_max.
|
|
*
|
|
* The procedure of received data from card is explained in cmx_receive.
|
|
* The procedure of received data from user space is explained in cmx_transmit.
|
|
*
|
|
*
|
|
* LIMITS:
|
|
*
|
|
* The max_queue value is 2* the samples of largest packet ever received by any
|
|
* conference member from her card. It also changes during life of conference.
|
|
*
|
|
*
|
|
* AUDIO PROCESS:
|
|
*
|
|
* Writing data to conference's and member's buffer is done by adding the sample
|
|
* value to the existing ring buffer. Writing user space data to the member's
|
|
* buffer is done by substracting the sample value from the existing ring
|
|
* buffer.
|
|
*
|
|
*
|
|
* Interaction with other features:
|
|
*
|
|
* DTMF:
|
|
* DTMF decoding is done before the data is crossconnected.
|
|
*
|
|
* Volume change:
|
|
* Changing rx-volume is done before the data is crossconnected. The tx-volume
|
|
* must be changed whenever data is transmitted to the card by the cmx.
|
|
*
|
|
* Tones:
|
|
* If a tone is enabled, it will be processed whenever data is transmitted to
|
|
* the card. It will replace the tx-data from the user space.
|
|
* If tones are generated by hardware, this conference member is removed for
|
|
* this time.
|
|
*
|
|
* Disable rx-data:
|
|
* If cmx is realized in hardware, rx data will be disabled if requested by
|
|
* the upper layer. If dtmf decoding is done by software and enabled, rx data
|
|
* will not be diabled but blocked to the upper layer.
|
|
*
|
|
* HFC conference engine:
|
|
* If it is possible to realize all features using hardware, hardware will be
|
|
* used if not forbidden by control command. Disabling rx-data provides
|
|
* absolutely traffic free audio processing. (except for the quick 1-frame
|
|
* upload of a tone loop, only once for a new tone)
|
|
*
|
|
*/
|
|
|
|
#include <linux/vmalloc.h>
|
|
#include "layer1.h"
|
|
#include "helper.h"
|
|
#include "debug.h"
|
|
#include "dsp.h"
|
|
|
|
//#define CMX_DEBUG /* massive read/write pointer output */
|
|
|
|
conference_t *Conf_list = NULL;
|
|
|
|
/*
|
|
* debug cmx memory structure
|
|
*/
|
|
void
|
|
dsp_cmx_debug(dsp_t *dsp)
|
|
{
|
|
conference_t *conf;
|
|
conf_member_t *member;
|
|
dsp_t *odsp;
|
|
|
|
printk(KERN_DEBUG "-----Current DSP\n");
|
|
odsp = (dsp_t *)dsp_obj.ilist;
|
|
while(odsp)
|
|
{
|
|
printk(KERN_DEBUG "* %s echo=%d txmix=%d", odsp->inst.name, odsp->echo, odsp->tx_mix);
|
|
if (odsp->conf)
|
|
printk(" (Conf %d)", odsp->conf->id);
|
|
if (dsp == odsp)
|
|
printk(" *this*");
|
|
printk("\n");
|
|
odsp = odsp->next;
|
|
}
|
|
|
|
printk(KERN_DEBUG "-----Current Conf:\n");
|
|
conf = Conf_list;
|
|
while(conf)
|
|
{
|
|
printk(KERN_DEBUG "* Conf %d (0x%lx) solution=%d\n", conf->id, (unsigned long)conf, conf->solution);
|
|
member = conf->mlist;
|
|
while(member)
|
|
{
|
|
printk(KERN_DEBUG " - member = %s %s\n", member->dsp->inst.name, (member->dsp==dsp)?" *this*":"");
|
|
member = member->next;
|
|
}
|
|
conf = conf->next;
|
|
}
|
|
printk(KERN_DEBUG "-----end\n");
|
|
}
|
|
|
|
/*
|
|
* search conference
|
|
*/
|
|
static conference_t
|
|
*dsp_cmx_search_conf(unsigned long id)
|
|
{
|
|
conference_t *conf;
|
|
|
|
if (!id) {
|
|
printk(KERN_WARNING "%s: conference ID is 0.\n",
|
|
__FUNCTION__);
|
|
return(NULL);
|
|
}
|
|
|
|
/* search conference */
|
|
conf = Conf_list;
|
|
while(conf) {
|
|
if (conf->id == id)
|
|
return(conf);
|
|
conf = conf->next;
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* add member to conference
|
|
*/
|
|
static int
|
|
dsp_cmx_add_conf_member(dsp_t *dsp, conference_t *conf)
|
|
{
|
|
conf_member_t *member;
|
|
|
|
if (!conf || !dsp) {
|
|
printk(KERN_WARNING "%s: conf or dsp is 0.\n", __FUNCTION__);
|
|
return(-EINVAL);
|
|
}
|
|
if (dsp->member) {
|
|
printk(KERN_WARNING "%s: dsp is already member in a conf.\n",
|
|
__FUNCTION__);
|
|
return(-EINVAL);
|
|
}
|
|
|
|
if (dsp->conf) {
|
|
printk(KERN_WARNING "%s: dsp is already in a conf.\n",
|
|
__FUNCTION__);
|
|
return(-EINVAL);
|
|
}
|
|
|
|
if (!(member = vmalloc(sizeof(conf_member_t)))) {
|
|
printk(KERN_ERR "vmalloc conf_member_t failed\n");
|
|
return(-ENOMEM);
|
|
}
|
|
memset(member, 0, sizeof(conf_member_t));
|
|
memset(dsp->rx_buff, silence, sizeof(dsp->rx_buff));
|
|
member->dsp = dsp;
|
|
/* set initial values */
|
|
dsp->W_rx = conf->W_max;
|
|
dsp->R_rx = conf->W_max;
|
|
|
|
APPEND_TO_LIST(member, ((conf_member_t *)conf->mlist));
|
|
|
|
/* zero conf-buffer if we change from 2 to 3 members */
|
|
if (conf->mlist->next) if (!conf->mlist->next->next)
|
|
memset(conf->conf_buff, 0, sizeof(conf->conf_buff));
|
|
|
|
dsp->conf = conf;
|
|
dsp->member = member;
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
/*
|
|
* del member from conference
|
|
*/
|
|
int
|
|
dsp_cmx_del_conf_member(dsp_t *dsp)
|
|
{
|
|
conf_member_t *member;
|
|
|
|
if (!dsp) {
|
|
printk(KERN_WARNING "%s: dsp is 0.\n",
|
|
__FUNCTION__);
|
|
return(-EINVAL);
|
|
}
|
|
|
|
if (!dsp->conf) {
|
|
printk(KERN_WARNING "%s: dsp is not in a conf.\n",
|
|
__FUNCTION__);
|
|
return(-EINVAL);
|
|
}
|
|
|
|
member = dsp->conf->mlist;
|
|
if (!member) {
|
|
printk(KERN_WARNING "%s: dsp has linked an empty conf.\n",
|
|
__FUNCTION__);
|
|
return(-EINVAL);
|
|
}
|
|
|
|
/* find us in conf */
|
|
while(member) {
|
|
if (member->dsp == dsp)
|
|
break;
|
|
member = member->next;
|
|
}
|
|
if (!member) {
|
|
printk(KERN_WARNING "%s: dsp is not present in its own conf_meber list.\n",
|
|
__FUNCTION__);
|
|
return(-EINVAL);
|
|
}
|
|
|
|
REMOVE_FROM_LISTBASE(member, ((conf_member_t *)dsp->conf->mlist));
|
|
vfree(member);
|
|
dsp->conf = NULL;
|
|
dsp->member = NULL;
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
/*
|
|
* new conference
|
|
*/
|
|
static conference_t
|
|
*dsp_cmx_new_conf(unsigned long id)
|
|
{
|
|
conference_t *conf;
|
|
|
|
if (!id) {
|
|
printk(KERN_WARNING "%s: id is 0.\n",
|
|
__FUNCTION__);
|
|
return(NULL);
|
|
}
|
|
|
|
if (!(conf = vmalloc(sizeof(conference_t)))) {
|
|
printk(KERN_ERR "vmalloc conference_t failed\n");
|
|
return(NULL);
|
|
}
|
|
memset(conf, 0, sizeof(conference_t));
|
|
conf->id = id;
|
|
conf->solution = -1;
|
|
|
|
APPEND_TO_LIST(conf, ((conference_t *)Conf_list));
|
|
|
|
return(conf);
|
|
}
|
|
|
|
|
|
/*
|
|
* del conference
|
|
*/
|
|
int
|
|
dsp_cmx_del_conf(conference_t *conf)
|
|
{
|
|
if (!conf) {
|
|
printk(KERN_WARNING "%s: conf is null.\n",
|
|
__FUNCTION__);
|
|
return(-EINVAL);
|
|
}
|
|
|
|
if (conf->mlist) {
|
|
printk(KERN_WARNING "%s: conf not empty.\n",
|
|
__FUNCTION__);
|
|
return(-EINVAL);
|
|
}
|
|
|
|
REMOVE_FROM_LISTBASE(conf, ((conference_t *)Conf_list));
|
|
vfree(conf);
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
/*
|
|
* check if conference members are on an HFC-multi hardware
|
|
*
|
|
* analyse the members and check if they can be realized in hardware
|
|
* also check if we have a conference available, which is limited to 8
|
|
* dsp - one memeber (our own)
|
|
* connect - the conference ID
|
|
*
|
|
* returns
|
|
* -1 = not possible or only one member, no need for hardware
|
|
* 0 = only two members, we can do it without restrictions
|
|
* 1-8 = more than three members, the given conference number is available
|
|
*/
|
|
static int
|
|
dsp_cmx_hfc(conference_t *conf, int debug)
|
|
{
|
|
conf_member_t *member;
|
|
conference_t *conf_temp;
|
|
int memb = 0, unit = 0, i;
|
|
int freeunits[8] = {0,0,0,0,0,0,0,0};
|
|
|
|
if(!conf)
|
|
return(-1);
|
|
|
|
member = conf->mlist;
|
|
/* check all members in our conference */
|
|
while(member) {
|
|
/* check if member uses mixing */
|
|
if (member->dsp->tx_mix)
|
|
return(-1);
|
|
#ifdef WITH_HARDWARE
|
|
/* check if member changes volume at an not suppoted level */
|
|
if (member->dsp.tx_volume )
|
|
to be done: welche lautstärken werden vom hfc unterstützt
|
|
if (member->dsp.rx_volume )
|
|
}
|
|
#endif
|
|
/* check if member is not on an HFC based card */
|
|
if (!member->dsp->hfc_id)
|
|
return(-1);
|
|
/* check if relations are not on the same card */
|
|
if (member->dsp->hfc_id != conf->mlist->dsp->hfc_id)
|
|
return(-1);
|
|
|
|
member = member->next;
|
|
memb++;
|
|
}
|
|
|
|
/* if we have less than two members, we don't need any hardware */
|
|
if (memb < 2)
|
|
return(-1);
|
|
|
|
/* ok, now we are sure that all members are on the same card.
|
|
* now we will see if we have only two members, so we can do
|
|
* crossconnections, which don't have any limitations.
|
|
*/
|
|
|
|
/* if we have only two members, we return 0 */
|
|
if (memb == 2)
|
|
return(0);
|
|
|
|
/* if we have more than two, we may check if we have a conference
|
|
* unit available on the chip.
|
|
*/
|
|
conf_temp = Conf_list;
|
|
while(conf_temp) {
|
|
if (conf_temp != conf) { /* check only other conferences */
|
|
if (conf->hfc_id == conf_temp->hfc_id) {
|
|
if (conf_temp->solution > 8) {
|
|
printk(KERN_WARNING "%s: unit(%d) of conference %d out of range.\n", __FUNCTION__, conf_temp->solution, conf_temp->id);
|
|
return(-1);
|
|
}
|
|
if (conf_temp->solution > 0) {
|
|
if (freeunits[conf_temp->solution-1]) {
|
|
printk(KERN_WARNING "%s: unit(%d) of conference %d was used by another conference(%d).\n", __FUNCTION__, conf_temp->solution, conf_temp->id, freeunits[conf_temp->solution-1]);
|
|
return(-1);
|
|
}
|
|
freeunits[conf_temp->solution-1] = conf_temp->id;
|
|
unit++;
|
|
}
|
|
}
|
|
}
|
|
conf_temp = conf_temp->next;
|
|
}
|
|
if (debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "%s: currently there are %d other conf units in use.\n", __FUNCTION__, unit);
|
|
if (unit > 8) {
|
|
printk(KERN_WARNING "%s: too many units(%d) in use.\n", __FUNCTION__, unit);
|
|
return(-1);
|
|
}
|
|
/* return the current unit, if available */
|
|
if (conf->solution > 0)
|
|
return(conf->solution);
|
|
/* return the free unit number */
|
|
i = 0;
|
|
while(i < 8)
|
|
if (unit > 8) {
|
|
if (freeunits[i] == 0)
|
|
return(i+1);
|
|
i++;
|
|
}
|
|
|
|
return(-1);
|
|
}
|
|
|
|
|
|
/*
|
|
* send crossconnect message to hfc card
|
|
*/
|
|
void
|
|
dsp_cmx_hfc_cross_message(dsp_t *dsp1, dsp_t *dsp2, int enable)
|
|
{
|
|
#ifdef WITH_HARDWARE
|
|
to be done!
|
|
#endif
|
|
}
|
|
|
|
void
|
|
dsp_cmx_hfc_conf_message(dsp_t *dsp, int num)
|
|
{
|
|
#ifdef WITH_HARDWARE
|
|
to be done!
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
* add or remove party to the conference and check hardware capability
|
|
*
|
|
*
|
|
* the cmx will handle audio data via software process. if it is possible
|
|
* to do it in hardware, the hardware feature is used.
|
|
*
|
|
* dsp - the layer object
|
|
* connect - conference ID to connect to (0 = remove from any conference)
|
|
*/
|
|
int
|
|
dsp_cmx(dsp_t *dsp)
|
|
{
|
|
int err;
|
|
int old, new;
|
|
conf_member_t *member, *member1, *member2;
|
|
conference_t *conf;
|
|
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "dsp_cmx called with conf_id=%d\n",
|
|
dsp->conf_id);
|
|
|
|
/* check if we are not in a conf */
|
|
if (!dsp->conf) {
|
|
if (!dsp->conf_id)
|
|
return(0);
|
|
goto add_to_conf;
|
|
}
|
|
|
|
if (dsp->conf_id && dsp->b_active) {
|
|
/* if conference changes, we need to remove first */
|
|
if (dsp->conf_id != dsp->conf->id)
|
|
goto remove_from_conf;
|
|
else
|
|
return(0); /* nothing changed */
|
|
}
|
|
|
|
/* see if we get removed from a conference */
|
|
if (!dsp->conf_id || !dsp->b_active) {
|
|
remove_from_conf:
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "removing us from conference %d\n",
|
|
dsp->conf->id);
|
|
conf = dsp->conf;
|
|
old = conf->solution;
|
|
member1 = NULL;
|
|
member2 = NULL;
|
|
if (conf->mlist)
|
|
if (conf->mlist->next)
|
|
if (!conf->mlist->next->next) {
|
|
member1 = conf->mlist;
|
|
member2 = conf->mlist->next;
|
|
}
|
|
/* remove member from conference */
|
|
err = dsp_cmx_del_conf_member(dsp);
|
|
if (err)
|
|
return(err);
|
|
new = dsp_cmx_hfc(conf, dsp->debug);
|
|
/* check changes in hardware settings */
|
|
if (new>0 && old>0)
|
|
{
|
|
/* only we will get removed */
|
|
dsp_cmx_hfc_conf_message(dsp, 0);
|
|
}
|
|
if (new<=0 && old>0) {
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "hw conference not needed anymore, so we remove it.\n");
|
|
/* conferrence is not needed anymore */
|
|
member = conf->mlist;
|
|
while(member) {
|
|
dsp_cmx_hfc_conf_message(member->dsp, 0);
|
|
member = member->next;
|
|
}
|
|
conf->solution = -1; /* software from now on */
|
|
conf->hfc_id = 0; /* conf is not supported by a hfc chip */
|
|
}
|
|
if (new<0 && old==0) {
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "hw crossconnect not needed anymore, so we remove it.\n");
|
|
/* crossconnect is not needed anymore */
|
|
if (!member1 || !member2) {
|
|
only2members:
|
|
printk(KERN_ERR "%s: fatal error. expecting exactly two crossconnected members.\n", __FUNCTION__);
|
|
return(-EINVAL);
|
|
}
|
|
dsp_cmx_hfc_cross_message(member1->dsp, member2->dsp, 0);
|
|
conf->solution = -1; /* software from now on */
|
|
conf->hfc_id = 0; /* conf is not supported by a hfc chip */
|
|
}
|
|
if (new==0) {
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "hw crossconnect has become possible.\n");
|
|
/* crossconnect is now possible */
|
|
member = conf->mlist;
|
|
if (!member)
|
|
goto only2members;
|
|
if (!member->next)
|
|
goto only2members;
|
|
if (member->next->next)
|
|
goto only2members;
|
|
dsp_cmx_hfc_cross_message(member->dsp, member->next->dsp, 1);
|
|
conf->solution = 0; /* hard crossconnect from now on */
|
|
conf->hfc_id = dsp->hfc_id; /*cross is supported by hfc chip*/
|
|
}
|
|
|
|
if (!conf->mlist)
|
|
{
|
|
err = dsp_cmx_del_conf(conf);
|
|
if (err)
|
|
return(err);
|
|
}
|
|
}
|
|
|
|
add_to_conf:
|
|
/* see if we get connected to a conference */
|
|
if (dsp->conf_id && dsp->b_active) {
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "searching conference %d\n",
|
|
dsp->conf_id);
|
|
conf = dsp_cmx_search_conf(dsp->conf_id);
|
|
if (!conf)
|
|
{
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "conference doesn't exist yet, creating.\n");
|
|
/* the conference doesn't exist, so we create */
|
|
conf = dsp_cmx_new_conf(dsp->conf_id);
|
|
if (!conf)
|
|
return(-EINVAL);
|
|
}
|
|
member1 = NULL;
|
|
member2 = NULL;
|
|
if (conf->mlist)
|
|
if (conf->mlist->next)
|
|
if (!conf->mlist->next->next) {
|
|
member1 = conf->mlist;
|
|
member2 = conf->mlist->next;
|
|
}
|
|
/* add conference member */
|
|
err = dsp_cmx_add_conf_member(dsp, conf);
|
|
if (err)
|
|
return(err);
|
|
|
|
/* if we are alone, we do nothing! */
|
|
if (!conf->next) {
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "we are alone in this conference, so exit.\n");
|
|
return(0);
|
|
}
|
|
|
|
/* check changes in hardware settings */
|
|
new = dsp_cmx_hfc(conf, dsp->debug);
|
|
old = conf->solution;
|
|
if (new<=0 && old>0) {
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "hw conference has become too complex, so we remove it.\n");
|
|
/* conferrence will get removed due to complexity of conference */
|
|
member = conf->mlist;
|
|
while(member) {
|
|
dsp_cmx_hfc_conf_message(member->dsp, 0);
|
|
member = member->next;
|
|
}
|
|
conf->solution = -1; /* software from now on */
|
|
conf->hfc_id = 0; /* conf is not supported by a hfc chip */
|
|
}
|
|
if (new!=0 && old==0) {
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "hw crossconnect has become too complex, so we remove simple crossconnect.\n");
|
|
/* crossconnect will get removed due to complexity of conference */
|
|
if (!member1 || !member2)
|
|
goto only2members;
|
|
dsp_cmx_hfc_cross_message(member1->dsp, member2->dsp, 0);
|
|
conf->solution = -1; /* software from now on */
|
|
conf->hfc_id = 0; /* conf is not supported by a hfc chip */
|
|
}
|
|
if (new>0) {
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "hw conference becomes possible.\n");
|
|
/* conference is now possible */
|
|
member = conf->mlist;
|
|
while(member) {
|
|
dsp_cmx_hfc_conf_message(member->dsp, new);
|
|
member = member->next;
|
|
}
|
|
conf->solution = new; /* conference from now on */
|
|
conf->hfc_id = dsp->hfc_id; /*conf is supported by hfc chip*/
|
|
}
|
|
if (new==0) {
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "hw crossconnect has become possible.\n");
|
|
/* crossconnect is now possible */
|
|
member = conf->mlist;
|
|
if (!member)
|
|
goto only2members;
|
|
if (!member->next)
|
|
goto only2members;
|
|
if (member->next->next)
|
|
goto only2members;
|
|
dsp_cmx_hfc_cross_message(member->dsp, member->next->dsp, 1);
|
|
conf->solution = 0; /* hard crossconnect from now on */
|
|
conf->hfc_id = dsp->hfc_id; /*cross is supported by hfc chip*/
|
|
}
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
/*
|
|
* audio data is received from card
|
|
*/
|
|
|
|
void
|
|
dsp_cmx_receive(dsp_t *dsp, struct sk_buff *skb)
|
|
{
|
|
conference_t *conf = dsp->conf;
|
|
conf_member_t *member;
|
|
signed long *c;
|
|
unsigned char *d, *p;
|
|
int len = skb->len;
|
|
int w, ww, i, ii;
|
|
int W_min, W_max;
|
|
|
|
/* check if we have sompen */
|
|
if (len < 1)
|
|
return;
|
|
|
|
/* -> if length*2 is greater largest */
|
|
if (dsp->largest < (len<<1))
|
|
dsp->largest = (len<<1);
|
|
|
|
/* half of the buffer should be 4 time larger than maximum packet size */
|
|
if (len >= CMX_BUFF_HALF>>2) {
|
|
printk(KERN_ERR "%s line %d: packet from card is too large (%d bytes). please make card send smaller packets OR increase CMX_BUFF_SIZE\n", __FILE__, __LINE__, len);
|
|
return;
|
|
}
|
|
|
|
/* STEP 1: WRITE DOWN WHAT WE GOT (into the buffer(s) */
|
|
|
|
/* -> new W-min is calculated:
|
|
* W_min will be the write pointer of this dsp (after writing 'len'
|
|
* of bytes).
|
|
* If there are other members in a conference, W_min will be the
|
|
* lowest of all member's writer pointers.
|
|
*/
|
|
W_min = (dsp->W_rx + len) & CMX_BUFF_MASK;
|
|
if (conf) {
|
|
/* -> who is larger? dsp or conf */
|
|
if (conf->largest < dsp->largest)
|
|
conf->largest = dsp->largest;
|
|
if (conf->largest > dsp->largest)
|
|
dsp->largest = conf->largest;
|
|
|
|
member = conf->mlist;
|
|
while(member) {
|
|
if (member != dsp->member)
|
|
/* if W_rx is lower */
|
|
if (((member->dsp->W_rx - W_min) & CMX_BUFF_MASK) >= CMX_BUFF_HALF)
|
|
W_min = member->dsp->W_rx;
|
|
member = member->next;
|
|
}
|
|
/* store for dsp_cmx_send */
|
|
conf->W_min = W_min;
|
|
}
|
|
/* -> new W-max is calculated:
|
|
* W_max will be the highest write pointer in the conference.
|
|
*/
|
|
W_max = W_min;
|
|
if (conf) {
|
|
/* if conf->W_max is higher */
|
|
if (((W_max - conf->W_max) & CMX_BUFF_MASK) >= CMX_BUFF_HALF)
|
|
W_max = conf->W_max;
|
|
// W_max = conf->W_max;
|
|
// /* if W_rx+len is higher */
|
|
// if (((W_max - dsp->W_rx - len) & CMX_BUFF_MASK) >= CMX_BUFF_HALF)
|
|
// W_max = (dsp->W_rx + len) & CMX_BUFF_MASK;
|
|
}
|
|
|
|
#ifdef CMX_DEBUG
|
|
printk(KERN_DEBUG "cmx_receive(dsp=%lx): W_rx(dsp)=%05x W_min=%05x W_max=%05x largest=%05x %s\n", dsp, dsp->W_rx, W_min, W_max, dsp->largest, dsp->inst.name);
|
|
#endif
|
|
|
|
/* -> if data is not too fast (exceed maximum queue):
|
|
* data is written if W_max is not too high (largest).
|
|
* W_max will be increased if we are the fastest writer, but if we
|
|
* are not too far off the slowest member, which is W_min.
|
|
* if we are alone, we are always the fastest and the slowest, so we
|
|
* always write.
|
|
*/
|
|
if (((W_max - W_min) & CMX_BUFF_MASK) <= dsp->largest) {
|
|
/* -> received data is written to rx-buffer */
|
|
p = skb->data;
|
|
d = dsp->rx_buff;
|
|
w = dsp->W_rx;
|
|
i = 0;
|
|
ii = len;
|
|
while(i < ii) {
|
|
d[w++ & CMX_BUFF_MASK] = *p++;
|
|
i++;
|
|
}
|
|
/* -> if conference has three or more members */
|
|
if (conf) if (conf->next) if (conf->next->next) {
|
|
/* -> received data is added to conf-buffer
|
|
* new space is overwritten */
|
|
p = skb->data;
|
|
c = conf->conf_buff;
|
|
w = dsp->W_rx;
|
|
ww = conf->W_max;
|
|
i = 0;
|
|
ii = len;
|
|
/* loop until done or old W_max is reached */
|
|
while(i<ii && w!=ww) {
|
|
c[w] += dsp_audio_law_to_s32[*p++]; /* add to existing */
|
|
w = (w+1) & CMX_BUFF_MASK;
|
|
i++;
|
|
}
|
|
/* loop the rest */
|
|
while(i < ii) {
|
|
c[w] = dsp_audio_law_to_s32[*p++]; /* write to new */
|
|
w = (w+1) & CMX_BUFF_MASK;
|
|
i++;
|
|
}
|
|
}
|
|
/* -> write new W_max and W_rx */
|
|
if (conf)
|
|
conf->W_max = W_max;
|
|
dsp->W_rx = (dsp->W_rx + len) & CMX_BUFF_MASK;
|
|
} else {
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "CMX: receiving too fast (rx_buff).\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* send mixed audio data to card
|
|
*/
|
|
|
|
struct sk_buff
|
|
*dsp_cmx_send(dsp_t *dsp, int len, int dinfo)
|
|
{
|
|
conference_t *conf = dsp->conf;
|
|
dsp_t *member, *other;
|
|
register signed long sample;
|
|
signed long *c;
|
|
unsigned char *d, *o, *p, *q;
|
|
struct sk_buff *nskb;
|
|
int r, rr, t, tt;
|
|
|
|
/* PREPARE RESULT */
|
|
nskb = alloc_skb(len, GFP_ATOMIC);
|
|
if (!nskb) {
|
|
printk(KERN_ERR "FATAL ERROR in mISDN_dsp.o: cannot alloc %d bytes\n", len);
|
|
return(NULL);
|
|
}
|
|
mISDN_sethead(PH_DATA | REQUEST, dinfo, nskb);
|
|
/* set pointers, indexes and stuff */
|
|
member = dsp;
|
|
p = dsp->tx_buff; /* transmit data */
|
|
q = dsp->rx_buff; /* received data */
|
|
d = skb_put(nskb, len); /* result */
|
|
t = dsp->R_tx; /* tx-pointers */
|
|
tt = dsp->W_tx;
|
|
r = dsp->R_rx; /* rx-pointers */
|
|
if (conf)
|
|
/* W_min is also limit for read */
|
|
rr = conf->W_min;
|
|
else
|
|
rr = dsp->W_rx;
|
|
/* calculate actual r (if r+len would overrun rr) */
|
|
if (((rr - r - len) & CMX_BUFF_MASK) >= CMX_BUFF_HALF) {
|
|
/* r is set "len" bytes before W_min */
|
|
r = (rr - len) & CMX_BUFF_MASK;
|
|
if (dsp->debug & DEBUG_DSP_CMX)
|
|
printk(KERN_DEBUG "CMX: sending too fast (tx_buff).\n");
|
|
} else
|
|
/* rr is set "len" bytes after R_rx */
|
|
rr = (r + len) & CMX_BUFF_MASK;
|
|
dsp->R_rx = rr;
|
|
/* now: rr is exactly "len" bytes after r now */
|
|
#ifdef CMX_DEBUG
|
|
printk(KERN_DEBUG "CMX_SEND(dsp=%lx) %d bytes from tx:0x%05x-0x%05x rx:0x%05x-0x%05x echo=%d %s\n", dsp, len, t, tt, r, rr, dsp->echo, dsp->inst.name);
|
|
#endif
|
|
|
|
/* STEP 2.0: PROCESS TONES/TX-DATA ONLY */
|
|
if (dsp->tone.tone) {
|
|
/* -> copy tone */
|
|
dsp_tone_copy(dsp, d, len);
|
|
dsp->R_tx = dsp->W_tx = 0; /* clear tx buffer */
|
|
return(nskb);
|
|
}
|
|
/* if we have tx-data but do not use mixing */
|
|
if (!dsp->tx_mix && t!=tt) {
|
|
/* -> send tx-data and continue when not enough */
|
|
while(r!=rr && t!=tt) {
|
|
*d++ = p[t]; /* write tx_buff */
|
|
t = (t+1) & CMX_BUFF_MASK;
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
if(r == rr) {
|
|
dsp->R_tx = t;
|
|
return(nskb);
|
|
}
|
|
}
|
|
/* STEP 2.1: PROCESS DATA (one member / no conf) */
|
|
if (!conf) {
|
|
single:
|
|
/* -> if echo is NOT enabled */
|
|
if (!dsp->echo) {
|
|
/* -> send tx-data if available or use 0-volume */
|
|
while(r!=rr && t!=tt) {
|
|
*d++ = p[t]; /* write tx_buff */
|
|
t = (t+1) & CMX_BUFF_MASK;
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
if(r != rr)
|
|
memset(d, silence, (rr-r)&CMX_BUFF_MASK);
|
|
/* -> if echo is enabled */
|
|
} else {
|
|
//printk(KERN_DEBUG "echo krach:%x %x %x %x ulaw=%d\n", r, rr, t, tt,dsp->ulaw);
|
|
/* -> mix tx-data with echo if available, or use echo only */
|
|
while(r!=rr && t!=tt) {
|
|
*d++ = dsp_audio_mix_law[(p[t]<<8)|q[r]];
|
|
t = (t+1) & CMX_BUFF_MASK;
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
while(r != rr) {
|
|
*d++ = q[r]; /* echo */
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
}
|
|
dsp->R_tx = t;
|
|
return(nskb);
|
|
}
|
|
if (!conf->mlist->next) {
|
|
goto single;
|
|
}
|
|
/* STEP 2.2: PROCESS DATA (two members) */
|
|
if (!conf->mlist->next->next) {
|
|
/* "other" becomes other party */
|
|
other = conf->mlist->dsp;
|
|
if (other == member)
|
|
other = conf->mlist->next->dsp;
|
|
o = other->rx_buff; /* received data */
|
|
/* -> if echo is NOT enabled */
|
|
if (!dsp->echo) {
|
|
/* -> copy other member's rx-data, if tx-data is available, mix */
|
|
while(r!=rr && t!=tt) {
|
|
*d++ = dsp_audio_mix_law[(p[t]<<8)|o[r]];
|
|
t = (t+1) & CMX_BUFF_MASK;
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
while(r != rr) {
|
|
*d++ = o[r];
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
/* -> if echo is enabled */
|
|
} else {
|
|
/* -> mix other member's rx-data with echo, if tx-data is available, mix */
|
|
while(r!=rr && t!=tt) {
|
|
sample = dsp_audio_law_to_s32[p[t]] + dsp_audio_law_to_s32[o[r]] + dsp_audio_law_to_s32[q[r]];
|
|
if (sample < -32768)
|
|
sample = -32768;
|
|
else if (sample > 32767)
|
|
sample = 32767;
|
|
*d++ = dsp_audio_s16_to_law[sample & 0xffff]; /* tx-data + rx_data + echo */
|
|
t = (t+1) & CMX_BUFF_MASK;
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
while(r != rr) {
|
|
*d++ = dsp_audio_mix_law[(o[r]<<8)|q[r]];
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
}
|
|
dsp->R_tx = t;
|
|
return(nskb);
|
|
}
|
|
/* STEP 2.3: PROCESS DATA (three or more members) */
|
|
c = conf->conf_buff;
|
|
/* -> if echo is NOT enabled */
|
|
if (!dsp->echo) {
|
|
/* -> substract rx-data from conf-data, if tx-data is available, mix */
|
|
while(r!=rr && t!=tt) {
|
|
sample = dsp_audio_law_to_s32[p[t]] + c[r] - dsp_audio_law_to_s32[q[r]];
|
|
if (sample < -32768)
|
|
sample = -32768;
|
|
else if (sample > 32767)
|
|
sample = 32767;
|
|
*d++ = dsp_audio_s16_to_law[sample & 0xffff]; /* conf-rx+tx */
|
|
t = (t+1) & CMX_BUFF_MASK;
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
while(r != rr) {
|
|
sample = c[r] - dsp_audio_law_to_s32[q[r]];
|
|
if (sample < -32768)
|
|
sample = -32768;
|
|
else if (sample > 32767)
|
|
sample = 32767;
|
|
*d++ = dsp_audio_s16_to_law[sample & 0xffff]; /* conf-rx */
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
/* -> if echo is enabled */
|
|
} else {
|
|
/* -> encode conf-data, if tx-data is available, mix */
|
|
while(r!=rr && t!=tt) {
|
|
sample = dsp_audio_law_to_s32[p[t]] + c[r];
|
|
if (sample < -32768)
|
|
sample = -32768;
|
|
else if (sample > 32767)
|
|
sample = 32767;
|
|
*d++ = dsp_audio_s16_to_law[sample & 0xffff]; /* conf(echo)+tx */
|
|
t = (t+1) & CMX_BUFF_MASK;
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
while(r != rr) {
|
|
sample = c[r];
|
|
if (sample < -32768)
|
|
sample = -32768;
|
|
else if (sample > 32767)
|
|
sample = 32767;
|
|
*d++ = dsp_audio_s16_to_law[sample & 0xffff]; /* conf(echo) */
|
|
r = (r+1) & CMX_BUFF_MASK;
|
|
}
|
|
}
|
|
dsp->R_tx = t;
|
|
return(nskb);
|
|
}
|
|
|
|
/*
|
|
* audio data is transmitted from upper layer to the dsp
|
|
*/
|
|
void
|
|
dsp_cmx_transmit(dsp_t *dsp, struct sk_buff *skb)
|
|
{
|
|
u_int w, ww;
|
|
unsigned char *d, *p;
|
|
int space;
|
|
|
|
/* check if we have sompen */
|
|
if (skb->len < 1)
|
|
return;
|
|
|
|
/* check if there is enough space, and then copy */
|
|
p = dsp->tx_buff;
|
|
d = skb->data;
|
|
w = dsp->W_tx;
|
|
ww = dsp->R_tx;
|
|
space = ww-w;
|
|
if (space <= 0)
|
|
space += CMX_BUFF_SIZE;
|
|
/* write-pointer should not overrun nor reach read pointer */
|
|
if (space-1 < skb->len)
|
|
/* write to the space we have left */
|
|
ww = (ww - 1) & CMX_BUFF_MASK;
|
|
else
|
|
/* write until all byte are copied */
|
|
ww = (w + skb->len) & CMX_BUFF_MASK;
|
|
dsp->W_tx = ww;
|
|
|
|
#ifdef CMX_DEBUG
|
|
printk(KERN_DEBUG "cmx_transmit(dsp=%lx) %d bytes to 0x%x-0x%x. %s\n", dsp, (ww-w)&CMX_BUFF_MASK, w, ww, dsp->inst.name);
|
|
#endif
|
|
|
|
/* copy transmit data to tx-buffer */
|
|
while(w != ww) {
|
|
p[w]= *d++;
|
|
w = (w+1) & CMX_BUFF_MASK;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|