/// <summary> /// ユーザを追加する。 /// </summary> /// <param name="id">追加する端末ID</param> /// <param name="diffTime">端末間の時間差(相手の時計が進んでいる場合は正の値, 遅れていれば不の値のTicks)</param> public void AddUser(string id, long diffTime) { try { if (userDictionary.ContainsKey(id)) { userDictionary[id] = 0; } else { userDictionary.Add(id, 0); } if (diffTimeDictonary.ContainsKey(id)) { diffTimeDictonary[id] = diffTime; } else { diffTimeDictonary.Add(id, diffTime); } } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 音声リングバッファからのデータを取り出す。 /// </summary> /// <param name="index">開始位置(BufferPacketSize単位)</param> /// <param name="length">取り出すデータの長さ(BufferPacketSize単位)</param> /// <returns>音声データ配列</returns> private short[] GetData(int index, int length) { try { short[] buffer = new short[length * BufferPacketSize]; // バッファからコピー if ((index + length) > RingBufferLength) { // リングバッファの境界をまたぐ場合 int formerLength = RingBufferLength - index; int latterLength = length - formerLength; Array.Copy(soundBuffer, index * BufferPacketSize, buffer, 0, formerLength * BufferPacketSize); Array.Copy(soundBuffer, 0, buffer, formerLength * BufferPacketSize, latterLength * BufferPacketSize); } else { // 境界をまたぐ処理が必要ない場合 Array.Copy(soundBuffer, index * BufferPacketSize, buffer, 0, length * BufferPacketSize); } return(buffer); } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); return(new short[0]); } }
/// <summary> /// バイト列からインスタンスを再構成する。 /// </summary> /// <param name="dataBytes">音声データのバイト列</param> /// <returns>再構成したインスタンス</returns> public static SoundData CreateInstance(byte[] dataBytes) { try { // 時刻の読み取り long time = BitConverter.ToInt64(dataBytes, 0); int index = sizeof(long); // 端末IDの読み取り int idLength = BitConverter.ToInt32(dataBytes, index); index += sizeof(int); string id = Encoding.UTF8.GetString(dataBytes, index, idLength); index += idLength; // 音声データの読み取り int dataLength = (dataBytes.Length - index) / sizeof(double); double[] data = new double[dataLength]; for (int i = 0; i < dataLength; i++) { data[i] = BitConverter.ToDouble(dataBytes, index); index += sizeof(double); } return(new SoundData(time, data, id)); } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); return(null); } }
/// <summary> /// 音声処理管理部を作成する。 /// </summary> /// <param name="id">この端末のID(どの端末が近くにいるのかの表示に利用)</param> /// <param name="sendBack">比較用データ取得deligate</param> /// <param name="sendInterval">近接センシング用データ送信周期(ミリ秒, 7000ms以下とする)</param> /// <param name="fftLength">比較をかけるサウンドデータのバイト数(32000/s)</param> /// <param name="proximityKeepCycle">一度のTrue判定が影響を及ぼす周期数</param> /// <param name="soundDirectory">音声ログ(16kbps, 16bit)保存のためのディレクトリ</param> /// <param name="errorFile">エラーログを書き出すファイル</param> public SoundControl(string id, SendBackSignalDelegate sendBack, int fftLength, int sendInterval, int proximityKeepCycle, string soundDirectory, string errorFile) { try { this.id = id; SystemDirectory = Path.Combine(soundDirectory, "system"); LogFile = Path.Combine(SystemDirectory, DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".log"); diffTimeDictonary = new Dictionary <string, long>(); sendBackDeligate = sendBack; completeSendBack = new AsyncCallback(CompleteSendBackMethod); SoundControl.soundDirectory = soundDirectory; SoundControl.errorFile = errorFile; if (sendInterval > 7000) { sendInterval = 7000; } CreateDirectory(); this.sendInterval = sendInterval; this.proximityKeepCycle = proximityKeepCycle; dataBuffer = new DataBuffer(this, fftLength); //音量の設定 int mixer; Mixer.init(out mixer); //Mixer初期化 Mixer.SetMainVolume(mixer, 50); //Mainのボリュームを50%に設定 Mixer.SetWaveOutVolume(mixer, 50); //WavOutを50%に設定 Mixer.SetMicRecordVolume(mixer, 100); //マイク録音を100%に設定 // デバイス初期化 CaptureDevicesCollection devices = new CaptureDevicesCollection(); applicationDevice = new Capture(devices[0].DriverGuid); if (applicationDevice != null) { inputFormat = new WaveFormat(); inputFormat.BitsPerSample = BitsPerSample; inputFormat.Channels = Channels; inputFormat.SamplesPerSecond = SamplesPerSec; inputFormat.FormatTag = WaveFormatTag.Pcm; inputFormat.BlockAlign = BlockAlign; inputFormat.AverageBytesPerSecond = AvgBytesPerSec; recordingThread = new Thread(new ThreadStart(RecordWorker)); recordingThread.IsBackground = true; CreateCaptureBuffer(); isActive = true; } } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// ログウィンドウに出力する。 /// </summary> /// <param name="log">出力するログ</param> public void WriteLogWindow(string log) { try { //client.Form.LogWindow.AddLine(log); } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 近接センサ用音声データを送信する。 /// </summary> /// <param name="soundData">送信する音声データ</param> public void SendAll(SoundData soundData) { try { sendBackDeligate.BeginInvoke((ConversationFieldDetector.ISendable)soundData, completeSendBack, null); } catch (Exception e) { System.Windows.Forms.MessageBox.Show(e.ToString()); SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// ユーザを削除する。 /// </summary> /// <param name="id">削除する端末ID</param> public void RemoveUser(string id) { try { userDictionary.Remove(id); nearUserList.Remove(id); } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 常時録音を開始する。 /// </summary> public void Start() { try { recordingThread.Start(); applicationBuffer.Start(true); } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 近接センサ用音声データを送信する。 /// </summary> /// <param name="soundData">送信する音声データ</param> public void SendAll(SoundData soundData) { try { soundData.Time -= diffTimeDictonary[soundData.ID]; sendBackDeligate.BeginInvoke((ConversationFieldDetector.ISendable)soundData, completeSendBack, null); } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 音声データをファイルに出力する。 /// </summary> /// <param name="position">出力するリングバッファの位置</param> private void Record(int position) { try { int index = SeekPosition(position, -SpeechCandidateLength); short[] buffer = GetData(index, 1); encoder.encode(buffer, BufferPacketSize); } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 受信した近接センサ用音声データを処理する。 /// </summary> /// <param name="soundData">受信した音声データ</param> public void Receive(SoundData soundData) { try { if (!diffTimeDictonary.ContainsKey(soundData.ID)) { return; } soundData.Time -= diffTimeDictonary[soundData.ID]; // コサイン類似度の計算 double similarity = dataBuffer.Compair(soundData); if (similarity > ProximityThreshold) { // 類似度が閾値以上なら近接ユーザリストに追加 userDictionary[soundData.ID] = proximityKeepCycle; if (!nearUserList.Contains(soundData.ID)) { nearUserList.Add(soundData.ID); } } else { int temp; if (userDictionary.TryGetValue(soundData.ID, out temp)) { if (temp > 0) { // 一定周期は近接判定を継続 userDictionary[soundData.ID] = temp - 1; if (userDictionary[soundData.ID] == 0) { nearUserList.Remove(soundData.ID); } } } } WriteLog(soundData.ID + " : " + similarity.ToString()); WriteLogWindow( "\r\n##Proximity##\r\n" + similarity.ToString() + " with " + soundData.ID); } catch (Exception e) { System.Windows.Forms.MessageBox.Show(e.ToString()); SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 常時録音処理を行う。 /// </summary> private void RecordWorker() { short[] buffer = new short[DataBuffer.PacketSize / sizeof(short)]; while (isActive) { try { // キャプチャバッファが埋まるまで待機 notificationEvent.WaitOne(Timeout.Infinite, true); // キャプチャサイズを算出 int capturePosition, readPosition; applicationBuffer.GetCurrentPosition(out capturePosition, out readPosition); int lockSize = readPosition - nextCaptureOffset; if (lockSize < 0) { lockSize += captureBufferSize; } lockSize -= lockSize % notifySize; if (lockSize == 0) { continue; } // キャプチャデータ取得 byte[] captureData = (byte[])applicationBuffer.Read( nextCaptureOffset, typeof(byte), LockFlag.None, lockSize); // パケットに切り分けて処理 for (int i = 0; i < captureData.Length / DataBuffer.PacketSize; i++) { System.Buffer.BlockCopy(captureData, DataBuffer.PacketSize * i, buffer, 0, DataBuffer.PacketSize); dataBuffer.AddData(buffer, DetectTalking(buffer), DateTime.Now.Ticks); } // 次回のオフセットを計算 nextCaptureOffset += captureData.Length; if (nextCaptureOffset >= captureBufferSize) { nextCaptureOffset -= captureBufferSize; } } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } } }
/// <summary> /// ログファイルに追記する。 /// </summary> /// <param name="log">追記するログ</param> public static void WriteLog(string log) { try { using (StreamWriter sw = new StreamWriter(LogFile)) { sw.WriteLine(log); sw.Flush(); } } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// プログラム終了処理 /// </summary> public void Close() { try { if (isRecording) { isRecording = false; encoder.end(); } } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 常時録音を終了する。 /// </summary> public void Stop() { try { applicationBuffer.Stop(); if (notificationEvent != null) { notificationEvent.Set(); } } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 録音を停止する。 /// </summary> /// <returns>音声ファイル名</returns> public string StopRecording() { try { if (isRecording) { isRecording = false; encoder.end(); soundControl.WriteLogWindow("RecStop\r\n"); } } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } return(soundFile + ".mp3"); }
/// <summary> /// 受信した近接センサ用音声データを処理する。 /// </summary> /// <param name="soundData">受信した音声データ</param> public void Receive(SoundData soundData) { try { // コサイン類似度の計算 double similarity = dataBuffer.Compair(soundData); StreamWriter writer = new StreamWriter("000.txt", true); writer.WriteLine(similarity.ToString()); writer.Close(); if (similarity > ProximityThreshold) { // 類似度が閾値以上なら近接ユーザリストに追加 userDictionary[soundData.ID] = proximityKeepCycle; if (!nearUserList.Contains(soundData.ID)) { nearUserList.Add(soundData.ID); } } else { int temp; if (userDictionary.TryGetValue(soundData.ID, out temp)) { if (temp > 0) { // 一定周期は近接判定を継続 userDictionary[soundData.ID] = temp - 1; if (userDictionary[soundData.ID] == 0) { nearUserList.Remove(soundData.ID); } } } } WriteLog(soundData.ID + " : " + similarity.ToString()); WriteLogWindow( "\r\n##Proximity##\r\n" + similarity.ToString() + " with " + soundData.ID); } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 音声処理部を閉じる。 /// </summary> public void Close() { if (!isActive) { return; } isActive = false; try { Stop(); dataBuffer.Close(); } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 録音を開始する。 /// </summary> /// <param name="position">リングバッファの先頭</param> private void StartRecording(int position) { try { if (!isRecording) { isRecording = true; soundFile = Path.Combine(SoundControl.SoundDirectory, GetNowString()); encoder.setFileName(soundFile); encoder.init(SoundControl.SamplesPerSec, 32, 64); Record(position); soundControl.WriteLogWindow("RecStart\r\n"); } } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 近接センサ用音声データを送信する。 /// </summary> /// <param name="position">最新バッファ位置</param> private void SendFftData(int position) { try { int index = GetSendDataPosition(position); if (index == -1) { soundControl.SendAll( new SoundData(timeBuffer[position], new double[0], soundControl.ID)); } else { double[] fftData = UseFFT.GetPower(GetData(index, fftPacketCount)); soundControl.SendAll( new SoundData(timeBuffer[index], fftData, soundControl.ID)); } } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 通信で送るデータに変換 /// </summary> /// <returns>送信バイト列</returns> public byte[] GetDataBytes() { try { // 時刻と端末IDをバイト列に変換 byte[] timeBytes = BitConverter.GetBytes(time); byte[] idBytes = Encoding.UTF8.GetBytes(id); byte[] idLengthBytes = BitConverter.GetBytes(idBytes.Length); // 全体のバイト列を作成 int totalLength = timeBytes.Length + idLengthBytes.Length + idBytes.Length + data.Length * sizeof(double); byte[] dataBytes = new byte[totalLength]; // 時刻と端末IDをコピー int index = 0; timeBytes.CopyTo(dataBytes, index); index += timeBytes.Length; idLengthBytes.CopyTo(dataBytes, index); index += idLengthBytes.Length; idBytes.CopyTo(dataBytes, index); index += idBytes.Length; // 音声データをコピー for (int i = 0; i < data.Length; i++) { BitConverter.GetBytes(data[i]).CopyTo(dataBytes, index); index += sizeof(double); } return(dataBytes); } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); return(new byte[0]); } }
/// <summary> /// 近接センサ用音声データを比較する。 /// </summary> /// <param name="soundData">受信した音声データ</param> /// <returns>比較結果(類似度)</returns> public double Compair(SoundData soundData) { try { if (soundData.Data.Length > 1) { // データ時刻が最も近いブロックを探す int index = SeekPosition(currentPosition, 100); while (index != currentPosition) { // 簡単に相手のデータ時刻を越えた瞬間とする if (timeBuffer[index] > soundData.Time) { break; } if (++index == RingBufferLength) { index = 0; } } // 対応する自分の音声バッファが有声区間であるか確認 bool[] localTalkingFlag = GetTalkFlags(index, fftPacketCount); if (CountBool(localTalkingFlag) > SilentDataThreshold) { // 音声データの比較 short[] data = GetData(index, fftPacketCount); double[] localFrequency = UseFFT.GetPower(data); return(UseFFT.Compair(localFrequency, soundData.Data)); } } } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } return(0); }
/// <summary> /// コンストラクタ /// </summary> /// <param name="soundControl">サウンドデータをやりとりするためのSoundControlオブジェクト</param> /// <param name="fftLength">比較にかける長さ(32000/秒)</param> public DataBuffer(SoundControl soundControl, int fftLength) { this.soundControl = soundControl; encoder = new Codec.mp3_Encoder(); isRecording = false; // FFT初期化 fftInterval = soundControl.SendInterval / RecInterval * PacketDivisor; this.fftLength = 2 ^ ((int)(Math.Log(fftLength) / Math.Log(2)) + 1); fftPacketCount = fftLength / BufferPacketSize + 1; fftCount = 0; // リングバッファ初期化 Array.Clear(soundBuffer, 0, soundBuffer.Length); Array.Clear(talkFlagBuffer, 0, talkFlagBuffer.Length); Array.Clear(timeBuffer, 0, timeBuffer.Length); currentPosition = 0; // 有声・無声フラグカウント初期化 silentCount = SilentCandidateLength; silentJudgeHead = RingBufferLength - SilentCandidateLength; speechCount = 0; speechJudgeHead = RingBufferLength - SpeechCandidateLength; }
/// <summary> /// リングバッファにデータを追加する。 /// </summary> /// <param name="sound">音声データ</param> /// <param name="talkFlag">有声区間かどうか(無音ならfalse)</param> /// <param name="time">音声データの時刻</param> public void AddData(short[] sound, bool talkFlag, long time) { try { sound.CopyTo(soundBuffer, currentPosition * BufferPacketSize); // 分割して処理 for (int i = 0; i < PacketDivisor; i++) { talkFlagBuffer[currentPosition] = talkFlag; timeBuffer[currentPosition] = time + (RecInterval / PacketDivisor) * i; ProcessData(currentPosition); currentPosition++; if (currentPosition == RingBufferLength) { currentPosition = 0; } } } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); } }
/// <summary> /// 有声フラグリングバッファからデータを取り出す。 /// </summary> /// <param name="index">開始位置</param> /// <param name="length">取り出す長さ</param> /// <returns>有声フラグ配列</returns> private bool[] GetTalkFlags(int index, int length) { try { bool[] talkFlags = new bool[length]; // バッファからコピー for (int i = 0; i < length; i++) { talkFlags[i] = talkFlagBuffer[index++]; if (index == RingBufferLength) { index = 0; } } return(talkFlags); } catch (Exception e) { SoundControl.WriteErrorLog(e.ToString()); return(new bool[0]); } }