├── Duplicator
├── WinDuplicator
│ ├── trace.bat
│ ├── Duplicator.ico
│ ├── trace64.bat
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── config.xml
│ ├── Program.cs
│ ├── ChooseAudioDevice.cs
│ ├── About.cs
│ ├── ChooseAdapter.cs
│ ├── ChooseOutput.cs
│ ├── Main.cs
│ ├── WinDuplicator.csproj
│ ├── About.resx
│ ├── Main.resx
│ ├── ChooseAdapter.resx
│ ├── ChooseOutput.resx
│ ├── ChooseAudioDevice.resx
│ ├── About.Designer.cs
│ ├── ChooseAudioDevice.Designer.cs
│ ├── ChooseOutput.Designer.cs
│ ├── ChooseAdapter.Designer.cs
│ ├── Main.Designer.cs
│ └── WinDuplicatorOptions.cs
├── Doc
│ └── WinDuplicator.png
├── Duplicator
│ ├── DuplicatorState.cs
│ ├── DuplicatorInformationEventArgs.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── Duplicator.csproj
│ ├── H264Encoder.cs
│ ├── DuplicatorOptions.cs
│ ├── AudioCapture.cs
│ └── DictionaryObject.cs
└── Duplicator.sln
├── LICENSE
├── README.md
└── .gitignore
/Duplicator/WinDuplicator/trace.bat:
--------------------------------------------------------------------------------
1 | c:mftrace -c config.xml winduplicator.exe
--------------------------------------------------------------------------------
/Duplicator/Doc/WinDuplicator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shehryar/Duplicator/HEAD/Duplicator/Doc/WinDuplicator.png
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/Duplicator.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shehryar/Duplicator/HEAD/Duplicator/WinDuplicator/Duplicator.ico
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/trace64.bat:
--------------------------------------------------------------------------------
1 | "C:\Program Files (x86)\Windows Kits\10\bin\10.0.16299.0\x64\mftrace.exe" -c config.xml winduplicator.exe
--------------------------------------------------------------------------------
/Duplicator/Duplicator/DuplicatorState.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Duplicator
3 | {
4 | public enum DuplicatorState
5 | {
6 | Stopped,
7 | Starting,
8 | Started,
9 | Stopping,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Duplicator/Duplicator/DuplicatorInformationEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Duplicator
4 | {
5 | public class DuplicatorInformationEventArgs : EventArgs
6 | {
7 | internal DuplicatorInformationEventArgs(string information)
8 | {
9 | Information = information;
10 | }
11 |
12 | public string Information { get; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Duplicator/Duplicator/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("Duplicator")]
5 | [assembly: AssemblyProduct("Duplicator")]
6 | [assembly: AssemblyCopyright("Copyright © 2018 Simon Mourier. All rights reserved.")]
7 | [assembly: AssemblyCulture("")]
8 | [assembly: ComVisible(false)]
9 | [assembly: Guid("ad8cfc36-9dff-4429-8e08-698cedd35f1e")]
10 | [assembly: AssemblyVersion("1.0.0.0")]
11 | [assembly: AssemblyFileVersion("1.3.0.0")]
12 | [assembly: AssemblyInformationalVersion("1.3.0.0")]
13 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("WinDuplicator")]
5 | [assembly: AssemblyProduct("WinDuplicator")]
6 | [assembly: AssemblyCopyright("Copyright © 2018 Simon Mourier. All rights reserved.")]
7 | [assembly: AssemblyCulture("")]
8 | [assembly: ComVisible(false)]
9 | [assembly: Guid("2e9fc0e9-8589-4245-a222-f13cfa0ef29a")]
10 | [assembly: AssemblyVersion("1.0.0.0")]
11 | [assembly: AssemblyFileVersion("1.3.0.0")]
12 | [assembly: AssemblyInformationalVersion("1.3.0.0")]
13 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Simon Mourier
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Duplicator
2 | Screen Capture and Recording (Audio and Video) C# samples. There is only a Winform sample right now, but it should work with WPF or any Windows presentation technology because it only uses an Hwnd handle as a target.
3 |
4 | ## Key points:
5 |
6 | * Uses SharpDX exclusively for DirectX, DXGI, Direct2D and Media Foundation interop.
7 | * Uses Windows Desktop Duplication API for desktop duplication. It uses almost zero CPU and zero RAM.
8 | * Uses an integrated custom optimized interop layer (over Windows Core Audio) for sound capture (loopback and microphone).
9 | * Uses H264 + AAC for recording format.
10 | * Uses few resources for H264 encoding when a hardware encoder is available (Intel (C) Media SDK, etc.).
11 | * Never uses GDI nor GDI+ or legacy techology.
12 |
13 | ## Remarks
14 |
15 | * It requires a recent Windows version (10+). Untested on other Windows versions.
16 | * The Frame Rate choice (which is optional, you can choose to use the `` mode) may influence the encoder choice when more than one is available (this choice is automatic). Some hardware encoders support only some frame rates. The choosen encoder will be displayed by the app after recording has started.
17 |
18 | 
19 |
--------------------------------------------------------------------------------
/Duplicator/Duplicator.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2027
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinDuplicator", "WinDuplicator\WinDuplicator.csproj", "{2E9FC0E9-8589-4245-A222-F13CFA0EF29A}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicator", "Duplicator\Duplicator.csproj", "{AD8CFC36-9DFF-4429-8E08-698CEDD35F1E}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {2E9FC0E9-8589-4245-A222-F13CFA0EF29A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {2E9FC0E9-8589-4245-A222-F13CFA0EF29A}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {2E9FC0E9-8589-4245-A222-F13CFA0EF29A}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {2E9FC0E9-8589-4245-A222-F13CFA0EF29A}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {AD8CFC36-9DFF-4429-8E08-698CEDD35F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {AD8CFC36-9DFF-4429-8E08-698CEDD35F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {AD8CFC36-9DFF-4429-8E08-698CEDD35F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {AD8CFC36-9DFF-4429-8E08-698CEDD35F1E}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {4A11A695-103B-46D2-8D32-7E67283FD688}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Runtime.InteropServices;
4 | using System.Windows.Forms;
5 |
6 | namespace WinDuplicator
7 | {
8 | static class Program
9 | {
10 | [STAThread]
11 | static void Main()
12 | {
13 | SetErrorMode(ErrorModes.SEM_NONE);
14 | if (!Debugger.IsAttached)
15 | {
16 | AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
17 | Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
18 | }
19 |
20 | Application.EnableVisualStyles();
21 | Application.SetCompatibleTextRenderingDefault(false);
22 | Application.Run(new Main());
23 | }
24 |
25 | private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
26 | {
27 | var ex = e.ExceptionObject as Exception;
28 | if (ex == null)
29 | return;
30 |
31 | var dlg = new ThreadExceptionDialog(ex);
32 | dlg.ShowDialog();
33 | }
34 |
35 | private enum ErrorModes : int
36 | {
37 | SYSTEM_DEFAULT = 0x0,
38 | SEM_FAILCRITICALERRORS = 0x0001,
39 | SEM_NOALIGNMENTFAULTEXCEPT = 0x0004,
40 | SEM_NOGPFAULTERRORBOX = 0x0002,
41 | SEM_NOOPENFILEERRORBOX = 0x8000,
42 | SEM_NONE = SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX
43 | }
44 |
45 | [DllImport("kernel32.dll")]
46 | private static extern ErrorModes SetErrorMode(ErrorModes uMode);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/ChooseAudioDevice.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Windows.Forms;
3 | using Duplicator;
4 |
5 | namespace WinDuplicator
6 | {
7 | public partial class ChooseAudioDevice : Form
8 | {
9 | public ChooseAudioDevice(string selectedAudioDevice, AudioCapture.DataFlow flow)
10 | {
11 | InitializeComponent();
12 | Icon = Main.EmbeddedIcon;
13 |
14 | listViewMain.Select();
15 | foreach (var device in AudioCapture.GetDevices(flow).Where(d => d.State == AudioCapture.AudioDeviceState.Active))
16 | {
17 | var item = listViewMain.Items.Add(device.FriendlyName);
18 | item.Tag = device;
19 | if (selectedAudioDevice != null && device.FriendlyName == selectedAudioDevice)
20 | {
21 | Device = device;
22 | item.Selected = true;
23 | }
24 | }
25 |
26 | listViewMain.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
27 | UpdateControls();
28 | }
29 |
30 | public AudioCapture.AudioDevice Device { get; private set; }
31 |
32 | private void ChooseAdapter_FormClosing(object sender, FormClosingEventArgs e)
33 | {
34 | if ((e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.None) && DialogResult == DialogResult.OK)
35 | {
36 | if (listViewMain.SelectedItems.Count > 0)
37 | {
38 | Device = (AudioCapture.AudioDevice)listViewMain.SelectedItems[0].Tag;
39 | }
40 | }
41 | }
42 |
43 | private void UpdateControls()
44 | {
45 | buttonOk.Enabled = listViewMain.SelectedItems.Count > 0;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/About.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Text;
4 | using System.Windows.Forms;
5 | using Duplicator;
6 |
7 | namespace WinDuplicator
8 | {
9 | public partial class About : Form
10 | {
11 | public About()
12 | {
13 | InitializeComponent();
14 | Icon = Main.EmbeddedIcon;
15 | string version = Assembly.GetExecutingAssembly().GetCustomAttribute().Version;
16 | string cr = Assembly.GetExecutingAssembly().GetCustomAttribute().Copyright;
17 | labelCopyright.Text = "Duplicator V" + version + Environment.NewLine + cr;
18 |
19 | var sb = new StringBuilder();
20 | sb.AppendLine("* System:");
21 | sb.AppendLine();
22 | sb.AppendLine(" OS: " + Environment.OSVersion);
23 | sb.AppendLine(" Processors: " + Environment.ProcessorCount);
24 | sb.AppendLine(" Bitness: " + (Environment.Is64BitProcess ? "64" : "32") + "-bit");
25 | sb.AppendLine();
26 | sb.AppendLine("* Detected H264 encoders:");
27 | sb.AppendLine();
28 | foreach (var dec in H264Encoder.Enumerate())
29 | {
30 | sb.AppendLine(" " + dec.FriendlyName);
31 | sb.AppendLine(" Clsid : " + dec.Clsid);
32 | sb.AppendLine(" DllPath : " + dec.DllPath);
33 | sb.AppendLine(" Flags : " + dec.Flags);
34 | sb.AppendLine(" D3D11 Aware: " + dec.IsDirect3D11Aware);
35 | sb.AppendLine(" Supported Input types:");
36 | foreach (var type in dec.InputTypes)
37 | {
38 | sb.AppendLine(" " + type);
39 | }
40 | sb.AppendLine();
41 | }
42 |
43 | textBoxInfo.Text = sb.ToString();
44 | }
45 |
46 | private void buttonOk_Click(object sender, EventArgs e)
47 | {
48 | Close();
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/ChooseAdapter.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Forms;
2 | using SharpDX.DXGI;
3 |
4 | namespace WinDuplicator
5 | {
6 | public partial class ChooseAdapter : Form
7 | {
8 | public ChooseAdapter(string selectedAdapter)
9 | {
10 | InitializeComponent();
11 | Icon = Main.EmbeddedIcon;
12 |
13 | using (var fac = new Factory1())
14 | {
15 | listViewMain.Select();
16 | foreach (var adapter in fac.Adapters1)
17 | {
18 | var item = listViewMain.Items.Add(adapter.Description1.Description);
19 | item.Tag = adapter;
20 | item.SubItems.Add(adapter.Description1.Flags.ToString());
21 | item.SubItems.Add(adapter.Description1.Revision.ToString());
22 | item.SubItems.Add(adapter.Description1.DedicatedVideoMemory.ToString());
23 | if (selectedAdapter != null && adapter.Description.Description == selectedAdapter)
24 | {
25 | Adapter = adapter;
26 | item.Selected = true;
27 | }
28 | }
29 | }
30 |
31 | listViewMain.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
32 | }
33 |
34 | public Adapter1 Adapter { get; private set; }
35 |
36 | private void ChooseAdapter_FormClosing(object sender, FormClosingEventArgs e)
37 | {
38 | if ((e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.None) && DialogResult == DialogResult.OK)
39 | {
40 | if (listViewMain.SelectedItems.Count > 0)
41 | {
42 | Adapter = (Adapter1)listViewMain.SelectedItems[0].Tag;
43 | }
44 | }
45 | }
46 |
47 | private void UpdateControls()
48 | {
49 | buttonOk.Enabled = listViewMain.SelectedItems.Count > 0;
50 | }
51 |
52 | private void listViewMain_SelectedIndexChanged(object sender, System.EventArgs e)
53 | {
54 | UpdateControls();
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/ChooseOutput.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Forms;
2 | using Duplicator;
3 | using SharpDX.DXGI;
4 |
5 | namespace WinDuplicator
6 | {
7 | public partial class ChooseOutput : Form
8 | {
9 | public ChooseOutput(Adapter1 adapter, string selectedDeviceName)
10 | {
11 | InitializeComponent();
12 | Icon = Main.EmbeddedIcon;
13 |
14 | listViewMain.Select();
15 | foreach (var output in adapter.Outputs)
16 | {
17 | string name = DuplicatorOptions.GetDisplayDeviceName(output.Description.DeviceName);
18 | var item = listViewMain.Items.Add(name);
19 | item.Tag = output.Description.DeviceName;
20 | int width = output.Description.DesktopBounds.Right - output.Description.DesktopBounds.Left;
21 | int height = output.Description.DesktopBounds.Bottom - output.Description.DesktopBounds.Top;
22 | item.SubItems.Add(width + " x " + height);
23 | item.SubItems.Add(output.Description.DesktopBounds.Left + ", " + output.Description.DesktopBounds.Top);
24 | item.SubItems.Add(output.Description.Rotation.ToString());
25 | if (selectedDeviceName != null && output.Description.DeviceName == selectedDeviceName)
26 | {
27 | DeviceName = name;
28 | item.Selected = true;
29 | }
30 | }
31 |
32 | listViewMain.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
33 | UpdateControls();
34 | Text = "Choose Monitor for " + adapter.Description.Description;
35 | }
36 |
37 | public string DeviceName { get; private set; }
38 |
39 | private void ChooseOutput_FormClosing(object sender, FormClosingEventArgs e)
40 | {
41 | if ((e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.None) && DialogResult == DialogResult.OK)
42 | {
43 | if (listViewMain.SelectedItems.Count > 0)
44 | {
45 | DeviceName = (string)listViewMain.SelectedItems[0].Tag;
46 | }
47 | }
48 | }
49 |
50 | private void UpdateControls()
51 | {
52 | buttonOk.Enabled = listViewMain.SelectedItems.Count > 0;
53 | }
54 |
55 | private void listViewMain_SelectedIndexChanged(object sender, System.EventArgs e)
56 | {
57 | UpdateControls();
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Duplicator/Duplicator/Duplicator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {AD8CFC36-9DFF-4429-8E08-698CEDD35F1E}
8 | Library
9 | Properties
10 | Duplicator
11 | Duplicator
12 | v4.6.1
13 | 512
14 |
15 |
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 | true
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | 4.0.1
53 |
54 |
55 | 4.0.1
56 |
57 |
58 | 4.0.1
59 |
60 |
61 | 4.0.1
62 |
63 |
64 | 4.0.1
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/Main.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Drawing;
4 | using System.IO;
5 | using System.Reflection;
6 | using System.Windows.Forms;
7 |
8 | namespace WinDuplicator
9 | {
10 | public partial class Main : Form
11 | {
12 | private static Lazy _embeddedIcon = new Lazy(() => { using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(Main), "Duplicator.ico")) return new Icon(stream); });
13 | public static Icon EmbeddedIcon => _embeddedIcon.Value;
14 |
15 | private WinDuplicatorOptions _options;
16 | private Duplicator.Duplicator _duplicator;
17 |
18 | public Main()
19 | {
20 | InitializeComponent();
21 | MinimumSize = Size;
22 | Icon = EmbeddedIcon;
23 | _options = new WinDuplicatorOptions();
24 | propertyGridMain.SelectedObject = _options;
25 |
26 | _duplicator = new Duplicator.Duplicator(_options);
27 | _duplicator.PropertyChanged += OnDuplicatorPropertyChanged;
28 | _duplicator.InformationAvailable += OnDuplicatorInformationAvailable;
29 | _duplicator.Size = new SharpDX.Size2(splitContainerMain.Panel1.Width, splitContainerMain.Panel1.Height);
30 |
31 | splitContainerMain.Panel1.SizeChanged += (sender, e) =>
32 | {
33 | _duplicator.Size = new SharpDX.Size2(splitContainerMain.Panel1.Width, splitContainerMain.Panel1.Height);
34 | };
35 |
36 | splitContainerMain.Panel1.HandleCreated += (sender, e) =>
37 | {
38 | _duplicator.Hwnd = splitContainerMain.Panel1.Handle;
39 | };
40 |
41 | splitContainerMain.Panel1.HandleDestroyed += (sender, e) =>
42 | {
43 | _duplicator.Hwnd = IntPtr.Zero;
44 | };
45 |
46 | #if DEBUG
47 | Text += " - DEBUG";
48 | #endif
49 | }
50 |
51 | private void OnDuplicatorInformationAvailable(object sender, Duplicator.DuplicatorInformationEventArgs e)
52 | {
53 | BeginInvoke((Action)(() =>
54 | {
55 | if (!string.IsNullOrEmpty(textBoxStatus.Text))
56 | {
57 | textBoxStatus.AppendText(Environment.NewLine);
58 | }
59 | textBoxStatus.AppendText(e.Information);
60 | }));
61 | }
62 |
63 | private void OnDuplicatorPropertyChanged(object sender, PropertyChangedEventArgs e)
64 | {
65 | // update chekboxes accordingly with duplicator state
66 | // note: these events arrive on another thread
67 | switch (e.PropertyName)
68 | {
69 | case nameof(_duplicator.DuplicatingState):
70 | BeginInvoke((Action)(() =>
71 | {
72 | bool dup = _duplicator.DuplicatingState == Duplicator.DuplicatorState.Started || _duplicator.DuplicatingState == Duplicator.DuplicatorState.Starting;
73 | checkBoxDuplicate.Checked = dup;
74 | checkBoxRecord.Enabled = dup;
75 | }));
76 | break;
77 |
78 | case nameof(_duplicator.RecordingState):
79 | BeginInvoke((Action)(() =>
80 | {
81 | checkBoxRecord.Checked = _duplicator.RecordingState == Duplicator.DuplicatorState.Started || _duplicator.RecordingState == Duplicator.DuplicatorState.Starting;
82 | }));
83 | break;
84 |
85 | case nameof(_duplicator.RecordFilePath):
86 | string mode = _duplicator.IsUsingHardwareBasedEncoder ? "Hardware" : "Software";
87 | BeginInvoke((Action)(() =>
88 | {
89 | if (_duplicator.RecordingState == Duplicator.DuplicatorState.Started && !string.IsNullOrEmpty(_duplicator.RecordFilePath))
90 | {
91 | Text = "Duplicator - " + mode + " Recording " + Path.GetFileName(_duplicator.RecordFilePath);
92 | }
93 | else
94 | {
95 | Text = "Duplicator";
96 | }
97 | }));
98 | break;
99 | }
100 | }
101 |
102 | protected override void Dispose(bool disposing)
103 | {
104 | if (disposing)
105 | {
106 | _duplicator?.Dispose();
107 | }
108 | base.Dispose(disposing);
109 | }
110 |
111 | private void buttonQuit_Click(object sender, EventArgs e)
112 | {
113 | Close();
114 | }
115 |
116 | private void checkBoxDuplicate_CheckedChanged(object sender, EventArgs e)
117 | {
118 | if (checkBoxDuplicate.Checked)
119 | {
120 | _duplicator.StartDuplicating();
121 | }
122 | else
123 | {
124 | _duplicator.StopDuplicating();
125 | }
126 | }
127 |
128 | private void buttonAbout_Click(object sender, EventArgs e)
129 | {
130 | var about = new About();
131 | about.ShowDialog(this);
132 | }
133 |
134 | private void checkBoxRecord_CheckedChanged(object sender, EventArgs e)
135 | {
136 | if (checkBoxRecord.Checked)
137 | {
138 | _duplicator.StartRecording();
139 | }
140 | else
141 | {
142 | _duplicator.StopRecording();
143 | }
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/WinDuplicator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {2E9FC0E9-8589-4245-A222-F13CFA0EF29A}
8 | WinExe
9 | WinDuplicator
10 | WinDuplicator
11 | v4.6.1
12 | 512
13 | true
14 |
15 |
16 | AnyCPU
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 | false
25 | true
26 |
27 |
28 | AnyCPU
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 | false
36 |
37 |
38 | Duplicator.ico
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Form
52 |
53 |
54 | About.cs
55 |
56 |
57 | Form
58 |
59 |
60 | ChooseAudioDevice.cs
61 |
62 |
63 | Form
64 |
65 |
66 | ChooseOutput.cs
67 |
68 |
69 | Form
70 |
71 |
72 | ChooseAdapter.cs
73 |
74 |
75 | Form
76 |
77 |
78 | Main.cs
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | {ad8cfc36-9dff-4429-8e08-698cedd35f1e}
87 | Duplicator
88 |
89 |
90 |
91 |
92 | About.cs
93 |
94 |
95 | ChooseAudioDevice.cs
96 |
97 |
98 | ChooseOutput.cs
99 |
100 |
101 | ChooseAdapter.cs
102 |
103 |
104 | Main.cs
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | 4.0.1
113 |
114 |
115 | 4.0.1
116 |
117 |
118 | 4.0.1
119 |
120 |
121 | 4.0.1
122 |
123 |
124 | 4.0.1
125 |
126 |
127 |
128 |
129 | PreserveNewest
130 |
131 |
132 |
133 |
134 | PreserveNewest
135 |
136 |
137 | PreserveNewest
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/About.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/Main.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 |
187 | # Visual Studio cache files
188 | # files ending in .cache can be ignored
189 | *.[Cc]ache
190 | # but keep track of directories ending in .cache
191 | !*.[Cc]ache/
192 |
193 | # Others
194 | ClientBin/
195 | ~$*
196 | *~
197 | *.dbmdl
198 | *.dbproj.schemaview
199 | *.jfm
200 | *.pfx
201 | *.publishsettings
202 | orleans.codegen.cs
203 |
204 | # Since there are multiple workflows, uncomment next line to ignore bower_components
205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
206 | #bower_components/
207 |
208 | # RIA/Silverlight projects
209 | Generated_Code/
210 |
211 | # Backup & report files from converting an old project file
212 | # to a newer Visual Studio version. Backup files are not needed,
213 | # because we have git ;-)
214 | _UpgradeReport_Files/
215 | Backup*/
216 | UpgradeLog*.XML
217 | UpgradeLog*.htm
218 |
219 | # SQL Server files
220 | *.mdf
221 | *.ldf
222 | *.ndf
223 |
224 | # Business Intelligence projects
225 | *.rdl.data
226 | *.bim.layout
227 | *.bim_*.settings
228 |
229 | # Microsoft Fakes
230 | FakesAssemblies/
231 |
232 | # GhostDoc plugin setting file
233 | *.GhostDoc.xml
234 |
235 | # Node.js Tools for Visual Studio
236 | .ntvs_analysis.dat
237 | node_modules/
238 |
239 | # Typescript v1 declaration files
240 | typings/
241 |
242 | # Visual Studio 6 build log
243 | *.plg
244 |
245 | # Visual Studio 6 workspace options file
246 | *.opt
247 |
248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
249 | *.vbw
250 |
251 | # Visual Studio LightSwitch build output
252 | **/*.HTMLClient/GeneratedArtifacts
253 | **/*.DesktopClient/GeneratedArtifacts
254 | **/*.DesktopClient/ModelManifest.xml
255 | **/*.Server/GeneratedArtifacts
256 | **/*.Server/ModelManifest.xml
257 | _Pvt_Extensions
258 |
259 | # Paket dependency manager
260 | .paket/paket.exe
261 | paket-files/
262 |
263 | # FAKE - F# Make
264 | .fake/
265 |
266 | # JetBrains Rider
267 | .idea/
268 | *.sln.iml
269 |
270 | # CodeRush
271 | .cr/
272 |
273 | # Python Tools for Visual Studio (PTVS)
274 | __pycache__/
275 | *.pyc
276 |
277 | # Cake - Uncomment if you are using it
278 | # tools/**
279 | # !tools/packages.config
280 |
281 | # Telerik's JustMock configuration file
282 | *.jmconfig
283 |
284 | # BizTalk build output
285 | *.btp.cs
286 | *.btm.cs
287 | *.odx.cs
288 | *.xsd.cs
289 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/ChooseAdapter.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/ChooseOutput.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/ChooseAudioDevice.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/About.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace WinDuplicator
2 | {
3 | partial class About
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.tableLayoutPanelMain = new System.Windows.Forms.TableLayoutPanel();
32 | this.buttonOk = new System.Windows.Forms.Button();
33 | this.labelCopyright = new System.Windows.Forms.Label();
34 | this.textBoxInfo = new System.Windows.Forms.TextBox();
35 | this.tableLayoutPanelMain.SuspendLayout();
36 | this.SuspendLayout();
37 | //
38 | // tableLayoutPanelMain
39 | //
40 | this.tableLayoutPanelMain.ColumnCount = 1;
41 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
42 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
43 | this.tableLayoutPanelMain.Controls.Add(this.buttonOk, 0, 2);
44 | this.tableLayoutPanelMain.Controls.Add(this.labelCopyright, 0, 0);
45 | this.tableLayoutPanelMain.Controls.Add(this.textBoxInfo, 0, 1);
46 | this.tableLayoutPanelMain.Dock = System.Windows.Forms.DockStyle.Fill;
47 | this.tableLayoutPanelMain.Location = new System.Drawing.Point(0, 0);
48 | this.tableLayoutPanelMain.Name = "tableLayoutPanelMain";
49 | this.tableLayoutPanelMain.RowCount = 3;
50 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 50F));
51 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
52 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
53 | this.tableLayoutPanelMain.Size = new System.Drawing.Size(607, 308);
54 | this.tableLayoutPanelMain.TabIndex = 0;
55 | //
56 | // buttonOk
57 | //
58 | this.buttonOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
59 | this.buttonOk.DialogResult = System.Windows.Forms.DialogResult.OK;
60 | this.buttonOk.Location = new System.Drawing.Point(529, 281);
61 | this.buttonOk.Name = "buttonOk";
62 | this.buttonOk.Size = new System.Drawing.Size(75, 23);
63 | this.buttonOk.TabIndex = 0;
64 | this.buttonOk.Text = "Close";
65 | this.buttonOk.UseVisualStyleBackColor = true;
66 | this.buttonOk.Click += new System.EventHandler(this.buttonOk_Click);
67 | //
68 | // labelCopyright
69 | //
70 | this.labelCopyright.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
71 | | System.Windows.Forms.AnchorStyles.Left)
72 | | System.Windows.Forms.AnchorStyles.Right)));
73 | this.labelCopyright.AutoSize = true;
74 | this.labelCopyright.Location = new System.Drawing.Point(3, 0);
75 | this.labelCopyright.Name = "labelCopyright";
76 | this.labelCopyright.Size = new System.Drawing.Size(601, 50);
77 | this.labelCopyright.TabIndex = 1;
78 | this.labelCopyright.Text = "Copyright";
79 | this.labelCopyright.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
80 | //
81 | // textBoxInfo
82 | //
83 | this.textBoxInfo.BackColor = System.Drawing.SystemColors.HighlightText;
84 | this.textBoxInfo.Dock = System.Windows.Forms.DockStyle.Fill;
85 | this.textBoxInfo.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
86 | this.textBoxInfo.Location = new System.Drawing.Point(3, 53);
87 | this.textBoxInfo.Multiline = true;
88 | this.textBoxInfo.Name = "textBoxInfo";
89 | this.textBoxInfo.ReadOnly = true;
90 | this.textBoxInfo.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
91 | this.textBoxInfo.Size = new System.Drawing.Size(601, 222);
92 | this.textBoxInfo.TabIndex = 2;
93 | //
94 | // About
95 | //
96 | this.AcceptButton = this.buttonOk;
97 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
98 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
99 | this.CancelButton = this.buttonOk;
100 | this.ClientSize = new System.Drawing.Size(607, 308);
101 | this.Controls.Add(this.tableLayoutPanelMain);
102 | this.MaximizeBox = false;
103 | this.MinimizeBox = false;
104 | this.Name = "About";
105 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
106 | this.Text = "About Duplicator";
107 | this.tableLayoutPanelMain.ResumeLayout(false);
108 | this.tableLayoutPanelMain.PerformLayout();
109 | this.ResumeLayout(false);
110 |
111 | }
112 |
113 | #endregion
114 |
115 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanelMain;
116 | private System.Windows.Forms.Button buttonOk;
117 | private System.Windows.Forms.Label labelCopyright;
118 | private System.Windows.Forms.TextBox textBoxInfo;
119 | }
120 | }
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/ChooseAudioDevice.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace WinDuplicator
2 | {
3 | partial class ChooseAudioDevice
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.tableLayoutPanelMain = new System.Windows.Forms.TableLayoutPanel();
32 | this.panelCommands = new System.Windows.Forms.Panel();
33 | this.buttonCancel = new System.Windows.Forms.Button();
34 | this.buttonOk = new System.Windows.Forms.Button();
35 | this.listViewMain = new System.Windows.Forms.ListView();
36 | this.columnName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
37 | this.tableLayoutPanelMain.SuspendLayout();
38 | this.panelCommands.SuspendLayout();
39 | this.SuspendLayout();
40 | //
41 | // tableLayoutPanelMain
42 | //
43 | this.tableLayoutPanelMain.ColumnCount = 1;
44 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
45 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
46 | this.tableLayoutPanelMain.Controls.Add(this.panelCommands, 0, 1);
47 | this.tableLayoutPanelMain.Controls.Add(this.listViewMain, 0, 0);
48 | this.tableLayoutPanelMain.Dock = System.Windows.Forms.DockStyle.Fill;
49 | this.tableLayoutPanelMain.Location = new System.Drawing.Point(0, 0);
50 | this.tableLayoutPanelMain.Name = "tableLayoutPanelMain";
51 | this.tableLayoutPanelMain.RowCount = 2;
52 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
53 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
54 | this.tableLayoutPanelMain.Size = new System.Drawing.Size(509, 255);
55 | this.tableLayoutPanelMain.TabIndex = 0;
56 | //
57 | // panelCommands
58 | //
59 | this.panelCommands.Controls.Add(this.buttonCancel);
60 | this.panelCommands.Controls.Add(this.buttonOk);
61 | this.panelCommands.Dock = System.Windows.Forms.DockStyle.Fill;
62 | this.panelCommands.Location = new System.Drawing.Point(0, 225);
63 | this.panelCommands.Margin = new System.Windows.Forms.Padding(0);
64 | this.panelCommands.Name = "panelCommands";
65 | this.panelCommands.Size = new System.Drawing.Size(509, 30);
66 | this.panelCommands.TabIndex = 0;
67 | //
68 | // buttonCancel
69 | //
70 | this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
71 | this.buttonCancel.Location = new System.Drawing.Point(341, 3);
72 | this.buttonCancel.Name = "buttonCancel";
73 | this.buttonCancel.Size = new System.Drawing.Size(75, 23);
74 | this.buttonCancel.TabIndex = 0;
75 | this.buttonCancel.Text = "&Cancel";
76 | this.buttonCancel.UseVisualStyleBackColor = true;
77 | //
78 | // buttonOk
79 | //
80 | this.buttonOk.DialogResult = System.Windows.Forms.DialogResult.OK;
81 | this.buttonOk.Location = new System.Drawing.Point(422, 3);
82 | this.buttonOk.Name = "buttonOk";
83 | this.buttonOk.Size = new System.Drawing.Size(75, 23);
84 | this.buttonOk.TabIndex = 1;
85 | this.buttonOk.Text = "OK";
86 | this.buttonOk.UseVisualStyleBackColor = true;
87 | //
88 | // listViewMain
89 | //
90 | this.listViewMain.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
91 | this.columnName});
92 | this.listViewMain.Dock = System.Windows.Forms.DockStyle.Fill;
93 | this.listViewMain.FullRowSelect = true;
94 | this.listViewMain.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
95 | this.listViewMain.Location = new System.Drawing.Point(3, 3);
96 | this.listViewMain.MultiSelect = false;
97 | this.listViewMain.Name = "listViewMain";
98 | this.listViewMain.Size = new System.Drawing.Size(503, 219);
99 | this.listViewMain.TabIndex = 0;
100 | this.listViewMain.UseCompatibleStateImageBehavior = false;
101 | this.listViewMain.View = System.Windows.Forms.View.Details;
102 | //
103 | // columnName
104 | //
105 | this.columnName.Text = "Name";
106 | this.columnName.Width = 211;
107 | //
108 | // ChooseAudioDevice
109 | //
110 | this.AcceptButton = this.buttonOk;
111 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
112 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
113 | this.CancelButton = this.buttonCancel;
114 | this.ClientSize = new System.Drawing.Size(509, 255);
115 | this.Controls.Add(this.tableLayoutPanelMain);
116 | this.MaximizeBox = false;
117 | this.MinimizeBox = false;
118 | this.Name = "ChooseAudioDevice";
119 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
120 | this.Text = "Choose Audio Device";
121 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ChooseAdapter_FormClosing);
122 | this.tableLayoutPanelMain.ResumeLayout(false);
123 | this.panelCommands.ResumeLayout(false);
124 | this.ResumeLayout(false);
125 |
126 | }
127 |
128 | #endregion
129 |
130 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanelMain;
131 | private System.Windows.Forms.Panel panelCommands;
132 | private System.Windows.Forms.Button buttonCancel;
133 | private System.Windows.Forms.Button buttonOk;
134 | private System.Windows.Forms.ListView listViewMain;
135 | private System.Windows.Forms.ColumnHeader columnName;
136 | }
137 | }
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/ChooseOutput.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace WinDuplicator
2 | {
3 | partial class ChooseOutput
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.tableLayoutPanelMain = new System.Windows.Forms.TableLayoutPanel();
32 | this.panelCommands = new System.Windows.Forms.Panel();
33 | this.buttonCancel = new System.Windows.Forms.Button();
34 | this.buttonOk = new System.Windows.Forms.Button();
35 | this.listViewMain = new System.Windows.Forms.ListView();
36 | this.columnName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
37 | this.columnSize = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
38 | this.columnPosition = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
39 | this.columnRotation = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
40 | this.tableLayoutPanelMain.SuspendLayout();
41 | this.panelCommands.SuspendLayout();
42 | this.SuspendLayout();
43 | //
44 | // tableLayoutPanelMain
45 | //
46 | this.tableLayoutPanelMain.ColumnCount = 1;
47 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
48 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
49 | this.tableLayoutPanelMain.Controls.Add(this.panelCommands, 0, 1);
50 | this.tableLayoutPanelMain.Controls.Add(this.listViewMain, 0, 0);
51 | this.tableLayoutPanelMain.Dock = System.Windows.Forms.DockStyle.Fill;
52 | this.tableLayoutPanelMain.Location = new System.Drawing.Point(0, 0);
53 | this.tableLayoutPanelMain.Name = "tableLayoutPanelMain";
54 | this.tableLayoutPanelMain.RowCount = 2;
55 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
56 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
57 | this.tableLayoutPanelMain.Size = new System.Drawing.Size(509, 255);
58 | this.tableLayoutPanelMain.TabIndex = 0;
59 | //
60 | // panelCommands
61 | //
62 | this.panelCommands.Controls.Add(this.buttonCancel);
63 | this.panelCommands.Controls.Add(this.buttonOk);
64 | this.panelCommands.Dock = System.Windows.Forms.DockStyle.Fill;
65 | this.panelCommands.Location = new System.Drawing.Point(0, 225);
66 | this.panelCommands.Margin = new System.Windows.Forms.Padding(0);
67 | this.panelCommands.Name = "panelCommands";
68 | this.panelCommands.Size = new System.Drawing.Size(509, 30);
69 | this.panelCommands.TabIndex = 0;
70 | //
71 | // buttonCancel
72 | //
73 | this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
74 | this.buttonCancel.Location = new System.Drawing.Point(341, 3);
75 | this.buttonCancel.Name = "buttonCancel";
76 | this.buttonCancel.Size = new System.Drawing.Size(75, 23);
77 | this.buttonCancel.TabIndex = 0;
78 | this.buttonCancel.Text = "&Cancel";
79 | this.buttonCancel.UseVisualStyleBackColor = true;
80 | //
81 | // buttonOk
82 | //
83 | this.buttonOk.DialogResult = System.Windows.Forms.DialogResult.OK;
84 | this.buttonOk.Location = new System.Drawing.Point(422, 3);
85 | this.buttonOk.Name = "buttonOk";
86 | this.buttonOk.Size = new System.Drawing.Size(75, 23);
87 | this.buttonOk.TabIndex = 1;
88 | this.buttonOk.Text = "OK";
89 | this.buttonOk.UseVisualStyleBackColor = true;
90 | //
91 | // listViewMain
92 | //
93 | this.listViewMain.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
94 | this.columnName,
95 | this.columnSize,
96 | this.columnPosition,
97 | this.columnRotation});
98 | this.listViewMain.Dock = System.Windows.Forms.DockStyle.Fill;
99 | this.listViewMain.FullRowSelect = true;
100 | this.listViewMain.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
101 | this.listViewMain.Location = new System.Drawing.Point(3, 3);
102 | this.listViewMain.MultiSelect = false;
103 | this.listViewMain.Name = "listViewMain";
104 | this.listViewMain.Size = new System.Drawing.Size(503, 219);
105 | this.listViewMain.TabIndex = 0;
106 | this.listViewMain.UseCompatibleStateImageBehavior = false;
107 | this.listViewMain.View = System.Windows.Forms.View.Details;
108 | this.listViewMain.SelectedIndexChanged += new System.EventHandler(this.listViewMain_SelectedIndexChanged);
109 | //
110 | // columnName
111 | //
112 | this.columnName.Text = "Name";
113 | this.columnName.Width = 211;
114 | //
115 | // columnSize
116 | //
117 | this.columnSize.Text = "Size";
118 | //
119 | // columnPosition
120 | //
121 | this.columnPosition.Text = "Position";
122 | this.columnPosition.Width = 120;
123 | //
124 | // columnRotation
125 | //
126 | this.columnRotation.Text = "Rotation";
127 | //
128 | // ChooseOutput
129 | //
130 | this.AcceptButton = this.buttonOk;
131 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
132 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
133 | this.CancelButton = this.buttonCancel;
134 | this.ClientSize = new System.Drawing.Size(509, 255);
135 | this.Controls.Add(this.tableLayoutPanelMain);
136 | this.MaximizeBox = false;
137 | this.MinimizeBox = false;
138 | this.Name = "ChooseOutput";
139 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
140 | this.Text = "Choose Output";
141 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ChooseOutput_FormClosing);
142 | this.tableLayoutPanelMain.ResumeLayout(false);
143 | this.panelCommands.ResumeLayout(false);
144 | this.ResumeLayout(false);
145 |
146 | }
147 |
148 | #endregion
149 |
150 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanelMain;
151 | private System.Windows.Forms.Panel panelCommands;
152 | private System.Windows.Forms.Button buttonCancel;
153 | private System.Windows.Forms.Button buttonOk;
154 | private System.Windows.Forms.ListView listViewMain;
155 | private System.Windows.Forms.ColumnHeader columnName;
156 | private System.Windows.Forms.ColumnHeader columnSize;
157 | private System.Windows.Forms.ColumnHeader columnPosition;
158 | private System.Windows.Forms.ColumnHeader columnRotation;
159 | }
160 | }
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/ChooseAdapter.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace WinDuplicator
2 | {
3 | partial class ChooseAdapter
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.tableLayoutPanelMain = new System.Windows.Forms.TableLayoutPanel();
32 | this.panelCommands = new System.Windows.Forms.Panel();
33 | this.buttonCancel = new System.Windows.Forms.Button();
34 | this.buttonOk = new System.Windows.Forms.Button();
35 | this.listViewMain = new System.Windows.Forms.ListView();
36 | this.columnDescription = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
37 | this.columnFlags = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
38 | this.columnRevision = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
39 | this.columnVRAM = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
40 | this.tableLayoutPanelMain.SuspendLayout();
41 | this.panelCommands.SuspendLayout();
42 | this.SuspendLayout();
43 | //
44 | // tableLayoutPanelMain
45 | //
46 | this.tableLayoutPanelMain.ColumnCount = 1;
47 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
48 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
49 | this.tableLayoutPanelMain.Controls.Add(this.panelCommands, 0, 1);
50 | this.tableLayoutPanelMain.Controls.Add(this.listViewMain, 0, 0);
51 | this.tableLayoutPanelMain.Dock = System.Windows.Forms.DockStyle.Fill;
52 | this.tableLayoutPanelMain.Location = new System.Drawing.Point(0, 0);
53 | this.tableLayoutPanelMain.Name = "tableLayoutPanelMain";
54 | this.tableLayoutPanelMain.RowCount = 2;
55 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
56 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
57 | this.tableLayoutPanelMain.Size = new System.Drawing.Size(509, 255);
58 | this.tableLayoutPanelMain.TabIndex = 0;
59 | //
60 | // panelCommands
61 | //
62 | this.panelCommands.Controls.Add(this.buttonCancel);
63 | this.panelCommands.Controls.Add(this.buttonOk);
64 | this.panelCommands.Dock = System.Windows.Forms.DockStyle.Fill;
65 | this.panelCommands.Location = new System.Drawing.Point(0, 225);
66 | this.panelCommands.Margin = new System.Windows.Forms.Padding(0);
67 | this.panelCommands.Name = "panelCommands";
68 | this.panelCommands.Size = new System.Drawing.Size(509, 30);
69 | this.panelCommands.TabIndex = 0;
70 | //
71 | // buttonCancel
72 | //
73 | this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
74 | this.buttonCancel.Location = new System.Drawing.Point(341, 3);
75 | this.buttonCancel.Name = "buttonCancel";
76 | this.buttonCancel.Size = new System.Drawing.Size(75, 23);
77 | this.buttonCancel.TabIndex = 0;
78 | this.buttonCancel.Text = "&Cancel";
79 | this.buttonCancel.UseVisualStyleBackColor = true;
80 | //
81 | // buttonOk
82 | //
83 | this.buttonOk.DialogResult = System.Windows.Forms.DialogResult.OK;
84 | this.buttonOk.Location = new System.Drawing.Point(422, 3);
85 | this.buttonOk.Name = "buttonOk";
86 | this.buttonOk.Size = new System.Drawing.Size(75, 23);
87 | this.buttonOk.TabIndex = 1;
88 | this.buttonOk.Text = "OK";
89 | this.buttonOk.UseVisualStyleBackColor = true;
90 | //
91 | // listViewMain
92 | //
93 | this.listViewMain.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
94 | this.columnDescription,
95 | this.columnFlags,
96 | this.columnRevision,
97 | this.columnVRAM});
98 | this.listViewMain.Dock = System.Windows.Forms.DockStyle.Fill;
99 | this.listViewMain.FullRowSelect = true;
100 | this.listViewMain.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
101 | this.listViewMain.Location = new System.Drawing.Point(3, 3);
102 | this.listViewMain.MultiSelect = false;
103 | this.listViewMain.Name = "listViewMain";
104 | this.listViewMain.Size = new System.Drawing.Size(503, 219);
105 | this.listViewMain.TabIndex = 0;
106 | this.listViewMain.UseCompatibleStateImageBehavior = false;
107 | this.listViewMain.View = System.Windows.Forms.View.Details;
108 | this.listViewMain.SelectedIndexChanged += new System.EventHandler(this.listViewMain_SelectedIndexChanged);
109 | //
110 | // columnDescription
111 | //
112 | this.columnDescription.Text = "Description";
113 | this.columnDescription.Width = 211;
114 | //
115 | // columnFlags
116 | //
117 | this.columnFlags.Text = "Flags";
118 | //
119 | // columnRevision
120 | //
121 | this.columnRevision.Text = "Revision";
122 | this.columnRevision.Width = 120;
123 | //
124 | // columnVRAM
125 | //
126 | this.columnVRAM.Text = "VRAM";
127 | //
128 | // ChooseAdapter
129 | //
130 | this.AcceptButton = this.buttonOk;
131 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
132 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
133 | this.CancelButton = this.buttonCancel;
134 | this.ClientSize = new System.Drawing.Size(509, 255);
135 | this.Controls.Add(this.tableLayoutPanelMain);
136 | this.MaximizeBox = false;
137 | this.MinimizeBox = false;
138 | this.Name = "ChooseAdapter";
139 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
140 | this.Text = "Choose Adapter";
141 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ChooseAdapter_FormClosing);
142 | this.tableLayoutPanelMain.ResumeLayout(false);
143 | this.panelCommands.ResumeLayout(false);
144 | this.ResumeLayout(false);
145 |
146 | }
147 |
148 | #endregion
149 |
150 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanelMain;
151 | private System.Windows.Forms.Panel panelCommands;
152 | private System.Windows.Forms.Button buttonCancel;
153 | private System.Windows.Forms.Button buttonOk;
154 | private System.Windows.Forms.ListView listViewMain;
155 | private System.Windows.Forms.ColumnHeader columnDescription;
156 | private System.Windows.Forms.ColumnHeader columnFlags;
157 | private System.Windows.Forms.ColumnHeader columnRevision;
158 | private System.Windows.Forms.ColumnHeader columnVRAM;
159 | }
160 | }
--------------------------------------------------------------------------------
/Duplicator/Duplicator/H264Encoder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Runtime.InteropServices;
6 | using Microsoft.Win32;
7 | using SharpDX.MediaFoundation;
8 | using SharpDX.Multimedia;
9 |
10 | namespace Duplicator
11 | {
12 | public class H264Encoder : IDisposable
13 | {
14 | private H264Encoder(Activate activate)
15 | {
16 | Activate = activate;
17 | FriendlyName = activate.Get(TransformAttributeKeys.MftFriendlyNameAttribute);
18 | Clsid = activate.Get(TransformAttributeKeys.MftTransformClsidAttribute);
19 | Flags = (TransformEnumFlag)activate.Get(TransformAttributeKeys.TransformFlagsAttribute);
20 | var list = new List();
21 | var inputTypes = activate.Get(TransformAttributeKeys.MftInputTypesAttributes);
22 | for (int j = 0; j < inputTypes.Length; j += 32) // two guids
23 | {
24 | var majorType = new Guid(Enumerable.Range(0, 16).Select(index => Marshal.ReadByte(inputTypes, j + index)).ToArray()); // Should be video in this context
25 | var subType = new Guid(Enumerable.Range(0, 16).Select(index => Marshal.ReadByte(inputTypes, j + 16 + index)).ToArray());
26 | list.Add(GetFourCC(subType));
27 | }
28 |
29 | list.Sort();
30 | InputTypes = list;
31 | try
32 | {
33 | using (var tf = activate.ActivateObject())
34 | {
35 | IsBuiltin = IsBuiltinEncoder(tf);
36 | IsDirect3D11Aware = IsDirect3D11AwareEncoder(tf);
37 | IsHardwareBased = IsHardwareBasedEncoder(tf);
38 | }
39 | }
40 | catch
41 | {
42 | // do nothing
43 | }
44 |
45 | using (var key = Registry.ClassesRoot.OpenSubKey(Path.Combine("CLSID", Clsid.ToString("B"), "InprocServer32")))
46 | {
47 | if (key != null)
48 | {
49 | DllPath = key.GetValue(null) as string;
50 | }
51 | }
52 | }
53 |
54 | public Activate Activate { get; }
55 | public string FriendlyName { get; }
56 | public Guid Clsid { get; }
57 | public IEnumerable InputTypes { get; }
58 | public TransformEnumFlag Flags { get; }
59 | public bool IsBuiltin { get; }
60 | public bool IsDirect3D11Aware { get; }
61 | public bool IsHardwareBased { get; }
62 | public string DllPath { get; }
63 | public override string ToString() => FriendlyName;
64 |
65 | public Transform GetTransform() => Activate.ActivateObject();
66 |
67 | public void Dispose() => Activate.Dispose();
68 |
69 | private static IntPtr GetTransformPtr(SinkWriter writer, int streamIndex)
70 | {
71 | if (writer == null)
72 | throw new ArgumentNullException(nameof(writer));
73 |
74 | var tf = IntPtr.Zero;
75 | try
76 | {
77 | writer.GetServiceForStream(streamIndex, Guid.Empty, typeof(Transform).GUID, out tf);
78 | }
79 | catch
80 | {
81 | // do nothing
82 | }
83 | return tf;
84 | }
85 |
86 | public static Transform GetTransform(SinkWriter writer, int streamIndex)
87 | {
88 | var ptr = GetTransformPtr(writer, streamIndex);
89 | return ptr != IntPtr.Zero ? new Transform(ptr) : null;
90 | }
91 |
92 | public static bool IsBuiltinEncoder(SinkWriter writer, int streamIndex)
93 | {
94 | var ptr = GetTransformPtr(writer, streamIndex);
95 | if (ptr == IntPtr.Zero)
96 | return false;
97 |
98 | return Marshal.GetObjectForIUnknown(ptr) as IMFObjectInformation != null;
99 | }
100 |
101 | public static TOutputStreamInformation GetOutputStreamInfo(SinkWriter writer, int streamIndex)
102 | {
103 | using (var transform = GetTransform(writer, streamIndex))
104 | {
105 | if (transform == null)
106 | return new TOutputStreamInformation();
107 |
108 | transform.GetOutputStreamInfo(streamIndex, out TOutputStreamInformation info);
109 | return info;
110 | }
111 | }
112 |
113 | public static bool IsDirect3D11AwareEncoder(SinkWriter writer, int streamIndex)
114 | {
115 | using (var transform = GetTransform(writer, streamIndex))
116 | {
117 | if (transform == null)
118 | return false;
119 |
120 | return IsDirect3D11AwareEncoder(transform);
121 | }
122 | }
123 |
124 | public static string GetEncoderFriendlyName(SinkWriter writer, int streamIndex)
125 | {
126 | try
127 | {
128 | using (var transform = GetTransform(writer, streamIndex))
129 | {
130 | if (transform != null)
131 | {
132 | if (IsBuiltinEncoder(transform))
133 | return Enumerate().First(e => e.IsBuiltin).FriendlyName;
134 |
135 | var clsid = transform.Attributes.Get(TransformAttributeKeys.MftTransformClsidAttribute);
136 | return Enumerate().First(e => e.Clsid == clsid).FriendlyName;
137 | }
138 | }
139 | }
140 | catch
141 | {
142 | // continue
143 | }
144 | return "Unknown";
145 | }
146 |
147 | public static bool IsHardwareBasedEncoder(SinkWriter writer, int streamIndex)
148 | {
149 | using (var transform = GetTransform(writer, streamIndex))
150 | {
151 | if (transform == null)
152 | return false;
153 |
154 | return IsHardwareBasedEncoder(transform);
155 | }
156 | }
157 |
158 | public static bool IsHardwareBasedEncoder(Transform transform)
159 | {
160 | if (transform == null)
161 | throw new ArgumentNullException(nameof(transform));
162 |
163 | return EnumerateAttributes(transform.Attributes).Any(a => a.Key == TransformAttributeKeys.MftEnumHardwareUrlAttribute.Guid);
164 | }
165 |
166 | public static bool IsDirect3D11AwareEncoder(Transform transform)
167 | {
168 | if (transform == null)
169 | throw new ArgumentNullException(nameof(transform));
170 |
171 | return EnumerateAttributes(transform.Attributes).Any(a => a.Key == TransformAttributeKeys.D3D11Aware.Guid && a.Value.Equals(1));
172 | }
173 |
174 | public static IReadOnlyDictionary GetAttributes(MediaAttributes atts)
175 | {
176 | var dic = new Dictionary();
177 | if (atts != null)
178 | {
179 | for (int i = 0; i < atts.Count; i++)
180 | {
181 | object value = atts.GetByIndex(i, out Guid guid);
182 | dic[guid] = value;
183 | }
184 | }
185 | return dic;
186 | }
187 |
188 | internal static IEnumerable> EnumerateAttributes(MediaAttributes atts)
189 | {
190 | for (int i = 0; i < atts.Count; i++)
191 | {
192 | object value = atts.GetByIndex(i, out Guid guid);
193 | yield return new KeyValuePair(guid, value);
194 | }
195 | }
196 |
197 | public static bool IsBuiltinEncoder(Transform transform)
198 | {
199 | if (transform == null)
200 | throw new ArgumentNullException(nameof(transform));
201 |
202 | return Marshal.GetObjectForIUnknown(transform.NativePointer) as IMFObjectInformation != null;
203 | }
204 |
205 | public static IEnumerable Enumerate() => Enumerate(TransformEnumFlag.All);
206 | public static IEnumerable Enumerate(TransformEnumFlag flags)
207 | {
208 | var output = new TRegisterTypeInformation();
209 | output.GuidMajorType = MediaTypeGuids.Video;
210 | output.GuidSubtype = VideoFormatGuids.FromFourCC(new FourCC("H264"));
211 | foreach (var activate in MediaFactory.FindTransform(TransformCategoryGuids.VideoEncoder, flags, null, output))
212 | {
213 | yield return new H264Encoder(activate);
214 | }
215 | }
216 |
217 | private static string GetFourCC(Guid guid)
218 | {
219 | var s = guid.ToString();
220 | if (s.EndsWith("0000-0010-8000-00aa00389b71"))
221 | {
222 | var bytes = guid.ToByteArray();
223 | if (bytes.Take(4).Any(b => b < 32 || b > 127))
224 | return s;
225 |
226 | return new string(bytes.Take(4).Select(b => (char)b).ToArray());
227 | }
228 |
229 | return s;
230 | }
231 |
232 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("CE6BE8E7-D757-435F-9DE9-BE3EF330B805")]
233 | private interface IMFObjectInformation { }
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/Main.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace WinDuplicator
2 | {
3 | partial class Main
4 | {
5 | #region Windows Form Designer generated code
6 |
7 | ///
8 | /// Required method for Designer support - do not modify
9 | /// the contents of this method with the code editor.
10 | ///
11 | private void InitializeComponent()
12 | {
13 | this.splitContainerMain = new System.Windows.Forms.SplitContainer();
14 | this.tableLayoutPanelMain = new System.Windows.Forms.TableLayoutPanel();
15 | this.panelCommands = new System.Windows.Forms.Panel();
16 | this.buttonAbout = new System.Windows.Forms.Button();
17 | this.checkBoxRecord = new System.Windows.Forms.CheckBox();
18 | this.checkBoxDuplicate = new System.Windows.Forms.CheckBox();
19 | this.buttonQuit = new System.Windows.Forms.Button();
20 | this.propertyGridMain = new System.Windows.Forms.PropertyGrid();
21 | this.textBoxStatus = new System.Windows.Forms.TextBox();
22 | ((System.ComponentModel.ISupportInitialize)(this.splitContainerMain)).BeginInit();
23 | this.splitContainerMain.Panel2.SuspendLayout();
24 | this.splitContainerMain.SuspendLayout();
25 | this.tableLayoutPanelMain.SuspendLayout();
26 | this.panelCommands.SuspendLayout();
27 | this.SuspendLayout();
28 | //
29 | // splitContainerMain
30 | //
31 | this.splitContainerMain.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
32 | this.splitContainerMain.Dock = System.Windows.Forms.DockStyle.Fill;
33 | this.splitContainerMain.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;
34 | this.splitContainerMain.Location = new System.Drawing.Point(0, 0);
35 | this.splitContainerMain.Name = "splitContainerMain";
36 | //
37 | // splitContainerMain.Panel2
38 | //
39 | this.splitContainerMain.Panel2.Controls.Add(this.tableLayoutPanelMain);
40 | this.splitContainerMain.Size = new System.Drawing.Size(806, 513);
41 | this.splitContainerMain.SplitterDistance = 455;
42 | this.splitContainerMain.TabIndex = 0;
43 | //
44 | // tableLayoutPanelMain
45 | //
46 | this.tableLayoutPanelMain.ColumnCount = 1;
47 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
48 | this.tableLayoutPanelMain.Controls.Add(this.panelCommands, 0, 2);
49 | this.tableLayoutPanelMain.Controls.Add(this.propertyGridMain, 0, 0);
50 | this.tableLayoutPanelMain.Controls.Add(this.textBoxStatus, 0, 1);
51 | this.tableLayoutPanelMain.Dock = System.Windows.Forms.DockStyle.Fill;
52 | this.tableLayoutPanelMain.Location = new System.Drawing.Point(0, 0);
53 | this.tableLayoutPanelMain.Name = "tableLayoutPanelMain";
54 | this.tableLayoutPanelMain.RowCount = 3;
55 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
56 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 80F));
57 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
58 | this.tableLayoutPanelMain.Size = new System.Drawing.Size(345, 511);
59 | this.tableLayoutPanelMain.TabIndex = 0;
60 | //
61 | // panelCommands
62 | //
63 | this.panelCommands.Controls.Add(this.buttonAbout);
64 | this.panelCommands.Controls.Add(this.checkBoxRecord);
65 | this.panelCommands.Controls.Add(this.checkBoxDuplicate);
66 | this.panelCommands.Controls.Add(this.buttonQuit);
67 | this.panelCommands.Dock = System.Windows.Forms.DockStyle.Fill;
68 | this.panelCommands.Location = new System.Drawing.Point(0, 481);
69 | this.panelCommands.Margin = new System.Windows.Forms.Padding(0);
70 | this.panelCommands.Name = "panelCommands";
71 | this.panelCommands.Size = new System.Drawing.Size(345, 30);
72 | this.panelCommands.TabIndex = 2;
73 | //
74 | // buttonAbout
75 | //
76 | this.buttonAbout.DialogResult = System.Windows.Forms.DialogResult.OK;
77 | this.buttonAbout.Location = new System.Drawing.Point(3, 4);
78 | this.buttonAbout.Name = "buttonAbout";
79 | this.buttonAbout.Size = new System.Drawing.Size(75, 23);
80 | this.buttonAbout.TabIndex = 0;
81 | this.buttonAbout.Text = "&About...";
82 | this.buttonAbout.UseVisualStyleBackColor = true;
83 | this.buttonAbout.Click += new System.EventHandler(this.buttonAbout_Click);
84 | //
85 | // checkBoxRecord
86 | //
87 | this.checkBoxRecord.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
88 | this.checkBoxRecord.AutoSize = true;
89 | this.checkBoxRecord.Location = new System.Drawing.Point(101, 8);
90 | this.checkBoxRecord.Name = "checkBoxRecord";
91 | this.checkBoxRecord.Size = new System.Drawing.Size(61, 17);
92 | this.checkBoxRecord.TabIndex = 1;
93 | this.checkBoxRecord.Text = "&Record";
94 | this.checkBoxRecord.UseVisualStyleBackColor = true;
95 | this.checkBoxRecord.CheckedChanged += new System.EventHandler(this.checkBoxRecord_CheckedChanged);
96 | //
97 | // checkBoxDuplicate
98 | //
99 | this.checkBoxDuplicate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
100 | this.checkBoxDuplicate.AutoSize = true;
101 | this.checkBoxDuplicate.Location = new System.Drawing.Point(180, 8);
102 | this.checkBoxDuplicate.Name = "checkBoxDuplicate";
103 | this.checkBoxDuplicate.Size = new System.Drawing.Size(71, 17);
104 | this.checkBoxDuplicate.TabIndex = 2;
105 | this.checkBoxDuplicate.Text = "&Duplicate";
106 | this.checkBoxDuplicate.UseVisualStyleBackColor = true;
107 | this.checkBoxDuplicate.CheckedChanged += new System.EventHandler(this.checkBoxDuplicate_CheckedChanged);
108 | //
109 | // buttonQuit
110 | //
111 | this.buttonQuit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
112 | this.buttonQuit.DialogResult = System.Windows.Forms.DialogResult.OK;
113 | this.buttonQuit.Location = new System.Drawing.Point(266, 4);
114 | this.buttonQuit.Name = "buttonQuit";
115 | this.buttonQuit.Size = new System.Drawing.Size(75, 23);
116 | this.buttonQuit.TabIndex = 3;
117 | this.buttonQuit.Text = "&Quit";
118 | this.buttonQuit.UseVisualStyleBackColor = true;
119 | this.buttonQuit.Click += new System.EventHandler(this.buttonQuit_Click);
120 | //
121 | // propertyGridMain
122 | //
123 | this.propertyGridMain.Dock = System.Windows.Forms.DockStyle.Fill;
124 | this.propertyGridMain.HelpVisible = false;
125 | this.propertyGridMain.Location = new System.Drawing.Point(3, 3);
126 | this.propertyGridMain.Name = "propertyGridMain";
127 | this.propertyGridMain.Size = new System.Drawing.Size(339, 395);
128 | this.propertyGridMain.TabIndex = 0;
129 | this.propertyGridMain.ToolbarVisible = false;
130 | //
131 | // textBoxStatus
132 | //
133 | this.textBoxStatus.Dock = System.Windows.Forms.DockStyle.Fill;
134 | this.textBoxStatus.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
135 | this.textBoxStatus.Location = new System.Drawing.Point(3, 404);
136 | this.textBoxStatus.Multiline = true;
137 | this.textBoxStatus.Name = "textBoxStatus";
138 | this.textBoxStatus.ReadOnly = true;
139 | this.textBoxStatus.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
140 | this.textBoxStatus.Size = new System.Drawing.Size(339, 74);
141 | this.textBoxStatus.TabIndex = 3;
142 | //
143 | // Main
144 | //
145 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
146 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
147 | this.ClientSize = new System.Drawing.Size(806, 513);
148 | this.Controls.Add(this.splitContainerMain);
149 | this.Name = "Main";
150 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
151 | this.Text = "Duplicator";
152 | this.splitContainerMain.Panel2.ResumeLayout(false);
153 | ((System.ComponentModel.ISupportInitialize)(this.splitContainerMain)).EndInit();
154 | this.splitContainerMain.ResumeLayout(false);
155 | this.tableLayoutPanelMain.ResumeLayout(false);
156 | this.tableLayoutPanelMain.PerformLayout();
157 | this.panelCommands.ResumeLayout(false);
158 | this.panelCommands.PerformLayout();
159 | this.ResumeLayout(false);
160 |
161 | }
162 |
163 | #endregion
164 |
165 | private System.Windows.Forms.SplitContainer splitContainerMain;
166 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanelMain;
167 | private System.Windows.Forms.PropertyGrid propertyGridMain;
168 | private System.Windows.Forms.Panel panelCommands;
169 | private System.Windows.Forms.Button buttonAbout;
170 | private System.Windows.Forms.CheckBox checkBoxRecord;
171 | private System.Windows.Forms.CheckBox checkBoxDuplicate;
172 | private System.Windows.Forms.Button buttonQuit;
173 | private System.Windows.Forms.TextBox textBoxStatus;
174 | }
175 | }
176 |
177 |
--------------------------------------------------------------------------------
/Duplicator/Duplicator/DuplicatorOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Globalization;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Runtime.InteropServices;
7 | using SharpDX.DXGI;
8 |
9 | namespace Duplicator
10 | {
11 | public class DuplicatorOptions : DictionaryObject
12 | {
13 | public const string DisplayCategory = "Duplicated Display";
14 | public const string RecordingCategory = "Recording";
15 | public const string SoundRecordingCategory = "Sound Recording";
16 | public const string InputCategory = "Input";
17 | public const string DiagnosticsCategory = "Diagnostics";
18 | private const string DefaultFileFormat = "Capture_{0:yyyy_MM_dd_HH_mm_ss}";
19 |
20 | public DuplicatorOptions()
21 | {
22 | Adapter1 adapter;
23 | using (var fac = new Factory1())
24 | {
25 | adapter = fac.Adapters1.FirstOrDefault(a => !a.Description1.Flags.HasFlag(AdapterFlags.Software));
26 | if (adapter == null)
27 | {
28 | adapter = fac.Adapters1.First();
29 | }
30 | }
31 |
32 | Adapter = adapter.Description.Description;
33 | Output = adapter.Outputs.First().Description.DeviceName;
34 | FrameAcquisitionTimeout = 500;
35 | AudioAcquisitionTimeout = 500;
36 | ShowCursor = true;
37 | PreserveRatio = true;
38 | RecordingFrameRate = 0;
39 | OutputFileFormat = DefaultFileFormat;
40 | OutputDirectoryPath = GetDefaultOutputDirectoryPath();
41 | EnableHardwareTransforms = true;
42 | CaptureSound = true;
43 | CaptureMicrophone = false;
44 | SoundDevice = AudioCapture.GetSpeakersDevice()?.FriendlyName;
45 | MicrophoneDevice = AudioCapture.GetMicrophoneDevice()?.FriendlyName;
46 | UseRecordingQueue = false;
47 | }
48 |
49 | [DisplayName("File Format")]
50 | [Category(RecordingCategory)]
51 | [DefaultValue(DefaultFileFormat)]
52 | public virtual string OutputFileFormat { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
53 |
54 | [DisplayName("Directory Path")]
55 | [Category(RecordingCategory)]
56 | public virtual string OutputDirectoryPath { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
57 |
58 | [Browsable(false)] // doesn't work
59 | [DisplayName("Use Intermediate Queue")]
60 | [Category(RecordingCategory)]
61 | [DefaultValue(false)]
62 | public virtual bool UseRecordingQueue { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
63 |
64 | //[Browsable(false)] // not used
65 | [DisplayName("Frame Rate")]
66 | [Category(RecordingCategory)]
67 | [DefaultValue(0f)]
68 | public virtual float RecordingFrameRate { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(Math.Max(0f, value)); }
69 |
70 | [DisplayName("Enable Hardware Encoding")]
71 | [Category(RecordingCategory)]
72 | [DefaultValue(true)]
73 | public virtual bool EnableHardwareTransforms { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
74 |
75 | [DisplayName("Capture Sound")]
76 | [Category(SoundRecordingCategory)]
77 | [DefaultValue(true)]
78 | public virtual bool CaptureSound { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
79 |
80 | [DisplayName("Capture Microphone")]
81 | [Category(SoundRecordingCategory)]
82 | [DefaultValue(false)]
83 | public virtual bool CaptureMicrophone { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
84 |
85 | [DisplayName("Sound Device")]
86 | [Category(SoundRecordingCategory)]
87 | public virtual string SoundDevice { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
88 |
89 | [DisplayName("Microphone Device")]
90 | [Category(SoundRecordingCategory)]
91 | public virtual string MicrophoneDevice { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
92 |
93 | [DisplayName("Disable Throttling")]
94 | [Category(RecordingCategory)]
95 | [DefaultValue(false)]
96 | public virtual bool DisableThrottling { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
97 |
98 | [DisplayName("Low Latency")]
99 | [Category(RecordingCategory)]
100 | [DefaultValue(false)]
101 | public virtual bool LowLatency { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
102 |
103 | [DisplayName("Video Adapter")]
104 | [Category(InputCategory)]
105 | public virtual string Adapter { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
106 |
107 | [DisplayName("Video Monitor")]
108 | [Category(InputCategory)]
109 | [TypeConverter(typeof(DisplayDeviceTypeConverter))]
110 | public virtual string Output { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
111 |
112 | [DisplayName("Show Cursor")]
113 | [Category(DisplayCategory)]
114 | [DefaultValue(true)]
115 | public virtual bool ShowCursor { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
116 |
117 | [DisplayName("Proportional Cursor")]
118 | [Category(DisplayCategory)]
119 | [DefaultValue(false)]
120 | public virtual bool IsCursorProportional { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
121 |
122 | [DisplayName("Show Acquisition Rate")]
123 | [Category(DiagnosticsCategory)]
124 | [DefaultValue(false)]
125 | public virtual bool ShowInputFps { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
126 |
127 | [DisplayName("Preserve Input Ratio")]
128 | [Category(DisplayCategory)]
129 | [DefaultValue(true)]
130 | public virtual bool PreserveRatio { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
131 |
132 | [DisplayName("Show Accumulated Frames")]
133 | [Category(DiagnosticsCategory)]
134 | [DefaultValue(false)]
135 | public virtual bool ShowAccumulatedFrames { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); }
136 |
137 | [DisplayName("Frame Acquisition Timeout")]
138 | [Category(InputCategory)]
139 | [DefaultValue(500)]
140 | public virtual int FrameAcquisitionTimeout
141 | {
142 | get => DictionaryObjectGetPropertyValue();
143 | set
144 | {
145 | // we don't want infinite
146 | DictionaryObjectSetPropertyValue(Math.Max(0, value));
147 | }
148 | }
149 |
150 | [DisplayName("Audio Acquisition Timeout")]
151 | [Category(InputCategory)]
152 | [DefaultValue(500)]
153 | public virtual int AudioAcquisitionTimeout
154 | {
155 | get => DictionaryObjectGetPropertyValue();
156 | set
157 | {
158 | // we don't want infinite
159 | DictionaryObjectSetPropertyValue(Math.Max(0, value));
160 | }
161 | }
162 |
163 | public AudioCapture.AudioDevice GetSoundDevice() => AudioCapture.GetDevices(AudioCapture.DataFlow.All).FirstOrDefault(d => d.FriendlyName == SoundDevice);
164 | public AudioCapture.AudioDevice GetMicrophoneDevice() => AudioCapture.GetDevices(AudioCapture.DataFlow.All).FirstOrDefault(d => d.FriendlyName == MicrophoneDevice);
165 |
166 | public Adapter1 GetAdapter()
167 | {
168 | using (var fac = new Factory1())
169 | {
170 | return fac.Adapters1.FirstOrDefault(a => a.Description.Description == Adapter);
171 | }
172 | }
173 |
174 | public Output1 GetOutput()
175 | {
176 | using (var adapter = GetAdapter())
177 | {
178 | var output = adapter.Outputs.FirstOrDefault(o => o.Description.DeviceName == Output);
179 | if (output == null)
180 | return null; // this can happen if the adapter is not connected to a display
181 |
182 | return output.QueryInterface();
183 | }
184 | }
185 |
186 | public static string GetDefaultOutputDirectoryPath()
187 | {
188 | var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Duplicator");
189 | if (!Directory.Exists(dir))
190 | {
191 | Directory.CreateDirectory(dir);
192 | }
193 | return dir;
194 | }
195 |
196 | public string GetNewFilePath()
197 | {
198 | var dir = OutputDirectoryPath;
199 | if (string.IsNullOrWhiteSpace(dir))
200 | {
201 | dir = GetDefaultOutputDirectoryPath();
202 | }
203 |
204 | string format = OutputFileFormat;
205 | if (string.IsNullOrEmpty(OutputFileFormat))
206 | {
207 | format = DefaultFileFormat;
208 | }
209 |
210 | string fileName = string.Format(format, DateTime.Now);
211 | return Path.Combine(dir, fileName);
212 | }
213 |
214 | public static string GetDisplayDeviceName(string deviceName)
215 | {
216 | if (deviceName == null)
217 | throw new ArgumentNullException(nameof(deviceName));
218 |
219 | var dd = new DISPLAY_DEVICE();
220 | dd.cb = Marshal.SizeOf();
221 | if (!EnumDisplayDevices(deviceName, 0, ref dd, 0))
222 | return deviceName;
223 |
224 | return dd.DeviceString;
225 | }
226 |
227 | private class DisplayDeviceTypeConverter : TypeConverter
228 | {
229 | public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
230 | {
231 | var name = value as string;
232 | if (name != null)
233 | return GetDisplayDeviceName(name);
234 |
235 | return base.ConvertTo(context, culture, value, destinationType);
236 | }
237 | }
238 |
239 | [Flags]
240 | private enum DISPLAY_DEVICE_FLAGS
241 | {
242 | DISPLAY_DEVICE_ATTACHED_TO_DESKTOP = 0x00000001,
243 | DISPLAY_DEVICE_MULTI_DRIVER = 0x00000002,
244 | DISPLAY_DEVICE_PRIMARY_DEVICE = 0x00000004,
245 | DISPLAY_DEVICE_MIRRORING_DRIVER = 0x00000008,
246 | DISPLAY_DEVICE_VGA_COMPATIBLE = 0x00000010,
247 | DISPLAY_DEVICE_REMOVABLE = 0x00000020,
248 | DISPLAY_DEVICE_ACC_DRIVER = 0x00000040,
249 | DISPLAY_DEVICE_MODESPRUNED = 0x08000000,
250 | DISPLAY_DEVICE_RDPUDD = 0x01000000,
251 | DISPLAY_DEVICE_REMOTE = 0x04000000,
252 | DISPLAY_DEVICE_DISCONNECT = 0x02000000,
253 | DISPLAY_DEVICE_TS_COMPATIBLE = 0x00200000,
254 | DISPLAY_DEVICE_UNSAFE_MODES_ON = 0x00080000,
255 | DISPLAY_DEVICE_ACTIVE = 0x00000001,
256 | DISPLAY_DEVICE_ATTACHED = 0x00000002,
257 | }
258 |
259 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
260 | private struct DISPLAY_DEVICE
261 | {
262 | public int cb;
263 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
264 | public string DeviceName;
265 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
266 | public string DeviceString;
267 | public DISPLAY_DEVICE_FLAGS StateFlags;
268 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
269 | public string DeviceID;
270 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
271 | public string DeviceKey;
272 | }
273 |
274 | [DllImport("user32", CharSet = CharSet.Auto)]
275 | private static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/Duplicator/WinDuplicator/WinDuplicatorOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Drawing.Design;
5 | using System.Globalization;
6 | using System.Runtime.InteropServices;
7 | using System.Windows.Forms;
8 | using System.Windows.Forms.Design;
9 | using Duplicator;
10 |
11 | namespace WinDuplicator
12 | {
13 | public class WinDuplicatorOptions : DuplicatorOptions
14 | {
15 | [Editor(typeof(OutputEditor), typeof(UITypeEditor))]
16 | public override string Output { get => base.Output; set => base.Output = value; }
17 |
18 | [Editor(typeof(AdapterEditor), typeof(UITypeEditor))]
19 | public override string Adapter { get => base.Adapter; set => base.Adapter = value; }
20 |
21 | [Editor(typeof(RenderAudioDeviceEditor), typeof(UITypeEditor))]
22 | public override string SoundDevice { get => base.SoundDevice; set => base.SoundDevice = value; }
23 |
24 | [Editor(typeof(CaptureAudioDeviceEditor), typeof(UITypeEditor))]
25 | public override string MicrophoneDevice { get => base.MicrophoneDevice; set => base.MicrophoneDevice = value; }
26 |
27 | [TypeConverter(typeof(FrameRateConverter))]
28 | public override float RecordingFrameRate { get => base.RecordingFrameRate; set => base.RecordingFrameRate = value; }
29 |
30 | [Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
31 | public override string OutputDirectoryPath { get => base.OutputDirectoryPath; set => base.OutputDirectoryPath = value; }
32 |
33 | private class FrameRateConverter : TypeConverter
34 | {
35 | private const string Automatic = "";
36 |
37 | public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => true;
38 |
39 | public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
40 | {
41 | if (Automatic.Equals(value))
42 | return 0f;
43 |
44 | return float.Parse((string)value);
45 | }
46 |
47 | public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
48 | {
49 | if (0f.Equals(value))
50 | return Automatic;
51 |
52 | return base.ConvertTo(context, culture, value, destinationType);
53 | }
54 |
55 | public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true;
56 | public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
57 | {
58 | var list = new List();
59 | list.Add(0f);
60 | list.Add(23.976f);
61 | list.Add(24f);
62 | list.Add(25);
63 | list.Add(29.97f);
64 | list.Add(30);
65 | list.Add(60);
66 | return new StandardValuesCollection(list);
67 | }
68 | }
69 |
70 | private class OutputEditor : UITypeEditor
71 | {
72 | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal;
73 |
74 | public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
75 | {
76 | var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
77 | if (editorService == null)
78 | return base.EditValue(context, provider, value);
79 |
80 | var options = context.Instance as DuplicatorOptions;
81 | if (options == null)
82 | return base.EditValue(context, provider, value);
83 |
84 | var adapter = options.GetAdapter();
85 | if (adapter == null)
86 | return base.EditValue(context, provider, value);
87 |
88 | var form = new ChooseOutput(adapter, value as string);
89 | if (editorService.ShowDialog(form) == DialogResult.OK)
90 | return form.DeviceName;
91 |
92 | return base.EditValue(context, provider, value);
93 | }
94 | }
95 |
96 | private class AdapterEditor : UITypeEditor
97 | {
98 | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal;
99 |
100 | public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
101 | {
102 | var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
103 | if (editorService == null)
104 | return base.EditValue(context, provider, value);
105 |
106 | var form = new ChooseAdapter(value as string);
107 | if (editorService.ShowDialog(form) == DialogResult.OK)
108 | return form.Adapter.Description1.Description;
109 |
110 | return base.EditValue(context, provider, value);
111 | }
112 | }
113 |
114 | private class RenderAudioDeviceEditor : UITypeEditor
115 | {
116 | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal;
117 |
118 | public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
119 | {
120 | var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
121 | if (editorService == null)
122 | return base.EditValue(context, provider, value);
123 |
124 | var form = new ChooseAudioDevice(value as string, AudioCapture.DataFlow.Render);
125 | if (editorService.ShowDialog(form) == DialogResult.OK)
126 | return form.Device.FriendlyName;
127 |
128 | return base.EditValue(context, provider, value);
129 | }
130 | }
131 |
132 | private class CaptureAudioDeviceEditor : UITypeEditor
133 | {
134 | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal;
135 |
136 | public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
137 | {
138 | var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
139 | if (editorService == null)
140 | return base.EditValue(context, provider, value);
141 |
142 | var form = new ChooseAudioDevice(value as string, AudioCapture.DataFlow.Capture);
143 | if (editorService.ShowDialog(form) == DialogResult.OK)
144 | return form.Device.FriendlyName;
145 |
146 | return base.EditValue(context, provider, value);
147 | }
148 | }
149 |
150 | private class FolderNameEditor : UITypeEditor
151 | {
152 | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal;
153 |
154 | public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
155 | {
156 | var browser = new FolderBrowser();
157 | if (value != null)
158 | {
159 | browser.DirectoryPath = string.Format("{0}", value);
160 | }
161 |
162 | if (browser.ShowDialog(null) == DialogResult.OK)
163 | return browser.DirectoryPath;
164 |
165 | return value;
166 | }
167 | }
168 |
169 | public class FolderBrowser
170 | {
171 | public string DirectoryPath { get; set; }
172 |
173 | public DialogResult ShowDialog(IWin32Window owner)
174 | {
175 | var hwndOwner = owner != null ? owner.Handle : GetActiveWindow();
176 | var dialog = (IFileOpenDialog)new FileOpenDialog();
177 | IShellItem item;
178 | if (!string.IsNullOrEmpty(DirectoryPath))
179 | {
180 | SHCreateItemFromParsingName(DirectoryPath, IntPtr.Zero, typeof(IShellItem).GUID, out item);
181 | if (item != null)
182 | {
183 | dialog.SetFolder(item);
184 | }
185 | }
186 |
187 | dialog.SetOptions(FOS.FOS_PICKFOLDERS | FOS.FOS_FORCEFILESYSTEM);
188 | int hr = dialog.Show(hwndOwner);
189 | if (hr == ERROR_CANCELLED)
190 | return DialogResult.Cancel;
191 |
192 | if (hr != 0)
193 | return DialogResult.Abort;
194 |
195 | dialog.GetResult(out item);
196 | item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string path);
197 | DirectoryPath = path;
198 | return DialogResult.OK;
199 | }
200 |
201 | [DllImport("shell32")]
202 | private static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv);
203 |
204 | [DllImport("user32")]
205 | private static extern IntPtr GetActiveWindow();
206 |
207 | private const int ERROR_CANCELLED = unchecked((int)0x800704C7);
208 |
209 | [ComImport]
210 | [Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")]
211 | private class FileOpenDialog { }
212 |
213 | [Guid("42f85136-db7e-439c-85f1-e4075d135fc8")]
214 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
215 | private interface IFileOpenDialog
216 | {
217 | [PreserveSig]
218 | int Show(IntPtr parent); // IModalWindow
219 | void SetFileTypes(); // not fully defined
220 | void SetFileTypeIndex([In] uint iFileType);
221 | void GetFileTypeIndex(out uint piFileType);
222 | void Advise(); // not fully defined
223 | void Unadvise();
224 | void SetOptions(FOS fos);
225 | void GetOptions(out FOS pfos);
226 | void SetDefaultFolder(IShellItem psi);
227 | void SetFolder(IShellItem psi);
228 | void GetFolder(out IShellItem ppsi);
229 | void GetCurrentSelection(out IShellItem ppsi);
230 | void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
231 | void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
232 | void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
233 | void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
234 | void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
235 | void GetResult(out IShellItem ppsi);
236 | void AddPlace(IShellItem psi, int alignment);
237 | void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
238 | void Close(int hr);
239 | void SetClientGuid(); // not fully defined
240 | void ClearClientData();
241 | void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter);
242 | void GetResults([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenum); // not fully defined
243 | void GetSelectedItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppsai); // not fully defined
244 | }
245 |
246 | [Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")]
247 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
248 | private interface IShellItem
249 | {
250 | void BindToHandler(); // not fully defined
251 | void GetParent(); // not fully defined
252 | void GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
253 | void GetAttributes(); // not fully defined
254 | void Compare(); // not fully defined
255 | }
256 |
257 | private enum SIGDN : uint
258 | {
259 | SIGDN_FILESYSPATH = 0x80058000,
260 | SIGDN_NORMALDISPLAY = 0,
261 | SIGDN_PARENTRELATIVE = 0x80080001,
262 | SIGDN_PARENTRELATIVEEDITING = 0x80031001,
263 | SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001,
264 | SIGDN_PARENTRELATIVEPARSING = 0x80018001,
265 | SIGDN_URL = 0x80068000
266 | }
267 |
268 | [Flags]
269 | private enum FOS
270 | {
271 | FOS_ALLNONSTORAGEITEMS = 0x80,
272 | FOS_ALLOWMULTISELECT = 0x200,
273 | FOS_CREATEPROMPT = 0x2000,
274 | FOS_DEFAULTNOMINIMODE = 0x20000000,
275 | FOS_DONTADDTORECENT = 0x2000000,
276 | FOS_FILEMUSTEXIST = 0x1000,
277 | FOS_FORCEFILESYSTEM = 0x40,
278 | FOS_FORCESHOWHIDDEN = 0x10000000,
279 | FOS_HIDEMRUPLACES = 0x20000,
280 | FOS_HIDEPINNEDPLACES = 0x40000,
281 | FOS_NOCHANGEDIR = 8,
282 | FOS_NODEREFERENCELINKS = 0x100000,
283 | FOS_NOREADONLYRETURN = 0x8000,
284 | FOS_NOTESTFILECREATE = 0x10000,
285 | FOS_NOVALIDATE = 0x100,
286 | FOS_OVERWRITEPROMPT = 2,
287 | FOS_PATHMUSTEXIST = 0x800,
288 | FOS_PICKFOLDERS = 0x20,
289 | FOS_SHAREAWARE = 0x4000,
290 | FOS_STRICTFILETYPES = 4
291 | }
292 | }
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/Duplicator/Duplicator/AudioCapture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Runtime.InteropServices;
7 | using System.Threading;
8 | using Microsoft.Win32.SafeHandles;
9 |
10 | namespace Duplicator
11 | {
12 | public sealed class AudioCapture : IDisposable
13 | {
14 | private AutoResetEvent _dataEvent = new AutoResetEvent(false);
15 | private AutoResetEvent _stopEvent;
16 | private AudioDevice _device;
17 | private bool _stopping;
18 | private int _waitTimeout;
19 |
20 | public event EventHandler NativeDataReady;
21 | public event EventHandler DataReady;
22 |
23 | public AudioCapture()
24 | {
25 | RaiseDataEvents = true;
26 | WaitTimeout = 100;
27 | }
28 |
29 | public bool RaiseNativeDataEvents { get; set; }
30 | public bool RaiseDataEvents { get; set; }
31 | public AudioDevice Device => _device;
32 |
33 | public int WaitTimeout
34 | {
35 | get => _waitTimeout;
36 | set => _waitTimeout = Math.Max(1, value);
37 | }
38 |
39 | public WaveFormat GetFormat()
40 | {
41 | using (var device = GetSpeakersDevice())
42 | {
43 | return GetFormat(device);
44 | }
45 | }
46 |
47 | public WaveFormat GetFormat(AudioDevice device)
48 | {
49 | if (device == null)
50 | throw new ArgumentNullException(nameof(device));
51 |
52 | var audioClient = device.ActivateClient();
53 | try
54 | {
55 | audioClient.GetMixFormat(out IntPtr format);
56 | var fex = Marshal.PtrToStructure(format);
57 | Marshal.FreeCoTaskMem(format);
58 | return new WaveFormat(fex);
59 |
60 | }
61 | finally
62 | {
63 | Marshal.ReleaseComObject(audioClient);
64 | }
65 | }
66 |
67 | public void Start() => Start(GetSpeakersDevice());
68 | public void Start(AudioDevice device) => Start(device, null);
69 | public void Start(AudioDevice device, string threadTaskName)
70 | {
71 | if (_device != null)
72 | return;
73 |
74 | var thread = new Thread(ThreadLoop);
75 | thread.Name = nameof(AudioCapture) + DateTime.Now.TimeOfDay;
76 | thread.IsBackground = true;
77 | //thread.Priority = ThreadPriority.Lowest;
78 | var state = new ThreadState();
79 | state.Device = device;
80 | state.TaskName = threadTaskName;
81 | thread.Start(state);
82 | }
83 |
84 | private class ThreadState
85 | {
86 | public string TaskName;
87 | public AudioDevice Device;
88 | }
89 |
90 | public void Stop()
91 | {
92 | if (_device == null)
93 | return;
94 |
95 | _stopEvent.Set();
96 | _stopping = true;
97 | }
98 |
99 | private void ThreadLoop(object obj)
100 | {
101 | var state = (ThreadState)obj;
102 | _stopEvent?.Dispose();
103 | _stopEvent = new AutoResetEvent(false);
104 |
105 | if (state.Device == null)
106 | {
107 | Loop(_stopEvent, state.TaskName);
108 | }
109 | else
110 | {
111 | Loop(state.Device, _stopEvent, state.TaskName);
112 | }
113 | }
114 |
115 | private static bool IsRenderDevice(AudioDevice device) => GetDevices(DataFlow.Render).Any(d => d.Id == device.Id);
116 |
117 | // loops is public in case someone wants to handle his own threading/task stuff
118 | public void Loop(WaitHandle stopHandle) => Loop(stopHandle, null);
119 | public void Loop(WaitHandle stopHandle, string threadTaskName)
120 | {
121 | using (var device = GetSpeakersDevice())
122 | {
123 | Loop(device, stopHandle, threadTaskName);
124 | }
125 | }
126 |
127 | public void Loop(AudioDevice device, WaitHandle stopHandle) => Loop(device, stopHandle, null);
128 | public void Loop(AudioDevice device, WaitHandle stopHandle, string threadTaskName)
129 | {
130 | if (device == null)
131 | throw new ArgumentNullException(nameof(device));
132 |
133 | if (stopHandle == null)
134 | throw new ArgumentNullException(nameof(stopHandle));
135 |
136 | if (_device != null)
137 | throw new InvalidOperationException("Capture loop was already started.");
138 |
139 | _device = device;
140 | bool renderDevice = IsRenderDevice(_device);
141 |
142 | if (string.IsNullOrEmpty(threadTaskName))
143 | {
144 | threadTaskName = "Audio";
145 | }
146 |
147 | var audioClient = device.ActivateClient();
148 | try
149 | {
150 | audioClient.GetMixFormat(out IntPtr format);
151 | var fex = Marshal.PtrToStructure(format);
152 | Marshal.FreeCoTaskMem(format);
153 |
154 | // ask MF to do the resampling work to PCM 16 for us
155 | fex.SubFormat = CoreAudio.KSDATAFORMAT_SUBTYPE_PCM;
156 | fex.wValidBitsPerSample = 16;
157 | fex.Format.wBitsPerSample = 16;
158 | fex.Format.nBlockAlign = (short)(fex.Format.nChannels * fex.Format.wBitsPerSample / 8);
159 | fex.Format.nAvgBytesPerSec = fex.Format.nBlockAlign * fex.Format.nSamplesPerSec;
160 |
161 | format = Marshal.AllocCoTaskMem(Marshal.SizeOf());
162 | Marshal.StructureToPtr(fex, format, false);
163 |
164 | var initFlags = CoreAudio.AUDCLNT_FLAGS.AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
165 | if (renderDevice)
166 | {
167 | initFlags |= CoreAudio.AUDCLNT_FLAGS.AUDCLNT_STREAMFLAGS_LOOPBACK;
168 | }
169 |
170 | try
171 | {
172 | audioClient.Initialize(CoreAudio.AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_SHARED, initFlags, 0, 0, format, Guid.Empty);
173 | }
174 | finally
175 | {
176 | Marshal.FreeCoTaskMem(format);
177 | }
178 |
179 | int blockAlign = fex.Format.nBlockAlign;
180 |
181 | audioClient.SetEventHandle(_dataEvent.SafeWaitHandle);
182 | audioClient.GetService(typeof(CoreAudio.IAudioCaptureClient).GUID, out object acc);
183 | var captureClient = (CoreAudio.IAudioCaptureClient)acc;
184 | try
185 | {
186 | // profiles names are stored as sub keys of HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks
187 | var task = CoreAudio.AvSetMmThreadCharacteristics(threadTaskName, out int taskIndex);
188 | if (task == IntPtr.Zero)
189 | throw new Win32Exception(Marshal.GetLastWin32Error());
190 |
191 | // reuse the same buffer
192 | byte[] data = null;
193 | try
194 | {
195 | audioClient.Start();
196 | RaiseEvents(IntPtr.Zero, 0, ref data);
197 | do
198 | {
199 | if (_stopping || _dataEvent == null) // we've been disposed
200 | break;
201 |
202 | do
203 | {
204 | int size = captureClient.GetNextPacketSize();
205 | if (size == 0)
206 | break;
207 |
208 | captureClient.GetBuffer(out IntPtr dataPtr, out int frames, out CoreAudio.AUDCLNT_BUFFERFLAGS flags, out long devPosition, out long qpcPosition);
209 | //Duplicator.Trace("frames:" + frames + " flags: " + flags);
210 | int bytesCount;
211 | if (flags.HasFlag(CoreAudio.AUDCLNT_BUFFERFLAGS.AUDCLNT_BUFFERFLAGS_SILENT))
212 | {
213 | bytesCount = 0;
214 | }
215 | else
216 | {
217 | bytesCount = frames * blockAlign;
218 | }
219 | RaiseEvents(dataPtr, bytesCount, ref data);
220 | captureClient.ReleaseBuffer(frames);
221 | }
222 | while (true);
223 |
224 | int index;
225 | try
226 | {
227 | index = WaitHandle.WaitAny(new[] { stopHandle, _dataEvent }, WaitTimeout);
228 | }
229 | catch (ObjectDisposedException)
230 | {
231 | index = 0;
232 | }
233 |
234 | if (index == WaitHandle.WaitTimeout)
235 | continue;
236 |
237 | if (index == 0) // stop
238 | break;
239 |
240 | }
241 | while (true);
242 | audioClient.Stop();
243 | }
244 | finally
245 | {
246 | CoreAudio.AvRevertMmThreadCharacteristics(task);
247 | }
248 | }
249 | finally
250 | {
251 | Marshal.ReleaseComObject(captureClient);
252 | }
253 | }
254 | finally
255 | {
256 | Marshal.ReleaseComObject(audioClient);
257 | }
258 |
259 | _device?.Dispose();
260 | _device = null;
261 | _stopping = false;
262 | }
263 |
264 | private void RaiseEvents(IntPtr dataPtr, int bytesCount, ref byte[] data)
265 | {
266 | long ticks = Stopwatch.GetTimestamp();
267 | bool handled = false;
268 | if (RaiseNativeDataEvents)
269 | {
270 | var ne = new AudioCaptureNativeDataEventArgs(dataPtr, bytesCount, ticks);
271 | NativeDataReady?.Invoke(this, ne);
272 | handled = ne.Handled;
273 | }
274 |
275 | if (!handled && RaiseDataEvents)
276 | {
277 | if (bytesCount > 0 && (data == null || data.Length < bytesCount))
278 | {
279 | data = new byte[bytesCount];
280 | }
281 |
282 | if (bytesCount > 0)
283 | {
284 | Marshal.Copy(dataPtr, data, 0, bytesCount);
285 | }
286 |
287 | var e = new AudioCaptureDataEventArgs(data, bytesCount, ticks);
288 | DataReady?.Invoke(this, e);
289 | }
290 | }
291 |
292 | public void Dispose()
293 | {
294 | // we want to handling looping in another thread
295 | var dataEvent = Interlocked.Exchange(ref _dataEvent, null);
296 | if (dataEvent != null)
297 | {
298 | _stopping = true;
299 | dataEvent.Set();
300 | dataEvent.Dispose();
301 | while (_device != null && _stopping)
302 | {
303 | Thread.Sleep(10);
304 | }
305 | }
306 | }
307 |
308 | public enum AudioDeviceState
309 | {
310 | Active = 0x00000001,
311 | Disabled = 0x00000002,
312 | NotPresent = 0x00000004,
313 | Unplugged = 0x00000008,
314 | }
315 |
316 | public static AudioDevice GetSpeakersDevice() => CreateDevice(GetSpeakers());
317 | public static AudioDevice GetMicrophoneDevice() => CreateDevice(GetMicrophone());
318 |
319 | public static IReadOnlyList GetDevices(DataFlow flow)
320 | {
321 | var list = new List();
322 | CoreAudio.IMMDeviceEnumerator deviceEnumerator = null;
323 | try
324 | {
325 | deviceEnumerator = (CoreAudio.IMMDeviceEnumerator)(new CoreAudio.MMDeviceEnumerator());
326 | }
327 | catch
328 | {
329 | }
330 |
331 | if (deviceEnumerator != null)
332 | {
333 | const int DEVICE_STATEMASK_ALL = 0x0000000f;
334 | deviceEnumerator.EnumAudioEndpoints(flow, (AudioDeviceState)DEVICE_STATEMASK_ALL, out CoreAudio.IMMDeviceCollection collection);
335 | if (collection != null)
336 | {
337 | int count = collection.GetCount();
338 | for (int i = 0; i < count; i++)
339 | {
340 | var adev = CreateDevice(collection.Item(i));
341 | if (adev != null)
342 | {
343 | list.Add(adev);
344 | }
345 | }
346 | }
347 | }
348 | return list;
349 | }
350 |
351 | private static AudioDevice CreateDevice(CoreAudio.IMMDevice dev)
352 | {
353 | if (dev == null)
354 | return null;
355 |
356 | dev.GetId(out string id);
357 | var state = dev.GetState();
358 | var store = dev.OpenPropertyStore(CoreAudio.STGM.STGM_READ);
359 | string friendlyName = GetValue(store, new CoreAudio.PROPERTYKEY { fmtid = new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"), pid = 14 });
360 | string description = GetValue(store, new CoreAudio.PROPERTYKEY { fmtid = new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"), pid = 2 });
361 | return new AudioDevice(dev, id, state, friendlyName, description);
362 | }
363 |
364 | private static string GetValue(CoreAudio.IPropertyStore ps, CoreAudio.PROPERTYKEY pk)
365 | {
366 | if (ps == null)
367 | return null;
368 |
369 | var pv = Marshal.AllocCoTaskMem(IntPtr.Size == 8 ? 24 : 16);
370 | if (ps.GetValue(ref pk, pv) != 0)
371 | return null;
372 |
373 | try
374 | {
375 | CoreAudio.PropVariantToStringAlloc(pv, out IntPtr ptr);
376 | if (ptr == IntPtr.Zero)
377 | return null;
378 |
379 | var str = Marshal.PtrToStringUni(ptr);
380 | Marshal.FreeCoTaskMem(ptr);
381 | return str;
382 | }
383 | finally
384 | {
385 | Marshal.FreeCoTaskMem(pv);
386 | }
387 | }
388 |
389 | private static CoreAudio.IMMDevice GetSpeakers()
390 | {
391 | // get the speakers (1st render + multimedia) device
392 | try
393 | {
394 | var deviceEnumerator = (CoreAudio.IMMDeviceEnumerator)(new CoreAudio.MMDeviceEnumerator());
395 | deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, CoreAudio.ERole.eMultimedia, out CoreAudio.IMMDevice speakers);
396 | return speakers;
397 | }
398 | catch
399 | {
400 | // huh? not on vista?
401 | return null;
402 | }
403 | }
404 |
405 | private static CoreAudio.IMMDevice GetMicrophone()
406 | {
407 | try
408 | {
409 | var deviceEnumerator = (CoreAudio.IMMDeviceEnumerator)(new CoreAudio.MMDeviceEnumerator());
410 | deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, CoreAudio.ERole.eMultimedia, out CoreAudio.IMMDevice mic);
411 | return mic;
412 | }
413 | catch
414 | {
415 | // huh? not on vista?
416 | return null;
417 | }
418 | }
419 |
420 | public enum DataFlow
421 | {
422 | Render,
423 | Capture,
424 | All,
425 | }
426 |
427 | // this is public so the client can choose the device
428 | public sealed class AudioDevice : IDisposable
429 | {
430 | private CoreAudio.IMMDevice _device;
431 |
432 | internal AudioDevice(CoreAudio.IMMDevice device, string id, AudioDeviceState state, string friendlyName, string description)
433 | {
434 | _device = device;
435 | Id = id;
436 | State = state;
437 | FriendlyName = friendlyName;
438 | Description = description;
439 | }
440 |
441 | public string Id { get; }
442 | public AudioDeviceState State { get; }
443 | public string FriendlyName { get; }
444 | public string Description { get; }
445 |
446 | internal CoreAudio.IAudioClient ActivateClient()
447 | {
448 | var o = _device.Activate(typeof(CoreAudio.IAudioClient).GUID, CoreAudio.CLSCTX.CLSCTX_ALL, IntPtr.Zero);
449 | return (CoreAudio.IAudioClient)o;
450 | }
451 |
452 | public override string ToString() => FriendlyName;
453 |
454 | public void Dispose() => Marshal.ReleaseComObject(_device);
455 | }
456 |
457 | internal static class CoreAudio
458 | {
459 | public static readonly Guid KSDATAFORMAT_SUBTYPE_PCM = new Guid("00000001-0000-0010-8000-00aa00389b71");
460 |
461 | [ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
462 | public class MMDeviceEnumerator { }
463 |
464 | public enum AUDCLNT_SHAREMODE
465 | {
466 | AUDCLNT_SHAREMODE_SHARED,
467 | AUDCLNT_SHAREMODE_EXCLUSIVE
468 | }
469 |
470 | [Flags]
471 | public enum AUDCLNT_BUFFERFLAGS
472 | {
473 | AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY = 0x1,
474 | AUDCLNT_BUFFERFLAGS_SILENT = 0x2,
475 | AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR = 0x4
476 | }
477 |
478 | [Flags]
479 | public enum AUDCLNT_FLAGS
480 | {
481 | AUDCLNT_STREAMFLAGS_CROSSPROCESS = 0x00010000,
482 | AUDCLNT_STREAMFLAGS_LOOPBACK = 0x00020000,
483 | AUDCLNT_STREAMFLAGS_EVENTCALLBACK = 0x00040000,
484 | AUDCLNT_STREAMFLAGS_NOPERSIST = 0x00080000,
485 | AUDCLNT_STREAMFLAGS_RATEADJUST = 0x00100000,
486 | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY = 0x08000000,
487 | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM = unchecked((int)0x80000000),
488 | AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED = 0x10000000,
489 | AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE = 0x20000000,
490 | AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED = 0x40000000,
491 | }
492 |
493 | [StructLayout(LayoutKind.Sequential, Pack = 2)]
494 | public struct WAVEFORMATEX
495 | {
496 | public ushort wFormatTag;
497 | public short nChannels;
498 | public int nSamplesPerSec;
499 | public int nAvgBytesPerSec;
500 | public short nBlockAlign;
501 | public ushort wBitsPerSample;
502 | public ushort cbSize;
503 | }
504 |
505 | [StructLayout(LayoutKind.Sequential, Pack = 2)]
506 | public struct WAVEFORMATEXTENSIBLE
507 | {
508 | public WAVEFORMATEX Format;
509 | public ushort wValidBitsPerSample;
510 | public uint dwChannelMask;
511 | public Guid SubFormat;
512 | }
513 |
514 | [StructLayout(LayoutKind.Sequential)]
515 | public struct PROPERTYKEY
516 | {
517 | public Guid fmtid;
518 | public int pid;
519 | public override string ToString() => fmtid.ToString("B") + " " + pid;
520 | }
521 |
522 | public enum STGM
523 | {
524 | STGM_READ = 0x00000000,
525 | }
526 |
527 | [Flags]
528 | public enum CLSCTX
529 | {
530 | CLSCTX_INPROC_SERVER = 0x1,
531 | CLSCTX_INPROC_HANDLER = 0x2,
532 | CLSCTX_LOCAL_SERVER = 0x4,
533 | CLSCTX_REMOTE_SERVER = 0x10,
534 | CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER
535 | }
536 |
537 | public enum ERole
538 | {
539 | eConsole,
540 | eMultimedia,
541 | eCommunications,
542 | }
543 |
544 | [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
545 | public interface IMMDeviceEnumerator
546 | {
547 | [PreserveSig]
548 | int EnumAudioEndpoints(DataFlow dataFlow, AudioDeviceState dwStateMask, out IMMDeviceCollection ppDevices);
549 |
550 | [PreserveSig]
551 | int GetDefaultAudioEndpoint(DataFlow dataFlow, ERole role, out IMMDevice ppEndpoint);
552 |
553 | [PreserveSig]
554 | int GetDevice([MarshalAs(UnmanagedType.LPWStr)] string pwstrId, out IMMDevice ppDevice);
555 | }
556 |
557 | [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
558 | public interface IMMDeviceCollection
559 | {
560 | int GetCount();
561 | IMMDevice Item(int nDevice);
562 | }
563 |
564 | [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
565 | public interface IMMDevice
566 | {
567 | [return: MarshalAs(UnmanagedType.IUnknown)]
568 | object Activate([MarshalAs(UnmanagedType.LPStruct)] Guid riid, CLSCTX dwClsCtx, IntPtr pActivationParams);
569 |
570 | IPropertyStore OpenPropertyStore(STGM stgmAccess);
571 |
572 | [PreserveSig]
573 | int GetId([MarshalAs(UnmanagedType.LPWStr)] out string ppstrId);
574 |
575 | AudioDeviceState GetState();
576 | }
577 |
578 | [Guid("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
579 | public interface IAudioClient
580 | {
581 | void Initialize(AUDCLNT_SHAREMODE ShareMode, AUDCLNT_FLAGS StreamFlags, long hnsBufferDuration, long hnsPeriodicity, /*ref WAVEFORMATEX*/ IntPtr pFormat, [MarshalAs(UnmanagedType.LPStruct)] Guid AudioSessionGuid);
582 | int GetBufferSize();
583 | long GetStreamLatency();
584 | int GetCurrentPadding();
585 |
586 | [PreserveSig]
587 | int IsFormatSupported(AUDCLNT_SHAREMODE ShareMode, ref WAVEFORMATEX pFormat, out WAVEFORMATEX ppClosestMatch);
588 |
589 | void GetMixFormat(out IntPtr ppDeviceFormat);
590 |
591 | void GetDevicePeriod(out long phnsDefaultDevicePeriod, out long phnsMinimumDevicePeriod);
592 |
593 | void Start();
594 | void Stop();
595 | void Reset();
596 | void SetEventHandle(SafeWaitHandle eventHandle);
597 | void GetService([MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppv);
598 | }
599 |
600 | [Guid("C8ADBD64-E71E-48a0-A4DE-185C395CD317"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
601 | public interface IAudioCaptureClient
602 | {
603 | void GetBuffer(out IntPtr ppData, out int NumFramesToRead, out AUDCLNT_BUFFERFLAGS pdwFlags, out long pu64DevicePosition, out long pu64QPCPosition);
604 | void ReleaseBuffer(int NumFramesRead);
605 | int GetNextPacketSize();
606 | }
607 |
608 | [Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
609 | public interface IPropertyStore
610 | {
611 | int GetCount();
612 |
613 | [PreserveSig]
614 | int GetAt(int iProp, out PROPERTYKEY pkey);
615 |
616 | [PreserveSig]
617 | int GetValue(ref PROPERTYKEY key, IntPtr pv);
618 | }
619 |
620 | [DllImport("propsys")]
621 | public static extern int PropVariantToStringAlloc(IntPtr propvar, out IntPtr ppszOut);
622 |
623 | [DllImport("avrt", SetLastError = true, CharSet = CharSet.Unicode)]
624 | public static extern IntPtr AvSetMmThreadCharacteristics([MarshalAs(UnmanagedType.LPWStr)] string TaskName, out int TaskIndex);
625 |
626 | [DllImport("avrt", SetLastError = true)]
627 | public static extern bool AvRevertMmThreadCharacteristics(IntPtr AvrtHandle);
628 | }
629 | }
630 |
631 | public class WaveFormat
632 | {
633 | internal WaveFormat(AudioCapture.CoreAudio.WAVEFORMATEXTENSIBLE fex)
634 | {
635 | ChannelsCount = fex.Format.nChannels;
636 | SamplesPerSecond = fex.Format.nSamplesPerSec;
637 | AverageBytesPerSecond = fex.Format.nAvgBytesPerSec;
638 | BitsPerSample = fex.Format.wBitsPerSample;
639 | ChannelMask = (int)fex.dwChannelMask;
640 | }
641 |
642 | public int ChannelsCount { get; }
643 | public int SamplesPerSecond { get; }
644 | public int AverageBytesPerSecond { get; }
645 | public int BitsPerSample { get; }
646 | public int ChannelMask { get; }
647 | public Guid Format { get; }
648 | }
649 |
650 | public class AudioCaptureNativeDataEventArgs : HandledEventArgs
651 | {
652 | internal AudioCaptureNativeDataEventArgs(IntPtr data, int size, long time)
653 | {
654 | Data = data;
655 | Size = size;
656 | Time = time;
657 | }
658 |
659 | public IntPtr Data { get; }
660 | public int Size { get; }
661 | public long Time { get; }
662 | }
663 |
664 | public class AudioCaptureDataEventArgs : EventArgs
665 | {
666 | internal AudioCaptureDataEventArgs(byte[] data, int size, long time)
667 | {
668 | Data = data;
669 | Size = size;
670 | Time = time;
671 | }
672 |
673 | public byte[] Data { get; }
674 | public int Size { get; }
675 | public long Time { get; }
676 | }
677 | }
678 |
--------------------------------------------------------------------------------
/Duplicator/Duplicator/DictionaryObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Concurrent;
4 | using System.Collections.Generic;
5 | using System.ComponentModel;
6 | using System.Globalization;
7 | using System.Linq;
8 | using System.Reflection;
9 | using System.Runtime.CompilerServices;
10 |
11 | namespace Duplicator
12 | {
13 | // all properties and methods start with DictionaryObject and are protected so they won't interfere with super type
14 | public abstract class DictionaryObject : INotifyPropertyChanged, INotifyPropertyChanging, IDataErrorInfo, INotifyDataErrorInfo
15 | {
16 | private ConcurrentDictionary _properties = new ConcurrentDictionary();
17 |
18 | protected DictionaryObject()
19 | {
20 | DictionaryObjectRaiseOnPropertyChanging = true;
21 | DictionaryObjectRaiseOnPropertyChanged = true;
22 | DictionaryObjectRaiseOnErrorsChanged = true;
23 | }
24 |
25 | protected virtual ConcurrentDictionary DictionaryObjectProperties => _properties;
26 |
27 | // these PropertyChangxxx are public and don't start with BaseObject because used by everyone
28 | public event PropertyChangingEventHandler PropertyChanging;
29 | public event PropertyChangedEventHandler PropertyChanged;
30 | public event EventHandler ErrorsChanged;
31 | public event EventHandler PropertyRollback;
32 |
33 | protected virtual bool DictionaryObjectRaiseOnPropertyChanging { get; set; }
34 | protected virtual bool DictionaryObjectRaiseOnPropertyChanged { get; set; }
35 | protected virtual bool DictionaryObjectRaiseOnErrorsChanged { get; set; }
36 |
37 | protected string DictionaryObjectError => DictionaryObjectGetError(null);
38 | protected bool DictionaryObjectHasErrors => (DictionaryObjectGetErrors(null)?.Cast