This commit is contained in:
Genteure 2018-03-19 16:51:35 +08:00
parent 2459867f8f
commit 0abe31752f
6 changed files with 373 additions and 26 deletions

View File

@ -26,6 +26,7 @@ namespace BililiveRecorder.Core
public RecordedRoom()
{
Processor.TagProcessed += Processor_TagProcessed;
Processor.StreamFinalized += Processor_StreamFinalized;
streamMonitor.StreamStatusChanged += StreamMonitor_StreamStatusChanged;
UpdateRoomInfo();
@ -153,8 +154,6 @@ namespace BililiveRecorder.Core
public void Clip()
{
var clip = Processor.Clip();
// TODO: 多个线程同时运行,这个位置有可能会导致 Clip 丢数据
// 考虑在此处加锁, Clip 操作时停止向主 Processor 添加数据
clip.ClipFinalized += CallBack_ClipFinalized;
Clips.Add(clip);
}
@ -173,7 +172,12 @@ namespace BililiveRecorder.Core
private void Processor_TagProcessed(object sender, TagProcessedArgs e)
{
Clips.ToList().ForEach((fcp) => fcp.AddTag(e.Tag));
Clips.ToList().ForEach(fcp => fcp.AddTag(e.Tag));
}
private void Processor_StreamFinalized(object sender, StreamFinalizedArgs e)
{
Clips.ToList().ForEach(fcp => fcp.FinallizeFile());
}

View File

@ -17,4 +17,10 @@ namespace BililiveRecorder.FlvProcessor
public FlvClipProcessor ClipProcessor;
}
public delegate void StreamFinalizedEvent(object sender, StreamFinalizedArgs e);
public class StreamFinalizedArgs
{
public FlvStreamProcessor StreamProcessor;
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace BililiveRecorder.FlvProcessor
@ -7,17 +8,58 @@ namespace BililiveRecorder.FlvProcessor
public class FlvClipProcessor : IDisposable
{
public readonly FlvMetadata Header;
public List<FlvTag> Tags;
private int target = -1;
public FlvClipProcessor(FlvMetadata header)
public FlvClipProcessor(FlvMetadata header, List<FlvTag> past, int future)
{
Header = header;
Tags = past;
target = Tags[Tags.Count - 1].TimeStamp + future;
}
public void AddTag(FlvTag tag)
{
throw new NotImplementedException();
Tags.Add(tag);
if (tag.TimeStamp >= target)
{
FinallizeFile();
}
}
public void FinallizeFile()
{
using (var fs = new FileStream("", FileMode.CreateNew, FileAccess.ReadWrite))
{
fs.Write(FlvStreamProcessor.FLV_HEADER_BYTES, 0, FlvStreamProcessor.FLV_HEADER_BYTES.Length);
Header.Meta["duration"] = Tags[Tags.Count - 1].TimeStamp / 1000.0;
Header.Meta["lasttimestamp"] = (double)Tags[Tags.Count - 1].TimeStamp;
var t = new FlvTag();
t.TagType = TagType.DATA;
t.Data = Header.ToBytes();
var b = t.ToBytes();
fs.Write(b, 0, b.Length);
fs.Write(t.Data, 0, t.Data.Length);
fs.Write(BitConverter.GetBytes(t.Data.Length + b.Length).ToBE(), 0, 4);
int timestamp = Tags[0].TimeStamp;
Tags.ForEach(tag =>
{
tag.TimeStamp -= timestamp;
var vs = tag.ToBytes();
fs.Write(vs, 0, vs.Length);
fs.Write(tag.Data, 0, tag.Data.Length);
fs.Write(BitConverter.GetBytes(tag.Data.Length + vs.Length).ToBE(), 0, 4);
});
fs.Close();
}
ClipFinalized?.Invoke(this, new ClipFinalizedArgs() { ClipProcessor = this });
}
public event ClipFinalizedEvent ClipFinalized;

View File

@ -1,14 +1,198 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace BililiveRecorder.FlvProcessor
{
public class FlvMetadata
{
public IDictionary<string, object> Meta = new Dictionary<string, object>();
public static FlvMetadata Parse(byte[] data)
{
var m = new FlvMetadata();
m.Meta = _Decode(data);
if (!m.Meta.ContainsKey("duration"))
m.Meta["duration"] = 0.0;
if (!m.Meta.ContainsKey("lasttimestamp"))
m.Meta["lasttimestamp"] = 0.0;
return m;
}
public byte[] ToBytes()
{
throw new NotImplementedException();
return _Encode();
}
#region - Encode -
private byte[] _Encode()
{
using (MemoryStream ms = new MemoryStream())
{
const string onMetaData = "onMetaData";
ms.WriteByte((byte)AMFTypes.String);
UInt16 strSize = (UInt16)onMetaData.Length;
byte[] strSizeb = BitConverter.GetBytes(strSize).ToBE();
ms.Write(strSizeb, 0, strSizeb.Length);
ms.Write(Encoding.ASCII.GetBytes(onMetaData), 0, onMetaData.Length);
ms.WriteByte((byte)AMFTypes.Array);
byte[] asize = BitConverter.GetBytes(Meta.Keys.Count).ToBE();
ms.Write(asize, 0, asize.Length);
foreach (string key in Meta.Keys)
{
object val = Meta[key];
byte[] valBytes = _EncodeVal(val);
if (!string.IsNullOrWhiteSpace(key) && valBytes != null)
{
byte[] keyBytes = _EncodeKey(key);
ms.Write(keyBytes, 0, keyBytes.Length);
ms.Write(valBytes, 0, valBytes.Length);
}
}
/* *
* SCRIPTDATAVARIABLEEND
* Script Data Variable End
* Type: UI24
* Always 9
* */
ms.WriteByte(0x0);
ms.WriteByte(0x0);
ms.WriteByte((byte)AMFTypes.End);
return ms.ToArray();
}
}
private byte[] _EncodeKey(string key)
{
byte[] ret = new byte[2 + key.Length]; // 2 for the size at the front
UInt16 strSize = (UInt16)key.Length;
byte[] strSizeb = BitConverter.GetBytes(strSize).ToBE();
Buffer.BlockCopy(strSizeb, 0, ret, 0, strSizeb.Length);
Buffer.BlockCopy(Encoding.ASCII.GetBytes(key), 0, ret, 2, key.Length);
return ret;
}
private byte[] _EncodeVal(object val)
{
if (val is double)
{
double num = (double)val;
byte[] ret = new byte[1 + sizeof(double)];
ret[0] = (byte)AMFTypes.Number;
byte[] numbits = BitConverter.GetBytes(num).ToBE();
Buffer.BlockCopy(numbits, 0, ret, 1, numbits.Length);
return ret;
}
else if (val is string)
{
string str = val as string;
byte[] ret = new byte[3 + str.Length];
ret[0] = (byte)AMFTypes.String;
UInt16 strSize = (UInt16)str.Length;
byte[] strSizeb = BitConverter.GetBytes(strSize).ToBE();
Buffer.BlockCopy(strSizeb, 0, ret, 1, strSizeb.Length);
Buffer.BlockCopy(Encoding.ASCII.GetBytes(str), 0, ret, 3, str.Length);
return ret;
}
else if (val is byte)
{
byte bit = (byte)val;
byte[] ret = new byte[2];
ret[0] = (byte)AMFTypes.Boolean;
ret[1] = bit;
return ret;
}
else
{
Debug.Write(string.Format("Unknown Value type: {0}\n", val.GetType().Name));
return null;
}
}
#endregion
#region - Decode -
private static string _DecodeKey(byte[] buff, ref int _readHead)
{
// get length of string name
byte[] flip = new byte[sizeof(short)];
flip[0] = buff[_readHead++];
flip[1] = buff[_readHead++];
ushort klen = BitConverter.ToUInt16(flip.ToBE(), 0);
string name = Encoding.Default.GetString(buff, _readHead, klen);
_readHead += klen;
return name;
}
private static object _DecodeVal(byte[] buff, ref int _readHead)
{
byte type = buff[_readHead++];
AMFTypes amfType = (AMFTypes)Enum.ToObject(typeof(AMFTypes), (int)type);
switch (amfType)
{
case AMFTypes.String:
return _DecodeKey(buff, ref _readHead);
case AMFTypes.Number:
byte[] flip = new byte[sizeof(double)];
Buffer.BlockCopy(buff, _readHead, flip, 0, flip.Length);
double num = BitConverter.ToDouble(flip.ToBE(), 0);
_readHead += sizeof(double);
return num;
case AMFTypes.Boolean:
byte b = buff[_readHead++];
return b;
case AMFTypes.End:
return null;
default:
throw new MissingMethodException();
}
}
private static IDictionary<string, object> _Decode(byte[] buff)
{
IDictionary<string, object> keyval = new Dictionary<string, object>();
int _readHead = 0;
// get the onMetadata
string onMeta = _DecodeVal(buff, ref _readHead) as string;
// read array type
byte type = buff[_readHead++];
Debug.Assert(type == (byte)AMFTypes.Array || type == (byte)AMFTypes.Object);
if (type == (byte)AMFTypes.Array)
{
byte[] alen = new byte[sizeof(int)];
Buffer.BlockCopy(buff, _readHead, alen, 0, alen.Length);
_readHead += alen.Length;
int arrayLen = BitConverter.ToInt32(alen.ToBE(), 0);
Debug.Write(string.Format("onMetaData Array Len: {0}\n", arrayLen));
}
else if (type == (byte)AMFTypes.Object)
{
Debug.Write("onMetaData isn't an Array but Object!\n");
}
else
{
throw new Exception("Parse Script Tag Error"); // TODO: custom Exception
}
while (_readHead <= buff.Length - 1)
{
string key = _DecodeKey(buff, ref _readHead);
object val = _DecodeVal(buff, ref _readHead);
Debug.Write(string.Format("Parse Script Tag: {0} => {1}\n", key, val));
keyval[key] = val;
}
return keyval;
}
#endregion
}
}

View File

@ -11,32 +11,77 @@ namespace BililiveRecorder.FlvProcessor
public class FlvStreamProcessor : IDisposable
{
private const int MIN_BUFFER_SIZE = 1024 * 2;
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
0x00, // ---
0x00, // |
0x00, // |
0x00, // the "0th" tag has a length of 0
};
public RecordInfo Info; // not used for now.
public readonly FlvMetadata Metadata = new FlvMetadata();
public FlvMetadata Metadata = null;
public event TagProcessedEvent TagProcessed;
private byte[] headers;
public event StreamFinalizedEvent StreamFinalized;
private bool _headerParsed = false;
private readonly List<FlvTag> Tags = new List<FlvTag>();
private readonly MemoryStream _buffer = new MemoryStream();
private readonly MemoryStream _data = new MemoryStream();
private FlvTag currentTag = null;
private object _writelock = new object();
private bool Finallized = false;
public FlvStreamProcessor(RecordInfo info)
private readonly FileStream _fs;
public int MaxTimeStamp { get; private set; }
public FlvStreamProcessor(RecordInfo info, string path)
{
Info = info;
_fs = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite);
if (!_fs.CanSeek)
{
_fs.Dispose();
try { File.Delete(path); } catch (Exception) { }
throw new NotSupportedException("Target File Cannot Seek");
}
}
public void AddBytes(byte[] data)
{
// lock ( ) { _AddBytes() }
lock (_writelock)
_AddBytes(data);
}
private void _AddBytes(byte[] data)
{
if (currentTag == null)
if (Finallized)
{
throw new Exception("Stream File Already Closed");
}
else if (!_headerParsed)
{
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());
}
else if (currentTag == null)
{
if (_buffer.Position >= MIN_BUFFER_SIZE)
{
@ -65,17 +110,41 @@ namespace BililiveRecorder.FlvProcessor
private void _TagCreated(FlvTag tag)
{
tag.TimeStamp -= 0;//TODO: 修复时间戳
Tags.Add(tag);
// TODO: remove old tag
TagProcessed?.Invoke(this, new TagProcessedArgs() { Tag = tag });
if (tag.TagType == TagType.DATA)
if (Metadata == null)
{
// TODO: onMetaData
if (tag.TagType == TagType.DATA)
{
_fs.Write(FLV_HEADER_BYTES, 0, FLV_HEADER_BYTES.Length);
Metadata = FlvMetadata.Parse(tag.Data);
// TODO: 添加录播姬标记、录制信息
tag.Data = Metadata.ToBytes();
var b = tag.ToBytes();
_fs.Write(b, 0, b.Length);
_fs.Write(tag.Data, 0, tag.Data.Length);
_fs.Write(BitConverter.GetBytes(tag.Data.Length + b.Length).ToBE(), 0, 4);
}
else
{
throw new Exception("onMetaData not found");
}
}
else
{
tag.TimeStamp -= 0; // TODO: 修复时间戳
Tags.Add(tag);
// TODO: remove old tag
var b = tag.ToBytes();
_fs.Write(b, 0, b.Length);
_fs.Write(tag.Data, 0, tag.Data.Length);
_fs.Write(BitConverter.GetBytes(tag.Data.Length + b.Length).ToBE(), 0, 4);
TagProcessed?.Invoke(this, new TagProcessedArgs() { Tag = tag });
}
}
private void _ParseTag(byte[] data)
{
byte[] b = { 0, 0, 0, 0, };
@ -108,16 +177,47 @@ namespace BililiveRecorder.FlvProcessor
_AddBytes(rest);
}
private void _ParseHeader()
{
}
public FlvClipProcessor Clip()
{
throw new NotImplementedException();
lock (_writelock)
{
return new FlvClipProcessor(Metadata, Tags, 30);
}
}
public void FinallizeFile()
{
lock (_writelock)
{
Metadata.Meta["duration"] = MaxTimeStamp / 1000.0;
Metadata.Meta["lasttimestamp"] = (double)MaxTimeStamp;
byte[] metadata = Metadata.ToBytes();
// 13 for FLV header & "0th" tag size
// 11 for 1st tag header
_fs.Seek(13 + 11, SeekOrigin.Begin);
_fs.Write(metadata, 0, metadata.Length);
_fs.Close();
_fs.Dispose();
_buffer.Close();
_buffer.Dispose();
_data.Close();
_data.Dispose();
Tags.Clear();
Finallized = true;
StreamFinalized?.Invoke(this, new StreamFinalizedArgs() { StreamProcessor = this });
// TODO: 通知 Clip 也进行保存
// TODO: 阻止再尝试写入任何数据
// TODO: 清空 Tags List
}
}

View File

@ -16,7 +16,18 @@ namespace BililiveRecorder.FlvProcessor
public byte[] ToBytes()
{
throw new NotImplementedException();
var tag = new byte[11];
tag[0] = (byte)TagType;
Buffer.BlockCopy(BitConverter.GetBytes(Data.Length).ToBE(), 0, tag, 1, 3);
byte[] timing = new byte[4];
Buffer.BlockCopy(BitConverter.GetBytes(this.TimeStamp).ToBE(), 0, timing, 0, timing.Length);
Buffer.BlockCopy(timing, 1, tag, 4, 3);
Buffer.BlockCopy(timing, 0, tag, 7, 1);
Buffer.BlockCopy(StreamId, 0, tag, 8, 3);
return tag;
}
}
}