瀏覽代碼

1.增加指数哥伦布编码及测试

2.增加H264解析器
3.修改ci为core3
tags/v1.1.0
smallchi 5 年之前
父節點
當前提交
1179d18036
共有 11 個檔案被更改,包括 590 行新增1 行删除
  1. +1
    -1
      .travis.yml
  2. +20
    -0
      src/JT1078.Flv.Test/H264/NALUHeaderTest.cs
  3. +30
    -0
      src/JT1078.Flv.Test/MessagePack/ExpGolombReaderTest.cs
  4. +66
    -0
      src/JT1078.Flv/H264/H264Demuxer.cs
  5. +16
    -0
      src/JT1078.Flv/H264/H264NALU.cs
  6. +25
    -0
      src/JT1078.Flv/H264/NALUHeader.cs
  7. +4
    -0
      src/JT1078.Flv/JT1078.Flv.csproj
  8. +312
    -0
      src/JT1078.Flv/MessagePack/EXPGolombReader.cs
  9. +65
    -0
      src/JT1078.Flv/Metadata/AVCDecoderConfigurationRecord.cs
  10. +46
    -0
      src/JT1078.Protocol/JT1078Demuxer.cs
  11. +5
    -0
      src/JT1078.Protocol/JT1078Package.cs

+ 1
- 1
.travis.yml 查看文件

@@ -1,6 +1,6 @@
language: csharp
solution: JT1078.sln
dotnet: 2.2.101
dotnet: 3.0.100
os: linux
mono: none
dist: trusty2


+ 20
- 0
src/JT1078.Flv.Test/H264/NALUHeaderTest.cs 查看文件

@@ -0,0 +1,20 @@
using JT1078.Flv.H264;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;

namespace JT1078.Flv.Test.H264
{
public class NALUHeaderTest
{
[Fact]
public void Test1()
{
NALUHeader header = new NALUHeader(0xc0);
Assert.Equal(1, header.ForbiddenZeroBit);
Assert.Equal(2, header.NalRefIdc);
Assert.Equal(0, header.NalUnitType);
}
}
}

+ 30
- 0
src/JT1078.Flv.Test/MessagePack/ExpGolombReaderTest.cs 查看文件

@@ -0,0 +1,30 @@
using System.Buffers.Binary;
using Xunit;
using JT1078.Flv.MessagePack;

namespace JT1078.Flv.Test.MessagePack
{
public class ExpGolombReaderTest
{
[Fact]
public void Test1()
{
ExpGolombReader h264GolombReader = new ExpGolombReader(new byte[] { 103, 77, 0, 20, 149, 168, 88, 37, 144, 0 });
var result = h264GolombReader.ReadSPS();
Assert.Equal(77, result.profileIdc);
Assert.Equal(0u, result.profileCompat);
Assert.Equal(20, result.levelIdc);
Assert.Equal(352, result.width);
Assert.Equal(288, result.height);
//profileIdc 77
//profileCompat 0
//levelIdc 20
//picOrderCntType 2
//picWidthInMbsMinus1 21
//picHeightInMapUnitsMinus1 17
//frameMbsOnlyFlag 1
//width 352
//height 288
}
}
}

+ 66
- 0
src/JT1078.Flv/H264/H264Demuxer.cs 查看文件

@@ -0,0 +1,66 @@
using JT1078.Flv.Extensions;
using JT1078.Flv.MessagePack;
using JT1078.Protocol;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace JT1078.Flv.H264
{
public class H264Demuxer
{
public const string codecstring = "avc1.";


/// <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>
public byte[] DiscardEmulationPreventionBytes(ReadOnlySpan<byte> srcBuffer)
{
int length = srcBuffer.Length;
List<int> EPBPositions = new List<int>();
int i = 1;
// Find all `Emulation Prevention Bytes`
while (i < length - 2)
{
if (srcBuffer[i] == 0 && srcBuffer[i + 1] == 0 && srcBuffer[i + 2] == 0x03)
{
EPBPositions.Add(i + 2);
i += 2;
}
else
{
i++;
}
}
// If no Emulation Prevention Bytes were found just return the original
// array
if (EPBPositions.Count == 0)
{
return srcBuffer.ToArray();
}
// Create a new array to hold the NAL unit data
int newLength = length - EPBPositions.Count;
byte[] newBuffer = new byte[newLength];
var sourceIndex = 0;
for (i = 0; i < newLength; sourceIndex++, i++)
{
if (sourceIndex == EPBPositions[0])
{
// Skip this byte
sourceIndex++;
// Remove this position index
EPBPositions.RemoveAt(0);
}
newBuffer[i] = srcBuffer[sourceIndex];
}
return newBuffer;
}
}
}

+ 16
- 0
src/JT1078.Flv/H264/H264NALU.cs 查看文件

@@ -0,0 +1,16 @@
using JT1078.Protocol;
using System;
using System.Collections.Generic;
using System.Text;

namespace JT1078.Flv.H264
{
public class H264NALU
{
public readonly static byte[] Start1 = new byte[3] { 0, 0, 1 };
public readonly static byte[] Start2 = new byte[4] { 0, 0, 0, 1 };
public byte[] StartCodePrefix { get; set; }
public NALUHeader NALUHeader { get; set; }
public JT1078Package JT1078Package { get; set; }
}
}

+ 25
- 0
src/JT1078.Flv/H264/NALUHeader.cs 查看文件

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace JT1078.Flv.H264
{
public struct NALUHeader
{
public NALUHeader(byte value)
{
ForbiddenZeroBit = (value & 0x80) >> 7;
NalRefIdc = (value & 0x60) >> 5;
NalUnitType = value & 0x1f;
}
public NALUHeader(ReadOnlySpan<byte> value)
{
ForbiddenZeroBit = (value[0] & 0x80) >> 7;
NalRefIdc = (value[0] & 0x60) >> 5;
NalUnitType = value[0] & 0x1f;
}
public int ForbiddenZeroBit { get; set; }
public int NalRefIdc { get; set; }
public int NalUnitType { get; set; }
}
}

+ 4
- 0
src/JT1078.Flv/JT1078.Flv.csproj 查看文件

@@ -8,5 +8,9 @@
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Memory" Version="4.5.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\JT1078.Protocol\JT1078.Protocol.csproj" />
</ItemGroup>
</Project>

+ 312
- 0
src/JT1078.Flv/MessagePack/EXPGolombReader.cs 查看文件

@@ -0,0 +1,312 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Text;

namespace JT1078.Flv.MessagePack
{
/// <summary>
/// Exp-Golomb指数哥伦布编码
/// </summary>
public ref struct ExpGolombReader
{
public ReadOnlySpan<byte> SrcBuffer { get; }
public int BytesAvailable { get; private set; }
public int Word { get; private set; }
public int BitsAvailable { get; private set; }
public ExpGolombReader(ReadOnlySpan<byte> srcBuffer)
{
SrcBuffer = srcBuffer;
BytesAvailable = srcBuffer.Length;
Word = 0;
BitsAvailable = 0;
}
public (byte profileIdc,byte levelIdc,uint profileCompat,int width, int height) ReadSPS()
{
int sarScale = 1;
uint frameCropLeftOffset=0;
uint frameCropRightOffset = 0;
uint frameCropTopOffset = 0;
uint frameCropBottomOffset = 0;
ReadByte();
//profile_idc
byte profileIdc = ReadByte();
//constraint_set[0-4]_flag, u(5)
uint profileCompat = ReadBits(5);
//reserved_zero_3bits
SkipBits(3);
//level_idc u(8)
byte levelIdc = ReadByte();
//seq_parameter_set_id
SkipUEG();
if (profileIdc == 100 ||
profileIdc == 110 ||
profileIdc == 122 ||
profileIdc == 244 ||
profileIdc == 44 ||
profileIdc == 83 ||
profileIdc == 86 ||
profileIdc == 118 ||
profileIdc == 128)
{
uint chromaFormatIdc = ReadUEG();
if (chromaFormatIdc == 3)
{
SkipBits(1); // separate_colour_plane_flag
}
SkipUEG(); // bit_depth_luma_minus8
SkipUEG(); // bit_depth_chroma_minus8
SkipBits(1); // qpprime_y_zero_transform_bypass_flag
if (ReadBoolean())
{ // seq_scaling_matrix_present_flag
int scalingListCount = (chromaFormatIdc != 3) ? 8 : 12;
for (int i = 0; i < scalingListCount; i++)
{
if (ReadBoolean())
{ // seq_scaling_list_present_flag[ i ]
if (i < 6)
{
SkipScalingList(16);
}
else
{
SkipScalingList(64);
}
}
}
}
}
// log2_max_frame_num_minus4
SkipUEG();
var picOrderCntType = ReadUEG();
if (picOrderCntType == 0)
{
ReadUEG(); //log2_max_pic_order_cnt_lsb_minus4
}
else if (picOrderCntType == 1)
{
SkipBits(1); // delta_pic_order_always_zero_flag
SkipEG(); // offset_for_non_ref_pic
SkipEG(); // offset_for_top_to_bottom_field
uint numRefFramesInPicOrderCntCycle = ReadUEG();
for (int i = 0; i < numRefFramesInPicOrderCntCycle; i++)
{
SkipEG(); // offset_for_ref_frame[ i ]
}
}
SkipUEG(); // max_num_ref_frames
SkipBits(1); // gaps_in_frame_num_value_allowed_flag
uint picWidthInMbsMinus1 = ReadUEG();
uint picHeightInMapUnitsMinus1 = ReadUEG();
uint frameMbsOnlyFlag = ReadBits(1);
if (frameMbsOnlyFlag == 0)
{
SkipBits(1); // mb_adaptive_frame_field_flag
}
this.SkipBits(1); // direct_8x8_inference_flag
if (ReadBoolean())
{
// frame_cropping_flag
frameCropLeftOffset = ReadUEG();
frameCropRightOffset = ReadUEG();
frameCropTopOffset = ReadUEG();
frameCropBottomOffset = ReadUEG();
}
if (ReadBoolean())
{
// vui_parameters_present_flag
if (ReadBoolean())
{
// aspect_ratio_info_present_flag
byte[] sarRatio=null;
byte aspectRatioIdc = ReadByte();
switch (aspectRatioIdc)
{
case 1: sarRatio =new byte[2] { 1, 1 }; break;
case 2: sarRatio =new byte[2] { 12, 11}; break;
case 3: sarRatio =new byte[2] { 10, 11}; break;
case 4: sarRatio =new byte[2] { 16, 11}; break;
case 5: sarRatio =new byte[2] { 40, 33}; break;
case 6: sarRatio =new byte[2] { 24, 11}; break;
case 7: sarRatio =new byte[2] { 20, 11}; break;
case 8: sarRatio =new byte[2] { 32, 11 }; break;
case 9: sarRatio = new byte[2] {80, 33 }; break;
case 10: sarRatio = new byte[2]{18, 11 }; break;
case 11: sarRatio = new byte[2]{15, 11 }; break;
case 12: sarRatio = new byte[2]{64, 33 }; break;
case 13: sarRatio = new byte[2]{160, 99 }; break;
case 14: sarRatio = new byte[2]{4, 3 }; break;
case 15: sarRatio = new byte[2]{3, 2 }; break;
case 16: sarRatio = new byte[2]{ 2, 1 }; break;
case 255:
{
sarRatio = new byte[2] { (byte)(ReadByte() << 8 | ReadByte()), (byte)(ReadByte() << 8 | ReadByte()) };
break;
}
}
if (sarRatio != null)
{
sarScale = sarRatio[0] / sarRatio[1];
}
}
}
int width= (int)((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale);
int height = (int)(((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - ((frameMbsOnlyFlag == 1U ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset)));
return (profileIdc, levelIdc, profileCompat,width, height);
}
public void LoadWord()
{
var position = SrcBuffer.Length - BytesAvailable;
int tmpAvailableBytes = BytesAvailable - 4;
int availableBytes = Math.Min(4, BytesAvailable);
//if (availableBytes == 0)
//{
// throw new OverflowException("no bytes available");
//}
ReadOnlySpan<byte> workingBytes=ReadOnlySpan<byte>.Empty;
if (tmpAvailableBytes < 0)
{
var buffer = new byte[4];
Array.Copy(SrcBuffer.Slice(position, BytesAvailable).ToArray(), buffer, BytesAvailable);
workingBytes = buffer;
}
else
{
workingBytes = SrcBuffer.Slice(position, 4);
}
Word = BinaryPrimitives.ReadInt32BigEndian(workingBytes);
// track the amount of this.data that has been processed
BitsAvailable = availableBytes * 8;
BytesAvailable -= availableBytes;
}
public void SkipBits(int count)
{
if (BitsAvailable > count)
{
Word <<= count;
BitsAvailable -= count;
}
else
{
count -= BitsAvailable;
int skipBytes = count >> 3;
count -= (skipBytes >> 3);
LoadWord();
Word <<= count;
BitsAvailable -= count;
}
}
public uint ReadBits(int size)
{
var bits = Math.Min(BitsAvailable, size); // :uint
var valu = (uint)Word >> (32 - bits); // :uint
if (size > 32)
{
throw new OverflowException("Cannot read more than 32 bits at a time");
}
BitsAvailable -= bits;
if (BitsAvailable > 0)
{
Word <<= bits;
}
else if (BytesAvailable > 0)
{
LoadWord();
}
bits = size - bits;
if (bits > 0)
{
return ((valu << bits) | ReadBits(bits));
}
else
{
return valu;
}
}
public int SkipLZ()
{
int leadingZeroCount; // :uint
for (leadingZeroCount = 0; leadingZeroCount < this.BitsAvailable; ++leadingZeroCount)
{
if (0 != (Word & (0x80000000 >> leadingZeroCount)))
{
// the first bit of working word is 1
Word <<= leadingZeroCount;
BitsAvailable -= leadingZeroCount;
return leadingZeroCount;
}
}
// we exhausted word and still have not found a 1
LoadWord();
return (leadingZeroCount + SkipLZ());
}
public void SkipUEG()
{
SkipBits(1 + SkipLZ());
}
public void SkipEG()
{
SkipBits(1 + SkipLZ());
}
public uint ReadUEG()
{
var clz =SkipLZ();
return ReadBits(clz + 1) - 1;
}
public int ReadEG()
{
var valu = (int)ReadUEG(); // :int
if ((0x01 & valu)==1)
{
// the number is odd if the low order bit is set
return (1 + valu) >> 1; // add 1 to make it even, and divide by 2
}
else
{
return -1 * (valu >> 1); // divide by two then make it negative
}
}
public bool ReadBoolean()
{
return 1 == ReadBits(1);
}
public byte ReadByte()
{
return (byte)ReadBits(8);
}
public ushort ReadUShort()
{
return (ushort)ReadBits(16);
}
public uint ReadUInt()
{
return ReadBits(32);
}

/// <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>
public void SkipScalingList(int count)
{
int lastScale = 8,
nextScale = 8,
j,
deltaScale;
for (j = 0; j < count; j++)
{
if (nextScale != 0)
{
deltaScale = ReadEG();
nextScale = (lastScale + deltaScale + 256) % 256;
}
lastScale = (nextScale == 0) ? lastScale : nextScale;
}
}
}
}

+ 65
- 0
src/JT1078.Flv/Metadata/AVCDecoderConfigurationRecord.cs 查看文件

@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace JT1078.Flv.Metadata
{


/// <summary>
/// <code>
///AVCDecoderConfigurationRecord 结构的定义:
///aligned(8) class AVCDecoderConfigurationRecord
///{
///unsigned int (8) configurationVersion = 1;
///unsigned int (8) AVCProfileIndication;
///unsigned int (8) profile_compatibility;
///unsigned int (8) AVCLevelIndication;
///bit(6) reserved = ‘111111’b;
///unsigned int (2) lengthSizeMinusOne;
///bit(3) reserved = ‘111’b;
///unsigned int (5) numOfSequenceParameterSets;
///for (i=0; i<numOfSequenceParameterSets; i++) {
///unsigned int (16) sequenceParameterSetLength ;
///bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;
///}
///unsigned int (8) numOfPictureParameterSets;
///for (i=0; i<numOfPictureParameterSets; i++) {
///unsigned int (16) pictureParameterSetLength;
///bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;
///}
///}
/// </code>
/// </summary>
public class AVCDecoderConfigurationRecord
{
public byte ConfigurationVersion { get; set; } = 1;
public byte AVCProfileIndication { get; set; }
public byte ProfileCompatibility { get; set; }
public byte AVCLevelIndication { get; set; }
public int LengthSizeMinusOne { get; set; }
public int NumOfSequenceParameterSets { get; set; }
public List<SPSInfo> SPS { get; set; }
public byte[] SPSBuffer { get; set; }
public byte NumOfPictureParameterSets { get; set; } = 1;
public List<PPSInfo> PPS { get; set; }
public byte[] PPSBuffer { get; set; }
#region Just for non-spec-conform encoders ref:org.mp4parser.boxes.iso14496.part15.AvcDecoderConfigurationRecord
public const int LengthSizeMinusOnePaddingBits = 63;
public const int NumberOfSequenceParameterSetsPaddingBits = 7;
public const int ChromaFormatPaddingBits = 31;
public const int BitDepthLumaMinus8PaddingBits = 31;
public const int BitDepthChromaMinus8PaddingBits = 31;
#endregion
public struct SPSInfo
{
public ushort SequenceParameterSetLength { get; set; }
public byte[] SequenceParameterSetNALUnit { get; set; }
}
public struct PPSInfo
{
public ushort PictureParameterSetLength { get; set; }
public byte[] PictureParameterSetNALUnit { get; set; }
}
}
}

+ 46
- 0
src/JT1078.Protocol/JT1078Demuxer.cs 查看文件

@@ -0,0 +1,46 @@
using JT1078.Protocol.Enums;
using System;
using System.Linq;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;

namespace JT1078.Protocol
{
public static class JT1078Demuxer
{
private readonly static ConcurrentDictionary<string, JT1078Package> JT1078PackageGroupDict = new ConcurrentDictionary<string, JT1078Package>(StringComparer.OrdinalIgnoreCase);
public static JT1078Package Demuxer(JT1078Package jT1078Package)
{
string cacheKey = jT1078Package.GetKey();
if (jT1078Package.Label3.SubpackageType == JT1078SubPackageType.分包处理时的第一个包)
{
JT1078PackageGroupDict.TryRemove(cacheKey, out _);
JT1078PackageGroupDict.TryAdd(cacheKey, jT1078Package);
return default;
}
else if (jT1078Package.Label3.SubpackageType == JT1078SubPackageType.分包处理时的中间包)
{
if (JT1078PackageGroupDict.TryGetValue(cacheKey, out var tmpPackage))
{
tmpPackage.Bodies.Concat(jT1078Package.Bodies).ToArray();
JT1078PackageGroupDict[cacheKey] = tmpPackage;
}
return default;
}
else if (jT1078Package.Label3.SubpackageType == JT1078SubPackageType.分包处理时的最后一个包)
{
if (JT1078PackageGroupDict.TryGetValue(cacheKey, out var tmpPackage))
{
tmpPackage.Bodies.Concat(jT1078Package.Bodies).ToArray();
return tmpPackage;
}
return default;
}
else
{
return jT1078Package;
}
}
}
}

+ 5
- 0
src/JT1078.Protocol/JT1078Package.cs 查看文件

@@ -86,5 +86,10 @@ namespace JT1078.Protocol
/// 数据体
/// </summary>
public byte[] Bodies{ get; set; }

public string GetKey()
{
return $"{SIM}_{LogicChannelNumber.ToString()}";
}
}
}

Loading…
取消
儲存