Initial support for ApplicationLoopback audio recording (WASAPI only)

This commit is contained in:
Mercurio 2024-12-15 15:42:33 +01:00
parent d6010162fc
commit 139b64dd1b
5 changed files with 82 additions and 24 deletions

View file

@ -5,7 +5,6 @@ using System.Text.RegularExpressions;
using NAudio.CoreAudioApi; using NAudio.CoreAudioApi;
using NAudio.Wave; using NAudio.Wave;
using System.Net; using System.Net;
// ReSharper disable PossibleInvalidCastExceptionInForeachLoop
namespace _2dxAutoClip; namespace _2dxAutoClip;
#pragma warning disable CA1416 #pragma warning disable CA1416
@ -15,6 +14,7 @@ class Program
{ {
private static readonly string WebsocketAddress = "localhost"; private static readonly string WebsocketAddress = "localhost";
private static Process? _ffmpegProcess; private static Process? _ffmpegProcess;
private static Process? _recorderprocess;
private static string _ffmpegFolderPath = GetDefaultVideosFolderPath(); private static string _ffmpegFolderPath = GetDefaultVideosFolderPath();
private static WasapiCapture? _waveSource; private static WasapiCapture? _waveSource;
private static WaveFileWriter _writer = null!; private static WaveFileWriter _writer = null!;
@ -69,6 +69,16 @@ class Program
} }
} }
} }
public static int GetFirstProcessIdByName(string executableName)
{
if (string.IsNullOrWhiteSpace(executableName))
{
throw new ArgumentException("Executable name cannot be null or empty.", nameof(executableName));
}
var processes = Process.GetProcessesByName(executableName);
Console.WriteLine($"Found spice PID: {processes.FirstOrDefault()?.Id}");
return processes.FirstOrDefault()?.Id ?? -1;
}
private static void LoadSettingsFromPropFile() private static void LoadSettingsFromPropFile()
{ {
const string filePath = "prop.txt"; const string filePath = "prop.txt";
@ -228,30 +238,46 @@ class Program
private static void StartRecording(string songName) private static void StartRecording(string songName)
{ {
Task.Run(() => StartAudioRecording(songName)); StartAudioProcessRecording(songName);
StartFfmpegRecording(songName); StartFfmpegRecording(songName);
} }
private static void StartAudioRecording(string songName)
private static void StartAudioProcessRecording(string songName)
{ {
try try
{ {
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";
Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!);
_waveSource = new WasapiLoopbackCapture(); var directory = Path.GetDirectoryName(_audioFilePath);
_writer = new WaveFileWriter(_audioFilePath, _waveSource.WaveFormat); if (directory == null)
_waveSource.DataAvailable += (sender, args) =>
{ {
_writer.Write(args.Buffer, 0, args.BytesRecorded); throw new InvalidOperationException("Invalid audio file path.");
}; }
_waveSource.RecordingStopped += (sender, args) => Directory.CreateDirectory(directory);
var procid = GetFirstProcessIdByName(_gameProcessName);
if (procid == -1)
{ {
_writer.Dispose(); throw new InvalidOperationException("Target process is not running.");
_waveSource.Dispose(); }
var args = $"{procid} includetree {_audioFilePath}";
_recorderprocess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "applb",
Arguments = args,
UseShellExecute = false,
RedirectStandardError = true,
CreateNoWindow = true
}
}; };
_waveSource.StartRecording();
Console.WriteLine("WASAPI Audio recording started."); _recorderprocess.Start();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -259,6 +285,7 @@ class Program
} }
} }
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");
@ -380,15 +407,34 @@ class Program
private static void StopRecording(string songName) private static void StopRecording(string songName)
{ {
StopAudioRecording(); //StopAudioRecording();
TerminateProcessByName();
StopFfmpegRecording(); StopFfmpegRecording();
CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName); CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName);
} }
private static void StopAudioRecording() public static void TerminateProcessByName()
{ {
_waveSource?.StopRecording(); var executableName = "applb";
Console.WriteLine("WASAPI Audio recording stopped."); try
{
int processId = GetFirstProcessIdByName(executableName);
if (processId == -1)
{
Console.WriteLine($"No running process found for '{executableName}'.");
return;
}
var process = Process.GetProcessById(processId);
process.Kill();
Console.WriteLine($"Process '{executableName}' with ID {processId} terminated successfully.");
}
catch (Exception ex)
{
Console.WriteLine($"Error terminating process '{executableName}': {ex.Message}");
}
} }
private static void StopFfmpegRecording() private static void StopFfmpegRecording()

View file

@ -39,7 +39,13 @@
"warnAsError": [ "warnAsError": [
"NU1605" "NU1605"
] ]
} },
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.100"
}, },
"frameworks": { "frameworks": {
"net8.0": { "net8.0": {
@ -70,7 +76,7 @@
"privateAssets": "all" "privateAssets": "all"
} }
}, },
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.300/PortableRuntimeIdentifierGraph.json" "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.101/PortableRuntimeIdentifierGraph.json"
} }
} }
} }

View file

@ -7,7 +7,7 @@
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot> <NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\Mercury\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</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.9.1</NuGetToolVersion> <NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.12.2</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\" />

View file

@ -490,7 +490,13 @@
"warnAsError": [ "warnAsError": [
"NU1605" "NU1605"
] ]
} },
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.100"
}, },
"frameworks": { "frameworks": {
"net8.0": { "net8.0": {
@ -521,7 +527,7 @@
"privateAssets": "all" "privateAssets": "all"
} }
}, },
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.300/PortableRuntimeIdentifierGraph.json" "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.101/PortableRuntimeIdentifierGraph.json"
} }
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"version": 2, "version": 2,
"dgSpecHash": "xTfdkrpqvxmeY+pzZcnpBIJbSyoO94xa04eSLg8IlKmzbTfi/q7IfP/sLGg2XwIVxlpAVQKwcAeegUkFcOHA5A==", "dgSpecHash": "Cu8IH0pDN8c=",
"success": true, "success": true,
"projectFilePath": "E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj", "projectFilePath": "E:\\csharpcazzo\\2dxAutoClip\\2dxAutoClip\\2dxAutoClip.csproj",
"expectedPackageFiles": [ "expectedPackageFiles": [