├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── dotnet-desktop.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── Sample
├── App.xaml
├── App.xaml.cs
├── BooleanToObjectConverter.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Recorder.cs
├── Sample.csproj
├── SettingsWindow.xaml
├── SettingsWindow.xaml.cs
├── Styles.xaml
├── WindowMoveBehavior.cs
├── lameenc32.dll
└── lameenc64.dll
├── SharpAvi.ImageSharp
├── Codecs
│ ├── ImageSharpEncoderStreamFactory.cs
│ └── MJpegImageSharpVideoEncoder.cs
├── SharpAvi.ImageSharp.csproj
└── readme.md
├── SharpAvi.sln
└── SharpAvi
├── AudioFormats.cs
├── BitsPerPixel.cs
├── CodecIds.cs
├── Codecs
├── CodecInfo.cs
├── EncodingStreamFactory.cs
├── IAudioEncoder.cs
├── IVideoEncoder.cs
├── MJpegWpfVideoEncoder.cs
├── Mp3LameAudioEncoder.ILameFacade.cs
├── Mp3LameAudioEncoder.LameFacadeImpl.cs
├── Mp3LameAudioEncoder.cs
├── Mpeg4VcmVideoEncoder.cs
├── SingleThreadedVideoEncoderWrapper.cs
├── UncompressedVideoEncoder.cs
└── VfwApi.cs
├── Format
├── Index1Entry.cs
├── IndexType.cs
├── KnownFourCCs.cs
├── MainHeaderFlags.cs
├── StandardIndexEntry.cs
├── StreamHeaderFlags.cs
└── SuperIndexEntry.cs
├── FourCC.cs
├── Output
├── AsyncAudioStreamWrapper.cs
├── AsyncVideoStreamWrapper.cs
├── AudioStreamWrapperBase.cs
├── AviAudioStream.cs
├── AviStreamBase.cs
├── AviStreamInfo.cs
├── AviVideoStream.cs
├── AviWriter.cs
├── EncodingAudioStreamWrapper.cs
├── EncodingVideoStreamWrapper.cs
├── IAviAudioStream.cs
├── IAviAudioStreamInternal.cs
├── IAviStream.cs
├── IAviStreamInternal.cs
├── IAviStreamWriteHandler.cs
├── IAviVideoStream.cs
├── IAviVideoStreamInternal.cs
├── RiffItem.cs
├── RiffWriterExtensions.cs
└── VideoStreamWrapperBase.cs
├── SharpAvi.csproj
├── Utilities
├── Argument.cs
├── AviUtils.cs
├── BitmapUtils.cs
├── RedirectDllResolver.cs
├── SequentialInvoker.cs
└── SingleThreadTaskScheduler.cs
└── readme.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # CS0419: Ambiguous reference in cref attribute
4 | dotnet_diagnostic.CS0419.severity = silent
5 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-desktop.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 |
6 | # This workflow will build, test, sign and package a WPF or Windows Forms desktop application
7 | # built on .NET Core.
8 | # To learn how to migrate your existing application to .NET Core,
9 | # refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework
10 | #
11 | # To configure this workflow:
12 | #
13 | # 1. Configure environment variables
14 | # GitHub sets default environment variables for every workflow run.
15 | # Replace the variables relative to your project in the "env" section below.
16 | #
17 | # 2. Signing
18 | # Generate a signing certificate in the Windows Application
19 | # Packaging Project or add an existing signing certificate to the project.
20 | # Next, use PowerShell to encode the .pfx file using Base64 encoding
21 | # by running the following Powershell script to generate the output string:
22 | #
23 | # $pfx_cert = Get-Content '.\SigningCertificate.pfx' -Encoding Byte
24 | # [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt'
25 | #
26 | # Open the output file, SigningCertificate_Encoded.txt, and copy the
27 | # string inside. Then, add the string to the repo as a GitHub secret
28 | # and name it "Base64_Encoded_Pfx."
29 | # For more information on how to configure your signing certificate for
30 | # this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing
31 | #
32 | # Finally, add the signing certificate password to the repo as a secret and name it "Pfx_Key".
33 | # See "Build the Windows Application Packaging project" below to see how the secret is used.
34 | #
35 | # For more information on GitHub Actions, refer to https://github.com/features/actions
36 | # For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications,
37 | # refer to https://github.com/microsoft/github-actions-for-desktop-apps
38 |
39 | name: .NET Core Desktop
40 |
41 | on:
42 | push:
43 | branches: [ master ]
44 | pull_request:
45 | branches: [ master ]
46 |
47 | jobs:
48 |
49 | build:
50 |
51 | strategy:
52 | matrix:
53 | configuration: [Debug, Release]
54 |
55 | runs-on: windows-latest # For a list of available runner types, refer to
56 | # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
57 |
58 | env:
59 | Solution_Name: SharpAvi.sln # Replace with your solution name, i.e. MyWpfApp.sln.
60 | #Test_Project_Path: your-test-project-path # Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj.
61 |
62 | steps:
63 | - name: Checkout
64 | uses: actions/checkout@v2
65 | with:
66 | fetch-depth: 0
67 |
68 | # Install the .NET Core workload
69 | - name: Install .NET Core
70 | uses: actions/setup-dotnet@v1
71 | with:
72 | dotnet-version: 6.0.x
73 |
74 | # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
75 | - name: Setup MSBuild.exe
76 | uses: microsoft/setup-msbuild@v1.0.2
77 |
78 | # Execute all unit tests in the solution
79 | - name: Execute unit tests
80 | run: dotnet test
81 |
82 | # Restore the application to populate the obj folder with RuntimeIdentifiers
83 | - name: Restore the application
84 | run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration
85 | env:
86 | Configuration: ${{ matrix.configuration }}
87 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## Visual Studio
34 | #################
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | *.suo
41 | *.user
42 | *.sln.docstates
43 |
44 | # Build results
45 |
46 | [Dd]ebug/
47 | [Rr]elease/
48 | x64/
49 | build/
50 | [Bb]in/
51 | [Oo]bj/
52 |
53 | # MSTest test Results
54 | [Tt]est[Rr]esult*/
55 | [Bb]uild[Ll]og.*
56 |
57 | *_i.c
58 | *_p.c
59 | *.ilk
60 | *.meta
61 | *.obj
62 | *.pch
63 | *.pdb
64 | *.pgc
65 | *.pgd
66 | *.rsp
67 | *.sbr
68 | *.tlb
69 | *.tli
70 | *.tlh
71 | *.tmp
72 | *.tmp_proj
73 | *.log
74 | *.vspscc
75 | *.vssscc
76 | .builds
77 | *.pidb
78 | *.log
79 | *.scc
80 |
81 | # Visual C++ cache files
82 | ipch/
83 | *.aps
84 | *.ncb
85 | *.opensdf
86 | *.sdf
87 | *.cachefile
88 |
89 | # Visual Studio profiler
90 | *.psess
91 | *.vsp
92 | *.vspx
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 |
101 | # TeamCity is a build add-in
102 | _TeamCity*
103 |
104 | # DotCover is a Code Coverage Tool
105 | *.dotCover
106 |
107 | # NCrunch
108 | *.ncrunch*
109 | .*crunch*.local.xml
110 |
111 | # Installshield output folder
112 | [Ee]xpress/
113 |
114 | # DocProject is a documentation generator add-in
115 | DocProject/buildhelp/
116 | DocProject/Help/*.HxT
117 | DocProject/Help/*.HxC
118 | DocProject/Help/*.hhc
119 | DocProject/Help/*.hhk
120 | DocProject/Help/*.hhp
121 | DocProject/Help/Html2
122 | DocProject/Help/html
123 |
124 | # Click-Once directory
125 | publish/
126 |
127 | # Publish Web Output
128 | *.Publish.xml
129 | *.pubxml
130 |
131 | # NuGet Packages Directory
132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
133 | #packages/
134 |
135 | # Windows Azure Build Output
136 | csx
137 | *.build.csdef
138 |
139 | # Windows Store app package directory
140 | AppPackages/
141 |
142 | # Others
143 | sql/
144 | *.Cache
145 | ClientBin/
146 | [Ss]tyle[Cc]op.*
147 | ~$*
148 | *~
149 | *.dbmdl
150 | *.[Pp]ublish.xml
151 | *.pfx
152 | *.publishsettings
153 |
154 | # RIA/Silverlight projects
155 | Generated_Code/
156 |
157 | # Backup & report files from converting an old project file to a newer
158 | # Visual Studio version. Backup files are not needed, because we have git ;-)
159 | _UpgradeReport_Files/
160 | Backup*/
161 | UpgradeLog*.XML
162 | UpgradeLog*.htm
163 |
164 | # SQL Server files
165 | App_Data/*.mdf
166 | App_Data/*.ldf
167 |
168 | #############
169 | ## Windows detritus
170 | #############
171 |
172 | # Windows image file caches
173 | Thumbs.db
174 | ehthumbs.db
175 |
176 | # Folder config file
177 | Desktop.ini
178 |
179 | # Recycle Bin used on file shares
180 | $RECYCLE.BIN/
181 |
182 | # Mac crap
183 | .DS_Store
184 |
185 |
186 | #############
187 | ## Python
188 | #############
189 |
190 | *.py[co]
191 |
192 | # Packages
193 | *.egg
194 | *.egg-info
195 | dist/
196 | build/
197 | eggs/
198 | parts/
199 | var/
200 | sdist/
201 | develop-eggs/
202 | .installed.cfg
203 |
204 | # Installer logs
205 | pip-log.txt
206 |
207 | # Unit test / coverage reports
208 | .coverage
209 | .tox
210 |
211 | #Translations
212 | *.mo
213 |
214 | #Mr Developer
215 | .mr.developer.cfg
216 | packages/
217 | .vs/
218 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2022 Vasili Maslov
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **SharpAvi** is a simple .NET library for creating video files in the AVI format.
2 |
3 | If you want to render some video sequence, but do not want to touch native APIs like DirectShow or to depend on command-line utilities like FFmpeg then **SharpAvi** may be what you need.
4 | Writing uncompressed AVI does not require any external dependencies, it's a pure .NET code.
5 | Files are produced in compliance with the [OpenDML extensions](http://www.jmcgowan.com/avitech.html#OpenDML) which allow (nearly) unlimited file size (no 2GB limit).
6 |
7 | A video is created by supplying individual in-memory bitmaps and audio samples.
8 | There are a few built-in encoders for video and audio.
9 | Output format is always AVI, regardless of a specific codec used.
10 |
11 | :book: To get started, jump to [the project's docs](https://bassill.github.io/SharpAvi).
12 |
13 | The project is published to NuGet as a few packages:
14 | * [SharpAvi
15 | ](https://www.nuget.org/packages/SharpAvi/)
16 | Contains core functions and some encoders which do not depend on external packages.
17 | * A Motion JPEG video encoder based on WPF.
18 | * An MPEG-4 video encoder based on the Video for Windows (VFW) API.
19 | * [LAME](https://lame.sourceforge.io/)-based MP3 audio encoder.
20 | * [SharpAvi.ImageSharp
21 | ](https://www.nuget.org/packages/SharpAvi.ImageSharp/)
22 | Contains a Motion JPEG video encoder based on [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp).
23 | It's a cross-platform alternative to the WPF-based encoder.
24 |
25 | ***
26 |
27 | A bit of history. This project has started on [CodePlex](https://sharpavi.codeplex.com/) in 2013. Then the repository was mirrored to GitHub for some time, until the project has been moved to GitHub completely in the middle of 2017.
28 |
--------------------------------------------------------------------------------
/Sample/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Sample/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Reflection;
4 | using System.Windows;
5 | using SharpAvi.Codecs;
6 |
7 | namespace SharpAvi.Sample
8 | {
9 | ///
10 | /// Interaction logic for App.xaml
11 | ///
12 | public partial class App : Application
13 | {
14 | protected override void OnStartup(StartupEventArgs e)
15 | {
16 | base.OnStartup(e);
17 |
18 | // Set LAME DLL path for MP3 encoder
19 | var asmDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
20 | var dllName = string.Format("lameenc{0}.dll", Environment.Is64BitProcess ? "64" : "32");
21 | Mp3LameAudioEncoder.SetLameDllLocation(Path.Combine(asmDir, dllName));
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sample/BooleanToObjectConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Data;
3 |
4 | namespace SharpAvi.Sample
5 | {
6 | public class BooleanToObjectConverter : IValueConverter
7 | {
8 | public object FalseValue { get; set; }
9 | public object TrueValue { get; set; }
10 |
11 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
12 | {
13 | return Equals(value, true) ? TrueValue : FalseValue;
14 | }
15 |
16 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
17 | {
18 | return Equals(value, TrueValue) ? true : Equals(value, FalseValue) ? false : (bool?)null;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sample/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
27 |
28 |
38 |
39 |
49 |
50 |
54 |
55 |
61 |
62 |
83 |
84 |
105 |
106 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/Sample/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using NAudio.Wave;
2 | using SharpAvi.Codecs;
3 | using System;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Windows;
7 | using System.Windows.Threading;
8 |
9 | namespace SharpAvi.Sample
10 | {
11 | ///
12 | /// Interaction logic for MainWindow.xaml
13 | ///
14 | public partial class MainWindow : Window
15 | {
16 | public MainWindow()
17 | {
18 | InitializeComponent();
19 |
20 | recordingTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
21 | recordingTimer.Tick += recordingTimer_Tick;
22 | DataContext = this;
23 |
24 | InitDefaultSettings();
25 |
26 | WindowMoveBehavior.Attach(this);
27 | }
28 |
29 |
30 | #region Recording
31 |
32 | private readonly DispatcherTimer recordingTimer;
33 | private readonly Stopwatch recordingStopwatch = new Stopwatch();
34 | private Recorder recorder;
35 | private string lastFileName;
36 |
37 | private static readonly DependencyPropertyKey IsRecordingPropertyKey =
38 | DependencyProperty.RegisterReadOnly("IsRecording", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));
39 | public static readonly DependencyProperty IsRecordingProperty = IsRecordingPropertyKey.DependencyProperty;
40 |
41 | public bool IsRecording
42 | {
43 | get { return (bool)GetValue(IsRecordingProperty); }
44 | private set { SetValue(IsRecordingPropertyKey, value); }
45 | }
46 |
47 | private static readonly DependencyPropertyKey ElapsedPropertyKey =
48 | DependencyProperty.RegisterReadOnly("Elapsed", typeof(string), typeof(MainWindow), new PropertyMetadata(string.Empty));
49 | public static readonly DependencyProperty ElapsedProperty = ElapsedPropertyKey.DependencyProperty;
50 |
51 | public string Elapsed
52 | {
53 | get { return (string)GetValue(ElapsedProperty); }
54 | private set { SetValue(ElapsedPropertyKey, value); }
55 | }
56 |
57 | private static readonly DependencyPropertyKey HasLastScreencastPropertyKey =
58 | DependencyProperty.RegisterReadOnly("HasLastScreencast", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));
59 | public static readonly DependencyProperty HasLastScreencastProperty = HasLastScreencastPropertyKey.DependencyProperty;
60 |
61 | public bool HasLastScreencast
62 | {
63 | get { return (bool)GetValue(HasLastScreencastProperty); }
64 | private set { SetValue(HasLastScreencastPropertyKey, value); }
65 | }
66 |
67 | private void StartRecording()
68 | {
69 | if (IsRecording)
70 | throw new InvalidOperationException("Already recording.");
71 |
72 | if (minimizeOnStart)
73 | WindowState = WindowState.Minimized;
74 |
75 | Elapsed = "00:00";
76 | HasLastScreencast = false;
77 | IsRecording = true;
78 |
79 | recordingStopwatch.Reset();
80 | recordingTimer.Start();
81 |
82 | lastFileName = System.IO.Path.Combine(outputFolder, DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + ".avi");
83 | var bitRate = Mp3LameAudioEncoder.SupportedBitRates.OrderBy(br => br).ElementAt(audioQuality);
84 | recorder = new Recorder(lastFileName,
85 | encoder, encodingQuality,
86 | audioSourceIndex, audioWaveFormat, encodeAudio, bitRate);
87 |
88 | recordingStopwatch.Start();
89 | }
90 |
91 | private void StopRecording()
92 | {
93 | if (!IsRecording)
94 | throw new InvalidOperationException("Not recording.");
95 |
96 | try
97 | {
98 | recorder?.Dispose();
99 | recorder = null;
100 | }
101 | finally
102 | {
103 | recordingTimer.Stop();
104 | recordingStopwatch.Stop();
105 |
106 | IsRecording = false;
107 | HasLastScreencast = true;
108 |
109 | WindowState = WindowState.Normal;
110 | }
111 | }
112 |
113 | private void recordingTimer_Tick(object sender, EventArgs e)
114 | {
115 | var elapsed = recordingStopwatch.Elapsed;
116 | Elapsed = string.Format(
117 | "{0:00}:{1:00}",
118 | Math.Floor(elapsed.TotalMinutes),
119 | elapsed.Seconds);
120 | }
121 |
122 | #endregion
123 |
124 |
125 | #region Settings
126 |
127 | private string outputFolder;
128 | private FourCC encoder;
129 | private int encodingQuality;
130 | private int audioSourceIndex;
131 | private SupportedWaveFormat audioWaveFormat;
132 | private bool encodeAudio;
133 | private int audioQuality;
134 | private bool minimizeOnStart;
135 |
136 | private void InitDefaultSettings()
137 | {
138 | var exePath = new Uri(System.Reflection.Assembly.GetEntryAssembly().Location).LocalPath;
139 | outputFolder = System.IO.Path.GetDirectoryName(exePath);
140 |
141 | encoder = CodecIds.MotionJpeg;
142 | encodingQuality = 70;
143 |
144 | audioSourceIndex = -1;
145 | audioWaveFormat = SupportedWaveFormat.WAVE_FORMAT_44M16;
146 | encodeAudio = true;
147 | audioQuality = (Mp3LameAudioEncoder.SupportedBitRates.Length + 1) / 2;
148 |
149 | minimizeOnStart = true;
150 | }
151 |
152 | private void ShowSettingsDialog()
153 | {
154 | var dlg = new SettingsWindow()
155 | {
156 | Owner = this,
157 | Folder = outputFolder,
158 | Encoder = encoder,
159 | Quality = encodingQuality,
160 | SelectedAudioSourceIndex = audioSourceIndex,
161 | AudioWaveFormat = audioWaveFormat,
162 | EncodeAudio = encodeAudio,
163 | AudioQuality = audioQuality,
164 | MinimizeOnStart = minimizeOnStart
165 | };
166 |
167 | if (dlg.ShowDialog() == true)
168 | {
169 | outputFolder = dlg.Folder;
170 | encoder = dlg.Encoder;
171 | encodingQuality = dlg.Quality;
172 | audioSourceIndex = dlg.SelectedAudioSourceIndex;
173 | audioWaveFormat = dlg.AudioWaveFormat;
174 | encodeAudio = dlg.EncodeAudio;
175 | audioQuality = dlg.AudioQuality;
176 | minimizeOnStart = dlg.MinimizeOnStart;
177 | }
178 | }
179 |
180 | #endregion
181 |
182 |
183 | private void StartRecording_Click(object sender, RoutedEventArgs e)
184 | {
185 | try
186 | {
187 | StartRecording();
188 | }
189 | catch (Exception ex)
190 | {
191 | MessageBox.Show("Error starting recording\r\n" + ex.ToString());
192 | StopRecording();
193 | }
194 | }
195 |
196 | private void StopRecording_Click(object sender, RoutedEventArgs e)
197 | {
198 | try
199 | {
200 | StopRecording();
201 | }
202 | catch (Exception ex)
203 | {
204 | MessageBox.Show("Error stopping recording\r\n" + ex.ToString());
205 | }
206 | }
207 |
208 | private void GoToLastScreencast_Click(object sender, RoutedEventArgs e)
209 | {
210 | Process.Start("explorer.exe", string.Format("/select, \"{0}\"", lastFileName));
211 | }
212 |
213 | private void Settings_Click(object sender, RoutedEventArgs e)
214 | {
215 | ShowSettingsDialog();
216 | }
217 |
218 | private void Exit_Click(object sender, RoutedEventArgs e)
219 | {
220 | if (IsRecording)
221 | StopRecording();
222 |
223 | Close();
224 | }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/Sample/Recorder.cs:
--------------------------------------------------------------------------------
1 | using NAudio.Wave;
2 | using SharpAvi.Codecs;
3 | using SharpAvi.Output;
4 | using System;
5 | using System.Diagnostics;
6 | using System.Drawing;
7 | using System.Drawing.Imaging;
8 | using System.Runtime.InteropServices;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 | using System.Windows;
12 | using System.Windows.Interop;
13 |
14 | namespace SharpAvi.Sample
15 | {
16 | internal class Recorder : IDisposable
17 | {
18 | public static readonly FourCC MJPEG_IMAGE_SHARP = "IMG#";
19 |
20 | private readonly int screenWidth;
21 | private readonly int screenHeight;
22 | private readonly AviWriter writer;
23 | private readonly IAviVideoStream videoStream;
24 | private readonly IAviAudioStream audioStream;
25 | private readonly WaveInEvent audioSource;
26 | private readonly Thread screenThread;
27 | private readonly ManualResetEvent stopThread = new ManualResetEvent(false);
28 | private readonly AutoResetEvent videoFrameWritten = new AutoResetEvent(false);
29 | private readonly AutoResetEvent audioBlockWritten = new AutoResetEvent(false);
30 |
31 | public Recorder(string fileName,
32 | FourCC codec, int quality,
33 | int audioSourceIndex, SupportedWaveFormat audioWaveFormat, bool encodeAudio, int audioBitRate)
34 | {
35 | System.Windows.Media.Matrix toDevice;
36 | using (var source = new HwndSource(new HwndSourceParameters()))
37 | {
38 | toDevice = source.CompositionTarget.TransformToDevice;
39 | }
40 |
41 | screenWidth = (int)Math.Round(SystemParameters.PrimaryScreenWidth * toDevice.M11);
42 | screenHeight = (int)Math.Round(SystemParameters.PrimaryScreenHeight * toDevice.M22);
43 |
44 | // Create AVI writer and specify FPS
45 | writer = new AviWriter(fileName)
46 | {
47 | FramesPerSecond = 10,
48 | EmitIndex1 = true,
49 | };
50 |
51 | // Create video stream
52 | videoStream = CreateVideoStream(codec, quality);
53 | // Set only name. Other properties were when creating stream,
54 | // either explicitly by arguments or implicitly by the encoder used
55 | videoStream.Name = "Screencast";
56 |
57 | if (audioSourceIndex >= 0)
58 | {
59 | var waveFormat = ToWaveFormat(audioWaveFormat);
60 |
61 | audioStream = CreateAudioStream(waveFormat, encodeAudio, audioBitRate);
62 | // Set only name. Other properties were when creating stream,
63 | // either explicitly by arguments or implicitly by the encoder used
64 | audioStream.Name = "Voice";
65 |
66 | audioSource = new WaveInEvent
67 | {
68 | DeviceNumber = audioSourceIndex,
69 | WaveFormat = waveFormat,
70 | // Buffer size to store duration of 1 frame
71 | BufferMilliseconds = (int)Math.Ceiling(1000 / writer.FramesPerSecond),
72 | NumberOfBuffers = 3,
73 | };
74 | audioSource.DataAvailable += audioSource_DataAvailable;
75 | }
76 |
77 | screenThread = new Thread(RecordScreen)
78 | {
79 | Name = typeof(Recorder).Name + ".RecordScreen",
80 | IsBackground = true
81 | };
82 |
83 | if (audioSource != null)
84 | {
85 | videoFrameWritten.Set();
86 | audioBlockWritten.Reset();
87 | audioSource.StartRecording();
88 | }
89 | screenThread.Start();
90 | }
91 |
92 | private IAviVideoStream CreateVideoStream(FourCC codec, int quality)
93 | {
94 | // Select encoder type based on FOURCC of codec
95 | if (codec == CodecIds.Uncompressed)
96 | {
97 | return writer.AddUncompressedVideoStream(screenWidth, screenHeight);
98 | }
99 | else if (codec == CodecIds.MotionJpeg)
100 | {
101 | // Use M-JPEG based on WPF (Windows only)
102 | return writer.AddMJpegWpfVideoStream(screenWidth, screenHeight, quality);
103 | }
104 | else if (codec == MJPEG_IMAGE_SHARP)
105 | {
106 | // Use M-JPEG based on the SixLabors.ImageSharp package (cross-platform)
107 | // Included in the SharpAvi.ImageSharp package
108 | return writer.AddMJpegImageSharpVideoStream(screenWidth, screenHeight, quality);
109 | }
110 | else
111 | {
112 | return writer.AddMpeg4VcmVideoStream(screenWidth, screenHeight, (double)writer.FramesPerSecond,
113 | // It seems that all tested MPEG-4 VfW codecs ignore the quality affecting parameters passed through VfW API
114 | // They only respect the settings from their own configuration dialogs, and Mpeg4VideoEncoder currently has no support for this
115 | quality: quality,
116 | codec: codec,
117 | // Most of VfW codecs expect single-threaded use, so we wrap this encoder to special wrapper
118 | // Thus all calls to the encoder (including its instantiation) will be invoked on a single thread although encoding (and writing) is performed asynchronously
119 | forceSingleThreadedAccess: true);
120 | }
121 | }
122 |
123 | private IAviAudioStream CreateAudioStream(WaveFormat waveFormat, bool encode, int bitRate)
124 | {
125 | // Create encoding or simple stream based on settings
126 | if (encode)
127 | {
128 | // LAME DLL path is set in App.OnStartup()
129 | return writer.AddMp3LameAudioStream(waveFormat.Channels, waveFormat.SampleRate, bitRate);
130 | }
131 | else
132 | {
133 | return writer.AddAudioStream(
134 | channelCount: waveFormat.Channels,
135 | samplesPerSecond: waveFormat.SampleRate,
136 | bitsPerSample: waveFormat.BitsPerSample);
137 | }
138 | }
139 |
140 | private static WaveFormat ToWaveFormat(SupportedWaveFormat waveFormat)
141 | {
142 | switch (waveFormat)
143 | {
144 | case SupportedWaveFormat.WAVE_FORMAT_44M16:
145 | return new WaveFormat(44100, 16, 1);
146 | case SupportedWaveFormat.WAVE_FORMAT_44S16:
147 | return new WaveFormat(44100, 16, 2);
148 | default:
149 | throw new NotSupportedException("Wave formats other than '16-bit 44.1kHz' are not currently supported.");
150 | }
151 | }
152 |
153 | public void Dispose()
154 | {
155 | stopThread.Set();
156 | screenThread.Join();
157 | if (audioSource != null)
158 | {
159 | audioSource.StopRecording();
160 | audioSource.DataAvailable -= audioSource_DataAvailable;
161 | }
162 |
163 | // Close writer: the remaining data is written to a file and file is closed
164 | writer.Close();
165 |
166 | stopThread.Close();
167 | }
168 |
169 | private void RecordScreen()
170 | {
171 | var stopwatch = new Stopwatch();
172 | var buffer = new byte[screenWidth * screenHeight * 4];
173 | Task videoWriteTask = null;
174 | var isFirstFrame = true;
175 | var shotsTaken = 0;
176 | var timeTillNextFrame = TimeSpan.Zero;
177 | stopwatch.Start();
178 |
179 | while (!stopThread.WaitOne(timeTillNextFrame))
180 | {
181 | GetScreenshot(buffer);
182 | shotsTaken++;
183 |
184 | // Wait for the previous frame is written
185 | if (!isFirstFrame)
186 | {
187 | videoWriteTask.Wait();
188 | videoFrameWritten.Set();
189 | }
190 |
191 | if (audioStream != null)
192 | {
193 | var signalled = WaitHandle.WaitAny(new WaitHandle[] {audioBlockWritten, stopThread});
194 | if (signalled == 1)
195 | break;
196 | }
197 |
198 | // Start asynchronous (encoding and) writing of the new frame
199 | // Overloads with Memory parameters are available on .NET 5+
200 | #if NET5_0_OR_GREATER
201 | videoWriteTask = videoStream.WriteFrameAsync(true, buffer.AsMemory(0, buffer.Length));
202 | #else
203 | videoWriteTask = videoStream.WriteFrameAsync(true, buffer, 0, buffer.Length);
204 | #endif
205 |
206 | timeTillNextFrame = TimeSpan.FromSeconds(shotsTaken / (double)writer.FramesPerSecond - stopwatch.Elapsed.TotalSeconds);
207 | if (timeTillNextFrame < TimeSpan.Zero)
208 | timeTillNextFrame = TimeSpan.Zero;
209 |
210 | isFirstFrame = false;
211 | }
212 |
213 | stopwatch.Stop();
214 |
215 | // Wait for the last frame is written
216 | if (!isFirstFrame)
217 | {
218 | videoWriteTask.Wait();
219 | }
220 | }
221 |
222 | private void GetScreenshot(byte[] buffer)
223 | {
224 | using (var bitmap = new Bitmap(screenWidth, screenHeight))
225 | using (var graphics = Graphics.FromImage(bitmap))
226 | {
227 | graphics.CopyFromScreen(0, 0, 0, 0, new System.Drawing.Size(screenWidth, screenHeight));
228 | var bits = bitmap.LockBits(new Rectangle(0, 0, screenWidth, screenHeight), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
229 | Marshal.Copy(bits.Scan0, buffer, 0, buffer.Length);
230 | bitmap.UnlockBits(bits);
231 |
232 | // Should also capture the mouse cursor here, but skipping for simplicity
233 | // For those who are interested, look at http://www.codeproject.com/Articles/12850/Capturing-the-Desktop-Screen-with-the-Mouse-Cursor
234 | }
235 | }
236 |
237 | private void audioSource_DataAvailable(object sender, WaveInEventArgs e)
238 | {
239 | var signalled = WaitHandle.WaitAny(new WaitHandle[] { videoFrameWritten, stopThread });
240 | if (signalled == 0 && e.BytesRecorded > 0)
241 | {
242 | // Overloads with Span parameters are available on .NET 5+
243 | #if NET5_0_OR_GREATER
244 | audioStream.WriteBlock(e.Buffer.AsSpan(0, e.BytesRecorded));
245 | #else
246 | audioStream.WriteBlock(e.Buffer, 0, e.BytesRecorded);
247 | #endif
248 | audioBlockWritten.Set();
249 | }
250 | }
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/Sample/Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48;net6.0-windows
4 | x86
5 | WinExe
6 | SharpAvi.Sample
7 | true
8 | true
9 | true
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 | PreserveNewest
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Sample/SettingsWindow.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
18 |
19 |
24 |
27 |
29 |
31 |
32 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
46 |
48 |
49 |
50 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
69 |
72 | Switch target platform to 'x86' to use 32-bit VfW codecs.
73 |
74 |
75 |
76 |
81 |
82 |
83 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
104 |
105 |
108 |
109 |
112 |
114 |
117 |
125 |
126 |
127 |
128 |
131 | Minimize on capture start
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/Sample/SettingsWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using NAudio.Wave;
2 | using SharpAvi.Codecs;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Windows;
7 | using System.Windows.Forms;
8 | using System.Windows.Interop;
9 |
10 | namespace SharpAvi.Sample
11 | {
12 | ///
13 | /// Interaction logic for SettingsWindow.xaml
14 | ///
15 | public partial class SettingsWindow : Window, System.Windows.Forms.IWin32Window
16 | {
17 | public SettingsWindow()
18 | {
19 | InitializeComponent();
20 |
21 | InitAvailableCodecs();
22 |
23 | InitAvailableAudioSources();
24 |
25 | AudioQuality = (MaximumAudioQuality + 1) / 2;
26 |
27 | DataContext = this;
28 |
29 | WindowMoveBehavior.Attach(this);
30 | }
31 |
32 | private void InitAvailableCodecs()
33 | {
34 | var codecs = new List();
35 | codecs.Add(new CodecInfo(CodecIds.Uncompressed, "(none)"));
36 | codecs.Add(new CodecInfo(CodecIds.MotionJpeg, "Motion JPEG (WPF)"));
37 | codecs.Add(new CodecInfo(Recorder.MJPEG_IMAGE_SHARP, "Motion JPEG (ImageSharp)"));
38 | codecs.AddRange(Mpeg4VcmVideoEncoder.GetAvailableCodecs());
39 | AvailableCodecs = codecs;
40 | }
41 |
42 | private void InitAvailableAudioSources()
43 | {
44 | var deviceList = new Dictionary();
45 | deviceList.Add(-1, "(no sound)");
46 | for (var i = 0; i < WaveInEvent.DeviceCount; i++)
47 | {
48 | var caps = WaveInEvent.GetCapabilities(i);
49 | if (audioFormats.All(caps.SupportsWaveFormat))
50 | {
51 | deviceList.Add(i, caps.ProductName);
52 | }
53 | }
54 | AvailableAudioSources = deviceList;
55 | SelectedAudioSourceIndex = -1;
56 | }
57 |
58 |
59 | public static readonly DependencyProperty FolderProperty =
60 | DependencyProperty.Register("Folder", typeof(string), typeof(SettingsWindow));
61 |
62 | public string Folder
63 | {
64 | get { return (string)GetValue(FolderProperty); }
65 | set { SetValue(FolderProperty, value); }
66 | }
67 |
68 | public static readonly DependencyProperty EncoderProperty =
69 | DependencyProperty.Register("Encoder", typeof(FourCC), typeof(SettingsWindow));
70 |
71 | public FourCC Encoder
72 | {
73 | get { return (FourCC)GetValue(EncoderProperty); }
74 | set { SetValue(EncoderProperty, value); }
75 | }
76 |
77 | public static readonly DependencyProperty QualityProperty =
78 | DependencyProperty.Register("Quality", typeof(int), typeof(SettingsWindow));
79 |
80 | public int Quality
81 | {
82 | get { return (int)GetValue(QualityProperty); }
83 | set { SetValue(QualityProperty, value); }
84 | }
85 |
86 | public static readonly DependencyProperty SelectedAudioSourceIndexProperty =
87 | DependencyProperty.Register("SelectedAudioSourceIndex", typeof(int), typeof(SettingsWindow));
88 |
89 | public int SelectedAudioSourceIndex
90 | {
91 | get { return (int)GetValue(SelectedAudioSourceIndexProperty); }
92 | set { SetValue(SelectedAudioSourceIndexProperty, value); }
93 | }
94 |
95 | public static readonly DependencyProperty UseStereoProperty =
96 | DependencyProperty.Register("UseStereo", typeof(bool), typeof(SettingsWindow),
97 | new PropertyMetadata(false));
98 |
99 | public bool UseStereo
100 | {
101 | get { return (bool)GetValue(UseStereoProperty); }
102 | set { SetValue(UseStereoProperty, value); }
103 | }
104 |
105 | public SupportedWaveFormat AudioWaveFormat
106 | {
107 | // TODO: Make wave format more adjustable
108 | get => UseStereo ? audioFormats[1] : audioFormats[0];
109 | set => UseStereo = (value == audioFormats[1]);
110 | }
111 |
112 | public static readonly DependencyProperty EncodeAudioProperty =
113 | DependencyProperty.Register("EncodeAudio", typeof(bool), typeof(SettingsWindow),
114 | new PropertyMetadata(true));
115 |
116 | public bool EncodeAudio
117 | {
118 | get { return (bool)GetValue(EncodeAudioProperty); }
119 | set { SetValue(EncodeAudioProperty, value); }
120 | }
121 |
122 | public static readonly DependencyProperty AudioQualityProperty =
123 | DependencyProperty.Register("AudioQuality", typeof(int), typeof(SettingsWindow));
124 |
125 | public int AudioQuality
126 | {
127 | get { return (int)GetValue(AudioQualityProperty); }
128 | set { SetValue(AudioQualityProperty, value); }
129 | }
130 |
131 | public static readonly DependencyProperty MinimizeOnStartProperty =
132 | DependencyProperty.Register("MinimizeOnStart", typeof(bool), typeof(SettingsWindow));
133 |
134 | public bool MinimizeOnStart
135 | {
136 | get { return (bool)GetValue(MinimizeOnStartProperty); }
137 | set { SetValue(MinimizeOnStartProperty, value); }
138 | }
139 |
140 | public IEnumerable AvailableCodecs { get; private set; }
141 |
142 | public IEnumerable> AvailableAudioSources { get; private set; }
143 |
144 | public IEnumerable AvailableAudioWaveFormats => audioFormats;
145 | private readonly SupportedWaveFormat[] audioFormats = new[]
146 | {
147 | SupportedWaveFormat.WAVE_FORMAT_44M16,
148 | SupportedWaveFormat.WAVE_FORMAT_44S16
149 | };
150 |
151 | public int MaximumAudioQuality => Mp3LameAudioEncoder.SupportedBitRates.Length - 1;
152 |
153 |
154 | private void OK_Click(object sender, RoutedEventArgs e)
155 | {
156 | DialogResult = true;
157 | }
158 |
159 | private void BrowseFolder_Click(object sender, RoutedEventArgs e)
160 | {
161 | var dlg = new FolderBrowserDialog()
162 | {
163 | SelectedPath = Folder,
164 | ShowNewFolderButton = true,
165 | Description = "Select folder for screencasts"
166 | };
167 |
168 | if (dlg.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
169 | {
170 | Folder = dlg.SelectedPath;
171 | }
172 | }
173 |
174 | IntPtr System.Windows.Forms.IWin32Window.Handle => new WindowInteropHelper(this).Handle;
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/Sample/Styles.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
18 |
19 |
77 |
78 |
130 |
131 |
141 |
142 |
146 |
--------------------------------------------------------------------------------
/Sample/WindowMoveBehavior.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Input;
4 |
5 | namespace SharpAvi.Sample
6 | {
7 | internal static class WindowMoveBehavior
8 | {
9 | private static readonly DependencyProperty MoveOriginProperty =
10 | DependencyProperty.RegisterAttached("MoveOrigin", typeof(Point), typeof(WindowMoveBehavior));
11 |
12 | public static void Attach(Window window)
13 | {
14 | window.Closed += window_Closed;
15 | window.MouseLeftButtonDown += window_MouseLeftButtonDown;
16 | window.MouseLeftButtonUp += window_MouseLeftButtonUp;
17 | window.MouseMove += window_MouseMove;
18 | }
19 |
20 | private static void window_Closed(object sender, EventArgs e)
21 | {
22 | var window = (Window)sender;
23 | window.Closed -= window_Closed;
24 | window.MouseLeftButtonDown -= window_MouseLeftButtonDown;
25 | window.MouseLeftButtonUp -= window_MouseLeftButtonUp;
26 | window.MouseMove -= window_MouseMove;
27 | }
28 |
29 | private static void window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
30 | {
31 | var window = (Window)sender;
32 | window.SetValue(MoveOriginProperty, e.GetPosition(window));
33 | window.CaptureMouse();
34 | }
35 |
36 | private static void window_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
37 | {
38 | var window = (Window)sender;
39 | if (window.IsMouseCaptured)
40 | {
41 | window.ReleaseMouseCapture();
42 | }
43 | }
44 |
45 | private static void window_MouseMove(object sender, MouseEventArgs e)
46 | {
47 | var window = (Window)sender;
48 | if (window.IsMouseCaptured)
49 | {
50 | var offset = e.GetPosition(window) - (Point)window.GetValue(MoveOriginProperty);
51 | window.Left += offset.X;
52 | window.Top += offset.Y;
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sample/lameenc32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baSSiLL/SharpAvi/b86f69d4b77e96a7d59de63f750f1deaa431f4b7/Sample/lameenc32.dll
--------------------------------------------------------------------------------
/Sample/lameenc64.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baSSiLL/SharpAvi/b86f69d4b77e96a7d59de63f750f1deaa431f4b7/Sample/lameenc64.dll
--------------------------------------------------------------------------------
/SharpAvi.ImageSharp/Codecs/ImageSharpEncoderStreamFactory.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Output;
2 | using SharpAvi.Utilities;
3 |
4 | namespace SharpAvi.Codecs
5 | {
6 | ///
7 | /// Contains extension methods for creating video streams.
8 | ///
9 | public static class ImageSharpEncoderStreamFactory
10 | {
11 | ///
12 | /// Adds new video stream with .
13 | ///
14 | /// Writer object to which new stream is added.
15 | /// Frame width.
16 | /// Frame height.
17 | /// Requested quality of compression.
18 | ///
19 | ///
20 | public static IAviVideoStream AddMJpegImageSharpVideoStream(this AviWriter writer, int width, int height, int quality = 70)
21 | {
22 | Argument.IsNotNull(writer, nameof(writer));
23 | Argument.IsPositive(width, nameof(width));
24 | Argument.IsPositive(height, nameof(height));
25 | Argument.IsInRange(quality, 1, 100, nameof(quality));
26 |
27 | var encoder = new MJpegImageSharpVideoEncoder(width, height, quality);
28 | return writer.AddEncodingVideoStream(encoder, true, width, height);
29 | }
30 |
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/SharpAvi.ImageSharp/Codecs/MJpegImageSharpVideoEncoder.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Utilities;
2 | using SixLabors.ImageSharp;
3 | using SixLabors.ImageSharp.Formats.Jpeg;
4 | using System;
5 | using System.IO;
6 |
7 | namespace SharpAvi.Codecs
8 | {
9 | ///
10 | /// Encodes frames in Motion JPEG format.
11 | ///
12 | ///
13 | /// The implementation relies on from the SixLabors.ImageSharp package.
14 | ///
15 | public sealed class MJpegImageSharpVideoEncoder : IVideoEncoder
16 | {
17 | private readonly int width;
18 | private readonly int height;
19 | private readonly JpegEncoder jpegEncoder;
20 | #if NET5_0_OR_GREATER
21 | private readonly MemoryStream buffer;
22 | #endif
23 |
24 | ///
25 | /// Creates a new instance of .
26 | ///
27 | /// Frame width.
28 | /// Frame height.
29 | ///
30 | /// Compression quality in the range [1..100].
31 | /// Less values mean less size and lower image quality.
32 | ///
33 | public MJpegImageSharpVideoEncoder(int width, int height, int quality)
34 | {
35 | Argument.IsPositive(width, nameof(width));
36 | Argument.IsPositive(height, nameof(height));
37 | Argument.IsInRange(quality, 1, 100, nameof(quality));
38 |
39 | this.width = width;
40 | this.height = height;
41 |
42 | #if NET5_0_OR_GREATER
43 | buffer = new MemoryStream(MaxEncodedSize);
44 | #endif
45 | jpegEncoder = new JpegEncoder()
46 | {
47 | Quality = quality
48 | };
49 | }
50 |
51 | /// Video codec.
52 | public FourCC Codec => CodecIds.MotionJpeg;
53 |
54 | ///
55 | /// Number of bits per pixel in encoded image.
56 | ///
57 | public BitsPerPixel BitsPerPixel => BitsPerPixel.Bpp24;
58 |
59 | ///
60 | /// Maximum size of encoded frmae.
61 | ///
62 | public int MaxEncodedSize => Math.Max(width * height * 3, 1024);
63 |
64 | ///
65 | /// Encodes a frame.
66 | ///
67 | public int EncodeFrame(byte[] source, int srcOffset, byte[] destination, int destOffset, out bool isKeyFrame)
68 | {
69 | Argument.IsNotNull(source, nameof(source));
70 | Argument.IsNotNegative(srcOffset, nameof(srcOffset));
71 | Argument.ConditionIsMet(srcOffset + 4 * width * height <= source.Length,
72 | "Source end offset exceeds the source length.");
73 | Argument.IsNotNull(destination, nameof(destination));
74 | Argument.IsNotNegative(destOffset, nameof(destOffset));
75 |
76 | int length;
77 | using (var stream = new MemoryStream(destination))
78 | {
79 | stream.Position = destOffset;
80 | length = LoadAndEncodeImage(source.AsSpan(srcOffset), stream);
81 | }
82 |
83 | isKeyFrame = true;
84 | return length;
85 | }
86 |
87 | #if NET5_0_OR_GREATER
88 | ///
89 | /// Encodes a frame.
90 | ///
91 | public int EncodeFrame(ReadOnlySpan source, Span destination, out bool isKeyFrame)
92 | {
93 | Argument.ConditionIsMet(4 * width * height <= source.Length,
94 | "Source end offset exceeds the source length.");
95 |
96 | buffer.SetLength(0);
97 | var length = LoadAndEncodeImage(source, buffer);
98 | buffer.GetBuffer().AsSpan(0, length).CopyTo(destination);
99 |
100 | isKeyFrame = true;
101 | return length;
102 | }
103 | #endif
104 |
105 | private int LoadAndEncodeImage(ReadOnlySpan source, Stream destination)
106 | {
107 | var startPosition = (int)destination.Position;
108 | using (var image = Image.LoadPixelData(source, width, height))
109 | {
110 | jpegEncoder.Encode(image, destination);
111 | }
112 | destination.Flush();
113 | return (int)(destination.Position - startPosition);
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/SharpAvi.ImageSharp/SharpAvi.ImageSharp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net50
5 | True
6 | True
7 | 3.0.0
8 | baSSiLL
9 |
10 | SharpAvi
11 | A Motion JPEG video encoder for SharpAvi based on the SixLabors.ImageSharp library.
12 | Copyright © Vasili Maslov 2022
13 | https://github.com/baSSiLL/SharpAvi
14 | https://github.com/baSSiLL/SharpAvi.git
15 | SharpAvi ImageSharp AVI MJPG M-JPEG video
16 | MIT
17 | SharpAvi
18 | readme.md
19 | $(Version).0
20 | $(Version).0
21 | True
22 | https://github.com/baSSiLL/SharpAvi/releases/tag/v$(Version)
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | True
32 | \
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/SharpAvi.ImageSharp/readme.md:
--------------------------------------------------------------------------------
1 | **SharpAvi.ImageSharp** contains a cross-platform Motion JPEG encoder for [SharpAvi](https://www.nuget.org/packages/SharpAvi/).
2 | It is based on the [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) library.
--------------------------------------------------------------------------------
/SharpAvi.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32014.148
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpAvi", "SharpAvi\SharpAvi.csproj", "{07B83677-E9B8-4166-A383-CF3B3D393FBD}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{419ABA1F-198D-4E85-8BFB-58EE6FDAE67F}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2D19245D-C897-44AF-9D4C-1187702F68C3}"
11 | ProjectSection(SolutionItems) = preProject
12 | .editorconfig = .editorconfig
13 | README.md = README.md
14 | EndProjectSection
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpAvi.ImageSharp", "SharpAvi.ImageSharp\SharpAvi.ImageSharp.csproj", "{ED29C1C8-634A-4325-A5B0-1F3563D50BB4}"
17 | EndProject
18 | Global
19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 | Debug|Any CPU = Debug|Any CPU
21 | Release|Any CPU = Release|Any CPU
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {07B83677-E9B8-4166-A383-CF3B3D393FBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {07B83677-E9B8-4166-A383-CF3B3D393FBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {07B83677-E9B8-4166-A383-CF3B3D393FBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {07B83677-E9B8-4166-A383-CF3B3D393FBD}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {419ABA1F-198D-4E85-8BFB-58EE6FDAE67F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {419ABA1F-198D-4E85-8BFB-58EE6FDAE67F}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {419ABA1F-198D-4E85-8BFB-58EE6FDAE67F}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {419ABA1F-198D-4E85-8BFB-58EE6FDAE67F}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {ED29C1C8-634A-4325-A5B0-1F3563D50BB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {ED29C1C8-634A-4325-A5B0-1F3563D50BB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {ED29C1C8-634A-4325-A5B0-1F3563D50BB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {ED29C1C8-634A-4325-A5B0-1F3563D50BB4}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {1D772E7F-38FC-438C-AE1E-BC0A3626D9D5}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/SharpAvi/AudioFormats.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi
2 | {
3 | ///
4 | /// Contains codes of some popular wave formats.
5 | ///
6 | public static class AudioFormats
7 | {
8 | ///
9 | /// Unknown format.
10 | ///
11 | public static readonly short Unknown = 0x0000;
12 |
13 | ///
14 | /// Pulse-code modulation (PCM).
15 | ///
16 | public static readonly short Pcm = 0x0001;
17 |
18 | ///
19 | /// MPEG Layer 3 (MP3).
20 | ///
21 | public static readonly short Mp3 = 0x0055;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/SharpAvi/BitsPerPixel.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi
2 | {
3 | /// Number of bits per pixel.
4 | public enum BitsPerPixel
5 | {
6 | /// 8 bits per pixel.
7 | ///
8 | /// When used with uncompressed video streams,
9 | /// a grayscale palette is implied.
10 | ///
11 | Bpp8 = 8,
12 | /// 16 bits per pixel.
13 | Bpp16 = 16,
14 | /// 24 bits per pixel.
15 | Bpp24 = 24,
16 | /// 32 bits per pixel.
17 | Bpp32 = 32,
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/SharpAvi/CodecIds.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi
2 | {
3 | /// Identifiers of various codecs.
4 | public static class CodecIds
5 | {
6 | /// Identifier used for non-compressed data.
7 | public static readonly FourCC Uncompressed = new FourCC(0);
8 |
9 | /// Motion JPEG.
10 | public static readonly FourCC MotionJpeg = new FourCC("MJPG");
11 |
12 | /// Microsoft MPEG-4 V3.
13 | public static readonly FourCC MicrosoftMpeg4V3 = new FourCC("MP43");
14 |
15 | /// Microsoft MPEG-4 V2.
16 | public static readonly FourCC MicrosoftMpeg4V2 = new FourCC("MP42");
17 |
18 | /// Xvid MPEG-4.
19 | public static readonly FourCC Xvid = new FourCC("XVID");
20 |
21 | /// DivX MPEG-4.
22 | public static readonly FourCC DivX = new FourCC("DIVX");
23 |
24 | /// x264 H.264/MPEG-4 AVC.
25 | public static readonly FourCC X264 = new FourCC("X264");
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/SharpAvi/Codecs/CodecInfo.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi.Codecs
2 | {
3 | ///
4 | /// Information about a codec.
5 | ///
6 | public class CodecInfo
7 | {
8 |
9 | ///
10 | /// Creates a new instance of .
11 | ///
12 | public CodecInfo(FourCC codec, string name)
13 | {
14 | this.Codec = codec;
15 | this.Name = name;
16 | }
17 |
18 | /// Codec ID.
19 | public FourCC Codec { get; }
20 |
21 | ///
22 | /// Descriptive codec name that may be show to a user.
23 | ///
24 | public string Name { get; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/SharpAvi/Codecs/EncodingStreamFactory.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Output;
2 | using SharpAvi.Utilities;
3 | using System;
4 | using System.Linq;
5 |
6 | namespace SharpAvi.Codecs
7 | {
8 | ///
9 | /// Provides extension methods for creating encoding streams with specific encoders.
10 | ///
11 | public static class EncodingStreamFactory
12 | {
13 | ///
14 | /// Adds new video stream with .
15 | ///
16 | ///
17 | ///
18 | public static IAviVideoStream AddUncompressedVideoStream(this AviWriter writer, int width, int height)
19 | {
20 | Argument.IsNotNull(writer, nameof(writer));
21 | Argument.IsPositive(width, nameof(width));
22 | Argument.IsPositive(height, nameof(height));
23 |
24 | var encoder = new UncompressedVideoEncoder(width, height);
25 | return writer.AddEncodingVideoStream(encoder, true, width, height);
26 | }
27 |
28 | #if NET45 || NET5_0_OR_GREATER && WINDOWS
29 | ///
30 | /// Adds new video stream with .
31 | ///
32 | /// Writer object to which new stream is added.
33 | /// Frame width.
34 | /// Frame height.
35 | /// Requested quality of compression.
36 | ///
37 | ///
38 | public static IAviVideoStream AddMJpegWpfVideoStream(this AviWriter writer, int width, int height, int quality = 70)
39 | {
40 | Argument.IsNotNull(writer, nameof(writer));
41 | Argument.IsPositive(width, nameof(width));
42 | Argument.IsPositive(height, nameof(height));
43 | Argument.IsInRange(quality, 1, 100, nameof(quality));
44 |
45 | var encoder = new MJpegWpfVideoEncoder(width, height, quality);
46 | return writer.AddEncodingVideoStream(encoder, true, width, height);
47 | }
48 | #endif
49 |
50 | ///
51 | /// Adds new video stream with .
52 | ///
53 | /// Writer object to which new stream is added.
54 | /// Frame width.
55 | /// Frame height.
56 | /// Frames rate of the video.
57 | /// Number of frames if known in advance. Otherwise, specify 0.
58 | /// Requested quality of compression.
59 | /// Specific MPEG-4 codec to use.
60 | ///
61 | /// When true, the created instance is wrapped into
62 | /// .
63 | ///
64 | ///
65 | ///
66 | ///
67 | public static IAviVideoStream AddMpeg4VcmVideoStream(this AviWriter writer, int width, int height,
68 | double fps, int frameCount = 0, int quality = 70, FourCC? codec = null,
69 | bool forceSingleThreadedAccess = false)
70 | {
71 | Argument.IsNotNull(writer, nameof(writer));
72 | Argument.IsPositive(width, nameof(width));
73 | Argument.IsPositive(height, nameof(height));
74 | Argument.IsPositive(fps, nameof(fps));
75 | Argument.IsNotNegative(frameCount, nameof(frameCount));
76 | Argument.IsInRange(quality, 1, 100, nameof(quality));
77 |
78 | var encoderFactory = codec.HasValue
79 | ? new Func(() => new Mpeg4VcmVideoEncoder(width, height, fps, frameCount, quality, codec.Value))
80 | : new Func(() => new Mpeg4VcmVideoEncoder(width, height, fps, frameCount, quality));
81 | var encoder = forceSingleThreadedAccess
82 | ? new SingleThreadedVideoEncoderWrapper(encoderFactory)
83 | : encoderFactory.Invoke();
84 | return writer.AddEncodingVideoStream(encoder, true, width, height);
85 | }
86 |
87 | #if !NETSTANDARD
88 | ///
89 | /// Adds new audio stream with .
90 | ///
91 | ///
92 | ///
93 | public static IAviAudioStream AddMp3LameAudioStream(this AviWriter writer, int channelCount, int sampleRate, int outputBitRateKbps = 160)
94 | {
95 | Argument.IsNotNull(writer, nameof(writer));
96 | Argument.IsInRange(channelCount, 1, 2, nameof(channelCount));
97 | Argument.IsPositive(sampleRate, nameof(sampleRate));
98 | Argument.Meets(Mp3LameAudioEncoder.SupportedBitRates.Contains(outputBitRateKbps), nameof(outputBitRateKbps));
99 |
100 | var encoder = new Mp3LameAudioEncoder(channelCount, sampleRate, outputBitRateKbps);
101 | return writer.AddEncodingAudioStream(encoder, true);
102 | }
103 | #endif
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/SharpAvi/Codecs/IAudioEncoder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SharpAvi.Codecs
4 | {
5 | ///
6 | /// Encoder of audio streams.
7 | ///
8 | public interface IAudioEncoder
9 | {
10 | ///
11 | /// Number of channels in encoded audio.
12 | ///
13 | int ChannelCount { get; }
14 |
15 | ///
16 | /// Sample rate of encoded audio, in samples per second.
17 | ///
18 | int SamplesPerSecond { get; }
19 |
20 | ///
21 | /// Number of bits per sample per single channel in encoded audio (usually 8 or 16).
22 | ///
23 | int BitsPerSample { get; }
24 |
25 | ///
26 | /// Format of encoded audio.
27 | ///
28 | short Format { get; }
29 |
30 | ///
31 | /// Byte rate of encoded audio, in bytes per second.
32 | ///
33 | int BytesPerSecond { get; }
34 |
35 | ///
36 | /// Size in bytes of minimum item of encoded data.
37 | ///
38 | ///
39 | /// Corresponds to nBlockAlign field of WAVEFORMATEX structure.
40 | ///
41 | int Granularity { get; }
42 |
43 | ///
44 | /// Extra data defined by a specific format which should be added to the stream header.
45 | ///
46 | ///
47 | /// Contains data of specific structure like MPEGLAYER3WAVEFORMAT that follow
48 | /// common WAVEFORMATEX field.
49 | ///
50 | byte[] FormatSpecificData { get; }
51 |
52 | ///
53 | /// Gets the maximum number of bytes in encoded data for a given number of source bytes.
54 | ///
55 | /// Number of source bytes. Specify 0 for a flush buffer size.
56 | ///
57 | ///
58 | int GetMaxEncodedLength(int sourceCount);
59 |
60 | ///
61 | /// Encodes block of audio data.
62 | ///
63 | /// Buffer with audio data.
64 | /// Offset to start reading .
65 | /// Number of bytes to read from .
66 | /// Buffer for encoded audio data.
67 | /// Offset to start writing to .
68 | /// The number of bytes written to .
69 | ///
70 | int EncodeBlock(byte[] source, int sourceOffset, int sourceCount, byte[] destination, int destinationOffset);
71 |
72 | ///
73 | /// Flushes internal encoder buffers if any.
74 | ///
75 | /// Buffer for encoded audio data.
76 | /// Offset to start writing to .
77 | /// The number of bytes written to .
78 | ///
79 | int Flush(byte[] destination, int destinationOffset);
80 |
81 | #if NET5_0_OR_GREATER
82 | ///
83 | /// Encodes block of audio data.
84 | ///
85 | /// Buffer with audio data.
86 | /// Buffer for encoded audio data.
87 | /// The number of bytes written to .
88 | ///
89 | int EncodeBlock(ReadOnlySpan source, Span destination);
90 |
91 | ///
92 | /// Flushes internal encoder buffers if any.
93 | ///
94 | /// Buffer for encoded audio data.
95 | /// The number of bytes written to .
96 | ///
97 | int Flush(Span destination);
98 | #endif
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/SharpAvi/Codecs/IVideoEncoder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SharpAvi.Codecs
4 | {
5 | ///
6 | /// Encoder for video AVI stream.
7 | ///
8 | public interface IVideoEncoder
9 | {
10 | /// Codec ID.
11 | FourCC Codec { get; }
12 |
13 | ///
14 | /// Number of bits per pixel in encoded image.
15 | ///
16 | BitsPerPixel BitsPerPixel { get; }
17 |
18 | ///
19 | /// Determines the amount of space needed in the destination buffer for storing the encoded data of a single frame.
20 | ///
21 | int MaxEncodedSize { get; }
22 |
23 | ///
24 | /// Encodes video frame.
25 | ///
26 | ///
27 | /// Frame bitmap data. The expected bitmap format is BGR32 top-to-bottom. Alpha component is not used.
28 | ///
29 | ///
30 | /// Start offset of the frame data in the .
31 | /// Expected length of the data is determined by the parameters specified when instantinating the encoder.
32 | ///
33 | ///
34 | /// Buffer for storing the encoded frame data.
35 | ///
36 | ///
37 | /// Start offset of the encoded data in the buffer.
38 | /// There should be enough space till the end of the buffer, see .
39 | ///
40 | ///
41 | /// When the method returns, contains the value indicating whether this frame was encoded as a key frame.
42 | ///
43 | ///
44 | /// The actual number of bytes written to the buffer.
45 | ///
46 | int EncodeFrame(byte[] source, int srcOffset, byte[] destination, int destOffset, out bool isKeyFrame);
47 |
48 | #if NET5_0_OR_GREATER
49 | ///
50 | /// Encodes video frame.
51 | ///
52 | ///
53 | /// Frame bitmap data. The expected bitmap format is BGR32 top-to-bottom. Alpha component is not used.
54 | ///
55 | ///
56 | /// Buffer for storing the encoded frame data.
57 | ///
58 | ///
59 | /// When the method returns, contains the value indicating whether this frame was encoded as a key frame.
60 | ///
61 | ///
62 | /// The actual number of bytes written to the buffer.
63 | ///
64 | int EncodeFrame(ReadOnlySpan source, Span destination, out bool isKeyFrame);
65 | #endif
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/SharpAvi/Codecs/MJpegWpfVideoEncoder.cs:
--------------------------------------------------------------------------------
1 | #if NET45 || NET5_0_OR_GREATER && WINDOWS
2 | using SharpAvi.Format;
3 | using SharpAvi.Utilities;
4 | using System;
5 | using System.IO;
6 | using System.Threading;
7 | using System.Windows;
8 | using System.Windows.Media;
9 | using System.Windows.Media.Imaging;
10 |
11 | namespace SharpAvi.Codecs
12 | {
13 | ///
14 | /// Encodes frames in Motion JPEG format.
15 | ///
16 | ///
17 | ///
18 | /// The implementation relies on .
19 | ///
20 | ///
21 | public sealed class MJpegWpfVideoEncoder : IVideoEncoder
22 | {
23 | private readonly Int32Rect rect;
24 | private readonly int quality;
25 | private readonly ThreadLocal bitmapHolder;
26 |
27 | ///
28 | /// Creates a new instance of .
29 | ///
30 | /// Frame width.
31 | /// Frame height.
32 | ///
33 | /// Compression quality in the range [1..100].
34 | /// Less values mean less size and lower image quality.
35 | ///
36 | public MJpegWpfVideoEncoder(int width, int height, int quality)
37 | {
38 | Argument.IsPositive(width, nameof(width));
39 | Argument.IsPositive(height, nameof(height));
40 | Argument.IsInRange(quality, 1, 100, nameof(quality));
41 |
42 | rect = new Int32Rect(0, 0, width, height);
43 | this.quality = quality;
44 |
45 | #if NET5_0_OR_GREATER
46 | buffer = new MemoryStream(MaxEncodedSize);
47 | #endif
48 |
49 | bitmapHolder = new ThreadLocal(
50 | () => new WriteableBitmap(rect.Width, rect.Height, 96, 96, PixelFormats.Bgr32, null),
51 | false);
52 | }
53 |
54 |
55 | #region IVideoEncoder Members
56 |
57 | /// Video codec.
58 | public FourCC Codec => CodecIds.MotionJpeg;
59 |
60 | ///
61 | /// Number of bits per pixel in encoded image.
62 | ///
63 | public BitsPerPixel BitsPerPixel => BitsPerPixel.Bpp24;
64 |
65 | ///
66 | /// Maximum size of encoded frmae.
67 | ///
68 | public int MaxEncodedSize
69 | {
70 | get
71 | {
72 | // Assume that JPEG is always less than raw bitmap when dimensions are not tiny
73 | return Math.Max(rect.Width * rect.Height * 3, 1024);
74 | }
75 | }
76 |
77 | ///
78 | /// Encodes a frame.
79 | ///
80 | public int EncodeFrame(byte[] source, int srcOffset, byte[] destination, int destOffset, out bool isKeyFrame)
81 | {
82 | Argument.IsNotNull(source, nameof(source));
83 | Argument.IsNotNegative(srcOffset, nameof(srcOffset));
84 | Argument.ConditionIsMet(srcOffset + 4 * rect.Width * rect.Height <= source.Length,
85 | "Source end offset exceeds the source length.");
86 | Argument.IsNotNull(destination, nameof(destination));
87 | Argument.IsNotNegative(destOffset, nameof(destOffset));
88 |
89 | var bitmap = bitmapHolder.Value;
90 | bitmap.WritePixels(rect, source, rect.Width * 4, srcOffset);
91 |
92 | var encoderImpl = new JpegBitmapEncoder
93 | {
94 | QualityLevel = quality
95 | };
96 | encoderImpl.Frames.Add(BitmapFrame.Create(bitmap));
97 |
98 | int length;
99 | using (var stream = new MemoryStream(destination))
100 | {
101 | stream.Position = destOffset;
102 | encoderImpl.Save(stream);
103 | stream.Flush();
104 | length = (int)stream.Position - destOffset;
105 | }
106 |
107 | isKeyFrame = true;
108 | return length;
109 | }
110 |
111 | #if NET5_0_OR_GREATER
112 | private readonly MemoryStream buffer;
113 |
114 | ///
115 | /// Encodes a frame.
116 | ///
117 | public unsafe int EncodeFrame(ReadOnlySpan source, Span destination, out bool isKeyFrame)
118 | {
119 | Argument.ConditionIsMet(4 * rect.Width * rect.Height <= source.Length,
120 | "Source end offset exceeds the source length.");
121 |
122 | var bitmap = bitmapHolder.Value;
123 | fixed (void* srcPtr = source)
124 | {
125 | var srcIntPtr = new IntPtr(srcPtr);
126 | bitmap.WritePixels(rect, srcIntPtr, source.Length, rect.Width * 4);
127 | }
128 |
129 | var encoderImpl = new JpegBitmapEncoder
130 | {
131 | QualityLevel = quality
132 | };
133 | encoderImpl.Frames.Add(BitmapFrame.Create(bitmap));
134 |
135 | buffer.SetLength(0);
136 | encoderImpl.Save(buffer);
137 | buffer.Flush();
138 |
139 | var length = (int)buffer.Length;
140 | buffer.GetBuffer().AsSpan(0, length).CopyTo(destination);
141 | isKeyFrame = true;
142 | return length;
143 | }
144 | #endif
145 |
146 | #endregion
147 | }
148 | }
149 | #endif
150 |
--------------------------------------------------------------------------------
/SharpAvi/Codecs/Mp3LameAudioEncoder.ILameFacade.cs:
--------------------------------------------------------------------------------
1 | // Mp3LameAudioEncoder is not supported on .NET Standard yet
2 | #if !NETSTANDARD
3 | using System;
4 |
5 | namespace SharpAvi.Codecs
6 | {
7 | partial class Mp3LameAudioEncoder
8 | {
9 | ///
10 | /// Interface is used to access the API of the LAME DLL.
11 | ///
12 | ///
13 | /// Clients of class need not to work with
14 | /// this interface directly.
15 | ///
16 | public interface ILameFacade
17 | {
18 | ///
19 | /// Number of audio channels.
20 | ///
21 | int ChannelCount { get; set; }
22 |
23 | ///
24 | /// Sample rate of source audio data.
25 | ///
26 | int InputSampleRate { get; set; }
27 |
28 | ///
29 | /// Bit rate of encoded data.
30 | ///
31 | int OutputBitRate { get; set; }
32 |
33 | ///
34 | /// Sample rate of encoded data.
35 | ///
36 | int OutputSampleRate { get; }
37 |
38 | ///
39 | /// Frame size of encoded data.
40 | ///
41 | int FrameSize { get; }
42 |
43 | ///
44 | /// Encoder delay.
45 | ///
46 | int EncoderDelay { get; }
47 |
48 | ///
49 | /// Initializes the encoding process.
50 | ///
51 | void PrepareEncoding();
52 |
53 | #if NET5_0_OR_GREATER
54 | ///
55 | /// Encodes a chunk of audio data.
56 | ///
57 | int Encode(ReadOnlySpan source, int sampleCount, Span dest);
58 |
59 | ///
60 | /// Finalizes the encoding process.
61 | ///
62 | int FinishEncoding(Span dest);
63 | #else
64 | ///
65 | /// Encodes a chunk of audio data.
66 | ///
67 | int Encode(byte[] source, int sourceIndex, int sampleCount, byte[] dest, int destIndex);
68 |
69 | ///
70 | /// Finalizes the encoding process.
71 | ///
72 | int FinishEncoding(byte[] dest, int destIndex);
73 | #endif
74 | }
75 | }
76 | }
77 | #endif
--------------------------------------------------------------------------------
/SharpAvi/Codecs/Mp3LameAudioEncoder.cs:
--------------------------------------------------------------------------------
1 | // Thanks to NAudio.Lame project (by Corey Murtagh) for inspiration
2 | // https://github.com/Corey-M/NAudio.Lame
3 |
4 | // Mp3LameAudioEncoder is not supported on .NET Standard yet
5 | #if !NETSTANDARD
6 |
7 | using SharpAvi.Utilities;
8 | using System;
9 | using System.Diagnostics;
10 | using System.IO;
11 | using System.Linq;
12 | using System.Reflection;
13 | using System.Runtime.InteropServices;
14 |
15 | namespace SharpAvi.Codecs
16 | {
17 | ///
18 | /// Mpeg Layer 3 (MP3) audio encoder using the LAME codec in an external DLL.
19 | ///
20 | ///
21 | /// The class is designed for using only a single instance at a time.
22 | /// Find information about and downloads of the LAME project at http://lame.sourceforge.net/
23 | ///
24 | public sealed partial class Mp3LameAudioEncoder : IAudioEncoder, IDisposable
25 | {
26 | ///
27 | /// Supported output bit rates (in kilobits per second).
28 | ///
29 | ///
30 | /// Currently supported are 64, 96, 128, 160, 192 and 320 kbps.
31 | ///
32 | public static readonly int[] SupportedBitRates = new[] { 64, 96, 128, 160, 192, 320 };
33 |
34 |
35 | #region Loading LAME DLL
36 |
37 | private static readonly object lameFacadeSync = new object();
38 | private static Type lameFacadeType;
39 | private static string lastLameLibraryName;
40 |
41 | ///
42 | /// Sets the location of the LAME library for using by this class.
43 | ///
44 | ///
45 | /// This method may be called before creating any instances of this class.
46 | /// The LAME library should have the appropriate bitness (32/64), depending on the current process.
47 | /// If it is not already loaded into the process, the method loads it automatically.
48 | ///
49 | public static void SetLameDllLocation(string lameLibraryPath)
50 | {
51 | Argument.IsNotNullOrEmpty(lameLibraryPath, nameof(lameLibraryPath));
52 |
53 | lock (lameFacadeSync)
54 | {
55 | var libraryName = Path.GetFileName(lameLibraryPath);
56 | if (!IsLibraryLoaded(libraryName))
57 | #if NET5_0_OR_GREATER
58 | {
59 | NativeLibrary.Load(lameLibraryPath);
60 | }
61 | ResolveFacadeImpl50(libraryName);
62 | #else
63 | {
64 | LoadLameLibrary45(lameLibraryPath);
65 | }
66 | ResolveFacadeImpl45(libraryName);
67 | #endif
68 | lastLameLibraryName = libraryName;
69 | }
70 | }
71 |
72 | #if NET5_0_OR_GREATER
73 | private static void ResolveFacadeImpl50(string libraryName)
74 | {
75 | // Redirect [DllImport]s in LameFacadeImpl to a specified library
76 | // using a custom DLL resolver for NativeLibrary
77 | RedirectDllResolver.SetRedirect(LameFacadeImpl.DLL_NAME, libraryName);
78 | lameFacadeType = typeof(LameFacadeImpl);
79 | }
80 | #else
81 | private static void LoadLameLibrary45(string libraryPath)
82 | {
83 | var loadResult = LoadLibrary(libraryPath);
84 | if (loadResult == IntPtr.Zero)
85 | {
86 | throw new DllNotFoundException(string.Format("Library '{0}' could not be loaded.", libraryPath));
87 | }
88 | }
89 |
90 | [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto)]
91 | private static extern IntPtr LoadLibrary(string fileName);
92 |
93 | private static void ResolveFacadeImpl45(string libraryName)
94 | {
95 | if (lameFacadeType is null || lastLameLibraryName != libraryName)
96 | {
97 | // Generate a new in-memory assembly with another version
98 | // of LameFacadeImpl class referencing a specified DLL name
99 | var facadeAsm = GenerateLameFacadeAssembly(libraryName);
100 |
101 | // A new class has the same full name as the original one,
102 | // just is loaded from a different assembly
103 | lameFacadeType = facadeAsm.GetType(typeof(Runtime.LameFacadeImpl).FullName);
104 | }
105 | }
106 |
107 | private static Assembly GenerateLameFacadeAssembly(string lameDllName)
108 | {
109 | // Get a modified source with proper DLL name
110 | // Should be good for .NET Framework 4.5 without any define constants.
111 | // Otherwise, add proper define(s) to the compiler options
112 | var thisAsm = typeof(Mp3LameAudioEncoder).Assembly;
113 | var source = GetLameFacadeAssemblySource(lameDllName, thisAsm);
114 |
115 | // Compile it to a new in-memory assembly
116 | var compiler = new Microsoft.CSharp.CSharpCodeProvider();
117 | var compilerOptions = new System.CodeDom.Compiler.CompilerParameters()
118 | {
119 | GenerateInMemory = true,
120 | GenerateExecutable = false,
121 | IncludeDebugInformation = false,
122 | CompilerOptions = "/optimize",
123 | ReferencedAssemblies = { "mscorlib.dll", thisAsm.Location }
124 | };
125 | var compilerResult = compiler.CompileAssemblyFromSource(compilerOptions, source);
126 | if (compilerResult.Errors.HasErrors)
127 | {
128 | throw new Exception("Could not generate LAME facade assembly.");
129 | }
130 |
131 | return compilerResult.CompiledAssembly;
132 | }
133 |
134 | private static string GetLameFacadeAssemblySource(string lameDllName, Assembly resourceAsm)
135 | {
136 | // Load the original source code of LameFacadeImple from resources
137 | var resourceName = $"{typeof(Mp3LameAudioEncoder).FullName}.{nameof(Runtime.LameFacadeImpl)}.cs";
138 | string source;
139 | using (var sourceStream = resourceAsm.GetManifestResourceStream(resourceName))
140 | using (var sourceReader = new StreamReader(sourceStream))
141 | {
142 | source = sourceReader.ReadToEnd();
143 | sourceReader.Close();
144 | }
145 |
146 | // Replace the DLL name in the source
147 | var lameDllNameLiteral = string.Format("\"{0}\"", lameDllName);
148 | source = source.Replace($"\"{Runtime.LameFacadeImpl.DLL_NAME}\"", lameDllNameLiteral);
149 |
150 | return source;
151 | }
152 | #endif
153 |
154 | private static bool IsLibraryLoaded(string libraryName)
155 | {
156 | var process = Process.GetCurrentProcess();
157 | return process.Modules.Cast().
158 | Any(m => string.Compare(m.ModuleName, libraryName, StringComparison.InvariantCultureIgnoreCase) == 0);
159 | }
160 |
161 | #endregion
162 |
163 |
164 | private const int SAMPLE_BYTE_SIZE = 2;
165 |
166 | private readonly ILameFacade lame;
167 | private readonly byte[] formatData;
168 |
169 | ///
170 | /// Creates a new instance of .
171 | ///
172 | /// Channel count.
173 | /// Sample rate (in samples per second).
174 | /// Output bit rate (in kilobits per second).
175 | ///
176 | /// Encoder expects audio data in 16-bit samples.
177 | /// Stereo data should be interleaved: left sample first, right sample second.
178 | ///
179 | public Mp3LameAudioEncoder(int channelCount, int sampleRate, int outputBitRateKbps)
180 | {
181 | Argument.IsInRange(channelCount, 1, 2, nameof(channelCount));
182 | Argument.IsPositive(sampleRate, nameof(sampleRate));
183 | Argument.Meets(SupportedBitRates.Contains(outputBitRateKbps), nameof(outputBitRateKbps));
184 |
185 | lock (lameFacadeSync)
186 | {
187 | if (lameFacadeType is null)
188 | {
189 | throw new InvalidOperationException("LAME DLL is not loaded. Call SetLameDllLocation first.");
190 | }
191 |
192 | lame = (ILameFacade)Activator.CreateInstance(lameFacadeType);
193 | }
194 |
195 | lame.ChannelCount = channelCount;
196 | lame.InputSampleRate = sampleRate;
197 | lame.OutputBitRate = outputBitRateKbps;
198 |
199 | lame.PrepareEncoding();
200 |
201 | formatData = FillFormatData();
202 | }
203 |
204 | ///
205 | /// Releases resources.
206 | ///
207 | public void Dispose() => (lame as IDisposable)?.Dispose();
208 |
209 | ///
210 | /// Encodes block of audio data.
211 | ///
212 | public int EncodeBlock(byte[] source, int sourceOffset, int sourceCount, byte[] destination, int destinationOffset)
213 | {
214 | Argument.IsNotNull(source, nameof(source));
215 | Argument.IsNotNegative(sourceOffset, nameof(sourceOffset));
216 | Argument.IsPositive(sourceCount, nameof(sourceCount));
217 | Argument.ConditionIsMet(sourceOffset + sourceCount <= source.Length,
218 | "Source end offset exceeds the source length.");
219 | Argument.IsNotNull(destination, nameof(destination));
220 | Argument.IsNotNegative(destinationOffset, nameof(destinationOffset));
221 |
222 | #if NET5_0_OR_GREATER
223 | return EncodeBlock(source.AsSpan(sourceOffset, sourceCount), destination.AsSpan(destinationOffset));
224 | #else
225 | return lame.Encode(source, sourceOffset, sourceCount / SAMPLE_BYTE_SIZE, destination, destinationOffset);
226 | #endif
227 | }
228 |
229 | ///
230 | /// Flushes internal encoder's buffers.
231 | ///
232 | public int Flush(byte[] destination, int destinationOffset)
233 | {
234 | Argument.IsNotNull(destination, nameof(destination));
235 | Argument.IsNotNegative(destinationOffset, nameof(destinationOffset));
236 |
237 | #if NET5_0_OR_GREATER
238 | return Flush(destination.AsSpan(destinationOffset));
239 | #else
240 | return lame.FinishEncoding(destination, destinationOffset);
241 | #endif
242 | }
243 |
244 | #if NET5_0_OR_GREATER
245 | ///
246 | /// Encodes block of audio data.
247 | ///
248 | public int EncodeBlock(ReadOnlySpan source, Span destination)
249 | {
250 | Argument.Meets(source.Length > 0, nameof(source), "Cannot write an empty block.");
251 |
252 | return lame.Encode(source, source.Length / SAMPLE_BYTE_SIZE, destination);
253 | }
254 |
255 | ///
256 | /// Flushes internal encoder's buffers.
257 | ///
258 | public int Flush(Span destination)
259 | {
260 | return lame.FinishEncoding(destination);
261 | }
262 | #endif
263 |
264 | ///
265 | /// Gets maximum length of encoded data.
266 | ///
267 | public int GetMaxEncodedLength(int sourceCount)
268 | {
269 | Argument.IsNotNegative(sourceCount, nameof(sourceCount));
270 |
271 | // Estimate taken from the description of 'lame_encode_buffer' method in 'lame.h'
272 | var numberOfSamples = sourceCount / SAMPLE_BYTE_SIZE;
273 | return (int)Math.Ceiling(1.25 * numberOfSamples + 7200);
274 | }
275 |
276 |
277 | ///
278 | /// Number of audio channels.
279 | ///
280 | public int ChannelCount => lame.ChannelCount;
281 |
282 | ///
283 | /// Sample rate.
284 | ///
285 | public int SamplesPerSecond => lame.OutputSampleRate;
286 |
287 | ///
288 | /// Bits per sample per single channel.
289 | ///
290 | public int BitsPerSample => SAMPLE_BYTE_SIZE * 8;
291 |
292 | ///
293 | /// Audio format.
294 | ///
295 | public short Format => AudioFormats.Mp3;
296 |
297 | ///
298 | /// Byte rate of the stream.
299 | ///
300 | public int BytesPerSecond => lame.OutputBitRate * 1000 / 8;
301 |
302 | ///
303 | /// Minimum amount of data.
304 | ///
305 | public int Granularity => 1;
306 |
307 | ///
308 | /// Format-specific data.
309 | ///
310 | public byte[] FormatSpecificData => formatData;
311 |
312 |
313 | private byte[] FillFormatData()
314 | {
315 | // See MPEGLAYER3WAVEFORMAT structure
316 | var mp3Data = new MemoryStream(4 * sizeof(ushort) + sizeof(uint));
317 | using (var writer = new BinaryWriter(mp3Data))
318 | {
319 | writer.Write((ushort)1); // MPEGLAYER3_ID_MPEG
320 | writer.Write(0x00000002U); // MPEGLAYER3_FLAG_PADDING_OFF
321 | writer.Write((ushort)lame.FrameSize); // nBlockSize
322 | writer.Write((ushort)1); // nFramesPerBlock
323 | writer.Write((ushort)lame.EncoderDelay);
324 | }
325 | return mp3Data.ToArray();
326 | }
327 | }
328 | }
329 | #endif
--------------------------------------------------------------------------------
/SharpAvi/Codecs/SingleThreadedVideoEncoderWrapper.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Utilities;
2 | using System;
3 | using System.Threading.Tasks;
4 |
5 | namespace SharpAvi.Codecs
6 | {
7 | ///
8 | /// Ensures that all access to the enclosed instance is made
9 | /// on a single thread.
10 | ///
11 | ///
12 | ///
13 | /// Especially useful for unmanaged encoders like in multi-threaded scenarios
14 | /// like asynchronous encoding.
15 | ///
16 | ///
17 | public class SingleThreadedVideoEncoderWrapper : IVideoEncoder, IDisposable
18 | {
19 | private readonly IVideoEncoder encoder;
20 | private readonly SingleThreadTaskScheduler scheduler;
21 |
22 | ///
23 | /// Creates a new instance of .
24 | ///
25 | ///
26 | /// Factory for creating an encoder instance.
27 | /// It will be invoked on the same thread as all subsequent operations of the interface.
28 | ///
29 | public SingleThreadedVideoEncoderWrapper(Func encoderFactory)
30 | {
31 | Argument.IsNotNull(encoderFactory, nameof(encoderFactory));
32 |
33 | scheduler = new SingleThreadTaskScheduler();
34 |
35 | // TODO: Create encoder on the first frame
36 | encoder = SchedulerInvoke(encoderFactory)
37 | ?? throw new InvalidOperationException("Encoder factory has created no instance.");
38 | }
39 |
40 | ///
41 | /// Disposes the enclosed encoder and stops the internal thread.
42 | ///
43 | public void Dispose()
44 | {
45 | if (!scheduler.IsDisposed)
46 | {
47 | if (encoder is IDisposable disposable)
48 | {
49 | new Task(disposable.Dispose).RunSynchronously(scheduler);
50 | }
51 | scheduler.Dispose();
52 | }
53 | }
54 |
55 | /// Codec ID.
56 | public FourCC Codec => SchedulerInvoke(() => encoder.Codec);
57 |
58 | ///
59 | /// Number of bits per pixel in encoded image.
60 | ///
61 | public BitsPerPixel BitsPerPixel => SchedulerInvoke(() => encoder.BitsPerPixel);
62 |
63 | ///
64 | /// Determines the amount of space needed in the destination buffer for storing the encoded data of a single frame.
65 | ///
66 | public int MaxEncodedSize => SchedulerInvoke(() => encoder.MaxEncodedSize);
67 |
68 | ///
69 | /// Encodes a video frame.
70 | ///
71 | public int EncodeFrame(byte[] source, int srcOffset, byte[] destination, int destOffset, out bool isKeyFrame)
72 | {
73 | var result = SchedulerInvoke(
74 | () => EncodeFrame(source, srcOffset, destination, destOffset));
75 | isKeyFrame = result.IsKeyFrame;
76 | return result.EncodedLength;
77 | }
78 |
79 | private EncodeResult EncodeFrame(byte[] source, int srcOffset, byte[] destination, int destOffset)
80 | {
81 | bool isKeyFrame;
82 | var result = encoder.EncodeFrame(source, srcOffset, destination, destOffset, out isKeyFrame);
83 | return new EncodeResult
84 | {
85 | EncodedLength = result,
86 | IsKeyFrame = isKeyFrame
87 | };
88 | }
89 |
90 | #if NET5_0_OR_GREATER
91 | ///
92 | /// Encodes a video frame.
93 | ///
94 | public unsafe int EncodeFrame(ReadOnlySpan source, Span destination, out bool isKeyFrame)
95 | {
96 | EncodeResult result;
97 | fixed (void* srcPtr = source, destPtr = destination)
98 | {
99 | var srcIntPtr = new IntPtr(srcPtr);
100 | var srcLength = source.Length;
101 | var destIntPtr = new IntPtr(destPtr);
102 | var destLength = destination.Length;
103 | result = SchedulerInvoke(
104 | () => EncodeFrame(srcIntPtr, srcLength, destIntPtr, destLength));
105 | }
106 | isKeyFrame = result.IsKeyFrame;
107 | return result.EncodedLength;
108 | }
109 |
110 | private unsafe EncodeResult EncodeFrame(IntPtr source, int srcLength, IntPtr destination, int destLength)
111 | {
112 | bool isKeyFrame;
113 | var sourceSpan = new Span(source.ToPointer(), srcLength);
114 | var destSpan = new Span(destination.ToPointer(), destLength);
115 | var result = encoder.EncodeFrame(sourceSpan, destSpan, out isKeyFrame);
116 | return new EncodeResult
117 | {
118 | EncodedLength = result,
119 | IsKeyFrame = isKeyFrame
120 | };
121 | }
122 | #endif
123 |
124 | private struct EncodeResult
125 | {
126 | public int EncodedLength;
127 | public bool IsKeyFrame;
128 | }
129 |
130 |
131 | private TResult SchedulerInvoke(Func func)
132 | {
133 | var task = new Task(func);
134 | task.RunSynchronously(scheduler);
135 | return task.Result;
136 | }
137 | }
138 | }
--------------------------------------------------------------------------------
/SharpAvi/Codecs/UncompressedVideoEncoder.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Utilities;
2 | using System;
3 |
4 | namespace SharpAvi.Codecs
5 | {
6 | ///
7 | /// Encodes frames in BGR24 format without compression.
8 | ///
9 | ///
10 | /// The main purpose of this encoder is to flip bitmap vertically (from top-down to bottom-up)
11 | /// and to convert pixel format to 24 bits.
12 | ///
13 | public class UncompressedVideoEncoder : IVideoEncoder
14 | {
15 | private readonly int width;
16 | private readonly int height;
17 | private readonly int stride;
18 |
19 | ///
20 | /// Creates a new instance of .
21 | ///
22 | /// Frame width.
23 | /// Frame height.
24 | public UncompressedVideoEncoder(int width, int height)
25 | {
26 | Argument.IsPositive(width, nameof(width));
27 | Argument.IsPositive(height, nameof(height));
28 |
29 | this.width = width;
30 | this.height = height;
31 | // Scan lines in Windows bitmaps should be aligned by 4 bytes (DWORDs)
32 | this.stride = (width * 3 + 3) / 4 * 4;
33 | }
34 |
35 | #region IVideoEncoder Members
36 |
37 | /// Video codec.
38 | public FourCC Codec => CodecIds.Uncompressed;
39 |
40 | ///
41 | /// Number of bits per pixel in encoded image.
42 | ///
43 | public BitsPerPixel BitsPerPixel => BitsPerPixel.Bpp24;
44 |
45 | ///
46 | /// Maximum size of encoded frame.
47 | ///
48 | public int MaxEncodedSize => stride * height;
49 |
50 | ///
51 | /// Encodes a frame.
52 | ///
53 | public int EncodeFrame(byte[] source, int srcOffset, byte[] destination, int destOffset, out bool isKeyFrame)
54 | {
55 | Argument.IsNotNull(source, nameof(source));
56 | Argument.IsNotNegative(srcOffset, nameof(srcOffset));
57 | Argument.ConditionIsMet(srcOffset + 4 * width * height <= source.Length,
58 | "Source end offset exceeds the source length.");
59 | Argument.IsNotNull(destination, nameof(destination));
60 | Argument.IsNotNegative(destOffset, nameof(destOffset));
61 |
62 | #if NET5_0_OR_GREATER
63 | return EncodeFrame(source.AsSpan(srcOffset), destination.AsSpan(destOffset), out isKeyFrame);
64 | #else
65 | // Flip vertical and convert to 24 bpp
66 | for (var y = 0; y < height; y++)
67 | {
68 | var srcLineOffset = srcOffset + y * width * 4;
69 | var destLineOffset = destOffset + (height - 1 - y) * stride;
70 | BitmapUtils.Bgr32ToBgr24(source, srcLineOffset, destination, destLineOffset, width);
71 | }
72 | isKeyFrame = true;
73 | return MaxEncodedSize;
74 | #endif
75 | }
76 |
77 | #if NET5_0_OR_GREATER
78 | ///
79 | /// Encodes a frame.
80 | ///
81 | public int EncodeFrame(ReadOnlySpan source, Span destination, out bool isKeyFrame)
82 | {
83 | Argument.ConditionIsMet(4 * width * height <= source.Length,
84 | "Source end offset exceeds the source length.");
85 |
86 | // Flip vertical and convert to 24 bpp
87 | for (var y = 0; y < height; y++)
88 | {
89 | var srcOffset = y * width * 4;
90 | var destOffset = (height - 1 - y) * stride;
91 | BitmapUtils.Bgr32ToBgr24(source.Slice(srcOffset), destination.Slice(destOffset), width);
92 | }
93 | isKeyFrame = true;
94 | return MaxEncodedSize;
95 | }
96 | #endif
97 |
98 | #endregion
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/SharpAvi/Codecs/VfwApi.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Runtime.InteropServices;
6 |
7 | namespace SharpAvi.Codecs
8 | {
9 | ///
10 | /// Selected constants, structures and functions from Video for Windows APIs.
11 | ///
12 | ///
13 | /// Useful for implementing stream encoding using VCM codecs.
14 | /// See Windows API documentation on the meaning and usage of all this stuff.
15 | ///
16 | internal static class VfwApi
17 | {
18 | public const int ICERR_OK = 0;
19 |
20 | public static string GetErrorDescription(int error)
21 | {
22 | switch (error)
23 | {
24 | case 0: return "OK";
25 | case -1: return "Unsupported";
26 | case -2: return "Bad format";
27 | case -3: return "Memory";
28 | case -4: return "Internal";
29 | case -5: return "Bad flags";
30 | case -6: return "Bad parameter";
31 | case -7: return "Bad size";
32 | case -8: return "Bad handle";
33 | case -9: return "Can't update";
34 | case -10: return "Abort";
35 | case -100: return "Error";
36 | case -200: return "Bad bit depth";
37 | case -201: return "Bad image size";
38 | default: return null;
39 | }
40 | }
41 |
42 | public const short ICMODE_COMPRESS = 1;
43 |
44 | public const int ICCOMPRESS_KEYFRAME = 0x00000001;
45 |
46 | public const int AVIIF_KEYFRAME = 0x00000010;
47 |
48 | private const int VIDCF_QUALITY = 0x0001;
49 | private const int VIDCF_COMPRESSFRAMES = 0x0008;
50 | private const int VIDCF_FASTTEMPORALC = 0x0020;
51 |
52 | public const int ICM_COMPRESS_GET_SIZE = 0x4005;
53 | public const int ICM_COMPRESS_QUERY = 0x4006;
54 | public const int ICM_COMPRESS_BEGIN = 0x4007;
55 | public const int ICM_COMPRESS_END = 0x4009;
56 | public const int ICM_COMPRESS_FRAMES_INFO = 0x4046;
57 |
58 |
59 | ///
60 | /// Corresponds to the BITMAPINFOHEADER structure.
61 | ///
62 | [StructLayout(LayoutKind.Sequential)]
63 | public struct BitmapInfoHeader
64 | {
65 | public uint SizeOfStruct;
66 | public int Width;
67 | public int Height;
68 | public ushort Planes;
69 | public ushort BitCount;
70 | public uint Compression;
71 | public uint ImageSize;
72 | public int PixelsPerMeterX;
73 | public int PixelsPerMeterY;
74 | public uint ColorsUsed;
75 | public uint ColorsImportant;
76 | }
77 |
78 | ///
79 | /// Corresponds to the ICINFO structure.
80 | ///
81 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
82 | public unsafe struct CompressorInfo
83 | {
84 | private uint sizeOfStruct;
85 | private uint fccType;
86 | private uint fccHandler;
87 | private uint flags;
88 | private uint version;
89 | private uint versionIcm;
90 | private fixed char szName[16];
91 | private fixed char szDescription[128];
92 | private fixed char szDriver[128];
93 |
94 | public bool SupportsQuality
95 | {
96 | get { return (flags & VIDCF_QUALITY) == VIDCF_QUALITY; }
97 | }
98 |
99 | public bool SupportsFastTemporalCompression
100 | {
101 | get { return (flags & VIDCF_FASTTEMPORALC) == VIDCF_FASTTEMPORALC; }
102 | }
103 |
104 | public bool RequestsCompressFrames
105 | {
106 | get { return (flags & VIDCF_COMPRESSFRAMES) == VIDCF_COMPRESSFRAMES; }
107 | }
108 |
109 | public string Name
110 | {
111 | get
112 | {
113 | fixed (char* name = szName)
114 | {
115 | return new string(name);
116 | }
117 | }
118 | }
119 |
120 | public string Description
121 | {
122 | get
123 | {
124 | fixed (char* desc = szDescription)
125 | {
126 | return new string(desc);
127 | }
128 | }
129 | }
130 | }
131 |
132 | ///
133 | /// Corresponds to the ICCOMPRESSFRAMES structure.
134 | ///
135 | [StructLayout(LayoutKind.Sequential)]
136 | public struct CompressFramesInfo
137 | {
138 | private uint flags;
139 | public IntPtr OutBitmapInfoPtr;
140 | private int outputSize;
141 | public IntPtr InBitmapInfoPtr;
142 | private int inputSize;
143 | public int StartFrame;
144 | public int FrameCount;
145 | /// Quality from 0 to 10000.
146 | public int Quality;
147 | private int dataRate;
148 | /// Interval between key frames.
149 | /// Equal to 1 if each frame is a key frame.
150 | public int KeyRate;
151 | ///
152 | public uint FrameRateNumerator;
153 | public uint FrameRateDenominator;
154 | private uint overheadPerFrame;
155 | private uint reserved2;
156 | private IntPtr getDataFuncPtr;
157 | private IntPtr setDataFuncPtr;
158 | }
159 |
160 | private const string VFW_DLL = "msvfw32.dll";
161 |
162 | [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
163 | public static extern IntPtr ICOpen(uint fccType, uint fccHandler, int mode);
164 |
165 | [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
166 | public static extern int ICClose(IntPtr handle);
167 |
168 | [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
169 | public static extern int ICSendMessage(IntPtr handle, int message, IntPtr param1, IntPtr param2);
170 |
171 | [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
172 | public static extern int ICSendMessage(IntPtr handle, int message, ref BitmapInfoHeader inHeader, ref BitmapInfoHeader outHeader);
173 |
174 | [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
175 | public static extern int ICSendMessage(IntPtr handle, int message, ref CompressFramesInfo info, int sizeOfInfo);
176 |
177 | [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
178 | public static extern int ICGetInfo(IntPtr handle, out CompressorInfo info, int infoSize);
179 |
180 | [DllImport(VFW_DLL, CallingConvention = CallingConvention.Cdecl)]
181 | public static extern int ICCompress(IntPtr handle, int inFlags,
182 | ref BitmapInfoHeader outHeader, IntPtr encodedData,
183 | ref BitmapInfoHeader inHeader, IntPtr frameData,
184 | out int chunkID, out int outFlags, int frameNumber,
185 | int requestedFrameSize, int requestedQuality,
186 | IntPtr prevHeaderPtr, IntPtr prevFrameData);
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/SharpAvi/Format/Index1Entry.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi.Format
2 | {
3 | ///
4 | /// Entry of AVI v1 index.
5 | ///
6 | internal sealed class Index1Entry
7 | {
8 | public bool IsKeyFrame { get; set; }
9 | public uint DataOffset { get; set; }
10 | public uint DataSize { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SharpAvi/Format/IndexType.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi.Format
2 | {
3 | internal enum IndexType : byte
4 | {
5 | Indexes = 0x00,
6 | Chunks = 0x01,
7 | Data = 0x80,
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/SharpAvi/Format/KnownFourCCs.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Utilities;
2 |
3 | namespace SharpAvi.Format
4 | {
5 | ///
6 | /// Contains definitions of known FOURCC values.
7 | ///
8 | internal static class KnownFourCCs
9 | {
10 | ///
11 | /// RIFF chunk indentifiers used in AVI format.
12 | ///
13 | internal static class Chunks
14 | {
15 | /// Main AVI header.
16 | public static readonly FourCC AviHeader = new FourCC("avih");
17 |
18 | /// Stream header.
19 | public static readonly FourCC StreamHeader = new FourCC("strh");
20 |
21 | /// Stream format.
22 | public static readonly FourCC StreamFormat = new FourCC("strf");
23 |
24 | /// Stream name.
25 | public static readonly FourCC StreamName = new FourCC("strn");
26 |
27 | /// Stream index.
28 | public static readonly FourCC StreamIndex = new FourCC("indx");
29 |
30 | /// Index v1.
31 | public static readonly FourCC Index1 = new FourCC("idx1");
32 |
33 | /// OpenDML header.
34 | public static readonly FourCC OpenDmlHeader = new FourCC("dmlh");
35 |
36 | /// Junk chunk.
37 | public static readonly FourCC Junk = new FourCC("JUNK");
38 |
39 | /// Gets the identifier of a video frame chunk.
40 | /// Sequential number of the stream.
41 | /// Whether stream contents is compressed.
42 | public static FourCC VideoFrame(int streamIndex, bool compressed)
43 | {
44 | CheckStreamIndex(streamIndex);
45 | return string.Format(compressed ? "{0:00}dc" : "{0:00}db", streamIndex);
46 | }
47 |
48 | /// Gets the identifier of an audio data chunk.
49 | /// Sequential number of the stream.
50 | public static FourCC AudioData(int streamIndex)
51 | {
52 | CheckStreamIndex(streamIndex);
53 | return string.Format("{0:00}wb", streamIndex);
54 | }
55 |
56 | /// Gets the identifier of an index chunk.
57 | /// Sequential number of the stream.
58 | public static FourCC IndexData(int streamIndex)
59 | {
60 | CheckStreamIndex(streamIndex);
61 | return string.Format("ix{0:00}", streamIndex);
62 | }
63 |
64 | private static void CheckStreamIndex(int streamIndex)
65 | {
66 | Argument.IsInRange(streamIndex, 0, 99, nameof(streamIndex));
67 | }
68 | }
69 |
70 | ///
71 | /// RIFF lists identifiers used in AVI format.
72 | ///
73 | internal static class Lists
74 | {
75 | /// Top-level AVI list.
76 | public static readonly FourCC Avi = new FourCC("AVI");
77 |
78 | /// Top-level extended AVI list.
79 | public static readonly FourCC AviExtended = new FourCC("AVIX");
80 |
81 | /// Header list.
82 | public static readonly FourCC Header = new FourCC("hdrl");
83 |
84 | /// List containing stream information.
85 | public static readonly FourCC Stream = new FourCC("strl");
86 |
87 | /// List containing OpenDML headers.
88 | public static readonly FourCC OpenDml = new FourCC("odml");
89 |
90 | /// List with content chunks.
91 | public static readonly FourCC Movie = new FourCC("movi");
92 | }
93 |
94 | ///
95 | /// Identifiers of the list types used in RIFF format.
96 | ///
97 | internal static class ListTypes
98 | {
99 | /// Top-level list type.
100 | public static readonly FourCC Riff = new FourCC("RIFF");
101 |
102 | /// Non top-level list type.
103 | public static readonly FourCC List = new FourCC("LIST");
104 | }
105 |
106 | ///
107 | /// Identifiers of the stream types used in AVI format.
108 | ///
109 | internal static class StreamTypes
110 | {
111 | /// Video stream.
112 | public static readonly FourCC Video = new FourCC("vids");
113 |
114 | /// Audio stream.
115 | public static readonly FourCC Audio = new FourCC("auds");
116 | }
117 |
118 |
119 | ///
120 | /// Identifiers of codec types used in Video for Windows API.
121 | ///
122 | internal static class CodecTypes
123 | {
124 | /// Video codec.
125 | public static readonly FourCC Video = new FourCC("VIDC");
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/SharpAvi/Format/MainHeaderFlags.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SharpAvi.Format
4 | {
5 | [Flags]
6 | internal enum MainHeaderFlags : uint
7 | {
8 | HasIndex = 0x00000010U,
9 | MustUseIndex = 0x00000020U,
10 | IsInterleaved = 0x00000100U,
11 | TrustChunkType = 0x00000800U,
12 | WasCaptureFile = 0x00010000U,
13 | Copyrighted = 0x000200000U,
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SharpAvi/Format/StandardIndexEntry.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi.Format
2 | {
3 | internal sealed class StandardIndexEntry
4 | {
5 | public long DataOffset;
6 | public uint DataSize;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/SharpAvi/Format/StreamHeaderFlags.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi.Format
2 | {
3 | internal enum StreamHeaderFlags : uint
4 | {
5 | Disabled = 0x00000001,
6 | VideoPaletteChanges = 0x00010000,
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/SharpAvi/Format/SuperIndexEntry.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi.Format
2 | {
3 | internal sealed class SuperIndexEntry
4 | {
5 | public long ChunkOffset;
6 | public int ChunkSize;
7 | public int Duration;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/SharpAvi/FourCC.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Utilities;
2 | using System;
3 | using System.Linq;
4 |
5 | namespace SharpAvi
6 | {
7 | ///
8 | /// Represents four character code (FOURCC).
9 | ///
10 | ///
11 | /// FOURCCs are used widely across AVI format.
12 | ///
13 | public struct FourCC : IEquatable
14 | {
15 | private readonly uint valueDWord;
16 | private readonly string valueString;
17 |
18 | ///
19 | /// Creates a new instance of with an integer value.
20 | ///
21 | /// Integer value of FOURCC.
22 | public FourCC(uint value)
23 | {
24 | valueDWord = value;
25 | valueString = new string
26 | (
27 | new[]
28 | {
29 | (char)(value & 0xFF),
30 | (char)((value & 0xFF00) >> 8),
31 | (char)((value & 0xFF0000) >> 16),
32 | (char)((value & 0xFF000000U) >> 24)
33 | }
34 | );
35 | }
36 |
37 | ///
38 | /// Creates a new instance of with a string value.
39 | ///
40 | ///
41 | /// String value of FOURCC.
42 | /// Should be not longer than 4 characters, all of them are printable ASCII characters.
43 | ///
44 | ///
45 | /// If the value of is shorter than 4 characters, it is right-padded with spaces.
46 | ///
47 | public FourCC(string value)
48 | {
49 | Argument.IsNotNull(value, nameof(value));
50 | Argument.Meets(value.Length <= 4, nameof(value), "Value cannot be longer than 4 characters.");
51 | Argument.Meets(value.All(c => ' ' <= c && c <= '~'), nameof(value), "Value can only contain printable ASCII characters.");
52 |
53 | valueString = value.PadRight(4);
54 | valueDWord = valueString[0] + ((uint)valueString[1] << 8) + ((uint)valueString[2] << 16) + ((uint)valueString[3] << 24);
55 | }
56 |
57 | ///
58 | /// Returns string representation of this instance.
59 | ///
60 | ///
61 | /// String value if all bytes are printable ASCII characters. Otherwise, the hexadecimal representation of integer value.
62 | ///
63 | public override string ToString()
64 | {
65 | var isPrintable = valueString.All(c => ' ' <= c && c <= '~');
66 | return isPrintable ? valueString : valueDWord.ToString("X8");
67 | }
68 |
69 | ///
70 | /// Gets hash code of this instance.
71 | ///
72 | public override int GetHashCode() => valueDWord.GetHashCode();
73 |
74 | ///
75 | /// Determines whether this instance is equal to other object.
76 | ///
77 | public override bool Equals(object obj)
78 | => obj is FourCC other ? Equals(other) : base.Equals(obj);
79 |
80 | ///
81 | /// Determines whether this instance is equal to another value.
82 | ///
83 | ///
84 | ///
85 | public bool Equals(FourCC other) => this.valueDWord == other.valueDWord;
86 |
87 |
88 | ///
89 | /// Converts an integer value to .
90 | ///
91 | public static implicit operator FourCC(uint value) => new FourCC(value);
92 |
93 | ///
94 | /// Converts a string value to .
95 | ///
96 | public static implicit operator FourCC(string value) => new FourCC(value);
97 |
98 | ///
99 | /// Gets the integer value of instance.
100 | ///
101 | public static explicit operator uint(FourCC value) => value.valueDWord;
102 |
103 | ///
104 | /// Gets the string value of instance.
105 | ///
106 | public static explicit operator string(FourCC value) => value.valueString;
107 |
108 | ///
109 | /// Determines whether two instances of are equal.
110 | ///
111 | public static bool operator ==(FourCC value1, FourCC value2) => value1.Equals(value2);
112 |
113 | ///
114 | /// Determines whether two instances of are not equal.
115 | ///
116 | public static bool operator !=(FourCC value1, FourCC value2) => !value1.Equals(value2);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/SharpAvi/Output/AsyncAudioStreamWrapper.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Utilities;
2 | using System;
3 | using System.Threading.Tasks;
4 |
5 | namespace SharpAvi.Output
6 | {
7 | ///
8 | /// Adds asynchronous writes support for underlying stream.
9 | ///
10 | internal class AsyncAudioStreamWrapper : AudioStreamWrapperBase
11 | {
12 | private readonly SequentialInvoker writeInvoker = new SequentialInvoker();
13 |
14 | public AsyncAudioStreamWrapper(IAviAudioStreamInternal baseStream)
15 | : base(baseStream)
16 | {
17 | }
18 |
19 | public override void WriteBlock(byte[] data, int startIndex, int length)
20 | {
21 | Argument.IsNotNull(data, nameof(data));
22 | Argument.IsNotNegative(startIndex, nameof(startIndex));
23 | Argument.IsPositive(length, nameof(length));
24 | Argument.ConditionIsMet(startIndex + length <= data.Length, "End offset exceeds the length of data.");
25 |
26 | writeInvoker.Invoke(() => base.WriteBlock(data, startIndex, length));
27 | }
28 |
29 | public override Task WriteBlockAsync(byte[] data, int startIndex, int length)
30 | {
31 | Argument.IsNotNull(data, nameof(data));
32 | Argument.IsNotNegative(startIndex, nameof(startIndex));
33 | Argument.IsPositive(length, nameof(length));
34 | Argument.ConditionIsMet(startIndex + length <= data.Length, "End offset exceeds the length of data.");
35 |
36 | return writeInvoker.InvokeAsync(() => base.WriteBlock(data, startIndex, length));
37 | }
38 |
39 | #if NET5_0_OR_GREATER
40 | public unsafe override void WriteBlock(ReadOnlySpan data)
41 | {
42 | Argument.Meets(data.Length > 0, nameof(data), "Cannot write an empty block.");
43 |
44 | fixed (void* ptr = data)
45 | {
46 | var dataPtr = new IntPtr(ptr);
47 | var dataLength = data.Length;
48 | writeInvoker.Invoke(() =>
49 | {
50 | var dataSpan = new Span(dataPtr.ToPointer(), dataLength);
51 | base.WriteBlock(dataSpan);
52 | });
53 | }
54 | }
55 |
56 | public override Task WriteBlockAsync(ReadOnlyMemory data)
57 | {
58 | Argument.Meets(data.Length > 0, nameof(data), "Cannot write an empty block.");
59 |
60 | return writeInvoker.InvokeAsync(() => base.WriteBlock(data.Span));
61 | }
62 | #endif
63 |
64 | public override void FinishWriting()
65 | {
66 | // Perform all pending writes and then let the base stream to finish
67 | // (possibly writing some more data synchronously)
68 | writeInvoker.WaitForPendingInvocations();
69 |
70 | base.FinishWriting();
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/SharpAvi/Output/AsyncVideoStreamWrapper.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Utilities;
2 | using System;
3 | using System.Threading.Tasks;
4 |
5 | namespace SharpAvi.Output
6 | {
7 | ///
8 | /// Adds asynchronous writes support for underlying stream.
9 | ///
10 | internal class AsyncVideoStreamWrapper : VideoStreamWrapperBase
11 | {
12 | private readonly SequentialInvoker writeInvoker = new SequentialInvoker();
13 |
14 | public AsyncVideoStreamWrapper(IAviVideoStreamInternal baseStream)
15 | : base(baseStream)
16 | {
17 | }
18 |
19 | public override void WriteFrame(bool isKeyFrame, byte[] frameData, int startIndex, int length)
20 | {
21 | Argument.IsNotNull(frameData, nameof(frameData));
22 | Argument.IsNotNegative(startIndex, nameof(startIndex));
23 | Argument.IsPositive(length, nameof(length));
24 | Argument.ConditionIsMet(startIndex + length <= frameData.Length, "End offset exceeds the length of frame data.");
25 |
26 | writeInvoker.Invoke(() => base.WriteFrame(isKeyFrame, frameData, startIndex, length));
27 | }
28 |
29 | public override Task WriteFrameAsync(bool isKeyFrame, byte[] frameData, int startIndex, int length)
30 | {
31 | Argument.IsNotNull(frameData, nameof(frameData));
32 | Argument.IsNotNegative(startIndex, nameof(startIndex));
33 | Argument.IsPositive(length, nameof(length));
34 | Argument.ConditionIsMet(startIndex + length <= frameData.Length, "End offset exceeds the length of frame data.");
35 |
36 | return writeInvoker.InvokeAsync(() => base.WriteFrame(isKeyFrame, frameData, startIndex, length));
37 | }
38 |
39 | #if NET5_0_OR_GREATER
40 | public unsafe override void WriteFrame(bool isKeyFrame, ReadOnlySpan frameData)
41 | {
42 | Argument.Meets(frameData.Length > 0, nameof(frameData), "Cannot write an empty frame.");
43 |
44 | fixed (void* ptr = frameData)
45 | {
46 | var dataPtr = new IntPtr(ptr);
47 | var dataLength = frameData.Length;
48 | writeInvoker.Invoke(() =>
49 | {
50 | var dataSpan = new Span(dataPtr.ToPointer(), dataLength);
51 | base.WriteFrame(isKeyFrame, dataSpan);
52 | });
53 | }
54 | }
55 |
56 | public override Task WriteFrameAsync(bool isKeyFrame, ReadOnlyMemory frameData)
57 | {
58 | Argument.Meets(frameData.Length > 0, nameof(frameData), "Cannot write an empty frame.");
59 |
60 | return writeInvoker.InvokeAsync(() => base.WriteFrame(isKeyFrame, frameData.Span));
61 | }
62 | #endif
63 |
64 | public override void FinishWriting()
65 | {
66 | // Perform all pending writes and then let the base stream to finish
67 | // (possibly writing some more data synchronously)
68 | writeInvoker.WaitForPendingInvocations();
69 |
70 | base.FinishWriting();
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/SharpAvi/Output/AudioStreamWrapperBase.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Utilities;
2 | using System;
3 | using System.Threading.Tasks;
4 |
5 | namespace SharpAvi.Output
6 | {
7 | ///
8 | /// Base class for wrappers around .
9 | ///
10 | ///
11 | /// Simply delegates all operations to wrapped stream.
12 | ///
13 | internal abstract class AudioStreamWrapperBase : IAviAudioStreamInternal, IDisposable
14 | {
15 | protected AudioStreamWrapperBase(IAviAudioStreamInternal baseStream)
16 | {
17 | Argument.IsNotNull(baseStream, nameof(baseStream));
18 |
19 | this.BaseStream = baseStream;
20 | }
21 |
22 | protected IAviAudioStreamInternal BaseStream { get; }
23 |
24 | public virtual void Dispose() => (BaseStream as IDisposable)?.Dispose();
25 |
26 | public virtual int ChannelCount
27 | {
28 | get { return BaseStream.ChannelCount; }
29 | set { BaseStream.ChannelCount = value; }
30 | }
31 |
32 | public virtual int SamplesPerSecond
33 | {
34 | get { return BaseStream.SamplesPerSecond; }
35 | set { BaseStream.SamplesPerSecond = value; }
36 | }
37 |
38 | public virtual int BitsPerSample
39 | {
40 | get { return BaseStream.BitsPerSample; }
41 | set { BaseStream.BitsPerSample = value; }
42 | }
43 |
44 | public virtual short Format
45 | {
46 | get { return BaseStream.Format; }
47 | set { BaseStream.Format = value; }
48 | }
49 |
50 | public virtual int BytesPerSecond
51 | {
52 | get { return BaseStream.BytesPerSecond; }
53 | set { BaseStream.BytesPerSecond = value; }
54 | }
55 |
56 | public virtual int Granularity
57 | {
58 | get { return BaseStream.Granularity; }
59 | set { BaseStream.Granularity = value; }
60 | }
61 |
62 | public virtual byte[] FormatSpecificData
63 | {
64 | get { return BaseStream.FormatSpecificData; }
65 | set { BaseStream.FormatSpecificData = value; }
66 | }
67 |
68 | public virtual void WriteBlock(byte[] data, int startIndex, int length)
69 | {
70 | Argument.IsNotNull(data, nameof(data));
71 | Argument.IsNotNegative(startIndex, nameof(startIndex));
72 | Argument.IsPositive(length, nameof(length));
73 | Argument.ConditionIsMet(startIndex + length <= data.Length, "End offset exceeds the length of data.");
74 |
75 | BaseStream.WriteBlock(data, startIndex, length);
76 | }
77 |
78 | public virtual Task WriteBlockAsync(byte[] data, int startIndex, int length)
79 | {
80 | Argument.IsNotNull(data, nameof(data));
81 | Argument.IsNotNegative(startIndex, nameof(startIndex));
82 | Argument.IsPositive(length, nameof(length));
83 | Argument.ConditionIsMet(startIndex + length <= data.Length, "End offset exceeds the length of data.");
84 |
85 | return BaseStream.WriteBlockAsync(data, startIndex, length);
86 | }
87 |
88 | #if NET5_0_OR_GREATER
89 | public virtual void WriteBlock(ReadOnlySpan data)
90 | {
91 | Argument.Meets(data.Length > 0, nameof(data), "Cannot write an empty block.");
92 |
93 | BaseStream.WriteBlock(data);
94 | }
95 |
96 | public virtual Task WriteBlockAsync(ReadOnlyMemory data)
97 | {
98 | Argument.Meets(data.Length > 0, nameof(data), "Cannot write an empty block.");
99 |
100 | return BaseStream.WriteBlockAsync(data);
101 | }
102 | #endif
103 |
104 | public int BlocksWritten => BaseStream.BlocksWritten;
105 |
106 | public int Index => BaseStream.Index;
107 |
108 | public virtual string Name
109 | {
110 | get { return BaseStream.Name; }
111 | set { BaseStream.Name = value; }
112 | }
113 |
114 | public FourCC StreamType => BaseStream.StreamType;
115 |
116 | public FourCC ChunkId => BaseStream.ChunkId;
117 |
118 | public virtual void PrepareForWriting() => BaseStream.PrepareForWriting();
119 |
120 | public virtual void FinishWriting() => BaseStream.FinishWriting();
121 |
122 | public void WriteHeader() => BaseStream.WriteHeader();
123 |
124 | public void WriteFormat() => BaseStream.WriteFormat();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/SharpAvi/Output/AviAudioStream.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Format;
2 | using SharpAvi.Utilities;
3 | using System;
4 | using System.Threading.Tasks;
5 |
6 | namespace SharpAvi.Output
7 | {
8 | internal class AviAudioStream : AviStreamBase, IAviAudioStreamInternal
9 | {
10 | private readonly IAviStreamWriteHandler writeHandler;
11 | private int channelCount = 1;
12 | private int samplesPerSecond = 44100;
13 | private int bitsPerSample = 8;
14 | private short format = AudioFormats.Unknown;
15 | private int bytesPerSecond = 44100;
16 | private int granularity = 1;
17 | private byte[] formatData;
18 | private int blocksWritten;
19 |
20 |
21 | public AviAudioStream(int index, IAviStreamWriteHandler writeHandler,
22 | int channelCount, int samplesPerSecond, int bitsPerSample)
23 | : base(index)
24 | {
25 | Argument.IsNotNull(writeHandler, nameof(writeHandler));
26 |
27 | this.writeHandler = writeHandler;
28 |
29 | this.format = AudioFormats.Pcm;
30 | this.formatData = null;
31 |
32 | this.channelCount = channelCount;
33 | this.samplesPerSecond = samplesPerSecond;
34 | this.bitsPerSample = bitsPerSample;
35 | this.granularity = (bitsPerSample * channelCount + 7) / 8;
36 | this.bytesPerSecond = granularity * samplesPerSecond;
37 | }
38 |
39 |
40 | public int ChannelCount
41 | {
42 | get { return channelCount; }
43 | set
44 | {
45 | CheckNotFrozen();
46 | channelCount = value;
47 | }
48 | }
49 |
50 | public int SamplesPerSecond
51 | {
52 | get { return samplesPerSecond; }
53 | set
54 | {
55 | CheckNotFrozen();
56 | samplesPerSecond = value;
57 | }
58 | }
59 |
60 | public int BitsPerSample
61 | {
62 | get { return bitsPerSample; }
63 | set
64 | {
65 | CheckNotFrozen();
66 | bitsPerSample = value;
67 | }
68 | }
69 |
70 | public short Format
71 | {
72 | get { return format; }
73 | set
74 | {
75 | CheckNotFrozen();
76 | format = value;
77 | }
78 | }
79 |
80 | public int BytesPerSecond
81 | {
82 | get { return bytesPerSecond; }
83 | set
84 | {
85 | CheckNotFrozen();
86 | bytesPerSecond = value;
87 | }
88 | }
89 |
90 | public int Granularity
91 | {
92 | get { return granularity; }
93 | set
94 | {
95 | CheckNotFrozen();
96 | granularity = value;
97 | }
98 | }
99 |
100 | public byte[] FormatSpecificData
101 | {
102 | get { return formatData; }
103 | set
104 | {
105 | CheckNotFrozen();
106 | formatData = value;
107 | }
108 | }
109 |
110 | public void WriteBlock(byte[] data, int startIndex, int length)
111 | {
112 | Argument.IsNotNull(data, nameof(data));
113 | Argument.IsNotNegative(startIndex, nameof(startIndex));
114 | Argument.IsPositive(length, nameof(length));
115 | Argument.ConditionIsMet(startIndex + length <= data.Length, "End offset exceeds the length of data.");
116 |
117 | #if NET5_0_OR_GREATER
118 | WriteBlock(data.AsSpan(startIndex, length));
119 | #else
120 | writeHandler.WriteAudioBlock(this, data, startIndex, length);
121 | System.Threading.Interlocked.Increment(ref blocksWritten);
122 | #endif
123 | }
124 |
125 | public Task WriteBlockAsync(byte[] data, int startIndex, int length)
126 | => throw new NotSupportedException("Asynchronous writes are not supported.");
127 |
128 | #if NET5_0_OR_GREATER
129 | public void WriteBlock(ReadOnlySpan data)
130 | {
131 | Argument.Meets(data.Length > 0, nameof(data), "Cannot write an empty block.");
132 |
133 | writeHandler.WriteAudioBlock(this, data);
134 | System.Threading.Interlocked.Increment(ref blocksWritten);
135 | }
136 |
137 | public Task WriteBlockAsync(ReadOnlyMemory data)
138 | => throw new NotSupportedException("Asynchronous writes are not supported.");
139 | #endif
140 |
141 | public int BlocksWritten => blocksWritten;
142 |
143 |
144 | public override FourCC StreamType => KnownFourCCs.StreamTypes.Audio;
145 |
146 | protected override FourCC GenerateChunkId() => KnownFourCCs.Chunks.AudioData(Index);
147 |
148 | public override void WriteHeader() => writeHandler.WriteStreamHeader(this);
149 |
150 | public override void WriteFormat() => writeHandler.WriteStreamFormat(this);
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/SharpAvi/Output/AviStreamBase.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Utilities;
2 | using System;
3 |
4 | namespace SharpAvi.Output
5 | {
6 | internal abstract class AviStreamBase : IAviStream, IAviStreamInternal
7 | {
8 | private bool isFrozen;
9 | private string name;
10 | private FourCC chunkId;
11 |
12 | protected AviStreamBase(int index)
13 | {
14 | Argument.IsNotNegative(index, nameof(index));
15 |
16 | this.Index = index;
17 | }
18 |
19 | public int Index { get; }
20 |
21 | public string Name
22 | {
23 | get { return name; }
24 | set
25 | {
26 | CheckNotFrozen();
27 | name = value;
28 | }
29 | }
30 |
31 | public abstract FourCC StreamType { get; }
32 |
33 | public FourCC ChunkId
34 | {
35 | get
36 | {
37 | if (!isFrozen)
38 | {
39 | throw new InvalidOperationException("Chunk ID is not defined until the stream is frozen.");
40 | }
41 |
42 | return chunkId;
43 | }
44 | }
45 |
46 | public abstract void WriteHeader();
47 |
48 | public abstract void WriteFormat();
49 |
50 | ///
51 | /// Prepares the stream for writing.
52 | ///
53 | ///
54 | /// Default implementation freezes properties of the stream (further modifications are not allowed).
55 | ///
56 | public virtual void PrepareForWriting()
57 | {
58 | if (!isFrozen)
59 | {
60 | isFrozen = true;
61 |
62 | chunkId = GenerateChunkId();
63 | }
64 | }
65 |
66 | ///
67 | /// Performs actions before closing the stream.
68 | ///
69 | ///
70 | /// Default implementation does nothing.
71 | ///
72 | public virtual void FinishWriting()
73 | {
74 | }
75 |
76 |
77 | protected abstract FourCC GenerateChunkId();
78 |
79 | protected void CheckNotFrozen()
80 | {
81 | if (isFrozen)
82 | {
83 | throw new InvalidOperationException("No stream information can be changed after starting to write frames.");
84 | }
85 | }
86 |
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/SharpAvi/Output/AviStreamInfo.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Format;
2 | using SharpAvi.Utilities;
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | namespace SharpAvi.Output
7 | {
8 | internal class StreamInfo
9 | {
10 | public StreamInfo(FourCC standardIndexChunkId)
11 | {
12 | this.StandardIndexChunkId = standardIndexChunkId;
13 | FrameCount = 0;
14 | MaxChunkDataSize = 0;
15 | TotalDataSize = 0;
16 | }
17 |
18 | public int FrameCount { get; private set; }
19 |
20 | public int MaxChunkDataSize { get; private set; }
21 |
22 | public long TotalDataSize { get; private set; }
23 |
24 | public IList SuperIndex { get; } = new List();
25 |
26 | public IList StandardIndex { get; } = new List();
27 |
28 | public IList Index1 { get; } = new List();
29 |
30 | public FourCC StandardIndexChunkId { get; }
31 |
32 | public void OnFrameWritten(int chunkDataSize)
33 | {
34 | Argument.IsNotNegative(chunkDataSize, nameof(chunkDataSize));
35 |
36 | FrameCount++;
37 | MaxChunkDataSize = Math.Max(MaxChunkDataSize, chunkDataSize);
38 | TotalDataSize += chunkDataSize;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/SharpAvi/Output/AviVideoStream.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Format;
2 | using SharpAvi.Utilities;
3 | using System;
4 | using System.Threading.Tasks;
5 |
6 | namespace SharpAvi.Output
7 | {
8 | internal class AviVideoStream : AviStreamBase, IAviVideoStreamInternal
9 | {
10 | private readonly IAviStreamWriteHandler writeHandler;
11 | private FourCC streamCodec;
12 | private int width;
13 | private int height;
14 | private BitsPerPixel bitsPerPixel;
15 | private int framesWritten;
16 |
17 | public AviVideoStream(int index, IAviStreamWriteHandler writeHandler,
18 | int width, int height, BitsPerPixel bitsPerPixel)
19 | : base(index)
20 | {
21 | Argument.IsNotNull(writeHandler, nameof(writeHandler));
22 | Argument.IsPositive(width, nameof(width));
23 | Argument.IsPositive(height, nameof(height));
24 | Argument.IsEnumMember(bitsPerPixel, nameof(bitsPerPixel));
25 |
26 | this.writeHandler = writeHandler;
27 | this.width = width;
28 | this.height = height;
29 | this.bitsPerPixel = bitsPerPixel;
30 | this.streamCodec = CodecIds.Uncompressed;
31 | }
32 |
33 |
34 | public int Width
35 | {
36 | get { return width; }
37 | set
38 | {
39 | CheckNotFrozen();
40 | width = value;
41 | }
42 | }
43 |
44 | public int Height
45 | {
46 | get { return height; }
47 | set
48 | {
49 | CheckNotFrozen();
50 | height = value;
51 | }
52 | }
53 |
54 | public BitsPerPixel BitsPerPixel
55 | {
56 | get { return bitsPerPixel; }
57 | set
58 | {
59 | CheckNotFrozen();
60 | bitsPerPixel = value;
61 | }
62 | }
63 |
64 | public FourCC Codec
65 | {
66 | get { return streamCodec; }
67 | set
68 | {
69 | CheckNotFrozen();
70 | streamCodec = value;
71 | }
72 | }
73 |
74 | public void WriteFrame(bool isKeyFrame, byte[] frameData, int startIndex, int count)
75 | {
76 | Argument.IsNotNull(frameData, nameof(frameData));
77 | Argument.IsNotNegative(startIndex, nameof(startIndex));
78 | Argument.IsPositive(count, nameof(count));
79 | Argument.ConditionIsMet(startIndex + count <= frameData.Length, "End offset exceeds the length of frame data.");
80 |
81 | #if NET5_0_OR_GREATER
82 | WriteFrame(isKeyFrame, frameData.AsSpan(startIndex, count));
83 | #else
84 | writeHandler.WriteVideoFrame(this, isKeyFrame, frameData, startIndex, count);
85 | System.Threading.Interlocked.Increment(ref framesWritten);
86 | #endif
87 | }
88 |
89 | public Task WriteFrameAsync(bool isKeyFrame, byte[] frameData, int startIndex, int count)
90 | => throw new NotSupportedException("Asynchronous writes are not supported.");
91 |
92 | #if NET5_0_OR_GREATER
93 | public void WriteFrame(bool isKeyFrame, ReadOnlySpan frameData)
94 | {
95 | Argument.Meets(frameData.Length > 0, nameof(frameData), "Cannot write an empty frame.");
96 |
97 | writeHandler.WriteVideoFrame(this, isKeyFrame, frameData);
98 | System.Threading.Interlocked.Increment(ref framesWritten);
99 | }
100 |
101 | public Task WriteFrameAsync(bool isKeyFrame, ReadOnlyMemory frameData)
102 | => throw new NotSupportedException("Asynchronous writes are not supported.");
103 | #endif
104 |
105 | public int FramesWritten => framesWritten;
106 |
107 |
108 | public override FourCC StreamType => KnownFourCCs.StreamTypes.Video;
109 |
110 | protected override FourCC GenerateChunkId()
111 | => KnownFourCCs.Chunks.VideoFrame(Index, Codec != CodecIds.Uncompressed);
112 |
113 | public override void WriteHeader() => writeHandler.WriteStreamHeader(this);
114 |
115 | public override void WriteFormat() => writeHandler.WriteStreamFormat(this);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/SharpAvi/Output/EncodingAudioStreamWrapper.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Codecs;
2 | using SharpAvi.Utilities;
3 | using System;
4 | using System.Threading.Tasks;
5 |
6 | namespace SharpAvi.Output
7 | {
8 | ///
9 | /// Wrapper on the object to provide encoding.
10 | ///
11 | internal class EncodingAudioStreamWrapper : AudioStreamWrapperBase
12 | {
13 | private readonly IAudioEncoder encoder;
14 | private readonly bool ownsEncoder;
15 | private byte[] encodedBuffer;
16 | private readonly object syncBuffer = new object();
17 |
18 | public EncodingAudioStreamWrapper(IAviAudioStreamInternal baseStream, IAudioEncoder encoder, bool ownsEncoder)
19 | : base(baseStream)
20 | {
21 | Argument.IsNotNull(encoder, nameof(encoder));
22 |
23 | this.encoder = encoder;
24 | this.ownsEncoder = ownsEncoder;
25 | }
26 |
27 | public override void Dispose()
28 | {
29 | if (ownsEncoder)
30 | {
31 | (encoder as IDisposable)?.Dispose();
32 | }
33 |
34 | base.Dispose();
35 | }
36 |
37 | ///
38 | /// Number of channels in this audio stream.
39 | ///
40 | public override int ChannelCount
41 | {
42 | get { return encoder.ChannelCount; }
43 | set { ThrowPropertyDefinedByEncoder(); }
44 | }
45 |
46 | ///
47 | /// Sample rate, in samples per second (herz).
48 | ///
49 | public override int SamplesPerSecond
50 | {
51 | get { return encoder.SamplesPerSecond; }
52 | set { ThrowPropertyDefinedByEncoder(); }
53 | }
54 |
55 | ///
56 | /// Number of bits per sample per single channel (usually 8 or 16).
57 | ///
58 | public override int BitsPerSample
59 | {
60 | get { return encoder.BitsPerSample; }
61 | set { ThrowPropertyDefinedByEncoder(); }
62 | }
63 |
64 | ///
65 | /// Format of the audio data.
66 | ///
67 | public override short Format
68 | {
69 | get { return encoder.Format; }
70 | set { ThrowPropertyDefinedByEncoder(); }
71 | }
72 |
73 | ///
74 | /// Average byte rate of the stream.
75 | ///
76 | public override int BytesPerSecond
77 | {
78 | get { return encoder.BytesPerSecond; }
79 | set { ThrowPropertyDefinedByEncoder(); }
80 | }
81 |
82 | ///
83 | /// Size in bytes of minimum item of data in the stream.
84 | ///
85 | public override int Granularity
86 | {
87 | get { return encoder.Granularity; }
88 | set { ThrowPropertyDefinedByEncoder(); }
89 | }
90 |
91 | ///
92 | /// Extra data defined by a specific format which should be added to the stream header.
93 | ///
94 | public override byte[] FormatSpecificData
95 | {
96 | get { return encoder.FormatSpecificData; }
97 | set { ThrowPropertyDefinedByEncoder(); }
98 | }
99 |
100 | ///
101 | /// Encodes and writes a block of audio data.
102 | ///
103 | public override void WriteBlock(byte[] data, int startIndex, int length)
104 | {
105 | Argument.IsNotNull(data, nameof(data));
106 | Argument.IsNotNegative(startIndex, nameof(startIndex));
107 | Argument.IsPositive(length, nameof(length));
108 | Argument.ConditionIsMet(startIndex + length <= data.Length, "End offset exceeds the length of data.");
109 |
110 | // Prevent accessing encoded buffer by multiple threads simultaneously
111 | lock (syncBuffer)
112 | {
113 | EnsureBufferIsSufficient(length);
114 | var encodedLength = encoder.EncodeBlock(data, startIndex, length, encodedBuffer, 0);
115 | if (encodedLength > 0)
116 | {
117 | base.WriteBlock(encodedBuffer, 0, encodedLength);
118 | }
119 | }
120 | }
121 |
122 | public override Task WriteBlockAsync(byte[] data, int startIndex, int length)
123 | => throw new NotSupportedException("Asynchronous writes are not supported.");
124 |
125 | #if NET5_0_OR_GREATER
126 | public override void WriteBlock(ReadOnlySpan data)
127 | {
128 | Argument.Meets(data.Length > 0, nameof(data), "Cannot write an empty block.");
129 |
130 | // Prevent accessing encoded buffer by multiple threads simultaneously
131 | lock (syncBuffer)
132 | {
133 | EnsureBufferIsSufficient(data.Length);
134 | var encodedLength = encoder.EncodeBlock(data, encodedBuffer.AsSpan());
135 | if (encodedLength > 0)
136 | {
137 | base.WriteBlock(encodedBuffer.AsSpan(0, encodedLength));
138 | }
139 | }
140 | }
141 |
142 | public override Task WriteBlockAsync(ReadOnlyMemory data)
143 | => throw new NotSupportedException("Asynchronous writes are not supported.");
144 | #endif
145 |
146 | public override void PrepareForWriting()
147 | {
148 | // Set properties of the base stream
149 | BaseStream.ChannelCount = ChannelCount;
150 | BaseStream.SamplesPerSecond = SamplesPerSecond;
151 | BaseStream.BitsPerSample = BitsPerSample;
152 | BaseStream.Format = Format;
153 | BaseStream.FormatSpecificData = FormatSpecificData;
154 | BaseStream.BytesPerSecond = BytesPerSecond;
155 | BaseStream.Granularity = Granularity;
156 |
157 | base.PrepareForWriting();
158 | }
159 |
160 | public override void FinishWriting()
161 | {
162 | // Prevent accessing encoded buffer by multiple threads simultaneously
163 | lock (syncBuffer)
164 | {
165 | // Flush the encoder
166 | EnsureBufferIsSufficient(0);
167 | var encodedLength = encoder.Flush(encodedBuffer, 0);
168 | if (encodedLength > 0)
169 | {
170 | base.WriteBlock(encodedBuffer, 0, encodedLength);
171 | }
172 | }
173 |
174 | base.FinishWriting();
175 | }
176 |
177 |
178 | private void EnsureBufferIsSufficient(int sourceCount)
179 | {
180 | var maxLength = encoder.GetMaxEncodedLength(sourceCount);
181 | if (encodedBuffer != null && encodedBuffer.Length >= maxLength)
182 | {
183 | return;
184 | }
185 |
186 | var newLength = encodedBuffer == null ? 1024 : encodedBuffer.Length * 2;
187 | while (newLength < maxLength)
188 | {
189 | newLength *= 2;
190 | }
191 |
192 | encodedBuffer = new byte[newLength];
193 | }
194 |
195 | private void ThrowPropertyDefinedByEncoder()
196 | => throw new NotSupportedException("The value of the property is defined by the encoder.");
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/SharpAvi/Output/EncodingVideoStreamWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using SharpAvi.Codecs;
4 | using SharpAvi.Utilities;
5 |
6 | namespace SharpAvi.Output
7 | {
8 | ///
9 | /// Wrapper on the object to provide encoding.
10 | ///
11 | internal class EncodingVideoStreamWrapper : VideoStreamWrapperBase
12 | {
13 | private readonly IVideoEncoder encoder;
14 | private readonly bool ownsEncoder;
15 | private readonly byte[] encodedBuffer;
16 | private readonly object syncBuffer = new object();
17 |
18 | ///
19 | /// Creates a new instance of .
20 | ///
21 | /// Video stream to be wrapped.
22 | /// Encoder to be used.
23 | /// Whether to dispose the encoder.
24 | public EncodingVideoStreamWrapper(IAviVideoStreamInternal baseStream, IVideoEncoder encoder, bool ownsEncoder)
25 | : base(baseStream)
26 | {
27 | Argument.IsNotNull(encoder, nameof(encoder));
28 |
29 | this.encoder = encoder;
30 | this.ownsEncoder = ownsEncoder;
31 | encodedBuffer = new byte[encoder.MaxEncodedSize];
32 | }
33 |
34 | public override void Dispose()
35 | {
36 | if (ownsEncoder)
37 | {
38 | (encoder as IDisposable)?.Dispose();
39 | }
40 |
41 | base.Dispose();
42 | }
43 |
44 |
45 | /// Video codec.
46 | public override FourCC Codec
47 | {
48 | get => encoder.Codec;
49 | set => ThrowPropertyDefinedByEncoder();
50 | }
51 |
52 | /// Bits per pixel.
53 | public override BitsPerPixel BitsPerPixel
54 | {
55 | get => encoder.BitsPerPixel;
56 | set => ThrowPropertyDefinedByEncoder();
57 | }
58 |
59 | /// Encodes and writes a frame.
60 | public override void WriteFrame(bool isKeyFrame, byte[] frameData, int startIndex, int length)
61 | {
62 | Argument.IsNotNull(frameData, nameof(frameData));
63 | Argument.IsNotNegative(startIndex, nameof(startIndex));
64 | Argument.IsPositive(length, nameof(length));
65 | Argument.ConditionIsMet(startIndex + length <= frameData.Length, "End offset exceeds the length of frame data.");
66 |
67 | // Prevent accessing encoded buffer by multiple threads simultaneously
68 | lock (syncBuffer)
69 | {
70 | length = encoder.EncodeFrame(frameData, startIndex, encodedBuffer, 0, out isKeyFrame);
71 | base.WriteFrame(isKeyFrame, encodedBuffer, 0, length);
72 | }
73 | }
74 |
75 | public override Task WriteFrameAsync(bool isKeyFrame, byte[] frameData, int startIndex, int length)
76 | => throw new NotSupportedException("Asynchronous writes are not supported.");
77 |
78 | #if NET5_0_OR_GREATER
79 | public override void WriteFrame(bool isKeyFrame, ReadOnlySpan frameData)
80 | {
81 | Argument.Meets(frameData.Length > 0, nameof(frameData), "Cannot write an empty frame.");
82 |
83 | // Prevent accessing encoded buffer by multiple threads simultaneously
84 | lock (syncBuffer)
85 | {
86 | var encodedLength = encoder.EncodeFrame(frameData, encodedBuffer.AsSpan(), out isKeyFrame);
87 | base.WriteFrame(isKeyFrame, encodedBuffer.AsSpan(0, encodedLength));
88 | }
89 | }
90 | #endif
91 |
92 | public override void PrepareForWriting()
93 | {
94 | // Set properties of the base stream
95 | BaseStream.Codec = encoder.Codec;
96 | BaseStream.BitsPerPixel = encoder.BitsPerPixel;
97 |
98 | base.PrepareForWriting();
99 | }
100 |
101 |
102 | private void ThrowPropertyDefinedByEncoder()
103 | => throw new NotSupportedException("The value of the property is defined by the encoder.");
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/SharpAvi/Output/IAviAudioStream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace SharpAvi.Output
5 | {
6 | ///
7 | /// Audio stream of AVI file.
8 | ///
9 | public interface IAviAudioStream : IAviStream
10 | {
11 | ///
12 | /// Number of channels in this audio stream.
13 | ///
14 | ///
15 | /// For example, 1 for mono and 2 for stereo.
16 | ///
17 | int ChannelCount { get; set; }
18 |
19 | ///
20 | /// Sample rate, in samples per second (herz).
21 | ///
22 | int SamplesPerSecond { get; set; }
23 |
24 | ///
25 | /// Number of bits per sample per single channel (usually 8 or 16).
26 | ///
27 | int BitsPerSample { get; set; }
28 |
29 | ///
30 | /// Format of the audio data.
31 | ///
32 | ///
33 | /// The formats are defined in mmreg.h from Windows SDK.
34 | /// Some of the well-known formats are listed in the class.
35 | ///
36 | short Format { get; set; }
37 |
38 | ///
39 | /// Average byte rate of the stream.
40 | ///
41 | int BytesPerSecond { get; set; }
42 |
43 | ///
44 | /// Size in bytes of minimum item of data in the stream.
45 | ///
46 | ///
47 | /// Corresponds to nBlockAlign field of WAVEFORMATEX structure.
48 | ///
49 | int Granularity { get; set; }
50 |
51 | ///
52 | /// Extra data defined by a specific format which should be added to the stream header.
53 | ///
54 | ///
55 | /// Contains data of specific structure like MPEGLAYER3WAVEFORMAT that follow
56 | /// common WAVEFORMATEX field.
57 | ///
58 | byte[] FormatSpecificData { get; set; }
59 |
60 | ///
61 | /// Writes a block of audio data.
62 | ///
63 | /// Data buffer.
64 | /// Start index of data.
65 | /// Length of data.
66 | ///
67 | /// Division of audio data into blocks may be arbitrary.
68 | /// However, it is reasonable to write blocks of approximately the same duration
69 | /// as a single video frame.
70 | ///
71 | void WriteBlock(byte[] data, int startIndex, int length);
72 |
73 | ///
74 | /// Asynchronously writes a block of audio data.
75 | ///
76 | /// Data buffer.
77 | /// Start index of data.
78 | /// Length of data.
79 | ///
80 | /// A task representing the asynchronous write operation.
81 | ///
82 | ///
83 | /// Division of audio data into blocks may be arbitrary.
84 | /// However, it is reasonable to write blocks of approximately the same duration
85 | /// as a single video frame.
86 | /// The contents of should not be modified until this write operation ends.
87 | ///
88 | Task WriteBlockAsync(byte[] data, int startIndex, int length);
89 |
90 | #if NET5_0_OR_GREATER
91 | ///
92 | /// Writes a block of audio data.
93 | ///
94 | /// Data buffer.
95 | ///
96 | /// Division of audio data into blocks may be arbitrary.
97 | /// However, it is reasonable to write blocks of approximately the same duration
98 | /// as a single video frame.
99 | ///
100 | void WriteBlock(ReadOnlySpan data);
101 |
102 | ///
103 | /// Asynchronously writes a block of audio data.
104 | ///
105 | /// Data buffer.
106 | ///
107 | /// A task representing the asynchronous write operation.
108 | ///
109 | ///
110 | /// Division of audio data into blocks may be arbitrary.
111 | /// However, it is reasonable to write blocks of approximately the same duration
112 | /// as a single video frame.
113 | ///
114 | Task WriteBlockAsync(ReadOnlyMemory data);
115 | #endif
116 |
117 | ///
118 | /// Number of blocks written.
119 | ///
120 | int BlocksWritten { get; }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/SharpAvi/Output/IAviAudioStreamInternal.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi.Output
2 | {
3 | interface IAviAudioStreamInternal : IAviAudioStream, IAviStreamInternal
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SharpAvi/Output/IAviStream.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi.Output
2 | {
3 | ///
4 | /// A stream of AVI files.
5 | ///
6 | public interface IAviStream
7 | {
8 | ///
9 | /// Serial number of this stream in AVI file.
10 | ///
11 | int Index { get; }
12 |
13 | /// Name of the stream.
14 | /// May be used by some players when displaying the list of available streams.
15 | string Name { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/SharpAvi/Output/IAviStreamInternal.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SharpAvi.Output
4 | {
5 | ///
6 | /// Interface of streams used for internal workings of .
7 | ///
8 | internal interface IAviStreamInternal : IAviStream
9 | {
10 | ///
11 | /// Stream type written in AVISTREAMHEADER.
12 | ///
13 | FourCC StreamType { get; }
14 |
15 | ///
16 | /// Chunk ID for stream data.
17 | ///
18 | FourCC ChunkId { get; }
19 |
20 | ///
21 | /// Prepares the stream for writing.
22 | ///
23 | ///
24 | /// Called by when writing starts. More exactly,
25 | /// on the first call to the Write method of any stream, before any data is actually written.
26 | ///
27 | void PrepareForWriting();
28 |
29 | ///
30 | /// Finishes writing of the stream.
31 | ///
32 | ///
33 | /// Called by just before it closes (if writing had started).
34 | /// Allows to write a final data to the stream.
35 | /// This is not appropriate place for freeing resources, better to implement .
36 | /// All streams are disposed on disposing of even if writing had not yet started.
37 | ///
38 | void FinishWriting();
39 |
40 | ///
41 | /// Called to delegate writing of the stream header to a proper overload
42 | /// of IAviStreamWriteHandler.WriteStreamHeader.
43 | ///
44 | void WriteHeader();
45 |
46 | ///
47 | /// Called to delegate writing of the stream format to a proper overload
48 | /// of IAviStreamWriteHandler.WriteStreamFormat.
49 | ///
50 | void WriteFormat();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/SharpAvi/Output/IAviStreamWriteHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SharpAvi.Output
4 | {
5 | ///
6 | /// Interface of an object performing actual writing for the streams.
7 | ///
8 | internal interface IAviStreamWriteHandler
9 | {
10 | #if NET5_0_OR_GREATER
11 | void WriteVideoFrame(AviVideoStream stream, bool isKeyFrame, ReadOnlySpan frameData);
12 | void WriteAudioBlock(AviAudioStream stream, ReadOnlySpan blockData);
13 | #else
14 | void WriteVideoFrame(AviVideoStream stream, bool isKeyFrame, byte[] frameData, int startIndex, int count);
15 | void WriteAudioBlock(AviAudioStream stream, byte[] blockData, int startIndex, int count);
16 | #endif
17 |
18 | void WriteStreamHeader(AviVideoStream stream);
19 | void WriteStreamHeader(AviAudioStream stream);
20 |
21 | void WriteStreamFormat(AviVideoStream stream);
22 | void WriteStreamFormat(AviAudioStream stream);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/SharpAvi/Output/IAviVideoStream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace SharpAvi.Output
5 | {
6 | ///
7 | /// Video stream of AVI file.
8 | ///
9 | ///
10 | /// After the first invocation of no properties of the stream can be changed.
11 | ///
12 | public interface IAviVideoStream : IAviStream
13 | {
14 | /// Frame width.
15 | int Width { get; set; }
16 |
17 | /// Frame height.
18 | int Height { get; set; }
19 |
20 | ///
21 | /// Number of bits per pixel in the frame's image.
22 | ///
23 | BitsPerPixel BitsPerPixel { get; set; }
24 |
25 | ///
26 | /// ID of the codec used to encode the stream contents.
27 | ///
28 | FourCC Codec { get; set; }
29 |
30 | /// Writes a frame to the stream.
31 | /// Is this frame a key frame?
32 | /// Array containing the frame data.
33 | /// Index of the first byte of the frame data.
34 | /// Length of the frame data.
35 | void WriteFrame(bool isKeyFrame, byte[] frameData, int startIndex, int length);
36 |
37 | /// Asynchronously writes a frame to the stream.
38 | /// Is this frame a key frame?
39 | /// Array containing the frame data.
40 | /// Index of the first byte of the frame data.
41 | /// Length of the frame data.
42 | /// A task that represents the asynchronous write operation.
43 | ///
44 | /// The contents of should not be modified until this write operation ends.
45 | ///
46 | Task WriteFrameAsync(bool isKeyFrame, byte[] frameData, int startIndex, int length);
47 |
48 | #if NET5_0_OR_GREATER
49 | /// Writes a frame to the stream.
50 | /// Is this frame a key frame?
51 | /// Array containing the frame data.
52 | void WriteFrame(bool isKeyFrame, ReadOnlySpan frameData);
53 |
54 | /// Asynchronously writes a frame to the stream.
55 | /// Is this frame a key frame?
56 | /// Array containing the frame data.
57 | /// A task that represents the asynchronous write operation.
58 | ///
59 | /// The contents of should not be modified until this write operation ends.
60 | ///
61 | Task WriteFrameAsync(bool isKeyFrame, ReadOnlyMemory frameData);
62 | #endif
63 |
64 | ///
65 | /// Number of frames written.
66 | ///
67 | int FramesWritten { get; }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/SharpAvi/Output/IAviVideoStreamInternal.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi.Output
2 | {
3 | internal interface IAviVideoStreamInternal : IAviVideoStream, IAviStreamInternal
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SharpAvi/Output/RiffItem.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Utilities;
2 | using System;
3 |
4 | namespace SharpAvi.Output
5 | {
6 | ///
7 | /// Item of a RIFF file - either list or chunk.
8 | ///
9 | internal struct RiffItem
10 | {
11 | public const int ITEM_HEADER_SIZE = 2 * sizeof(uint);
12 | private int dataSize;
13 |
14 | public RiffItem(long dataStart, int dataSize = -1)
15 | {
16 | Argument.Meets(dataStart >= ITEM_HEADER_SIZE, nameof(dataStart));
17 | Argument.Meets(dataSize <= int.MaxValue - ITEM_HEADER_SIZE, nameof(dataSize));
18 |
19 | this.DataStart = dataStart;
20 | this.dataSize = dataSize;
21 | }
22 |
23 | public long DataStart { get; }
24 |
25 | public long ItemStart => DataStart - ITEM_HEADER_SIZE;
26 |
27 | public long DataSizeStart => DataStart - sizeof(uint);
28 |
29 | public int DataSize
30 | {
31 | get { return dataSize; }
32 | set
33 | {
34 | Argument.IsNotNegative(value, nameof(value));
35 |
36 | if (DataSize >= 0)
37 | throw new InvalidOperationException("Data size has been already set.");
38 |
39 | dataSize = value;
40 | }
41 | }
42 |
43 | public int ItemSize => dataSize < 0 ? -1 : dataSize + ITEM_HEADER_SIZE;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/SharpAvi/Output/RiffWriterExtensions.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Format;
2 | using SharpAvi.Utilities;
3 | using System;
4 | using System.IO;
5 |
6 | namespace SharpAvi.Output
7 | {
8 | internal static class RiffWriterExtensions
9 | {
10 | public static RiffItem OpenChunk(this BinaryWriter writer, FourCC fourcc, int expectedDataSize = -1)
11 | {
12 | Argument.IsNotNull(writer, nameof(writer));
13 | Argument.Meets(expectedDataSize <= int.MaxValue - RiffItem.ITEM_HEADER_SIZE, nameof(expectedDataSize));
14 |
15 | writer.Write((uint)fourcc);
16 | writer.Write((uint)(expectedDataSize >= 0 ? expectedDataSize : 0));
17 |
18 | return new RiffItem(writer.BaseStream.Position, expectedDataSize);
19 | }
20 |
21 | public static RiffItem OpenList(this BinaryWriter writer, FourCC fourcc)
22 | {
23 | Argument.IsNotNull(writer, nameof(writer));
24 |
25 | return writer.OpenList(fourcc, KnownFourCCs.ListTypes.List);
26 | }
27 |
28 | public static RiffItem OpenList(this BinaryWriter writer, FourCC fourcc, FourCC listType)
29 | {
30 | Argument.IsNotNull(writer, nameof(writer));
31 |
32 | var result = writer.OpenChunk(listType);
33 | writer.Write((uint)fourcc);
34 | return result;
35 | }
36 |
37 | public static void CloseItem(this BinaryWriter writer, RiffItem item)
38 | {
39 | Argument.IsNotNull(writer, nameof(writer));
40 |
41 | var position = writer.BaseStream.Position;
42 |
43 | var dataSize = position - item.DataStart;
44 | if (dataSize > int.MaxValue - RiffItem.ITEM_HEADER_SIZE)
45 | {
46 | throw new InvalidOperationException("Item size is too big.");
47 | }
48 |
49 | if (item.DataSize < 0)
50 | {
51 | item.DataSize = (int)dataSize;
52 | writer.BaseStream.Position = item.DataSizeStart;
53 | writer.Write((uint)dataSize);
54 | writer.BaseStream.Position = position;
55 | }
56 | else if (dataSize != item.DataSize)
57 | {
58 | throw new InvalidOperationException("Item size is not equal to what was previously specified.");
59 | }
60 |
61 | // Pad to the WORD boundary according to the RIFF spec
62 | if (position % 2 > 0)
63 | {
64 | writer.SkipBytes(1);
65 | }
66 | }
67 |
68 | private static readonly byte[] cleanBuffer = new byte[1024];
69 |
70 | public static void SkipBytes(this BinaryWriter writer, int count)
71 | {
72 | Argument.IsNotNull(writer, nameof(writer));
73 | Argument.IsNotNegative(count, nameof(count));
74 |
75 | while (count > 0)
76 | {
77 | var toWrite = Math.Min(count, cleanBuffer.Length);
78 | writer.Write(cleanBuffer, 0, toWrite);
79 | count -= toWrite;
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/SharpAvi/Output/VideoStreamWrapperBase.cs:
--------------------------------------------------------------------------------
1 | using SharpAvi.Utilities;
2 | using System;
3 | using System.Threading.Tasks;
4 |
5 | namespace SharpAvi.Output
6 | {
7 | ///
8 | /// Base class for wrappers around .
9 | ///
10 | ///
11 | /// Simply delegates all operations to wrapped stream.
12 | ///
13 | internal abstract class VideoStreamWrapperBase : IAviVideoStreamInternal, IDisposable
14 | {
15 | protected VideoStreamWrapperBase(IAviVideoStreamInternal baseStream)
16 | {
17 | Argument.IsNotNull(baseStream, nameof(baseStream));
18 |
19 | this.BaseStream = baseStream;
20 | }
21 |
22 | protected IAviVideoStreamInternal BaseStream { get; }
23 |
24 | public virtual void Dispose() => (BaseStream as IDisposable)?.Dispose();
25 |
26 | public virtual int Width
27 | {
28 | get { return BaseStream.Width; }
29 | set { BaseStream.Width = value; }
30 | }
31 |
32 | public virtual int Height
33 | {
34 | get { return BaseStream.Height; }
35 | set { BaseStream.Height = value; }
36 | }
37 |
38 | public virtual BitsPerPixel BitsPerPixel
39 | {
40 | get { return BaseStream.BitsPerPixel; }
41 | set { BaseStream.BitsPerPixel = value; }
42 | }
43 |
44 | public virtual FourCC Codec
45 | {
46 | get { return BaseStream.Codec; }
47 | set { BaseStream.Codec = value; }
48 | }
49 |
50 | public virtual void WriteFrame(bool isKeyFrame, byte[] frameData, int startIndex, int length)
51 | {
52 | Argument.IsNotNull(frameData, nameof(frameData));
53 | Argument.IsNotNegative(startIndex, nameof(startIndex));
54 | Argument.IsPositive(length, nameof(length));
55 | Argument.ConditionIsMet(startIndex + length <= frameData.Length, "End offset exceeds the length of frame data.");
56 |
57 | BaseStream.WriteFrame(isKeyFrame, frameData, startIndex, length);
58 | }
59 |
60 | public virtual Task WriteFrameAsync(bool isKeyFrame, byte[] frameData, int startIndex, int length)
61 | {
62 | Argument.IsNotNull(frameData, nameof(frameData));
63 | Argument.IsNotNegative(startIndex, nameof(startIndex));
64 | Argument.IsPositive(length, nameof(length));
65 | Argument.ConditionIsMet(startIndex + length <= frameData.Length, "End offset exceeds the length of frame data.");
66 |
67 | return BaseStream.WriteFrameAsync(isKeyFrame, frameData, startIndex, length);
68 | }
69 |
70 | #if NET5_0_OR_GREATER
71 | public virtual void WriteFrame(bool isKeyFrame, ReadOnlySpan frameData)
72 | {
73 | Argument.Meets(frameData.Length > 0, nameof(frameData), "Cannot write an empty frame.");
74 |
75 | BaseStream.WriteFrame(isKeyFrame, frameData);
76 | }
77 |
78 | public virtual Task WriteFrameAsync(bool isKeyFrame, ReadOnlyMemory frameData)
79 | {
80 | Argument.Meets(frameData.Length > 0, nameof(frameData), "Cannot write an empty frame.");
81 |
82 | return BaseStream.WriteFrameAsync(isKeyFrame, frameData);
83 | }
84 | #endif
85 |
86 | public int FramesWritten => BaseStream.FramesWritten;
87 |
88 | public int Index => BaseStream.Index;
89 |
90 | public virtual string Name
91 | {
92 | get { return BaseStream.Name; }
93 | set { BaseStream.Name = value; }
94 | }
95 |
96 | public FourCC StreamType => BaseStream.StreamType;
97 |
98 | public FourCC ChunkId => BaseStream.ChunkId;
99 |
100 | public virtual void PrepareForWriting() => BaseStream.PrepareForWriting();
101 |
102 | public virtual void FinishWriting() => BaseStream.FinishWriting();
103 |
104 | public void WriteHeader() => BaseStream.WriteHeader();
105 |
106 | public void WriteFormat() => BaseStream.WriteFormat();
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/SharpAvi/SharpAvi.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net45;netstandard2.0;net50;net50-windows
4 | x86
5 | Library
6 |
7 |
8 |
9 | True
10 | Copyright © Vasili Maslov 2013-2022
11 | A simple library for creating video files in the AVI format.
12 | baSSiLL
13 |
14 | https://github.com/baSSiLL/SharpAvi
15 | https://github.com/baSSiLL/SharpAvi.git
16 | readme.md
17 | AVI video authoring encoding OpenDML
18 | MIT
19 | 3.0.1
20 | $(Version).0
21 | $(Version).0
22 | True
23 | https://github.com/baSSiLL/SharpAvi/releases/tag/v$(Version)
24 |
25 |
26 | true
27 |
28 |
29 | true
30 |
31 |
32 |
33 |
34 |
35 |
36 | True
37 | \
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | true
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | TextTemplatingFileGenerator
59 |
60 |
61 |
--------------------------------------------------------------------------------
/SharpAvi/Utilities/Argument.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SharpAvi.Utilities
4 | {
5 | ///
6 | /// An utility class for argument checks.
7 | ///
8 | ///
9 | /// The methods are not extensions to make argument checks look more explicit
10 | /// (at the expense of a bit more verbosity).
11 | ///
12 | internal static class Argument
13 | {
14 | public static void IsNotNull(object value, string name)
15 | {
16 | if (value is null)
17 | throw new ArgumentNullException(name);
18 | }
19 |
20 | public static void IsNotNull(T? value, string name) where T : struct
21 | {
22 | if (value is null)
23 | throw new ArgumentNullException(name);
24 | }
25 |
26 | public static void IsNotNullOrEmpty(string value, string name)
27 | {
28 | IsNotNull(value, name);
29 |
30 | if (value.Length == 0)
31 | throw new ArgumentOutOfRangeException(name, "A non-empty string is expected.");
32 | }
33 |
34 | public static void IsNotNegative(int value, string name)
35 | {
36 | if (value < 0)
37 | throw new ArgumentOutOfRangeException(name, message: "A non-negative number is expected.");
38 | }
39 |
40 | public static void IsPositive(int value, string name)
41 | {
42 | if (value <= 0)
43 | throw new ArgumentOutOfRangeException(name, message: "A positive number is expected.");
44 | }
45 |
46 | public static void IsInRange(int value, int min, int max, string name)
47 | {
48 | if (value < min || max < value)
49 | throw new ArgumentOutOfRangeException(name, message: $"A value in the range [{min}..{max}] is expected.");
50 | }
51 |
52 | public static void IsNotNegative(double value, string name)
53 | {
54 | if (value < 0)
55 | throw new ArgumentOutOfRangeException(name, message: "A non-negative number is expected.");
56 | }
57 |
58 | public static void IsPositive(double value, string name)
59 | {
60 | if (value <= 0)
61 | throw new ArgumentOutOfRangeException(name, message: "A positive number is expected.");
62 | }
63 |
64 | public static void IsNotNegative(decimal value, string name)
65 | {
66 | if (value < 0)
67 | throw new ArgumentOutOfRangeException(name, message: "A non-negative number is expected.");
68 | }
69 |
70 | public static void IsPositive(decimal value, string name)
71 | {
72 | if (value <= 0)
73 | throw new ArgumentOutOfRangeException(name, message: "A positive number is expected.");
74 | }
75 |
76 | public static void IsEnumMember(TEnum value, string name) where TEnum : struct
77 | {
78 | if (!Enum.IsDefined(typeof(TEnum), value))
79 | throw new ArgumentOutOfRangeException(name, message: $"A member of {typeof(TEnum).Name} is expected.");
80 | }
81 |
82 | public static void Meets(bool condition, string name, string failureDescription = null)
83 | {
84 | if (!condition)
85 | throw new ArgumentOutOfRangeException(name, message: failureDescription);
86 | }
87 |
88 | public static void ConditionIsMet(bool condition, string failureDescription)
89 | {
90 | if (!condition)
91 | throw new ArgumentException(failureDescription);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/SharpAvi/Utilities/AviUtils.cs:
--------------------------------------------------------------------------------
1 | namespace SharpAvi.Utilities
2 | {
3 | ///
4 | /// Auxiliary methods helping to deal with AVI files.
5 | ///
6 | public static class AviUtils
7 | {
8 | ///
9 | /// Splits frame rate value to integer rate and scale values used in some AVI headers
10 | /// and VfW APIs.
11 | ///
12 | ///
13 | /// Frame rate. Rounded to 3 fractional digits.
14 | ///
15 | ///
16 | /// When the method returns, contains rate value.
17 | ///
18 | ///
19 | /// When the method returns, contains scale value.
20 | ///
21 | public static void SplitFrameRate(decimal frameRate, out uint rate, out uint scale)
22 | {
23 | if (decimal.Round(frameRate) == frameRate)
24 | {
25 | rate = (uint)decimal.Truncate(frameRate);
26 | scale = 1;
27 | }
28 | else if (decimal.Round(frameRate, 1) == frameRate)
29 | {
30 | rate = (uint)decimal.Truncate(frameRate * 10m);
31 | scale = 10;
32 | }
33 | else if (decimal.Round(frameRate, 2) == frameRate)
34 | {
35 | rate = (uint)decimal.Truncate(frameRate * 100m);
36 | scale = 100;
37 | }
38 | else
39 | {
40 | rate = (uint)decimal.Truncate(frameRate * 1000m);
41 | scale = 1000;
42 | }
43 |
44 | // Make mutually prime (needed for some hardware players)
45 | while (rate % 2 == 0 && scale % 2 == 0)
46 | {
47 | rate /= 2;
48 | scale /= 2;
49 | }
50 | while (rate % 5 == 0 && scale % 5 == 0)
51 | {
52 | rate /= 5;
53 | scale /= 5;
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/SharpAvi/Utilities/BitmapUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SharpAvi.Utilities
4 | {
5 | internal static partial class BitmapUtils
6 | {
7 | #if NET5_0_OR_GREATER
8 | public static unsafe void Bgr32ToBgr24(ReadOnlySpan source, Span destination, int pixelCount)
9 | {
10 | Argument.IsPositive(pixelCount, nameof(pixelCount));
11 | Argument.ConditionIsMet(4 * pixelCount <= source.Length,
12 | "Source end offset exceeds the source length.");
13 | Argument.ConditionIsMet(3 * pixelCount <= destination.Length,
14 | "Destination end offset exceeds the destination length.");
15 |
16 | fixed (byte* sourcePtr = source, destinationPtr = destination)
17 | {
18 | Bgr32ToBgr24(sourcePtr, 0, destinationPtr, 0, pixelCount);
19 | }
20 | }
21 | #else
22 | public static unsafe void Bgr32ToBgr24(byte[] source, int srcOffset, byte[] destination, int destOffset, int pixelCount)
23 | {
24 | Argument.IsNotNull(source, nameof(source));
25 | Argument.IsNotNegative(srcOffset, nameof(srcOffset));
26 | Argument.IsNotNull(destination, nameof(destination));
27 | Argument.IsNotNegative(destOffset, nameof(destOffset));
28 | Argument.IsPositive(pixelCount, nameof(pixelCount));
29 | Argument.ConditionIsMet(srcOffset + 4 * pixelCount <= source.Length,
30 | "Source end offset exceeds the source length.");
31 | Argument.ConditionIsMet(destOffset + 3 * pixelCount <= destination.Length,
32 | "Destination end offset exceeds the destination length.");
33 |
34 | fixed (byte* sourcePtr = source, destinationPtr = destination)
35 | {
36 | Bgr32ToBgr24(sourcePtr, srcOffset, destinationPtr, destOffset, pixelCount);
37 | }
38 | }
39 | #endif
40 |
41 | private static unsafe void Bgr32ToBgr24(byte* sourcePtr, int srcOffset, byte* destinationPtr, int destOffset, int pixelCount)
42 | {
43 | var sourceStart = sourcePtr + srcOffset;
44 | var destinationStart = destinationPtr + destOffset;
45 | var sourceEnd = sourceStart + 4 * pixelCount;
46 | var src = sourceStart;
47 | var dest = destinationStart;
48 | while (src < sourceEnd)
49 | {
50 | *dest++ = *src++;
51 | *dest++ = *src++;
52 | *dest++ = *src++;
53 | src++;
54 | }
55 | }
56 |
57 | #if NET5_0_OR_GREATER
58 | public static void FlipVertical(ReadOnlySpan source, Span destination, int height, int stride)
59 | {
60 | Argument.IsPositive(height, nameof(height));
61 | Argument.IsPositive(stride, nameof(stride));
62 | Argument.ConditionIsMet(stride * height <= source.Length,
63 | "Source end offset exceeds the source length.");
64 | Argument.ConditionIsMet(stride * height <= destination.Length,
65 | "Destination end offset exceeds the destination length.");
66 |
67 | for (var y = 0; y < height; y++)
68 | {
69 | var srcOffset = y * stride;
70 | var destOffset = (height - 1 - y) * stride;
71 | source.Slice(srcOffset, stride).CopyTo(destination.Slice(destOffset));
72 | }
73 | }
74 | #else
75 | public static void FlipVertical(byte[] source, int srcOffset, byte[] destination, int destOffset, int height, int stride)
76 | {
77 | Argument.IsNotNull(source, nameof(source));
78 | Argument.IsNotNull(destination, nameof(destination));
79 | Argument.IsPositive(height, nameof(height));
80 | Argument.IsPositive(stride, nameof(stride));
81 | Argument.ConditionIsMet(srcOffset + stride * height <= source.Length,
82 | "Source end offset exceeds the source length.");
83 | Argument.ConditionIsMet(destOffset + stride * height <= destination.Length,
84 | "Destination end offset exceeds the destination length.");
85 |
86 | var src = srcOffset;
87 | var dest = destOffset + (height - 1) * stride;
88 | for (var y = 0; y < height; y++)
89 | {
90 | Buffer.BlockCopy(source, src, destination, dest, stride);
91 | src += stride;
92 | dest -= stride;
93 | }
94 | }
95 | #endif
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/SharpAvi/Utilities/RedirectDllResolver.cs:
--------------------------------------------------------------------------------
1 | #if NET5_0_OR_GREATER
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Reflection;
5 | using System.Runtime.InteropServices;
6 |
7 | namespace SharpAvi.Utilities
8 | {
9 | internal static class RedirectDllResolver
10 | {
11 | private static readonly object sync = new();
12 | private static readonly Dictionary redirects = new();
13 |
14 | static RedirectDllResolver()
15 | {
16 | NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), ResolveDllImport);
17 | }
18 |
19 | public static void SetRedirect(string libraryName, string targetLibraryName)
20 | {
21 | lock (sync)
22 | {
23 | redirects[libraryName] = targetLibraryName;
24 | }
25 | }
26 |
27 | private static IntPtr ResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
28 | {
29 | string targetLibraryName;
30 | lock (sync)
31 | {
32 | if (!redirects.TryGetValue(libraryName, out targetLibraryName))
33 | {
34 | // Fall back to default resolver
35 | return IntPtr.Zero;
36 | }
37 | }
38 | return NativeLibrary.Load(targetLibraryName, assembly, searchPath);
39 | }
40 | }
41 | }
42 | #endif
43 |
--------------------------------------------------------------------------------
/SharpAvi/Utilities/SequentialInvoker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace SharpAvi.Utilities
5 | {
6 | ///
7 | /// Serializes synchronous and asynchronous invocations in one queue.
8 | ///
9 | internal sealed class SequentialInvoker
10 | {
11 | private readonly object sync = new object();
12 | private Task lastTask;
13 |
14 | ///
15 | /// Creates a new instance of .
16 | ///
17 | public SequentialInvoker()
18 | {
19 | // Initialize lastTask to already completed task
20 | lastTask = Task.FromResult(true);
21 | }
22 |
23 | ///
24 | /// Invokes an action synchronously.
25 | ///
26 | /// Action.
27 | ///
28 | /// Waits for any previously scheduled invocations to complete.
29 | ///
30 | public void Invoke(Action action)
31 | {
32 | Argument.IsNotNull(action, nameof(action));
33 |
34 | Task prevTask;
35 | var tcs = new TaskCompletionSource();
36 |
37 | lock (sync)
38 | {
39 | prevTask = lastTask;
40 | lastTask = tcs.Task;
41 | }
42 |
43 | try
44 | {
45 | prevTask.Wait();
46 | try
47 | {
48 | action.Invoke();
49 | }
50 | catch (Exception ex)
51 | {
52 | tcs.SetException(ex);
53 | throw;
54 | }
55 | tcs.SetResult(true);
56 | }
57 | finally
58 | {
59 | tcs.TrySetResult(false);
60 | }
61 | }
62 |
63 | ///
64 | /// Schedules an action asynchronously.
65 | ///
66 | /// Action.
67 | /// Task corresponding to asunchronous invocation.
68 | ///
69 | /// This action will be invoked after all previously scheduled invocations complete.
70 | ///
71 | public Task InvokeAsync(Action action)
72 | {
73 | Argument.IsNotNull(action, nameof(action));
74 |
75 | Task result;
76 | lock (sync)
77 | {
78 | result = lastTask.ContinueWith(_ => action.Invoke());
79 | lastTask = result;
80 | }
81 |
82 | return result;
83 | }
84 |
85 | ///
86 | /// Waits for currently pending invocations to complete.
87 | ///
88 | ///
89 | /// New invocations, which are possibly scheduled during this call, are not considered.
90 | ///
91 | public void WaitForPendingInvocations()
92 | {
93 | Task taskToWait;
94 | lock (sync)
95 | {
96 | taskToWait = lastTask;
97 | }
98 | taskToWait.Wait();
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/SharpAvi/Utilities/SingleThreadTaskScheduler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace SharpAvi.Utilities
8 | {
9 | internal sealed class SingleThreadTaskScheduler : TaskScheduler, IDisposable
10 | {
11 | private readonly BlockingCollection tasks = new BlockingCollection();
12 | private readonly Thread thread;
13 |
14 | public SingleThreadTaskScheduler()
15 | {
16 | thread = new Thread(RunTasks)
17 | {
18 | IsBackground = true,
19 | Name = nameof(SingleThreadTaskScheduler)
20 | };
21 | thread.Start();
22 | }
23 |
24 | public void Dispose()
25 | {
26 | if (!IsDisposed)
27 | {
28 | tasks.CompleteAdding();
29 | if (thread.ThreadState != ThreadState.Unstarted)
30 | {
31 | thread.Join();
32 | }
33 | tasks.Dispose();
34 | IsDisposed = true;
35 | }
36 | }
37 |
38 | public bool IsDisposed { get; private set; }
39 |
40 | public override int MaximumConcurrencyLevel => 1;
41 |
42 | protected override void QueueTask(Task task) => tasks.Add(task);
43 |
44 | protected override IEnumerable GetScheduledTasks() => tasks.ToArray();
45 |
46 | protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => false;
47 |
48 | protected override bool TryDequeue(Task task) => false;
49 |
50 | private void RunTasks()
51 | {
52 | foreach (var task in tasks.GetConsumingEnumerable())
53 | {
54 | TryExecuteTask(task);
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/SharpAvi/readme.md:
--------------------------------------------------------------------------------
1 | **SharpAvi** is a simple .NET library for creating video files in the AVI format.
2 | Files are produced in compliance with the [OpenDML extensions](http://www.jmcgowan.com/avitech.html#OpenDML) which allow (nearly) unlimited file size (no 2GB limit).
3 | There are built-in encoders for video (Motion JPEG, MPEG-4) and audio (MP3). Not all of them are supported on each target platform though.
--------------------------------------------------------------------------------