C#屏幕共享:如何让Windows.Media.Transcoding输出UDP实时流?
解决Windows.Media.Transcoding实时获取编码帧并UDP组播的问题
Windows.Media.Transcoding的TranscodeAsync是封装好的文件输出流程,没法直接拿到每帧编码后的数据。要实现实时UDP组播,得绕开这个封装,自己构建编码管线并通过自定义MediaSink捕获编码后的帧数据。
核心思路
- 放弃
MediaTranscoder的文件输出逻辑,改用MediaEncodingProfile配合自定义IMediaSink处理编码后的样本 - 自定义Sink中捕获每帧编码后的字节数组,直接通过UDP组播发送
- 将Windows.Graphics.Capture的帧源接入编码管线
关键代码实现
1. 自定义MediaSink捕获编码帧
创建实现IMediaExtension和IMediaSink的类,用于接收编码后的媒体样本:
using Windows.Media.Core; using Windows.Media.MediaProperties; using Windows.Storage.Streams; using System.Net; using System.Net.Sockets; public class UdpMulticastMediaSink : IMediaExtension, IMediaSink { private IPropertySet _properties; private IMediaStreamSink _streamSink; private UdpClient _udpClient; private IPEndPoint _multicastEndPoint; public void SetProperties(IPropertySet configuration) { _properties = configuration; if (_properties.TryGetValue("MulticastAddress", out var addrObj) && _properties.TryGetValue("MulticastPort", out var portObj)) { var multicastAddr = IPAddress.Parse(addrObj.ToString()); var port = int.Parse(portObj.ToString()); _multicastEndPoint = new IPEndPoint(multicastAddr, port); _udpClient = new UdpClient(); _udpClient.JoinMulticastGroup(multicastAddr); } } public void AddStreamSink(int streamId, MediaEncodingProperties mediaEncodingProperties) { _streamSink = new UdpMulticastStreamSink(this, mediaEncodingProperties); } public void RemoveStreamSink(int streamId) { _streamSink?.Dispose(); } public IMediaStreamSink GetStreamSinkById(int streamId) { return _streamSink; } public void BeginShutdown(IMediaSinkShutdownCallback callback) { _udpClient?.Close(); callback.OnShutdownCompleted(); } public void EndShutdown() { // 清理资源 } private class UdpMulticastStreamSink : IMediaStreamSink, IDisposable { private readonly UdpMulticastMediaSink _parentSink; private readonly MediaEncodingProperties _encodingProps; public UdpMulticastStreamSink(UdpMulticastMediaSink parent, MediaEncodingProperties props) { _parentSink = parent; _encodingProps = props; } public void ProcessSample(MediaStreamSample sample) { // 获取编码后的帧字节数组 using (var reader = DataReader.FromBuffer(sample.Buffer)) { byte[] frameData = new byte[sample.Buffer.Length]; reader.ReadBytes(frameData); // 发送UDP组播 _parentSink._udpClient.Send(frameData, frameData.Length, _parentSink._multicastEndPoint); } } // 其他接口空实现 public void SetPresentationClock(MediaClock clock) { } public void NotifyPresentationTimeRangeChanged(MediaTimeRange timeRange) { } public void Flush() { } public void Dispose() { } public MediaEncodingProperties EncodingProperties => _encodingProps; public int StreamId => 0; } }
2. 构建编码管线并接入屏幕捕获帧
替换原Transcoder逻辑,用自定义Sink构建实时编码流程:
using Windows.Graphics.Capture; using Windows.Media.Core; using Windows.Media.MediaProperties; using Windows.Media.Transcoding; public async Task StartScreenCaptureAndStreaming() { // 获取要捕获的屏幕/窗口 var picker = new GraphicsCapturePicker(); var captureItem = await picker.PickSingleItemAsync(); if (captureItem == null) return; // 初始化屏幕捕获池和会话 var captureFramePool = GraphicsCaptureFramePool.Create( DirectXPixelFormat.B8G8R8A8UIntNormalized, (int)captureItem.Size.Width, (int)captureItem.Size.Height); var captureSession = captureFramePool.CreateCaptureSession(captureItem); // 创建实时传输用的编码配置(H.264 Baseline Profile) var encodingProfile = MediaEncodingProfile.CreateH264(VideoEncodingQuality.HD720p); encodingProfile.Video.FrameRate = new MediaRatio { Numerator = 30, Denominator = 1 }; encodingProfile.Video.Bitrate = 2000000; // 2Mbps,按需调整 // 配置自定义Sink的组播参数 var sinkProperties = new PropertySet(); sinkProperties.Add("MulticastAddress", "224.0.0.1"); sinkProperties.Add("MulticastPort", 5000); // 初始化编码器和自定义Sink var customSink = new UdpMulticastMediaSink(); customSink.SetProperties(sinkProperties); var transcoder = new MediaTranscoder { HardwareAccelerationEnabled = true }; // 准备编码管线,关联自定义Sink替代文件输出 var mediaStreamSource = captureFramePool.CreateMediaStreamSource(); var transcodeResult = await transcoder.PrepareMediaStreamSourceTranscodeAsync( mediaStreamSource, customSink, encodingProfile); if (transcodeResult.CanTranscode) { captureSession.StartCapture(); await transcodeResult.TranscodeAsync(); } else { // 处理转码失败逻辑,比如打印失败原因 var failureReason = transcodeResult.FailureReason; } }
注意事项
- 性能优化:编码和UDP发送属于高IO操作,建议在后台线程处理;可使用内存池复用byte[]减少GC压力
- 组播配置:确保网络允许UDP组播,组播地址需在
224.0.0.0到239.255.255.255范围内 - 帧同步:发送时可携带帧时间戳,接收端根据时间戳做同步,避免画面卡顿
- 错误处理:添加UDP发送失败的重试逻辑,以及编码管线异常捕获,保证程序稳定性
内容的提问来源于stack exchange,提问作者bigq
相关产品推荐
相关产品推荐

