BililiveRecorder/BililiveRecorder.FlvProcessor/FlvStreamProcessor.cs

291 lines
9.7 KiB
C#
Raw Normal View History

2018-03-12 18:57:20 +08:00
using System;
using System.Collections.Generic;
2018-03-19 01:05:02 +08:00
using System.Diagnostics;
using System.IO;
using System.Linq;
2018-03-13 13:56:44 +08:00
using System.Net;
2018-03-12 18:57:20 +08:00
using System.Text;
namespace BililiveRecorder.FlvProcessor
{
public class FlvStreamProcessor : IDisposable
{
2018-03-19 17:35:27 +08:00
internal const int SEC_TO_MS = 1000; // 1 second = 1000 ms
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-03-19 16:51:35 +08:00
public FlvMetadata Metadata = null;
2018-03-19 01:05:02 +08:00
public event TagProcessedEvent TagProcessed;
2018-03-19 16:51:35 +08:00
public event StreamFinalizedEvent StreamFinalized;
2018-03-20 00:12:32 +08:00
public Func<string> GetFileName;
2018-03-12 18:57:20 +08:00
2018-03-19 17:35:27 +08:00
public int Clip_Past = 90;
public int Clip_Future = 30;
2018-03-19 01:05:02 +08:00
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;
2018-03-19 16:51:35 +08:00
private object _writelock = new object();
private bool Finallized = false;
private readonly FileStream _fs;
2018-03-13 13:56:44 +08:00
2018-03-19 17:35:27 +08:00
public int MaxTimeStamp { get; private set; } = -1;
public int BaseTimeStamp { get; private set; } = 0;
public int TagVideoCount { get; private set; } = 0;
public int TagAudioCount { get; private set; } = 0;
private bool hasOffset = false;
2018-03-19 16:51:35 +08:00
2018-03-20 00:12:32 +08:00
public FlvStreamProcessor(string path)
2018-03-12 18:57:20 +08:00
{
2018-03-19 16:51:35 +08:00
_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");
}
2018-03-12 18:57:20 +08:00
}
2018-03-18 18:55:28 +08:00
public void AddBytes(byte[] data)
{
2018-03-19 16:51:35 +08:00
lock (_writelock)
_AddBytes(data);
2018-03-19 01:05:02 +08:00
}
private void _AddBytes(byte[] data)
{
2018-03-19 16:51:35 +08:00
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)
2018-03-19 01:05:02 +08:00
{
2018-03-22 00:40:25 +08:00
_buffer.Write(data, 0, data.Length);
2018-03-19 01:05:02 +08:00
if (_buffer.Position >= MIN_BUFFER_SIZE)
{
2018-03-22 00:40:25 +08:00
_ParseTag(_buffer.GetBuffer().Take((int)_buffer.Position).ToArray());
2018-03-19 01:05:02 +08:00
}
}
else
{
_WriteTagData(data);
}
}
private void _WriteTagData(byte[] data)
{
int toRead = Math.Min(data.Length, (currentTag.TagSize - (int)_data.Position));
_data.Write(data, 0, toRead);
if ((int)_data.Position == currentTag.TagSize)
{
currentTag.Data = _data.ToArray();
_data.SetLength(0); // reset data buffer
_TagCreated(currentTag);
currentTag = null;
_AddBytes(data.Skip(toRead).ToArray());
}
}
private void _TagCreated(FlvTag tag)
{
2018-03-19 16:51:35 +08:00
if (Metadata == null)
2018-03-19 01:05:02 +08:00
{
2018-03-19 16:51:35 +08:00
if (tag.TagType == TagType.DATA)
{
_fs.Write(FLV_HEADER_BYTES, 0, FLV_HEADER_BYTES.Length);
2018-03-22 06:34:53 +08:00
_fs.Write(new byte[] { 0, 0, 0, 0, }, 0, 4);
2018-03-19 16:51:35 +08:00
Metadata = FlvMetadata.Parse(tag.Data);
// TODO: 添加录播姬标记、录制信息
tag.Data = Metadata.ToBytes();
2018-03-22 06:34:53 +08:00
var b = tag.ToBytes(true);
2018-03-19 16:51:35 +08:00
_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");
}
2018-03-19 01:05:02 +08:00
}
2018-03-19 16:51:35 +08:00
else
{
2018-03-19 17:35:27 +08:00
switch (tag.TagType)
{
case TagType.AUDIO:
TagAudioCount++;
if (!hasOffset && TagAudioCount == 2)
{
hasOffset = true;
BaseTimeStamp = tag.TimeStamp;
Debug.Write(string.Format("Reseting to this base timestamp {0} ms\n", BaseTimeStamp));
}
break;
case TagType.VIDEO:
TagVideoCount++;
if (!hasOffset && TagVideoCount == 2)
{
hasOffset = true;
BaseTimeStamp = tag.TimeStamp;
Debug.Write(string.Format("Reseting to this base timestamp {0} ms\n", BaseTimeStamp));
}
break;
case TagType.DATA:
default:
break;
}
2018-03-19 01:05:02 +08:00
2018-03-23 06:57:22 +08:00
if (hasOffset)
{
tag.TimeStamp -= BaseTimeStamp; // 修复时间戳
MaxTimeStamp = Math.Max(MaxTimeStamp, tag.TimeStamp);
}
else
{
tag.TimeStamp = 0;
MaxTimeStamp = 0;
}
2018-03-19 17:35:27 +08:00
Tags.Add(tag); // Clip 缓存
Tags.Where(x => (MaxTimeStamp - x.TimeStamp) > (Clip_Past * SEC_TO_MS)).Any(x => Tags.Remove(x)); // 移除过旧的数据
// 写入硬盘
2018-03-22 06:34:53 +08:00
var b = tag.ToBytes(true);
2018-03-19 16:51:35 +08:00
_fs.Write(b, 0, b.Length);
_fs.Write(tag.Data, 0, tag.Data.Length);
2018-03-19 17:35:27 +08:00
_fs.Write(BitConverter.GetBytes(tag.Data.Length + b.Length).ToBE(), 0, 4); // Last Tag Size
2018-03-19 16:51:35 +08:00
TagProcessed?.Invoke(this, new TagProcessedArgs() { Tag = tag });
}
}
2018-03-19 01:05:02 +08:00
private void _ParseTag(byte[] data)
{
2018-03-22 00:40:25 +08:00
_buffer.Position = 0;
_buffer.SetLength(0);
2018-03-19 17:35:27 +08:00
byte[] b = new byte[4];
2018-03-19 01:05:02 +08:00
_buffer.Write(data, 0, data.Length);
long dataLen = _buffer.Position;
_buffer.Position = 0;
FlvTag tag = new FlvTag();
2018-03-19 17:35:27 +08:00
// Previous Tag Size
_buffer.Read(b, 0, 4);
b = new byte[4];
2018-03-19 01:05:02 +08:00
// TagType UI8
tag.TagType = (TagType)_buffer.ReadByte();
Debug.Write(string.Format("Tag Type: {0}\n", tag.TagType));
// DataSize UI24
_buffer.Read(b, 1, 3);
tag.TagSize = BitConverter.ToInt32(b.ToBE(), 0); // TODO: test this
// Timestamp UI24
_buffer.Read(b, 1, 3);
// TimestampExtended UI8
_buffer.Read(b, 0, 1);
tag.TimeStamp = BitConverter.ToInt32(b.ToBE(), 0);
// StreamID UI24
_buffer.Read(tag.StreamId, 0, 3);
currentTag = tag;
byte[] rest = _buffer.GetBuffer().Skip((int)_buffer.Position).Take((int)(dataLen - _buffer.Position)).ToArray();
_buffer.Position = 0;
_AddBytes(rest);
}
2018-03-19 16:51:35 +08:00
public FlvClipProcessor Clip()
2018-03-19 01:05:02 +08:00
{
2018-03-21 20:56:56 +08:00
if (!Finallized)
lock (_writelock)
{
2018-03-23 06:57:22 +08:00
return new FlvClipProcessor(Metadata, new List<FlvTag>(Tags.ToArray()), Clip_Future);
2018-03-21 20:56:56 +08:00
}
return 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-03-21 20:56:56 +08:00
if (!Finallized)
lock (_writelock)
{
Metadata.Meta["duration"] = MaxTimeStamp / 1000.0;
Metadata.Meta["lasttimestamp"] = (double)MaxTimeStamp;
byte[] metadata = Metadata.ToBytes();
2018-03-20 00:12:32 +08:00
2018-03-21 20:56:56 +08:00
// 13 for FLV header & "0th" tag size
// 11 for 1st tag header
_fs.Seek(13 + 11, SeekOrigin.Begin);
_fs.Write(metadata, 0, metadata.Length);
2018-03-19 16:51:35 +08:00
2018-03-21 20:56:56 +08:00
_fs.Close();
_buffer.Close();
_data.Close();
Tags.Clear();
2018-03-19 16:51:35 +08:00
2018-03-21 20:56:56 +08:00
Finallized = true;
2018-03-19 16:51:35 +08:00
2018-03-21 20:56:56 +08:00
StreamFinalized?.Invoke(this, new StreamFinalizedArgs() { StreamProcessor = this });
}
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
_buffer.Dispose();
_data.Dispose();
_fs.Dispose();
2018-03-12 18:57:20 +08:00
}
2018-03-21 20:56:56 +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
}
}