From 9a1bc44a089dd59b4b8bf9352eb6e68f48aa7e32 Mon Sep 17 00:00:00 2001 From: "Yuto Terada (indigo-san)" Date: Thu, 11 Apr 2024 13:12:24 +0900 Subject: [PATCH] =?UTF-8?q?Beutl.Extensions.MediaFoundation.Encoding=20?= =?UTF-8?q?=E3=83=97=E3=83=AD=E3=82=B8=E3=82=A7=E3=82=AF=E3=83=88=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Encoding/MFEncoderInfo.cs | 40 ++++++ .../Encoding/MFEncodingExtension.cs | 26 ++++ .../Encoding/MFVideoEncoderSettings.cs | 12 ++ .../Encoding/MFVideoFormat.cs | 117 ++++++++++++++++++ .../Encoding/MFWriter.cs | 114 +++++++++++++++++ 5 files changed, 309 insertions(+) create mode 100644 src/Beutl.Extensions.MediaFoundation/Encoding/MFEncoderInfo.cs create mode 100644 src/Beutl.Extensions.MediaFoundation/Encoding/MFEncodingExtension.cs create mode 100644 src/Beutl.Extensions.MediaFoundation/Encoding/MFVideoEncoderSettings.cs create mode 100644 src/Beutl.Extensions.MediaFoundation/Encoding/MFVideoFormat.cs create mode 100644 src/Beutl.Extensions.MediaFoundation/Encoding/MFWriter.cs diff --git a/src/Beutl.Extensions.MediaFoundation/Encoding/MFEncoderInfo.cs b/src/Beutl.Extensions.MediaFoundation/Encoding/MFEncoderInfo.cs new file mode 100644 index 000000000..5e9ee3e76 --- /dev/null +++ b/src/Beutl.Extensions.MediaFoundation/Encoding/MFEncoderInfo.cs @@ -0,0 +1,40 @@ +using Beutl.Media.Encoding; + +#if MF_BUILD_IN +namespace Beutl.Embedding.MediaFoundation.Encoding; +#else +namespace Beutl.Extensions.MediaFoundation.Encoding; +#endif + +public class MFEncoderInfo : IEncoderInfo +{ + public string Name => "Media Foundation Encoder"; + + public MediaWriter? Create(string file, VideoEncoderSettings videoConfig, AudioEncoderSettings audioConfig) + { + if (videoConfig is not MFVideoEncoderSettings mfVideoConfig) + return null; + + return new MFWriter(file, mfVideoConfig, audioConfig); + } + + public IEnumerable SupportExtensions() + { + yield return ".mp4"; + yield return ".mov"; + yield return ".m4v"; + yield return ".avi"; + yield return ".wmv"; + yield return ".sami"; + yield return ".smi"; + yield return ".adts"; + yield return ".asf"; + yield return ".3gp"; + yield return ".3gp2"; + yield return ".3gpp"; + } + + public VideoEncoderSettings DefaultVideoConfig() => new MFVideoEncoderSettings(); + + public AudioEncoderSettings DefaultAudioConfig() => new AudioEncoderSettings(); +} diff --git a/src/Beutl.Extensions.MediaFoundation/Encoding/MFEncodingExtension.cs b/src/Beutl.Extensions.MediaFoundation/Encoding/MFEncodingExtension.cs new file mode 100644 index 000000000..998e41efc --- /dev/null +++ b/src/Beutl.Extensions.MediaFoundation/Encoding/MFEncodingExtension.cs @@ -0,0 +1,26 @@ +using Beutl.Extensibility; +using Beutl.Media.Encoding; + +#if MF_BUILD_IN +namespace Beutl.Embedding.MediaFoundation.Encoding; +#else +namespace Beutl.Extensions.MediaFoundation.Encoding; +#endif + +[Export] +public class MFEncodingExtension : EncodingExtension +{ + public override string Name => "Media Foundation Encoder"; + + public override string DisplayName => "Media Foundation Encoder"; + + public override IEncoderInfo GetEncoderInfo() => new MFEncoderInfo(); + + public override void Load() + { + if (OperatingSystem.IsWindows()) + { + EncoderRegistry.Register(GetEncoderInfo()); + } + } +} diff --git a/src/Beutl.Extensions.MediaFoundation/Encoding/MFVideoEncoderSettings.cs b/src/Beutl.Extensions.MediaFoundation/Encoding/MFVideoEncoderSettings.cs new file mode 100644 index 000000000..2a35c859b --- /dev/null +++ b/src/Beutl.Extensions.MediaFoundation/Encoding/MFVideoEncoderSettings.cs @@ -0,0 +1,12 @@ +using Beutl.Media.Encoding; + +#if MF_BUILD_IN +namespace Beutl.Embedding.MediaFoundation.Encoding; +#else +namespace Beutl.Extensions.MediaFoundation.Encoding; +#endif + +public class MFVideoEncoderSettings : VideoEncoderSettings +{ + public MFVideoFormat Format { get; set; } +} diff --git a/src/Beutl.Extensions.MediaFoundation/Encoding/MFVideoFormat.cs b/src/Beutl.Extensions.MediaFoundation/Encoding/MFVideoFormat.cs new file mode 100644 index 000000000..6ceff81cf --- /dev/null +++ b/src/Beutl.Extensions.MediaFoundation/Encoding/MFVideoFormat.cs @@ -0,0 +1,117 @@ +using SharpDX.MediaFoundation; + +#if MF_BUILD_IN +namespace Beutl.Embedding.MediaFoundation.Encoding; +#else +namespace Beutl.Extensions.MediaFoundation.Encoding; +#endif + +// MFVideoFormatとVideoFormatGuidsを相互に変換するクラス +public static class MFVideoFormatExtension +{ + // MFVideoFormatからVideoFormatGuidsに変換する + public static Guid ToVideoFormatGuid(this MFVideoFormat format) + { + return format switch + { + MFVideoFormat.Wmv1 => VideoFormatGuids.Wmv1, + MFVideoFormat.Wmv2 => VideoFormatGuids.Wmv2, + MFVideoFormat.Wmv3 => VideoFormatGuids.Wmv3, + MFVideoFormat.Dvc => VideoFormatGuids.Dvc, + MFVideoFormat.Dv50 => VideoFormatGuids.Dv50, + MFVideoFormat.Dv25 => VideoFormatGuids.Dv25, + MFVideoFormat.H263 => VideoFormatGuids.H263, + MFVideoFormat.H264 => VideoFormatGuids.H264, + MFVideoFormat.H265 => VideoFormatGuids.H265, + MFVideoFormat.Hevc => VideoFormatGuids.Hevc, + MFVideoFormat.HevcEs => VideoFormatGuids.HevcEs, + MFVideoFormat.Vp80 => VideoFormatGuids.Vp80, + MFVideoFormat.Vp90 => VideoFormatGuids.Vp90, + MFVideoFormat.MultisampledS2 => VideoFormatGuids.MultisampledS2, + MFVideoFormat.M4S2 => VideoFormatGuids.M4S2, + MFVideoFormat.Wvc1 => VideoFormatGuids.Wvc1, + MFVideoFormat.P010 => VideoFormatGuids.P010, + MFVideoFormat.AI44 => VideoFormatGuids.AI44, + MFVideoFormat.Dvh1 => VideoFormatGuids.Dvh1, + MFVideoFormat.Dvhd => VideoFormatGuids.Dvhd, + MFVideoFormat.MultisampledS1 => VideoFormatGuids.MultisampledS1, + MFVideoFormat.Mp43 => VideoFormatGuids.Mp43, + MFVideoFormat.Mp4s => VideoFormatGuids.Mp4s, + MFVideoFormat.Mp4v => VideoFormatGuids.Mp4v, + MFVideoFormat.Mpg1 => VideoFormatGuids.Mpg1, + MFVideoFormat.Mjpg => VideoFormatGuids.Mjpg, + MFVideoFormat.Dvsl => VideoFormatGuids.Dvsl, + MFVideoFormat.YUY2 => VideoFormatGuids.YUY2, + MFVideoFormat.Yv12 => VideoFormatGuids.Yv12, + MFVideoFormat.P016 => VideoFormatGuids.P016, + MFVideoFormat.P210 => VideoFormatGuids.P210, + MFVideoFormat.P216 => VideoFormatGuids.P216, + MFVideoFormat.I420 => VideoFormatGuids.I420, + MFVideoFormat.Dvsd => VideoFormatGuids.Dvsd, + MFVideoFormat.Y42T => VideoFormatGuids.Y42T, + MFVideoFormat.NV12 => VideoFormatGuids.NV12, + MFVideoFormat.NV11 => VideoFormatGuids.NV11, + MFVideoFormat.Y210 => VideoFormatGuids.Y210, + MFVideoFormat.Y216 => VideoFormatGuids.Y216, + MFVideoFormat.Y410 => VideoFormatGuids.Y410, + MFVideoFormat.Y416 => VideoFormatGuids.Y416, + MFVideoFormat.Y41P => VideoFormatGuids.Y41P, + MFVideoFormat.Y41T => VideoFormatGuids.Y41T, + MFVideoFormat.Yvu9 => VideoFormatGuids.Yvu9, + MFVideoFormat.Yvyu => VideoFormatGuids.Yvyu, + MFVideoFormat.Iyuv => VideoFormatGuids.Iyuv, + _ => throw new ArgumentOutOfRangeException(nameof(format), format, null) + }; + } +} + +public enum MFVideoFormat +{ + // SharpDX.MediaFoundation.VideoFormatGuidsから作成 + Wmv1, + Wmv2, + Wmv3, + Dvc, + Dv50, + Dv25, + H263, + H264, + H265, + Hevc, + HevcEs, + Vp80, + Vp90, + MultisampledS2, + M4S2, + Wvc1, + P010, + AI44, + Dvh1, + Dvhd, + MultisampledS1, + Mp43, + Mp4s, + Mp4v, + Mpg1, + Mjpg, + Dvsl, + YUY2, + Yv12, + P016, + P210, + P216, + I420, + Dvsd, + Y42T, + NV12, + NV11, + Y210, + Y216, + Y410, + Y416, + Y41P, + Y41T, + Yvu9, + Yvyu, + Iyuv +} diff --git a/src/Beutl.Extensions.MediaFoundation/Encoding/MFWriter.cs b/src/Beutl.Extensions.MediaFoundation/Encoding/MFWriter.cs new file mode 100644 index 000000000..6b3e46cc3 --- /dev/null +++ b/src/Beutl.Extensions.MediaFoundation/Encoding/MFWriter.cs @@ -0,0 +1,114 @@ +using Beutl.Media; +using Beutl.Media.Encoding; +using Beutl.Media.Music; +using Beutl.Media.Pixel; +using SharpDX; +using SharpDX.Direct3D9; +using SharpDX.MediaFoundation; +using SharpDX.Multimedia; +using SharpDX.Win32; + +#if MF_BUILD_IN +namespace Beutl.Embedding.MediaFoundation.Encoding; +#else +namespace Beutl.Extensions.MediaFoundation.Encoding; +#endif + +// MediaFoundationを使用して、Bitmapから動画を作成するクラス +public unsafe class MFWriter : MediaWriter +{ + private SinkWriter _sinkWriter; + private int _videoStreamIndex; + + public MFWriter(string file, MFVideoEncoderSettings videoConfig, AudioEncoderSettings audioConfig) + : base(videoConfig, audioConfig) + { + // sinkwriterを初期化 + _sinkWriter = MediaFactory.CreateSinkWriterFromURL(file, null, null); + _videoStreamIndex = ConfigureVideoEncoder(videoConfig); + + _sinkWriter.BeginWriting(); + } + + // IMFMediaTypeを作成 + private static MediaType CreateMediaTypeFromSubtype(Guid subtype, int width, int height, double rate) + { + var mediaType = new MediaType(); + mediaType.Set(MediaTypeAttributeKeys.MajorType, MediaTypeGuids.Video); + mediaType.Set(MediaTypeAttributeKeys.Subtype, subtype); + mediaType.Set(MediaTypeAttributeKeys.InterlaceMode, (int)VideoInterlaceMode.Progressive); + mediaType.Set(MediaTypeAttributeKeys.FrameSize, ((long)width << 32) | (uint)height); + mediaType.Set(MediaTypeAttributeKeys.FrameRate, ((long)(int)(rate * 10000000) << 32 | 10000000)); + return mediaType; + } + + // ConfigureVideoEncoder + private int ConfigureVideoEncoder(MFVideoEncoderSettings videoConfig) + { + using var outputType = CreateMediaTypeFromSubtype( + videoConfig.Format.ToVideoFormatGuid(), + videoConfig.DestinationSize.Width, + videoConfig.DestinationSize.Height, + videoConfig.FrameRate.ToDouble()); + outputType.Set(MediaTypeAttributeKeys.AvgBitrate, videoConfig.Bitrate); + _sinkWriter.AddStream(outputType, out int streamIndex); + + // InputType + using var inputType = CreateMediaTypeFromSubtype( + VideoFormatGuids.Argb32, + videoConfig.SourceSize.Width, + videoConfig.SourceSize.Height, + videoConfig.FrameRate.ToDouble()); + _sinkWriter.SetInputMediaType(streamIndex, inputType, null); + + return streamIndex; + } + + public override long NumberOfFrames { get; } + + public override long NumberOfSamples { get; } + + public override bool AddVideo(IBitmap image) + { + bool requireDispose = false; + + if (image is not Bitmap) + { + image = image.Convert(); + requireDispose = true; + } + + try + { + using var buffer = MediaFactory.CreateMemoryBuffer(image.ByteCount); + IntPtr ptr = buffer.Lock(out _, out _); + Buffer.MemoryCopy((void*)image.Data, (void*)ptr, image.ByteCount, image.ByteCount); + buffer.Unlock(); + buffer.CurrentLength = image.ByteCount; + + using var sample = MediaFactory.CreateSample(); + sample.AddBuffer(buffer); + + _sinkWriter.WriteSample(_videoStreamIndex, sample); + + return true; + } + finally + { + if (requireDispose) + image.Dispose(); + } + } + + public override bool AddAudio(IPcm sound) + { + return false; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _sinkWriter.Finalize(); + _sinkWriter.Dispose(); + } +}