2016-01-11 23 views
16

Obecnie mam działający strumień na żywo za pomocą webapi. Otrzymując strumień flv bezpośrednio z ffmpeg i wysyłając go bezpośrednio do klienta za pomocą PushStreamContent. Działa to doskonale, jeśli strona internetowa jest już otwarta po uruchomieniu strumienia. Problem polega na tym, że kiedy otwieram kolejną stronę lub odświeżam tę stronę, nie możesz już oglądać strumienia (strumień jest nadal przesyłany do klienta). Myślę, że to z powodu czegoś, czego brakowało na początku strumienia, ale nie jestem pewien, co robić. Wszelkie wskazówki byłyby bardzo mile widziane.Live FLV streaming w C# WebApi

Kod do czytania klienta strumień

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

kodeksu do odbierania strumienia, a następnie wysłanie do klienta

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

Odpowiedz

2

nigdy nie używałem FLV lub studiował formatów wideo Dokładnie

Większość formatów plików jest uporządkowana, szczególnie w formatach wideo. Zawierają ramki (to znaczy pełne lub częściowe zrzuty ekranu w zależności od formatu kompresji).

Powinieneś być naprawdę szczęśliwy, jeśli uda ci się trafić określoną ramkę, gdy zaczniesz przesyłać strumieniowo do nowego subskrybenta. Dlatego gdy zaczynają otrzymywać strumień, nie mogą zidentyfikować formatu, ponieważ ramka jest częściowa.

Możesz przeczytać więcej ramek FLV w wikipedia article. To jest najprawdopodobniej twój problem.

Prostą próbą byłoby próba zapisania początkowego nagłówka otrzymanego z serwera strumieniowego po nawiązaniu połączenia przez pierwszego subskrybenta.

Coś jak:

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

.. który umożliwia wysyłanie nagłówka do następnego abonenta łączącego:

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

Gdy to zrobisz, módl się, że odbiornik będzie ignorować częściowych ramek. Jeśli nie, musisz przeanalizować strumień, aby określić, gdzie znajduje się następna klatka.

Aby to zrobić, że trzeba zrobić coś takiego:

  1. Tworzenie zmiennej BytesLeftToNextFrame.
  2. Store nagłówek pakietu otrzymał w buforze
  3. Convert „wielkość” Ładunek bitów int
  4. Przywracanie stanu BytesLeftToNextFrame do analizowanej wartości
  5. Odliczanie do następnego razu należy przeczytać nagłówek.

Wreszcie, gdy nowy klient się połączy, nie rozpoczynaj przesyłania strumieniowego, dopóki nie będziesz wiedział, że pojawi się następna klatka.

Pseudo kod:

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

Znaleziono prosty czytnik metadanych FLV: http://johndyer.name/flash-flv-meta-reader-in-net-c/ – jgauffin

1

Nie jestem ekspertem w streamingu, ale wygląda jak należy zamknąć strumień następnie wszystko dane zostaną zapisane:

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

Podobno wspomina o WebAPI StreamContent vs PushStreamContent

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

Problemem jest to strumień na żywo i chcę utrzymać go na żywo, gdy nowe osoby połączyć –

-2

Lubię ten Kodeksu, ponieważ to pokazuje podstawowy błąd gdy ma do czynienia z programowaniem asynchronicznym

while (true) 
{ 

} 

To jest zsynchronizowana pętla, że ​​pętle się tak szybko, jak to możliwe .. co drugi może wykonać tysiące razy (w zależności od dostępnej oprogramowania i zasobów sprzętowych)

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

to polecenie asynchroniczny (jeśli to nie było wystarczająco jasne), które są wykonywane w innym wątku (podczas pętla reprezentuje wykonanie głównego wątku)

teraz ... aby pętla while czekała na komendę async używamy czekając na ... brzmi dobrze (inaczej pętla while będzie wykonywana tysiące razy, wykonujące niezliczone asynchroniczny polecenia)

ale dlatego, że pętla (abonentów) potrzebują do przesyłania strumienia dla wszystkich abonentów simulatanly to get stucked słowem kluczowym czekają

Dlatego RELOAD/nowy abonent zamrozić cała sprawa (nowe połączenie = nowy subskrybent)

Podsumowanie: cała pętla for powinna znajdować się wewnątrz zadania. Zadanie musi poczekać, aż serwer wyśle ​​strumień do wszystkich subskrybentów. TYLKO WTEDY powinien kontynuować pętlę while z Kontynuując z (dlatego tak się to nazywa, prawda?)

, więc ...polecenie zapisu trzeba się wykonać bez czekają hasła

theSubscriber.WriteAsync 

pętla foreach powinno stosować zadanie kontynuowania pętli while po to się robi

+0

synchronizowane pętli? O czym mówisz? To pętla. Kropka. To, co w nim robisz, jest ważne. Używa 'await' w pętli, więc jest całkowicie poprawny. – jgauffin