视频监控平台-GB28181-2016语音对讲功能
很多人在问我,语音对讲是怎么流程, 实现了怎么去测试,语音对讲是在2014版本提出来的,我这里详细写一遍国标28181-2016语音对讲的功能介绍。(如需交流可联系QQ:123011785)
提前先把GB28181检测需要注意的事项说一下:
1、语音对讲其实主要的是音频流的输入输出, 输入设备类型是136,输出设备类型是137
2、目前检测的时候,海康摄像机是定制的检测版本有固定的137类型id,一般海康摄像机是没有配置音频输出通道的,需要自己通过SDK模拟一个音频输出设备或则让海康提供 28181检测的固件版本。
3、检测时候需要用笔记本电脑模拟一个136音频输入的设备,一般和客户端绑定一起(或则后台服务器配置相应的id和客户端绑定一起)
4、音频输入源一般是采用笔记本采集的音频信号,音频采集代码后面的博客提供。
下面看一下语音对讲的流程:
其中, 信令 1 、
2 、 3 、 4 为语音广播通知、 语音广播应答消息流程; 信令 5 、 1 2 、 1 3 、 1 4 、 1 5 、 1 6 为 S I P 服务
器接收到客户端的呼叫请求通过 B 2 B UA 代理方式建立语音流接收者与媒体服务器之间的媒体流信令
过程, 信令 6~1 1 为 S I P 服务器通过三方呼叫控制建立媒体服务器与语音流发送者之间的媒体流信令
过程, 信令 1 7~2 0 为 S I P 服务器断开语音流接收者与媒体服务器之间的媒体流信令过程, 信令 2 1~2 4
6 4
G B / T2 8 1 8 1 — 2 0 1 6
为 S I P 服务器断开媒体服务器与语音流发送者之间的媒体流信令过程。
命令流程描述如下:
a ) 1 : S I P 服务器向语音流接收者发送语音广播通知消息, 消息中通过 T o 头域标明作为目的地址
的语音流接收者 I D , 消息采用 M e s s a g e 方法携带。
b ) 2 : 语音流接收者收到语音广播通知消息后, 向 S I P 服务器发送 2 0 0OK 响应。
c ) 3 : 语音流接收者向 S I P 服务器发送语音广播应答消息, 消息中通过 T o 头域标明作为目的地
址的 S I P 服务器 I D , 消息采用 M e s s a g e 方法携带。
d ) 4 : S I P 服务器收到语音广播应答消息后, 向语音流接收者发送 2 0 0OK 响应。
e ) 5 : 语音流接收者向 S I P 服务器发送 I n v i t e 消息, 消息中通过 T o 头域标明作为目的地址的语音
流发送者 I D , 消息头域中携带 S u b j e c t 字段, 表明请求的语音流发送者 I D 、 发送方媒体流序列
号、 语音流接收者 I D 、 接收方媒体流序列号等参数, S D P 消息体中 s 字段为“ P l a y ” 代表实时点
播, m 字段中媒体参数标识为“
a u d i o ” 表示请求语音媒体流。
f ) 6 : S I P 服务器收到 I n v i t e 请求后, 通过三方呼叫控制建立媒体服务器和语音流发送者之间的
媒体连接。向媒体服务器发送 I n v i t e 消息, 此消息不携带 S D P 消息体。
g ) 7 : 媒体服务器收到 S I P 服务器的 I n v i t e 请求后, 回复 2 0 0OK 响应, 携带 S D P 消息体, 消息体
中描述了媒体服务器接收媒体流的 I P 、 端口、 媒体格式等内容。
h ) 8 : S I P 服务器收到媒体服务器返回的 2 0 0OK 响应后, 向语音流发送者发送 I n v i t e 请求, 消息
中通过 T o 头域标明作为目的地址的语音流发送者 I D , 消息头域中携带 S u b j e c t 字段, 表明请
求的语音流发送者 I D 、 发送方媒体流序列号、 语音流接收者 I D 、 接收方媒体流序列号等参数,
请求中携带消息 7 中媒体服务器回复的 2 0 0OK 响应消息体, s 字段为“ P l a y ” 代表实时点播,
m 字段中媒体参数标识为“ a u d i o ” 表示请求语音媒体流, 增加 y 字段描述 S S R C 值, f 字段描述
媒体参数。
i ) 9 : 语音流发送者收到 S I P 服务器的 I n v i t e 请求后, 回复 2 0 0OK 响应, 携带 S D P 消息体, 消息
体中描述了媒体流发送者发送媒体流的 I P 、 端口、 媒体格式、 S S R C 字段等内容, s 字段为
“
P l a y ” 代表实时点播,
m 字段中媒体参数标识为“ a u d i o ” 表示请求语音媒体流。
j ) 1 0 : S I P 服务器收到语音流发送者返回的 2 0 0OK 响应后, 向媒体服务器发送 A C K 请求, 请求
中携带消息 9 中语音流发送者回复的 2 0 0OK 响应消息体, 完成与媒体服务器的 I n v i t e 会话
建立过程。
k ) 1 1 : S I P 服务器收到语音流发送者返回的 2 0 0OK 响应后, 向语音流发送者发送 A C K 请求, 请
求中不携带消息体, 完成与语音流发送者的 I n v i t e 会话建立过程。
l ) 1 2 : 完成三方呼叫控制后, S I P 服务器通过 B 2 B UA 代理方式建立语音流接收者和媒体服务器
之间的媒体连接。在消息 5 中增加 S S R C 值, 转发给媒体服务器。
m ) 1 3 : 媒体服务器收到 I n v i t e 请求, 回复 2 0 0OK 响应, 携带 S D P 消息体, 消息体中描述了媒体服
务器发送媒体流的 I P 、 端口、 媒体格式、 S S R C 值等内容,
s 字段为“ P l a y ” 代表实时点播, m 字段
中媒体参数标识为“
a u d i o ” 表示请求语音媒体流。
n ) 1 4 : S I P 服务器将消息 1 3 转发给语音流接收者。
o ) 1 5 : 语音流接收者收到 2 0 0OK 响应后, 回复 A C K 消息, 完成与 S I P 服务器的 I n v i t e 会话建立
过程。
p ) 1 6 : S I P 服务器将消息 1 5 转发给媒体服务器, 完成与媒体服务器的 I n v i t e 会话建立过程。
q ) 1 7 : S I P 服务器向语音流接收者发送 B Y E 消息, 断开消息 5 、 1 4 、 1 5 建立的 I n v i t e 会话。
r ) 1 8 : 语音流接收者收到 B Y E 消息后回复 2 0 0OK 响应, 会话断开。
s ) 1 9 : S I P 服务器向媒体服务器发送 B Y E 消息, 断开消息 1 2 、 1 3 、 1 6 建立的同媒体服务器的
I n v i t e 会话。
上面是28181协议里面规定的流程,直接照搬过来,不管怎么实现语音对讲也要根据流程走。
下面我把抓包详情粘贴下:
MESSAGE sip:[email protected]:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.93:5060;rport;branch=z9hG4bK-3d09000-1047e076-A8X5JYC1
From: ;tag=CN2Ei3Vu
To:
Call-ID: [email protected]
CSeq: 55 MESSAGE
Contact:
Content-Type: Application/MANSCDP+xml
Max-Forwards: 70
User-Agent: iVMS 1.0
Content-Length: 173
Broadcast
20
64000000001360000001
34020000001370000001
SIP/2.0 200 OK
To: ;tag=75600014_53173353_c376baa4-b5f9-4f2a-a739-653dc3299ae1
Via: SIP/2.0/UDP 192.168.1.93:5060;rport=5060;branch=z9hG4bK-3d09000-1047e076-A8X5JYC1;received=192.168.1.93
CSeq: 55 MESSAGE
Call-ID: [email protected]
From: ;tag=CN2Ei3Vu
Content-Length: 0
MESSAGE sip:64000000002000000001@6400000000 SIP/2.0
Call-ID: [email protected]
CSeq: 1 MESSAGE
From: ;tag=78679367_53173353_5e822bd3-744e-4d50-a7ae-3dcb31308ad5
To:
Max-Forwards: 70
Content-Encoding: GB2312
Content-Type: Application/MANSCDP+xml
Route:
Via: SIP/2.0/UDP 192.168.1.81:5060;branch=z9hG4bK5e822bd3-744e-4d50-a7ae-3dcb31308ad5_53173353_28675579067886
Content-Length: 147
Broadcast
20
34020000001370000001
OK
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.81:5060;branch=z9hG4bK5e822bd3-744e-4d50-a7ae-3dcb31308ad5_53173353_28675579067886
From: ;tag=78679367_53173353_5e822bd3-744e-4d50-a7ae-3dcb31308ad5
To: ;tag=nVa5oJ2n
Call-ID: [email protected]
CSeq: 1 MESSAGE
Contact:
Content-Length: 0
INVITE sip:64000000001360000001@6400000000 SIP/2.0
Call-ID: [email protected]
CSeq: 1 INVITE
From: ;tag=84133916_53173353_4063c926-989f-4a9a-af9c-867f8219c6ab
To:
Max-Forwards: 70
Contact: "34020000002000000001"
Subject: 64000000001360000001:0-4-0,34020000002000000001:1
Content-Type: application/sdp
Route:
Via: SIP/2.0/UDP 192.168.1.81:5060;branch=z9hG4bK4063c926-989f-4a9a-af9c-867f8219c6ab_53173353_28675585450209
Content-Length: 171
v=0
o=64010000002020000001 0 0 IN IP4 192.168.1.81
s=Play
c=IN IP4 192.168.1.81
t=0 0
m=audio 8000 RTP/AVP 96
a=recvonly
a=rtpmap:96 PS/90000
y=0100000001
f=v/////a/1/8/1
SIP/2.0 100 Trying
Via: SIP/2.0/UDP 192.168.1.81:5060;branch=z9hG4bK4063c926-989f-4a9a-af9c-867f8219c6ab_53173353_28675585450209
From: ;tag=84133916_53173353_4063c926-989f-4a9a-af9c-867f8219c6ab
To:
Call-ID: [email protected]
CSeq: 1 INVITE
Content-Length: 0
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.81:5060;branch=z9hG4bK4063c926-989f-4a9a-af9c-867f8219c6ab_53173353_28675585450209
Record-Route:
From: ;tag=84133916_53173353_4063c926-989f-4a9a-af9c-867f8219c6ab
To: ;tag=Wn1J54GK
Call-ID: [email protected]
CSeq: 1 INVITE
Contact:
Content-Type: application/sdp
Content-Length: 180
v=0
o=64000000001360000001 0 0 IN IP4 192.168.1.93
s=Play
c=IN IP4 192.168.1.93
t=0 0
m=audio 20104 RTP/AVP 8
a=sendonly
a=rtpmap:8 PS/90000
y=0100000001
f=v/////a/1/8/1
ACK sip:[email protected]:5060 SIP/2.0
Call-ID: [email protected]
CSeq: 1 ACK
From: ;tag=84133916_53173353_4063c926-989f-4a9a-af9c-867f8219c6ab
To: ;tag=Wn1J54GK
Max-Forwards: 70
Route:
Via: SIP/2.0/UDP 192.168.1.81:5060;branch=z9hG4bK4063c926-989f-4a9a-af9c-867f8219c6ab_53173353_28675725931057
Content-Length: 0
流程抓包截图:
音频流采用的G711格式:
下面粘贴音频采集代码的片段:
#pragma once
#include
#include
#include
#include
#include
#include "WaveHeader.h"
#include
#include "SoundRecord.h"
#include "g711.h"
class CDSRecord : public CSoundRecord
{
public:
CDSRecord(ISoundNotify* pNotify);
~CDSRecord(void);
LPDIRECTSOUNDCAPTURE m_pDSCapture;
LPDIRECTSOUNDCAPTUREBUFFER m_pDSBCapture;
LPDIRECTSOUNDNOTIFY m_pDSNotify;
HINSTANCE m_hInst;
bool m_bRecording;
WAVEFORMATEX m_wfxInput;
DSBPOSITIONNOTIFY m_aPosNotify[4];
HANDLE m_hNotificationEvent;
HANDLE m_hStopThreadEvent;
BOOL m_abInputFormatSupported[20];
DWORD m_dwCaptureBufferSize;
DWORD m_dwNextCaptureOffset;
DWORD m_dwNotifySize;
HANDLE m_hThread;
WAVEFORMATEX WaveFormat;
float bSampleReal[1024];
float bSampleImg[1024];
bool InitDS(void);
bool SaveDataToFile(LPCTSTR m_pathname);
bool ReadCaptureBuffer(void);
bool StarRecord(void);
bool StopRecord(void);
std::list SizeList;
std::list BufList;
CWaveHeader WaveHeader;
bool Sampling(void);
void FFT(float xreal [], float ximag [], int n);
//通知接口
ISoundNotify* m_pISoundNotify;
// 销毁
void Destroy(void);
// 开始
bool Start(void);
// 结束
bool Stop(void);
protected:
void bitrp(float xreal [], float ximag [], int n);
void IFFT (float xreal [], float ximag [], int n);
};
#include "StdAfx.h"
#include "DSRecord.h"
UINT ReceiveDataThread(void* pParam);
CDSRecord::CDSRecord(ISoundNotify* pNotify)
:m_pDSCapture(0)
,m_pDSBCapture(0)
,m_pDSNotify(0)
,m_bRecording(false)
{
ZeroMemory(&bSampleReal,1024);
ZeroMemory(&bSampleImg,1024);
m_pISoundNotify = pNotify;
}
CDSRecord::~CDSRecord(void)
{
while(!BufList.empty())
{
free(BufList.front());
BufList.pop_front();
}
}
bool CDSRecord::InitDS(void)
{
HRESULT hr;
// 创建DSC对象
if( FAILED( hr = DirectSoundCaptureCreate(NULL, &m_pDSCapture, NULL ) ) )
{
_WRITE_LOG( LOG_LEVEL_WARNING, "创建DSC对象失败");
return false;
}
//初始化录音格式----------------------------------------------
WaveFormat.wFormatTag = WAVE_FORMAT_PCM;
WaveFormat.nSamplesPerSec = 8000;//G.711采样频率
WaveFormat.wBitsPerSample = 16; //16位
WaveFormat.nChannels = 2;
WaveFormat.nBlockAlign = WaveFormat.nChannels * ( WaveFormat.wBitsPerSample / 8 );
WaveFormat.nAvgBytesPerSec = WaveFormat.nBlockAlign * WaveFormat.nSamplesPerSec;
//初始化buffer
// Create the capture buffer
m_dwCaptureBufferSize = WaveFormat.nAvgBytesPerSec / 20*8; //设定8秒的缓冲区;
m_dwNextCaptureOffset=0;
DSCBUFFERDESC dscbd;
ZeroMemory( &dscbd, sizeof(dscbd) );
dscbd.dwSize = sizeof(dscbd);
dscbd.dwBufferBytes = m_dwCaptureBufferSize;
dscbd.lpwfxFormat = &WaveFormat; // Set the format during creatation
if( FAILED( hr = m_pDSCapture->CreateCaptureBuffer(&dscbd, &m_pDSBCapture, NULL ) ) )
{
_WRITE_LOG(LOG_LEVEL_WARNING,"创建缓冲区失败");
return false;
}
//初始化消息
// Create a notification event, for when the sound stops playing
if( FAILED( hr = m_pDSBCapture->QueryInterface( IID_IDirectSoundNotify,(VOID**)&m_pDSNotify ) ) )
{
_WRITE_LOG(LOG_LEVEL_WARNING,"创建消息界面失败");
return false;
}
// Set the notification size
m_dwNotifySize =m_dwCaptureBufferSize/4; //到文件尾时
m_dwNotifySize -=m_dwNotifySize %WaveFormat.nBlockAlign;
// Setup the notification positions
m_hNotificationEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
m_hStopThreadEvent = CreateEvent(NULL,FALSE,FALSE, NULL);
ZeroMemory( &m_aPosNotify, sizeof(DSBPOSITIONNOTIFY) * 4 );
for(int i=0;i<4;i++)
{
m_aPosNotify[i].dwOffset = m_dwNotifySize*i%m_dwCaptureBufferSize;
m_aPosNotify[i].hEventNotify = m_hNotificationEvent;
}
// Tell DirectSound when to notify us. the notification will come in the from
// of signaled events that are handled in WinMain()
if( FAILED( hr =m_pDSNotify->SetNotificationPositions(2, m_aPosNotify) ) )
{
_WRITE_LOG(LOG_LEVEL_WARNING,"设置消息位置失败");
return false;
}
return true;
}
bool CDSRecord::ReadCaptureBuffer(void)
{
if(!m_pDSBCapture|!m_bRecording)
return false;
HRESULT hr;
VOID* pbCaptureData = NULL;
DWORD dwCaptureLength=0;
VOID* pbCaptureData2 = NULL;
DWORD dwCaptureLength2=0;
DWORD dwReadPos;
DWORD dwCapturePos;
LONG lLockSize;
if( FAILED( hr = m_pDSBCapture->GetCurrentPosition( &dwCapturePos, &dwReadPos ) ) )
{
this->m_bRecording=false;
_WRITE_LOG(LOG_LEVEL_WARNING,"读取当前指针位置错误");
}
lLockSize = dwReadPos - m_dwNextCaptureOffset;
if( lLockSize < 0 )
lLockSize += m_dwCaptureBufferSize;
if( lLockSize == 0 )
return false;
// Lock the capture buffer down
if( FAILED( hr = m_pDSBCapture->Lock( m_dwNextCaptureOffset, lLockSize,
&pbCaptureData, &dwCaptureLength,
&pbCaptureData2, &dwCaptureLength2, 0L ) ) )
return false;
if(dwCaptureLength+dwCaptureLength2!=lLockSize)
_WRITE_LOG(LOG_LEVEL_WARNING,"error");
// Write the data into list
WaveHeader.dwRIFFLen+=lLockSize;
WaveHeader.dwdataLen+=lLockSize;
void* TmpBuf=malloc(lLockSize);
//BufList.push_back(TmpBuf);
//SizeList.push_back(lLockSize);
memcpy(TmpBuf,pbCaptureData,dwCaptureLength);
if( pbCaptureData2)
{
memcpy((BYTE*)TmpBuf+dwCaptureLength,pbCaptureData2,dwCaptureLength2);
}
int G711Size = 0;
unsigned char* G711Buf = (unsigned char*)malloc(lLockSize/2);
for(int i = 0;i < lLockSize/2;i++)
{
int out_size = 1;//8位G711
unsigned char g711_val = 0;
g711_val = linear2alaw(*((short*)TmpBuf + i));
memcpy(G711Buf + G711Size,&g711_val,out_size);
G711Size += out_size;
}
#if 0
static FILE* m_myfVideo = NULL;
if (m_myfVideo == NULL)
{
m_myfVideo = fopen("c:\\audio.pcm", "ab+");
}
fwrite(G711Buf,1,G711Size,m_myfVideo);
#endif
m_pISoundNotify->OnGetAudioData(G711Buf,G711Size);
// Unlock the capture buffer
m_pDSBCapture->Unlock( pbCaptureData, dwCaptureLength,
pbCaptureData2, dwCaptureLength2 );
//让m_dwNextCaptureOffset向前移动到读指针处
m_dwNextCaptureOffset+=lLockSize;
m_dwNextCaptureOffset=m_dwNextCaptureOffset% m_dwCaptureBufferSize;
free(TmpBuf);
free(G711Buf);
return true;
}
bool CDSRecord::Sampling(void)
{
if(!m_pDSBCapture|!m_bRecording )
return false;
HRESULT hr;
VOID* pbCaptureData = NULL;
DWORD dwCaptureLength=0;
VOID* pbCaptureData2 = NULL;
DWORD dwCaptureLength2=0;
DWORD dwReadPos;
DWORD dwCapturePos;
LONG lLockSize;
DWORD dwStartPos;
if( FAILED( hr = m_pDSBCapture->GetCurrentPosition( &dwCapturePos, &dwReadPos ) ) )
{
this->m_bRecording=false;
_WRITE_LOG(LOG_LEVEL_WARNING,"读取当前指针位置错误");
}
lLockSize = 1024;
dwStartPos=dwReadPos-lLockSize;
if(dwStartPos<0)
dwStartPos+=lLockSize;
// Lock the capture buffer down
if( FAILED( hr = m_pDSBCapture->Lock( dwStartPos, lLockSize,
&pbCaptureData, &dwCaptureLength,
&pbCaptureData2, &dwCaptureLength2, 0L ) ) )
return false;
for(DWORD i=0;ibSampleReal[i]=pPos/255;
this->bSampleImg[i]=0;
}
if( pbCaptureData2)
{
for(DWORD i=0;ibSampleReal[dwCaptureLength+i]=(float)*pPos/255;
this->bSampleImg[i]=0;
}
}
// Unlock the capture buffer
m_pDSBCapture->Unlock( pbCaptureData, dwCaptureLength,
pbCaptureData2, dwCaptureLength2 );
return true;
}
UINT ReceiveDataThread(void* pParam)
{
CDSRecord *pApp=(CDSRecord*)pParam;
HANDLE hArray[2]={pApp->m_hNotificationEvent,pApp->m_hStopThreadEvent};
DWORD EventResult;
DWORD HandleNumber=sizeof(hArray)/sizeof(HANDLE);
while(pApp->m_bRecording)
{
EventResult = WaitForMultipleObjects(
HandleNumber,
hArray,
FALSE,
INFINITE);
if(WAIT_OBJECT_0+1 == EventResult)
break;
if(WAIT_OBJECT_0 == EventResult)
{
pApp->ReadCaptureBuffer();
ResetEvent(pApp->m_hNotificationEvent);
}
}
return 0;
}
bool CDSRecord::StarRecord(void)
{
if(!InitDS())
return false;
if( FAILED(m_pDSBCapture->Start( DSCBSTART_LOOPING ) ) )
{
_WRITE_LOG(LOG_LEVEL_WARNING,"开始录音失败");
return false;
}
unsigned int ThrdAddr;
m_hThread = (HANDLE) _beginthreadex(NULL,
0,
(unsigned int (__stdcall *)(void *))ReceiveDataThread,
(void *)(this),//NULL,
0, &ThrdAddr);
this->m_bRecording=true;
return true;
}
bool CDSRecord::StopRecord(void)
{
if(m_pDSBCapture == 0)
{
return false;
}
if( FAILED(m_pDSBCapture->Stop() ) )
{
_WRITE_LOG(LOG_LEVEL_WARNING,"停止录音失败");
return false;
}
SetEvent(m_hStopThreadEvent);
this->m_bRecording=false;
return true;
}
bool CDSRecord::SaveDataToFile(LPCTSTR m_pathname)
{
//m_pDSBCapture->GetFormat(&WaveHeader.WaveFormat, sizeof(WAVEFORMATEX),NULL);
//if(m_pathname)
//{
// CFile file(m_pathname,CFile::modeWrite|CFile::modeCreate);
// file.Write(&WaveHeader,46);
// for(;!BufList.empty();)
// {
// void* pBuf=BufList.front();
// file.Write(pBuf,SizeList.front());
// BufList.pop_front();
// SizeList.pop_front();
// free(pBuf);
// }
// file.Flush();
// file.Close();
//}
//else
//{
// _WRITE_LOG(LOG_LEVEL_WARNING,"请选择保存路径");
//}
return true;
}
inline void swap (float &a, float &b)
{
float t;
t = a;
a = b;
b = t;
}
void CDSRecord::bitrp(float xreal [], float ximag [], int n)
{
// 位反转置换 Bit-reversal Permutation
int i, j, a, b, p;
for (i = 1, p = 0; i < n; i *= 2)
{
p ++;
}
for (i = 0; i < n; i ++)
{
a = i;
b = 0;
for (j = 0; j < p; j ++)
{
b = (b << 1) + (a & 1); // b = b * 2 + a % 2;
a >>= 1; // a = a / 2;
}
if ( b > i)
{
swap (xreal [i], xreal [b]);
swap (ximag [i], ximag [b]);
}
}
}
void CDSRecord::FFT(float xreal [], float ximag [], int n)
{
const int N = 1024;
const float PI = 3.1416;
// 快速傅立叶变换,将复数 x 变换后仍保存在 x 中,xreal, ximag 分别是 x 的实部和虚部
float* wreal=new float [n / 2];
float* wimag=new float [n / 2];
float treal, timag, ureal, uimag, arg;
int m, k, j, t, index1, index2;
bitrp (xreal, ximag, n);
// 计算 1 的前 n / 2 个 n 次方根的共轭复数 W'j = wreal [j] + i * wimag [j] , j = 0, 1, , n / 2 - 1
arg = - 2 * PI / n;
treal = cos (arg);
timag = sin (arg);
wreal [0] = 1.0;
wimag [0] = 0.0;
for (j = 1; j < n / 2; j ++)
{
wreal [j] = wreal [j - 1] * treal - wimag [j - 1] * timag;
wimag [j] = wreal [j - 1] * timag + wimag [j - 1] * treal;
}
for (m = 2; m <= n; m *= 2)
{
for (k = 0; k < n; k += m)
{
for (j = 0; j < m / 2; j ++)
{
index1 = k + j;
index2 = index1 + m / 2;
t = n * j / m; // 旋转因子 w 的实部在 wreal [] 中的下标为 t
treal = wreal [t] * xreal [index2] - wimag [t] * ximag [index2];
timag = wreal [t] * ximag [index2] + wimag [t] * xreal [index2];
ureal = xreal [index1];
uimag = ximag [index1];
xreal [index1] = ureal + treal;
ximag [index1] = uimag + timag;
xreal [index2] = ureal - treal;
ximag [index2] = uimag - timag;
}
}
}
delete []wreal;
delete []wimag;
}
void CDSRecord::IFFT (float xreal [], float ximag [], int n)
{
const float PI = 3.1416;
// 快速傅立叶逆变换
float * wreal=new float [n / 2];
float * wimag=new float [n / 2];
float treal, timag, ureal, uimag, arg;
int m, k, j, t, index1, index2;
bitrp (xreal, ximag, n);
// 计算 1 的前 n / 2 个 n 次方根 Wj = wreal [j] + i * wimag [j] , j = 0, 1, , n / 2 - 1
arg = 2 * PI / n;
treal = cos (arg);
timag = sin (arg);
wreal [0] = 1.0;
wimag [0] = 0.0;
for (j = 1; j < n / 2; j ++)
{
wreal [j] = wreal [j - 1] * treal - wimag [j - 1] * timag;
wimag [j] = wreal [j - 1] * timag + wimag [j - 1] * treal;
}
for (m = 2; m <= n; m *= 2)
{
for (k = 0; k < n; k += m)
{
for (j = 0; j < m / 2; j ++)
{
index1 = k + j;
index2 = index1 + m / 2;
t = n * j / m; // 旋转因子 w 的实部在 wreal [] 中的下标为 t
treal = wreal [t] * xreal [index2] - wimag [t] * ximag [index2];
timag = wreal [t] * ximag [index2] + wimag [t] * xreal [index2];
ureal = xreal [index1];
uimag = ximag [index1];
xreal [index1] = ureal + treal;
ximag [index1] = uimag + timag;
xreal [index2] = ureal - treal;
ximag [index2] = uimag - timag;
}
}
}
for (j=0; j < n; j ++)
{
xreal [j] /= n;
ximag [j] /= n;
}
delete []wreal;
delete []wimag;
}
bool CDSRecord::Start(void)
{
return StarRecord();
}
bool CDSRecord::Stop(void)
{
return StopRecord();
}
void CDSRecord::Destroy(void)
{
Stop();
delete this;
return;
}