nreco - Transmisión FLV en vivo en C#WebApi
nreco videoconverter (3)
Nunca utilicé FLV o estudié formatos de video muy de cerca
La mayoría de los formatos de archivo están estructurados, especialmente los formatos de video. Contienen marcos (es decir, capturas de pantalla completas o parciales dependiendo del formato de compresión).
Deberías ser realmente afortunado si logras alcanzar un marco específico cuando comienzas a transmitir al nuevo suscriptor. Por lo tanto, cuando comienzan a recibir la secuencia, no pueden identificar el formato, ya que frame es parcial.
Puede leer más cuadros FLV en el artículo de Wikipedia . Este es probablemente tu problema.
Un simple intento sería tratar de guardar el encabezado inicial que recibe del servidor de transmisión cuando se conecta el primer suscriptor.
Algo como:
static byte _header = new byte[9]; //signature, version, flags, headerSize
public void YourStreamMethod()
{
int bytesRec = handler.Receive(bytes);
if (!_headerIsStored)
{
//store header
Buffer.BlockCopy(bytes, 0, _header, 0, 9);
_headerIsStored = true;
}
}
.. que le permite enviar el encabezado al siguiente suscriptor de conexión:
private async Task WriteToStream( Stream arg1, HttpContent arg2, TransportContext arg3 )
{
// send the FLV header
arg1.Write(_header, 0, 9);
Startup.AddSubscriber( arg1 );
await Task.Yield();
}
Una vez hecho esto, ore para que el receptor ignore los cuadros parciales. Si no es así, debe analizar la secuencia para identificar dónde está el siguiente cuadro.
Para hacer eso, necesitas hacer algo como esto:
- Crea una variable
BytesLeftToNextFrame
. - Almacenar el encabezado del paquete recibido en un buffer
- Convierta los bits de "tamaño de carga útil" a una int
- Restablezca
BytesLeftToNextFrame
al valor analizado - Cuenta regresiva hasta la próxima vez que deba leer un encabezado.
Finalmente, cuando un nuevo cliente se conecta, no comience a transmitir hasta que sepa que llega el siguiente marco.
Pseudo código:
var bytesLeftToNextFrame = 0;
while (true)
{
bytes = new byte[8024000];
int bytesRec = handler.Receive(bytes);
foreach (var subscriber in Startup.Subscribers.ToList())
{
var theSubscriber = subscriber;
try
{
if (subscriber.IsNew && bytesLeftToNextFrame < bytesRec)
{
//start from the index where the new frame starts
await theSubscriber.WriteAsync( bytes, bytesLeftToNextFrame, bytesRec - bytesLeftToNextFrame);
subscriber.IsNew = false;
}
else
{
//send everything, since we''ve already in streaming mode
await theSubscriber.WriteAsync( bytes, 0, bytesRec );
}
}
catch
{
Startup.Subscribers.Remove(theSubscriber);
}
}
//TODO: check if the current frame is done
// then parse the next header and reset the counter.
}
Actualmente tengo una transmisión en vivo que funciona usando webapi. Al recibir una flv stream directamente desde ffmpeg y enviarla directamente al cliente utilizando PushStreamContent. Esto funciona perfectamente bien si la página web ya está abierta cuando comienza la transmisión. El problema es que cuando abro otra página o actualizo esta página, ya no se puede ver la transmisión (la transmisión aún se envía correctamente al cliente). Creo que se debe a que falta algo desde el inicio de la transmisión, pero no estoy seguro de qué hacer. Cualquier puntero sería muy apreciado.
Código para flujo de lectura del cliente
public class VideosController : ApiController
{
public HttpResponseMessage Get()
{
var response = Request.CreateResponse();
response.Content = new PushStreamContent(WriteToStream, new MediaTypeHeaderValue("video/x-flv"));
return response;
}
private async Task WriteToStream( Stream arg1, HttpContent arg2, TransportContext arg3 )
{
//I think metadata needs to be written here but not sure how
Startup.AddSubscriber( arg1 );
await Task.Yield();
}
}
Código para recibir la transmisión y luego enviarla al cliente
while (true)
{
bytes = new byte[8024000];
int bytesRec = handler.Receive(bytes);
foreach (var subscriber in Startup.Subscribers.ToList())
{
var theSubscriber = subscriber;
try
{
await theSubscriber.WriteAsync( bytes, 0, bytesRec );
}
catch
{
Startup.Subscribers.Remove(theSubscriber);
}
}
}
ME GUSTA ESTE CÓDIGO PORQUE DEMUESTRA UN ERROR FUNDAMENTAL cuando se trata de programación asíncrona
while (true)
{
}
este es un bucle sincronizado, que se bucles lo más rápido posible ... cada segundo se puede ejecutar miles de veces (dependiendo de los recursos de software y hardware disponibles)
await theSubscriber.WriteAsync( bytes, 0, bytesRec );
este es un comando asincrónico (si no fue lo suficientemente claro) que se ejecuta en un subproceso DIFERENTE (mientras que el ciclo representa la ejecución del subproceso principal)
ahora ... para hacer que el bucle while espere al comando async usamos await ... suena bien (o sino el bucle while se ejecutará miles de veces, ejecutando innumerables comandos async)
PERO porque el bucle (de suscriptores) necesita transmitir la transmisión para todos los suscriptores simulaneamente queda atrapado por la palabra clave await
ES POR ESO QUE RELOAD / NEW SUSSCRIBER CONGELA TODO (nueva conexión = nuevo suscriptor)
conclusión : todo el bucle for debe estar dentro de una Tarea. la tarea debe esperar hasta que el servidor envíe la transmisión a todos los suscriptores. SÓLO ENTONCES debería continuar en el ciclo while con ContinueWith (es por eso que llamó así, ¿verdad?)
así que ... el comando de escritura necesita ejecutar sin esperar la palabra clave
theSubscriber.WriteAsync
el bucle foreach debería usar una tarea que continúe con el bucle while después de que se haya completado
No soy un experto en la transmisión, pero parece que deberías cerrar la transmisión y luego todos los datos se escribirán
await theSubscriber.WriteAsync( bytes, 0, bytesRec );
Como se menciona en WebAPI StreamContent vs PushStreamContent
{
// After save we close the stream to signal that we are done writing.
xDoc.Save(stream);
stream.Close();
}