From 13fad59f2c2f07fa24d56f64bdb578d236c23361 Mon Sep 17 00:00:00 2001 From: deepch Date: Thu, 27 Jan 2022 04:10:06 +0300 Subject: [PATCH] mkv in progress --- format/mkv/demuxer.go | 147 ++++++++++++++ format/mkv/handler.go | 29 +++ format/mkv/mkvio/ebml.go | 29 +++ format/mkv/mkvio/elements.go | 359 +++++++++++++++++++++++++++++++++++ format/mkv/mkvio/parser.go | 214 +++++++++++++++++++++ format/mkv/mkvio/utils.go | 23 +++ format/mkv/stream.go | 24 +++ go.mod | 1 + go.sum | 2 + 9 files changed, 828 insertions(+) create mode 100644 format/mkv/demuxer.go create mode 100644 format/mkv/handler.go create mode 100644 format/mkv/mkvio/ebml.go create mode 100644 format/mkv/mkvio/elements.go create mode 100644 format/mkv/mkvio/parser.go create mode 100644 format/mkv/mkvio/utils.go create mode 100644 format/mkv/stream.go diff --git a/format/mkv/demuxer.go b/format/mkv/demuxer.go new file mode 100644 index 0000000..a8940be --- /dev/null +++ b/format/mkv/demuxer.go @@ -0,0 +1,147 @@ +package mkv + +import ( + "encoding/binary" + "errors" + "io" + "time" + + "github.com/deepch/vdk/av" + "github.com/deepch/vdk/codec/h264parser" + "github.com/deepch/vdk/format/mkv/mkvio" +) + +type Demuxer struct { + r *mkvio.Document + pkts []av.Packet + sps []byte + pps []byte + streams []*Stream + ps uint32 + stage int + fc int + ls time.Duration +} + +func NewDemuxer(r io.Reader) *Demuxer { + + return &Demuxer{ + r: mkvio.InitDocument(r), + } + +} + +func (self *Demuxer) Streams() (streams []av.CodecData, err error) { + + if err = self.probe(); err != nil { + return + } + + for _, stream := range self.streams { + streams = append(streams, stream.CodecData) + } + + if len(streams) == 0 { + return nil, errors.New("streams not found") + } + + return streams, err + +} + +func (self *Demuxer) probe() (err error) { + if self.stage == 0 { + + var el *mkvio.Element + el, err = self.r.GetVideoCodec() + if err != nil { + return + } + + if el.ElementRegister.ID == mkvio.ElementCodecPrivate.ID { + payload := el.Content[6:] + var reader int + for pos := 0; pos < len(payload); pos = reader { + lens := int(binary.BigEndian.Uint16(payload[reader:])) + reader += 2 + nal := payload[reader : reader+lens] + naluType := nal[0] & 0x1f + switch naluType { + case h264parser.NALU_SPS: + self.sps = nal + case h264parser.NALU_PPS: + self.pps = nal + } + reader += lens + reader++ + } + } + + if len(self.sps) > 0 && len(self.pps) > 0 { + var codec av.CodecData + codec, err = h264parser.NewCodecDataFromSPSAndPPS(self.sps, self.pps) + + if err != nil { + return + } + + stream := &Stream{} + stream.idx = 0 + stream.demuxer = self + stream.CodecData = codec + self.streams = append(self.streams, stream) + + } + self.stage++ + } + return +} + +func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { + + var el mkvio.Element + + for { + el, err = self.r.ParseElement() + if err != nil { + return + } + + if el.Type == 6 && el.ElementRegister.ID == mkvio.ElementSimpleBlock.ID { + self.fc++ + nals, _ := h264parser.SplitNALUs(el.Content[4:]) + for _, nal := range nals { + + naluType := nal[0] & 0x1f + + if naluType == 5 { + l1 := int(binary.BigEndian.Uint16(el.Content[2:4])) + dur := time.Duration(uint32(l1)) * time.Millisecond + self.ls += time.Duration(uint32(l1)) * time.Millisecond + self.ps = 0 + pkt = av.Packet{IsKeyFrame: true, Idx: 0, Duration: dur, Time: self.ls, Data: append(binSize(len(nal)), nal...)} + return + + } else if naluType == 1 { + + l1 := int(binary.BigEndian.Uint16(el.Content[1:3])) + dur := time.Duration(uint32(l1)-self.ps) * time.Millisecond + self.ls += time.Duration(uint32(l1)-self.ps) * time.Millisecond + self.ps = uint32(l1) + pkt = av.Packet{Idx: 0, Duration: dur, Time: self.ls, Data: append(binSize(len(nal)), nal...)} + return + + } + } + } + } + + return + +} + +func binSize(val int) []byte { + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, uint32(val)) + return buf +} diff --git a/format/mkv/handler.go b/format/mkv/handler.go new file mode 100644 index 0000000..5db2100 --- /dev/null +++ b/format/mkv/handler.go @@ -0,0 +1,29 @@ +package mkv + +import ( + "io" + + "github.com/deepch/vdk/av" + "github.com/deepch/vdk/av/avutil" +) + +var CodecTypes = []av.CodecType{av.H264, av.AAC} + +func Handler(h *avutil.RegisterHandler) { + h.Ext = ".mkv" + + h.Probe = func(b []byte) bool { + return b[0] == 0x47 && b[188] == 0x47 + } + + h.ReaderDemuxer = func(r io.Reader) av.Demuxer { + return NewDemuxer(r) + } + + h.WriterMuxer = func(w io.Writer) av.Muxer { + //return NewMuxer(w) + return nil + } + + h.CodecTypes = CodecTypes +} diff --git a/format/mkv/mkvio/ebml.go b/format/mkv/mkvio/ebml.go new file mode 100644 index 0000000..aca4c01 --- /dev/null +++ b/format/mkv/mkvio/ebml.go @@ -0,0 +1,29 @@ +package mkvio + +import ( + "io" +) + +// Document represents a WebM file +type Document struct { + r io.Reader +} + +// ElementRegister contains the ID, type and name of the +// standard WebM/Matroska elements +type ElementRegister struct { + ID uint32 + Type uint8 + Name string +} + +// Element is a Matroska/WebM/EBML element +type Element struct { + ElementRegister + + Parent *Element + Level int32 + Size uint64 + Content []byte // Data contained in the element, nil if it is a master element + Bytes []byte // Whole binary representation of the element (nil if data is missing) +} diff --git a/format/mkv/mkvio/elements.go b/format/mkv/mkvio/elements.go new file mode 100644 index 0000000..9ccf98a --- /dev/null +++ b/format/mkv/mkvio/elements.go @@ -0,0 +1,359 @@ +package mkvio + +const ( + ElementType uint8 = 0x0 + ElementTypeUnknown uint8 = 0x0 + ElementTypeMaster uint8 = 0x1 + ElementTypeUint uint8 = 0x2 + ElementTypeInt uint8 = 0x3 + ElementTypeString uint8 = 0x4 + ElementTypeUnicode uint8 = 0x5 + ElementTypeBinary uint8 = 0x6 + ElementTypeFloat uint8 = 0x7 + ElementTypeDate uint8 = 0x8 +) + +var ( + ElementUnknown = ElementRegister{0x0, ElementTypeUnknown, "Unknown"} + ElementEBML = ElementRegister{0x1a45dfa3, ElementTypeMaster, "EBML"} + ElementEBMLVersion = ElementRegister{0x4286, ElementTypeUint, "EBMLVersion"} + ElementEBMLReadVersion = ElementRegister{0x42f7, ElementTypeUint, "EBMLReadVersion"} + ElementEBMLMaxIDLength = ElementRegister{0x42f2, ElementTypeUint, "EBMLMaxIDLength"} + ElementEBMLMaxSizeLength = ElementRegister{0x42f3, ElementTypeUint, "EBMLMaxSizeLength"} + ElementDocType = ElementRegister{0x4282, ElementTypeString, "DocType"} + ElementDocTypeVersion = ElementRegister{0x4287, ElementTypeUint, "DocTypeVersion"} + ElementDocTypeReadVersion = ElementRegister{0x4285, ElementTypeUint, "DocTypeReadVersion"} + ElementVoid = ElementRegister{0xec, ElementTypeBinary, "Void"} + ElementCRC32 = ElementRegister{0xbf, ElementTypeBinary, "CRC-32"} + ElementSegment = ElementRegister{0x18538067, ElementTypeMaster, "Segment"} + ElementSeekHead = ElementRegister{0x114d9b74, ElementTypeMaster, "SeekHead"} + ElementSeek = ElementRegister{0x4dbb, ElementTypeMaster, "Seek"} + ElementSeekID = ElementRegister{0x53ab, ElementTypeBinary, "SeekID"} + ElementSeekPosition = ElementRegister{0x53ac, ElementTypeUint, "SeekPosition"} + ElementInfo = ElementRegister{0x1549a966, ElementTypeMaster, "Info"} + ElementSegmentUID = ElementRegister{0x73a4, ElementTypeBinary, "SegmentUID"} + ElementSegmentFilename = ElementRegister{0x7384, ElementTypeUnicode, "SegmentFilename"} + ElementPrevUID = ElementRegister{0x3cb923, ElementTypeBinary, "PrevUID"} + ElementPrevFilename = ElementRegister{0x3c83ab, ElementTypeUnicode, "PrevFilename"} + ElementNextUID = ElementRegister{0x3eb923, ElementTypeBinary, "NextUID"} + ElementNextFilename = ElementRegister{0x3e83bb, ElementTypeUnicode, "NextFilename"} + ElementSegmentFamily = ElementRegister{0x4444, ElementTypeBinary, "SegmentFamily"} + ElementChapterTranslate = ElementRegister{0x6924, ElementTypeMaster, "ChapterTranslate"} + ElementChapterTranslateEditionUID = ElementRegister{0x69fc, ElementTypeUint, "ChapterTranslateEditionUID"} + ElementChapterTranslateCodec = ElementRegister{0x69bf, ElementTypeUint, "ChapterTranslateCodec"} + ElementChapterTranslateID = ElementRegister{0x69a5, ElementTypeBinary, "ChapterTranslateID"} + ElementTimecodeScale = ElementRegister{0x2ad7b1, ElementTypeUint, "TimecodeScale"} + ElementDuration = ElementRegister{0x4489, ElementTypeFloat, "Duration"} + ElementDateUTC = ElementRegister{0x4461, ElementTypeDate, "DateUTC"} + ElementTitle = ElementRegister{0x7ba9, ElementTypeUnicode, "Title"} + ElementMuxingApp = ElementRegister{0x4d80, ElementTypeUnicode, "MuxingApp"} + ElementWritingApp = ElementRegister{0x5741, ElementTypeUnicode, "WritingApp"} + ElementCluster = ElementRegister{0x1f43b675, ElementTypeMaster, "Cluster"} + ElementTimecode = ElementRegister{0xe7, ElementTypeUint, "Timecode"} + ElementSlientTracks = ElementRegister{0x5854, ElementTypeMaster, "SlientTracks"} + ElementSlientTrackNumber = ElementRegister{0x58d7, ElementTypeUint, "SlientTrackNumber"} + ElementPosition = ElementRegister{0xa7, ElementTypeUint, "Position"} + ElementPrevSize = ElementRegister{0xab, ElementTypeUint, "PrevSize"} + ElementSimpleBlock = ElementRegister{0xa3, ElementTypeBinary, "SimpleBlock"} + ElementBlockGroup = ElementRegister{0xa0, ElementTypeMaster, "BlockGroup"} + ElementBlock = ElementRegister{0xa1, ElementTypeBinary, "Block"} + ElementBlockAdditions = ElementRegister{0x75a1, ElementTypeMaster, "BlockAdditions"} + ElementBlockMore = ElementRegister{0xa6, ElementTypeMaster, "BlockMore"} + ElementBlockAddID = ElementRegister{0xee, ElementTypeUint, "BlockAddID"} + ElementBlockAdditional = ElementRegister{0xa5, ElementTypeBinary, "BlockAdditional"} + ElementBlockDuration = ElementRegister{0x9b, ElementTypeUint, "BlockDuration"} + ElementReferencePriority = ElementRegister{0xfa, ElementTypeUint, "ReferencePriority"} + ElementReferenceBlock = ElementRegister{0xfb, ElementTypeInt, "ReferenceBlock"} + ElementCodecState = ElementRegister{0xa4, ElementTypeBinary, "CodecState"} + ElementDiscardPadding = ElementRegister{0x75a2, ElementTypeInt, "DiscardPadding"} + ElementSlices = ElementRegister{0x8e, ElementTypeMaster, "Slices"} + ElementTimeSlice = ElementRegister{0xe8, ElementTypeMaster, "TimeSlice"} + ElementLaceNumber = ElementRegister{0xcc, ElementTypeUint, "LaceNumber"} + ElementTracks = ElementRegister{0x1654ae6b, ElementTypeMaster, "Tracks"} + ElementTrackEntry = ElementRegister{0xae, ElementTypeMaster, "TrackEntry"} + ElementTrackNumber = ElementRegister{0xd7, ElementTypeUint, "TrackNumber"} + ElementTrackUID = ElementRegister{0x73c5, ElementTypeUint, "TrackUID"} + ElementTrackType = ElementRegister{0x83, ElementTypeUint, "TrackType"} + ElementFlagEnabled = ElementRegister{0xb9, ElementTypeUint, "FlagEnabled"} + ElementFlagDefault = ElementRegister{0x88, ElementTypeUint, "FlagDefault"} + ElementFlagForced = ElementRegister{0x55aa, ElementTypeUint, "FlagForced"} + ElementFlagLacing = ElementRegister{0x9c, ElementTypeUint, "FlagLacing"} + ElementMinCache = ElementRegister{0x6de7, ElementTypeUint, "MinCache"} + ElementMaxCache = ElementRegister{0x6df8, ElementTypeUint, "MaxCache"} + ElementDefaultDuration = ElementRegister{0x23e383, ElementTypeUint, "DefaultDuration"} + ElementDefaultDecodedFieldDuration = ElementRegister{0x234e7a, ElementTypeUint, "DefaultDecodedFieldDuration"} + ElementMaxBlockAdditionID = ElementRegister{0x55ee, ElementTypeUint, "MaxBlockAdditionID"} + ElementName = ElementRegister{0x536e, ElementTypeUnicode, "Name"} + ElementLanguage = ElementRegister{0x22b59c, ElementTypeString, "Language"} + ElementCodecID = ElementRegister{0x86, ElementTypeString, "CodecID"} + ElementCodecPrivate = ElementRegister{0x63a2, ElementTypeBinary, "CodecPrivate"} + ElementCodecName = ElementRegister{0x258688, ElementTypeUnicode, "CodecName"} + ElementAttachmentLink = ElementRegister{0x7446, ElementTypeUint, "AttachmentLink"} + ElementCodecDecodeAll = ElementRegister{0xaa, ElementTypeUint, "CodecDecodeAll"} + ElementTrackOverlay = ElementRegister{0x6fab, ElementTypeUint, "TrackOverlay"} + ElementCodecDelay = ElementRegister{0x56aa, ElementTypeUint, "CodecDelay"} + ElementSeekPreRoll = ElementRegister{0x56bb, ElementTypeUint, "SeekPreRoll"} + ElementTrackTranslate = ElementRegister{0x6624, ElementTypeMaster, "TrackTranslate"} + ElementTrackTranslateEditionUID = ElementRegister{0x66fc, ElementTypeUint, "TrackTranslateEditionUID"} + ElementTrackTranslateCodec = ElementRegister{0x66bf, ElementTypeUint, "TrackTranslateCodec"} + ElementTrackTranslateTrackID = ElementRegister{0x66a5, ElementTypeBinary, "TrackTranslateTrackID"} + ElementVideo = ElementRegister{0xe0, ElementTypeMaster, "Video"} + ElementFlagInterlaced = ElementRegister{0x9a, ElementTypeUint, "FlagInterlaced"} + ElementStereoMode = ElementRegister{0x53b8, ElementTypeUint, "StereoMode"} + ElementAlphaMode = ElementRegister{0x53c0, ElementTypeUint, "AlphaMode"} + ElementPixelWidth = ElementRegister{0xb0, ElementTypeUint, "PixelWidth"} + ElementPixelHeight = ElementRegister{0xba, ElementTypeUint, "PixelHeight"} + ElementPixelCropBottom = ElementRegister{0x54aa, ElementTypeUint, "PixelCropBottom"} + ElementPixelCropTop = ElementRegister{0x54bb, ElementTypeUint, "PixelCropTop"} + ElementPixelCropLeft = ElementRegister{0x54cc, ElementTypeUint, "PixelCropLeft"} + ElementPixelCropRight = ElementRegister{0x54dd, ElementTypeUint, "PixelCropRight"} + ElementDisplayWidth = ElementRegister{0x54b0, ElementTypeUint, "DisplayWidth"} + ElementDisplayHeight = ElementRegister{0x54ba, ElementTypeUint, "DisplayHeight"} + ElementDisplayUint = ElementRegister{0x54b2, ElementTypeUint, "DisplayUint"} + ElementAspectRatioType = ElementRegister{0x54b3, ElementTypeUint, "AspectRatioType"} + ElementColourSpace = ElementRegister{0x2eb524, ElementTypeBinary, "ColourSpace"} + ElementAudio = ElementRegister{0xe1, ElementTypeMaster, "Audio"} + ElementSamplingFrequency = ElementRegister{0xb5, ElementTypeFloat, "SamplingFrequency"} + ElementOutputSamplingFrequency = ElementRegister{0x78b5, ElementTypeFloat, "OutputSamplingFrequency"} + ElementChannels = ElementRegister{0x9f, ElementTypeUint, "Channels"} + ElementBitDepth = ElementRegister{0x6264, ElementTypeUint, "BitDepth"} + ElementTrackOperation = ElementRegister{0xe2, ElementTypeMaster, "TrackOperation"} + ElementTrackCombinePlanes = ElementRegister{0xe3, ElementTypeMaster, "TrackCombinePlanes"} + ElementTrackPlane = ElementRegister{0xe4, ElementTypeMaster, "TrackPlane"} + ElementTrackPlaneUID = ElementRegister{0xe5, ElementTypeUint, "TrackPlaneUID"} + ElementTrackPlaneType = ElementRegister{0xe6, ElementTypeUint, "TrackPlaneType"} + ElementTrackJoinBlocks = ElementRegister{0xe9, ElementTypeMaster, "TrackJoinBlocks"} + ElementTrackJoinUID = ElementRegister{0xed, ElementTypeUint, "TrackJoinUID"} + ElementContentEncodings = ElementRegister{0x6d80, ElementTypeMaster, "ContentEncodings"} + ElementContentEncoding = ElementRegister{0x6240, ElementTypeMaster, "ContentEncoding"} + ElementContentEncodingOrder = ElementRegister{0x5031, ElementTypeUint, "ContentEncodingOrder"} + ElementContentEncodingScope = ElementRegister{0x5032, ElementTypeUint, "ContentEncodingScope"} + ElementContentEncodingType = ElementRegister{0x5033, ElementTypeUint, "ContentEncodingType"} + ElementContentCompression = ElementRegister{0x5034, ElementTypeMaster, "ContentCompression"} + ElementContentCompAlgo = ElementRegister{0x4254, ElementTypeUint, "ContentCompAlgo"} + ElementContentCompSettings = ElementRegister{0x4255, ElementTypeBinary, "ContentCompSettings"} + ElementContentEncryption = ElementRegister{0x5035, ElementTypeMaster, "ContentEncryption"} + ElementContentEncAlgo = ElementRegister{0x47e1, ElementTypeUint, "ContentEncAlgo"} + ElementContentEncKeyID = ElementRegister{0x47e2, ElementTypeUint, "ContentEncKeyID"} + ElementContentSignature = ElementRegister{0x47e3, ElementTypeBinary, "ContentSignature"} + ElementContentSigKeyID = ElementRegister{0x47e4, ElementTypeBinary, "ContentSigKeyID"} + ElementContentSigAlgo = ElementRegister{0x47e5, ElementTypeUint, "ContentSigAlgo"} + ElementContentSigHashAlgo = ElementRegister{0x47e6, ElementTypeUint, "ContentSigHashAlgo"} + ElementCues = ElementRegister{0x1c53bb6b, ElementTypeMaster, "Cues"} + ElementCuePoint = ElementRegister{0xbb, ElementTypeMaster, "CuePoint"} + ElementCueTime = ElementRegister{0xb3, ElementTypeUint, "CueTime"} + ElementCueTrackPositions = ElementRegister{0xb7, ElementTypeMaster, "CueTrackPositions"} + ElementCueTrack = ElementRegister{0xf7, ElementTypeUint, "CueTrack"} + ElementCueClusterPosition = ElementRegister{0xf1, ElementTypeUint, "CueClusterPosition"} + ElementCueRelativePosition = ElementRegister{0xf0, ElementTypeUint, "CueRelativePosition"} + ElementCueDuration = ElementRegister{0xb2, ElementTypeUint, "CueDuration"} + ElementCueBlockNumber = ElementRegister{0x5378, ElementTypeUint, "CueBlockNumber"} + ElementCueCodecState = ElementRegister{0xea, ElementTypeUint, "CueCodecState"} + ElementCueReference = ElementRegister{0xdb, ElementTypeMaster, "CueReference"} + ElementCueRefTime = ElementRegister{0x96, ElementTypeUint, "CueRefTime"} + ElementAttachments = ElementRegister{0x1941a469, ElementTypeMaster, "Attachments"} + ElementAttachedFile = ElementRegister{0x61a7, ElementTypeMaster, "AttachedFile"} + ElementFileDescription = ElementRegister{0x467e, ElementTypeUnicode, "FileDescription"} + ElementFileName = ElementRegister{0x466e, ElementTypeUnicode, "FileName"} + ElementFileMimeType = ElementRegister{0x6460, ElementTypeString, "FileMimeType"} + ElementFileData = ElementRegister{0x465c, ElementTypeBinary, "FileData"} + ElementFileUID = ElementRegister{0x46ae, ElementTypeUint, "FileUID"} + ElementChapters = ElementRegister{0x1043a770, ElementTypeMaster, "Chapters"} + ElementEditionEntry = ElementRegister{0x45b9, ElementTypeMaster, "EditionEntry"} + ElementEditionUID = ElementRegister{0x45bc, ElementTypeUint, "EditionUID"} + ElementEditionFlagHidden = ElementRegister{0x45bd, ElementTypeUint, "EditionFlagHidden"} + ElementEditionFlagDefault = ElementRegister{0x45db, ElementTypeUint, "EditionFlagDefault"} + ElementEditionFlagOrdered = ElementRegister{0x45dd, ElementTypeUint, "EditionFlagOrdered"} + ElementChapterAtom = ElementRegister{0xb6, ElementTypeMaster, "ChapterAtom"} + ElementChapterUID = ElementRegister{0x73c4, ElementTypeUint, "ChapterUID"} + ElementChapterStringUID = ElementRegister{0x5654, ElementTypeUnicode, "ChapterStringUID"} + ElementChapterTimeStart = ElementRegister{0x91, ElementTypeUint, "ChapterTimeStart"} + ElementChapterTimeEnd = ElementRegister{0x92, ElementTypeUint, "ChapterTimeEnd"} + ElementChapterFlagHidden = ElementRegister{0x98, ElementTypeUint, "ChapterFlagHidden"} + ElementChapterFlagEnabled = ElementRegister{0x4598, ElementTypeUint, "ChapterFlagEnabled"} + ElementChapterSegmentUID = ElementRegister{0x6e67, ElementTypeBinary, "ChapterSegmentUID"} + ElementChapterSegmentEditionUID = ElementRegister{0x6ebc, ElementTypeUint, "ChapterSegmentEditionUID"} + ElementChapterPhysicalEquiv = ElementRegister{0x63c3, ElementTypeUint, "ChapterPhysicalEquiv"} + ElementChapterTrack = ElementRegister{0x8f, ElementTypeMaster, "ChapterTrack"} + ElementChapterTrackNumber = ElementRegister{0x89, ElementTypeUint, "ChapterTrackNumber"} + ElementChapterDisplay = ElementRegister{0x80, ElementTypeMaster, "ChapterDisplay"} + ElementChapString = ElementRegister{0x85, ElementTypeUnicode, "ChapString"} + ElementChapLanguage = ElementRegister{0x437c, ElementTypeString, "ChapLanguage"} + ElementChapCountry = ElementRegister{0x437e, ElementTypeString, "ChapCountry"} + ElementChapProcess = ElementRegister{0x6944, ElementTypeMaster, "ChapProcess"} + ElementChapProcessCodecID = ElementRegister{0x6955, ElementTypeUint, "ChapProcessCodecID"} + ElementChapProcessPrivate = ElementRegister{0x450d, ElementTypeBinary, "ChapProcessPrivate"} + ElementChapProcessCommand = ElementRegister{0x6911, ElementTypeMaster, "ChapProcessCommand"} + ElementChapProcessTime = ElementRegister{0x6922, ElementTypeUint, "ChapProcessTime"} + ElementChapProcessData = ElementRegister{0x6933, ElementTypeBinary, "ChapProcessData"} + + // TODO: Add tags +) + +// GetElementRegister returns the infos concerning the provided element ID +func GetElementRegister(id uint32) ElementRegister { + switch id { + case ElementEBML.ID: + return ElementEBML + case ElementEBMLVersion.ID: + return ElementEBMLVersion + case ElementEBMLReadVersion.ID: + return ElementEBMLReadVersion + case ElementEBMLMaxIDLength.ID: + return ElementEBMLMaxIDLength + case ElementEBMLMaxSizeLength.ID: + return ElementEBMLMaxSizeLength + case ElementDocType.ID: + return ElementDocType + case ElementDocTypeVersion.ID: + return ElementDocTypeVersion + case ElementDocTypeReadVersion.ID: + return ElementDocTypeReadVersion + case ElementVoid.ID: + return ElementVoid + case ElementCRC32.ID: + return ElementCRC32 + case ElementSegment.ID: + return ElementSegment + case ElementSeekHead.ID: + return ElementSeekHead + case ElementSeek.ID: + return ElementSeek + case ElementSeekID.ID: + return ElementSeekID + case ElementSeekPosition.ID: + return ElementSeekPosition + case ElementInfo.ID: + return ElementInfo + case ElementTimecodeScale.ID: + return ElementTimecodeScale + case ElementDuration.ID: + return ElementDuration + case ElementDateUTC.ID: + return ElementDateUTC + case ElementTitle.ID: + return ElementTitle + case ElementMuxingApp.ID: + return ElementMuxingApp + case ElementWritingApp.ID: + return ElementWritingApp + case ElementCluster.ID: + return ElementCluster + case ElementTimecode.ID: + return ElementTimecode + case ElementPrevSize.ID: + return ElementPrevSize + case ElementSimpleBlock.ID: + return ElementSimpleBlock + case ElementBlockGroup.ID: + return ElementBlockGroup + case ElementBlock.ID: + return ElementBlock + case ElementBlockAdditions.ID: + return ElementBlockAdditions + case ElementBlockMore.ID: + return ElementBlockMore + case ElementBlockAddID.ID: + return ElementBlockAddID + case ElementBlockAdditional.ID: + return ElementBlockAdditional + case ElementBlockDuration.ID: + return ElementBlockDuration + case ElementReferenceBlock.ID: + return ElementReferenceBlock + case ElementDiscardPadding.ID: + return ElementDiscardPadding + case ElementTracks.ID: + return ElementTracks + case ElementTrackEntry.ID: + return ElementTrackEntry + case ElementTrackNumber.ID: + return ElementTrackNumber + case ElementTrackUID.ID: + return ElementTrackUID + case ElementTrackType.ID: + return ElementTrackType + case ElementFlagEnabled.ID: + return ElementFlagEnabled + case ElementFlagDefault.ID: + return ElementFlagDefault + case ElementFlagForced.ID: + return ElementFlagForced + case ElementFlagLacing.ID: + return ElementFlagLacing + case ElementDefaultDuration.ID: + return ElementDefaultDuration + case ElementName.ID: + return ElementName + case ElementLanguage.ID: + return ElementLanguage + case ElementCodecID.ID: + return ElementCodecID + case ElementCodecPrivate.ID: + return ElementCodecPrivate + case ElementCodecName.ID: + return ElementCodecName + case ElementCodecDelay.ID: + return ElementCodecDelay + case ElementSeekPreRoll.ID: + return ElementSeekPreRoll + case ElementVideo.ID: + return ElementVideo + case ElementFlagInterlaced.ID: + return ElementFlagInterlaced + case ElementStereoMode.ID: + return ElementStereoMode + case ElementAlphaMode.ID: + return ElementAlphaMode + case ElementPixelWidth.ID: + return ElementPixelWidth + case ElementPixelHeight.ID: + return ElementPixelHeight + case ElementPixelCropBottom.ID: + return ElementPixelCropBottom + case ElementPixelCropTop.ID: + return ElementPixelCropTop + case ElementPixelCropLeft.ID: + return ElementPixelCropLeft + case ElementPixelCropRight.ID: + return ElementPixelCropRight + case ElementDisplayWidth.ID: + return ElementDisplayWidth + case ElementDisplayHeight.ID: + return ElementDisplayHeight + case ElementDisplayUint.ID: + return ElementDisplayUint + case ElementAspectRatioType.ID: + return ElementAspectRatioType + case ElementAudio.ID: + return ElementAudio + case ElementSamplingFrequency.ID: + return ElementSamplingFrequency + case ElementOutputSamplingFrequency.ID: + return ElementOutputSamplingFrequency + case ElementChannels.ID: + return ElementChannels + case ElementBitDepth.ID: + return ElementBitDepth + case ElementContentEncodings.ID: + return ElementContentEncodings + case ElementContentEncoding.ID: + return ElementContentEncoding + case ElementContentEncodingOrder.ID: + return ElementContentEncodingOrder + case ElementContentEncodingScope.ID: + return ElementContentEncodingScope + case ElementContentEncodingType.ID: + return ElementContentEncodingType + case ElementContentEncryption.ID: + return ElementContentEncryption + case ElementContentEncAlgo.ID: + return ElementContentEncAlgo + case ElementContentEncKeyID.ID: + return ElementContentEncKeyID + case ElementUnknown.ID: + return ElementUnknown + default: + return ElementUnknown + } +} diff --git a/format/mkv/mkvio/parser.go b/format/mkv/mkvio/parser.go new file mode 100644 index 0000000..234a963 --- /dev/null +++ b/format/mkv/mkvio/parser.go @@ -0,0 +1,214 @@ +package mkvio + +import ( + "errors" + "io" +) + +var ( + ErrParse = errors.New("Parse error") + ErrUnexpectedEOF = errors.New("Unexpected EOF") +) + +// InitDocument creates a MKV/WebM document containing the file data +// It does not do any parsing +func InitDocument(r io.Reader) *Document { + doc := new(Document) + doc.r = r + + return doc +} + +// ParseAll parses the entire MKV/WebM document +// When an EBML/WebM element is encountered, it calls the provided function +// and passes the newly parsed element +func (doc *Document) ParseAll(c func(Element)) error { + for { + el, err := doc.ParseElement() + if err != nil { + return err + } + + c(el) + } + + return nil +} + +func (doc *Document) GetVideoCodec() (*Element, error) { + for { + + el, err := doc.ParseElement() + + if err != nil { + return nil, err + } + + if el.ElementRegister.ID == ElementCodecPrivate.ID { + return &el, nil + } + + } + + return nil, errors.New("not found") +} + +// ParseElement parses an EBML element starting at the document's current cursor position. +// Because of its nature, it does not set the elements's parent or level. +func (doc *Document) ParseElement() (Element, error) { + var el Element + + id, err := doc.GetElementID(&el) + if err != nil { + return el, err + } + + size, err := doc.GetElementSize(&el) + if err != nil { + return el, err + } + + reg := GetElementRegister(id) + el.ID = reg.ID + el.Type = reg.Type + el.Name = reg.Name + el.Size = size + + if el.Type != ElementTypeMaster { + d, err := doc.GetElementContent(&el) + if err != nil { + return el, err + } + + el.Content = d + } + + return el, nil +} + +// GetElementID tries to parse the next element's id, +// starting from the document's current cursor position. +func (doc *Document) GetElementID(el *Element) (uint32, error) { + b := make([]byte, 1) + + _, err := io.ReadFull(doc.r, b) + if err != nil { + return 0, err + } + + if ((b[0] & 0x80) >> 7) == 1 { // Class A ID (on 1 byte) + el.Bytes = append(el.Bytes, b[0]) + return uint32(b[0]), nil + } + if ((b[0] & 0x40) >> 6) == 1 { // Class B ID (on 2 byte) + bb := make([]byte, 2) + copy(bb, b) + + _, err = io.ReadFull(doc.r, bb[1:]) + if err != nil { + return 0, err + } + + el.Bytes = append(el.Bytes, bb...) + return uint32(pack(2, bb)), nil + } + if ((b[0] & 0x20) >> 5) == 1 { // Class C ID (on 3 bytes) + bb := make([]byte, 3) + copy(bb, b) + + _, err = io.ReadFull(doc.r, bb[1:]) + if err != nil { + return 0, err + } + + el.Bytes = append(el.Bytes, bb...) + return uint32(pack(3, bb)), nil + } + if ((b[0] & 0x10) >> 4) == 1 { // Class D ID (on 4 bytes) + bb := make([]byte, 4) + copy(bb, b) + + _, err = io.ReadFull(doc.r, bb[1:]) + if err != nil { + return 0, err + } + + el.Bytes = append(el.Bytes, bb...) + return uint32(pack(4, bb)), nil + } + + return 0, ErrParse +} + +// GetElementSize tries to parse the next element's size, +// starting from the document's current cursor position. +func (doc *Document) GetElementSize(el *Element) (uint64, error) { + b := make([]byte, 1) + + _, err := io.ReadFull(doc.r, b) + if err != nil { + return 0, err + } + + var mask byte + var length uint64 + + if b[0] >= 0x80 { + length = 1 + mask = 0x7f + } else if b[0] >= 0x40 { + length = 2 + mask = 0x3f + } else if b[0] >= 0x20 { + length = 3 + mask = 0x1f + } else if b[0] >= 0x10 { + length = 4 + mask = 0x0f + } else if b[0] >= 0x08 { + length = 5 + mask = 0x07 + } else if b[0] >= 0x04 { + length = 6 + mask = 0x03 + } else if b[0] >= 0x02 { + length = 7 + mask = 0x01 + } else if b[0] >= 0x01 { + length = 8 + mask = 0x00 + } else { + return 0, ErrParse + } + + bb := make([]byte, length) + bb[0] = b[0] + + if length > 1 { + _, err = io.ReadFull(doc.r, bb[1:]) + if err != nil { + return 0, err + } + } + + el.Bytes = append(el.Bytes, bb...) + + bb[0] &= mask + v := pack(int(length), bb) + + return v, nil +} + +// GetElementContent returns the element's data (if any) +// Data is present if the element's type is not Master +func (doc *Document) GetElementContent(el *Element) ([]byte, error) { + buf := make([]byte, el.Size) + + _, err := io.ReadFull(doc.r, buf) + if err != nil { + return nil, err + } + + el.Bytes = append(el.Bytes, buf...) + return buf, nil +} diff --git a/format/mkv/mkvio/utils.go b/format/mkv/mkvio/utils.go new file mode 100644 index 0000000..406af35 --- /dev/null +++ b/format/mkv/mkvio/utils.go @@ -0,0 +1,23 @@ +package mkvio + +func pack(n int, b []byte) uint64 { + var v uint64 + var k uint64 = (uint64(n) - 1) * 8 + + for i := 0; i < n; i++ { + v |= uint64(b[i]) << k + k -= 8 + } + + return v +} + +func unpack(n int, v uint64) []byte { + var b []byte + + for i := uint(n); i > 0; i-- { + b = append(b, byte(v>>(8*i))&0xff) + } + + return b +} diff --git a/format/mkv/stream.go b/format/mkv/stream.go new file mode 100644 index 0000000..b5f9da7 --- /dev/null +++ b/format/mkv/stream.go @@ -0,0 +1,24 @@ +package mkv + +import ( + "time" + + "github.com/deepch/vdk/av" +) + +type Stream struct { + av.CodecData + + demuxer *Demuxer + + pid uint16 + streamId uint8 + streamType uint8 + + idx int + + iskeyframe bool + pts, dts time.Duration + data []byte + datalen int +} diff --git a/go.mod b/go.mod index b7282fd..8671a87 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/pion/interceptor v0.1.6 github.com/pion/webrtc/v2 v2.2.26 github.com/pion/webrtc/v3 v3.1.17 + github.com/quadrifoglio/go-mkv v0.0.0-20180620161916-e7a1fc70199c // indirect ) diff --git a/go.sum b/go.sum index 6938a86..0105c6c 100644 --- a/go.sum +++ b/go.sum @@ -154,6 +154,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quadrifoglio/go-mkv v0.0.0-20180620161916-e7a1fc70199c h1:Nu1I2o5Lk5nvn2RChPOJ1MzTXKZoHiy665a5aANHssQ= +github.com/quadrifoglio/go-mkv v0.0.0-20180620161916-e7a1fc70199c/go.mod h1:Mtv+LuzkLR5r7fwBlvqI/N/5d4nOaH/7fpgQFXwxnFk= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=