2021-04-20 20:41:26 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
namespace BililiveRecorder.Flv.Pipeline.Rules
|
|
|
|
|
{
|
|
|
|
|
public class UpdateTimestampOffsetRule : ISimpleProcessingRule
|
|
|
|
|
{
|
|
|
|
|
private static readonly ProcessingComment comment1 = new ProcessingComment(CommentType.Unrepairable, "GOP 内音频或视频时间戳不连续");
|
2021-04-22 22:40:40 +08:00
|
|
|
|
private static readonly ProcessingComment comment2 = new ProcessingComment(CommentType.Unrepairable, "出现了无法计算偏移量的音视频偏移");
|
2021-04-20 20:41:26 +08:00
|
|
|
|
|
|
|
|
|
public void Run(FlvProcessingContext context, Action next)
|
|
|
|
|
{
|
|
|
|
|
context.PerActionRun(this.RunPerAction);
|
|
|
|
|
next();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool CheckIfNormal(IEnumerable<Tag> data) => !data.Any2((a, b) => a.Timestamp > b.Timestamp);
|
|
|
|
|
|
|
|
|
|
private IEnumerable<PipelineAction?> RunPerAction(FlvProcessingContext context, PipelineAction action)
|
|
|
|
|
{
|
|
|
|
|
if (action is PipelineDataAction data)
|
|
|
|
|
{
|
|
|
|
|
var isNormal = this.CheckIfNormal(data.Tags);
|
|
|
|
|
if (isNormal)
|
|
|
|
|
{
|
|
|
|
|
yield return data;
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(this.CheckIfNormal(data.Tags.Where(x => x.Type == TagType.Audio)) && this.CheckIfNormal(data.Tags.Where(x => x.Type == TagType.Video))))
|
|
|
|
|
{
|
2021-04-22 22:40:40 +08:00
|
|
|
|
// 音频或视频自身就有问题,没救了
|
|
|
|
|
yield return PipelineDisconnectAction.Instance;
|
2021-04-20 20:41:26 +08:00
|
|
|
|
context.AddComment(comment1);
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-04-22 22:40:40 +08:00
|
|
|
|
var oc = new OffsetCalculator();
|
2021-04-20 20:41:26 +08:00
|
|
|
|
|
2021-04-22 22:40:40 +08:00
|
|
|
|
foreach (var tag in data.Tags)
|
|
|
|
|
oc.AddTag(tag);
|
2021-04-20 20:41:26 +08:00
|
|
|
|
|
2021-04-22 22:40:40 +08:00
|
|
|
|
if (oc.Calculate(out var videoOffset))
|
2021-04-20 20:41:26 +08:00
|
|
|
|
{
|
2021-04-22 22:40:40 +08:00
|
|
|
|
if (videoOffset != 0)
|
2021-04-20 20:41:26 +08:00
|
|
|
|
{
|
2021-04-22 22:40:40 +08:00
|
|
|
|
context.AddComment(new ProcessingComment(CommentType.TimestampOffset, $"音视频时间戳偏移, D: {videoOffset}"));
|
2021-04-20 20:41:26 +08:00
|
|
|
|
|
2021-04-22 22:40:40 +08:00
|
|
|
|
foreach (var tag in data.Tags)
|
|
|
|
|
if (tag.Type == TagType.Video)
|
|
|
|
|
tag.Timestamp += videoOffset;
|
|
|
|
|
}
|
2021-04-20 20:41:26 +08:00
|
|
|
|
|
2021-04-22 22:40:40 +08:00
|
|
|
|
yield return data;
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-04-27 21:30:49 +08:00
|
|
|
|
context.AddComment(comment2);
|
|
|
|
|
yield return PipelineDisconnectAction.Instance;
|
2021-04-22 22:40:40 +08:00
|
|
|
|
yield break;
|
|
|
|
|
}
|
2021-04-20 20:41:26 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
yield return action;
|
|
|
|
|
}
|
2021-04-22 22:40:40 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 音视频偏差量计算
|
|
|
|
|
/// </summary>
|
|
|
|
|
private class OffsetCalculator
|
|
|
|
|
{
|
|
|
|
|
/* 算法思路和原理
|
|
|
|
|
* 设定作调整的为视频帧,参照每个视频帧左右(左为前、右为后)的音频帧的时间戳
|
|
|
|
|
* 计算出最多和最少能符合“不小于前面的帧并且不大于后面的帧”的要求的偏移量
|
|
|
|
|
* 如果当前偏移量比总偏移量要求更严,则使用当前偏移量范围作为总偏移量范围
|
|
|
|
|
* */
|
|
|
|
|
|
|
|
|
|
private Tag? lastAudio = null;
|
|
|
|
|
private readonly Stack<Tag> tags = new Stack<Tag>();
|
|
|
|
|
|
|
|
|
|
private int maxOffset = int.MaxValue;
|
|
|
|
|
private int minOffset = int.MinValue;
|
|
|
|
|
|
|
|
|
|
public void AddTag(Tag tag)
|
|
|
|
|
{
|
|
|
|
|
if (tag.Type == TagType.Audio)
|
|
|
|
|
{
|
|
|
|
|
this.ReduceOffsetRange(this.lastAudio, tag);
|
|
|
|
|
this.lastAudio = tag;
|
|
|
|
|
}
|
|
|
|
|
else if (tag.Type == TagType.Video)
|
|
|
|
|
{
|
|
|
|
|
this.tags.Push(tag);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
throw new ArgumentException("unexpected tag type");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Calculate(out int offset)
|
|
|
|
|
{
|
2021-04-27 21:30:49 +08:00
|
|
|
|
{
|
|
|
|
|
var last = this.lastAudio;
|
|
|
|
|
this.lastAudio = null;
|
|
|
|
|
this.ReduceOffsetRange(last, null);
|
|
|
|
|
}
|
2021-04-22 22:40:40 +08:00
|
|
|
|
|
|
|
|
|
if (this.minOffset == this.maxOffset)
|
|
|
|
|
{
|
2021-04-27 21:30:49 +08:00
|
|
|
|
// 理想情况允许偏移范围只有一个值
|
2021-04-22 22:40:40 +08:00
|
|
|
|
offset = this.minOffset;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2021-04-27 21:30:49 +08:00
|
|
|
|
else if (this.minOffset < this.maxOffset)
|
2021-04-22 22:40:40 +08:00
|
|
|
|
{
|
2021-04-27 21:30:49 +08:00
|
|
|
|
// 允许偏移的值是一个范围
|
|
|
|
|
if (this.minOffset != int.MinValue)
|
|
|
|
|
{
|
|
|
|
|
if (this.maxOffset != int.MaxValue)
|
|
|
|
|
{
|
|
|
|
|
// 有一个有效范围,取平均值
|
|
|
|
|
offset = (int)(((long)this.minOffset + this.maxOffset) / 2L);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// 无效最大偏移,以最小偏移为准
|
|
|
|
|
offset = this.minOffset + 1;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (this.maxOffset != int.MaxValue)
|
|
|
|
|
{
|
|
|
|
|
// 无效最小偏移,以最大偏移为准
|
|
|
|
|
offset = this.maxOffset - 1;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// 无效结果
|
|
|
|
|
offset = 0;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-04-22 22:40:40 +08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-04-27 21:30:49 +08:00
|
|
|
|
// 范围无效
|
2021-04-22 22:40:40 +08:00
|
|
|
|
offset = 0;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ReduceOffsetRange(Tag? leftAudio, Tag? rightAudio)
|
|
|
|
|
{
|
|
|
|
|
while (this.tags.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var video = this.tags.Pop();
|
|
|
|
|
|
|
|
|
|
if (leftAudio is not null)
|
|
|
|
|
{
|
|
|
|
|
var min = leftAudio.Timestamp - video.Timestamp;
|
|
|
|
|
if (this.minOffset < min)
|
|
|
|
|
this.minOffset = min;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rightAudio is not null)
|
|
|
|
|
{
|
|
|
|
|
var max = rightAudio.Timestamp - video.Timestamp;
|
|
|
|
|
if (this.maxOffset > max)
|
|
|
|
|
this.maxOffset = max;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-04-20 20:41:26 +08:00
|
|
|
|
}
|
|
|
|
|
}
|