【问题标题】:Live FLV streaming in C# WebApiC# WebApi 中的实时 FLV 流式传输
【发布时间】:2016-04-16 00:58:39
【问题描述】:

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

【问题讨论】:

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


    【解决方案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】:

    我喜欢这段代码,因为它在处理异步编程时显示了一个基本错误

    while (true)
    {
    
    }
    

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

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

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

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

    但是因为(订阅者的)循环需要同时为所有订阅者传输流,它被 await 关键字卡住了

    这就是为什么重新加载/新订阅者冻结整个事情(新连接 = 新订阅者)

    结论:整个for循环都应该在一个Task里面。任务需要等到服务器将流发送给所有订阅者。只有这样它才应该使用 ContinueWith 继续到 while 循环(这就是它这样调用的原因,对吧?)

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

    theSubscriber.WriteAsync
    

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

    【讨论】:

    • 同步循环?你在说什么?这是一个循环。时期。你在其中做什么很重要。他确实在循环中使用了await,因此它是完全有效的。
    【解决方案3】:

    我从未使用过 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. 倒计时,直到您下次阅读标题。

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

    伪代码:

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

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多