在C#WebApi中直播FLV

Jak*_*ote 16 c# flv ffmpeg stream asp.net-web-api

目前我有一个使用webapi的工作实时流.通过直接从ffmpeg接收flv流并使用PushStreamContent将其直接发送到客户端.如果在流启动时网页已经打开,则此工作完全正常.问题是当我打开另一个页面或刷新此页面时,您无法再查看该流(该流仍然正在被发送到客户端).我认为这是由于流的开头缺少一些东西,但我不知道该怎么办.任何指针都将非常感激.

客户端读取流的代码

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

用于接收流然后发送到客户端的代码

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

jga*_*fin 2

我从未使用过 FLV 或仔细研究过视频格式

大多数文件格式都是结构化的,尤其是视频格式。它们包含帧(即完整或部分屏幕截图,具体取决于压缩格式)。

如果您在开始向新订阅者流式传输时能够达到特定帧,那么您应该非常幸运。因此,当他们开始接收流时,他们无法识别格式,因为帧是部分的。

您可以在维基百科文章中阅读更多 FLV 帧。这很可能是你的问题。

一个简单的尝试是尝试保存第一个订阅者连接时从流媒体服务器收到的初始标头。

就像是:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

..它允许您将标头发送到下一个连接的订阅者:

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();
}
Run Code Online (Sandbox Code Playgroud)

完成后,祈祷接收者忽略部分帧。如果没有,您需要分析流以确定下一帧在​​哪里。

为此,您需要执行以下操作:

  1. 创建一个BytesLeftToNextFrame变量。
  2. 将接收到的数据包头存储在缓冲区中
  3. 将“有效负载大小”位转换为 int
  4. 将 重置BytesLeftToNextFrame 为解析的值
  5. 倒计时,直到下次您应该阅读标题。

最后,当新客户端连接时,在知道下一帧到达之前不要开始流式传输。

伪代码:

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.
}
Run Code Online (Sandbox Code Playgroud)