Codemaru
1.0.0.0
QR 코드, 모바일 랜딩 페이지, vCard 연락처 저장과 명함 디자인을 한 화면에서 제공하는 디지털 명함 서비스입니다.
Toggle main menu visibility
로딩중...
검색중...
일치하는것 없음
FfmpegHlsService.cs
이 파일의 문서화 페이지로 가기
1
11
12
using
System.Diagnostics;
13
using
System.IO;
14
using
System.Text.RegularExpressions;
15
using
Microsoft.Extensions.Hosting;
16
using
Microsoft.Extensions.Logging;
17
using
Microsoft.Extensions.Options;
18
using
Microsoft.AspNetCore.Hosting;
// IWebHostEnvironment
19
using
Codemaru.Models
;
20
21
namespace
Codemaru.Services
22
{
31
public
sealed
class
FfmpegHlsService
: BackgroundService
32
{
41
private
readonly ILogger<FfmpegHlsService>
_log
;
50
private
readonly
FfmpegOptions
_opt
;
59
private
readonly IReadOnlyList<StreamConfig>
_streams
;
68
private
readonly IWebHostEnvironment
_env
;
69
70
// 스트림명 → ffmpeg 프로세스
79
private
readonly Dictionary<string, Process>
_procs
=
new
();
80
81
// 스트림명 → 최근 m3u8 시퀀스/타임스탬프
90
private
readonly Dictionary<string, long>
_lastSeq
=
new
();
99
private
readonly Dictionary<string, DateTime>
_lastWrite
=
new
();
100
109
private
static
readonly Regex
MediaSeqRegex
=
new
(
@"#EXT-X-MEDIA-SEQUENCE:(\d+)"
, RegexOptions.Compiled);
110
151
public
FfmpegHlsService
(
152
ILogger<FfmpegHlsService> log,
153
IOptions<FfmpegOptions> opt,
154
IOptions<List<StreamConfig>> streams,
155
IWebHostEnvironment env)
156
{
157
_log
= log;
158
_opt
= opt.Value;
159
_streams
= streams.Value;
160
_env
= env;
161
}
162
187
protected
override
async Task
ExecuteAsync
(CancellationToken ct)
188
{
189
Directory.CreateDirectory(
GetOutputRoot
());
190
191
foreach
(var s
in
_streams
)
192
{
193
TryStartOne
(s);
194
InitWatchStats
(s);
195
}
196
197
// 주기적으로 프로세스 상태와 m3u8 갱신을 점검
198
while
(!ct.IsCancellationRequested)
199
{
200
foreach
(var s
in
_streams
)
201
{
202
// 1) ffmpeg 죽었으면 재시작
203
if
(!
_procs
.TryGetValue(s.Name, out var p) || p.HasExited)
204
{
205
_log
.LogWarning(
"ffmpeg 재시작(프로세스 종료 감지): {Name}"
, s.Name);
206
TryStartOne
(s);
207
InitWatchStats
(s);
208
continue
;
209
}
210
211
// 2) m3u8 갱신 감시(워치독)
212
var (
m3u8Path
, _) =
GetOutputPaths
(s);
213
if
(File.Exists(
m3u8Path
))
214
{
215
var (
seq
, write) =
ReadSeqAndTime
(
m3u8Path
);
216
var lastSeq =
_lastSeq
[s.Name];
217
var lastWrite =
_lastWrite
[s.Name];
218
219
// 시퀀스 증가 또는 수정 시간이 갱신되면 정상
220
if
(
seq
> lastSeq || write > lastWrite)
221
{
222
_lastSeq
[s.Name] = Math.Max(
seq
, lastSeq);
223
_lastWrite
[s.Name] = write > lastWrite ? write : lastWrite;
224
}
225
else
226
{
227
// 일정 시간 이상 변화가 없으면 재시작
228
var idleSec = (DateTime.UtcNow - lastWrite.ToUniversalTime()).TotalSeconds;
229
var idleLimit = Math.Max(
_opt
.WatchdogIdleSeconds,
_opt
.HlsSegmentSeconds *
_opt
.HlsListSize);
230
if
(idleSec >= idleLimit)
231
{
232
_log
.LogWarning(
"m3u8 갱신 정지 감지({Idle}s >= {Limit}s): {Name} → ffmpeg 재시작"
,
233
idleSec, idleLimit, s.Name);
234
SafeRestart
(s);
235
InitWatchStats
(s);
236
continue
;
237
}
238
}
239
}
240
else
241
{
242
// 파일이 없으면 ffmpeg가 아직 시작 중이거나 실패→ 잠시 후 다시 확인
243
_log
.LogDebug(
"m3u8 미존재: {Name} ({Path})"
, s.Name,
m3u8Path
);
244
}
245
}
246
247
await Task.Delay(TimeSpan.FromSeconds(
_opt
.WatchdogIntervalSeconds), ct);
248
}
249
}
250
275
public
override
Task
StopAsync
(CancellationToken cancellationToken)
276
{
277
foreach
(var p
in
_procs
.Values)
278
{
279
try
280
{
281
if
(!p.HasExited)
282
{
283
p.Kill(entireProcessTree:
true
);
284
p.WaitForExit(2000);
285
}
286
}
287
catch
{
/* ignore */
}
288
}
289
_procs
.Clear();
290
return
Task.CompletedTask;
291
}
292
309
private
string
GetOutputRoot
()
310
{
311
// appsettings에 상대경로("wwwroot\\hls")인 경우 ContentRoot 기준으로 절대경로화
312
return
Path.IsPathRooted(
_opt
.OutputRoot)
313
? _opt.OutputRoot
314
: Path.Combine(
_env
.ContentRootPath,
_opt
.OutputRoot);
315
}
316
341
private
(
string
m3u8Path
,
string
segFmt)
GetOutputPaths
(
StreamConfig
s)
342
{
343
var outRoot =
GetOutputRoot
();
344
var outDir = Path.Combine(outRoot, s.
Name
);
345
Directory.CreateDirectory(outDir);
346
347
// 가독성 및 캐싱 안전성 위해 seg_%05d.ts 권장
348
var
m3u8Path
= Path.Combine(outDir,
"index.m3u8"
);
349
var segFmt = Path.Combine(outDir,
"seg_%05d.ts"
);
350
return
(
m3u8Path
, segFmt);
351
}
352
369
private
void
SafeRestart
(
StreamConfig
s)
370
{
371
if
(
_procs
.TryGetValue(s.
Name
, out var old))
372
{
373
try
374
{
375
if
(!old.HasExited)
376
{
377
old.Kill(entireProcessTree:
true
);
378
old.WaitForExit(2000);
379
}
380
}
381
catch
{
/* ignore */
}
382
}
383
TryStartOne
(s);
384
}
385
402
private
void
TryStartOne
(
StreamConfig
s)
403
{
404
var (
m3u8Path
, segFmt) =
GetOutputPaths
(s);
405
406
// 입력 옵션: TCP 강제(안정), 타임스탬프 보정
407
var inputOpts =
"-rtsp_transport tcp -rtsp_flags prefer_tcp -fflags +genpts -use_wallclock_as_timestamps 1"
;
408
409
// 인코딩/복사 분기
410
var v = (s.VideoCodec ??
_opt
.VideoCodec).ToLowerInvariant();
411
var a = (s.AudioCodec ??
_opt
.AudioCodec).ToLowerInvariant();
412
413
// 비디오 파트
414
// - 트랜스코딩: GOP 고정 (hls_time 2초라면 gop≈50@25fps / 60@30fps 등으로 맞추세요)
415
var videoPart =
416
v ==
"copy"
417
?
"-c:v copy -bsf:v h264_mp4toannexb"
418
:
"-c:v libx264 -preset veryfast -tune zerolatency -profile:v high -level 4.1 -pix_fmt yuv420p "
+
419
"-g 50 -keyint_min 50 -sc_threshold 0 -force_key_frames \"expr:gte(t,n_forced*2)\""
;
420
421
// 오디오 파트
422
var audioPart =
423
a ==
"an"
?
"-an"
424
: $
"-c:a {a} -b:a {_opt.AudioBitrate} -ar {_opt.AudioRate} -ac {_opt.AudioChannels}"
;
425
426
// HLS 파트(독립 세그먼트 + 프로그램 시간 + 삭제)
427
var hlsPart =
string
.Join(
' '
,
new
[]
428
{
429
"-f hls"
,
430
$
"-hls_time {_opt.HlsSegmentSeconds}"
,
431
$
"-hls_list_size {_opt.HlsListSize}"
,
432
"-hls_flags delete_segments+program_date_time+independent_segments"
,
433
$
"-hls_segment_filename \"{segFmt}\""
,
434
$
"\"{m3u8Path}\""
435
});
436
437
// 전체 인자 조합
438
var args =
string
.Join(
' '
,
new
[]
439
{
440
inputOpts,
441
$
"-i \"{s.RtspUrl}\""
,
442
videoPart,
443
audioPart,
444
hlsPart
445
});
446
447
var psi =
new
ProcessStartInfo
448
{
449
FileName =
_opt
.Path,
450
Arguments = args,
451
UseShellExecute =
false
,
452
RedirectStandardError =
true
,
453
RedirectStandardOutput =
true
,
454
CreateNoWindow =
true
455
};
456
457
var proc =
new
Process { StartInfo = psi, EnableRaisingEvents =
true
};
458
proc.ErrorDataReceived += (_, e) =>
459
{
460
if
(!
string
.IsNullOrEmpty(e.Data))
461
_log
.LogInformation(
"[{name}] {line}"
, s.
Name
, e.Data);
462
};
463
proc.OutputDataReceived += (_, e) =>
464
{
465
if
(!
string
.IsNullOrEmpty(e.Data))
466
_log
.LogDebug(
"[{name}] {line}"
, s.
Name
, e.Data);
467
};
468
proc.Exited += (_, __) =>
469
{
470
_log
.LogWarning(
"ffmpeg 종료 감지: {Name} (ExitCode={Code})"
, s.
Name
, proc.ExitCode);
471
};
472
473
if
(proc.Start())
474
{
475
proc.BeginErrorReadLine();
476
proc.BeginOutputReadLine();
477
_procs
[s.
Name
] = proc;
478
_log
.LogInformation(
"ffmpeg 시작: {Name} → {Out}"
, s.
Name
, Path.GetDirectoryName(
m3u8Path
));
479
}
480
else
481
{
482
_log
.LogError(
"ffmpeg 시작 실패: {Name}"
, s.
Name
);
483
}
484
}
485
502
private
void
InitWatchStats
(
StreamConfig
s)
503
{
504
_lastSeq
[s.
Name
] = -1;
505
_lastWrite
[s.
Name
] = DateTime.MinValue;
506
}
507
532
private
(
long
seq
, DateTime lastWrite)
ReadSeqAndTime
(
string
m3u8Path
)
533
{
534
try
535
{
536
var text = File.ReadAllText(
m3u8Path
);
537
var m =
MediaSeqRegex
.Match(text);
538
var
seq
= m.Success ?
long
.Parse(m.Groups[1].Value) : -1;
539
var info =
new
FileInfo(
m3u8Path
);
540
return
(
seq
, info.LastWriteTimeUtc);
541
}
542
catch
(Exception ex)
543
{
544
_log
.LogDebug(
"m3u8 읽기 실패: {Path} - {Msg}"
,
m3u8Path
, ex.Message);
545
return
(-1, DateTime.MinValue);
546
}
547
}
548
}
549
}
Codemaru.Models
Definition
CardHistoryEntry.cs:1
Codemaru.Services
Definition
CardHybridCircuitSession.cs:8
Codemaru.Models.FfmpegOptions
Definition
FfmpegOptions.cs:17
Codemaru.Models.StreamConfig
Definition
FfmpegOptions.cs:138
Codemaru.Models.StreamConfig.Name
string Name
Definition
FfmpegOptions.cs:147
Codemaru.Services.FfmpegHlsService.ExecuteAsync
override async Task ExecuteAsync(CancellationToken ct)
Definition
FfmpegHlsService.cs:187
Codemaru.Services.FfmpegHlsService.TryStartOne
void TryStartOne(StreamConfig s)
Definition
FfmpegHlsService.cs:402
Codemaru.Services.FfmpegHlsService.InitWatchStats
void InitWatchStats(StreamConfig s)
Definition
FfmpegHlsService.cs:502
Codemaru.Services.FfmpegHlsService._opt
readonly FfmpegOptions _opt
Definition
FfmpegHlsService.cs:50
Codemaru.Services.FfmpegHlsService.StopAsync
override Task StopAsync(CancellationToken cancellationToken)
Definition
FfmpegHlsService.cs:275
Codemaru.Services.FfmpegHlsService._env
readonly IWebHostEnvironment _env
Definition
FfmpegHlsService.cs:68
Codemaru.Services.FfmpegHlsService.SafeRestart
void SafeRestart(StreamConfig s)
Definition
FfmpegHlsService.cs:369
Codemaru.Services.FfmpegHlsService.GetOutputRoot
string GetOutputRoot()
Definition
FfmpegHlsService.cs:309
Codemaru.Services.FfmpegHlsService._lastWrite
readonly Dictionary< string, DateTime > _lastWrite
Definition
FfmpegHlsService.cs:99
Codemaru.Services.FfmpegHlsService.FfmpegHlsService
FfmpegHlsService(ILogger< FfmpegHlsService > log, IOptions< FfmpegOptions > opt, IOptions< List< StreamConfig > > streams, IWebHostEnvironment env)
Definition
FfmpegHlsService.cs:151
Codemaru.Services.FfmpegHlsService.ReadSeqAndTime
long DateTime lastWrite ReadSeqAndTime(string m3u8Path)
Definition
FfmpegHlsService.cs:532
Codemaru.Services.FfmpegHlsService.m3u8Path
string m3u8Path
Definition
FfmpegHlsService.cs:341
Codemaru.Services.FfmpegHlsService._procs
readonly Dictionary< string, Process > _procs
Definition
FfmpegHlsService.cs:79
Codemaru.Services.FfmpegHlsService.MediaSeqRegex
static readonly Regex MediaSeqRegex
Definition
FfmpegHlsService.cs:109
Codemaru.Services.FfmpegHlsService._lastSeq
readonly Dictionary< string, long > _lastSeq
Definition
FfmpegHlsService.cs:90
Codemaru.Services.FfmpegHlsService.seq
long seq
Definition
FfmpegHlsService.cs:532
Codemaru.Services.FfmpegHlsService._streams
readonly IReadOnlyList< StreamConfig > _streams
Definition
FfmpegHlsService.cs:59
Codemaru.Services.FfmpegHlsService.GetOutputPaths
string string segFmt GetOutputPaths(StreamConfig s)
Definition
FfmpegHlsService.cs:341
Codemaru.Services.FfmpegHlsService._log
readonly ILogger< FfmpegHlsService > _log
Definition
FfmpegHlsService.cs:41
Services
FfmpegHlsService.cs
다음에 의해 생성됨 :
1.17.0