@@ -19,8 +19,7 @@ | |||||
//var mimeCodec = 'video/mp4;codecs="avc1.4D0014, mp4a.40.2"'; | //var mimeCodec = 'video/mp4;codecs="avc1.4D0014, mp4a.40.2"'; | ||||
// *** USER PARAMETERS *** | // *** USER PARAMETERS *** | ||||
var verbose = true; | 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; | var buffering_sec_seek = buffering_sec * 0.9; | ||||
// ..seek the stream if it's this much away or | // ..seek the stream if it's this much away or | ||||
// from the last available timestamp | // from the last available timestamp | ||||
@@ -119,45 +118,44 @@ | |||||
// return; | // return; | ||||
// } | // } | ||||
// keep the latency to minimum | // 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]); } | if (verbose) { console.log("Streaming started: ", memview[0], memview[1], memview[2], memview[3], memview[4]); } | ||||
stream_started = true; | stream_started = true; | ||||
source_buffer.appendBuffer(data); | |||||
source_buffer.appendBuffer(arr); | |||||
cc = cc + 1; | 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); } | if (verbose) { console.log("queue push:", queue.length); } | ||||
} | } | ||||
function loadPacket() { // called when source_buffer is ready for more | function loadPacket() { // called when source_buffer is ready for more | ||||
if (!source_buffer.updating) { // really, really ready | if (!source_buffer.updating) { // really, really ready | ||||
if (queue.length > 0) { | 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); } | if (verbose) { console.log("queue pop:", queue.length); } | ||||
var memview = new Uint8Array(inp); | var memview = new Uint8Array(inp); | ||||
if (verbose) { console.log(" ==> writing buffer with", memview[0], memview[1], memview[2], memview[3]); } | if (verbose) { console.log(" ==> writing buffer with", memview[0], memview[1], memview[2], memview[3]); } | ||||
source_buffer.appendBuffer(inp); | source_buffer.appendBuffer(inp); | ||||
cc = cc + 1; | 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 | function opened() { // MediaSource object is ready to go | ||||
@@ -175,7 +173,10 @@ | |||||
ws.on("video", (message) => { | ws.on("video", (message) => { | ||||
var buff=base64ToArrayBuffer(message); | var buff=base64ToArrayBuffer(message); | ||||
//console.log(buff); | //console.log(buff); | ||||
putPacket(buff); | |||||
//putPacket(buff); | |||||
//先直接喂进去 | |||||
//mvhd.duration 谷歌浏览器不会缓存 | |||||
source_buffer.appendBuffer(buff); | |||||
}); | }); | ||||
ws.start().catch(err => console.error(err)); | ws.start().catch(err => console.error(err)); | ||||
} | } | ||||
@@ -40,9 +40,6 @@ | |||||
<None Update="FMP4\fragmented_demo_trun.txt"> | <None Update="FMP4\fragmented_demo_trun.txt"> | ||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
</None> | </None> | ||||
<None Update="H264\1078视频数据.txt"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
<None Update="H264\index.html"> | <None Update="H264\index.html"> | ||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
</None> | </None> | ||||
@@ -24,7 +24,7 @@ namespace JT1078.FMp4.Test | |||||
var jT1078Package = ParseNALUTest(); | var jT1078Package = ParseNALUTest(); | ||||
H264Decoder decoder = new H264Decoder(); | H264Decoder decoder = new H264Decoder(); | ||||
var nalus = decoder.ParseNALU(jT1078Package); | 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 | //SPS | ||||
spsNALU.RawData = decoder.DiscardEmulationPreventionBytes(spsNALU.RawData); | spsNALU.RawData = decoder.DiscardEmulationPreventionBytes(spsNALU.RawData); | ||||
var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.PPS); | var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == NalUnitType.PPS); | ||||
@@ -434,67 +434,68 @@ namespace JT1078.FMp4.Test | |||||
} | } | ||||
[Fact] | [Fact] | ||||
public void Test3_1() | |||||
public void Test4() | |||||
{ | { | ||||
FMp4EncoderInfo encoderInfo = new FMp4EncoderInfo(); | |||||
FMp4Encoder fMp4Encoder = new FMp4Encoder(); | 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)) | if (File.Exists(filepath)) | ||||
{ | { | ||||
File.Delete(filepath); | File.Delete(filepath); | ||||
} | } | ||||
using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | 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) | 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(); | fileStream.Close(); | ||||
} | } | ||||
[Fact] | [Fact] | ||||
public void Test4() | |||||
public void Test5() | |||||
{ | { | ||||
FMp4EncoderInfo encoderInfo = new FMp4EncoderInfo(); | |||||
FMp4Encoder fMp4Encoder = new FMp4Encoder(); | FMp4Encoder fMp4Encoder = new FMp4Encoder(); | ||||
H264Decoder h264Decoder = new H264Decoder(); | H264Decoder h264Decoder = new H264Decoder(); | ||||
var packages = ParseNALUTests(); | 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)) | if (File.Exists(filepath)) | ||||
{ | { | ||||
File.Delete(filepath); | File.Delete(filepath); | ||||
} | } | ||||
using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); | ||||
var ftyp = fMp4Encoder.EncoderFtypBox(); | var ftyp = fMp4Encoder.EncoderFtypBox(); | ||||
encoderInfo.SampleSize += (uint)ftyp.Length; | |||||
fileStream.Write(ftyp); | fileStream.Write(ftyp); | ||||
var iNalus = h264Decoder.ParseNALU(packages[0]); | var iNalus = h264Decoder.ParseNALU(packages[0]); | ||||
@@ -502,36 +503,31 @@ namespace JT1078.FMp4.Test | |||||
var moov = fMp4Encoder.EncoderMoovBox( | var moov = fMp4Encoder.EncoderMoovBox( | ||||
iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.SPS), | iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.SPS), | ||||
iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.PPS)); | iNalus.FirstOrDefault(f => f.NALUHeader.NalUnitType == NalUnitType.PPS)); | ||||
encoderInfo.SampleSize += (uint)moov.Length; | |||||
fileStream.Write(moov); | fileStream.Write(moov); | ||||
List<H264NALU> nalus = new List<H264NALU>(); | List<H264NALU> nalus = new List<H264NALU>(); | ||||
foreach (var package in packages) | foreach (var package in packages) | ||||
{ | { | ||||
List<H264NALU> h264NALUs = h264Decoder.ParseNALU(package); | 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(); | fileStream.Close(); | ||||
} | |||||
} | |||||
[Fact] | [Fact] | ||||
public void tkhd_width_height_test() | public void tkhd_width_height_test() | ||||
@@ -588,7 +584,7 @@ namespace JT1078.FMp4.Test | |||||
public JT1078Package ParseNALUTest() | public JT1078Package ParseNALUTest() | ||||
{ | { | ||||
JT1078Package Package = null; | 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; | int mergeBodyLength = 0; | ||||
foreach (var line in lines) | foreach (var line in lines) | ||||
{ | { | ||||
@@ -600,6 +596,7 @@ namespace JT1078.FMp4.Test | |||||
} | } | ||||
return Package; | return Package; | ||||
} | } | ||||
public List<JT1078Package> ParseNALUTests() | public List<JT1078Package> ParseNALUTests() | ||||
{ | { | ||||
List<JT1078Package> packages = new List<JT1078Package>(); | List<JT1078Package> packages = new List<JT1078Package>(); | ||||
@@ -619,24 +616,5 @@ namespace JT1078.FMp4.Test | |||||
} | } | ||||
return packages; | 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> | /// </summary> | ||||
public class FMp4Encoder | public class FMp4Encoder | ||||
{ | { | ||||
//Dictionary<string, TrackInfo> TrackInfos; | |||||
Dictionary<string, TrackInfo> TrackInfos; | |||||
const uint DefaultSampleDuration = 48000u; | const uint DefaultSampleDuration = 48000u; | ||||
const uint DefaultSampleFlags = 0x1010000; | const uint DefaultSampleFlags = 0x1010000; | ||||
@@ -45,7 +45,7 @@ namespace JT1078.FMp4 | |||||
/// </summary> | /// </summary> | ||||
public FMp4Encoder() | public FMp4Encoder() | ||||
{ | { | ||||
//TrackInfos = new Dictionary<string, TrackInfo>(StringComparer.OrdinalIgnoreCase); | |||||
TrackInfos = new Dictionary<string, TrackInfo>(StringComparer.OrdinalIgnoreCase); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -63,16 +63,11 @@ namespace JT1078.FMp4 | |||||
fileTypeBox.MajorBrand = "isom"; | fileTypeBox.MajorBrand = "isom"; | ||||
fileTypeBox.MinorVersion = "\0\0\u0002\0"; | fileTypeBox.MinorVersion = "\0\0\u0002\0"; | ||||
fileTypeBox.CompatibleBrands.Add("isom"); | fileTypeBox.CompatibleBrands.Add("isom"); | ||||
fileTypeBox.CompatibleBrands.Add("iso6"); | |||||
fileTypeBox.CompatibleBrands.Add("iso2"); | fileTypeBox.CompatibleBrands.Add("iso2"); | ||||
fileTypeBox.CompatibleBrands.Add("avc1"); | fileTypeBox.CompatibleBrands.Add("avc1"); | ||||
fileTypeBox.CompatibleBrands.Add("mp41"); | 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); | fileTypeBox.ToBuffer(ref writer); | ||||
var data = writer.FlushAndGetArray(); | var data = writer.FlushAndGetArray(); | ||||
return data; | return data; | ||||
@@ -97,19 +92,17 @@ namespace JT1078.FMp4 | |||||
var spsInfo = h264GolombReader.ReadSPS(); | var spsInfo = h264GolombReader.ReadSPS(); | ||||
//moov | //moov | ||||
MovieBox movieBox = new MovieBox(); | MovieBox movieBox = new MovieBox(); | ||||
movieBox.MovieHeaderBox = new MovieHeaderBox(0, 0); | |||||
movieBox.MovieHeaderBox = new MovieHeaderBox(0, 2); | |||||
movieBox.MovieHeaderBox.CreationTime = 0; | movieBox.MovieHeaderBox.CreationTime = 0; | ||||
movieBox.MovieHeaderBox.ModificationTime = 0; | movieBox.MovieHeaderBox.ModificationTime = 0; | ||||
movieBox.MovieHeaderBox.Duration = 0; | movieBox.MovieHeaderBox.Duration = 0; | ||||
movieBox.MovieHeaderBox.Timescale = 1000; | 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 = new TrackBox(); | ||||
movieBox.TrackBox.TrackHeaderBox = new TrackHeaderBox(0, 3); | movieBox.TrackBox.TrackHeaderBox = new TrackHeaderBox(0, 3); | ||||
movieBox.TrackBox.TrackHeaderBox.CreationTime = 0; | movieBox.TrackBox.TrackHeaderBox.CreationTime = 0; | ||||
movieBox.TrackBox.TrackHeaderBox.ModificationTime = 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.Duration = 0; | ||||
movieBox.TrackBox.TrackHeaderBox.TrackIsAudio = false; | movieBox.TrackBox.TrackHeaderBox.TrackIsAudio = false; | ||||
movieBox.TrackBox.TrackHeaderBox.Width = (uint)spsInfo.width; | movieBox.TrackBox.TrackHeaderBox.Width = (uint)spsInfo.width; | ||||
@@ -144,15 +137,15 @@ namespace JT1078.FMp4 | |||||
avc1.AVCConfigurationBox.SPSs = new List<byte[]>() { sps.RawData }; | avc1.AVCConfigurationBox.SPSs = new List<byte[]>() { sps.RawData }; | ||||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries.Add(avc1); | movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries.Add(avc1); | ||||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.TimeToSampleBox = new TimeToSampleBox(); | 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.SampleToChunkBox = new SampleToChunkBox(); | ||||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleSizeBox = new SampleSizeBox(); | movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleSizeBox = new SampleSizeBox(); | ||||
movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.ChunkOffsetBox = new ChunkOffsetBox(); | movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.ChunkOffsetBox = new ChunkOffsetBox(); | ||||
movieBox.MovieExtendsBox = new MovieExtendsBox(); | movieBox.MovieExtendsBox = new MovieExtendsBox(); | ||||
movieBox.MovieExtendsBox.TrackExtendsBoxs = new List<TrackExtendsBox>(); | movieBox.MovieExtendsBox.TrackExtendsBoxs = new List<TrackExtendsBox>(); | ||||
TrackExtendsBox trex = new TrackExtendsBox(); | TrackExtendsBox trex = new TrackExtendsBox(); | ||||
trex.TrackID = TrackID;//1 | |||||
trex.DefaultSampleDescriptionIndex = SampleDescriptionIndex;//1 | |||||
trex.TrackID = TrackID; | |||||
trex.DefaultSampleDescriptionIndex = SampleDescriptionIndex; | |||||
trex.DefaultSampleDuration = 0; | trex.DefaultSampleDuration = 0; | ||||
trex.DefaultSampleSize = 0; | trex.DefaultSampleSize = 0; | ||||
trex.DefaultSampleFlags = 0; | trex.DefaultSampleFlags = 0; | ||||
@@ -171,113 +164,99 @@ namespace JT1078.FMp4 | |||||
/// 编码其他视频数据盒子 | /// 编码其他视频数据盒子 | ||||
/// </summary> | /// </summary> | ||||
/// <returns></returns> | /// <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); | FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); | ||||
try | 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 | //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(); | var data = writer.FlushAndGetArray(); | ||||
return data; | return data; | ||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
FMp4ArrayPool.Return(buffer); | 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> | <licenseUrl>https://github.com/SmallChi/JT1078/blob/master/LICENSE</licenseUrl> | ||||
<license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | <license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | ||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> | <GeneratePackageOnBuild>true</GeneratePackageOnBuild> | ||||
<Version>1.0.0-preview2</Version> | |||||
<Version>1.0.0-preview3</Version> | |||||
<SignAssembly>false</SignAssembly> | <SignAssembly>false</SignAssembly> | ||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | ||||
<PackageLicenseFile>LICENSE</PackageLicenseFile> | <PackageLicenseFile>LICENSE</PackageLicenseFile> | ||||
@@ -1337,35 +1337,12 @@ | |||||
</summary> | </summary> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </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> | ||||
编码其他视频数据盒子 | 编码其他视频数据盒子 | ||||
</summary> | </summary> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </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"> | <member name="P:JT1078.FMp4.FullBox.Version"> | ||||
<summary> | <summary> | ||||
unsigned int(8) | unsigned int(8) | ||||
@@ -42,10 +42,7 @@ | |||||
<None Include="..\..\doc\video\jt1078_2.txt" Link="H264\jt1078_2.txt"> | <None Include="..\..\doc\video\jt1078_2.txt" Link="H264\jt1078_2.txt"> | ||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
</None> | </None> | ||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<None Update="H264\1078视频数据.txt"> | |||||
<None Include="..\..\doc\video\jt1078_5.txt" Link="H264\jt1078_5.txt"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
</None> | </None> | ||||
</ItemGroup> | </ItemGroup> | ||||
@@ -20,7 +20,7 @@ namespace JT1078.Hls.Test | |||||
{ | { | ||||
try | 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); | Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); | ||||
clientSocket.Connect("127.0.0.1",1078); | clientSocket.Connect("127.0.0.1",1078); | ||||
long lasttime = 0; | long lasttime = 0; | ||||
@@ -95,5 +95,26 @@ namespace JT1078.Protocol.Test.H264 | |||||
} | } | ||||
fileStream.Close(); | 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"> | <None Include="..\..\doc\video\jt1078_2.txt" Link="H264\jt1078_2.txt"> | ||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
</None> | </None> | ||||
<None Include="..\..\doc\video\jt1078_5.txt" Link="H264\jt1078_5.txt"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |
@@ -20,5 +20,8 @@ | |||||
<None Include="..\..\doc\video\jt1078_1_fragmented.mp4" Link="H264\jt1078_1_fragmented.mp4"> | <None Include="..\..\doc\video\jt1078_1_fragmented.mp4" Link="H264\jt1078_1_fragmented.mp4"> | ||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
</None> | </None> | ||||
<None Include="..\..\doc\video\jt1078_5.txt" Link="H264\jt1078_5.txt"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |
@@ -52,12 +52,16 @@ namespace JT1078.SignalR.Test.Services | |||||
public void a() | public void a() | ||||
{ | { | ||||
List<JT1078Package> packages = new List<JT1078Package>(); | 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; | int mergeBodyLength = 0; | ||||
foreach (var line in lines) | foreach (var line in lines) | ||||
{ | { | ||||
var data = line.Split(','); | 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); | JT1078Package package = JT1078Serializer.Deserialize(bytes); | ||||
mergeBodyLength += package.DataBodyLength; | mergeBodyLength += package.DataBodyLength; | ||||
var packageMerge = JT1078Serializer.Merge(package); | var packageMerge = JT1078Serializer.Merge(package); | ||||