@@ -17,7 +17,7 @@ namespace JT1078.Flv.Test | |||
public class FlvEncoderTest | |||
{ | |||
[Fact] | |||
public void FlvEncoder_Test_1() | |||
public void 测试第一帧的数据() | |||
{ | |||
JT1078Package Package = null; | |||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_1.txt")); | |||
@@ -42,11 +42,14 @@ namespace JT1078.Flv.Test | |||
{ | |||
File.Delete(filepath); | |||
} | |||
File.WriteAllBytes(filepath, contents); | |||
FileStream fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||
fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); | |||
fileStream.Write(contents); | |||
fileStream.Close(); | |||
} | |||
[Fact] | |||
public void FlvEncoder_Test_2() | |||
public void 测试前几帧的数据() | |||
{ | |||
FileStream fileStream=null; | |||
try | |||
@@ -78,6 +81,7 @@ namespace JT1078.Flv.Test | |||
File.Delete(filepath); | |||
} | |||
fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||
fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); | |||
var totalPage = (h264NALULs.Count + 10 - 1) / 10; | |||
for(var i=0;i< totalPage; i++) | |||
{ | |||
@@ -100,7 +104,7 @@ namespace JT1078.Flv.Test | |||
} | |||
[Fact] | |||
public void FlvEncoder_Test_3() | |||
public void 测试可以播放的Flv1() | |||
{ | |||
FileStream fileStream = null; | |||
Flv.H264.H264Decoder decoder = new Flv.H264.H264Decoder(); | |||
@@ -134,6 +138,7 @@ namespace JT1078.Flv.Test | |||
} | |||
fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||
fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); | |||
var totalPage = (h264NALULs.Count + 10 - 1) / 10; | |||
for (var i = 0; i < totalPage; i++) | |||
{ | |||
@@ -156,7 +161,7 @@ namespace JT1078.Flv.Test | |||
} | |||
[Fact] | |||
public void FlvEncoder_Test_4() | |||
public void 测试可以播放的Flv2() | |||
{ | |||
FileStream fileStream = null; | |||
Flv.H264.H264Decoder decoder = new Flv.H264.H264Decoder(); | |||
@@ -189,7 +194,8 @@ namespace JT1078.Flv.Test | |||
} | |||
} | |||
fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||
fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||
fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); | |||
var totalPage = (h264NALULs.Count + 10 - 1) / 10; | |||
for (var i = 0; i < totalPage; i++) | |||
{ | |||
@@ -212,7 +218,7 @@ namespace JT1078.Flv.Test | |||
} | |||
[Fact] | |||
public void FlvEncoder_Test_5() | |||
public void 测试主次码流切换() | |||
{ | |||
FileStream fileStream = null; | |||
Flv.H264.H264Decoder decoder = new Flv.H264.H264Decoder(); | |||
@@ -19,6 +19,9 @@ namespace JT1078.Flv | |||
{ | |||
public class FlvEncoder | |||
{ | |||
/// <summary> | |||
/// Flv固定头部数据 | |||
/// </summary> | |||
public static readonly byte[] VideoFlvHeaderBuffer; | |||
private static readonly Flv.H264.H264Decoder H264Decoder; | |||
private static readonly ConcurrentDictionary<string, SPSInfo> VideoSPSDict; | |||
@@ -42,7 +45,7 @@ namespace JT1078.Flv | |||
{ | |||
logger = loggerFactory.CreateLogger("FlvEncoder"); | |||
} | |||
public byte[] CreateScriptTagFrame(int width, int height, double frameRate = 25d) | |||
internal byte[] CreateScriptTagFrame(int width, int height, double frameRate = 25d) | |||
{ | |||
byte[] buffer = FlvArrayPool.Rent(1024); | |||
try | |||
@@ -90,7 +93,7 @@ namespace JT1078.Flv | |||
FlvArrayPool.Return(buffer); | |||
} | |||
} | |||
public byte[] CreateVideoTag0Frame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo) | |||
internal byte[] CreateVideoTag0Frame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo) | |||
{ | |||
byte[] buffer = FlvArrayPool.Rent(2048); | |||
try | |||
@@ -125,7 +128,7 @@ namespace JT1078.Flv | |||
FlvArrayPool.Return(buffer); | |||
} | |||
} | |||
public byte[] CreateSecondVideoTag0Frame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo) | |||
internal byte[] CreateSecondVideoTag0Frame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo) | |||
{ | |||
byte[] buffer = FlvArrayPool.Rent(2048); | |||
try | |||
@@ -160,7 +163,7 @@ namespace JT1078.Flv | |||
FlvArrayPool.Return(buffer); | |||
} | |||
} | |||
public byte[] CreateVideoTagOtherFrame(FlvFrameInfo flvFrameInfo, H264NALU nALU, H264NALU sei) | |||
internal byte[] CreateVideoTagOtherFrame(FlvFrameInfo flvFrameInfo, H264NALU nALU, H264NALU sei) | |||
{ | |||
byte[] buffer = FlvArrayPool.Rent(65535); | |||
try | |||
@@ -334,7 +337,7 @@ namespace JT1078.Flv | |||
FlvArrayPool.Return(buffer); | |||
} | |||
} | |||
public (byte[] Buffer, uint PreviousTagSize) CreateFirstFlvKeyFrame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo) | |||
internal (byte[] Buffer, uint PreviousTagSize) CreateFirstFlvKeyFrame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo) | |||
{ | |||
byte[] buffer = FlvArrayPool.Rent(65535); | |||
try | |||
@@ -358,7 +361,7 @@ namespace JT1078.Flv | |||
FlvArrayPool.Return(buffer); | |||
} | |||
} | |||
public (byte[] Buffer,uint PreviousTagSize) CreateSecondFlvKeyFrame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo, FlvFrameInfo flvFrameInfo) | |||
internal (byte[] Buffer,uint PreviousTagSize) CreateSecondFlvKeyFrame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo, FlvFrameInfo flvFrameInfo) | |||
{ | |||
byte[] buffer = FlvArrayPool.Rent(65535); | |||
try | |||
@@ -384,17 +387,29 @@ namespace JT1078.Flv | |||
FlvArrayPool.Return(buffer); | |||
} | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
/// <param name="package">完整的1078包</param> | |||
/// <param name="minimumLength">默认65535</param> | |||
/// <returns></returns> | |||
public byte[] CreateFlvFrame(JT1078Package package,int minimumLength = 65535) | |||
{ | |||
var nalus = H264Decoder.ParseNALU(package); | |||
if (nalus == null || nalus.Count <= 0) return default; | |||
return CreateFlvFrame(nalus, minimumLength); | |||
} | |||
public byte[] GetFirstFlvFrame(string key,byte[] bufferFlvFrame) | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
/// <param name="key">设备号+通道号(1111111_1)</param> | |||
/// <param name="currentBufferFlvFrame">当前接收到的flv数据</param> | |||
/// <returns></returns> | |||
public byte[] GetFirstFlvFrame(string key,byte[] currentBufferFlvFrame) | |||
{ | |||
if (FirstFlvFrameCache.TryGetValue(key, out var firstBuffer)) | |||
{ | |||
var length = firstBuffer.Buffer.Length + bufferFlvFrame.Length + VideoFlvHeaderBuffer.Length; | |||
var length = firstBuffer.Buffer.Length + currentBufferFlvFrame.Length + VideoFlvHeaderBuffer.Length; | |||
byte[] buffer = FlvArrayPool.Rent(length); | |||
try | |||
{ | |||
@@ -406,16 +421,16 @@ namespace JT1078.Flv | |||
BinaryPrimitives.WriteUInt32BigEndian(firstBuffer.Buffer, 0); | |||
firstBuffer.Buffer.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length)); | |||
//新用户进来需要替换为上一包的PreviousTagSize | |||
BinaryPrimitives.WriteUInt32BigEndian(bufferFlvFrame, firstBuffer.PreviousTagSize); | |||
bufferFlvFrame.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length + firstBuffer.Buffer.Length)); | |||
BinaryPrimitives.WriteUInt32BigEndian(currentBufferFlvFrame, firstBuffer.PreviousTagSize); | |||
currentBufferFlvFrame.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length + firstBuffer.Buffer.Length)); | |||
return tmp.Slice(0, length).ToArray(); | |||
} | |||
else | |||
{ | |||
firstBuffer.Buffer.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length)); | |||
//新用户进来需要替换为首包的PreviousTagSize | |||
BinaryPrimitives.WriteUInt32BigEndian(bufferFlvFrame, firstBuffer.PreviousTagSize); | |||
bufferFlvFrame.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length + firstBuffer.Buffer.Length)); | |||
BinaryPrimitives.WriteUInt32BigEndian(currentBufferFlvFrame, firstBuffer.PreviousTagSize); | |||
currentBufferFlvFrame.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length + firstBuffer.Buffer.Length)); | |||
return tmp.Slice(0, length).ToArray(); | |||
} | |||
} | |||
@@ -428,7 +443,7 @@ namespace JT1078.Flv | |||
} | |||
} | |||
public class FlvFrameInfo | |||
internal class FlvFrameInfo | |||
{ | |||
public uint PreviousTagSize { get; set; } | |||
public ulong Timestamp { get; set; } | |||
@@ -14,12 +14,20 @@ | |||
<licenseUrl>https://github.com/SmallChi/JT1078/blob/master/LICENSE</licenseUrl> | |||
<license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | |||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> | |||
<Version>1.0.0-preview1</Version> | |||
<Version>1.0.0-preview2</Version> | |||
<SignAssembly>false</SignAssembly> | |||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | |||
<PackageLicenseFile>LICENSE</PackageLicenseFile> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'"> | |||
<DocumentationFile>JT1078.Flv.xml</DocumentationFile> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'"> | |||
<DocumentationFile>JT1078.Flv.xml</DocumentationFile> | |||
</PropertyGroup> | |||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' "> | |||
<PackageReference Include="System.Memory" Version="4.5.3" /> | |||
</ItemGroup> | |||
@@ -0,0 +1,203 @@ | |||
<?xml version="1.0"?> | |||
<doc> | |||
<assembly> | |||
<name>JT1078.Flv</name> | |||
</assembly> | |||
<members> | |||
<member name="T:JT1078.Flv.FlvBufferWriter"> | |||
<summary> | |||
<see cref="!:System.Buffers.Writer"/> | |||
</summary> | |||
</member> | |||
<member name="F:JT1078.Flv.Enums.FrameType.KeyFrame"> | |||
<summary> | |||
00010000 | |||
</summary> | |||
</member> | |||
<member name="F:JT1078.Flv.Enums.FrameType.InterFrame"> | |||
<summary> | |||
00100000 | |||
</summary> | |||
</member> | |||
<member name="F:JT1078.Flv.Enums.FrameType.DisposableInterFrame"> | |||
<summary> | |||
00110000 | |||
</summary> | |||
</member> | |||
<member name="F:JT1078.Flv.Enums.FrameType.GeneratedKeyFrame"> | |||
<summary> | |||
01000000 | |||
</summary> | |||
</member> | |||
<member name="F:JT1078.Flv.Enums.FrameType.VideoInfoOrCommandFrame"> | |||
<summary> | |||
01010000 | |||
</summary> | |||
</member> | |||
<member name="T:JT1078.Flv.Extensions.HexExtensions"> | |||
<summary> | |||
ref:"www.codeproject.com/tips/447938/high-performance-csharp-byte-array-to-hex-string-t" | |||
</summary> | |||
</member> | |||
<member name="M:JT1078.Flv.Extensions.HexExtensions.ToHexBytes(System.String)"> | |||
<summary> | |||
16进制字符串转16进制数组 | |||
</summary> | |||
<param name="hexString"></param> | |||
<param name="separator"></param> | |||
<returns></returns> | |||
</member> | |||
<member name="F:JT1078.Flv.FlvEncoder.VideoFlvHeaderBuffer"> | |||
<summary> | |||
Flv固定头部数据 | |||
</summary> | |||
</member> | |||
<member name="M:JT1078.Flv.FlvEncoder.CreateFlvFrame(JT1078.Protocol.JT1078Package,System.Int32)"> | |||
<summary> | |||
</summary> | |||
<param name="package">完整的1078包</param> | |||
<param name="minimumLength">默认65535</param> | |||
<returns></returns> | |||
</member> | |||
<member name="M:JT1078.Flv.FlvEncoder.GetFirstFlvFrame(System.String,System.Byte[])"> | |||
<summary> | |||
</summary> | |||
<param name="key">设备号+通道号(1111111_1)</param> | |||
<param name="currentBufferFlvFrame">当前接收到的flv数据</param> | |||
<returns></returns> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvTags.DataSize"> | |||
<summary> | |||
Tag Data部分大小 | |||
3个字节 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvTags.Timestamp"> | |||
<summary> | |||
Tag时间戳 | |||
3个字节 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvTags.StreamId"> | |||
<summary> | |||
stream id 总是0 | |||
3个字节 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvTags.VideoTagsData"> | |||
<summary> | |||
根据tag类型 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvTags.DataTagsData"> | |||
<summary> | |||
根据tag类型 | |||
</summary> | |||
</member> | |||
<member name="M:JT1078.Flv.H264.H264Decoder.ParseNALU(JT1078.Protocol.JT1078Package)"> | |||
<summary> | |||
<see cref="!:https://github.com/samirkumardas/jmuxer/blob/master/src/parsers/h264.js"/> | |||
</summary> | |||
<param name="package"></param> | |||
<returns></returns> | |||
</member> | |||
<member name="M:JT1078.Flv.H264.H264Decoder.DiscardEmulationPreventionBytes(System.ReadOnlySpan{System.Byte})"> | |||
<summary> | |||
Expunge any "Emulation Prevention" bytes from a "Raw Byte Sequence Payload" | |||
<see cref="!:https://blog.csdn.net/u011399342/article/details/80472084"/> | |||
防止竞争插入0x03 | |||
</summary> | |||
<param name="srcBuffer"></param> | |||
<returns></returns> | |||
</member> | |||
<member name="P:JT1078.Flv.H264.H264NALU.SIM"> | |||
<summary> | |||
终端设备SIM卡号 | |||
BCD[6] | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.H264.H264NALU.LogicChannelNumber"> | |||
<summary> | |||
逻辑通道号 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.H264.H264NALU.DataType"> | |||
<summary> | |||
数据类型 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.H264.H264NALU.LastIFrameInterval"> | |||
<summary> | |||
该帧与上一个关键帧之间的时间间隔,单位毫秒(ms), | |||
当数据类型为非视频帧时,则没有该字段 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.H264.H264NALU.LastFrameInterval"> | |||
<summary> | |||
该帧与上一个帧之间的时间间隔,单位毫秒(ms), | |||
当数据类型为非视频帧时,则没有该字段 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.H264.H264NALU.Timestamp"> | |||
<summary> | |||
时间戳 | |||
标识此RTP数据包当前帧的相对时间,单位毫秒(ms)。 | |||
当数据类型为01000时,则没有该字段 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.H264.H264NALU.RawData"> | |||
<summary> | |||
数据体 | |||
</summary> | |||
</member> | |||
<member name="T:JT1078.Flv.MessagePack.ExpGolombReader"> | |||
<summary> | |||
Exp-Golomb指数哥伦布编码 | |||
</summary> | |||
</member> | |||
<member name="M:JT1078.Flv.MessagePack.ExpGolombReader.SkipScalingList(System.Int32)"> | |||
<summary> | |||
Advance the ExpGolomb decoder past a scaling list.The scaling | |||
list is optionally transmitted as part of a sequence parameter | |||
set and is not relevant to transmuxing. | |||
@param count { number} | |||
the number of entries in this scaling list | |||
@see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 | |||
</summary> | |||
<param name="count"></param> | |||
</member> | |||
<member name="P:JT1078.Flv.Metadata.Amf3.DataType"> | |||
<summary> | |||
AMF3数据类型 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.Metadata.Amf3.Count"> | |||
<summary> | |||
元素个数 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.Metadata.Amf3Metadata_VideoCodecId.Value"> | |||
<summary> | |||
<see cref="!:typeof(JT1078.Flv.Enums.CodecId.AvcVideoPacke)"/> | |||
</summary> | |||
</member> | |||
<!-- Badly formed XML comment ignored for member "T:JT1078.Flv.Metadata.AVCDecoderConfigurationRecord" --> | |||
<member name="P:JT1078.Flv.Metadata.VideoTags.FrameType"> | |||
<summary> | |||
高4位 | |||
1: keyframe(for AVC, a seekable frame) —— 即H.264的IDR帧; | |||
2: inter frame(for AVC, a non- seekable frame) —— H.264的普通I帧; | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.Metadata.VideoTags.CodecId"> | |||
<summary> | |||
第四位 | |||
当 CodecID 为 7 时,VideoData 为 AVCVIDEOPACKE,也即 H.264媒体数据 | |||
</summary> | |||
</member> | |||
</members> | |||
</doc> |