Skip to content

Commit

Permalink
Merge pull request #27 from samunohito/develop
Browse files Browse the repository at this point in the history
v0.2.2
  • Loading branch information
samunohito authored May 16, 2022
2 parents b7648f3 + 039e55f commit cbe0d90
Show file tree
Hide file tree
Showing 21 changed files with 432 additions and 173 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: build-and-test-application

on:
push:

env:
buildConfigrationName: Debug
buildPlatformName: Any CPU
pathOfSolution: ${{ github.workspace }}\SimpleVolumeMixer.sln

jobs:
build-artifact:
runs-on: windows-2019

steps:

- name: setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: |
3.1.x
5.x
6.x
- name: checkout this solution
uses: actions/checkout@v2
with:
fetch-depth: 0

- name: build and test
id: build-and-test
uses: ./.github/actions/build-and-test
with:
solution: ${{ env.pathOfSolution }}
buildConfiguration: ${{ env.buildConfigrationName }}
buildPlatform: ${{ env.buildPlatformName }}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Moq" Version="4.18.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Update="Nerdbank.GitVersioning">
<Version>3.5.104</Version>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions SimpleVolumeMixer/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ protected override void RegisterTypes(IContainerRegistry containerRegistry)
// UseCases
containerRegistry.GetContainer()
.RegisterType<AudioSessionsPageUseCase>(new DisposableComponentLifetimeManager());
containerRegistry.GetContainer()
.RegisterType<AudioDevicesPageUseCase>(new DisposableComponentLifetimeManager());

// Views
containerRegistry.RegisterForNavigation<SettingsPage, SettingsPageViewModel>(PageKeys.Settings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,47 @@ namespace SimpleVolumeMixer.Core.Contracts.Models.Repository;
public interface ICoreAudioRepository
{
/// <summary>
/// 現在使用可能な<see cref="AudioDeviceAccessor"/>の一覧を取得する。
/// 現在再生用として使用可能な<see cref="AudioDeviceAccessor"/>の一覧を取得する。
/// 読み取り専用であり、このオブジェクトからデバイスの増減を行うことは出来ない。
/// デバイスの増減は<see cref="AudioDeviceAccessorManager.CollectAudioEndpoints"/>によりCoreAudioAPIからデバイス一覧を取り直すか、
/// <see cref="AudioDeviceAccessorManager"/>がCoreAudioAPIからの通知を受け、その結果デバイスが追加されるかに限る。
/// </summary>
ReadOnlyObservableCollection<AudioDeviceAccessor> AudioDevices { get; }
ReadOnlyObservableCollection<AudioDeviceAccessor> RenderAudioDevices { get; }

/// <summary>
/// 現在録音用として使用可能な<see cref="AudioDeviceAccessor"/>の一覧を取得する。
/// 読み取り専用であり、このオブジェクトからデバイスの増減を行うことは出来ない。
/// デバイスの増減は<see cref="AudioDeviceAccessorManager.CollectAudioEndpoints"/>によりCoreAudioAPIからデバイス一覧を取り直すか、
/// <see cref="AudioDeviceAccessorManager"/>がCoreAudioAPIからの通知を受け、その結果デバイスが追加されるかに限る。
/// </summary>
ReadOnlyObservableCollection<AudioDeviceAccessor> CaptureAudioDevices { get; }

/// <summary>
/// <see cref="RoleType.Communications"/>ロールのデバイスを取得する。
/// インスタンスは<see cref="AudioDevices"/>から<see cref="RoleType.Communications"/>ロールを持つ物を検索して取得できる値と同一である。
/// <see cref="RoleType.Communications"/>ロールの再生用デバイスを取得する。
/// インスタンスは<see cref="RenderAudioDevices"/>から<see cref="RoleType.Communications"/>ロールを持つ物を検索して取得できる値と同一である。
/// </summary>
IReadOnlyReactiveProperty<AudioDeviceAccessor?> CommunicationRoleRenderDevice { get; }

/// <summary>
/// <see cref="RoleType.Multimedia"/>ロールの再生用デバイスを取得する。
/// インスタンスは<see cref="RenderAudioDevices"/>から<see cref="RoleType.Multimedia"/>ロールを持つ物を検索して取得できる値と同一である。
/// </summary>
IReadOnlyReactiveProperty<AudioDeviceAccessor?> MultimediaRoleRenderDevice { get; }

/// <summary>
/// <see cref="RoleType.Communications"/>ロールの録音用デバイスを取得する。
/// インスタンスは<see cref="CaptureAudioDevices"/>から<see cref="RoleType.Communications"/>ロールを持つ物を検索して取得できる値と同一である。
/// </summary>
IReadOnlyReactiveProperty<AudioDeviceAccessor?> CommunicationRoleDevice { get; }
IReadOnlyReactiveProperty<AudioDeviceAccessor?> CommunicationRoleCaptureDevice { get; }

/// <summary>
/// <see cref="RoleType.Multimedia"/>ロールのデバイスを取得する
/// インスタンスは<see cref="AudioDevices"/>から<see cref="RoleType.Multimedia"/>ロールを持つ物を検索して取得できる値と同一である。
/// <see cref="RoleType.Multimedia"/>ロールの録音用デバイスを取得する
/// インスタンスは<see cref="CaptureAudioDevices"/>から<see cref="RoleType.Multimedia"/>ロールを持つ物を検索して取得できる値と同一である。
/// </summary>
IReadOnlyReactiveProperty<AudioDeviceAccessor?> MultimediaRoleDevice { get; }
IReadOnlyReactiveProperty<AudioDeviceAccessor?> MultimediaRoleCaptureDevice { get; }

/// <summary>
/// 引数のデバイスと<see cref="DataFlowType"/>のデバイスに対し、<see cref="RoleType"/>のロールを割り当てる。
/// 引数のデバイスに対し、<see cref="RoleType"/>のロールを割り当てる。
/// </summary>
/// <param name="accessor"></param>
/// <param name="dataFlowType"></param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ namespace SimpleVolumeMixer.Core.Contracts.Services;

public interface ICoreAudioService
{
ReadOnlyReactiveCollection<AudioDevice> Devices { get; }
IReadOnlyReactiveProperty<AudioDevice?> CommunicationRoleDevice { get; }
IReadOnlyReactiveProperty<AudioDevice?> MultimediaRoleDevice { get; }
ReadOnlyReactiveCollection<AudioDevice> RenderDevices { get; }
ReadOnlyReactiveCollection<AudioDevice> CaptureDevices { get; }
IReadOnlyReactiveProperty<AudioDevice?> CommunicationRoleRenderDevice { get; }
IReadOnlyReactiveProperty<AudioDevice?> MultimediaRoleRenderDevice { get; }
IReadOnlyReactiveProperty<AudioDevice?> CommunicationRoleCaptureDevice { get; }
IReadOnlyReactiveProperty<AudioDevice?> MultimediaRoleCaptureDevice { get; }
void SetDefaultDevice(AudioDevice device, DataFlowType dataFlowType, RoleType roleType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,16 @@ public class AudioDeviceAccessorManager : SynchronizedObservableCollectionWrappe
private readonly MMDeviceEnumerator _deviceEnumerator;
private readonly MMNotificationClient _notificationClient;
private readonly NotificationClientEventAdapter _clientEventAdapter;
private readonly DataFlowType _targetDataFlow;

/// <summary>
/// ctor
/// </summary>
/// <param name="dataFlowType"></param>
/// <param name="logger"></param>
public AudioDeviceAccessorManager(ILogger logger)
public AudioDeviceAccessorManager(DataFlowType dataFlowType, ILogger logger)
{
_targetDataFlow = dataFlowType;
_logger = logger;
_deviceEnumerator = new MMDeviceEnumerator().AddTo(Disposable);
_notificationClient = new MMNotificationClient(_deviceEnumerator).AddTo(Disposable);
Expand Down Expand Up @@ -96,9 +99,8 @@ public bool Contains(MMDevice device)
/// 引数のデバイスIDとデータフローを持つデバイスを取得する
/// </summary>
/// <param name="deviceId"></param>
/// <param name="dataFlowType"></param>
/// <returns></returns>
public AudioDeviceAccessor? GetDevice(string? deviceId, DataFlowType dataFlowType)
public AudioDeviceAccessor? GetDevice(string? deviceId)
{
if (deviceId == null)
{
Expand All @@ -107,28 +109,27 @@ public bool Contains(MMDevice device)

lock (Gate)
{
return Collection.FirstOrDefault(x => x.DeviceId == deviceId && x.DataFlow == dataFlowType);
return Collection.FirstOrDefault(x => x.DeviceId == deviceId && x.DataFlow == _targetDataFlow);
}
}

/// <summary>
/// 引数の<see cref="DataFlowType"/>と<see cref="RoleType"/>が割り当てられているデバイスを取得する
/// <see cref="RoleType"/>が割り当てられているデバイスを取得する
/// </summary>
/// <param name="dataFlowType"></param>
/// <param name="roleType"></param>
/// <returns></returns>
/// <exception cref="ApplicationException"></exception>
public AudioDeviceAccessor? GetDefaultDevice(DataFlowType dataFlowType, RoleType roleType)
public AudioDeviceAccessor? GetDefaultDevice(RoleType roleType)
{
lock (Gate)
{
if (Count <= 0 || dataFlowType == DataFlowType.Unknown || roleType == RoleType.Unknown)
if (Count <= 0 || roleType == RoleType.Unknown)
{
return null;
}

using var defaultDevice = _deviceEnumerator.GetDefaultAudioEndpoint(
AccessorHelper.DataFlowsRev[dataFlowType],
AccessorHelper.DataFlowsRev[_targetDataFlow],
AccessorHelper.RolesRev[roleType]);
if (defaultDevice == null)
{
Expand All @@ -140,7 +141,7 @@ public bool Contains(MMDevice device)
throw new ApplicationException("unknown device : " + defaultDevice);
}

return GetDevice(defaultDevice.DeviceID, dataFlowType);
return GetDevice(defaultDevice.DeviceID);
}
}

Expand Down Expand Up @@ -194,7 +195,7 @@ public void Remove(string deviceId)
/// <param name="device"></param>
public void Remove(MMDevice device)
{
var target = GetDevice(device.DeviceID, AccessorHelper.DataFlows[device.DataFlow]);
var target = GetDevice(device.DeviceID);
if (target == null)
{
return;
Expand Down Expand Up @@ -226,7 +227,8 @@ public void CollectAudioEndpoints()
{
lock (Gate)
{
using var devices = _deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active);
var dataFlow = AccessorHelper.DataFlowsRev[_targetDataFlow];
using var devices = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active);
if (devices == null)
{
throw new InvalidOperationException("通常はありえない");
Expand All @@ -237,22 +239,13 @@ public void CollectAudioEndpoints()
Add(device);
}

if (Collection.Any(x => x.DataFlow == DataFlowType.Render))
{
// deviceが1つもない状態でGetDefaultAudioEndpointを呼ぶとエラー落ちする
using var mulDevice = _deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
using var comDevice = _deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Communications);
DeviceRoleSync(mulDevice.DeviceID, DataFlowType.Render, RoleType.Multimedia);
DeviceRoleSync(comDevice.DeviceID, DataFlowType.Render, RoleType.Communications);
}

if (Collection.Any(x => x.DataFlow == DataFlowType.Capture))
if (Collection.Any(x => x.DataFlow == _targetDataFlow))
{
// deviceが1つもない状態でGetDefaultAudioEndpointを呼ぶとエラー落ちする
using var mulDevice = _deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia);
using var comDevice = _deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Communications);
DeviceRoleSync(mulDevice.DeviceID, DataFlowType.Capture, RoleType.Multimedia);
DeviceRoleSync(comDevice.DeviceID, DataFlowType.Capture, RoleType.Communications);
using var mulDevice = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia);
using var comDevice = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Communications);
DeviceRoleSync(mulDevice.DeviceID, _targetDataFlow, RoleType.Multimedia);
DeviceRoleSync(comDevice.DeviceID, _targetDataFlow, RoleType.Communications);
}
}
}
Expand Down Expand Up @@ -288,7 +281,7 @@ private void DeviceRoleSync(string deviceId, DataFlowType dataFlowType, RoleType

// 今回あたらしくCommunications/Multimediaに設定されたものにフラグを立てる.
// 1つのデバイスが複数のRoleを持ったとしても、持っているRoleの数だけ通知してくるため、都度確認して対応するRoleのフラグを管理する必要がある
var target = GetDevice(deviceId, dataFlowType);
var target = GetDevice(deviceId);
if (target == null)
{
return;
Expand All @@ -313,7 +306,8 @@ private void DeviceRoleSync(string deviceId, DataFlowType dataFlowType, RoleType
/// <param name="e"></param>
private void OnDeviceAdded(object? sender, DeviceNotificationEventArgs e)
{
if (e.TryGetDevice(out var newDevice) && !Contains(newDevice))
var targetDataFlow = AccessorHelper.DataFlowsRev[_targetDataFlow];
if (e.TryGetDevice(out var newDevice) && newDevice.DataFlow == targetDataFlow)
{
Add(newDevice);
}
Expand All @@ -326,7 +320,11 @@ private void OnDeviceAdded(object? sender, DeviceNotificationEventArgs e)
/// <param name="e"></param>
private void OnDeviceRemoved(object? sender, DeviceNotificationEventArgs e)
{
Remove(e.DeviceId);
var targetDataFlow = AccessorHelper.DataFlowsRev[_targetDataFlow];
if (e.TryGetDevice(out var removedDevice) && removedDevice.DataFlow == targetDataFlow)
{
Remove(e.DeviceId);
}
}

/// <summary>
Expand All @@ -348,20 +346,20 @@ private void OnDefaultDeviceChanged(object? sender, DefaultDeviceChangedEventArg
/// <param name="e"></param>
private void OnDeviceStateChanged(object? sender, DeviceStateChangedEventArgs e)
{
switch (e.DeviceState)
var targetDataFlow = AccessorHelper.DataFlowsRev[_targetDataFlow];
if (e.TryGetDevice(out var device) && device.DataFlow == targetDataFlow)
{
case DeviceState.Active:
if (e.TryGetDevice(out var device))
{
switch (e.DeviceState)
{
case DeviceState.Active:
Add(device);
}

break;
case DeviceState.Disabled:
case DeviceState.NotPresent:
case DeviceState.UnPlugged:
Remove(e.DeviceId);
break;
break;
case DeviceState.Disabled:
case DeviceState.NotPresent:
case DeviceState.UnPlugged:
Remove(e.DeviceId);
break;
}
}
}

Expand Down
Loading

0 comments on commit cbe0d90

Please sign in to comment.