Unity-WebSocket/Assets/OpusMicRecorder.cs

148 lines
4.5 KiB
C#
Raw Normal View History

2025-06-28 07:58:54 +00:00
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;
}
}
}