Hallo Tom,
Mann Du bastelst ja immer noch an der CAPI rum ...
DTMF erkennen wird über den Görtzel Algo gemacht. Vor Jahren hatte ich mir mal
aus den erhältlichen Quellen eine Klasse zusammengebastelt. Ich poste sie mal hier
(siehe unten).
Soweit ich in Erinnerung habe, berücksichtigen die Tabellen schon die inverse ISDN Darstellung der ISDN B-Kanal Daten. Meist haben ja die ISDN Karten den DTMF Kram in Form eines Hardware Codecs. Der Code ist nicht super optimiert, kann man
besser machen.
Das Codeplex Projekt kenne ich. Viel zu kompliziert programmiert für meinen Geschmack ...
Wenn Du fragen hast poste sie.
elli
typedef void (CALLBACK * PDTMFSignalProc)(
PVOID pvContext,
char ch
);
typedef struct
{
int freq;
int grp; /* low/high group */
int k; /* k */
int k2; /* k fuer 2. harmonic */
} TDTMF;
typedef struct
{
int freq1;
int freq2;
} TFrequenceDesc;
#define MAX_DTMF_TONES 8 + 1
#define NCOEFF 16 + 2 // number of frequencies to be analyzed
class CDTMFCodec
{
public:
CDTMFCodec();
void init (
DWORD dwNumberSamples,
DWORD dwSampleRate = 8000
);
void setSignalProc(
PDTMFSignalProc signalProc,
PVOID signalContext
);
void scanAlaw (PBYTE pvBuffer, int iLength );
void scan (PWORD pvBuffer, int iLength );
BOOL generateTone (PWORD buffer, DWORD dwNumOfSamples, char ch);
void setCNGDetection (BOOL bFlag);
protected:
virtual void signalChar (char ch);
void scanTones (int * pvBuffer);
void goertzel (int * pvBuffer);
private:
static int cos2pik[NCOEFF];
static TDTMF tones[MAX_DTMF_TONES];
static char chMatrix[4][4];
static TFrequenceDesc freqTones[NCOEFF];
PDTMFSignalProc dtmfSignalProc;
PVOID dtmfSignalContext;
char lastChar;
int iIndex;
int pcmBuffer [2048]; // we use int for faster 32 bit access
int tempBuffer[NCOEFF];
BOOL bCNGDetection;
};
typedef CDTMFCodec * PDTMFCodec;
inline void
CDTMFCodec::signalChar (char ch)
{
if ( dtmfSignalProc )
{
dtmfSignalProc( dtmfSignalContext, ch );
}
}
inline void
CDTMFCodec::setSignalProc(
PDTMFSignalProc signalProc,
PVOID signalContext
)
{
dtmfSignalProc = signalProc;
dtmfSignalContext = signalContext;
}
inline void
CDTMFCodec::setCNGDetection(BOOL bFlag)
{
bCNGDetection = bFlag;
}
//#define DTMF_TRESH 100000 // above this is dtmf
//#define H2_TRESH 20000 // 2nd harmonic
#define DTMF_TRESH 50000 // above this is dtmf
#define H2_TRESH 10000 // 2nd harmonic
#define SILENCE_TRESH 100 // below this is silence
#define AMP_BITS 9 // bits per sample, reduced to avoid overflow
#define DTMF_NPOINTS 205 // Number of samples for DTMF recognition
#define LOGRP 0
#define HIGRP 1
#define MAX_N 384
#define PI 3.1415
#pragma warning (disable : 4309) // Verkuerzung eines konstanten Wertes
#pragma warning (disable : 4305) // Verkuerzung von 'const int' in 'short'
//---------------------------------------------------------------------------
// alaw -> signed 16-bit
//---------------------------------------------------------------------------
static short isdnAlawTo16BitPCM[] =
{
0x13fc, 0xec04, 0x0144, 0xfebc, 0x517c, 0xae84, 0x051c, 0xfae4,
0x0a3c, 0xf5c4, 0x0048, 0xffb8, 0x287c, 0xd784, 0x028c, 0xfd74,
0x1bfc, 0xe404, 0x01cc, 0xfe34, 0x717c, 0x8e84, 0x071c, 0xf8e4,
0x0e3c, 0xf1c4, 0x00c4, 0xff3c, 0x387c, 0xc784, 0x039c, 0xfc64,
0x0ffc, 0xf004, 0x0104, 0xfefc, 0x417c, 0xbe84, 0x041c, 0xfbe4,
0x083c, 0xf7c4, 0x0008, 0xfff8, 0x207c, 0xdf84, 0x020c, 0xfdf4,
0x17fc, 0xe804, 0x018c, 0xfe74, 0x617c, 0x9e84, 0x061c, 0xf9e4,
0x0c3c, 0xf3c4, 0x0084, 0xff7c, 0x307c, 0xcf84, 0x030c, 0xfcf4,
0x15fc, 0xea04, 0x0164, 0xfe9c, 0x597c, 0xa684, 0x059c, 0xfa64,
0x0b3c, 0xf4c4, 0x0068, 0xff98, 0x2c7c, 0xd384, 0x02cc, 0xfd34,
0x1dfc, 0xe204, 0x01ec, 0xfe14, 0x797c, 0x8684, 0x07bc, 0xf844,
0x0f3c, 0xf0c4, 0x00e4, 0xff1c, 0x3c7c, 0xc384, 0x03dc, 0xfc24,
0x11fc, 0xee04, 0x0124, 0xfedc, 0x497c, 0xb684, 0x049c, 0xfb64,
0x093c, 0xf6c4, 0x0028, 0xffd8, 0x247c, 0xdb84, 0x024c, 0xfdb4,
0x19fc, 0xe604, 0x01ac, 0xfe54, 0x697c, 0x9684, 0x069c, 0xf964,
0x0d3c, 0xf2c4, 0x00a4, 0xff5c, 0x347c, 0xcb84, 0x034c, 0xfcb4,
0x12fc, 0xed04, 0x0134, 0xfecc, 0x4d7c, 0xb284, 0x04dc, 0xfb24,
0x09bc, 0xf644, 0x0038, 0xffc8, 0x267c, 0xd984, 0x026c, 0xfd94,
0x1afc, 0xe504, 0x01ac, 0xfe54, 0x6d7c, 0x9284, 0x06dc, 0xf924,
0x0dbc, 0xf244, 0x00b4, 0xff4c, 0x367c, 0xc984, 0x036c, 0xfc94,
0x0f3c, 0xf0c4, 0x00f4, 0xff0c, 0x3e7c, 0xc184, 0x03dc, 0xfc24,
0x07bc, 0xf844, 0x0008, 0xfff8, 0x1efc, 0xe104, 0x01ec, 0xfe14,
0x16fc, 0xe904, 0x0174, 0xfe8c, 0x5d7c, 0xa284, 0x05dc, 0xfa24,
0x0bbc, 0xf444, 0x0078, 0xff88, 0x2e7c, 0xd184, 0x02ec, 0xfd14,
0x14fc, 0xeb04, 0x0154, 0xfeac, 0x557c, 0xaa84, 0x055c, 0xfaa4,
0x0abc, 0xf544, 0x0058, 0xffa8, 0x2a7c, 0xd584, 0x02ac, 0xfd54,
0x1cfc, 0xe304, 0x01cc, 0xfe34, 0x757c, 0x8a84, 0x075c, 0xf8a4,
0x0ebc, 0xf144, 0x00d4, 0xff2c, 0x3a7c, 0xc584, 0x039c, 0xfc64,
0x10fc, 0xef04, 0x0114, 0xfeec, 0x457c, 0xba84, 0x045c, 0xfba4,
0x08bc, 0xf744, 0x0018, 0xffe8, 0x227c, 0xdd84, 0x022c, 0xfdd4,
0x18fc, 0xe704, 0x018c, 0xfe74, 0x657c, 0x9a84, 0x065c, 0xf9a4,
0x0cbc, 0xf344, 0x0094, 0xff6c, 0x327c, 0xcd84, 0x032c, 0xfcd4
};
//---------------------------------------------------------------------------
// For dtmf recognition: * 2 * cos(2 * PI * k / N) precalculated for ISDN
// (8000 Hz) and all k
//---------------------------------------------------------------------------
int CDTMFCodec::cos2pik[NCOEFF] =
{
55812, 29528, 53603, 24032, 51193, 14443, 48590, 6517,
38113, -21204, 33057, -32186, 25889, -45081, 18332, -55279, 42450, -10225
};
//---------------------------------------------------------------------------
//
//---------------------------------------------------------------------------
TDTMF CDTMFCodec::tones[MAX_DTMF_TONES] =
{
{ 697, LOGRP, 0, 1 },
{ 770, LOGRP, 2, 3 },
{ 852, LOGRP, 4, 5 },
{ 941, LOGRP, 6, 7 },
{1209, HIGRP, 8, 9 },
{1336, HIGRP, 10, 11 },
{1477, HIGRP, 12, 13 },
{1633, HIGRP, 14, 15 },
{1100, HIGRP, 16, 17 } // FAX CNG detection
};
//---------------------------------------------------------------------------
//
//---------------------------------------------------------------------------
char CDTMFCodec::chMatrix[4][4] =
{
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
//---------------------------------------------------------------------------
//
//---------------------------------------------------------------------------
TFrequenceDesc CDTMFCodec::freqTones[NCOEFF] =
{
{697, 1209}, {697, 1336}, {697, 1477}, {697, 1633},
{770, 1209}, {770, 1336}, {770, 1477}, {770, 1633},
{852, 1209}, {852, 1336}, {852, 1477}, {852, 1633},
{941, 1209}, {941, 1336}, {941, 1477}, {941, 1633}, {1100,1100}
};
//---------------------------------------------------------------------------
//
//---------------------------------------------------------------------------
CDTMFCodec::CDTMFCodec()
{
iIndex = 0;
lastChar = ' ';
dtmfSignalProc = NULL;
dtmfSignalContext = NULL;
bCNGDetection = FALSE;
}
//---------------------------------------------------------------------------
// Der Goertzel-Algorithmus:
// Ist ein genauso schlichtes, wie geniales numerisches Verfahren (!) und
// beinhaltet im Groben "folgendes":
//
// Sog. "Feedback-Phase":
// Ein Schritt bezieht sich immer auf einen Block von 205 Samples
// (also n=0, ... 204) bei 8kHz.
// DTMF hat 8 Frequenzen: 697, 770, 852, 941, 1209, 1336, 1477, 1633 Hz
// Jede Frequenz hat einen eigenen Koeffizienten coef.
// coef = 2 cos(2 * PI * k / N), mit N=205, k=(2N/samplingrate) * DTMF frequenz .
// 4 Parameter sind dabei im Spiel: Q[n] = coef * Q[n-1] - Q[n-2] + Sample[n] .
// Q[n-1] & Q[n-2] werden am anfang gleich Null gesetzt.
// Sog. "Feedforward-Phase":
// Wurden alle 205 Samples verrechnet tritt die sog "Feedforward-Phase" ein.
// "quadrierte Werte": Y=(Q[n-1]) ^2 - (q[n-2])^2 - Q[n-1] * Q[n-2] * coef .
// Y representiert den "Energie-Gehalt" der gesuchten Frequenz.
// Weiter mit dem nächsten Block.
// Dabei kommen idR sehr grosse Werte heraus, so das es Sinn macht, die
// Samples auf doubles zwischen 0 ... 1 zu skalieren
//---------------------------------------------------------------------------
void
CDTMFCodec::goertzel(int * pvBuffer)
{
int tempCos2Pik;
int sk;
int sk1;
int sk2;
int k;
int n;
int * result;
int maxCoefficients;
if ( bCNGDetection )
maxCoefficients = NCOEFF + 2;
else
maxCoefficients = NCOEFF;
tempCos2Pik = 0;
result = tempBuffer;
for (k = 0; k < maxCoefficients ; k++)
{
tempCos2Pik = cos2pik[k];
sk = 0;
sk1 = 0;
sk2 = 0;
for (n = 0; n < DTMF_NPOINTS; n++)
{
sk = pvBuffer[n] + ((tempCos2Pik * sk1) >> 15) - sk2;
sk2 = sk1;
sk1 = sk;
}
result[k] = ((sk * sk) >> AMP_BITS) -
(((( tempCos2Pik * sk) >> 15) * sk2) >> AMP_BITS) +
((sk2 * sk2) >> AMP_BITS);
}
scanTones( tempBuffer );
}
//---------------------------------------------------------------------------
//
//---------------------------------------------------------------------------
void
CDTMFCodec::scanAlaw( PBYTE pvBuffer, int iLength )
{
int i;
int c;
while ( iLength )
{
c = min(iLength, (DTMF_NPOINTS - iIndex));
if (c <= 0) break;
for (i = 0; i < c; i++)
{
pcmBuffer[ iIndex++ ] = isdnAlawTo16BitPCM[*pvBuffer++] >> (15 - AMP_BITS);
}
if (iIndex == DTMF_NPOINTS)
{
goertzel(pcmBuffer);
iIndex = 0;
}
iLength -= c;
}
}
//---------------------------------------------------------------------------
//
//---------------------------------------------------------------------------
void
CDTMFCodec::scan( PWORD pvBuffer, int iLength )
{
int i;
int c;
while ( iLength )
{
c = min(iLength, (DTMF_NPOINTS - iIndex));
if (c <= 0) break;
for (i = 0; i < c; i++)
{
pcmBuffer[ iIndex++ ] = *pvBuffer++; // >> (15 - AMP_BITS);
}
if (iIndex == DTMF_NPOINTS)
{
goertzel(pcmBuffer);
iIndex = 0;
}
iLength -= c;
}
}
//---------------------------------------------------------------------------
//
//---------------------------------------------------------------------------
BOOL
CDTMFCodec::generateTone(PWORD pvBuffer, DWORD dwNumOfSamples, char ch)
{
int col, row, idx;
double f1, f2;
double value = 12000.0;
int i1;
int i2;
idx = -1;
i1 = 0;
i2 = 0;
for (row = 0; row < 4; row++)
{
for (col = 0; col < 4; col++)
{
if (ch == chMatrix[row][col])
{
idx = row * 4 + col;
}
}
}
if (idx < 0)
{
return FALSE;
}
f1 = (((double) freqTones[idx].freq1) * PI );
f2 = (((double) freqTones[idx].freq2) * PI );
for (idx = 0; idx < (int) dwNumOfSamples; idx++)
{
pvBuffer[idx] = (WORD)(value * sin((double)i1 * f1/4000));
pvBuffer[idx] = pvBuffer[idx] + (WORD)(value * sin((double)i2 * f2/4000));
i1++;
i2++;
}
return TRUE;
}
//---------------------------------------------------------------------------
//
//---------------------------------------------------------------------------
void
CDTMFCodec::scanTones(int * pvBuffer )
{
int *result;
int silence;
int i;
int grp[2];
char what;
result = pvBuffer;
grp[LOGRP] = -2;
grp[HIGRP] = -2;
silence = 0;
for (i = 0; i < 8; i++)
{
if ( (result[ tones[i].k ] > DTMF_TRESH) && (result[tones[i].k2] < H2_TRESH))
{
grp[tones[i].grp] = (grp[tones[i].grp] == -2) ? i : -1;
}
else if ((result[tones[i].k] < SILENCE_TRESH) && (result[tones[i].k2] < SILENCE_TRESH))
{
silence++;
}
}
if (silence == 8)
{
what = ' ';
if ( bCNGDetection )
{
if ( (result[ tones[9].k ] > DTMF_TRESH) && (result[tones[9].k2] < H2_TRESH))
{
// start CNG timer
signalChar( 'F' );
}
else
{
// stop CNG Timer, calculate elapsed time
}
}
}
else
{
if ( (grp[LOGRP] >= 0) && (grp[HIGRP] >= 0) )
{
what = chMatrix[grp[LOGRP]] [grp[HIGRP] - 4];
if ( (lastChar != ' ') && (lastChar != '.'))
{
lastChar = what; // min. 1 non-DTMF between DTMF
}
}
else
{
what = '.';
}
}
if ( (what != lastChar) && (what != ' ') && (what != '.'))
{
signalChar( what );
}
lastChar = what;
}
Hi,
Ich habe das Projekt ins Leben gerufen, damit ich mich in das neue .NET 3.5
Cool zu hören. Ich bin noch am überlegen auf welches ich umstellen will, denn ich will
V 2.4 benutzen wegen den GAIN Tags. Den Weg den MP3Gain geht, alle MP3 Frame Header zur verdändern gefällt mir nicht.
Allgeimein habe ich gemerkt, dass es sehr schwierig ist eine "saubere" ID3 Kodierung zu finden.
Jopp, da hast Du ja das Grundproblem von Standards schon mitbekommen. Ich plage mich mit sowas schon seit 20 Jahren rum. Man muß es allen recht machen. Pochen auf Standards und die Regeln nützt nichts. Tolerant sein und alles unterstützen 😃., schlicht kompatibel sein.
elli
hi,
es gibt 2 fette ID3 Tag Projekte. Es lohnt sich nicht sowas selber zu machen.
wahrscheinlich noch besser das vom Mono Project, weil es auch mehr als
MP3 kann.
EDIT von winSharp93: Link zur TagLibSharp Wiki entfernt, da Inhalt nun anstößig (möglicherweise durch gehackten Server)
Allerdings hat die Seite gerade Probleme. Hier sind ein paar Beispiele:
http://developer.novell.com/wiki/index.php/TagLib_Sharp:_Examples
elli
Hallo!
@maxibk:
meinst du per CAPI einen Ruf annehmen dann einen zweiten aufbauen und die
die B-Kanäle quais verbinden ?
Zuerst sollte Du die Export Funktionen von accapi20.dll prüfen., ob es überhaupt eine
CAPI 2.0 Implementierung ist.
Muß je eine sehr alte HiPath sein, denn die Software der letzte 6 Jahre kenne ich dachte ich.
>Gibts von dem c# Wrapper
c# ist nicht immer der eleganteste Weg 🙂
elli
Hallo,
lustig das es weiter geht.
Prinzipiell sollte man bei CAPI-Anwendungen den Empfang von Nachrichten in einen eigenen Prozess auslagern, welcher bei neuen Nachrichten ein Event auslöst. In diesem können die "erwarteten" Nachrichten entsprechend behandelt werden.
Thread ist schon mal OK. In dem kann Du CAPI_WAIT_FOR_SIGNAL machen.
Es gibt keine erwarteten Nachrichten ! Prinzipiel müßen die State Machine alles
abfangen und je nach dem was Du als CAPI Nachricht gesendet hast und was Du erreichen willst entsprechend reagieren. Das hat bei mir auch etliche Zeit gedauert
und es gibt diverse unterschiedliche Implementierungen. Dann kann es durch Race Conditions auch noch zu lustigsten Effekten kommen. Erst heute würde ich sagen ich fange eigentlich alles ab was so kommt.
Aus Sicht einer API gibt es wirklich nur eine Reihe von Events die eine Applikation interessieren.
Frage ruhig, ich stehe bereit 🙂
elli
Hallo,
Microsoft hat ein ACM Wrapper interface. Das kann auch so was ...
Habe mal ein uraltes Sample aus meiner Sammlung rangehangen.
elli
Hallo,
ich nehme mal an Du hast einen Slider 'audioVolume'. Der kann ein Event auslösen wenn der Wert sich ändert
this.audioVolume.ValueChanged += new System.EventHandler(this.audioVolumeChanged);
private void audioVolumeChanged(object sender, EventArgs e)
{
Bass.BASS_SetVolume(value);
}
Wenn Du da komische VB Interface für Bass benutzt. Ich halte nix vom dem Ding weil es eine blöde Abbildung einer DLL ist und mitnichten irgendeinen OOP Ansatz verfolgt.
Das sollte reichen als Anstoß denke ich.
elli
hallo,
soviel wie ich sehen kann ist diese Listbox konkret in einer Form verankert, also nicht unbedingt wiederverwendbar. Besser wäre daraus ein einzelnes Control zu machen, welches in einer Form eingebunden werden kann.
// Da mein Fenster eine feste Größe hat, kann ich hier ganz einfach ermitten, auf was der User geklickt hat. // sind immer 16 Pixel Abstand
diese Annahme hat auch nicht unbedingt allgemeingültigen Character.
elli
Hallo.
hier scheint Synchronisation möglich ...
CSharp Twain Project
sprich eine Assembly zur Fernsteuerung (WCF?) der anderen Komponente.
elli
Hallo Tom.
Die TWAIN-Komponente soll letztendlich nahezu GUI-unabhängig sein
sie muß sogar, Du weisst doch Eingabe, Verarbeitung, Ausgabe. Nicht so wie viele tolle Sachen wo die Vearbeitung mit einer Fom verwuselt ist.
elli
PS. Ich bin gespannt
Hallo Tom,
EDIT: Was genau fehlt dir persönlich für ein Digitalisierungsprogramm?
Tja wenn ich das so genau benennen könnte ... Wenn man sich den Ablauf (Workflow) vorstellt, den man hat wenn z.B. persönliche Dokumente einscannt, kommt man an den Punkt wo man ja mehrere Scannergebnisse hat die logisch zusammengehören.
Alle einzelnen Images laufen durch die gleiche Filtergruppe (Predefines für S/W , Gray)
und können als Einzelbilder und/oder als ein PDF mit mehreren Seiten abgelegt werden.
Denkbar wäre noch eine Zusammenfassung der einzelnen Bilder auf einer Html Seite.
Jeder Vorgang in ein Directory. Das alles möglichst bequem mit den Eingaben.
Also Button für nächsten Scanvorgang oder Fertig ...
Anderer Vorgang wenn man z.B. Dias/Negative scannt ...
Anderer Vorgang wenn man nur Kopien für Drucker macht ...
Hier noch eine Idee für die Thumbnails - die Größe frei angebbar.
elli
hallo tom,
bei mir funktioniert es. 🙂
Ich habe einen Canon 4200F. Ist schon mal ein recht schöner Anfang. Ich will auch irgendwann mal meine persönlichen Unterlagen digitalisieren und das ist zumindest die
Assembly ein erster Schritt. Das was ich machen will deckt das Programm erst mal so nicht ab.
Ein paar Ideen ...
Bugs
Contextmenu des Thumbnail Tabs,(wenn kein Thumbnail selektiert ist) darf nicht
angezeigt werden, bzw ein anderes (z.B save all as ...)
Mehrfach Selektion von Thumbnails führt nicht zu erwarteten Aktion.
File Save All darf nicht selektierbar sein wenn noch nichts gemacht wurde.
OK, sehr guter Ansatz. Gibt es auch mal den Source Code ?. Immer Refl.. ist langweilig...
elli
@hulkstar
hallo,
der plci ist eine Resource die beim D-Kanal Verbindungsaubau, sprich beim Connect Request oder Connect Indication vergeben wird. Wenn noch kein Verbindungaufbau erfolgte, muß ein PLCI immer zu Verfügung stehen. Ist das nicht der Fall, kann es sich nur um ein Problem der Capi Implmentierung handeln oder wie bei Dir anscheinend um ein Konfigurationsproblem des Capi Servers.
Da Du anscheinend schon eine Verbindung augebaut hattest, schreibe mal die Sequence der Capi Befehle auf die Du beim Verbindungsabbau machst.
elli
ps. ein nicht erfolgter disconnect response auf ein disconnect indication oder dabei eine fasche Angabe verhindern das Freigeben des PLCIs.
hallo,
eine mögliche Ursache könnte an gelöschten INF Files liegen. Typischerweise
liegn unter \windows\inf folgende Files:
23.08.2001 13:00 40.950 usb.inf
25.08.2006 17:00 43.968 usb.PNF
03.08.2004 23:51 47.648 usbport.inf
28.11.2005 17:20 51.832 usbport.PNF
18.08.2001 12:00 3.354 usbprint.inf
03.01.2006 21:17 5.492 usbprint.PNF
18.08.2001 12:00 29.652 usbstor.inf
23.12.2005 20:38 37.952 usbstor.PNF
03.08.2004 23:51 24.042 usbvideo.inf
28.11.2005 15:16 24.992 usbvideo.PNF
Ich hatte mal was ähnliches und da fehlte usb.inf. (Kein Ahnung wie und warum).
Anhand dieser INF Files kennt Windows so die meisten Standard Geräte.
elli
Hallo,
also nochmal die Logik. Jedes Byte von der ISDN Leitung (sprich CAPI) bzw. muß erst
über z.b. eine Tabelle bit inevrtiert werden. Anbei die Tabelle, bzw. wie man sie
aufbaut (geht besser).
BYTE lookUpTable[256];
int b;
BYTE o;
for ( b = 0; b <= 255; b++ )
{
o = 0;
if ( b & 0x01) o |= 0x80;
if ( b & 0x02) o |= 0x40;
if ( b & 0x04) o |= 0x20;
if ( b & 0x08) o |= 0x10;
if ( b & 0x10) o |= 0x08;
if ( b & 0x20) o |= 0x04;
if ( b & 0x40) o |= 0x02;
if ( b & 0x80) o |= 0x01;
lookUpTable[b] = o;
}
Also für jedes BYTE von DATA_B3_IND genannt b ist i = lookupTable**.
i wird dann von A-Law nach Wave konvertiert.
Diese Konvertierung kann durch eine Tabelle erfolgen da wir nur 255 Werte haben.
Umgekehrt Senden von PCM Daten, erfolgt zuerst die PCM -> A-LAW konvertierung.
Jedes BYTE b das da rauskommt wid jetzt zu j = lookupTables**.
j geht dann als Byte beim DATA_B3_REQ raus.
Das reine Anwenden des G.711 Algos kann nie zu einem hörbaren Erlebnis bei ISDN werden. Es gibt Linux Sourcen die das besser verdeutlichen.
elli
//EDIT: c#-code-tags ergänzt
Hallo,
@balaban_s
Also Codeproject hast Du ja schon entdeckt, dann hätte Dir ein wenig mehr weitersuchen auch geholfen. Der PCM -> A-Law Codec muß nun einen Dynamic
Bereich von 0xFFFF Werten zu 255 Werten abbilden. Kann man natürlich mit
Tabellen machen, aber der Artikel G.711
wird Dir da sicherlich helfen.
Wenn Du also ankommende WAVE IN Daten nach A-Law Konvertiert hast, müßen sie noch mit der Tabelle die ich schon mal gepostet hatte (oder nicht ?) bitinvertiert werden. Dann können sie raus mit DATA B3 REQUEST.
Das ruckeln beim Abbspielen bekommst Du nur weg wenn Du ein wenig Latency und
damit Echo erzeugst und das abspielen erst anfängst wenn Du den 2. oder 3. Block empfangen hast. Beide Datenströme arbeiten mit 8Kz sind aber nicht synchronisiert. Mit diesem kleinen Vorlaufbuffer kannst Du eventuelle Differenzen überleben. Andere Methode wäre einen kleinen Silent Block abzuspielen. Das kann Dich aber alles nicht gegen Windows schützen. Da es kein Realtime System ist, kann es passieren das Du für 100ms und mehr im Usermode keine Rechenzeit bekommst. Dann ist nicht mehr mit einem kontinuirlichen Datenstrom. (Ich habe es erlebt als ich 8 ISDN Kanäle gebündelt haben und eine Applikation alle 10 ms die entsprechenden Daten liefern muß).
Je kleiner die Datenblockgröße wird, umso geringer ist das Echo das Du auf der Leitung hast.
Ich hoffe das hilft weiter. Wenn nicht, noch mal nachfragen.
elli
Hallo,
@hulkstar
die Nummer mit der ihr verbunden seit wird bei CON_ACT_IND angezeigt. Von außen
kommt man aber nicht mehr ran an eine CAPI. Hast Du die Kontrolle über die Verbindung ?, wenn nicht mußt Du sowiso eine neue Verbindung aufbauen. Male mal
eine kleine Skizze, denn so ganz habe ich die Architektur nicht kapiert.
Kleiner Tip am Rande, ich würde nie bei jeder DATA_B3_IND einen neuen Buffer
mit new allozieren. Baut euch eine Resource Queue mit der Anzahl von freien
Buffern die Ihr bei CAPI_REGISTER angegeben habt. Die maximale Größe eines dieser Buffer habt ihr bereits bei CAPI_REGISTER festgesetzt.
Wenn ein DATA_B3_IND Nachricht bearbeitet wird, freien Buffer Block ausqueuen.
Daten kopieren und zur Verarbeitung schicken. Wenn DATA_B3_RESP gemacht wird
diese Buffer wieder zurück an die Resourcequeue.
Bei Kommunikation treten viele asynchrone Ereignisse auf. Ich halte es für einen schlechten Stil und da es führt zu nicht deterministischen Laufzeitverhalten wenn
immer fleißig mit new gearbeitet wird.
@Balaban_S
if (command == CapiCommand.cmDataB3Indication) { IntPtr DataRaw =Marshal.AllocHGlobal(256); /* byte DataLenght =1; word CapiCommand= 2, dword Datahandle = 4 also: 1+2+4=7*/ DataRaw = Marshal.ReadIntPtr(msg, 7);//<--7 Marshal.Copy(DataRaw, b3Data, 0, 256); Marshal.FreeHGlobal( DataRaw);
Die Datenlänge sind immer 2 Byte.
und mit diese Länge dann das Copy.
Du hast bereits einen unmanged Pointer, also ist MarshallhGlobal() falsch und überflüßig. Das braucst Du beim DATA_B3_REQ.
Der Code von hulkstar ist fast korrekt, man braucht keine 4096 Byte allozieren,
sondern nur von der Größe 'len'.
Viel Spaß weiterhin 🙂
elli
@hulkstar
hallo,
ch hatte vergessen, auf die DATA_B3_INDICATIONs mit DATA_B3_RESPONSEs zu antworten.
Naja ein paar mal kann man das machen, zumindest einmal weniger als man bei
CAPI_REGISTER und B3 Block Count angegeben hat. Dann noch ein Block und dann ist Sense 🙂
Aber ein DISC_B3_IND bzw. DISC_IND hat damit nix zu tun. Das muß auch so kommen. Das würde mich mal interessieren was passiert wenn Du wieder keine
DATA_B3_RESP schickst und dann den ISDN Stecker ziehst.
elli
@hulkstar,
nur mal so, CAPI_WAIT_FOR_SIGNAL würde ich ständig in einem Thread laufen lassen
und wenn WAIT_FOR_SIGNAL beendet ist 2 mal CAPI_GET_MESSAGE aufrufen.
Der zweite Aufruf falls noch was da ist. (in der Regel ja nicht). Da wenn eine Nachricht
da ist , dispatcht Du sie. Es können ja nur Indications oder Confirmations kommen.
Wenn Du alles beendet machst Du ja CAPI_RELEASE, das beendet normalerweise
WAIT_FOR_SIGNAL und Du kommts sauber raus aus dem Thread.
Anwendung aufhängen weil von der CAPI nichts mehr kommt ist Quatsch.
CAPI ist Kommunikation und damit asynchron, ankommende Messages immer in einem Thread.
Wenn Du laut Deinem Protokoll jeztzt nach dem Senden einer Nachricht an einem SMS Server eine Antwort erwartest, muß ja ein DATA_B3_INDICATION kommen.
Kann es sein das der Server ein anderes "Protokoll" spricht ?. Ich habe mich um SMS nie gekümmert.
Was mich dabei erst richtig verwirrt ist, dass der Server nach ein paar Sekunden inaktivität die Verbindung beendet aber ich auch keine Disconnect_(B3_)Indication oder sonstwas bekomme...
Das geht nicht. Wenn der Server die Verbindung abbaut, spricht auf dem D-Kanal muß die CAPI Implementation Dir zuerst ein DICONNECT B3 INDICATION und danach ein DISCONNECT INDICATION senden. Alles andere verstößt gegen die Regeln. Da die meisten eine Fritz CARD haben ist das auch so. Wenn Du in diesem Zustand die ISDN Leitung mal abziehst mußt Du auch einen DISC_B3_IND und dann ein DISC_IND mit Grund 0x3301 bekommen.
Als erstes würde ich Dir empfehlen Dein Programmlogik zu ändern. Mann kann keine Nachricht an die CAPI senden und dann mal auf eine Anwort der CAPI warten, das ist schlechte Programmierung an dieser Stelle. Alles wird durch States und Ereignisse gesteuert. Ein Ereignis ist hier, das eine neue CAPI Nachricht da ist, die abhängig vom Verbindungszustand eine neue Aktion bzw. Zustandswechsel bewirkt. Hast Du eine Verbindung löst der Inhalt der Daten einen neuen Zustandsautomaten deiner Anwendung aus.
Wenn Du Fritzt hast, hat da nicht mal sowas wie ein CAPI Trace existiert ? Ich hatte auch mal eine Löungs da habe ich eine Trace DLL in eine Fremd CAPI injeziert. Weiß aber nicht mehr ob das noch geht.
elli
PS. Stück Source Code habe ich noch gefunden. Es ist AS IS, denn ich habe den C# Pfad nicht weiterverfolgt ....
Hallo,
Töne aus dem Lautsprecher, das ist so eine Sache. Eine Wave Komponente hast
Du hoffentlich. Die Daten der CAPI kommen praktisch als 8Khz Stream.
Wenn man jetzt z.b. Hören und Sprechen will hat man ein gewisse Verzögerung, abhängig von der Blockgröße die man bei der CAPI Applikation einstellt.
256 Byte ist ein guter Wert wenn man Voice macht. (Je geringer desto besser, aber
mehr Overhead im Gesamtsystem).
Die Daten kommen dann so ca. alle 32ms. Anfangen mit dem Abspielen sollte man nicht unbedingt gleich, weil man ja 2 synchrone Datenströme hat (von ISDN, zum Audio Device) die nicht untereinander synchronisiert sind. Also kann ein kleiner Zwischenbuffer von Vorteil sein.
Das Hauptproblem sind die Daten an sich. Sie liegen im G.711 Format vor. Bei uns in
Mitteleuropa in G.711 ALaw. Diese müßen jetzt nach PCM Wave Mono Khz 16Bit gewandelt werden. Die sie aus dem Chips noch bitinvertiert rauskommen ist zusätzlich noch eine Tabelle vorzusetzen.
G.711 Codecs sind im Windows System vorhanden. Das ACM System dafür ist aber relativ umständlich (halt Microsoft) zu bedienen und eigentlich zu oversized dafür.
Mann kann das durch simple Tabellen machen, dabei werden beide notwendigen Transformationen zu einer zusammengefaßt.
Ich habe sowas irgendwo rumzuliegen, war aber bis lang zu faul es anzuwenden weil mein ACM Code seit Jahren läuft.
Ich werde mal rumsuchen, um was zum posten bereitzustellen.
elli
@Balaban_S
Es gibt doch das CAPI-ADK von AVM (Supermächtig das Teil, da von AVM), welches frei zur Verfügung steht.
Könnte man das nicht per C++/CLI wrappen ?
Sollte kein Problem sein. Allerdings ist es ja C-Code und Du musst daraus erst
mal eine Art Obekt Modell machen. Aber nicht in der Art ich mache ein .NET Low Level CAPI Schnittstelle. Davon halte ich überhaupt nichts. Wir haben es hier immer mit
Verbindungen zu tun und man sollte da eine andere Sicht haben. So in der Art
IsdnConnection
Connect
Disconnect
AcceptCall
RejectCall
IgnoreCall
Transmit
DataResponse
und Ereignisse der Art
OnConnectionEstablished
OnConnectionFailed
OnIncomingCall
OnDataReceived
OnDataTransmitted
...
mit dieser Art des herangehens (im Grund mehr oder weniger OSI-ISO - Schichtenmodell) kann man jede Art von Kommunilationsmodell abbilden.
Sei es ISDN, Bluetooth, TCPIP ...
elli
hallo,
vergesse immer reinzuschauen 🙂
Selbst wenn die CIP etc. nicht stimmt, muß der Ruf mit einem Disconnect Indication terminieren und anzeigen das der PLCI vom Connect Confirmation nicht mehr gültig ist.
Gerade bei den Sprachen und Voice CIP kann es lustig werden...
elli
Hallo,
habe vergessen mal wieder vorbeizuschauen, bzw. bin total dicht und arbeite 14h am Tag ...
Anyway, ich schicke die mal meinen ganzen Code. Ist extrem unvollständig, eine Linie
müßte aber zu erkennen sein (ein Ansatz wie ma programmieren könnte) und wenn ich mich nicht irre konnten ankommende Rufe gemonitort werden. Da ich inzwischen wie gesagt alles in C++ CLI habe brauche ich die C# Linie nicht weiter zu verfolgen. Ein C++ Kernel hat den schönen Vorteil das ich ihn überall verwenden kann.
Mal sehen ob ich den Anhang schaffe...
elli
Hallo,
OK verstehe. Bei meinen ersten C# Übungen habe ich einen anderen Ansatzt
gewählt. Das dumme Struktur Nachbilden ist sowiso für die Katz da alles
variante Strukturen sind. Ich habe also eine Message definiert die einen
unmanged buffer enthält und die einzelnen Element per Marshall etc.
setzt. Hier mal mein alter Code als Idee. Ich programmiere CAPI und ISDN
Applikationen und Treiber seit nun fast 20 Jahren und langsam wird es langweilig 🙂
elli
using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace CAPI
{
public class CapiMessage : IDisposable
{
#region Constructor, Desctructor, Dispose
public CapiMessage(CapiApplication application)
{
this.application = application;
internalMemory = Marshal.AllocHGlobal( 2048 + 32 );
umBuffer = internalMemory;
writeIndex = 8;
length = 0;
streamBuffer = new byte[2048 + 32 ];
if ( streamBuffer == null )
{
//throw()
}
}
public CapiMessage( IntPtr msg)
{
// Save unmanged message buffer position
streamBuffer = new byte[2048 + 32 ];
Marshal.Copy( msg, streamBuffer,0,8);
length = readWord(0);
command = (CapiCommand) (readWord(4) & 0xFFFF);
// check for DATA B3 Indication
Marshal.Copy( msg, streamBuffer,0,length);
}
~CapiMessage()
{
if( internalMemory != IntPtr.Zero )
{
Marshal.FreeHGlobal( internalMemory );
}
}
public void Dispose()
{
if ( ! fDisposed )
{
fDisposed = true;
umBuffer = IntPtr.Zero;
//umBuffer = IntPtr.Zero;
System.GC.SuppressFinalize( this );
}
}
#endregion
public int sendMessage (CapiCommand command)
{
this.command = command;
writeIndex = 0;
length += 8;
writeWord( (ushort) length );
writeWord( (ushort) application.ApplicationID );
writeWord( (ushort) command );
writeWord( (ushort) application.NextMessageNumber);
return application.putMessage(this, command,length );
}
#region message request section
public int listenRequest(
int controllerNumber,
Int32 infoMask,
CIPMask cipMask,
Int32 cipMask2
)
{
reset();
writeDWord( controllerNumber);
writeDWord( infoMask );
writeDWord( (Int32) cipMask );
writeDWord( cipMask2 );
writeStruct( null );
writeStruct( null );
return sendMessage( CapiCommand.cmListenRequest );
}
public int connectRequest(
int controllerNumber,
string calledPartyNumber,
string callingPartyNumber,
CIPValue cipValue,
BProtocol bProtocol
)
{
reset();
writeDWord( controllerNumber );
writeWord( (ushort) cipValue );
writeCalledPartyNumber(calledPartyNumber);
writeCallingPartyNumber(callingPartyNumber,cipValue);
writeStruct( null ); // Called party subaddress
writeStruct( null ); // Calling party subaddress
writeStruct( null ); // param B protocol
writeStruct( null ); // Bearer Capaility
writeStruct( null ); // Low Layer Compatibility
writeStruct( null ); // High Layer Compatibility
writeStruct( null ); // Additional information elements
return sendMessage(CAPI.CapiCommand.cmConnectRequest);
}
public int disconnectRequest(Int32 plci)
{
reset();
writeDWord( plci );
return sendMessage(CAPI.CapiCommand.cmDisconnectRequest);
}
public int alertRequest(Int32 plci)
{
reset();
writeDWord( plci );
writeStruct( null ); // Additional information elements
return sendMessage(CAPI.CapiCommand.cmAlertRequest);
}
public int connectB3Request(Int32 plci)
{
reset();
writeDWord( plci );
writeStruct( null ); // NCPI
return sendMessage(CAPI.CapiCommand.cmConnectB3Request);
}
public int disconnectB3Request(Int32 plci)
{
reset();
writeDWord( plci );
writeStruct( null ); // NCPI
return sendMessage(CAPI.CapiCommand.cmDisconnectB3Request);
}
#endregion
#region message response section
public int connectResponse(Int32 plci, int reason)
{
reset();
writeDWord( plci );
writeWord( (ushort) reason );
writeStruct( null ); // B protocol struct
writeStruct( null ); // Connected number
writeStruct( null ); // Connected subaddress
writeStruct( null ); // Low Layer Compatibility
writeStruct( null ); // Additional information elements
return sendMessage(CAPI.CapiCommand.cmConnectResponse);
}
public int connectActiveResponse(Int32 plci)
{
reset();
writeDWord( plci );
return sendMessage(CAPI.CapiCommand.cmConnectActiveResponse);
}
public int disconnectResponse(Int32 plci)
{
reset();
writeDWord( plci );
return sendMessage(CAPI.CapiCommand.cmDisconnectResponse);
}
public int infoResponse(Int32 plci)
{
reset();
writeDWord( plci );
return sendMessage(CAPI.CapiCommand.cmDisconnectResponse);
}
public int disconnectB3Response(Int32 ncci)
{
reset();
writeDWord( ncci );
writeStruct( null ); // NCPI
return sendMessage(CAPI.CapiCommand.cmDisconnectB3Response);
}
public int connectB3Response(Int32 ncci,int reject)
{
reset();
writeDWord( ncci );
writeWord( (ushort) reject );
writeStruct( null ); // NCPI
return sendMessage(CAPI.CapiCommand.cmConnectResponse);
}
public int resetB3Response(Int32 ncci)
{
reset();
writeDWord( ncci );
return sendMessage(CAPI.CapiCommand.cmResetB3Response);
}
public int data3Response(Int32 ncci)
{
reset();
writeDWord( ncci );
return sendMessage(CAPI.CapiCommand.cmDataB3Response);
}
public int connectB3ActiveResponse(Int32 ncci)
{
reset();
writeDWord( ncci );
return sendMessage(CAPI.CapiCommand.cmConnectB3ActiveResponse);
}
#endregion
protected void reset()
{
writeIndex = 8;
length = 0;
}
public byte[] MessageBuffer
{
get
{
return streamBuffer;
}
}
#region Message properties (member access section)
public int ApplicationID
{
get
{
return readWord(0);
}
}
public CapiCommand Command
{
get { return command; }
set { command = value; }
}
public ushort MessageNumber
{
get
{
return readWord(6);
}
}
public int Length
{
get
{
return length;
}
}
public Int32 Plci
{
get
{
switch ( command )
{
case CapiCommand.cmConnectRequest:
case CapiCommand.cmListenRequest:
return 0; // Throw ??
}
return readDWord(8);
}
}
public Int32 Ncci
{
get
{
return readDWord(8);
}
}
public int ControllerNumber
{
get // todo check request type
{
return readDWord(8);
}
}
public int InfoMask
{
get
{
return readDWord(12);
}
}
public int CIPMask
{
get
{
return readDWord(16);
}
}
public CapiInfo Info
{
get
{
switch ( command )
{
case CapiCommand.cmDataB3Confirmation:
return (CapiInfo) readWord(14);
}
return (CapiInfo) readWord(12);
}
}
public string CalledPartyNumber
{
get { return getCalledPartyNumber(); }
}
public string CalledPartySubAddress
{
get { return getCalledPartySubAddress(); }
}
public string CallingPartyNumber
{
get { return getCallingPartyNumber(); }
}
public string CallingPartySubAddress
{
get { return getCallingPartySubAddress(); }
}
public CIPValue CIP
{
get { return (CIPValue) readWord(4); }
}
#endregion
# region Low level Message assembly stuff
/// <summary>
/// Write an byte into bufferStream, update the writeIndex and the written length
/// </summary>
protected void writeByte( byte val )
{
//Marshal.WriteByte( umBuffer, writeIndex, val );
streamBuffer[writeIndex++] = val;
length++;
}
/// <summary>
/// Write an short into bufferStream in little endian byte order.
/// </summary>
protected void writeWord( ushort val )
{
writeByte( (byte) val );
writeByte( (byte) (val >> 8) );
}
/// <summary>
/// Write an int into bufferStream in little endian byte order.
/// </summary>
protected void writeDWord( int val )
{
writeWord( (ushort) val );
writeWord( (ushort) (val >> 16));
}
protected void writeStruct( object val )
{
if( val == null )
{
writeByte(0);
return;
}
int size = Marshal.SizeOf( val );
if( size < 255 )
{
Marshal.WriteByte( umBuffer, writeIndex, (byte) size );
writeIndex++;
length++;
}
else
{
Marshal.WriteByte( (IntPtr) writeIndex, 0, 255 );
writeIndex++;
length++;
Marshal.WriteInt16( (IntPtr) writeIndex, 0, (short) size );
writeIndex += 2;
length += 2;
}
Marshal.StructureToPtr( val, (IntPtr) writeIndex, false );
writeIndex += size;
length += size;
}
protected void writeCalledPartyNumber(string number)
{
writeByte( (byte) (1 + number.Length)); // length
writeByte( 0x80 ); // type
foreach( char ch in number)
{
writeByte( (byte) ch );
}
}
protected void writeCallingPartyNumber(string number, CIPValue cipValue)
{
writeByte( (byte) (2 + number.Length)); // length
writeByte( 0x00 ); // type
if ( cipValue == CIPValue.cipValueNoProfile)
{
writeByte( 0x81 ); // screen indicatior
}
else
{
writeByte( 0x80 ); // screen indicatior
}
foreach( char ch in number)
{
writeByte( (byte) ch );
}
}
protected void writeNCPI( NCPI ncpi )
{
if( ncpi == null )
{
writeByte(0);
return;
}
}
#endregion
#region Low Level Message disassembly section
private string getCallingPartyNumber()
{
return "";
}
private string getCallingPartySubAddress()
{
return "";
}
private string getCalledPartySubAddress()
{
return "";
}
private string getCalledPartyNumber()
{
// BYTE length
// BYTE type;
// BYTE digits[ capiMaxAddressLength ];
int offset;
int length;
switch ( command )
{
case CapiCommand.cmConnectRequest:
case CapiCommand.cmConnectIndication:
case CapiCommand.cmInfoRequest:
offset = seekStructure(0,1);
length = readByte(offset++);
return getString( length - 1, offset );
default:
return "";
}
}
private int seekStructure(int offset, int index)
{
int length;
offset += 8; // byte offset into variable message part
while ( --index > 0)
{
length = readByte( offset );
if ( length + offset > Length )
{
Trace.WriteLine("! bad structure at index "+index);
return 0;
}
offset += (length + 1);
}
return offset;
}
private string getString(int length, int offset)
{
string st;
st = "";
return st;
}
public byte readByte(int index )
{
return streamBuffer[index];
}
public ushort readWord(int index)
{
ushort lb;
ushort hb;
lb = streamBuffer[index];
hb = streamBuffer[index+1];
hb <<= 8;
hb |= lb;
return hb;
}
public int readDWord(int index )
{
int lb;
int hb;
lb = readWord(index);
hb = readWord(index+2);
return (hb << 16)|lb;
}
#endregion
#region Private identifier
private IntPtr umBuffer;
private IntPtr internalMemory;
private int writeIndex;
private CapiCommand command;
private int length;
bool fDisposed;
byte [] streamBuffer;
CapiApplication application;
#endregion
}
}
hallo,
wenn Du schon C oder C++ Code hast, solltest Du ihn benutzen um ein
Assembly daraus zu machen. Mit entsprechenden C++ CLI Wrappern
kannst Du Deinen alten CAPI C/C++ Code wunderbar weiter benutzen
und eine managed Assembly daraus machen.
Es ist relativ blödsinning und unproduktiv mit C# alles von Grund auf
neu zu erfinden mit neuen Fehlern. Die Zeit spare ich mir, mein CAPI C++ Code läuft
seit über 10 Jahren in jeder Windows Umgebung (ob User oder Kernel Mode)
und jetzt auch in einem Managed Wrapper.
elli