227 lines
6.6 KiB
C#
227 lines
6.6 KiB
C#
|
|
using System.Windows;
|
|||
|
|
using Gst;
|
|||
|
|
using GstVideo;
|
|||
|
|
using Unosquare.FFME;
|
|||
|
|
using Task = System.Threading.Tasks.Task;
|
|||
|
|
using UserControl = System.Windows.Controls.UserControl;
|
|||
|
|
|
|||
|
|
namespace OperationControl.Controls
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// Interaction logic for GstVideoView.xaml
|
|||
|
|
/// </summary>
|
|||
|
|
public partial class GstVideoView : UserControl
|
|||
|
|
{
|
|||
|
|
private Pipeline? _pipeline;
|
|||
|
|
private Element? _videoSink;
|
|||
|
|
private Bus? _bus;
|
|||
|
|
|
|||
|
|
#region DP: Host e Port
|
|||
|
|
|
|||
|
|
public string Host
|
|||
|
|
{
|
|||
|
|
get => (string)GetValue(HostProperty);
|
|||
|
|
set => SetValue(HostProperty, value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static readonly DependencyProperty HostProperty =
|
|||
|
|
DependencyProperty.Register(
|
|||
|
|
nameof(Host),
|
|||
|
|
typeof(string),
|
|||
|
|
typeof(GstVideoView),
|
|||
|
|
new PropertyMetadata("0.0.0.0", OnEndpointChanged));
|
|||
|
|
|
|||
|
|
public int Port
|
|||
|
|
{
|
|||
|
|
get => (int)GetValue(PortProperty);
|
|||
|
|
set => SetValue(PortProperty, value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static readonly DependencyProperty PortProperty =
|
|||
|
|
DependencyProperty.Register(
|
|||
|
|
nameof(Port),
|
|||
|
|
typeof(int),
|
|||
|
|
typeof(GstVideoView),
|
|||
|
|
new PropertyMetadata(5000, OnEndpointChanged));
|
|||
|
|
|
|||
|
|
private static void OnEndpointChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|||
|
|
{
|
|||
|
|
var view = (GstVideoView)d;
|
|||
|
|
// Se já estiver carregado, reinicia o pipeline com IP/porta novos
|
|||
|
|
if (view.IsLoaded)
|
|||
|
|
{
|
|||
|
|
//view.RestartPipeline();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
|
|||
|
|
public string SourceUrl { get; set; } = "udp://0.0.0.0:5000";
|
|||
|
|
|
|||
|
|
public GstVideoView()
|
|||
|
|
{
|
|||
|
|
InitializeComponent();
|
|||
|
|
|
|||
|
|
InitGStreamer();
|
|||
|
|
|
|||
|
|
Loaded += (_, _) =>
|
|||
|
|
{
|
|||
|
|
//if (VideoPanel.IsHandleCreated == false)
|
|||
|
|
// VideoPanel.CreateControl();
|
|||
|
|
};
|
|||
|
|
Unloaded += (_, _) => Stop();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void InitGStreamer()
|
|||
|
|
{
|
|||
|
|
//Gst.Application.Init();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void StartPipeline()
|
|||
|
|
{
|
|||
|
|
if (_pipeline != null)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
// pipeline simples só pra testar: videotestsrc → videoconvert → autovideosink
|
|||
|
|
_pipeline = new Pipeline();
|
|||
|
|
|
|||
|
|
var src = ElementFactory.Make("videotestsrc", "src");
|
|||
|
|
var conv = ElementFactory.Make("videoconvert", "conv");
|
|||
|
|
var sink = ElementFactory.Make("autovideosink", "sink");
|
|||
|
|
|
|||
|
|
if (src is null || conv is null || sink is null)
|
|||
|
|
throw new Exception("Falha ao criar algum elemento do pipeline.");
|
|||
|
|
|
|||
|
|
_pipeline.Add(src);
|
|||
|
|
_pipeline.Add(conv);
|
|||
|
|
_pipeline.Add(sink);
|
|||
|
|
|
|||
|
|
if (!src.Link(conv) || !conv.Link(sink))
|
|||
|
|
throw new Exception("Falha ao linkar o pipeline.");
|
|||
|
|
|
|||
|
|
// >>> AQUI entra a correção do Bus (ver seção 2) <<<
|
|||
|
|
|
|||
|
|
_pipeline.SetState(State.Playing);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Start2()
|
|||
|
|
{
|
|||
|
|
if (_pipeline != null)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
//if (!VideoPanel.IsHandleCreated)
|
|||
|
|
// VideoPanel.CreateControl();
|
|||
|
|
|
|||
|
|
// Monta o pipeline de recepção (ajuste caps se precisar)
|
|||
|
|
string pipeDesc =
|
|||
|
|
$"udpsrc port={Port} " +
|
|||
|
|
"caps = application/x-rtp, media=video, encoding-name=H264, payload=96 " +
|
|||
|
|
"! rtph264depay ! h264parse ! avdec_h264 ! videoconvert " +
|
|||
|
|
"! d3d11videosink name=vsink sync=false";
|
|||
|
|
|
|||
|
|
// Cria pipeline a partir da string
|
|||
|
|
_pipeline = new Pipeline(new Gst.Internal.PipelineHandle(1, false));
|
|||
|
|
|
|||
|
|
if (_pipeline is null)
|
|||
|
|
throw new InvalidOperationException("Falha ao criar pipeline GStreamer.");
|
|||
|
|
|
|||
|
|
// Pega o sink pelo nome
|
|||
|
|
_videoSink = _pipeline.GetChildByName("vsink") as Element;
|
|||
|
|
|
|||
|
|
if (_videoSink is null)
|
|||
|
|
throw new InvalidOperationException("Não foi possível encontrar o elemento 'vsink' no pipeline.");
|
|||
|
|
|
|||
|
|
// Faz o cast para VideoOverlay
|
|||
|
|
var overlay = _videoSink as VideoOverlay;
|
|||
|
|
|
|||
|
|
if (overlay is null)
|
|||
|
|
throw new InvalidOperationException("O sink não implementa VideoOverlay.");
|
|||
|
|
|
|||
|
|
// Passa o handle da janela do Panel para o sink
|
|||
|
|
//var hwnd = VideoPanel.Handle;
|
|||
|
|
//overlay.SetWindowHandle((nuint)hwnd);
|
|||
|
|
|
|||
|
|
// (Opcional) configurar bus para logar erros
|
|||
|
|
_bus = _pipeline.GetBus();
|
|||
|
|
_bus.AddSignalWatch();
|
|||
|
|
_bus.OnMessage += OnBusMessage;
|
|||
|
|
|
|||
|
|
// Play!
|
|||
|
|
_pipeline.SetState(State.Playing);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Stop2()
|
|||
|
|
{
|
|||
|
|
if (_pipeline == null)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
_pipeline.SetState(State.Null);
|
|||
|
|
|
|||
|
|
if (_bus != null)
|
|||
|
|
{
|
|||
|
|
_bus.OnMessage -= OnBusMessage;
|
|||
|
|
_bus.Dispose();
|
|||
|
|
_bus = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_pipeline.Dispose();
|
|||
|
|
_pipeline = null;
|
|||
|
|
_videoSink = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnBusMessage(object? sender, Bus.MessageSignalArgs e)
|
|||
|
|
{
|
|||
|
|
switch (e.Message.Type)
|
|||
|
|
{
|
|||
|
|
case MessageType.Error:
|
|||
|
|
//e.Message.ParseError(out var err, out var debug);
|
|||
|
|
//Console.WriteLine($"[GST ERROR] {err}: {debug}");
|
|||
|
|
Console.WriteLine($"[GST ERROR] {e.Message}");
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case MessageType.Eos:
|
|||
|
|
Console.WriteLine("[GST] End of stream");
|
|||
|
|
// Se quiser, reinicia ou dá Stop();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public async Task Start()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (Media.IsOpen)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
// Abre o stream (udp, rtsp, http…)
|
|||
|
|
await Media.Open(new System.Uri(SourceUrl));
|
|||
|
|
|
|||
|
|
await Media.Play();
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
System.Diagnostics.Debug.WriteLine($"[FFME] Erro ao iniciar vídeo: {ex}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async void Stop()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (Media.IsOpen)
|
|||
|
|
{
|
|||
|
|
await Media.Stop();
|
|||
|
|
await Media.Close();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
System.Diagnostics.Debug.WriteLine($"[FFME] Erro ao parar vídeo: {ex}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|