Декодирование DTMF из файла WAV

Исходя из моего предыдущего вопроса, моя цель - обнаружить тоны DTMF в файле WAV из C#. Однако я действительно изо всех сил пытаюсь понять, как это можно сделать.

Я понимаю, что DTMF использует комбинацию частот, и алгоритм Гертцеля может быть использован... каким-то образом. Я взял фрагмент кода Goertzel и попытался вставить в него файл.WAV (используя NAudio для чтения файла, который представляет собой моно 16-разрядный PCM WAV 8 КГц):

 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)));
    }

Я знаю, что я делаю неправильно: я предполагаю, что мне нужно пройти через буфер и вычислять только значение Гертцеля для небольшого фрагмента за раз - это правильно?

Во-вторых, я не совсем понимаю, что говорит мне вывод метода Гёртцела: я получаю двойное число (пример: 210.985812), но я не знаю, чтобы сравнить это с наличием и значением тона DTMF в аудиофайле.

Я всюду искал ответ, включая библиотеки, на которые есть ссылки в этом ответе; к сожалению, код здесь не работает (как отмечено в комментариях на сайте). TAPIEx предлагает коммерческую библиотеку; Я попробовал их оценочную библиотеку, и она делает именно то, что мне нужно, но они не отвечают на электронные письма, что заставляет меня опасаться за покупку их продукта.

Я очень хорошо понимаю, что ищу ответ, когда, возможно, я не знаю точного вопроса, но в конечном итоге все, что мне нужно, - это найти тоны DTMF в файле.WAV. Я на правильном пути, и если нет, может ли кто-нибудь указать мне правильное направление?

РЕДАКТИРОВАТЬ: Используя код @Abbondanza в качестве основы, и исходя из предположения (вероятно, в корне неверного), что мне нужно подавать небольшие фрагменты аудиофайла, у меня теперь есть это (очень грубое, только для проверки концепции)) код:

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);
            }

Это образец файла, как показано в Audacity; Я добавил в клавиши DTMF, которые были нажаты-

и... это почти работает. Из приведенного выше файла я не должен видеть DTMF почти ровно через 3 секунды, однако мой код сообщает:

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)

... пока не дойдет до 3 секунд, а затем он начинает принимать правильный ответ: что 1 был нажат:

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)

Если я подниму AdjustmentFactor после 1.2 я получаю очень мало обнаружения вообще.

Я чувствую, что я почти на месте, но кто-нибудь может увидеть, что мне не хватает?

EDIT2: тестовый файл выше доступен здесь. adjustedMeanPower в приведенном выше примере 47.6660450354638и полномочия:

1 ответ

Решение

CalculateGoertzel() возвращает мощность выбранной частоты в предоставленной выборке.

Рассчитайте эту мощность для каждой из частот DTMF (697, 770, 852, 941, 1209, 1336 и 1477 Гц), отсортируйте полученные мощности и выберите две старшие. Если оба значения выше определенного порога, то обнаружен тональный сигнал DTMF.

То, что вы используете в качестве порога, зависит от отношения сигнал / шум (SNR) вашей выборки. Для начала должно быть достаточно рассчитать среднее значение всех значений Гёрзеля, умножить среднее значение на множитель (например, 2 или 3) и проверить, превышают ли два самых высоких значения Гёрзеля это значение.

Вот фрагмент кода, чтобы выразить то, что я имею в виду, более формально:

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.
}

Начните с AdjustmentFactor из 1.0, Если вы получаете ложные срабатывания из ваших тестовых данных (т.е. вы обнаруживаете тоны DTMF в образцах, где не должно быть никаких тонов DTMF), продолжайте увеличивать его до тех пор, пока ложные срабатывания не прекратятся.


Обновление № 1

Я попробовал ваш код в волновом файле и скорректировал несколько вещей:

Я материализовал перечислимое после вычисления Гоерцеля (важно для производительности):

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

Я не использовал скорректированное среднее для порогового значения. Я просто использовал 100.0 в качестве порога:

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

Я удвоил размер выборки (я думаю, что вы использовали 160):

int sampleSize = 160 * 2;

Я исправил вашу классификацию DTMF. Я использовал вложенные словари, чтобы охватить все возможные случаи:

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, "?"}}}
}

Телефонный ключ затем извлекается с:

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

Результаты не идеальны, но несколько надежны.


Обновление № 2

Я думаю, что я понял проблему, но не могу попробовать это непосредственно сейчас. Вы не можете передать целевой частоты напрямую CalculateGoertzel(), Это должно быть нормализовано, чтобы быть сосредоточенным по бинам DFT. При расчете полномочий попробуйте этот подход:

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

Также вы должны использовать 205 как sampleSize чтобы минимизировать ошибку.


Обновление № 3

Я переписал прототип, чтобы использовать NAudio ISampleProvider интерфейс, который возвращает нормализованные выборочные значения (floats в диапазоне [-1,0; 1,0]). Также я переписал CalculateGoertzel() с нуля. Он по-прежнему не оптимизирован по производительности, но дает гораздо более выраженные различия в мощности между частотами. Больше нет ложных срабатываний, когда я запускаю ваши тестовые данные. Я настоятельно рекомендую вам взглянуть на это: http://pastebin.com/serxw5nG


Обновление № 4

Я создал проект GitHub и два пакета NuGet для обнаружения тонов DTMF в живом (захваченном) аудио и предварительно записанных аудиофайлах.

Другие вопросы по тегам