@@ -19,8 +19,7 @@ | |||
//var mimeCodec = 'video/mp4;codecs="avc1.4D0014, mp4a.40.2"'; | |||
// *** USER PARAMETERS *** | |||
var verbose = true; | |||
// var verbose = true; // enable for saturating the console .. | |||
var buffering_sec = 1; // use some reasonable value | |||
var buffering_sec = 3; // use some reasonable value | |||
var buffering_sec_seek = buffering_sec * 0.9; | |||
// ..seek the stream if it's this much away or | |||
// from the last available timestamp | |||
@@ -119,45 +118,44 @@ | |||
// return; | |||
// } | |||
// keep the latency to minimum | |||
let latest = stream_live.duration; | |||
if ((stream_live.duration >= buffering_sec) && | |||
((latest - stream_live.currentTime) > buffering_sec_seek)) { | |||
console.log("seek from ", stream_live.currentTime, " to ", latest); | |||
df = (stream_live.duration - stream_live.currentTime); // this much away from the last available frame | |||
if ((df > buffering_sec_seek)) { | |||
seek_to = stream_live.duration - buffering_sec_seek_distance; | |||
stream_live.currentTime = seek_to; | |||
} | |||
} | |||
data = arr; | |||
if (!stream_started) { | |||
// let latest = stream_live.duration; | |||
// if ((stream_live.duration >= buffering_sec) && ((latest - stream_live.currentTime) > buffering_sec_seek)) { | |||
// console.log("seek from ", stream_live.currentTime, " to ", latest); | |||
// df = (stream_live.duration - stream_live.currentTime); // this much away from the last available frame | |||
// if ((df > buffering_sec_seek)) { | |||
// seek_to = stream_live.duration - buffering_sec_seek_distance; | |||
// stream_live.currentTime = seek_to; | |||
// } | |||
// } | |||
if (!source_buffer.updating) { | |||
if (verbose) { console.log("Streaming started: ", memview[0], memview[1], memview[2], memview[3], memview[4]); } | |||
stream_started = true; | |||
source_buffer.appendBuffer(data); | |||
source_buffer.appendBuffer(arr); | |||
cc = cc + 1; | |||
return; | |||
}else{ | |||
queue.push(arr); // add to the end | |||
} | |||
queue.push(data); // add to the end | |||
if (verbose) { console.log("queue push:", queue.length); } | |||
} | |||
function loadPacket() { // called when source_buffer is ready for more | |||
if (!source_buffer.updating) { // really, really ready | |||
if (queue.length > 0) { | |||
inp = queue.shift(); // pop from the beginning | |||
var inp = queue.shift(); // pop from the beginning | |||
if (verbose) { console.log("queue pop:", queue.length); } | |||
var memview = new Uint8Array(inp); | |||
if (verbose) { console.log(" ==> writing buffer with", memview[0], memview[1], memview[2], memview[3]); } | |||
source_buffer.appendBuffer(inp); | |||
cc = cc + 1; | |||
} | |||
else { // the queue runs empty, so the next packet is fed directly | |||
stream_started = false; | |||
} | |||
} | |||
else { // so it was not? | |||
// else { // the queue runs empty, so the next packet is fed directly | |||
// stream_started = false; | |||
// } | |||
} | |||
// else { | |||
// // so it was not? | |||
// } | |||
} | |||
function opened() { // MediaSource object is ready to go | |||
@@ -175,7 +173,10 @@ | |||
ws.on("video", (message) => { | |||
var buff=base64ToArrayBuffer(message); | |||
//console.log(buff); | |||
putPacket(buff); | |||
//putPacket(buff); | |||
//先直接喂进去 | |||
//mvhd.duration 谷歌浏览器不会缓存 | |||
source_buffer.appendBuffer(buff); | |||
}); | |||
ws.start().catch(err => console.error(err)); | |||
} | |||
@@ -40,9 +40,6 @@ | |||
<None Update="FMP4\fragmented_demo_trun.txt"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="H264\1078视频数据.txt"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="H264\index.html"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
@@ -24,7 +24,7 @@ namespace JT1078.FMp4.Test | |||
var jT1078Package = ParseNALUTest(); | |||
H264Decoder decoder = new H264Decoder(); | |||
var nalus = decoder.ParseNALU(jT1078Package); | |||
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.SPS); | |||
var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.SPS); | |||
//SPS | |||
spsNALU.RawData = decoder.DiscardEmulationPreventionBytes(spsNALU.RawData); | |||
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.PPS); | |||
@@ -434,67 +434,68 @@ namespace JT1078.FMp4.Test | |||
} | |||
[Fact] | |||
public void Test3_1() | |||
public void Test4() | |||
{ | |||
FMp4EncoderInfo encoderInfo = new FMp4EncoderInfo(); | |||
FMp4Encoder fMp4Encoder = new FMp4Encoder(); | |||
var packages = ParseNALUTests1(); | |||
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_3.mp4"); | |||
H264Decoder h264Decoder = new H264Decoder(); | |||
var packages = ParseNALUTests(); | |||
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_5.mp4"); | |||
if (File.Exists(filepath)) | |||
{ | |||
File.Delete(filepath); | |||
} | |||
using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||
//fragment moof n | |||
Dictionary<string, JT1078Package> memoryCache = new Dictionary<string, JT1078Package>(); | |||
bool flag = true; | |||
var ftyp = fMp4Encoder.EncoderFtypBox(); | |||
fileStream.Write(ftyp); | |||
var iNalus = h264Decoder.ParseNALU(packages[0]); | |||
//判断第一帧是否关键帧 | |||
var moov = fMp4Encoder.EncoderMoovBox( | |||
iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.SPS), | |||
iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.PPS)); | |||
fileStream.Write(moov); | |||
List<H264NALU> nalus = new List<H264NALU>(); | |||
foreach (var package in packages) | |||
{ | |||
string key = $"{package.GetKey()}_{0}"; | |||
if (package.Label3.DataType == Protocol.Enums.JT1078DataType.视频I帧) | |||
List<H264NALU> h264NALUs = h264Decoder.ParseNALU(package); | |||
foreach (var nalu in h264NALUs) | |||
{ | |||
if (memoryCache.TryGetValue(key, out var pack)) | |||
if (nalu.Slice) | |||
{ | |||
memoryCache[key] = package; | |||
} | |||
else { | |||
memoryCache.Add(key, package); | |||
//H264 NALU slice first_mb_in_slice | |||
nalus.Add(nalu); | |||
} | |||
} | |||
if (flag) | |||
{ | |||
if (memoryCache.TryGetValue(key, out var data)) | |||
else | |||
{ | |||
var buffer = fMp4Encoder.EncoderVideo(package, encoderInfo, true); | |||
fileStream.Write(buffer); | |||
if (nalus.Count > 0) | |||
{ | |||
var otherBuffer = fMp4Encoder.EncoderOtherVideoBox(nalus); | |||
fileStream.Write(otherBuffer); | |||
nalus.Clear(); | |||
} | |||
nalus.Add(nalu); | |||
} | |||
flag = false; | |||
} | |||
else { | |||
var buffer = fMp4Encoder.EncoderVideo(package, encoderInfo, false); | |||
fileStream.Write(buffer); | |||
} | |||
} | |||
} | |||
fileStream.Close(); | |||
} | |||
[Fact] | |||
public void Test4() | |||
public void Test5() | |||
{ | |||
FMp4EncoderInfo encoderInfo = new FMp4EncoderInfo(); | |||
FMp4Encoder fMp4Encoder = new FMp4Encoder(); | |||
H264Decoder h264Decoder = new H264Decoder(); | |||
var packages = ParseNALUTests(); | |||
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_5.mp4"); | |||
var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_6.mp4"); | |||
if (File.Exists(filepath)) | |||
{ | |||
File.Delete(filepath); | |||
} | |||
using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||
var ftyp = fMp4Encoder.EncoderFtypBox(); | |||
encoderInfo.SampleSize += (uint)ftyp.Length; | |||
fileStream.Write(ftyp); | |||
var iNalus = h264Decoder.ParseNALU(packages[0]); | |||
@@ -502,36 +503,31 @@ namespace JT1078.FMp4.Test | |||
var moov = fMp4Encoder.EncoderMoovBox( | |||
iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.SPS), | |||
iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.PPS)); | |||
encoderInfo.SampleSize += (uint)moov.Length; | |||
fileStream.Write(moov); | |||
List<H264NALU> nalus = new List<H264NALU>(); | |||
foreach (var package in packages) | |||
{ | |||
List<H264NALU> h264NALUs = h264Decoder.ParseNALU(package); | |||
foreach (var nalu in h264NALUs) | |||
if (package.Label3.DataType == Protocol.Enums.JT1078DataType.视频I帧) | |||
{ | |||
if (nalu.Slice) | |||
if (nalus.Count > 0) | |||
{ | |||
//H264 NALU slice first_mb_in_slice | |||
nalus.Add(nalu); | |||
} | |||
else | |||
{ | |||
if (nalus.Count > 0) | |||
{ | |||
var otherBuffer = fMp4Encoder.EncoderOtherVideoBox(nalus, encoderInfo); | |||
encoderInfo.SampleSize += (uint)otherBuffer.Length; | |||
fileStream.Write(otherBuffer); | |||
nalus.Clear(); | |||
} | |||
nalus.Add(nalu); | |||
var otherBuffer = fMp4Encoder.EncoderOtherVideoBox(nalus); | |||
fileStream.Write(otherBuffer); | |||
nalus.Clear(); | |||
} | |||
} | |||
nalus = nalus.Concat(h264NALUs).ToList(); | |||
} | |||
if (nalus.Count > 0) | |||
{ | |||
var otherBuffer = fMp4Encoder.EncoderOtherVideoBox(nalus); | |||
fileStream.Write(otherBuffer); | |||
nalus.Clear(); | |||
} | |||
fileStream.Close(); | |||
} | |||
} | |||
[Fact] | |||
public void tkhd_width_height_test() | |||
@@ -588,7 +584,7 @@ namespace JT1078.FMp4.Test | |||
public JT1078Package ParseNALUTest() | |||
{ | |||
JT1078Package Package = null; | |||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "jt1078_3.txt")); | |||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "jt1078_1.txt")); | |||
int mergeBodyLength = 0; | |||
foreach (var line in lines) | |||
{ | |||
@@ -600,6 +596,7 @@ namespace JT1078.FMp4.Test | |||
} | |||
return Package; | |||
} | |||
public List<JT1078Package> ParseNALUTests() | |||
{ | |||
List<JT1078Package> packages = new List<JT1078Package>(); | |||
@@ -619,24 +616,5 @@ namespace JT1078.FMp4.Test | |||
} | |||
return packages; | |||
} | |||
public List<JT1078Package> ParseNALUTests1() | |||
{ | |||
List<JT1078Package> packages = new List<JT1078Package>(); | |||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "1078视频数据.txt")); | |||
int mergeBodyLength = 0; | |||
foreach (var line in lines) | |||
{ | |||
var data = line.Split(','); | |||
var bytes = data[1].ToHexBytes(); | |||
JT1078Package package = JT1078Serializer.Deserialize(bytes); | |||
mergeBodyLength += package.DataBodyLength; | |||
var packageMerge = JT1078Serializer.Merge(package); | |||
if (packageMerge != null) | |||
{ | |||
packages.Add(packageMerge); | |||
} | |||
} | |||
return packages; | |||
} | |||
} | |||
} |
@@ -30,7 +30,7 @@ namespace JT1078.FMp4 | |||
/// </summary> | |||
public class FMp4Encoder | |||
{ | |||
//Dictionary<string, TrackInfo> TrackInfos; | |||
Dictionary<string, TrackInfo> TrackInfos; | |||
const uint DefaultSampleDuration = 48000u; | |||
const uint DefaultSampleFlags = 0x1010000; | |||
@@ -45,7 +45,7 @@ namespace JT1078.FMp4 | |||
/// </summary> | |||
public FMp4Encoder() | |||
{ | |||
//TrackInfos = new Dictionary<string, TrackInfo>(StringComparer.OrdinalIgnoreCase); | |||
TrackInfos = new Dictionary<string, TrackInfo>(StringComparer.OrdinalIgnoreCase); | |||
} | |||
/// <summary> | |||
@@ -63,16 +63,11 @@ namespace JT1078.FMp4 | |||
fileTypeBox.MajorBrand = "isom"; | |||
fileTypeBox.MinorVersion = "\0\0\u0002\0"; | |||
fileTypeBox.CompatibleBrands.Add("isom"); | |||
fileTypeBox.CompatibleBrands.Add("iso6"); | |||
fileTypeBox.CompatibleBrands.Add("iso2"); | |||
fileTypeBox.CompatibleBrands.Add("avc1"); | |||
fileTypeBox.CompatibleBrands.Add("mp41"); | |||
//fileTypeBox.CompatibleBrands.Add("isom"); | |||
//fileTypeBox.CompatibleBrands.Add("iso2"); | |||
//fileTypeBox.CompatibleBrands.Add("avc1"); | |||
//fileTypeBox.CompatibleBrands.Add("mp41"); | |||
//fileTypeBox.CompatibleBrands.Add("iso5"); | |||
//fileTypeBox.CompatibleBrands.Add("iso6"); | |||
fileTypeBox.CompatibleBrands.Add("iso5"); | |||
fileTypeBox.CompatibleBrands.Add("iso6"); | |||
fileTypeBox.ToBuffer(ref writer); | |||
var data = writer.FlushAndGetArray(); | |||
return data; | |||
@@ -97,19 +92,17 @@ namespace JT1078.FMp4 | |||
var spsInfo = h264GolombReader.ReadSPS(); | |||
//moov | |||
MovieBox movieBox = new MovieBox(); | |||
movieBox.MovieHeaderBox = new MovieHeaderBox(0, 0); | |||
movieBox.MovieHeaderBox = new MovieHeaderBox(0, 2); | |||
movieBox.MovieHeaderBox.CreationTime = 0; | |||
movieBox.MovieHeaderBox.ModificationTime = 0; | |||
movieBox.MovieHeaderBox.Duration = 0; | |||
movieBox.MovieHeaderBox.Timescale = 1000; | |||
movieBox.MovieHeaderBox.Rate = 0x00010000;//typically 1.0 媒体速率,这个值代表原始倍速 | |||
movieBox.MovieHeaderBox.Volume = 0x0100;// typically,1.0 full volume 媒体音量,这个值代表满音量 | |||
movieBox.MovieHeaderBox.NextTrackID = 2; | |||
movieBox.MovieHeaderBox.NextTrackID = 99; | |||
movieBox.TrackBox = new TrackBox(); | |||
movieBox.TrackBox.TrackHeaderBox = new TrackHeaderBox(0, 3); | |||
movieBox.TrackBox.TrackHeaderBox.CreationTime = 0; | |||
movieBox.TrackBox.TrackHeaderBox.ModificationTime = 0; | |||
movieBox.TrackBox.TrackHeaderBox.TrackID = TrackID;//1 | |||
movieBox.TrackBox.TrackHeaderBox.TrackID = TrackID; | |||
movieBox.TrackBox.TrackHeaderBox.Duration = 0; | |||
movieBox.TrackBox.TrackHeaderBox.TrackIsAudio = false; | |||
movieBox.TrackBox.TrackHeaderBox.Width = (uint)spsInfo.width; | |||
@@ -144,15 +137,15 @@ namespace JT1078.FMp4 | |||
avc1.AVCConfigurationBox.SPSs = new List<byte[]>() { sps.RawData }; | |||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries.Add(avc1); | |||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.TimeToSampleBox = new TimeToSampleBox(); | |||
//movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SyncSampleBox = new SyncSampleBox(); | |||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SyncSampleBox = new SyncSampleBox(); | |||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleToChunkBox = new SampleToChunkBox(); | |||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleSizeBox = new SampleSizeBox(); | |||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.ChunkOffsetBox = new ChunkOffsetBox(); | |||
movieBox.MovieExtendsBox = new MovieExtendsBox(); | |||
movieBox.MovieExtendsBox.TrackExtendsBoxs = new List<TrackExtendsBox>(); | |||
TrackExtendsBox trex = new TrackExtendsBox(); | |||
trex.TrackID = TrackID;//1 | |||
trex.DefaultSampleDescriptionIndex = SampleDescriptionIndex;//1 | |||
trex.TrackID = TrackID; | |||
trex.DefaultSampleDescriptionIndex = SampleDescriptionIndex; | |||
trex.DefaultSampleDuration = 0; | |||
trex.DefaultSampleSize = 0; | |||
trex.DefaultSampleFlags = 0; | |||
@@ -171,113 +164,99 @@ namespace JT1078.FMp4 | |||
/// 编码其他视频数据盒子 | |||
/// </summary> | |||
/// <returns></returns> | |||
public byte[] EncoderOtherVideoBox(List<H264NALU> nalus, FMp4EncoderInfo encoderInfo) | |||
public byte[] EncoderOtherVideoBox(in List<H264NALU> nalus) | |||
{ | |||
byte[] buffer= buffer = FMp4ArrayPool.Rent(nalus.Sum(m=>m.RawData.Length + m.StartCodePrefix.Length) + 4096); | |||
byte[] buffer = FMp4ArrayPool.Rent(nalus.Sum(s => s.RawData.Length + s.StartCodePrefix.Length) + 4096); | |||
FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); | |||
try | |||
{ | |||
FragmentBox fragmentBox = new FragmentBox(); | |||
fragmentBox.MovieFragmentBox = new MovieFragmentBox(); | |||
fragmentBox.MovieFragmentBox.MovieFragmentHeaderBox = new MovieFragmentHeaderBox(); | |||
fragmentBox.MovieFragmentBox.MovieFragmentHeaderBox.SequenceNumber = encoderInfo.SequenceNumber; | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox = new TrackFragmentBox(); | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox = new TrackFragmentHeaderBox(TfhdFlags);//0x39 | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.TrackID = TrackID;//1 | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.BaseDataOffset = encoderInfo.SampleSize;//基于前面盒子的长度 | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.SampleDescriptionIndex = SampleDescriptionIndex; | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleDuration = DefaultSampleDuration; | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleSize = (uint)(nalus.Sum(m => m.RawData.Length + m.StartCodePrefix.Length)); | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleFlags = DefaultSampleFlags; | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox = new TrackFragmentBaseMediaDecodeTimeBox(); | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = nalus[0].Timestamp * 1000; | |||
var truns = new List<TrackRunBox.TrackRunInfo>(); | |||
List<byte[]> rawdatas = new List<byte[]>(); | |||
uint iSize = 0; | |||
ulong lastTimestamp = 0; | |||
string key = string.Empty; | |||
for (var i = 0; i < nalus.Count; i++) | |||
{ | |||
var nalu = nalus[i]; | |||
if (string.IsNullOrEmpty(key)) | |||
{ | |||
key = nalu.GetKey(); | |||
} | |||
rawdatas.Add(nalu.RawData); | |||
if (nalu.DataType == Protocol.Enums.JT1078DataType.视频I帧) | |||
{ | |||
iSize += (uint)(nalu.RawData.Length + nalu.StartCodePrefix.Length); | |||
} | |||
else | |||
{ | |||
if (iSize > 0) | |||
{ | |||
truns.Add(new TrackRunBox.TrackRunInfo() | |||
{ | |||
SampleSize = iSize, | |||
}); | |||
iSize = 0; | |||
} | |||
truns.Add(new TrackRunBox.TrackRunInfo() | |||
{ | |||
SampleSize = (uint)(nalu.RawData.Length + nalu.StartCodePrefix.Length), | |||
}); | |||
} | |||
if (i == (nalus.Count - 1)) | |||
{ | |||
lastTimestamp = nalu.Timestamp; | |||
} | |||
} | |||
if (TrackInfos.TryGetValue(key, out TrackInfo trackInfo)) | |||
{ | |||
if (trackInfo.SN == uint.MaxValue) | |||
{ | |||
trackInfo.SN = 1; | |||
} | |||
trackInfo.SN++; | |||
} | |||
else | |||
{ | |||
trackInfo = new TrackInfo { SN = 1, DTS = 0 }; | |||
TrackInfos.Add(key, trackInfo); | |||
} | |||
var movieFragmentBox = new MovieFragmentBox(); | |||
movieFragmentBox.MovieFragmentHeaderBox = new MovieFragmentHeaderBox(); | |||
movieFragmentBox.MovieFragmentHeaderBox.SequenceNumber = trackInfo.SN; | |||
movieFragmentBox.TrackFragmentBox = new TrackFragmentBox(); | |||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox = new TrackFragmentHeaderBox(TfhdFlags); | |||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.TrackID = TrackID; | |||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.SampleDescriptionIndex = SampleDescriptionIndex; | |||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleDuration = DefaultSampleDuration; | |||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleSize = truns[0].SampleSize; | |||
movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleFlags = DefaultSampleFlags; | |||
movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox = new TrackFragmentBaseMediaDecodeTimeBox(); | |||
movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = trackInfo.DTS; | |||
trackInfo.DTS += (ulong)(truns.Count * DefaultSampleDuration); | |||
TrackInfos[key] = trackInfo; | |||
//trun | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(flags: 0x5);//TrunFlags | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackRunBox.FirstSampleFlags = FirstSampleFlags;// 0; | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos = new List<TrackRunBox.TrackRunInfo>(); | |||
fragmentBox.MovieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos.Add(new TrackRunBox.TrackRunInfo()); | |||
fragmentBox.MediaDataBox = new MediaDataBox(); | |||
fragmentBox.MediaDataBox.Data = nalus.Select(s => s.RawData).ToList(); | |||
fragmentBox.ToBuffer(ref writer); | |||
movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(flags: TrunFlags); | |||
movieFragmentBox.TrackFragmentBox.TrackRunBox.FirstSampleFlags = FirstSampleFlags; | |||
movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos = truns; | |||
movieFragmentBox.ToBuffer(ref writer); | |||
var mediaDataBox = new MediaDataBox(); | |||
mediaDataBox.Data = rawdatas; | |||
mediaDataBox.ToBuffer(ref writer); | |||
var data = writer.FlushAndGetArray(); | |||
return data; | |||
} | |||
finally | |||
{ | |||
FMp4ArrayPool.Return(buffer); | |||
} | |||
} | |||
/// <summary> | |||
/// 编码mp4 视频 | |||
/// </summary> | |||
/// <param name="package"></param> | |||
/// <param name="needVideoHeader"></param> | |||
/// <returns></returns> | |||
public byte[] EncoderVideo(JT1078Package package, FMp4EncoderInfo encoderInfo, bool needVideoHeader = false) { | |||
H264Decoder h264Decoder = new H264Decoder(); | |||
byte[] buffer = FMp4ArrayPool.Rent(package.Bodies.Length * 2 + 4096); | |||
FMp4MessagePackWriter flvMessagePackWriter = new FMp4MessagePackWriter(buffer); | |||
var nalus = h264Decoder.ParseNALU(package); | |||
if (nalus != null && nalus.Count > 0) | |||
{ | |||
try | |||
{ | |||
var sei = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == NalUnitType.SEI); | |||
var sps = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == NalUnitType.SPS); | |||
var pps = nalus.FirstOrDefault(x => x.NALUHeader.NalUnitType == NalUnitType.PPS); | |||
nalus.Remove(sei); | |||
nalus.Remove(sps); | |||
nalus.Remove(pps); | |||
if (needVideoHeader) | |||
{ | |||
//mp4 header | |||
//ftype | |||
var ftypHeader = EncoderFtypBox(); | |||
encoderInfo.SampleSize += (uint)ftypHeader.Length; | |||
flvMessagePackWriter.WriteArray(ftypHeader); | |||
// moov | |||
var moov = EncoderMoovBox(sps, pps); | |||
encoderInfo.SampleSize += (uint)moov.Length; | |||
flvMessagePackWriter.WriteArray(moov); | |||
//解析sps | |||
} | |||
var otherVideoTag = EncoderOtherVideoBox(nalus, encoderInfo); | |||
encoderInfo.SampleSize += (uint)otherVideoTag.Length; | |||
flvMessagePackWriter.WriteArray(otherVideoTag); | |||
} | |||
finally | |||
{ | |||
FMp4ArrayPool.Return(buffer); | |||
} | |||
} | |||
var data = flvMessagePackWriter.FlushAndGetArray(); | |||
return data; | |||
} | |||
//struct TrackInfo | |||
//{ | |||
// public uint SN { get; set; } | |||
// public ulong DTS { get; set; } | |||
//} | |||
} | |||
/// <summary> | |||
/// 编码信息 | |||
/// </summary> | |||
public class FMp4EncoderInfo | |||
{ | |||
/// <summary> | |||
/// 样本大小,即盒子大小 | |||
/// </summary> | |||
public uint SampleSize { get; set; } = 0; | |||
private uint sequenceNum = 0; | |||
/// <summary> | |||
/// 轨道序号 | |||
/// </summary> | |||
public uint SequenceNumber | |||
struct TrackInfo | |||
{ | |||
get | |||
{ | |||
return sequenceNum++; | |||
} | |||
public uint SN { get; set; } | |||
public ulong DTS { get; set; } | |||
} | |||
} | |||
} |
@@ -14,7 +14,7 @@ | |||
<licenseUrl>https://github.com/SmallChi/JT1078/blob/master/LICENSE</licenseUrl> | |||
<license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | |||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> | |||
<Version>1.0.0-preview2</Version> | |||
<Version>1.0.0-preview3</Version> | |||
<SignAssembly>false</SignAssembly> | |||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | |||
<PackageLicenseFile>LICENSE</PackageLicenseFile> | |||
@@ -1337,35 +1337,12 @@ | |||
</summary> | |||
<returns></returns> | |||
</member> | |||
<member name="M:JT1078.FMp4.FMp4Encoder.EncoderOtherVideoBox(System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU},JT1078.FMp4.FMp4EncoderInfo)"> | |||
<member name="M:JT1078.FMp4.FMp4Encoder.EncoderOtherVideoBox(System.Collections.Generic.List{JT1078.Protocol.H264.H264NALU}@)"> | |||
<summary> | |||
编码其他视频数据盒子 | |||
</summary> | |||
<returns></returns> | |||
</member> | |||
<member name="M:JT1078.FMp4.FMp4Encoder.EncoderVideo(JT1078.Protocol.JT1078Package,JT1078.FMp4.FMp4EncoderInfo,System.Boolean)"> | |||
<summary> | |||
编码mp4 视频 | |||
</summary> | |||
<param name="package"></param> | |||
<param name="needVideoHeader"></param> | |||
<returns></returns> | |||
</member> | |||
<member name="T:JT1078.FMp4.FMp4EncoderInfo"> | |||
<summary> | |||
编码信息 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.FMp4.FMp4EncoderInfo.SampleSize"> | |||
<summary> | |||
样本大小,即盒子大小 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.FMp4.FMp4EncoderInfo.SequenceNumber"> | |||
<summary> | |||
轨道序号 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.FMp4.FullBox.Version"> | |||
<summary> | |||
unsigned int(8) | |||
@@ -42,10 +42,7 @@ | |||
<None Include="..\..\doc\video\jt1078_2.txt" Link="H264\jt1078_2.txt"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Update="H264\1078视频数据.txt"> | |||
<None Include="..\..\doc\video\jt1078_5.txt" Link="H264\jt1078_5.txt"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
</ItemGroup> | |||
@@ -20,7 +20,7 @@ namespace JT1078.Hls.Test | |||
{ | |||
try | |||
{ | |||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "1078视频数据.txt")); | |||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "jt1078_5.txt")); | |||
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); | |||
clientSocket.Connect("127.0.0.1",1078); | |||
long lasttime = 0; | |||
@@ -95,5 +95,26 @@ namespace JT1078.Protocol.Test.H264 | |||
} | |||
fileStream.Close(); | |||
} | |||
[Fact] | |||
public void ParseNALUTest4() | |||
{ | |||
string file = "jt1078_5"; | |||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", $"{file}.txt")); | |||
string filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", $"{file}.h264"); | |||
if (File.Exists(filepath)) | |||
{ | |||
File.Delete(filepath); | |||
} | |||
using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | |||
foreach (var line in lines) | |||
{ | |||
var data = line.Split(','); | |||
var bytes = data[1].ToHexBytes(); | |||
JT1078Package package = JT1078Serializer.Deserialize(bytes); | |||
fileStream.Write(package.Bodies); | |||
} | |||
fileStream.Close(); | |||
} | |||
} | |||
} |
@@ -29,5 +29,8 @@ | |||
<None Include="..\..\doc\video\jt1078_2.txt" Link="H264\jt1078_2.txt"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Include="..\..\doc\video\jt1078_5.txt" Link="H264\jt1078_5.txt"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
</ItemGroup> | |||
</Project> |
@@ -20,5 +20,8 @@ | |||
<None Include="..\..\doc\video\jt1078_1_fragmented.mp4" Link="H264\jt1078_1_fragmented.mp4"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Include="..\..\doc\video\jt1078_5.txt" Link="H264\jt1078_5.txt"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
</ItemGroup> | |||
</Project> |
@@ -52,12 +52,16 @@ namespace JT1078.SignalR.Test.Services | |||
public void a() | |||
{ | |||
List<JT1078Package> packages = new List<JT1078Package>(); | |||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "jt1078_3.txt")); | |||
//var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "jt1078_3.txt")); | |||
var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "jt1078_5.txt")); | |||
int mergeBodyLength = 0; | |||
foreach (var line in lines) | |||
{ | |||
var data = line.Split(','); | |||
var bytes = data[6].ToHexBytes(); | |||
//jt1078_5 | |||
var bytes = data[1].ToHexBytes(); | |||
//jt1078_3 | |||
//var bytes = data[6].ToHexBytes(); | |||
JT1078Package package = JT1078Serializer.Deserialize(bytes); | |||
mergeBodyLength += package.DataBodyLength; | |||
var packageMerge = JT1078Serializer.Merge(package); | |||