N_m3u8DL-RE/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs
2025-02-01 19:13:42 +01:00

869 lines
34 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Mp4SubtitleParser;
using N_m3u8DL_RE.Common.Entity;
using N_m3u8DL_RE.Common.Util;
using System.Text;
using System.Text.RegularExpressions;
// https://github.com/canalplus/rx-player/blob/48d1f845064cea5c5a3546d2c53b1855c2be149d/src/parsers/manifest/smooth/get_codecs.ts
// https://github.dev/Dash-Industry-Forum/dash.js/blob/2aad3e79079b4de0bcd961ce6b4957103d98a621/src/mss/MssFragmentMoovProcessor.js
// https://github.com/yt-dlp/yt-dlp/blob/3639df54c3298e35b5ae2a96a25bc4d3c38950d0/yt_dlp/downloader/ism.py
// https://github.com/google/ExoPlayer/blob/a9444c880230d2c2c79097e89259ce0b9f80b87d/library/extractor/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java#L38
// https://github.com/sannies/mp4parser/blob/master/isoparser/src/main/java/org/mp4parser/boxes/iso14496/part15/HevcDecoderConfigurationRecord.java
namespace N_m3u8DL_RE.Parser.Mp4;
public partial class MSSMoovProcessor
{
[GeneratedRegex(@"\<KID\>(.*?)\<")]
private static partial Regex KIDRegex();
private static string StartCode = "00000001";
private StreamSpec StreamSpec;
private int TrackId = 2;
private string FourCC;
private string CodecPrivateData;
private int Timesacle;
private long Duration;
private string Language => StreamSpec.Language ?? "und";
private int Width => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').First());
private int Height => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').Last());
private string StreamType;
private int Channels;
private int BitsPerSample;
private int SamplingRate;
private int NalUnitLengthField;
private long CreationTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
private bool IsProtection;
private string ProtectionSystemId;
private string ProtectionData;
private string? ProtectionKID;
private string? ProtectionKID_PR;
private byte[] UnityMatrix
{
get
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.WriteInt(0x10000);
writer.WriteInt(0);
writer.WriteInt(0);
writer.WriteInt(0);
writer.WriteInt(0x10000);
writer.WriteInt(0);
writer.WriteInt(0);
writer.WriteInt(0);
writer.WriteInt(0x40000000);
return stream.ToArray();
}
}
private static byte TRACK_ENABLED = 0x1;
private static byte TRACK_IN_MOVIE = 0x2;
private static byte TRACK_IN_PREVIEW = 0x4;
private static byte SELF_CONTAINED = 0x1;
private static List<string> SupportedFourCC =
["HVC1", "HEV1", "AACL", "AACH", "EC-3", "H264", "AVC1", "DAVC", "AVC1", "TTML", "DVHE", "DVH1"];
public MSSMoovProcessor(StreamSpec streamSpec)
{
this.StreamSpec = streamSpec;
var data = streamSpec.MSSData!;
this.NalUnitLengthField = data.NalUnitLengthField;
this.CodecPrivateData = data.CodecPrivateData;
this.FourCC = data.FourCC;
this.Timesacle = data.Timesacle;
this.Duration = data.Duration;
this.StreamType = data.Type;
this.Channels = data.Channels;
this.SamplingRate = data.SamplingRate;
this.BitsPerSample = data.BitsPerSample;
this.IsProtection = data.IsProtection;
this.ProtectionData = data.ProtectionData;
this.ProtectionSystemId = data.ProtectionSystemID;
// 需要手动生成CodecPrivateData
if (string.IsNullOrEmpty(CodecPrivateData))
{
GenCodecPrivateDataForAAC();
}
// 解析KID
if (IsProtection)
{
ExtractKID();
}
}
private static string[] HEVC_GENERAL_PROFILE_SPACE_STRINGS = ["", "A", "B", "C"];
private int SamplingFrequencyIndex(int samplingRate) => samplingRate switch
{
96000 => 0x0,
88200 => 0x1,
64000 => 0x2,
48000 => 0x3,
44100 => 0x4,
32000 => 0x5,
24000 => 0x6,
22050 => 0x7,
16000 => 0x8,
12000 => 0x9,
11025 => 0xA,
8000 => 0xB,
7350 => 0xC,
_ => 0x0
};
private void GenCodecPrivateDataForAAC()
{
var objectType = 0x02; // AAC Main Low Complexity => object Type = 2
var indexFreq = SamplingFrequencyIndex(SamplingRate);
if (FourCC == "AACH")
{
// 4 bytes : XXXXX XXXX XXXX XXXX XXXXX XXX XXXXXXX
// ' ObjectType' 'Freq Index' 'Channels value' 'Extens Sampl Freq' 'ObjectType' 'GAS' 'alignment = 0'
objectType = 0x05; // High Efficiency AAC Profile = object Type = 5 SBR
var codecPrivateData = new byte[4];
var extensionSamplingFrequencyIndex = SamplingFrequencyIndex(SamplingRate * 2); // in HE AAC Extension Sampling frequence
// equals to SamplingRate*2
// Freq Index is present for 3 bits in the first byte, last bit is in the second
codecPrivateData[0] = (byte)((objectType << 3) | (indexFreq >> 1));
codecPrivateData[1] = (byte)((indexFreq << 7) | (Channels << 3) | (extensionSamplingFrequencyIndex >> 1));
codecPrivateData[2] = (byte)((extensionSamplingFrequencyIndex << 7) | (0x02 << 2)); // origin object type equals to 2 => AAC Main Low Complexity
codecPrivateData[3] = 0x0; // alignment bits
var arr16 = new ushort[2];
arr16[0] = (ushort)((codecPrivateData[0] << 8) + codecPrivateData[1]);
arr16[1] = (ushort)((codecPrivateData[2] << 8) + codecPrivateData[3]);
// convert decimal to hex value
this.CodecPrivateData = HexUtil.BytesToHex(BitConverter.GetBytes(arr16[0])).PadLeft(16, '0');
this.CodecPrivateData += HexUtil.BytesToHex(BitConverter.GetBytes(arr16[1])).PadLeft(16, '0');
}
else if (FourCC.StartsWith("AAC"))
{
// 2 bytes : XXXXX XXXX XXXX XXX
// ' ObjectType' 'Freq Index' 'Channels value' 'GAS = 000'
var codecPrivateData = new byte[2];
// Freq Index is present for 3 bits in the first byte, last bit is in the second
codecPrivateData[0] = (byte)((objectType << 3) | (indexFreq >> 1));
codecPrivateData[1] = (byte)((indexFreq << 7) | Channels << 3);
// put the 2 bytes in an 16 bits array
var arr16 = new ushort[1];
arr16[0] = (ushort)((codecPrivateData[0] << 8) + codecPrivateData[1]);
// convert decimal to hex value
this.CodecPrivateData = HexUtil.BytesToHex(BitConverter.GetBytes(arr16[0])).PadLeft(16, '0');
}
}
private void ExtractKID()
{
// playready
if (ProtectionSystemId.ToUpper() == "9A04F079-9840-4286-AB92-E65BE0885F95")
{
var bytes = HexUtil.HexToBytes(ProtectionData.Replace("00", ""));
var text = Encoding.ASCII.GetString(bytes);
var kidBytes = Convert.FromBase64String(KIDRegex().Match(text).Groups[1].Value);
// save kid for playready
this.ProtectionKID_PR = HexUtil.BytesToHex(kidBytes);
// fix byte order
var reverse1 = new[] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] };
var reverse2 = new[] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] };
Array.Copy(reverse1, 0, kidBytes, 0, reverse1.Length);
Array.Copy(reverse2, 0, kidBytes, 4, reverse1.Length);
this.ProtectionKID = HexUtil.BytesToHex(kidBytes);
}
// widevine
else if (ProtectionSystemId.ToUpper() == "EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED")
{
throw new NotSupportedException();
}
}
public static bool CanHandle(string fourCC) => SupportedFourCC.Contains(fourCC);
private byte[] Box(string boxType, byte[] payload)
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.WriteUInt(8 + (uint)payload.Length);
writer.Write(boxType);
writer.Write(payload);
return stream.ToArray();
}
private byte[] FullBox(string boxType, byte version, uint flags, byte[] payload)
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.Write(version);
writer.WriteUInt(flags, offset: 1);
writer.Write(payload);
return Box(boxType, stream.ToArray());
}
private byte[] GenSinf(string codec)
{
var frmaBox = Box("frma", Encoding.ASCII.GetBytes(codec));
var sinfPayload = new List<byte>();
sinfPayload.AddRange(frmaBox);
var schmPayload = new List<byte>();
schmPayload.AddRange(Encoding.ASCII.GetBytes("cenc")); // scheme_type 'cenc' => common encryption
schmPayload.AddRange([0, 1, 0, 0]); // scheme_version Major version 1, Minor version 0
var schmBox = FullBox("schm", 0, 0, schmPayload.ToArray());
sinfPayload.AddRange(schmBox);
var tencPayload = new List<byte>();
tencPayload.AddRange([0, 0]);
tencPayload.Add(0x1); // default_IsProtected
tencPayload.Add(0x8); // default_Per_Sample_IV_size
tencPayload.AddRange(HexUtil.HexToBytes(ProtectionKID)); // default_KID
// tencPayload.Add(0x8);// default_constant_IV_size
// tencPayload.AddRange(new byte[8]);// default_constant_IV
var tencBox = FullBox("tenc", 0, 0, tencPayload.ToArray());
var schiBox = Box("schi", tencBox);
sinfPayload.AddRange(schiBox);
var sinfBox = Box("sinf", sinfPayload.ToArray());
return sinfBox;
}
private byte[] GenFtyp()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.Write("isml"); // major brand
writer.WriteUInt(1); // minor version
writer.Write("iso5"); // compatible brand
writer.Write("iso6"); // compatible brand
writer.Write("piff"); // compatible brand
writer.Write("msdh"); // compatible brand
return Box("ftyp", stream.ToArray());
}
private byte[] GenMvhd()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.WriteULong(CreationTime); // creation_time
writer.WriteULong(CreationTime); // modification_time
writer.WriteUInt(Timesacle); // timescale
writer.WriteULong(Duration); // duration
writer.WriteUShort(1, padding: 2); // rate
writer.WriteByte(1, padding: 1); // volume
writer.WriteUShort(0); // reserved
writer.WriteUInt(0);
writer.WriteUInt(0);
writer.Write(UnityMatrix);
writer.WriteUInt(0); // pre defined
writer.WriteUInt(0);
writer.WriteUInt(0);
writer.WriteUInt(0);
writer.WriteUInt(0);
writer.WriteUInt(0);
writer.WriteUInt(0xffffffff); // next track id
return FullBox("mvhd", 1, 0, stream.ToArray());
}
private byte[] GenTkhd()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.WriteULong(CreationTime); // creation_time
writer.WriteULong(CreationTime); // modification_time
writer.WriteUInt(TrackId); // track id
writer.WriteUInt(0); // reserved
writer.WriteULong(Duration); // duration
writer.WriteUInt(0); // reserved
writer.WriteUInt(0);
writer.WriteShort(0); // layer
writer.WriteShort(0); // alternate group
writer.WriteByte(StreamType == "audio" ? (byte)1 : (byte)0, padding: 1); // volume
writer.WriteUShort(0); // reserved
writer.Write(UnityMatrix);
writer.WriteUShort(Width, padding: 2); // width
writer.WriteUShort(Height, padding: 2); // height
return FullBox("tkhd", 1, (uint)TRACK_ENABLED | TRACK_IN_MOVIE | TRACK_IN_PREVIEW, stream.ToArray());
}
private byte[] GenMdhd()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.WriteULong(CreationTime); // creation_time
writer.WriteULong(CreationTime); // modification_time
writer.WriteUInt(Timesacle); // timescale
writer.WriteULong(Duration); // duration
writer.WriteUShort((Language[0] - 0x60) << 10 | (Language[1] - 0x60) << 5 | (Language[2] - 0x60)); // language
writer.WriteUShort(0); // pre defined
return FullBox("mdhd", 1, 0, stream.ToArray());
}
private byte[] GenHdlr()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.WriteUInt(0); // pre defined
if (StreamType == "audio") writer.Write("soun");
else if (StreamType == "video") writer.Write("vide");
else if (StreamType == "text") writer.Write("subt");
else throw new NotSupportedException();
writer.WriteUInt(0); // reserved
writer.WriteUInt(0);
writer.WriteUInt(0);
writer.Write($"{StreamSpec.GroupId ?? "RE Handler"}\0"); // name
return FullBox("hdlr", 0, 0, stream.ToArray());
}
private byte[] GenMinf()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
var minfPayload = new List<byte>();
if (StreamType == "audio")
{
var smhd = new List<byte>();
smhd.Add(0); smhd.Add(0); // balance
smhd.Add(0); smhd.Add(0); // reserved
minfPayload.AddRange(FullBox("smhd", 0, 0, smhd.ToArray())); // Sound Media Header
}
else if (StreamType == "video")
{
var vmhd = new List<byte>();
vmhd.Add(0); vmhd.Add(0); // graphics mode
vmhd.Add(0); vmhd.Add(0); vmhd.Add(0); vmhd.Add(0); vmhd.Add(0); vmhd.Add(0);// opcolor
minfPayload.AddRange(FullBox("vmhd", 0, 1, vmhd.ToArray())); // Video Media Header
}
else if (StreamType == "text")
{
minfPayload.AddRange(FullBox("sthd", 0, 0, [])); // Subtitle Media Header
}
else
{
throw new NotSupportedException();
}
var drefPayload = new List<byte>();
drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(1); // entry count
drefPayload.AddRange(FullBox("url ", 0, SELF_CONTAINED, [])); // Data Entry URL Box
var dinfPayload = FullBox("dref", 0, 0, drefPayload.ToArray()); // Data Reference Box
minfPayload.AddRange(Box("dinf", dinfPayload.ToArray())); // Data Information Box
return minfPayload.ToArray();
}
private byte[] GenEsds(byte[] audioSpecificConfig)
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
// ESDS length = esds box header length (= 12) +
// ES_Descriptor header length (= 5) +
// DecoderConfigDescriptor header length (= 15) +
// decoderSpecificInfo header length (= 2) +
// AudioSpecificConfig length (= codecPrivateData length)
// esdsLength = 34 + len(audioSpecificConfig)
// ES_Descriptor (see ISO/IEC 14496-1 (Systems))
writer.WriteByte(0x03); // tag = 0x03 (ES_DescrTag)
writer.WriteByte((byte)(20 + audioSpecificConfig.Length)); // size
writer.WriteByte((byte)((TrackId & 0xFF00) >> 8)); // ES_ID = track_id
writer.WriteByte((byte)(TrackId & 0x00FF));
writer.WriteByte(0); // flags and streamPriority
// DecoderConfigDescriptor (see ISO/IEC 14496-1 (Systems))
writer.WriteByte(0x04); // tag = 0x04 (DecoderConfigDescrTag)
writer.WriteByte((byte)(15 + audioSpecificConfig.Length)); // size
writer.WriteByte(0x40); // objectTypeIndication = 0x40 (MPEG-4 AAC)
writer.WriteByte((0x05 << 2) | (0 << 1) | 1); // reserved = 1
writer.WriteByte(0xFF); // buffersizeDB = undefined
writer.WriteByte(0xFF);
writer.WriteByte(0xFF);
var bandwidth = StreamSpec.Bandwidth!;
writer.WriteByte((byte)((bandwidth & 0xFF000000) >> 24)); // maxBitrate
writer.WriteByte((byte)((bandwidth & 0x00FF0000) >> 16));
writer.WriteByte((byte)((bandwidth & 0x0000FF00) >> 8));
writer.WriteByte((byte)(bandwidth & 0x000000FF));
writer.WriteByte((byte)((bandwidth & 0xFF000000) >> 24)); // avgbitrate
writer.WriteByte((byte)((bandwidth & 0x00FF0000) >> 16));
writer.WriteByte((byte)((bandwidth & 0x0000FF00) >> 8));
writer.WriteByte((byte)(bandwidth & 0x000000FF));
// DecoderSpecificInfo (see ISO/IEC 14496-1 (Systems))
writer.WriteByte(0x05); // tag = 0x05 (DecSpecificInfoTag)
writer.WriteByte((byte)audioSpecificConfig.Length); // size
writer.Write(audioSpecificConfig); // AudioSpecificConfig bytes
return FullBox("esds", 0, 0, stream.ToArray());
}
private byte[] GetSampleEntryBox()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.WriteByte(0); // reserved
writer.WriteByte(0);
writer.WriteByte(0);
writer.WriteByte(0);
writer.WriteByte(0);
writer.WriteByte(0);
writer.WriteUShort(1); // data reference index
if (StreamType == "audio")
{
writer.WriteUInt(0); // reserved2
writer.WriteUInt(0);
writer.WriteUShort(Channels); // channels
writer.WriteUShort(BitsPerSample); // bits_per_sample
writer.WriteUShort(0); // pre defined
writer.WriteUShort(0); // reserved3
writer.WriteUShort(SamplingRate, padding: 2); // sampling_rate
var audioSpecificConfig = HexUtil.HexToBytes(CodecPrivateData);
var esdsBox = GenEsds(audioSpecificConfig);
writer.Write(esdsBox);
if (FourCC.StartsWith("AAC"))
{
if (IsProtection)
{
var sinfBox = GenSinf("mp4a");
writer.Write(sinfBox);
return Box("enca", stream.ToArray()); // Encrypted Audio
}
return Box("mp4a", stream.ToArray());
}
if (FourCC == "EC-3")
{
if (IsProtection)
{
var sinfBox = GenSinf("ec-3");
writer.Write(sinfBox);
return Box("enca", stream.ToArray()); // Encrypted Audio
}
return Box("ec-3", stream.ToArray());
}
}
else if (StreamType == "video")
{
writer.WriteUShort(0); // pre defined
writer.WriteUShort(0); // reserved
writer.WriteUInt(0); // pre defined
writer.WriteUInt(0);
writer.WriteUInt(0);
writer.WriteUShort(Width); // width
writer.WriteUShort(Height); // height
writer.WriteUShort(0x48, padding: 2); // horiz resolution 72 dpi
writer.WriteUShort(0x48, padding: 2); // vert resolution 72 dpi
writer.WriteUInt(0); // reserved
writer.WriteUShort(1); // frame count
for (int i = 0; i < 32; i++) // compressor name
{
writer.WriteByte(0);
}
writer.WriteUShort(0x18); // depth
writer.WriteUShort(65535); // pre defined
var codecPrivateData = HexUtil.HexToBytes(CodecPrivateData);
if (FourCC is "H264" or "AVC1" or "DAVC")
{
var arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries);
var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 7));
var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 8));
// make avcC
var avcC = GetAvcC(sps, pps);
writer.Write(avcC);
if (IsProtection)
{
var sinfBox = GenSinf("avc1");
writer.Write(sinfBox);
return Box("encv", stream.ToArray()); // Encrypted Video
}
return Box("avc1", stream.ToArray()); // AVC Simple Entry
}
if (FourCC is "HVC1" or "HEV1")
{
var arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries);
var vps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20));
var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21));
var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22));
// make hvcC
var hvcC = GetHvcC(sps, pps, vps);
writer.Write(hvcC);
if (IsProtection)
{
var sinfBox = GenSinf("hvc1");
writer.Write(sinfBox);
return Box("encv", stream.ToArray()); // Encrypted Video
}
return Box("hvc1", stream.ToArray()); // HEVC Simple Entry
}
// 杜比视界也按照hevc处理
if (FourCC is "DVHE" or "DVH1")
{
var arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries);
var vps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20));
var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21));
var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22));
// make hvcC
var hvcC = GetHvcC(sps, pps, vps, "dvh1");
writer.Write(hvcC);
if (IsProtection)
{
var sinfBox = GenSinf("dvh1");
writer.Write(sinfBox);
return Box("encv", stream.ToArray()); // Encrypted Video
}
return Box("dvh1", stream.ToArray()); // HEVC Simple Entry
}
throw new NotSupportedException();
}
else if (StreamType == "text")
{
if (FourCC == "TTML")
{
writer.Write("http://www.w3.org/ns/ttml\0"); // namespace
writer.Write("\0"); // schema location
writer.Write("\0"); // auxilary mime types(??)
return Box("stpp", stream.ToArray()); // TTML Simple Entry
}
throw new NotSupportedException();
}
else
{
throw new NotSupportedException();
}
throw new NotSupportedException();
}
private byte[] GetAvcC(byte[] sps, byte[] pps)
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.WriteByte(1); // configuration version
writer.Write(sps[1..4]); // avc profile indication + profile compatibility + avc level indication
writer.WriteByte((byte)(0xfc | (NalUnitLengthField - 1))); // complete representation (1) + reserved (11111) + length size minus one
writer.WriteByte(1); // reserved (0) + number of sps (0000001)
writer.WriteUShort(sps.Length);
writer.Write(sps);
writer.WriteByte(1); // number of pps
writer.WriteUShort(pps.Length);
writer.Write(pps);
return Box("avcC", stream.ToArray()); // AVC Decoder Configuration Record
}
private byte[] GetHvcC(byte[] sps, byte[] pps, byte[] vps, string code = "hvc1")
{
var oriSps = new List<byte>(sps);
// https://www.itu.int/rec/dologin.asp?lang=f&id=T-REC-H.265-201504-S!!PDF-E&type=items
// Read generalProfileSpace, generalTierFlag, generalProfileIdc,
// generalProfileCompatibilityFlags, constraintBytes, generalLevelIdc
// from sps
var encList = new List<byte>();
/**
* 处理payload, 有00 00 03 0,1,2,3的情况 统一换成00 00 XX 即丢弃03
* 注意此处采用的逻辑是直接简单粗暴地判断列表末尾3字节如果是0x000003就删掉最后的0x03可能会导致以下情况
* 00 00 03 03 03 03 03 01 会被直接处理成 => 00 00 01
* 此处经过测试只有直接跳过才正常,如果处理成 00 00 03 03 03 03 01 是有问题的
*
* 测试的数据如下:
* 原始42 01 01 01 60 00 00 03 00 90 00 00 03 00 00 03 00 96 a0 01 e0 20 06 61 65 95 9a 49 30 bf fc 0c 7c 0c 81 a8 08 08 08 20 00 00 03 00 20 00 00 03 03 01
* 处理后42 01 01 01 60 00 00 00 90 00 00 00 00 00 96 A0 01 E0 20 06 61 65 95 9A 49 30 BF FC 0C 7C 0C 81 A8 08 08 08 20 00 00 00 20 00 00 01
*/
using (var _reader = new BinaryReader(new MemoryStream(sps)))
{
while (_reader.BaseStream.Position < _reader.BaseStream.Length)
{
encList.Add(_reader.ReadByte());
if (encList is [.., 0x00, 0x00, 0x03])
{
encList.RemoveAt(encList.Count - 1);
}
}
}
sps = encList.ToArray();
using var reader = new BinaryReader2(new MemoryStream(sps));
reader.ReadBytes(2); // Skip 2 bytes unit header
var firstByte = reader.ReadByte();
var maxSubLayersMinus1 = (firstByte & 0xe) >> 1;
var nextByte = reader.ReadByte();
var generalProfileSpace = (nextByte & 0xc0) >> 6;
var generalTierFlag = (nextByte & 0x20) >> 5;
var generalProfileIdc = nextByte & 0x1f;
var generalProfileCompatibilityFlags = reader.ReadUInt32();
var constraintBytes = reader.ReadBytes(6);
var generalLevelIdc = reader.ReadByte();
/*var skipBit = 0;
for (int i = 0; i < maxSubLayersMinus1; i++)
{
skipBit += 2; // sub_layer_profile_present_flag sub_layer_level_present_flag
}
if (maxSubLayersMinus1 > 0)
{
for (int i = maxSubLayersMinus1; i < 8; i++)
{
skipBit += 2; // reserved_zero_2bits
}
}
for (int i = 0; i < maxSubLayersMinus1; i++)
{
skipBit += 2; // sub_layer_profile_present_flag sub_layer_level_present_flag
}*/
// 生成编码信息
var codecs = code +
$".{HEVC_GENERAL_PROFILE_SPACE_STRINGS[generalProfileSpace]}{generalProfileIdc}" +
$".{Convert.ToString(generalProfileCompatibilityFlags, 16)}" +
$".{(generalTierFlag == 1 ? 'H' : 'L')}{generalLevelIdc}" +
$".{HexUtil.BytesToHex(constraintBytes.Where(b => b != 0).ToArray())}";
StreamSpec.Codecs = codecs;
///////////////////////
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
// var reserved1 = 0xF;
writer.WriteByte(1); // configuration version
writer.WriteByte((byte)((generalProfileSpace << 6) + (generalTierFlag == 1 ? 0x20 : 0) | generalProfileIdc)); // general_profile_space + general_tier_flag + general_profile_idc
writer.WriteUInt(generalProfileCompatibilityFlags); // general_profile_compatibility_flags
writer.Write(constraintBytes); // general_constraint_indicator_flags
writer.WriteByte((byte)generalProfileIdc); // general_level_idc
writer.WriteUShort(0xf000); // reserved + min_spatial_segmentation_idc
writer.WriteByte(0xfc); // reserved + parallelismType
writer.WriteByte(0 | 0xfc); // reserved + chromaFormat
writer.WriteByte(0 | 0xf8); // reserved + bitDepthLumaMinus8
writer.WriteByte(0 | 0xf8); // reserved + bitDepthChromaMinus8
writer.WriteUShort(0); // avgFrameRate
writer.WriteByte((byte)(0 << 6 | 0 << 3 | 0 << 2 | (NalUnitLengthField - 1))); // constantFrameRate + numTemporalLayers + temporalIdNested + lengthSizeMinusOne
writer.WriteByte(0x03); // numOfArrays (vps sps pps)
sps = oriSps.ToArray();
writer.WriteByte(0x20); // array_completeness + reserved + NAL_unit_type
writer.WriteUShort(1); // numNalus
writer.WriteUShort(vps.Length);
writer.Write(vps);
writer.WriteByte(0x21);
writer.WriteUShort(1); // numNalus
writer.WriteUShort(sps.Length);
writer.Write(sps);
writer.WriteByte(0x22);
writer.WriteUShort(1); // numNalus
writer.WriteUShort(pps.Length);
writer.Write(pps);
return Box("hvcC", stream.ToArray()); // HEVC Decoder Configuration Record
}
private byte[] GetStsd()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.WriteUInt(1); // entry count
var sampleEntryData = GetSampleEntryBox();
writer.Write(sampleEntryData);
return stream.ToArray();
}
private byte[] GetMehd()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.WriteULong(Duration);
return FullBox("mehd", 1, 0, stream.ToArray()); // Movie Extends Header Box
}
private byte[] GetTrex()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
writer.WriteUInt(TrackId); // track id
writer.WriteUInt(1); // default sample description index
writer.WriteUInt(0); // default sample duration
writer.WriteUInt(0); // default sample size
writer.WriteUInt(0); // default sample flags
return FullBox("trex", 0, 0, stream.ToArray()); // Track Extends Box
}
private byte[] GenPsshBoxForPlayReady()
{
using var _stream = new MemoryStream();
using var _writer = new BinaryWriter2(_stream);
var sysIdData = HexUtil.HexToBytes(ProtectionSystemId.Replace("-", ""));
var psshData = HexUtil.HexToBytes(ProtectionData);
_writer.Write(sysIdData); // SystemID 16 bytes
_writer.WriteUInt(psshData.Length); // Size of Data 4 bytes
_writer.Write(psshData); // Data
var psshBox = FullBox("pssh", 0, 0, _stream.ToArray());
return psshBox;
}
private byte[] GenPsshBoxForWideVine()
{
using var _stream = new MemoryStream();
using var _writer = new BinaryWriter2(_stream);
var sysIdData = HexUtil.HexToBytes("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed".Replace("-", ""));
// var kid = HexUtil.HexToBytes(ProtectionKID);
_writer.Write(sysIdData); // SystemID 16 bytes
var psshData = HexUtil.HexToBytes($"08011210{ProtectionKID}1A046E647265220400000000");
_writer.WriteUInt(psshData.Length); // Size of Data 4 bytes
_writer.Write(psshData); // Data
var psshBox = FullBox("pssh", 0, 0, _stream.ToArray());
return psshBox;
}
private byte[] GenMoof()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter2(stream);
// make senc
writer.WriteUInt(1); // sample_count
writer.Write(new byte[8]); // 8 bytes IV
var sencBox = FullBox("senc", 1, 0, stream.ToArray());
var moofBox = Box("moof", sencBox); // Movie Extends Box
return moofBox;
}
public byte[] GenHeader(byte[] firstSegment)
{
new MP4Parser()
.Box("moof", MP4Parser.Children)
.Box("traf", MP4Parser.Children)
.FullBox("tfhd", box =>
{
TrackId = (int)box.Reader.ReadUInt32();
})
.Parse(firstSegment);
return GenHeader();
}
public byte[] GenHeader()
{
using var stream = new MemoryStream();
var ftyp = GenFtyp(); // File Type Box
stream.Write(ftyp);
var moovPayload = GenMvhd(); // Movie Header Box
var trakPayload = GenTkhd(); // Track Header Box
var mdhdPayload = GenMdhd(); // Media Header Box
var hdlrPayload = GenHdlr(); // Handler Reference Box
var mdiaPayload = mdhdPayload.Concat(hdlrPayload).ToArray();
var minfPayload = GenMinf();
var sttsPayload = new byte[] { 0, 0, 0, 0 }; // entry count
var stblPayload = FullBox("stts", 0, 0, sttsPayload); // Decoding Time to Sample Box
var stscPayload = new byte[] { 0, 0, 0, 0 }; // entry count
var stscBox = FullBox("stsc", 0, 0, stscPayload); // Sample To Chunk Box
var stcoPayload = new byte[] { 0, 0, 0, 0 }; // entry count
var stcoBox = FullBox("stco", 0, 0, stcoPayload); // Chunk Offset Box
var stszPayload = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; // sample size, sample count
var stszBox = FullBox("stsz", 0, 0, stszPayload); // Sample Size Box
var stsdPayload = GetStsd();
var stsdBox = FullBox("stsd", 0, 0, stsdPayload); // Sample Description Box
stblPayload = stblPayload.Concat(stscBox).Concat(stcoBox).Concat(stszBox).Concat(stsdBox).ToArray();
var stblBox = Box("stbl", stblPayload); // Sample Table Box
minfPayload = minfPayload.Concat(stblBox).ToArray();
var minfBox = Box("minf", minfPayload); // Media Information Box
mdiaPayload = mdiaPayload.Concat(minfBox).ToArray();
var mdiaBox = Box("mdia", mdiaPayload); // Media Box
trakPayload = trakPayload.Concat(mdiaBox).ToArray();
var trakBox = Box("trak", trakPayload); // Track Box
moovPayload = moovPayload.Concat(trakBox).ToArray();
var mvexPayload = GetMehd();
var trexBox = GetTrex();
mvexPayload = mvexPayload.Concat(trexBox).ToArray();
var mvexBox = Box("mvex", mvexPayload); // Movie Extends Box
moovPayload = moovPayload.Concat(mvexBox).ToArray();
if (IsProtection)
{
var psshBox1 = GenPsshBoxForPlayReady();
var psshBox2 = GenPsshBoxForWideVine();
moovPayload = moovPayload.Concat(psshBox1).Concat(psshBox2).ToArray();
}
var moovBox = Box("moov", moovPayload); // Movie Box
stream.Write(moovBox);
// var moofBox = GenMoof(); // Movie Extends Box
// stream.Write(moofBox);
return stream.ToArray();
}
}