DTMF – FFT декодер
raxp@mail.zp.ua
Данный материал является продолжением цикла статей по VoIP телефонии [1].
F1/F2 |
1209 Гц |
1336 Гц |
1477 Гц |
1633 Гц |
697 Гц |
1 |
2 |
3 |
A |
770 Гц |
4 |
5 |
6 |
B |
852 Гц |
7 |
8 |
9 |
C |
941 Гц |
* |
0 |
# |
D |
В быту и промышленности…
Используя тональный набор, можно легко управлять приложениями компьютерной телефонии, отвечая на запросы системы нажатием клавиш на телефоне в режиме тонального набора, так и организовать помехоустойчивый канал связи-управления между цехами на основе имеющихся телефонных сетей или городскими сетями, в частности – светофоров (что уже существует)…
Немножко теории
DTMF (Dual Tone Multi Frequency) - термин тонального набора. DTMF сигнал является комбинацией двух частот, высокой и низкой. Система сигналов DTMF включает восемь тонов, подобранных так, чтобы передаваться через ТФоП с минимальным влиянием друг на друга.
Проблема и решение
Недостатка в готовых аппаратных реализациях кодеров-декодеров DTMF не наблюдается. Достаточно вспомнить отечественные микросхемы BЖ19/ВЖ19 (MV8870) или множество вариантов на обычных так и DSP контроллерах [2, 3]. А вот что касается программных визуальных решений для ПК, то дальше “демо” версий с закрытыми алгоритмами, исключая специализированные военные применения, дело не дошло. Как быть?
Сразу же введем таблицу массивов тонов:
keys = '1234567890*#abcd';
dtmf1: array[1..16]of integer =(697,697,697,770,770,770,852,852,852,941,941,941,697,770,852,941);
dtmf2: array[1..16]of integer =(1209,1336,1477,1209,1336,1477,1209,1336,1477,1336,1209,1477,1633,1633,1633,1633)
и разделим “проблему” на подзадачи:
- выделить набор частот (спектр) из исходного сигнала
- идентифицировать по двум частотам, согласно таблице, символ DTMF
Вспомним курс математики [4]. Для выделения спектра как правило используется быстрое преобразование Фурье (БПФ). В нашем случае реализация БПФ будет следующей:
procedure FFTQuad(number: dword; InData, OutData: Parraysingle);
function inv_diskret(a: Integer): Integer;
var b: Integer;
begin
Result:= 0;
for b:= 0 to Base - 1 do
if a and (1 shl b) <> 0 then
Result:= Result or (1 shl (Base - b - 1))
end;
begin
maxbase:= 0;
repeat inc(Base);
until (Number shr Base)=1;
ZeroMemory(OutData, Number * SizeOf(Single));
for i := 1 to Number - 1 do
begin
j := inv_diskret(i);
if i <= j then begin
C := InData[i];
InData[i] := InData[j];
InData[j] := C
end
end;
for i := 1 to maxBase do begin
lp := 1 shl i;
lp2 := Number div lp;
lp3 := lp div 2;
StepAng := -pi / lp3;
Ang := 0;
for j:=0 to lp3-1 do begin
C := Cos(Ang);
S := Sin(Ang);
Ang := Ang + StepAng;
k1 := j;
for k := 0 to lp2 - 1 do begin
k2 := k1 + lp3;
//наши квадратуры-
Re := C * InData[k2] + S * OutData[k2];
Im := C * OutData[k2] - S * InData[k2];
InData[k2] := InData[k1] - Re;
OutData[k2] := OutData[k1] - Im;
InData[k1] := InData[k1] + Re;
OutData[k1] := OutData[k1] + Im;
k1 := k1 + lp
end
end
end;
for i := 0 to Number - 1 do begin
InData[i] := InData[i] / Number;
OutData[i] := OutData[i] / Number
end
end;
Для идентификации полученного символа DTMF из вычисленных наборов частот применяется алгоритм декодирования. На практике нам достаточно найти 2 максимальных амплитуды из набора и сопоставить их частоты – частотам DTMF. Достоверность этого сопоставления определяется двумя критериями:
- оба максимальных значения должны превышать уровень шума
- незначительность отличия этих амплитуд друг от друга
Практика
Для работы нам следует запастись следующим:
- среда разработки Borland Delphi 5-7
- аудио - редактор Sound Forge 8.0
Прежде всего в SoundForge на вкладке Tools/Synthesis/DTMF-MF Tones создадим тестовые DTMF сигналы, что значительно облегчит нам процесс отладки. Для загрузки данных из WAV- файла PCM формата
//wav-
r:= ReadWave(s);
r.Data.seek(0, soFromBeginning);
for i := 1 to r.Data.Size div 2 do begin
r.Data.readBuffer(W1,1);
r.Data.readBuffer(W2,1);
series3.Add(W1); //1- канал
series4.Add(W2) //2-
end;
понадобится его заголовок
function ReadWave(FileName: AnsiString) : TWaveResult;
var f : TFileStream;
wFileSize : Cardinal;
wChankSize : Cardinal;
ID : array[0..3] of Char;
Header : TWaveHeaderChank;
RealFileSize : Cardinal;
Begin
FillChar(Result, SizeOf(Result), 0);
Try
f := TFileStream.Create(FileName, fmOpenRead);
f.Seek(0, soFromBeginning);
f.ReadBuffer(ID[0], 4);
if String(ID) <> 'RIFF' then Begin //
Result.ERROR := IncorectFileFormat;
f.Free;
exit;
end;
f.ReadBuffer(wFileSize, 4);
if f.size <> (wFileSize + 8) then begin
Result.ERROR := FileCorrupt;
f.Free;
exit;
end;
f.ReadBuffer(ID[0], 4);
if String(ID) <> 'WAVE' then begin //проверяем-
Result.ERROR := IncorectFileFormat;
f.Free;
exit;
end;
wChankSize := 0;
repeat
f.Seek(wChankSize, soFromCurrent);
f.ReadBuffer(ID[0], 4);
f.ReadBuffer(wChankSize, 4);
if wChankSize > High(integer) then Begin
Result.ERROR := DataError;
f.Free;
exit;
end;
until (String(ID)='fmt ') or (String(ID)='data');
if String(ID)='data' then Begin
Result.ERROR := HeaderError;
f.Free;
exit;
end;
f.ReadBuffer(Header, Min(wChankSize, SizeOf(TWaveHeaderChank)));
if wChankSize > SizeOf(TWaveHeaderChank)
then f.Seek(wChankSize - SizeOf(TWaveHeaderChank), soFromCurrent);
wChankSize := 0;
repeat
f.Seek(wChankSize, soFromCurrent);
f.ReadBuffer(ID[0], 4);
f.ReadBuffer(wChankSize, 4);
until String(ID)='data';
Result.ERROR:= noError;
Result.wAvgBytesPerSec := Header.wAvgBytesPerSec;
Result.wBitsPerSample := Header.wBitsPerSample;
Result.wChannels := Header.wChannels;
Result.Data := TMemoryStream.Create;
Result.Data.Seek(0, soFromBeginning);
Result.Data.Size := wChankSize;
f.ReadBuffer(Result.Data.Memory^, wChankSize);
Except
Result.ERROR := ReadError;
end;
f.Free
end;
//===============
далее передаем набор данных в процедуру БПФ:
FFTQuad(series4,ser2,1024); //набор отсчетов- на 1024 точки
Переходим ко второй подзадаче – поиск и идентификация 2-х частот:
//2-х проходной поиск-
a1:= -1000;
for i:= 0 to (ser2.YValues.Count)-1 do begin //
a:= ser2.YValues[i];
f:= i / (ser2.YValues.Count/2) * (r.wSamplePerSec / 2);
Series1.AddXY(f,a);
if a > a1 then begin a1:= a; f1:= f end
end;
a2:= -1000;
for i:= (ser2.YValues.Count)-1 downto 0 do begin
a:= ser2.YValues[i];
f:= i / (ser2.YValues.Count/2) * (r.wSamplePerSec / 2);
if (a > a2)and(a<>a1) then begin a2:= a; f2:= f end
end;
По нашей таблице массивов делаем выборку:
//идентификация-
dtmf_sig:= 'not';
for i:= 1 to 16 do begin
if (dtmf2[i]*0.98f1)and //1 амплитуда >2
// 0.98 и 1.02 это своего рода доверительный интервал
(dtmf1[i]*0.98f2)then begin // определяющий помехоустойчивость
dtmf_sig:= keys[i]; // и учитывающий разброс в параметрах генераторов DTMF
break
end;
if (dtmf1[i]*0.98f1)and //1 амплитуда >2
(dtmf2[i]*0.98f2)then begin
dtmf_sig:= keys[i];
break
end;
end;
Как быть с обработкой в реальном времени? Воспользуемся ф-цией WaveInOpen, чтобы получить доступ к *аудиоустройству:
WaveInOpen(Addr(hwi2), WAVE_MAPPER, addr(header),
integer(@waveInProc2), 0,CALLBACK_FUNCTION);
//= systimer2 =
procedure waveInProc2(hwi: HWAVEIN; uMsg,dwInstance,
dwParam1,dwParam2: DWORD);stdcall;
var i: integer;
data16: PData16;
h: integer;
XScale, YScale: single;
temp: pWaveHdr;
a,a1,a2, //амплитуды-
f,f1,f2: double; //частоты-
begin
if (uMsg=WIM_DATA) then begin
temp:= adr2;
if adr2= @bufhead1 then adr2:= @bufhead2
else adr2:= @bufhead1;
if stp2 then WaveInAddBuffer(hwi,adr2,SizeOf(TWaveHdr));
data16:= PData16(temp.lpData);
inwav.Clear; outwav.Clear;
form1.series5.Clear;
form1.series6.Clear;
for i := 0 to BufSize - 1 do begin //набивка-
form1.series6.add(data16^[i]);
inwav.add(data16^[i])
end;
FFTQuad(inwav,outwav,512);
//2-х проходной поиск-
a1:= -1000;
for i:= 0 to (outwav.YValues.Count)-1 do begin //
a:= outwav.YValues[i];
f:= i / (outwav.YValues.Count/2) * (header.nSamplesPerSec / 2);
form1.series5.AddXY(f,a);
if a > a1 then begin a1:= a; f1:= f end
end;
a2:= -1000;
for i:= (outwav.YValues.Count)-1 downto 0 do begin
a:= outwav.YValues[i];
f:= i / (outwav.YValues.Count/2) * (header.nSamplesPerSec / 2);
if (a > a2)and(a<>a1) then begin a2:= a; f2:= f end
end;
//идентификация-
dtmf_sig:= 'not';
for i:= 1 to 16 do begin
if (dtmf2[i]*0.98f1)and //1 амплитуда >2
(dtmf1[i]*0.98f2)then begin
dtmf_sig:= keys[i];
break
end;
if (dtmf1[i]*0.98f1)and //1 амплитуда <2
(dtmf2[i]*0.98f2)then begin
dtmf_sig:= keys[i];
break
end;
end;
//-----------------------------------
end else Exit
end;
* не забудьте в проекте подключить модуль mmsystem
Итак, в результате компиляции получаем тестовую версию декодера DTMF (см. рис.):
Рис. Окно декодера
Ссылки:
P>Контактная информация:
raxp@mail.zp.ua
01.03.2008
[Переход к списку статей]
|