流式 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
编辑
- 我试图限制分辨率和比特率,但我不能那样配置相机,还有其他应用程序在使用它。相机没有任何流 url 允许此配置
- 更改 api 响应的内容类型后,我已经能够从我的代码中获取图像。我改变了:
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);
}
描述:
我有一个 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
编辑
- 我试图限制分辨率和比特率,但我不能那样配置相机,还有其他应用程序在使用它。相机没有任何流 url 允许此配置
- 更改 api 响应的内容类型后,我已经能够从我的代码中获取图像。我改变了:
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);
}