瀏覽代碼

重构flv编码器

tags/v1.1.0
yedajiang44 5 年之前
父節點
當前提交
6c0cc80bb3
共有 10 個文件被更改,包括 496 次插入385 次删除
  1. +3
    -3
      src/JT1078.Flv/Audio/FaacEncoder.cs
  2. +5
    -0
      src/JT1078.Flv/Extensions/Amf3Extensions.cs
  3. +260
    -332
      src/JT1078.Flv/FlvEncoder.cs
  4. +62
    -45
      src/JT1078.Flv/JT1078.Flv.xml
  5. +27
    -0
      src/JT1078.Flv/Metadata/Amf3Metadata_AudioCodecId.cs
  6. +27
    -0
      src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleRate.cs
  7. +27
    -0
      src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleSize.cs
  8. +27
    -0
      src/JT1078.Flv/Metadata/Amf3Metadata_AudioStereo.cs
  9. +41
    -0
      src/JT1078.Protocol/Enums/Jt1078AudioType.cs
  10. +17
    -5
      src/JT1078.Protocol/JT1078Label2.cs

+ 3
- 3
src/JT1078.Flv/Audio/FaacEncoder.cs 查看文件

@@ -14,7 +14,7 @@ namespace JT1078.Flv.Audio
private readonly int maxOutput;
public readonly int frameSize;
private List<byte> frameCache = new List<byte>();
public FaacEncoder(int sampleRate, int channels, int sampleBit)
public FaacEncoder(int sampleRate, int channels, int sampleBit, bool adts = false)
{
var inputSampleBytes = new byte[4];
var maxOutputBytes = new byte[4];
@@ -27,7 +27,7 @@ namespace JT1078.Flv.Audio
var ptr = FaacEncGetCurrentConfiguration(faacEncHandle);
var configuration = InteropExtensions.IntPtrToStruct<FaacEncConfiguration>(ptr);
configuration.inputFormat = 1;
configuration.outputFormat = 0;
configuration.outputFormat = adts ? 1 : 0;
configuration.useTns = 0;
configuration.useLfe = 0;
configuration.aacObjectType = 2;
@@ -97,7 +97,7 @@ namespace JT1078.Flv.Audio
[DllImport(DLLFile, EntryPoint = "faacEncEncode", CallingConvention = CallingConvention.StdCall)]
private extern static int FaacEncEncode(IntPtr hEncoder, byte[] inputBuffer, int samplesInput, byte[] outputBuffer, int bufferSize);

[DllImport(DLLFile, EntryPoint= "faacEncClose", CallingConvention = CallingConvention.StdCall)]
[DllImport(DLLFile, EntryPoint = "faacEncClose", CallingConvention = CallingConvention.StdCall)]
//int FAACAPI faacEncClose(faacEncHandle hEncoder);
private extern static IntPtr FaacEncClose(IntPtr hEncoder);



+ 5
- 0
src/JT1078.Flv/Extensions/Amf3Extensions.cs 查看文件

@@ -15,5 +15,10 @@ namespace JT1078.Flv.Extensions
flvBuffer.Reverse();
flvBuffer.CopyTo(value);
}
public static void WriteBool(this IAmf3Metadata metadata, Span<byte> value)
{
var flvBuffer = BitConverter.GetBytes(Convert.ToBoolean(metadata.Value)).AsSpan();
flvBuffer.CopyTo(value);
}
}
}

+ 260
- 332
src/JT1078.Flv/FlvEncoder.cs 查看文件

@@ -1,194 +1,115 @@
using JT1078.Flv.Enums;
using JT1078.Flv.Extensions;
using JT1078.Flv.MessagePack;
using JT1078.Flv.Metadata;
using JT1078.Protocol;
using JT1078.Protocol.Enums;
using JT1078.Protocol.H264;
using JT1078.Protocol.MessagePack;
using Microsoft.Extensions.Logging;
using JT1078.Flv.Audio;
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using JT1078.Protocol;

[assembly: InternalsVisibleTo("JT1078.Flv.Test")]
namespace JT1078.Flv
{
public class FlvEncoder
/// <summary>
/// Flv编码器
/// 一个客户端对应一个实例
/// <para>
/// 当实例不适用时,尽量手动调用下<see cref="Dispose"/>
/// </para>
///
/// 手动编码
/// 1、<see cref="EncoderFlvHeader"/>
/// 2、<see cref="EncoderScriptTag"/>
/// 3、<see cref="EncoderFirstVideoTag"/>
/// 4、<see cref="EncoderFirstAudioTag"/>
/// 5、<see cref="EncoderVideoTag"/>第二个参数传false
/// 6、<see cref="EncoderAudioTag"/>第二个参数传false
/// 自动编码
/// 1、<see cref="EncoderFlvHeader"/>
/// 2、<see cref="EncoderScriptTag"/>
/// 3、<see cref="EncoderVideoTag"/>第二个参数传true
/// 4、<see cref="EncoderAudioTag"/>第二个参数传true
/// </summary>
public class FlvEncoder : IDisposable
{
/// <summary>
/// Flv固定头部数据
/// </summary>
public static readonly byte[] VideoFlvHeaderBuffer;
private static readonly H264Decoder H264Decoder;
private static readonly ConcurrentDictionary<string, SPSInfo> VideoSPSDict;
private static readonly ConcurrentDictionary<string, FlvFrameInfo> FlvFrameInfoDict;
internal static readonly ConcurrentDictionary<string, (uint PreviousTagSize, byte[] Buffer, bool Changed)> FirstFlvFrameCache;
private readonly ILogger logger;
static FlvEncoder()
uint previousTagSize;
FlvHeader flvHeader = new FlvHeader(true, true);
readonly FaacEncoder faacEncoder;
readonly H264Decoder h264Decoder = new H264Decoder();

public FlvEncoder(int sampleRate = 8000, int channels = 1, int sampleBit = 16, bool adts = false)
{
FlvHeader VideoFlvHeader = new FlvHeader(true, false);
VideoFlvHeaderBuffer = VideoFlvHeader.ToArray().ToArray();
VideoSPSDict = new ConcurrentDictionary<string, SPSInfo>(StringComparer.OrdinalIgnoreCase);
FlvFrameInfoDict = new ConcurrentDictionary<string, FlvFrameInfo>(StringComparer.OrdinalIgnoreCase);
FirstFlvFrameCache = new ConcurrentDictionary<string, (uint PreviousTagSize, byte[] Buffer, bool Changed)>(StringComparer.OrdinalIgnoreCase);
H264Decoder = new H264Decoder();
faacEncoder = new FaacEncoder(sampleRate, channels, sampleBit, adts);
}
public FlvEncoder()
{

}
public FlvEncoder(ILoggerFactory loggerFactory)
/// <summary>
/// 编码flv头
/// <para>
/// 注意:本方法已写入<see cref="previousTagSize"/>
/// </para>
/// </summary>
/// <param name="hasVideo"></param>
/// <param name="hasAudio"></param>
/// <returns></returns>
public byte[] EncoderFlvHeader(bool hasVideo = true, bool hasAudio = false)
{
logger = loggerFactory.CreateLogger("FlvEncoder");
previousTagSize = 0;
flvHeader = new FlvHeader(hasVideo, hasAudio);
return flvHeader.ToArray().ToArray();
}

/// <summary>
///
/// 编码脚本Tag
/// <para>
/// 注意:本方法已写入<see cref="previousTagSize"/>
/// </para>
/// </summary>
/// <param name="nALUs"></param>
/// <param name="key">由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值</param>
/// <param name="minimumLength"></param>
/// <param name="width">视频宽度</param>
/// <param name="height">视频高度</param>
/// <param name="hasAudio">是否含有音频,如果有,则写入音频配置,后来发现即便是有音频,这里给<c>false</c>也没关系</param>
/// <param name="frameRate">帧率</param>
/// <returns></returns>
public byte[] CreateFlvFrame(List<H264NALU> nALUs, string key = null, int minimumLength = 65535)
public byte[] EncoderScriptTag(bool hasAudio = false, double frameRate = 25d)
{
byte[] buffer = FlvArrayPool.Rent(minimumLength);
byte[] buffer = FlvArrayPool.Rent(1024);
try
{
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer);
H264NALU sps = null, pps = null, sei = null;
foreach (var naln in nALUs)
//flv body script tag
//flv body tag header
FlvTags flvTags = new FlvTags
{
key = key ?? naln.GetKey();
if (sps != null && pps != null)
Type = TagType.ScriptData,
//flv body tag body
DataTagsData = new Amf3
{
var rawData = H264Decoder.DiscardEmulationPreventionBytes(sps.RawData);
ExpGolombReader h264GolombReader = new ExpGolombReader(rawData);
SPSInfo spsInfo = h264GolombReader.ReadSPS();
if (VideoSPSDict.TryGetValue(key, out var spsInfoCache))
{
//切换主次码流
//根据宽高来判断
if (spsInfoCache.height != spsInfo.height && spsInfoCache.width != spsInfo.width)
{
if (logger != null)
{
if (logger.IsEnabled(LogLevel.Debug))
{
logger.LogDebug($"Cache:{spsInfoCache.height}-{spsInfoCache.width},Current:{spsInfo.height}-{spsInfo.width}");
}
}
VideoSPSDict.TryUpdate(key, spsInfo, spsInfoCache);
if (FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo flvFrameInfo))
{
flvFrameInfo.Timestamp = naln.Timestamp;
if (logger != null)
{
if (logger.IsEnabled(LogLevel.Debug))
{
logger.LogDebug($"Cache:{spsInfoCache.height}-{spsInfoCache.width},Current:{spsInfo.height}-{spsInfo.width}");
}
}
var secondFlvKeyFrame = CreateFirstFlvKeyFrame(sps.RawData, pps.RawData, spsInfo, flvFrameInfo.PreviousTagSize);
flvMessagePackWriter.WriteArray(secondFlvKeyFrame.Buffer);
flvFrameInfo.PreviousTagSize = secondFlvKeyFrame.PreviousTagSize;
FlvFrameInfoDict.TryUpdate(key, flvFrameInfo, flvFrameInfo);
if (FirstFlvFrameCache.TryGetValue(key, out var firstFlvFrameCacche))
{
FirstFlvFrameCache.TryUpdate(key, (secondFlvKeyFrame.PreviousTagSize, secondFlvKeyFrame.Buffer, true), firstFlvFrameCacche);
}
}
}
}
else
Amf3Metadatas = new List<IAmf3Metadata>
{
var firstFlvKeyFrame = CreateFirstFlvKeyFrame(sps.RawData, pps.RawData, spsInfo);
flvMessagePackWriter.WriteArray(firstFlvKeyFrame.Buffer);
if (logger != null)
{
if (logger.IsEnabled(LogLevel.Debug))
{
logger.LogDebug($"Current:{spsInfo.height}-{spsInfo.width}");
}
}
//cache PreviousTagSize
FlvFrameInfoDict.TryAdd(key, new FlvFrameInfo
{
PreviousTagSize = firstFlvKeyFrame.PreviousTagSize,
Interval = (uint)(pps.Timestamp - sps.Timestamp),
Timestamp = pps.Timestamp,
}
);
FirstFlvFrameCache.TryAdd(key, (firstFlvKeyFrame.PreviousTagSize, firstFlvKeyFrame.Buffer, false));
VideoSPSDict.TryAdd(key, spsInfo);
new Amf3Metadata_Duration{Value = 0d},
new Amf3Metadata_VideoDataRate{Value = 0d},
new Amf3Metadata_VideoCodecId{Value = 7d},
new Amf3Metadata_FrameRate{Value = frameRate},
new Amf3Metadata_Width(),
new Amf3Metadata_Height(),
}
sps = null;
pps = null;
continue;
}
if (logger != null)
{
if (logger.IsEnabled(LogLevel.Trace))
{
logger.LogTrace($"SIM:{naln.SIM},CH:{naln.LogicChannelNumber},{naln.DataType.ToString()},NalUnitType:{naln.NALUHeader.NalUnitType},RawData:{naln.RawData.ToHexString()}");
}
}
//7 8 6 5 1 1 1 1 7 8 6 5 1 1 1 1 1 7 8 6 5 1 1 1 1 1
switch (naln.NALUHeader.NalUnitType)
{
#warning 是否需要IDR帧? 每次发送IDR帧? 在测试的时候,是否由于图像变化不大所以不需要IDR帧?
case 5:// IDR
if (FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo idrInfo))
{
//当前的1078包与上一包1078的时间戳相减再进行累加
uint interval = (uint)(naln.Timestamp - idrInfo.Timestamp);
idrInfo.Interval += interval;
idrInfo.Timestamp = naln.Timestamp;
// PreviousTagSize
flvMessagePackWriter.WriteUInt32(idrInfo.PreviousTagSize);
// Data Tag Frame
var flvFrameBuffer = CreateVideoTagOtherFrame(idrInfo, naln, sei);
flvMessagePackWriter.WriteArray(flvFrameBuffer);
idrInfo.PreviousTagSize = (uint)flvFrameBuffer.Length;
idrInfo.LastDataType = naln.DataType;
FlvFrameInfoDict.TryUpdate(key, idrInfo, idrInfo);
}
break;
case 1:// I/P/B
if (FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo flvFrameInfo))
{
//当前的1078包与上一包1078的时间戳相减再进行累加
uint interval = (uint)(naln.Timestamp - flvFrameInfo.Timestamp);
flvFrameInfo.Interval += interval;
flvFrameInfo.Timestamp = naln.Timestamp;
// PreviousTagSize
flvMessagePackWriter.WriteUInt32(flvFrameInfo.PreviousTagSize);
// Data Tag Frame
var flvFrameBuffer = CreateVideoTagOtherFrame(flvFrameInfo, naln, sei);
flvMessagePackWriter.WriteArray(flvFrameBuffer);
flvFrameInfo.PreviousTagSize = (uint)flvFrameBuffer.Length;
flvFrameInfo.LastDataType = naln.DataType;
FlvFrameInfoDict.TryUpdate(key, flvFrameInfo, flvFrameInfo);
}
break;
case 7:// SPS
sps = naln;
break;
case 8:// PPS
pps = naln;
break;
case 6://SEI
sei = naln;
break;
default:
break;
}
};
if (hasAudio)
{
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioCodecId());
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioSampleRate());
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioSampleSize());
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_AudioStereo());
}
return flvMessagePackWriter.FlushAndGetArray();
flvMessagePackWriter.WriteUInt32(previousTagSize);
flvMessagePackWriter.WriteFlvTag(flvTags);
var data = flvMessagePackWriter.FlushAndGetArray();
previousTagSize = (uint)(flvTags.DataSize + 11);
return data;
}
finally
{
@@ -197,145 +118,180 @@ namespace JT1078.Flv
}

/// <summary>
///
/// </summary>
/// <param name="package">完整的1078包</param>
/// <param name="key">由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值</param>
/// <param name="minimumLength">默认65535</param>
/// <returns></returns>
public byte[] CreateFlvFrame(JT1078Package package, string key = null, int minimumLength = 65535)
{
var nalus = H264Decoder.ParseNALU(package);
if (nalus == null || nalus.Count <= 0) return default;
return CreateFlvFrame(nalus, key, minimumLength);
}
/// <summary>
///
/// 编码首帧视频,即videoTag[0]
/// <para>
/// 注意:本方法已写入<see cref="previousTagSize"/>
/// </para>
/// </summary>
/// <param name="key">设备号+通道号(1111111_1)</param>
/// <param name="currentBufferFlvFrame">当前接收到的flv数据</param>
/// <param name="sps"></param>
/// <param name="pps"></param>
/// <param name="sei"></param>
/// <returns></returns>
public byte[] GetFirstFlvFrame(string key, byte[] currentBufferFlvFrame)
{
if (FirstFlvFrameCache.TryGetValue(key, out var firstBuffer))
{
var length = firstBuffer.Buffer.Length + currentBufferFlvFrame.Length + VideoFlvHeaderBuffer.Length;
byte[] buffer = FlvArrayPool.Rent(length);
try
{
Span<byte> tmp = buffer;
VideoFlvHeaderBuffer.CopyTo(tmp);
if (firstBuffer.Changed)
{
//新用户进来需要替换为首包的PreviousTagSize 0
BinaryPrimitives.WriteUInt32BigEndian(firstBuffer.Buffer, 0);
firstBuffer.Buffer.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length));
//新用户进来需要替换为上一包的PreviousTagSize
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(currentBufferFlvFrame, firstBuffer.PreviousTagSize);
currentBufferFlvFrame.CopyTo(tmp.Slice(VideoFlvHeaderBuffer.Length + firstBuffer.Buffer.Length));
return tmp.Slice(0, length).ToArray();
}
}
finally
{
FlvArrayPool.Return(buffer);
}
}
return default;
}

internal byte[] CreateScriptTagFrame(int width, int height, double frameRate = 25d)
public byte[] EncoderFirstVideoTag(H264NALU sps, H264NALU pps, H264NALU sei)
{
byte[] buffer = FlvArrayPool.Rent(1024);
byte[] buffer = FlvArrayPool.Rent(2048);
try
{
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer);
//flv body script tag
var rawData = h264Decoder.DiscardEmulationPreventionBytes(sps.RawData);
ExpGolombReader h264GolombReader = new ExpGolombReader(rawData);
SPSInfo spsInfo = h264GolombReader.ReadSPS();
//flv body video tag
//flv body tag header
FlvTags flvTags = new FlvTags();
flvTags.Type = TagType.ScriptData;
flvTags.Timestamp = 0;
flvTags.TimestampExt = 0;
flvTags.StreamId = 0;
//flv body tag body
flvTags.DataTagsData = new Amf3();
flvTags.DataTagsData.Amf3Metadatas = new List<IAmf3Metadata>();
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_Duration
{
Value = 0d
});
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_VideoDataRate
FlvTags flvTags = new FlvTags
{
Value = 0d
});
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_VideoCodecId
{
Value = 7d
});
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_FrameRate
{
Value = frameRate
});
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_Width
Type = TagType.Video,
Timestamp = (uint)sps.Timestamp,
TimestampExt = 0,
StreamId = 0,
//flv body tag body
VideoTagsData = new VideoTags()
};
flvTags.VideoTagsData.FrameType = FrameType.KeyFrame;
flvTags.VideoTagsData.VideoData = new AvcVideoPacke
{
Value = width
});
flvTags.DataTagsData.Amf3Metadatas.Add(new Amf3Metadata_Height
AvcPacketType = AvcPacketType.SequenceHeader,
CompositionTime = 0
};
AVCDecoderConfigurationRecord aVCDecoderConfigurationRecord = new AVCDecoderConfigurationRecord
{
Value = height
});
AVCProfileIndication = spsInfo.profileIdc,
ProfileCompatibility = (byte)spsInfo.profileCompat,
AVCLevelIndication = spsInfo.levelIdc,
NumOfPictureParameterSets = 1,
PPSBuffer = pps.RawData,
SPSBuffer = sps.RawData
};
flvTags.VideoTagsData.VideoData.AVCDecoderConfiguration = aVCDecoderConfigurationRecord;
flvMessagePackWriter.WriteUInt32(previousTagSize);
flvMessagePackWriter.WriteFlvTag(flvTags);
return flvMessagePackWriter.FlushAndGetArray();
var data = flvMessagePackWriter.FlushAndGetArray();
previousTagSize = (uint)(flvTags.DataSize + 11);
return data;
}
finally
{
FlvArrayPool.Return(buffer);
}
}
internal byte[] CreateVideoTag0Frame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo)

/// <summary>
/// 编码首帧音频,即audioTag[0]
/// <para>
/// 注意:本方法已写入<see cref="previousTagSize"/>
/// </para>
/// </summary>
/// <returns></returns>
public byte[] EncoderFirstAudioTag(ulong timestamp)
{
byte[] buffer = FlvArrayPool.Rent(2048);
try
{
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer);
//flv body video tag
//flv body audio tag
//flv body tag header
FlvTags flvTags = new FlvTags();
flvTags.Type = TagType.Video;
flvTags.Timestamp = 0;
flvTags.TimestampExt = 0;
flvTags.StreamId = 0;
//flv body tag body
flvTags.VideoTagsData = new VideoTags();
flvTags.VideoTagsData.FrameType = FrameType.KeyFrame;
flvTags.VideoTagsData.VideoData = new AvcVideoPacke();
flvTags.VideoTagsData.VideoData.AvcPacketType = AvcPacketType.SequenceHeader;
flvTags.VideoTagsData.VideoData.CompositionTime = 0;
AVCDecoderConfigurationRecord aVCDecoderConfigurationRecord = new AVCDecoderConfigurationRecord();
aVCDecoderConfigurationRecord.AVCProfileIndication = spsInfo.profileIdc;
aVCDecoderConfigurationRecord.ProfileCompatibility = (byte)spsInfo.profileCompat;
aVCDecoderConfigurationRecord.AVCLevelIndication = spsInfo.levelIdc;
aVCDecoderConfigurationRecord.NumOfPictureParameterSets = 1;
aVCDecoderConfigurationRecord.PPSBuffer = ppsRawData;
aVCDecoderConfigurationRecord.SPSBuffer = spsRawData;
flvTags.VideoTagsData.VideoData.AVCDecoderConfiguration = aVCDecoderConfigurationRecord;
FlvTags flvTags = new FlvTags
{
Type = TagType.Audio,
Timestamp = (uint)timestamp,
//flv body tag body
AudioTagsData = new AudioTags(AACPacketType.AudioSpecificConfig)
};
flvMessagePackWriter.WriteUInt32(previousTagSize);
flvMessagePackWriter.WriteFlvTag(flvTags);
return flvMessagePackWriter.FlushAndGetArray();
var data = flvMessagePackWriter.FlushAndGetArray();
previousTagSize = (uint)(flvTags.DataSize + 11);
return data;
}
finally
{
FlvArrayPool.Return(buffer);
}
}
internal byte[] CreateVideoTagOtherFrame(FlvFrameInfo flvFrameInfo, H264NALU nALU, H264NALU sei)

/// <summary>
/// 编码非首帧视频
/// </summary>
/// <param name="package"></param>
/// <param name="needVideoHeader">是否需要首帧视频</param>
/// <returns></returns>
public byte[] EncoderVideoTag(JT1078Package package, bool needVideoHeader = false)
{
if (package.Label3.DataType == JT1078DataType.音频帧) return default;
byte[] buffer = FlvArrayPool.Rent(65535);
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer);
var nalus = h264Decoder.ParseNALU(package);
if (nalus != null && nalus.Count > 0)
{
var sei = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == 6);
var sps = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == 7);
var pps = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == 8);
nalus.Remove(sps);
nalus.Remove(pps);
nalus.Remove(sei);
if (needVideoHeader)
{
if (needVideoHeader)
{
var firstVideoTag = EncoderFirstVideoTag(sps, pps, sei);
flvMessagePackWriter.WriteArray(firstVideoTag);
}
}
foreach (var naln in nalus)
{
flvMessagePackWriter.WriteUInt32(previousTagSize);
var videoTag = ConversionNaluToVideoTag(naln);
flvMessagePackWriter.WriteArray(videoTag);
}
}
return flvMessagePackWriter.FlushAndGetArray();
}

/// <summary>
/// 编码非首帧音频
/// </summary>
/// <param name="package"></param>
/// <param name="needAacHeader">是否需要首帧音频</param>
/// <returns></returns>
public byte[] EncoderAudioTag(JT1078Package package, bool needAacHeader = false)
{
if (package.Label3.DataType != JT1078DataType.音频帧) throw new Exception("Incorrect parameter, package must be audio frame");
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(new byte[65536]);
if (needAacHeader)
{
flvMessagePackWriter.WriteArray(EncoderFirstAudioTag(package.Timestamp));
}
byte[] aacFrameData = null;
switch (package.Label2.PT)
{
case Jt1078AudioType.ADPCM:
ReadOnlySpan<byte> adpcm = package.Bodies;
// 海思芯片编码的音频需要移除海思头,可能还有其他的海思头
if (adpcm.StartsWith(new byte[] { 0x00, 0x01, 0x52, 0x00 })) adpcm = adpcm.Slice(4);
aacFrameData = faacEncoder.Encode(new AdpcmCodec().ToPcm(adpcm.Slice(4).ToArray(), new State()
{
Valprev = (short)((adpcm[1] << 8) | adpcm[0]),
Index = adpcm[2],
Reserved = adpcm[3]
})); break;
case Jt1078AudioType.G711A:
aacFrameData = faacEncoder.Encode(new G711ACodec().ToPcm(package.Bodies));
break;
case Jt1078AudioType.AACLC:
aacFrameData = package.Bodies;
break;
}
if (aacFrameData != null && aacFrameData.Any())//编码成功,此时为一帧aac音频数据
{
// PreviousTagSize
flvMessagePackWriter.WriteUInt32(previousTagSize);
// Data Tag Frame
flvMessagePackWriter.WriteArray(ConversionAacDataToAudioTag((uint)package.Timestamp, aacFrameData));
}
return flvMessagePackWriter.FlushAndGetArray();
}

byte[] ConversionNaluToVideoTag(H264NALU nALU)
{
byte[] buffer = FlvArrayPool.Rent(65535);
try
@@ -343,16 +299,20 @@ namespace JT1078.Flv
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer);
//flv body video tag
//flv body tag header
FlvTags flvTags = new FlvTags();
flvTags.Type = TagType.Video;
//pts
flvTags.Timestamp = flvFrameInfo.Interval;
flvTags.TimestampExt = 0;
flvTags.StreamId = 0;
//flv body tag body
flvTags.VideoTagsData = new VideoTags();
flvTags.VideoTagsData.VideoData = new AvcVideoPacke();
flvTags.VideoTagsData.VideoData.AvcPacketType = AvcPacketType.Raw;
FlvTags flvTags = new FlvTags
{
Type = TagType.Video,
//pts
Timestamp = (uint)nALU.Timestamp,
TimestampExt = 0,
StreamId = 0,
//flv body tag body
VideoTagsData = new VideoTags()
};
flvTags.VideoTagsData.VideoData = new AvcVideoPacke
{
AvcPacketType = AvcPacketType.Raw
};
//1: keyframe (for AVC, a seekable frame) —— 即H.264的IDR帧;
//2: inter frame(for AVC, a non - seekable frame) —— H.264的普通I帧;
//ref:https://www.cnblogs.com/chyingp/p/flv-getting-started.html
@@ -364,16 +324,7 @@ namespace JT1078.Flv
{
flvTags.VideoTagsData.FrameType = FrameType.InterFrame;
}
if (flvFrameInfo.LastDataType == JT1078DataType.视频I帧)
{
//cts
flvTags.VideoTagsData.VideoData.CompositionTime = nALU.LastIFrameInterval;
}
else
{
//cts
flvTags.VideoTagsData.VideoData.CompositionTime = nALU.LastFrameInterval;
}
flvTags.VideoTagsData.VideoData.CompositionTime = nALU.LastFrameInterval;
flvTags.VideoTagsData.VideoData.MultiData = new List<byte[]>();
flvTags.VideoTagsData.VideoData.MultiData.Add(nALU.RawData);
//忽略sei
@@ -382,69 +333,46 @@ namespace JT1078.Flv
// flvTags.VideoTagsData.VideoData.MultiData.Add(sei.RawData);
//}
flvMessagePackWriter.WriteFlvTag(flvTags);
return flvMessagePackWriter.FlushAndGetArray();
var data = flvMessagePackWriter.FlushAndGetArray();
previousTagSize = (uint)(flvTags.DataSize + 11);
return data;
}
finally
{
FlvArrayPool.Return(buffer);
}
}
internal (byte[] Buffer, uint PreviousTagSize) CreateFirstFlvKeyFrame(byte[] spsRawData, byte[] ppsRawData, SPSInfo spsInfo, uint previousTagSize = 0)

byte[] ConversionAacDataToAudioTag(uint timestamp, byte[] aacFrameData)
{
byte[] buffer = FlvArrayPool.Rent(65535);
try
{
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer);
//flv body PreviousTagSize awalys 0
flvMessagePackWriter.WriteUInt32(previousTagSize);
//flv body script tag
var scriptTagFrameBuffer = CreateScriptTagFrame(spsInfo.width, spsInfo.height);
flvMessagePackWriter.WriteArray(scriptTagFrameBuffer);
//flv script tag PreviousTagSize
flvMessagePackWriter.WriteUInt32((uint)scriptTagFrameBuffer.Length);
//flv body video tag 0
var videoTagFrame0Buffer = CreateVideoTag0Frame(spsRawData, ppsRawData, spsInfo);
flvMessagePackWriter.WriteArray(videoTagFrame0Buffer);
uint videoTag0PreviousTagSize = (uint)videoTagFrame0Buffer.Length;
return (flvMessagePackWriter.FlushAndGetArray(), videoTag0PreviousTagSize);
//flv body audio tag
//flv body tag header
FlvTags flvTags = new FlvTags
{
Type = TagType.Audio,
Timestamp = timestamp,
TimestampExt = 0,
StreamId = 0,
//flv body tag body
AudioTagsData = new AudioTags(AACPacketType.AudioFrame, aacFrameData)
};
flvMessagePackWriter.WriteFlvTag(flvTags);
previousTagSize = (uint)(flvTags.DataSize + 11);
return flvMessagePackWriter.FlushAndGetArray();
}
finally
{
FlvArrayPool.Return(buffer);
}
}
}

/// <summary>
/// flv存储帧信息
/// </summary>
internal class FlvFrameInfo
{
/// <summary>
/// flv上一帧的数据大小
/// </summary>
public uint PreviousTagSize { get; set; }
/// <summary>
/// 1078当前时间戳
/// </summary>
public ulong Timestamp { get; set; }

/// <summary>
/// 1078当前音频时间戳
/// </summary>
public ulong AudioTimestamp { get; set; }
/// <summary>
/// 与flv上一帧相减的时间间隔
/// </summary>
public uint Interval { get; set; }

/// <summary>
/// 上一帧音频的时间间隔
/// </summary>
public uint AudioInterval { get; set; }
/// <summary>
/// 1078数据类型
/// </summary>
public JT1078DataType LastDataType { get; set; }
public void Dispose()
{
faacEncoder.Dispose();
}
}
}

+ 62
- 45
src/JT1078.Flv/JT1078.Flv.xml 查看文件

@@ -205,71 +205,88 @@
<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(System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU},System.String,System.Int32)">
<member name="T:JT1078.Flv.FlvEncoder">
<summary>
Flv编码器
一个客户端对应一个实例
<para>
当实例不适用时,尽量手动调用下<see cref="M:JT1078.Flv.FlvEncoder.Dispose"/>
</para>
</summary>
<param name="nALUs"></param>
<param name="key">由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值</param>
<param name="minimumLength"></param>
手动编码
1、<see cref="M:JT1078.Flv.FlvEncoder.DecoderFlvHeader(System.Boolean,System.Boolean)"/>
2、<see cref="M:JT1078.Flv.FlvEncoder.DecoderScriptTag(System.Boolean,System.Double)"/>
3、<see cref="M:JT1078.Flv.FlvEncoder.DecoderFirstVideoTag(JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU)"/>
4、<see cref="M:JT1078.Flv.FlvEncoder.DecoderFirstAudioTag(System.UInt64)"/>
5、<see cref="M:JT1078.Flv.FlvEncoder.DecoderVideoTag(JT1078.Protocol.JT1078Package,System.Boolean)"/>第二个参数传false
6、<see cref="M:JT1078.Flv.FlvEncoder.DecoderAudioTag(JT1078.Protocol.JT1078Package,System.Boolean)"/>第二个参数传false
自动编码
1、<see cref="M:JT1078.Flv.FlvEncoder.DecoderFlvHeader(System.Boolean,System.Boolean)"/>
2、<see cref="M:JT1078.Flv.FlvEncoder.DecoderScriptTag(System.Boolean,System.Double)"/>
3、<see cref="M:JT1078.Flv.FlvEncoder.DecoderVideoTag(JT1078.Protocol.JT1078Package,System.Boolean)"/>第二个参数传true
4、<see cref="M:JT1078.Flv.FlvEncoder.DecoderAudioTag(JT1078.Protocol.JT1078Package,System.Boolean)"/>第二个参数传true
</summary>
</member>
<member name="M:JT1078.Flv.FlvEncoder.DecoderFlvHeader(System.Boolean,System.Boolean)">
<summary>
编码flv头
<para>
注意:本方法已写入<see cref="F:JT1078.Flv.FlvEncoder.previousTagSize"/>
</para>
</summary>
<param name="hasVideo"></param>
<param name="hasAudio"></param>
<returns></returns>
</member>
<member name="M:JT1078.Flv.FlvEncoder.CreateFlvFrame(JT1078.Protocol.JT1078Package,System.String,System.Int32)">
<member name="M:JT1078.Flv.FlvEncoder.DecoderScriptTag(System.Boolean,System.Double)">
<summary>
编码脚本Tag
<para>
注意:本方法已写入<see cref="F:JT1078.Flv.FlvEncoder.previousTagSize"/>
</para>
</summary>
<param name="package">完整的1078包</param>
<param name="key">由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值</param>
<param name="minimumLength">默认65535</param>
<param name="width">视频宽度</param>
<param name="height">视频高度</param>
<param name="hasAudio">是否含有音频,如果有,则写入音频配置,后来发现即便是有音频,这里给<c>false</c>也没关系</param>
<param name="frameRate">帧率</param>
<returns></returns>
</member>
<member name="M:JT1078.Flv.FlvEncoder.GetFirstFlvFrame(System.String,System.Byte[])">
<member name="M:JT1078.Flv.FlvEncoder.DecoderFirstVideoTag(JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU,JT1078.Protocol.H264.H264NALU)">
<summary>
编码首帧视频,即videoTag[0]
<para>
注意:本方法已写入<see cref="F:JT1078.Flv.FlvEncoder.previousTagSize"/>
</para>
</summary>
<param name="key">设备号+通道号(1111111_1)</param>
<param name="currentBufferFlvFrame">当前接收到的flv数据</param>
<param name="sps"></param>
<param name="pps"></param>
<param name="sei"></param>
<returns></returns>
</member>
<member name="T:JT1078.Flv.FlvFrameInfo">
<member name="M:JT1078.Flv.FlvEncoder.DecoderFirstAudioTag(System.UInt64)">
<summary>
flv存储帧信息
</summary>
</member>
<member name="P:JT1078.Flv.FlvFrameInfo.PreviousTagSize">
<summary>
flv上一帧的数据大小
</summary>
</member>
<member name="P:JT1078.Flv.FlvFrameInfo.Timestamp">
<summary>
1078当前时间戳
</summary>
</member>
<member name="P:JT1078.Flv.FlvFrameInfo.AudioTimestamp">
<summary>
1078当前音频时间戳
</summary>
</member>
<member name="P:JT1078.Flv.FlvFrameInfo.Interval">
<summary>
与flv上一帧相减的时间间隔
编码首帧音频,即audioTag[0]
<para>
注意:本方法已写入<see cref="F:JT1078.Flv.FlvEncoder.previousTagSize"/>
</para>
</summary>
<returns></returns>
</member>
<member name="P:JT1078.Flv.FlvFrameInfo.AudioInterval">
<member name="M:JT1078.Flv.FlvEncoder.DecoderVideoTag(JT1078.Protocol.JT1078Package,System.Boolean)">
<summary>
上一帧音频的时间间隔
编码非首帧视频
</summary>
<param name="package"></param>
<param name="needVideoHeader">是否需要首帧视频</param>
<returns></returns>
</member>
<member name="P:JT1078.Flv.FlvFrameInfo.LastDataType">
<member name="M:JT1078.Flv.FlvEncoder.DecoderAudioTag(JT1078.Protocol.JT1078Package,System.Boolean)">
<summary>
1078数据类型
编码非首帧音频
</summary>
<param name="package"></param>
<param name="needAacHeader">是否需要首帧音频</param>
<returns></returns>
</member>
<member name="P:JT1078.Flv.FlvTags.DataSize">
<summary>


+ 27
- 0
src/JT1078.Flv/Metadata/Amf3Metadata_AudioCodecId.cs 查看文件

@@ -0,0 +1,27 @@
using JT1078.Flv.Extensions;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Text;

namespace JT1078.Flv.Metadata
{
public class Amf3Metadata_AudioCodecId : IAmf3Metadata
{
public ushort FieldNameLength { get; set; }
public string FieldName { get; set; } = "audiocodecid";
public byte DataType { get; set; } = 0x00;
public object Value { get; set; } = 10d;

public ReadOnlySpan<byte> ToBuffer()
{
var b1 = Encoding.ASCII.GetBytes(FieldName);
Span<byte> tmp = new byte[2 + b1.Length + 1 + 8];
BinaryPrimitives.WriteUInt16BigEndian(tmp, (ushort)b1.Length);
b1.CopyTo(tmp.Slice(2));
tmp[FieldName.Length + 2] = DataType;
this.WriteDouble(tmp.Slice(b1.Length + 3));
return tmp;
}
}
}

+ 27
- 0
src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleRate.cs 查看文件

@@ -0,0 +1,27 @@
using JT1078.Flv.Extensions;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Text;

namespace JT1078.Flv.Metadata
{
public class Amf3Metadata_AudioSampleRate : IAmf3Metadata
{
public ushort FieldNameLength { get; set; }
public string FieldName { get; set; } = "audiosamplerate";
public byte DataType { get; set; } = 0x00;
public object Value { get; set; } = 8000d;

public ReadOnlySpan<byte> ToBuffer()
{
var b1 = Encoding.ASCII.GetBytes(FieldName);
Span<byte> tmp = new byte[2 + b1.Length + 1 + 8];
BinaryPrimitives.WriteUInt16BigEndian(tmp, (ushort)b1.Length);
b1.CopyTo(tmp.Slice(2));
tmp[FieldName.Length + 2] = DataType;
this.WriteDouble(tmp.Slice(b1.Length + 3));
return tmp;
}
}
}

+ 27
- 0
src/JT1078.Flv/Metadata/Amf3Metadata_AudioSampleSize.cs 查看文件

@@ -0,0 +1,27 @@
using JT1078.Flv.Extensions;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Text;

namespace JT1078.Flv.Metadata
{
public class Amf3Metadata_AudioSampleSize : IAmf3Metadata
{
public ushort FieldNameLength { get; set; }
public string FieldName { get; set; } = "audiosamplesize";
public byte DataType { get; set; } = 0x00;
public object Value { get; set; } = 16d;

public ReadOnlySpan<byte> ToBuffer()
{
var b1 = Encoding.ASCII.GetBytes(FieldName);
Span<byte> tmp = new byte[2 + b1.Length + 1 + 8];
BinaryPrimitives.WriteUInt16BigEndian(tmp, (ushort)b1.Length);
b1.CopyTo(tmp.Slice(2));
tmp[FieldName.Length + 2] = DataType;
this.WriteDouble(tmp.Slice(b1.Length + 3));
return tmp;
}
}
}

+ 27
- 0
src/JT1078.Flv/Metadata/Amf3Metadata_AudioStereo.cs 查看文件

@@ -0,0 +1,27 @@
using JT1078.Flv.Extensions;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Text;

namespace JT1078.Flv.Metadata
{
public class Amf3Metadata_AudioStereo : IAmf3Metadata
{
public ushort FieldNameLength { get; set; }
public string FieldName { get; set; } = "stereo";
public byte DataType { get; set; } = 0x01;
public object Value { get; set; } = false;

public ReadOnlySpan<byte> ToBuffer()
{
var b1 = Encoding.ASCII.GetBytes(FieldName);
Span<byte> tmp = new byte[2 + b1.Length + 1 + 1];
BinaryPrimitives.WriteUInt16BigEndian(tmp, (ushort)b1.Length);
b1.CopyTo(tmp.Slice(2));
tmp[FieldName.Length + 2] = DataType;
this.WriteBool(tmp.Slice(b1.Length + 3));
return tmp;
}
}
}

+ 41
- 0
src/JT1078.Protocol/Enums/Jt1078AudioType.cs 查看文件

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

namespace JT1078.Protocol.Enums
{
/// <summary>
/// 音频类型
/// </summary>
public enum Jt1078AudioType : byte
{
G721 = 1,
G722 = 2,
G723 = 3,
G728 = 4,
G729 = 5,
G711A = 6,
G711U = 7,
G726 = 8,
G729A = 9,
DVI4_3 = 10,
DVI_4 = 11,
DVI4_8K = 12,
DVI4_16K = 13,
LPC = 14,
S16BE_STEREO = 15,
S16E_MONO = 16,
MPEGAUDIO = 17,
LPCM = 18,
AAC = 19,
WMA9STD = 20,
HEAAC = 21,
PCM_VOICE = 22,
PCM_AUDIO = 23,
AACLC = 24,
MP3 = 25,
ADPCM = 26,
MP4AUDIO = 27,
AMR = 28
}
}

+ 17
- 5
src/JT1078.Protocol/JT1078Label2.cs 查看文件

@@ -1,4 +1,5 @@
using System;
using JT1078.Protocol.Enums;
using System;
using System.Text;

namespace JT1078.Protocol
@@ -12,7 +13,7 @@ namespace JT1078.Protocol
public JT1078Label2(byte value)
{
M = (byte)(value >> 7);
PT = (byte)(value & 0x7f);
PT = (Jt1078AudioType)(value & 0x7f);
}

/// <summary>
@@ -20,12 +21,23 @@ namespace JT1078.Protocol
/// </summary>
/// <param name="m">0-1</param>
/// <param name="pt">0-127</param>
public JT1078Label2(byte m,byte pt)
public JT1078Label2(byte m, Jt1078AudioType pt)
{
M = m;
PT = pt;
}

/// <summary>
///
/// </summary>
/// <param name="m">0-1</param>
/// <param name="pt">0-127</param>
public JT1078Label2(byte m,byte pt)
{
M = m;
PT = (Jt1078AudioType)pt;
}

/// <summary>
/// M - 1 - 标志位,确定是否是完整数据帧的边界
/// </summary>
@@ -34,11 +46,11 @@ namespace JT1078.Protocol
/// PT - 7 - 负载类型
/// 用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等
/// </summary>
public byte PT { get; set; }
public Jt1078AudioType PT { get; set; }

public byte ToByte()
{
return (byte)((M << 7) | PT);
return (byte)((M << 7) | (byte)PT);
}

public string BinaryCode { get { return ToString(); } }


Loading…
取消
儲存