305 lines
7.5 KiB
C#
305 lines
7.5 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using Crosstales.Radio.Model;
|
|
using Crosstales.Radio.Util;
|
|
using UnityEngine;
|
|
|
|
namespace Crosstales.Radio.Tool
|
|
{
|
|
[RequireComponent(typeof(AudioSource))]
|
|
[HelpURLAttribute("https://crosstales.com/media/data/assets/radio/api/class_crosstales_1_1_radio_1_1_tool_1_1_stream_saver.html")]
|
|
public class StreamSaver : MonoBehaviour
|
|
{
|
|
[Tooltip("Origin Player.")]
|
|
public BasePlayer Player;
|
|
|
|
[Tooltip("Silence the origin (default: true).")]
|
|
public bool SilenceSource = true;
|
|
|
|
[Tooltip("Output path for the audio files.")]
|
|
public string OutputPath;
|
|
|
|
[Tooltip("Record delay in seconds before start saving the audio (default: 0).")]
|
|
[Range(0f, 20f)]
|
|
public float RecordStartDelay;
|
|
|
|
[Range(0f, 20f)]
|
|
[Tooltip("Record delay in seconds before stop saving the audio (default: 0).")]
|
|
public float RecordStopDelay;
|
|
|
|
[Tooltip("Add the station name to the audio files (default: true).")]
|
|
public bool AddStationName = true;
|
|
|
|
[Tooltip("Add the current timestamp to the audio files (default: false).")]
|
|
public bool AddTimestamp;
|
|
|
|
private FileStream fileStream;
|
|
|
|
private const int HEADER_SIZE = 44;
|
|
|
|
private const float RESCALE_FACTOR = 32767f;
|
|
|
|
private AudioSource audioSource;
|
|
|
|
private bool recOutput;
|
|
|
|
private bool stopped = true;
|
|
|
|
private long dataPosition;
|
|
|
|
private string fileName;
|
|
|
|
public bool isSilenceSource
|
|
{
|
|
get
|
|
{
|
|
return SilenceSource;
|
|
}
|
|
set
|
|
{
|
|
SilenceSource = value;
|
|
}
|
|
}
|
|
|
|
public void Awake()
|
|
{
|
|
audioSource = GetComponent<AudioSource>();
|
|
audioSource.playOnAwake = false;
|
|
audioSource.Stop();
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
if (Player == null)
|
|
{
|
|
Debug.LogWarning("No 'Player' added to the StreamSaver!");
|
|
}
|
|
else
|
|
{
|
|
Player.isCaptureDataStream = true;
|
|
Player.isLegacyMode = false;
|
|
}
|
|
if (string.IsNullOrEmpty(OutputPath))
|
|
{
|
|
Debug.LogWarning("No 'OutputPath' added to the StreamSaver, saving in the project root!");
|
|
}
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
if (Player != null && Player.isAudioPlaying)
|
|
{
|
|
if (stopped)
|
|
{
|
|
stopped = false;
|
|
dataPosition = Player.DataStream.Position;
|
|
AudioClip clip = AudioClip.Create(Player.RadioStation.Name, int.MaxValue, Player.Channels, Player.SampleRate, true, readPCMData);
|
|
audioSource.clip = clip;
|
|
audioSource.Play();
|
|
if (SilenceSource)
|
|
{
|
|
Player.Silence();
|
|
}
|
|
}
|
|
}
|
|
else if (!stopped)
|
|
{
|
|
audioSource.Stop();
|
|
audioSource.clip = null;
|
|
stopped = true;
|
|
}
|
|
}
|
|
|
|
public void OnEnable()
|
|
{
|
|
if (Player != null)
|
|
{
|
|
Player.OnAudioEnd += onAudioEnd;
|
|
Player.OnNextRecordChange += onNextRecordChange;
|
|
}
|
|
}
|
|
|
|
public void OnDisable()
|
|
{
|
|
if (Player != null)
|
|
{
|
|
Player.OnAudioEnd -= onAudioEnd;
|
|
Player.OnNextRecordChange -= onNextRecordChange;
|
|
}
|
|
closeFile();
|
|
audioSource.Stop();
|
|
audioSource.clip = null;
|
|
stopped = true;
|
|
}
|
|
|
|
public void OnValidate()
|
|
{
|
|
if (!string.IsNullOrEmpty(OutputPath))
|
|
{
|
|
OutputPath = Helper.ValidatePath(OutputPath);
|
|
}
|
|
}
|
|
|
|
private void openFile()
|
|
{
|
|
if (Config.DEBUG)
|
|
{
|
|
Debug.Log("openFile: " + fileName);
|
|
}
|
|
if (fileStream != null && fileStream.CanWrite)
|
|
{
|
|
closeFile();
|
|
}
|
|
try
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fileName));
|
|
fileStream = new FileStream(fileName, FileMode.Create);
|
|
byte value = 0;
|
|
for (int i = 0; i < 44; i++)
|
|
{
|
|
fileStream.WriteByte(value);
|
|
}
|
|
recOutput = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError("Could not open file '" + fileName + "': " + ex);
|
|
}
|
|
}
|
|
|
|
private void convertAndWrite(float[] dataSource)
|
|
{
|
|
if (fileStream != null && fileStream.CanWrite)
|
|
{
|
|
short[] array = new short[dataSource.Length];
|
|
byte[] array2 = new byte[dataSource.Length * 2];
|
|
byte[] array3 = new byte[2];
|
|
for (int i = 0; i < dataSource.Length; i++)
|
|
{
|
|
array[i] = (short)(dataSource[i] * 32767f);
|
|
array3 = BitConverter.GetBytes(array[i]);
|
|
array3.CopyTo(array2, i * 2);
|
|
}
|
|
try
|
|
{
|
|
fileStream.Write(array2, 0, array2.Length);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError("Could write to file '" + fileName + "': " + ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void closeFile()
|
|
{
|
|
if (Config.DEBUG)
|
|
{
|
|
Debug.Log("closeFile");
|
|
}
|
|
recOutput = false;
|
|
if (fileStream == null || !fileStream.CanWrite)
|
|
{
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
fileStream.Seek(0L, SeekOrigin.Begin);
|
|
byte[] bytes = Encoding.UTF8.GetBytes("RIFF");
|
|
fileStream.Write(bytes, 0, 4);
|
|
byte[] bytes2 = BitConverter.GetBytes(fileStream.Length - 8);
|
|
fileStream.Write(bytes2, 0, 4);
|
|
byte[] bytes3 = Encoding.UTF8.GetBytes("WAVE");
|
|
fileStream.Write(bytes3, 0, 4);
|
|
byte[] bytes4 = Encoding.UTF8.GetBytes("fmt ");
|
|
fileStream.Write(bytes4, 0, 4);
|
|
byte[] bytes5 = BitConverter.GetBytes(16);
|
|
fileStream.Write(bytes5, 0, 4);
|
|
ushort value = 1;
|
|
byte[] bytes6 = BitConverter.GetBytes(value);
|
|
fileStream.Write(bytes6, 0, 2);
|
|
byte[] bytes7 = BitConverter.GetBytes(Player.Channels);
|
|
fileStream.Write(bytes7, 0, 2);
|
|
byte[] bytes8 = BitConverter.GetBytes(Player.SampleRate);
|
|
fileStream.Write(bytes8, 0, 4);
|
|
byte[] bytes9 = BitConverter.GetBytes(Player.SampleRate * Player.Channels * 2);
|
|
fileStream.Write(bytes9, 0, 4);
|
|
ushort value2 = (ushort)(Player.Channels * 2);
|
|
fileStream.Write(BitConverter.GetBytes(value2), 0, 2);
|
|
ushort value3 = 16;
|
|
byte[] bytes10 = BitConverter.GetBytes(value3);
|
|
fileStream.Write(bytes10, 0, 2);
|
|
byte[] bytes11 = Encoding.UTF8.GetBytes("data");
|
|
fileStream.Write(bytes11, 0, 4);
|
|
byte[] bytes12 = BitConverter.GetBytes(fileStream.Length - 44);
|
|
fileStream.Write(bytes12, 0, 4);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError("Could write header for file '" + fileName + "': " + ex);
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
fileStream.Close();
|
|
}
|
|
catch (Exception ex2)
|
|
{
|
|
Debug.LogError("Could close file '" + fileName + "': " + ex2);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void readPCMData(float[] data)
|
|
{
|
|
if (Player.isAudioPlaying && Player.DataStream != null)
|
|
{
|
|
byte[] array = new byte[data.Length * 2];
|
|
long position = Player.DataStream.Position;
|
|
Player.DataStream.Position = dataPosition;
|
|
int num;
|
|
if ((num = Player.DataStream.Read(array, 0, array.Length)) > 0)
|
|
{
|
|
float[] array2 = Helper.ConvertByteArrayToFloatArray(array, num);
|
|
Buffer.BlockCopy(array2, 0, data, 0, data.Length * 4);
|
|
if (recOutput)
|
|
{
|
|
convertAndWrite(array2);
|
|
}
|
|
dataPosition += num;
|
|
}
|
|
Player.DataStream.Position = position;
|
|
}
|
|
else
|
|
{
|
|
Buffer.BlockCopy(new float[data.Length], 0, data, 0, data.Length * 4);
|
|
}
|
|
}
|
|
|
|
private void onAudioEnd(RadioStation station)
|
|
{
|
|
if (Config.DEBUG)
|
|
{
|
|
Debug.Log("onAudioEnd");
|
|
}
|
|
closeFile();
|
|
}
|
|
|
|
private void onNextRecordChange(RadioStation station, RecordInfo nextRecord, float delay)
|
|
{
|
|
if (Config.DEBUG)
|
|
{
|
|
Debug.Log("onNextRecordChange: " + delay);
|
|
}
|
|
if (delay > 0f)
|
|
{
|
|
Invoke("closeFile", delay - RecordStopDelay - 0.2f);
|
|
}
|
|
fileName = Helper.ValidateFile(string.Concat(str1: (!string.IsNullOrEmpty(nextRecord.Artist) && !string.IsNullOrEmpty(nextRecord.Title)) ? (((!AddStationName) ? string.Empty : (station.Name + " - ")) + ((!AddTimestamp) ? string.Empty : (DateTime.Now.ToString("yyyyMMdd HH_mm_ss") + " - ")) + nextRecord.Artist + " - " + nextRecord.Title + ".wav") : (station.Name + " - " + DateTime.Now.ToString("yyyyMMdd HH_mm_ss") + ".wav"), str0: OutputPath));
|
|
Invoke("openFile", delay + RecordStartDelay + 0.2f);
|
|
}
|
|
}
|
|
}
|