Skip to content

Using FFmpegVideoEndPoint, converting BGR to RGB causes the CPU to overheat. #1600

@344089386

Description

@344089386

The function is currently functioning properly.
However, using ConvertBGRtoRGB will cause the CPU usage to be excessively high.

using SIPSorcery.Net;
using SIPSorcery.Sys;
using SIPSorceryMedia.Abstractions;
using SIPSorceryMedia.FFmpeg;
using System.Net;

namespace Kvm.Control
{
    /// <summary>
    /// 解析WebRTC视频数据
    /// </summary>
    public class WebRtcHelper : IDisposable
    {
        private RTCPeerConnection pc;
        public Action<RawImage> OnVideoSinkDecodedSampleFaster { get; set; }

        public Action<byte[], uint, uint, int, VideoPixelFormatsEnum> OnVideoSinkDecodedSample { get; set; }
        public Action<RTCIceCandidate> OnIceCandidateReceived { get; set; }
        public Action<string> OnAnswerReceived { get; set; }
        public Action OnDisConnected { get; set; }
        public Action OnConnected {  get; set; }
        public bool IsConnected { get; private set; } = false;

        private DateTime _lastRenderTime = DateTime.MinValue; // 上次渲染时间
        private const int MinFrameIntervalMs = 33; // 限制最大30帧/秒 (1000/30 ≈ 33ms)

        public WebRtcHelper()
        {
            var ffmpegLib = Path.Combine(AppContext.BaseDirectory, "ffmpeg", "bin");
            SIPSorceryMedia.FFmpeg.FFmpegInit.Initialise(FfmpegLogLevelEnum.AV_LOG_VERBOSE, ffmpegLib);
        }

        public async Task CreatePeerConnection(string sdpOffer)
        {
            var videoEP = new FFmpegVideoEndPoint();
            videoEP.RestrictFormats(format => format.Codec == VideoCodecsEnum.H264);

            videoEP.OnVideoSinkDecodedSampleFaster += (RawImage rawImage) =>
            {
                var now = DateTime.UtcNow;
                if ((now - _lastRenderTime).TotalMilliseconds < MinFrameIntervalMs)
                    return;
                _lastRenderTime = now;

                OnVideoSinkDecodedSampleFaster?.Invoke(rawImage);
            };

            RTCConfiguration config = new RTCConfiguration
            {
                X_BindAddress = IPAddress.Any,
            };
            pc = new RTCPeerConnection(config, portRange: new PortRange(20000, 20050));
            pc.onicecandidate += (candidate) =>
            {
                if (candidate != null)
                {
                    OnIceCandidateReceived?.Invoke(candidate);
                }
            };

            MediaStreamTrack videoTrack = new MediaStreamTrack(videoEP.GetVideoSinkFormats(), MediaStreamStatusEnum.RecvOnly);
            pc.addTrack(videoTrack);

            pc.OnVideoFrameReceived += videoEP.GotVideoFrame;
            pc.OnVideoFormatsNegotiated += (formats) => videoEP.SetVideoSinkFormat(formats.First());

            // 监听连接状态
            pc.onconnectionstatechange += async (state) =>
            {
                KvmLog.Information($"PeerConnection state: {state}");
                if(state == RTCPeerConnectionState.connected)
                {
                    IsConnected = true;
                    OnConnected?.Invoke();
                }
                else if (state == RTCPeerConnectionState.failed || state == RTCPeerConnectionState.closed)
                {
                    IsConnected = false;
                    pc.Close("ice disconnection");
                    await videoEP.CloseVideo();
                    OnDisConnected?.Invoke();
                }
            };

            // 设置Answer
            pc.setRemoteDescription(new RTCSessionDescriptionInit { type = RTCSdpType.offer, sdp = sdpOffer });
            var answer = pc.createAnswer(null);
            await pc.setLocalDescription(answer);

            OnAnswerReceived?.Invoke(answer.sdp);
        }

        public void Dispose()
        {
            pc?.Dispose();
        }
    }
}
using Serilog.Extensions.Logging;
using SIPSorceryMedia.Abstractions;
using System.Drawing.Imaging;

namespace Kvm.Control
{
    internal class WebRTCMediaPlayer : KvmMediaPlayerBase
    {
        private JanusHelper janusHelper;
        private WebRtcHelper webRtcHelper;
        private PictureBox picBox;
        private Bitmap _cachedBitmap;  // 复用Bitmap对象,优化性能

        public override bool Connected => janusHelper != null && janusHelper.IsConnected && webRtcHelper != null && webRtcHelper.IsConnected ? true : false;

        public WebRTCMediaPlayer(AspectRatio ratio) : base(ratio)
        {
            if (KvmLog.Logger != null)
                SIPSorcery.LogFactory.Set(new SerilogLoggerFactory(KvmLog.Logger));

            janusHelper = new JanusHelper();
            webRtcHelper = new WebRtcHelper();
            picBox = new PictureBox();
        }

        public override async void Play(string ip, int port, System.Windows.Forms.Control control)
        {
            try
            {
                Url = $"ws://{ip}:{port}/janus/ws?auth_token={AuthToken}";

                webRtcHelper.OnIceCandidateReceived = (candidate) =>
                {
                    janusHelper.SendTrickleMessage(candidate);
                };
                webRtcHelper.OnAnswerReceived = (answerMessage) =>
                {
                    janusHelper.SendAnswerMessage(answerMessage);
                };
                #region MyRegion
                webRtcHelper.OnVideoSinkDecodedSampleFaster = async (rawImage) =>
                {
                    if (control.IsHandleCreated)
                    {
                        try
                        {
                            #region Bitmap 渲染方式
                            control.BeginInvoke(() =>
                            {
                                if (!control.IsHandleCreated || control.Width <= 0 || control.Height <= 0 || !control.Visible)
                                {
                                    return;
                                }

                                if (rawImage.PixelFormat == SIPSorceryMedia.Abstractions.VideoPixelFormatsEnum.Rgb)
                                {
                                    Bitmap bmpImage = ConvertBGRtoRGB(rawImage);
                                    //Bitmap bmpImage = new Bitmap(rawImage.Width, rawImage.Height, rawImage.Stride, PixelFormat.Format24bppRgb, rawImage.Sample);

                                    if (bmpImage != null)
                                    {
                                        picBox.Image = bmpImage;

                                        if (aspectRatio == AspectRatio.Fill)
                                        {
                                            picBox.SizeMode = PictureBoxSizeMode.StretchImage; // 拉伸填满
                                            picBox.Dock = DockStyle.Fill;
                                        }
                                        else if (aspectRatio == AspectRatio.BestFit)
                                        {
                                            picBox.SizeMode = PictureBoxSizeMode.Zoom; // 等比例缩放,不超出控件边界
                                            picBox.Dock = DockStyle.Fill;
                                        }
                                    }
                                }
                            });

                            #endregion
                        }
                        catch (Exception ex)
                        {
                            KvmLog.Error("WebRTC 视频渲染错误:" + ex.Message);
                        }
                    }
                };
                #endregion

                webRtcHelper.OnConnected = () =>
                {
                    OnConnected?.Invoke();
                };
                webRtcHelper.OnDisConnected = () =>
                {
                    janusHelper.Dispose();
                    OnDisConnected?.Invoke();
                };

                janusHelper.OnConnected = async (sdpOffer) =>
                {
                    await webRtcHelper.CreatePeerConnection(sdpOffer);

                    control.BeginInvoke(new Action(() =>
                    {
                        picBox.Enabled = false;
                        picBox.Dock = DockStyle.Fill;

                        control.Controls.Add(picBox);
                        control.SizeChanged += (s, e) =>
                        {
                            picBox.Size = control.Size;
                            picBox.Anchor = AnchorStyles.None;
                            int x = (picBox.Parent.ClientSize.Width - picBox.Width) / 2;
                            int y = (picBox.Parent.ClientSize.Height - picBox.Height) / 2;

                            picBox.Location = new Point(x, y);

                            UpdateAspectRatio();
                        };
                    }));
                };
                janusHelper.OnDisConnected = () =>
                {
                    webRtcHelper.Dispose();
                    OnDisConnected?.Invoke();
                };
                await janusHelper.ConnectToJanus(Url);
            }
            catch (Exception ex)
            {
                Dispose();
                throw new Exception($"kvm media play error: {ex.Message}");
            }
        }

        private unsafe Bitmap ConvertBGRtoRGB(RawImage rawImage)
        {
            // 分辨率改变时重建Bitmap
            if (_cachedBitmap == null || _cachedBitmap?.Width != rawImage.Width || _cachedBitmap?.Height != rawImage.Height)
            {
                _cachedBitmap?.Dispose();
                _cachedBitmap = new Bitmap(rawImage.Width, rawImage.Height, PixelFormat.Format24bppRgb);
            }

            BitmapData bmpData = _cachedBitmap.LockBits(
                new Rectangle(0, 0, rawImage.Width, rawImage.Height),
                ImageLockMode.WriteOnly,
                PixelFormat.Format24bppRgb);

            try
            {
                byte* srcPtr = (byte*)rawImage.Sample;
                byte* dstPtr = (byte*)bmpData.Scan0;
                int width = rawImage.Width;
                int height = rawImage.Height;
                int srcStride = rawImage.Stride;
                int dstStride = bmpData.Stride;

                // 逐行交换 BGR -> RGB
                for (int y = 0; y < height; y++)
                {
                    byte* srcRow = srcPtr + y * srcStride;
                    byte* dstRow = dstPtr + y * dstStride;
                    for (int x = 0; x < width; x++)
                    {
                        int srcOff = x * 3;
                        int dstOff = x * 3;
                        dstRow[dstOff] = srcRow[srcOff + 2]; // B -> R
                        dstRow[dstOff + 1] = srcRow[srcOff + 1]; // G -> G
                        dstRow[dstOff + 2] = srcRow[srcOff];     // R -> B
                    }
                }
            }
            finally
            {
                _cachedBitmap.UnlockBits(bmpData);
            }

            return _cachedBitmap;
        }

        public override void UpdateAspectRatio()
        {
            
        }

        public override void Dispose()
        {
            _cachedBitmap?.Dispose();
            janusHelper?.Dispose();
            webRtcHelper?.Dispose();
            base.Dispose();
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions