diff --git a/BililiveRecorder.Core/BililiveRecorder.Core.csproj b/BililiveRecorder.Core/BililiveRecorder.Core.csproj index 0adb136..9123447 100644 --- a/BililiveRecorder.Core/BililiveRecorder.Core.csproj +++ b/BililiveRecorder.Core/BililiveRecorder.Core.csproj @@ -10,6 +10,7 @@ + diff --git a/BililiveRecorder.Core/RecordedRoom.cs b/BililiveRecorder.Core/RecordedRoom.cs index f24e545..a5e672b 100644 --- a/BililiveRecorder.Core/RecordedRoom.cs +++ b/BililiveRecorder.Core/RecordedRoom.cs @@ -24,8 +24,8 @@ namespace BililiveRecorder.Core public bool IsMonitoring => StreamMonitor.receiver.IsConnected; public bool IsRecording => flvStream != null; - public FlvStreamProcessor Processor; // FlvProcessor - public ObservableCollection Clips { get; private set; } = new ObservableCollection(); + public IFlvStreamProcessor Processor; // FlvProcessor + public ObservableCollection Clips { get; private set; } = new ObservableCollection(); internal StreamMonitor StreamMonitor { get; } private Settings _settings { get; } diff --git a/BililiveRecorder.FlvProcessor/BililiveRecorder.FlvProcessor.csproj b/BililiveRecorder.FlvProcessor/BililiveRecorder.FlvProcessor.csproj index 0c0eeaa..588bbfe 100644 --- a/BililiveRecorder.FlvProcessor/BililiveRecorder.FlvProcessor.csproj +++ b/BililiveRecorder.FlvProcessor/BililiveRecorder.FlvProcessor.csproj @@ -10,6 +10,7 @@ + diff --git a/BililiveRecorder.FlvProcessor/Events.cs b/BililiveRecorder.FlvProcessor/Events.cs index de663d4..79a065a 100644 --- a/BililiveRecorder.FlvProcessor/Events.cs +++ b/BililiveRecorder.FlvProcessor/Events.cs @@ -1,26 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace BililiveRecorder.FlvProcessor +namespace BililiveRecorder.FlvProcessor { public delegate void TagProcessedEvent(object sender, TagProcessedArgs e); public class TagProcessedArgs { - public FlvTag Tag; + public IFlvTag Tag; } - - + public delegate void ClipFinalizedEvent(object sender, ClipFinalizedArgs e); public class ClipFinalizedArgs { - public FlvClipProcessor ClipProcessor; + public IFlvClipProcessor ClipProcessor; } public delegate void StreamFinalizedEvent(object sender, StreamFinalizedArgs e); public class StreamFinalizedArgs { - public FlvStreamProcessor StreamProcessor; + public IFlvStreamProcessor StreamProcessor; } } diff --git a/BililiveRecorder.FlvProcessor/FlvClipProcessor.cs b/BililiveRecorder.FlvProcessor/FlvClipProcessor.cs index c121c27..b61e293 100644 --- a/BililiveRecorder.FlvProcessor/FlvClipProcessor.cs +++ b/BililiveRecorder.FlvProcessor/FlvClipProcessor.cs @@ -2,22 +2,21 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text; namespace BililiveRecorder.FlvProcessor { - public class FlvClipProcessor + public class FlvClipProcessor : IFlvClipProcessor { private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - public readonly FlvMetadata Header; - public readonly List HTags; - public readonly List Tags; - private int target = -1; + public IFlvMetadata Header { get; } + public List HTags { get; } + public List Tags { get; } + private readonly int target = -1; - public Func GetFileName; + public Func GetFileName { get; set; } - public FlvClipProcessor(FlvMetadata header, List head, List past, uint future) + public FlvClipProcessor(IFlvMetadata header, List head, List past, uint future) { Header = header; HTags = head; @@ -27,7 +26,7 @@ namespace BililiveRecorder.FlvProcessor Tags.Count, Tags[0].TimeStamp, Tags[Tags.Count - 1].TimeStamp, (Tags[Tags.Count - 1].TimeStamp - Tags[0].TimeStamp) / 1000d); } - public void AddTag(FlvTag tag) + public void AddTag(IFlvTag tag) { Tags.Add(tag); if (tag.TimeStamp >= target) diff --git a/BililiveRecorder.FlvProcessor/FlvMetadata.cs b/BililiveRecorder.FlvProcessor/FlvMetadata.cs index 2b754b0..7a83141 100644 --- a/BililiveRecorder.FlvProcessor/FlvMetadata.cs +++ b/BililiveRecorder.FlvProcessor/FlvMetadata.cs @@ -6,23 +6,29 @@ using System.Text; namespace BililiveRecorder.FlvProcessor { - public class FlvMetadata + public class FlvMetadata : IFlvMetadata { - public IDictionary Meta = new Dictionary(); + public IDictionary Meta { get; set; } = new Dictionary(); - public static FlvMetadata Parse(byte[] data) + public FlvMetadata() { - var m = new FlvMetadata + Meta["duration"] = 0.0; + Meta["lasttimestamp"] = 0.0; + } + + public FlvMetadata(byte[] data) + { + Meta = _Decode(data); + + if (!Meta.ContainsKey("duration")) { - Meta = _Decode(data) - }; + Meta["duration"] = 0.0; + } - if (!m.Meta.ContainsKey("duration")) - m.Meta["duration"] = 0.0; - if (!m.Meta.ContainsKey("lasttimestamp")) - m.Meta["lasttimestamp"] = 0.0; - - return m; + if (!Meta.ContainsKey("lasttimestamp")) + { + Meta["lasttimestamp"] = 0.0; + } } public byte[] ToBytes() diff --git a/BililiveRecorder.FlvProcessor/FlvStreamProcessor.cs b/BililiveRecorder.FlvProcessor/FlvStreamProcessor.cs index c843898..63fea39 100644 --- a/BililiveRecorder.FlvProcessor/FlvStreamProcessor.cs +++ b/BililiveRecorder.FlvProcessor/FlvStreamProcessor.cs @@ -1,18 +1,15 @@ using NLog; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Net; -using System.Text; namespace BililiveRecorder.FlvProcessor { // TODO: 重构 Tag 解析流程 // TODO: 添加测试 // 注:下载时会按照 4 11 N bytes 下载 - public class FlvStreamProcessor : IDisposable + public class FlvStreamProcessor : IFlvStreamProcessor { private static readonly Logger logger = LogManager.GetCurrentClassLogger(); @@ -35,22 +32,22 @@ namespace BililiveRecorder.FlvProcessor // 0x00, // the "0th" tag has a length of 0 }; - public FlvMetadata Metadata = null; + public IFlvMetadata Metadata { get; set; } = null; public event TagProcessedEvent TagProcessed; public event StreamFinalizedEvent StreamFinalized; - public Func GetFileName; + public Func GetFileName { get; set; } - public uint Clip_Past = 90; - public uint Clip_Future = 30; + public uint Clip_Past { get; set; } = 90; + public uint Clip_Future { get; set; } = 30; private readonly bool _noClip = false; private bool _headerParsed = false; - private readonly List HTags = new List(); - private readonly List Tags = new List(); + private readonly List HTags = new List(); + private readonly List Tags = new List(); private readonly MemoryStream _buffer = new MemoryStream(); private readonly MemoryStream _data = new MemoryStream(); - private FlvTag currentTag = null; - private object _writelock = new object(); + private IFlvTag currentTag = null; + private readonly object _writelock = new object(); private bool Finallized = false; private readonly FileStream _fs; @@ -85,7 +82,9 @@ namespace BililiveRecorder.FlvProcessor public void AddBytes(byte[] data) { lock (_writelock) + { _AddBytes(data); + } } private void _AddBytes(byte[] data) @@ -104,10 +103,15 @@ namespace BililiveRecorder.FlvProcessor } var r = new bool[FLV_HEADER_BYTES.Length]; for (int i = 0; i < FLV_HEADER_BYTES.Length; i++) + { r[i] = data[i] == FLV_HEADER_BYTES[i]; + } + bool succ = r.All(x => x); if (!succ) + { throw new NotSupportedException("Not FLV Stream or Not Supported"); // TODO: custom Exception. + } _headerParsed = true; _AddBytes(data.Skip(FLV_HEADER_BYTES.Length).ToArray()); @@ -140,7 +144,7 @@ namespace BililiveRecorder.FlvProcessor } } - private void _TagCreated(FlvTag tag) + private void _TagCreated(IFlvTag tag) { if (Metadata == null) { @@ -149,7 +153,7 @@ namespace BililiveRecorder.FlvProcessor _fs?.Write(FLV_HEADER_BYTES, 0, FLV_HEADER_BYTES.Length); _fs?.Write(new byte[] { 0, 0, 0, 0, }, 0, 4); - Metadata = FlvMetadata.Parse(tag.Data); + Metadata = new FlvMetadata(tag.Data); // TODO: 添加录播姬标记、录制信息 @@ -202,7 +206,10 @@ namespace BililiveRecorder.FlvProcessor { tag.TimeStamp -= BaseTimeStamp; // 修复时间戳 if (tag.TimeStamp < 0) + { tag.TimeStamp = 0; + } + MaxTimeStamp = Math.Max(MaxTimeStamp, tag.TimeStamp); } else @@ -221,7 +228,9 @@ namespace BililiveRecorder.FlvProcessor LasttimeRemovedTimestamp = MaxTimeStamp; int max_remove_index = Tags.FindLastIndex(x => x.IsVideoKeyframe && ((MaxTimeStamp - x.TimeStamp) > (Clip_Past * 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 就把关键帧删除了 } @@ -242,7 +251,7 @@ namespace BililiveRecorder.FlvProcessor _buffer.Write(data, 0, data.Length); long dataLen = _buffer.Position; _buffer.Position = 0; - FlvTag tag = new FlvTag(); + IFlvTag tag = new FlvTag(); // Previous Tag Size _buffer.Read(b, 0, 4); @@ -272,7 +281,7 @@ namespace BililiveRecorder.FlvProcessor _AddBytes(rest); } - public FlvClipProcessor Clip() + public IFlvClipProcessor Clip() { // 如果禁用 clip 功能 或者 已经结束处理了 if (_noClip || Finallized) @@ -284,7 +293,7 @@ namespace BililiveRecorder.FlvProcessor lock (_writelock) { logger.Info("剪辑处理中,将会保存过去 {0} 秒和将来 {1} 秒的直播流", (Tags[Tags.Count - 1].TimeStamp - Tags[0].TimeStamp) / 1000d, Clip_Future); - return new FlvClipProcessor(Metadata, HTags, new List(Tags.ToArray()), Clip_Future); + return new FlvClipProcessor(Metadata, HTags, new List(Tags.ToArray()), Clip_Future); } } } @@ -292,6 +301,7 @@ namespace BililiveRecorder.FlvProcessor public void FinallizeFile() { if (!Finallized) + { lock (_writelock) { try @@ -322,6 +332,7 @@ namespace BililiveRecorder.FlvProcessor StreamFinalized?.Invoke(this, new StreamFinalizedArgs() { StreamProcessor = this }); } } + } } #region IDisposable Support diff --git a/BililiveRecorder.FlvProcessor/FlvTag.cs b/BililiveRecorder.FlvProcessor/FlvTag.cs index 01b2144..cd5f183 100644 --- a/BililiveRecorder.FlvProcessor/FlvTag.cs +++ b/BililiveRecorder.FlvProcessor/FlvTag.cs @@ -1,22 +1,19 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; namespace BililiveRecorder.FlvProcessor { - public class FlvTag + public class FlvTag : IFlvTag { - public TagType TagType = 0; - public int TagSize = 0; - public int TimeStamp = 0; - public byte[] StreamId = new byte[3]; + public TagType TagType { get; set; } = 0; + public int TagSize { get; set; } = 0; + public int TimeStamp { get; set; } = 0; + public byte[] StreamId { get; set; } = new byte[3]; public bool IsVideoKeyframe => _IsVideoKeyframe != -1 ? _IsVideoKeyframe == 1 : 1 == (_IsVideoKeyframe = _ParseIsVideoKeyframe()); private int _IsVideoKeyframe = -1; - public byte[] Data = null; + public byte[] Data { get; set; } = null; public byte[] ToBytes(bool useDataSize, int offset = 0) { @@ -38,9 +35,14 @@ namespace BililiveRecorder.FlvProcessor private int _ParseIsVideoKeyframe() { if (TagType != TagType.VIDEO) + { return 0; + } + if (Data.Length < 1) + { return -1; + } const byte mask = 0b00001111; const byte compare = 0b00011111; diff --git a/BililiveRecorder.FlvProcessor/IFlvClipProcessor.cs b/BililiveRecorder.FlvProcessor/IFlvClipProcessor.cs new file mode 100644 index 0000000..3b24bc1 --- /dev/null +++ b/BililiveRecorder.FlvProcessor/IFlvClipProcessor.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace BililiveRecorder.FlvProcessor +{ + public interface IFlvClipProcessor + { + IFlvMetadata Header { get; } + List HTags { get; } + List Tags { get; } + Func GetFileName { get; set; } + + void AddTag(IFlvTag tag); + void FinallizeFile(); + event ClipFinalizedEvent ClipFinalized; + + } +} diff --git a/BililiveRecorder.FlvProcessor/IFlvMetadata.cs b/BililiveRecorder.FlvProcessor/IFlvMetadata.cs new file mode 100644 index 0000000..d790de5 --- /dev/null +++ b/BililiveRecorder.FlvProcessor/IFlvMetadata.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace BililiveRecorder.FlvProcessor +{ + public interface IFlvMetadata + { + IDictionary Meta { get; set; } + byte[] ToBytes(); + } +} diff --git a/BililiveRecorder.FlvProcessor/IFlvStreamProcessor.cs b/BililiveRecorder.FlvProcessor/IFlvStreamProcessor.cs new file mode 100644 index 0000000..bcec1c5 --- /dev/null +++ b/BililiveRecorder.FlvProcessor/IFlvStreamProcessor.cs @@ -0,0 +1,24 @@ +using System; + +namespace BililiveRecorder.FlvProcessor +{ + public interface IFlvStreamProcessor : IDisposable + { + event TagProcessedEvent TagProcessed; + event StreamFinalizedEvent StreamFinalized; + + IFlvMetadata Metadata { get; set; } + Func GetFileName { get; set; } + uint Clip_Past { get; set; } + uint Clip_Future { get; set; } + int LasttimeRemovedTimestamp { get; } + int MaxTimeStamp { get; } + int BaseTimeStamp { get; } + int TagVideoCount { get; } + int TagAudioCount { get; } + + void AddBytes(byte[] data); + IFlvClipProcessor Clip(); + void FinallizeFile(); + } +} diff --git a/BililiveRecorder.FlvProcessor/IFlvTag.cs b/BililiveRecorder.FlvProcessor/IFlvTag.cs new file mode 100644 index 0000000..bec5faf --- /dev/null +++ b/BililiveRecorder.FlvProcessor/IFlvTag.cs @@ -0,0 +1,17 @@ +using System.IO; + +namespace BililiveRecorder.FlvProcessor +{ + public interface IFlvTag + { + TagType TagType { get; set; } + int TagSize { get; set; } + int TimeStamp { get; set; } + byte[] StreamId { get; set; } + bool IsVideoKeyframe { get; } + byte[] Data { get; set; } + + byte[] ToBytes(bool useDataSize, int offset = 0); + void WriteTo(Stream stream, int offset = 0); + } +} diff --git a/BililiveRecorder.WPF/BililiveRecorder.WPF.csproj b/BililiveRecorder.WPF/BililiveRecorder.WPF.csproj index 94cbf1e..8e92594 100644 --- a/BililiveRecorder.WPF/BililiveRecorder.WPF.csproj +++ b/BililiveRecorder.WPF/BililiveRecorder.WPF.csproj @@ -75,6 +75,9 @@ false + + ..\packages\Autofac.4.8.1\lib\net45\Autofac.dll + ..\packages\WindowsAPICodePack-Core.1.1.2\lib\Microsoft.WindowsAPICodePack.dll diff --git a/BililiveRecorder.WPF/packages.config b/BililiveRecorder.WPF/packages.config index 2f4c6bc..3d1c844 100644 --- a/BililiveRecorder.WPF/packages.config +++ b/BililiveRecorder.WPF/packages.config @@ -1,5 +1,6 @@  +