2016-01-11 18 views
16

Attualmente ho un live streaming funzionante utilizzando webapi. Ricevendo un flusso flv direttamente da ffmpeg e inviandolo direttamente al client utilizzando PushStreamContent. Funziona perfettamente se la pagina web è già aperta all'avvio del flusso. Il problema è quando apro un'altra pagina o aggiorno questa pagina non è più possibile visualizzare il flusso (lo stream è ancora inviato al client). Penso che sia dovuto a qualcosa che manca dall'inizio del flusso, ma non sono sicuro di cosa fare. Qualsiasi suggerimento sarebbe molto apprezzato.Flusso live FLV in C# WebApi

Codice per il cliente la lettura flusso

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(); 
    } 
} 

Codice per la ricezione di stream e poi inviarli 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); 
     } 
    } 
} 

risposta

2

non ho mai usato FLV o studiato formati video da vicino

La maggior parte dei formati di file sono strutturati, in particolare i formati video. Contengono frame (cioè schermate complete o parziali a seconda del formato di compressione).

Si dovrebbe essere davvero fortunati se si riesce a colpire un frame specifico quando si avvia lo streaming al nuovo sottoscrittore. Quindi quando iniziano a ricevere il flusso non possono identificare il formato come il frame è parziale.

È possibile leggere più fotogrammi FLV in wikipedia article. Questo è molto probabilmente il tuo problema.

Un tentativo semplice sarebbe provare a salvare l'intestazione iniziale che si riceve dal server di streaming quando si connette il primo sottoscrittore.

Qualcosa di simile:

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; 
    } 
} 

.. che permette di inviare l'intestazione al successivo collegamento abbonato:

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 volta fatto, prega che il ricevitore ignorerà fotogrammi parziali. In caso contrario, è necessario analizzare il flusso per identificare dove si trova il frame successivo.

Per fare questo è necessario fare qualcosa di simile:

  1. Creare una variabile BytesLeftToNextFrame.
  2. Conservare l'intestazione del pacchetto ricevuto in un buffer
  3. Convertire i bit "dimensione del payload" per un int
  4. Ripristinare il BytesLeftToNextFrame al valore analizzato
  5. conto alla rovescia fino alla prossima volta che si dovrebbe leggere un colpo di testa.

Infine, quando un nuovo client si connette, non avviare lo streaming finché non si è certi che il frame successivo arrivi.

Pseudo codice:

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. 
} 
+0

Trovato un semplice lettore di metadati FLV: http://johndyer.name/flash-flv-meta-reader-in-net-c/ – jgauffin

1

Io non sono un esperto in streaming, ma sembra che si dovrebbe chiudere flusso allora tutto i dati saranno scritti

await theSubscriber.WriteAsync(bytes, 0, bytesRec); 

Come se ne parla in WebAPI StreamContent vs PushStreamContent

{ 
    // After save we close the stream to signal that we are done writing. 
    xDoc.Save(stream); 
    stream.Close(); 
} 
+0

Il problema è che questo è un flusso dal vivo e voglio tenerlo vivo quando nuove persone si connettono –

-2

Mi piace questo CODICE perché dimostra un errore fondamentale quando si tratta di programmazione asincrona

while (true) 
{ 

} 

questo è un ciclo sincronizzato, che si loop il più velocemente possibile .. ogni secondo può eseguire migliaia di volte (a seconda del software availabe e risorse hardware)

await theSubscriber.WriteAsync(bytes, 0, bytesRec); 

questo è un comando asincrono (se non fosse abbastanza chiaro) che esegue in un thread diverso (mentre ciclo representes l'esecuzione thread principale)

ora ... al fine di rendere il ciclo while per attendere al comando asincrona usiamo attendono ... suona bene (altrimenti il ​​ciclo while eseguirà migliaia di volte, l'esecuzione di innumerevoli comandi asincroni)

ma perché il loop (di abbonati) necessità di trasmettere il flusso per tutti gli abbonati simulatanly di venire stucked dalla parola chiave attendono

ECCO PERCHÉ RELOAD/nuovo abbonato CONGELARE lA COSA TUTTO (nuova connessione = nuovo abbonato)

conclusione: l'intero ciclo for deve essere all'interno di un'attività. l'attività deve attendere fino a quando il server invia lo streaming a tutti gli abbonati. SOLO THEN dovrebbe continuare al ciclo while con ContinueWith (è per questo che si chiama così, vero?)

quindi ...il comando di scrittura necessità di ottenere l'esecuzione senza attendere parola chiave

theSubscriber.WriteAsync 

il ciclo foreach dovrebbe usare un compito che continua con il ciclo while dopo che è stato fatto

+0

un ciclo sincronizzato? Di cosa stai parlando? È un ciclo. periodo. E 'importante quello che fai in esso. Usa 'await' nel ciclo, quindi è perfettamente valido. – jgauffin