流式 RTSP(AspNet 5 API、FFMPEG、Angular 10、videoJs)

Streaming RTSP (AspNet 5 API, FFMPEG, Angular 10, videoJs)

描述:

我有一个 API (ASP.Net 5) 通过 RTSP 连接到网络摄像机。摄像机发送一个用 ffmpeg 转换为 m3u8 流的 h264 流,returned 到 angular 客户端如下:

public async Task<ActionResult> GetCameraH264Stream()
{
        string deviceIp = "rtsp://[CAMERA_IP]/";
        string recordingUri = "rtsp://[USER:PASSWORD]@[CAMERA_IP]/axis-media/media.amp";
        
        string output = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".m3u8");
        var mediaInfo = await FFmpeg.GetMediaInfo(recordingUri);

        var conversionResult = FFmpeg.Conversions.New()
            .AddStream(mediaInfo.Streams)
            .SetOutput(output)
            .Start();
        
        // Allow any Cors
        Response.Headers.Add("Access-Control-Allow-Origin", "*");
        Response.Headers.Add("Cache-Control", "no-cache");
        
        // Open the file, and read the stream to return to the client
        FileStreamResult result = new FileStreamResult(System.IO.File.Open(output, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream");
        result.EnableRangeProcessing = true;
        return result;
}

如果我直接调用这个方法,浏览器会下载一个文件,我可以用 VLC 读取它。

在我的 Angular 应用程序中,我有这个组件:

app-vjs-player:

@Component({
       selector: 'app-vjs-player',
       template: '<video #target class="video-js" controls muted playsinline preload="none"> 
                  </video>',
       encapsulation: ViewEncapsulation.None,
    })
export class VjsPlayerComponent implements OnInit, OnDestroy {
  @ViewChild('target', {static: true}) target: ElementRef;
  
  @Input() options: {
      fluid: boolean,
      aspectRatio: string,
      autoplay: boolean,
      sources: {
          src: string,
          type: string,
      }[],
      vhs: {
        overrideNative: true
      },
  };
  player: videojs.Player;

  constructor(
    private elementRef: ElementRef,
  ) { }

  ngOnInit() {
    // instantiate Video.js
    this.player = videojs(this.target.nativeElement, this.options, function onPlayerReady() {
      console.log('onPlayerReady', this);
    });
    
  }

  ngOnDestroy() {
    // destroy player
    if (this.player) {
      this.player.dispose();
    }
  }
}

这个组件是这样使用的:

TS:

playerOptions = {
    fluid: false,
    aspectRatio: "16:9",
      autoplay: false,
      sources: [{
          src: 'https://localhost:44311/api/GetCameraH264Stream',
          type: 'application/x-mpegURL',
      }],
}

HTML:

<app-vjs-player #videoJs [options]="playerOptions"></app-vjs-player>

问题

这一切似乎工作得很好,直到 vjs 在 api return 流时抛出此错误:

ERROR: (CODE:4 MEDIA_ERR_SRC_NOT_SUPPORTED) The media could not be loaded, either because the server or network failed or because the format is not supported

当我打开网络开发工具时,请求状态是“已取消”,但我不知道是videojs取消它是因为无法读取文件流,还是因为API return 流.

有什么想法吗?

来源

Forwarding RTSP stream from IP Camera to Browser in ASP.NET Core

VideoJs Angular integration

Xabe.FFMPEG

编辑

FileStreamResult result = new FileStreamResult(System.IO.File.Open(output, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream");

FileStreamResult result = new FileStreamResult(System.IO.File.Open(output, FileMode.Open, FileAccess.Read, FileShare.Read), "application/x-mpegURL");

这样第一个数据包就显示出来了,但是后面的请求还是被取消了。

对响应 ContentType 的更改正在起作用(请参阅问题的最后编辑)。

看来请求被取消是因为网络慢。除了最后一个修改 ( application/octet-stream => application/x-mpegURL ),上面的所有代码都按原样工作。这是更新后的 api 方法:


public async Task<ActionResult> GetCameraH264Stream()
{
        string deviceIp = "rtsp://[CAMERA_IP]/";
        string recordingUri = "rtsp://[USER:PASSWORD]@[CAMERA_IP]/axis-media/media.amp";
        
        string output = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".m3u8");
        var mediaInfo = await FFmpeg.GetMediaInfo(recordingUri);

        var conversionResult = FFmpeg.Conversions.New()
            .AddStream(mediaInfo.Streams)
            .SetOutput(output)
            .Start();
        
        // Allow any Cors
        Response.Headers.Add("Access-Control-Allow-Origin", "*");
        Response.Headers.Add("Cache-Control", "no-cache");
        
        // Open the file, and read the stream to return to the client
        FileStreamResult result = new FileStreamResult(System.IO.File.Open(output, FileMode.Open, FileAccess.Read, FileShare.Read), "application/x-mpegURL");
        result.EnableRangeProcessing = true;
        return result;
}

编辑

似乎上面的代码会在每次发出请求时创建一个ffmpeg.exe进程。这个过程永远不会结束,因为这是来自相机的流,永远不会结束。我还不知道如何终止 ffmpeg 进程,但我已经修改了流转换检索,因此如果流已经存在,它会使用现有的 ffmpeg 进程:

public async Task<ActionResult> GetCameraH264Stream()
{
        string deviceIp = "rtsp://[CAMERA_IP]/";
        string recordingUri = "rtsp://[USER:PASSWORD]@[CAMERA_IP]/axis-media/media.amp";

        
        if (!this.cache.GetCache("camstream").TryGetValue(streamingUri, out object output)) 
        {
                output = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".m3u8");
                var mediaInfo = await FFmpeg.GetMediaInfo(streamingUri);
                var conversionResult = FFmpeg.Conversions.New()
                .AddStream(mediaInfo.Streams)
                .SetOutput((string) output)
                .Start();
                this.cache.GetCache("camstream").Set(streamingUri, output);

                // Delay until the file is created
                while (!System.IO.File.Exists((string)output))
                {
                    await Task.Delay(100);
                }
        }

        // Allow any Cors
        Response.Headers.Add("Access-Control-Allow-Origin", "*");
        Response.Headers.Add("Cache-Control", "no-cache");

        // Open the file, and read the stream to return to the client
        FileStreamResult result = new FileStreamResult(System.IO.File.Open(output, FileMode.Open, FileAccess.Read, FileShare.Read), "application/x-mpegURL");
        result.EnableRangeProcessing = true;
        return result;
}

对于 .ts 文件:


private async Task<ActionResult> GetCameraH264StreamTSFile(string tsFileName)
{
            string output = Path.Combine(Path.GetTempPath(), tsFileName);
            Response.Headers.Add("Access-Control-Allow-Origin", "*");
            return File(System.IO.File.OpenRead(output), "application/octet-stream", enableRangeProcessing: true);
}