BililiveRecorder/BililiveRecorder.FlvProcessor/FlvStreamProcessor.cs

446 lines
16 KiB
C#
Raw Normal View History

2018-03-24 04:58:13 +08:00
using NLog;
using System;
2018-03-12 18:57:20 +08:00
using System.Collections.Generic;
2018-10-31 06:22:38 +08:00
using System.Collections.ObjectModel;
2018-03-19 01:05:02 +08:00
using System.IO;
using System.Linq;
2018-03-12 18:57:20 +08:00
namespace BililiveRecorder.FlvProcessor
{
2018-03-24 02:27:58 +08:00
// TODO: 添加测试
2018-10-24 13:33:43 +08:00
public class FlvStreamProcessor : IFlvStreamProcessor
2018-03-12 18:57:20 +08:00
{
2018-03-24 04:58:13 +08:00
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
2018-03-24 11:07:43 +08:00
internal const uint SEC_TO_MS = 1000; // 1 second = 1000 ms
2018-03-19 17:35:27 +08:00
internal const int MIN_BUFFER_SIZE = 1024 * 2;
2018-03-19 16:51:35 +08:00
internal static readonly byte[] FLV_HEADER_BYTES = new byte[]
{
0x46, // F
0x4c, // L
0x56, // V
0x01, // Version 1
0x05, // bit 00000 1 0 1 (have video and audio)
0x00, // ---
0x00, // |
0x00, // |
0x09, // total of 9 bytes
2018-03-19 17:35:27 +08:00
// 0x00, // ---
// 0x00, // |
// 0x00, // |
// 0x00, // the "0th" tag has a length of 0
2018-03-19 16:51:35 +08:00
};
2018-03-13 13:56:44 +08:00
2018-10-31 06:23:01 +08:00
2018-10-24 13:33:43 +08:00
private readonly object _writelock = new object();
2018-10-31 06:23:01 +08:00
private readonly List<IFlvTag> _headerTags = new List<IFlvTag>();
private readonly List<IFlvTag> _tags = new List<IFlvTag>();
private readonly MemoryStream _data = new MemoryStream();
private FileStream _targetFile;
private IFlvTag _currentTag = null;
private byte[] _leftover = null;
2018-11-07 06:07:49 +08:00
private bool _finalized = false;
2018-10-31 06:23:01 +08:00
private bool _headerParsed = false;
private bool _hasOffset = false;
private int _lasttimeRemovedTimestamp = 0;
private int _baseTimeStamp = 0;
2018-11-07 06:07:49 +08:00
private int _writeTimeStamp = 0;
2018-10-31 06:23:01 +08:00
private int _tagVideoCount = 0;
private int _tagAudioCount = 0;
2018-03-19 16:51:35 +08:00
2018-11-07 06:07:49 +08:00
public int TotalMaxTimestamp { get; private set; } = 0;
public int CurrentMaxTimestamp { get => TotalMaxTimestamp - _writeTimeStamp; }
public DateTime StartDateTime { get; private set; } = DateTime.Now;
2018-11-03 07:45:56 +08:00
2018-10-31 06:22:38 +08:00
private readonly Func<IFlvClipProcessor> funcFlvClipProcessor;
private readonly Func<byte[], IFlvMetadata> funcFlvMetadata;
private readonly Func<IFlvTag> funcFlvTag;
2018-10-31 06:23:01 +08:00
private Func<string> GetStreamFileName;
private Func<string> GetClipFileName;
2018-10-31 06:22:38 +08:00
public event TagProcessedEvent TagProcessed;
public event StreamFinalizedEvent StreamFinalized;
2019-01-17 00:28:09 +08:00
public event FlvMetadataEvent OnMetaData;
2018-10-31 06:22:38 +08:00
2018-11-07 06:07:49 +08:00
public uint ClipLengthPast { get; set; } = 20;
public uint ClipLengthFuture { get; set; } = 10;
public uint CuttingNumber { get; set; } = 10;
private EnabledFeature EnabledFeature;
private AutoCuttingMode CuttingMode;
2018-03-13 13:56:44 +08:00
2018-10-31 06:23:01 +08:00
public IFlvMetadata Metadata { get; set; } = null;
public ObservableCollection<IFlvClipProcessor> Clips { get; } = new ObservableCollection<IFlvClipProcessor>();
2018-03-19 16:51:35 +08:00
2018-10-31 06:22:38 +08:00
public FlvStreamProcessor(Func<IFlvClipProcessor> funcFlvClipProcessor, Func<byte[], IFlvMetadata> funcFlvMetadata, Func<IFlvTag> funcFlvTag)
2018-03-12 18:57:20 +08:00
{
2018-10-25 19:20:23 +08:00
this.funcFlvClipProcessor = funcFlvClipProcessor;
this.funcFlvMetadata = funcFlvMetadata;
this.funcFlvTag = funcFlvTag;
2018-10-31 06:22:38 +08:00
}
2018-11-07 06:07:49 +08:00
public IFlvStreamProcessor Initialize(Func<string> getStreamFileName, Func<string> getClipFileName, EnabledFeature enabledFeature, AutoCuttingMode autoCuttingMode)
2018-10-31 06:22:38 +08:00
{
GetStreamFileName = getStreamFileName;
GetClipFileName = getClipFileName;
EnabledFeature = enabledFeature;
2018-11-07 06:07:49 +08:00
CuttingMode = autoCuttingMode;
2018-10-31 06:22:38 +08:00
return this;
2018-03-12 18:57:20 +08:00
}
2018-11-07 06:07:49 +08:00
private void OpenNewRecordFile()
{
string path = GetStreamFileName();
logger.Debug("打开新录制文件: " + path);
try { Directory.CreateDirectory(Path.GetDirectoryName(path)); } catch (Exception) { }
_targetFile = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite);
if (_headerParsed)
{
_targetFile.Write(FlvStreamProcessor.FLV_HEADER_BYTES, 0, FlvStreamProcessor.FLV_HEADER_BYTES.Length);
_targetFile.Write(new byte[] { 0, 0, 0, 0, }, 0, 4);
2019-01-16 23:05:54 +08:00
var script_tag = funcFlvTag();
script_tag.TagType = TagType.DATA;
2019-01-17 22:29:49 +08:00
if (Metadata.ContainsKey("BililiveRecorder"))
{
// TODO: 更好的写法
(Metadata["BililiveRecorder"] as Dictionary<string, object>)["starttime"] = DateTime.UtcNow;
}
2019-01-16 23:05:54 +08:00
script_tag.Data = Metadata.ToBytes();
script_tag.WriteTo(_targetFile);
2018-11-07 06:07:49 +08:00
_headerTags.ForEach(tag => tag.WriteTo(_targetFile));
}
}
2018-03-18 18:55:28 +08:00
public void AddBytes(byte[] data)
{
2018-03-19 16:51:35 +08:00
lock (_writelock)
2018-10-24 13:33:43 +08:00
{
2018-11-07 06:07:49 +08:00
if (_finalized) { throw new InvalidOperationException("Processor Already Closed"); }
2018-10-31 06:23:01 +08:00
if (_leftover != null)
{
byte[] c = new byte[_leftover.Length + data.Length];
_leftover.CopyTo(c, 0);
data.CopyTo(c, _leftover.Length);
_leftover = null;
ParseBytes(c);
}
else
{
ParseBytes(data);
}
2018-10-24 13:33:43 +08:00
}
2018-03-19 01:05:02 +08:00
}
2018-10-31 06:23:01 +08:00
private void ParseBytes(byte[] data)
2018-03-19 01:05:02 +08:00
{
2018-10-31 06:23:01 +08:00
int position = 0;
2018-11-07 06:07:49 +08:00
if (!_headerParsed) { ReadFlvHeader(); }
2018-10-31 06:23:01 +08:00
while (position < data.Length)
{
if (_currentTag == null)
{
if (!ParseTagHead())
{
_leftover = data.Skip(position).ToArray();
break;
}
}
else
2018-11-07 06:07:49 +08:00
{ FillTagData(); }
2018-10-31 06:23:01 +08:00
}
bool ParseTagHead()
{
if (data.Length - position < 15) { return false; }
byte[] b = new byte[4];
IFlvTag tag = funcFlvTag();
// Previous Tag Size UI24
position += 4;
// TagType UI8
tag.TagType = (TagType)data[position++];
// DataSize UI24
b[1] = data[position++];
b[2] = data[position++];
b[3] = data[position++];
tag.TagSize = BitConverter.ToInt32(b.ToBE(), 0);
// Timestamp UI24
b[1] = data[position++];
b[2] = data[position++];
b[3] = data[position++];
// TimestampExtended UI8
b[0] = data[position++];
2018-11-07 06:07:49 +08:00
tag.SetTimeStamp(BitConverter.ToInt32(b.ToBE(), 0));
2018-10-31 06:23:01 +08:00
// StreamID UI24
tag.StreamId[0] = data[position++];
tag.StreamId[1] = data[position++];
tag.StreamId[2] = data[position++];
_currentTag = tag;
return true;
}
void FillTagData()
2018-03-19 16:51:35 +08:00
{
2018-10-31 06:23:01 +08:00
int toRead = Math.Min(data.Length - position, _currentTag.TagSize - (int)_data.Position);
_data.Write(buffer: data, offset: position, count: toRead);
position += toRead;
if ((int)_data.Position == _currentTag.TagSize)
{
_currentTag.Data = _data.ToArray();
_data.SetLength(0); // reset data buffer
TagCreated(_currentTag);
_currentTag = null;
}
2018-03-19 16:51:35 +08:00
}
2018-11-07 06:07:49 +08:00
void ReadFlvHeader()
2018-03-19 16:51:35 +08:00
{
2018-03-24 04:58:13 +08:00
if (data[4] != FLV_HEADER_BYTES[4])
{
// 七牛 直播云 的高端 FLV 头
logger.Debug("FLV头[4]的值是 {0}", data[4]);
data[4] = FLV_HEADER_BYTES[4];
}
2018-03-19 16:51:35 +08:00
var r = new bool[FLV_HEADER_BYTES.Length];
for (int i = 0; i < FLV_HEADER_BYTES.Length; i++)
2018-10-24 13:33:43 +08:00
{
2018-03-19 16:51:35 +08:00
r[i] = data[i] == FLV_HEADER_BYTES[i];
2018-10-24 13:33:43 +08:00
}
2018-03-19 16:51:35 +08:00
bool succ = r.All(x => x);
if (!succ)
2018-10-24 13:33:43 +08:00
{
2018-03-19 16:51:35 +08:00
throw new NotSupportedException("Not FLV Stream or Not Supported"); // TODO: custom Exception.
2018-10-24 13:33:43 +08:00
}
2018-03-19 16:51:35 +08:00
_headerParsed = true;
2018-10-31 06:23:01 +08:00
position += FLV_HEADER_BYTES.Length;
2018-03-19 01:05:02 +08:00
}
}
2018-10-31 06:23:01 +08:00
private void TagCreated(IFlvTag tag)
2018-03-19 01:05:02 +08:00
{
2018-10-31 06:23:01 +08:00
if (Metadata == null)
2018-03-19 01:05:02 +08:00
{
2018-10-31 06:23:01 +08:00
ParseMetadata();
2018-03-19 01:05:02 +08:00
}
2018-10-31 06:23:01 +08:00
else
2018-03-19 01:05:02 +08:00
{
2018-11-07 06:07:49 +08:00
if (!_hasOffset) { ParseTimestampOffset(); }
2018-10-31 06:23:01 +08:00
SetTimestamp();
2018-03-19 16:51:35 +08:00
2018-11-07 06:07:49 +08:00
if (EnabledFeature.IsRecordEnabled()) { ProcessRecordLogic(); }
if (EnabledFeature.IsClipEnabled()) { ProcessClipLogic(); }
2018-10-31 06:23:01 +08:00
TagProcessed?.Invoke(this, new TagProcessedArgs() { Tag = tag });
2018-11-07 06:07:49 +08:00
}
return;
2018-10-31 06:23:01 +08:00
void SetTimestamp()
{
if (_hasOffset)
2018-03-23 06:57:22 +08:00
{
2019-11-24 08:43:49 +08:00
tag.SetTimeStamp(tag.TimeStamp - _baseTimeStamp);
2018-11-07 06:07:49 +08:00
TotalMaxTimestamp = Math.Max(TotalMaxTimestamp, tag.TimeStamp);
}
else
{ tag.SetTimeStamp(0); }
}
void ProcessRecordLogic()
{
if (CuttingMode != AutoCuttingMode.Disabled && tag.IsVideoKeyframe)
{
bool byTime = (CuttingMode == AutoCuttingMode.ByTime) && (CurrentMaxTimestamp / 1000 >= CuttingNumber * 60);
bool bySize = (CuttingMode == AutoCuttingMode.BySize) && ((_targetFile.Length / 1024 / 1024) >= CuttingNumber);
if (byTime || bySize)
2018-10-24 13:33:43 +08:00
{
2018-11-07 06:07:49 +08:00
FinallizeCurrentFile();
OpenNewRecordFile();
_writeTimeStamp = TotalMaxTimestamp;
2018-10-24 13:33:43 +08:00
}
2018-03-23 06:57:22 +08:00
}
2018-11-07 06:07:49 +08:00
if (!(_targetFile?.CanWrite ?? false))
{
OpenNewRecordFile();
}
2018-11-07 06:07:49 +08:00
tag.WriteTo(_targetFile, _writeTimeStamp);
}
void ProcessClipLogic()
{
_tags.Add(tag);
// 移除过旧的数据
if (TotalMaxTimestamp - _lasttimeRemovedTimestamp > 800)
2018-03-23 06:57:22 +08:00
{
2018-11-07 06:07:49 +08:00
_lasttimeRemovedTimestamp = TotalMaxTimestamp;
int max_remove_index = _tags.FindLastIndex(x => x.IsVideoKeyframe && ((TotalMaxTimestamp - x.TimeStamp) > (ClipLengthPast * SEC_TO_MS)));
if (max_remove_index > 0)
{
_tags.RemoveRange(0, max_remove_index);
}
// Tags.RemoveRange(0, max_remove_index + 1 - 1);
// 给将来的备注:这里是故意 + 1 - 1 的,因为要保留选中的那个关键帧, + 1 就把关键帧删除了
2018-03-23 06:57:22 +08:00
}
2018-11-07 06:07:49 +08:00
Clips.ToList().ForEach(fcp => fcp.AddTag(tag));
2018-10-31 06:23:01 +08:00
}
void ParseTimestampOffset()
{
if (tag.TagType == TagType.VIDEO)
2018-03-24 08:34:57 +08:00
{
2018-10-31 06:23:01 +08:00
_tagVideoCount++;
if (_tagVideoCount < 2)
{
2018-10-31 06:23:01 +08:00
logger.Trace("第一个 Video Tag 时间戳 {0} ms", tag.TimeStamp);
_headerTags.Add(tag);
}
else
{
_baseTimeStamp = tag.TimeStamp;
_hasOffset = true;
2018-11-03 07:45:56 +08:00
StartDateTime = DateTime.Now;
2018-10-31 06:23:01 +08:00
logger.Trace("重设时间戳 {0} 毫秒", _baseTimeStamp);
}
}
else if (tag.TagType == TagType.AUDIO)
{
_tagAudioCount++;
if (_tagAudioCount < 2)
{
logger.Trace("第一个 Audio Tag 时间戳 {0} ms", tag.TimeStamp);
_headerTags.Add(tag);
}
else
{
_baseTimeStamp = tag.TimeStamp;
_hasOffset = true;
2018-11-03 07:45:56 +08:00
StartDateTime = DateTime.Now;
2018-10-31 06:23:01 +08:00
logger.Trace("重设时间戳 {0} 毫秒", _baseTimeStamp);
}
2018-03-24 08:34:57 +08:00
}
2018-10-31 06:23:01 +08:00
}
void ParseMetadata()
{
if (tag.TagType == TagType.DATA)
{
_targetFile?.Write(FLV_HEADER_BYTES, 0, FLV_HEADER_BYTES.Length);
_targetFile?.Write(new byte[] { 0, 0, 0, 0, }, 0, 4);
2018-03-24 08:34:57 +08:00
2018-10-31 06:23:01 +08:00
Metadata = funcFlvMetadata(tag.Data);
2018-03-19 16:51:35 +08:00
2019-01-17 00:28:09 +08:00
OnMetaData?.Invoke(this, new FlvMetadataArgs() { Metadata = Metadata });
2018-03-19 01:05:02 +08:00
2018-10-31 06:23:01 +08:00
tag.Data = Metadata.ToBytes();
tag.WriteTo(_targetFile);
}
else
{
throw new Exception("onMetaData not found");
}
}
2018-03-19 01:05:02 +08:00
}
2018-10-24 13:33:43 +08:00
public IFlvClipProcessor Clip()
2018-03-19 01:05:02 +08:00
{
2018-11-07 06:07:49 +08:00
if (!EnabledFeature.IsClipEnabled()) { return null; }
lock (_writelock)
{
if (_finalized) { throw new InvalidOperationException("Processor Already Closed"); }
logger.Info("剪辑处理中,将会保存过去 {0} 秒和将来 {1} 秒的直播流", (_tags[_tags.Count - 1].TimeStamp - _tags[0].TimeStamp) / 1000d, ClipLengthFuture);
IFlvClipProcessor clip = funcFlvClipProcessor().Initialize(GetClipFileName(), Metadata, _headerTags, new List<IFlvTag>(_tags.ToArray()), ClipLengthFuture);
clip.ClipFinalized += (sender, e) => { Clips.Remove(e.ClipProcessor); };
Clips.Add(clip);
return clip;
}
}
private void FinallizeCurrentFile()
{
try
{
2019-01-16 22:59:42 +08:00
logger.Debug("正在关闭当前录制文件: " + _targetFile?.Name);
2019-01-09 15:51:43 +08:00
Metadata["duration"] = CurrentMaxTimestamp / 1000.0;
Metadata["lasttimestamp"] = (double)CurrentMaxTimestamp;
2018-11-07 06:07:49 +08:00
byte[] metadata = Metadata.ToBytes();
// 13 for FLV header & "0th" tag size
// 11 for 1st tag header
_targetFile?.Seek(13 + 11, SeekOrigin.Begin);
_targetFile?.Write(metadata, 0, metadata.Length);
}
2018-11-07 06:07:49 +08:00
catch (Exception ex)
{
2018-11-07 06:07:49 +08:00
logger.Error(ex, "保存录制文件时出错");
}
finally
{
_targetFile?.Close();
_targetFile = null;
}
2018-03-18 18:55:28 +08:00
}
2018-03-13 13:21:01 +08:00
2018-03-19 16:51:35 +08:00
public void FinallizeFile()
2018-03-13 13:21:01 +08:00
{
2018-11-07 06:07:49 +08:00
if (!_finalized)
2018-10-24 13:33:43 +08:00
{
2018-03-21 20:56:56 +08:00
lock (_writelock)
{
try
{
2018-11-07 06:07:49 +08:00
FinallizeCurrentFile();
}
finally
{
2018-10-31 06:23:01 +08:00
_targetFile?.Close();
_data.Close();
2018-10-31 06:23:01 +08:00
_tags.Clear();
2018-03-19 16:51:35 +08:00
2018-11-07 06:07:49 +08:00
_finalized = true;
2018-03-19 16:51:35 +08:00
2018-11-07 06:07:49 +08:00
Clips.ToList().ForEach(fcp => fcp.FinallizeFile()); // TODO: check
StreamFinalized?.Invoke(this, new StreamFinalizedArgs() { StreamProcessor = this });
}
2018-03-21 20:56:56 +08:00
}
2018-10-24 13:33:43 +08:00
}
2018-03-19 16:51:35 +08:00
}
2018-03-13 13:21:01 +08:00
2018-03-12 18:57:20 +08:00
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
2018-03-21 20:56:56 +08:00
_data.Dispose();
2018-10-31 06:23:01 +08:00
_targetFile?.Dispose();
2019-01-17 00:28:09 +08:00
OnMetaData = null;
StreamFinalized = null;
TagProcessed = null;
2018-03-12 18:57:20 +08:00
}
2018-10-31 06:23:01 +08:00
_tags.Clear();
2018-03-12 18:57:20 +08:00
disposedValue = true;
}
}
2018-03-21 20:56:56 +08:00
public void Dispose()
2018-03-12 18:57:20 +08:00
{
Dispose(true);
}
#endregion
}
}