Skip to content

Commit

Permalink
fix: 在linux上面获取音频设备
Browse files Browse the repository at this point in the history
  • Loading branch information
withsalt committed Sep 28, 2024
1 parent fb66ba6 commit 9f239b0
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 43 deletions.
79 changes: 79 additions & 0 deletions src/BilibiliAutoLiver/Models/Dtos/AudioDeviceInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using FlashCap;

namespace BilibiliAutoLiver.Models.Dtos
{
public class AudioDeviceInfo
{
public string Name { get; set; }

public string DeviceName { get; set; }

public AudioDeviceType DeviceType { get; set; }

public int CardIndex { get; set; }

public int DeviceIndex { get; set; }

public string Identity
{
get
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return this.Name;
}
else
{
return $"hw:{this.CardIndex},{this.DeviceIndex}";
}
}
}

public string Description
{
get
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return this.Name;
}
else
{
if (!string.IsNullOrEmpty(this.Identity) && this.Name != this.Identity)
{
return $"{this.Name}({this.Identity})";
}
else
{
return this.Name;
}
}
}
}

public string DeviceTypeName
{
get
{
switch (DeviceType)
{
case AudioDeviceType.DirectShow:
return "dshow";
case AudioDeviceType.Alsa:
return "alsa";
default:
return "unsupport";
}
}
}
}

public enum AudioDeviceType
{
DirectShow = 1,

Alsa = 2,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class PushIndexPageViewModel
/// <summary>
/// 支持的音频设备
/// </summary>
public List<string> AudioDevices { get; set; }
public List<AudioDeviceInfo> AudioDevices { get; set; }

/// <summary>
/// 支持的视频编码器
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Text;
using System.Threading.Tasks;
using BilibiliAutoLiver.Models;
using BilibiliAutoLiver.Models.Dtos;
using BilibiliAutoLiver.Models.FFMpeg;
using CliWrap;
using CliWrap.Buffered;
Expand All @@ -21,9 +22,9 @@ public BaseFFMpegCliBinder(string ffmpegPath, string workingDirectory)
this.WorkingDirectory = workingDirectory;
}

public abstract Task<List<string>> GetVideoDevices();
public abstract Task<List<VideoDeviceInfo>> GetVideoDevices();

public abstract Task<List<string>> GetAudioDevices();
public abstract Task<List<AudioDeviceInfo>> GetAudioDevices();

public abstract Task<List<DeviceResolution>> ListVideoDeviceSupportResolutions(string deviceName);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BilibiliAutoLiver.Models.Dtos;
using BilibiliAutoLiver.Models.FFMpeg;
using CliWrap;
using CliWrap.Buffered;
Expand All @@ -16,15 +18,58 @@ public FFMpegLinuxCliBinder(string ffmpegPath, string workingDirectory) : base(f

}

public override async Task<List<string>> GetVideoDevices()
public override Task<List<VideoDeviceInfo>> GetVideoDevices()
{
List<string> devices = new List<string>();
return devices;
throw new NotSupportedException("暂不支持在Linux操作系统上面,通过FFMpeg获取视频输入设备。");
}

public override async Task<List<string>> GetAudioDevices()
public override async Task<List<AudioDeviceInfo>> GetAudioDevices()
{
List<string> devices = new List<string>();
List<AudioDeviceInfo> devices = new List<AudioDeviceInfo>();

string output = await GetExcuteResult("arecord", "-l");
if (string.IsNullOrWhiteSpace(output))
{
return devices;
}

string[] outputArgs = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
List<string> cardLines = new List<string>();
foreach (var outputItem in outputArgs)
{
string outputItemNew = outputItem.Trim('\r', '\n', ' ');
if (!string.IsNullOrWhiteSpace(outputItemNew) && outputItemNew.StartsWith("card"))
{
cardLines.Add(outputItemNew);
}
}
if (cardLines.Count == 0)
{
return devices;
}

string pattern = @"card (\d+): ([^]]+) \[([^]]+)\], device (\d+): ([^]]+) \[([^]]+)\]";
foreach (var cardLine in cardLines)
{
var match = Regex.Match(cardLine, pattern);
if (match.Success
&& match.Groups.Count > 6
&& int.TryParse(match.Groups[1].Value, out int cardIndex)
&& int.TryParse(match.Groups[4].Value, out int deviceIndex)
&& cardIndex >= 0
&& deviceIndex >= 0)
{
AudioDeviceInfo deviceInfo = new AudioDeviceInfo
{
CardIndex = cardIndex,
Name = match.Groups[3].Value,
DeviceIndex = deviceIndex,
DeviceName = match.Groups[6].Value,
DeviceType = AudioDeviceType.Alsa,
};
devices.Add(deviceInfo);
}
}
return devices;
}

Expand All @@ -37,14 +82,14 @@ public override async Task<List<DeviceResolution>> ListVideoDeviceSupportResolut
return new List<DeviceResolution>();
}

private async Task<string> GetExcuteResult()
private async Task<string> GetExcuteResult(string name, string args)
{
if (!string.IsNullOrWhiteSpace(_excuteResult))
{
return _excuteResult;
}
var result = await Cli.Wrap(this.FFMpegPath)
.WithArguments("-list_devices true -f dshow -i dummy")
var result = await Cli.Wrap(name)
.WithArguments(args)
.WithWorkingDirectory(this.WorkingDirectory)
.WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BilibiliAutoLiver.Models.Dtos;
using BilibiliAutoLiver.Models.FFMpeg;
using BilibiliAutoLiver.Utils;
using CliWrap;
using CliWrap.Buffered;
using FlashCap;

namespace BilibiliAutoLiver.Services.FFMpeg.Services.CliBinder
{
Expand All @@ -18,18 +20,47 @@ public FFMpegWindowsCliBinder(string ffmpegPath, string workingDirectory) : base

}

public override async Task<List<string>> GetVideoDevices()
public override async Task<List<VideoDeviceInfo>> GetVideoDevices()
{
string output = await GetExcuteResult("-list_devices true -f dshow -i dummy".GetHashCode(), "-list_devices true -f dshow -i dummy");
List<string> devices = ExtractDevices(output, "video");
return devices;
string output = await GetExcuteResult("-list_devices true -f dshow -i dummy");
List<(string type, string name)> devices = ExtractDevices(output, "video");
List<VideoDeviceInfo> deviceInfos = new List<VideoDeviceInfo>();
if (devices?.Any() != true)
{
return deviceInfos;
}
foreach (var item in devices)
{
deviceInfos.Add(new VideoDeviceInfo()
{
Name = item.name,
DeviceType = DeviceTypes.DirectShow,
Identity = item.name
});
}
return deviceInfos;
}

public override async Task<List<string>> GetAudioDevices()
public override async Task<List<AudioDeviceInfo>> GetAudioDevices()
{
string output = await GetExcuteResult("-list_devices true -f dshow -i dummy".GetHashCode(), "-list_devices true -f dshow -i dummy");
List<string> devices = ExtractDevices(output, "audio");
return devices;
string output = await GetExcuteResult("-list_devices true -f dshow -i dummy");
List<(string type, string name)> devices = ExtractDevices(output, "audio");
List<AudioDeviceInfo> deviceInfos = new List<AudioDeviceInfo>();
if (devices?.Any() != true)
{
return deviceInfos;
}
foreach (var item in devices)
{
deviceInfos.Add(new AudioDeviceInfo()
{
Name = item.name,
DeviceType = AudioDeviceType.DirectShow,
CardIndex = -1,
DeviceIndex = -1,
});
}
return deviceInfos;
}

public override async Task<List<DeviceResolution>> ListVideoDeviceSupportResolutions(string deviceName)
Expand All @@ -39,13 +70,12 @@ public override async Task<List<DeviceResolution>> ListVideoDeviceSupportResolut
throw new ArgumentNullException("deviceName", "设备名称不能为空");
}
string argStr = $"-f dshow -list_options true -i video=\"{deviceName}\"";
int type = argStr.GetHashCode();
string output = await GetExcuteResult(type, argStr);
string output = await GetExcuteResult(argStr);
List<DeviceResolution> devices = ExtractResolutions(output, deviceName);
return devices;
}

private async Task<string> GetExcuteResult(int type, string args)
private async Task<string> GetExcuteResult(string args)
{
var result = await Cli.Wrap(this.FFMpegPath)
.WithArguments(args)
Expand All @@ -69,14 +99,14 @@ private async Task<string> GetExcuteResult(int type, string args)
/// <param name="ffmpegOutput"></param>
/// <param name="type">audio/video</param>
/// <returns></returns>
private List<string> ExtractDevices(string ffmpegOutput, string type)
private List<(string type, string name)> ExtractDevices(string ffmpegOutput, string type)
{
if (string.IsNullOrEmpty(ffmpegOutput))
{
throw new ArgumentNullException(nameof(ffmpegOutput), "FFMpeg输出内容为空");
}
string[] lines = ffmpegOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries);
List<string> devices = new List<string>();
List<(string, string)> devices = new List<(string, string)>();

foreach (var line in lines)
{
Expand All @@ -91,7 +121,7 @@ private List<string> ExtractDevices(string ffmpegOutput, string type)
if (firstQuote != lastQuote)
{
string deviceName = line.Substring(firstQuote + 1, lastQuote - firstQuote - 1).Trim(' ');
devices.Add("dshow," + deviceName);
devices.Add(("dshow", deviceName));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using BilibiliAutoLiver.Models;
using BilibiliAutoLiver.Models.Dtos;
using BilibiliAutoLiver.Models.FFMpeg;

namespace BilibiliAutoLiver.Services.FFMpeg.Services.CliBinder
Expand All @@ -9,9 +10,9 @@ public interface IFFMpegCliBinder
{
Task<LibVersion> GetVersion();

Task<List<string>> GetVideoDevices();
Task<List<VideoDeviceInfo>> GetVideoDevices();

Task<List<string>> GetAudioDevices();
Task<List<AudioDeviceInfo>> GetAudioDevices();

Task<List<DeviceResolution>> ListVideoDeviceSupportResolutions(string deviceName);
}
Expand Down
22 changes: 11 additions & 11 deletions src/BilibiliAutoLiver/Services/FFMpeg/Services/FFMpegService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,7 @@ public Task<List<VideoDeviceInfo>> GetVideoDevices()
}));
}

private string GetVideoDeviceIdentity(CaptureDeviceDescriptor captureDevice)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return captureDevice.Name;
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return captureDevice.Identity.ToString();
else
throw new PlatformNotSupportedException($"Unsupported system type: {RuntimeInformation.OSDescription}");
}

public async Task<List<string>> GetAudioDevices()
public async Task<List<AudioDeviceInfo>> GetAudioDevices()
{
return await _cache.GetOrCreateAsync("FFMPEG_MACHINE_AUDIO_DEVICES", async (entry) =>
{
Expand Down Expand Up @@ -170,6 +160,16 @@ private IFFMpegCliBinder GetCliBinder()
return binder;
}

private string GetVideoDeviceIdentity(CaptureDeviceDescriptor captureDevice)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return captureDevice.Name;
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return captureDevice.Identity.ToString();
else
throw new PlatformNotSupportedException($"Unsupported system type: {RuntimeInformation.OSDescription}");
}

#region Log

private LinkedList<FFMpegLog> _ffmegLogs = new LinkedList<FFMpegLog>();
Expand Down
2 changes: 1 addition & 1 deletion src/BilibiliAutoLiver/Services/Interface/IFFMpegService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public interface IFFMpegService

Task<List<VideoDeviceInfo>> GetVideoDevices();

Task<List<string>> GetAudioDevices();
Task<List<AudioDeviceInfo>> GetAudioDevices();

IReadOnlyList<Codec> GetVideoCodecs();

Expand Down
Loading

0 comments on commit 9f239b0

Please sign in to comment.