forked from Mercury/2dxAutoClip
ASIO Recording
This commit is contained in:
parent
80a1e6d7bb
commit
390dc02117
|
@ -3,312 +3,389 @@ using System.Net.WebSockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using NAudio.CoreAudioApi;
|
using NAudio.CoreAudioApi;
|
||||||
using NAudio.Wave;
|
using NAudio.Wave;
|
||||||
|
using NAudio.Wave.Asio;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace _2dxAutoClip;
|
namespace _2dxAutoClip
|
||||||
|
|
||||||
class Program
|
|
||||||
{
|
{
|
||||||
private static readonly string WebsocketAddress = "localhost";
|
class Program
|
||||||
private static readonly int WebsocketPort = 10573;
|
|
||||||
private static Process? _ffmpegProcess;
|
|
||||||
private static string _ffmpegFolderPath = GetDefaultVideosFolderPath();
|
|
||||||
private static WasapiCapture? _waveSource;
|
|
||||||
private static WaveFileWriter _writer = null!;
|
|
||||||
private static string _audioFilePath = 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)
|
|
||||||
{
|
{
|
||||||
// Load settings from prop.txt
|
private static readonly string WebsocketAddress = "localhost";
|
||||||
LoadSettingsFromPropFile();
|
private static readonly int WebsocketPort = 10573;
|
||||||
|
private static Process? _ffmpegProcess;
|
||||||
|
private static string _ffmpegFolderPath = GetDefaultVideosFolderPath();
|
||||||
|
private static WasapiCapture? _waveSource;
|
||||||
|
private static AsioOut? _asioSource;
|
||||||
|
private static WaveFileWriter _writer = null!;
|
||||||
|
private static string _audioFilePath = 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 bool _useAsio = false;
|
||||||
|
private static string _gameProcessName = "spice64"; // Default game process name
|
||||||
|
|
||||||
var gameProcesses = Process.GetProcessesByName(_gameProcessName);
|
private static async Task Main(string[] args)
|
||||||
if (gameProcesses.Length > 0)
|
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Found {_gameProcessName}, Attempting connection to TickerHookWS...");
|
// Load settings from prop.txt
|
||||||
await TryConnectWebSocket();
|
LoadSettingsFromPropFile();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Unable to find {_gameProcessName}. Is the game running and TickerHook enabled?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void LoadSettingsFromPropFile()
|
var gameProcesses = Process.GetProcessesByName(_gameProcessName);
|
||||||
{
|
if (gameProcesses.Length > 0)
|
||||||
const string filePath = "prop.txt";
|
|
||||||
if (File.Exists(filePath))
|
|
||||||
{
|
|
||||||
var fileContent = File.ReadAllText(filePath);
|
|
||||||
var lines = fileContent.Split('\n');
|
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
{
|
||||||
if (line.StartsWith("path:"))
|
Console.WriteLine($"Found {_gameProcessName}, Attempting connection to TickerHookWS...");
|
||||||
{
|
await TryConnectWebSocket();
|
||||||
var path = line["path:".Length..].Trim();
|
|
||||||
if (Directory.Exists(path))
|
|
||||||
{
|
|
||||||
_ffmpegFolderPath = path; // Recording path
|
|
||||||
|
|
||||||
}
|
|
||||||
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
|
|
||||||
{
|
|
||||||
Console.WriteLine($"The file {filePath} does not exist. Using default values.");
|
|
||||||
_ffmpegFolderPath = GetDefaultVideosFolderPath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetDefaultVideosFolderPath()
|
|
||||||
{
|
|
||||||
var userName = Environment.UserName;
|
|
||||||
return Path.Combine($@"C:\Users\{userName}", "Videos");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task TryConnectWebSocket()
|
|
||||||
{
|
|
||||||
const int maxRetries = 5;
|
|
||||||
int attempt = 0;
|
|
||||||
while (attempt < maxRetries)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Failed to connect after 5 attempts.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<bool> ConnectWebSocket()
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer = new byte[1024];
|
|
||||||
while (clientWebSocket.State == WebSocketState.Open)
|
|
||||||
{
|
|
||||||
WebSocketReceiveResult result;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
consecutiveMessageCount = 1;
|
Console.WriteLine($"Unable to find {_gameProcessName}. Is the game running and TickerHook enabled?");
|
||||||
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;
|
private static void LoadSettingsFromPropFile()
|
||||||
}
|
{
|
||||||
|
const string filePath = "prop.txt";
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
var fileContent = File.ReadAllText(filePath);
|
||||||
|
var lines = fileContent.Split('\n');
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
if (line.StartsWith("path:"))
|
||||||
|
{
|
||||||
|
var path = line["path:".Length..].Trim();
|
||||||
|
if (Directory.Exists(path))
|
||||||
|
{
|
||||||
|
_ffmpegFolderPath = path; // Recording path
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"The path specified in {filePath} does not exist. Using default recording path.");
|
||||||
|
_ffmpegFolderPath = GetDefaultVideosFolderPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void StartRecording(string songName)
|
if (line.StartsWith("resolution:"))
|
||||||
{
|
{
|
||||||
Task.Run(() => StartAudioRecording(songName));
|
_resolution = line["resolution:".Length..].Trim();
|
||||||
StartFfmpegRecording(songName);
|
Console.WriteLine($"Custom Resolution: {_resolution}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StartAudioRecording(string songName)
|
if (line.StartsWith("framerate:"))
|
||||||
{
|
{
|
||||||
try
|
_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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.StartsWith("useAsio"))
|
||||||
|
{
|
||||||
|
_useAsio = line.Split('=')[1].Trim().ToLower() == "true";
|
||||||
|
Console.WriteLine("ASIO Recording is enabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"The file {filePath} does not exist. Using default values.");
|
||||||
|
_ffmpegFolderPath = GetDefaultVideosFolderPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetDefaultVideosFolderPath()
|
||||||
|
{
|
||||||
|
var userName = Environment.UserName;
|
||||||
|
return Path.Combine($@"C:\Users\{userName}", "Videos");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task TryConnectWebSocket()
|
||||||
|
{
|
||||||
|
const int maxRetries = 5;
|
||||||
|
int attempt = 0;
|
||||||
|
while (attempt < maxRetries)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Failed to connect after 5 attempts.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> ConnectWebSocket()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = new byte[1024];
|
||||||
|
while (clientWebSocket.State == WebSocketState.Open)
|
||||||
|
{
|
||||||
|
WebSocketReceiveResult result;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StartRecording(string songName)
|
||||||
|
{
|
||||||
|
Task.Run(() => StartAudioRecording(songName));
|
||||||
|
StartFfmpegRecording(songName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StartAudioRecording(string songName)
|
||||||
|
{
|
||||||
|
if (_useAsio)
|
||||||
|
{
|
||||||
|
StartAsioAudioRecording(songName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StartWasapiAudioRecording(songName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StartWasapiAudioRecording(string songName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||||
|
_audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav";
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!);
|
||||||
|
_waveSource = new WasapiLoopbackCapture();
|
||||||
|
_writer = new WaveFileWriter(_audioFilePath, _waveSource.WaveFormat);
|
||||||
|
_waveSource.DataAvailable += (sender, args) => { _writer.Write(args.Buffer, 0, args.BytesRecorded); };
|
||||||
|
_waveSource.RecordingStopped += (sender, args) =>
|
||||||
|
{
|
||||||
|
_writer.Dispose();
|
||||||
|
_waveSource.Dispose();
|
||||||
|
};
|
||||||
|
_waveSource.StartRecording();
|
||||||
|
Console.WriteLine("WASAPI Audio recording started.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error starting WASAPI audio recording: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StartAsioAudioRecording(string songName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||||
|
_audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav";
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!);
|
||||||
|
|
||||||
|
_asioSource = new AsioOut(0);
|
||||||
|
_asioSource.InitRecordAndPlayback(null, 2, 48000);
|
||||||
|
|
||||||
|
_writer = new WaveFileWriter(_audioFilePath, new WaveFormat(44100, 16, 2));
|
||||||
|
|
||||||
|
_asioSource.AudioAvailable += (s, e) =>
|
||||||
|
{
|
||||||
|
var buffer = new byte[e.SamplesPerBuffer * 4];
|
||||||
|
int bufferIndex = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < e.SamplesPerBuffer; i++)
|
||||||
|
{
|
||||||
|
short sampleL = (short)(e.GetAsInterleavedSamples()[i * 2] * short.MaxValue);
|
||||||
|
short sampleR = (short)(e.GetAsInterleavedSamples()[i * 2 + 1] * short.MaxValue);
|
||||||
|
|
||||||
|
buffer[bufferIndex++] = (byte)(sampleL & 0xFF);
|
||||||
|
buffer[bufferIndex++] = (byte)((sampleL >> 8) & 0xFF);
|
||||||
|
buffer[bufferIndex++] = (byte)(sampleR & 0xFF);
|
||||||
|
buffer[bufferIndex++] = (byte)((sampleR >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.Write(buffer, 0, buffer.Length);
|
||||||
|
};
|
||||||
|
|
||||||
|
_asioSource.Play();
|
||||||
|
Console.WriteLine("ASIO Audio recording started.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error starting ASIO audio recording: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void StopRecording(string songName)
|
||||||
|
{
|
||||||
|
StopAudioRecording();
|
||||||
|
StopFfmpegRecording();
|
||||||
|
CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StopAudioRecording()
|
||||||
|
{
|
||||||
|
if (_useAsio)
|
||||||
|
{
|
||||||
|
StopAsioAudioRecording();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StopWasapiAudioRecording();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StopWasapiAudioRecording()
|
||||||
|
{
|
||||||
|
_waveSource?.StopRecording();
|
||||||
|
_waveSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StopAsioAudioRecording()
|
||||||
|
{
|
||||||
|
_asioSource?.Stop();
|
||||||
|
_asioSource?.Dispose();
|
||||||
|
_asioSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
_audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav";
|
_videoFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.mkv";
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!);
|
Directory.CreateDirectory(Path.GetDirectoryName(_videoFilePath)!);
|
||||||
_waveSource = new WasapiLoopbackCapture();
|
var ffmpegArgs =
|
||||||
_writer = new WaveFileWriter(_audioFilePath, _waveSource.WaveFormat);
|
$"-f dshow -framerate {_framerate} -i video=\"screen-capture-recorder\" -c:v libx264 -preset ultrafast -crf {_crf} -r {_framerate} -s {_resolution} \"{_videoFilePath}\"";
|
||||||
_waveSource.DataAvailable += (sender, args) =>
|
var startInfo = new ProcessStartInfo
|
||||||
{
|
|
||||||
_writer.Write(args.Buffer, 0, args.BytesRecorded);
|
|
||||||
};
|
|
||||||
_waveSource.RecordingStopped += (sender, args) =>
|
|
||||||
{
|
|
||||||
_writer.Dispose();
|
|
||||||
_waveSource.Dispose();
|
|
||||||
};
|
|
||||||
_waveSource.StartRecording();
|
|
||||||
Console.WriteLine("WASAPI Audio recording started.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Error starting audio recording: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void StartFfmpegRecording(string songName)
|
|
||||||
{
|
|
||||||
var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
|
||||||
_videoFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.mkv";
|
|
||||||
var ffmpegArguments = $"-framerate {_framerate} " +
|
|
||||||
$"-filter_complex \"ddagrab=framerate={_framerate},hwdownload,format=bgra\" " +
|
|
||||||
$"-c:v libx264 -tune zerolatency -crf {_crf} -video_size {_resolution} -movflags +faststart -y \"{_videoFilePath}\"";
|
|
||||||
_ffmpegProcess = new Process
|
|
||||||
{
|
|
||||||
StartInfo = new ProcessStartInfo
|
|
||||||
{
|
{
|
||||||
FileName = "ffmpeg",
|
FileName = "ffmpeg",
|
||||||
Arguments = ffmpegArguments,
|
Arguments = ffmpegArgs,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
RedirectStandardError = true,
|
|
||||||
CreateNoWindow = true
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
_ffmpegProcess = Process.Start(startInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StopFfmpegRecording()
|
||||||
|
{
|
||||||
|
if (_ffmpegProcess != null && !_ffmpegProcess.HasExited)
|
||||||
|
{
|
||||||
|
_ffmpegProcess.Kill();
|
||||||
|
_ffmpegProcess.Dispose();
|
||||||
|
_ffmpegProcess = null;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
_ffmpegProcess.ErrorDataReceived += (_, args) => Console.WriteLine(args.Data);
|
|
||||||
_ffmpegProcess.Start();
|
|
||||||
_ffmpegProcess.BeginErrorReadLine();
|
|
||||||
|
|
||||||
Console.WriteLine("FFmpeg recording started.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void StopRecording(string songName)
|
|
||||||
{
|
|
||||||
StopAudioRecording();
|
|
||||||
StopFfmpegRecording();
|
|
||||||
CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void StopAudioRecording()
|
|
||||||
{
|
|
||||||
_waveSource?.StopRecording();
|
|
||||||
Console.WriteLine("WASAPI Audio recording stopped.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void StopFfmpegRecording()
|
|
||||||
{
|
|
||||||
if (_ffmpegProcess != null && !_ffmpegProcess.HasExited)
|
|
||||||
{
|
|
||||||
_ffmpegProcess.Kill();
|
|
||||||
_ffmpegProcess.WaitForExit();
|
|
||||||
_ffmpegProcess = null!;
|
|
||||||
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 ffmpegArgs =
|
|
||||||
$"-y -i \"{videoFilePath}\" -i \"{audioFilePath}\" -c:v copy -c:a aac -strict experimental -shortest \"{combinedOutputFilePath}\"";
|
|
||||||
|
|
||||||
var processInfo = new ProcessStartInfo
|
|
||||||
{
|
{
|
||||||
FileName = "ffmpeg",
|
var combinedOutputFilePath = $"{_ffmpegFolderPath}\\{songName}_combined_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.mp4";
|
||||||
Arguments = ffmpegArgs,
|
var ffmpegArgs =
|
||||||
RedirectStandardOutput = false,
|
$"-y -i \"{videoFilePath}\" -i \"{audioFilePath}\" -c:v copy -c:a aac -strict experimental -shortest \"{combinedOutputFilePath}\"";
|
||||||
RedirectStandardError = true,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
|
|
||||||
using var process = Process.Start(processInfo);
|
var processInfo = new ProcessStartInfo
|
||||||
if (process == null)
|
{
|
||||||
{
|
FileName = "ffmpeg",
|
||||||
Console.WriteLine("FFmpeg failed to start.");
|
Arguments = ffmpegArgs,
|
||||||
return;
|
RedirectStandardOutput = false,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
using var process = Process.Start(processInfo);
|
||||||
|
if (process == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("FFmpeg failed to start.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = process.StandardError.ReadToEnd();
|
||||||
|
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
Console.WriteLine("FFmpeg Error: " + error);
|
||||||
|
|
||||||
|
Console.WriteLine(File.Exists(combinedOutputFilePath)
|
||||||
|
? "Audio and video have been successfully combined."
|
||||||
|
: "Failed to combine audio and video. Check the logs for errors.");
|
||||||
|
File.Delete(videoFilePath);
|
||||||
|
File.Delete(audioFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
var error = process.StandardError.ReadToEnd();
|
|
||||||
|
|
||||||
process.WaitForExit();
|
|
||||||
|
|
||||||
Console.WriteLine("FFmpeg Error: " + error);
|
|
||||||
|
|
||||||
Console.WriteLine(File.Exists(combinedOutputFilePath)
|
|
||||||
? "Audio and video have been successfully combined."
|
|
||||||
: "Failed to combine audio and video. Check the logs for errors.");
|
|
||||||
File.Delete(videoFilePath);
|
|
||||||
File.Delete(audioFilePath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,25 +1,32 @@
|
||||||
{
|
{
|
||||||
"format": 1,
|
"format": 1,
|
||||||
"restore": {
|
"restore": {
|
||||||
"D:\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj": {}
|
"E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj": {}
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"D:\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj": {
|
"E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"restore": {
|
"restore": {
|
||||||
"projectUniqueName": "D:\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj",
|
"projectUniqueName": "E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj",
|
||||||
"projectName": "2dxAutoClip",
|
"projectName": "2dxAutoClip",
|
||||||
"projectPath": "D:\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj",
|
"projectPath": "E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj",
|
||||||
"packagesPath": "C:\\Users\\Mercury\\.nuget\\packages\\",
|
"packagesPath": "C:\\Users\\Mercury\\.nuget\\packages\\",
|
||||||
"outputPath": "D:\\2dxAutoClip\\2dxAutoClip\\obj\\",
|
"outputPath": "E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\obj\\",
|
||||||
"projectStyle": "PackageReference",
|
"projectStyle": "PackageReference",
|
||||||
|
"fallbackFolders": [
|
||||||
|
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||||
|
],
|
||||||
"configFilePaths": [
|
"configFilePaths": [
|
||||||
"C:\\Users\\Mercury\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
"C:\\Users\\Mercury\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||||
|
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||||
|
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||||
],
|
],
|
||||||
"originalTargetFrameworks": [
|
"originalTargetFrameworks": [
|
||||||
"net8.0"
|
"net8.0"
|
||||||
],
|
],
|
||||||
"sources": {
|
"sources": {
|
||||||
|
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||||
|
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||||
"https://api.nuget.org/v3/index.json": {}
|
"https://api.nuget.org/v3/index.json": {}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
@ -32,11 +39,6 @@
|
||||||
"warnAsError": [
|
"warnAsError": [
|
||||||
"NU1605"
|
"NU1605"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"restoreAuditProperties": {
|
|
||||||
"enableAudit": "true",
|
|
||||||
"auditLevel": "low",
|
|
||||||
"auditMode": "direct"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
@ -68,7 +70,7 @@
|
||||||
"privateAssets": "all"
|
"privateAssets": "all"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"runtimeIdentifierGraphPath": "C:\\Users\\Mercury\\.dotnet\\sdk\\8.0.303/PortableRuntimeIdentifierGraph.json"
|
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.300/PortableRuntimeIdentifierGraph.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,12 @@
|
||||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\Mercury\.nuget\packages\</NuGetPackageFolders>
|
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\Mercury\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.10.1</NuGetToolVersion>
|
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.9.1</NuGetToolVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
<SourceRoot Include="C:\Users\Mercury\.nuget\packages\" />
|
<SourceRoot Include="C:\Users\Mercury\.nuget\packages\" />
|
||||||
|
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -452,24 +452,32 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"packageFolders": {
|
"packageFolders": {
|
||||||
"C:\\Users\\Mercury\\.nuget\\packages\\": {}
|
"C:\\Users\\Mercury\\.nuget\\packages\\": {},
|
||||||
|
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {}
|
||||||
},
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"restore": {
|
"restore": {
|
||||||
"projectUniqueName": "D:\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj",
|
"projectUniqueName": "E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj",
|
||||||
"projectName": "2dxAutoClip",
|
"projectName": "2dxAutoClip",
|
||||||
"projectPath": "D:\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj",
|
"projectPath": "E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj",
|
||||||
"packagesPath": "C:\\Users\\Mercury\\.nuget\\packages\\",
|
"packagesPath": "C:\\Users\\Mercury\\.nuget\\packages\\",
|
||||||
"outputPath": "D:\\2dxAutoClip\\2dxAutoClip\\obj\\",
|
"outputPath": "E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\obj\\",
|
||||||
"projectStyle": "PackageReference",
|
"projectStyle": "PackageReference",
|
||||||
|
"fallbackFolders": [
|
||||||
|
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||||
|
],
|
||||||
"configFilePaths": [
|
"configFilePaths": [
|
||||||
"C:\\Users\\Mercury\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
"C:\\Users\\Mercury\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||||
|
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||||
|
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||||
],
|
],
|
||||||
"originalTargetFrameworks": [
|
"originalTargetFrameworks": [
|
||||||
"net8.0"
|
"net8.0"
|
||||||
],
|
],
|
||||||
"sources": {
|
"sources": {
|
||||||
|
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||||
|
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||||
"https://api.nuget.org/v3/index.json": {}
|
"https://api.nuget.org/v3/index.json": {}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
@ -482,11 +490,6 @@
|
||||||
"warnAsError": [
|
"warnAsError": [
|
||||||
"NU1605"
|
"NU1605"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"restoreAuditProperties": {
|
|
||||||
"enableAudit": "true",
|
|
||||||
"auditLevel": "low",
|
|
||||||
"auditMode": "direct"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
@ -518,7 +521,7 @@
|
||||||
"privateAssets": "all"
|
"privateAssets": "all"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"runtimeIdentifierGraphPath": "C:\\Users\\Mercury\\.dotnet\\sdk\\8.0.303/PortableRuntimeIdentifierGraph.json"
|
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.300/PortableRuntimeIdentifierGraph.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"dgSpecHash": "71ChNRMXyyA=",
|
"dgSpecHash": "xTfdkrpqvxmeY+pzZcnpBIJbSyoO94xa04eSLg8IlKmzbTfi/q7IfP/sLGg2XwIVxlpAVQKwcAeegUkFcOHA5A==",
|
||||||
"success": true,
|
"success": true,
|
||||||
"projectFilePath": "D:\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj",
|
"projectFilePath": "E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj",
|
||||||
"expectedPackageFiles": [
|
"expectedPackageFiles": [
|
||||||
"C:\\Users\\Mercury\\.nuget\\packages\\microsoft.netcore.platforms\\3.1.0\\microsoft.netcore.platforms.3.1.0.nupkg.sha512",
|
"C:\\Users\\Mercury\\.nuget\\packages\\microsoft.netcore.platforms\\3.1.0\\microsoft.netcore.platforms.3.1.0.nupkg.sha512",
|
||||||
"C:\\Users\\Mercury\\.nuget\\packages\\microsoft.win32.registry\\4.7.0\\microsoft.win32.registry.4.7.0.nupkg.sha512",
|
"C:\\Users\\Mercury\\.nuget\\packages\\microsoft.win32.registry\\4.7.0\\microsoft.win32.registry.4.7.0.nupkg.sha512",
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
"restore":{"projectUniqueName":"D:\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj","projectName":"2dxAutoClip","projectPath":"D:\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj","outputPath":"D:\\2dxAutoClip\\2dxAutoClip\\obj\\","projectStyle":"PackageReference","originalTargetFrameworks":["net8.0"],"sources":{"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net8.0":{"targetAlias":"net8.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"}}"frameworks":{"net8.0":{"targetAlias":"net8.0","dependencies":{"NAudio":{"target":"Package","version":"[2.2.1, )"},"NAudio.Wasapi":{"target":"Package","version":"[2.2.1, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Users\\Mercury\\.dotnet\\sdk\\8.0.303/PortableRuntimeIdentifierGraph.json"}}
|
"restore":{"projectUniqueName":"E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj","projectName":"2dxAutoClip","projectPath":"E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj","outputPath":"E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\obj\\","projectStyle":"PackageReference","fallbackFolders":["C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"],"originalTargetFrameworks":["net8.0"],"sources":{"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\":{},"C:\\Program Files\\dotnet\\library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net8.0":{"targetAlias":"net8.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]}}"frameworks":{"net8.0":{"targetAlias":"net8.0","dependencies":{"NAudio":{"target":"Package","version":"[2.2.1, )"},"NAudio.Wasapi":{"target":"Package","version":"[2.2.1, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Program Files\\dotnet\\sdk\\8.0.300/PortableRuntimeIdentifierGraph.json"}}
|
|
@ -1 +1 @@
|
||||||
17255542395188510
|
17259084205730795
|
|
@ -1 +1 @@
|
||||||
17255543032402240
|
17259084285638427
|
Loading…
Reference in a new issue