Unity-WebSocket/Assets/OpusMicRecorder.cs
2025-06-28 11:28:54 +03:30

148 lines
4.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using Concentus.Structs;
using Concentus.Enums;
public class OpusMicRecorder : MonoBehaviour
{
[Header("Audio Settings")]
public int sampleRate = 16000;
public int frameSizeMs = 20; // 20 میلی‌ثانیه
[Header("Bandpass Filter Frequencies (Hz)")]
[Range(50f, 1000f)]
public float lowFreq = 300f;
[Range(1000f, 8000f)]
public float highFreq = 3400f;
private AudioClip micClip;
private int frameSizeSamples;
private int lastSamplePos = 0;
private OpusEncoder encoder;
private BandpassFilter voiceFilter;
public delegate void EncodedAudioReady(byte[] data);
public event EncodedAudioReady OnEncodedAudio;
void Start()
{
frameSizeSamples = (sampleRate / 1000) * frameSizeMs;
encoder = new OpusEncoder(sampleRate, 1, OpusApplication.OPUS_APPLICATION_VOIP);
encoder.Bitrate = 16000;
micClip = Microphone.Start(null, true, 1, sampleRate);
// اعتبارسنجی ورودی فرکانس ها
if (lowFreq >= highFreq)
{
Debug.LogWarning("lowFreq باید کمتر از highFreq باشد. تنظیم به مقادیر پیش‌فرض.");
lowFreq = 300f;
highFreq = 3400f;
}
voiceFilter = new BandpassFilter(sampleRate, lowFreq, highFreq);
}
void Update()
{
int micPos = Microphone.GetPosition(null);
int available = micPos - lastSamplePos;
if (available < 0) available += micClip.samples;
if (available < frameSizeSamples) return;
float[] samples = new float[frameSizeSamples];
micClip.GetData(samples, lastSamplePos);
lastSamplePos = (lastSamplePos + frameSizeSamples) % micClip.samples;
// آپدیت فیلتر اگر کاربر در Inspector مقادیر را تغییر داد
if (voiceFilter.LowFreq != lowFreq || voiceFilter.HighFreq != highFreq)
{
voiceFilter.SetFrequencies(lowFreq, highFreq);
}
voiceFilter.Process(samples);
short[] pcm = new short[frameSizeSamples];
for (int i = 0; i < frameSizeSamples; i++)
pcm[i] = (short)Mathf.Clamp(samples[i] * short.MaxValue, short.MinValue, short.MaxValue);
byte[] encoded = new byte[1275];
int encodedLength = encoder.Encode(pcm, 0, frameSizeSamples, encoded, 0, encoded.Length);
byte[] packet = new byte[encodedLength];
System.Buffer.BlockCopy(encoded, 0, packet, 0, encodedLength);
OnEncodedAudio?.Invoke(packet);
}
}
// کلاس فیلتر میان‌گذر (Bandpass) با قابلیت تنظیم داینامیک فرکانس‌ها
public class BandpassFilter
{
private float a0, a1, a2, b1, b2;
private float z1, z2;
private float sampleRate;
public float LowFreq { get; private set; }
public float HighFreq { get; private set; }
public BandpassFilter(float sampleRate, float lowFreq, float highFreq)
{
this.sampleRate = sampleRate;
SetFrequencies(lowFreq, highFreq);
z1 = 0;
z2 = 0;
}
public void SetFrequencies(float lowFreq, float highFreq)
{
// اعتبارسنجی ساده
if (lowFreq <= 0) lowFreq = 50f;
if (highFreq >= sampleRate / 2f) highFreq = sampleRate / 2f - 100;
if (lowFreq >= highFreq)
{
Debug.LogWarning("BandpassFilter: lowFreq must be less than highFreq");
return;
}
LowFreq = lowFreq;
HighFreq = highFreq;
float omegaL = 2.0f * Mathf.PI * LowFreq / sampleRate;
float omegaH = 2.0f * Mathf.PI * HighFreq / sampleRate;
float centerFreq = (omegaL + omegaH) / 2.0f;
float bandwidth = omegaH - omegaL;
float Q = centerFreq / bandwidth;
float alpha = Mathf.Sin(centerFreq) / (2.0f * Q);
float cosW0 = Mathf.Cos(centerFreq);
float norm = 1.0f + alpha;
a0 = alpha / norm;
a1 = 0;
a2 = -alpha / norm;
b1 = -2.0f * cosW0 / norm;
b2 = (1.0f - alpha) / norm;
// ریست فیلتر برای جلوگیری از artifact بعد تغییر فرکانس
z1 = 0;
z2 = 0;
}
public void Process(float[] samples)
{
for (int i = 0; i < samples.Length; i++)
{
float input = samples[i];
float output = a0 * input + a1 * z1 + a2 * z2 - b1 * z1 - b2 * z2;
z2 = z1;
z1 = output;
samples[i] = output;
}
}
}