English version: https://medium.com/p/700649d36a6d
Este proyecto surgió de la necesidad de conectarse a distintas cámaras del mercado cuya forma de conexión es a través de RTSP, básicamente la necesidad era poder realizar la conexión y sacar fotos de las cámaras.
En Windows gran parte de esta conexión se puede hacer a través de EMGUCV pero en Xamarin resultó un poco mas complicado de implementar sin acudir al uso de librerías licenciadas.
No profundizaré mucho en el protocolo de conexión, básicamente es implementado usando TCP para los datos de control y UDP o TCP para los datos de Video y Audio, su puerto por defecto es el 554, de acuerdo al fabricante de la cámara se disponibilizan distintas urls para el acceso al streaming.
En el proceso de investigación nos encontramos con varias alternativas como:
- RtspClientSharp (https://github.com/BogdanovKirill/RtspClientSharp): resulto súper conveniente para acceder a los frames de forma rápida pero nos encontramos con distintos inconvenientes al momento de hacer decode de los frames en Android
- LibVLCSharp (https://github.com/videolan/libvlcsharp): fue una de las alternativas más rápidas de implementar y de hacer la conexión con la cámara, existe un delay de un par de segundos al momento de la visualización por lo que seguimos buscando otras alternativas
Con las distintas alternativas nos topabamos con otras dificultades que nos hacían buscar la implementación de otras librerías para complementar las deficiencias o carencias que tenían y en el proceso nos conseguimos con el siguiente proyecto: https://github.com/alexeyvasilyev/rtsp-client-android.
El proyecto resolvía varios de los inconvenientes con los que nos encontramos, permitía la conexión RTSP/RTSPS por TCP, permite la decodificación del H264 y el audio, soporta autenticación y soporta la mayoría de las cámaras IP así que hice un proyecto de ejemplo en Xamarin Forms.
El proyecto se divide en 3 partes:
- El proyecto de código base
- El proyecto de Android
- El proyecto de biding a la librería elegida
Lo primero fue generar una interfaz que encapsularía la información de la cámara:
using Xamarin.Forms;
namespace Xamarin.Rtsp.Renderers
{
public class CameraView : View
{
public string Url { get; set; }
public string User { get; set; }
public string Password { get; set; }
public CameraView()
{
}
}
}Y seguidamente en el proyecto de Android se generó un renderer para usar la librería:
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Rtsp.Droid.Renderers;
using Xamarin.Rtsp.Renderers;
[assembly: ExportRenderer(typeof(CameraView), typeof(CameraViewRenderer))]
namespace Xamarin.Rtsp.Droid.Renderers
{
public class CameraViewRenderer : ViewRenderer<CameraView, SurfaceView>, ISurfaceHolderCallback
{
SurfaceView currentView;
Surface surface;
int width;
int height;
Context _context;
RtspClient rtspClient;
public CameraViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<CameraView> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
currentView = new SurfaceView(_context) {
LayoutParameters =
new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent)
};
currentView.Holder.AddCallback(this);
base.SetNativeControl(currentView);
rtspClient = new RtspClient();//DependencyService.Get<IRtspClient>();
if (e.NewElement != null)
{
rtspClient.Url = e.NewElement.Url ?? "";
rtspClient.Username = e.NewElement.User ?? "";
rtspClient.Password = e.NewElement.Password ?? "";
}
//rtspClient.StartStreaming(mSurfaceView);
}
}
public void SurfaceChanged(ISurfaceHolder holder, [GeneratedEnum] Format format, int width, int height)
{
}
public void SurfaceCreated(ISurfaceHolder holder)
{
if (rtspClient != null)
{
Task.Run(async () =>
{
rtspClient.StartStreaming(currentView);
});
}
}
public void SurfaceDestroyed(ISurfaceHolder holder)
{
}
}
}Seguidamente se creó un cliente RTSP que es el encargado de crear el socket junto a los demás datos de la cámara para hacer el llamado a la librería:
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Com.Alexvas.Rtsp;
using Java.Interop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Rtsp.Droid.Core;
using Xamarin.Rtsp.Renderers;
[assembly: Dependency(typeof(Xamarin.Rtsp.Droid.Renderers.RtspClient))]
namespace Xamarin.Rtsp.Droid.Renderers
{
public class RtspClient : IRtspClient<SurfaceView>
{
Com.Alexvas.Rtsp.RtspClient localClient;
SurfaceView _surfaceView;
public string Url { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public async Task<bool> StartStreaming(SurfaceView surfaceView)
{
try
{
_surfaceView = surfaceView;
Uri uri = new Uri(this.Url);
var socket = await NetUtils.CreateSocketAndConnect(uri.Host, 554, 5000);
int port = uri.Port;
Java.Util.Concurrent.Atomic.AtomicBoolean rtspStopped = new Java.Util.Concurrent.Atomic.AtomicBoolean(false);
var listener = new RtspListener(surfaceView.Holder.Surface, surfaceView.Width, surfaceView.Height);
localClient = new Com.Alexvas.Rtsp.RtspClient.Builder(socket, this.Url, rtspStopped, listener)
.RequestVideo(true).RequestAudio(false).WithDebug(true).WithCredentials(this.Username, this.Password).Build();
localClient.Execute();
NetUtils.CloseSocket(socket);
}
catch (Exception ex)
{
}
//NetUtils.CloseSocket(socket);
return false;
}
}
}Si quieres ver el proyecto en mayor detalle puedes ir a la siguiente url:
rretamal/Xamarin-RTSPExample project to show an RTSP streaming on Xamarin Forms Project based on the following repository…github.com