ASIO Recording

This commit is contained in:
Mercurio 2024-09-09 21:00:34 +02:00
parent 80a1e6d7bb
commit 390dc02117
8 changed files with 398 additions and 315 deletions

View file

@ -3,22 +3,26 @@ 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
{ {
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 = GetDefaultVideosFolderPath(); private static string _ffmpegFolderPath = GetDefaultVideosFolderPath();
private static WasapiCapture? _waveSource; private static WasapiCapture? _waveSource;
private static AsioOut? _asioSource;
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 string _resolution = "1920x1080"; // Default resolution
private static int _framerate = 60; // Default framerate private static int _framerate = 60; // Default framerate
private static float _crf = 23; // Default CRF value private static float _crf = 23; // Default CRF value
private static bool _useAsio = false;
private static string _gameProcessName = "spice64"; // Default game process name private static string _gameProcessName = "spice64"; // Default game process name
private static async Task Main(string[] args) private static async Task Main(string[] args)
@ -53,33 +57,43 @@ class Program
if (Directory.Exists(path)) if (Directory.Exists(path))
{ {
_ffmpegFolderPath = path; // Recording path _ffmpegFolderPath = path; // Recording path
} }
else else
{ {
Console.WriteLine($"The path specified in {filePath} does not exist. Using default recording path."); Console.WriteLine(
$"The path specified in {filePath} does not exist. Using default recording path.");
_ffmpegFolderPath = GetDefaultVideosFolderPath(); _ffmpegFolderPath = GetDefaultVideosFolderPath();
} }
} }
if (line.StartsWith("resolution:")) if (line.StartsWith("resolution:"))
{ {
_resolution = line["resolution:".Length..].Trim(); _resolution = line["resolution:".Length..].Trim();
Console.WriteLine($"Custom Resolution: {_resolution}"); Console.WriteLine($"Custom Resolution: {_resolution}");
} }
if (line.StartsWith("framerate:")) if (line.StartsWith("framerate:"))
{ {
_framerate = int.Parse(line["framerate:".Length..].Trim()); _framerate = int.Parse(line["framerate:".Length..].Trim());
Console.WriteLine($"Custom framerate: {_framerate}"); Console.WriteLine($"Custom framerate: {_framerate}");
} }
if (line.StartsWith("crf:")) if (line.StartsWith("crf:"))
{ {
_crf = float.Parse(line["crf:".Length..].Trim()); _crf = float.Parse(line["crf:".Length..].Trim());
Console.WriteLine($"custom crf: {_crf}"); Console.WriteLine($"Custom crf: {_crf}");
} }
if (line.StartsWith("game_process_name:")) if (line.StartsWith("game_process_name:"))
{ {
_gameProcessName = line["game_process_name:".Length..].Trim(); _gameProcessName = line["game_process_name:".Length..].Trim();
Console.WriteLine($"custom process name: {_gameProcessName}"); Console.WriteLine($"Custom process name: {_gameProcessName}");
}
if (line.StartsWith("useAsio"))
{
_useAsio = line.Split('=')[1].Trim().ToLower() == "true";
Console.WriteLine("ASIO Recording is enabled");
} }
} }
} }
@ -112,6 +126,7 @@ class Program
await Task.Delay(10000); await Task.Delay(10000);
} }
} }
if (attempt == maxRetries) if (attempt == maxRetries)
{ {
Console.WriteLine("Failed to connect after 5 attempts."); Console.WriteLine("Failed to connect after 5 attempts.");
@ -202,6 +217,18 @@ class Program
} }
private static void StartAudioRecording(string songName) private static void StartAudioRecording(string songName)
{
if (_useAsio)
{
StartAsioAudioRecording(songName);
}
else
{
StartWasapiAudioRecording(songName);
}
}
private static void StartWasapiAudioRecording(string songName)
{ {
try try
{ {
@ -210,10 +237,7 @@ class Program
Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!); Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!);
_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) => { _writer.Write(args.Buffer, 0, args.BytesRecorded); };
{
_writer.Write(args.Buffer, 0, args.BytesRecorded);
};
_waveSource.RecordingStopped += (sender, args) => _waveSource.RecordingStopped += (sender, args) =>
{ {
_writer.Dispose(); _writer.Dispose();
@ -224,35 +248,52 @@ class Program
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Error starting audio recording: {ex.Message}"); Console.WriteLine($"Error starting WASAPI audio recording: {ex.Message}");
} }
} }
private static void StartFfmpegRecording(string songName) private static void StartAsioAudioRecording(string songName)
{
try
{ {
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"; _audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav";
var ffmpegArguments = $"-framerate {_framerate} " + Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!);
$"-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",
Arguments = ffmpegArguments,
UseShellExecute = false,
RedirectStandardError = true,
CreateNoWindow = true
}
};
_ffmpegProcess.ErrorDataReceived += (_, args) => Console.WriteLine(args.Data);
_ffmpegProcess.Start();
_ffmpegProcess.BeginErrorReadLine();
Console.WriteLine("FFmpeg recording started."); _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) private static void StopRecording(string songName)
{ {
StopAudioRecording(); StopAudioRecording();
@ -261,9 +302,45 @@ class Program
} }
private static void StopAudioRecording() private static void StopAudioRecording()
{
if (_useAsio)
{
StopAsioAudioRecording();
}
else
{
StopWasapiAudioRecording();
}
}
private static void StopWasapiAudioRecording()
{ {
_waveSource?.StopRecording(); _waveSource?.StopRecording();
Console.WriteLine("WASAPI Audio recording stopped."); _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");
_videoFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.mkv";
Directory.CreateDirectory(Path.GetDirectoryName(_videoFilePath)!);
var ffmpegArgs =
$"-f dshow -framerate {_framerate} -i video=\"screen-capture-recorder\" -c:v libx264 -preset ultrafast -crf {_crf} -r {_framerate} -s {_resolution} \"{_videoFilePath}\"";
var startInfo = new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = ffmpegArgs,
UseShellExecute = false,
CreateNoWindow = true
};
_ffmpegProcess = Process.Start(startInfo);
} }
private static void StopFfmpegRecording() private static void StopFfmpegRecording()
@ -271,9 +348,8 @@ class Program
if (_ffmpegProcess != null && !_ffmpegProcess.HasExited) if (_ffmpegProcess != null && !_ffmpegProcess.HasExited)
{ {
_ffmpegProcess.Kill(); _ffmpegProcess.Kill();
_ffmpegProcess.WaitForExit(); _ffmpegProcess.Dispose();
_ffmpegProcess = null!; _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)
@ -311,4 +387,5 @@ class Program
File.Delete(videoFilePath); File.Delete(videoFilePath);
File.Delete(audioFilePath); File.Delete(audioFilePath);
} }
}
} }

View file

@ -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"
} }
} }
} }

View file

@ -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>

View file

@ -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"
} }
} }
} }

View file

@ -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",

View file

@ -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"}}

View file

@ -1 +1 @@
17255542395188510 17259084205730795

View file

@ -1 +1 @@
17255543032402240 17259084285638427