using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using BililiveRecorder.Flv.Amf;
namespace BililiveRecorder.Flv.Writer
{
///
/// 输出时用于生成 OnMetaData 中的 keyframes。直接建 object tree 再序列化太太太太慢了
///
internal class KeyframesScriptDataValue : IScriptDataValue
{
/*
* 最少能保存大约 6300 * 2 second = 3.5 hour 的关键帧索引
* 如果以 5 秒计算则 6300 * 5 second = 8.75 hour
*/
private const int MaxDataCount = 6300;
private const double MinInterval = 1900;
private const string Keyframes = "keyframes";
private const string Times = "times";
private const string FilePositions = "filepositions";
private const string Spacer = "spacer";
private static readonly byte[] EndBytes = new byte[] { 0, 0, 9 };
private static readonly byte[] TimesBytes = Encoding.UTF8.GetBytes(Times);
private static readonly byte[] FilePositionsBytes = Encoding.UTF8.GetBytes(FilePositions);
private static readonly byte[] SpacerBytes = Encoding.UTF8.GetBytes(Spacer);
public ScriptDataType Type => ScriptDataType.Object;
private readonly List KeyframesData = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddData(double time_in_ms, double filePosition)
{
var keyframesData = this.KeyframesData;
if (keyframesData.Count < MaxDataCount && (keyframesData.Count == 0 || ((time_in_ms - keyframesData[keyframesData.Count - 1].Time) > MinInterval)))
{
keyframesData.Add(new Data(time: time_in_ms / 1000d, filePosition: filePosition));
}
}
///
///
///
///
///
///
public void WriteTo(Stream stream)
{
stream.WriteByte((byte)this.Type);
var keyframesData = this.KeyframesData;
var buffer = new byte[sizeof(double)];
{
// key
WriteKey(stream, TimesBytes);
// array
WriteStrictArray(stream, (uint)keyframesData.Count);
// value
for (var i = 0; i < keyframesData.Count; i++)
{
stream.WriteByte((byte)ScriptDataType.Number);
BinaryPrimitives.WriteInt64BigEndian(buffer, BitConverter.DoubleToInt64Bits(keyframesData[i].Time));
stream.Write(buffer);
}
}
{
// key
WriteKey(stream, FilePositionsBytes);
// array
WriteStrictArray(stream, (uint)keyframesData.Count);
// value
for (var i = 0; i < keyframesData.Count; i++)
{
stream.WriteByte((byte)ScriptDataType.Number);
BinaryPrimitives.WriteInt64BigEndian(buffer, BitConverter.DoubleToInt64Bits(keyframesData[i].FilePosition));
stream.Write(buffer);
}
}
{
// key
WriteKey(stream, SpacerBytes);
// array
var count = 2u * (uint)(MaxDataCount - keyframesData.Count);
WriteStrictArray(stream, count);
// value
BinaryPrimitives.WriteInt64BigEndian(buffer, BitConverter.DoubleToInt64Bits(double.NaN));
for (var i = 0; i < count; i++)
{
stream.WriteByte((byte)ScriptDataType.Number);
stream.Write(buffer);
}
}
stream.Write(EndBytes);
}
public readonly struct Data
{
public readonly double Time;
public readonly double FilePosition;
public Data(double time, double filePosition)
{
this.Time = time;
this.FilePosition = filePosition;
}
}
///
///
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void WriteStrictArray(Stream stream, uint count)
{
stream.WriteByte((byte)ScriptDataType.StrictArray);
var buffer = new byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32BigEndian(buffer, count);
stream.Write(buffer);
}
///
///
///
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteKey(Stream stream, string key)
{
var bytes = Encoding.UTF8.GetBytes(key);
if (bytes.Length > ushort.MaxValue)
throw new AmfException($"Cannot write more than {ushort.MaxValue} into ScriptDataString");
var buffer = new byte[sizeof(ushort)];
BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)bytes.Length);
stream.Write(buffer);
stream.Write(bytes);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteKey(Stream stream, byte[] bytes)
{
//if (bytes.Length > ushort.MaxValue)
// throw new AmfException($"Cannot write more than {ushort.MaxValue} into ScriptDataString");
var buffer = new byte[sizeof(ushort)];
BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)bytes.Length);
stream.Write(buffer);
stream.Write(bytes);
}
}
}