0.0.4 prep

- added custom settings loading from prop.txt
- added `-tune zerolatency` to FFMPEG capture
- reduced latency for WASAPI audio capture
- cleared up a bit of the ffmpeg spawning/killing logic
This commit is contained in:
Mercurio 2024-09-06 15:02:18 +02:00
parent c67c1ac504
commit 8f96bb6029

View file

@ -11,29 +11,34 @@ class Program
private static readonly string WebsocketAddress = "localhost"; private static readonly string WebsocketAddress = "localhost";
private static readonly int WebsocketPort = 10573; private static readonly int WebsocketPort = 10573;
private static Process? _ffmpegProcess; private static Process? _ffmpegProcess;
private static string _ffmpegFolderPath = GetFolderPath(); private static string _ffmpegFolderPath = GetDefaultVideosFolderPath();
private static WasapiCapture? _waveSource; private static WasapiCapture? _waveSource;
private static WaveFileWriter _writer = null!; private static WaveFileWriter _writer = null!;
private static string _audioFilePath = null!; private static string _audioFilePath = null!;
private static string _videoFilePath = null!; private static string _videoFilePath = null!;
private static string _resolution = "1920x1080"; // Default resolution
private static int _framerate = 60; // Default framerate
private static float _crf = 23; // Default CRF value
private static string _gameProcessName = "spice64"; // Default game process name
private static async Task Main(string[] args) private static async Task Main(string[] args)
{ {
var spiceProcesses = Process.GetProcessesByName("spice64"); // Load settings from prop.txt
LoadSettingsFromPropFile();
if (spiceProcesses.Length > 0) var gameProcesses = Process.GetProcessesByName(_gameProcessName);
if (gameProcesses.Length > 0)
{ {
Console.WriteLine("Found spice64, Attempting connection to TickerHookWS..."); Console.WriteLine($"Found {_gameProcessName}, Attempting connection to TickerHookWS...");
await TryConnectWebSocket(); await TryConnectWebSocket();
} }
else else
{ {
Console.WriteLine("Unable to find Spice64. Are you sure Beatmania IIDX is running and TickerHook is enabled?"); Console.WriteLine($"Unable to find {_gameProcessName}. Is the game running and TickerHook enabled?");
} }
} }
private static void LoadSettingsFromPropFile()
private static string GetFolderPath()
{ {
const string filePath = "prop.txt"; const string filePath = "prop.txt";
if (File.Exists(filePath)) if (File.Exists(filePath))
@ -47,142 +52,153 @@ class Program
var path = line["path:".Length..].Trim(); var path = line["path:".Length..].Trim();
if (Directory.Exists(path)) if (Directory.Exists(path))
{ {
return path; _ffmpegFolderPath = path; // Recording path
} }
Console.WriteLine($"The path specified in {filePath} does not exist."); else
{
Console.WriteLine($"The path specified in {filePath} does not exist. Using default recording path.");
_ffmpegFolderPath = GetDefaultVideosFolderPath();
}
}
if (line.StartsWith("resolution:"))
{
_resolution = line["resolution:".Length..].Trim();
Console.WriteLine($"Custom Resolution: {_resolution}");
}
if (line.StartsWith("framerate:"))
{
_framerate = int.Parse(line["framerate:".Length..].Trim());
Console.WriteLine($"Custom framerate: {_framerate}");
}
if (line.StartsWith("crf:"))
{
_crf = float.Parse(line["crf:".Length..].Trim());
Console.WriteLine($"custom crf: {_crf}");
}
if (line.StartsWith("game_process_name:"))
{
_gameProcessName = line["game_process_name:".Length..].Trim();
Console.WriteLine($"custom process name: {_gameProcessName}");
} }
} }
} }
else else
{ {
Console.WriteLine($"The file {filePath} does not exist."); Console.WriteLine($"The file {filePath} does not exist. Using default values.");
_ffmpegFolderPath = GetDefaultVideosFolderPath();
} }
return GetDefaultVideosFolderPath();
} }
private static string GetDefaultVideosFolderPath() private static string GetDefaultVideosFolderPath()
{ {
var userName = Environment.UserName; var userName = Environment.UserName;
var defaultPath = Path.Combine($@"C:\Users\{userName}", "Videos"); return Path.Combine($@"C:\Users\{userName}", "Videos");
return defaultPath;
} }
private static async Task TryConnectWebSocket() private static async Task TryConnectWebSocket()
{
const int maxRetries = 5;
int attempt = 0;
while (attempt < maxRetries)
{ {
Console.WriteLine($"Attempt {attempt + 1} of {maxRetries} to connect..."); const int maxRetries = 5;
int attempt = 0;
bool success = await ConnectWebSocket(); while (attempt < maxRetries)
if (success)
{ {
break; // Exit the loop if connection was successful Console.WriteLine($"Attempt {attempt + 1} of {maxRetries} to connect...");
bool success = await ConnectWebSocket();
if (success) break;
attempt++;
if (attempt < maxRetries)
{
Console.WriteLine($"Retrying in 10 seconds... {maxRetries - attempt} attempts remaining.");
await Task.Delay(10000);
}
} }
if (attempt == maxRetries)
attempt++;
if (attempt < maxRetries)
{ {
Console.WriteLine($"Retrying in 10 seconds... {maxRetries - attempt} attempts remaining."); Console.WriteLine("Failed to connect after 5 attempts.");
await Task.Delay(10000); // Wait for 10 seconds before retrying
} }
} }
if (attempt == maxRetries) private static async Task<bool> ConnectWebSocket()
{ {
Console.WriteLine("Failed to connect after 5 attempts."); var tickerUri = new Uri($"ws://{WebsocketAddress}:{WebsocketPort}");
} var reconnecting = false;
} var lastMessage = string.Empty;
var consecutiveMessageCount = 0;
var isRecording = false;
var currentSongName = string.Empty;
var shouldCheckForMusicSelect = false;
private static async Task<bool> ConnectWebSocket() using var clientWebSocket = new ClientWebSocket();
{
var tickerUri = new Uri($"ws://{WebsocketAddress}:{WebsocketPort}");
var reconnecting = false;
var lastMessage = string.Empty;
var consecutiveMessageCount = 0;
var isRecording = false;
var currentSongName = string.Empty;
var shouldCheckForMusicSelect = false;
using var clientWebSocket = new ClientWebSocket();
try
{
await clientWebSocket.ConnectAsync(tickerUri, CancellationToken.None);
Console.WriteLine("Connected to TickerHook WebSocket.");
}
catch (Exception ex)
{
Console.WriteLine($"Error connecting to TickerHook WebSocket: {ex.Message}");
return false; // Indicate connection failure
}
var buffer = new byte[1024];
while (clientWebSocket.State == WebSocketState.Open)
{
WebSocketReceiveResult result;
try try
{ {
result = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); await clientWebSocket.ConnectAsync(tickerUri, CancellationToken.None);
Console.WriteLine("Connected to TickerHook WebSocket.");
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Error receiving message: {ex.Message}"); Console.WriteLine($"Error connecting to TickerHook WebSocket: {ex.Message}");
reconnecting = true; return false;
break;
} }
var message = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim().ToUpper(); var buffer = new byte[1024];
Console.WriteLine($"Received message: {message}"); while (clientWebSocket.State == WebSocketState.Open)
if (message == lastMessage && !message.Contains("SELECT FROM"))
{ {
consecutiveMessageCount++; WebSocketReceiveResult result;
} try
else
{
consecutiveMessageCount = 1;
lastMessage = message;
}
if (consecutiveMessageCount >= 2 && !message.Contains("SELECT FROM") && !isRecording)
{
currentSongName = message;
Console.WriteLine("Starting recording...");
StartRecording(currentSongName);
isRecording = true;
}
if (isRecording)
{
if (shouldCheckForMusicSelect && message.Contains("MUSIC SELECT!!"))
{ {
Console.WriteLine("Stopping recording..."); result = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
StopRecording(currentSongName);
isRecording = false;
shouldCheckForMusicSelect = false;
} }
else if (message.EndsWith("CLEAR!") || message.EndsWith("FAILED..")) catch (Exception ex)
{ {
shouldCheckForMusicSelect = true; Console.WriteLine($"Error receiving message: {ex.Message}");
reconnecting = true;
break;
}
var message = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim().ToUpper();
Console.WriteLine($"Received message: {message}");
if (message == lastMessage && !message.Contains("SELECT FROM"))
{
consecutiveMessageCount++;
}
else
{
consecutiveMessageCount = 1;
lastMessage = message;
}
if (consecutiveMessageCount >= 2 && !message.Contains("SELECT FROM") && !isRecording)
{
currentSongName = message;
Console.WriteLine("Starting recording...");
StartRecording(currentSongName);
isRecording = true;
}
if (isRecording)
{
if (shouldCheckForMusicSelect && message.Contains("MUSIC SELECT!!"))
{
Console.WriteLine("Stopping recording...");
StopRecording(currentSongName);
isRecording = false;
shouldCheckForMusicSelect = false;
}
else if (message.EndsWith("CLEAR!") || message.EndsWith("FAILED.."))
{
shouldCheckForMusicSelect = true;
}
} }
} }
}
return !reconnecting; return !reconnecting;
} }
private static void StartRecording(string songName) private static void StartRecording(string songName)
{ {
Task.Run(() => StartAudioRecording(songName)); Task.Run(() => StartAudioRecording(songName));
StartFfmpegRecording(songName); StartFfmpegRecording(songName);
} }
private static void StartAudioRecording(string songName) private static void StartAudioRecording(string songName)
@ -191,8 +207,7 @@ private static async Task<bool> ConnectWebSocket()
{ {
var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
_audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav"; _audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav";
var outputFolder = Path.GetDirectoryName(_audioFilePath)!; Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!);
Directory.CreateDirectory(outputFolder);
_waveSource = new WasapiLoopbackCapture(); _waveSource = new WasapiLoopbackCapture();
_writer = new WaveFileWriter(_audioFilePath, _waveSource.WaveFormat); _writer = new WaveFileWriter(_audioFilePath, _waveSource.WaveFormat);
_waveSource.DataAvailable += (sender, args) => _waveSource.DataAvailable += (sender, args) =>
@ -202,11 +217,8 @@ private static async Task<bool> ConnectWebSocket()
_waveSource.RecordingStopped += (sender, args) => _waveSource.RecordingStopped += (sender, args) =>
{ {
_writer.Dispose(); _writer.Dispose();
_writer = null!;
_waveSource.Dispose(); _waveSource.Dispose();
_waveSource = null;
}; };
_waveSource.StartRecording(); _waveSource.StartRecording();
Console.WriteLine("WASAPI Audio recording started."); Console.WriteLine("WASAPI Audio recording started.");
} }
@ -216,65 +228,54 @@ private static async Task<bool> ConnectWebSocket()
} }
} }
private static void StartFfmpegRecording(string songName) private static void StartFfmpegRecording(string songName)
{ {
var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
_videoFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.mkv"; _videoFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.mkv";
var ffmpegArguments = $"-framerate {_framerate} " +
var ffmpegArguments = $"-framerate 60 " +
$"-filter_complex \"ddagrab=0,hwdownload,format=bgra\" " + $"-filter_complex \"ddagrab=0,hwdownload,format=bgra\" " +
$"-c:v libx264 -movflags +faststart -crf 20 -y \"{_videoFilePath}\""; $"-c:v libx264 -tune zerolatency -crf {_crf} -video_size {_resolution} -movflags +faststart -y \"{_videoFilePath}\"";
_ffmpegProcess = new Process
_ffmpegProcess = new Process(); {
_ffmpegProcess.StartInfo.FileName = "ffmpeg"; StartInfo = new ProcessStartInfo
_ffmpegProcess.StartInfo.Arguments = ffmpegArguments; {
_ffmpegProcess.StartInfo.UseShellExecute = false; FileName = "ffmpeg",
_ffmpegProcess.StartInfo.RedirectStandardOutput = false; Arguments = ffmpegArguments,
_ffmpegProcess.StartInfo.RedirectStandardError = true; UseShellExecute = false,
_ffmpegProcess.StartInfo.CreateNoWindow = true; RedirectStandardError = true,
CreateNoWindow = true
}
};
_ffmpegProcess.ErrorDataReceived += (_, args) => Console.WriteLine(args.Data); _ffmpegProcess.ErrorDataReceived += (_, args) => Console.WriteLine(args.Data);
_ffmpegProcess.Start(); _ffmpegProcess.Start();
_ffmpegProcess.BeginErrorReadLine(); _ffmpegProcess.BeginErrorReadLine();
Console.WriteLine("FFmpeg recording started.");
} }
private static void StopRecording(string songName) private static void StopRecording(string songName)
{ {
StopFfmpegRecording();
StopAudioRecording(); StopAudioRecording();
StopFfmpegRecording();
CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName); CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName);
} }
private static void StopAudioRecording() private static void StopAudioRecording()
{ {
if (_waveSource == null) return; _waveSource?.StopRecording();
Console.WriteLine("WASAPI Audio recording stopped.");
_waveSource.StopRecording();
_waveSource.Dispose();
_waveSource = null;
if (_writer != null)
{
_writer.Dispose();
_writer = null!;
}
Console.WriteLine("Audio recording stopped.");
} }
private static void StopFfmpegRecording() private static void StopFfmpegRecording()
{ {
if (_ffmpegProcess != null && _ffmpegProcess.HasExited) return; if (_ffmpegProcess != null && !_ffmpegProcess.HasExited)
_ffmpegProcess?.Kill(); {
_ffmpegProcess?.WaitForExit(); _ffmpegProcess.Kill();
_ffmpegProcess?.Dispose(); _ffmpegProcess.WaitForExit();
_ffmpegProcess = null; _ffmpegProcess = null!;
Console.WriteLine("FFMPEG process stopped."); Console.WriteLine("FFmpeg recording stopped.");
}
} }
private static void CombineAudioAndVideo(string videoFilePath, string audioFilePath, string songName) private static void CombineAudioAndVideo(string videoFilePath, string audioFilePath, string songName)
{ {
var combinedOutputFilePath = $"{_ffmpegFolderPath}\\{songName}_combined_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.mp4"; var combinedOutputFilePath = $"{_ffmpegFolderPath}\\{songName}_combined_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.mp4";