@@ -10,6 +10,7 @@ using JT1078.Protocol.Enums; | |||||
using JT1078.Flv.H264; | using JT1078.Flv.H264; | ||||
using JT1078.Flv.MessagePack; | using JT1078.Flv.MessagePack; | ||||
using JT1078.Flv.Metadata; | using JT1078.Flv.Metadata; | ||||
using System.Diagnostics; | |||||
namespace JT1078.Flv.Test | namespace JT1078.Flv.Test | ||||
{ | { | ||||
@@ -35,15 +36,12 @@ namespace JT1078.Flv.Test | |||||
FlvEncoder encoder = new FlvEncoder(); | FlvEncoder encoder = new FlvEncoder(); | ||||
var contents = encoder.CreateFlvFrame(nalus); | var contents = encoder.CreateFlvFrame(nalus); | ||||
byte[] tmp = new byte[FlvEncoder.VideoFlvHeaderBuffer.Length+ contents.Length]; | |||||
Array.Copy(FlvEncoder.VideoFlvHeaderBuffer, tmp, FlvEncoder.VideoFlvHeaderBuffer.Length); | |||||
Array.Copy(contents,0,tmp, FlvEncoder.VideoFlvHeaderBuffer.Length, contents.Length); | |||||
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_1.flv"); | var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_1.flv"); | ||||
if (File.Exists(filepath)) | if (File.Exists(filepath)) | ||||
{ | { | ||||
File.Delete(filepath); | File.Delete(filepath); | ||||
} | } | ||||
File.WriteAllBytes(filepath, tmp); | |||||
File.WriteAllBytes(filepath, contents); | |||||
} | } | ||||
[Fact] | [Fact] | ||||
@@ -79,7 +77,6 @@ namespace JT1078.Flv.Test | |||||
File.Delete(filepath); | File.Delete(filepath); | ||||
} | } | ||||
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; | var totalPage = (h264NALULs.Count + 10 - 1) / 10; | ||||
for(var i=0;i< totalPage; i++) | for(var i=0;i< totalPage; i++) | ||||
{ | { | ||||
@@ -136,7 +133,6 @@ 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; | var totalPage = (h264NALULs.Count + 10 - 1) / 10; | ||||
for (var i = 0; i < totalPage; i++) | for (var i = 0; i < totalPage; i++) | ||||
{ | { | ||||
@@ -192,9 +188,7 @@ namespace JT1078.Flv.Test | |||||
} | } | ||||
} | } | ||||
fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||||
fileStream.Write(FlvEncoder.VideoFlvHeaderBuffer); | |||||
fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||||
var totalPage = (h264NALULs.Count + 10 - 1) / 10; | var totalPage = (h264NALULs.Count + 10 - 1) / 10; | ||||
for (var i = 0; i < totalPage; i++) | for (var i = 0; i < totalPage; i++) | ||||
{ | { | ||||
@@ -251,17 +245,16 @@ namespace JT1078.Flv.Test | |||||
} | } | ||||
//var tmp1 = h264NALULs.Where(w => w.NALUHeader.NalUnitType == 7).ToList(); | //var tmp1 = h264NALULs.Where(w => w.NALUHeader.NalUnitType == 7).ToList(); | ||||
List<SPSInfo> tmpSpss = new List<SPSInfo>(); | List<SPSInfo> tmpSpss = new List<SPSInfo>(); | ||||
List<ushort> times = new List<ushort>(); | |||||
List<ulong> times = new List<ulong>(); | |||||
List<int> type = new List<int>(); | List<int> type = new List<int>(); | ||||
//foreach (var item in h264NALULs) | |||||
//{ | |||||
// //ExpGolombReader expGolombReader = new ExpGolombReader(item.RawData); | |||||
// type.Add(item.NALUHeader.NalUnitType); | |||||
// times.Add(item.LastFrameInterval); | |||||
// //tmpSpss.Add(expGolombReader.ReadSPS()); | |||||
//} | |||||
foreach (var item in h264NALULs) | |||||
{ | |||||
//ExpGolombReader expGolombReader = new ExpGolombReader(item.RawData); | |||||
//type.Add(item.NALUHeader.NalUnitType); | |||||
times.Add(item.Timestamp); | |||||
//tmpSpss.Add(expGolombReader.ReadSPS()); | |||||
} | |||||
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; | var totalPage = (h264NALULs.Count + 10 - 1) / 10; | ||||
for (var i = 0; i < totalPage; i++) | for (var i = 0; i < totalPage; i++) | ||||
{ | { | ||||
@@ -9,13 +9,20 @@ | |||||
<body> | <body> | ||||
<video muted="muted" webkit-playsinline="true" autoplay="true" id="player"></video> | <video muted="muted" webkit-playsinline="true" autoplay="true" id="player"></video> | ||||
<script> | <script> | ||||
//ref https://github.com/bilibili/flv.js/issues/427 | |||||
//ref https://github.com/bilibili/flv.js/issues/498 | |||||
if (flvjs.isSupported()) { | if (flvjs.isSupported()) { | ||||
var player = document.getElementById('player'); | var player = document.getElementById('player'); | ||||
var flvPlayer = flvjs.createPlayer({ | var flvPlayer = flvjs.createPlayer({ | ||||
type: 'flv', | type: 'flv', | ||||
url: "ws://localhost:1818/jt1078live?token=" + Math.floor((Math.random() * 1000000) + 1) | |||||
//url: "jt1078_5.flv" | |||||
lazyLoad: false, | |||||
lazyLoadMaxDuration: 0, | |||||
lazyLoadRecoverDuration: 0, | |||||
deferLoadAfterSourceOpen: false, | |||||
fixAudioTimestampGap: false, | |||||
//url: "ws://localhost:1818/jt1078live?token=" + Math.floor((Math.random() * 1000000) + 1) | |||||
//isLive: true, | |||||
url: "JT1078_3.flv" | |||||
}); | }); | ||||
var width, height, flag; | var width, height, flag; | ||||
flvPlayer.attachMediaElement(player); | flvPlayer.attachMediaElement(player); | ||||
@@ -40,6 +40,9 @@ | |||||
<None Update="H264\JT1078_4.txt"> | <None Update="H264\JT1078_4.txt"> | ||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
</None> | </None> | ||||
<None Update="H264\JT1078_5.txt"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |
@@ -22,6 +22,9 @@ namespace JT1078.Flv | |||||
public uint PreviousTagSize { get; set;} | public uint PreviousTagSize { get; set;} | ||||
public uint LastFrameInterval { get; set; } | public uint LastFrameInterval { get; set; } | ||||
public uint LastIFrameInterval { get; set; } | public uint LastIFrameInterval { get; set; } | ||||
public ulong Timestamp { get; set; } | |||||
public uint Interval { get; set; } | |||||
public JT1078DataType DataType { get; set; } | |||||
} | } | ||||
public static readonly byte[] VideoFlvHeaderBuffer; | public static readonly byte[] VideoFlvHeaderBuffer; | ||||
@@ -122,7 +125,7 @@ namespace JT1078.Flv | |||||
FlvArrayPool.Return(buffer); | FlvArrayPool.Return(buffer); | ||||
} | } | ||||
} | } | ||||
public byte[] CreateVideoTagOtherFrame(FlvFrameInfo flvFrameInfo, H264NALU nALU) | |||||
public byte[] CreateVideoTagOtherFrame(FlvFrameInfo flvFrameInfo, H264NALU nALU, H264NALU sei) | |||||
{ | { | ||||
byte[] buffer = FlvArrayPool.Rent(65535); | byte[] buffer = FlvArrayPool.Rent(65535); | ||||
try | try | ||||
@@ -137,21 +140,30 @@ namespace JT1078.Flv | |||||
//flv body tag body | //flv body tag body | ||||
flvTags.VideoTagsData = new VideoTags(); | flvTags.VideoTagsData = new VideoTags(); | ||||
flvTags.VideoTagsData.VideoData = new AvcVideoPacke(); | flvTags.VideoTagsData.VideoData = new AvcVideoPacke(); | ||||
flvTags.VideoTagsData.VideoData.CompositionTime = 0; | |||||
flvTags.VideoTagsData.VideoData.AvcPacketType = AvcPacketType.Raw; | flvTags.VideoTagsData.VideoData.AvcPacketType = AvcPacketType.Raw; | ||||
if (nALU.NALUHeader.NalUnitType == 5 || nALU.DataType == JT1078DataType.视频I帧) | if (nALU.NALUHeader.NalUnitType == 5 || nALU.DataType == JT1078DataType.视频I帧) | ||||
{ | { | ||||
flvTags.VideoTagsData.FrameType = FrameType.KeyFrame; | flvTags.VideoTagsData.FrameType = FrameType.KeyFrame; | ||||
flvTags.Timestamp = flvFrameInfo.LastIFrameInterval; | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
flvTags.Timestamp = flvFrameInfo.LastFrameInterval; | |||||
flvTags.VideoTagsData.FrameType = FrameType.InterFrame; | flvTags.VideoTagsData.FrameType = FrameType.InterFrame; | ||||
} | } | ||||
#warning Timestamp时间戳没有控制好 | |||||
flvTags.Timestamp = flvFrameInfo.LastIFrameInterval; | |||||
flvTags.VideoTagsData.VideoData.Data = nALU.RawData; | |||||
if(flvFrameInfo.DataType== JT1078DataType.视频I帧) | |||||
{ | |||||
flvTags.VideoTagsData.VideoData.CompositionTime = flvFrameInfo.LastIFrameInterval; | |||||
} | |||||
else | |||||
{ | |||||
flvTags.VideoTagsData.VideoData.CompositionTime = flvFrameInfo.LastFrameInterval; | |||||
} | |||||
flvTags.Timestamp = flvFrameInfo.Interval; | |||||
flvTags.VideoTagsData.VideoData.MultiData = new List<byte[]>(); | |||||
flvTags.VideoTagsData.VideoData.MultiData.Add(nALU.RawData); | |||||
if(sei!=null && sei.RawData!=null && sei.RawData.Length > 0) | |||||
{ | |||||
flvTags.VideoTagsData.VideoData.MultiData.Add(sei.RawData); | |||||
} | |||||
flvMessagePackWriter.WriteFlvTag(flvTags); | flvMessagePackWriter.WriteFlvTag(flvTags); | ||||
return flvMessagePackWriter.FlushAndGetArray(); | return flvMessagePackWriter.FlushAndGetArray(); | ||||
} | } | ||||
@@ -160,14 +172,13 @@ namespace JT1078.Flv | |||||
FlvArrayPool.Return(buffer); | FlvArrayPool.Return(buffer); | ||||
} | } | ||||
} | } | ||||
public byte[] CreateFlvFrame(List<H264NALU> nALUs,int minimumLength = 65535) | public byte[] CreateFlvFrame(List<H264NALU> nALUs,int minimumLength = 65535) | ||||
{ | { | ||||
byte[] buffer = FlvArrayPool.Rent(minimumLength); | byte[] buffer = FlvArrayPool.Rent(minimumLength); | ||||
try | try | ||||
{ | { | ||||
FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | FlvMessagePackWriter flvMessagePackWriter = new FlvMessagePackWriter(buffer); | ||||
H264NALU sps=null, pps=null; | |||||
H264NALU sps=null, pps=null, sei=null; | |||||
SPSInfo spsInfo = new SPSInfo(); | SPSInfo spsInfo = new SPSInfo(); | ||||
foreach (var naln in nALUs) | foreach (var naln in nALUs) | ||||
{ | { | ||||
@@ -177,19 +188,25 @@ namespace JT1078.Flv | |||||
if (VideoSPSDict.TryGetValue(key, out var spsInfoCache)) | if (VideoSPSDict.TryGetValue(key, out var spsInfoCache)) | ||||
{ | { | ||||
//todo: 主次码流 | //todo: 主次码流 | ||||
if (spsInfoCache.height != spsInfo.height && spsInfoCache.width != spsInfo.width) | |||||
{ | |||||
if (FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo flvFrameInfo)) | |||||
{ | |||||
CreateFlvKeyFrame(ref flvMessagePackWriter, key, sps.RawData, pps.RawData, spsInfo); | |||||
VideoSPSDict.TryUpdate(key, spsInfo, spsInfo); | |||||
flvFrameInfo.LastIFrameInterval = 0; | |||||
FlvFrameInfoDict.TryUpdate(key, flvFrameInfo, flvFrameInfo); | |||||
} | |||||
} | |||||
//if (spsInfoCache.height != spsInfo.height && spsInfoCache.width != spsInfo.width) | |||||
//{ | |||||
// if (FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo flvFrameInfo)) | |||||
// { | |||||
// var rawData = H264Decoder.DiscardEmulationPreventionBytes(sps.RawData); | |||||
// ExpGolombReader h264GolombReader = new ExpGolombReader(rawData); | |||||
// spsInfo = h264GolombReader.ReadSPS(); | |||||
// CreateFlvKeyFrame(ref flvMessagePackWriter, key, sps.RawData, pps.RawData, spsInfo); | |||||
// VideoSPSDict.TryUpdate(key, spsInfo, spsInfo); | |||||
// flvFrameInfo.LastIFrameInterval = 0; | |||||
// FlvFrameInfoDict.TryUpdate(key, flvFrameInfo, flvFrameInfo); | |||||
// } | |||||
//} | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
var rawData = H264Decoder.DiscardEmulationPreventionBytes(sps.RawData); | |||||
ExpGolombReader h264GolombReader = new ExpGolombReader(rawData); | |||||
spsInfo = h264GolombReader.ReadSPS(); | |||||
flvMessagePackWriter.WriteArray(VideoFlvHeaderBuffer); | flvMessagePackWriter.WriteArray(VideoFlvHeaderBuffer); | ||||
CreateFlvKeyFrame(ref flvMessagePackWriter, key, sps.RawData, pps.RawData, spsInfo); | CreateFlvKeyFrame(ref flvMessagePackWriter, key, sps.RawData, pps.RawData, spsInfo); | ||||
VideoSPSDict.TryAdd(key, spsInfo); | VideoSPSDict.TryAdd(key, spsInfo); | ||||
@@ -205,27 +222,29 @@ namespace JT1078.Flv | |||||
case 1:// I/P/B | case 1:// I/P/B | ||||
if (FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo flvFrameInfo)) | if (FlvFrameInfoDict.TryGetValue(key, out FlvFrameInfo flvFrameInfo)) | ||||
{ | { | ||||
flvFrameInfo.LastIFrameInterval += naln.LastIFrameInterval; | |||||
flvFrameInfo.LastFrameInterval += naln.LastFrameInterval; | |||||
flvFrameInfo.LastIFrameInterval = naln.LastIFrameInterval; | |||||
flvFrameInfo.LastFrameInterval = naln.LastFrameInterval; | |||||
uint interval = (uint)(naln.Timestamp - flvFrameInfo.Timestamp); | |||||
flvFrameInfo.Interval += interval; | |||||
flvFrameInfo.Timestamp = naln.Timestamp; | |||||
// PreviousTagSize | // PreviousTagSize | ||||
flvMessagePackWriter.WriteUInt32(flvFrameInfo.PreviousTagSize); | flvMessagePackWriter.WriteUInt32(flvFrameInfo.PreviousTagSize); | ||||
// Data Tag Frame | // Data Tag Frame | ||||
var flvFrameBuffer = CreateVideoTagOtherFrame(flvFrameInfo, naln); | |||||
var flvFrameBuffer = CreateVideoTagOtherFrame(flvFrameInfo, naln, sei); | |||||
flvMessagePackWriter.WriteArray(flvFrameBuffer); | flvMessagePackWriter.WriteArray(flvFrameBuffer); | ||||
flvFrameInfo.PreviousTagSize = (uint)flvFrameBuffer.Length; | flvFrameInfo.PreviousTagSize = (uint)flvFrameBuffer.Length; | ||||
flvFrameInfo.DataType = naln.DataType; | |||||
FlvFrameInfoDict.TryUpdate(key, flvFrameInfo, flvFrameInfo); | FlvFrameInfoDict.TryUpdate(key, flvFrameInfo, flvFrameInfo); | ||||
} | } | ||||
break; | break; | ||||
case 7:// sps | |||||
case 7:// SPS | |||||
sps = naln; | sps = naln; | ||||
var rawData = H264Decoder.DiscardEmulationPreventionBytes(naln.RawData); | |||||
ExpGolombReader h264GolombReader = new ExpGolombReader(rawData); | |||||
spsInfo = h264GolombReader.ReadSPS(); | |||||
break; | break; | ||||
case 8:// pps | |||||
case 8:// PPS | |||||
pps = naln; | pps = naln; | ||||
break; | break; | ||||
case 6://SEI | case 6://SEI | ||||
sei = naln; | |||||
break; | break; | ||||
default: | default: | ||||
break; | break; | ||||
@@ -247,7 +266,7 @@ namespace JT1078.Flv | |||||
flvMessagePackWriter.WriteArray(scriptTagFrameBuffer); | flvMessagePackWriter.WriteArray(scriptTagFrameBuffer); | ||||
//flv script tag PreviousTagSize | //flv script tag PreviousTagSize | ||||
flvMessagePackWriter.WriteUInt32((uint)scriptTagFrameBuffer.Length); | flvMessagePackWriter.WriteUInt32((uint)scriptTagFrameBuffer.Length); | ||||
//flv body video tag | |||||
//flv body video tag 0 | |||||
var videoTagFrame0Buffer= CreateVideoTag0Frame(spsRawData, ppsRawData, spsInfo); | var videoTagFrame0Buffer= CreateVideoTag0Frame(spsRawData, ppsRawData, spsInfo); | ||||
flvMessagePackWriter.WriteArray(videoTagFrame0Buffer); | flvMessagePackWriter.WriteArray(videoTagFrame0Buffer); | ||||
uint videoTag0PreviousTagSize = (uint)videoTagFrame0Buffer.Length; | uint videoTag0PreviousTagSize = (uint)videoTagFrame0Buffer.Length; | ||||
@@ -75,6 +75,7 @@ namespace JT1078.Flv.H264 | |||||
nALU.LogicChannelNumber = package.LogicChannelNumber; | nALU.LogicChannelNumber = package.LogicChannelNumber; | ||||
nALU.LastFrameInterval = package.LastFrameInterval; | nALU.LastFrameInterval = package.LastFrameInterval; | ||||
nALU.LastIFrameInterval = package.LastIFrameInterval; | nALU.LastIFrameInterval = package.LastIFrameInterval; | ||||
nALU.Timestamp = package.Timestamp; | |||||
if (startCodePrefix == 3) | if (startCodePrefix == 3) | ||||
{ | { | ||||
nALU.StartCodePrefix = H264NALU.Start1; | nALU.StartCodePrefix = H264NALU.Start1; | ||||
@@ -35,6 +35,12 @@ namespace JT1078.Flv.H264 | |||||
/// </summary> | /// </summary> | ||||
public ushort LastFrameInterval { get; set; } | public ushort LastFrameInterval { get; set; } | ||||
/// <summary> | /// <summary> | ||||
/// 时间戳 | |||||
/// 标识此RTP数据包当前帧的相对时间,单位毫秒(ms)。 | |||||
/// 当数据类型为01000时,则没有该字段 | |||||
/// </summary> | |||||
public ulong Timestamp { get; set; } | |||||
/// <summary> | |||||
/// 数据体 | /// 数据体 | ||||
/// </summary> | /// </summary> | ||||
public byte[] RawData { get; set; } | public byte[] RawData { get; set; } | ||||
@@ -65,7 +65,6 @@ namespace JT1078.Flv.MessagePack | |||||
public void WriteVideoTags(VideoTags videoTags) | public void WriteVideoTags(VideoTags videoTags) | ||||
{ | { | ||||
WriteByte((byte)((byte)videoTags.FrameType | (byte)videoTags.CodecId)); | WriteByte((byte)((byte)videoTags.FrameType | (byte)videoTags.CodecId)); | ||||
#warning 只处理H.264媒体数据 | |||||
if (videoTags.CodecId== CodecId.AvcVideoPacke) | if (videoTags.CodecId== CodecId.AvcVideoPacke) | ||||
{ | { | ||||
WriteAvcVideoPacke(videoTags.VideoData); | WriteAvcVideoPacke(videoTags.VideoData); | ||||
@@ -85,10 +84,13 @@ namespace JT1078.Flv.MessagePack | |||||
else if(videoPacke.AvcPacketType == AvcPacketType.Raw) | else if(videoPacke.AvcPacketType == AvcPacketType.Raw) | ||||
{ | { | ||||
WriteUInt24(videoPacke.CompositionTime); | WriteUInt24(videoPacke.CompositionTime); | ||||
if (videoPacke.Data != null && videoPacke.Data.Length>0) | |||||
if (videoPacke.MultiData != null && videoPacke.MultiData.Count>0) | |||||
{ | { | ||||
WriteInt32(videoPacke.Data.Length); | |||||
WriteArray(videoPacke.Data); | |||||
foreach(var item in videoPacke.MultiData) | |||||
{ | |||||
WriteInt32(item.Length); | |||||
WriteArray(item); | |||||
} | |||||
} | } | ||||
} | } | ||||
else | else | ||||
@@ -12,7 +12,6 @@ namespace JT1078.Flv.Metadata | |||||
public uint CompositionTime { get; set; } | public uint CompositionTime { get; set; } | ||||
public AVCDecoderConfigurationRecord AVCDecoderConfiguration { get; set; } | public AVCDecoderConfigurationRecord AVCDecoderConfiguration { get; set; } | ||||
public byte[] Data { get; set; } | |||||
public List<byte[]> MultiData { get; set; } | |||||
} | } | ||||
} | } |