2021-02-08 16:51:19 +08:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
namespace BililiveRecorder.Flv.Pipeline.Rules
|
|
|
|
{
|
|
|
|
public class UpdateTimestampRule : ISimpleProcessingRule
|
|
|
|
{
|
2021-03-08 23:31:24 +08:00
|
|
|
private const string TS_STORE_KEY = "Timestamp_Store_Key";
|
2021-02-08 16:51:19 +08:00
|
|
|
|
|
|
|
private const int JUMP_THRESHOLD = 50;
|
|
|
|
|
|
|
|
private const int AUDIO_DURATION_FALLBACK = 22;
|
|
|
|
private const int AUDIO_DURATION_MIN = 20;
|
|
|
|
private const int AUDIO_DURATION_MAX = 24;
|
|
|
|
|
|
|
|
private const int VIDEO_DURATION_FALLBACK = 33;
|
|
|
|
private const int VIDEO_DURATION_MIN = 15;
|
|
|
|
private const int VIDEO_DURATION_MAX = 50;
|
|
|
|
|
2021-03-09 00:50:13 +08:00
|
|
|
public void Run(FlvProcessingContext context, Action next)
|
2021-02-08 16:51:19 +08:00
|
|
|
{
|
2021-03-09 00:50:13 +08:00
|
|
|
next();
|
2021-02-08 16:51:19 +08:00
|
|
|
|
|
|
|
var ts = context.SessionItems.ContainsKey(TS_STORE_KEY) ? context.SessionItems[TS_STORE_KEY] as TimestampStore ?? new TimestampStore() : new TimestampStore();
|
|
|
|
context.SessionItems[TS_STORE_KEY] = ts;
|
|
|
|
|
2021-03-09 00:50:13 +08:00
|
|
|
foreach (var action in context.Actions)
|
2021-02-08 16:51:19 +08:00
|
|
|
{
|
|
|
|
if (action is PipelineDataAction dataAction)
|
|
|
|
{
|
|
|
|
this.SetDataTimestamp(dataAction.Tags, ts, context);
|
|
|
|
}
|
2021-03-08 23:31:24 +08:00
|
|
|
else if (action is PipelineEndAction endAction)
|
|
|
|
{
|
|
|
|
var tag = endAction.Tag;
|
|
|
|
var diff = tag.Timestamp - ts.LastOriginal;
|
|
|
|
if (diff < 0 || diff > JUMP_THRESHOLD)
|
|
|
|
{
|
|
|
|
tag.Timestamp = ts.NextTimestampTarget;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tag.Timestamp -= ts.CurrentOffset;
|
|
|
|
}
|
|
|
|
}
|
2021-02-08 16:51:19 +08:00
|
|
|
else if (action is PipelineNewFileAction)
|
|
|
|
{
|
|
|
|
ts.Reset();
|
|
|
|
}
|
|
|
|
else if (action is PipelineScriptAction s)
|
|
|
|
{
|
|
|
|
s.Tag.Timestamp = 0;
|
|
|
|
ts.Reset();
|
|
|
|
}
|
|
|
|
else if (action is PipelineHeaderAction h)
|
|
|
|
{
|
|
|
|
if (h.VideoHeader != null)
|
|
|
|
h.VideoHeader.Timestamp = 0;
|
|
|
|
if (h.AudioHeader != null)
|
|
|
|
h.AudioHeader.Timestamp = 0;
|
|
|
|
ts.Reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-08 23:31:24 +08:00
|
|
|
private void SetDataTimestamp(IReadOnlyList<Tag> tags, TimestampStore ts, FlvProcessingContext context)
|
2021-02-08 16:51:19 +08:00
|
|
|
{
|
|
|
|
var diff = tags[0].Timestamp - ts.LastOriginal;
|
|
|
|
if (diff < 0)
|
|
|
|
{
|
2021-03-08 23:31:24 +08:00
|
|
|
context.AddComment(new ProcessingComment(CommentType.TimestampJump, "时间戳问题:变小, Diff: " + diff));
|
|
|
|
ts.CurrentOffset = tags[0].Timestamp - ts.NextTimestampTarget;
|
2021-02-08 16:51:19 +08:00
|
|
|
}
|
|
|
|
else if (diff > JUMP_THRESHOLD)
|
|
|
|
{
|
2021-03-08 23:31:24 +08:00
|
|
|
context.AddComment(new ProcessingComment(CommentType.TimestampJump, "时间戳问题:间隔过大, Diff: " + diff));
|
|
|
|
ts.CurrentOffset = tags[0].Timestamp - ts.NextTimestampTarget;
|
2021-02-08 16:51:19 +08:00
|
|
|
}
|
|
|
|
|
2021-03-08 23:31:24 +08:00
|
|
|
ts.LastOriginal = tags.Last().Timestamp;
|
2021-02-08 16:51:19 +08:00
|
|
|
|
|
|
|
foreach (var tag in tags)
|
|
|
|
tag.Timestamp -= ts.CurrentOffset;
|
|
|
|
|
2021-03-08 23:31:24 +08:00
|
|
|
ts.NextTimestampTarget = this.CalculateNewTarget(tags);
|
2021-02-08 16:51:19 +08:00
|
|
|
}
|
|
|
|
|
2021-03-08 23:31:24 +08:00
|
|
|
private int CalculateNewTarget(IReadOnlyList<Tag> tags)
|
2021-02-08 16:51:19 +08:00
|
|
|
{
|
2021-04-15 23:05:29 +08:00
|
|
|
// 有可能出现只有音频或只有视频的情况
|
|
|
|
int video = 0, audio = 0;
|
|
|
|
|
|
|
|
if (tags.Any(x => x.Type == TagType.Video))
|
|
|
|
video = CalculatePerChannel(tags, VIDEO_DURATION_FALLBACK, VIDEO_DURATION_MAX, VIDEO_DURATION_MIN, TagType.Video);
|
2021-02-08 16:51:19 +08:00
|
|
|
|
2021-03-08 23:31:24 +08:00
|
|
|
if (tags.Any(x => x.Type == TagType.Audio))
|
2021-04-15 23:05:29 +08:00
|
|
|
audio = CalculatePerChannel(tags, AUDIO_DURATION_FALLBACK, AUDIO_DURATION_MAX, AUDIO_DURATION_MIN, TagType.Audio);
|
|
|
|
|
|
|
|
return Math.Max(video, audio);
|
2021-02-08 16:51:19 +08:00
|
|
|
|
2021-03-08 23:31:24 +08:00
|
|
|
static int CalculatePerChannel(IReadOnlyList<Tag> tags, int fallback, int max, int min, TagType type)
|
|
|
|
{
|
|
|
|
var sample = tags.Where(x => x.Type == type).Take(2).ToArray();
|
|
|
|
int durationPerTag;
|
|
|
|
if (sample.Length != 2)
|
|
|
|
{
|
|
|
|
durationPerTag = fallback;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
durationPerTag = sample[1].Timestamp - sample[0].Timestamp;
|
2021-02-08 16:51:19 +08:00
|
|
|
|
2021-03-08 23:31:24 +08:00
|
|
|
if (durationPerTag < min || durationPerTag > max)
|
|
|
|
durationPerTag = fallback;
|
|
|
|
}
|
2021-02-08 16:51:19 +08:00
|
|
|
|
2021-03-08 23:31:24 +08:00
|
|
|
return durationPerTag + tags.Last(x => x.Type == type).Timestamp;
|
|
|
|
}
|
2021-02-08 16:51:19 +08:00
|
|
|
}
|
|
|
|
|
2021-03-08 23:31:24 +08:00
|
|
|
private class TimestampStore
|
2021-02-08 16:51:19 +08:00
|
|
|
{
|
2021-03-08 23:31:24 +08:00
|
|
|
public int NextTimestampTarget;
|
2021-02-08 16:51:19 +08:00
|
|
|
|
2021-03-08 23:31:24 +08:00
|
|
|
public int LastOriginal;
|
2021-02-08 16:51:19 +08:00
|
|
|
|
|
|
|
public int CurrentOffset;
|
|
|
|
|
|
|
|
public void Reset()
|
|
|
|
{
|
2021-03-08 23:31:24 +08:00
|
|
|
this.NextTimestampTarget = 0;
|
2021-02-08 16:51:19 +08:00
|
|
|
this.LastOriginal = 0;
|
|
|
|
this.CurrentOffset = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|