在C#WebApi中直播FLV

时间:2016-01-11 16:17:48

标签: c# asp.net-web-api ffmpeg stream flv

目前,我有一个使用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();
    }
}

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

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

3 个答案:

答案 0 :(得分:2)

我从未使用FLV或密切研究过视频格式

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

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

您可以在wikipedia article中阅读更多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;
    }
}

..允许您将标题发送给下一个连接订阅者:

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

完成后,请祈祷接收器忽略部分帧。如果它没有,你需要分析流以确定下一帧的位置。

要做到这一点,你需要做这样的事情:

  1. 创建BytesLeftToNextFrame变量。
  2. 将收到的数据包标头存储在缓冲区中
  3. 转换"有效载荷大小"位到int
  4. BytesLeftToNextFrame重置为已解析的值
  5. 倒计时,直到您下次阅读标题。
  6. 最后,当新客户端连接时,在您知道下一帧到达之前不要开始流式传输。

    伪代码:

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

答案 1 :(得分:1)

我不是流媒体专家,但看起来你应该关闭流然后所有数据都会被写入

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

WebAPI StreamContent vs PushStreamContent

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

答案 2 :(得分:-2)

我喜欢这个代码,因为它在处理异步编程时会出现基本错误

while (true)
{

}

这是一个同步循环,它尽可能快地循环自己。每秒可以执行数千次(取决于可用的软件和硬件资源)

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

这是一个异步命令(如果它不够清楚),它在一个DIFFERENT线程中执行(while循环代表主线程执行)

现在......为了使while循环等待async命令,我们使用 await ...听起来不错(否则while循环将执行数千次,执行无数异步命令)

但是因为(订阅者)的循环需要为所有订阅者传输流 simulatanly 它被await关键字所困扰

为什么RELOAD / NEW SUBSCRIBER冻结了整个过程(新连接=新用户)

结论 :整个for循环应该在Task中。任务需要等到服务器将流发送给所有订户。那么它应该继续使用 ContinueWith 进行while循环(这就是为什么它会这样调用,对吗?)

所以...... write命令需要在没有await关键字的情况下执行

theSubscriber.WriteAsync

foreach循环应该使用在完成后继续使用while循环的任务