├── .editorconfig
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE.md
└── stale.yml
├── .gitignore
├── Analyzers.ruleset
├── LICENSE
├── README.md
├── Support
├── arch-michelob-2.0.png
├── arch-michelob-2.0.pptx
├── ffme.png
├── ffmeplay.png
├── nugetdoc.md
└── readme.txt
├── Unosquare.FFME.MediaElement
├── Common
│ ├── DataFrameReceivedEventArgs.cs
│ ├── FrameDecodedEventArgs.cs
│ ├── InputFormatEventArgs.cs
│ ├── MediaFailedEventArgs.cs
│ ├── MediaInitializingEventArgs.cs
│ ├── MediaLogMessageEventArgs.cs
│ ├── MediaOpenedEventArgs.cs
│ ├── MediaOpeningEventArgs.cs
│ ├── MediaStateChangedEventArgs.cs
│ ├── PacketReadEventArgs.cs
│ ├── PositionChangedEventArgs.cs
│ ├── SubtitleDecodedEventArgs.cs
│ └── ViewModelBase.cs
├── Constants.cs
├── MediaElement.Events.cs
├── MediaElement.Properties.cs
├── MediaElement.cs
├── Platform
│ ├── ClassProxy.cs
│ ├── IGuiContext.cs
│ ├── IPropertyProxy.cs
│ ├── MediaConnector.cs
│ ├── PropertyMapper.cs
│ └── PropertyProxy.cs
├── Unosquare.FFME.MediaElement.projitems
└── Unosquare.FFME.MediaElement.shproj
├── Unosquare.FFME.Windows.Sample
├── App.Icons.xaml
├── App.Styles.xaml
├── App.xaml
├── App.xaml.cs
├── AppCommands.cs
├── Controls
│ ├── ControllerPanelControl.xaml
│ ├── ControllerPanelControl.xaml.cs
│ ├── PlaylistPanelControl.xaml
│ ├── PlaylistPanelControl.xaml.cs
│ ├── PropertiesPanelControl.xaml
│ └── PropertiesPanelControl.xaml.cs
├── Foundation
│ ├── CustomPlaylistEntry.cs
│ ├── CustomPlaylistEntryCollection.cs
│ ├── DeferredAction.cs
│ ├── DelegateCommand.cs
│ ├── FileInputStream.cs
│ ├── ReactiveExtensions.cs
│ ├── ThumbnailGenerator.cs
│ ├── TransportStreamRecorder.cs
│ ├── ValueConverters.cs
│ └── WindowStatus.cs
├── GlobalSuppressions.cs
├── MainWindow.MediaEvents.cs
├── MainWindow.MediaRendering.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Properties
│ └── launchSettings.json
├── Unosquare.FFME.Windows.Sample.csproj
├── ViewModels
│ ├── AttachedViewModel.cs
│ ├── ControllerViewModel.cs
│ ├── PlaylistViewModel.cs
│ └── RootViewModel.cs
└── ffmpeg.ico
├── Unosquare.FFME.Windows
├── Common
│ ├── AudioDeviceInfo.cs
│ ├── BitmapDataBuffer.cs
│ ├── LegacyAudioException.cs
│ ├── LegacyAudioResult.cs
│ ├── RendererOptions.cs
│ ├── RenderingAudioEventArgs.cs
│ ├── RenderingEventArgs.cs
│ ├── RenderingSubtitlesEventArgs.cs
│ ├── RenderingVideoEventArgs.cs
│ └── VideoRendererImageType.cs
├── GlobalSuppressions.cs
├── Library.cs
├── MediaElement.Events.cs
├── MediaElement.Properties.cs
├── MediaElement.cs
├── MediaElement.png
├── Platform
│ ├── GuiContext.cs
│ ├── GuiContextType.cs
│ ├── MediaConnector.cs
│ └── SoundTouch.cs
├── Properties
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ └── launchSettings.json
├── Rendering
│ ├── AudioRenderer.cs
│ ├── ClosedCaptionsBuffer.cs
│ ├── ClosedCaptionsCell.cs
│ ├── ClosedCaptionsCellState.cs
│ ├── ClosedCaptionsControl.cs
│ ├── ElementHostBase.cs
│ ├── ImageHost.cs
│ ├── InteropVideoRenderer.cs
│ ├── SubtitleRenderer.cs
│ ├── SubtitlesControl.cs
│ ├── VideoRenderer.cs
│ ├── VideoRendererBase.cs
│ └── Wave
│ │ ├── DirectSoundDeviceData.cs
│ │ ├── DirectSoundPlayer.cs
│ │ ├── IWavePlayer.cs
│ │ ├── IWaveProvider.cs
│ │ ├── LegacyAudioDeviceData.cs
│ │ ├── LegacyAudioPlayer.cs
│ │ ├── MmTime.cs
│ │ ├── PlaybackState.cs
│ │ ├── SupportedWaveFormat.cs
│ │ ├── WaveFormat.cs
│ │ ├── WaveHeader.cs
│ │ ├── WaveHeaderFlags.cs
│ │ ├── WaveInterop.cs
│ │ ├── WaveOutBuffer.cs
│ │ └── WaveOutSupport.cs
├── Resources
│ └── ffme-preview.png
├── Subtitles.cs
└── Unosquare.FFME.Windows.csproj
├── Unosquare.FFME.sln
├── Unosquare.FFME
├── ClosedCaptions
│ ├── CaptionsChannel.cs
│ ├── CaptionsColor.cs
│ ├── CaptionsCommand.cs
│ ├── CaptionsPacketType.cs
│ ├── CaptionsStyle.cs
│ ├── CaptionsXdsClass.cs
│ └── ClosedCaptionPacket.cs
├── Commands
│ ├── CommandManager.Direct.cs
│ ├── CommandManager.Enums.cs
│ ├── CommandManager.Priority.cs
│ ├── CommandManager.Seek.cs
│ └── CommandManager.cs
├── Common
│ ├── ContainerConfiguration.cs
│ ├── DataFrame.cs
│ ├── DecoderOptions.cs
│ ├── DemuxerGlobalOptions.cs
│ ├── HardwareDeviceInfo.cs
│ ├── IMediaEngineState.cs
│ ├── IMediaInputStream.cs
│ ├── MediaContainerException.cs
│ ├── MediaInfo.cs
│ ├── MediaLogMessageType.cs
│ ├── MediaOptions.cs
│ ├── MediaPlaybackState.cs
│ ├── MediaType.cs
│ ├── OptionMetadata.cs
│ ├── VideoResolutionDivider.cs
│ ├── VideoSeekIndex.cs
│ └── VideoSeekIndexEntry.cs
├── Constants.cs
├── Container
│ ├── AudioBlock.cs
│ ├── AudioComponent.cs
│ ├── AudioFrame.cs
│ ├── DataComponentSet.cs
│ ├── HardwareAccelerator.cs
│ ├── MediaBlock.cs
│ ├── MediaBlockBuffer.cs
│ ├── MediaComponent.cs
│ ├── MediaComponentSet.cs
│ ├── MediaContainer.cs
│ ├── MediaFrame.cs
│ ├── MediaPacket.cs
│ ├── PacketBufferState.cs
│ ├── PacketQueue.cs
│ ├── PacketQueueOp.cs
│ ├── SubtitleBlock.cs
│ ├── SubtitleComponent.cs
│ ├── SubtitleFrame.cs
│ ├── VideoBlock.cs
│ ├── VideoComponent.cs
│ └── VideoFrame.cs
├── Diagnostics
│ ├── Aspects.cs
│ ├── Benchmark.cs
│ ├── BenchmarkResult.cs
│ ├── ILoggingHandler.cs
│ ├── ILoggingSource.cs
│ ├── Logging.cs
│ ├── LoggingMessage.cs
│ └── RC.cs
├── Engine
│ ├── BlockRenderingWorker.cs
│ ├── FrameDecodingWorker.cs
│ ├── IMediaWorker.cs
│ ├── MediaEngine.Connector.cs
│ ├── MediaEngine.Controller.cs
│ ├── MediaEngine.Workers.cs
│ ├── MediaEngine.cs
│ ├── MediaEngineState.cs
│ ├── MediaWorkerSet.cs
│ ├── MediaWorkerType.cs
│ ├── PacketReadingWorker.cs
│ └── TimingController.cs
├── FFmpeg
│ ├── FFAudioParams.cs
│ ├── FFBPrint.cs
│ ├── FFDictionary.cs
│ ├── FFDictionaryEntry.cs
│ └── FFInterop.cs
├── Library.cs
├── Platform
│ ├── IMediaConnector.cs
│ └── IMediaRenderer.cs
├── Playlists
│ ├── PlaylistEntry.cs
│ ├── PlaylistEntryAttributeDictionary.cs
│ ├── PlaylistEntryCollection.cs
│ └── PlaylistExtensions.cs
├── Primitives
│ ├── AtomicBoolean.cs
│ ├── AtomicDateTime.cs
│ ├── AtomicDouble.cs
│ ├── AtomicInteger.cs
│ ├── AtomicLong.cs
│ ├── AtomicTimeSpan.cs
│ ├── AtomicTypeBase.cs
│ ├── CircularBuffer.cs
│ ├── ISyncLocker.cs
│ ├── IWorker.cs
│ ├── IntervalWorkerBase.cs
│ ├── MediaTypeDictionary.cs
│ ├── RealtimeClock.cs
│ ├── StepTimer.cs
│ ├── SyncLockerFactory.cs
│ ├── VerticalSyncContext.cs
│ ├── WorkerBase.cs
│ └── WorkerState.cs
├── Unosquare.FFME.projitems
├── Unosquare.FFME.shproj
├── Utilities.Audio.cs
├── Utilities.Media.cs
├── Utilities.Time.cs
└── Utilities.cs
├── appveyor.yml
├── docfx.json
└── toc.yml
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.cs]
4 | indent_style = space
5 | indent_size = 4
6 |
7 | # CA2000: Dispose objects before losing scope
8 | dotnet_diagnostic.CA2000.severity = suggestion
9 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Explicitly declare text files you want to always be normalized and converted
5 | # to native line endings on checkout.
6 | *.c text
7 | *.h text
8 |
9 | # Declare files that will always have CRLF line endings on checkout.
10 | *.sln text eol=crlf
11 | *.cs text eol=crlf
12 | *.xml text eol=crlf
13 | *.xaml text eol=crlf
14 |
15 | # Denote all files that are truly binary and should not be modified.
16 | *.png binary
17 | *.jpg binary
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Issue Title (*change this title!*)
2 |
3 | *Please enter a general description for the issue. Delete sections that are not relevant and provide additional sections if necessary.*
4 |
5 | ## Issue Categories
6 |
7 | - [ ] Bug
8 | - [ ] Feature Request
9 | - [ ] Question
10 | - [ ] Not sure
11 |
12 | ## Version Information
13 |
14 | - [ ] NuGet Package *Enter Version Number Here*
15 | - [ ] Build From *Master* branch, Commit *Enter commit id. Example: 704c482*
16 | - [ ] Build from *Branch Name*, Commit *Enter commit id. Example: 704c482*
17 |
18 | ## Steps to Reproduce
19 |
20 | 1. Step 1
21 | 2. Step 2
22 | 3. Step 3
23 |
24 | ## Expected Results
25 |
26 | - Result 1
27 | - Result 2
28 |
29 | ## Sample Code
30 |
31 | ### XAML
32 | ```xml
33 |
34 |
35 | ```
36 |
37 | ### C#
38 | ```csharp
39 | System.Console.WriteLine("Change Me Please!");
40 | ```
41 |
42 | ### Command line ffplay
43 | ```batch
44 | ffplay "hello.mp4"
45 | ```
46 | ## Delete This Section
47 | - Please consider a donation here: https://www.paypal.me/mariodivece/50usd
48 | - I work on this project for fun and on my free time.
49 | - Provide as much information as possible and any sample files
50 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: false
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: wontfix
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. Thank you for your contributions.
15 | # Comment to post when closing a stale issue. Set to `false` to disable
16 | closeComment: false
17 |
--------------------------------------------------------------------------------
/Support/arch-michelob-2.0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Support/arch-michelob-2.0.png
--------------------------------------------------------------------------------
/Support/arch-michelob-2.0.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Support/arch-michelob-2.0.pptx
--------------------------------------------------------------------------------
/Support/ffme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Support/ffme.png
--------------------------------------------------------------------------------
/Support/ffmeplay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Support/ffmeplay.png
--------------------------------------------------------------------------------
/Support/nugetdoc.md:
--------------------------------------------------------------------------------
1 | Here is a quick guide on how to get started.
2 | 1. Open Visual Studio (v2019 recommended), and create a new WPF Application. Target Framework must be .Net 5.0 or above.
3 | 2. Install the NuGet Package from your Package Manager Console: `PM> Install-Package FFME.Windows`
4 | 3. You need FFmpeg **shared** binaries (64 or 32 bit, depending on your app's target architecture). Build your own or download a compatible build from [FFmpeg Windows Downloads](https://ffmpeg.org/download.html).
5 | 4. Your FFmpeg build should have a `bin` folder with 3 exe files and some dll files. Copy all those files to a folder such as `c:\ffmpeg`
6 | 5. Within you application's startup code (`Main` method), set `Unosquare.FFME.Library.FFmpegDirectory = @"c:\ffmpeg";`.
7 | 6. Use the FFME `MediaElement` control as any other WPF control.
8 | For example: In your `MainForm.xaml`, add the namespace: `xmlns:ffme="clr-namespace:Unosquare.FFME;assembly=ffme.win"` and then add the FFME control your window's XAML: ``
9 | 7. To play files or streams, simply call the asynchronous `Open` method: `await Media.Open(new Uri(@"c:\your-file-here"));`.
10 |
--------------------------------------------------------------------------------
/Support/readme.txt:
--------------------------------------------------------------------------------
1 | How to use FFME
2 |
3 | In order to use the FFME MediaElement control, you will need to setup a folder with FFmpeg binaries and point to it from your application code.
4 | Here are the steps:
5 |
6 | 1. You can build your own FFmpeg **shared** binaries or download a compatible build from the FFmpeg Windows Downloads page: (https://ffmpeg.org/download.html).
7 | 2. Your FFmpeg build (see the bin folder) should have 3 exe files and a number of dll files and must match your app's architecture (32-bit or 64-bit). Copy all of them to a folder such as (c:\ffmpeg)
8 | 3. Within you application's startup code (Main method), set Unosquare.FFME.Library.FFmpegDirectory = @"path to ffmpeg binaries from the previous step";.
9 | 4. Use the FFME MediaElement control as any other WPF control!
10 | For example: In your MainForm.xaml, add the namespace: xmlns:ffme="clr-namespace:Unosquare.FFME;assembly=ffme.win"
11 | And then add the FFME control your window:
12 | To play files or streams, call the asyncrhonous Open method: await Media.Open(new Uri(@"c:\your-file-here"));
13 |
14 | Happy coding!
15 | *Mario, Unosquare and the FFME contributors.*
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/DataFrameReceivedEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using System;
4 |
5 | ///
6 | /// Event arguments corresponding to the reading of data (non-media) frames.
7 | ///
8 | public sealed class DataFrameReceivedEventArgs : EventArgs
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The data frame.
14 | /// The stream.
15 | internal DataFrameReceivedEventArgs(DataFrame dataFrame, StreamInfo stream)
16 | {
17 | Frame = dataFrame;
18 | Stream = stream;
19 | }
20 |
21 | ///
22 | /// Contains the data frame.
23 | ///
24 | public DataFrame Frame { get; }
25 |
26 | ///
27 | /// Gets the associated stream information.
28 | ///
29 | public StreamInfo Stream { get; }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/FrameDecodedEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using FFmpeg.AutoGen;
4 |
5 | ///
6 | /// Event arguments corresponding to the audio or video frame decoded events. Useful for capturing streams.
7 | ///
8 | public sealed unsafe class FrameDecodedEventArgs : InputFormatEventArgs
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The audio or video frame pointer.
14 | /// The input format context.
15 | internal FrameDecodedEventArgs(AVFrame* frame, AVFormatContext* context)
16 | : base(context)
17 | {
18 | Frame = frame;
19 | }
20 |
21 | ///
22 | /// Gets the pointer to the audio or video frame that was decoded.
23 | ///
24 | public AVFrame* Frame { get; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/InputFormatEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using FFmpeg.AutoGen;
4 | using System;
5 |
6 | ///
7 | /// Generic Input format event arguments. Useful for capturing streams.
8 | ///
9 | public abstract unsafe class InputFormatEventArgs : EventArgs
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | /// The input format context.
15 | protected InputFormatEventArgs(AVFormatContext* context)
16 | {
17 | InputContext = context;
18 | }
19 |
20 | ///
21 | /// Gets a pointer to the unmanaged input format context.
22 | ///
23 | public AVFormatContext* InputContext { get; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/MediaFailedEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using System;
4 |
5 | ///
6 | /// The Media failed event arguments.
7 | ///
8 | public sealed class MediaFailedEventArgs : EventArgs
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The exception.
14 | internal MediaFailedEventArgs(Exception errorException)
15 | {
16 | ErrorException = errorException;
17 | }
18 |
19 | ///
20 | /// Gets the error exception.
21 | ///
22 | public Exception ErrorException { get; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/MediaInitializingEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using System;
4 | using System.Windows;
5 |
6 | ///
7 | /// Represents the event arguments of the MediaInitializing routed event.
8 | ///
9 | ///
10 | public class MediaInitializingEventArgs : EventArgs
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// The container configuration options.
16 | /// The URL.
17 | internal MediaInitializingEventArgs(ContainerConfiguration config, string mediaSource)
18 | {
19 | Configuration = config;
20 | MediaSource = mediaSource;
21 | }
22 |
23 | ///
24 | /// Set or change the container configuration options before the media is opened.
25 | ///
26 | public ContainerConfiguration Configuration { get; }
27 |
28 | ///
29 | /// Gets the URL.
30 | ///
31 | public string MediaSource { get; }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/MediaLogMessageEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using Diagnostics;
4 | using System;
5 |
6 | ///
7 | /// Contains the Message Logged Event Arguments.
8 | ///
9 | ///
10 | public class MediaLogMessageEventArgs : EventArgs
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// The message.
16 | internal MediaLogMessageEventArgs(LoggingMessage message)
17 | {
18 | TimestampUtc = message.TimestampUtc;
19 | MessageType = message.MessageType;
20 | Message = message.Message;
21 | AspectName = message.AspectName;
22 | }
23 |
24 | ///
25 | /// Gets the timestamp.
26 | ///
27 | public DateTime TimestampUtc { get; }
28 |
29 | ///
30 | /// Gets the type of the message.
31 | ///
32 | public MediaLogMessageType MessageType { get; }
33 |
34 | ///
35 | /// Gets the contents of the message.
36 | ///
37 | public string Message { get; }
38 |
39 | ///
40 | /// Gets the aspect or feature that sent the logged message.
41 | /// May or may not be available.
42 | ///
43 | public string AspectName { get; }
44 |
45 | ///
46 | public override string ToString() =>
47 | $"[{TimestampUtc.Minute:00}:{TimestampUtc.Second:00}.{TimestampUtc.Millisecond:000} " +
48 | $"| {GetTypePrefix()} | {AspectName,-20}] {Message}";
49 |
50 | ///
51 | /// Gets the type prefix.
52 | ///
53 | /// A 3-letter abbreviation.
54 | private string GetTypePrefix()
55 | {
56 | switch (MessageType)
57 | {
58 | case MediaLogMessageType.Debug:
59 | return "DBG";
60 | case MediaLogMessageType.Error:
61 | return "ERR";
62 | case MediaLogMessageType.Info:
63 | return "INF";
64 | case MediaLogMessageType.None:
65 | return "NON";
66 | case MediaLogMessageType.Trace:
67 | return "TRC";
68 | case MediaLogMessageType.Warning:
69 | return "WRN";
70 | default:
71 | return "INV";
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/MediaOpenedEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using Common;
4 | using System;
5 |
6 | ///
7 | /// Represents the event arguments of the or
8 | /// routed events.
9 | ///
10 | ///
11 | public class MediaOpenedEventArgs : EventArgs
12 | {
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// The input information.
17 | internal MediaOpenedEventArgs(MediaInfo info)
18 | {
19 | Info = info;
20 | }
21 |
22 | ///
23 | /// Provides internal details of the media, including its component streams.
24 | /// Typically, options are set based on what this information contains.
25 | ///
26 | public MediaInfo Info { get; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/MediaOpeningEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using System;
4 |
5 | ///
6 | /// Represents the event arguments of the
7 | /// or routed events.
8 | ///
9 | ///
10 | public class MediaOpeningEventArgs : EventArgs
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// The options.
16 | /// The input information.
17 | internal MediaOpeningEventArgs(MediaOptions options, MediaInfo info)
18 | {
19 | Options = options;
20 | Info = info;
21 | }
22 |
23 | ///
24 | /// Set or change the options before the media is opened.
25 | ///
26 | public MediaOptions Options { get; }
27 |
28 | ///
29 | /// Provides internal details of the media, including its component streams.
30 | /// Typically, options are set based on what this information contains.
31 | ///
32 | public MediaInfo Info { get; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/MediaStateChangedEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using Common;
4 | using System;
5 |
6 | ///
7 | /// Contains the media state changed event args.
8 | ///
9 | ///
10 | public class MediaStateChangedEventArgs : EventArgs
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// State of the previous.
16 | /// The new state.
17 | internal MediaStateChangedEventArgs(MediaPlaybackState oldState, MediaPlaybackState newState)
18 | {
19 | OldMediaState = oldState;
20 | MediaState = newState;
21 | }
22 |
23 | ///
24 | /// Gets the current media state.
25 | ///
26 | public MediaPlaybackState MediaState { get; }
27 |
28 | ///
29 | /// Gets the position.
30 | ///
31 | public MediaPlaybackState OldMediaState { get; }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/PacketReadEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using FFmpeg.AutoGen;
4 |
5 | ///
6 | /// Event arguments corresponding to the packet reading event. Useful for capturing streams.
7 | ///
8 | public sealed unsafe class PacketReadEventArgs : InputFormatEventArgs
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The packet pointer.
14 | /// The input format context.
15 | internal PacketReadEventArgs(AVPacket* packet, AVFormatContext* context)
16 | : base(context)
17 | {
18 | Packet = packet;
19 | }
20 |
21 | ///
22 | /// Gets the pointer to the packet that was read.
23 | ///
24 | public AVPacket* Packet { get; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/PositionChangedEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using System;
4 |
5 | ///
6 | /// Contains the position changed routed event args.
7 | ///
8 | ///
9 | public class PositionChangedEventArgs : EventArgs
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | /// State of the engine.
15 | /// The old position.
16 | /// The new position.
17 | internal PositionChangedEventArgs(IMediaEngineState engineState, TimeSpan oldPosition, TimeSpan newPosition)
18 | {
19 | Position = newPosition;
20 | OldPosition = oldPosition;
21 | EngineState = engineState;
22 | }
23 |
24 | ///
25 | /// Gets the current position.
26 | ///
27 | public TimeSpan Position { get; }
28 |
29 | ///
30 | /// Gets the old position.
31 | ///
32 | public TimeSpan OldPosition { get; }
33 |
34 | ///
35 | /// Provides access to the underlying media engine state.
36 | ///
37 | public IMediaEngineState EngineState { get; }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Common/SubtitleDecodedEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using FFmpeg.AutoGen;
4 |
5 | ///
6 | /// Event arguments corresponding to the subtitle decoded event. Useful for capturing streams.
7 | ///
8 | public sealed unsafe class SubtitleDecodedEventArgs : InputFormatEventArgs
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The subtitle pointer.
14 | /// The input format context.
15 | internal SubtitleDecodedEventArgs(AVSubtitle* subtitle, AVFormatContext* context)
16 | : base(context)
17 | {
18 | Subtitle = subtitle;
19 | }
20 |
21 | ///
22 | /// Gets the pointer to subtitle that was decoded.
23 | ///
24 | public AVSubtitle* Subtitle { get; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME
2 | {
3 | using System;
4 |
5 | internal static partial class Constants
6 | {
7 | ///
8 | /// Gets the period at which media state properties are updated.
9 | ///
10 | public static TimeSpan PropertyUpdatesInterval { get; } = TimeSpan.FromMilliseconds(30);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Platform/IGuiContext.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Platform
2 | {
3 | using System;
4 | using System.Runtime.CompilerServices;
5 |
6 | ///
7 | /// Provides platform-specific methods sensible to a UI context.
8 | ///
9 | internal interface IGuiContext
10 | {
11 | ///
12 | /// Gets the type of the context.
13 | ///
14 | GuiContextType Type { get; }
15 |
16 | ///
17 | /// Invokes a task on the GUI thread with the possibility of awaiting it.
18 | ///
19 | /// The callback.
20 | /// The awaitable task.
21 | ConfiguredTaskAwaitable InvokeAsync(Action callback);
22 |
23 | ///
24 | /// Invokes a task on the GUI thread and does not await it.
25 | ///
26 | /// The callback.
27 | void EnqueueInvoke(Action callback);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Platform/IPropertyProxy.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Platform
2 | {
3 | using System;
4 |
5 | ///
6 | /// Defines methods and properties for a proxy related to a property
7 | /// containing getter and setter delegates along with property metadata.
8 | ///
9 | internal interface IPropertyProxy
10 | {
11 | ///
12 | /// Gets the property name.
13 | ///
14 | string Name { get; }
15 |
16 | ///
17 | /// Gets the property type.
18 | ///
19 | Type Type { get; }
20 |
21 | ///
22 | /// Gets a value indicating whether this property has a getter.
23 | ///
24 | bool CanRead { get; }
25 |
26 | ///
27 | /// Gets a value indicating whether this property has a setter.
28 | ///
29 | bool CanWrite { get; }
30 |
31 | ///
32 | /// Calls the getter method for the property on the specified instance.
33 | ///
34 | /// The instance.
35 | /// The value of the property.
36 | object GetValue(object instance);
37 |
38 | ///
39 | /// Calls the setter method for the property on the specified instance.
40 | ///
41 | /// The instance.
42 | /// The value.
43 | void SetValue(object instance, object value);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Platform/PropertyProxy.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable CA1812
2 | namespace Unosquare.FFME.Platform
3 | {
4 | using System;
5 | using System.Reflection;
6 | using System.Runtime.CompilerServices;
7 |
8 | ///
9 | /// Defines property metadata and delegate methods to get and set property values.
10 | ///
11 | /// The type of the object.
12 | /// The type of the value.
13 | internal sealed class PropertyProxy : IPropertyProxy
14 | where TClass : class
15 | {
16 | private readonly Func Getter;
17 | private readonly Action Setter;
18 |
19 | ///
20 | /// Initializes a new instance of the class.
21 | ///
22 | /// The property.
23 | public PropertyProxy(PropertyInfo property)
24 | {
25 | Name = property.Name;
26 | Type = property.PropertyType;
27 |
28 | var getterInfo = property.GetGetMethod(false);
29 | if (getterInfo != null)
30 | {
31 | CanRead = true;
32 | Getter = (Func)Delegate.CreateDelegate(typeof(Func), getterInfo);
33 | }
34 |
35 | var setterInfo = property.GetSetMethod(false);
36 | if (setterInfo != null)
37 | {
38 | CanWrite = true;
39 | Setter = (Action)Delegate.CreateDelegate(typeof(Action), setterInfo);
40 | }
41 | }
42 |
43 | ///
44 | public string Name { get; }
45 |
46 | ///
47 | public Type Type { get; }
48 |
49 | ///
50 | public bool CanRead { get; }
51 |
52 | ///
53 | public bool CanWrite { get; }
54 |
55 | ///
56 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
57 | object IPropertyProxy.GetValue(object instance) =>
58 | Getter(instance as TClass);
59 |
60 | ///
61 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
62 | void IPropertyProxy.SetValue(object instance, object value) =>
63 | Setter(instance as TClass, (TProperty)value);
64 | }
65 | }
66 | #pragma warning restore CA1812
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Unosquare.FFME.MediaElement.projitems:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
5 | true
6 | 4f84c6a1-ba3a-4a1e-84ee-7a7aa214f317
7 |
8 |
9 | Unosquare.FFME
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/Unosquare.FFME.MediaElement/Unosquare.FFME.MediaElement.shproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 4f84c6a1-ba3a-4a1e-84ee-7a7aa214f317
5 | 14.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/Controls/ControllerPanelControl.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Windows.Sample.Controls
2 | {
3 | ///
4 | /// Interaction logic for ControllerPanelControl.xaml.
5 | ///
6 | public partial class ControllerPanelControl
7 | {
8 | ///
9 | /// Initializes a new instance of the class.
10 | ///
11 | public ControllerPanelControl() => InitializeComponent();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/Controls/PlaylistPanelControl.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Windows.Sample.Controls
2 | {
3 | using Foundation;
4 | using System;
5 | using System.Windows;
6 | using System.Windows.Controls.Primitives;
7 | using System.Windows.Input;
8 | using ViewModels;
9 |
10 | ///
11 | /// Interaction logic for PlaylistPanelControl.xaml.
12 | ///
13 | public partial class PlaylistPanelControl
14 | {
15 | ///
16 | /// Initializes a new instance of the class.
17 | ///
18 | public PlaylistPanelControl()
19 | {
20 | InitializeComponent();
21 |
22 | // Prevent binding to the events
23 | if (App.IsInDesignMode)
24 | return;
25 |
26 | // Bind the Enter key to the command
27 | OpenFileTextBox.KeyDown += async (s, e) =>
28 | {
29 | if (e.Key != Key.Enter) return;
30 | await App.ViewModel.Commands.OpenCommand.ExecuteAsync(OpenFileTextBox.Text);
31 | e.Handled = true;
32 | };
33 |
34 | SearchTextBox.IsEnabledChanged += (s, e) =>
35 | {
36 | if ((bool)e.OldValue == false && (bool)e.NewValue)
37 | FocusSearchBox();
38 |
39 | if ((bool)e.OldValue && (bool)e.NewValue == false)
40 | FocusFileBox();
41 | };
42 |
43 | IsVisibleChanged += (s, e) =>
44 | {
45 | if (SearchTextBox.IsEnabled)
46 | FocusSearchBox();
47 | else
48 | FocusFileBox();
49 | };
50 | }
51 |
52 | #region Properties
53 |
54 | ///
55 | /// A proxy, strongly-typed property to the underlying DataContext.
56 | ///
57 | public RootViewModel ViewModel => DataContext as RootViewModel;
58 |
59 | #endregion
60 |
61 | private static void FocusTextBox(TextBoxBase textBox)
62 | {
63 | DeferredAction.Create(context =>
64 | {
65 | if (textBox == null || Application.Current == null || Application.Current.MainWindow == null)
66 | return;
67 |
68 | textBox.Focus();
69 | textBox.SelectAll();
70 | FocusManager.SetFocusedElement(Application.Current.MainWindow, textBox);
71 | Keyboard.Focus(textBox);
72 |
73 | if (textBox.IsVisible == false || textBox.IsKeyboardFocused)
74 | context?.Dispose();
75 | else
76 | context?.Defer(TimeSpan.FromSeconds(0.25));
77 | }).Defer(TimeSpan.FromSeconds(0.25));
78 | }
79 |
80 | ///
81 | /// Focuses the search box.
82 | ///
83 | private void FocusSearchBox()
84 | {
85 | FocusTextBox(SearchTextBox);
86 | }
87 |
88 | ///
89 | /// Focuses the file box.
90 | ///
91 | private void FocusFileBox()
92 | {
93 | FocusTextBox(OpenFileTextBox);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/Controls/PropertiesPanelControl.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Windows.Sample.Controls
2 | {
3 | ///
4 | /// Interaction logic for MediaPropertyPanelControl.xaml.
5 | ///
6 | public partial class PropertiesPanelControl
7 | {
8 | ///
9 | /// Initializes a new instance of the class.
10 | ///
11 | public PropertiesPanelControl()
12 | {
13 | InitializeComponent();
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/Foundation/CustomPlaylistEntry.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Windows.Sample.Foundation
2 | {
3 | using Playlists;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Globalization;
7 | using System.Runtime.CompilerServices;
8 |
9 | ///
10 | /// A custom playlist entry with notification properties backed by Attributes.
11 | ///
12 | ///
13 | public sealed class CustomPlaylistEntry : PlaylistEntry
14 | {
15 | private static readonly Dictionary PropertyMap = new Dictionary
16 | {
17 | { nameof(Thumbnail), "ffme-thumbnail" },
18 | { nameof(Format), "info-format" },
19 | { nameof(LastOpenedUtc), "ffme-lastopened" }
20 | };
21 |
22 | ///
23 | /// Gets or sets the thumbnail.
24 | ///
25 | public string Thumbnail
26 | {
27 | get => GetMappedAttributeValue();
28 | set => SetMappedAttributeValue(value);
29 | }
30 |
31 | ///
32 | /// Gets or sets the format.
33 | ///
34 | public string Format
35 | {
36 | get => GetMappedAttributeValue();
37 | set => SetMappedAttributeValue(value);
38 | }
39 |
40 | ///
41 | /// Gets or sets the last opened UTC.
42 | ///
43 | public DateTime? LastOpenedUtc
44 | {
45 | get
46 | {
47 | var currentValue = GetMappedAttributeValue();
48 | if (string.IsNullOrWhiteSpace(currentValue))
49 | return default;
50 |
51 | return long.TryParse(currentValue, out var binaryValue) ?
52 | DateTime.FromBinary(binaryValue) :
53 | default(DateTime?);
54 | }
55 | set
56 | {
57 | if (value == null)
58 | {
59 | SetMappedAttributeValue(null);
60 | return;
61 | }
62 |
63 | var binaryValue = value.Value.ToBinary().ToString(CultureInfo.InvariantCulture);
64 | SetMappedAttributeValue(binaryValue);
65 | }
66 | }
67 |
68 | private string GetMappedAttributeValue([CallerMemberName] string propertyName = null) =>
69 | Attributes.GetEntryValue(PropertyMap[propertyName ?? throw new ArgumentNullException(nameof(propertyName))]);
70 |
71 | private void SetMappedAttributeValue(string value, [CallerMemberName] string propertyName = null)
72 | {
73 | if (Attributes.SetEntryValue(PropertyMap[propertyName ?? throw new ArgumentNullException(nameof(propertyName))], value))
74 | OnPropertyChanged(propertyName);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/Foundation/DeferredAction.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Windows.Sample.Foundation
2 | {
3 | using System;
4 | using System.Threading;
5 | using System.Windows;
6 |
7 | ///
8 | /// Represents a timer which performs an action on the UI thread when time elapses. Rescheduling is supported.
9 | /// Original code from here: https://www.codeproject.com/Articles/32426/Deferring-ListCollectionView-filter-updates-for-a address.
10 | ///
11 | public sealed class DeferredAction : IDisposable
12 | {
13 | private readonly Timer DeferTimer;
14 | private bool IsDisposed;
15 |
16 | ///
17 | /// Initializes a new instance of the class.
18 | ///
19 | /// The action.
20 | private DeferredAction(Action action)
21 | {
22 | DeferTimer = new Timer(s => Application.Current?.Dispatcher?.Invoke(() => action(this)));
23 | }
24 |
25 | ///
26 | /// Creates a new DeferredAction.
27 | ///
28 | ///
29 | /// The action that will be deferred. It is not performed until after is called.
30 | ///
31 | /// The Deferred Action.
32 | public static DeferredAction Create(Action action)
33 | {
34 | if (action == null)
35 | throw new ArgumentNullException(nameof(action));
36 |
37 | return new DeferredAction(action);
38 | }
39 |
40 | ///
41 | /// Defers performing the action until after time elapses. Repeated calls will reschedule the action
42 | /// if it has not already been performed.
43 | ///
44 | ///
45 | /// The amount of time to wait before performing the action.
46 | ///
47 | public void Defer(TimeSpan delay)
48 | {
49 | // Fire action when time elapses (with no subsequent calls).
50 | DeferTimer.Change(delay, Timeout.InfiniteTimeSpan);
51 | }
52 |
53 | #region IDisposable Implementation
54 |
55 | ///
56 | public void Dispose()
57 | {
58 | if (IsDisposed) return;
59 | IsDisposed = true;
60 | DeferTimer.Dispose();
61 | }
62 |
63 | #endregion
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/Foundation/FileInputStream.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Windows.Sample.Foundation;
2 |
3 | using Common;
4 | using FFmpeg.AutoGen;
5 | using System;
6 | using System.IO;
7 | using System.Runtime.InteropServices;
8 |
9 | ///
10 | ///
11 | /// Provides an example of a very simple custom input stream.
12 | ///
13 | ///
14 | public sealed unsafe class FileInputStream : IMediaInputStream
15 | {
16 | private readonly FileStream BackingStream;
17 | private readonly object ReadLock = new();
18 | private readonly byte[] ReadBuffer;
19 |
20 | ///
21 | /// Initializes a new instance of the class.
22 | ///
23 | /// The path.
24 | public FileInputStream(string path)
25 | {
26 | var fullPath = Path.GetFullPath(path);
27 | BackingStream = File.OpenRead(fullPath);
28 | var uri = new Uri(fullPath);
29 | StreamUri = new Uri(uri.ToString().ReplaceOrdinal("file://", Scheme));
30 | CanSeek = true;
31 | ReadBuffer = new byte[ReadBufferLength];
32 | }
33 |
34 | ///
35 | /// The custom file scheme (URL prefix) including the :// sequence.
36 | ///
37 | public static string Scheme => "customfile://";
38 |
39 | ///
40 | public Uri StreamUri { get; }
41 |
42 | ///
43 | public bool CanSeek { get; }
44 |
45 | ///
46 | public int ReadBufferLength => 1024 * 16;
47 |
48 | ///
49 | public InputStreamInitializing OnInitializing { get; }
50 |
51 | ///
52 | public InputStreamInitialized OnInitialized { get; }
53 |
54 | ///
55 | public void Dispose()
56 | {
57 | BackingStream?.Dispose();
58 | }
59 |
60 | ///
61 | /// Reads from the underlying stream and writes up to bytes
62 | /// to the . Returns the number of bytes that were written.
63 | ///
64 | /// The opaque.
65 | /// The target buffer.
66 | /// Length of the target buffer.
67 | ///
68 | /// The number of bytes that have been read.
69 | ///
70 | public int Read(void* opaque, byte* targetBuffer, int targetBufferLength)
71 | {
72 | lock (ReadLock)
73 | {
74 | try
75 | {
76 | var readCount = BackingStream.Read(ReadBuffer, 0, ReadBuffer.Length);
77 | if (readCount > 0)
78 | Marshal.Copy(ReadBuffer, 0, (IntPtr)targetBuffer, readCount);
79 | else if (readCount == 0)
80 | return ffmpeg.AVERROR_EOF;
81 |
82 | return readCount;
83 | }
84 | catch (Exception)
85 | {
86 | return ffmpeg.AVERROR_EOF;
87 | }
88 | }
89 | }
90 |
91 | ///
92 | public long Seek(void* opaque, long offset, int whence)
93 | {
94 | lock (ReadLock)
95 | {
96 | try
97 | {
98 | return whence == ffmpeg.AVSEEK_SIZE ?
99 | BackingStream.Length : BackingStream.Seek(offset, SeekOrigin.Begin);
100 | }
101 | catch
102 | {
103 | return ffmpeg.AVERROR_EOF;
104 | }
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/Foundation/ReactiveExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Windows.Sample.Foundation
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.ComponentModel;
6 |
7 | ///
8 | /// A very simple set of extensions to more easily handle UI state changes based on
9 | /// notification properties. The main idea is to bind to the PropertyChanged event
10 | /// for a publisher only one and add a set of callbacks with matching property names
11 | /// when the publisher raises the event.
12 | ///
13 | internal static class ReactiveExtensions
14 | {
15 | ///
16 | /// Contains a list of subscriptions Subscriptions[Publisher][PropertyName].List of subscriber-action pairs.
17 | ///
18 | private static readonly Dictionary Subscriptions
19 | = new Dictionary();
20 |
21 | private static readonly object SyncLock = new object();
22 |
23 | ///
24 | /// Specifies a callback when properties change.
25 | ///
26 | /// The publisher.
27 | /// The callback.
28 | /// The property names.
29 | public static void WhenChanged(this INotifyPropertyChanged publisher, Action callback, params string[] propertyNames)
30 | {
31 | var bindPropertyChanged = false;
32 |
33 | lock (SyncLock)
34 | {
35 | // Create the subscription set for the publisher if it does not exist.
36 | if (Subscriptions.ContainsKey(publisher) == false)
37 | {
38 | Subscriptions[publisher] = new SubscriptionSet();
39 |
40 | // if it did not exist before, we need to bind to the
41 | // PropertyChanged event of the publisher.
42 | bindPropertyChanged = true;
43 | }
44 |
45 | foreach (var propertyName in propertyNames)
46 | {
47 | // Create the set of callback references for the publisher's property if it does not exist.
48 | if (Subscriptions[publisher].ContainsKey(propertyName) == false)
49 | Subscriptions[publisher][propertyName] = new CallbackList();
50 |
51 | // Add the callback for the publisher's property changed
52 | Subscriptions[publisher][propertyName].Add(callback);
53 | }
54 | }
55 |
56 | // Make an initial call
57 | callback();
58 |
59 | // No need to bind to the PropertyChanged event if we are already bound to it.
60 | if (bindPropertyChanged == false)
61 | return;
62 |
63 | // Finally, bind to property changed
64 | publisher.PropertyChanged += (s, e) =>
65 | {
66 | CallbackList propertyCallbacks = null;
67 |
68 | lock (SyncLock)
69 | {
70 | // we don't need to perform any action if there are no subscriptions to
71 | // this property name.
72 | if (Subscriptions[publisher].ContainsKey(e.PropertyName) == false)
73 | return;
74 |
75 | // Get the list of alive subscriptions for this property name
76 | propertyCallbacks = Subscriptions[publisher][e.PropertyName];
77 | }
78 |
79 | // Call the subscription's callbacks
80 | foreach (var propertyCallback in propertyCallbacks)
81 | {
82 | // if the subscription is alive, invoke the matching action
83 | propertyCallback.Invoke();
84 | }
85 | };
86 | }
87 |
88 | internal sealed class SubscriptionSet : Dictionary { }
89 |
90 | internal sealed class CallbackList : List
91 | {
92 | public CallbackList()
93 | : base(32)
94 | {
95 | // placeholder
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/Foundation/WindowStatus.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Windows.Sample.Foundation
2 | {
3 | using System;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | ///
8 | /// Represents the general state of a Window.
9 | ///
10 | public sealed class WindowStatus
11 | {
12 | ///
13 | /// Gets or sets the state of the window.
14 | ///
15 | ///
16 | /// The state of the window.
17 | ///
18 | public WindowState WindowState { get; set; }
19 |
20 | ///
21 | /// Gets or sets the top.
22 | ///
23 | ///
24 | /// The top.
25 | ///
26 | public double Top { get; set; }
27 |
28 | ///
29 | /// Gets or sets the left.
30 | ///
31 | ///
32 | /// The left.
33 | ///
34 | public double Left { get; set; }
35 |
36 | ///
37 | /// Gets or sets the window style.
38 | ///
39 | ///
40 | /// The window style.
41 | ///
42 | public WindowStyle WindowStyle { get; set; }
43 |
44 | ///
45 | /// Gets or sets a value indicating whether this is topmost.
46 | ///
47 | ///
48 | /// true if topmost; otherwise, false.
49 | ///
50 | public bool Topmost { get; set; }
51 |
52 | ///
53 | /// Gets or sets the resize mode.
54 | ///
55 | ///
56 | /// The resize mode.
57 | ///
58 | public ResizeMode ResizeMode { get; set; }
59 |
60 | ///
61 | /// Disables the display timeout.
62 | ///
63 | public static void DisableDisplayTimeout()
64 | {
65 | NativeMethods.SetThreadExecutionState(
66 | NativeMethods.EXECUTION_STATE.ES_DISPLAY_REQUIRED | NativeMethods.EXECUTION_STATE.ES_CONTINUOUS);
67 | }
68 |
69 | ///
70 | /// Allows the display timeout.
71 | ///
72 | public static void EnableDisplayTimeout()
73 | {
74 | NativeMethods.SetThreadExecutionState(
75 | NativeMethods.EXECUTION_STATE.ES_CONTINUOUS);
76 | }
77 |
78 | ///
79 | /// Captures the specified window object.
80 | ///
81 | /// The window state object.
82 | public void Capture(Window w)
83 | {
84 | if (w == null) throw new ArgumentNullException(nameof(w));
85 |
86 | WindowState = w.WindowState;
87 | Top = w.Top;
88 | Left = w.Left;
89 | WindowStyle = w.WindowStyle;
90 | Topmost = w.Topmost;
91 | ResizeMode = w.ResizeMode;
92 | }
93 |
94 | ///
95 | /// Applies the specified window state.
96 | ///
97 | /// The w.
98 | public void Apply(Window w)
99 | {
100 | if (w == null) throw new ArgumentNullException(nameof(w));
101 |
102 | w.WindowState = WindowState;
103 | w.Top = Top;
104 | w.Left = Left;
105 | w.WindowStyle = WindowStyle;
106 | w.Topmost = Topmost;
107 | w.ResizeMode = ResizeMode;
108 | }
109 |
110 | ///
111 | /// Provides access to disable or enable screen timeout. Original idea taken from:
112 | /// http://www.blackwasp.co.uk/DisableScreensaver.aspx address.
113 | ///
114 | private static class NativeMethods
115 | {
116 | [Flags]
117 | public enum EXECUTION_STATE : uint
118 | {
119 | ES_AWAYMODE_REQUIRED = 0x00000040,
120 | ES_CONTINUOUS = 0x80000000,
121 | ES_DISPLAY_REQUIRED = 0x00000002,
122 | ES_SYSTEM_REQUIRED = 0x00000001
123 | }
124 |
125 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
126 | public static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "Better formatting", Scope = "namespaceanddescendants", Target = "~N:Unosquare.FFME")]
9 | [assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "Better formatting", Scope = "member", Target = "~F:Unosquare.FFME.Windows.Sample.MainWindow.TogglePlayPauseKeys")]
10 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
40 |
41 |
45 |
46 |
47 |
50 |
51 |
54 |
55 |
58 |
59 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Unosquare.FFME.Windows.Sample": {
4 | "commandName": "Project",
5 | "nativeDebugging": false
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/ViewModels/AttachedViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Windows.Sample.ViewModels
2 | {
3 | using Common;
4 |
5 | ///
6 | /// A base class for Root VM-attached view models.
7 | ///
8 | ///
9 | public abstract class AttachedViewModel : ViewModelBase
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | /// The root.
15 | protected AttachedViewModel(RootViewModel root)
16 | {
17 | Root = root;
18 | }
19 |
20 | ///
21 | /// Gets the root VM this object belongs to.
22 | ///
23 | public RootViewModel Root { get; }
24 |
25 | ///
26 | /// Called by the root ViewModel when the application is loaded and fully available.
27 | ///
28 | internal virtual void OnApplicationLoaded()
29 | {
30 | // placeholder
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows.Sample/ffmpeg.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Unosquare.FFME.Windows.Sample/ffmpeg.ico
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Common/AudioDeviceInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using System;
4 |
5 | ///
6 | /// Represents a device identifier.
7 | ///
8 | /// The type of the device identifier.
9 | public class AudioDeviceInfo
10 | where T : struct
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// The device identifier.
16 | /// The name.
17 | /// The provider.
18 | /// if set to true [is default].
19 | /// The tag.
20 | internal AudioDeviceInfo(T deviceId, string name, string provider, bool isDefault, string tag)
21 | {
22 | DeviceId = deviceId;
23 | Name = name;
24 | Provider = provider;
25 | Tag = tag;
26 | IsDefault = isDefault;
27 | }
28 |
29 | ///
30 | /// Gets the device identifier.
31 | ///
32 | public T DeviceId { get; }
33 |
34 | ///
35 | /// Gets the name.
36 | ///
37 | public string Name { get; }
38 |
39 | ///
40 | /// Gets the provider.
41 | ///
42 | public string Provider { get; }
43 |
44 | ///
45 | /// Gets the tag.
46 | ///
47 | public string Tag { get; }
48 |
49 | ///
50 | /// Gets a value indicating whether this device is the default.
51 | ///
52 | public bool IsDefault { get; }
53 |
54 | ///
55 | /// Returns a that represents this instance.
56 | ///
57 | ///
58 | /// A that represents this instance.
59 | ///
60 | public override string ToString()
61 | {
62 | return $"{Provider}: {Name}";
63 | }
64 | }
65 |
66 | ///
67 | /// Represents information about a legacy WinMM audio device.
68 | ///
69 | public sealed class LegacyAudioDeviceInfo : AudioDeviceInfo
70 | {
71 | ///
72 | /// Initializes a new instance of the class.
73 | ///
74 | /// The device identifier.
75 | /// The name.
76 | /// The provider.
77 | /// if set to true [is default].
78 | /// The tag.
79 | internal LegacyAudioDeviceInfo(int deviceId, string name, string provider, bool isDefault, string tag)
80 | : base(deviceId, name, provider, isDefault, tag)
81 | {
82 | // placeholder
83 | }
84 | }
85 |
86 | ///
87 | /// Represents information about a DirectSound audio device.
88 | ///
89 | public sealed class DirectSoundDeviceInfo : AudioDeviceInfo
90 | {
91 | ///
92 | /// Initializes a new instance of the class.
93 | ///
94 | /// The device identifier.
95 | /// The name.
96 | /// The provider.
97 | /// if set to true [is default].
98 | /// The tag.
99 | internal DirectSoundDeviceInfo(Guid deviceId, string name, string provider, bool isDefault, string tag)
100 | : base(deviceId, name, provider, isDefault, tag)
101 | {
102 | // placeholder
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Common/LegacyAudioException.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common;
2 |
3 | using System;
4 |
5 | ///
6 | ///
7 | /// An exception representing an error in Windows Multimedia Audio.
8 | ///
9 | [Serializable]
10 | public sealed class LegacyAudioException : MediaContainerException
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// The result returned by the Windows API call.
16 | /// The name of the Windows API that failed.
17 | public LegacyAudioException(LegacyAudioResult result, string functionName)
18 | : base(ErrorMessage(result, functionName))
19 | {
20 | Result = result;
21 | FunctionName = functionName;
22 | }
23 |
24 | ///
25 | /// Initializes a new instance of the class.
26 | ///
27 | public LegacyAudioException()
28 | : this(LegacyAudioResult.UnspecifiedError, $"{nameof(LegacyAudioException)}.ctor()")
29 | {
30 | // placeholder
31 | }
32 |
33 | ///
34 | /// Initializes a new instance of the class.
35 | ///
36 | /// The error message.
37 | public LegacyAudioException(string message)
38 | : this(LegacyAudioResult.UnspecifiedError, $"{nameof(LegacyAudioException)}.ctor(): {message}")
39 | {
40 | }
41 |
42 | ///
43 | /// Initializes a new instance of the class.
44 | ///
45 | /// The error message.
46 | /// The inner exception.
47 | public LegacyAudioException(string message, Exception innerException)
48 | : base(message, innerException)
49 | {
50 | Result = LegacyAudioResult.UnspecifiedError;
51 | FunctionName = $"{nameof(LegacyAudioException)}.ctor()";
52 | }
53 |
54 | ///
55 | /// Gets the name of the function that failed.
56 | ///
57 | public string FunctionName { get; }
58 |
59 | ///
60 | /// Gets the Windows API result code.
61 | ///
62 | public LegacyAudioResult Result { get; }
63 |
64 | ///
65 | /// Helper function to automatically raise an exception on failure.
66 | ///
67 | /// The result of the API call.
68 | /// The API function name.
69 | internal static void Try(LegacyAudioResult result, string function)
70 | {
71 | if (result != LegacyAudioResult.NoError)
72 | throw new LegacyAudioException(result, function);
73 | }
74 |
75 | ///
76 | /// Creates an error message base don an error result.
77 | ///
78 | /// The result.
79 | /// The function.
80 | /// A descriptive error message.
81 | private static string ErrorMessage(LegacyAudioResult result, string function) => $"{result} calling {function}";
82 | }
83 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Common/LegacyAudioResult.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | ///
4 | /// Windows multimedia error codes from mmsystem.h.
5 | ///
6 | public enum LegacyAudioResult
7 | {
8 | /// no error.
9 | NoError = 0,
10 |
11 | /// unspecified error.
12 | UnspecifiedError = 1,
13 |
14 | /// device ID out of range.
15 | BadDeviceId = 2,
16 |
17 | /// driver failed enable.
18 | NotEnabled = 3,
19 |
20 | /// device already allocated.
21 | AlreadyAllocated = 4,
22 |
23 | /// device handle is invalid.
24 | InvalidHandle = 5,
25 |
26 | /// no device driver present.
27 | NoDriver = 6,
28 |
29 | /// memory allocation error.
30 | MemoryAllocationError = 7,
31 |
32 | /// function isn't supported.
33 | NotSupported = 8,
34 |
35 | /// error value out of range.
36 | BadErrorNumber = 9,
37 |
38 | /// invalid flag passed.
39 | InvalidFlag = 10,
40 |
41 | /// invalid parameter passed.
42 | InvalidParameter = 11,
43 |
44 | /// handle being used simultaneously on another thread (eg callback).
45 | HandleBusy = 12,
46 |
47 | /// specified alias not found.
48 | InvalidAlias = 13,
49 |
50 | /// bad registry database.
51 | BadRegistryDatabase = 14,
52 |
53 | /// registry key not found.
54 | RegistryKeyNotFound = 15,
55 |
56 | /// registry read error.
57 | RegistryReadError = 16,
58 |
59 | /// registry write error.
60 | RegistryWriteError = 17,
61 |
62 | /// registry delete error.
63 | RegistryDeleteError = 18,
64 |
65 | /// registry value not found.
66 | RegistryValueNotFound = 19,
67 |
68 | /// driver does not call DriverCallback.
69 | NoDriverCallback = 20,
70 |
71 | /// more data to be returned.
72 | MoreData = 21,
73 |
74 | /// unsupported wave format.
75 | WaveBadFormat = 32,
76 |
77 | /// still something playing.
78 | WaveStillPlaying = 33,
79 |
80 | /// header not prepared.
81 | WaveHeaderUnprepared = 34,
82 |
83 | /// device is synchronous.
84 | WaveSync = 35,
85 |
86 | // ACM error codes, found in msacm.h
87 |
88 | /// Conversion not possible.
89 | AcmNotPossible = 512,
90 |
91 | /// Busy.
92 | AcmBusy = 513,
93 |
94 | /// Header Unprepared.
95 | AcmHeaderUnprepared = 514,
96 |
97 | /// Cancelled.
98 | AcmCancelled = 515,
99 |
100 | // Mixer error codes, found in mmresult.h
101 |
102 | /// invalid line.
103 | MixerInvalidLine = 1024,
104 |
105 | /// invalid control.
106 | MixerInvalidControl = 1025,
107 |
108 | /// invalid value.
109 | MixerInvalidValue = 1026
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Common/RendererOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | ///
4 | /// Provides access to various internal media renderer options.
5 | ///
6 | public sealed class RendererOptions
7 | {
8 | ///
9 | /// By default, the audio renderer will skip or wait for samples to
10 | /// synchronize to video.
11 | ///
12 | public bool AudioDisableSync { get; set; }
13 |
14 | ///
15 | /// Gets or sets the DirectSound device identifier. It is the default playback device by default.
16 | /// Only valid if is set to false which is the default.
17 | ///
18 | public DirectSoundDeviceInfo DirectSoundDevice { get; set; } = Library.DefaultDirectSoundDevice;
19 |
20 | ///
21 | /// Gets or sets the wave device identifier. -1 is the default playback device.
22 | /// Only valid if is set to true.
23 | ///
24 | public LegacyAudioDeviceInfo LegacyAudioDevice { get; set; } = Library.DefaultLegacyAudioDevice;
25 |
26 | ///
27 | /// Gets or sets a value indicating whether the legacy MME (WinMM) should be used
28 | /// as an audio output device as opposed to DirectSound. This defaults to false.
29 | ///
30 | public bool UseLegacyAudioOut { get; set; }
31 |
32 | ///
33 | /// Gets or sets the frame refresh rate limit for the video renderer.
34 | /// Defaults to 0 and means no limit. Units are in frames per second.
35 | ///
36 | public int VideoRefreshRateLimit { get; set; }
37 |
38 | ///
39 | /// Gets or sets which image type is used for the video renderer.
40 | /// Use WriteableBitmap for tear-free scenarios, or use the InteropBitmap for
41 | /// faster, lower CPU usage. InteropBitmap might introduce some tearing.
42 | ///
43 | public VideoRendererImageType VideoImageType { get; set; } = VideoRendererImageType.WriteableBitmap;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Common/RenderingAudioEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | ///
7 | /// Provides the audio samples rendering payload as event arguments.
8 | ///
9 | ///
10 | public sealed class RenderingAudioEventArgs : RenderingEventArgs
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// The buffer.
16 | /// The length.
17 | /// The latency between the buffer position and the real-time playback clock.
18 | /// The engine.
19 | /// The stream.
20 | /// The start time.
21 | /// The duration.
22 | /// The clock.
23 | /// The unadjusted PTS of the frame in stream Time Base units.
24 | internal RenderingAudioEventArgs(
25 | byte[] buffer, int length, TimeSpan latency, IMediaEngineState engineState, StreamInfo stream, TimeSpan startTime, TimeSpan duration, TimeSpan clock, long pts)
26 | : base(engineState, stream, startTime, duration, clock, pts)
27 | {
28 | Buffer = buffer;
29 | BufferLength = length;
30 | SampleRate = Constants.AudioSampleRate;
31 | ChannelCount = Constants.AudioChannelCount;
32 | BitsPerSample = Constants.AudioBitsPerSample;
33 | Latency = latency;
34 | }
35 |
36 | ///
37 | /// Gets the latency between the audio buffer position and the real-time playback clock.
38 | ///
39 | public TimeSpan Latency { get; }
40 |
41 | ///
42 | /// Gets a the raw data buffer going into the audio device.
43 | /// Samples are provided in PCM 16-bit signed, interleaved stereo.
44 | ///
45 | public IReadOnlyList Buffer { get; }
46 |
47 | ///
48 | /// Gets the length in bytes of the samples buffer.
49 | ///
50 | public int BufferLength { get; }
51 |
52 | ///
53 | /// Gets the number of samples in 1 second.
54 | ///
55 | public int SampleRate { get; }
56 |
57 | ///
58 | /// Gets the number of channels.
59 | ///
60 | public int ChannelCount { get; }
61 |
62 | ///
63 | /// Gets the number of bits per sample.
64 | ///
65 | public int BitsPerSample { get; }
66 |
67 | ///
68 | /// Gets the number of samples in the buffer for all channels.
69 | ///
70 | public int Samples => BufferLength / (BitsPerSample / 8);
71 |
72 | ///
73 | /// Gets the number of samples in the buffer per channel.
74 | ///
75 | public int SamplesPerChannel => Samples / ChannelCount;
76 |
77 | ///
78 | /// Gets a the raw data buffer going into the audio device.
79 | /// Samples are provided in PCM 16-bit signed, interleaved stereo.
80 | ///
81 | /// The buffer data as an array.
82 | public byte[] GetBufferData() => Buffer as byte[];
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Common/RenderingEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using System;
4 |
5 | ///
6 | /// A base class to represent media block
7 | /// rendering event arguments.
8 | ///
9 | ///
10 | public abstract class RenderingEventArgs : EventArgs
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// The media engine state.
16 | /// The stream.
17 | /// The position.
18 | /// The duration.
19 | /// The clock.
20 | /// The original unadjusted PTS of the frame.
21 | protected RenderingEventArgs(IMediaEngineState engineState, StreamInfo stream, TimeSpan startTime, TimeSpan duration, TimeSpan clock, long pts)
22 | {
23 | EngineState = engineState;
24 | StartTime = startTime;
25 | Duration = duration;
26 | Clock = clock;
27 | Stream = stream;
28 | PresentationTime = pts;
29 | }
30 |
31 | ///
32 | /// Provides access to the underlying media engine state.
33 | ///
34 | public IMediaEngineState EngineState { get; }
35 |
36 | ///
37 | /// Provides Stream Information coming from the media container.
38 | ///
39 | public StreamInfo Stream { get; }
40 |
41 | ///
42 | /// Gets the clock position at which the media
43 | /// was called for rendering.
44 | ///
45 | public TimeSpan Clock { get; }
46 |
47 | ///
48 | /// Gets the starting time at which this media
49 | /// has to be presented.
50 | ///
51 | public TimeSpan StartTime { get; }
52 |
53 | ///
54 | /// Gets how long this media has to be presented.
55 | ///
56 | public TimeSpan Duration { get; }
57 |
58 | ///
59 | /// Gets the unadjusted, original presentation timestamp (PTS) of the frame in
60 | /// units. May return
61 | /// when invialid, not applicable (as in continuous audio rendering), or unavailable.
62 | ///
63 | public long PresentationTime { get; }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Common/RenderingSubtitlesEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using FFmpeg.AutoGen;
4 | using System;
5 | using System.Collections.Generic;
6 |
7 | ///
8 | /// Provides the subtitles rendering payload as event arguments.
9 | ///
10 | ///
11 | public sealed class RenderingSubtitlesEventArgs : RenderingEventArgs
12 | {
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// The text.
17 | /// The original text.
18 | /// The format.
19 | /// The engine.
20 | /// The stream.
21 | /// The start time.
22 | /// The duration.
23 | /// The clock.
24 | /// The unadjusted PTS of the frame given in stream Time Base units.
25 | internal RenderingSubtitlesEventArgs(
26 | IList text,
27 | IList originalText,
28 | AVSubtitleType format,
29 | IMediaEngineState engineState,
30 | StreamInfo stream,
31 | TimeSpan startTime,
32 | TimeSpan duration,
33 | TimeSpan clock,
34 | long pts)
35 | : base(engineState, stream, startTime, duration, clock, pts)
36 | {
37 | Text = text;
38 | Format = format;
39 | OriginalText = originalText;
40 | }
41 |
42 | ///
43 | /// Gets the text stripped out of ASS or SRT formatting.
44 | /// This is what the default subtitle renderer will display
45 | /// on the screen.
46 | ///
47 | public IList Text { get; }
48 |
49 | ///
50 | /// Gets the text as originally decoded including
51 | /// all markup and formatting.
52 | ///
53 | public IList OriginalText { get; }
54 |
55 | ///
56 | /// Gets the type of subtitle format the original
57 | /// subtitle text is in.
58 | ///
59 | public AVSubtitleType Format { get; }
60 |
61 | ///
62 | /// When set to true, clears the current subtitle and
63 | /// prevents the subtitle block from being rendered.
64 | ///
65 | public bool Cancel { get; set; }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Common/RenderingVideoEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using ClosedCaptions;
4 | using FFmpeg.AutoGen;
5 | using System;
6 | using System.Collections.Generic;
7 |
8 | ///
9 | /// The video rendering event arguments.
10 | ///
11 | ///
12 | public sealed class RenderingVideoEventArgs : RenderingEventArgs
13 | {
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | /// The bitmap.
18 | /// The closed captions.
19 | /// The smtpe time code.
20 | /// The picture number.
21 | /// The origination picture type.
22 | /// The engine.
23 | /// The stream.
24 | /// The start time.
25 | /// The duration.
26 | /// The clock.
27 | /// The unadjusted, original presentation timestamp (PTS) of the frame.
28 | internal RenderingVideoEventArgs(
29 | BitmapDataBuffer bitmap,
30 | IReadOnlyList closedCaptions,
31 | string smtpeTimeCode,
32 | long pictureNumber,
33 | AVPictureType pictureType,
34 | IMediaEngineState engineState,
35 | StreamInfo stream,
36 | TimeSpan startTime,
37 | TimeSpan duration,
38 | TimeSpan clock,
39 | long pts)
40 | : base(engineState, stream, startTime, duration, clock, pts)
41 | {
42 | PictureNumber = pictureNumber;
43 | Bitmap = bitmap;
44 | SmtpeTimeCode = smtpeTimeCode;
45 | ClosedCaptions = closedCaptions;
46 | PictureType = pictureType;
47 | }
48 |
49 | ///
50 | /// Gets the writable bitmap filled with the video frame pixels.
51 | /// Feel free to capture or change this buffer.
52 | ///
53 | public BitmapDataBuffer Bitmap { get; }
54 |
55 | ///
56 | /// Gets the closed caption decoded packets.
57 | ///
58 | public IReadOnlyList ClosedCaptions { get; }
59 |
60 | ///
61 | /// Gets the display picture number (frame number).
62 | /// If not set by the decoder, this attempts to obtain it by dividing the start time by the
63 | /// frame duration.
64 | ///
65 | public long PictureNumber { get; }
66 |
67 | ///
68 | /// Gets the SMTPE time code.
69 | ///
70 | public string SmtpeTimeCode { get; }
71 |
72 | ///
73 | /// Gets the picture type.
74 | ///
75 | public AVPictureType PictureType { get; }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Common/VideoRendererImageType.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | ///
4 | /// Enumerates the different Video renderer image types.
5 | ///
6 | public enum VideoRendererImageType
7 | {
8 | ///
9 | /// Uses a tear-free WriteableBitmap.
10 | ///
11 | WriteableBitmap,
12 |
13 | ///
14 | /// Uses the faster, non tear-free InteropBitmap.
15 | ///
16 | InteropBitmap
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "Better formatting", Scope = "namespaceanddescendants", Target = "~N:Unosquare.FFME")]
9 | [assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "Better formatting", Scope = "namespaceanddescendants", Target = "~N:FFmpeg")]
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Library.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME
2 | {
3 | using Common;
4 | using Rendering.Wave;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.ComponentModel;
8 | using System.Windows;
9 |
10 | public static partial class Library
11 | {
12 | ///
13 | /// Gets or sets a value indicating whether the video visualization control
14 | /// creates its own dispatcher thread to handle rendering of video frames.
15 | /// This is an experimental feature and it is useful when creating video walls.
16 | /// For example if you want to display multiple videos at a time and don't want to
17 | /// use time from the main UI thread. This feature is only valid if we are in
18 | /// a WPF context.
19 | ///
20 | public static bool EnableWpfMultiThreadedVideo { get; set; }
21 |
22 | ///
23 | /// The default DirectSound device.
24 | ///
25 | public static DirectSoundDeviceInfo DefaultDirectSoundDevice { get; } = new DirectSoundDeviceInfo(
26 | DirectSoundPlayer.DefaultPlaybackDeviceId, nameof(DefaultDirectSoundDevice), nameof(DirectSoundPlayer), true, Guid.Empty.ToString());
27 |
28 | ///
29 | /// The default Windows Multimeda Extensions Legacy Audio Device.
30 | ///
31 | public static LegacyAudioDeviceInfo DefaultLegacyAudioDevice { get; } = new LegacyAudioDeviceInfo(
32 | -1, nameof(DefaultLegacyAudioDevice), nameof(LegacyAudioPlayer), true, Guid.Empty.ToString());
33 |
34 | ///
35 | /// Determines if the control library is currently in design-time mode (as opposed to run-time).
36 | ///
37 | internal static bool IsInDesignMode =>
38 | (bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue;
39 |
40 | ///
41 | /// Enumerates the DirectSound devices.
42 | ///
43 | /// The available DirectSound devices.
44 | public static IEnumerable EnumerateDirectSoundDevices()
45 | {
46 | var devices = DirectSoundPlayer.EnumerateDevices();
47 | var result = new List(16) { DefaultDirectSoundDevice };
48 |
49 | foreach (var device in devices)
50 | {
51 | result.Add(new DirectSoundDeviceInfo(
52 | device.Guid, device.Description, nameof(DirectSoundPlayer), false, device.ModuleName));
53 | }
54 |
55 | return result;
56 | }
57 |
58 | ///
59 | /// Enumerates the (Legacy) Windows Multimedia Extensions devices.
60 | ///
61 | /// The available MME devices.
62 | public static IEnumerable EnumerateLegacyAudioDevices()
63 | {
64 | var devices = LegacyAudioPlayer.EnumerateDevices();
65 | var result = new List(16) { DefaultLegacyAudioDevice };
66 |
67 | for (var deviceId = 0; deviceId < devices.Count; deviceId++)
68 | {
69 | var device = devices[deviceId];
70 | result.Add(new LegacyAudioDeviceInfo(
71 | deviceId, device.ProductName, nameof(LegacyAudioPlayer), false, device.ProductGuid.ToString()));
72 | }
73 |
74 | return result;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/MediaElement.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Unosquare.FFME.Windows/MediaElement.png
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Platform/GuiContextType.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Platform
2 | {
3 | ///
4 | /// Enumerates GUI Context Types.
5 | ///
6 | internal enum GuiContextType
7 | {
8 | ///
9 | /// An invalid GUI context (console applications).
10 | ///
11 | None,
12 |
13 | ///
14 | /// A WPF GUI context (i.e. has dispatcher and is not Windows Forms).
15 | ///
16 | WPF,
17 |
18 | ///
19 | /// A Windows Forms GUI Context.
20 | ///
21 | WinForms
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Platform/MediaConnector.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Platform
2 | {
3 | using Common;
4 | using Engine;
5 | using Rendering;
6 | using System;
7 |
8 | internal partial class MediaConnector
9 | {
10 | ///
11 | public IMediaRenderer CreateRenderer(MediaType mediaType, MediaEngine mediaCore)
12 | {
13 | switch (mediaType)
14 | {
15 | case MediaType.Audio:
16 | return new AudioRenderer(mediaCore);
17 | case MediaType.Video:
18 | return ((mediaCore.Parent as MediaElement)?.RendererOptions.VideoImageType ?? VideoRendererImageType.WriteableBitmap) switch
19 | {
20 | VideoRendererImageType.WriteableBitmap => new VideoRenderer(mediaCore),
21 | VideoRendererImageType.InteropBitmap => new InteropVideoRenderer(mediaCore),
22 | _ => new VideoRenderer(mediaCore),
23 | };
24 | case MediaType.Subtitle:
25 | return new SubtitleRenderer(mediaCore);
26 | default:
27 | throw new NotSupportedException($"No suitable renderer for Media Type '{mediaType}'");
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Unosquare.FFME.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Unosquare.FFME.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized resource of type System.Drawing.Bitmap.
65 | ///
66 | internal static System.Drawing.Bitmap FFmpegMediaElementBackground {
67 | get {
68 | object obj = ResourceManager.GetObject("FFmpegMediaElementBackground", resourceCulture);
69 | return ((System.Drawing.Bitmap)(obj));
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Unosquare.FFME.Windows": {
4 | "commandName": "Project",
5 | "nativeDebugging": false
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/ClosedCaptionsCell.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering
2 | {
3 | ///
4 | /// Represents a grid cell state containing a Display and a back-buffer
5 | /// of a character and its properties.
6 | ///
7 | internal sealed class ClosedCaptionsCell
8 | {
9 | ///
10 | /// Initializes a new instance of the class.
11 | ///
12 | /// Index of the row.
13 | /// Index of the column.
14 | public ClosedCaptionsCell(int rowIndex, int columnIndex)
15 | {
16 | RowIndex = rowIndex;
17 | ColumnIndex = columnIndex;
18 | }
19 |
20 | ///
21 | /// Gets the index of the row this cell belongs to.
22 | ///
23 | public int RowIndex { get; }
24 |
25 | ///
26 | /// Gets the index of the column this cell belongs to.
27 | ///
28 | public int ColumnIndex { get; }
29 |
30 | ///
31 | /// Gets or sets the character.
32 | ///
33 | public ClosedCaptionsCellState Display { get; } = new ClosedCaptionsCellState();
34 |
35 | ///
36 | /// Gets or sets the buffered character.
37 | ///
38 | public ClosedCaptionsCellState Buffer { get; } = new ClosedCaptionsCellState();
39 |
40 | ///
41 | /// Copies the buffer content on to the display content
42 | /// and clears the buffer content.
43 | ///
44 | public void DisplayBuffer()
45 | {
46 | Display.CopyFrom(Buffer);
47 | Buffer.Clear();
48 | }
49 |
50 | ///
51 | /// Resets the entire state and contents of this cell.
52 | ///
53 | public void Reset()
54 | {
55 | Display.Clear();
56 | Buffer.Clear();
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/ClosedCaptionsCellState.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering
2 | {
3 | using System.Globalization;
4 | using System.Windows.Media;
5 |
6 | ///
7 | /// Contains single-character text and its attributes.
8 | ///
9 | internal sealed class ClosedCaptionsCellState
10 | {
11 | ///
12 | /// Gets the character as a string.
13 | ///
14 | public string Text => Character == default
15 | ? string.Empty
16 | : Character.ToString(CultureInfo.InvariantCulture);
17 |
18 | ///
19 | /// Gets or sets the character.
20 | ///
21 | public char Character { get; set; }
22 |
23 | ///
24 | /// Gets or sets the opacity (from 0.0 to 1.0 opaque).
25 | ///
26 | public double Opacity { get; set; } = 0.80;
27 |
28 | ///
29 | /// Gets or sets the foreground text color.
30 | ///
31 | public Brush Foreground { get; set; } = Brushes.White;
32 |
33 | ///
34 | /// Gets or sets the background color.
35 | ///
36 | public Brush Background { get; set; } = Brushes.Black;
37 |
38 | ///
39 | /// Gets or sets a value indicating whether this instance is underline.
40 | ///
41 | public bool IsUnderlined { get; set; }
42 |
43 | ///
44 | /// Gets or sets a value indicating whether this instance is italics.
45 | ///
46 | public bool IsItalics { get; set; }
47 |
48 | ///
49 | /// Copies text and attributes from another cell state content.
50 | ///
51 | /// The cell.
52 | public void CopyFrom(ClosedCaptionsCellState cell)
53 | {
54 | Character = cell.Character;
55 | Opacity = cell.Opacity;
56 | Foreground = cell.Foreground;
57 | Background = cell.Background;
58 | IsUnderlined = cell.IsUnderlined;
59 | IsItalics = cell.IsItalics;
60 | }
61 |
62 | ///
63 | /// Clears the text and its attributes.
64 | ///
65 | public void Clear()
66 | {
67 | Character = default;
68 | Opacity = 0.80;
69 | Foreground = Brushes.White;
70 | Background = Brushes.Black;
71 | IsUnderlined = default;
72 | IsItalics = default;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/ImageHost.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering
2 | {
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using System.Windows.Media;
6 |
7 | ///
8 | /// Implements an Image control that is hosted on its own independent dispatcher
9 | /// but maintains composability with the main UI.
10 | ///
11 | internal sealed class ImageHost : ElementHostBase
12 | {
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | public ImageHost()
17 | : base(true)
18 | {
19 | // placeholder
20 | }
21 |
22 | public ImageHost(bool hasOwnDispatcher)
23 | : base(hasOwnDispatcher)
24 | {
25 | // placeholder
26 | }
27 |
28 | ///
29 | /// Gets or sets the source.
30 | ///
31 | public ImageSource Source
32 | {
33 | get => GetElementProperty(Image.SourceProperty);
34 | set => SetElementProperty(Image.SourceProperty, value);
35 | }
36 |
37 | ///
38 | /// Gets or sets the stretch.
39 | ///
40 | public Stretch Stretch
41 | {
42 | get => GetElementProperty(Image.StretchProperty);
43 | set => SetElementProperty(Image.StretchProperty, value);
44 | }
45 |
46 | ///
47 | /// Gets or sets the stretch direction.
48 | ///
49 | public StretchDirection StretchDirection
50 | {
51 | get => GetElementProperty(Image.StretchDirectionProperty);
52 | set => SetElementProperty(Image.StretchDirectionProperty, value);
53 | }
54 |
55 | ///
56 | protected override Image CreateHostedElement()
57 | {
58 | var control = new Image();
59 | control.BeginInit();
60 | control.HorizontalAlignment = HorizontalAlignment.Stretch;
61 | control.VerticalAlignment = VerticalAlignment.Stretch;
62 | control.EndInit();
63 | return control;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/Wave/DirectSoundDeviceData.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering.Wave
2 | {
3 | using System;
4 |
5 | ///
6 | /// Class for enumerating DirectSound devices.
7 | ///
8 | internal sealed class DirectSoundDeviceData
9 | {
10 | ///
11 | /// The device identifier.
12 | ///
13 | public Guid Guid { get; internal set; }
14 |
15 | ///
16 | /// Device description.
17 | ///
18 | public string Description { get; internal set; }
19 |
20 | ///
21 | /// Device module name.
22 | ///
23 | public string ModuleName { get; internal set; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/Wave/IWavePlayer.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering.Wave
2 | {
3 | using System;
4 |
5 | ///
6 | /// Represents the interface to a device that can play a Wave data.
7 | ///
8 | internal interface IWavePlayer : IDisposable
9 | {
10 | ///
11 | /// Current playback state.
12 | ///
13 | PlaybackState PlaybackState { get; }
14 |
15 | ///
16 | /// Gets a value indicating whether the audio playback is running.
17 | ///
18 | bool IsRunning { get; }
19 |
20 | ///
21 | /// Gets or sets the desired latency in milliseconds
22 | /// Should be set before a call to Init.
23 | ///
24 | int DesiredLatency { get; }
25 |
26 | ///
27 | /// Gets the renderer that owns this wave player.
28 | ///
29 | AudioRenderer Renderer { get; }
30 |
31 | ///
32 | /// Begin playback.
33 | ///
34 | void Start();
35 |
36 | ///
37 | /// Clears the internal audio data with silence data.
38 | ///
39 | void Clear();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/Wave/IWaveProvider.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering.Wave
2 | {
3 | ///
4 | /// Generic interface for all WaveProviders.
5 | ///
6 | internal interface IWaveProvider
7 | {
8 | ///
9 | /// Gets the WaveFormat of this WaveProvider.
10 | ///
11 | WaveFormat WaveFormat { get; }
12 |
13 | ///
14 | /// Fill the specified buffer with wave data.
15 | ///
16 | /// The buffer to fill of wave data.
17 | /// Offset into buffer.
18 | /// The number of bytes to read.
19 | ///
20 | /// the number of bytes written to the buffer.
21 | ///
22 | int Read(byte[] buffer, int offset, int count);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/Wave/MmTime.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering.Wave
2 | {
3 | using System;
4 | using System.Runtime.InteropServices;
5 |
6 | ///
7 | /// http://msdn.microsoft.com/en-us/library/dd757347(v=VS.85).aspx.
8 | ///
9 | [StructLayout(LayoutKind.Explicit)]
10 | internal struct MmTime : IEquatable
11 | {
12 | public const int TimeMs = 0x0001;
13 | public const int TimeSamples = 0x0002;
14 | public const int TimeBytes = 0x0004;
15 |
16 | [FieldOffset(0)]
17 | public uint Type;
18 |
19 | [FieldOffset(4)]
20 | public uint Ms;
21 |
22 | [FieldOffset(4)]
23 | public uint Sample;
24 |
25 | [FieldOffset(4)]
26 | public uint CB;
27 |
28 | [FieldOffset(4)]
29 | public uint Ticks;
30 |
31 | [FieldOffset(4)]
32 | public byte SmpteHour;
33 |
34 | [FieldOffset(5)]
35 | public byte SmpteMin;
36 |
37 | [FieldOffset(6)]
38 | public byte SmpteSec;
39 |
40 | [FieldOffset(7)]
41 | public byte SmpteFrame;
42 |
43 | [FieldOffset(8)]
44 | public byte SmpteFps;
45 |
46 | [FieldOffset(9)]
47 | public byte SmpteDummy;
48 |
49 | [FieldOffset(10)]
50 | public byte SmptePad0;
51 |
52 | [FieldOffset(11)]
53 | public byte SmptePad1;
54 |
55 | [FieldOffset(4)]
56 | public uint MidiSongPtrPos;
57 |
58 | ///
59 | public bool Equals(MmTime other)
60 | {
61 | return Type == other.Type &&
62 | Ms == other.Ms &&
63 | Sample == other.Sample &&
64 | CB == other.CB &&
65 | Ticks == other.Ticks;
66 | }
67 |
68 | ///
69 | public override bool Equals(object obj) =>
70 | obj is MmTime && Equals((MmTime)obj);
71 |
72 | ///
73 | public override int GetHashCode() =>
74 | throw new NotSupportedException($"{nameof(MmTime)} does not support hashing.");
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/Wave/PlaybackState.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering.Wave
2 | {
3 | ///
4 | /// Enumerates the various wave output playback states.
5 | ///
6 | internal enum PlaybackState
7 | {
8 | ///
9 | /// Stopped.
10 | ///
11 | Stopped,
12 |
13 | ///
14 | /// Playing.
15 | ///
16 | Playing
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/Wave/SupportedWaveFormat.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering.Wave
2 | {
3 | using System;
4 |
5 | ///
6 | /// Supported wave formats for WaveOutCapabilities.
7 | ///
8 | [Flags]
9 | internal enum SupportedWaveFormat
10 | {
11 | ///
12 | /// 11.025 kHz, Mono, 8-bit.
13 | ///
14 | WAVE_FORMAT_1M08 = 0x00000001,
15 |
16 | ///
17 | /// 11.025 kHz, Stereo, 8-bit.
18 | ///
19 | WAVE_FORMAT_1S08 = 0x00000002,
20 |
21 | ///
22 | /// 11.025 kHz, Mono, 16-bit.
23 | ///
24 | WAVE_FORMAT_1M16 = 0x00000004,
25 |
26 | ///
27 | /// 11.025 kHz, Stereo, 16-bit.
28 | ///
29 | WAVE_FORMAT_1S16 = 0x00000008,
30 |
31 | ///
32 | /// 22.05 kHz, Mono, 8-bit.
33 | ///
34 | WAVE_FORMAT_2M08 = 0x00000010,
35 |
36 | ///
37 | /// 22.05 kHz, Stereo, 8-bit.
38 | ///
39 | WAVE_FORMAT_2S08 = 0x00000020,
40 |
41 | ///
42 | /// 22.05 kHz, Mono, 16-bit.
43 | ///
44 | WAVE_FORMAT_2M16 = 0x00000040,
45 |
46 | ///
47 | /// 22.05 kHz, Stereo, 16-bit.
48 | ///
49 | WAVE_FORMAT_2S16 = 0x00000080,
50 |
51 | ///
52 | /// 44.1 kHz, Mono, 8-bit.
53 | ///
54 | WAVE_FORMAT_44M08 = 0x00000100,
55 |
56 | ///
57 | /// 44.1 kHz, Stereo, 8-bit.
58 | ///
59 | WAVE_FORMAT_44S08 = 0x00000200,
60 |
61 | ///
62 | /// 44.1 kHz, Mono, 16-bit.
63 | ///
64 | WAVE_FORMAT_44M16 = 0x00000400,
65 |
66 | ///
67 | /// 44.1 kHz, Stereo, 16-bit.
68 | ///
69 | WAVE_FORMAT_44S16 = 0x00000800,
70 |
71 | ///
72 | /// 48 kHz, Mono, 8-bit.
73 | ///
74 | WAVE_FORMAT_48M08 = 0x00001000,
75 |
76 | ///
77 | /// 48 kHz, Stereo, 8-bit.
78 | ///
79 | WAVE_FORMAT_48S08 = 0x00002000,
80 |
81 | ///
82 | /// 48 kHz, Mono, 16-bit.
83 | ///
84 | WAVE_FORMAT_48M16 = 0x00004000,
85 |
86 | ///
87 | /// 48 kHz, Stereo, 16-bit.
88 | ///
89 | WAVE_FORMAT_48S16 = 0x00008000,
90 |
91 | ///
92 | /// 96 kHz, Mono, 8-bit.
93 | ///
94 | WAVE_FORMAT_96M08 = 0x00010000,
95 |
96 | ///
97 | /// 96 kHz, Stereo, 8-bit.
98 | ///
99 | WAVE_FORMAT_96S08 = 0x00020000,
100 |
101 | ///
102 | /// 96 kHz, Mono, 16-bit.
103 | ///
104 | WAVE_FORMAT_96M16 = 0x00040000,
105 |
106 | ///
107 | /// 96 kHz, Stereo, 16-bit.
108 | ///
109 | WAVE_FORMAT_96S16 = 0x00080000
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/Wave/WaveHeader.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable SA1401 // Fields must be private
2 | namespace Unosquare.FFME.Rendering.Wave
3 | {
4 | using System;
5 | using System.Runtime.InteropServices;
6 |
7 | ///
8 | /// WaveHeader interop structure (WAVEHDR)
9 | /// http://msdn.microsoft.com/en-us/library/dd743837%28VS.85%29.aspx.
10 | ///
11 | [StructLayout(LayoutKind.Sequential)]
12 | internal class WaveHeader
13 | {
14 | /// pointer to locked data buffer (lpData).
15 | public IntPtr DataBuffer;
16 |
17 | /// length of data buffer (dwBufferLength).
18 | public int BufferLength;
19 |
20 | /// used for input only (dwBytesRecorded).
21 | public int BytesRecorded;
22 |
23 | /// for client's use (dwUser).
24 | public IntPtr UserData;
25 |
26 | /// assorted flags (dwFlags).
27 | public WaveHeaderFlags Flags;
28 |
29 | /// loop control counter (dwLoops).
30 | public int Loops;
31 |
32 | /// PWaveHdr, reserved for driver (lpNext).
33 | public IntPtr Next;
34 |
35 | /// reserved for driver.
36 | public IntPtr Reserved;
37 | }
38 | }
39 | #pragma warning restore SA1401 // Fields must be private
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/Wave/WaveHeaderFlags.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering.Wave
2 | {
3 | using System;
4 |
5 | ///
6 | /// Wave Header Flags enumeration.
7 | ///
8 | [Flags]
9 | internal enum WaveHeaderFlags
10 | {
11 | ///
12 | /// This buffer is the first buffer in a loop. This flag is used only with output buffers.
13 | ///
14 | BeginLoop = 0x00000004,
15 |
16 | ///
17 | /// Set by the device driver to indicate that it is finished with the buffer and is returning it to the application.
18 | ///
19 | Done = 0x00000001,
20 |
21 | ///
22 | /// This buffer is the last buffer in a loop. This flag is used only with output buffers.
23 | ///
24 | EndLoop = 0x00000008,
25 |
26 | ///
27 | /// Set by Windows to indicate that the buffer is queued for playback.
28 | ///
29 | InQueue = 0x00000010,
30 |
31 | ///
32 | /// Set by Windows to indicate that the buffer has been prepared with the waveInPrepareHeader or waveOutPrepareHeader function.
33 | ///
34 | Prepared = 0x00000002
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/Wave/WaveOutBuffer.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering.Wave
2 | {
3 | using Primitives;
4 | using System;
5 | using System.Runtime.InteropServices;
6 |
7 | ///
8 | ///
9 | /// A buffer of Wave samples for streaming to a Wave Output device.
10 | ///
11 | internal sealed class WaveOutBuffer : IDisposable
12 | {
13 | private readonly AtomicBoolean m_IsDisposed = new AtomicBoolean(false);
14 | private readonly WaveHeader header;
15 | private readonly byte[] Buffer;
16 | private readonly IWaveProvider WaveStream;
17 |
18 | // Structs
19 | private GCHandle BufferHandle;
20 | private GCHandle HeaderHandle; // we need to pin the header structure
21 | private IntPtr DeviceHandle;
22 |
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | /// WaveOut device to write to.
27 | /// Buffer size in bytes.
28 | /// Stream to provide more data.
29 | public WaveOutBuffer(IntPtr deviceHandle, int bufferSize, IWaveProvider waveStream)
30 | {
31 | BufferSize = bufferSize;
32 | Buffer = new byte[BufferSize];
33 | DeviceHandle = deviceHandle;
34 | WaveStream = waveStream;
35 |
36 | BufferHandle = GCHandle.Alloc(Buffer, GCHandleType.Pinned);
37 | header = new WaveHeader();
38 | HeaderHandle = GCHandle.Alloc(header, GCHandleType.Pinned);
39 |
40 | header.DataBuffer = BufferHandle.AddrOfPinnedObject();
41 | header.BufferLength = bufferSize;
42 | header.Loops = 1;
43 | header.UserData = IntPtr.Zero;
44 |
45 | WaveInterop.AllocateHeader(DeviceHandle, header);
46 | }
47 |
48 | ///
49 | /// Whether the header's in queue flag is set.
50 | ///
51 | public bool IsQueued => header.Flags.HasFlag(WaveHeaderFlags.InQueue);
52 |
53 | ///
54 | /// The buffer size in bytes.
55 | ///
56 | public int BufferSize { get; }
57 |
58 | ///
59 | public void Dispose()
60 | {
61 | if (m_IsDisposed.Value) return;
62 | m_IsDisposed.Value = true;
63 |
64 | // Release the wave header
65 | WaveInterop.ReleaseHeader(DeviceHandle, header);
66 |
67 | // Unpin The header
68 | if (HeaderHandle.IsAllocated)
69 | HeaderHandle.Free();
70 |
71 | // Unpin the buffer
72 | if (BufferHandle.IsAllocated)
73 | BufferHandle.Free();
74 |
75 | // Reset the struct fields
76 | HeaderHandle = default;
77 | BufferHandle = default;
78 | DeviceHandle = IntPtr.Zero;
79 | }
80 |
81 | ///
82 | /// Clears the internal buffer data.
83 | ///
84 | public void Clear()
85 | {
86 | if (Buffer != null)
87 | Array.Clear(Buffer, 0, Buffer.Length);
88 | }
89 |
90 | ///
91 | /// this is called by the Wave callback and should be used to refill the buffer.
92 | /// This calls the method on the stream.
93 | ///
94 | /// True when bytes were written. False if no bytes were written.
95 | internal bool ReadWaveStream()
96 | {
97 | var readCount = WaveStream.Read(Buffer, 0, Buffer.Length);
98 | if (readCount <= 0) return false;
99 |
100 | if (readCount < Buffer.Length)
101 | Array.Clear(Buffer, readCount, Buffer.Length - readCount);
102 |
103 | WaveInterop.WriteAudioData(DeviceHandle, header);
104 | return true;
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Rendering/Wave/WaveOutSupport.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Rendering.Wave
2 | {
3 | using System;
4 |
5 | ///
6 | /// Flags indicating what features this WaveOut device supports.
7 | ///
8 | [Flags]
9 | internal enum WaveOutSupport
10 | {
11 | /// supports pitch control.
12 | Pitch = 0x0001,
13 |
14 | /// supports playback rate control.
15 | PlaybackRate = 0x0002,
16 |
17 | /// supports volume control (WAVECAPS_VOLUME).
18 | Volume = 0x0004,
19 |
20 | /// supports separate left-right volume control.
21 | LRVolume = 0x0008,
22 |
23 | /// Sync.
24 | Sync = 0x0010,
25 |
26 | /// Sample-Accurate.
27 | SampleAccurate = 0x0020
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Unosquare.FFME.Windows/Resources/ffme-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Unosquare.FFME.Windows/Resources/ffme-preview.png
--------------------------------------------------------------------------------
/Unosquare.FFME/ClosedCaptions/CaptionsChannel.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.ClosedCaptions
2 | {
3 | ///
4 | /// Enumerates the 4 different CC channels.
5 | ///
6 | public enum CaptionsChannel
7 | {
8 | ///
9 | /// No channel specified -- use previous.
10 | ///
11 | CCP = 0,
12 |
13 | ///
14 | /// Field 1, Channel 1.
15 | ///
16 | CC1 = 1,
17 |
18 | ///
19 | /// Field 1, Channel 2.
20 | ///
21 | CC2 = 2,
22 |
23 | ///
24 | /// Field 2, Channel 1.
25 | ///
26 | CC3 = 3,
27 |
28 | ///
29 | /// Field 2, Channel 2.
30 | ///
31 | CC4 = 4
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Unosquare.FFME/ClosedCaptions/CaptionsColor.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.ClosedCaptions
2 | {
3 | ///
4 | /// Enumerates the different Closed-Captioning Colors.
5 | ///
6 | public enum CaptionsColor
7 | {
8 | ///
9 | /// No color.
10 | ///
11 | None = 0x00,
12 |
13 | ///
14 | /// The white color.
15 | ///
16 | White = 0x20,
17 |
18 | ///
19 | /// The white transparent color.
20 | ///
21 | WhiteTransparent = 0x21,
22 |
23 | ///
24 | /// The green color.
25 | ///
26 | Green = 0x22,
27 |
28 | ///
29 | /// The green transparent color.
30 | ///
31 | GreenTransparent = 0x23,
32 |
33 | ///
34 | /// The blue color.
35 | ///
36 | Blue = 0x24,
37 |
38 | ///
39 | /// The blue transparent color.
40 | ///
41 | BlueTransparent = 0x25,
42 |
43 | ///
44 | /// The cyan color.
45 | ///
46 | Cyan = 0x26,
47 |
48 | ///
49 | /// The cyan transparent color.
50 | ///
51 | CyanTransparent = 0x27,
52 |
53 | ///
54 | /// The red color.
55 | ///
56 | Red = 0x28,
57 |
58 | ///
59 | /// The red transparent color.
60 | ///
61 | RedTransparent = 0x29,
62 |
63 | ///
64 | /// The yellow color.
65 | ///
66 | Yellow = 0x2A,
67 |
68 | ///
69 | /// The yellow transparent color.
70 | ///
71 | YellowTransparent = 0x2B,
72 |
73 | ///
74 | /// The magenta color.
75 | ///
76 | Magenta = 0x2C,
77 |
78 | ///
79 | /// The magenta transparent color.
80 | ///
81 | MagentaTransparent = 0x2D,
82 |
83 | ///
84 | /// The white italics color.
85 | ///
86 | WhiteItalics = 0x2E,
87 |
88 | ///
89 | /// The white italics transparent color.
90 | ///
91 | WhiteItalicsTransparent = 0x2F,
92 |
93 | ///
94 | /// The background transparent color.
95 | ///
96 | BackgroundTransparent = 0x2D00,
97 |
98 | ///
99 | /// The foreground black color.
100 | ///
101 | ForegroundBlack = 0x2E00,
102 |
103 | ///
104 | /// The foreground black underline color.
105 | ///
106 | ForegroundBlackUnderline = 0x2F00
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Unosquare.FFME/ClosedCaptions/CaptionsCommand.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.ClosedCaptions
2 | {
3 | ///
4 | /// Enumerates the Closed-Captioning misc commands.
5 | ///
6 | public enum CaptionsCommand
7 | {
8 | ///
9 | /// No command.
10 | ///
11 | None = 0,
12 |
13 | ///
14 | /// The resume command.
15 | ///
16 | Resume = 0x20,
17 |
18 | ///
19 | /// The backspace command.
20 | ///
21 | Backspace = 0x21,
22 |
23 | ///
24 | /// The alarm off command.
25 | ///
26 | AlarmOff = 0x22,
27 |
28 | ///
29 | /// The alarm on command.
30 | ///
31 | AlarmOn = 0x23,
32 |
33 | ///
34 | /// The clear line command
35 | /// Delete to end of Row (DER).
36 | ///
37 | ClearLine = 0x24,
38 |
39 | ///
40 | /// The roll up 2 command.
41 | ///
42 | RollUp2 = 0x25,
43 |
44 | ///
45 | /// The roll up 3 command.
46 | ///
47 | RollUp3 = 0x26,
48 |
49 | ///
50 | /// The roll up 4 command.
51 | ///
52 | RollUp4 = 0x27,
53 |
54 | ///
55 | /// The start caption command.
56 | ///
57 | StartCaption = 0x29,
58 |
59 | ///
60 | /// The start non caption command.
61 | ///
62 | StartNonCaption = 0x2A,
63 |
64 | ///
65 | /// The resume non caption command.
66 | ///
67 | ResumeNonCaption = 0x2B,
68 |
69 | ///
70 | /// The clear screen command.
71 | ///
72 | ClearScreen = 0x2C,
73 |
74 | ///
75 | /// The new line command.
76 | ///
77 | NewLine = 0x2D,
78 |
79 | ///
80 | /// The clear buffer command.
81 | ///
82 | ClearBuffer = 0x2E,
83 |
84 | ///
85 | /// The end caption command.
86 | ///
87 | EndCaption = 0x2F
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Unosquare.FFME/ClosedCaptions/CaptionsPacketType.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.ClosedCaptions
2 | {
3 | ///
4 | /// Defines Closed-Captioning Packet types.
5 | ///
6 | public enum CaptionsPacketType
7 | {
8 | ///
9 | /// The unrecognized packet type.
10 | ///
11 | Unrecognized,
12 |
13 | ///
14 | /// The null pad packet type.
15 | ///
16 | NullPad,
17 |
18 | ///
19 | /// The XDS class packet type.
20 | ///
21 | XdsClass,
22 |
23 | ///
24 | /// The misc command packet type.
25 | ///
26 | Command,
27 |
28 | ///
29 | /// The text packet type.
30 | ///
31 | Text,
32 |
33 | ///
34 | /// The mid row packet type.
35 | ///
36 | MidRow,
37 |
38 | ///
39 | /// The preamble packet type.
40 | ///
41 | Preamble,
42 |
43 | ///
44 | /// The color packet type.
45 | ///
46 | Color,
47 |
48 | ///
49 | /// The charset packet type.
50 | ///
51 | PrivateCharset,
52 |
53 | ///
54 | /// The tabs packet type
55 | /// Section B.4 Tab Offsets.
56 | ///
57 | Tabs
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Unosquare.FFME/ClosedCaptions/CaptionsXdsClass.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.ClosedCaptions
2 | {
3 | ///
4 | /// Defines Closed-Captioning XDS Packet Classes.
5 | ///
6 | public enum CaptionsXdsClass
7 | {
8 | ///
9 | /// The none XDS Class.
10 | ///
11 | None = 0x00,
12 |
13 | ///
14 | /// The current start XDS Class.
15 | ///
16 | CurrentStart = 0x01,
17 |
18 | ///
19 | /// The current continue XDS Class.
20 | ///
21 | CurrentContinue = 0x02,
22 |
23 | ///
24 | /// The future start XDS Class.
25 | ///
26 | FutureStart = 0x03,
27 |
28 | ///
29 | /// The future continue XDS Class.
30 | ///
31 | FutureContinue = 0x04,
32 |
33 | ///
34 | /// The channel start XDS Class.
35 | ///
36 | ChannelStart = 0x05,
37 |
38 | ///
39 | /// The channel continue XDS Class.
40 | ///
41 | ChannelContinue = 0x06,
42 |
43 | ///
44 | /// The misc start XDS Class.
45 | ///
46 | MiscStart = 0x07,
47 |
48 | ///
49 | /// The misc continue XDS Class.
50 | ///
51 | MiscContinue = 0x08,
52 |
53 | ///
54 | /// The public service start XDS Class.
55 | ///
56 | PublicServiceStart = 0x09,
57 |
58 | ///
59 | /// The public service continue XDS Class.
60 | ///
61 | PublicServiceContinue = 0x0A,
62 |
63 | ///
64 | /// The reserved start XDS Class.
65 | ///
66 | ReservedStart = 0x0B,
67 |
68 | ///
69 | /// The reserved continue XDS Class.
70 | ///
71 | ReservedContinue = 0x0C,
72 |
73 | ///
74 | /// The private start XDS Class.
75 | ///
76 | PrivateStart = 0x0D,
77 |
78 | ///
79 | /// The private continue XDS Class.
80 | ///
81 | PrivateContinue = 0x0E,
82 |
83 | ///
84 | /// The end all XDS Class.
85 | ///
86 | EndAll = 0x0F
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Commands/CommandManager.Enums.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Commands
2 | {
3 | internal partial class CommandManager
4 | {
5 | ///
6 | /// Enumerates the different seek modes for the seek command.
7 | ///
8 | public enum SeekMode
9 | {
10 | /// Normal seek mode.
11 | Normal,
12 |
13 | /// Stop seek mode.
14 | Stop,
15 |
16 | /// Frame step forward.
17 | StepForward,
18 |
19 | /// Frame step backward.
20 | StepBackward
21 | }
22 |
23 | ///
24 | /// Enumerates the different direct command types.
25 | ///
26 | private enum DirectCommandType
27 | {
28 | ///
29 | /// No command Type.
30 | ///
31 | None,
32 |
33 | ///
34 | /// The open command.
35 | ///
36 | Open,
37 |
38 | ///
39 | /// The close command.
40 | ///
41 | Close,
42 |
43 | ///
44 | /// The change command.
45 | ///
46 | Change
47 | }
48 |
49 | ///
50 | /// The priority command types.
51 | ///
52 | private enum PriorityCommandType
53 | {
54 | ///
55 | /// The none command.
56 | ///
57 | None,
58 |
59 | ///
60 | /// The play command.
61 | ///
62 | Play,
63 |
64 | ///
65 | /// The pause command.
66 | ///
67 | Pause,
68 |
69 | ///
70 | /// The stop command.
71 | ///
72 | Stop
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Common/ContainerConfiguration.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | ///
7 | /// Represents a set of options that are used to initialize a media container before opening the stream.
8 | /// This includes both, demuxer and decoder options.
9 | ///
10 | public sealed class ContainerConfiguration
11 | {
12 | ///
13 | /// The scan all PMTS private option name.
14 | ///
15 | internal const string ScanAllPmts = "scan_all_pmts";
16 |
17 | ///
18 | /// Initializes a new instance of the class.
19 | ///
20 | internal ContainerConfiguration()
21 | {
22 | // placeholder
23 | }
24 |
25 | ///
26 | /// Gets or sets the forced input format. If let null or empty,
27 | /// the input format will be selected automatically.
28 | ///
29 | public string ForcedInputFormat { get; set; }
30 |
31 | ///
32 | /// Gets the protocol prefix.
33 | /// Typically async for local files and empty for other types.
34 | ///
35 | public string ProtocolPrefix { get; set; }
36 |
37 | ///
38 | /// Gets or sets the amount of time to wait for a an open or read
39 | /// operation to complete before it times out. It is 30 seconds by default.
40 | ///
41 | public TimeSpan ReadTimeout { get; set; } = TimeSpan.FromSeconds(30);
42 |
43 | ///
44 | /// Contains global options for the demuxer. For additional info
45 | /// please see: https://ffmpeg.org/ffmpeg-formats.html#Format-Options.
46 | ///
47 | public DemuxerGlobalOptions GlobalOptions { get; } = new();
48 |
49 | ///
50 | /// Contains private demuxer options. For additional info
51 | /// please see: https://ffmpeg.org/ffmpeg-all.html#Demuxers.
52 | ///
53 | public Dictionary PrivateOptions { get; } =
54 | new(512, StringComparer.InvariantCultureIgnoreCase);
55 |
56 | ///
57 | /// Gets or sets a method to be called when reading the input stream has timed out.
58 | ///
59 | internal Action ReadTimeoutCallback { get; set; }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Common/HardwareDeviceInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using FFmpeg.AutoGen;
4 |
5 | ///
6 | /// Represents a hardware configuration pair of device and pixel format.
7 | ///
8 | public sealed unsafe class HardwareDeviceInfo
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The source configuration.
14 | internal HardwareDeviceInfo(AVCodecHWConfig* config)
15 | {
16 | DeviceType = config->device_type;
17 | PixelFormat = config->pix_fmt;
18 | DeviceTypeName = ffmpeg.av_hwdevice_get_type_name(DeviceType);
19 | PixelFormatName = ffmpeg.av_get_pix_fmt_name(PixelFormat);
20 | }
21 |
22 | ///
23 | /// Gets the type of hardware device.
24 | ///
25 | public AVHWDeviceType DeviceType { get; }
26 |
27 | ///
28 | /// Gets the name of the device type.
29 | ///
30 | public string DeviceTypeName { get; }
31 |
32 | ///
33 | /// Gets the hardware output pixel format.
34 | ///
35 | public AVPixelFormat PixelFormat { get; }
36 |
37 | ///
38 | /// Gets the name of the pixel format.
39 | ///
40 | public string PixelFormatName { get; }
41 |
42 | ///
43 | /// Returns a that represents this instance.
44 | ///
45 | ///
46 | /// A that represents this instance.
47 | ///
48 | public override string ToString()
49 | {
50 | return $"Device {DeviceTypeName}: {PixelFormatName}";
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Common/IMediaInputStream.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using System;
4 | using FFmpeg.AutoGen;
5 |
6 | ///
7 | /// A callback that gets called before the media container's input context gets initialized for the .
8 | ///
9 | /// The container configuration.
10 | /// The media source URL.
11 | public unsafe delegate void InputStreamInitializing(ContainerConfiguration containerConfig, string mediaSource);
12 |
13 | ///
14 | /// A callback that gets called when the media container's input format context has been initialized for the .
15 | ///
16 | /// The unmanaged input format pointer.
17 | /// The unmanaged format context.
18 | /// The detected media info.
19 | public unsafe delegate void InputStreamInitialized(AVInputFormat* inputFormat, AVFormatContext* formatContext, MediaInfo mediaInfo);
20 |
21 | ///
22 | /// Defines the properties and methods necessary for implementing a
23 | /// custom media input stream.
24 | ///
25 | public unsafe interface IMediaInputStream : IDisposable
26 | {
27 | ///
28 | /// Gets the stream URI. This is just a pseudo URI to identify the stream.
29 | ///
30 | Uri StreamUri { get; }
31 |
32 | ///
33 | /// Gets a value indicating whether this stream is seekable.
34 | ///
35 | bool CanSeek { get; }
36 |
37 | ///
38 | /// Gets the length in bytes of the read buffer that will be allocated.
39 | /// Something like 4096 is recommended.
40 | ///
41 | int ReadBufferLength { get; }
42 |
43 | ///
44 | /// Gets the callback that gets called before the media container's input context gets initialized for the .
45 | ///
46 | InputStreamInitializing OnInitializing { get; }
47 |
48 | ///
49 | /// Gets the callback that gets called when the media container's input format context has been initialized for the .
50 | ///
51 | InputStreamInitialized OnInitialized { get; }
52 |
53 | ///
54 | /// Reads from the underlying stream and writes up to bytes
55 | /// to the . Returns the number of bytes that were written.
56 | ///
57 | /// An FFmpeg provided opaque reference.
58 | /// The target buffer.
59 | /// Length of the target buffer.
60 | /// The number of bytes that have been read.
61 | int Read(void* opaque, byte* targetBuffer, int targetBufferLength);
62 |
63 | ///
64 | /// Seeks to the specified offset. The offset can be in byte position or in time units.
65 | /// This is specified by the whence parameter which is one of the AVSEEK prefixed constants.
66 | ///
67 | /// The opaque.
68 | /// The offset.
69 | /// The whence.
70 | /// The position in bytes or time scale that has been read.
71 | long Seek(void* opaque, long offset, int whence);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Common/MediaContainerException.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using System;
4 |
5 | ///
6 | ///
7 | /// A Media Container Exception.
8 | ///
9 | [Serializable]
10 | public class MediaContainerException : Exception
11 | {
12 | // TODO: Add error code property and enumerate error codes.
13 |
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | /// The message that describes the error.
18 | public MediaContainerException(string message)
19 | : base(message)
20 | {
21 | // placeholder
22 | }
23 |
24 | ///
25 | /// Initializes a new instance of the class.
26 | ///
27 | public MediaContainerException()
28 | : base("Unidentified media container exception")
29 | {
30 | }
31 |
32 | ///
33 | /// Initializes a new instance of the class.
34 | ///
35 | /// The message.
36 | /// The inner exception.
37 | public MediaContainerException(string message, Exception innerException)
38 | : base(message, innerException)
39 | {
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Common/MediaLogMessageType.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | ///
4 | /// Defines the different log message types received by the log handler.
5 | ///
6 | public enum MediaLogMessageType
7 | {
8 | ///
9 | /// The none message type.
10 | ///
11 | None = 0,
12 |
13 | ///
14 | /// The information message type.
15 | ///
16 | Info = 1,
17 |
18 | ///
19 | /// The debug message type.
20 | ///
21 | Debug = 2,
22 |
23 | ///
24 | /// The trace message type.
25 | ///
26 | Trace = 4,
27 |
28 | ///
29 | /// The error message type.
30 | ///
31 | Error = 8,
32 |
33 | ///
34 | /// The warning message type.
35 | ///
36 | Warning = 16
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Common/MediaPlaybackState.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | ///
4 | /// Media States compatible with MediaState enumeration.
5 | ///
6 | public enum MediaPlaybackState
7 | {
8 | ///
9 | /// The manual status.
10 | ///
11 | Manual = 0,
12 |
13 | ///
14 | /// The play status.
15 | ///
16 | Play = 1,
17 |
18 | ///
19 | /// The close status.
20 | ///
21 | Close = 2,
22 |
23 | ///
24 | /// The pause status.
25 | ///
26 | Pause = 3,
27 |
28 | ///
29 | /// The stop status.
30 | ///
31 | Stop = 4
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Common/MediaType.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | using FFmpeg.AutoGen;
4 |
5 | ///
6 | /// Enumerates the different Media Types compatible with AVMEDIATYPE_* constants
7 | /// defined by FFmpeg.
8 | ///
9 | public enum MediaType
10 | {
11 | ///
12 | /// Represents an un-existing media type (-1).
13 | ///
14 | None = AVMediaType.AVMEDIA_TYPE_UNKNOWN,
15 |
16 | ///
17 | /// The video media type (0).
18 | ///
19 | Video = AVMediaType.AVMEDIA_TYPE_VIDEO,
20 |
21 | ///
22 | /// The audio media type (1).
23 | ///
24 | Audio = AVMediaType.AVMEDIA_TYPE_AUDIO,
25 |
26 | ///
27 | /// The subtitle media type (3).
28 | ///
29 | Subtitle = AVMediaType.AVMEDIA_TYPE_SUBTITLE
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Common/VideoResolutionDivider.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Common
2 | {
3 | ///
4 | /// Enumerates the different low resolution divider indices.
5 | ///
6 | public enum VideoResolutionDivider
7 | {
8 | ///
9 | /// Represents no resolution reduction.
10 | ///
11 | Full = 0,
12 |
13 | ///
14 | /// Represents 1/2 resolution.
15 | ///
16 | Half = 1,
17 |
18 | ///
19 | /// Represents 1/4 resolution.
20 | ///
21 | Quarter = 2,
22 |
23 | ///
24 | /// Represents 1/8 resolution.
25 | ///
26 | Eighth = 3
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Container/AudioBlock.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Container
2 | {
3 | using Common;
4 |
5 | ///
6 | /// A scaled, pre-allocated audio frame container.
7 | /// The buffer is in 16-bit signed, interleaved sample data.
8 | ///
9 | internal sealed class AudioBlock : MediaBlock
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | internal AudioBlock()
15 | : base(MediaType.Audio)
16 | {
17 | // placeholder
18 | }
19 |
20 | #region Properties
21 |
22 | ///
23 | /// Gets the sample rate.
24 | ///
25 | public int SampleRate { get; internal set; }
26 |
27 | ///
28 | /// Gets the channel count.
29 | ///
30 | public int ChannelCount { get; internal set; }
31 |
32 | ///
33 | /// Gets the available samples per channel.
34 | ///
35 | public int SamplesPerChannel { get; internal set; }
36 |
37 | ///
38 | /// Gets the length of the samples buffer. This might differ from the
39 | /// property after scaling but must always be less than or equal to it.
40 | ///
41 | ///
42 | /// The length of the samples buffer.
43 | ///
44 | public int SamplesBufferLength { get; internal set; }
45 |
46 | #endregion
47 |
48 | #region Methods
49 |
50 | ///
51 | protected override void Deallocate()
52 | {
53 | base.Deallocate();
54 | SampleRate = default;
55 | ChannelCount = default;
56 | SamplesPerChannel = default;
57 | SamplesBufferLength = 0;
58 | }
59 |
60 | #endregion
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Container/AudioFrame.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Container;
2 |
3 | using Common;
4 | using FFmpeg.AutoGen;
5 | using System;
6 |
7 | ///
8 | ///
9 | /// Represents a wrapper from an unmanaged FFmpeg audio frame.
10 | ///
11 | ///
12 | ///
13 | internal sealed unsafe class AudioFrame : MediaFrame
14 | {
15 | #region Private Members
16 |
17 | private readonly object DisposeLock = new();
18 | private bool IsDisposed;
19 |
20 | #endregion
21 |
22 | #region Constructor
23 |
24 | ///
25 | /// Initializes a new instance of the class.
26 | ///
27 | /// The frame.
28 | /// The component.
29 | internal AudioFrame(AVFrame* frame, MediaComponent component)
30 | : base(frame, component, MediaType.Audio)
31 | {
32 | // Compute the start time.
33 | frame->pts = frame->best_effort_timestamp;
34 | HasValidStartTime = frame->pts != ffmpeg.AV_NOPTS_VALUE;
35 | StartTime = frame->pts == ffmpeg.AV_NOPTS_VALUE ?
36 | TimeSpan.FromTicks(0) :
37 | TimeSpan.FromTicks(frame->pts.ToTimeSpan(StreamTimeBase).Ticks);
38 |
39 | // Compute the audio frame duration
40 | Duration = frame->duration > 0 ?
41 | frame->duration.ToTimeSpan(StreamTimeBase) :
42 | TimeSpan.FromTicks(Convert.ToInt64(TimeSpan.TicksPerMillisecond * 1000d * frame->nb_samples / frame->sample_rate));
43 |
44 | // Compute the audio frame end time
45 | EndTime = TimeSpan.FromTicks(StartTime.Ticks + Duration.Ticks);
46 | }
47 |
48 | #endregion
49 |
50 | #region Properties
51 |
52 | ///
53 | /// Gets the pointer to the unmanaged frame.
54 | ///
55 | internal AVFrame* Pointer => (AVFrame*)InternalPointer;
56 |
57 | #endregion
58 |
59 | #region IDisposable Support
60 |
61 | ///
62 | public override void Dispose()
63 | {
64 | lock (DisposeLock)
65 | {
66 | if (IsDisposed)
67 | return;
68 |
69 | if (InternalPointer != IntPtr.Zero)
70 | ReleaseAVFrame(Pointer);
71 |
72 | InternalPointer = IntPtr.Zero;
73 | IsDisposed = true;
74 | }
75 | }
76 |
77 | #endregion
78 | }
79 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Container/DataComponentSet.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Container
2 | {
3 | using Common;
4 |
5 | ///
6 | /// Represents the component set for non-media packets.
7 | /// Data packets sent to this component is not queued and decoding of
8 | /// data frames is up to the user.
9 | ///
10 | internal sealed class DataComponentSet
11 | {
12 | private readonly object SyncLock = new object();
13 | public delegate void OnDataPacketReceivedDelegate(MediaPacket dataPacket, StreamInfo stream);
14 |
15 | ///
16 | /// Gets or sets a method that gets called when a data packet is received from the input stream.
17 | ///
18 | public OnDataPacketReceivedDelegate OnDataPacketReceived { get; set; }
19 |
20 | ///
21 | /// Tries to handle processing of a data packet. If the packet is in fact a data packet, it is
22 | /// automatically disposed after executing the appropriate callbacks and returns true.
23 | /// If the packet is not a data packet, this method returns false and does not dispose of the
24 | /// packet so that the media component set tries to handle it.
25 | ///
26 | /// The container.
27 | /// The packet.
28 | /// Returns false if the packet is not a data packet.
29 | public bool TryHandleDataPacket(MediaContainer container, MediaPacket packet)
30 | {
31 | lock (SyncLock)
32 | {
33 | // ensure packet and container are not null
34 | if (packet == null || container == null)
35 | return false;
36 |
37 | // Get the associated stream
38 | var stream = container.MediaInfo.Streams.ContainsKey(packet.StreamIndex)
39 | ? container.MediaInfo.Streams[packet.StreamIndex]
40 | : null;
41 |
42 | // Ensure the stream is in fact a data stream
43 | if (stream == null || !stream.IsNonMedia)
44 | return false;
45 |
46 | try
47 | {
48 | // Execute the packet handling callback
49 | OnDataPacketReceived?.Invoke(packet, stream);
50 | }
51 | catch
52 | {
53 | // Ignore
54 | }
55 | finally
56 | {
57 | // always dispose of the packet
58 | packet.Dispose();
59 | }
60 |
61 | // Signal that the packet has been handled as a data packet
62 | return true;
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Container/PacketBufferState.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Container
2 | {
3 | using System;
4 |
5 | ///
6 | /// A value type that representing the packet buffer state.
7 | ///
8 | internal struct PacketBufferState : IEquatable
9 | {
10 | ///
11 | /// The length in bytes of the packet buffer.
12 | ///
13 | public long Length;
14 |
15 | ///
16 | /// The number of packets in the packet buffer.
17 | ///
18 | public int Count;
19 |
20 | ///
21 | /// The minimum number of packets so is set to true.
22 | ///
23 | public int CountThreshold;
24 |
25 | ///
26 | /// Whether the packet buffer has enough packets.
27 | ///
28 | public bool HasEnoughPackets;
29 |
30 | ///
31 | /// The duration of the packets. An invalid value will return .
32 | ///
33 | public TimeSpan Duration;
34 |
35 | ///
36 | public bool Equals(PacketBufferState other) =>
37 | Length == other.Length &&
38 | Count == other.Count &&
39 | CountThreshold == other.CountThreshold &&
40 | HasEnoughPackets == other.HasEnoughPackets;
41 |
42 | ///
43 | public override bool Equals(object obj) =>
44 | obj is PacketBufferState state && Equals(state);
45 |
46 | ///
47 | public override int GetHashCode() =>
48 | throw new NotSupportedException($"{nameof(PacketBufferState)} does not support hashing.");
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Container/PacketQueueOp.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Container
2 | {
3 | ///
4 | /// Defines the multiple packet queue operations.
5 | ///
6 | internal enum PacketQueueOp
7 | {
8 | ///
9 | /// The packet queue was cleared.
10 | ///
11 | Clear = 0,
12 |
13 | ///
14 | /// The packet queue queued a packet.
15 | ///
16 | Queued = 1,
17 |
18 | ///
19 | /// The packet queue dequeued a packet.
20 | ///
21 | Dequeued = 2
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Container/SubtitleBlock.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Container
2 | {
3 | using Common;
4 | using FFmpeg.AutoGen;
5 | using System.Collections.Generic;
6 |
7 | ///
8 | /// A subtitle frame container. Simply contains text lines.
9 | ///
10 | internal sealed class SubtitleBlock : MediaBlock
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | internal SubtitleBlock()
16 | : base(MediaType.Subtitle)
17 | {
18 | // placeholder
19 | }
20 |
21 | #region Properties
22 |
23 | ///
24 | /// Gets the lines of text for this subtitle frame with all formatting stripped out.
25 | ///
26 | public IList Text { get; } = new List(16);
27 |
28 | ///
29 | /// Gets the original text in SRT or ASS format.
30 | ///
31 | public IList OriginalText { get; } = new List(16);
32 |
33 | ///
34 | /// Gets the type of the original text.
35 | /// Returns None when it's a bitmap or when it's None.
36 | ///
37 | public AVSubtitleType OriginalTextType { get; internal set; }
38 |
39 | #endregion
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Container/SubtitleFrame.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Container
2 | {
3 | using FFmpeg.AutoGen;
4 | using System;
5 | using System.Collections.Generic;
6 |
7 | ///
8 | /// Represents a wrapper for an unmanaged Subtitle frame.
9 | /// TODO: Only text (ASS and SRT) subtitles are supported currently.
10 | /// There is no support to bitmap subtitles.
11 | ///
12 | ///
13 | internal sealed unsafe class SubtitleFrame : MediaFrame
14 | {
15 | #region Private Members
16 |
17 | private readonly object DisposeLock = new object();
18 | private bool IsDisposed;
19 |
20 | #endregion
21 |
22 | #region Constructors
23 |
24 | ///
25 | /// Initializes a new instance of the class.
26 | ///
27 | /// The frame.
28 | /// The component.
29 | internal SubtitleFrame(AVSubtitle* frame, MediaComponent component)
30 | : base(frame, component)
31 | {
32 | // Extract timing information (pts for Subtitles is always in AV_TIME_BASE units)
33 | HasValidStartTime = frame->pts != ffmpeg.AV_NOPTS_VALUE;
34 | var timeOffset = frame->pts.ToTimeSpan(ffmpeg.AV_TIME_BASE);
35 |
36 | // start_display_time and end_display_time are relative to timeOffset
37 | StartTime = TimeSpan.FromMilliseconds(timeOffset.TotalMilliseconds + frame->start_display_time);
38 | EndTime = TimeSpan.FromMilliseconds(timeOffset.TotalMilliseconds + frame->end_display_time);
39 | Duration = TimeSpan.FromMilliseconds(frame->end_display_time - frame->start_display_time);
40 |
41 | // Extract text strings
42 | TextType = AVSubtitleType.SUBTITLE_NONE;
43 |
44 | for (var i = 0; i < frame->num_rects; i++)
45 | {
46 | var rect = frame->rects[i];
47 |
48 | if (rect->type == AVSubtitleType.SUBTITLE_TEXT)
49 | {
50 | if (rect->text == null) continue;
51 | Text.Add(Utilities.PtrToStringUTF8(rect->text));
52 | TextType = AVSubtitleType.SUBTITLE_TEXT;
53 | break;
54 | }
55 |
56 | if (rect->type == AVSubtitleType.SUBTITLE_ASS)
57 | {
58 | if (rect->ass == null) continue;
59 | Text.Add(Utilities.PtrToStringUTF8(rect->ass));
60 | TextType = AVSubtitleType.SUBTITLE_ASS;
61 | break;
62 | }
63 |
64 | TextType = rect->type;
65 | }
66 | }
67 |
68 | #endregion
69 |
70 | #region Properties
71 |
72 | ///
73 | /// Gets lines of text that the subtitle frame contains.
74 | ///
75 | public IList Text { get; } = new List(16);
76 |
77 | ///
78 | /// Gets the type of the text.
79 | ///
80 | ///
81 | /// The type of the text.
82 | ///
83 | public AVSubtitleType TextType { get; }
84 |
85 | ///
86 | /// Gets the pointer to the unmanaged subtitle struct.
87 | ///
88 | internal AVSubtitle* Pointer => (AVSubtitle*)InternalPointer;
89 |
90 | #endregion
91 |
92 | #region Static Methods
93 |
94 | ///
95 | public override void Dispose()
96 | {
97 | lock (DisposeLock)
98 | {
99 | if (IsDisposed)
100 | return;
101 |
102 | if (InternalPointer != IntPtr.Zero)
103 | ReleaseAVSubtitle(Pointer);
104 |
105 | InternalPointer = IntPtr.Zero;
106 | IsDisposed = true;
107 | }
108 | }
109 |
110 | #endregion
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Diagnostics/Aspects.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Diagnostics
2 | {
3 | ///
4 | /// Provides constants for logging aspect identifiers.
5 | ///
6 | internal static class Aspects
7 | {
8 | public static string None => "Log.Text";
9 |
10 | public static string FFmpegLog => "FFmpeg.Log";
11 |
12 | public static string EngineCommand => "Engine.Commands";
13 |
14 | public static string ReadingWorker => "Engine.Reading";
15 |
16 | public static string DecodingWorker => "Engine.Decoding";
17 |
18 | public static string RenderingWorker => "Engine.Rendering";
19 |
20 | public static string Connector => "Engine.Connector";
21 |
22 | public static string Container => "Container";
23 |
24 | public static string Timing => "Timing";
25 |
26 | public static string Component => "Container.Component";
27 |
28 | public static string ReferenceCounter => "ReferenceCounter";
29 |
30 | public static string VideoRenderer => "Element.Video";
31 |
32 | public static string AudioRenderer => "Element.Audio";
33 |
34 | public static string SubtitleRenderer => "Element.Subtitle";
35 |
36 | public static string Events => "Element.Events";
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Diagnostics/BenchmarkResult.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Diagnostics
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | ///
8 | /// Contains benchmark summary data.
9 | ///
10 | internal sealed class BenchmarkResult
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// The identifier.
16 | /// The measures.
17 | internal BenchmarkResult(string identifier, List measures)
18 | {
19 | Identifier = identifier;
20 | Count = measures.Count;
21 | Average = measures.Average(t => t.TotalMilliseconds);
22 | Min = measures.Min(t => t.TotalMilliseconds);
23 | Max = measures.Max(t => t.TotalMilliseconds);
24 | }
25 |
26 | ///
27 | /// Gets the benchmark identifier.
28 | ///
29 | public string Identifier { get; }
30 |
31 | ///
32 | /// Gets the measure count.
33 | ///
34 | public int Count { get; }
35 |
36 | ///
37 | /// Gets the average time in milliseconds.
38 | ///
39 | public double Average { get; }
40 |
41 | ///
42 | /// Gets the minimum time in milliseconds.
43 | ///
44 | public double Min { get; }
45 |
46 | ///
47 | /// Gets the maximum time in milliseconds.
48 | ///
49 | public double Max { get; }
50 |
51 | ///
52 | public override string ToString()
53 | {
54 | return $"BID: {Identifier,-30} | CNT: {Count,6} | " +
55 | $"AVG: {Average,8:0.000} ms. | " +
56 | $"MAX: {Max,8:0.000} ms. | " +
57 | $"MIN: {Min,8:0.000} ms. | ";
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Diagnostics/ILoggingHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Diagnostics
2 | {
3 | ///
4 | /// Defines interface methods for logging message handlers.
5 | ///
6 | internal interface ILoggingHandler
7 | {
8 | ///
9 | /// Handles a log message.
10 | ///
11 | /// The message object containing the data.
12 | void HandleLogMessage(LoggingMessage message);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Diagnostics/ILoggingSource.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Diagnostics
2 | {
3 | ///
4 | /// Defines interface members for a class that
5 | /// defines a logging message handler .
6 | ///
7 | internal interface ILoggingSource
8 | {
9 | ///
10 | /// Gets the logging handler.
11 | ///
12 | ILoggingHandler LoggingHandler { get; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Diagnostics/LoggingMessage.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Diagnostics
2 | {
3 | using Common;
4 | using System;
5 |
6 | ///
7 | /// Represents the contents of a logging message that was sent to the log manager.
8 | ///
9 | internal sealed class LoggingMessage
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | /// The object that shall handle the message when it is output by the queue.
15 | /// Type of the message.
16 | /// The message text.
17 | /// Name of the code aspect the message came from.
18 | internal LoggingMessage(ILoggingHandler loggingHandler, MediaLogMessageType messageType, string messageText, string aspectName)
19 | {
20 | MessageType = messageType;
21 | Message = messageText;
22 | TimestampUtc = DateTime.UtcNow;
23 | Handler = loggingHandler;
24 | AspectName = aspectName;
25 | }
26 |
27 | ///
28 | /// Gets the object that shall handle the message when it is output by the queue.
29 | ///
30 | public ILoggingHandler Handler { get; }
31 |
32 | ///
33 | /// Gets the timestamp.
34 | ///
35 | public DateTime TimestampUtc { get; }
36 |
37 | ///
38 | /// Gets the type of the message.
39 | ///
40 | public MediaLogMessageType MessageType { get; }
41 |
42 | ///
43 | /// Gets the contents of the message.
44 | ///
45 | public string Message { get; }
46 |
47 | ///
48 | /// Gets the aspect or feature that sent the logged message.
49 | /// May or may not be available.
50 | ///
51 | public string AspectName { get; }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Engine/IMediaWorker.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Engine
2 | {
3 | using Primitives;
4 |
5 | ///
6 | /// Represents a worker API owned by a .
7 | ///
8 | ///
9 | internal interface IMediaWorker : IWorker
10 | {
11 | ///
12 | /// Gets the media core this worker belongs to.
13 | ///
14 | MediaEngine MediaCore { get; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Engine/MediaWorkerType.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Engine
2 | {
3 | ///
4 | /// Defines the different worker types.
5 | ///
6 | internal enum MediaWorkerType
7 | {
8 | ///
9 | /// The packet reading worker.
10 | ///
11 | Read,
12 |
13 | ///
14 | /// The frame decoding worker.
15 | ///
16 | Decode,
17 |
18 | ///
19 | /// The block rendering worker.
20 | ///
21 | Render
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Engine/PacketReadingWorker.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Engine
2 | {
3 | using Common;
4 | using Container;
5 | using Diagnostics;
6 | using Primitives;
7 | using System;
8 | using System.Threading;
9 |
10 | ///
11 | /// Implement packet reading worker logic.
12 | ///
13 | ///
14 | internal sealed class PacketReadingWorker : IntervalWorkerBase, IMediaWorker, ILoggingSource
15 | {
16 | public PacketReadingWorker(MediaEngine mediaCore)
17 | : base(nameof(PacketReadingWorker))
18 | {
19 | MediaCore = mediaCore;
20 | Container = mediaCore.Container;
21 |
22 | // Enable data frame processing as a connector callback (i.e. hanlde non-media frames)
23 | Container.Data.OnDataPacketReceived = (dataPacket, stream) =>
24 | {
25 | try
26 | {
27 | var dataFrame = new DataFrame(dataPacket, stream, MediaCore);
28 | MediaCore.Connector?.OnDataFrameReceived(dataFrame, stream);
29 | }
30 | catch
31 | {
32 | // ignore
33 | }
34 | };
35 |
36 | // Packet Buffer Notification Callbacks
37 | Container.Components.OnPacketQueueChanged = (op, packet, mediaType, state) =>
38 | {
39 | MediaCore.State.UpdateBufferingStats(state.Length, state.Count, state.CountThreshold, state.Duration);
40 |
41 | if (op != PacketQueueOp.Queued)
42 | return;
43 |
44 | unsafe
45 | {
46 | MediaCore.Connector?.OnPacketRead(packet.Pointer, Container.InputContext);
47 | }
48 | };
49 | }
50 |
51 | ///
52 | public MediaEngine MediaCore { get; }
53 |
54 | ///
55 | ILoggingHandler ILoggingSource.LoggingHandler => MediaCore;
56 |
57 | ///
58 | /// Gets the Media Engine's container.
59 | ///
60 | private MediaContainer Container { get; }
61 |
62 | ///
63 | protected override void ExecuteCycleLogic(CancellationToken ct)
64 | {
65 | while (MediaCore.ShouldReadMorePackets)
66 | {
67 | if (Container.IsReadAborted || Container.IsAtEndOfStream || ct.IsCancellationRequested ||
68 | WorkerState != WantedWorkerState)
69 | {
70 | break;
71 | }
72 |
73 | try { Container.Read(); }
74 | catch (MediaContainerException) { /* ignore */ }
75 | }
76 | }
77 |
78 | ///
79 | protected override void OnCycleException(Exception ex) =>
80 | this.LogError(Aspects.ReadingWorker, "Worker Cycle exception thrown", ex);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Unosquare.FFME/FFmpeg/FFBPrint.cs:
--------------------------------------------------------------------------------
1 | namespace FFmpeg;
2 |
3 | using FFmpeg.AutoGen;
4 | using System;
5 | using System.Runtime.InteropServices;
6 | using Unosquare.FFME;
7 |
8 | internal unsafe sealed class FFBPrint : IDisposable
9 | {
10 | private static readonly nint ReservedFieldOffset = sizeof(nint) + (3 * sizeof(uint));
11 |
12 | public FFBPrint()
13 | {
14 | Update(AllocateAutoAVBPrint());
15 | }
16 |
17 | public AVBPrint* Target { get; private set; }
18 |
19 | public string Contents
20 | {
21 | get
22 | {
23 | if (Target is null)
24 | return string.Empty;
25 |
26 | var bpStruct = Marshal.PtrToStructure(new IntPtr(Target));
27 | return Utilities.PtrToStringUTF8(bpStruct.str) ?? string.Empty;
28 | }
29 | }
30 |
31 | public unsafe void Dispose()
32 | {
33 | var bpStruct = Marshal.PtrToStructure((nint)Target);
34 |
35 | var isAllocated = Target + ReservedFieldOffset != bpStruct.str;
36 |
37 | if (isAllocated)
38 | ffmpeg.av_freep(&bpStruct.str);
39 |
40 | var target = Target;
41 | ffmpeg.av_freep(&target);
42 | Update(null);
43 | }
44 |
45 | private static unsafe AVBPrint* AllocateAutoAVBPrint()
46 | {
47 | // https://ffmpeg.org/doxygen/1.0/bprint_8h-source.html
48 | const int StructurePadding = 1024;
49 | var bpStructAddress = ffmpeg.av_mallocz(StructurePadding);
50 | var bStruct = default(AVBPrintExtended);
51 |
52 | bStruct.len = 0;
53 | bStruct.size = 1;
54 | bStruct.size_max = uint.MaxValue - 1;
55 | bStruct.reserved_internal_buffer = 0;
56 |
57 | // point at the address of the reserved_internal_buffer
58 | bStruct.str = (byte*)((nint)bpStructAddress + ReservedFieldOffset);
59 |
60 | Marshal.StructureToPtr(bStruct, (nint)bpStructAddress, true);
61 | return (AVBPrint*)bpStructAddress;
62 | }
63 |
64 | private unsafe void Update(AVBPrint* target)
65 | {
66 | Target = target;
67 | }
68 |
69 | #pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
70 | #pragma warning disable SA1310 // Field names should not contain underscore
71 | [StructLayout(LayoutKind.Sequential)]
72 | private struct AVBPrintExtended
73 | {
74 | public byte* str;
75 | public uint len;
76 | public uint size;
77 | public uint size_max;
78 | public byte reserved_internal_buffer;
79 | }
80 | #pragma warning restore SA1310 // Field names should not contain underscore
81 | #pragma warning restore SA1307 // Accessible fields should begin with upper-case letter
82 | }
--------------------------------------------------------------------------------
/Unosquare.FFME/FFmpeg/FFDictionaryEntry.cs:
--------------------------------------------------------------------------------
1 | namespace FFmpeg.AutoGen
2 | {
3 | using System;
4 | using Unosquare.FFME;
5 |
6 | ///
7 | /// An AVDictionaryEntry wrapper.
8 | ///
9 | internal unsafe class FFDictionaryEntry
10 | {
11 | // This pointer is generated in unmanaged code.
12 | private readonly IntPtr m_Pointer;
13 |
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | /// The entry pointer.
18 | public FFDictionaryEntry(AVDictionaryEntry* entryPointer)
19 | {
20 | m_Pointer = new IntPtr(entryPointer);
21 | }
22 |
23 | ///
24 | /// Gets the unmanaged pointer.
25 | ///
26 | public AVDictionaryEntry* Pointer => (AVDictionaryEntry*)m_Pointer;
27 |
28 | ///
29 | /// Gets the key.
30 | ///
31 | public string Key => m_Pointer != IntPtr.Zero ? Utilities.PtrToStringUTF8(Pointer->key) : null;
32 |
33 | ///
34 | /// Gets the value.
35 | ///
36 | public string Value => m_Pointer != IntPtr.Zero ? Utilities.PtrToStringUTF8(Pointer->value) : null;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Platform/IMediaRenderer.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Platform
2 | {
3 | using Container;
4 | using Engine;
5 | using System;
6 |
7 | ///
8 | /// Provides a unified API for media rendering classes.
9 | ///
10 | internal interface IMediaRenderer
11 | {
12 | ///
13 | /// Gets the parent media engine.
14 | ///
15 | MediaEngine MediaCore { get; }
16 |
17 | ///
18 | /// Waits for the renderer to be ready to render.
19 | /// This is called only once before all Render calls are made.
20 | ///
21 | void OnStarting();
22 |
23 | ///
24 | /// Executed when the Play method is called on the parent Media Engine.
25 | ///
26 | void OnPlay();
27 |
28 | ///
29 | /// Executed when the Pause method is called on the parent Media Engine.
30 | ///
31 | void OnPause();
32 |
33 | ///
34 | /// Executed when the Stop method is called on the parent Media Engine.
35 | ///
36 | void OnStop();
37 |
38 | ///
39 | /// Executed when the Close method is called on the parent Media Engine.
40 | /// Release all resources when this call is received.
41 | ///
42 | void OnClose();
43 |
44 | ///
45 | /// Executed after a Seek operation is performed on the parent Media Engine.
46 | ///
47 | void OnSeek();
48 |
49 | ///
50 | /// Called when a media block is due rendering.
51 | /// This needs to return immediately so the calling thread is not disturbed.
52 | ///
53 | /// The media block.
54 | /// The clock position.
55 | void Render(MediaBlock mediaBlock, TimeSpan clockPosition);
56 |
57 | ///
58 | /// Called on every block rendering clock cycle just in case some update operation needs to be performed.
59 | /// This needs to return immediately so the calling thread is not disturbed.
60 | ///
61 | /// The clock position.
62 | void Update(TimeSpan clockPosition);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Playlists/PlaylistEntry.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Playlists
2 | {
3 | using System;
4 | using System.ComponentModel;
5 | using System.Runtime.CompilerServices;
6 |
7 | ///
8 | /// Represents a generic playlist entry.
9 | ///
10 | public class PlaylistEntry : INotifyPropertyChanged
11 | {
12 | private string m_MediaSource;
13 | private string m_Title;
14 | private TimeSpan m_Duration;
15 |
16 | ///
17 | /// Occurs when a property value changes.
18 | ///
19 | public event PropertyChangedEventHandler PropertyChanged;
20 |
21 | ///
22 | /// Gets or sets the media source URL.
23 | ///
24 | public string MediaSource
25 | {
26 | get => m_MediaSource;
27 | set => SetProperty(ref m_MediaSource, value);
28 | }
29 |
30 | ///
31 | /// Gets or sets the title.
32 | ///
33 | public string Title
34 | {
35 | get => m_Title;
36 | set => SetProperty(ref m_Title, value);
37 | }
38 |
39 | ///
40 | /// Gets or sets the duration.
41 | ///
42 | public TimeSpan Duration
43 | {
44 | get => m_Duration;
45 | set => SetProperty(ref m_Duration, value);
46 | }
47 |
48 | ///
49 | /// Gets the extended attributes.
50 | ///
51 | public PlaylistEntryAttributeDictionary Attributes { get; } = new PlaylistEntryAttributeDictionary();
52 |
53 | ///
54 | /// Checks if a property already matches a desired value. Sets the property and
55 | /// notifies listeners only when necessary.
56 | ///
57 | /// Type of the property.
58 | /// Reference to a property with both getter and setter.
59 | /// Desired value for the property.
60 | /// Name of the property used to notify listeners. This
61 | /// value is optional and can be provided automatically when invoked from compilers that
62 | /// support CallerMemberName.
63 | /// True if the value was changed, false if the existing value matched the
64 | /// desired value.
65 | protected virtual bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null)
66 | {
67 | if (Equals(storage, value)) return false;
68 | storage = value;
69 | OnPropertyChanged(propertyName);
70 | return true;
71 | }
72 |
73 | ///
74 | /// Notifies listeners that a property value has changed.
75 | ///
76 | /// Name of the property used to notify listeners. This
77 | /// value is optional and can be provided automatically when invoked from compilers
78 | /// that support .
79 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
80 | {
81 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Playlists/PlaylistEntryAttributeDictionary.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Playlists
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Web;
6 |
7 | ///
8 | /// Represents a dictionary of attributes (key-value pairs).
9 | ///
10 | [Serializable]
11 | public class PlaylistEntryAttributeDictionary : Dictionary
12 | {
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | public PlaylistEntryAttributeDictionary()
17 | : base(16)
18 | {
19 | // placeholder
20 | }
21 |
22 | ///
23 | /// Returns a that represents this instance.
24 | ///
25 | ///
26 | /// A that represents this instance.
27 | ///
28 | public override string ToString()
29 | {
30 | var attributes = new List(Count);
31 | foreach (var kvp in this)
32 | {
33 | if (string.IsNullOrWhiteSpace(kvp.Key))
34 | continue;
35 |
36 | var value = string.IsNullOrWhiteSpace(kvp.Value) ? string.Empty : kvp.Value;
37 | attributes.Add($"{HttpUtility.UrlEncode(kvp.Key)}=\"{HttpUtility.UrlEncode(value)}\"");
38 | }
39 |
40 | return string.Join(" ", attributes);
41 | }
42 |
43 | ///
44 | /// Gets the entry value safely.
45 | ///
46 | /// The entry key.
47 | /// The entry value or null.
48 | public string GetEntryValue(string entryKey)
49 | {
50 | return ContainsKey(entryKey) ? this[entryKey] : null;
51 | }
52 |
53 | ///
54 | /// Sets the entry value and returns true if the value changes.
55 | ///
56 | /// The entry key.
57 | /// The value.
58 | /// True if the property changed, false otherwise.
59 | public bool SetEntryValue(string entryKey, string value)
60 | {
61 | var existingValue = GetEntryValue(entryKey);
62 | this[entryKey] = value;
63 | if (existingValue == null) return true;
64 | return Equals(existingValue, value) == false;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Primitives/AtomicBoolean.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Primitives
2 | {
3 | ///
4 | /// Fast, atomic boolean combining interlocked to write value and volatile to read values
5 | /// Idea taken from Memory model and .NET operations in article:
6 | /// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
7 | ///
8 | internal sealed class AtomicBoolean : AtomicTypeBase
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// if set to true [initial value].
14 | public AtomicBoolean(bool initialValue)
15 | : base(initialValue ? 1 : 0)
16 | {
17 | // placeholder
18 | }
19 |
20 | ///
21 | /// Initializes a new instance of the class.
22 | ///
23 | public AtomicBoolean()
24 | : base(0)
25 | {
26 | // placeholder
27 | }
28 |
29 | ///
30 | protected override bool FromLong(long backingValue) => backingValue != 0;
31 |
32 | ///
33 | protected override long ToLong(bool value) => value ? 1 : 0;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Primitives/AtomicDateTime.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Primitives
2 | {
3 | using System;
4 |
5 | ///
6 | /// Defines an atomic DateTime.
7 | ///
8 | internal sealed class AtomicDateTime : AtomicTypeBase
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The initial value.
14 | public AtomicDateTime(DateTime initialValue)
15 | : base(initialValue.Ticks)
16 | {
17 | // placeholder
18 | }
19 |
20 | ///
21 | protected override DateTime FromLong(long backingValue) => new DateTime(backingValue);
22 |
23 | ///
24 | protected override long ToLong(DateTime value) => value.Ticks;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Primitives/AtomicDouble.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Primitives
2 | {
3 | using System;
4 |
5 | ///
6 | /// Fast, atomic double combining interlocked to write value and volatile to read values
7 | /// Idea taken from Memory model and .NET operations in article:
8 | /// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
9 | ///
10 | internal sealed class AtomicDouble : AtomicTypeBase
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// if set to true [initial value].
16 | public AtomicDouble(double initialValue)
17 | : base(BitConverter.DoubleToInt64Bits(initialValue))
18 | {
19 | // placeholder
20 | }
21 |
22 | ///
23 | /// Initializes a new instance of the class.
24 | ///
25 | public AtomicDouble()
26 | : base(BitConverter.DoubleToInt64Bits(0))
27 | {
28 | // placeholder
29 | }
30 |
31 | ///
32 | protected override double FromLong(long backingValue) => BitConverter.Int64BitsToDouble(backingValue);
33 |
34 | ///
35 | protected override long ToLong(double value) => BitConverter.DoubleToInt64Bits(value);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Primitives/AtomicInteger.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Primitives
2 | {
3 | using System;
4 |
5 | ///
6 | /// Represents an atomically readable or writable integer.
7 | ///
8 | internal sealed class AtomicInteger : AtomicTypeBase
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// if set to true [initial value].
14 | public AtomicInteger(int initialValue)
15 | : base(Convert.ToInt64(initialValue))
16 | {
17 | // placeholder
18 | }
19 |
20 | ///
21 | /// Initializes a new instance of the class.
22 | ///
23 | public AtomicInteger()
24 | : base(0)
25 | {
26 | // placeholder
27 | }
28 |
29 | ///
30 | protected override int FromLong(long backingValue) => Convert.ToInt32(backingValue);
31 |
32 | ///
33 | protected override long ToLong(int value) => Convert.ToInt64(value);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Primitives/AtomicLong.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Primitives
2 | {
3 | ///
4 | /// Fast, atomic long combining interlocked to write value and volatile to read values
5 | /// Idea taken from Memory model and .NET operations in article:
6 | /// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
7 | ///
8 | internal sealed class AtomicLong : AtomicTypeBase
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// if set to true [initial value].
14 | public AtomicLong(long initialValue)
15 | : base(initialValue)
16 | {
17 | // placeholder
18 | }
19 |
20 | ///
21 | /// Initializes a new instance of the class.
22 | ///
23 | public AtomicLong()
24 | : base(0)
25 | {
26 | // placeholder
27 | }
28 |
29 | ///
30 | protected override long FromLong(long backingValue) => backingValue;
31 |
32 | ///
33 | protected override long ToLong(long value) => value;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Primitives/AtomicTimeSpan.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Primitives
2 | {
3 | using System;
4 |
5 | ///
6 | /// Represents an atomic TimeSpan type.
7 | ///
8 | internal sealed class AtomicTimeSpan : AtomicTypeBase
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The initial value.
14 | public AtomicTimeSpan(TimeSpan initialValue)
15 | : base(initialValue.Ticks)
16 | {
17 | // placeholder
18 | }
19 |
20 | ///
21 | protected override TimeSpan FromLong(long backingValue) => TimeSpan.FromTicks(backingValue);
22 |
23 | ///
24 | protected override long ToLong(TimeSpan value) => value.Ticks;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Primitives/ISyncLocker.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Primitives
2 | {
3 | using System;
4 |
5 | ///
6 | /// Defines a generic interface for synchronized locking mechanisms.
7 | ///
8 | internal interface ISyncLocker : IDisposable
9 | {
10 | ///
11 | /// Gets a value indicating whether this instance is disposed.
12 | ///
13 | bool IsDisposed { get; }
14 |
15 | ///
16 | /// Acquires a writer lock.
17 | /// The lock is released when the returned locking object is disposed.
18 | ///
19 | /// A disposable locking object.
20 | IDisposable AcquireWriterLock();
21 |
22 | ///
23 | /// Tries to acquire a writer lock with a timeout.
24 | ///
25 | /// The timeout milliseconds.
26 | /// The locker.
27 | /// True if the lock was acquired.
28 | bool TryAcquireWriterLock(int timeoutMilliseconds, out IDisposable locker);
29 |
30 | ///
31 | /// Tries to acquire a writer lock with a default timeout.
32 | ///
33 | /// The locker.
34 | /// /// True if the lock was acquired.
35 | bool TryAcquireWriterLock(out IDisposable locker);
36 |
37 | ///
38 | /// Acquires a reader lock.
39 | /// The lock is released when the returned locking object is disposed.
40 | ///
41 | /// A disposable locking object.
42 | IDisposable AcquireReaderLock();
43 |
44 | ///
45 | /// Tries to acquire a reader lock with a timeout.
46 | ///
47 | /// The timeout milliseconds.
48 | /// The locker.
49 | /// True if the lock was acquired.
50 | bool TryAcquireReaderLock(int timeoutMilliseconds, out IDisposable locker);
51 |
52 | ///
53 | /// Tries to acquire a reader lock with a default timeout.
54 | ///
55 | /// The locker.
56 | /// True if the lock was acquired.
57 | bool TryAcquireReaderLock(out IDisposable locker);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Primitives/IWorker.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Primitives
2 | {
3 | using System;
4 | using System.Threading.Tasks;
5 |
6 | ///
7 | /// Defines a standard API to control background application workers.
8 | ///
9 | ///
10 | internal interface IWorker : IDisposable
11 | {
12 | ///
13 | /// Gets the current state of the worker.
14 | ///
15 | WorkerState WorkerState { get; }
16 |
17 | ///
18 | /// Gets a value indicating whether this instance is disposed.
19 | ///
20 | ///
21 | /// true if this instance is disposed; otherwise, false.
22 | ///
23 | bool IsDisposed { get; }
24 |
25 | ///
26 | /// Gets the name identifier of this worker.
27 | ///
28 | string Name { get; }
29 |
30 | ///
31 | /// Starts execution of worker cycles.
32 | ///
33 | /// The awaitable task.
34 | Task StartAsync();
35 |
36 | ///
37 | /// Pauses execution of worker cycles.
38 | ///
39 | /// The awaitable task.
40 | Task PauseAsync();
41 |
42 | ///
43 | /// Resumes execution of worker cycles.
44 | ///
45 | /// The awaitable task.
46 | Task ResumeAsync();
47 |
48 | ///
49 | /// Permanently stops execution of worker cycles.
50 | /// An interrupt is always sent to the worker. If you wish to stop
51 | /// the worker without interrupting then call the
52 | /// method, await it, and finally call the method.
53 | ///
54 | /// The awaitable task.
55 | Task StopAsync();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Primitives/IntervalWorkerBase.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Primitives
2 | {
3 | ///
4 | /// A base class for implementing interval workers.
5 | ///
6 | internal abstract class IntervalWorkerBase : WorkerBase
7 | {
8 | private readonly StepTimer QuantumTimer;
9 |
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// The name.
14 | protected IntervalWorkerBase(string name)
15 | : base(name)
16 | {
17 | QuantumTimer = new StepTimer(OnQuantumTicked);
18 | }
19 |
20 | ///
21 | protected override void Dispose(bool alsoManaged)
22 | {
23 | base.Dispose(alsoManaged);
24 | QuantumTimer.Dispose();
25 | }
26 |
27 | ///
28 | /// Called when every quantum of time occurs.
29 | ///
30 | private void OnQuantumTicked()
31 | {
32 | if (!TryBeginCycle())
33 | return;
34 |
35 | ExecuteCyle();
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Primitives/MediaTypeDictionary.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Primitives
2 | {
3 | using Common;
4 | using System;
5 | using System.Collections.Generic;
6 |
7 | ///
8 | /// Represents a very simple dictionary for MediaType keys.
9 | ///
10 | /// The type of the value.
11 | [Serializable]
12 | internal sealed class MediaTypeDictionary
13 | : Dictionary
14 | {
15 | ///
16 | /// Initializes a new instance of the class.
17 | ///
18 | public MediaTypeDictionary()
19 | : base(Enum.GetValues(typeof(MediaType)).Length)
20 | {
21 | // placeholder
22 | }
23 |
24 | ///
25 | /// Gets or sets the item with the specified key.
26 | /// return the default value of the value type when the key does not exist.
27 | ///
28 | /// The key.
29 | /// The item.
30 | public new TValue this[MediaType key]
31 | {
32 | get => ContainsKey(key) == false ? default : base[key];
33 | internal set => base[key] = value;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Primitives/WorkerState.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME.Primitives
2 | {
3 | ///
4 | /// Enumerates the different states in which a worker can be.
5 | ///
6 | internal enum WorkerState
7 | {
8 | ///
9 | /// The worker has been created and it is ready to start.
10 | ///
11 | Created,
12 |
13 | ///
14 | /// The worker is running it cycle logic.
15 | ///
16 | Running,
17 |
18 | ///
19 | /// The worker is running its delay logic.
20 | ///
21 | Waiting,
22 |
23 | ///
24 | /// The worker is in the paused or suspended state.
25 | ///
26 | Paused,
27 |
28 | ///
29 | /// The worker is stopped and ready for disposal.
30 | ///
31 | Stopped
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Unosquare.FFME.shproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | b14a1cd8-e4e8-44e1-805a-66d0fafc6263
5 | 14.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Utilities.Audio.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME
2 | {
3 | using System;
4 | using System.Runtime.CompilerServices;
5 |
6 | public static partial class Utilities
7 | {
8 | ///
9 | /// Gets the audio sample amplitude (absolute value of the sample).
10 | ///
11 | /// The buffer.
12 | /// The offset.
13 | /// The sample amplitude.
14 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
15 | public static short GetAudioSampleAmplitude(this byte[] buffer, int offset)
16 | {
17 | var value = buffer.GetAudioSample(offset);
18 | return value == short.MinValue ? short.MaxValue : Math.Abs(value);
19 | }
20 |
21 | ///
22 | /// Gets the audio sample level for 0 to 1.
23 | ///
24 | /// The buffer.
25 | /// The offset.
26 | /// The amplitude level.
27 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
28 | public static double GetAudioSampleLevel(this byte[] buffer, int offset)
29 | {
30 | return buffer.GetAudioSampleAmplitude(offset) / Convert.ToDouble(short.MaxValue);
31 | }
32 |
33 | ///
34 | /// Puts a short value in the target buffer as bytes.
35 | ///
36 | /// The target.
37 | /// The offset.
38 | /// The value.
39 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
40 | internal static void PutAudioSample(this byte[] buffer, int offset, short value)
41 | {
42 | if (BitConverter.IsLittleEndian)
43 | {
44 | buffer[offset] = (byte)(value & 0x00ff); // set the LSB
45 | buffer[offset + 1] = (byte)(value >> 8); // set the MSB
46 | return;
47 | }
48 |
49 | buffer[offset] = (byte)(value >> 8); // set the MSB
50 | buffer[offset + 1] = (byte)(value & 0x00ff); // set the LSB
51 | }
52 |
53 | ///
54 | /// Gets the a signed 16 bit integer at the given offset.
55 | ///
56 | /// The buffer.
57 | /// The offset.
58 | /// The signed integer.
59 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
60 | internal static short GetAudioSample(this byte[] buffer, int offset) =>
61 | BitConverter.ToInt16(buffer, offset);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Unosquare.FFME/Utilities.cs:
--------------------------------------------------------------------------------
1 | namespace Unosquare.FFME;
2 |
3 | using System;
4 | using System.Runtime.CompilerServices;
5 | using System.Text;
6 |
7 | ///
8 | /// Provides various helpers and extension methods.
9 | ///
10 | public static partial class Utilities
11 | {
12 | ///
13 | /// Converts the given value to a value that is of the given multiple.
14 | ///
15 | /// The value.
16 | /// The multiple.
17 | /// The value snapped to the given multiple.
18 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
19 | public static double ToMultipleOf(this double value, double multiple)
20 | {
21 | var factor = Convert.ToInt32(value / multiple);
22 | return factor * multiple;
23 | }
24 |
25 | ///
26 | /// Converts a byte pointer to a UTF8 encoded string.
27 | ///
28 | /// The pointer to the starting character.
29 | /// The string.
30 | public static unsafe string PtrToStringUTF8(byte* stringAddress)
31 | {
32 | if (stringAddress == null) return null;
33 | if (*stringAddress == 0) return string.Empty;
34 |
35 | var byteLength = 0;
36 | while (true)
37 | {
38 | if (stringAddress[byteLength] == 0)
39 | break;
40 |
41 | byteLength++;
42 | }
43 |
44 | var stringBuffer = stackalloc byte[byteLength];
45 | Buffer.MemoryCopy(stringAddress, stringBuffer, byteLength, byteLength);
46 | return Encoding.UTF8.GetString(stringBuffer, byteLength);
47 | }
48 |
49 | ///
50 | /// A cross-platform implementation of string.Replace.
51 | ///
52 | /// The string to search.
53 | /// The string to find.
54 | /// The string to replace.
55 | /// The string with the replacement.
56 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
57 | public static string ReplaceOrdinal(this string source, string find, string replace) =>
58 | #if NETCOREAPP
59 | source?.Replace(find, replace, StringComparison.Ordinal);
60 | #else
61 | source?.Replace(find, replace);
62 | #endif
63 |
64 | ///
65 | /// Determines if the string contains the search term in ordinal (binary) comparison.
66 | ///
67 | /// The string to search.
68 | /// The search term.
69 | /// Thether the search term is contained in the string.
70 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
71 | public static bool ContainsOrdinal(this string source, string find) =>
72 | source != null && source.IndexOf(find, StringComparison.Ordinal) > -1;
73 | }
74 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '7.0.361.{build}'
2 | image: Visual Studio 2019 Preview
3 | configuration:
4 | - Release
5 | platform: Any CPU
6 | notifications:
7 | - provider: Slack
8 | auth_token:
9 | secure: Q+xg4/yU5OR9BVF14cw4yZ+3qlhMeYDsAhUQyOIszmF1mHvq44tIvQpWByBJCd/cgUIZk3SwBpk4hh1MrkQIk6rnaOZ2LNBTev4zrq36oXk=
10 | channel: '#builds'
11 | environment:
12 | # Don't report back to the mothership
13 | DOTNET_CLI_TELEMETRY_OPTOUT: 1
14 | COVERALLS_REPO_TOKEN:
15 | secure: dVpAqavd3jP7LMW+oswLBPfihwiGDZUtWDQWJadMO1Gj61SYegxNsGTZOT2BYN6+
16 | op_build_user: "Geo Perez"
17 | op_build_user_email: "geovanni.perez@gmail.com"
18 | access_token:
19 | secure: HzWdswNyfQbQ0vLk9IQyO+Ei9mxoPYp9rvv6HPhtC9J/Fm7EHRzyV953pbPRXI9I
20 | before_build:
21 | - nuget restore -verbosity quiet
22 | - ps: |
23 | if(-Not $env:APPVEYOR_PULL_REQUEST_TITLE)
24 | {
25 | git checkout $env:APPVEYOR_REPO_BRANCH -q
26 | cinst docfx -y --no-progress
27 | }
28 | build_script:
29 | - cd Unosquare.FFME.Windows.Sample
30 | - msbuild /p:Configuration=Release /verbosity:quiet
31 | - cd ..
32 | after_build:
33 | - ps: |
34 | if(-Not $env:APPVEYOR_PULL_REQUEST_TITLE)
35 | {
36 | git config --global credential.helper store
37 | Add-Content "$env:USERPROFILE\.git-credentials" "https://$($env:access_token):x-oauth-basic@github.com`n"
38 | git config --global core.autocrlf false
39 | git config --global user.email $env:op_build_user_email
40 | git config --global user.name $env:op_build_user
41 | git clone https://github.com/unosquare/ffmediaelement.git -b gh-pages origin_site -q
42 | git clone -b documentation https://github.com/unosquare/best-practices.git -q
43 | docfx docfx.json --logLevel Error
44 | Copy-Item origin_site/.git _site -recurse
45 | CD _site
46 | Copy-Item README.html index.html -force
47 | git add -A 2>&1
48 | git commit -m "Documentation update" -q
49 | git push origin gh-pages -q
50 | CD ..
51 | }
52 |
--------------------------------------------------------------------------------
/docfx.json:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": [
3 | {
4 | "src": [
5 | {
6 | "files": [ "Unosquare.FFME.Windows/**/*.cs" ],
7 | "exclude": [ "**/bin/**", "**/obj/**" ]
8 | }
9 | ],
10 | "dest": "obj/api"
11 | }
12 | ],
13 | "build": {
14 | "xrefService": "https://xref.docs.microsoft.com/query?uid={uid}",
15 | "template": [
16 | "best-practices/templates/default"
17 | ],
18 | "content": [
19 | {
20 | "files": [
21 | "**/*.yml"
22 | ],
23 | "cwd": "obj/api",
24 | "dest": "api"
25 | },
26 | {
27 | "files": [
28 | "*.md",
29 | "toc.yml"
30 | ]
31 | }
32 | ],
33 | "resource": [
34 | {
35 | "files": [ "best-practices/resources/**", "ffme.png"]
36 | }
37 | ],
38 | "globalMetadata": {
39 | "_appTitle": "FFME: WPF MediaElement replacement based on FFmpeg",
40 | "_enableSearch": true,
41 | "_docLogo": "ffme.png",
42 | "_appLogoPath": "best-practices/resources/images/logo.png"
43 | },
44 | "dest": "_site"
45 | }
46 | }
--------------------------------------------------------------------------------
/toc.yml:
--------------------------------------------------------------------------------
1 | - name: FFME Documentation
2 | href: api/Unosquare.FFME.html
3 |
--------------------------------------------------------------------------------