mirror of
https://github.com/datarhei/core.git
synced 2025-10-10 10:20:06 +08:00
Add v16.7.2
This commit is contained in:
21
vendor/github.com/datarhei/joy4/LICENSE
generated
vendored
Normal file
21
vendor/github.com/datarhei/joy4/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
316
vendor/github.com/datarhei/joy4/av/av.go
generated
vendored
Normal file
316
vendor/github.com/datarhei/joy4/av/av.go
generated
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
|
||||
// Package av defines basic interfaces and data structures of container demux/mux and audio encode/decode.
|
||||
package av
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Audio sample format.
|
||||
type SampleFormat uint8
|
||||
|
||||
const (
|
||||
U8 = SampleFormat(iota + 1) // 8-bit unsigned integer
|
||||
S16 // signed 16-bit integer
|
||||
S32 // signed 32-bit integer
|
||||
FLT // 32-bit float
|
||||
DBL // 64-bit float
|
||||
U8P // 8-bit unsigned integer in planar
|
||||
S16P // signed 16-bit integer in planar
|
||||
S32P // signed 32-bit integer in planar
|
||||
FLTP // 32-bit float in planar
|
||||
DBLP // 64-bit float in planar
|
||||
U32 // unsigned 32-bit integer
|
||||
)
|
||||
|
||||
func (self SampleFormat) BytesPerSample() int {
|
||||
switch self {
|
||||
case U8, U8P:
|
||||
return 1
|
||||
case S16, S16P:
|
||||
return 2
|
||||
case FLT, FLTP, S32, S32P, U32:
|
||||
return 4
|
||||
case DBL, DBLP:
|
||||
return 8
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (self SampleFormat) String() string {
|
||||
switch self {
|
||||
case U8:
|
||||
return "U8"
|
||||
case S16:
|
||||
return "S16"
|
||||
case S32:
|
||||
return "S32"
|
||||
case FLT:
|
||||
return "FLT"
|
||||
case DBL:
|
||||
return "DBL"
|
||||
case U8P:
|
||||
return "U8P"
|
||||
case S16P:
|
||||
return "S16P"
|
||||
case FLTP:
|
||||
return "FLTP"
|
||||
case DBLP:
|
||||
return "DBLP"
|
||||
case U32:
|
||||
return "U32"
|
||||
default:
|
||||
return "?"
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this sample format is in planar.
|
||||
func (self SampleFormat) IsPlanar() bool {
|
||||
switch self {
|
||||
case S16P, S32P, FLTP, DBLP:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Audio channel layout.
|
||||
type ChannelLayout uint16
|
||||
|
||||
func (self ChannelLayout) String() string {
|
||||
return fmt.Sprintf("%dch", self.Count())
|
||||
}
|
||||
|
||||
const (
|
||||
CH_FRONT_CENTER = ChannelLayout(1 << iota)
|
||||
CH_FRONT_LEFT
|
||||
CH_FRONT_RIGHT
|
||||
CH_BACK_CENTER
|
||||
CH_BACK_LEFT
|
||||
CH_BACK_RIGHT
|
||||
CH_SIDE_LEFT
|
||||
CH_SIDE_RIGHT
|
||||
CH_LOW_FREQ
|
||||
CH_NR
|
||||
|
||||
CH_MONO = ChannelLayout(CH_FRONT_CENTER)
|
||||
CH_STEREO = ChannelLayout(CH_FRONT_LEFT | CH_FRONT_RIGHT)
|
||||
CH_2_1 = ChannelLayout(CH_STEREO | CH_BACK_CENTER)
|
||||
CH_2POINT1 = ChannelLayout(CH_STEREO | CH_LOW_FREQ)
|
||||
CH_SURROUND = ChannelLayout(CH_STEREO | CH_FRONT_CENTER)
|
||||
CH_3POINT1 = ChannelLayout(CH_SURROUND | CH_LOW_FREQ)
|
||||
// TODO: add all channel_layout in ffmpeg
|
||||
)
|
||||
|
||||
func (self ChannelLayout) Count() (n int) {
|
||||
for self != 0 {
|
||||
n++
|
||||
self = (self - 1) & self
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Video/Audio codec type. can be H264/AAC/SPEEX/...
|
||||
type CodecType uint32
|
||||
|
||||
var (
|
||||
H264 = MakeVideoCodecType(avCodecTypeMagic + 1)
|
||||
AAC = MakeAudioCodecType(avCodecTypeMagic + 1)
|
||||
PCM_MULAW = MakeAudioCodecType(avCodecTypeMagic + 2)
|
||||
PCM_ALAW = MakeAudioCodecType(avCodecTypeMagic + 3)
|
||||
SPEEX = MakeAudioCodecType(avCodecTypeMagic + 4)
|
||||
NELLYMOSER = MakeAudioCodecType(avCodecTypeMagic + 5)
|
||||
)
|
||||
|
||||
const codecTypeAudioBit = 0x1
|
||||
const codecTypeOtherBits = 1
|
||||
|
||||
func (self CodecType) String() string {
|
||||
switch self {
|
||||
case H264:
|
||||
return "H264"
|
||||
case AAC:
|
||||
return "AAC"
|
||||
case PCM_MULAW:
|
||||
return "PCM_MULAW"
|
||||
case PCM_ALAW:
|
||||
return "PCM_ALAW"
|
||||
case SPEEX:
|
||||
return "SPEEX"
|
||||
case NELLYMOSER:
|
||||
return "NELLYMOSER"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self CodecType) IsAudio() bool {
|
||||
return self&codecTypeAudioBit != 0
|
||||
}
|
||||
|
||||
func (self CodecType) IsVideo() bool {
|
||||
return self&codecTypeAudioBit == 0
|
||||
}
|
||||
|
||||
// Make a new audio codec type.
|
||||
func MakeAudioCodecType(base uint32) (c CodecType) {
|
||||
c = CodecType(base)<<codecTypeOtherBits | CodecType(codecTypeAudioBit)
|
||||
return
|
||||
}
|
||||
|
||||
// Make a new video codec type.
|
||||
func MakeVideoCodecType(base uint32) (c CodecType) {
|
||||
c = CodecType(base) << codecTypeOtherBits
|
||||
return
|
||||
}
|
||||
|
||||
const avCodecTypeMagic = 233333
|
||||
|
||||
// CodecData is some important bytes for initializing audio/video decoder,
|
||||
// can be converted to VideoCodecData or AudioCodecData using:
|
||||
//
|
||||
// codecdata.(AudioCodecData) or codecdata.(VideoCodecData)
|
||||
//
|
||||
// for H264, CodecData is AVCDecoderConfigure bytes, includes SPS/PPS.
|
||||
type CodecData interface {
|
||||
Type() CodecType // Video/Audio codec type
|
||||
}
|
||||
|
||||
type VideoCodecData interface {
|
||||
CodecData
|
||||
Width() int // Video width
|
||||
Height() int // Video height
|
||||
}
|
||||
|
||||
type AudioCodecData interface {
|
||||
CodecData
|
||||
SampleFormat() SampleFormat // audio sample format
|
||||
SampleRate() int // audio sample rate
|
||||
ChannelLayout() ChannelLayout // audio channel layout
|
||||
PacketDuration([]byte) (time.Duration, error) // get audio compressed packet duration
|
||||
}
|
||||
|
||||
type PacketWriter interface {
|
||||
WritePacket(Packet) error
|
||||
}
|
||||
|
||||
type PacketReader interface {
|
||||
ReadPacket() (Packet,error)
|
||||
}
|
||||
|
||||
// Muxer describes the steps of writing compressed audio/video packets into container formats like MP4/FLV/MPEG-TS.
|
||||
//
|
||||
// Container formats, rtmp.Conn, and transcode.Muxer implements Muxer interface.
|
||||
type Muxer interface {
|
||||
WriteHeader([]CodecData) error // write the file header
|
||||
PacketWriter // write compressed audio/video packets
|
||||
WriteTrailer() error // finish writing file, this func can be called only once
|
||||
}
|
||||
|
||||
// Muxer with Close() method
|
||||
type MuxCloser interface {
|
||||
Muxer
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Demuxer can read compressed audio/video packets from container formats like MP4/FLV/MPEG-TS.
|
||||
type Demuxer interface {
|
||||
PacketReader // read compressed audio/video packets
|
||||
Streams() ([]CodecData, error) // reads the file header, contains video/audio meta infomations
|
||||
}
|
||||
|
||||
// Demuxer with Close() method
|
||||
type DemuxCloser interface {
|
||||
Demuxer
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Packet stores compressed audio/video data.
|
||||
type Packet struct {
|
||||
IsKeyFrame bool // video packet is key frame
|
||||
Idx int8 // stream index in container format
|
||||
CompositionTime time.Duration // packet presentation time minus decode time for H264 B-Frame
|
||||
Time time.Duration // packet decode time
|
||||
Data []byte // packet data
|
||||
}
|
||||
|
||||
// Raw audio frame.
|
||||
type AudioFrame struct {
|
||||
SampleFormat SampleFormat // audio sample format, e.g: S16,FLTP,...
|
||||
ChannelLayout ChannelLayout // audio channel layout, e.g: CH_MONO,CH_STEREO,...
|
||||
SampleCount int // sample count in this frame
|
||||
SampleRate int // sample rate
|
||||
Data [][]byte // data array for planar format len(Data) > 1
|
||||
}
|
||||
|
||||
func (self AudioFrame) Duration() time.Duration {
|
||||
return time.Second * time.Duration(self.SampleCount) / time.Duration(self.SampleRate)
|
||||
}
|
||||
|
||||
// Check this audio frame has same format as other audio frame.
|
||||
func (self AudioFrame) HasSameFormat(other AudioFrame) bool {
|
||||
if self.SampleRate != other.SampleRate {
|
||||
return false
|
||||
}
|
||||
if self.ChannelLayout != other.ChannelLayout {
|
||||
return false
|
||||
}
|
||||
if self.SampleFormat != other.SampleFormat {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Split sample audio sample from this frame.
|
||||
func (self AudioFrame) Slice(start int, end int) (out AudioFrame) {
|
||||
if start > end {
|
||||
panic(fmt.Sprintf("av: AudioFrame split failed start=%d end=%d invalid", start, end))
|
||||
}
|
||||
out = self
|
||||
out.Data = append([][]byte(nil), out.Data...)
|
||||
out.SampleCount = end - start
|
||||
size := self.SampleFormat.BytesPerSample()
|
||||
for i := range out.Data {
|
||||
out.Data[i] = out.Data[i][start*size : end*size]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Concat two audio frames.
|
||||
func (self AudioFrame) Concat(in AudioFrame) (out AudioFrame) {
|
||||
out = self
|
||||
out.Data = append([][]byte(nil), out.Data...)
|
||||
out.SampleCount += in.SampleCount
|
||||
for i := range out.Data {
|
||||
out.Data[i] = append(out.Data[i], in.Data[i]...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AudioEncoder can encode raw audio frame into compressed audio packets.
|
||||
// cgo/ffmpeg inplements AudioEncoder, using ffmpeg.NewAudioEncoder to create it.
|
||||
type AudioEncoder interface {
|
||||
CodecData() (AudioCodecData, error) // encoder's codec data can put into container
|
||||
Encode(AudioFrame) ([][]byte, error) // encode raw audio frame into compressed pakcet(s)
|
||||
Close() // close encoder, free cgo contexts
|
||||
SetSampleRate(int) (error) // set encoder sample rate
|
||||
SetChannelLayout(ChannelLayout) (error) // set encoder channel layout
|
||||
SetSampleFormat(SampleFormat) (error) // set encoder sample format
|
||||
SetBitrate(int) (error) // set encoder bitrate
|
||||
SetOption(string,interface{}) (error) // encoder setopt, in ffmpeg is av_opt_set_dict()
|
||||
GetOption(string,interface{}) (error) // encoder getopt
|
||||
}
|
||||
|
||||
// AudioDecoder can decode compressed audio packets into raw audio frame.
|
||||
// use ffmpeg.NewAudioDecoder to create it.
|
||||
type AudioDecoder interface {
|
||||
Decode([]byte) (bool, AudioFrame, error) // decode one compressed audio packet
|
||||
Close() // close decode, free cgo contexts
|
||||
}
|
||||
|
||||
// AudioResampler can convert raw audio frames in different sample rate/format/channel layout.
|
||||
type AudioResampler interface {
|
||||
Resample(AudioFrame) (AudioFrame, error) // convert raw audio frames
|
||||
}
|
||||
|
311
vendor/github.com/datarhei/joy4/av/avutil/avutil.go
generated
vendored
Normal file
311
vendor/github.com/datarhei/joy4/av/avutil/avutil.go
generated
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
package avutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"fmt"
|
||||
"bytes"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
type HandlerDemuxer struct {
|
||||
av.Demuxer
|
||||
r io.ReadCloser
|
||||
}
|
||||
|
||||
func (self *HandlerDemuxer) Close() error {
|
||||
return self.r.Close()
|
||||
}
|
||||
|
||||
type HandlerMuxer struct {
|
||||
av.Muxer
|
||||
w io.WriteCloser
|
||||
stage int
|
||||
}
|
||||
|
||||
func (self *HandlerMuxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||
if self.stage == 0 {
|
||||
if err = self.Muxer.WriteHeader(streams); err != nil {
|
||||
return
|
||||
}
|
||||
self.stage++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *HandlerMuxer) WriteTrailer() (err error) {
|
||||
if self.stage == 1 {
|
||||
self.stage++
|
||||
if err = self.Muxer.WriteTrailer(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *HandlerMuxer) Close() (err error) {
|
||||
if err = self.WriteTrailer(); err != nil {
|
||||
return
|
||||
}
|
||||
return self.w.Close()
|
||||
}
|
||||
|
||||
type RegisterHandler struct {
|
||||
Ext string
|
||||
ReaderDemuxer func(io.Reader)av.Demuxer
|
||||
WriterMuxer func(io.Writer)av.Muxer
|
||||
UrlMuxer func(string)(bool,av.MuxCloser,error)
|
||||
UrlDemuxer func(string)(bool,av.DemuxCloser,error)
|
||||
UrlReader func(string)(bool,io.ReadCloser,error)
|
||||
Probe func([]byte)bool
|
||||
AudioEncoder func(av.CodecType)(av.AudioEncoder,error)
|
||||
AudioDecoder func(av.AudioCodecData)(av.AudioDecoder,error)
|
||||
ServerDemuxer func(string)(bool,av.DemuxCloser,error)
|
||||
ServerMuxer func(string)(bool,av.MuxCloser,error)
|
||||
CodecTypes []av.CodecType
|
||||
}
|
||||
|
||||
type Handlers struct {
|
||||
handlers []RegisterHandler
|
||||
}
|
||||
|
||||
func (self *Handlers) Add(fn func(*RegisterHandler)) {
|
||||
handler := &RegisterHandler{}
|
||||
fn(handler)
|
||||
self.handlers = append(self.handlers, *handler)
|
||||
}
|
||||
|
||||
func (self *Handlers) openUrl(u *url.URL, uri string) (r io.ReadCloser, err error) {
|
||||
if u != nil && u.Scheme != "" {
|
||||
for _, handler := range self.handlers {
|
||||
if handler.UrlReader != nil {
|
||||
var ok bool
|
||||
if ok, r, err = handler.UrlReader(uri); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("avutil: openUrl %s failed", uri)
|
||||
} else {
|
||||
r, err = os.Open(uri)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Handlers) createUrl(u *url.URL, uri string) (w io.WriteCloser, err error) {
|
||||
w, err = os.Create(uri)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Handlers) NewAudioEncoder(typ av.CodecType) (enc av.AudioEncoder, err error) {
|
||||
for _, handler := range self.handlers {
|
||||
if handler.AudioEncoder != nil {
|
||||
if enc, _ = handler.AudioEncoder(typ); enc != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("avutil: encoder", typ, "not found")
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Handlers) NewAudioDecoder(codec av.AudioCodecData) (dec av.AudioDecoder, err error) {
|
||||
for _, handler := range self.handlers {
|
||||
if handler.AudioDecoder != nil {
|
||||
if dec, _ = handler.AudioDecoder(codec); dec != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("avutil: decoder", codec.Type(), "not found")
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Handlers) Open(uri string) (demuxer av.DemuxCloser, err error) {
|
||||
listen := false
|
||||
if strings.HasPrefix(uri, "listen:") {
|
||||
uri = uri[len("listen:"):]
|
||||
listen = true
|
||||
}
|
||||
|
||||
for _, handler := range self.handlers {
|
||||
if listen {
|
||||
if handler.ServerDemuxer != nil {
|
||||
var ok bool
|
||||
if ok, demuxer, err = handler.ServerDemuxer(uri); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if handler.UrlDemuxer != nil {
|
||||
var ok bool
|
||||
if ok, demuxer, err = handler.UrlDemuxer(uri); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var r io.ReadCloser
|
||||
var ext string
|
||||
var u *url.URL
|
||||
if u, _ = url.Parse(uri); u != nil && u.Scheme != "" {
|
||||
ext = path.Ext(u.Path)
|
||||
} else {
|
||||
ext = path.Ext(uri)
|
||||
}
|
||||
|
||||
if ext != "" {
|
||||
for _, handler := range self.handlers {
|
||||
if handler.Ext == ext {
|
||||
if handler.ReaderDemuxer != nil {
|
||||
if r, err = self.openUrl(u, uri); err != nil {
|
||||
return
|
||||
}
|
||||
demuxer = &HandlerDemuxer{
|
||||
Demuxer: handler.ReaderDemuxer(r),
|
||||
r: r,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var probebuf [1024]byte
|
||||
if r, err = self.openUrl(u, uri); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = io.ReadFull(r, probebuf[:]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, handler := range self.handlers {
|
||||
if handler.Probe != nil && handler.Probe(probebuf[:]) && handler.ReaderDemuxer != nil {
|
||||
var _r io.Reader
|
||||
if rs, ok := r.(io.ReadSeeker); ok {
|
||||
if _, err = rs.Seek(0, 0); err != nil {
|
||||
return
|
||||
}
|
||||
_r = rs
|
||||
} else {
|
||||
_r = io.MultiReader(bytes.NewReader(probebuf[:]), r)
|
||||
}
|
||||
demuxer = &HandlerDemuxer{
|
||||
Demuxer: handler.ReaderDemuxer(_r),
|
||||
r: r,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.Close()
|
||||
err = fmt.Errorf("avutil: open %s failed", uri)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Handlers) Create(uri string) (muxer av.MuxCloser, err error) {
|
||||
_, muxer, err = self.FindCreate(uri)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Handlers) FindCreate(uri string) (handler RegisterHandler, muxer av.MuxCloser, err error) {
|
||||
listen := false
|
||||
if strings.HasPrefix(uri, "listen:") {
|
||||
uri = uri[len("listen:"):]
|
||||
listen = true
|
||||
}
|
||||
|
||||
for _, handler = range self.handlers {
|
||||
if listen {
|
||||
if handler.ServerMuxer != nil {
|
||||
var ok bool
|
||||
if ok, muxer, err = handler.ServerMuxer(uri); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if handler.UrlMuxer != nil {
|
||||
var ok bool
|
||||
if ok, muxer, err = handler.UrlMuxer(uri); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ext string
|
||||
var u *url.URL
|
||||
if u, _ = url.Parse(uri); u != nil && u.Scheme != "" {
|
||||
ext = path.Ext(u.Path)
|
||||
} else {
|
||||
ext = path.Ext(uri)
|
||||
}
|
||||
|
||||
if ext != "" {
|
||||
for _, handler = range self.handlers {
|
||||
if handler.Ext == ext && handler.WriterMuxer != nil {
|
||||
var w io.WriteCloser
|
||||
if w, err = self.createUrl(u, uri); err != nil {
|
||||
return
|
||||
}
|
||||
muxer = &HandlerMuxer{
|
||||
Muxer: handler.WriterMuxer(w),
|
||||
w: w,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = fmt.Errorf("avutil: create muxer %s failed", uri)
|
||||
return
|
||||
}
|
||||
|
||||
var DefaultHandlers = &Handlers{}
|
||||
|
||||
func Open(url string) (demuxer av.DemuxCloser, err error) {
|
||||
return DefaultHandlers.Open(url)
|
||||
}
|
||||
|
||||
func Create(url string) (muxer av.MuxCloser, err error) {
|
||||
return DefaultHandlers.Create(url)
|
||||
}
|
||||
|
||||
func CopyPackets(dst av.PacketWriter, src av.PacketReader) (err error) {
|
||||
for {
|
||||
var pkt av.Packet
|
||||
if pkt, err = src.ReadPacket(); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
if err = dst.WritePacket(pkt); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CopyFile(dst av.Muxer, src av.Demuxer) (err error) {
|
||||
var streams []av.CodecData
|
||||
if streams, err = src.Streams(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = dst.WriteHeader(streams); err != nil {
|
||||
return
|
||||
}
|
||||
if err = CopyPackets(dst, src); err != nil {
|
||||
if err != io.EOF {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err = dst.WriteTrailer(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
73
vendor/github.com/datarhei/joy4/av/pktque/buf.go
generated
vendored
Normal file
73
vendor/github.com/datarhei/joy4/av/pktque/buf.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package pktque
|
||||
|
||||
import (
|
||||
"github.com/datarhei/joy4/av"
|
||||
)
|
||||
|
||||
type Buf struct {
|
||||
Head, Tail BufPos
|
||||
pkts []av.Packet
|
||||
Size int
|
||||
Count int
|
||||
}
|
||||
|
||||
func NewBuf() *Buf {
|
||||
return &Buf{
|
||||
pkts: make([]av.Packet, 64),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Buf) Pop() av.Packet {
|
||||
if self.Count == 0 {
|
||||
panic("pktque.Buf: Pop() when count == 0")
|
||||
}
|
||||
|
||||
i := int(self.Head) & (len(self.pkts) - 1)
|
||||
pkt := self.pkts[i]
|
||||
self.pkts[i] = av.Packet{}
|
||||
self.Size -= len(pkt.Data)
|
||||
self.Head++
|
||||
self.Count--
|
||||
|
||||
return pkt
|
||||
}
|
||||
|
||||
func (self *Buf) grow() {
|
||||
newpkts := make([]av.Packet, len(self.pkts)*2)
|
||||
for i := self.Head; i.LT(self.Tail); i++ {
|
||||
newpkts[int(i)&(len(newpkts)-1)] = self.pkts[int(i)&(len(self.pkts)-1)]
|
||||
}
|
||||
self.pkts = newpkts
|
||||
}
|
||||
|
||||
func (self *Buf) Push(pkt av.Packet) {
|
||||
if self.Count == len(self.pkts) {
|
||||
self.grow()
|
||||
}
|
||||
self.pkts[int(self.Tail)&(len(self.pkts)-1)] = pkt
|
||||
self.Tail++
|
||||
self.Count++
|
||||
self.Size += len(pkt.Data)
|
||||
}
|
||||
|
||||
func (self *Buf) Get(pos BufPos) av.Packet {
|
||||
return self.pkts[int(pos)&(len(self.pkts)-1)]
|
||||
}
|
||||
|
||||
func (self *Buf) IsValidPos(pos BufPos) bool {
|
||||
return pos.GE(self.Head) && pos.LT(self.Tail)
|
||||
}
|
||||
|
||||
type BufPos int
|
||||
|
||||
func (self BufPos) LT(pos BufPos) bool {
|
||||
return self-pos < 0
|
||||
}
|
||||
|
||||
func (self BufPos) GE(pos BufPos) bool {
|
||||
return self-pos >= 0
|
||||
}
|
||||
|
||||
func (self BufPos) GT(pos BufPos) bool {
|
||||
return self-pos > 0
|
||||
}
|
191
vendor/github.com/datarhei/joy4/av/pktque/filters.go
generated
vendored
Normal file
191
vendor/github.com/datarhei/joy4/av/pktque/filters.go
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
|
||||
// Package pktque provides packet Filter interface and structures used by other components.
|
||||
package pktque
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/datarhei/joy4/av"
|
||||
)
|
||||
|
||||
type Filter interface {
|
||||
// Change packet time or drop packet
|
||||
ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error)
|
||||
}
|
||||
|
||||
// Combine multiple Filters into one, ModifyPacket will be called in order.
|
||||
type Filters []Filter
|
||||
|
||||
func (self Filters) ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) {
|
||||
for _, filter := range self {
|
||||
if drop, err = filter.ModifyPacket(pkt, streams, videoidx, audioidx); err != nil {
|
||||
return
|
||||
}
|
||||
if drop {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Wrap origin Demuxer and Filter into a new Demuxer, when read this Demuxer filters will be called.
|
||||
type FilterDemuxer struct {
|
||||
av.Demuxer
|
||||
Filter Filter
|
||||
streams []av.CodecData
|
||||
videoidx int
|
||||
audioidx int
|
||||
}
|
||||
|
||||
func (self FilterDemuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||
if self.streams == nil {
|
||||
if self.streams, err = self.Demuxer.Streams(); err != nil {
|
||||
return
|
||||
}
|
||||
for i, stream := range self.streams {
|
||||
if stream.Type().IsVideo() {
|
||||
self.videoidx = i
|
||||
} else if stream.Type().IsAudio() {
|
||||
self.audioidx = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
if pkt, err = self.Demuxer.ReadPacket(); err != nil {
|
||||
return
|
||||
}
|
||||
var drop bool
|
||||
if drop, err = self.Filter.ModifyPacket(&pkt, self.streams, self.videoidx, self.audioidx); err != nil {
|
||||
return
|
||||
}
|
||||
if !drop {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Drop packets until first video key frame arrived.
|
||||
type WaitKeyFrame struct {
|
||||
ok bool
|
||||
}
|
||||
|
||||
func (self *WaitKeyFrame) ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) {
|
||||
if !self.ok && pkt.Idx == int8(videoidx) && pkt.IsKeyFrame {
|
||||
self.ok = true
|
||||
}
|
||||
drop = !self.ok
|
||||
return
|
||||
}
|
||||
|
||||
// Fix incorrect packet timestamps.
|
||||
type FixTime struct {
|
||||
zerobase time.Duration
|
||||
incrbase time.Duration
|
||||
lasttime time.Duration
|
||||
StartFromZero bool // make timestamp start from zero
|
||||
MakeIncrement bool // force timestamp increment
|
||||
}
|
||||
|
||||
func (self *FixTime) ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) {
|
||||
if self.StartFromZero {
|
||||
if self.zerobase == 0 {
|
||||
self.zerobase = pkt.Time
|
||||
}
|
||||
pkt.Time -= self.zerobase
|
||||
}
|
||||
|
||||
if self.MakeIncrement {
|
||||
pkt.Time -= self.incrbase
|
||||
if self.lasttime == 0 {
|
||||
self.lasttime = pkt.Time
|
||||
}
|
||||
if pkt.Time < self.lasttime || pkt.Time > self.lasttime+time.Millisecond*500 {
|
||||
self.incrbase += pkt.Time - self.lasttime
|
||||
pkt.Time = self.lasttime
|
||||
}
|
||||
self.lasttime = pkt.Time
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Drop incorrect packets to make A/V sync.
|
||||
type AVSync struct {
|
||||
MaxTimeDiff time.Duration
|
||||
time []time.Duration
|
||||
}
|
||||
|
||||
func (self *AVSync) ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) {
|
||||
if self.time == nil {
|
||||
self.time = make([]time.Duration, len(streams))
|
||||
if self.MaxTimeDiff == 0 {
|
||||
self.MaxTimeDiff = time.Millisecond*500
|
||||
}
|
||||
}
|
||||
|
||||
start, end, correctable, correcttime := self.check(int(pkt.Idx))
|
||||
if pkt.Time >= start && pkt.Time < end {
|
||||
self.time[pkt.Idx] = pkt.Time
|
||||
} else {
|
||||
if correctable {
|
||||
pkt.Time = correcttime
|
||||
for i := range self.time {
|
||||
self.time[i] = correcttime
|
||||
}
|
||||
} else {
|
||||
drop = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *AVSync) check(i int) (start time.Duration, end time.Duration, correctable bool, correcttime time.Duration) {
|
||||
minidx := -1
|
||||
maxidx := -1
|
||||
for j := range self.time {
|
||||
if minidx == -1 || self.time[j] < self.time[minidx] {
|
||||
minidx = j
|
||||
}
|
||||
if maxidx == -1 || self.time[j] > self.time[maxidx] {
|
||||
maxidx = j
|
||||
}
|
||||
}
|
||||
allthesame := self.time[minidx] == self.time[maxidx]
|
||||
|
||||
if i == maxidx {
|
||||
if allthesame {
|
||||
correctable = true
|
||||
} else {
|
||||
correctable = false
|
||||
}
|
||||
} else {
|
||||
correctable = true
|
||||
}
|
||||
|
||||
start = self.time[minidx]
|
||||
end = start + self.MaxTimeDiff
|
||||
correcttime = start + time.Millisecond*40
|
||||
return
|
||||
}
|
||||
|
||||
// Make packets reading speed as same as walltime, effect like ffmpeg -re option.
|
||||
type Walltime struct {
|
||||
firsttime time.Time
|
||||
}
|
||||
|
||||
func (self *Walltime) ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) {
|
||||
if pkt.Idx == 0 {
|
||||
if self.firsttime.IsZero() {
|
||||
self.firsttime = time.Now()
|
||||
}
|
||||
pkttime := self.firsttime.Add(pkt.Time)
|
||||
delta := pkttime.Sub(time.Now())
|
||||
if delta > 0 {
|
||||
time.Sleep(delta)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
61
vendor/github.com/datarhei/joy4/av/pktque/timeline.go
generated
vendored
Normal file
61
vendor/github.com/datarhei/joy4/av/pktque/timeline.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
package pktque
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
pop push
|
||||
|
||||
seg seg seg
|
||||
|--------| |---------| |---|
|
||||
20ms 40ms 5ms
|
||||
----------------- time -------------------->
|
||||
headtm tailtm
|
||||
*/
|
||||
|
||||
type tlSeg struct {
|
||||
tm, dur time.Duration
|
||||
}
|
||||
|
||||
type Timeline struct {
|
||||
segs []tlSeg
|
||||
headtm time.Duration
|
||||
}
|
||||
|
||||
func (self *Timeline) Push(tm time.Duration, dur time.Duration) {
|
||||
if len(self.segs) > 0 {
|
||||
tail := self.segs[len(self.segs)-1]
|
||||
diff := tm-(tail.tm+tail.dur)
|
||||
if diff < 0 {
|
||||
tm -= diff
|
||||
}
|
||||
}
|
||||
self.segs = append(self.segs, tlSeg{tm, dur})
|
||||
}
|
||||
|
||||
func (self *Timeline) Pop(dur time.Duration) (tm time.Duration) {
|
||||
if len(self.segs) == 0 {
|
||||
return self.headtm
|
||||
}
|
||||
|
||||
tm = self.segs[0].tm
|
||||
for dur > 0 && len(self.segs) > 0 {
|
||||
seg := &self.segs[0]
|
||||
sub := dur
|
||||
if seg.dur < sub {
|
||||
sub = seg.dur
|
||||
}
|
||||
seg.dur -= sub
|
||||
dur -= sub
|
||||
seg.tm += sub
|
||||
self.headtm += sub
|
||||
if seg.dur == 0 {
|
||||
copy(self.segs[0:], self.segs[1:])
|
||||
self.segs = self.segs[:len(self.segs)-1]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
217
vendor/github.com/datarhei/joy4/av/pubsub/queue.go
generated
vendored
Normal file
217
vendor/github.com/datarhei/joy4/av/pubsub/queue.go
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
// Packege pubsub implements publisher-subscribers model used in multi-channel streaming.
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/av/pktque"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// time
|
||||
// ----------------->
|
||||
//
|
||||
// V-A-V-V-A-V-V-A-V-V
|
||||
// | |
|
||||
// 0 5 10
|
||||
// head tail
|
||||
// oldest latest
|
||||
//
|
||||
|
||||
// One publisher and multiple subscribers thread-safe packet buffer queue.
|
||||
type Queue struct {
|
||||
buf *pktque.Buf
|
||||
head, tail int
|
||||
lock *sync.RWMutex
|
||||
cond *sync.Cond
|
||||
curgopcount, maxgopcount int
|
||||
streams []av.CodecData
|
||||
videoidx int
|
||||
closed bool
|
||||
}
|
||||
|
||||
func NewQueue() *Queue {
|
||||
q := &Queue{}
|
||||
q.buf = pktque.NewBuf()
|
||||
q.maxgopcount = 2
|
||||
q.lock = &sync.RWMutex{}
|
||||
q.cond = sync.NewCond(q.lock.RLocker())
|
||||
q.videoidx = -1
|
||||
return q
|
||||
}
|
||||
|
||||
func (self *Queue) SetMaxGopCount(n int) {
|
||||
self.lock.Lock()
|
||||
self.maxgopcount = n
|
||||
self.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Queue) WriteHeader(streams []av.CodecData) error {
|
||||
self.lock.Lock()
|
||||
|
||||
self.streams = streams
|
||||
for i, stream := range streams {
|
||||
if stream.Type().IsVideo() {
|
||||
self.videoidx = i
|
||||
}
|
||||
}
|
||||
self.cond.Broadcast()
|
||||
|
||||
self.lock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Queue) WriteTrailer() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// After Close() called, all QueueCursor's ReadPacket will return io.EOF.
|
||||
func (self *Queue) Close() (err error) {
|
||||
self.lock.Lock()
|
||||
|
||||
self.closed = true
|
||||
self.cond.Broadcast()
|
||||
|
||||
self.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Put packet into buffer, old packets will be discared.
|
||||
func (self *Queue) WritePacket(pkt av.Packet) (err error) {
|
||||
self.lock.Lock()
|
||||
|
||||
self.buf.Push(pkt)
|
||||
if pkt.Idx == int8(self.videoidx) && pkt.IsKeyFrame {
|
||||
self.curgopcount++
|
||||
}
|
||||
|
||||
for self.curgopcount >= self.maxgopcount && self.buf.Count > 1 {
|
||||
pkt := self.buf.Pop()
|
||||
if pkt.Idx == int8(self.videoidx) && pkt.IsKeyFrame {
|
||||
self.curgopcount--
|
||||
}
|
||||
if self.curgopcount < self.maxgopcount {
|
||||
break
|
||||
}
|
||||
}
|
||||
//println("shrink", self.curgopcount, self.maxgopcount, self.buf.Head, self.buf.Tail, "count", self.buf.Count, "size", self.buf.Size)
|
||||
|
||||
self.cond.Broadcast()
|
||||
|
||||
self.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
type QueueCursor struct {
|
||||
que *Queue
|
||||
pos pktque.BufPos
|
||||
gotpos bool
|
||||
init func(buf *pktque.Buf, videoidx int) pktque.BufPos
|
||||
}
|
||||
|
||||
func (self *Queue) newCursor() *QueueCursor {
|
||||
return &QueueCursor{
|
||||
que: self,
|
||||
}
|
||||
}
|
||||
|
||||
// Create cursor position at latest packet.
|
||||
func (self *Queue) Latest() *QueueCursor {
|
||||
cursor := self.newCursor()
|
||||
cursor.init = func(buf *pktque.Buf, videoidx int) pktque.BufPos {
|
||||
return buf.Tail
|
||||
}
|
||||
return cursor
|
||||
}
|
||||
|
||||
// Create cursor position at oldest buffered packet.
|
||||
func (self *Queue) Oldest() *QueueCursor {
|
||||
cursor := self.newCursor()
|
||||
cursor.init = func(buf *pktque.Buf, videoidx int) pktque.BufPos {
|
||||
return buf.Head
|
||||
}
|
||||
return cursor
|
||||
}
|
||||
|
||||
// Create cursor position at specific time in buffered packets.
|
||||
func (self *Queue) DelayedTime(dur time.Duration) *QueueCursor {
|
||||
cursor := self.newCursor()
|
||||
cursor.init = func(buf *pktque.Buf, videoidx int) pktque.BufPos {
|
||||
i := buf.Tail - 1
|
||||
if buf.IsValidPos(i) {
|
||||
end := buf.Get(i)
|
||||
for buf.IsValidPos(i) {
|
||||
if end.Time-buf.Get(i).Time > dur {
|
||||
break
|
||||
}
|
||||
i--
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
return cursor
|
||||
}
|
||||
|
||||
// Create cursor position at specific delayed GOP count in buffered packets.
|
||||
func (self *Queue) DelayedGopCount(n int) *QueueCursor {
|
||||
cursor := self.newCursor()
|
||||
cursor.init = func(buf *pktque.Buf, videoidx int) pktque.BufPos {
|
||||
i := buf.Tail - 1
|
||||
if videoidx != -1 {
|
||||
for gop := 0; buf.IsValidPos(i) && gop < n; i-- {
|
||||
pkt := buf.Get(i)
|
||||
if pkt.Idx == int8(self.videoidx) && pkt.IsKeyFrame {
|
||||
gop++
|
||||
}
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
return cursor
|
||||
}
|
||||
|
||||
func (self *QueueCursor) Streams() (streams []av.CodecData, err error) {
|
||||
self.que.cond.L.Lock()
|
||||
for self.que.streams == nil && !self.que.closed {
|
||||
self.que.cond.Wait()
|
||||
}
|
||||
if self.que.streams != nil {
|
||||
streams = self.que.streams
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
self.que.cond.L.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// ReadPacket will not consume packets in Queue, it's just a cursor.
|
||||
func (self *QueueCursor) ReadPacket() (pkt av.Packet, err error) {
|
||||
self.que.cond.L.Lock()
|
||||
buf := self.que.buf
|
||||
if !self.gotpos {
|
||||
self.pos = self.init(buf, self.que.videoidx)
|
||||
self.gotpos = true
|
||||
}
|
||||
for {
|
||||
if self.pos.LT(buf.Head) {
|
||||
self.pos = buf.Head
|
||||
} else if self.pos.GT(buf.Tail) {
|
||||
self.pos = buf.Tail
|
||||
}
|
||||
if buf.IsValidPos(self.pos) {
|
||||
pkt = buf.Get(self.pos)
|
||||
self.pos++
|
||||
break
|
||||
}
|
||||
if self.que.closed {
|
||||
err = io.EOF
|
||||
break
|
||||
}
|
||||
self.que.cond.Wait()
|
||||
}
|
||||
self.que.cond.L.Unlock()
|
||||
return
|
||||
}
|
311
vendor/github.com/datarhei/joy4/codec/aacparser/parser.go
generated
vendored
Normal file
311
vendor/github.com/datarhei/joy4/codec/aacparser/parser.go
generated
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
package aacparser
|
||||
|
||||
import (
|
||||
"github.com/datarhei/joy4/utils/bits"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"time"
|
||||
"fmt"
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// copied from libavcodec/mpeg4audio.h
|
||||
const (
|
||||
AOT_AAC_MAIN = 1 + iota ///< Y Main
|
||||
AOT_AAC_LC ///< Y Low Complexity
|
||||
AOT_AAC_SSR ///< N (code in SoC repo) Scalable Sample Rate
|
||||
AOT_AAC_LTP ///< Y Long Term Prediction
|
||||
AOT_SBR ///< Y Spectral Band Replication
|
||||
AOT_AAC_SCALABLE ///< N Scalable
|
||||
AOT_TWINVQ ///< N Twin Vector Quantizer
|
||||
AOT_CELP ///< N Code Excited Linear Prediction
|
||||
AOT_HVXC ///< N Harmonic Vector eXcitation Coding
|
||||
AOT_TTSI = 12 + iota ///< N Text-To-Speech Interface
|
||||
AOT_MAINSYNTH ///< N Main Synthesis
|
||||
AOT_WAVESYNTH ///< N Wavetable Synthesis
|
||||
AOT_MIDI ///< N General MIDI
|
||||
AOT_SAFX ///< N Algorithmic Synthesis and Audio Effects
|
||||
AOT_ER_AAC_LC ///< N Error Resilient Low Complexity
|
||||
AOT_ER_AAC_LTP = 19 + iota ///< N Error Resilient Long Term Prediction
|
||||
AOT_ER_AAC_SCALABLE ///< N Error Resilient Scalable
|
||||
AOT_ER_TWINVQ ///< N Error Resilient Twin Vector Quantizer
|
||||
AOT_ER_BSAC ///< N Error Resilient Bit-Sliced Arithmetic Coding
|
||||
AOT_ER_AAC_LD ///< N Error Resilient Low Delay
|
||||
AOT_ER_CELP ///< N Error Resilient Code Excited Linear Prediction
|
||||
AOT_ER_HVXC ///< N Error Resilient Harmonic Vector eXcitation Coding
|
||||
AOT_ER_HILN ///< N Error Resilient Harmonic and Individual Lines plus Noise
|
||||
AOT_ER_PARAM ///< N Error Resilient Parametric
|
||||
AOT_SSC ///< N SinuSoidal Coding
|
||||
AOT_PS ///< N Parametric Stereo
|
||||
AOT_SURROUND ///< N MPEG Surround
|
||||
AOT_ESCAPE ///< Y Escape Value
|
||||
AOT_L1 ///< Y Layer 1
|
||||
AOT_L2 ///< Y Layer 2
|
||||
AOT_L3 ///< Y Layer 3
|
||||
AOT_DST ///< N Direct Stream Transfer
|
||||
AOT_ALS ///< Y Audio LosslesS
|
||||
AOT_SLS ///< N Scalable LosslesS
|
||||
AOT_SLS_NON_CORE ///< N Scalable LosslesS (non core)
|
||||
AOT_ER_AAC_ELD ///< N Error Resilient Enhanced Low Delay
|
||||
AOT_SMR_SIMPLE ///< N Symbolic Music Representation Simple
|
||||
AOT_SMR_MAIN ///< N Symbolic Music Representation Main
|
||||
AOT_USAC_NOSBR ///< N Unified Speech and Audio Coding (no SBR)
|
||||
AOT_SAOC ///< N Spatial Audio Object Coding
|
||||
AOT_LD_SURROUND ///< N Low Delay MPEG Surround
|
||||
AOT_USAC ///< N Unified Speech and Audio Coding
|
||||
)
|
||||
|
||||
type MPEG4AudioConfig struct {
|
||||
SampleRate int
|
||||
ChannelLayout av.ChannelLayout
|
||||
ObjectType uint
|
||||
SampleRateIndex uint
|
||||
ChannelConfig uint
|
||||
}
|
||||
|
||||
var sampleRateTable = []int{
|
||||
96000, 88200, 64000, 48000, 44100, 32000,
|
||||
24000, 22050, 16000, 12000, 11025, 8000, 7350,
|
||||
}
|
||||
|
||||
/*
|
||||
These are the channel configurations:
|
||||
0: Defined in AOT Specifc Config
|
||||
1: 1 channel: front-center
|
||||
2: 2 channels: front-left, front-right
|
||||
3: 3 channels: front-center, front-left, front-right
|
||||
4: 4 channels: front-center, front-left, front-right, back-center
|
||||
5: 5 channels: front-center, front-left, front-right, back-left, back-right
|
||||
6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
|
||||
7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
|
||||
8-15: Reserved
|
||||
*/
|
||||
var chanConfigTable = []av.ChannelLayout{
|
||||
0,
|
||||
av.CH_FRONT_CENTER,
|
||||
av.CH_FRONT_LEFT|av.CH_FRONT_RIGHT,
|
||||
av.CH_FRONT_CENTER|av.CH_FRONT_LEFT|av.CH_FRONT_RIGHT,
|
||||
av.CH_FRONT_CENTER|av.CH_FRONT_LEFT|av.CH_FRONT_RIGHT|av.CH_BACK_CENTER,
|
||||
av.CH_FRONT_CENTER|av.CH_FRONT_LEFT|av.CH_FRONT_RIGHT|av.CH_BACK_LEFT|av.CH_BACK_RIGHT,
|
||||
av.CH_FRONT_CENTER|av.CH_FRONT_LEFT|av.CH_FRONT_RIGHT|av.CH_BACK_LEFT|av.CH_BACK_RIGHT|av.CH_LOW_FREQ,
|
||||
av.CH_FRONT_CENTER|av.CH_FRONT_LEFT|av.CH_FRONT_RIGHT|av.CH_SIDE_LEFT|av.CH_SIDE_RIGHT|av.CH_BACK_LEFT|av.CH_BACK_RIGHT|av.CH_LOW_FREQ,
|
||||
}
|
||||
|
||||
func ParseADTSHeader(frame []byte) (config MPEG4AudioConfig, hdrlen int, framelen int, samples int, err error) {
|
||||
if frame[0] != 0xff || frame[1]&0xf6 != 0xf0 {
|
||||
err = fmt.Errorf("aacparser: not adts header")
|
||||
return
|
||||
}
|
||||
config.ObjectType = uint(frame[2]>>6) + 1
|
||||
config.SampleRateIndex = uint(frame[2] >> 2 & 0xf)
|
||||
config.ChannelConfig = uint(frame[2]<<2&0x4 | frame[3]>>6&0x3)
|
||||
if config.ChannelConfig == uint(0) {
|
||||
err = fmt.Errorf("aacparser: adts channel count invalid")
|
||||
return
|
||||
}
|
||||
(&config).Complete()
|
||||
framelen = int(frame[3]&0x3)<<11 | int(frame[4])<<3 | int(frame[5]>>5)
|
||||
samples = (int(frame[6]&0x3) + 1) * 1024
|
||||
hdrlen = 7
|
||||
if frame[1]&0x1 == 0 {
|
||||
hdrlen = 9
|
||||
}
|
||||
if framelen < hdrlen {
|
||||
err = fmt.Errorf("aacparser: adts framelen < hdrlen")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const ADTSHeaderLength = 7
|
||||
|
||||
func FillADTSHeader(header []byte, config MPEG4AudioConfig, samples int, payloadLength int) {
|
||||
payloadLength += 7
|
||||
//AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)
|
||||
header[0] = 0xff
|
||||
header[1] = 0xf1
|
||||
header[2] = 0x50
|
||||
header[3] = 0x80
|
||||
header[4] = 0x43
|
||||
header[5] = 0xff
|
||||
header[6] = 0xcd
|
||||
//config.ObjectType = uint(frames[2]>>6)+1
|
||||
//config.SampleRateIndex = uint(frames[2]>>2&0xf)
|
||||
//config.ChannelConfig = uint(frames[2]<<2&0x4|frames[3]>>6&0x3)
|
||||
header[2] = (byte(config.ObjectType-1)&0x3)<<6 | (byte(config.SampleRateIndex)&0xf)<<2 | byte(config.ChannelConfig>>2)&0x1
|
||||
header[3] = header[3]&0x3f | byte(config.ChannelConfig&0x3)<<6
|
||||
header[3] = header[3]&0xfc | byte(payloadLength>>11)&0x3
|
||||
header[4] = byte(payloadLength >> 3)
|
||||
header[5] = header[5]&0x1f | (byte(payloadLength)&0x7)<<5
|
||||
header[6] = header[6]&0xfc | byte(samples/1024-1)
|
||||
return
|
||||
}
|
||||
|
||||
func readObjectType(r *bits.Reader) (objectType uint, err error) {
|
||||
if objectType, err = r.ReadBits(5); err != nil {
|
||||
return
|
||||
}
|
||||
if objectType == AOT_ESCAPE {
|
||||
var i uint
|
||||
if i, err = r.ReadBits(6); err != nil {
|
||||
return
|
||||
}
|
||||
objectType = 32 + i
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeObjectType(w *bits.Writer, objectType uint) (err error) {
|
||||
if objectType >= 32 {
|
||||
if err = w.WriteBits(AOT_ESCAPE, 5); err != nil {
|
||||
return
|
||||
}
|
||||
if err = w.WriteBits(objectType-32, 6); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = w.WriteBits(objectType, 5); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readSampleRateIndex(r *bits.Reader) (index uint, err error) {
|
||||
if index, err = r.ReadBits(4); err != nil {
|
||||
return
|
||||
}
|
||||
if index == 0xf {
|
||||
if index, err = r.ReadBits(24); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeSampleRateIndex(w *bits.Writer, index uint) (err error) {
|
||||
if index >= 0xf {
|
||||
if err = w.WriteBits(0xf, 4); err != nil {
|
||||
return
|
||||
}
|
||||
if err = w.WriteBits(index, 24); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = w.WriteBits(index, 4); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self MPEG4AudioConfig) IsValid() bool {
|
||||
return self.ObjectType > 0
|
||||
}
|
||||
|
||||
func (self *MPEG4AudioConfig) Complete() {
|
||||
if int(self.SampleRateIndex) < len(sampleRateTable) {
|
||||
self.SampleRate = sampleRateTable[self.SampleRateIndex]
|
||||
}
|
||||
if int(self.ChannelConfig) < len(chanConfigTable) {
|
||||
self.ChannelLayout = chanConfigTable[self.ChannelConfig]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ParseMPEG4AudioConfigBytes(data []byte) (config MPEG4AudioConfig, err error) {
|
||||
// copied from libavcodec/mpeg4audio.c avpriv_mpeg4audio_get_config()
|
||||
r := bytes.NewReader(data)
|
||||
br := &bits.Reader{R: r}
|
||||
if config.ObjectType, err = readObjectType(br); err != nil {
|
||||
return
|
||||
}
|
||||
if config.SampleRateIndex, err = readSampleRateIndex(br); err != nil {
|
||||
return
|
||||
}
|
||||
if config.ChannelConfig, err = br.ReadBits(4); err != nil {
|
||||
return
|
||||
}
|
||||
(&config).Complete()
|
||||
return
|
||||
}
|
||||
|
||||
func WriteMPEG4AudioConfig(w io.Writer, config MPEG4AudioConfig) (err error) {
|
||||
bw := &bits.Writer{W: w}
|
||||
if err = writeObjectType(bw, config.ObjectType); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if config.SampleRateIndex == 0 {
|
||||
for i, rate := range sampleRateTable {
|
||||
if rate == config.SampleRate {
|
||||
config.SampleRateIndex = uint(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err = writeSampleRateIndex(bw, config.SampleRateIndex); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ChannelConfig == 0 {
|
||||
for i, layout := range chanConfigTable {
|
||||
if layout == config.ChannelLayout {
|
||||
config.ChannelConfig = uint(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err = bw.WriteBits(config.ChannelConfig, 4); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = bw.FlushBits(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type CodecData struct {
|
||||
ConfigBytes []byte
|
||||
Config MPEG4AudioConfig
|
||||
}
|
||||
|
||||
func (self CodecData) Type() av.CodecType {
|
||||
return av.AAC
|
||||
}
|
||||
|
||||
func (self CodecData) MPEG4AudioConfigBytes() []byte {
|
||||
return self.ConfigBytes
|
||||
}
|
||||
|
||||
func (self CodecData) ChannelLayout() av.ChannelLayout {
|
||||
return self.Config.ChannelLayout
|
||||
}
|
||||
|
||||
func (self CodecData) SampleRate() int {
|
||||
return self.Config.SampleRate
|
||||
}
|
||||
|
||||
func (self CodecData) SampleFormat() av.SampleFormat {
|
||||
return av.FLTP
|
||||
}
|
||||
|
||||
func (self CodecData) PacketDuration(data []byte) (dur time.Duration, err error) {
|
||||
dur = time.Duration(1024) * time.Second / time.Duration(self.Config.SampleRate)
|
||||
return
|
||||
}
|
||||
|
||||
func NewCodecDataFromMPEG4AudioConfig(config MPEG4AudioConfig) (self CodecData, err error) {
|
||||
b := &bytes.Buffer{}
|
||||
WriteMPEG4AudioConfig(b, config)
|
||||
return NewCodecDataFromMPEG4AudioConfigBytes(b.Bytes())
|
||||
}
|
||||
|
||||
func NewCodecDataFromMPEG4AudioConfigBytes(config []byte) (self CodecData, err error) {
|
||||
self.ConfigBytes = config
|
||||
if self.Config, err = ParseMPEG4AudioConfigBytes(config); err != nil {
|
||||
err = fmt.Errorf("aacparser: parse MPEG4AudioConfig failed(%s)", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
64
vendor/github.com/datarhei/joy4/codec/codec.go
generated
vendored
Normal file
64
vendor/github.com/datarhei/joy4/codec/codec.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/codec/fake"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PCMUCodecData struct {
|
||||
typ av.CodecType
|
||||
}
|
||||
|
||||
func (self PCMUCodecData) Type() av.CodecType {
|
||||
return self.typ
|
||||
}
|
||||
|
||||
func (self PCMUCodecData) SampleRate() int {
|
||||
return 8000
|
||||
}
|
||||
|
||||
func (self PCMUCodecData) ChannelLayout() av.ChannelLayout {
|
||||
return av.CH_MONO
|
||||
}
|
||||
|
||||
func (self PCMUCodecData) SampleFormat() av.SampleFormat {
|
||||
return av.S16
|
||||
}
|
||||
|
||||
func (self PCMUCodecData) PacketDuration(data []byte) (time.Duration, error) {
|
||||
return time.Duration(len(data)) * time.Second / time.Duration(8000), nil
|
||||
}
|
||||
|
||||
func NewPCMMulawCodecData() av.AudioCodecData {
|
||||
return PCMUCodecData{
|
||||
typ: av.PCM_MULAW,
|
||||
}
|
||||
}
|
||||
|
||||
func NewPCMAlawCodecData() av.AudioCodecData {
|
||||
return PCMUCodecData{
|
||||
typ: av.PCM_ALAW,
|
||||
}
|
||||
}
|
||||
|
||||
type SpeexCodecData struct {
|
||||
fake.CodecData
|
||||
}
|
||||
|
||||
func (self SpeexCodecData) PacketDuration(data []byte) (time.Duration, error) {
|
||||
// libavcodec/libspeexdec.c
|
||||
// samples = samplerate/50
|
||||
// duration = 0.02s
|
||||
return time.Millisecond*20, nil
|
||||
}
|
||||
|
||||
func NewSpeexCodecData(sr int, cl av.ChannelLayout) SpeexCodecData {
|
||||
codec := SpeexCodecData{}
|
||||
codec.CodecType_ = av.SPEEX
|
||||
codec.SampleFormat_ = av.S16
|
||||
codec.SampleRate_ = sr
|
||||
codec.ChannelLayout_ = cl
|
||||
return codec
|
||||
}
|
||||
|
29
vendor/github.com/datarhei/joy4/codec/fake/fake.go
generated
vendored
Normal file
29
vendor/github.com/datarhei/joy4/codec/fake/fake.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package fake
|
||||
|
||||
import (
|
||||
"github.com/datarhei/joy4/av"
|
||||
)
|
||||
|
||||
type CodecData struct {
|
||||
CodecType_ av.CodecType
|
||||
SampleRate_ int
|
||||
SampleFormat_ av.SampleFormat
|
||||
ChannelLayout_ av.ChannelLayout
|
||||
}
|
||||
|
||||
func (self CodecData) Type() av.CodecType {
|
||||
return self.CodecType_
|
||||
}
|
||||
|
||||
func (self CodecData) SampleFormat() av.SampleFormat {
|
||||
return self.SampleFormat_
|
||||
}
|
||||
|
||||
func (self CodecData) ChannelLayout() av.ChannelLayout {
|
||||
return self.ChannelLayout_
|
||||
}
|
||||
|
||||
func (self CodecData) SampleRate() int {
|
||||
return self.SampleRate_
|
||||
}
|
||||
|
743
vendor/github.com/datarhei/joy4/codec/h264parser/parser.go
generated
vendored
Normal file
743
vendor/github.com/datarhei/joy4/codec/h264parser/parser.go
generated
vendored
Normal file
@@ -0,0 +1,743 @@
|
||||
|
||||
package h264parser
|
||||
|
||||
import (
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/utils/bits"
|
||||
"github.com/datarhei/joy4/utils/bits/pio"
|
||||
"fmt"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
const (
|
||||
NALU_SEI = 6
|
||||
NALU_PPS = 7
|
||||
NALU_SPS = 8
|
||||
NALU_AUD = 9
|
||||
)
|
||||
|
||||
func IsDataNALU(b []byte) bool {
|
||||
typ := b[0] & 0x1f
|
||||
return typ >= 1 && typ <= 5
|
||||
}
|
||||
|
||||
/*
|
||||
From: http://stackoverflow.com/questions/24884827/possible-locations-for-sequence-picture-parameter-sets-for-h-264-stream
|
||||
|
||||
First off, it's important to understand that there is no single standard H.264 elementary bitstream format. The specification document does contain an Annex, specifically Annex B, that describes one possible format, but it is not an actual requirement. The standard specifies how video is encoded into individual packets. How these packets are stored and transmitted is left open to the integrator.
|
||||
|
||||
1. Annex B
|
||||
Network Abstraction Layer Units
|
||||
The packets are called Network Abstraction Layer Units. Often abbreviated NALU (or sometimes just NAL) each packet can be individually parsed and processed. The first byte of each NALU contains the NALU type, specifically bits 3 through 7. (bit 0 is always off, and bits 1-2 indicate whether a NALU is referenced by another NALU).
|
||||
|
||||
There are 19 different NALU types defined separated into two categories, VCL and non-VCL:
|
||||
|
||||
VCL, or Video Coding Layer packets contain the actual visual information.
|
||||
Non-VCLs contain metadata that may or may not be required to decode the video.
|
||||
A single NALU, or even a VCL NALU is NOT the same thing as a frame. A frame can be ‘sliced’ into several NALUs. Just like you can slice a pizza. One or more slices are then virtually grouped into a Access Units (AU) that contain one frame. Slicing does come at a slight quality cost, so it is not often used.
|
||||
|
||||
Below is a table of all defined NALUs.
|
||||
|
||||
0 Unspecified non-VCL
|
||||
1 Coded slice of a non-IDR picture VCL
|
||||
2 Coded slice data partition A VCL
|
||||
3 Coded slice data partition B VCL
|
||||
4 Coded slice data partition C VCL
|
||||
5 Coded slice of an IDR picture VCL
|
||||
6 Supplemental enhancement information (SEI) non-VCL
|
||||
7 Sequence parameter set non-VCL
|
||||
8 Picture parameter set non-VCL
|
||||
9 Access unit delimiter non-VCL
|
||||
10 End of sequence non-VCL
|
||||
11 End of stream non-VCL
|
||||
12 Filler data non-VCL
|
||||
13 Sequence parameter set extension non-VCL
|
||||
14 Prefix NAL unit non-VCL
|
||||
15 Subset sequence parameter set non-VCL
|
||||
16 Depth parameter set non-VCL
|
||||
17..18 Reserved non-VCL
|
||||
19 Coded slice of an auxiliary coded picture without partitioning non-VCL
|
||||
20 Coded slice extension non-VCL
|
||||
21 Coded slice extension for depth view components non-VCL
|
||||
22..23 Reserved non-VCL
|
||||
24..31 Unspecified non-VCL
|
||||
There are a couple of NALU types where having knowledge of may be helpful later.
|
||||
|
||||
Sequence Parameter Set (SPS). This non-VCL NALU contains information required to configure the decoder such as profile, level, resolution, frame rate.
|
||||
Picture Parameter Set (PPS). Similar to the SPS, this non-VCL contains information on entropy coding mode, slice groups, motion prediction and deblocking filters.
|
||||
Instantaneous Decoder Refresh (IDR). This VCL NALU is a self contained image slice. That is, an IDR can be decoded and displayed without referencing any other NALU save SPS and PPS.
|
||||
Access Unit Delimiter (AUD). An AUD is an optional NALU that can be use to delimit frames in an elementary stream. It is not required (unless otherwise stated by the container/protocol, like TS), and is often not included in order to save space, but it can be useful to finds the start of a frame without having to fully parse each NALU.
|
||||
NALU Start Codes
|
||||
A NALU does not contain is its size. Therefore simply concatenating the NALUs to create a stream will not work because you will not know where one stops and the next begins.
|
||||
|
||||
The Annex B specification solves this by requiring ‘Start Codes’ to precede each NALU. A start code is 2 or 3 0x00 bytes followed with a 0x01 byte. e.g. 0x000001 or 0x00000001.
|
||||
|
||||
The 4 byte variation is useful for transmission over a serial connection as it is trivial to byte align the stream by looking for 31 zero bits followed by a one. If the next bit is 0 (because every NALU starts with a 0 bit), it is the start of a NALU. The 4 byte variation is usually only used for signaling random access points in the stream such as a SPS PPS AUD and IDR Where as the 3 byte variation is used everywhere else to save space.
|
||||
|
||||
Emulation Prevention Bytes
|
||||
Start codes work because the four byte sequences 0x000000, 0x000001, 0x000002 and 0x000003 are illegal within a non-RBSP NALU. So when creating a NALU, care is taken to escape these values that could otherwise be confused with a start code. This is accomplished by inserting an ‘Emulation Prevention’ byte 0x03, so that 0x000001 becomes 0x00000301.
|
||||
|
||||
When decoding, it is important to look for and ignore emulation prevention bytes. Because emulation prevention bytes can occur almost anywhere within a NALU, it is often more convenient in documentation to assume they have already been removed. A representation without emulation prevention bytes is called Raw Byte Sequence Payload (RBSP).
|
||||
|
||||
Example
|
||||
Let's look at a complete example.
|
||||
|
||||
0x0000 | 00 00 00 01 67 64 00 0A AC 72 84 44 26 84 00 00
|
||||
0x0010 | 03 00 04 00 00 03 00 CA 3C 48 96 11 80 00 00 00
|
||||
0x0020 | 01 68 E8 43 8F 13 21 30 00 00 01 65 88 81 00 05
|
||||
0x0030 | 4E 7F 87 DF 61 A5 8B 95 EE A4 E9 38 B7 6A 30 6A
|
||||
0x0040 | 71 B9 55 60 0B 76 2E B5 0E E4 80 59 27 B8 67 A9
|
||||
0x0050 | 63 37 5E 82 20 55 FB E4 6A E9 37 35 72 E2 22 91
|
||||
0x0060 | 9E 4D FF 60 86 CE 7E 42 B7 95 CE 2A E1 26 BE 87
|
||||
0x0070 | 73 84 26 BA 16 36 F4 E6 9F 17 DA D8 64 75 54 B1
|
||||
0x0080 | F3 45 0C 0B 3C 74 B3 9D BC EB 53 73 87 C3 0E 62
|
||||
0x0090 | 47 48 62 CA 59 EB 86 3F 3A FA 86 B5 BF A8 6D 06
|
||||
0x00A0 | 16 50 82 C4 CE 62 9E 4E E6 4C C7 30 3E DE A1 0B
|
||||
0x00B0 | D8 83 0B B6 B8 28 BC A9 EB 77 43 FC 7A 17 94 85
|
||||
0x00C0 | 21 CA 37 6B 30 95 B5 46 77 30 60 B7 12 D6 8C C5
|
||||
0x00D0 | 54 85 29 D8 69 A9 6F 12 4E 71 DF E3 E2 B1 6B 6B
|
||||
0x00E0 | BF 9F FB 2E 57 30 A9 69 76 C4 46 A2 DF FA 91 D9
|
||||
0x00F0 | 50 74 55 1D 49 04 5A 1C D6 86 68 7C B6 61 48 6C
|
||||
0x0100 | 96 E6 12 4C 27 AD BA C7 51 99 8E D0 F0 ED 8E F6
|
||||
0x0110 | 65 79 79 A6 12 A1 95 DB C8 AE E3 B6 35 E6 8D BC
|
||||
0x0120 | 48 A3 7F AF 4A 28 8A 53 E2 7E 68 08 9F 67 77 98
|
||||
0x0130 | 52 DB 50 84 D6 5E 25 E1 4A 99 58 34 C7 11 D6 43
|
||||
0x0140 | FF C4 FD 9A 44 16 D1 B2 FB 02 DB A1 89 69 34 C2
|
||||
0x0150 | 32 55 98 F9 9B B2 31 3F 49 59 0C 06 8C DB A5 B2
|
||||
0x0160 | 9D 7E 12 2F D0 87 94 44 E4 0A 76 EF 99 2D 91 18
|
||||
0x0170 | 39 50 3B 29 3B F5 2C 97 73 48 91 83 B0 A6 F3 4B
|
||||
0x0180 | 70 2F 1C 8F 3B 78 23 C6 AA 86 46 43 1D D7 2A 23
|
||||
0x0190 | 5E 2C D9 48 0A F5 F5 2C D1 FB 3F F0 4B 78 37 E9
|
||||
0x01A0 | 45 DD 72 CF 80 35 C3 95 07 F3 D9 06 E5 4A 58 76
|
||||
0x01B0 | 03 6C 81 20 62 45 65 44 73 BC FE C1 9F 31 E5 DB
|
||||
0x01C0 | 89 5C 6B 79 D8 68 90 D7 26 A8 A1 88 86 81 DC 9A
|
||||
0x01D0 | 4F 40 A5 23 C7 DE BE 6F 76 AB 79 16 51 21 67 83
|
||||
0x01E0 | 2E F3 D6 27 1A 42 C2 94 D1 5D 6C DB 4A 7A E2 CB
|
||||
0x01F0 | 0B B0 68 0B BE 19 59 00 50 FC C0 BD 9D F5 F5 F8
|
||||
0x0200 | A8 17 19 D6 B3 E9 74 BA 50 E5 2C 45 7B F9 93 EA
|
||||
0x0210 | 5A F9 A9 30 B1 6F 5B 36 24 1E 8D 55 57 F4 CC 67
|
||||
0x0220 | B2 65 6A A9 36 26 D0 06 B8 E2 E3 73 8B D1 C0 1C
|
||||
0x0230 | 52 15 CA B5 AC 60 3E 36 42 F1 2C BD 99 77 AB A8
|
||||
0x0240 | A9 A4 8E 9C 8B 84 DE 73 F0 91 29 97 AE DB AF D6
|
||||
0x0250 | F8 5E 9B 86 B3 B3 03 B3 AC 75 6F A6 11 69 2F 3D
|
||||
0x0260 | 3A CE FA 53 86 60 95 6C BB C5 4E F3
|
||||
|
||||
This is a complete AU containing 3 NALUs. As you can see, we begin with a Start code followed by an SPS (SPS starts with 67). Within the SPS, you will see two Emulation Prevention bytes. Without these bytes the illegal sequence 0x000000 would occur at these positions. Next you will see a start code followed by a PPS (PPS starts with 68) and one final start code followed by an IDR slice. This is a complete H.264 stream. If you type these values into a hex editor and save the file with a .264 extension, you will be able to convert it to this image:
|
||||
|
||||
Lena
|
||||
|
||||
Annex B is commonly used in live and streaming formats such as transport streams, over the air broadcasts, and DVDs. In these formats it is common to repeat the SPS and PPS periodically, usually preceding every IDR thus creating a random access point for the decoder. This enables the ability to join a stream already in progress.
|
||||
|
||||
2. AVCC
|
||||
The other common method of storing an H.264 stream is the AVCC format. In this format, each NALU is preceded with its length (in big endian format). This method is easier to parse, but you lose the byte alignment features of Annex B. Just to complicate things, the length may be encoded using 1, 2 or 4 bytes. This value is stored in a header object. This header is often called ‘extradata’ or ‘sequence header’. Its basic format is as follows:
|
||||
|
||||
bits
|
||||
8 version ( always 0x01 )
|
||||
8 avc profile ( sps[0][1] )
|
||||
8 avc compatibility ( sps[0][2] )
|
||||
8 avc level ( sps[0][3] )
|
||||
6 reserved ( all bits on )
|
||||
2 NALULengthSizeMinusOne
|
||||
3 reserved ( all bits on )
|
||||
5 number of SPS NALUs (usually 1)
|
||||
repeated once per SPS:
|
||||
16 SPS size
|
||||
variable SPS NALU data
|
||||
8 number of PPS NALUs (usually 1)
|
||||
repeated once per PPS
|
||||
16 PPS size
|
||||
variable PPS NALU data
|
||||
|
||||
Using the same example above, the AVCC extradata will look like this:
|
||||
|
||||
0x0000 | 01 64 00 0A FF E1 00 19 67 64 00 0A AC 72 84 44
|
||||
0x0010 | 26 84 00 00 03 00 04 00 00 03 00 CA 3C 48 96 11
|
||||
0x0020 | 80 01 00 07 68 E8 43 8F 13 21 30
|
||||
|
||||
You will notice SPS and PPS is now stored out of band. That is, separate from the elementary stream data. Storage and transmission of this data is the job of the file container, and beyond the scope of this document. Notice that even though we are not using start codes, emulation prevention bytes are still inserted.
|
||||
|
||||
Additionally, there is a new variable called NALULengthSizeMinusOne. This confusingly named variable tells us how many bytes to use to store the length of each NALU. So, if NALULengthSizeMinusOne is set to 0, then each NALU is preceded with a single byte indicating its length. Using a single byte to store the size, the max size of a NALU is 255 bytes. That is obviously pretty small. Way too small for an entire key frame. Using 2 bytes gives us 64k per NALU. It would work in our example, but is still a pretty low limit. 3 bytes would be perfect, but for some reason is not universally supported. Therefore, 4 bytes is by far the most common, and it is what we used here:
|
||||
|
||||
0x0000 | 00 00 02 41 65 88 81 00 05 4E 7F 87 DF 61 A5 8B
|
||||
0x0010 | 95 EE A4 E9 38 B7 6A 30 6A 71 B9 55 60 0B 76 2E
|
||||
0x0020 | B5 0E E4 80 59 27 B8 67 A9 63 37 5E 82 20 55 FB
|
||||
0x0030 | E4 6A E9 37 35 72 E2 22 91 9E 4D FF 60 86 CE 7E
|
||||
0x0040 | 42 B7 95 CE 2A E1 26 BE 87 73 84 26 BA 16 36 F4
|
||||
0x0050 | E6 9F 17 DA D8 64 75 54 B1 F3 45 0C 0B 3C 74 B3
|
||||
0x0060 | 9D BC EB 53 73 87 C3 0E 62 47 48 62 CA 59 EB 86
|
||||
0x0070 | 3F 3A FA 86 B5 BF A8 6D 06 16 50 82 C4 CE 62 9E
|
||||
0x0080 | 4E E6 4C C7 30 3E DE A1 0B D8 83 0B B6 B8 28 BC
|
||||
0x0090 | A9 EB 77 43 FC 7A 17 94 85 21 CA 37 6B 30 95 B5
|
||||
0x00A0 | 46 77 30 60 B7 12 D6 8C C5 54 85 29 D8 69 A9 6F
|
||||
0x00B0 | 12 4E 71 DF E3 E2 B1 6B 6B BF 9F FB 2E 57 30 A9
|
||||
0x00C0 | 69 76 C4 46 A2 DF FA 91 D9 50 74 55 1D 49 04 5A
|
||||
0x00D0 | 1C D6 86 68 7C B6 61 48 6C 96 E6 12 4C 27 AD BA
|
||||
0x00E0 | C7 51 99 8E D0 F0 ED 8E F6 65 79 79 A6 12 A1 95
|
||||
0x00F0 | DB C8 AE E3 B6 35 E6 8D BC 48 A3 7F AF 4A 28 8A
|
||||
0x0100 | 53 E2 7E 68 08 9F 67 77 98 52 DB 50 84 D6 5E 25
|
||||
0x0110 | E1 4A 99 58 34 C7 11 D6 43 FF C4 FD 9A 44 16 D1
|
||||
0x0120 | B2 FB 02 DB A1 89 69 34 C2 32 55 98 F9 9B B2 31
|
||||
0x0130 | 3F 49 59 0C 06 8C DB A5 B2 9D 7E 12 2F D0 87 94
|
||||
0x0140 | 44 E4 0A 76 EF 99 2D 91 18 39 50 3B 29 3B F5 2C
|
||||
0x0150 | 97 73 48 91 83 B0 A6 F3 4B 70 2F 1C 8F 3B 78 23
|
||||
0x0160 | C6 AA 86 46 43 1D D7 2A 23 5E 2C D9 48 0A F5 F5
|
||||
0x0170 | 2C D1 FB 3F F0 4B 78 37 E9 45 DD 72 CF 80 35 C3
|
||||
0x0180 | 95 07 F3 D9 06 E5 4A 58 76 03 6C 81 20 62 45 65
|
||||
0x0190 | 44 73 BC FE C1 9F 31 E5 DB 89 5C 6B 79 D8 68 90
|
||||
0x01A0 | D7 26 A8 A1 88 86 81 DC 9A 4F 40 A5 23 C7 DE BE
|
||||
0x01B0 | 6F 76 AB 79 16 51 21 67 83 2E F3 D6 27 1A 42 C2
|
||||
0x01C0 | 94 D1 5D 6C DB 4A 7A E2 CB 0B B0 68 0B BE 19 59
|
||||
0x01D0 | 00 50 FC C0 BD 9D F5 F5 F8 A8 17 19 D6 B3 E9 74
|
||||
0x01E0 | BA 50 E5 2C 45 7B F9 93 EA 5A F9 A9 30 B1 6F 5B
|
||||
0x01F0 | 36 24 1E 8D 55 57 F4 CC 67 B2 65 6A A9 36 26 D0
|
||||
0x0200 | 06 B8 E2 E3 73 8B D1 C0 1C 52 15 CA B5 AC 60 3E
|
||||
0x0210 | 36 42 F1 2C BD 99 77 AB A8 A9 A4 8E 9C 8B 84 DE
|
||||
0x0220 | 73 F0 91 29 97 AE DB AF D6 F8 5E 9B 86 B3 B3 03
|
||||
0x0230 | B3 AC 75 6F A6 11 69 2F 3D 3A CE FA 53 86 60 95
|
||||
0x0240 | 6C BB C5 4E F3
|
||||
|
||||
An advantage to this format is the ability to configure the decoder at the start and jump into the middle of a stream. This is a common use case where the media is available on a random access medium such as a hard drive, and is therefore used in common container formats such as MP4 and MKV.
|
||||
*/
|
||||
|
||||
var StartCodeBytes = []byte{0,0,1}
|
||||
var AUDBytes = []byte{0,0,0,1,0x9,0xf0,0,0,0,1} // AUD
|
||||
|
||||
func CheckNALUsType(b []byte) (typ int) {
|
||||
_, typ = SplitNALUs(b)
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
NALU_RAW = iota
|
||||
NALU_AVCC
|
||||
NALU_ANNEXB
|
||||
)
|
||||
|
||||
func SplitNALUs(b []byte) (nalus [][]byte, typ int) {
|
||||
if len(b) < 4 {
|
||||
return [][]byte{b}, NALU_RAW
|
||||
}
|
||||
|
||||
val3 := pio.U24BE(b)
|
||||
val4 := pio.U32BE(b)
|
||||
|
||||
// maybe AVCC
|
||||
if val4 <= uint32(len(b)) {
|
||||
_val4 := val4
|
||||
_b := b[4:]
|
||||
nalus := [][]byte{}
|
||||
for {
|
||||
nalus = append(nalus, _b[:_val4])
|
||||
_b = _b[_val4:]
|
||||
if len(_b) < 4 {
|
||||
break
|
||||
}
|
||||
_val4 = pio.U32BE(_b)
|
||||
_b = _b[4:]
|
||||
if _val4 > uint32(len(_b)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(_b) == 0 {
|
||||
return nalus, NALU_AVCC
|
||||
}
|
||||
}
|
||||
|
||||
// is Annex B
|
||||
if val3 == 1 || val4 == 1 {
|
||||
_val3 := val3
|
||||
_val4 := val4
|
||||
start := 0
|
||||
pos := 0
|
||||
for {
|
||||
if start != pos {
|
||||
nalus = append(nalus, b[start:pos])
|
||||
}
|
||||
if _val3 == 1 {
|
||||
pos += 3
|
||||
} else if _val4 == 1 {
|
||||
pos += 4
|
||||
}
|
||||
start = pos
|
||||
if start == len(b) {
|
||||
break
|
||||
}
|
||||
_val3 = 0
|
||||
_val4 = 0
|
||||
for pos < len(b) {
|
||||
if pos+2 < len(b) && b[pos] == 0 {
|
||||
_val3 = pio.U24BE(b[pos:])
|
||||
if _val3 == 0 {
|
||||
if pos+3 < len(b) {
|
||||
_val4 = uint32(b[pos+3])
|
||||
if _val4 == 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if _val3 == 1 {
|
||||
break
|
||||
}
|
||||
pos++
|
||||
} else {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
}
|
||||
typ = NALU_ANNEXB
|
||||
return
|
||||
}
|
||||
|
||||
return [][]byte{b}, NALU_RAW
|
||||
}
|
||||
|
||||
type SPSInfo struct {
|
||||
ProfileIdc uint
|
||||
LevelIdc uint
|
||||
|
||||
MbWidth uint
|
||||
MbHeight uint
|
||||
|
||||
CropLeft uint
|
||||
CropRight uint
|
||||
CropTop uint
|
||||
CropBottom uint
|
||||
|
||||
Width uint
|
||||
Height uint
|
||||
}
|
||||
|
||||
func ParseSPS(data []byte) (self SPSInfo, err error) {
|
||||
r := &bits.GolombBitReader{R: bytes.NewReader(data)}
|
||||
|
||||
if _, err = r.ReadBits(8); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if self.ProfileIdc, err = r.ReadBits(8); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// constraint_set0_flag-constraint_set6_flag,reserved_zero_2bits
|
||||
if _, err = r.ReadBits(8); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// level_idc
|
||||
if self.LevelIdc, err = r.ReadBits(8); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// seq_parameter_set_id
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if self.ProfileIdc == 100 || self.ProfileIdc == 110 ||
|
||||
self.ProfileIdc == 122 || self.ProfileIdc == 244 ||
|
||||
self.ProfileIdc == 44 || self.ProfileIdc == 83 ||
|
||||
self.ProfileIdc == 86 || self.ProfileIdc == 118 {
|
||||
|
||||
var chroma_format_idc uint
|
||||
if chroma_format_idc, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if chroma_format_idc == 3 {
|
||||
// residual_colour_transform_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// bit_depth_luma_minus8
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
// bit_depth_chroma_minus8
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
// qpprime_y_zero_transform_bypass_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var seq_scaling_matrix_present_flag uint
|
||||
if seq_scaling_matrix_present_flag, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if seq_scaling_matrix_present_flag != 0 {
|
||||
for i := 0; i < 8; i++ {
|
||||
var seq_scaling_list_present_flag uint
|
||||
if seq_scaling_list_present_flag, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
if seq_scaling_list_present_flag != 0 {
|
||||
var sizeOfScalingList uint
|
||||
if i < 6 {
|
||||
sizeOfScalingList = 16
|
||||
} else {
|
||||
sizeOfScalingList = 64
|
||||
}
|
||||
lastScale := uint(8)
|
||||
nextScale := uint(8)
|
||||
for j := uint(0); j < sizeOfScalingList; j++ {
|
||||
if nextScale != 0 {
|
||||
var delta_scale uint
|
||||
if delta_scale, err = r.ReadSE(); err != nil {
|
||||
return
|
||||
}
|
||||
nextScale = (lastScale + delta_scale + 256) % 256
|
||||
}
|
||||
if nextScale != 0 {
|
||||
lastScale = nextScale
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log2_max_frame_num_minus4
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var pic_order_cnt_type uint
|
||||
if pic_order_cnt_type, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
if pic_order_cnt_type == 0 {
|
||||
// log2_max_pic_order_cnt_lsb_minus4
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
} else if pic_order_cnt_type == 1 {
|
||||
// delta_pic_order_always_zero_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
// offset_for_non_ref_pic
|
||||
if _, err = r.ReadSE(); err != nil {
|
||||
return
|
||||
}
|
||||
// offset_for_top_to_bottom_field
|
||||
if _, err = r.ReadSE(); err != nil {
|
||||
return
|
||||
}
|
||||
var num_ref_frames_in_pic_order_cnt_cycle uint
|
||||
if num_ref_frames_in_pic_order_cnt_cycle, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
for i := uint(0); i < num_ref_frames_in_pic_order_cnt_cycle; i++ {
|
||||
if _, err = r.ReadSE(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// max_num_ref_frames
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// gaps_in_frame_num_value_allowed_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if self.MbWidth, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
self.MbWidth++
|
||||
|
||||
if self.MbHeight, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
self.MbHeight++
|
||||
|
||||
var frame_mbs_only_flag uint
|
||||
if frame_mbs_only_flag, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
if frame_mbs_only_flag == 0 {
|
||||
// mb_adaptive_frame_field_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// direct_8x8_inference_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var frame_cropping_flag uint
|
||||
if frame_cropping_flag, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
if frame_cropping_flag != 0 {
|
||||
if self.CropLeft, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
if self.CropRight, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
if self.CropTop, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
if self.CropBottom, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.Width = (self.MbWidth * 16) - self.CropLeft*2 - self.CropRight*2
|
||||
self.Height = ((2 - frame_mbs_only_flag) * self.MbHeight * 16) - self.CropTop*2 - self.CropBottom*2
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type CodecData struct {
|
||||
Record []byte
|
||||
RecordInfo AVCDecoderConfRecord
|
||||
SPSInfo SPSInfo
|
||||
}
|
||||
|
||||
func (self CodecData) Type() av.CodecType {
|
||||
return av.H264
|
||||
}
|
||||
|
||||
func (self CodecData) AVCDecoderConfRecordBytes() []byte {
|
||||
return self.Record
|
||||
}
|
||||
|
||||
func (self CodecData) SPS() []byte {
|
||||
return self.RecordInfo.SPS[0]
|
||||
}
|
||||
|
||||
func (self CodecData) PPS() []byte {
|
||||
return self.RecordInfo.PPS[0]
|
||||
}
|
||||
|
||||
func (self CodecData) Width() int {
|
||||
return int(self.SPSInfo.Width)
|
||||
}
|
||||
|
||||
func (self CodecData) Height() int {
|
||||
return int(self.SPSInfo.Height)
|
||||
}
|
||||
|
||||
func NewCodecDataFromAVCDecoderConfRecord(record []byte) (self CodecData, err error) {
|
||||
self.Record = record
|
||||
if _, err = (&self.RecordInfo).Unmarshal(record); err != nil {
|
||||
return
|
||||
}
|
||||
if len(self.RecordInfo.SPS) == 0 {
|
||||
err = fmt.Errorf("h264parser: no SPS found in AVCDecoderConfRecord")
|
||||
return
|
||||
}
|
||||
if len(self.RecordInfo.PPS) == 0 {
|
||||
err = fmt.Errorf("h264parser: no PPS found in AVCDecoderConfRecord")
|
||||
return
|
||||
}
|
||||
if self.SPSInfo, err = ParseSPS(self.RecordInfo.SPS[0]); err != nil {
|
||||
err = fmt.Errorf("h264parser: parse SPS failed(%s)", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewCodecDataFromSPSAndPPS(sps, pps []byte) (self CodecData, err error) {
|
||||
recordinfo := AVCDecoderConfRecord{}
|
||||
recordinfo.AVCProfileIndication = sps[1]
|
||||
recordinfo.ProfileCompatibility = sps[2]
|
||||
recordinfo.AVCLevelIndication = sps[3]
|
||||
recordinfo.SPS = [][]byte{sps}
|
||||
recordinfo.PPS = [][]byte{pps}
|
||||
recordinfo.LengthSizeMinusOne = 3
|
||||
|
||||
buf := make([]byte, recordinfo.Len())
|
||||
recordinfo.Marshal(buf)
|
||||
|
||||
self.RecordInfo = recordinfo
|
||||
self.Record = buf
|
||||
|
||||
if self.SPSInfo, err = ParseSPS(sps); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type AVCDecoderConfRecord struct {
|
||||
AVCProfileIndication uint8
|
||||
ProfileCompatibility uint8
|
||||
AVCLevelIndication uint8
|
||||
LengthSizeMinusOne uint8
|
||||
SPS [][]byte
|
||||
PPS [][]byte
|
||||
}
|
||||
|
||||
var ErrDecconfInvalid = fmt.Errorf("h264parser: AVCDecoderConfRecord invalid")
|
||||
|
||||
func (self *AVCDecoderConfRecord) Unmarshal(b []byte) (n int, err error) {
|
||||
if len(b) < 7 {
|
||||
err = ErrDecconfInvalid
|
||||
return
|
||||
}
|
||||
|
||||
self.AVCProfileIndication = b[1]
|
||||
self.ProfileCompatibility = b[2]
|
||||
self.AVCLevelIndication = b[3]
|
||||
self.LengthSizeMinusOne = b[4]&0x03
|
||||
spscount := int(b[5]&0x1f)
|
||||
n += 6
|
||||
|
||||
for i := 0; i < spscount; i++ {
|
||||
if len(b) < n+2 {
|
||||
err = ErrDecconfInvalid
|
||||
return
|
||||
}
|
||||
spslen := int(pio.U16BE(b[n:]))
|
||||
n += 2
|
||||
|
||||
if len(b) < n+spslen {
|
||||
err = ErrDecconfInvalid
|
||||
return
|
||||
}
|
||||
self.SPS = append(self.SPS, b[n:n+spslen])
|
||||
n += spslen
|
||||
}
|
||||
|
||||
if len(b) < n+1 {
|
||||
err = ErrDecconfInvalid
|
||||
return
|
||||
}
|
||||
ppscount := int(b[n])
|
||||
n++
|
||||
|
||||
for i := 0; i < ppscount; i++ {
|
||||
if len(b) < n+2 {
|
||||
err = ErrDecconfInvalid
|
||||
return
|
||||
}
|
||||
ppslen := int(pio.U16BE(b[n:]))
|
||||
n += 2
|
||||
|
||||
if len(b) < n+ppslen {
|
||||
err = ErrDecconfInvalid
|
||||
return
|
||||
}
|
||||
self.PPS = append(self.PPS, b[n:n+ppslen])
|
||||
n += ppslen
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self AVCDecoderConfRecord) Len() (n int) {
|
||||
n = 7
|
||||
for _, sps := range self.SPS {
|
||||
n += 2+len(sps)
|
||||
}
|
||||
for _, pps := range self.PPS {
|
||||
n += 2+len(pps)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self AVCDecoderConfRecord) Marshal(b []byte) (n int) {
|
||||
b[0] = 1
|
||||
b[1] = self.AVCProfileIndication
|
||||
b[2] = self.ProfileCompatibility
|
||||
b[3] = self.AVCLevelIndication
|
||||
b[4] = self.LengthSizeMinusOne|0xfc
|
||||
b[5] = uint8(len(self.SPS))|0xe0
|
||||
n += 6
|
||||
|
||||
for _, sps := range self.SPS {
|
||||
pio.PutU16BE(b[n:], uint16(len(sps)))
|
||||
n += 2
|
||||
copy(b[n:], sps)
|
||||
n += len(sps)
|
||||
}
|
||||
|
||||
b[n] = uint8(len(self.PPS))
|
||||
n++
|
||||
|
||||
for _, pps := range self.PPS {
|
||||
pio.PutU16BE(b[n:], uint16(len(pps)))
|
||||
n += 2
|
||||
copy(b[n:], pps)
|
||||
n += len(pps)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type SliceType uint
|
||||
|
||||
func (self SliceType) String() string {
|
||||
switch self {
|
||||
case SLICE_P:
|
||||
return "P"
|
||||
case SLICE_B:
|
||||
return "B"
|
||||
case SLICE_I:
|
||||
return "I"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
const (
|
||||
SLICE_P = iota+1
|
||||
SLICE_B
|
||||
SLICE_I
|
||||
)
|
||||
|
||||
func ParseSliceHeaderFromNALU(packet []byte) (sliceType SliceType, err error) {
|
||||
|
||||
if len(packet) <= 1 {
|
||||
err = fmt.Errorf("h264parser: packet too short to parse slice header")
|
||||
return
|
||||
}
|
||||
|
||||
nal_unit_type := packet[0]&0x1f
|
||||
switch nal_unit_type {
|
||||
case 1,2,5,19:
|
||||
// slice_layer_without_partitioning_rbsp
|
||||
// slice_data_partition_a_layer_rbsp
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("h264parser: nal_unit_type=%d has no slice header", nal_unit_type)
|
||||
return
|
||||
}
|
||||
|
||||
r := &bits.GolombBitReader{R: bytes.NewReader(packet[1:])}
|
||||
|
||||
// first_mb_in_slice
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// slice_type
|
||||
var u uint
|
||||
if u, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch u {
|
||||
case 0,3,5,8:
|
||||
sliceType = SLICE_P
|
||||
case 1,6:
|
||||
sliceType = SLICE_B
|
||||
case 2,4,7,9:
|
||||
sliceType = SLICE_I
|
||||
default:
|
||||
err = fmt.Errorf("h264parser: slice_type=%d invalid", u)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
124
vendor/github.com/datarhei/joy4/format/aac/aac.go
generated
vendored
Normal file
124
vendor/github.com/datarhei/joy4/format/aac/aac.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
|
||||
package aac
|
||||
|
||||
import (
|
||||
"github.com/datarhei/joy4/av/avutil"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/codec/aacparser"
|
||||
"time"
|
||||
"fmt"
|
||||
"io"
|
||||
"bufio"
|
||||
)
|
||||
|
||||
type Muxer struct {
|
||||
w io.Writer
|
||||
config aacparser.MPEG4AudioConfig
|
||||
adtshdr []byte
|
||||
}
|
||||
|
||||
func NewMuxer(w io.Writer) *Muxer {
|
||||
return &Muxer{
|
||||
adtshdr: make([]byte, aacparser.ADTSHeaderLength),
|
||||
w: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||
if len(streams) > 1 || streams[0].Type() != av.AAC {
|
||||
err = fmt.Errorf("aac: must be only one aac stream")
|
||||
return
|
||||
}
|
||||
self.config = streams[0].(aacparser.CodecData).Config
|
||||
if self.config.ObjectType > aacparser.AOT_AAC_LTP {
|
||||
err = fmt.Errorf("aac: AOT %d is not allowed in ADTS", self.config.ObjectType)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WritePacket(pkt av.Packet) (err error) {
|
||||
aacparser.FillADTSHeader(self.adtshdr, self.config, 1024, len(pkt.Data))
|
||||
if _, err = self.w.Write(self.adtshdr); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = self.w.Write(pkt.Data); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteTrailer() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
type Demuxer struct {
|
||||
r *bufio.Reader
|
||||
config aacparser.MPEG4AudioConfig
|
||||
codecdata av.CodecData
|
||||
ts time.Duration
|
||||
}
|
||||
|
||||
func NewDemuxer(r io.Reader) *Demuxer {
|
||||
return &Demuxer{
|
||||
r: bufio.NewReader(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Demuxer) Streams() (streams []av.CodecData, err error) {
|
||||
if self.codecdata == nil {
|
||||
var adtshdr []byte
|
||||
var config aacparser.MPEG4AudioConfig
|
||||
if adtshdr, err = self.r.Peek(9); err != nil {
|
||||
return
|
||||
}
|
||||
if config, _, _, _, err = aacparser.ParseADTSHeader(adtshdr); err != nil {
|
||||
return
|
||||
}
|
||||
if self.codecdata, err = aacparser.NewCodecDataFromMPEG4AudioConfig(config); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
streams = []av.CodecData{self.codecdata}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||
var adtshdr []byte
|
||||
var config aacparser.MPEG4AudioConfig
|
||||
var hdrlen, framelen, samples int
|
||||
if adtshdr, err = self.r.Peek(9); err != nil {
|
||||
return
|
||||
}
|
||||
if config, hdrlen, framelen, samples, err = aacparser.ParseADTSHeader(adtshdr); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pkt.Data = make([]byte, framelen)
|
||||
if _, err = io.ReadFull(self.r, pkt.Data); err != nil {
|
||||
return
|
||||
}
|
||||
pkt.Data = pkt.Data[hdrlen:]
|
||||
|
||||
pkt.Time = self.ts
|
||||
self.ts += time.Duration(samples) * time.Second / time.Duration(config.SampleRate)
|
||||
return
|
||||
}
|
||||
|
||||
func Handler(h *avutil.RegisterHandler) {
|
||||
h.Ext = ".aac"
|
||||
|
||||
h.ReaderDemuxer = func(r io.Reader) av.Demuxer {
|
||||
return NewDemuxer(r)
|
||||
}
|
||||
|
||||
h.WriterMuxer = func(w io.Writer) av.Muxer {
|
||||
return NewMuxer(w)
|
||||
}
|
||||
|
||||
h.Probe = func(b []byte) bool {
|
||||
_, _, _, _, err := aacparser.ParseADTSHeader(b)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
h.CodecTypes = []av.CodecType{av.AAC}
|
||||
}
|
494
vendor/github.com/datarhei/joy4/format/flv/flv.go
generated
vendored
Normal file
494
vendor/github.com/datarhei/joy4/format/flv/flv.go
generated
vendored
Normal file
@@ -0,0 +1,494 @@
|
||||
package flv
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/datarhei/joy4/utils/bits/pio"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/av/avutil"
|
||||
"github.com/datarhei/joy4/codec"
|
||||
"github.com/datarhei/joy4/codec/aacparser"
|
||||
"github.com/datarhei/joy4/codec/fake"
|
||||
"github.com/datarhei/joy4/codec/h264parser"
|
||||
"github.com/datarhei/joy4/format/flv/flvio"
|
||||
"io"
|
||||
)
|
||||
|
||||
var MaxProbePacketCount = 20
|
||||
|
||||
func NewMetadataByStreams(streams []av.CodecData) (metadata flvio.AMFMap, err error) {
|
||||
metadata = flvio.AMFMap{}
|
||||
|
||||
for _, _stream := range streams {
|
||||
typ := _stream.Type()
|
||||
switch {
|
||||
case typ.IsVideo():
|
||||
stream := _stream.(av.VideoCodecData)
|
||||
switch typ {
|
||||
case av.H264:
|
||||
metadata["videocodecid"] = flvio.VIDEO_H264
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("flv: metadata: unsupported video codecType=%v", stream.Type())
|
||||
return
|
||||
}
|
||||
|
||||
metadata["width"] = stream.Width()
|
||||
metadata["height"] = stream.Height()
|
||||
metadata["displayWidth"] = stream.Width()
|
||||
metadata["displayHeight"] = stream.Height()
|
||||
|
||||
case typ.IsAudio():
|
||||
stream := _stream.(av.AudioCodecData)
|
||||
switch typ {
|
||||
case av.AAC:
|
||||
metadata["audiocodecid"] = flvio.SOUND_AAC
|
||||
|
||||
case av.SPEEX:
|
||||
metadata["audiocodecid"] = flvio.SOUND_SPEEX
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("flv: metadata: unsupported audio codecType=%v", stream.Type())
|
||||
return
|
||||
}
|
||||
|
||||
metadata["audiosamplerate"] = stream.SampleRate()
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Prober struct {
|
||||
HasAudio, HasVideo bool
|
||||
GotAudio, GotVideo bool
|
||||
VideoStreamIdx, AudioStreamIdx int
|
||||
PushedCount int
|
||||
Streams []av.CodecData
|
||||
CachedPkts []av.Packet
|
||||
}
|
||||
|
||||
func (self *Prober) CacheTag(_tag flvio.Tag, timestamp int32) {
|
||||
pkt, _ := self.TagToPacket(_tag, timestamp)
|
||||
self.CachedPkts = append(self.CachedPkts, pkt)
|
||||
}
|
||||
|
||||
func (self *Prober) PushTag(tag flvio.Tag, timestamp int32) (err error) {
|
||||
self.PushedCount++
|
||||
|
||||
if self.PushedCount > MaxProbePacketCount {
|
||||
err = fmt.Errorf("flv: max probe packet count reached")
|
||||
return
|
||||
}
|
||||
|
||||
switch tag.Type {
|
||||
case flvio.TAG_VIDEO:
|
||||
switch tag.AVCPacketType {
|
||||
case flvio.AVC_SEQHDR:
|
||||
if !self.GotVideo {
|
||||
var stream h264parser.CodecData
|
||||
if stream, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(tag.Data); err != nil {
|
||||
err = fmt.Errorf("flv: h264 seqhdr invalid: %s", err.Error())
|
||||
return
|
||||
}
|
||||
self.VideoStreamIdx = len(self.Streams)
|
||||
self.Streams = append(self.Streams, stream)
|
||||
self.GotVideo = true
|
||||
}
|
||||
|
||||
case flvio.AVC_NALU:
|
||||
self.CacheTag(tag, timestamp)
|
||||
}
|
||||
|
||||
case flvio.TAG_AUDIO:
|
||||
switch tag.SoundFormat {
|
||||
case flvio.SOUND_AAC:
|
||||
switch tag.AACPacketType {
|
||||
case flvio.AAC_SEQHDR:
|
||||
if !self.GotAudio {
|
||||
var stream aacparser.CodecData
|
||||
if stream, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(tag.Data); err != nil {
|
||||
err = fmt.Errorf("flv: aac seqhdr invalid")
|
||||
return
|
||||
}
|
||||
self.AudioStreamIdx = len(self.Streams)
|
||||
self.Streams = append(self.Streams, stream)
|
||||
self.GotAudio = true
|
||||
}
|
||||
|
||||
case flvio.AAC_RAW:
|
||||
self.CacheTag(tag, timestamp)
|
||||
}
|
||||
|
||||
case flvio.SOUND_SPEEX:
|
||||
if !self.GotAudio {
|
||||
stream := codec.NewSpeexCodecData(16000, tag.ChannelLayout())
|
||||
self.AudioStreamIdx = len(self.Streams)
|
||||
self.Streams = append(self.Streams, stream)
|
||||
self.GotAudio = true
|
||||
self.CacheTag(tag, timestamp)
|
||||
}
|
||||
|
||||
case flvio.SOUND_NELLYMOSER:
|
||||
if !self.GotAudio {
|
||||
stream := fake.CodecData{
|
||||
CodecType_: av.NELLYMOSER,
|
||||
SampleRate_: 16000,
|
||||
SampleFormat_: av.S16,
|
||||
ChannelLayout_: tag.ChannelLayout(),
|
||||
}
|
||||
self.AudioStreamIdx = len(self.Streams)
|
||||
self.Streams = append(self.Streams, stream)
|
||||
self.GotAudio = true
|
||||
self.CacheTag(tag, timestamp)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Prober) Probed() (ok bool) {
|
||||
if self.HasAudio || self.HasVideo {
|
||||
if self.HasAudio == self.GotAudio && self.HasVideo == self.GotVideo {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if self.PushedCount == MaxProbePacketCount {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Prober) TagToPacket(tag flvio.Tag, timestamp int32) (pkt av.Packet, ok bool) {
|
||||
switch tag.Type {
|
||||
case flvio.TAG_VIDEO:
|
||||
pkt.Idx = int8(self.VideoStreamIdx)
|
||||
switch tag.AVCPacketType {
|
||||
case flvio.AVC_NALU:
|
||||
ok = true
|
||||
pkt.Data = tag.Data
|
||||
pkt.CompositionTime = flvio.TsToTime(tag.CompositionTime)
|
||||
pkt.IsKeyFrame = tag.FrameType == flvio.FRAME_KEY
|
||||
}
|
||||
|
||||
case flvio.TAG_AUDIO:
|
||||
pkt.Idx = int8(self.AudioStreamIdx)
|
||||
switch tag.SoundFormat {
|
||||
case flvio.SOUND_AAC:
|
||||
switch tag.AACPacketType {
|
||||
case flvio.AAC_RAW:
|
||||
ok = true
|
||||
pkt.Data = tag.Data
|
||||
}
|
||||
|
||||
case flvio.SOUND_SPEEX:
|
||||
ok = true
|
||||
pkt.Data = tag.Data
|
||||
|
||||
case flvio.SOUND_NELLYMOSER:
|
||||
ok = true
|
||||
pkt.Data = tag.Data
|
||||
}
|
||||
}
|
||||
|
||||
pkt.Time = flvio.TsToTime(timestamp)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Prober) Empty() bool {
|
||||
return len(self.CachedPkts) == 0
|
||||
}
|
||||
|
||||
func (self *Prober) PopPacket() av.Packet {
|
||||
pkt := self.CachedPkts[0]
|
||||
self.CachedPkts = self.CachedPkts[1:]
|
||||
return pkt
|
||||
}
|
||||
|
||||
func CodecDataToTag(stream av.CodecData) (_tag flvio.Tag, ok bool, err error) {
|
||||
switch stream.Type() {
|
||||
case av.H264:
|
||||
h264 := stream.(h264parser.CodecData)
|
||||
tag := flvio.Tag{
|
||||
Type: flvio.TAG_VIDEO,
|
||||
AVCPacketType: flvio.AVC_SEQHDR,
|
||||
CodecID: flvio.VIDEO_H264,
|
||||
Data: h264.AVCDecoderConfRecordBytes(),
|
||||
FrameType: flvio.FRAME_KEY,
|
||||
}
|
||||
ok = true
|
||||
_tag = tag
|
||||
|
||||
case av.NELLYMOSER:
|
||||
case av.SPEEX:
|
||||
|
||||
case av.AAC:
|
||||
aac := stream.(aacparser.CodecData)
|
||||
tag := flvio.Tag{
|
||||
Type: flvio.TAG_AUDIO,
|
||||
SoundFormat: flvio.SOUND_AAC,
|
||||
SoundRate: flvio.SOUND_44Khz,
|
||||
AACPacketType: flvio.AAC_SEQHDR,
|
||||
Data: aac.MPEG4AudioConfigBytes(),
|
||||
}
|
||||
switch aac.SampleFormat().BytesPerSample() {
|
||||
case 1:
|
||||
tag.SoundSize = flvio.SOUND_8BIT
|
||||
default:
|
||||
tag.SoundSize = flvio.SOUND_16BIT
|
||||
}
|
||||
switch aac.ChannelLayout().Count() {
|
||||
case 1:
|
||||
tag.SoundType = flvio.SOUND_MONO
|
||||
case 2:
|
||||
tag.SoundType = flvio.SOUND_STEREO
|
||||
}
|
||||
ok = true
|
||||
_tag = tag
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("flv: unspported codecType=%v", stream.Type())
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func PacketToTag(pkt av.Packet, stream av.CodecData) (tag flvio.Tag, timestamp int32) {
|
||||
switch stream.Type() {
|
||||
case av.H264:
|
||||
tag = flvio.Tag{
|
||||
Type: flvio.TAG_VIDEO,
|
||||
AVCPacketType: flvio.AVC_NALU,
|
||||
CodecID: flvio.VIDEO_H264,
|
||||
Data: pkt.Data,
|
||||
CompositionTime: flvio.TimeToTs(pkt.CompositionTime),
|
||||
}
|
||||
if pkt.IsKeyFrame {
|
||||
tag.FrameType = flvio.FRAME_KEY
|
||||
} else {
|
||||
tag.FrameType = flvio.FRAME_INTER
|
||||
}
|
||||
|
||||
case av.AAC:
|
||||
tag = flvio.Tag{
|
||||
Type: flvio.TAG_AUDIO,
|
||||
SoundFormat: flvio.SOUND_AAC,
|
||||
SoundRate: flvio.SOUND_44Khz,
|
||||
AACPacketType: flvio.AAC_RAW,
|
||||
Data: pkt.Data,
|
||||
}
|
||||
astream := stream.(av.AudioCodecData)
|
||||
switch astream.SampleFormat().BytesPerSample() {
|
||||
case 1:
|
||||
tag.SoundSize = flvio.SOUND_8BIT
|
||||
default:
|
||||
tag.SoundSize = flvio.SOUND_16BIT
|
||||
}
|
||||
switch astream.ChannelLayout().Count() {
|
||||
case 1:
|
||||
tag.SoundType = flvio.SOUND_MONO
|
||||
case 2:
|
||||
tag.SoundType = flvio.SOUND_STEREO
|
||||
}
|
||||
|
||||
case av.SPEEX:
|
||||
tag = flvio.Tag{
|
||||
Type: flvio.TAG_AUDIO,
|
||||
SoundFormat: flvio.SOUND_SPEEX,
|
||||
Data: pkt.Data,
|
||||
}
|
||||
|
||||
case av.NELLYMOSER:
|
||||
tag = flvio.Tag{
|
||||
Type: flvio.TAG_AUDIO,
|
||||
SoundFormat: flvio.SOUND_NELLYMOSER,
|
||||
Data: pkt.Data,
|
||||
}
|
||||
}
|
||||
|
||||
timestamp = flvio.TimeToTs(pkt.Time)
|
||||
return
|
||||
}
|
||||
|
||||
type Muxer struct {
|
||||
bufw writeFlusher
|
||||
b []byte
|
||||
streams []av.CodecData
|
||||
}
|
||||
|
||||
type writeFlusher interface {
|
||||
io.Writer
|
||||
Flush() error
|
||||
}
|
||||
|
||||
func NewMuxerWriteFlusher(w writeFlusher) *Muxer {
|
||||
return &Muxer{
|
||||
bufw: w,
|
||||
b: make([]byte, 256),
|
||||
}
|
||||
}
|
||||
|
||||
func NewMuxer(w io.Writer) *Muxer {
|
||||
return NewMuxerWriteFlusher(bufio.NewWriterSize(w, pio.RecommendBufioSize))
|
||||
}
|
||||
|
||||
var CodecTypes = []av.CodecType{av.H264, av.AAC, av.SPEEX}
|
||||
|
||||
func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||
var flags uint8
|
||||
for _, stream := range streams {
|
||||
if stream.Type().IsVideo() {
|
||||
flags |= flvio.FILE_HAS_VIDEO
|
||||
} else if stream.Type().IsAudio() {
|
||||
flags |= flvio.FILE_HAS_AUDIO
|
||||
}
|
||||
}
|
||||
|
||||
n := flvio.FillFileHeader(self.b, flags)
|
||||
if _, err = self.bufw.Write(self.b[:n]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, stream := range streams {
|
||||
var tag flvio.Tag
|
||||
var ok bool
|
||||
if tag, ok, err = CodecDataToTag(stream); err != nil {
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
if err = flvio.WriteTag(self.bufw, tag, 0, self.b); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.streams = streams
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WritePacket(pkt av.Packet) (err error) {
|
||||
stream := self.streams[pkt.Idx]
|
||||
tag, timestamp := PacketToTag(pkt, stream)
|
||||
|
||||
if err = flvio.WriteTag(self.bufw, tag, timestamp, self.b); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteTrailer() (err error) {
|
||||
if err = self.bufw.Flush(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Demuxer struct {
|
||||
prober *Prober
|
||||
bufr *bufio.Reader
|
||||
b []byte
|
||||
stage int
|
||||
}
|
||||
|
||||
func NewDemuxer(r io.Reader) *Demuxer {
|
||||
return &Demuxer{
|
||||
bufr: bufio.NewReaderSize(r, pio.RecommendBufioSize),
|
||||
prober: &Prober{},
|
||||
b: make([]byte, 256),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Demuxer) prepare() (err error) {
|
||||
for self.stage < 2 {
|
||||
switch self.stage {
|
||||
case 0:
|
||||
if _, err = io.ReadFull(self.bufr, self.b[:flvio.FileHeaderLength]); err != nil {
|
||||
return
|
||||
}
|
||||
var flags uint8
|
||||
var skip int
|
||||
if flags, skip, err = flvio.ParseFileHeader(self.b); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = self.bufr.Discard(skip); err != nil {
|
||||
return
|
||||
}
|
||||
if flags&flvio.FILE_HAS_AUDIO != 0 {
|
||||
self.prober.HasAudio = true
|
||||
}
|
||||
if flags&flvio.FILE_HAS_VIDEO != 0 {
|
||||
self.prober.HasVideo = true
|
||||
}
|
||||
self.stage++
|
||||
|
||||
case 1:
|
||||
for !self.prober.Probed() {
|
||||
var tag flvio.Tag
|
||||
var timestamp int32
|
||||
if tag, timestamp, err = flvio.ReadTag(self.bufr, self.b); err != nil {
|
||||
return
|
||||
}
|
||||
if err = self.prober.PushTag(tag, timestamp); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
self.stage++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) Streams() (streams []av.CodecData, err error) {
|
||||
if err = self.prepare(); err != nil {
|
||||
return
|
||||
}
|
||||
streams = self.prober.Streams
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||
if err = self.prepare(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !self.prober.Empty() {
|
||||
pkt = self.prober.PopPacket()
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
var tag flvio.Tag
|
||||
var timestamp int32
|
||||
if tag, timestamp, err = flvio.ReadTag(self.bufr, self.b); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if pkt, ok = self.prober.TagToPacket(tag, timestamp); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Handler(h *avutil.RegisterHandler) {
|
||||
h.Probe = func(b []byte) bool {
|
||||
return b[0] == 'F' && b[1] == 'L' && b[2] == 'V'
|
||||
}
|
||||
|
||||
h.Ext = ".flv"
|
||||
|
||||
h.ReaderDemuxer = func(r io.Reader) av.Demuxer {
|
||||
return NewDemuxer(r)
|
||||
}
|
||||
|
||||
h.WriterMuxer = func(w io.Writer) av.Muxer {
|
||||
return NewMuxer(w)
|
||||
}
|
||||
|
||||
h.CodecTypes = CodecTypes
|
||||
}
|
468
vendor/github.com/datarhei/joy4/format/flv/flvio/amf0.go
generated
vendored
Normal file
468
vendor/github.com/datarhei/joy4/format/flv/flvio/amf0.go
generated
vendored
Normal file
@@ -0,0 +1,468 @@
|
||||
package flvio
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"math"
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/datarhei/joy4/utils/bits/pio"
|
||||
)
|
||||
|
||||
type AMF0ParseError struct {
|
||||
Offset int
|
||||
Message string
|
||||
Next *AMF0ParseError
|
||||
}
|
||||
|
||||
func (self *AMF0ParseError) Error() string {
|
||||
s := []string{}
|
||||
for p := self; p != nil; p = p.Next {
|
||||
s = append(s, fmt.Sprintf("%s:%d", p.Message, p.Offset))
|
||||
}
|
||||
return "amf0 parse error: " + strings.Join(s, ",")
|
||||
}
|
||||
|
||||
func amf0ParseErr(message string, offset int, err error) error {
|
||||
next, _ := err.(*AMF0ParseError)
|
||||
return &AMF0ParseError{
|
||||
Offset: offset,
|
||||
Message: message,
|
||||
Next: next,
|
||||
}
|
||||
}
|
||||
|
||||
type AMFMap map[string]interface{}
|
||||
type AMFArray []interface{}
|
||||
type AMFECMAArray map[string]interface{}
|
||||
|
||||
func parseBEFloat64(b []byte) float64 {
|
||||
return math.Float64frombits(pio.U64BE(b))
|
||||
}
|
||||
|
||||
func fillBEFloat64(b []byte, f float64) int {
|
||||
pio.PutU64BE(b, math.Float64bits(f))
|
||||
return 8
|
||||
}
|
||||
|
||||
const lenAMF0Number = 9
|
||||
|
||||
func fillAMF0Number(b []byte, f float64) int {
|
||||
b[0] = numbermarker
|
||||
fillBEFloat64(b[1:], f)
|
||||
return lenAMF0Number
|
||||
}
|
||||
|
||||
const (
|
||||
amf3undefinedmarker = iota
|
||||
amf3nullmarker
|
||||
amf3falsemarker
|
||||
amf3truemarker
|
||||
amf3integermarker
|
||||
amf3doublemarker
|
||||
amf3stringmarker
|
||||
amf3xmldocmarker
|
||||
amf3datemarker
|
||||
amf3arraymarker
|
||||
amf3objectmarker
|
||||
amf3xmlmarker
|
||||
amf3bytearraymarker
|
||||
amf3vectorintmarker
|
||||
amf3vectoruintmarker
|
||||
amf3vectordoublemarker
|
||||
amf3vectorobjectmarker
|
||||
amf3dictionarymarker
|
||||
)
|
||||
|
||||
const (
|
||||
numbermarker = iota
|
||||
booleanmarker
|
||||
stringmarker
|
||||
objectmarker
|
||||
movieclipmarker
|
||||
nullmarker
|
||||
undefinedmarker
|
||||
referencemarker
|
||||
ecmaarraymarker
|
||||
objectendmarker
|
||||
strictarraymarker
|
||||
datemarker
|
||||
longstringmarker
|
||||
unsupportedmarker
|
||||
recordsetmarker
|
||||
xmldocumentmarker
|
||||
typedobjectmarker
|
||||
avmplusobjectmarker
|
||||
)
|
||||
|
||||
func LenAMF0Val(_val interface{}) (n int) {
|
||||
switch val := _val.(type) {
|
||||
case int8:
|
||||
n += lenAMF0Number
|
||||
case int16:
|
||||
n += lenAMF0Number
|
||||
case int32:
|
||||
n += lenAMF0Number
|
||||
case int64:
|
||||
n += lenAMF0Number
|
||||
case int:
|
||||
n += lenAMF0Number
|
||||
case uint8:
|
||||
n += lenAMF0Number
|
||||
case uint16:
|
||||
n += lenAMF0Number
|
||||
case uint32:
|
||||
n += lenAMF0Number
|
||||
case uint64:
|
||||
n += lenAMF0Number
|
||||
case uint:
|
||||
n += lenAMF0Number
|
||||
case float32:
|
||||
n += lenAMF0Number
|
||||
case float64:
|
||||
n += lenAMF0Number
|
||||
|
||||
case string:
|
||||
u := len(val)
|
||||
if u <= 65536 {
|
||||
n += 3
|
||||
} else {
|
||||
n += 5
|
||||
}
|
||||
n += int(u)
|
||||
|
||||
case AMFECMAArray:
|
||||
n += 5
|
||||
for k, v := range val {
|
||||
n += 2+len(k)
|
||||
n += LenAMF0Val(v)
|
||||
}
|
||||
n += 3
|
||||
|
||||
case AMFMap:
|
||||
n++
|
||||
for k, v := range val {
|
||||
if len(k) > 0 {
|
||||
n += 2+len(k)
|
||||
n += LenAMF0Val(v)
|
||||
}
|
||||
}
|
||||
n += 3
|
||||
|
||||
case AMFArray:
|
||||
n += 5
|
||||
for _, v := range val {
|
||||
n += LenAMF0Val(v)
|
||||
}
|
||||
|
||||
case time.Time:
|
||||
n += 1+8+2
|
||||
|
||||
case bool:
|
||||
n += 2
|
||||
|
||||
case nil:
|
||||
n++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func FillAMF0Val(b []byte, _val interface{}) (n int) {
|
||||
switch val := _val.(type) {
|
||||
case int8:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case int16:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case int32:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case int64:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case int:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case uint8:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case uint16:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case uint32:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case uint64:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case uint:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case float32:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
case float64:
|
||||
n += fillAMF0Number(b[n:], float64(val))
|
||||
|
||||
case string:
|
||||
u := len(val)
|
||||
if u <= 65536 {
|
||||
b[n] = stringmarker
|
||||
n++
|
||||
pio.PutU16BE(b[n:], uint16(u))
|
||||
n += 2
|
||||
} else {
|
||||
b[n] = longstringmarker
|
||||
n++
|
||||
pio.PutU32BE(b[n:], uint32(u))
|
||||
n += 4
|
||||
}
|
||||
copy(b[n:], []byte(val))
|
||||
n += len(val)
|
||||
|
||||
case AMFECMAArray:
|
||||
b[n] = ecmaarraymarker
|
||||
n++
|
||||
pio.PutU32BE(b[n:], uint32(len(val)))
|
||||
n += 4
|
||||
for k, v := range val {
|
||||
pio.PutU16BE(b[n:], uint16(len(k)))
|
||||
n += 2
|
||||
copy(b[n:], []byte(k))
|
||||
n += len(k)
|
||||
n += FillAMF0Val(b[n:], v)
|
||||
}
|
||||
pio.PutU24BE(b[n:], 0x000009)
|
||||
n += 3
|
||||
|
||||
case AMFMap:
|
||||
b[n] = objectmarker
|
||||
n++
|
||||
for k, v := range val {
|
||||
if len(k) > 0 {
|
||||
pio.PutU16BE(b[n:], uint16(len(k)))
|
||||
n += 2
|
||||
copy(b[n:], []byte(k))
|
||||
n += len(k)
|
||||
n += FillAMF0Val(b[n:], v)
|
||||
}
|
||||
}
|
||||
pio.PutU24BE(b[n:], 0x000009)
|
||||
n += 3
|
||||
|
||||
case AMFArray:
|
||||
b[n] = strictarraymarker
|
||||
n++
|
||||
pio.PutU32BE(b[n:], uint32(len(val)))
|
||||
n += 4
|
||||
for _, v := range val {
|
||||
n += FillAMF0Val(b[n:], v)
|
||||
}
|
||||
|
||||
case time.Time:
|
||||
b[n] = datemarker
|
||||
n++
|
||||
u := val.UnixNano()
|
||||
f := float64(u/1000000)
|
||||
n += fillBEFloat64(b[n:], f)
|
||||
pio.PutU16BE(b[n:], uint16(0))
|
||||
n += 2
|
||||
|
||||
case bool:
|
||||
b[n] = booleanmarker
|
||||
n++
|
||||
var u uint8
|
||||
if val {
|
||||
u = 1
|
||||
} else {
|
||||
u = 0
|
||||
}
|
||||
b[n] = u
|
||||
n++
|
||||
|
||||
case nil:
|
||||
b[n] = nullmarker
|
||||
n++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func ParseAMF0Val(b []byte) (val interface{}, n int, err error) {
|
||||
return parseAMF0Val(b, 0)
|
||||
}
|
||||
|
||||
func parseAMF0Val(b []byte, offset int) (val interface{}, n int, err error) {
|
||||
if len(b) < n+1 {
|
||||
err = amf0ParseErr("marker", offset+n, err)
|
||||
return
|
||||
}
|
||||
marker := b[n]
|
||||
n++
|
||||
|
||||
switch marker {
|
||||
case numbermarker:
|
||||
if len(b) < n+8 {
|
||||
err = amf0ParseErr("number", offset+n, err)
|
||||
return
|
||||
}
|
||||
val = parseBEFloat64(b[n:])
|
||||
n += 8
|
||||
|
||||
case booleanmarker:
|
||||
if len(b) < n+1 {
|
||||
err = amf0ParseErr("boolean", offset+n, err)
|
||||
return
|
||||
}
|
||||
val = b[n] != 0
|
||||
n++
|
||||
|
||||
case stringmarker:
|
||||
if len(b) < n+2 {
|
||||
err = amf0ParseErr("string.length", offset+n, err)
|
||||
return
|
||||
}
|
||||
length := int(pio.U16BE(b[n:]))
|
||||
n += 2
|
||||
|
||||
if len(b) < n+length {
|
||||
err = amf0ParseErr("string.body", offset+n, err)
|
||||
return
|
||||
}
|
||||
val = string(b[n:n+length])
|
||||
n += length
|
||||
|
||||
case objectmarker:
|
||||
obj := AMFMap{}
|
||||
for {
|
||||
if len(b) < n+2 {
|
||||
err = amf0ParseErr("object.key.length", offset+n, err)
|
||||
return
|
||||
}
|
||||
length := int(pio.U16BE(b[n:]))
|
||||
n += 2
|
||||
if length == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if len(b) < n+length {
|
||||
err = amf0ParseErr("object.key.body", offset+n, err)
|
||||
return
|
||||
}
|
||||
okey := string(b[n:n+length])
|
||||
n += length
|
||||
|
||||
var nval int
|
||||
var oval interface{}
|
||||
if oval, nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
|
||||
err = amf0ParseErr("object.val", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += nval
|
||||
|
||||
obj[okey] = oval
|
||||
}
|
||||
if len(b) < n+1 {
|
||||
err = amf0ParseErr("object.end", offset+n, err)
|
||||
return
|
||||
}
|
||||
n++
|
||||
val = obj
|
||||
|
||||
case nullmarker:
|
||||
case undefinedmarker:
|
||||
|
||||
case ecmaarraymarker:
|
||||
if len(b) < n+4 {
|
||||
err = amf0ParseErr("array.count", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += 4
|
||||
|
||||
obj := AMFMap{}
|
||||
for {
|
||||
if len(b) < n+2 {
|
||||
err = amf0ParseErr("array.key.length", offset+n, err)
|
||||
return
|
||||
}
|
||||
length := int(pio.U16BE(b[n:]))
|
||||
n += 2
|
||||
|
||||
if length == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if len(b) < n+length {
|
||||
err = amf0ParseErr("array.key.body", offset+n, err)
|
||||
return
|
||||
}
|
||||
okey := string(b[n:n+length])
|
||||
n += length
|
||||
|
||||
var nval int
|
||||
var oval interface{}
|
||||
if oval, nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
|
||||
err = amf0ParseErr("array.val", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += nval
|
||||
|
||||
obj[okey] = oval
|
||||
}
|
||||
if len(b) < n+1 {
|
||||
err = amf0ParseErr("array.end", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += 1
|
||||
val = obj
|
||||
|
||||
case objectendmarker:
|
||||
if len(b) < n+3 {
|
||||
err = amf0ParseErr("objectend", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += 3
|
||||
|
||||
case strictarraymarker:
|
||||
if len(b) < n+4 {
|
||||
err = amf0ParseErr("strictarray.count", offset+n, err)
|
||||
return
|
||||
}
|
||||
count := int(pio.U32BE(b[n:]))
|
||||
n += 4
|
||||
|
||||
obj := make(AMFArray, count)
|
||||
for i := 0; i < int(count); i++ {
|
||||
var nval int
|
||||
if obj[i], nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
|
||||
err = amf0ParseErr("strictarray.val", offset+n, err)
|
||||
return
|
||||
}
|
||||
n += nval
|
||||
}
|
||||
val = obj
|
||||
|
||||
case datemarker:
|
||||
if len(b) < n+8+2 {
|
||||
err = amf0ParseErr("date", offset+n, err)
|
||||
return
|
||||
}
|
||||
ts := parseBEFloat64(b[n:])
|
||||
n += 8+2
|
||||
|
||||
val = time.Unix(int64(ts/1000), (int64(ts)%1000)*1000000)
|
||||
|
||||
case longstringmarker:
|
||||
if len(b) < n+4 {
|
||||
err = amf0ParseErr("longstring.length", offset+n, err)
|
||||
return
|
||||
}
|
||||
length := int(pio.U32BE(b[n:]))
|
||||
n += 4
|
||||
|
||||
if len(b) < n+length {
|
||||
err = amf0ParseErr("longstring.body", offset+n, err)
|
||||
return
|
||||
}
|
||||
val = string(b[n:n+length])
|
||||
n += length
|
||||
|
||||
default:
|
||||
err = amf0ParseErr(fmt.Sprintf("invalidmarker=%d", marker), offset+n, err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
415
vendor/github.com/datarhei/joy4/format/flv/flvio/flvio.go
generated
vendored
Normal file
415
vendor/github.com/datarhei/joy4/format/flv/flvio/flvio.go
generated
vendored
Normal file
@@ -0,0 +1,415 @@
|
||||
package flvio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/datarhei/joy4/utils/bits/pio"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TsToTime(ts int32) time.Duration {
|
||||
return time.Millisecond * time.Duration(ts)
|
||||
}
|
||||
|
||||
func TimeToTs(tm time.Duration) int32 {
|
||||
return int32(tm / time.Millisecond)
|
||||
}
|
||||
|
||||
const MaxTagSubHeaderLength = 16
|
||||
|
||||
const (
|
||||
TAG_AUDIO = 8
|
||||
TAG_VIDEO = 9
|
||||
TAG_SCRIPTDATA = 18
|
||||
)
|
||||
|
||||
const (
|
||||
SOUND_MP3 = 2
|
||||
SOUND_NELLYMOSER_16KHZ_MONO = 4
|
||||
SOUND_NELLYMOSER_8KHZ_MONO = 5
|
||||
SOUND_NELLYMOSER = 6
|
||||
SOUND_ALAW = 7
|
||||
SOUND_MULAW = 8
|
||||
SOUND_AAC = 10
|
||||
SOUND_SPEEX = 11
|
||||
|
||||
SOUND_5_5Khz = 0
|
||||
SOUND_11Khz = 1
|
||||
SOUND_22Khz = 2
|
||||
SOUND_44Khz = 3
|
||||
|
||||
SOUND_8BIT = 0
|
||||
SOUND_16BIT = 1
|
||||
|
||||
SOUND_MONO = 0
|
||||
SOUND_STEREO = 1
|
||||
|
||||
AAC_SEQHDR = 0
|
||||
AAC_RAW = 1
|
||||
)
|
||||
|
||||
const (
|
||||
AVC_SEQHDR = 0
|
||||
AVC_NALU = 1
|
||||
AVC_EOS = 2
|
||||
|
||||
FRAME_KEY = 1
|
||||
FRAME_INTER = 2
|
||||
|
||||
VIDEO_H264 = 7
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
/*
|
||||
8 = Audio
|
||||
9 = Video
|
||||
18 = Script data
|
||||
*/
|
||||
Type uint8
|
||||
|
||||
/*
|
||||
SoundFormat: UB[4]
|
||||
0 = Linear PCM, platform endian
|
||||
1 = ADPCM
|
||||
2 = MP3
|
||||
3 = Linear PCM, little endian
|
||||
4 = Nellymoser 16-kHz mono
|
||||
5 = Nellymoser 8-kHz mono
|
||||
6 = Nellymoser
|
||||
7 = G.711 A-law logarithmic PCM
|
||||
8 = G.711 mu-law logarithmic PCM
|
||||
9 = reserved
|
||||
10 = AAC
|
||||
11 = Speex
|
||||
14 = MP3 8-Khz
|
||||
15 = Device-specific sound
|
||||
Formats 7, 8, 14, and 15 are reserved for internal use
|
||||
AAC is supported in Flash Player 9,0,115,0 and higher.
|
||||
Speex is supported in Flash Player 10 and higher.
|
||||
*/
|
||||
SoundFormat uint8
|
||||
|
||||
/*
|
||||
SoundRate: UB[2]
|
||||
Sampling rate
|
||||
0 = 5.5-kHz For AAC: always 3
|
||||
1 = 11-kHz
|
||||
2 = 22-kHz
|
||||
3 = 44-kHz
|
||||
*/
|
||||
SoundRate uint8
|
||||
|
||||
/*
|
||||
SoundSize: UB[1]
|
||||
0 = snd8Bit
|
||||
1 = snd16Bit
|
||||
Size of each sample.
|
||||
This parameter only pertains to uncompressed formats.
|
||||
Compressed formats always decode to 16 bits internally
|
||||
*/
|
||||
SoundSize uint8
|
||||
|
||||
/*
|
||||
SoundType: UB[1]
|
||||
0 = sndMono
|
||||
1 = sndStereo
|
||||
Mono or stereo sound For Nellymoser: always 0
|
||||
For AAC: always 1
|
||||
*/
|
||||
SoundType uint8
|
||||
|
||||
/*
|
||||
0: AAC sequence header
|
||||
1: AAC raw
|
||||
*/
|
||||
AACPacketType uint8
|
||||
|
||||
/*
|
||||
1: keyframe (for AVC, a seekable frame)
|
||||
2: inter frame (for AVC, a non- seekable frame)
|
||||
3: disposable inter frame (H.263 only)
|
||||
4: generated keyframe (reserved for server use only)
|
||||
5: video info/command frame
|
||||
*/
|
||||
FrameType uint8
|
||||
|
||||
/*
|
||||
1: JPEG (currently unused)
|
||||
2: Sorenson H.263
|
||||
3: Screen video
|
||||
4: On2 VP6
|
||||
5: On2 VP6 with alpha channel
|
||||
6: Screen video version 2
|
||||
7: AVC
|
||||
*/
|
||||
CodecID uint8
|
||||
|
||||
/*
|
||||
0: AVC sequence header
|
||||
1: AVC NALU
|
||||
2: AVC end of sequence (lower level NALU sequence ender is not required or supported)
|
||||
*/
|
||||
AVCPacketType uint8
|
||||
|
||||
CompositionTime int32
|
||||
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (self Tag) ChannelLayout() av.ChannelLayout {
|
||||
if self.SoundType == SOUND_MONO {
|
||||
return av.CH_MONO
|
||||
} else {
|
||||
return av.CH_STEREO
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Tag) audioParseHeader(b []byte) (n int, err error) {
|
||||
if len(b) < n+1 {
|
||||
err = fmt.Errorf("audiodata: parse invalid")
|
||||
return
|
||||
}
|
||||
|
||||
flags := b[n]
|
||||
n++
|
||||
self.SoundFormat = flags >> 4
|
||||
self.SoundRate = (flags >> 2) & 0x3
|
||||
self.SoundSize = (flags >> 1) & 0x1
|
||||
self.SoundType = flags & 0x1
|
||||
|
||||
switch self.SoundFormat {
|
||||
case SOUND_AAC:
|
||||
if len(b) < n+1 {
|
||||
err = fmt.Errorf("audiodata: parse invalid")
|
||||
return
|
||||
}
|
||||
self.AACPacketType = b[n]
|
||||
n++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self Tag) audioFillHeader(b []byte) (n int) {
|
||||
var flags uint8
|
||||
flags |= self.SoundFormat << 4
|
||||
flags |= self.SoundRate << 2
|
||||
flags |= self.SoundSize << 1
|
||||
flags |= self.SoundType
|
||||
b[n] = flags
|
||||
n++
|
||||
|
||||
switch self.SoundFormat {
|
||||
case SOUND_AAC:
|
||||
b[n] = self.AACPacketType
|
||||
n++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Tag) videoParseHeader(b []byte) (n int, err error) {
|
||||
if len(b) < n+1 {
|
||||
err = fmt.Errorf("videodata: parse invalid")
|
||||
return
|
||||
}
|
||||
flags := b[n]
|
||||
self.FrameType = flags >> 4
|
||||
self.CodecID = flags & 0xf
|
||||
n++
|
||||
|
||||
if self.FrameType == FRAME_INTER || self.FrameType == FRAME_KEY {
|
||||
if len(b) < n+4 {
|
||||
err = fmt.Errorf("videodata: parse invalid")
|
||||
return
|
||||
}
|
||||
self.AVCPacketType = b[n]
|
||||
n++
|
||||
|
||||
self.CompositionTime = pio.I24BE(b[n:])
|
||||
n += 3
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self Tag) videoFillHeader(b []byte) (n int) {
|
||||
flags := self.FrameType<<4 | self.CodecID
|
||||
b[n] = flags
|
||||
n++
|
||||
b[n] = self.AVCPacketType
|
||||
n++
|
||||
pio.PutI24BE(b[n:], self.CompositionTime)
|
||||
n += 3
|
||||
return
|
||||
}
|
||||
|
||||
func (self Tag) FillHeader(b []byte) (n int) {
|
||||
switch self.Type {
|
||||
case TAG_AUDIO:
|
||||
return self.audioFillHeader(b)
|
||||
|
||||
case TAG_VIDEO:
|
||||
return self.videoFillHeader(b)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Tag) ParseHeader(b []byte) (n int, err error) {
|
||||
switch self.Type {
|
||||
case TAG_AUDIO:
|
||||
return self.audioParseHeader(b)
|
||||
|
||||
case TAG_VIDEO:
|
||||
return self.videoParseHeader(b)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
// TypeFlagsReserved UB[5]
|
||||
// TypeFlagsAudio UB[1] Audio tags are present
|
||||
// TypeFlagsReserved UB[1] Must be 0
|
||||
// TypeFlagsVideo UB[1] Video tags are present
|
||||
FILE_HAS_AUDIO = 0x4
|
||||
FILE_HAS_VIDEO = 0x1
|
||||
)
|
||||
|
||||
const TagHeaderLength = 11
|
||||
const TagTrailerLength = 4
|
||||
|
||||
func ParseTagHeader(b []byte) (tag Tag, ts int32, datalen int, err error) {
|
||||
tagtype := b[0]
|
||||
|
||||
switch tagtype {
|
||||
case TAG_AUDIO, TAG_VIDEO, TAG_SCRIPTDATA:
|
||||
tag = Tag{Type: tagtype}
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("flvio: ReadTag tagtype=%d invalid", tagtype)
|
||||
return
|
||||
}
|
||||
|
||||
datalen = int(pio.U24BE(b[1:4]))
|
||||
|
||||
var tslo uint32
|
||||
var tshi uint8
|
||||
tslo = pio.U24BE(b[4:7])
|
||||
tshi = b[7]
|
||||
ts = int32(tslo | uint32(tshi)<<24)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ReadTag(r io.Reader, b []byte) (tag Tag, ts int32, err error) {
|
||||
if _, err = io.ReadFull(r, b[:TagHeaderLength]); err != nil {
|
||||
return
|
||||
}
|
||||
var datalen int
|
||||
if tag, ts, datalen, err = ParseTagHeader(b); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := make([]byte, datalen)
|
||||
if _, err = io.ReadFull(r, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var n int
|
||||
if n, err = (&tag).ParseHeader(data); err != nil {
|
||||
return
|
||||
}
|
||||
tag.Data = data[n:]
|
||||
|
||||
if _, err = io.ReadFull(r, b[:4]); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func FillTagHeader(b []byte, tagtype uint8, datalen int, ts int32) (n int) {
|
||||
b[n] = tagtype
|
||||
n++
|
||||
pio.PutU24BE(b[n:], uint32(datalen))
|
||||
n += 3
|
||||
pio.PutU24BE(b[n:], uint32(ts&0xffffff))
|
||||
n += 3
|
||||
b[n] = uint8(ts >> 24)
|
||||
n++
|
||||
pio.PutI24BE(b[n:], 0)
|
||||
n += 3
|
||||
return
|
||||
}
|
||||
|
||||
func FillTagTrailer(b []byte, datalen int) (n int) {
|
||||
pio.PutU32BE(b[n:], uint32(datalen+TagHeaderLength))
|
||||
n += 4
|
||||
return
|
||||
}
|
||||
|
||||
func WriteTag(w io.Writer, tag Tag, ts int32, b []byte) (err error) {
|
||||
data := tag.Data
|
||||
|
||||
n := tag.FillHeader(b[TagHeaderLength:])
|
||||
datalen := len(data) + n
|
||||
|
||||
n += FillTagHeader(b, tag.Type, datalen, ts)
|
||||
|
||||
if _, err = w.Write(b[:n]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = w.Write(data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n = FillTagTrailer(b, datalen)
|
||||
if _, err = w.Write(b[:n]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const FileHeaderLength = 9
|
||||
|
||||
func FillFileHeader(b []byte, flags uint8) (n int) {
|
||||
// 'FLV', version 1
|
||||
pio.PutU32BE(b[n:], 0x464c5601)
|
||||
n += 4
|
||||
|
||||
b[n] = flags
|
||||
n++
|
||||
|
||||
// DataOffset: UI32 Offset in bytes from start of file to start of body (that is, size of header)
|
||||
// The DataOffset field usually has a value of 9 for FLV version 1.
|
||||
pio.PutU32BE(b[n:], 9)
|
||||
n += 4
|
||||
|
||||
// PreviousTagSize0: UI32 Always 0
|
||||
pio.PutU32BE(b[n:], 0)
|
||||
n += 4
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ParseFileHeader(b []byte) (flags uint8, skip int, err error) {
|
||||
flv := pio.U24BE(b[0:3])
|
||||
if flv != 0x464c56 { // 'FLV'
|
||||
err = fmt.Errorf("flvio: file header cc3 invalid")
|
||||
return
|
||||
}
|
||||
|
||||
flags = b[4]
|
||||
|
||||
skip = int(pio.U32BE(b[5:9])) - 9 + 4
|
||||
if skip < 0 {
|
||||
err = fmt.Errorf("flvio: file header datasize invalid")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
21
vendor/github.com/datarhei/joy4/format/format.go
generated
vendored
Normal file
21
vendor/github.com/datarhei/joy4/format/format.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"github.com/datarhei/joy4/format/mp4"
|
||||
"github.com/datarhei/joy4/format/ts"
|
||||
"github.com/datarhei/joy4/format/rtmp"
|
||||
"github.com/datarhei/joy4/format/rtsp"
|
||||
"github.com/datarhei/joy4/format/flv"
|
||||
"github.com/datarhei/joy4/format/aac"
|
||||
"github.com/datarhei/joy4/av/avutil"
|
||||
)
|
||||
|
||||
func RegisterAll() {
|
||||
avutil.DefaultHandlers.Add(mp4.Handler)
|
||||
avutil.DefaultHandlers.Add(ts.Handler)
|
||||
avutil.DefaultHandlers.Add(rtmp.Handler)
|
||||
avutil.DefaultHandlers.Add(rtsp.Handler)
|
||||
avutil.DefaultHandlers.Add(flv.Handler)
|
||||
avutil.DefaultHandlers.Add(aac.Handler)
|
||||
}
|
||||
|
446
vendor/github.com/datarhei/joy4/format/mp4/demuxer.go
generated
vendored
Normal file
446
vendor/github.com/datarhei/joy4/format/mp4/demuxer.go
generated
vendored
Normal file
@@ -0,0 +1,446 @@
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/codec/aacparser"
|
||||
"github.com/datarhei/joy4/codec/h264parser"
|
||||
"github.com/datarhei/joy4/format/mp4/mp4io"
|
||||
)
|
||||
|
||||
type Demuxer struct {
|
||||
r io.ReadSeeker
|
||||
streams []*Stream
|
||||
movieAtom *mp4io.Movie
|
||||
}
|
||||
|
||||
func NewDemuxer(r io.ReadSeeker) *Demuxer {
|
||||
return &Demuxer{
|
||||
r: 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)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) readat(pos int64, b []byte) (err error) {
|
||||
if _, err = self.r.Seek(pos, 0); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = io.ReadFull(self.r, b); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) probe() (err error) {
|
||||
if self.movieAtom != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var moov *mp4io.Movie
|
||||
var atoms []mp4io.Atom
|
||||
|
||||
if atoms, err = mp4io.ReadFileAtoms(self.r); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = self.r.Seek(0, 0); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, atom := range atoms {
|
||||
if atom.Tag() == mp4io.MOOV {
|
||||
moov = atom.(*mp4io.Movie)
|
||||
}
|
||||
}
|
||||
|
||||
if moov == nil {
|
||||
err = fmt.Errorf("mp4: 'moov' atom not found")
|
||||
return
|
||||
}
|
||||
|
||||
self.streams = []*Stream{}
|
||||
for i, atrack := range moov.Tracks {
|
||||
stream := &Stream{
|
||||
trackAtom: atrack,
|
||||
demuxer: self,
|
||||
idx: i,
|
||||
}
|
||||
if atrack.Media != nil && atrack.Media.Info != nil && atrack.Media.Info.Sample != nil {
|
||||
stream.sample = atrack.Media.Info.Sample
|
||||
stream.timeScale = int64(atrack.Media.Header.TimeScale)
|
||||
} else {
|
||||
err = fmt.Errorf("mp4: sample table not found")
|
||||
return
|
||||
}
|
||||
|
||||
if avc1 := atrack.GetAVC1Conf(); avc1 != nil {
|
||||
if stream.CodecData, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(avc1.Data); err != nil {
|
||||
return
|
||||
}
|
||||
self.streams = append(self.streams, stream)
|
||||
} else if esds := atrack.GetElemStreamDesc(); esds != nil {
|
||||
if stream.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(esds.DecConfig); err != nil {
|
||||
return
|
||||
}
|
||||
self.streams = append(self.streams, stream)
|
||||
}
|
||||
}
|
||||
|
||||
self.movieAtom = moov
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) setSampleIndex(index int) (err error) {
|
||||
found := false
|
||||
start := 0
|
||||
self.chunkGroupIndex = 0
|
||||
|
||||
for self.chunkIndex = range self.sample.ChunkOffset.Entries {
|
||||
if self.chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) &&
|
||||
uint32(self.chunkIndex+1) == self.sample.SampleToChunk.Entries[self.chunkGroupIndex+1].FirstChunk {
|
||||
self.chunkGroupIndex++
|
||||
}
|
||||
n := int(self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk)
|
||||
if index >= start && index < start+n {
|
||||
found = true
|
||||
self.sampleIndexInChunk = index - start
|
||||
break
|
||||
}
|
||||
start += n
|
||||
}
|
||||
if !found {
|
||||
err = fmt.Errorf("mp4: stream[%d]: cannot locate sample index in chunk", self.idx)
|
||||
return
|
||||
}
|
||||
|
||||
if self.sample.SampleSize.SampleSize != 0 {
|
||||
self.sampleOffsetInChunk = int64(self.sampleIndexInChunk) * int64(self.sample.SampleSize.SampleSize)
|
||||
} else {
|
||||
if index >= len(self.sample.SampleSize.Entries) {
|
||||
err = fmt.Errorf("mp4: stream[%d]: sample index out of range", self.idx)
|
||||
return
|
||||
}
|
||||
self.sampleOffsetInChunk = int64(0)
|
||||
for i := index - self.sampleIndexInChunk; i < index; i++ {
|
||||
self.sampleOffsetInChunk += int64(self.sample.SampleSize.Entries[i])
|
||||
}
|
||||
}
|
||||
|
||||
self.dts = int64(0)
|
||||
start = 0
|
||||
found = false
|
||||
self.sttsEntryIndex = 0
|
||||
for self.sttsEntryIndex < len(self.sample.TimeToSample.Entries) {
|
||||
entry := self.sample.TimeToSample.Entries[self.sttsEntryIndex]
|
||||
n := int(entry.Count)
|
||||
if index >= start && index < start+n {
|
||||
self.sampleIndexInSttsEntry = index - start
|
||||
self.dts += int64(index-start) * int64(entry.Duration)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
start += n
|
||||
self.dts += int64(n) * int64(entry.Duration)
|
||||
self.sttsEntryIndex++
|
||||
}
|
||||
if !found {
|
||||
err = fmt.Errorf("mp4: stream[%d]: cannot locate sample index in stts entry", self.idx)
|
||||
return
|
||||
}
|
||||
|
||||
if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 {
|
||||
start = 0
|
||||
found = false
|
||||
self.cttsEntryIndex = 0
|
||||
for self.cttsEntryIndex < len(self.sample.CompositionOffset.Entries) {
|
||||
n := int(self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Count)
|
||||
if index >= start && index < start+n {
|
||||
self.sampleIndexInCttsEntry = index - start
|
||||
found = true
|
||||
break
|
||||
}
|
||||
start += n
|
||||
self.cttsEntryIndex++
|
||||
}
|
||||
if !found {
|
||||
err = fmt.Errorf("mp4: stream[%d]: cannot locate sample index in ctts entry", self.idx)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if self.sample.SyncSample != nil {
|
||||
self.syncSampleIndex = 0
|
||||
for self.syncSampleIndex < len(self.sample.SyncSample.Entries)-1 {
|
||||
if self.sample.SyncSample.Entries[self.syncSampleIndex+1]-1 > uint32(index) {
|
||||
break
|
||||
}
|
||||
self.syncSampleIndex++
|
||||
}
|
||||
}
|
||||
|
||||
if false {
|
||||
fmt.Printf("mp4: stream[%d]: setSampleIndex chunkGroupIndex=%d chunkIndex=%d sampleOffsetInChunk=%d\n",
|
||||
self.idx, self.chunkGroupIndex, self.chunkIndex, self.sampleOffsetInChunk)
|
||||
}
|
||||
|
||||
self.sampleIndex = index
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) isSampleValid() bool {
|
||||
if self.chunkIndex >= len(self.sample.ChunkOffset.Entries) {
|
||||
return false
|
||||
}
|
||||
if self.chunkGroupIndex >= len(self.sample.SampleToChunk.Entries) {
|
||||
return false
|
||||
}
|
||||
if self.sttsEntryIndex >= len(self.sample.TimeToSample.Entries) {
|
||||
return false
|
||||
}
|
||||
if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 {
|
||||
if self.cttsEntryIndex >= len(self.sample.CompositionOffset.Entries) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if self.sample.SyncSample != nil {
|
||||
if self.syncSampleIndex >= len(self.sample.SyncSample.Entries) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if self.sample.SampleSize.SampleSize != 0 {
|
||||
if self.sampleIndex >= len(self.sample.SampleSize.Entries) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *Stream) incSampleIndex() (duration int64) {
|
||||
if false {
|
||||
fmt.Printf("incSampleIndex sampleIndex=%d sampleOffsetInChunk=%d sampleIndexInChunk=%d chunkGroupIndex=%d chunkIndex=%d\n",
|
||||
self.sampleIndex, self.sampleOffsetInChunk, self.sampleIndexInChunk, self.chunkGroupIndex, self.chunkIndex)
|
||||
}
|
||||
|
||||
self.sampleIndexInChunk++
|
||||
if uint32(self.sampleIndexInChunk) == self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk {
|
||||
self.chunkIndex++
|
||||
self.sampleIndexInChunk = 0
|
||||
self.sampleOffsetInChunk = int64(0)
|
||||
} else {
|
||||
if self.sample.SampleSize.SampleSize != 0 {
|
||||
self.sampleOffsetInChunk += int64(self.sample.SampleSize.SampleSize)
|
||||
} else {
|
||||
self.sampleOffsetInChunk += int64(self.sample.SampleSize.Entries[self.sampleIndex])
|
||||
}
|
||||
}
|
||||
|
||||
if self.chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) &&
|
||||
uint32(self.chunkIndex+1) == self.sample.SampleToChunk.Entries[self.chunkGroupIndex+1].FirstChunk {
|
||||
self.chunkGroupIndex++
|
||||
}
|
||||
|
||||
sttsEntry := self.sample.TimeToSample.Entries[self.sttsEntryIndex]
|
||||
duration = int64(sttsEntry.Duration)
|
||||
self.sampleIndexInSttsEntry++
|
||||
self.dts += duration
|
||||
if uint32(self.sampleIndexInSttsEntry) == sttsEntry.Count {
|
||||
self.sampleIndexInSttsEntry = 0
|
||||
self.sttsEntryIndex++
|
||||
}
|
||||
|
||||
if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 {
|
||||
self.sampleIndexInCttsEntry++
|
||||
if uint32(self.sampleIndexInCttsEntry) == self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Count {
|
||||
self.sampleIndexInCttsEntry = 0
|
||||
self.cttsEntryIndex++
|
||||
}
|
||||
}
|
||||
|
||||
if self.sample.SyncSample != nil {
|
||||
entries := self.sample.SyncSample.Entries
|
||||
if self.syncSampleIndex+1 < len(entries) && entries[self.syncSampleIndex+1]-1 == uint32(self.sampleIndex+1) {
|
||||
self.syncSampleIndex++
|
||||
}
|
||||
}
|
||||
|
||||
self.sampleIndex++
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) sampleCount() int {
|
||||
if self.sample.SampleSize.SampleSize == 0 {
|
||||
chunkGroupIndex := 0
|
||||
count := 0
|
||||
for chunkIndex := range self.sample.ChunkOffset.Entries {
|
||||
n := int(self.sample.SampleToChunk.Entries[chunkGroupIndex].SamplesPerChunk)
|
||||
count += n
|
||||
if chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) &&
|
||||
uint32(chunkIndex+1) == self.sample.SampleToChunk.Entries[chunkGroupIndex+1].FirstChunk {
|
||||
chunkGroupIndex++
|
||||
}
|
||||
}
|
||||
return count
|
||||
} else {
|
||||
return len(self.sample.SampleSize.Entries)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||
if err = self.probe(); err != nil {
|
||||
return
|
||||
}
|
||||
if len(self.streams) == 0 {
|
||||
err = errors.New("mp4: no streams available while trying to read a packet")
|
||||
return
|
||||
}
|
||||
|
||||
var chosen *Stream
|
||||
var chosenidx int
|
||||
for i, stream := range self.streams {
|
||||
if chosen == nil || stream.tsToTime(stream.dts) < chosen.tsToTime(chosen.dts) {
|
||||
chosen = stream
|
||||
chosenidx = i
|
||||
}
|
||||
}
|
||||
if false {
|
||||
fmt.Printf("ReadPacket: chosen index=%v time=%v\n", chosen.idx, chosen.tsToTime(chosen.dts))
|
||||
}
|
||||
tm := chosen.tsToTime(chosen.dts)
|
||||
if pkt, err = chosen.readPacket(); err != nil {
|
||||
return
|
||||
}
|
||||
pkt.Time = tm
|
||||
pkt.Idx = int8(chosenidx)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) CurrentTime() (tm time.Duration) {
|
||||
if len(self.streams) > 0 {
|
||||
stream := self.streams[0]
|
||||
tm = stream.tsToTime(stream.dts)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) SeekToTime(tm time.Duration) (err error) {
|
||||
for _, stream := range self.streams {
|
||||
if stream.Type().IsVideo() {
|
||||
if err = stream.seekToTime(tm); err != nil {
|
||||
return
|
||||
}
|
||||
tm = stream.tsToTime(stream.dts)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, stream := range self.streams {
|
||||
if !stream.Type().IsVideo() {
|
||||
if err = stream.seekToTime(tm); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) readPacket() (pkt av.Packet, err error) {
|
||||
if !self.isSampleValid() {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
//fmt.Println("readPacket", self.sampleIndex)
|
||||
|
||||
chunkOffset := self.sample.ChunkOffset.Entries[self.chunkIndex]
|
||||
sampleSize := uint32(0)
|
||||
if self.sample.SampleSize.SampleSize != 0 {
|
||||
sampleSize = self.sample.SampleSize.SampleSize
|
||||
} else {
|
||||
sampleSize = self.sample.SampleSize.Entries[self.sampleIndex]
|
||||
}
|
||||
|
||||
sampleOffset := int64(chunkOffset) + self.sampleOffsetInChunk
|
||||
pkt.Data = make([]byte, sampleSize)
|
||||
if err = self.demuxer.readat(sampleOffset, pkt.Data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if self.sample.SyncSample != nil {
|
||||
if self.sample.SyncSample.Entries[self.syncSampleIndex]-1 == uint32(self.sampleIndex) {
|
||||
pkt.IsKeyFrame = true
|
||||
}
|
||||
}
|
||||
|
||||
//println("pts/dts", self.ptsEntryIndex, self.dtsEntryIndex)
|
||||
if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 {
|
||||
cts := int64(self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Offset)
|
||||
pkt.CompositionTime = self.tsToTime(cts)
|
||||
}
|
||||
|
||||
self.incSampleIndex()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) seekToTime(tm time.Duration) (err error) {
|
||||
index := self.timeToSampleIndex(tm)
|
||||
if err = self.setSampleIndex(index); err != nil {
|
||||
return
|
||||
}
|
||||
if false {
|
||||
fmt.Printf("stream[%d]: seekToTime index=%v time=%v cur=%v\n", self.idx, index, tm, self.tsToTime(self.dts))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) timeToSampleIndex(tm time.Duration) int {
|
||||
targetTs := self.timeToTs(tm)
|
||||
targetIndex := 0
|
||||
|
||||
startTs := int64(0)
|
||||
endTs := int64(0)
|
||||
startIndex := 0
|
||||
endIndex := 0
|
||||
found := false
|
||||
for _, entry := range self.sample.TimeToSample.Entries {
|
||||
endTs = startTs + int64(entry.Count*entry.Duration)
|
||||
endIndex = startIndex + int(entry.Count)
|
||||
if targetTs >= startTs && targetTs < endTs {
|
||||
targetIndex = startIndex + int((targetTs-startTs)/int64(entry.Duration))
|
||||
found = true
|
||||
}
|
||||
startTs = endTs
|
||||
startIndex = endIndex
|
||||
}
|
||||
if !found {
|
||||
if targetTs < 0 {
|
||||
targetIndex = 0
|
||||
} else {
|
||||
targetIndex = endIndex - 1
|
||||
}
|
||||
}
|
||||
|
||||
if self.sample.SyncSample != nil {
|
||||
entries := self.sample.SyncSample.Entries
|
||||
for i := len(entries) - 1; i >= 0; i-- {
|
||||
if entries[i]-1 < uint32(targetIndex) {
|
||||
targetIndex = int(entries[i] - 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targetIndex
|
||||
}
|
32
vendor/github.com/datarhei/joy4/format/mp4/handler.go
generated
vendored
Normal file
32
vendor/github.com/datarhei/joy4/format/mp4/handler.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"io"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/av/avutil"
|
||||
)
|
||||
|
||||
var CodecTypes = []av.CodecType{av.H264, av.AAC}
|
||||
|
||||
func Handler(h *avutil.RegisterHandler) {
|
||||
h.Ext = ".mp4"
|
||||
|
||||
h.Probe = func(b []byte) bool {
|
||||
switch string(b[4:8]) {
|
||||
case "moov","ftyp","free","mdat","moof":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
h.ReaderDemuxer = func(r io.Reader) av.Demuxer {
|
||||
return NewDemuxer(r.(io.ReadSeeker))
|
||||
}
|
||||
|
||||
h.WriterMuxer = func(w io.Writer) av.Muxer {
|
||||
return NewMuxer(w.(io.WriteSeeker))
|
||||
}
|
||||
|
||||
h.CodecTypes = CodecTypes
|
||||
}
|
||||
|
3528
vendor/github.com/datarhei/joy4/format/mp4/mp4io/atoms.go
generated
vendored
Normal file
3528
vendor/github.com/datarhei/joy4/format/mp4/mp4io/atoms.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
503
vendor/github.com/datarhei/joy4/format/mp4/mp4io/mp4io.go
generated
vendored
Normal file
503
vendor/github.com/datarhei/joy4/format/mp4/mp4io/mp4io.go
generated
vendored
Normal file
@@ -0,0 +1,503 @@
|
||||
|
||||
package mp4io
|
||||
|
||||
import (
|
||||
"github.com/datarhei/joy4/utils/bits/pio"
|
||||
"os"
|
||||
"io"
|
||||
"fmt"
|
||||
"time"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ParseError struct {
|
||||
Debug string
|
||||
Offset int
|
||||
prev *ParseError
|
||||
}
|
||||
|
||||
func (self *ParseError) Error() string {
|
||||
s := []string{}
|
||||
for p := self; p != nil; p = p.prev {
|
||||
s = append(s, fmt.Sprintf("%s:%d", p.Debug, p.Offset))
|
||||
}
|
||||
return "mp4io: parse error: "+strings.Join(s, ",")
|
||||
}
|
||||
|
||||
func parseErr(debug string, offset int, prev error) (err error) {
|
||||
_prev, _ := prev.(*ParseError)
|
||||
return &ParseError{Debug: debug, Offset: offset, prev: _prev}
|
||||
}
|
||||
|
||||
func GetTime32(b []byte) (t time.Time) {
|
||||
sec := pio.U32BE(b)
|
||||
t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
t = t.Add(time.Second*time.Duration(sec))
|
||||
return
|
||||
}
|
||||
|
||||
func PutTime32(b []byte, t time.Time) {
|
||||
dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC))
|
||||
sec := uint32(dur/time.Second)
|
||||
pio.PutU32BE(b, sec)
|
||||
}
|
||||
|
||||
func GetTime64(b []byte) (t time.Time) {
|
||||
sec := pio.U64BE(b)
|
||||
t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
t = t.Add(time.Second*time.Duration(sec))
|
||||
return
|
||||
}
|
||||
|
||||
func PutTime64(b []byte, t time.Time) {
|
||||
dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC))
|
||||
sec := uint64(dur/time.Second)
|
||||
pio.PutU64BE(b, sec)
|
||||
}
|
||||
|
||||
func PutFixed16(b []byte, f float64) {
|
||||
intpart, fracpart := math.Modf(f)
|
||||
b[0] = uint8(intpart)
|
||||
b[1] = uint8(fracpart*256.0)
|
||||
}
|
||||
|
||||
func GetFixed16(b []byte) float64 {
|
||||
return float64(b[0])+float64(b[1])/256.0
|
||||
}
|
||||
|
||||
func PutFixed32(b []byte, f float64) {
|
||||
intpart, fracpart := math.Modf(f)
|
||||
pio.PutU16BE(b[0:2], uint16(intpart))
|
||||
pio.PutU16BE(b[2:4], uint16(fracpart*65536.0))
|
||||
}
|
||||
|
||||
func GetFixed32(b []byte) float64 {
|
||||
return float64(pio.U16BE(b[0:2]))+float64(pio.U16BE(b[2:4]))/65536.0
|
||||
}
|
||||
|
||||
type Tag uint32
|
||||
|
||||
func (self Tag) String() string {
|
||||
var b [4]byte
|
||||
pio.PutU32BE(b[:], uint32(self))
|
||||
for i := 0; i < 4; i++ {
|
||||
if b[i] == 0 {
|
||||
b[i] = ' '
|
||||
}
|
||||
}
|
||||
return string(b[:])
|
||||
}
|
||||
|
||||
type Atom interface{
|
||||
Pos() (int,int)
|
||||
Tag() Tag
|
||||
Marshal([]byte) int
|
||||
Unmarshal([]byte, int) (int,error)
|
||||
Len() int
|
||||
Children() []Atom
|
||||
}
|
||||
|
||||
type AtomPos struct {
|
||||
Offset int
|
||||
Size int
|
||||
}
|
||||
|
||||
func (self AtomPos) Pos() (int,int) {
|
||||
return self.Offset, self.Size
|
||||
}
|
||||
|
||||
func (self *AtomPos) setPos(offset int, size int) {
|
||||
self.Offset, self.Size = offset, size
|
||||
}
|
||||
|
||||
type Dummy struct {
|
||||
Data []byte
|
||||
Tag_ Tag
|
||||
AtomPos
|
||||
}
|
||||
|
||||
func (self Dummy) Children() []Atom {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self Dummy) Tag() Tag {
|
||||
return self.Tag_
|
||||
}
|
||||
|
||||
func (self Dummy) Len() int {
|
||||
return len(self.Data)
|
||||
}
|
||||
|
||||
func (self Dummy) Marshal(b []byte) int {
|
||||
copy(b, self.Data)
|
||||
return len(self.Data)
|
||||
}
|
||||
|
||||
func (self *Dummy) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||
(&self.AtomPos).setPos(offset, len(b))
|
||||
self.Data = b
|
||||
n = len(b)
|
||||
return
|
||||
}
|
||||
|
||||
func StringToTag(tag string) Tag {
|
||||
var b [4]byte
|
||||
copy(b[:], []byte(tag))
|
||||
return Tag(pio.U32BE(b[:]))
|
||||
}
|
||||
|
||||
func FindChildrenByName(root Atom, tag string) Atom {
|
||||
return FindChildren(root, StringToTag(tag))
|
||||
}
|
||||
|
||||
func FindChildren(root Atom, tag Tag) Atom {
|
||||
if root.Tag() == tag {
|
||||
return root
|
||||
}
|
||||
for _, child := range root.Children() {
|
||||
if r := FindChildren(child, tag); r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
TFHD_BASE_DATA_OFFSET = 0x01
|
||||
TFHD_STSD_ID = 0x02
|
||||
TFHD_DEFAULT_DURATION = 0x08
|
||||
TFHD_DEFAULT_SIZE = 0x10
|
||||
TFHD_DEFAULT_FLAGS = 0x20
|
||||
TFHD_DURATION_IS_EMPTY = 0x010000
|
||||
TFHD_DEFAULT_BASE_IS_MOOF = 0x020000
|
||||
)
|
||||
|
||||
const (
|
||||
TRUN_DATA_OFFSET = 0x01
|
||||
TRUN_FIRST_SAMPLE_FLAGS = 0x04
|
||||
TRUN_SAMPLE_DURATION = 0x100
|
||||
TRUN_SAMPLE_SIZE = 0x200
|
||||
TRUN_SAMPLE_FLAGS = 0x400
|
||||
TRUN_SAMPLE_CTS = 0x800
|
||||
)
|
||||
|
||||
const (
|
||||
MP4ESDescrTag = 3
|
||||
MP4DecConfigDescrTag = 4
|
||||
MP4DecSpecificDescrTag = 5
|
||||
)
|
||||
|
||||
type ElemStreamDesc struct {
|
||||
DecConfig []byte
|
||||
TrackId uint16
|
||||
AtomPos
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) Children() []Atom {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillLength(b []byte, length int) (n int) {
|
||||
for i := 3; i > 0; i-- {
|
||||
b[n] = uint8(length>>uint(7*i))&0x7f|0x80
|
||||
n++
|
||||
}
|
||||
b[n] = uint8(length&0x7f)
|
||||
n++
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) lenDescHdr() (n int) {
|
||||
return 5
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillDescHdr(b []byte, tag uint8, datalen int) (n int) {
|
||||
b[n] = tag
|
||||
n++
|
||||
n += self.fillLength(b[n:], datalen)
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) lenESDescHdr() (n int) {
|
||||
return self.lenDescHdr()+3
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillESDescHdr(b []byte, datalen int) (n int) {
|
||||
n += self.fillDescHdr(b[n:], MP4ESDescrTag, datalen)
|
||||
pio.PutU16BE(b[n:], self.TrackId)
|
||||
n += 2
|
||||
b[n] = 0 // flags
|
||||
n++
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) lenDecConfigDescHdr() (n int) {
|
||||
return self.lenDescHdr()+2+3+4+4+self.lenDescHdr()
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) fillDecConfigDescHdr(b []byte, datalen int) (n int) {
|
||||
n += self.fillDescHdr(b[n:], MP4DecConfigDescrTag, datalen)
|
||||
b[n] = 0x40 // objectid
|
||||
n++
|
||||
b[n] = 0x15 // streamtype
|
||||
n++
|
||||
// buffer size db
|
||||
pio.PutU24BE(b[n:], 0)
|
||||
n += 3
|
||||
// max bitrage
|
||||
pio.PutU32BE(b[n:], uint32(200000))
|
||||
n += 4
|
||||
// avg bitrage
|
||||
pio.PutU32BE(b[n:], uint32(0))
|
||||
n += 4
|
||||
n += self.fillDescHdr(b[n:], MP4DecSpecificDescrTag, datalen-n)
|
||||
return
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) Len() (n int) {
|
||||
return 8+4+self.lenESDescHdr()+self.lenDecConfigDescHdr()+len(self.DecConfig)+self.lenDescHdr()+1
|
||||
}
|
||||
|
||||
// Version(4)
|
||||
// ESDesc(
|
||||
// MP4ESDescrTag
|
||||
// ESID(2)
|
||||
// ESFlags(1)
|
||||
// DecConfigDesc(
|
||||
// MP4DecConfigDescrTag
|
||||
// objectId streamType bufSize avgBitrate
|
||||
// DecSpecificDesc(
|
||||
// MP4DecSpecificDescrTag
|
||||
// decConfig
|
||||
// )
|
||||
// )
|
||||
// ?Desc(lenDescHdr+1)
|
||||
// )
|
||||
|
||||
func (self ElemStreamDesc) Marshal(b []byte) (n int) {
|
||||
pio.PutU32BE(b[4:], uint32(ESDS))
|
||||
n += 8
|
||||
pio.PutU32BE(b[n:], 0) // Version
|
||||
n += 4
|
||||
datalen := self.Len()
|
||||
n += self.fillESDescHdr(b[n:], datalen-n-self.lenESDescHdr())
|
||||
n += self.fillDecConfigDescHdr(b[n:], datalen-n-self.lenDescHdr()-1)
|
||||
copy(b[n:], self.DecConfig)
|
||||
n += len(self.DecConfig)
|
||||
n += self.fillDescHdr(b[n:], 0x06, datalen-n-self.lenDescHdr())
|
||||
b[n] = 0x02
|
||||
n++
|
||||
pio.PutU32BE(b[0:], uint32(n))
|
||||
return
|
||||
}
|
||||
|
||||
func (self *ElemStreamDesc) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||
if len(b) < n+12 {
|
||||
err = parseErr("hdr", offset+n, err)
|
||||
return
|
||||
}
|
||||
(&self.AtomPos).setPos(offset, len(b))
|
||||
n += 8
|
||||
n += 4
|
||||
return self.parseDesc(b[n:], offset+n)
|
||||
}
|
||||
|
||||
func (self *ElemStreamDesc) parseDesc(b []byte, offset int) (n int, err error) {
|
||||
var hdrlen int
|
||||
var datalen int
|
||||
var tag uint8
|
||||
if hdrlen, tag, datalen, err = self.parseDescHdr(b, offset); err != nil {
|
||||
return
|
||||
}
|
||||
n += hdrlen
|
||||
|
||||
if len(b) < n+datalen {
|
||||
err = parseErr("datalen", offset+n, err)
|
||||
return
|
||||
}
|
||||
|
||||
switch tag {
|
||||
case MP4ESDescrTag:
|
||||
if len(b) < n+3 {
|
||||
err = parseErr("MP4ESDescrTag", offset+n, err)
|
||||
return
|
||||
}
|
||||
if _, err = self.parseDesc(b[n+3:], offset+n+3); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case MP4DecConfigDescrTag:
|
||||
const size = 2+3+4+4
|
||||
if len(b) < n+size {
|
||||
err = parseErr("MP4DecSpecificDescrTag", offset+n, err)
|
||||
return
|
||||
}
|
||||
if _, err = self.parseDesc(b[n+size:], offset+n+size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case MP4DecSpecificDescrTag:
|
||||
self.DecConfig = b[n:]
|
||||
}
|
||||
|
||||
n += datalen
|
||||
return
|
||||
}
|
||||
|
||||
func (self *ElemStreamDesc) parseLength(b []byte, offset int) (n int, length int, err error) {
|
||||
for n < 4 {
|
||||
if len(b) < n+1 {
|
||||
err = parseErr("len", offset+n, err)
|
||||
return
|
||||
}
|
||||
c := b[n]
|
||||
n++
|
||||
length = (length<<7)|(int(c)&0x7f)
|
||||
if c&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *ElemStreamDesc) parseDescHdr(b []byte, offset int) (n int, tag uint8, datalen int, err error) {
|
||||
if len(b) < n+1 {
|
||||
err = parseErr("tag", offset+n, err)
|
||||
return
|
||||
}
|
||||
tag = b[n]
|
||||
n++
|
||||
var lenlen int
|
||||
if lenlen, datalen, err = self.parseLength(b[n:], offset+n); err != nil {
|
||||
return
|
||||
}
|
||||
n += lenlen
|
||||
return
|
||||
}
|
||||
|
||||
func ReadFileAtoms(r io.ReadSeeker) (atoms []Atom, err error) {
|
||||
for {
|
||||
offset, _ := r.Seek(0, 1)
|
||||
taghdr := make([]byte, 8)
|
||||
if _, err = io.ReadFull(r, taghdr); err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
size := pio.U32BE(taghdr[0:])
|
||||
tag := Tag(pio.U32BE(taghdr[4:]))
|
||||
|
||||
var atom Atom
|
||||
switch tag {
|
||||
case MOOV:
|
||||
atom = &Movie{}
|
||||
case MOOF:
|
||||
atom = &MovieFrag{}
|
||||
}
|
||||
|
||||
if atom != nil {
|
||||
b := make([]byte, int(size))
|
||||
if _, err = io.ReadFull(r, b[8:]); err != nil {
|
||||
return
|
||||
}
|
||||
copy(b, taghdr)
|
||||
if _, err = atom.Unmarshal(b, int(offset)); err != nil {
|
||||
return
|
||||
}
|
||||
atoms = append(atoms, atom)
|
||||
} else {
|
||||
dummy := &Dummy{Tag_: tag}
|
||||
dummy.setPos(int(offset), int(size))
|
||||
if _, err = r.Seek(int64(size)-8, 1); err != nil {
|
||||
return
|
||||
}
|
||||
atoms = append(atoms, dummy)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func printatom(out io.Writer, root Atom, depth int) {
|
||||
offset, size := root.Pos()
|
||||
|
||||
type stringintf interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
fmt.Fprintf(out,
|
||||
"%s%s offset=%d size=%d",
|
||||
strings.Repeat(" ", depth*2), root.Tag(), offset, size,
|
||||
)
|
||||
if str, ok := root.(stringintf); ok {
|
||||
fmt.Fprint(out, " ", str.String())
|
||||
}
|
||||
fmt.Fprintln(out)
|
||||
|
||||
children := root.Children()
|
||||
for _, child := range children {
|
||||
printatom(out, child, depth+1)
|
||||
}
|
||||
}
|
||||
|
||||
func FprintAtom(out io.Writer, root Atom) {
|
||||
printatom(out, root, 0)
|
||||
}
|
||||
|
||||
func PrintAtom(root Atom) {
|
||||
FprintAtom(os.Stdout, root)
|
||||
}
|
||||
|
||||
func (self MovieHeader) String() string {
|
||||
return fmt.Sprintf("dur=%d", self.Duration)
|
||||
}
|
||||
|
||||
func (self TimeToSample) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self SampleToChunk) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self SampleSize) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self SyncSample) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self CompositionOffset) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self ChunkOffset) String() string {
|
||||
return fmt.Sprintf("entries=%d", len(self.Entries))
|
||||
}
|
||||
|
||||
func (self TrackFragRun) String() string {
|
||||
return fmt.Sprintf("dataoffset=%d", self.DataOffset)
|
||||
}
|
||||
|
||||
func (self TrackFragHeader) String() string {
|
||||
return fmt.Sprintf("basedataoffset=%d", self.BaseDataOffset)
|
||||
}
|
||||
|
||||
func (self ElemStreamDesc) String() string {
|
||||
return fmt.Sprintf("configlen=%d", len(self.DecConfig))
|
||||
}
|
||||
|
||||
func (self *Track) GetAVC1Conf() (conf *AVC1Conf) {
|
||||
atom := FindChildren(self, AVCC)
|
||||
conf, _ = atom.(*AVC1Conf)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Track) GetElemStreamDesc() (esds *ElemStreamDesc) {
|
||||
atom := FindChildren(self, ESDS)
|
||||
esds, _ = atom.(*ElemStreamDesc)
|
||||
return
|
||||
}
|
||||
|
282
vendor/github.com/datarhei/joy4/format/mp4/muxer.go
generated
vendored
Normal file
282
vendor/github.com/datarhei/joy4/format/mp4/muxer.go
generated
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/codec/aacparser"
|
||||
"github.com/datarhei/joy4/codec/h264parser"
|
||||
"github.com/datarhei/joy4/format/mp4/mp4io"
|
||||
"github.com/datarhei/joy4/utils/bits/pio"
|
||||
"io"
|
||||
"bufio"
|
||||
)
|
||||
|
||||
type Muxer struct {
|
||||
w io.WriteSeeker
|
||||
bufw *bufio.Writer
|
||||
wpos int64
|
||||
streams []*Stream
|
||||
}
|
||||
|
||||
func NewMuxer(w io.WriteSeeker) *Muxer {
|
||||
return &Muxer{
|
||||
w: w,
|
||||
bufw: bufio.NewWriterSize(w, pio.RecommendBufioSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Muxer) newStream(codec av.CodecData) (err error) {
|
||||
switch codec.Type() {
|
||||
case av.H264, av.AAC:
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("mp4: codec type=%v is not supported", codec.Type())
|
||||
return
|
||||
}
|
||||
stream := &Stream{CodecData: codec}
|
||||
|
||||
stream.sample = &mp4io.SampleTable{
|
||||
SampleDesc: &mp4io.SampleDesc{},
|
||||
TimeToSample: &mp4io.TimeToSample{},
|
||||
SampleToChunk: &mp4io.SampleToChunk{
|
||||
Entries: []mp4io.SampleToChunkEntry{
|
||||
{
|
||||
FirstChunk: 1,
|
||||
SampleDescId: 1,
|
||||
SamplesPerChunk: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
SampleSize: &mp4io.SampleSize{},
|
||||
ChunkOffset: &mp4io.ChunkOffset{},
|
||||
}
|
||||
|
||||
stream.trackAtom = &mp4io.Track{
|
||||
Header: &mp4io.TrackHeader{
|
||||
TrackId: int32(len(self.streams)+1),
|
||||
Flags: 0x0003, // Track enabled | Track in movie
|
||||
Duration: 0, // fill later
|
||||
Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
|
||||
},
|
||||
Media: &mp4io.Media{
|
||||
Header: &mp4io.MediaHeader{
|
||||
TimeScale: 0, // fill later
|
||||
Duration: 0, // fill later
|
||||
Language: 21956,
|
||||
},
|
||||
Info: &mp4io.MediaInfo{
|
||||
Sample: stream.sample,
|
||||
Data: &mp4io.DataInfo{
|
||||
Refer: &mp4io.DataRefer{
|
||||
Url: &mp4io.DataReferUrl{
|
||||
Flags: 0x000001, // Self reference
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
switch codec.Type() {
|
||||
case av.H264:
|
||||
stream.sample.SyncSample = &mp4io.SyncSample{}
|
||||
}
|
||||
|
||||
stream.timeScale = 90000
|
||||
stream.muxer = self
|
||||
self.streams = append(self.streams, stream)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) fillTrackAtom() (err error) {
|
||||
self.trackAtom.Media.Header.TimeScale = int32(self.timeScale)
|
||||
self.trackAtom.Media.Header.Duration = int32(self.duration)
|
||||
|
||||
if self.Type() == av.H264 {
|
||||
codec := self.CodecData.(h264parser.CodecData)
|
||||
width, height := codec.Width(), codec.Height()
|
||||
self.sample.SampleDesc.AVC1Desc = &mp4io.AVC1Desc{
|
||||
DataRefIdx: 1,
|
||||
HorizontalResolution: 72,
|
||||
VorizontalResolution: 72,
|
||||
Width: int16(width),
|
||||
Height: int16(height),
|
||||
FrameCount: 1,
|
||||
Depth: 24,
|
||||
ColorTableId: -1,
|
||||
Conf: &mp4io.AVC1Conf{Data: codec.AVCDecoderConfRecordBytes()},
|
||||
}
|
||||
self.trackAtom.Media.Handler = &mp4io.HandlerRefer{
|
||||
SubType: [4]byte{'v','i','d','e'},
|
||||
Name: []byte("Video Media Handler"),
|
||||
}
|
||||
self.trackAtom.Media.Info.Video = &mp4io.VideoMediaInfo{
|
||||
Flags: 0x000001,
|
||||
}
|
||||
self.trackAtom.Header.TrackWidth = float64(width)
|
||||
self.trackAtom.Header.TrackHeight = float64(height)
|
||||
|
||||
} else if self.Type() == av.AAC {
|
||||
codec := self.CodecData.(aacparser.CodecData)
|
||||
self.sample.SampleDesc.MP4ADesc = &mp4io.MP4ADesc{
|
||||
DataRefIdx: 1,
|
||||
NumberOfChannels: int16(codec.ChannelLayout().Count()),
|
||||
SampleSize: int16(codec.SampleFormat().BytesPerSample()),
|
||||
SampleRate: float64(codec.SampleRate()),
|
||||
Conf: &mp4io.ElemStreamDesc{
|
||||
DecConfig: codec.MPEG4AudioConfigBytes(),
|
||||
},
|
||||
}
|
||||
self.trackAtom.Header.Volume = 1
|
||||
self.trackAtom.Header.AlternateGroup = 1
|
||||
self.trackAtom.Media.Handler = &mp4io.HandlerRefer{
|
||||
SubType: [4]byte{'s','o','u','n'},
|
||||
Name: []byte("Sound Handler"),
|
||||
}
|
||||
self.trackAtom.Media.Info.Sound = &mp4io.SoundMediaInfo{}
|
||||
|
||||
} else {
|
||||
err = fmt.Errorf("mp4: codec type=%d invalid", self.Type())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||
self.streams = []*Stream{}
|
||||
for _, stream := range streams {
|
||||
if err = self.newStream(stream); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
taghdr := make([]byte, 8)
|
||||
pio.PutU32BE(taghdr[4:], uint32(mp4io.MDAT))
|
||||
if _, err = self.w.Write(taghdr); err != nil {
|
||||
return
|
||||
}
|
||||
self.wpos += 8
|
||||
|
||||
for _, stream := range self.streams {
|
||||
if stream.Type().IsVideo() {
|
||||
stream.sample.CompositionOffset = &mp4io.CompositionOffset{}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WritePacket(pkt av.Packet) (err error) {
|
||||
stream := self.streams[pkt.Idx]
|
||||
if stream.lastpkt != nil {
|
||||
if err = stream.writePacket(*stream.lastpkt, pkt.Time-stream.lastpkt.Time); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
stream.lastpkt = &pkt
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) writePacket(pkt av.Packet, rawdur time.Duration) (err error) {
|
||||
if rawdur < 0 {
|
||||
err = fmt.Errorf("mp4: stream#%d time=%v < lasttime=%v", pkt.Idx, pkt.Time, self.lastpkt.Time)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = self.muxer.bufw.Write(pkt.Data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pkt.IsKeyFrame && self.sample.SyncSample != nil {
|
||||
self.sample.SyncSample.Entries = append(self.sample.SyncSample.Entries, uint32(self.sampleIndex+1))
|
||||
}
|
||||
|
||||
duration := uint32(self.timeToTs(rawdur))
|
||||
if self.sttsEntry == nil || duration != self.sttsEntry.Duration {
|
||||
self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, mp4io.TimeToSampleEntry{Duration: duration})
|
||||
self.sttsEntry = &self.sample.TimeToSample.Entries[len(self.sample.TimeToSample.Entries)-1]
|
||||
}
|
||||
self.sttsEntry.Count++
|
||||
|
||||
if self.sample.CompositionOffset != nil {
|
||||
offset := uint32(self.timeToTs(pkt.CompositionTime))
|
||||
if self.cttsEntry == nil || offset != self.cttsEntry.Offset {
|
||||
table := self.sample.CompositionOffset
|
||||
table.Entries = append(table.Entries, mp4io.CompositionOffsetEntry{Offset: offset})
|
||||
self.cttsEntry = &table.Entries[len(table.Entries)-1]
|
||||
}
|
||||
self.cttsEntry.Count++
|
||||
}
|
||||
|
||||
self.duration += int64(duration)
|
||||
self.sampleIndex++
|
||||
self.sample.ChunkOffset.Entries = append(self.sample.ChunkOffset.Entries, uint32(self.muxer.wpos))
|
||||
self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, uint32(len(pkt.Data)))
|
||||
|
||||
self.muxer.wpos += int64(len(pkt.Data))
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteTrailer() (err error) {
|
||||
for _, stream := range self.streams {
|
||||
if stream.lastpkt != nil {
|
||||
if err = stream.writePacket(*stream.lastpkt, 0); err != nil {
|
||||
return
|
||||
}
|
||||
stream.lastpkt = nil
|
||||
}
|
||||
}
|
||||
|
||||
moov := &mp4io.Movie{}
|
||||
moov.Header = &mp4io.MovieHeader{
|
||||
PreferredRate: 1,
|
||||
PreferredVolume: 1,
|
||||
Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
|
||||
NextTrackId: 2,
|
||||
}
|
||||
|
||||
maxDur := time.Duration(0)
|
||||
timeScale := int64(10000)
|
||||
for _, stream := range self.streams {
|
||||
if err = stream.fillTrackAtom(); err != nil {
|
||||
return
|
||||
}
|
||||
dur := stream.tsToTime(stream.duration)
|
||||
stream.trackAtom.Header.Duration = int32(timeToTs(dur, timeScale))
|
||||
if dur > maxDur {
|
||||
maxDur = dur
|
||||
}
|
||||
moov.Tracks = append(moov.Tracks, stream.trackAtom)
|
||||
}
|
||||
moov.Header.TimeScale = int32(timeScale)
|
||||
moov.Header.Duration = int32(timeToTs(maxDur, timeScale))
|
||||
|
||||
if err = self.bufw.Flush(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var mdatsize int64
|
||||
if mdatsize, err = self.w.Seek(0, 1); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = self.w.Seek(0, 0); err != nil {
|
||||
return
|
||||
}
|
||||
taghdr := make([]byte, 4)
|
||||
pio.PutU32BE(taghdr, uint32(mdatsize))
|
||||
if _, err = self.w.Write(taghdr); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = self.w.Seek(0, 2); err != nil {
|
||||
return
|
||||
}
|
||||
b := make([]byte, moov.Len())
|
||||
moov.Marshal(b)
|
||||
if _, err = self.w.Write(b); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
58
vendor/github.com/datarhei/joy4/format/mp4/stream.go
generated
vendored
Normal file
58
vendor/github.com/datarhei/joy4/format/mp4/stream.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/format/mp4/mp4io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Stream struct {
|
||||
av.CodecData
|
||||
|
||||
trackAtom *mp4io.Track
|
||||
idx int
|
||||
|
||||
lastpkt *av.Packet
|
||||
|
||||
timeScale int64
|
||||
duration int64
|
||||
|
||||
muxer *Muxer
|
||||
demuxer *Demuxer
|
||||
|
||||
sample *mp4io.SampleTable
|
||||
sampleIndex int
|
||||
|
||||
sampleOffsetInChunk int64
|
||||
syncSampleIndex int
|
||||
|
||||
dts int64
|
||||
sttsEntryIndex int
|
||||
sampleIndexInSttsEntry int
|
||||
|
||||
cttsEntryIndex int
|
||||
sampleIndexInCttsEntry int
|
||||
|
||||
chunkGroupIndex int
|
||||
chunkIndex int
|
||||
sampleIndexInChunk int
|
||||
|
||||
sttsEntry *mp4io.TimeToSampleEntry
|
||||
cttsEntry *mp4io.CompositionOffsetEntry
|
||||
}
|
||||
|
||||
func timeToTs(tm time.Duration, timeScale int64) int64 {
|
||||
return int64(tm*time.Duration(timeScale) / time.Second)
|
||||
}
|
||||
|
||||
func tsToTime(ts int64, timeScale int64) time.Duration {
|
||||
return time.Duration(ts)*time.Second / time.Duration(timeScale)
|
||||
}
|
||||
|
||||
func (self *Stream) timeToTs(tm time.Duration) int64 {
|
||||
return int64(tm*time.Duration(self.timeScale) / time.Second)
|
||||
}
|
||||
|
||||
func (self *Stream) tsToTime(ts int64) time.Duration {
|
||||
return time.Duration(ts)*time.Second / time.Duration(self.timeScale)
|
||||
}
|
1855
vendor/github.com/datarhei/joy4/format/rtmp/rtmp.go
generated
vendored
Normal file
1855
vendor/github.com/datarhei/joy4/format/rtmp/rtmp.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1239
vendor/github.com/datarhei/joy4/format/rtsp/client.go
generated
vendored
Normal file
1239
vendor/github.com/datarhei/joy4/format/rtsp/client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
25
vendor/github.com/datarhei/joy4/format/rtsp/conn.go
generated
vendored
Normal file
25
vendor/github.com/datarhei/joy4/format/rtsp/conn.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type connWithTimeout struct {
|
||||
Timeout time.Duration
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (self connWithTimeout) Read(p []byte) (n int, err error) {
|
||||
if self.Timeout > 0 {
|
||||
self.Conn.SetReadDeadline(time.Now().Add(self.Timeout))
|
||||
}
|
||||
return self.Conn.Read(p)
|
||||
}
|
||||
|
||||
func (self connWithTimeout) Write(p []byte) (n int, err error) {
|
||||
if self.Timeout > 0 {
|
||||
self.Conn.SetWriteDeadline(time.Now().Add(self.Timeout))
|
||||
}
|
||||
return self.Conn.Write(p)
|
||||
}
|
116
vendor/github.com/datarhei/joy4/format/rtsp/sdp/parser.go
generated
vendored
Normal file
116
vendor/github.com/datarhei/joy4/format/rtsp/sdp/parser.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
Uri string
|
||||
}
|
||||
|
||||
type Media struct {
|
||||
AVType string
|
||||
Type av.CodecType
|
||||
TimeScale int
|
||||
Control string
|
||||
Rtpmap int
|
||||
Config []byte
|
||||
SpropParameterSets [][]byte
|
||||
PayloadType int
|
||||
SizeLength int
|
||||
IndexLength int
|
||||
}
|
||||
|
||||
func Parse(content string) (sess Session, medias []Media) {
|
||||
var media *Media
|
||||
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
typeval := strings.SplitN(line, "=", 2)
|
||||
if len(typeval) == 2 {
|
||||
fields := strings.SplitN(typeval[1], " ", 2)
|
||||
|
||||
switch typeval[0] {
|
||||
case "m":
|
||||
if len(fields) > 0 {
|
||||
switch fields[0] {
|
||||
case "audio", "video":
|
||||
medias = append(medias, Media{AVType: fields[0]})
|
||||
media = &medias[len(medias)-1]
|
||||
mfields := strings.Split(fields[1], " ")
|
||||
if len(mfields) >= 3 {
|
||||
media.PayloadType, _ = strconv.Atoi(mfields[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "u":
|
||||
sess.Uri = typeval[1]
|
||||
|
||||
case "a":
|
||||
if media != nil {
|
||||
for _, field := range fields {
|
||||
keyval := strings.SplitN(field, ":", 2)
|
||||
if len(keyval) >= 2 {
|
||||
key := keyval[0]
|
||||
val := keyval[1]
|
||||
switch key {
|
||||
case "control":
|
||||
media.Control = val
|
||||
case "rtpmap":
|
||||
media.Rtpmap, _ = strconv.Atoi(val)
|
||||
}
|
||||
}
|
||||
keyval = strings.Split(field, "/")
|
||||
if len(keyval) >= 2 {
|
||||
key := keyval[0]
|
||||
switch strings.ToUpper(key) {
|
||||
case "MPEG4-GENERIC":
|
||||
media.Type = av.AAC
|
||||
case "H264":
|
||||
media.Type = av.H264
|
||||
}
|
||||
if i, err := strconv.Atoi(keyval[1]); err == nil {
|
||||
media.TimeScale = i
|
||||
}
|
||||
if false {
|
||||
fmt.Println("sdp:", keyval[1], media.TimeScale)
|
||||
}
|
||||
}
|
||||
keyval = strings.Split(field, ";")
|
||||
if len(keyval) > 1 {
|
||||
for _, field := range keyval {
|
||||
keyval := strings.SplitN(field, "=", 2)
|
||||
if len(keyval) == 2 {
|
||||
key := strings.TrimSpace(keyval[0])
|
||||
val := keyval[1]
|
||||
switch key {
|
||||
case "config":
|
||||
media.Config, _ = hex.DecodeString(val)
|
||||
case "sizelength":
|
||||
media.SizeLength, _ = strconv.Atoi(val)
|
||||
case "indexlength":
|
||||
media.IndexLength, _ = strconv.Atoi(val)
|
||||
case "sprop-parameter-sets":
|
||||
fields := strings.Split(val, ",")
|
||||
for _, field := range fields {
|
||||
val, _ := base64.StdEncoding.DecodeString(field)
|
||||
media.SpropParameterSets = append(media.SpropParameterSets, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
29
vendor/github.com/datarhei/joy4/format/rtsp/stream.go
generated
vendored
Normal file
29
vendor/github.com/datarhei/joy4/format/rtsp/stream.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/format/rtsp/sdp"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Stream struct {
|
||||
av.CodecData
|
||||
Sdp sdp.Media
|
||||
client *Client
|
||||
|
||||
// h264
|
||||
fuStarted bool
|
||||
fuBuffer []byte
|
||||
sps []byte
|
||||
pps []byte
|
||||
spsChanged bool
|
||||
ppsChanged bool
|
||||
|
||||
gotpkt bool
|
||||
pkt av.Packet
|
||||
timestamp uint32
|
||||
firsttimestamp uint32
|
||||
|
||||
lasttime time.Duration
|
||||
}
|
||||
|
290
vendor/github.com/datarhei/joy4/format/ts/demuxer.go
generated
vendored
Normal file
290
vendor/github.com/datarhei/joy4/format/ts/demuxer.go
generated
vendored
Normal file
@@ -0,0 +1,290 @@
|
||||
package ts
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/datarhei/joy4/utils/bits/pio"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/format/ts/tsio"
|
||||
"github.com/datarhei/joy4/codec/aacparser"
|
||||
"github.com/datarhei/joy4/codec/h264parser"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Demuxer struct {
|
||||
r *bufio.Reader
|
||||
|
||||
pkts []av.Packet
|
||||
|
||||
pat *tsio.PAT
|
||||
pmt *tsio.PMT
|
||||
streams []*Stream
|
||||
tshdr []byte
|
||||
|
||||
stage int
|
||||
}
|
||||
|
||||
func NewDemuxer(r io.Reader) *Demuxer {
|
||||
return &Demuxer{
|
||||
tshdr: make([]byte, 188),
|
||||
r: bufio.NewReaderSize(r, pio.RecommendBufioSize),
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) probe() (err error) {
|
||||
if self.stage == 0 {
|
||||
for {
|
||||
if self.pmt != nil {
|
||||
n := 0
|
||||
for _, stream := range self.streams {
|
||||
if stream.CodecData != nil {
|
||||
n++
|
||||
}
|
||||
}
|
||||
if n == len(self.streams) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err = self.poll(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
self.stage++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||
if err = self.probe(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for len(self.pkts) == 0 {
|
||||
if err = self.poll(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pkt = self.pkts[0]
|
||||
self.pkts = self.pkts[1:]
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) poll() (err error) {
|
||||
if err = self.readTSPacket(); err == io.EOF {
|
||||
var n int
|
||||
if n, err = self.payloadEnd(); err != nil {
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
err = io.EOF
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) initPMT(payload []byte) (err error) {
|
||||
var psihdrlen int
|
||||
var datalen int
|
||||
if _, _, psihdrlen, datalen, err = tsio.ParsePSI(payload); err != nil {
|
||||
return
|
||||
}
|
||||
self.pmt = &tsio.PMT{}
|
||||
if _, err = self.pmt.Unmarshal(payload[psihdrlen:psihdrlen+datalen]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
self.streams = []*Stream{}
|
||||
for i, info := range self.pmt.ElementaryStreamInfos {
|
||||
stream := &Stream{}
|
||||
stream.idx = i
|
||||
stream.demuxer = self
|
||||
stream.pid = info.ElementaryPID
|
||||
stream.streamType = info.StreamType
|
||||
switch info.StreamType {
|
||||
case tsio.ElementaryStreamTypeH264:
|
||||
self.streams = append(self.streams, stream)
|
||||
case tsio.ElementaryStreamTypeAdtsAAC:
|
||||
self.streams = append(self.streams, stream)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) payloadEnd() (n int, err error) {
|
||||
for _, stream := range self.streams {
|
||||
var i int
|
||||
if i, err = stream.payloadEnd(); err != nil {
|
||||
return
|
||||
}
|
||||
n += i
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Demuxer) readTSPacket() (err error) {
|
||||
var hdrlen int
|
||||
var pid uint16
|
||||
var start bool
|
||||
var iskeyframe bool
|
||||
|
||||
if _, err = io.ReadFull(self.r, self.tshdr); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pid, start, iskeyframe, hdrlen, err = tsio.ParseTSHeader(self.tshdr); err != nil {
|
||||
return
|
||||
}
|
||||
payload := self.tshdr[hdrlen:]
|
||||
|
||||
if self.pat == nil {
|
||||
if pid == 0 {
|
||||
var psihdrlen int
|
||||
var datalen int
|
||||
if _, _, psihdrlen, datalen, err = tsio.ParsePSI(payload); err != nil {
|
||||
return
|
||||
}
|
||||
self.pat = &tsio.PAT{}
|
||||
if _, err = self.pat.Unmarshal(payload[psihdrlen:psihdrlen+datalen]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if self.pmt == nil {
|
||||
for _, entry := range self.pat.Entries {
|
||||
if entry.ProgramMapPID == pid {
|
||||
if err = self.initPMT(payload); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, stream := range self.streams {
|
||||
if pid == stream.pid {
|
||||
if err = stream.handleTSPacket(start, iskeyframe, payload); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) addPacket(payload []byte, timedelta time.Duration) {
|
||||
dts := self.dts
|
||||
pts := self.pts
|
||||
if dts == 0 {
|
||||
dts = pts
|
||||
}
|
||||
|
||||
demuxer := self.demuxer
|
||||
pkt := av.Packet{
|
||||
Idx: int8(self.idx),
|
||||
IsKeyFrame: self.iskeyframe,
|
||||
Time: dts+timedelta,
|
||||
Data: payload,
|
||||
}
|
||||
if pts != dts {
|
||||
pkt.CompositionTime = pts-dts
|
||||
}
|
||||
demuxer.pkts = append(demuxer.pkts, pkt)
|
||||
}
|
||||
|
||||
func (self *Stream) payloadEnd() (n int, err error) {
|
||||
payload := self.data
|
||||
if payload == nil {
|
||||
return
|
||||
}
|
||||
if self.datalen != 0 && len(payload) != self.datalen {
|
||||
err = fmt.Errorf("ts: packet size mismatch size=%d correct=%d", len(payload), self.datalen)
|
||||
return
|
||||
}
|
||||
self.data = nil
|
||||
|
||||
switch self.streamType {
|
||||
case tsio.ElementaryStreamTypeAdtsAAC:
|
||||
var config aacparser.MPEG4AudioConfig
|
||||
|
||||
delta := time.Duration(0)
|
||||
for len(payload) > 0 {
|
||||
var hdrlen, framelen, samples int
|
||||
if config, hdrlen, framelen, samples, err = aacparser.ParseADTSHeader(payload); err != nil {
|
||||
return
|
||||
}
|
||||
if self.CodecData == nil {
|
||||
if self.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfig(config); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
self.addPacket(payload[hdrlen:framelen], delta)
|
||||
n++
|
||||
delta += time.Duration(samples) * time.Second / time.Duration(config.SampleRate)
|
||||
payload = payload[framelen:]
|
||||
}
|
||||
|
||||
case tsio.ElementaryStreamTypeH264:
|
||||
nalus, _ := h264parser.SplitNALUs(payload)
|
||||
var sps, pps []byte
|
||||
for _, nalu := range nalus {
|
||||
if len(nalu) > 0 {
|
||||
naltype := nalu[0] & 0x1f
|
||||
switch {
|
||||
case naltype == 7:
|
||||
sps = nalu
|
||||
case naltype == 8:
|
||||
pps = nalu
|
||||
case h264parser.IsDataNALU(nalu):
|
||||
// raw nalu to avcc
|
||||
b := make([]byte, 4+len(nalu))
|
||||
pio.PutU32BE(b[0:4], uint32(len(nalu)))
|
||||
copy(b[4:], nalu)
|
||||
self.addPacket(b, time.Duration(0))
|
||||
n++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.CodecData == nil && len(sps) > 0 && len(pps) > 0 {
|
||||
if self.CodecData, err = h264parser.NewCodecDataFromSPSAndPPS(sps, pps); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Stream) handleTSPacket(start bool, iskeyframe bool, payload []byte) (err error) {
|
||||
if start {
|
||||
if _, err = self.payloadEnd(); err != nil {
|
||||
return
|
||||
}
|
||||
var hdrlen int
|
||||
if hdrlen, _, self.datalen, self.pts, self.dts, err = tsio.ParsePESHeader(payload); err != nil {
|
||||
return
|
||||
}
|
||||
self.iskeyframe = iskeyframe
|
||||
if self.datalen == 0 {
|
||||
self.data = make([]byte, 0, 4096)
|
||||
} else {
|
||||
self.data = make([]byte, 0, self.datalen)
|
||||
}
|
||||
self.data = append(self.data, payload[hdrlen:]...)
|
||||
} else {
|
||||
self.data = append(self.data, payload...)
|
||||
}
|
||||
return
|
||||
}
|
26
vendor/github.com/datarhei/joy4/format/ts/handler.go
generated
vendored
Normal file
26
vendor/github.com/datarhei/joy4/format/ts/handler.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package ts
|
||||
|
||||
import (
|
||||
"io"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/av/avutil"
|
||||
)
|
||||
|
||||
func Handler(h *avutil.RegisterHandler) {
|
||||
h.Ext = ".ts"
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
h.CodecTypes = CodecTypes
|
||||
}
|
||||
|
205
vendor/github.com/datarhei/joy4/format/ts/muxer.go
generated
vendored
Normal file
205
vendor/github.com/datarhei/joy4/format/ts/muxer.go
generated
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
package ts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/codec/aacparser"
|
||||
"github.com/datarhei/joy4/codec/h264parser"
|
||||
"github.com/datarhei/joy4/format/ts/tsio"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var CodecTypes = []av.CodecType{av.H264, av.AAC}
|
||||
|
||||
type Muxer struct {
|
||||
w io.Writer
|
||||
streams []*Stream
|
||||
PaddingToMakeCounterCont bool
|
||||
|
||||
psidata []byte
|
||||
peshdr []byte
|
||||
tshdr []byte
|
||||
adtshdr []byte
|
||||
datav [][]byte
|
||||
nalus [][]byte
|
||||
|
||||
tswpat, tswpmt *tsio.TSWriter
|
||||
}
|
||||
|
||||
func NewMuxer(w io.Writer) *Muxer {
|
||||
return &Muxer{
|
||||
w: w,
|
||||
psidata: make([]byte, 188),
|
||||
peshdr: make([]byte, tsio.MaxPESHeaderLength),
|
||||
tshdr: make([]byte, tsio.MaxTSHeaderLength),
|
||||
adtshdr: make([]byte, aacparser.ADTSHeaderLength),
|
||||
nalus: make([][]byte, 16),
|
||||
datav: make([][]byte, 16),
|
||||
tswpmt: tsio.NewTSWriter(tsio.PMT_PID),
|
||||
tswpat: tsio.NewTSWriter(tsio.PAT_PID),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Muxer) newStream(codec av.CodecData) (err error) {
|
||||
ok := false
|
||||
for _, c := range CodecTypes {
|
||||
if codec.Type() == c {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
err = fmt.Errorf("ts: codec type=%s is not supported", codec.Type())
|
||||
return
|
||||
}
|
||||
|
||||
pid := uint16(len(self.streams) + 0x100)
|
||||
stream := &Stream{
|
||||
muxer: self,
|
||||
CodecData: codec,
|
||||
pid: pid,
|
||||
tsw: tsio.NewTSWriter(pid),
|
||||
}
|
||||
self.streams = append(self.streams, stream)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) writePaddingTSPackets(tsw *tsio.TSWriter) (err error) {
|
||||
for tsw.ContinuityCounter&0xf != 0x0 {
|
||||
if err = tsw.WritePackets(self.w, self.datav[:0], 0, false, true); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteTrailer() (err error) {
|
||||
if self.PaddingToMakeCounterCont {
|
||||
for _, stream := range self.streams {
|
||||
if err = self.writePaddingTSPackets(stream.tsw); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) SetWriter(w io.Writer) {
|
||||
self.w = w
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WritePATPMT() (err error) {
|
||||
pat := tsio.PAT{
|
||||
Entries: []tsio.PATEntry{
|
||||
{ProgramNumber: 1, ProgramMapPID: tsio.PMT_PID},
|
||||
},
|
||||
}
|
||||
patlen := pat.Marshal(self.psidata[tsio.PSIHeaderLength:])
|
||||
n := tsio.FillPSI(self.psidata, tsio.TableIdPAT, tsio.TableExtPAT, patlen)
|
||||
self.datav[0] = self.psidata[:n]
|
||||
if err = self.tswpat.WritePackets(self.w, self.datav[:1], 0, false, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var elemStreams []tsio.ElementaryStreamInfo
|
||||
for _, stream := range self.streams {
|
||||
switch stream.Type() {
|
||||
case av.AAC:
|
||||
elemStreams = append(elemStreams, tsio.ElementaryStreamInfo{
|
||||
StreamType: tsio.ElementaryStreamTypeAdtsAAC,
|
||||
ElementaryPID: stream.pid,
|
||||
})
|
||||
case av.H264:
|
||||
elemStreams = append(elemStreams, tsio.ElementaryStreamInfo{
|
||||
StreamType: tsio.ElementaryStreamTypeH264,
|
||||
ElementaryPID: stream.pid,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pmt := tsio.PMT{
|
||||
PCRPID: 0x100,
|
||||
ElementaryStreamInfos: elemStreams,
|
||||
}
|
||||
pmtlen := pmt.Len()
|
||||
if pmtlen+tsio.PSIHeaderLength > len(self.psidata) {
|
||||
err = fmt.Errorf("ts: pmt too large")
|
||||
return
|
||||
}
|
||||
pmt.Marshal(self.psidata[tsio.PSIHeaderLength:])
|
||||
n = tsio.FillPSI(self.psidata, tsio.TableIdPMT, tsio.TableExtPMT, pmtlen)
|
||||
self.datav[0] = self.psidata[:n]
|
||||
if err = self.tswpmt.WritePackets(self.w, self.datav[:1], 0, false, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||
self.streams = []*Stream{}
|
||||
for _, stream := range streams {
|
||||
if err = self.newStream(stream); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = self.WritePATPMT(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Muxer) WritePacket(pkt av.Packet) (err error) {
|
||||
stream := self.streams[pkt.Idx]
|
||||
pkt.Time += time.Second
|
||||
|
||||
switch stream.Type() {
|
||||
case av.AAC:
|
||||
codec := stream.CodecData.(aacparser.CodecData)
|
||||
|
||||
n := tsio.FillPESHeader(self.peshdr, tsio.StreamIdAAC, len(self.adtshdr)+len(pkt.Data), pkt.Time, 0)
|
||||
self.datav[0] = self.peshdr[:n]
|
||||
aacparser.FillADTSHeader(self.adtshdr, codec.Config, 1024, len(pkt.Data))
|
||||
self.datav[1] = self.adtshdr
|
||||
self.datav[2] = pkt.Data
|
||||
|
||||
if err = stream.tsw.WritePackets(self.w, self.datav[:3], pkt.Time, true, false); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case av.H264:
|
||||
codec := stream.CodecData.(h264parser.CodecData)
|
||||
|
||||
nalus := self.nalus[:0]
|
||||
if pkt.IsKeyFrame {
|
||||
nalus = append(nalus, codec.SPS())
|
||||
nalus = append(nalus, codec.PPS())
|
||||
}
|
||||
pktnalus, _ := h264parser.SplitNALUs(pkt.Data)
|
||||
for _, nalu := range pktnalus {
|
||||
nalus = append(nalus, nalu)
|
||||
}
|
||||
|
||||
datav := self.datav[:1]
|
||||
for i, nalu := range nalus {
|
||||
if i == 0 {
|
||||
datav = append(datav, h264parser.AUDBytes)
|
||||
} else {
|
||||
datav = append(datav, h264parser.StartCodeBytes)
|
||||
}
|
||||
datav = append(datav, nalu)
|
||||
}
|
||||
|
||||
n := tsio.FillPESHeader(self.peshdr, tsio.StreamIdH264, -1, pkt.Time+pkt.CompositionTime, pkt.Time)
|
||||
datav[0] = self.peshdr[:n]
|
||||
|
||||
if err = stream.tsw.WritePackets(self.w, datav, pkt.Time, pkt.IsKeyFrame, false); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
27
vendor/github.com/datarhei/joy4/format/ts/stream.go
generated
vendored
Normal file
27
vendor/github.com/datarhei/joy4/format/ts/stream.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package ts
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/datarhei/joy4/av"
|
||||
"github.com/datarhei/joy4/format/ts/tsio"
|
||||
)
|
||||
|
||||
type Stream struct {
|
||||
av.CodecData
|
||||
|
||||
demuxer *Demuxer
|
||||
muxer *Muxer
|
||||
|
||||
pid uint16
|
||||
streamId uint8
|
||||
streamType uint8
|
||||
|
||||
tsw *tsio.TSWriter
|
||||
idx int
|
||||
|
||||
iskeyframe bool
|
||||
pts, dts time.Duration
|
||||
data []byte
|
||||
datalen int
|
||||
}
|
||||
|
55
vendor/github.com/datarhei/joy4/format/ts/tsio/checksum.go
generated
vendored
Normal file
55
vendor/github.com/datarhei/joy4/format/ts/tsio/checksum.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package tsio
|
||||
|
||||
var ieeeCrc32Tbl = []uint32{
|
||||
0x00000000, 0xB71DC104, 0x6E3B8209, 0xD926430D, 0xDC760413, 0x6B6BC517,
|
||||
0xB24D861A, 0x0550471E, 0xB8ED0826, 0x0FF0C922, 0xD6D68A2F, 0x61CB4B2B,
|
||||
0x649B0C35, 0xD386CD31, 0x0AA08E3C, 0xBDBD4F38, 0x70DB114C, 0xC7C6D048,
|
||||
0x1EE09345, 0xA9FD5241, 0xACAD155F, 0x1BB0D45B, 0xC2969756, 0x758B5652,
|
||||
0xC836196A, 0x7F2BD86E, 0xA60D9B63, 0x11105A67, 0x14401D79, 0xA35DDC7D,
|
||||
0x7A7B9F70, 0xCD665E74, 0xE0B62398, 0x57ABE29C, 0x8E8DA191, 0x39906095,
|
||||
0x3CC0278B, 0x8BDDE68F, 0x52FBA582, 0xE5E66486, 0x585B2BBE, 0xEF46EABA,
|
||||
0x3660A9B7, 0x817D68B3, 0x842D2FAD, 0x3330EEA9, 0xEA16ADA4, 0x5D0B6CA0,
|
||||
0x906D32D4, 0x2770F3D0, 0xFE56B0DD, 0x494B71D9, 0x4C1B36C7, 0xFB06F7C3,
|
||||
0x2220B4CE, 0x953D75CA, 0x28803AF2, 0x9F9DFBF6, 0x46BBB8FB, 0xF1A679FF,
|
||||
0xF4F63EE1, 0x43EBFFE5, 0x9ACDBCE8, 0x2DD07DEC, 0x77708634, 0xC06D4730,
|
||||
0x194B043D, 0xAE56C539, 0xAB068227, 0x1C1B4323, 0xC53D002E, 0x7220C12A,
|
||||
0xCF9D8E12, 0x78804F16, 0xA1A60C1B, 0x16BBCD1F, 0x13EB8A01, 0xA4F64B05,
|
||||
0x7DD00808, 0xCACDC90C, 0x07AB9778, 0xB0B6567C, 0x69901571, 0xDE8DD475,
|
||||
0xDBDD936B, 0x6CC0526F, 0xB5E61162, 0x02FBD066, 0xBF469F5E, 0x085B5E5A,
|
||||
0xD17D1D57, 0x6660DC53, 0x63309B4D, 0xD42D5A49, 0x0D0B1944, 0xBA16D840,
|
||||
0x97C6A5AC, 0x20DB64A8, 0xF9FD27A5, 0x4EE0E6A1, 0x4BB0A1BF, 0xFCAD60BB,
|
||||
0x258B23B6, 0x9296E2B2, 0x2F2BAD8A, 0x98366C8E, 0x41102F83, 0xF60DEE87,
|
||||
0xF35DA999, 0x4440689D, 0x9D662B90, 0x2A7BEA94, 0xE71DB4E0, 0x500075E4,
|
||||
0x892636E9, 0x3E3BF7ED, 0x3B6BB0F3, 0x8C7671F7, 0x555032FA, 0xE24DF3FE,
|
||||
0x5FF0BCC6, 0xE8ED7DC2, 0x31CB3ECF, 0x86D6FFCB, 0x8386B8D5, 0x349B79D1,
|
||||
0xEDBD3ADC, 0x5AA0FBD8, 0xEEE00C69, 0x59FDCD6D, 0x80DB8E60, 0x37C64F64,
|
||||
0x3296087A, 0x858BC97E, 0x5CAD8A73, 0xEBB04B77, 0x560D044F, 0xE110C54B,
|
||||
0x38368646, 0x8F2B4742, 0x8A7B005C, 0x3D66C158, 0xE4408255, 0x535D4351,
|
||||
0x9E3B1D25, 0x2926DC21, 0xF0009F2C, 0x471D5E28, 0x424D1936, 0xF550D832,
|
||||
0x2C769B3F, 0x9B6B5A3B, 0x26D61503, 0x91CBD407, 0x48ED970A, 0xFFF0560E,
|
||||
0xFAA01110, 0x4DBDD014, 0x949B9319, 0x2386521D, 0x0E562FF1, 0xB94BEEF5,
|
||||
0x606DADF8, 0xD7706CFC, 0xD2202BE2, 0x653DEAE6, 0xBC1BA9EB, 0x0B0668EF,
|
||||
0xB6BB27D7, 0x01A6E6D3, 0xD880A5DE, 0x6F9D64DA, 0x6ACD23C4, 0xDDD0E2C0,
|
||||
0x04F6A1CD, 0xB3EB60C9, 0x7E8D3EBD, 0xC990FFB9, 0x10B6BCB4, 0xA7AB7DB0,
|
||||
0xA2FB3AAE, 0x15E6FBAA, 0xCCC0B8A7, 0x7BDD79A3, 0xC660369B, 0x717DF79F,
|
||||
0xA85BB492, 0x1F467596, 0x1A163288, 0xAD0BF38C, 0x742DB081, 0xC3307185,
|
||||
0x99908A5D, 0x2E8D4B59, 0xF7AB0854, 0x40B6C950, 0x45E68E4E, 0xF2FB4F4A,
|
||||
0x2BDD0C47, 0x9CC0CD43, 0x217D827B, 0x9660437F, 0x4F460072, 0xF85BC176,
|
||||
0xFD0B8668, 0x4A16476C, 0x93300461, 0x242DC565, 0xE94B9B11, 0x5E565A15,
|
||||
0x87701918, 0x306DD81C, 0x353D9F02, 0x82205E06, 0x5B061D0B, 0xEC1BDC0F,
|
||||
0x51A69337, 0xE6BB5233, 0x3F9D113E, 0x8880D03A, 0x8DD09724, 0x3ACD5620,
|
||||
0xE3EB152D, 0x54F6D429, 0x7926A9C5, 0xCE3B68C1, 0x171D2BCC, 0xA000EAC8,
|
||||
0xA550ADD6, 0x124D6CD2, 0xCB6B2FDF, 0x7C76EEDB, 0xC1CBA1E3, 0x76D660E7,
|
||||
0xAFF023EA, 0x18EDE2EE, 0x1DBDA5F0, 0xAAA064F4, 0x738627F9, 0xC49BE6FD,
|
||||
0x09FDB889, 0xBEE0798D, 0x67C63A80, 0xD0DBFB84, 0xD58BBC9A, 0x62967D9E,
|
||||
0xBBB03E93, 0x0CADFF97, 0xB110B0AF, 0x060D71AB, 0xDF2B32A6, 0x6836F3A2,
|
||||
0x6D66B4BC, 0xDA7B75B8, 0x035D36B5, 0xB440F7B1, 0x00000001,
|
||||
}
|
||||
|
||||
func calcCRC32(crc uint32, data []byte) uint32 {
|
||||
for _, b := range data {
|
||||
crc = ieeeCrc32Tbl[b^byte(crc)] ^ (crc >> 8)
|
||||
}
|
||||
return crc
|
||||
}
|
||||
|
590
vendor/github.com/datarhei/joy4/format/ts/tsio/tsio.go
generated
vendored
Normal file
590
vendor/github.com/datarhei/joy4/format/ts/tsio/tsio.go
generated
vendored
Normal file
@@ -0,0 +1,590 @@
|
||||
|
||||
package tsio
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
"fmt"
|
||||
"github.com/datarhei/joy4/utils/bits/pio"
|
||||
)
|
||||
|
||||
const (
|
||||
StreamIdH264 = 0xe0
|
||||
StreamIdAAC = 0xc0
|
||||
)
|
||||
|
||||
const (
|
||||
PAT_PID = 0
|
||||
PMT_PID = 0x1000
|
||||
)
|
||||
|
||||
const TableIdPMT = 2
|
||||
const TableExtPMT = 1
|
||||
|
||||
const TableIdPAT = 0
|
||||
const TableExtPAT = 1
|
||||
|
||||
const MaxPESHeaderLength = 19
|
||||
const MaxTSHeaderLength = 12
|
||||
|
||||
var ErrPESHeader = fmt.Errorf("invalid PES header")
|
||||
var ErrPSIHeader = fmt.Errorf("invalid PSI header")
|
||||
var ErrParsePMT = fmt.Errorf("invalid PMT")
|
||||
var ErrParsePAT = fmt.Errorf("invalid PAT")
|
||||
|
||||
const (
|
||||
ElementaryStreamTypeH264 = 0x1B
|
||||
ElementaryStreamTypeAdtsAAC = 0x0F
|
||||
)
|
||||
|
||||
type PATEntry struct {
|
||||
ProgramNumber uint16
|
||||
NetworkPID uint16
|
||||
ProgramMapPID uint16
|
||||
}
|
||||
|
||||
type PAT struct {
|
||||
Entries []PATEntry
|
||||
}
|
||||
|
||||
func (self PAT) Len() (n int) {
|
||||
return len(self.Entries)*4
|
||||
}
|
||||
|
||||
func (self PAT) Marshal(b []byte) (n int) {
|
||||
for _, entry := range self.Entries {
|
||||
pio.PutU16BE(b[n:], entry.ProgramNumber)
|
||||
n += 2
|
||||
if entry.ProgramNumber == 0 {
|
||||
pio.PutU16BE(b[n:], entry.NetworkPID&0x1fff|7<<13)
|
||||
n += 2
|
||||
} else {
|
||||
pio.PutU16BE(b[n:], entry.ProgramMapPID&0x1fff|7<<13)
|
||||
n += 2
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *PAT) Unmarshal(b []byte) (n int, err error) {
|
||||
for n < len(b) {
|
||||
if n+4 <= len(b) {
|
||||
var entry PATEntry
|
||||
entry.ProgramNumber = pio.U16BE(b[n:])
|
||||
n += 2
|
||||
if entry.ProgramNumber == 0 {
|
||||
entry.NetworkPID = pio.U16BE(b[n:])&0x1fff
|
||||
n += 2
|
||||
} else {
|
||||
entry.ProgramMapPID = pio.U16BE(b[n:])&0x1fff
|
||||
n += 2
|
||||
}
|
||||
self.Entries = append(self.Entries, entry)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if n < len(b) {
|
||||
err = ErrParsePAT
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Descriptor struct {
|
||||
Tag uint8
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type ElementaryStreamInfo struct {
|
||||
StreamType uint8
|
||||
ElementaryPID uint16
|
||||
Descriptors []Descriptor
|
||||
}
|
||||
|
||||
type PMT struct {
|
||||
PCRPID uint16
|
||||
ProgramDescriptors []Descriptor
|
||||
ElementaryStreamInfos []ElementaryStreamInfo
|
||||
}
|
||||
|
||||
func (self PMT) Len() (n int) {
|
||||
// 111(3)
|
||||
// PCRPID(13)
|
||||
n += 2
|
||||
|
||||
// desclen(16)
|
||||
n += 2
|
||||
|
||||
for _, desc := range self.ProgramDescriptors {
|
||||
n += 2+len(desc.Data)
|
||||
}
|
||||
|
||||
for _, info := range self.ElementaryStreamInfos {
|
||||
// streamType
|
||||
n += 1
|
||||
|
||||
// Reserved(3)
|
||||
// Elementary PID(13)
|
||||
n += 2
|
||||
|
||||
// Reserved(6)
|
||||
// ES Info length length(10)
|
||||
n += 2
|
||||
|
||||
for _, desc := range info.Descriptors {
|
||||
n += 2+len(desc.Data)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self PMT) fillDescs(b []byte, descs []Descriptor) (n int) {
|
||||
for _, desc := range descs {
|
||||
b[n] = desc.Tag
|
||||
n++
|
||||
b[n] = uint8(len(desc.Data))
|
||||
n++
|
||||
copy(b[n:], desc.Data)
|
||||
n += len(desc.Data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self PMT) Marshal(b []byte) (n int) {
|
||||
// 111(3)
|
||||
// PCRPID(13)
|
||||
pio.PutU16BE(b[n:], self.PCRPID|7<<13)
|
||||
n += 2
|
||||
|
||||
hold := n
|
||||
n += 2
|
||||
pos := n
|
||||
n += self.fillDescs(b[n:], self.ProgramDescriptors)
|
||||
desclen := n-pos
|
||||
pio.PutU16BE(b[hold:], uint16(desclen)|0xf<<12)
|
||||
|
||||
for _, info := range self.ElementaryStreamInfos {
|
||||
b[n] = info.StreamType
|
||||
n++
|
||||
|
||||
// Reserved(3)
|
||||
// Elementary PID(13)
|
||||
pio.PutU16BE(b[n:], info.ElementaryPID|7<<13)
|
||||
n += 2
|
||||
|
||||
hold := n
|
||||
n += 2
|
||||
pos := n
|
||||
n += self.fillDescs(b[n:], info.Descriptors)
|
||||
desclen := n-pos
|
||||
pio.PutU16BE(b[hold:], uint16(desclen)|0x3c<<10)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self PMT) parseDescs(b []byte) (descs []Descriptor, err error) {
|
||||
n := 0
|
||||
for n < len(b) {
|
||||
if n+2 <= len(b) {
|
||||
desc := Descriptor{}
|
||||
desc.Tag = b[n]
|
||||
desc.Data = make([]byte, b[n+1])
|
||||
n += 2
|
||||
if n+len(desc.Data) < len(b) {
|
||||
copy(desc.Data, b[n:])
|
||||
descs = append(descs, desc)
|
||||
n += len(desc.Data)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if n < len(b) {
|
||||
err = ErrParsePMT
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *PMT) Unmarshal(b []byte) (n int, err error) {
|
||||
if len(b) < n+4 {
|
||||
err = ErrParsePMT
|
||||
return
|
||||
}
|
||||
|
||||
// 111(3)
|
||||
// PCRPID(13)
|
||||
self.PCRPID = pio.U16BE(b[0:2])&0x1fff
|
||||
n += 2
|
||||
|
||||
// Reserved(4)=0xf
|
||||
// Reserved(2)=0x0
|
||||
// Program info length(10)
|
||||
desclen := int(pio.U16BE(b[2:4])&0x3ff)
|
||||
n += 2
|
||||
|
||||
if desclen > 0 {
|
||||
if len(b) < n+desclen {
|
||||
err = ErrParsePMT
|
||||
return
|
||||
}
|
||||
if self.ProgramDescriptors, err = self.parseDescs(b[n:n+desclen]); err != nil {
|
||||
return
|
||||
}
|
||||
n += desclen
|
||||
}
|
||||
|
||||
for n < len(b) {
|
||||
if len(b) < n+5 {
|
||||
err = ErrParsePMT
|
||||
return
|
||||
}
|
||||
|
||||
var info ElementaryStreamInfo
|
||||
info.StreamType = b[n]
|
||||
n++
|
||||
|
||||
// Reserved(3)
|
||||
// Elementary PID(13)
|
||||
info.ElementaryPID = pio.U16BE(b[n:])&0x1fff
|
||||
n += 2
|
||||
|
||||
// Reserved(6)
|
||||
// ES Info length(10)
|
||||
desclen := int(pio.U16BE(b[n:])&0x3ff)
|
||||
n += 2
|
||||
|
||||
if desclen > 0 {
|
||||
if len(b) < n+desclen {
|
||||
err = ErrParsePMT
|
||||
return
|
||||
}
|
||||
if info.Descriptors, err = self.parseDescs(b[n:n+desclen]); err != nil {
|
||||
return
|
||||
}
|
||||
n += desclen
|
||||
}
|
||||
|
||||
self.ElementaryStreamInfos = append(self.ElementaryStreamInfos, info)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ParsePSI(h []byte) (tableid uint8, tableext uint16, hdrlen int, datalen int, err error) {
|
||||
if len(h) < 8 {
|
||||
err = ErrPSIHeader
|
||||
return
|
||||
}
|
||||
|
||||
// pointer(8)
|
||||
pointer := h[0]
|
||||
hdrlen++
|
||||
if pointer > 0 {
|
||||
hdrlen += int(pointer)
|
||||
if len(h) < hdrlen {
|
||||
err = ErrPSIHeader
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(h) < hdrlen+12 {
|
||||
err = ErrPSIHeader
|
||||
return
|
||||
}
|
||||
|
||||
// table_id(8)
|
||||
tableid = h[hdrlen]
|
||||
hdrlen++
|
||||
|
||||
// section_syntax_indicator(1)=1,private_bit(1)=0,reserved(2)=3,unused(2)=0,section_length(10)
|
||||
datalen = int(pio.U16BE(h[hdrlen:]))&0x3ff - 9
|
||||
hdrlen += 2
|
||||
|
||||
if datalen < 0 {
|
||||
err = ErrPSIHeader
|
||||
return
|
||||
}
|
||||
|
||||
// Table ID extension(16)
|
||||
tableext = pio.U16BE(h[hdrlen:])
|
||||
hdrlen += 2
|
||||
|
||||
// resverd(2)=3
|
||||
// version(5)
|
||||
// Current_next_indicator(1)
|
||||
hdrlen++
|
||||
|
||||
// section_number(8)
|
||||
hdrlen++
|
||||
|
||||
// last_section_number(8)
|
||||
hdrlen++
|
||||
|
||||
// data
|
||||
|
||||
// crc(32)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const PSIHeaderLength = 9
|
||||
|
||||
func FillPSI(h []byte, tableid uint8, tableext uint16, datalen int) (n int) {
|
||||
// pointer(8)
|
||||
h[n] = 0
|
||||
n++
|
||||
|
||||
// table_id(8)
|
||||
h[n] = tableid
|
||||
n++
|
||||
|
||||
// section_syntax_indicator(1)=1,private_bit(1)=0,reserved(2)=3,unused(2)=0,section_length(10)
|
||||
pio.PutU16BE(h[n:], uint16(0xa<<12 | 2+3+4+datalen))
|
||||
n += 2
|
||||
|
||||
// Table ID extension(16)
|
||||
pio.PutU16BE(h[n:], tableext)
|
||||
n += 2
|
||||
|
||||
// resverd(2)=3,version(5)=0,Current_next_indicator(1)=1
|
||||
h[n] = 0x3<<6 | 1
|
||||
n++
|
||||
|
||||
// section_number(8)
|
||||
h[n] = 0
|
||||
n++
|
||||
|
||||
// last_section_number(8)
|
||||
h[n] = 0
|
||||
n++
|
||||
|
||||
n += datalen
|
||||
|
||||
crc := calcCRC32(0xffffffff, h[1:n])
|
||||
pio.PutU32LE(h[n:], crc)
|
||||
n += 4
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TimeToPCR(tm time.Duration) (pcr uint64) {
|
||||
// base(33)+resverd(6)+ext(9)
|
||||
ts := uint64(tm*PCR_HZ/time.Second)
|
||||
base := ts / 300
|
||||
ext := ts % 300
|
||||
pcr = base<<15 | 0x3f<<9 | ext
|
||||
return
|
||||
}
|
||||
|
||||
func PCRToTime(pcr uint64) (tm time.Duration) {
|
||||
base := pcr >> 15
|
||||
ext := pcr & 0x1ff
|
||||
ts := base*300 + ext
|
||||
tm = time.Duration(ts)*time.Second/time.Duration(PCR_HZ)
|
||||
return
|
||||
}
|
||||
|
||||
func TimeToTs(tm time.Duration) (v uint64) {
|
||||
ts := uint64(tm*PTS_HZ/time.Second)
|
||||
// 0010 PTS 32..30 1 PTS 29..15 1 PTS 14..00 1
|
||||
v = ((ts>>30)&0x7)<<33 | ((ts>>15)&0x7fff)<<17 | (ts&0x7fff)<<1 | 0x100010001
|
||||
return
|
||||
}
|
||||
|
||||
func TsToTime(v uint64) (tm time.Duration) {
|
||||
// 0010 PTS 32..30 1 PTS 29..15 1 PTS 14..00 1
|
||||
ts := (((v>>33)&0x7)<<30) | (((v>>17)&0x7fff) << 15) | ((v>>1)&0x7fff)
|
||||
tm = time.Duration(ts)*time.Second/time.Duration(PTS_HZ)
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
PTS_HZ = 90000
|
||||
PCR_HZ = 27000000
|
||||
)
|
||||
|
||||
func ParsePESHeader(h []byte) (hdrlen int, streamid uint8, datalen int, pts, dts time.Duration, err error) {
|
||||
if h[0] != 0 || h[1] != 0 || h[2] != 1 {
|
||||
err = ErrPESHeader
|
||||
return
|
||||
}
|
||||
streamid = h[3]
|
||||
|
||||
flags := h[7]
|
||||
hdrlen = int(h[8])+9
|
||||
|
||||
datalen = int(pio.U16BE(h[4:6]))
|
||||
if datalen > 0 {
|
||||
datalen -= int(h[8])+3
|
||||
}
|
||||
|
||||
const PTS = 1 << 7
|
||||
const DTS = 1 << 6
|
||||
|
||||
if flags&PTS != 0 {
|
||||
if len(h) < 14 {
|
||||
err = ErrPESHeader
|
||||
return
|
||||
}
|
||||
pts = TsToTime(pio.U40BE(h[9:14]))
|
||||
if flags&DTS != 0 {
|
||||
if len(h) < 19 {
|
||||
err = ErrPESHeader
|
||||
return
|
||||
}
|
||||
dts = TsToTime(pio.U40BE(h[14:19]))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func FillPESHeader(h []byte, streamid uint8, datalen int, pts, dts time.Duration) (n int) {
|
||||
h[0] = 0
|
||||
h[1] = 0
|
||||
h[2] = 1
|
||||
h[3] = streamid
|
||||
|
||||
const PTS = 1 << 7
|
||||
const DTS = 1 << 6
|
||||
|
||||
var flags uint8
|
||||
if pts != 0 {
|
||||
flags |= PTS
|
||||
if dts != 0 {
|
||||
flags |= DTS
|
||||
}
|
||||
}
|
||||
|
||||
if flags&PTS != 0 {
|
||||
n += 5
|
||||
}
|
||||
if flags&DTS != 0 {
|
||||
n += 5
|
||||
}
|
||||
|
||||
// packet_length(16) if zero then variable length
|
||||
// Specifies the number of bytes remaining in the packet after this field. Can be zero.
|
||||
// If the PES packet length is set to zero, the PES packet can be of any length.
|
||||
// A value of zero for the PES packet length can be used only when the PES packet payload is a **video** elementary stream.
|
||||
var pktlen uint16
|
||||
if datalen >= 0 {
|
||||
pktlen = uint16(datalen + n + 3)
|
||||
}
|
||||
pio.PutU16BE(h[4:6], pktlen)
|
||||
|
||||
h[6] = 2<<6|1 // resverd(6,2)=2,original_or_copy(0,1)=1
|
||||
h[7] = flags
|
||||
h[8] = uint8(n)
|
||||
|
||||
// pts(40)?
|
||||
// dts(40)?
|
||||
if flags&PTS != 0 {
|
||||
if flags&DTS != 0 {
|
||||
pio.PutU40BE(h[9:14], TimeToTs(pts)|3<<36)
|
||||
pio.PutU40BE(h[14:19], TimeToTs(dts)|1<<36)
|
||||
} else {
|
||||
pio.PutU40BE(h[9:14], TimeToTs(pts)|2<<36)
|
||||
}
|
||||
}
|
||||
|
||||
n += 9
|
||||
return
|
||||
}
|
||||
|
||||
type TSWriter struct {
|
||||
w io.Writer
|
||||
ContinuityCounter uint
|
||||
tshdr []byte
|
||||
}
|
||||
|
||||
func NewTSWriter(pid uint16) *TSWriter {
|
||||
w := &TSWriter{}
|
||||
w.tshdr = make([]byte, 188)
|
||||
w.tshdr[0] = 0x47
|
||||
pio.PutU16BE(w.tshdr[1:3], pid&0x1fff)
|
||||
for i := 6; i < 188; i++ {
|
||||
w.tshdr[i] = 0xff
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (self *TSWriter) WritePackets(w io.Writer, datav [][]byte, pcr time.Duration, sync bool, paddata bool) (err error) {
|
||||
datavlen := pio.VecLen(datav)
|
||||
writev := make([][]byte, len(datav))
|
||||
writepos := 0
|
||||
|
||||
for writepos < datavlen {
|
||||
self.tshdr[1] = self.tshdr[1]&0x1f
|
||||
self.tshdr[3] = byte(self.ContinuityCounter)&0xf|0x30
|
||||
self.tshdr[5] = 0 // flags
|
||||
hdrlen := 6
|
||||
self.ContinuityCounter++
|
||||
|
||||
if writepos == 0 {
|
||||
self.tshdr[1] = 0x40|self.tshdr[1] // Payload Unit Start Indicator
|
||||
if pcr != 0 {
|
||||
hdrlen += 6
|
||||
self.tshdr[5] = 0x10|self.tshdr[5] // PCR flag (Discontinuity indicator 0x80)
|
||||
pio.PutU48BE(self.tshdr[6:12], TimeToPCR(pcr))
|
||||
}
|
||||
if sync {
|
||||
self.tshdr[5] = 0x40|self.tshdr[5] // Random Access indicator
|
||||
}
|
||||
}
|
||||
|
||||
padtail := 0
|
||||
end := writepos + 188 - hdrlen
|
||||
if end > datavlen {
|
||||
if paddata {
|
||||
padtail = end - datavlen
|
||||
} else {
|
||||
hdrlen += end - datavlen
|
||||
}
|
||||
end = datavlen
|
||||
}
|
||||
n := pio.VecSliceTo(datav, writev, writepos, end)
|
||||
|
||||
self.tshdr[4] = byte(hdrlen)-5 // length
|
||||
if _, err = w.Write(self.tshdr[:hdrlen]); err != nil {
|
||||
return
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
if _, err = w.Write(writev[i]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if padtail > 0 {
|
||||
if _, err = w.Write(self.tshdr[188-padtail:188]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
writepos = end
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTSHeader(tshdr []byte) (pid uint16, start bool, iskeyframe bool, hdrlen int, err error) {
|
||||
// https://en.wikipedia.org/wiki/MPEG_transport_stream
|
||||
if tshdr[0] != 0x47 {
|
||||
err = fmt.Errorf("tshdr sync invalid")
|
||||
return
|
||||
}
|
||||
pid = uint16((tshdr[1]&0x1f))<<8|uint16(tshdr[2])
|
||||
start = tshdr[1]&0x40 != 0
|
||||
hdrlen += 4
|
||||
if tshdr[3]&0x20 != 0 {
|
||||
hdrlen += int(tshdr[4])+1
|
||||
iskeyframe = tshdr[5]&0x40 != 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
118
vendor/github.com/datarhei/joy4/utils/bits/bits.go
generated
vendored
Normal file
118
vendor/github.com/datarhei/joy4/utils/bits/bits.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
package bits
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
R io.Reader
|
||||
n int
|
||||
bits uint64
|
||||
}
|
||||
|
||||
func (self *Reader) ReadBits64(n int) (bits uint64, err error) {
|
||||
if self.n < n {
|
||||
var b [8]byte
|
||||
var got int
|
||||
want := (n - self.n + 7) / 8
|
||||
if got, err = self.R.Read(b[:want]); err != nil {
|
||||
return
|
||||
}
|
||||
if got < want {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
for i := 0; i < got; i++ {
|
||||
self.bits <<= 8
|
||||
self.bits |= uint64(b[i])
|
||||
}
|
||||
self.n += got * 8
|
||||
}
|
||||
bits = self.bits >> uint(self.n-n)
|
||||
self.bits ^= bits << uint(self.n-n)
|
||||
self.n -= n
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Reader) ReadBits(n int) (bits uint, err error) {
|
||||
var bits64 uint64
|
||||
if bits64, err = self.ReadBits64(n); err != nil {
|
||||
return
|
||||
}
|
||||
bits = uint(bits64)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Reader) Read(p []byte) (n int, err error) {
|
||||
for n < len(p) {
|
||||
want := 8
|
||||
if len(p)-n < want {
|
||||
want = len(p) - n
|
||||
}
|
||||
var bits uint64
|
||||
if bits, err = self.ReadBits64(want * 8); err != nil {
|
||||
break
|
||||
}
|
||||
for i := 0; i < want; i++ {
|
||||
p[n+i] = byte(bits >> uint((want-i-1)*8))
|
||||
}
|
||||
n += want
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
W io.Writer
|
||||
n int
|
||||
bits uint64
|
||||
}
|
||||
|
||||
func (self *Writer) WriteBits64(bits uint64, n int) (err error) {
|
||||
if self.n+n > 64 {
|
||||
move := uint(64 - self.n)
|
||||
mask := bits >> move
|
||||
self.bits = (self.bits << move) | mask
|
||||
self.n = 64
|
||||
if err = self.FlushBits(); err != nil {
|
||||
return
|
||||
}
|
||||
n -= int(move)
|
||||
bits ^= (mask << move)
|
||||
}
|
||||
self.bits = (self.bits << uint(n)) | bits
|
||||
self.n += n
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Writer) WriteBits(bits uint, n int) (err error) {
|
||||
return self.WriteBits64(uint64(bits), n)
|
||||
}
|
||||
|
||||
func (self *Writer) Write(p []byte) (n int, err error) {
|
||||
for n < len(p) {
|
||||
if err = self.WriteBits64(uint64(p[n]), 8); err != nil {
|
||||
return
|
||||
}
|
||||
n++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Writer) FlushBits() (err error) {
|
||||
if self.n > 0 {
|
||||
var b [8]byte
|
||||
bits := self.bits
|
||||
if self.n%8 != 0 {
|
||||
bits <<= uint(8 - (self.n % 8))
|
||||
}
|
||||
want := (self.n + 7) / 8
|
||||
for i := 0; i < want; i++ {
|
||||
b[i] = byte(bits >> uint((want-i-1)*8))
|
||||
}
|
||||
if _, err = self.W.Write(b[:want]); err != nil {
|
||||
return
|
||||
}
|
||||
self.n = 0
|
||||
}
|
||||
return
|
||||
}
|
65
vendor/github.com/datarhei/joy4/utils/bits/golomb_reader.go
generated
vendored
Normal file
65
vendor/github.com/datarhei/joy4/utils/bits/golomb_reader.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package bits
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type GolombBitReader struct {
|
||||
R io.Reader
|
||||
buf [1]byte
|
||||
left byte
|
||||
}
|
||||
|
||||
func (self *GolombBitReader) ReadBit() (res uint, err error) {
|
||||
if self.left == 0 {
|
||||
if _, err = self.R.Read(self.buf[:]); err != nil {
|
||||
return
|
||||
}
|
||||
self.left = 8
|
||||
}
|
||||
self.left--
|
||||
res = uint(self.buf[0]>>self.left) & 1
|
||||
return
|
||||
}
|
||||
|
||||
func (self *GolombBitReader) ReadBits(n int) (res uint, err error) {
|
||||
for i := 0; i < n; i++ {
|
||||
var bit uint
|
||||
if bit, err = self.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
res |= bit << uint(n-i-1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *GolombBitReader) ReadExponentialGolombCode() (res uint, err error) {
|
||||
i := 0
|
||||
for {
|
||||
var bit uint
|
||||
if bit, err = self.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
if !(bit == 0 && i < 32) {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if res, err = self.ReadBits(i); err != nil {
|
||||
return
|
||||
}
|
||||
res += (1 << uint(i)) - 1
|
||||
return
|
||||
}
|
||||
|
||||
func (self *GolombBitReader) ReadSE() (res uint, err error) {
|
||||
if res, err = self.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
if res&0x01 != 0 {
|
||||
res = (res + 1) / 2
|
||||
} else {
|
||||
res = -res / 2
|
||||
}
|
||||
return
|
||||
}
|
5
vendor/github.com/datarhei/joy4/utils/bits/pio/pio.go
generated
vendored
Normal file
5
vendor/github.com/datarhei/joy4/utils/bits/pio/pio.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
package pio
|
||||
|
||||
var RecommendBufioSize = 1024*64
|
||||
|
91
vendor/github.com/datarhei/joy4/utils/bits/pio/reader.go
generated
vendored
Normal file
91
vendor/github.com/datarhei/joy4/utils/bits/pio/reader.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
|
||||
package pio
|
||||
|
||||
func U8(b []byte) (i uint8) {
|
||||
return b[0]
|
||||
}
|
||||
|
||||
func U16BE(b []byte) (i uint16) {
|
||||
i = uint16(b[0])
|
||||
i <<= 8; i |= uint16(b[1])
|
||||
return
|
||||
}
|
||||
|
||||
func I16BE(b []byte) (i int16) {
|
||||
i = int16(b[0])
|
||||
i <<= 8; i |= int16(b[1])
|
||||
return
|
||||
}
|
||||
|
||||
func I24BE(b []byte) (i int32) {
|
||||
i = int32(int8(b[0]))
|
||||
i <<= 8; i |= int32(b[1])
|
||||
i <<= 8; i |= int32(b[2])
|
||||
return
|
||||
}
|
||||
|
||||
func U24BE(b []byte) (i uint32) {
|
||||
i = uint32(b[0])
|
||||
i <<= 8; i |= uint32(b[1])
|
||||
i <<= 8; i |= uint32(b[2])
|
||||
return
|
||||
}
|
||||
|
||||
func I32BE(b []byte) (i int32) {
|
||||
i = int32(int8(b[0]))
|
||||
i <<= 8; i |= int32(b[1])
|
||||
i <<= 8; i |= int32(b[2])
|
||||
i <<= 8; i |= int32(b[3])
|
||||
return
|
||||
}
|
||||
|
||||
func U32LE(b []byte) (i uint32) {
|
||||
i = uint32(b[3])
|
||||
i <<= 8; i |= uint32(b[2])
|
||||
i <<= 8; i |= uint32(b[1])
|
||||
i <<= 8; i |= uint32(b[0])
|
||||
return
|
||||
}
|
||||
|
||||
func U32BE(b []byte) (i uint32) {
|
||||
i = uint32(b[0])
|
||||
i <<= 8; i |= uint32(b[1])
|
||||
i <<= 8; i |= uint32(b[2])
|
||||
i <<= 8; i |= uint32(b[3])
|
||||
return
|
||||
}
|
||||
|
||||
func U40BE(b []byte) (i uint64) {
|
||||
i = uint64(b[0])
|
||||
i <<= 8; i |= uint64(b[1])
|
||||
i <<= 8; i |= uint64(b[2])
|
||||
i <<= 8; i |= uint64(b[3])
|
||||
i <<= 8; i |= uint64(b[4])
|
||||
return
|
||||
}
|
||||
|
||||
func U64BE(b []byte) (i uint64) {
|
||||
i = uint64(b[0])
|
||||
i <<= 8; i |= uint64(b[1])
|
||||
i <<= 8; i |= uint64(b[2])
|
||||
i <<= 8; i |= uint64(b[3])
|
||||
i <<= 8; i |= uint64(b[4])
|
||||
i <<= 8; i |= uint64(b[5])
|
||||
i <<= 8; i |= uint64(b[6])
|
||||
i <<= 8; i |= uint64(b[7])
|
||||
return
|
||||
}
|
||||
|
||||
func I64BE(b []byte) (i int64) {
|
||||
i = int64(int8(b[0]))
|
||||
i <<= 8; i |= int64(b[1])
|
||||
i <<= 8; i |= int64(b[2])
|
||||
i <<= 8; i |= int64(b[3])
|
||||
i <<= 8; i |= int64(b[4])
|
||||
i <<= 8; i |= int64(b[5])
|
||||
i <<= 8; i |= int64(b[6])
|
||||
i <<= 8; i |= int64(b[7])
|
||||
return
|
||||
}
|
||||
|
||||
|
69
vendor/github.com/datarhei/joy4/utils/bits/pio/vec.go
generated
vendored
Normal file
69
vendor/github.com/datarhei/joy4/utils/bits/pio/vec.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package pio
|
||||
|
||||
func VecLen(vec [][]byte) (n int) {
|
||||
for _, b := range vec {
|
||||
n += len(b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func VecSliceTo(in [][]byte, out [][]byte, s int, e int) (n int) {
|
||||
if s < 0 {
|
||||
s = 0
|
||||
}
|
||||
|
||||
if e >= 0 && e < s {
|
||||
panic("pio: VecSlice start > end")
|
||||
}
|
||||
|
||||
i := 0
|
||||
off := 0
|
||||
for s > 0 && i < len(in) {
|
||||
left := len(in[i])
|
||||
read := s
|
||||
if left < read {
|
||||
read = left
|
||||
}
|
||||
left -= read
|
||||
off += read
|
||||
s -= read
|
||||
e -= read
|
||||
if left == 0 {
|
||||
i++
|
||||
off = 0
|
||||
}
|
||||
}
|
||||
if s > 0 {
|
||||
panic("pio: VecSlice start out of range")
|
||||
}
|
||||
|
||||
for e != 0 && i < len(in) {
|
||||
left := len(in[i])-off
|
||||
read := left
|
||||
if e > 0 && e < read {
|
||||
read = e
|
||||
}
|
||||
out[n] = in[i][off:off+read]
|
||||
n++
|
||||
left -= read
|
||||
e -= read
|
||||
off += read
|
||||
if left == 0 {
|
||||
i++
|
||||
off = 0
|
||||
}
|
||||
}
|
||||
if e > 0 {
|
||||
panic("pio: VecSlice end out of range")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func VecSlice(in [][]byte, s int, e int) (out [][]byte) {
|
||||
out = make([][]byte, len(in))
|
||||
n := VecSliceTo(in, out, s, e)
|
||||
out = out[:n]
|
||||
return
|
||||
}
|
||||
|
89
vendor/github.com/datarhei/joy4/utils/bits/pio/writer.go
generated
vendored
Normal file
89
vendor/github.com/datarhei/joy4/utils/bits/pio/writer.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
|
||||
package pio
|
||||
|
||||
func PutU8(b []byte, v uint8) {
|
||||
b[0] = v
|
||||
}
|
||||
|
||||
func PutI16BE(b []byte, v int16) {
|
||||
b[0] = byte(v>>8)
|
||||
b[1] = byte(v)
|
||||
}
|
||||
|
||||
func PutU16BE(b []byte, v uint16) {
|
||||
b[0] = byte(v>>8)
|
||||
b[1] = byte(v)
|
||||
}
|
||||
|
||||
func PutI24BE(b []byte, v int32) {
|
||||
b[0] = byte(v>>16)
|
||||
b[1] = byte(v>>8)
|
||||
b[2] = byte(v)
|
||||
}
|
||||
|
||||
func PutU24BE(b []byte, v uint32) {
|
||||
b[0] = byte(v>>16)
|
||||
b[1] = byte(v>>8)
|
||||
b[2] = byte(v)
|
||||
}
|
||||
|
||||
func PutI32BE(b []byte, v int32) {
|
||||
b[0] = byte(v>>24)
|
||||
b[1] = byte(v>>16)
|
||||
b[2] = byte(v>>8)
|
||||
b[3] = byte(v)
|
||||
}
|
||||
|
||||
func PutU32BE(b []byte, v uint32) {
|
||||
b[0] = byte(v>>24)
|
||||
b[1] = byte(v>>16)
|
||||
b[2] = byte(v>>8)
|
||||
b[3] = byte(v)
|
||||
}
|
||||
|
||||
func PutU32LE(b []byte, v uint32) {
|
||||
b[3] = byte(v>>24)
|
||||
b[2] = byte(v>>16)
|
||||
b[1] = byte(v>>8)
|
||||
b[0] = byte(v)
|
||||
}
|
||||
|
||||
func PutU40BE(b []byte, v uint64) {
|
||||
b[0] = byte(v>>32)
|
||||
b[1] = byte(v>>24)
|
||||
b[2] = byte(v>>16)
|
||||
b[3] = byte(v>>8)
|
||||
b[4] = byte(v)
|
||||
}
|
||||
|
||||
func PutU48BE(b []byte, v uint64) {
|
||||
b[0] = byte(v>>40)
|
||||
b[1] = byte(v>>32)
|
||||
b[2] = byte(v>>24)
|
||||
b[3] = byte(v>>16)
|
||||
b[4] = byte(v>>8)
|
||||
b[5] = byte(v)
|
||||
}
|
||||
|
||||
func PutU64BE(b []byte, v uint64) {
|
||||
b[0] = byte(v>>56)
|
||||
b[1] = byte(v>>48)
|
||||
b[2] = byte(v>>40)
|
||||
b[3] = byte(v>>32)
|
||||
b[4] = byte(v>>24)
|
||||
b[5] = byte(v>>16)
|
||||
b[6] = byte(v>>8)
|
||||
b[7] = byte(v)
|
||||
}
|
||||
|
||||
func PutI64BE(b []byte, v int64) {
|
||||
b[0] = byte(v>>56)
|
||||
b[1] = byte(v>>48)
|
||||
b[2] = byte(v>>40)
|
||||
b[3] = byte(v>>32)
|
||||
b[4] = byte(v>>24)
|
||||
b[5] = byte(v>>16)
|
||||
b[6] = byte(v>>8)
|
||||
b[7] = byte(v)
|
||||
}
|
||||
|
Reference in New Issue
Block a user