2015-12-04 26 views
6

Im Anschluss an meine earlier question ist mein Ziel, DTMF-Töne in einer WAV-Datei von C# zu erkennen. Ich habe jedoch wirklich Schwierigkeiten zu verstehen, wie das gemacht werden kann.Decodieren von DTMF aus einer WAV-Datei

Ich verstehe die DTMF verwendet eine Kombination von Frequenzen, und ein Goertzel-Algorithmus kann verwendet werden ... irgendwie. Ich habe einen Goertzel Code-Schnipsel gepackt und ich habe versucht, in eine WAV-Datei schiebend (mit NAudio die Datei zu lesen, die ein 8 kHz Mono 16-Bit-PCM WAV ist):

using (WaveFileReader reader = new WaveFileReader(@"dtmftest_w.wav")) 
    { 
     byte[] buffer = new byte[reader.Length]; 

     int read = reader.Read(buffer, 0, buffer.Length); 
     short[] sampleBuffer = new short[read/2]; 
     Buffer.BlockCopy(buffer, 0, sampleBuffer, 0, read/2); 
     Console.WriteLine(CalculateGoertzel(sampleBuffer,8000,16));     
    } 

public static double CalculateGoertzel(short[] sample, double frequency, int samplerate) 
    { 
     double Skn, Skn1, Skn2; 
     Skn = Skn1 = Skn2 = 0; 
     for (int i = 0; i < sample.Length; i++) 
     { 
      Skn2 = Skn1; 
      Skn1 = Skn; 
      Skn = 2 * Math.Cos(2 * Math.PI * frequency/samplerate) * Skn1 - Skn2 + sample[i]; 
     } 
     double WNk = Math.Exp(-2 * Math.PI * frequency/samplerate); 
     return 20 * Math.Log10(Math.Abs((Skn - WNk * Skn1))); 
    } 

Ich weiß, was Ich mache es falsch: Ich gehe davon aus, dass ich den Puffer durchlaufen und nur den Goertzel-Wert für einen kleinen Block berechnen soll - ist das korrekt?

Zweitens verstehe ich nicht wirklich, was die Ausgabe der Goertzel Methode sagt mir: Ich habe ein Doppel (Beispiel: 210.985812) zurückgegeben, aber wenn ich weiß nicht, dass von einem auf das Vorhandensein und den Wert zu DTMF-Ton in der Audiodatei.

Ich habe überall nach einer Antwort gesucht, einschließlich der Bibliotheken, auf die in this Antwort verwiesen wird; Leider scheint der Code here nicht zu funktionieren (wie in den Kommentaren auf der Website erwähnt). Es gibt eine kommerzielle Bibliothek, die von TAPIEx angeboten wird; Ich habe ihre Auswertungsbibliothek ausprobiert und sie tut genau das, was ich brauche - aber sie reagieren nicht auf E-Mails, was mich davor skeptisch macht, ihr Produkt tatsächlich zu kaufen.

Ich bin mir sehr bewusst, dass ich nach einer Antwort suche, wenn ich vielleicht nicht die genaue Frage kenne, aber letztendlich brauche ich nur eine Möglichkeit, DTMF-Töne in einer .WAV-Datei zu finden. Bin ich auf der richtigen Linie, und wenn nicht, kann mir jemand in die richtige Richtung zeigen?

EDIT: Mit @Abbondanza 's Code als Grundlage, und auf der (wahrscheinlich grundsätzlich falschen) Annahme, dass ich kleine Teile der Audiodatei eintropfen lassen muss, habe ich jetzt diese (sehr grobe, (nur of-concept) Code:

const short sampleSize = 160; 

using (WaveFileReader reader = new WaveFileReader(@"\\mac\home\dtmftest.wav")) 
     {   
      byte[] buffer = new byte[reader.Length]; 

      reader.Read(buffer, 0, buffer.Length); 

      int bufferPos = 0; 

      while (bufferPos < buffer.Length-(sampleSize*2)) 
      { 
       short[] sampleBuffer = new short[sampleSize]; 
       Buffer.BlockCopy(buffer, bufferPos, sampleBuffer, 0, sampleSize*2); 


       var frequencies = new[] {697.0, 770.0, 852.0, 941.0, 1209.0, 1336.0, 1477.0}; 

       var powers = frequencies.Select(f => new 
       { 
        Frequency = f, 
        Power = CalculateGoertzel(sampleBuffer, f, 8000)    
       }); 

       const double AdjustmentFactor = 1.05; 
       var adjustedMeanPower = AdjustmentFactor*powers.Average(result => result.Power); 

       var sortedPowers = powers.OrderByDescending(result => result.Power); 
       var highestPowers = sortedPowers.Take(2).ToList(); 

       float seconds = bufferPos/(float)16000; 

       if (highestPowers.All(result => result.Power > adjustedMeanPower)) 
       { 
        // Use highestPowers[0].Frequency and highestPowers[1].Frequency to 
        // classify the detected DTMF tone. 

        switch (Convert.ToInt32(highestPowers[0].Frequency)) 
        { 
         case 1209: 
          switch (Convert.ToInt32(highestPowers[1].Frequency)) 
          { 
           case 697: 
            Console.WriteLine("1 pressed at " + bufferPos + " (" + seconds + "s)"); 
            break; 
           case 770: 
            Console.WriteLine("4 pressed at " + bufferPos + " (" + seconds + "s)"); 
            break; 
           case 852: 
            Console.WriteLine("7 pressed at " + bufferPos + " (" + seconds + "s)"); 
            break; 
           case 941: 
            Console.WriteLine("* pressed at " + bufferPos); 
            break; 
          } 
          break; 
         case 1336: 
          switch (Convert.ToInt32(highestPowers[1].Frequency)) 
          { 
           case 697: 
            Console.WriteLine("2 pressed at " + bufferPos + " (" + seconds + "s)"); 
            break; 
           case 770: 
            Console.WriteLine("5 pressed at " + bufferPos + " (" + seconds + "s)"); 
            break; 
           case 852: 
            Console.WriteLine("8 pressed at " + bufferPos + " (" + seconds + "s)"); 
            break; 
           case 941: 
            Console.WriteLine("0 pressed at " + bufferPos + " (" + seconds + "s)"); 
            break; 
          } 
          break; 
         case 1477: 
          switch (Convert.ToInt32(highestPowers[1].Frequency)) 
          { 
           case 697: 
            Console.WriteLine("3 pressed at " + bufferPos + " (" + seconds + "s)"); 
            break; 
           case 770: 
            Console.WriteLine("6 pressed at " + bufferPos + " (" + seconds + "s)"); 
            break; 
           case 852: 
            Console.WriteLine("9 pressed at " + bufferPos + " (" + seconds + "s)"); 
            break; 
           case 941: 
            Console.WriteLine("# pressed at " + bufferPos + " (" + seconds + "s)"); 
            break; 
          } 
          break; 
        } 
       } 
       else 
       { 
        Console.WriteLine("No DTMF at " + bufferPos + " (" + seconds + "s)"); 
       } 
       bufferPos = bufferPos + (sampleSize*2); 
      } 

Dies ist die Beispieldatei wie in Audacity angezeigt; Ich habe in den DTMF-Tastenfolgen hinzugefügt, die gedrückt- wurden

enter image description here

und ... es fast funktioniert. Aus der Datei oben soll ich bis fast genau 3 Sekunden sieht jede DTMF nicht in, aber mein Code Berichte:

9 pressed at 1920 (0.12s) 
1 pressed at 2880 (0.18s) 
* pressed at 3200 
1 pressed at 5120 (0.32s) 
1 pressed at 5440 (0.34s) 
7 pressed at 5760 (0.36s) 
7 pressed at 6080 (0.38s) 
7 pressed at 6720 (0.42s) 
5 pressed at 7040 (0.44s) 
7 pressed at 7360 (0.46s) 
7 pressed at 7680 (0.48s) 
1 pressed at 8000 (0.5s) 
7 pressed at 8320 (0.52s) 

... bis sie auf 3 Sekunden bekommen, und dann beginnt es sesshaft die richtige Antwort: dass 1 gedrückt wurde:

7 pressed at 40000 (2.5s) 
# pressed at 43840 (2.74s) 
No DTMF at 44800 (2.8s) 
1 pressed at 45120 (2.82s) 
1 pressed at 45440 (2.84s) 
1 pressed at 46080 (2.88s) 
1 pressed at 46720 (2.92s) 
4 pressed at 47040 (2.94s) 
1 pressed at 47360 (2.96s) 
1 pressed at 47680 (2.98s) 
1 pressed at 48000 (3s) 
1 pressed at 48960 (3.06s) 
4 pressed at 49600 (3.1s) 
1 pressed at 49920 (3.12s) 
1 pressed at 50560 (3.16s) 
1 pressed at 51520 (3.22s) 
1 pressed at 52160 (3.26s) 
4 pressed at 52480 (3.28s) 

Wenn ich die AdjustmentFactor über 1,2, stoßen erhalte ich sehr wenig Erkennung überhaupt.

Ich fühle, dass ich fast da bin, aber kann jemand sehen, was ich vermisse?

EDIT2: Die obige Testdatei ist verfügbar here.Die adjustedMeanPower in dem obigen Beispiel ist 47.6660450354638, und die Kräfte sind:

enter image description here

+0

Der DTMF-Band sollte mindestens 40ms lang sein und mindestens 40ms Platz haben. Siehe http://www.genave.com/dtmf-mark-space.htm –

+0

Auch die Frequenzen, die Sie erkennen müssen, sind 697Hz, 770Hz, 852Hz, 941Hz, 1209Hz, 1336Hz und 1477Hz gemäß http: //www.genave. com/dtmf.htm –

+0

Ich habe ein Code-Snippet zu meiner Antwort hinzugefügt. Lassen Sie mich wissen, ob es Ihnen geholfen hat, bei Ihrem Problem Fortschritte zu erzielen. –

Antwort

6

CalculateGoertzel() gibt den Strom der ausgewählten Frequenz innerhalb der vorgesehenen Probe.

Berechnen Sie diese Leistung für jede der DTMF-Frequenzen (697, 770, 852, 941, 1209, 1336 und 1477 Hz), sortieren Sie die resultierenden Leistungen und wählen Sie die höchsten zwei aus. Wenn beide über einem bestimmten Schwellenwert liegen, wurde ein DTMF-Ton erkannt.

Was Sie als Schwellenwert verwenden, hängt vom Signal-Rausch-Verhältnis (SNR) Ihrer Probe ab. Zunächst einmal sollte es ausreichen, den Mittelwert aller Goerzel-Werte zu berechnen, den Mittelwert mit einem Faktor (z. B. 2 oder 3) zu multiplizieren und zu prüfen, ob die beiden höchsten Goerzel-Werte über diesem Wert liegen.

Hier ist ein Code-Snippet zum Ausdruck zu bringen, was ich in einer formalen Art und Weise bedeuten:

var frequencies = new[] {697.0, 770.0, 852.0, 941.0, 1209.0, 1336.0, 1477.0}; 

var powers = frequencies.Select(f => new 
{ 
    Frequency = f, 
    Power = CalculateGoerzel(sample, f, samplerate) 
}); 

const double AdjustmentFactor = 1.0; 
var adjustedMeanPower = AdjustmentFactor * powers.Average(result => result.Power); 

var sortedPowers = powers.OrderByDescending(result => result.Power); 
var highestPowers = sortedPowers.Take(2).ToList(); 

if (highestPowers.All(result => result.Power > adjustedMeanPower)) 
{ 
    // Use highestPowers[0].Frequency and highestPowers[1].Frequency to 
    // classify the detected DTMF tone. 
} 

Beginnen Sie mit einem AdjustmentFactor von 1.0. Wenn Sie von Ihren Testdaten falsche Ergebnisse erhalten (d. H., Sie erkennen DTMF-Töne in Proben, in denen keine DTMF-Töne vorhanden sein sollten), erhöhen Sie sie weiter, bis die falschen positiven Ergebnisse aufhören.


Update # 1

habe ich versucht, den Code auf der Wave-Datei und angepasst, ein paar Dinge:

ich die zählbare nach der Goertzel Berechnung materialisierte (wichtig für die Leistung):

var powers = frequencies.Select(f => new 
{ 
    Frequency = f, 
    Power = CalculateGoertzel(sampleBuffer, f, 8000) 
// Materialize enumerable to avoid multiple calculations. 
}).ToList(); 

Ich habe den angepassten Mittelwert für die Schwellenwertbildung nicht verwendet. Früher habe ich nur 100.0 als Schwelle:

if (highestPowers.All(result => result.Power > 100.0)) 
{ 
    ... 
} 

Ich verdoppelte die Stichprobengröße (ich glaube, Sie 160 verwendet):

int sampleSize = 160 * 2; 

ich Ihre DTMF-Klassifikation festgelegt. Früher habe ich verschachtelten Wörterbücher alle möglichen Fälle zu erfassen:

var phoneKeyOf = new Dictionary<int, Dictionary<int, string>> 
{ 
    {1209, new Dictionary<int, string> {{1477, "?"}, {1336, "?"}, {1209, "?"}, {941, "*"}, {852, "7"}, {770, "4"}, {697, "1"}}}, 
    {1336, new Dictionary<int, string> {{1477, "?"}, {1336, "?"}, {1209, "?"}, {941, "0"}, {852, "8"}, {770, "5"}, {697, "2"}}}, 
    {1477, new Dictionary<int, string> {{1477, "?"}, {1336, "?"}, {1209, "?"}, {941, "#"}, {852, "9"}, {770, "6"}, {697, "3"}}}, 
    { 941, new Dictionary<int, string> {{1477, "#"}, {1336, "0"}, {1209, "*"}, {941, "?"}, {852, "?"}, {770, "?"}, {697, "?"}}}, 
    { 852, new Dictionary<int, string> {{1477, "9"}, {1336, "8"}, {1209, "7"}, {941, "?"}, {852, "?"}, {770, "?"}, {697, "?"}}}, 
    { 770, new Dictionary<int, string> {{1477, "6"}, {1336, "5"}, {1209, "4"}, {941, "?"}, {852, "?"}, {770, "?"}, {697, "?"}}}, 
    { 697, new Dictionary<int, string> {{1477, "3"}, {1336, "2"}, {1209, "1"}, {941, "?"}, {852, "?"}, {770, "?"}, {697, "?"}}} 
} 

Die Telefontaste dann mit abgerufen wird:

var key = phoneKeyOf[(int)highestPowers[0].Frequency][(int)highestPowers[1].Frequency]; 

Die Ergebnisse sind nicht perfekt, aber ein wenig zuverlässig.


Update # 2

Ich denke, ich habe das Problem herausgefunden, aber es nicht versuchen kann mich jetzt aus. Sie können die Zielfrequenz nicht direkt an CalculateGoertzel() übergeben. Es muss normalisiert werden, um über den DFT-Bins zentriert zu sein.Bei der Berechnung der Kräfte versuchen, diesen Ansatz:

var powers = frequencies.Select(f => new 
{ 
    Frequency = f, 
    // Pass normalized frequenzy 
    Power = CalculateGoertzel(sampleBuffer, Math.Round(f*sampleSize/8000.0), 8000) 
}).ToList(); 

Auch Sie haben 205 als sampleSize, um den Fehler minimieren das verwenden.


Update # 3

ich wieder schrieb der Prototyp NAudio der ISampleProvider Schnittstelle zu verwenden, die normalisierte Abtastwerte zurückgibt (float s in Bereich [-1,0, 1,0]). Auch ich schrieb CalculateGoertzel() von Grund auf neu. Es ist immer noch nicht leistungsoptimiert, aber es gibt viel, viel ausgeprägtere Leistungsunterschiede zwischen den Frequenzen. Es gibt keine mehr falsche Positive, wenn ich es Ihre Testdaten laufen lasse. Empfehle ich Ihnen einen Blick darauf werfen: http://pastebin.com/serxw5nG


Update # 4

ich eine GitHub project erstellt und two NuGet packages auf DTMF-Töne in Live (captured) Audio- und bespielten Audio-Dateien zu erkennen.

+1

Update # 2 funktionierte nicht für mich, ich habe Angst; Ich habe keine Ergebnisse von der Testdatei erhalten. Der Code in Update # 1 funktioniert jedoch (fast) perfekt, sicherlich gut genug für meine Zwecke. Vielen Dank für deine Hilfe, ich hätte es ohne dich nicht herausgefunden! – KenD

+0

Update # 3 funktioniert einwandfrei, nochmals vielen Dank! – KenD

+0

Funktioniert nicht, wenn die Abtastfrequenz 48000 ist. – moose