第三部分:放在一起


在第一部分中,我们检查了调制解调器以验证其是否支持语音。 如果是这样,我们记下了将要使用的语音数据格式。 在第二部分中,我们准备了一个wave文件,并用C#实现了一段代码,供HomeZIX使用,以将wave文件读入缓冲区。 现在,该将所有内容放在一起,以将该缓冲区作为音频流通过电话线发送到指定的电话号码了。

在这里的示例中,我们通过COM10连接到语音调制解调器。 这是我们从当地商店买来的旧货。 我们准备了一个波形文件:“休斯顿8位单声道8kHz.wav”,其格式为:PCM /未压缩,每个样本8位,单通道(单声道),每秒8000个样本。 我们在HomeZIX的工作场所放置了一个虚拟开关,以控制何时开始拨打电话号码和播放wave文件。 当然,它仅用于演示目的。 在实际使用中,它应拨打电话并根据特定事件选择适当的波形文件来播放,例如:检测到运动,温度过高,开门等。


下载脚本以将C#脚本导入HomeZIX,花点时间完成实现: 初始化


public void Initialize()
{   // Required function. DO NOT remove or change the name.
    // Called once when the script start executing.
    string fileName = "C:\\houston 8kHz Mono 8.wav";
    if (!System.IO.File.Exists(fileName)) 
 {
  Debug("Wave file not found.");
  return;
 } 
    System.IO.FileStream strm = new System.IO.FileStream(fileName, System.IO.FileMode.Open);
    System.IO.BinaryReader rdr = new System.IO.BinaryReader(strm);
    Wave.WAVEFORMATEX wfmt = new Wave.WAVEFORMATEX();
    wfmt.SeekTo(strm); 
    // Read in the WAVEFORMATEX structure and attempt to open the
    // device for playback.
    wfmt.Read(rdr);
 Debug("Wave file information:");
 Debug("Wave file encoding: " + wfmt.wFormatTag.ToString());
 Debug("Channels: " + wfmt.nChannels.ToString());
 Debug("Bits per Sample: " + wfmt.wBitsPerSample.ToString());
 Debug("Sampling rate: " + wfmt.nSamplesPerSec.ToString()); 
 if ((wfmt.wFormatTag == 1)&&(wfmt.nChannels == 1) && (wfmt.wBitsPerSample == 8) && (wfmt.nSamplesPerSec == 8000))
 {
     uint dataLength = (uint)(rdr.BaseStream.Length - Wave.WAVEFORMATEX.WF_OFFSET_DATA);
     m_whdr = new Wave.WAVEHDR();
     m_whdr.Read(rdr, dataLength, wfmt.nBlockAlign);
  Debug("Wave file data has been read successfully.");
 } else
 {
  Debug("Unsupported wave file.");
  Debug("This example supports only [PCM/Uncompressed], [Mono], [8 bits per sample], [8kHz samples per second] wave files.");
 } 
    rdr.BaseStream.Close();
    rdr.Close();
    rdr = null; 
 //Initializing COM port
 m_serialPort = new System.IO.Ports.SerialPort();
 m_serialPort.PortName = "COM10";
 m_serialPort.BaudRate = 115200;
 m_serialPort.DataBits = 8;
 m_serialPort.StopBits = System.IO.Ports.StopBits .One;
 m_serialPort.Parity = System.IO.Ports.Parity.None;
 m_serialPort.Handshake = System.IO.Ports.Handshake.None;
 m_serialPort.DtrEnable = true;
 try
 {
  m_serialPort.Open();
  Debug("COM port is ready.");
 } catch
 {
  Debug("Error opening the COM port.");
 }
 m_talking = false;
}


我们已经在第二部分中看到了此功能。 这次我们为COM端口添加了初始化。 在我们这里的例子中:串行COM10连接到我们的外部语音调制解调器。 大多数调制解调器具有用于串行连接的自动检测功能。 因此,此处的设置为:115200bps,8个数据位,1个停止位,无奇偶校验,无握手可能起作用。 但是,请查阅调制解调器的手册以确保正确设置它们。 超级终端是测试安装方法的便捷工具。 执行


此功能检测虚拟交换机是否已打开。 如果是这样,它将启动调用过程。 因为此函数每秒调用一次,所以我们不想在这里通过处理调制解调器连接来垄断主程序的处理时间。 相反,我们产生了另一个线程来完成这项工作。

public void Execute()
{   // Required function. DO NOT remove or change the name.
    // Called every second.
 int status = GetStatus("Virtual switch..0.0");
 if ((m_virtualSwitchPreviousState == 0) &&(status > 0))
 {
  if (!m_talking)
  {
   m_talking = true;
   m_phoneThread = new System.Threading.Thread(new System.Threading.ThreadStart(WaveToPhone));
            m_phoneThread.Start();
  }
  m_virtualSwitchPreviousState = status;
 }
 if ((m_virtualSwitchPreviousState > 0) &&(status == 0))
 {
  m_talking = false;
  m_virtualSwitchPreviousState = status;
 }
}


该线程包括3个部分:准备调制解调器,发送wave数据以及最终终止会话。

准备调制解调器:这些命令使调制解调器处于我们选择的语音数据编码格式的语音模式。 将命令发送到调制解调器后,脚本将等待响应。 因为我们知道响应有效(从第I部分开始),所以这里没有检查响应。我们只是打印出调试字符串。 此阶段的最后一步是呼叫号码(“ ATDT号码”)并将调制解调器切换到发送语音数据模式(“ AT + VTX”)

private void WaveToPhone()
{
 if ((m_serialPort != null)&&(m_serialPort.IsOpen))
 {
  SendCommand("ATZ");
  GetResponse(1, true);
  SendCommand("AT+FCLASS=8");
  GetResponse(1, true);
  SendCommand("AT+VSM=128,8000");
  GetResponse(1, true);
  SendCommand("ATDT" + m_phoneNumber);
  GetResponse(60, false); 
  SendCommand("AT+VTX"); 
  System.Threading.Thread.Sleep(500);
  SendVoiceData();
  System.Threading.Thread.Sleep(1000);
  SendVoiceData();
  System.Threading.Thread.Sleep(1000);
  SendVoiceData();
  System.Threading.Thread.Sleep(1000); 
  byte [] terminator = new byte [2];
  terminator[0] = 0x10;
  terminator[1] = 0x03;
  m_serialPort.Write(terminator, 0, 2); 
  SendCommand("ATH");
 }
 m_talking = false;
}


发送波浪数据。


在Initialize函数中,我们已经将数据从wave文件读入缓冲区。 现在是时候将该缓冲区作为音频流传输到调制解调器了。 但是,由于调制解调器可能无法一次处理大量数据,因此我们以1024个字节的块发送数据。 这极大地提高了总吞吐量,确保另一端的语音流畅。 在我们的示例中,我们还将相同的数据缓冲区重复3次。

private void SendVoiceData()
{
 if ((m_whdr == null)||(!m_serialPort.IsOpen)) return;
 int offset = 0;
 int blockSize = 1024;
 int dataLength = m_whdr.data.Length;
 if (dataLength == 0) return;
 try
 {
  Debug("Start sending WAVE data...");
  while (true)
  {
   m_serialPort.Write(m_whdr.data, offset, blockSize);
   if (blockSize < 1024) break;
   offset += blockSize;
   if (dataLength - offset < 1024) blockSize = dataLength - offset;
  }
  Debug("Done sending WAVE data.");
 } catch
 {
  Debug("Error sending WAVE data.");
 }
}


终止通话。


发送波形数据后,我们通过以下方式终止连接:向调制解调器发送0x10 0x03以关闭发送模式,然后通过发送“ ATH”挂断电话。 请注意,如果您打算让多个脚本处理不同事件的不同wave文件,则也必须在此处关闭COM端口。