You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#屏幕共享:如何让Windows.Media.Transcoding输出UDP实时流?

解决Windows.Media.Transcoding实时获取编码帧并UDP组播的问题

Windows.Media.Transcoding的TranscodeAsync是封装好的文件输出流程,没法直接拿到每帧编码后的数据。要实现实时UDP组播,得绕开这个封装,自己构建编码管线并通过自定义MediaSink捕获编码后的帧数据。

核心思路

  1. 放弃MediaTranscoder的文件输出逻辑,改用MediaEncodingProfile配合自定义IMediaSink处理编码后的样本
  2. 自定义Sink中捕获每帧编码后的字节数组,直接通过UDP组播发送
  3. 将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 20:40:58