full commit
parent
2d84df63dc
commit
3894723f67
@ -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.
|
@ -0,0 +1,321 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
I_FRAME = byte(0)
|
||||||
|
P_FRAME = byte(100)
|
||||||
|
B_FRAME = byte(101)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Packet stores compressed audio/video data.
|
||||||
|
type Packet struct {
|
||||||
|
IsKeyFrame bool // video packet is key frame
|
||||||
|
FrameType byte // 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
|
||||||
|
}
|
@ -0,0 +1,255 @@
|
|||||||
|
package avconv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/av/pktque"
|
||||||
|
"github.com/Danile71/joy4/av/transcode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Debug bool
|
||||||
|
|
||||||
|
type Option struct {
|
||||||
|
Transcode bool
|
||||||
|
Args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
OutputCodecTypes []av.CodecType
|
||||||
|
}
|
||||||
|
|
||||||
|
type Demuxer struct {
|
||||||
|
transdemux *transcode.Demuxer
|
||||||
|
streams []av.CodecData
|
||||||
|
Options
|
||||||
|
Demuxer av.Demuxer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Demuxer) Close() (err error) {
|
||||||
|
if self.transdemux != nil {
|
||||||
|
return self.transdemux.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Demuxer) Streams() (streams []av.CodecData, err error) {
|
||||||
|
if err = self.prepare(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
streams = self.streams
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||||
|
if err = self.prepare(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return self.transdemux.ReadPacket()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Demuxer) prepare() (err error) {
|
||||||
|
if self.transdemux != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
var streams []av.CodecData
|
||||||
|
if streams, err = self.Demuxer.Streams(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
supports := self.Options.OutputCodecTypes
|
||||||
|
|
||||||
|
transopts := transcode.Options{}
|
||||||
|
transopts.FindAudioDecoderEncoder = func(codec av.AudioCodecData, i int) (ok bool, dec av.AudioDecoder, enc av.AudioEncoder, err error) {
|
||||||
|
if len(supports) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
support := false
|
||||||
|
for _, typ := range supports {
|
||||||
|
if typ == codec.Type() {
|
||||||
|
support = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if support {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
var enctype av.CodecType
|
||||||
|
for _, typ:= range supports {
|
||||||
|
if typ.IsAudio() {
|
||||||
|
if enc, _ = avutil.DefaultHandlers.NewAudioEncoder(typ); enc != nil {
|
||||||
|
enctype = typ
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if enc == nil {
|
||||||
|
err = fmt.Errorf("avconv: convert %s->%s failed", codec.Type(), enctype)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: support per stream option
|
||||||
|
// enc.SetSampleRate ...
|
||||||
|
|
||||||
|
if dec, err = avutil.DefaultHandlers.NewAudioDecoder(codec); err != nil {
|
||||||
|
err = fmt.Errorf("avconv: decode %s failed", codec.Type())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.transdemux = &transcode.Demuxer{
|
||||||
|
Options: transopts,
|
||||||
|
Demuxer: self.Demuxer,
|
||||||
|
}
|
||||||
|
if self.streams, err = self.transdemux.Streams(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertCmdline(args []string) (err error) {
|
||||||
|
output := ""
|
||||||
|
input := ""
|
||||||
|
flagi := false
|
||||||
|
flagv := false
|
||||||
|
flagt := false
|
||||||
|
flagre := false
|
||||||
|
duration := time.Duration(0)
|
||||||
|
options := Options{}
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
switch arg {
|
||||||
|
case "-i":
|
||||||
|
flagi = true
|
||||||
|
|
||||||
|
case "-v":
|
||||||
|
flagv = true
|
||||||
|
|
||||||
|
case "-t":
|
||||||
|
flagt = true
|
||||||
|
|
||||||
|
case "-re":
|
||||||
|
flagre = true
|
||||||
|
|
||||||
|
default:
|
||||||
|
switch {
|
||||||
|
case flagi:
|
||||||
|
flagi = false
|
||||||
|
input = arg
|
||||||
|
|
||||||
|
case flagt:
|
||||||
|
flagt = false
|
||||||
|
var f float64
|
||||||
|
fmt.Sscanf(arg, "%f", &f)
|
||||||
|
duration = time.Duration(f*float64(time.Second))
|
||||||
|
|
||||||
|
default:
|
||||||
|
output = arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if input == "" {
|
||||||
|
err = fmt.Errorf("avconv: input file not specified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if output == "" {
|
||||||
|
err = fmt.Errorf("avconv: output file not specified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var demuxer av.DemuxCloser
|
||||||
|
var muxer av.MuxCloser
|
||||||
|
|
||||||
|
if demuxer, err = avutil.Open(input); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer demuxer.Close()
|
||||||
|
|
||||||
|
var handler avutil.RegisterHandler
|
||||||
|
if handler, muxer, err = avutil.DefaultHandlers.FindCreate(output); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer muxer.Close()
|
||||||
|
|
||||||
|
options.OutputCodecTypes = handler.CodecTypes
|
||||||
|
|
||||||
|
convdemux := &Demuxer{
|
||||||
|
Options: options,
|
||||||
|
Demuxer: demuxer,
|
||||||
|
}
|
||||||
|
defer convdemux.Close()
|
||||||
|
|
||||||
|
var streams []av.CodecData
|
||||||
|
if streams, err = demuxer.Streams(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var convstreams []av.CodecData
|
||||||
|
if convstreams, err = convdemux.Streams(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if flagv {
|
||||||
|
for _, stream := range streams {
|
||||||
|
fmt.Print(stream.Type(), " ")
|
||||||
|
}
|
||||||
|
fmt.Print("-> ")
|
||||||
|
for _, stream := range convstreams {
|
||||||
|
fmt.Print(stream.Type(), " ")
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = muxer.WriteHeader(convstreams); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filters := pktque.Filters{}
|
||||||
|
if flagre {
|
||||||
|
filters = append(filters, &pktque.Walltime{})
|
||||||
|
}
|
||||||
|
filterdemux := &pktque.FilterDemuxer{
|
||||||
|
Demuxer: convdemux,
|
||||||
|
Filter: filters,
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
var pkt av.Packet
|
||||||
|
if pkt, err = filterdemux.ReadPacket(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if flagv {
|
||||||
|
fmt.Println(pkt.Idx, pkt.Time, len(pkt.Data), pkt.IsKeyFrame)
|
||||||
|
}
|
||||||
|
if duration != 0 && pkt.Time > duration {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err = muxer.WritePacket(pkt); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = muxer.WriteTrailer(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,312 @@
|
|||||||
|
package avutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package pktque
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Danile71/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
|
||||||
|
}
|
@ -0,0 +1,191 @@
|
|||||||
|
|
||||||
|
// Package pktque provides packet Filter interface and structures used by other components.
|
||||||
|
package pktque
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"github.com/Danile71/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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,217 @@
|
|||||||
|
// Packege pubsub implements publisher-subscribers model used in multi-channel streaming.
|
||||||
|
package pubsub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/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
|
||||||
|
}
|
@ -0,0 +1,247 @@
|
|||||||
|
|
||||||
|
// Package transcoder implements Transcoder based on Muxer/Demuxer and AudioEncoder/AudioDecoder interface.
|
||||||
|
package transcode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/av/pktque"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Debug bool
|
||||||
|
|
||||||
|
type tStream struct {
|
||||||
|
codec av.CodecData
|
||||||
|
timeline *pktque.Timeline
|
||||||
|
aencodec, adecodec av.AudioCodecData
|
||||||
|
aenc av.AudioEncoder
|
||||||
|
adec av.AudioDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
// check if transcode is needed, and create the AudioDecoder and AudioEncoder.
|
||||||
|
FindAudioDecoderEncoder func(codec av.AudioCodecData, i int) (
|
||||||
|
need bool, dec av.AudioDecoder, enc av.AudioEncoder, err error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transcoder struct {
|
||||||
|
streams []*tStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTranscoder(streams []av.CodecData, options Options) (_self *Transcoder, err error) {
|
||||||
|
self := &Transcoder{}
|
||||||
|
self.streams = []*tStream{}
|
||||||
|
|
||||||
|
for i, stream := range streams {
|
||||||
|
ts := &tStream{codec: stream}
|
||||||
|
if stream.Type().IsAudio() {
|
||||||
|
if options.FindAudioDecoderEncoder != nil {
|
||||||
|
var ok bool
|
||||||
|
var enc av.AudioEncoder
|
||||||
|
var dec av.AudioDecoder
|
||||||
|
ok, dec, enc, err = options.FindAudioDecoderEncoder(stream.(av.AudioCodecData), i)
|
||||||
|
if ok {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ts.timeline = &pktque.Timeline{}
|
||||||
|
if ts.codec, err = enc.CodecData(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ts.aencodec = ts.codec.(av.AudioCodecData)
|
||||||
|
ts.adecodec = stream.(av.AudioCodecData)
|
||||||
|
ts.aenc = enc
|
||||||
|
ts.adec = dec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.streams = append(self.streams, ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
_self = self
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *tStream) audioDecodeAndEncode(inpkt av.Packet) (outpkts []av.Packet, err error) {
|
||||||
|
var dur time.Duration
|
||||||
|
var frame av.AudioFrame
|
||||||
|
var ok bool
|
||||||
|
if ok, frame, err = self.adec.Decode(inpkt.Data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if dur, err = self.adecodec.PacketDuration(inpkt.Data); err != nil {
|
||||||
|
err = fmt.Errorf("transcode: PacketDuration() failed for input stream #%d", inpkt.Idx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("transcode: push", inpkt.Time, dur)
|
||||||
|
}
|
||||||
|
self.timeline.Push(inpkt.Time, dur)
|
||||||
|
|
||||||
|
var _outpkts [][]byte
|
||||||
|
if _outpkts, err = self.aenc.Encode(frame); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, _outpkt := range _outpkts {
|
||||||
|
if dur, err = self.aencodec.PacketDuration(_outpkt); err != nil {
|
||||||
|
err = fmt.Errorf("transcode: PacketDuration() failed for output stream #%d", inpkt.Idx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outpkt := av.Packet{Idx: inpkt.Idx, Data: _outpkt}
|
||||||
|
outpkt.Time = self.timeline.Pop(dur)
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("transcode: pop", outpkt.Time, dur)
|
||||||
|
}
|
||||||
|
|
||||||
|
outpkts = append(outpkts, outpkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the transcode.
|
||||||
|
//
|
||||||
|
// In audio transcoding one Packet may transcode into many Packets
|
||||||
|
// packet time will be adjusted automatically.
|
||||||
|
func (self *Transcoder) Do(pkt av.Packet) (out []av.Packet, err error) {
|
||||||
|
stream := self.streams[pkt.Idx]
|
||||||
|
if stream.aenc != nil && stream.adec != nil {
|
||||||
|
if out, err = stream.audioDecodeAndEncode(pkt); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out = append(out, pkt)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get CodecDatas after transcoding.
|
||||||
|
func (self *Transcoder) Streams() (streams []av.CodecData, err error) {
|
||||||
|
for _, stream := range self.streams {
|
||||||
|
streams = append(streams, stream.codec)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close transcoder, close related encoder and decoders.
|
||||||
|
func (self *Transcoder) Close() (err error) {
|
||||||
|
for _, stream := range self.streams {
|
||||||
|
if stream.aenc != nil {
|
||||||
|
stream.aenc.Close()
|
||||||
|
stream.aenc = nil
|
||||||
|
}
|
||||||
|
if stream.adec != nil {
|
||||||
|
stream.adec.Close()
|
||||||
|
stream.adec = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.streams = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap transcoder and origin Muxer into new Muxer.
|
||||||
|
// Write to new Muxer will do transcoding automatically.
|
||||||
|
type Muxer struct {
|
||||||
|
av.Muxer // origin Muxer
|
||||||
|
Options // transcode options
|
||||||
|
transcoder *Transcoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||||
|
if self.transcoder, err = NewTranscoder(streams, self.Options); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var newstreams []av.CodecData
|
||||||
|
if newstreams, err = self.transcoder.Streams(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = self.Muxer.WriteHeader(newstreams); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Muxer) WritePacket(pkt av.Packet) (err error) {
|
||||||
|
var outpkts []av.Packet
|
||||||
|
if outpkts, err = self.transcoder.Do(pkt); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, pkt := range outpkts {
|
||||||
|
if err = self.Muxer.WritePacket(pkt); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Muxer) Close() (err error) {
|
||||||
|
if self.transcoder != nil {
|
||||||
|
return self.transcoder.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap transcoder and origin Demuxer into new Demuxer.
|
||||||
|
// Read this Demuxer will do transcoding automatically.
|
||||||
|
type Demuxer struct {
|
||||||
|
av.Demuxer
|
||||||
|
Options
|
||||||
|
transcoder *Transcoder
|
||||||
|
outpkts []av.Packet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Demuxer) prepare() (err error) {
|
||||||
|
if self.transcoder == nil {
|
||||||
|
var streams []av.CodecData
|
||||||
|
if streams, err = self.Demuxer.Streams(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.transcoder, err = NewTranscoder(streams, self.Options); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||||
|
if err = self.prepare(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if len(self.outpkts) > 0 {
|
||||||
|
pkt = self.outpkts[0]
|
||||||
|
self.outpkts = self.outpkts[1:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var rpkt av.Packet
|
||||||
|
if rpkt, err = self.Demuxer.ReadPacket(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.outpkts, err = self.transcoder.Do(rpkt); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Demuxer) Streams() (streams []av.CodecData, err error) {
|
||||||
|
if err = self.prepare(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return self.transcoder.Streams()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Demuxer) Close() (err error) {
|
||||||
|
if self.transcoder != nil {
|
||||||
|
return self.transcoder.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,754 @@
|
|||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include "ffmpeg.h"
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/codec/aacparser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const debug = false
|
||||||
|
|
||||||
|
type Resampler struct {
|
||||||
|
inSampleFormat, OutSampleFormat av.SampleFormat
|
||||||
|
inChannelLayout, OutChannelLayout av.ChannelLayout
|
||||||
|
inSampleRate, OutSampleRate int
|
||||||
|
avr *C.SwrContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Resampler) Resample(in av.AudioFrame) (out av.AudioFrame, err error) {
|
||||||
|
formatChange := in.SampleRate != self.inSampleRate || in.SampleFormat != self.inSampleFormat || in.ChannelLayout != self.inChannelLayout
|
||||||
|
|
||||||
|
var flush av.AudioFrame
|
||||||
|
|
||||||
|
if formatChange {
|
||||||
|
if self.avr != nil {
|
||||||
|
outChannels := self.OutChannelLayout.Count()
|
||||||
|
if !self.OutSampleFormat.IsPlanar() {
|
||||||
|
outChannels = 1
|
||||||
|
}
|
||||||
|
outData := make([]*C.uint8_t, outChannels)
|
||||||
|
outSampleCount := int(C.swr_get_out_samples(self.avr, C.int(in.SampleCount)))
|
||||||
|
outLinesize := outSampleCount * self.OutSampleFormat.BytesPerSample()
|
||||||
|
flush.Data = make([][]byte, outChannels)
|
||||||
|
for i := 0; i < outChannels; i++ {
|
||||||
|
flush.Data[i] = make([]byte, outLinesize)
|
||||||
|
outData[i] = (*C.uint8_t)(unsafe.Pointer(&flush.Data[i][0]))
|
||||||
|
}
|
||||||
|
flush.ChannelLayout = self.OutChannelLayout
|
||||||
|
flush.SampleFormat = self.OutSampleFormat
|
||||||
|
flush.SampleRate = self.OutSampleRate
|
||||||
|
|
||||||
|
convertSamples := int(C.swr_convert(
|
||||||
|
self.avr,
|
||||||
|
(**C.uint8_t)(unsafe.Pointer(&outData[0])), C.int(outSampleCount),
|
||||||
|
nil, C.int(0),
|
||||||
|
))
|
||||||
|
|
||||||
|
if convertSamples < 0 {
|
||||||
|
err = fmt.Errorf("ffmpeg: avresample_convert_frame failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
flush.SampleCount = convertSamples
|
||||||
|
if convertSamples < outSampleCount {
|
||||||
|
for i := 0; i < outChannels; i++ {
|
||||||
|
flush.Data[i] = flush.Data[i][:convertSamples*self.OutSampleFormat.BytesPerSample()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Println("flush:", "outSampleCount", outSampleCount, "convertSamples", convertSamples, "datasize", len(flush.Data[0]))
|
||||||
|
} else {
|
||||||
|
runtime.SetFinalizer(self, func(self *Resampler) {
|
||||||
|
self.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
C.swr_free(&self.avr)
|
||||||
|
self.inSampleFormat = in.SampleFormat
|
||||||
|
self.inSampleRate = in.SampleRate
|
||||||
|
self.inChannelLayout = in.ChannelLayout
|
||||||
|
avr := C.swr_alloc()
|
||||||
|
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("in_channel_layout"), C.int64_t(channelLayoutAV2FF(self.inChannelLayout)), 0)
|
||||||
|
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("out_channel_layout"), C.int64_t(channelLayoutAV2FF(self.OutChannelLayout)), 0)
|
||||||
|
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("in_sample_rate"), C.int64_t(self.inSampleRate), 0)
|
||||||
|
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("out_sample_rate"), C.int64_t(self.OutSampleRate), 0)
|
||||||
|
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("in_sample_fmt"), C.int64_t(sampleFormatAV2FF(self.inSampleFormat)), 0)
|
||||||
|
C.av_opt_set_int(unsafe.Pointer(avr), C.CString("out_sample_fmt"), C.int64_t(sampleFormatAV2FF(self.OutSampleFormat)), 0)
|
||||||
|
C.swr_init(avr)
|
||||||
|
self.avr = avr
|
||||||
|
}
|
||||||
|
|
||||||
|
var inChannels int
|
||||||
|
inSampleCount := in.SampleCount
|
||||||
|
if !self.inSampleFormat.IsPlanar() {
|
||||||
|
inChannels = 1
|
||||||
|
} else {
|
||||||
|
inChannels = self.inChannelLayout.Count()
|
||||||
|
}
|
||||||
|
inData := make([]*C.uint8_t, inChannels)
|
||||||
|
for i := 0; i < inChannels; i++ {
|
||||||
|
inData[i] = (*C.uint8_t)(unsafe.Pointer(&in.Data[i][0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
var outChannels, outLinesize, outBytesPerSample int
|
||||||
|
outSampleCount := int(C.swr_get_out_samples(self.avr, C.int(in.SampleCount)))
|
||||||
|
if !self.OutSampleFormat.IsPlanar() {
|
||||||
|
outChannels = 1
|
||||||
|
outBytesPerSample = self.OutSampleFormat.BytesPerSample() * self.OutChannelLayout.Count()
|
||||||
|
outLinesize = outSampleCount * outBytesPerSample
|
||||||
|
} else {
|
||||||
|
outChannels = self.OutChannelLayout.Count()
|
||||||
|
outBytesPerSample = self.OutSampleFormat.BytesPerSample()
|
||||||
|
outLinesize = outSampleCount * outBytesPerSample
|
||||||
|
}
|
||||||
|
outData := make([]*C.uint8_t, outChannels)
|
||||||
|
out.Data = make([][]byte, outChannels)
|
||||||
|
for i := 0; i < outChannels; i++ {
|
||||||
|
out.Data[i] = make([]byte, outLinesize)
|
||||||
|
outData[i] = (*C.uint8_t)(unsafe.Pointer(&out.Data[i][0]))
|
||||||
|
}
|
||||||
|
out.ChannelLayout = self.OutChannelLayout
|
||||||
|
out.SampleFormat = self.OutSampleFormat
|
||||||
|
out.SampleRate = self.OutSampleRate
|
||||||
|
|
||||||
|
convertSamples := int(C.swr_convert(
|
||||||
|
self.avr,
|
||||||
|
(**C.uint8_t)(unsafe.Pointer(&outData[0])), C.int(outSampleCount),
|
||||||
|
(**C.uint8_t)(unsafe.Pointer(&inData[0])), C.int(inSampleCount),
|
||||||
|
))
|
||||||
|
if convertSamples < 0 {
|
||||||
|
err = fmt.Errorf("ffmpeg: avresample_convert_frame failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out.SampleCount = convertSamples
|
||||||
|
if convertSamples < outSampleCount {
|
||||||
|
for i := 0; i < outChannels; i++ {
|
||||||
|
out.Data[i] = out.Data[i][:convertSamples*outBytesPerSample]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flush.SampleCount > 0 {
|
||||||
|
out = flush.Concat(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Resampler) Close() {
|
||||||
|
C.swr_free(&self.avr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AudioEncoder struct {
|
||||||
|
ff *ffctx
|
||||||
|
SampleRate int
|
||||||
|
Bitrate int
|
||||||
|
ChannelLayout av.ChannelLayout
|
||||||
|
SampleFormat av.SampleFormat
|
||||||
|
FrameSampleCount int
|
||||||
|
framebuf av.AudioFrame
|
||||||
|
codecData av.AudioCodecData
|
||||||
|
resampler *Resampler
|
||||||
|
}
|
||||||
|
|
||||||
|
func sampleFormatAV2FF(sampleFormat av.SampleFormat) (ffsamplefmt int32) {
|
||||||
|
switch sampleFormat {
|
||||||
|
case av.U8:
|
||||||
|
ffsamplefmt = C.AV_SAMPLE_FMT_U8
|
||||||
|
case av.S16:
|
||||||
|
ffsamplefmt = C.AV_SAMPLE_FMT_S16
|
||||||
|
case av.S32:
|
||||||
|
ffsamplefmt = C.AV_SAMPLE_FMT_S32
|
||||||
|
case av.FLT:
|
||||||
|
ffsamplefmt = C.AV_SAMPLE_FMT_FLT
|
||||||
|
case av.DBL:
|
||||||
|
ffsamplefmt = C.AV_SAMPLE_FMT_DBL
|
||||||
|
case av.U8P:
|
||||||
|
ffsamplefmt = C.AV_SAMPLE_FMT_U8P
|
||||||
|
case av.S16P:
|
||||||
|
ffsamplefmt = C.AV_SAMPLE_FMT_S16P
|
||||||
|
case av.S32P:
|
||||||
|
ffsamplefmt = C.AV_SAMPLE_FMT_S32P
|
||||||
|
case av.FLTP:
|
||||||
|
ffsamplefmt = C.AV_SAMPLE_FMT_FLTP
|
||||||
|
case av.DBLP:
|
||||||
|
ffsamplefmt = C.AV_SAMPLE_FMT_DBLP
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func sampleFormatFF2AV(ffsamplefmt int32) (sampleFormat av.SampleFormat) {
|
||||||
|
switch ffsamplefmt {
|
||||||
|
case C.AV_SAMPLE_FMT_U8: ///< unsigned 8 bits
|
||||||
|
sampleFormat = av.U8
|
||||||
|
case C.AV_SAMPLE_FMT_S16: ///< signed 16 bits
|
||||||
|
sampleFormat = av.S16
|
||||||
|
case C.AV_SAMPLE_FMT_S32: ///< signed 32 bits
|
||||||
|
sampleFormat = av.S32
|
||||||
|
case C.AV_SAMPLE_FMT_FLT: ///< float
|
||||||
|
sampleFormat = av.FLT
|
||||||
|
case C.AV_SAMPLE_FMT_DBL: ///< double
|
||||||
|
sampleFormat = av.DBL
|
||||||
|
case C.AV_SAMPLE_FMT_U8P: ///< unsigned 8 bits, planar
|
||||||
|
sampleFormat = av.U8P
|
||||||
|
case C.AV_SAMPLE_FMT_S16P: ///< signed 16 bits, planar
|
||||||
|
sampleFormat = av.S16P
|
||||||
|
case C.AV_SAMPLE_FMT_S32P: ///< signed 32 bits, planar
|
||||||
|
sampleFormat = av.S32P
|
||||||
|
case C.AV_SAMPLE_FMT_FLTP: ///< float, planar
|
||||||
|
sampleFormat = av.FLTP
|
||||||
|
case C.AV_SAMPLE_FMT_DBLP: ///< double, planar
|
||||||
|
sampleFormat = av.DBLP
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) SetSampleFormat(fmt av.SampleFormat) (err error) {
|
||||||
|
self.SampleFormat = fmt
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) SetSampleRate(rate int) (err error) {
|
||||||
|
self.SampleRate = rate
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) SetChannelLayout(ch av.ChannelLayout) (err error) {
|
||||||
|
self.ChannelLayout = ch
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) SetBitrate(bitrate int) (err error) {
|
||||||
|
self.Bitrate = bitrate
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) SetOption(key string, val interface{}) (err error) {
|
||||||
|
ff := &self.ff.ff
|
||||||
|
|
||||||
|
sval := fmt.Sprint(val)
|
||||||
|
if key == "profile" {
|
||||||
|
ff.profile = C.avcodec_profile_name_to_int(ff.codec, C.CString(sval))
|
||||||
|
if ff.profile == C.FF_PROFILE_UNKNOWN {
|
||||||
|
err = fmt.Errorf("ffmpeg: profile `%s` invalid", sval)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
C.av_dict_set(&ff.options, C.CString(key), C.CString(sval), 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) GetOption(key string, val interface{}) (err error) {
|
||||||
|
ff := &self.ff.ff
|
||||||
|
entry := C.av_dict_get(ff.options, C.CString(key), nil, 0)
|
||||||
|
if entry == nil {
|
||||||
|
err = fmt.Errorf("ffmpeg: GetOption failed: `%s` not exists", key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch p := val.(type) {
|
||||||
|
case *string:
|
||||||
|
*p = C.GoString(entry.value)
|
||||||
|
case *int:
|
||||||
|
fmt.Sscanf(C.GoString(entry.value), "%d", p)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("ffmpeg: GetOption failed: val must be *string or *int receiver")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) Setup() (err error) {
|
||||||
|
ff := &self.ff.ff
|
||||||
|
|
||||||
|
ff.frame = C.av_frame_alloc()
|
||||||
|
|
||||||
|
if self.SampleFormat == av.SampleFormat(0) {
|
||||||
|
self.SampleFormat = sampleFormatFF2AV(*ff.codec.sample_fmts)
|
||||||
|
}
|
||||||
|
|
||||||
|
//if self.Bitrate == 0 {
|
||||||
|
// self.Bitrate = 80000
|
||||||
|
//}
|
||||||
|
if self.SampleRate == 0 {
|
||||||
|
self.SampleRate = 44100
|
||||||
|
}
|
||||||
|
if self.ChannelLayout == av.ChannelLayout(0) {
|
||||||
|
self.ChannelLayout = av.CH_STEREO
|
||||||
|
}
|
||||||
|
|
||||||
|
ff.codecCtx.sample_fmt = sampleFormatAV2FF(self.SampleFormat)
|
||||||
|
ff.codecCtx.sample_rate = C.int(self.SampleRate)
|
||||||
|
ff.codecCtx.bit_rate = C.int64_t(self.Bitrate)
|
||||||
|
ff.codecCtx.channel_layout = channelLayoutAV2FF(self.ChannelLayout)
|
||||||
|
ff.codecCtx.strict_std_compliance = C.FF_COMPLIANCE_EXPERIMENTAL
|
||||||
|
ff.codecCtx.flags = C.AV_CODEC_FLAG_GLOBAL_HEADER
|
||||||
|
ff.codecCtx.profile = ff.profile
|
||||||
|
|
||||||
|
if C.avcodec_open2(ff.codecCtx, ff.codec, nil) != 0 {
|
||||||
|
err = fmt.Errorf("ffmpeg: encoder: avcodec_open2 failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.SampleFormat = sampleFormatFF2AV(ff.codecCtx.sample_fmt)
|
||||||
|
self.FrameSampleCount = int(ff.codecCtx.frame_size)
|
||||||
|
|
||||||
|
extradata := C.GoBytes(unsafe.Pointer(ff.codecCtx.extradata), ff.codecCtx.extradata_size)
|
||||||
|
|
||||||
|
switch ff.codecCtx.codec_id {
|
||||||
|
case C.AV_CODEC_ID_AAC:
|
||||||
|
if self.codecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(extradata); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
self.codecData = audioCodecData{
|
||||||
|
channelLayout: self.ChannelLayout,
|
||||||
|
sampleFormat: self.SampleFormat,
|
||||||
|
sampleRate: self.SampleRate,
|
||||||
|
codecId: ff.codecCtx.codec_id,
|
||||||
|
extradata: extradata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) prepare() (err error) {
|
||||||
|
ff := &self.ff.ff
|
||||||
|
|
||||||
|
if ff.frame == nil {
|
||||||
|
if err = self.Setup(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) CodecData() (codec av.AudioCodecData, err error) {
|
||||||
|
if err = self.prepare(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
codec = self.codecData
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) encodeOne(frame av.AudioFrame) (gotpkt bool, pkt []byte, err error) {
|
||||||
|
if err = self.prepare(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ff := &self.ff.ff
|
||||||
|
|
||||||
|
cpkt := C.AVPacket{}
|
||||||
|
cgotpkt := C.int(0)
|
||||||
|
audioFrameAssignToFF(frame, ff.frame)
|
||||||
|
|
||||||
|
if false {
|
||||||
|
farr := []string{}
|
||||||
|
for i := 0; i < len(frame.Data[0])/4; i++ {
|
||||||
|
var f *float64 = (*float64)(unsafe.Pointer(&frame.Data[0][i*4]))
|
||||||
|
farr = append(farr, fmt.Sprintf("%.8f", *f))
|
||||||
|
}
|
||||||
|
fmt.Println(farr)
|
||||||
|
}
|
||||||
|
cerr := C.encode(ff.codecCtx, &cpkt, &cgotpkt, ff.frame)
|
||||||
|
if cerr < C.int(0) {
|
||||||
|
err = fmt.Errorf("ffmpeg: avcodec_encode_audio2 failed: %d", cerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cgotpkt != 0 {
|
||||||
|
gotpkt = true
|
||||||
|
pkt = C.GoBytes(unsafe.Pointer(cpkt.data), cpkt.size)
|
||||||
|
C.av_packet_unref(&cpkt)
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
fmt.Println("ffmpeg: Encode", frame.SampleCount, frame.SampleRate, frame.ChannelLayout, frame.SampleFormat, "len", len(pkt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) resample(in av.AudioFrame) (out av.AudioFrame, err error) {
|
||||||
|
if self.resampler == nil {
|
||||||
|
self.resampler = &Resampler{
|
||||||
|
OutSampleFormat: self.SampleFormat,
|
||||||
|
OutSampleRate: self.SampleRate,
|
||||||
|
OutChannelLayout: self.ChannelLayout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if out, err = self.resampler.Resample(in); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) Encode(frame av.AudioFrame) (pkts [][]byte, err error) {
|
||||||
|
var gotpkt bool
|
||||||
|
var pkt []byte
|
||||||
|
|
||||||
|
if frame.SampleFormat != self.SampleFormat || frame.ChannelLayout != self.ChannelLayout || frame.SampleRate != self.SampleRate {
|
||||||
|
if frame, err = self.resample(frame); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.FrameSampleCount != 0 {
|
||||||
|
if self.framebuf.SampleCount == 0 {
|
||||||
|
self.framebuf = frame
|
||||||
|
} else {
|
||||||
|
self.framebuf = self.framebuf.Concat(frame)
|
||||||
|
}
|
||||||
|
for self.framebuf.SampleCount >= self.FrameSampleCount {
|
||||||
|
frame := self.framebuf.Slice(0, self.FrameSampleCount)
|
||||||
|
if gotpkt, pkt, err = self.encodeOne(frame); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if gotpkt {
|
||||||
|
pkts = append(pkts, pkt)
|
||||||
|
}
|
||||||
|
self.framebuf = self.framebuf.Slice(self.FrameSampleCount, self.framebuf.SampleCount)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if gotpkt, pkt, err = self.encodeOne(frame); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if gotpkt {
|
||||||
|
pkts = append(pkts, pkt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioEncoder) Close() {
|
||||||
|
freeFFCtx(self.ff)
|
||||||
|
if self.resampler != nil {
|
||||||
|
self.resampler.Close()
|
||||||
|
self.resampler = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func audioFrameAssignToAVParams(f *C.AVFrame, frame *av.AudioFrame) {
|
||||||
|
frame.SampleFormat = sampleFormatFF2AV(int32(f.format))
|
||||||
|
frame.ChannelLayout = channelLayoutFF2AV(f.channel_layout)
|
||||||
|
frame.SampleRate = int(f.sample_rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func audioFrameAssignToAVData(f *C.AVFrame, frame *av.AudioFrame) {
|
||||||
|
frame.SampleCount = int(f.nb_samples)
|
||||||
|
frame.Data = make([][]byte, int(f.channels))
|
||||||
|
for i := 0; i < int(f.channels); i++ {
|
||||||
|
frame.Data[i] = C.GoBytes(unsafe.Pointer(f.data[i]), f.linesize[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func audioFrameAssignToAV(f *C.AVFrame, frame *av.AudioFrame) {
|
||||||
|
audioFrameAssignToAVParams(f, frame)
|
||||||
|
audioFrameAssignToAVData(f, frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
func audioFrameAssignToFFParams(frame av.AudioFrame, f *C.AVFrame) {
|
||||||
|
f.format = C.int(sampleFormatAV2FF(frame.SampleFormat))
|
||||||
|
f.channel_layout = channelLayoutAV2FF(frame.ChannelLayout)
|
||||||
|
f.sample_rate = C.int(frame.SampleRate)
|
||||||
|
f.channels = C.int(frame.ChannelLayout.Count())
|
||||||
|
}
|
||||||
|
|
||||||
|
func audioFrameAssignToFFData(frame av.AudioFrame, f *C.AVFrame) {
|
||||||
|
f.nb_samples = C.int(frame.SampleCount)
|
||||||
|
for i := range frame.Data {
|
||||||
|
f.data[i] = (*C.uint8_t)(unsafe.Pointer(&frame.Data[i][0]))
|
||||||
|
f.linesize[i] = C.int(len(frame.Data[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func audioFrameAssignToFF(frame av.AudioFrame, f *C.AVFrame) {
|
||||||
|
audioFrameAssignToFFParams(frame, f)
|
||||||
|
audioFrameAssignToFFData(frame, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func channelLayoutFF2AV(layout C.uint64_t) (channelLayout av.ChannelLayout) {
|
||||||
|
if layout&C.AV_CH_FRONT_CENTER != 0 {
|
||||||
|
channelLayout |= av.CH_FRONT_CENTER
|
||||||
|
}
|
||||||
|
if layout&C.AV_CH_FRONT_LEFT != 0 {
|
||||||
|
channelLayout |= av.CH_FRONT_LEFT
|
||||||
|
}
|
||||||
|
if layout&C.AV_CH_FRONT_RIGHT != 0 {
|
||||||
|
channelLayout |= av.CH_FRONT_RIGHT
|
||||||
|
}
|
||||||
|
if layout&C.AV_CH_BACK_CENTER != 0 {
|
||||||
|
channelLayout |= av.CH_BACK_CENTER
|
||||||
|
}
|
||||||
|
if layout&C.AV_CH_BACK_LEFT != 0 {
|
||||||
|
channelLayout |= av.CH_BACK_LEFT
|
||||||
|
}
|
||||||
|
if layout&C.AV_CH_BACK_RIGHT != 0 {
|
||||||
|
channelLayout |= av.CH_BACK_RIGHT
|
||||||
|
}
|
||||||
|
if layout&C.AV_CH_SIDE_LEFT != 0 {
|
||||||
|
channelLayout |= av.CH_SIDE_LEFT
|
||||||
|
}
|
||||||
|
if layout&C.AV_CH_SIDE_RIGHT != 0 {
|
||||||
|
channelLayout |= av.CH_SIDE_RIGHT
|
||||||
|
}
|
||||||
|
if layout&C.AV_CH_LOW_FREQUENCY != 0 {
|
||||||
|
channelLayout |= av.CH_LOW_FREQ
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func channelLayoutAV2FF(channelLayout av.ChannelLayout) (layout C.uint64_t) {
|
||||||
|
if channelLayout&av.CH_FRONT_CENTER != 0 {
|
||||||
|
layout |= C.AV_CH_FRONT_CENTER
|
||||||
|
}
|
||||||
|
if channelLayout&av.CH_FRONT_LEFT != 0 {
|
||||||
|
layout |= C.AV_CH_FRONT_LEFT
|
||||||
|
}
|
||||||
|
if channelLayout&av.CH_FRONT_RIGHT != 0 {
|
||||||
|
layout |= C.AV_CH_FRONT_RIGHT
|
||||||
|
}
|
||||||
|
if channelLayout&av.CH_BACK_CENTER != 0 {
|
||||||
|
layout |= C.AV_CH_BACK_CENTER
|
||||||
|
}
|
||||||
|
if channelLayout&av.CH_BACK_LEFT != 0 {
|
||||||
|
layout |= C.AV_CH_BACK_LEFT
|
||||||
|
}
|
||||||
|
if channelLayout&av.CH_BACK_RIGHT != 0 {
|
||||||
|
layout |= C.AV_CH_BACK_RIGHT
|
||||||
|
}
|
||||||
|
if channelLayout&av.CH_SIDE_LEFT != 0 {
|
||||||
|
layout |= C.AV_CH_SIDE_LEFT
|
||||||
|
}
|
||||||
|
if channelLayout&av.CH_SIDE_RIGHT != 0 {
|
||||||
|
layout |= C.AV_CH_SIDE_RIGHT
|
||||||
|
}
|
||||||
|
if channelLayout&av.CH_LOW_FREQ != 0 {
|
||||||
|
layout |= C.AV_CH_LOW_FREQUENCY
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type AudioDecoder struct {
|
||||||
|
ff *ffctx
|
||||||
|
ChannelLayout av.ChannelLayout
|
||||||
|
SampleFormat av.SampleFormat
|
||||||
|
SampleRate int
|
||||||
|
Extradata []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioDecoder) Setup() (err error) {
|
||||||
|
ff := &self.ff.ff
|
||||||
|
|
||||||
|
ff.frame = C.av_frame_alloc()
|
||||||
|
|
||||||
|
if len(self.Extradata) > 0 {
|
||||||
|
ff.codecCtx.extradata = (*C.uint8_t)(unsafe.Pointer(&self.Extradata[0]))
|
||||||
|
ff.codecCtx.extradata_size = C.int(len(self.Extradata))
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Println("ffmpeg: Decoder.Setup Extradata.len", len(self.Extradata))
|
||||||
|
}
|
||||||
|
|
||||||
|
ff.codecCtx.sample_rate = C.int(self.SampleRate)
|
||||||
|
ff.codecCtx.channel_layout = channelLayoutAV2FF(self.ChannelLayout)
|
||||||
|
ff.codecCtx.channels = C.int(self.ChannelLayout.Count())
|
||||||
|
if C.avcodec_open2(ff.codecCtx, ff.codec, nil) != 0 {
|
||||||
|
err = fmt.Errorf("ffmpeg: decoder: avcodec_open2 failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.SampleFormat = sampleFormatFF2AV(ff.codecCtx.sample_fmt)
|
||||||
|
self.ChannelLayout = channelLayoutFF2AV(ff.codecCtx.channel_layout)
|
||||||
|
if self.SampleRate == 0 {
|
||||||
|
self.SampleRate = int(ff.codecCtx.sample_rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioDecoder) Decode(pkt []byte) (gotframe bool, frame av.AudioFrame, err error) {
|
||||||
|
ff := &self.ff.ff
|
||||||
|
|
||||||
|
cgotframe := C.int(0)
|
||||||
|
|
||||||
|
cerr := C.decode(ff.codecCtx, ff.frame, (*C.uchar)(unsafe.Pointer(&pkt[0])), C.int(len(pkt)), &cgotframe)
|
||||||
|
|
||||||
|
if cerr < C.int(0) {
|
||||||
|
err = fmt.Errorf("ffmpeg: avcodec_decode_audio4 failed: %d", cerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cgotframe != C.int(0) {
|
||||||
|
gotframe = true
|
||||||
|
audioFrameAssignToAV(ff.frame, &frame)
|
||||||
|
frame.SampleRate = self.SampleRate
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
fmt.Println("ffmpeg: Decode", frame.SampleCount, frame.SampleRate, frame.ChannelLayout, frame.SampleFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AudioDecoder) Close() {
|
||||||
|
freeFFCtx(self.ff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAudioEncoderByCodecType(typ av.CodecType) (enc *AudioEncoder, err error) {
|
||||||
|
var id uint32
|
||||||
|
|
||||||
|
switch typ {
|
||||||
|
case av.AAC:
|
||||||
|
id = C.AV_CODEC_ID_AAC
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("ffmpeg: cannot find encoder codecType=%d", typ)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
codec := C.avcodec_find_encoder(id)
|
||||||
|
if codec == nil || C.avcodec_get_type(id) != C.AVMEDIA_TYPE_AUDIO {
|
||||||
|
err = fmt.Errorf("ffmpeg: cannot find audio encoder codecId=%d", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_enc := &AudioEncoder{}
|
||||||
|
if _enc.ff, err = newFFCtxByCodec(codec); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc = _enc
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAudioEncoderByName(name string) (enc *AudioEncoder, err error) {
|
||||||
|
_enc := &AudioEncoder{}
|
||||||
|
|
||||||
|
codec := C.avcodec_find_encoder_by_name(C.CString(name))
|
||||||
|
if codec == nil || C.avcodec_get_type(codec.id) != C.AVMEDIA_TYPE_AUDIO {
|
||||||
|
err = fmt.Errorf("ffmpeg: cannot find audio encoder name=%s", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _enc.ff, err = newFFCtxByCodec(codec); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc = _enc
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAudioDecoder(codec av.AudioCodecData) (dec *AudioDecoder, err error) {
|
||||||
|
_dec := &AudioDecoder{}
|
||||||
|
var id uint32
|
||||||
|
|
||||||
|
switch codec.Type() {
|
||||||
|
case av.AAC:
|
||||||
|
if aaccodec, ok := codec.(aacparser.CodecData); ok {
|
||||||
|
_dec.Extradata = aaccodec.MPEG4AudioConfigBytes()
|
||||||
|
id = C.AV_CODEC_ID_AAC
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("ffmpeg: aac CodecData must be aacparser.CodecData")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case av.SPEEX:
|
||||||
|
id = C.AV_CODEC_ID_SPEEX
|
||||||
|
|
||||||
|
case av.PCM_MULAW:
|
||||||
|
id = C.AV_CODEC_ID_PCM_MULAW
|
||||||
|
|
||||||
|
case av.PCM_ALAW:
|
||||||
|
id = C.AV_CODEC_ID_PCM_ALAW
|
||||||
|
|
||||||
|
default:
|
||||||
|
if ffcodec, ok := codec.(audioCodecData); ok {
|
||||||
|
_dec.Extradata = ffcodec.extradata
|
||||||
|
id = ffcodec.codecId
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("ffmpeg: invalid CodecData for ffmpeg to decode")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := C.avcodec_find_decoder(id)
|
||||||
|
if c == nil || C.avcodec_get_type(c.id) != C.AVMEDIA_TYPE_AUDIO {
|
||||||
|
err = fmt.Errorf("ffmpeg: cannot find audio decoder id=%d", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _dec.ff, err = newFFCtxByCodec(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_dec.SampleFormat = codec.SampleFormat()
|
||||||
|
_dec.SampleRate = codec.SampleRate()
|
||||||
|
_dec.ChannelLayout = codec.ChannelLayout()
|
||||||
|
if err = _dec.Setup(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dec = _dec
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type audioCodecData struct {
|
||||||
|
codecId uint32
|
||||||
|
sampleFormat av.SampleFormat
|
||||||
|
channelLayout av.ChannelLayout
|
||||||
|
sampleRate int
|
||||||
|
extradata []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self audioCodecData) Type() av.CodecType {
|
||||||
|
return av.MakeAudioCodecType(self.codecId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self audioCodecData) SampleRate() int {
|
||||||
|
return self.sampleRate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self audioCodecData) SampleFormat() av.SampleFormat {
|
||||||
|
return self.sampleFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self audioCodecData) ChannelLayout() av.ChannelLayout {
|
||||||
|
return self.channelLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self audioCodecData) PacketDuration(data []byte) (dur time.Duration, err error) {
|
||||||
|
// TODO: implement it: ffmpeg get_audio_frame_duration
|
||||||
|
err = fmt.Errorf("ffmpeg: cannot get packet duration")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func AudioCodecHandler(h *avutil.RegisterHandler) {
|
||||||
|
h.AudioDecoder = func(codec av.AudioCodecData) (av.AudioDecoder, error) {
|
||||||
|
if dec, err := NewAudioDecoder(codec); err != nil {
|
||||||
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
return dec, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.AudioEncoder = func(typ av.CodecType) (av.AudioEncoder, error) {
|
||||||
|
if enc, err := NewAudioEncoderByCodecType(typ); err != nil {
|
||||||
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
return enc, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavutil/avutil.h>
|
||||||
|
#include <libavutil/imgutils.h>
|
||||||
|
#include <libswresample/swresample.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <libswscale/swscale.h>
|
||||||
|
#include "ffmpeg.h"
|
||||||
|
|
||||||
|
int decode(AVCodecContext *avctx, AVFrame *frame, uint8_t *data, int size, int *got_frame)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct AVPacket pkt = {.data = data, .size = size};
|
||||||
|
|
||||||
|
*got_frame = 0;
|
||||||
|
|
||||||
|
ret = avcodec_send_packet(avctx, &pkt);
|
||||||
|
|
||||||
|
av_packet_unref(&pkt);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return ret == AVERROR_EOF ? 0 : ret;
|
||||||
|
|
||||||
|
|
||||||
|
ret = avcodec_receive_frame(avctx, frame);
|
||||||
|
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
|
||||||
|
return ret;
|
||||||
|
if (ret >= 0)
|
||||||
|
*got_frame = 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int encode(AVCodecContext *avctx, AVPacket *pkt, int *got_packet, AVFrame *frame)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
*got_packet = 0;
|
||||||
|
|
||||||
|
ret = avcodec_send_frame(avctx, frame);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = avcodec_receive_packet(avctx, pkt);
|
||||||
|
if (!ret)
|
||||||
|
*got_packet = 1;
|
||||||
|
if (ret == AVERROR(EAGAIN))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int avcodec_encode_jpeg(AVCodecContext *pCodecCtx, AVFrame *pFrame,AVPacket *packet) {
|
||||||
|
AVCodec *jpegCodec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
if (!jpegCodec) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVCodecContext *jpegContext = avcodec_alloc_context3(jpegCodec);
|
||||||
|
if (!jpegContext) {
|
||||||
|
jpegCodec = NULL;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
jpegContext->pix_fmt = AV_PIX_FMT_YUVJ420P;
|
||||||
|
jpegContext->height = pFrame->height;
|
||||||
|
jpegContext->width = pFrame->width;
|
||||||
|
jpegContext->time_base= (AVRational){1,25};
|
||||||
|
|
||||||
|
ret = avcodec_open2(jpegContext, jpegCodec, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gotFrame;
|
||||||
|
|
||||||
|
ret = encode(jpegContext, packet, &gotFrame, pFrame);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
error:
|
||||||
|
avcodec_close(jpegContext);
|
||||||
|
avcodec_free_context(&jpegContext);
|
||||||
|
jpegCodec = NULL;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *convert(AVCodecContext *pCodecCtx,AVFrame *pFrame,AVFrame *nFrame,int *size, int format) {
|
||||||
|
struct SwsContext *img_convert_ctx = sws_getCachedContext( NULL, pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pFrame->width, pFrame->height, format, SWS_BICUBIC, NULL, NULL, NULL );
|
||||||
|
nFrame->format = format;
|
||||||
|
nFrame->width = pFrame->width;
|
||||||
|
nFrame->height = pFrame->height;
|
||||||
|
|
||||||
|
*size = av_image_get_buffer_size( format, pFrame->width, pFrame->height, 1);
|
||||||
|
|
||||||
|
uint8_t *tmp_picture_buf = (uint8_t *)malloc(*size);
|
||||||
|
|
||||||
|
av_image_fill_arrays(nFrame->data, nFrame->linesize, tmp_picture_buf, format, pFrame->width, pFrame->height, 1);
|
||||||
|
|
||||||
|
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, nFrame->height, nFrame->data, nFrame->linesize);
|
||||||
|
sws_freeContext(img_convert_ctx);
|
||||||
|
return tmp_picture_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
int avcodec_encode_jpeg_nv12(AVCodecContext *pCodecCtx, AVFrame *pFrame,AVFrame *nFrame,AVPacket *packet) {
|
||||||
|
int size = 0;
|
||||||
|
uint8_t * data = convert(pCodecCtx, pFrame, nFrame, &size, AV_PIX_FMT_YUV420P);
|
||||||
|
int ret = avcodec_encode_jpeg(pCodecCtx,nFrame,packet);
|
||||||
|
free(data);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
|||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lavformat -lavutil -lavcodec -lswresample -lswscale
|
||||||
|
#include "ffmpeg.h"
|
||||||
|
void ffinit() {
|
||||||
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
||||||
|
av_register_all();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
QUIET = int(C.AV_LOG_QUIET)
|
||||||
|
PANIC = int(C.AV_LOG_PANIC)
|
||||||
|
FATAL = int(C.AV_LOG_FATAL)
|
||||||
|
ERROR = int(C.AV_LOG_ERROR)
|
||||||
|
WARNING = int(C.AV_LOG_WARNING)
|
||||||
|
INFO = int(C.AV_LOG_INFO)
|
||||||
|
VERBOSE = int(C.AV_LOG_VERBOSE)
|
||||||
|
DEBUG = int(C.AV_LOG_DEBUG)
|
||||||
|
TRACE = int(C.AV_LOG_TRACE)
|
||||||
|
)
|
||||||
|
|
||||||
|
func HasEncoder(name string) bool {
|
||||||
|
return C.avcodec_find_encoder_by_name(C.CString(name)) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasDecoder(name string) bool {
|
||||||
|
return C.avcodec_find_decoder_by_name(C.CString(name)) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//func EncodersList() []string
|
||||||
|
//func DecodersList() []string
|
||||||
|
|
||||||
|
func SetLogLevel(level int) {
|
||||||
|
C.av_log_set_level(C.int(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
C.ffinit()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ffctx struct {
|
||||||
|
ff C.FFCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFFCtxByCodec(codec *C.AVCodec) (ff *ffctx, err error) {
|
||||||
|
ff = &ffctx{}
|
||||||
|
ff.ff.codec = codec
|
||||||
|
ff.ff.codecCtx = C.avcodec_alloc_context3(codec)
|
||||||
|
ff.ff.profile = C.FF_PROFILE_UNKNOWN
|
||||||
|
runtime.SetFinalizer(ff, freeFFCtx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func freeFFCtx(self *ffctx) {
|
||||||
|
ff := &self.ff
|
||||||
|
if ff.frame != nil {
|
||||||
|
C.av_frame_free(&ff.frame)
|
||||||
|
}
|
||||||
|
if ff.codecCtx != nil {
|
||||||
|
C.avcodec_close(ff.codecCtx)
|
||||||
|
C.av_free(unsafe.Pointer(ff.codecCtx))
|
||||||
|
ff.codecCtx = nil
|
||||||
|
}
|
||||||
|
if ff.options != nil {
|
||||||
|
C.av_dict_free(&ff.options)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavutil/avutil.h>
|
||||||
|
#include <libswresample/swresample.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <libswscale/swscale.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
AVCodec *codec;
|
||||||
|
AVCodecContext *codecCtx;
|
||||||
|
AVFrame *frame;
|
||||||
|
AVDictionary *options;
|
||||||
|
int profile;
|
||||||
|
} FFCtx;
|
||||||
|
|
||||||
|
|
||||||
|
static inline int avcodec_profile_name_to_int(AVCodec *codec, const char *name) {
|
||||||
|
const AVProfile *p;
|
||||||
|
for (p = codec->profiles; p != NULL && p->profile != FF_PROFILE_UNKNOWN; p++)
|
||||||
|
if (!strcasecmp(p->name, name))
|
||||||
|
return p->profile;
|
||||||
|
return FF_PROFILE_UNKNOWN;
|
||||||
|
}
|
||||||
|
uint8_t *convert(AVCodecContext *pCodecCtx,AVFrame *pFrame,AVFrame *nFrame,int *size, int format);
|
||||||
|
int encode(AVCodecContext *avctx, AVPacket *pkt, int *got_packet, AVFrame *frame);
|
||||||
|
int decode(AVCodecContext *avctx, AVFrame *frame, uint8_t *data, int size, int *got_frame);
|
||||||
|
int avcodec_encode_jpeg(AVCodecContext *pCodecCtx, AVFrame *pFrame,AVPacket *packet);
|
||||||
|
int avcodec_encode_jpeg_nv12(AVCodecContext *pCodecCtx, AVFrame *pFrame,AVFrame *nFrame,AVPacket *packet);
|
@ -0,0 +1,13 @@
|
|||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavutil/avutil.h>
|
||||||
|
#include <libavutil/imgutils.h>
|
||||||
|
#include <libswresample/swresample.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <libswscale/swscale.h>
|
||||||
|
#include "ffmpeg.h"
|
||||||
|
|
||||||
|
uint8_t *avcodec_encode_to_mat(AVCodecContext *pCodecCtx, AVFrame *pFrame,AVFrame *nFrame,int *size) {
|
||||||
|
return convert(pCodecCtx, pFrame, nFrame, size, AV_PIX_FMT_BGR24);
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
// +build gocv
|
||||||
|
|
||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
// #cgo !windows pkg-config: opencv4
|
||||||
|
//#cgo LDFLAGS: -lavformat -lavutil -lavcodec -lavresample -lswscale
|
||||||
|
// #cgo CXXFLAGS: --std=c++1z
|
||||||
|
// #cgo windows CPPFLAGS: -IC:/opencv/build/install/include
|
||||||
|
// #cgo windows LDFLAGS: -LC:/opencv/build/install/x64/mingw/lib -lopencv_core420 -lopencv_face420 -lopencv_videoio420 -lopencv_imgproc420 -lopencv_highgui420 -lopencv_imgcodecs420 -lopencv_objdetect420 -lopencv_features2d420 -lopencv_video420 -lopencv_dnn420 -lopencv_xfeatures2d420 -lopencv_plot420 -lopencv_tracking420 -lopencv_img_hash420 -lopencv_calib3d420
|
||||||
|
// #include <stdlib.h>
|
||||||
|
// #include <stdint.h>
|
||||||
|
// #include "gocv.h"
|
||||||
|
// #include "ffmpeg.h"
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"gocv.io/x/gocv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (self *VideoDecoder) DecodeMat(pkt []byte) (img gocv.Mat, err error) {
|
||||||
|
data, w, h, err := self.DecodeMatRaw(pkt)
|
||||||
|
if err == nil {
|
||||||
|
img, err = gocv.NewMatFromBytes(h, w, gocv.MatTypeCV8UC3, data)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return img, errors.New("no image")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *VideoDecoder) DecodeMatRaw(pkt []byte) (raw []byte, w, h int, err error) {
|
||||||
|
ff := &self.ff.ff
|
||||||
|
cgotimg := C.int(0)
|
||||||
|
frame := C.av_frame_alloc()
|
||||||
|
defer C.av_frame_free(&frame)
|
||||||
|
|
||||||
|
cerr := C.decode(ff.codecCtx, frame, (*C.uchar)(unsafe.Pointer(&pkt[0])), C.int(len(pkt)), &cgotimg)
|
||||||
|
|
||||||
|
if cerr < C.int(0) {
|
||||||
|
err = fmt.Errorf("ffmpeg: decode failed: %d", cerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cgotimg != C.int(0) {
|
||||||
|
w = int(frame.width)
|
||||||
|
h = int(frame.height)
|
||||||
|
sz := C.int(0)
|
||||||
|
nframe := C.av_frame_alloc()
|
||||||
|
defer C.av_frame_free(&nframe)
|
||||||
|
|
||||||
|
cdata := C.avcodec_encode_to_mat(ff.codecCtx, frame, nframe, &sz)
|
||||||
|
if cerr != C.int(0) {
|
||||||
|
err = fmt.Errorf("ffmpeg: avcodec_encode_jpeg failed: %d", cerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer C.free(unsafe.Pointer(cdata))
|
||||||
|
|
||||||
|
raw = make([]byte, int(sz))
|
||||||
|
copy(raw, *(*[]byte)(unsafe.Pointer(&cdata)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavutil/avutil.h>
|
||||||
|
#include <libswresample/swresample.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <libswscale/swscale.h>
|
||||||
|
|
||||||
|
uint8_t *avcodec_encode_to_mat(AVCodecContext *pCodecCtx, AVFrame *pFrame,AVFrame *nFrame,int *size);
|
@ -0,0 +1,204 @@
|
|||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
//#cgo LDFLAGS: -lavformat -lavutil -lavcodec -lavresample -lswscale
|
||||||
|
// #include "ffmpeg.h"
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/codec/h264parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VideoDecoder struct {
|
||||||
|
ff *ffctx
|
||||||
|
Extradata []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *VideoDecoder) Setup() (err error) {
|
||||||
|
ff := &self.ff.ff
|
||||||
|
if len(self.Extradata) > 0 {
|
||||||
|
ff.codecCtx.extradata = (*C.uint8_t)(unsafe.Pointer(&self.Extradata[0]))
|
||||||
|
ff.codecCtx.extradata_size = C.int(len(self.Extradata))
|
||||||
|
}
|
||||||
|
if C.avcodec_open2(ff.codecCtx, ff.codec, nil) != 0 {
|
||||||
|
err = fmt.Errorf("ffmpeg: decoder: avcodec_open2 failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromCPtr(buf unsafe.Pointer, size int) (ret []uint8) {
|
||||||
|
hdr := (*reflect.SliceHeader)((unsafe.Pointer(&ret)))
|
||||||
|
hdr.Cap = size
|
||||||
|
hdr.Len = size
|
||||||
|
hdr.Data = uintptr(buf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type VideoFrame struct {
|
||||||
|
Image image.YCbCr
|
||||||
|
Raw []byte
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *VideoFrame) Free() {
|
||||||
|
self.Image = image.YCbCr{}
|
||||||
|
self.Raw = make([]byte, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func freeVideoFrame(self *VideoFrame) {
|
||||||
|
self.Free()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *VideoDecoder) Decode(pkt []byte) (img *VideoFrame, err error) {
|
||||||
|
ff := &self.ff.ff
|
||||||
|
|
||||||
|
cgotimg := C.int(0)
|
||||||
|
frame := C.av_frame_alloc()
|
||||||
|
defer C.av_frame_free(&frame)
|
||||||
|
|
||||||
|
cerr := C.decode(ff.codecCtx, frame, (*C.uchar)(unsafe.Pointer(&pkt[0])), C.int(len(pkt)), &cgotimg)
|
||||||
|
|
||||||
|
if cerr < C.int(0) {
|
||||||
|
err = fmt.Errorf("ffmpeg: decode failed: %d", cerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cgotimg != C.int(0) {
|
||||||
|
w := int(frame.width)
|
||||||
|
h := int(frame.height)
|
||||||
|
ys := int(frame.linesize[0])
|
||||||
|
cs := int(frame.linesize[1])
|
||||||
|
|
||||||
|
img = &VideoFrame{Image: image.YCbCr{
|
||||||
|
Y: fromCPtr(unsafe.Pointer(frame.data[0]), ys*h),
|
||||||
|
Cb: fromCPtr(unsafe.Pointer(frame.data[1]), cs*h/2),
|
||||||
|
Cr: fromCPtr(unsafe.Pointer(frame.data[2]), cs*h/2),
|
||||||
|
YStride: ys,
|
||||||
|
CStride: cs,
|
||||||
|
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
||||||
|
Rect: image.Rect(0, 0, w, h),
|
||||||
|
}}
|
||||||
|
|
||||||
|
runtime.SetFinalizer(img, freeVideoFrame)
|
||||||
|
|
||||||
|
packet := C.AVPacket{}
|
||||||
|
defer C.av_packet_unref(&packet)
|
||||||
|
|
||||||
|
switch int(frame.format) {
|
||||||
|
case 0, 12:
|
||||||
|
cerr := C.avcodec_encode_jpeg(ff.codecCtx, frame, &packet)
|
||||||
|
if cerr != C.int(0) {
|
||||||
|
err = fmt.Errorf("ffmpeg: avcodec_encode_jpeg failed: %d", cerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
img.Size = int(packet.size)
|
||||||
|
img.Raw = make([]byte, img.Size)
|
||||||
|
copy(img.Raw, *(*[]byte)(unsafe.Pointer(&packet.data)))
|
||||||
|
default:
|
||||||
|
nframe := C.av_frame_alloc()
|
||||||
|
defer C.av_frame_free(&nframe)
|
||||||
|
cerr := C.avcodec_encode_jpeg_nv12(ff.codecCtx, frame, nframe, &packet)
|
||||||
|
if cerr != C.int(0) {
|
||||||
|
err = fmt.Errorf("ffmpeg: avcodec_encode_jpeg failed: %d", cerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
img.Size = int(packet.size)
|
||||||
|
img.Raw = make([]byte, img.Size)
|
||||||
|
copy(img.Raw, *(*[]byte)(unsafe.Pointer(&packet.data)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *VideoDecoder) DecodeBac(pkt []byte) (img *VideoFrame, err error) {
|
||||||
|
ff := &self.ff.ff
|
||||||
|
|
||||||
|
cgotimg := C.int(0)
|
||||||
|
frame := C.av_frame_alloc()
|
||||||
|
defer C.av_frame_free(&frame)
|
||||||
|
|
||||||
|
cerr := C.decode(ff.codecCtx, frame, (*C.uchar)(unsafe.Pointer(&pkt[0])), C.int(len(pkt)), &cgotimg)
|
||||||
|
|
||||||
|
if cerr < C.int(0) {
|
||||||
|
err = fmt.Errorf("ffmpeg: avcodec_decode_video2 failed: %d", cerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cgotimg != C.int(0) {
|
||||||
|
w := int(frame.width)
|
||||||
|
h := int(frame.height)
|
||||||
|
ys := int(frame.linesize[0])
|
||||||
|
cs := int(frame.linesize[1])
|
||||||
|
|
||||||
|
img = &VideoFrame{Image: image.YCbCr{
|
||||||
|
Y: fromCPtr(unsafe.Pointer(frame.data[0]), ys*h),
|
||||||
|
Cb: fromCPtr(unsafe.Pointer(frame.data[1]), cs*h/2),
|
||||||
|
Cr: fromCPtr(unsafe.Pointer(frame.data[2]), cs*h/2),
|
||||||
|
YStride: ys,
|
||||||
|
CStride: cs,
|
||||||
|
SubsampleRatio: image.YCbCrSubsampleRatio420,
|
||||||
|
Rect: image.Rect(0, 0, w, h),
|
||||||
|
}}
|
||||||
|
runtime.SetFinalizer(img, freeVideoFrame)
|
||||||
|
|
||||||
|
packet := C.AVPacket{}
|
||||||
|
defer C.av_packet_unref(&packet)
|
||||||
|
|
||||||
|
cerr := C.avcodec_encode_jpeg(ff.codecCtx, frame, &packet)
|
||||||
|
|
||||||
|
if cerr != C.int(0) {
|
||||||
|
err = fmt.Errorf("ffmpeg: avcodec_encode_jpeg failed: %d", cerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
img.Size = int(packet.size)
|
||||||
|
img.Raw = make([]byte, img.Size)
|
||||||
|
copy(img.Raw, *(*[]byte)(unsafe.Pointer(&packet.data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVideoDecoder(stream av.CodecData) (dec *VideoDecoder, err error) {
|
||||||
|
_dec := &VideoDecoder{}
|
||||||
|
var id uint32
|
||||||
|
|
||||||
|
switch stream.Type() {
|
||||||
|
case av.H264:
|
||||||
|
h264 := stream.(h264parser.CodecData)
|
||||||
|
_dec.Extradata = h264.AVCDecoderConfRecordBytes()
|
||||||
|
id = C.AV_CODEC_ID_H264
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("ffmpeg: NewVideoDecoder codec=%v unsupported", stream.Type())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c := C.avcodec_find_decoder_by_name(C.CString("h264_cuvid"))
|
||||||
|
if c == nil || C.avcodec_get_type(id) != C.AVMEDIA_TYPE_VIDEO {
|
||||||
|
c = C.avcodec_find_decoder(id)
|
||||||
|
if c == nil || C.avcodec_get_type(id) != C.AVMEDIA_TYPE_VIDEO {
|
||||||
|
err = fmt.Errorf("ffmpeg: cannot find video decoder codecId=%d", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _dec.ff, err = newFFCtxByCodec(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = _dec.Setup(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dec = _dec
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,311 @@
|
|||||||
|
package aacparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/utils/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package codec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/codec/fake"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package fake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Danile71/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_
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
package h264parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParser(t *testing.T) {
|
||||||
|
var ok bool
|
||||||
|
var nalus [][]byte
|
||||||
|
|
||||||
|
annexbFrame, _ := hex.DecodeString("00000001223322330000000122332233223300000133000001000001")
|
||||||
|
nalus, ok = SplitNALUs(annexbFrame)
|
||||||
|
t.Log(ok, len(nalus))
|
||||||
|
|
||||||
|
avccFrame, _ := hex.DecodeString(
|
||||||
|
"00000008aabbccaabbccaabb00000001aa",
|
||||||
|
)
|
||||||
|
nalus, ok = SplitNALUs(avccFrame)
|
||||||
|
t.Log(ok, len(nalus))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
// Package joy4 is a Golang audio/video library and streaming server.
|
||||||
|
// JOY4 is powerful library written in golang, well-designed interface makes a few lines
|
||||||
|
// of code can do a lot of things such as reading, writing, transcoding among
|
||||||
|
// variety media formats, or setting up high-performance live streaming server.
|
||||||
|
package joy4
|
@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/format"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/cgo/ffmpeg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// need ffmpeg installed
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
format.RegisterAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
file, _ := avutil.Open("projectindex.flv")
|
||||||
|
streams, _ := file.Streams()
|
||||||
|
var dec *ffmpeg.AudioDecoder
|
||||||
|
|
||||||
|
for _, stream := range streams {
|
||||||
|
if stream.Type() == av.AAC {
|
||||||
|
dec, _ = ffmpeg.NewAudioDecoder(stream.(av.AudioCodecData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
pkt, _ := file.ReadPacket()
|
||||||
|
if streams[pkt.Idx].Type() == av.AAC {
|
||||||
|
ok, frame, _ := dec.Decode(pkt.Data)
|
||||||
|
if ok {
|
||||||
|
println("decode samples", frame.SampleCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"github.com/Danile71/joy4/format"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/av/pubsub"
|
||||||
|
"github.com/Danile71/joy4/format/rtmp"
|
||||||
|
"github.com/Danile71/joy4/format/flv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
format.RegisterAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeFlusher struct {
|
||||||
|
httpflusher http.Flusher
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self writeFlusher) Flush() error {
|
||||||
|
self.httpflusher.Flush()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server := &rtmp.Server{}
|
||||||
|
|
||||||
|
l := &sync.RWMutex{}
|
||||||
|
type Channel struct {
|
||||||
|
que *pubsub.Queue
|
||||||
|
}
|
||||||
|
channels := map[string]*Channel{}
|
||||||
|
|
||||||
|
server.HandlePlay = func(conn *rtmp.Conn) {
|
||||||
|
l.RLock()
|
||||||
|
ch := channels[conn.URL.Path]
|
||||||
|
l.RUnlock()
|
||||||
|
|
||||||
|
if ch != nil {
|
||||||
|
cursor := ch.que.Latest()
|
||||||
|
avutil.CopyFile(conn, cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server.HandlePublish = func(conn *rtmp.Conn) {
|
||||||
|
streams, _ := conn.Streams()
|
||||||
|
|
||||||
|
l.Lock()
|
||||||
|
ch := channels[conn.URL.Path]
|
||||||
|
if ch == nil {
|
||||||
|
ch = &Channel{}
|
||||||
|
ch.que = pubsub.NewQueue()
|
||||||
|
ch.que.WriteHeader(streams)
|
||||||
|
channels[conn.URL.Path] = ch
|
||||||
|
} else {
|
||||||
|
ch = nil
|
||||||
|
}
|
||||||
|
l.Unlock()
|
||||||
|
if ch == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
avutil.CopyPackets(ch.que, conn)
|
||||||
|
|
||||||
|
l.Lock()
|
||||||
|
delete(channels, conn.URL.Path)
|
||||||
|
l.Unlock()
|
||||||
|
ch.que.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l.RLock()
|
||||||
|
ch := channels[r.URL.Path]
|
||||||
|
l.RUnlock()
|
||||||
|
|
||||||
|
if ch != nil {
|
||||||
|
w.Header().Set("Content-Type", "video/x-flv")
|
||||||
|
w.Header().Set("Transfer-Encoding", "chunked")
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
flusher := w.(http.Flusher)
|
||||||
|
flusher.Flush()
|
||||||
|
|
||||||
|
muxer := flv.NewMuxerWriteFlusher(writeFlusher{httpflusher: flusher, Writer: w})
|
||||||
|
cursor := ch.que.Latest()
|
||||||
|
|
||||||
|
avutil.CopyFile(muxer, cursor)
|
||||||
|
} else {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
go http.ListenAndServe(":8089", nil)
|
||||||
|
|
||||||
|
server.ListenAndServe()
|
||||||
|
|
||||||
|
// ffmpeg -re -i movie.flv -c copy -f flv rtmp://localhost/movie
|
||||||
|
// ffmpeg -f avfoundation -i "0:0" .... -f flv rtmp://localhost/screen
|
||||||
|
// ffplay http://localhost:8089/movie
|
||||||
|
// ffplay http://localhost:8089/screen
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
format.RegisterAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
file, _ := avutil.Open("projectindex.flv")
|
||||||
|
|
||||||
|
streams, _ := file.Streams()
|
||||||
|
for _, stream := range streams {
|
||||||
|
if stream.Type().IsAudio() {
|
||||||
|
astream := stream.(av.AudioCodecData)
|
||||||
|
fmt.Println(astream.Type(), astream.SampleRate(), astream.SampleFormat(), astream.ChannelLayout())
|
||||||
|
} else if stream.Type().IsVideo() {
|
||||||
|
vstream := stream.(av.VideoCodecData)
|
||||||
|
fmt.Println(vstream.Type(), vstream.Width(), vstream.Height())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
var pkt av.Packet
|
||||||
|
var err error
|
||||||
|
if pkt, err = file.ReadPacket(); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fmt.Println("pkt", i, streams[pkt.Idx].Type(), "len", len(pkt.Data), "keyframe", pkt.IsKeyFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Danile71/joy4/av/pktque"
|
||||||
|
"github.com/Danile71/joy4/format"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/format/rtmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
format.RegisterAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
// as same as: ffmpeg -re -i projectindex.flv -c copy -f flv rtmp://localhost:1936/app/publish
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
file, _ := avutil.Open("projectindex.flv")
|
||||||
|
conn, _ := rtmp.Dial("rtmp://localhost:1936/app/publish")
|
||||||
|
// conn, _ := avutil.Create("rtmp://localhost:1936/app/publish")
|
||||||
|
|
||||||
|
demuxer := &pktque.FilterDemuxer{Demuxer: file, Filter: &pktque.Walltime{}}
|
||||||
|
avutil.CopyFile(conn, demuxer)
|
||||||
|
|
||||||
|
file.Close()
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/av/pktque"
|
||||||
|
"github.com/Danile71/joy4/av/pubsub"
|
||||||
|
"github.com/Danile71/joy4/format"
|
||||||
|
"github.com/Danile71/joy4/format/rtmp"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
format.RegisterAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
type FrameDropper struct {
|
||||||
|
Interval int
|
||||||
|
n int
|
||||||
|
skipping bool
|
||||||
|
DelaySkip time.Duration
|
||||||
|
lasttime time.Time
|
||||||
|
lastpkttime time.Duration
|
||||||
|
delay time.Duration
|
||||||
|
SkipInterval int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FrameDropper) ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) {
|
||||||
|
if self.DelaySkip != 0 && pkt.Idx == int8(videoidx) {
|
||||||
|
now := time.Now()
|
||||||
|
if !self.lasttime.IsZero() {
|
||||||
|
realdiff := now.Sub(self.lasttime)
|
||||||
|
pktdiff := pkt.Time - self.lastpkttime
|
||||||
|
self.delay += realdiff - pktdiff
|
||||||
|
}
|
||||||
|
self.lasttime = time.Now()
|
||||||
|
self.lastpkttime = pkt.Time
|
||||||
|
|
||||||
|
if !self.skipping {
|
||||||
|
if self.delay > self.DelaySkip {
|
||||||
|
self.skipping = true
|
||||||
|
self.delay = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if pkt.IsKeyFrame {
|
||||||
|
self.skipping = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.skipping {
|
||||||
|
drop = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.SkipInterval != 0 && pkt.IsKeyFrame {
|
||||||
|
if self.n == self.SkipInterval {
|
||||||
|
self.n = 0
|
||||||
|
self.skipping = true
|
||||||
|
}
|
||||||
|
self.n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.Interval != 0 {
|
||||||
|
if self.n >= self.Interval && pkt.Idx == int8(videoidx) && !pkt.IsKeyFrame {
|
||||||
|
drop = true
|
||||||
|
self.n = 0
|
||||||
|
}
|
||||||
|
self.n++
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server := &rtmp.Server{}
|
||||||
|
|
||||||
|
l := &sync.RWMutex{}
|
||||||
|
type Channel struct {
|
||||||
|
que *pubsub.Queue
|
||||||
|
}
|
||||||
|
channels := map[string]*Channel{}
|
||||||
|
|
||||||
|
server.HandlePlay = func(conn *rtmp.Conn) {
|
||||||
|
l.RLock()
|
||||||
|
ch := channels[conn.URL.Path]
|
||||||
|
l.RUnlock()
|
||||||
|
|
||||||
|
if ch != nil {
|
||||||
|
cursor := ch.que.Latest()
|
||||||
|
query := conn.URL.Query()
|
||||||
|
|
||||||
|
if q := query.Get("delaygop"); q != "" {
|
||||||
|
n := 0
|
||||||
|
fmt.Sscanf(q, "%d", &n)
|
||||||
|
cursor = ch.que.DelayedGopCount(n)
|
||||||
|
} else if q := query.Get("delaytime"); q != "" {
|
||||||
|
dur, _ := time.ParseDuration(q)
|
||||||
|
cursor = ch.que.DelayedTime(dur)
|
||||||
|
}
|
||||||
|
|
||||||
|
filters := pktque.Filters{}
|
||||||
|
|
||||||
|
if q := query.Get("waitkey"); q != "" {
|
||||||
|
filters = append(filters, &pktque.WaitKeyFrame{})
|
||||||
|
}
|
||||||
|
|
||||||
|
filters = append(filters, &pktque.FixTime{StartFromZero: true, MakeIncrement: true})
|
||||||
|
|
||||||
|
if q := query.Get("framedrop"); q != "" {
|
||||||
|
n := 0
|
||||||
|
fmt.Sscanf(q, "%d", &n)
|
||||||
|
filters = append(filters, &FrameDropper{Interval: n})
|
||||||
|
}
|
||||||
|
|
||||||
|
if q := query.Get("delayskip"); q != "" {
|
||||||
|
dur, _ := time.ParseDuration(q)
|
||||||
|
skipper := &FrameDropper{DelaySkip: dur}
|
||||||
|
if q := query.Get("skipinterval"); q != "" {
|
||||||
|
n := 0
|
||||||
|
fmt.Sscanf(q, "%d", &n)
|
||||||
|
skipper.SkipInterval = n
|
||||||
|
}
|
||||||
|
filters = append(filters, skipper)
|
||||||
|
}
|
||||||
|
|
||||||
|
demuxer := &pktque.FilterDemuxer{
|
||||||
|
Filter: filters,
|
||||||
|
Demuxer: cursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
avutil.CopyFile(conn, demuxer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server.HandlePublish = func(conn *rtmp.Conn) {
|
||||||
|
l.Lock()
|
||||||
|
ch := channels[conn.URL.Path]
|
||||||
|
if ch == nil {
|
||||||
|
ch = &Channel{}
|
||||||
|
ch.que = pubsub.NewQueue()
|
||||||
|
query := conn.URL.Query()
|
||||||
|
if q := query.Get("cachegop"); q != "" {
|
||||||
|
var n int
|
||||||
|
fmt.Sscanf(q, "%d", &n)
|
||||||
|
ch.que.SetMaxGopCount(n)
|
||||||
|
}
|
||||||
|
channels[conn.URL.Path] = ch
|
||||||
|
} else {
|
||||||
|
ch = nil
|
||||||
|
}
|
||||||
|
l.Unlock()
|
||||||
|
if ch == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
avutil.CopyFile(ch.que, conn)
|
||||||
|
|
||||||
|
l.Lock()
|
||||||
|
delete(channels, conn.URL.Path)
|
||||||
|
l.Unlock()
|
||||||
|
ch.que.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
server.ListenAndServe()
|
||||||
|
|
||||||
|
// ffmpeg -re -i movie.flv -c copy -f flv rtmp://localhost/movie
|
||||||
|
// ffmpeg -f avfoundation -i "0:0" .... -f flv rtmp://localhost/screen
|
||||||
|
|
||||||
|
// with cache size options
|
||||||
|
|
||||||
|
// ffplay rtmp://localhost/movie
|
||||||
|
// ffplay rtmp://localhost/screen
|
||||||
|
// ffplay rtmp://localhost/movie?delaytime=5s
|
||||||
|
// ffplay rtmp://localhost/movie?delaytime=10s&waitkey=true
|
||||||
|
// ffplay rtmp://localhost/movie?delaytime=20s
|
||||||
|
|
||||||
|
// ffmpeg -re -i movie.flv -c copy -f flv rtmp://localhost/movie?cachegop=2
|
||||||
|
// ffmpeg -re -i movie.flv -c copy -f flv rtmp://localhost/movie?cachegop=1
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"github.com/Danile71/joy4/format"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/format/rtmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
format.RegisterAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server := &rtmp.Server{}
|
||||||
|
|
||||||
|
server.HandlePlay = func(conn *rtmp.Conn) {
|
||||||
|
segs := strings.Split(conn.URL.Path, "/")
|
||||||
|
url := fmt.Sprintf("%s://%s", segs[1], strings.Join(segs[2:], "/"))
|
||||||
|
src, _ := avutil.Open(url)
|
||||||
|
avutil.CopyFile(conn, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
server.ListenAndServe()
|
||||||
|
|
||||||
|
// ffplay rtmp://localhost/rtsp/192.168.1.1/camera1
|
||||||
|
// ffplay rtmp://localhost/rtmp/live.hkstv.hk.lxdns.com/live/hks
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/av/transcode"
|
||||||
|
"github.com/Danile71/joy4/format"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/format/rtmp"
|
||||||
|
"github.com/Danile71/joy4/cgo/ffmpeg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// need ffmpeg with libspeex and libfdkaac installed
|
||||||
|
//
|
||||||
|
// open http://www.wowza.com/resources/4.4.1/examples/WebcamRecording/FlashRTMPPlayer11/player.html
|
||||||
|
// click connect and recored
|
||||||
|
// input camera H264/SPEEX will converted H264/AAC and saved in out.ts
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
format.RegisterAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server := &rtmp.Server{}
|
||||||
|
|
||||||
|
server.HandlePublish = func(conn *rtmp.Conn) {
|
||||||
|
file, _ := avutil.Create("out.ts")
|
||||||
|
|
||||||
|
findcodec := func(stream av.AudioCodecData, i int) (need bool, dec av.AudioDecoder, enc av.AudioEncoder, err error) {
|
||||||
|
need = true
|
||||||
|
dec, _ = ffmpeg.NewAudioDecoder(stream)
|
||||||
|
enc, _ = ffmpeg.NewAudioEncoderByName("libfdk_aac")
|
||||||
|
enc.SetSampleRate(48000)
|
||||||
|
enc.SetChannelLayout(av.CH_STEREO)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
trans := &transcode.Demuxer{
|
||||||
|
Options: transcode.Options{
|
||||||
|
FindAudioDecoderEncoder: findcodec,
|
||||||
|
},
|
||||||
|
Demuxer: conn,
|
||||||
|
}
|
||||||
|
|
||||||
|
avutil.CopyFile(file, trans)
|
||||||
|
}
|
||||||
|
|
||||||
|
server.ListenAndServe()
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/av/transcode"
|
||||||
|
"github.com/Danile71/joy4/format"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/cgo/ffmpeg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// need ffmpeg with libfdkaac installed
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
format.RegisterAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
infile, _ := avutil.Open("speex.flv")
|
||||||
|
|
||||||
|
findcodec := func(stream av.AudioCodecData, i int) (need bool, dec av.AudioDecoder, enc av.AudioEncoder, err error) {
|
||||||
|
need = true
|
||||||
|
dec, _ = ffmpeg.NewAudioDecoder(stream)
|
||||||
|
enc, _ = ffmpeg.NewAudioEncoderByName("libfdk_aac")
|
||||||
|
enc.SetSampleRate(stream.SampleRate())
|
||||||
|
enc.SetChannelLayout(av.CH_STEREO)
|
||||||
|
enc.SetBitrate(12000)
|
||||||
|
enc.SetOption("profile", "HE-AACv2")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
trans := &transcode.Demuxer{
|
||||||
|
Options: transcode.Options{
|
||||||
|
FindAudioDecoderEncoder: findcodec,
|
||||||
|
},
|
||||||
|
Demuxer: infile,
|
||||||
|
}
|
||||||
|
|
||||||
|
outfile, _ := avutil.Create("out.ts")
|
||||||
|
avutil.CopyFile(outfile, trans)
|
||||||
|
|
||||||
|
outfile.Close()
|
||||||
|
infile.Close()
|
||||||
|
trans.Close()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
|||||||
|
package aac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/codec/aacparser"
|
||||||
|
)
|
||||||
|
|
||||||
|
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}
|
||||||
|
}
|
@ -0,0 +1,495 @@
|
|||||||
|
package flv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/codec"
|
||||||
|
"github.com/Danile71/joy4/codec/aacparser"
|
||||||
|
"github.com/Danile71/joy4/codec/fake"
|
||||||
|
"github.com/Danile71/joy4/codec/h264parser"
|
||||||
|
"github.com/Danile71/joy4/format/flv/flvio"
|
||||||
|
"github.com/Danile71/joy4/utils/bits/pio"
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,467 @@
|
|||||||
|
package flvio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/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
|
||||||
|
}
|
@ -0,0 +1,411 @@
|
|||||||
|
package flvio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/utils/bits/pio"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
"github.com/Danile71/joy4/format/aac"
|
||||||
|
"github.com/Danile71/joy4/format/flv"
|
||||||
|
"github.com/Danile71/joy4/format/mp4"
|
||||||
|
"github.com/Danile71/joy4/format/rtmp"
|
||||||
|
"github.com/Danile71/joy4/format/rtsp"
|
||||||
|
"github.com/Danile71/joy4/format/ts"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,25 @@
|
|||||||
|
package mjpeg
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package mjpeg
|
||||||
|
|
||||||
|
type Stream struct {
|
||||||
|
client *Client
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
package mp4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/codec/aacparser"
|
||||||
|
"github.com/Danile71/joy4/codec/h264parser"
|
||||||
|
"github.com/Danile71/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
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package mp4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/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
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,437 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func moov_Movie() {
|
||||||
|
atom(Header, MovieHeader)
|
||||||
|
atom(MovieExtend, MovieExtend)
|
||||||
|
atoms(Tracks, Track)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mvhd_MovieHeader() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
time32(CreateTime)
|
||||||
|
time32(ModifyTime)
|
||||||
|
int32(TimeScale)
|
||||||
|
int32(Duration)
|
||||||
|
fixed32(PreferredRate)
|
||||||
|
fixed16(PreferredVolume)
|
||||||
|
_skip(10)
|
||||||
|
array(Matrix, int32, 9)
|
||||||
|
time32(PreviewTime)
|
||||||
|
time32(PreviewDuration)
|
||||||
|
time32(PosterTime)
|
||||||
|
time32(SelectionTime)
|
||||||
|
time32(SelectionDuration)
|
||||||
|
time32(CurrentTime)
|
||||||
|
int32(NextTrackId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trak_Track() {
|
||||||
|
atom(Header, TrackHeader)
|
||||||
|
atom(Media, Media)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func tkhd_TrackHeader() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
time32(CreateTime)
|
||||||
|
time32(ModifyTime)
|
||||||
|
int32(TrackId)
|
||||||
|
_skip(4)
|
||||||
|
int32(Duration)
|
||||||
|
_skip(8)
|
||||||
|
int16(Layer)
|
||||||
|
int16(AlternateGroup)
|
||||||
|
fixed16(Volume)
|
||||||
|
_skip(2)
|
||||||
|
array(Matrix, int32, 9)
|
||||||
|
fixed32(TrackWidth)
|
||||||
|
fixed32(TrackHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hdlr_HandlerRefer() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
bytes(Type, 4)
|
||||||
|
bytes(SubType, 4)
|
||||||
|
bytesleft(Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mdia_Media() {
|
||||||
|
atom(Header, MediaHeader)
|
||||||
|
atom(Handler, HandlerRefer)
|
||||||
|
atom(Info, MediaInfo)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mdhd_MediaHeader() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
time32(CreateTime)
|
||||||
|
time32(ModifyTime)
|
||||||
|
int32(TimeScale)
|
||||||
|
int32(Duration)
|
||||||
|
int16(Language)
|
||||||
|
int16(Quality)
|
||||||
|
}
|
||||||
|
|
||||||
|
func minf_MediaInfo() {
|
||||||
|
atom(Sound, SoundMediaInfo)
|
||||||
|
atom(Video, VideoMediaInfo)
|
||||||
|
atom(Data, DataInfo)
|
||||||
|
atom(Sample, SampleTable)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dinf_DataInfo() {
|
||||||
|
atom(Refer, DataRefer)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dref_DataRefer() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
int32(_childrenNR)
|
||||||
|
atom(Url, DataReferUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func url__DataReferUrl() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func smhd_SoundMediaInfo() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
int16(Balance)
|
||||||
|
_skip(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func vmhd_VideoMediaInfo() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
int16(GraphicsMode)
|
||||||
|
array(Opcolor, int16, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stbl_SampleTable() {
|
||||||
|
atom(SampleDesc, SampleDesc)
|
||||||
|
atom(TimeToSample, TimeToSample)
|
||||||
|
atom(CompositionOffset, CompositionOffset)
|
||||||
|
atom(SampleToChunk, SampleToChunk)
|
||||||
|
atom(SyncSample, SyncSample)
|
||||||
|
atom(ChunkOffset, ChunkOffset)
|
||||||
|
atom(SampleSize, SampleSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stsd_SampleDesc() {
|
||||||
|
uint8(Version)
|
||||||
|
_skip(3)
|
||||||
|
int32(_childrenNR)
|
||||||
|
atom(AVC1Desc, AVC1Desc)
|
||||||
|
atom(MP4ADesc, MP4ADesc)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mp4a_MP4ADesc() {
|
||||||
|
_skip(6)
|
||||||
|
int16(DataRefIdx)
|
||||||
|
int16(Version)
|
||||||
|
int16(RevisionLevel)
|
||||||
|
int32(Vendor)
|
||||||
|
int16(NumberOfChannels)
|
||||||
|
int16(SampleSize)
|
||||||
|
int16(CompressionId)
|
||||||
|
_skip(2)
|
||||||
|
fixed32(SampleRate)
|
||||||
|
atom(Conf, ElemStreamDesc)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func avc1_AVC1Desc() {
|
||||||
|
_skip(6)
|
||||||
|
int16(DataRefIdx)
|
||||||
|
int16(Version)
|
||||||
|
int16(Revision)
|
||||||
|
int32(Vendor)
|
||||||
|
int32(TemporalQuality)
|
||||||
|
int32(SpatialQuality)
|
||||||
|
int16(Width)
|
||||||
|
int16(Height)
|
||||||
|
fixed32(HorizontalResolution)
|
||||||
|
fixed32(VorizontalResolution)
|
||||||
|
_skip(4)
|
||||||
|
int16(FrameCount)
|
||||||
|
bytes(CompressorName, 32)
|
||||||
|
int16(Depth)
|
||||||
|
int16(ColorTableId)
|
||||||
|
atom(Conf, AVC1Conf)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func avcC_AVC1Conf() {
|
||||||
|
bytesleft(Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stts_TimeToSample() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, TimeToSampleEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeToSampleEntry() {
|
||||||
|
uint32(Count)
|
||||||
|
uint32(Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stsc_SampleToChunk() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, SampleToChunkEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SampleToChunkEntry() {
|
||||||
|
uint32(FirstChunk)
|
||||||
|
uint32(SamplesPerChunk)
|
||||||
|
uint32(SampleDescId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ctts_CompositionOffset() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, CompositionOffsetEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompositionOffsetEntry() {
|
||||||
|
uint32(Count)
|
||||||
|
uint32(Offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stss_SyncSample() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stco_ChunkOffset() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moof_MovieFrag() {
|
||||||
|
atom(Header, MovieFragHeader)
|
||||||
|
atoms(Tracks, TrackFrag)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mfhd_MovieFragHeader() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(Seqnum)
|
||||||
|
}
|
||||||
|
|
||||||
|
func traf_TrackFrag() {
|
||||||
|
atom(Header, TrackFragHeader)
|
||||||
|
atom(DecodeTime, TrackFragDecodeTime)
|
||||||
|
atom(Run, TrackFragRun)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mvex_MovieExtend() {
|
||||||
|
atoms(Tracks, TrackExtend)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func trex_TrackExtend() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(TrackId)
|
||||||
|
uint32(DefaultSampleDescIdx)
|
||||||
|
uint32(DefaultSampleDuration)
|
||||||
|
uint32(DefaultSampleSize)
|
||||||
|
uint32(DefaultSampleFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stsz_SampleSize() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(SampleSize)
|
||||||
|
_code(func() {
|
||||||
|
if self.SampleSize != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trun_TrackFragRun() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
|
||||||
|
uint32(DataOffset, _code(func() {
|
||||||
|
if self.Flags&TRUN_DATA_OFFSET != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
uint32(FirstSampleFlags, _code(func() {
|
||||||
|
if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
slice(Entries, TrackFragRunEntry, _code(func() {
|
||||||
|
for i, entry := range self.Entries {
|
||||||
|
var flags uint32
|
||||||
|
if i > 0 {
|
||||||
|
flags = self.Flags
|
||||||
|
} else {
|
||||||
|
flags = self.FirstSampleFlags
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Duration)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Size)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Flags)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Cts)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, func() {
|
||||||
|
for i := range self.Entries {
|
||||||
|
var flags uint32
|
||||||
|
if i > 0 {
|
||||||
|
flags = self.Flags
|
||||||
|
} else {
|
||||||
|
flags = self.FirstSampleFlags
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, func() {
|
||||||
|
for i := 0; i < int(_len_Entries); i++ {
|
||||||
|
var flags uint32
|
||||||
|
if i > 0 {
|
||||||
|
flags = self.Flags
|
||||||
|
} else {
|
||||||
|
flags = self.FirstSampleFlags
|
||||||
|
}
|
||||||
|
entry := &self.Entries[i]
|
||||||
|
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||||
|
entry.Duration = pio.U32BE(b[n:])
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||||
|
entry.Size = pio.U32BE(b[n:])
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||||
|
entry.Flags = pio.U32BE(b[n:])
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||||
|
entry.Cts = pio.U32BE(b[n:])
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrackFragRunEntry() {
|
||||||
|
uint32(Duration)
|
||||||
|
uint32(Size)
|
||||||
|
uint32(Flags)
|
||||||
|
uint32(Cts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tfhd_TrackFragHeader() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
|
||||||
|
uint64(BaseDataOffset, _code(func() {
|
||||||
|
if self.Flags&TFHD_BASE_DATA_OFFSET != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
uint32(StsdId, _code(func() {
|
||||||
|
if self.Flags&TFHD_STSD_ID != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
uint32(DefaultDuration, _code(func() {
|
||||||
|
if self.Flags&TFHD_DEFAULT_DURATION != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
uint32(DefaultSize, _code(func() {
|
||||||
|
if self.Flags&TFHD_DEFAULT_SIZE != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
uint32(DefaultFlags, _code(func() {
|
||||||
|
if self.Flags&TFHD_DEFAULT_FLAGS != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func tfdt_TrackFragDecodeTime() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
time64(Time, _code(func() {
|
||||||
|
if self.Version != 0 {
|
||||||
|
PutTime64(b[n:], self.Time)
|
||||||
|
n += 8
|
||||||
|
} else {
|
||||||
|
PutTime32(b[n:], self.Time)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}, func() {
|
||||||
|
if self.Version != 0 {
|
||||||
|
n += 8
|
||||||
|
} else {
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}, func() {
|
||||||
|
if self.Version != 0 {
|
||||||
|
self.Time = GetTime64(b[n:])
|
||||||
|
n += 8
|
||||||
|
} else {
|
||||||
|
self.Time = GetTime32(b[n:])
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,502 @@
|
|||||||
|
package mp4io
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/utils/bits/pio"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,283 @@
|
|||||||
|
package mp4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/codec/aacparser"
|
||||||
|
"github.com/Danile71/joy4/codec/h264parser"
|
||||||
|
"github.com/Danile71/joy4/format/mp4/mp4io"
|
||||||
|
"github.com/Danile71/joy4/utils/bits/pio"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package mp4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/format/mp4/mp4io"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package mp4f
|
||||||
|
|
||||||
|
import "github.com/Danile71/joy4/format/mp4/mp4io"
|
||||||
|
|
||||||
|
type FDummy struct {
|
||||||
|
Data []byte
|
||||||
|
Tag_ mp4io.Tag
|
||||||
|
mp4io.AtomPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self FDummy) Children() []mp4io.Atom {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self FDummy) Tag() mp4io.Tag {
|
||||||
|
return self.Tag_
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self FDummy) Len() int {
|
||||||
|
return len(self.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self FDummy) Marshal(b []byte) int {
|
||||||
|
copy(b, self.Data)
|
||||||
|
return len(self.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self FDummy) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,356 @@
|
|||||||
|
package mp4fio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Danile71/joy4/format/mp4/mp4io"
|
||||||
|
"github.com/Danile71/joy4/utils/bits/pio"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (self MovieFrag) Tag() mp4io.Tag {
|
||||||
|
return mp4io.MOOF
|
||||||
|
}
|
||||||
|
|
||||||
|
type MovieFrag struct {
|
||||||
|
Header *MovieFragHeader
|
||||||
|
Tracks []*TrackFrag
|
||||||
|
Unknowns []mp4io.Atom
|
||||||
|
mp4io.AtomPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self MovieFrag) Marshal(b []byte) (n int) {
|
||||||
|
pio.PutU32BE(b[4:], uint32(mp4io.MOOF))
|
||||||
|
n += self.marshal(b[8:]) + 8
|
||||||
|
pio.PutU32BE(b[0:], uint32(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self MovieFrag) marshal(b []byte) (n int) {
|
||||||
|
if self.Header != nil {
|
||||||
|
n += self.Header.Marshal(b[n:])
|
||||||
|
}
|
||||||
|
for _, atom := range self.Tracks {
|
||||||
|
n += atom.Marshal(b[n:])
|
||||||
|
}
|
||||||
|
for _, atom := range self.Unknowns {
|
||||||
|
n += atom.Marshal(b[n:])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self MovieFrag) Len() (n int) {
|
||||||
|
n += 8
|
||||||
|
if self.Header != nil {
|
||||||
|
n += self.Header.Len()
|
||||||
|
}
|
||||||
|
for _, atom := range self.Tracks {
|
||||||
|
n += atom.Len()
|
||||||
|
}
|
||||||
|
for _, atom := range self.Unknowns {
|
||||||
|
n += atom.Len()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self *MovieFrag) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self MovieFrag) Children() (r []mp4io.Atom) {
|
||||||
|
if self.Header != nil {
|
||||||
|
r = append(r, self.Header)
|
||||||
|
}
|
||||||
|
for _, atom := range self.Tracks {
|
||||||
|
r = append(r, atom)
|
||||||
|
}
|
||||||
|
r = append(r, self.Unknowns...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self MovieFragHeader) Tag() mp4io.Tag {
|
||||||
|
return mp4io.MFHD
|
||||||
|
}
|
||||||
|
|
||||||
|
type MovieFragHeader struct {
|
||||||
|
Version uint8
|
||||||
|
Flags uint32
|
||||||
|
Seqnum uint32
|
||||||
|
mp4io.AtomPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self MovieFragHeader) Marshal(b []byte) (n int) {
|
||||||
|
pio.PutU32BE(b[4:], uint32(mp4io.MFHD))
|
||||||
|
n += self.marshal(b[8:]) + 8
|
||||||
|
pio.PutU32BE(b[0:], uint32(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self MovieFragHeader) marshal(b []byte) (n int) {
|
||||||
|
pio.PutU8(b[n:], self.Version)
|
||||||
|
n += 1
|
||||||
|
pio.PutU24BE(b[n:], self.Flags)
|
||||||
|
n += 3
|
||||||
|
pio.PutU32BE(b[n:], self.Seqnum)
|
||||||
|
n += 4
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self MovieFragHeader) Len() (n int) {
|
||||||
|
n += 8
|
||||||
|
n += 1
|
||||||
|
n += 3
|
||||||
|
n += 4
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self *MovieFragHeader) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self MovieFragHeader) Children() (r []mp4io.Atom) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self TrackFragRun) Tag() mp4io.Tag {
|
||||||
|
return mp4io.TRUN
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrackFragRun struct {
|
||||||
|
Version uint8
|
||||||
|
Flags uint32
|
||||||
|
DataOffset uint32
|
||||||
|
FirstSampleFlags uint32
|
||||||
|
Entries []mp4io.TrackFragRunEntry
|
||||||
|
mp4io.AtomPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self TrackFragRun) Marshal(b []byte) (n int) {
|
||||||
|
pio.PutU32BE(b[4:], uint32(mp4io.TRUN))
|
||||||
|
n += self.marshal(b[8:]) + 8
|
||||||
|
pio.PutU32BE(b[0:], uint32(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFragRun) marshal(b []byte) (n int) {
|
||||||
|
pio.PutU8(b[n:], self.Version)
|
||||||
|
n += 1
|
||||||
|
pio.PutU24BE(b[n:], self.Flags)
|
||||||
|
n += 3
|
||||||
|
pio.PutU32BE(b[n:], uint32(len(self.Entries)))
|
||||||
|
n += 4
|
||||||
|
if self.Flags&mp4io.TRUN_DATA_OFFSET != 0 {
|
||||||
|
{
|
||||||
|
pio.PutU32BE(b[n:], self.DataOffset)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.Flags&mp4io.TRUN_FIRST_SAMPLE_FLAGS != 0 {
|
||||||
|
{
|
||||||
|
pio.PutU32BE(b[n:], self.FirstSampleFlags)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, entry := range self.Entries {
|
||||||
|
var flags uint32
|
||||||
|
if i > 0 {
|
||||||
|
flags = self.Flags
|
||||||
|
} else {
|
||||||
|
flags = self.FirstSampleFlags
|
||||||
|
}
|
||||||
|
//if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Duration)
|
||||||
|
n += 4
|
||||||
|
//}
|
||||||
|
//if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Size)
|
||||||
|
n += 4
|
||||||
|
//}
|
||||||
|
if flags&mp4io.TRUN_SAMPLE_FLAGS != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Flags)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
//if flags&TRUN_SAMPLE_CTS != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Cts)
|
||||||
|
n += 4
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFragRun) Len() (n int) {
|
||||||
|
n += 8
|
||||||
|
n += 1
|
||||||
|
n += 3
|
||||||
|
n += 4
|
||||||
|
if self.Flags&mp4io.TRUN_DATA_OFFSET != 0 {
|
||||||
|
{
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.Flags&mp4io.TRUN_FIRST_SAMPLE_FLAGS != 0 {
|
||||||
|
{
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range self.Entries {
|
||||||
|
var flags uint32
|
||||||
|
if i > 0 {
|
||||||
|
flags = self.Flags
|
||||||
|
} else {
|
||||||
|
flags = self.FirstSampleFlags
|
||||||
|
}
|
||||||
|
//if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||||
|
n += 4
|
||||||
|
//}
|
||||||
|
//if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||||
|
n += 4
|
||||||
|
//}
|
||||||
|
if flags&mp4io.TRUN_SAMPLE_FLAGS != 0 {
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
//if flags&TRUN_SAMPLE_CTS != 0 {
|
||||||
|
n += 4
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self *TrackFragRun) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFragRun) Children() (r []mp4io.Atom) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self TrackFrag) Tag() mp4io.Tag {
|
||||||
|
return mp4io.TRAF
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrackFrag struct {
|
||||||
|
Header *TrackFragHeader
|
||||||
|
DecodeTime *TrackFragDecodeTime
|
||||||
|
Run *TrackFragRun
|
||||||
|
Unknowns []mp4io.Atom
|
||||||
|
mp4io.AtomPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self TrackFrag) Marshal(b []byte) (n int) {
|
||||||
|
pio.PutU32BE(b[4:], uint32(mp4io.TRAF))
|
||||||
|
n += self.marshal(b[8:]) + 8
|
||||||
|
pio.PutU32BE(b[0:], uint32(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFrag) marshal(b []byte) (n int) {
|
||||||
|
if self.Header != nil {
|
||||||
|
n += self.Header.Marshal(b[n:])
|
||||||
|
}
|
||||||
|
if self.DecodeTime != nil {
|
||||||
|
n += self.DecodeTime.Marshal(b[n:])
|
||||||
|
}
|
||||||
|
if self.Run != nil {
|
||||||
|
n += self.Run.Marshal(b[n:])
|
||||||
|
}
|
||||||
|
for _, atom := range self.Unknowns {
|
||||||
|
n += atom.Marshal(b[n:])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFrag) Len() (n int) {
|
||||||
|
n += 8
|
||||||
|
if self.Header != nil {
|
||||||
|
n += self.Header.Len()
|
||||||
|
}
|
||||||
|
if self.DecodeTime != nil {
|
||||||
|
n += self.DecodeTime.Len()
|
||||||
|
}
|
||||||
|
if self.Run != nil {
|
||||||
|
n += self.Run.Len()
|
||||||
|
}
|
||||||
|
for _, atom := range self.Unknowns {
|
||||||
|
n += atom.Len()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self *TrackFrag) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFrag) Children() (r []mp4io.Atom) {
|
||||||
|
if self.Header != nil {
|
||||||
|
r = append(r, self.Header)
|
||||||
|
}
|
||||||
|
if self.DecodeTime != nil {
|
||||||
|
r = append(r, self.DecodeTime)
|
||||||
|
}
|
||||||
|
if self.Run != nil {
|
||||||
|
r = append(r, self.Run)
|
||||||
|
}
|
||||||
|
r = append(r, self.Unknowns...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const LenTrackFragRunEntry = 16
|
||||||
|
|
||||||
|
func (self TrackFragHeader) Tag() mp4io.Tag {
|
||||||
|
return mp4io.TFHD
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrackFragHeader struct {
|
||||||
|
Data []byte
|
||||||
|
mp4io.AtomPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self TrackFragHeader) Marshal(b []byte) (n int) {
|
||||||
|
pio.PutU32BE(b[4:], uint32(mp4io.TFHD))
|
||||||
|
n += self.marshal(b[8:]) + 8
|
||||||
|
pio.PutU32BE(b[0:], uint32(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFragHeader) marshal(b []byte) (n int) {
|
||||||
|
copy(b, self.Data)
|
||||||
|
n += len(self.Data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFragHeader) Len() (n int) {
|
||||||
|
return len(self.Data) + 8
|
||||||
|
}
|
||||||
|
func (self *TrackFragHeader) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFragHeader) Children() (r []mp4io.Atom) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self TrackFragDecodeTime) Tag() mp4io.Tag {
|
||||||
|
return mp4io.TFDT
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrackFragDecodeTime struct {
|
||||||
|
Version uint8
|
||||||
|
Flags uint32
|
||||||
|
Time uint64
|
||||||
|
mp4io.AtomPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self TrackFragDecodeTime) Marshal(b []byte) (n int) {
|
||||||
|
pio.PutU32BE(b[4:], uint32(mp4io.TFDT))
|
||||||
|
n += self.marshal(b[8:]) + 8
|
||||||
|
pio.PutU32BE(b[0:], uint32(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFragDecodeTime) marshal(b []byte) (n int) {
|
||||||
|
pio.PutU8(b[n:], self.Version)
|
||||||
|
n += 1
|
||||||
|
pio.PutU24BE(b[n:], self.Flags)
|
||||||
|
n += 3
|
||||||
|
pio.PutU64BE(b[n:], self.Time)
|
||||||
|
n += 8
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFragDecodeTime) Len() (n int) {
|
||||||
|
n += 8
|
||||||
|
n += 1
|
||||||
|
n += 3
|
||||||
|
n += 8
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self *TrackFragDecodeTime) Unmarshal(b []byte, offset int) (n int, err error) {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (self TrackFragDecodeTime) Children() (r []mp4io.Atom) {
|
||||||
|
return
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,436 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func moov_Movie() {
|
||||||
|
atom(Header, MovieHeader)
|
||||||
|
atom(MovieExtend, MovieExtend)
|
||||||
|
atoms(Tracks, Track)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mvhd_MovieHeader() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
time32(CreateTime)
|
||||||
|
time32(ModifyTime)
|
||||||
|
int32(TimeScale)
|
||||||
|
int32(Duration)
|
||||||
|
fixed32(PreferredRate)
|
||||||
|
fixed16(PreferredVolume)
|
||||||
|
_skip(10)
|
||||||
|
array(Matrix, int32, 9)
|
||||||
|
time32(PreviewTime)
|
||||||
|
time32(PreviewDuration)
|
||||||
|
time32(PosterTime)
|
||||||
|
time32(SelectionTime)
|
||||||
|
time32(SelectionDuration)
|
||||||
|
time32(CurrentTime)
|
||||||
|
int32(NextTrackId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trak_Track() {
|
||||||
|
atom(Header, TrackHeader)
|
||||||
|
atom(Media, Media)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func tkhd_TrackHeader() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
time32(CreateTime)
|
||||||
|
time32(ModifyTime)
|
||||||
|
int32(TrackId)
|
||||||
|
_skip(4)
|
||||||
|
int32(Duration)
|
||||||
|
_skip(8)
|
||||||
|
int16(Layer)
|
||||||
|
int16(AlternateGroup)
|
||||||
|
fixed16(Volume)
|
||||||
|
_skip(2)
|
||||||
|
array(Matrix, int32, 9)
|
||||||
|
fixed32(TrackWidth)
|
||||||
|
fixed32(TrackHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hdlr_HandlerRefer() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
bytes(Type, 4)
|
||||||
|
bytes(SubType, 4)
|
||||||
|
bytesleft(Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mdia_Media() {
|
||||||
|
atom(Header, MediaHeader)
|
||||||
|
atom(Handler, HandlerRefer)
|
||||||
|
atom(Info, MediaInfo)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mdhd_MediaHeader() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
time32(CreateTime)
|
||||||
|
time32(ModifyTime)
|
||||||
|
int32(TimeScale)
|
||||||
|
int32(Duration)
|
||||||
|
int16(Language)
|
||||||
|
int16(Quality)
|
||||||
|
}
|
||||||
|
|
||||||
|
func minf_MediaInfo() {
|
||||||
|
atom(Sound, SoundMediaInfo)
|
||||||
|
atom(Video, VideoMediaInfo)
|
||||||
|
atom(Data, DataInfo)
|
||||||
|
atom(Sample, SampleTable)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dinf_DataInfo() {
|
||||||
|
atom(Refer, DataRefer)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dref_DataRefer() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
int32(_childrenNR)
|
||||||
|
atom(Url, DataReferUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func url__DataReferUrl() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func smhd_SoundMediaInfo() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
int16(Balance)
|
||||||
|
_skip(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func vmhd_VideoMediaInfo() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
int16(GraphicsMode)
|
||||||
|
array(Opcolor, int16, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stbl_SampleTable() {
|
||||||
|
atom(SampleDesc, SampleDesc)
|
||||||
|
atom(TimeToSample, TimeToSample)
|
||||||
|
atom(CompositionOffset, CompositionOffset)
|
||||||
|
atom(SampleToChunk, SampleToChunk)
|
||||||
|
atom(SyncSample, SyncSample)
|
||||||
|
atom(ChunkOffset, ChunkOffset)
|
||||||
|
atom(SampleSize, SampleSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stsd_SampleDesc() {
|
||||||
|
uint8(Version)
|
||||||
|
_skip(3)
|
||||||
|
int32(_childrenNR)
|
||||||
|
atom(AVC1Desc, AVC1Desc)
|
||||||
|
atom(MP4ADesc, MP4ADesc)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mp4a_MP4ADesc() {
|
||||||
|
_skip(6)
|
||||||
|
int16(DataRefIdx)
|
||||||
|
int16(Version)
|
||||||
|
int16(RevisionLevel)
|
||||||
|
int32(Vendor)
|
||||||
|
int16(NumberOfChannels)
|
||||||
|
int16(SampleSize)
|
||||||
|
int16(CompressionId)
|
||||||
|
_skip(2)
|
||||||
|
fixed32(SampleRate)
|
||||||
|
atom(Conf, ElemStreamDesc)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func avc1_AVC1Desc() {
|
||||||
|
_skip(6)
|
||||||
|
int16(DataRefIdx)
|
||||||
|
int16(Version)
|
||||||
|
int16(Revision)
|
||||||
|
int32(Vendor)
|
||||||
|
int32(TemporalQuality)
|
||||||
|
int32(SpatialQuality)
|
||||||
|
int16(Width)
|
||||||
|
int16(Height)
|
||||||
|
fixed32(HorizontalResolution)
|
||||||
|
fixed32(VorizontalResolution)
|
||||||
|
_skip(4)
|
||||||
|
int16(FrameCount)
|
||||||
|
bytes(CompressorName, 32)
|
||||||
|
int16(Depth)
|
||||||
|
int16(ColorTableId)
|
||||||
|
atom(Conf, AVC1Conf)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func avcC_AVC1Conf() {
|
||||||
|
bytesleft(Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stts_TimeToSample() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, TimeToSampleEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeToSampleEntry() {
|
||||||
|
uint32(Count)
|
||||||
|
uint32(Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stsc_SampleToChunk() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, SampleToChunkEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SampleToChunkEntry() {
|
||||||
|
uint32(FirstChunk)
|
||||||
|
uint32(SamplesPerChunk)
|
||||||
|
uint32(SampleDescId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ctts_CompositionOffset() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, CompositionOffsetEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompositionOffsetEntry() {
|
||||||
|
uint32(Count)
|
||||||
|
uint32(Offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stss_SyncSample() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stco_ChunkOffset() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moof_MovieFrag() {
|
||||||
|
atom(Header, MovieFragHeader)
|
||||||
|
atoms(Tracks, TrackFrag)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mfhd_MovieFragHeader() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(Seqnum)
|
||||||
|
}
|
||||||
|
|
||||||
|
func traf_TrackFrag() {
|
||||||
|
atom(Header, TrackFragHeader)
|
||||||
|
atom(DecodeTime, TrackFragDecodeTime)
|
||||||
|
atom(Run, TrackFragRun)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mvex_MovieExtend() {
|
||||||
|
atoms(Tracks, TrackExtend)
|
||||||
|
_unknowns()
|
||||||
|
}
|
||||||
|
|
||||||
|
func trex_TrackExtend() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(TrackId)
|
||||||
|
uint32(DefaultSampleDescIdx)
|
||||||
|
uint32(DefaultSampleDuration)
|
||||||
|
uint32(DefaultSampleSize)
|
||||||
|
uint32(DefaultSampleFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stsz_SampleSize() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(SampleSize)
|
||||||
|
_code(func() {
|
||||||
|
if self.SampleSize != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
uint32(_len_Entries)
|
||||||
|
slice(Entries, uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trun_TrackFragRun() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
uint32(_len_Entries)
|
||||||
|
|
||||||
|
uint32(DataOffset, _code(func() {
|
||||||
|
if self.Flags&TRUN_DATA_OFFSET != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
uint32(FirstSampleFlags, _code(func() {
|
||||||
|
if self.Flags&TRUN_FIRST_SAMPLE_FLAGS != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
slice(Entries, TrackFragRunEntry, _code(func() {
|
||||||
|
for i, entry := range self.Entries {
|
||||||
|
var flags uint32
|
||||||
|
if i > 0 {
|
||||||
|
flags = self.Flags
|
||||||
|
} else {
|
||||||
|
flags = self.FirstSampleFlags
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Duration)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Size)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Flags)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||||
|
pio.PutU32BE(b[n:], entry.Cts)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, func() {
|
||||||
|
for i := range self.Entries {
|
||||||
|
var flags uint32
|
||||||
|
if i > 0 {
|
||||||
|
flags = self.Flags
|
||||||
|
} else {
|
||||||
|
flags = self.FirstSampleFlags
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, func() {
|
||||||
|
for i := 0; i < int(_len_Entries); i++ {
|
||||||
|
var flags uint32
|
||||||
|
if i > 0 {
|
||||||
|
flags = self.Flags
|
||||||
|
} else {
|
||||||
|
flags = self.FirstSampleFlags
|
||||||
|
}
|
||||||
|
entry := &self.Entries[i]
|
||||||
|
if flags&TRUN_SAMPLE_DURATION != 0 {
|
||||||
|
entry.Duration = pio.U32BE(b[n:])
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_SIZE != 0 {
|
||||||
|
entry.Size = pio.U32BE(b[n:])
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_FLAGS != 0 {
|
||||||
|
entry.Flags = pio.U32BE(b[n:])
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
if flags&TRUN_SAMPLE_CTS != 0 {
|
||||||
|
entry.Cts = pio.U32BE(b[n:])
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrackFragRunEntry() {
|
||||||
|
uint32(Duration)
|
||||||
|
uint32(Size)
|
||||||
|
uint32(Flags)
|
||||||
|
uint32(Cts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tfhd_TrackFragHeader() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
|
||||||
|
uint64(BaseDataOffset, _code(func() {
|
||||||
|
if self.Flags&TFHD_BASE_DATA_OFFSET != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
uint32(StsdId, _code(func() {
|
||||||
|
if self.Flags&TFHD_STSD_ID != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
uint32(DefaultDuration, _code(func() {
|
||||||
|
if self.Flags&TFHD_DEFAULT_DURATION != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
uint32(DefaultSize, _code(func() {
|
||||||
|
if self.Flags&TFHD_DEFAULT_SIZE != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
uint32(DefaultFlags, _code(func() {
|
||||||
|
if self.Flags&TFHD_DEFAULT_FLAGS != 0 {
|
||||||
|
doit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func tfdt_TrackFragDecodeTime() {
|
||||||
|
uint8(Version)
|
||||||
|
uint24(Flags)
|
||||||
|
time64(Time, _code(func() {
|
||||||
|
if self.Version != 0 {
|
||||||
|
PutTime64(b[n:], self.Time)
|
||||||
|
n += 8
|
||||||
|
} else {
|
||||||
|
PutTime32(b[n:], self.Time)
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}, func() {
|
||||||
|
if self.Version != 0 {
|
||||||
|
n += 8
|
||||||
|
} else {
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}, func() {
|
||||||
|
if self.Version != 0 {
|
||||||
|
self.Time = GetTime64(b[n:])
|
||||||
|
n += 8
|
||||||
|
} else {
|
||||||
|
self.Time = GetTime32(b[n:])
|
||||||
|
n += 4
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
package mp4fio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Danile71/joy4/format/mp4/mp4io"
|
||||||
|
"github.com/Danile71/joy4/utils/bits/pio"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ElemStreamDesc struct {
|
||||||
|
DecConfig []byte
|
||||||
|
TrackId uint16
|
||||||
|
mp4io.AtomPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self ElemStreamDesc) Children() []mp4io.Atom {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self ElemStreamDesc) fillLength(b []byte, length int) (n int) {
|
||||||
|
b[n] = uint8(length & 0x7f)
|
||||||
|
n++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self ElemStreamDesc) lenDescHdr() (n int) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
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:], mp4io.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:], mp4io.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:], mp4io.MP4DecSpecificDescrTag, datalen-n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self ElemStreamDesc) Len() (n int) {
|
||||||
|
// len + tag
|
||||||
|
return 8 +
|
||||||
|
// ver + flags
|
||||||
|
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(mp4io.ESDS))
|
||||||
|
n += 8
|
||||||
|
pio.PutU32BE(b[n:], 0) // Version
|
||||||
|
n += 4
|
||||||
|
datalen := self.Len()
|
||||||
|
n += self.fillESDescHdr(b[n:], datalen-n-self.lenESDescHdr()+3)
|
||||||
|
n += self.fillDecConfigDescHdr(b[n:], datalen-n-self.lenDescHdr()-3)
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,402 @@
|
|||||||
|
package mp4f
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/codec/aacparser"
|
||||||
|
"github.com/Danile71/joy4/codec/h264parser"
|
||||||
|
"github.com/Danile71/joy4/format/mp4/mp4io"
|
||||||
|
"github.com/Danile71/joy4/format/mp4f/mp4fio"
|
||||||
|
"github.com/Danile71/joy4/utils/bits/pio"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Muxer struct {
|
||||||
|
maxFrames int
|
||||||
|
bufw *bufio.Writer
|
||||||
|
wpos int64
|
||||||
|
fragmentIndex int
|
||||||
|
streams []*Stream
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMuxer(w *os.File) *Muxer {
|
||||||
|
return &Muxer{}
|
||||||
|
}
|
||||||
|
func (self *Muxer) SetPath(path string) {
|
||||||
|
self.path = path
|
||||||
|
}
|
||||||
|
func (self *Muxer) SetMaxFrames(count int) {
|
||||||
|
self.maxFrames = count
|
||||||
|
}
|
||||||
|
func (self *Muxer) newStream(codec av.CodecData) (err error) {
|
||||||
|
switch codec.Type() {
|
||||||
|
case av.H264, av.AAC:
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("fmp4: 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{},
|
||||||
|
SampleSize: &mp4io.SampleSize{},
|
||||||
|
ChunkOffset: &mp4io.ChunkOffset{},
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.trackAtom = &mp4io.Track{
|
||||||
|
Header: &mp4io.TrackHeader{
|
||||||
|
TrackId: int32(len(self.streams) + 1),
|
||||||
|
Flags: 0x0007,
|
||||||
|
Duration: 0,
|
||||||
|
Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
|
||||||
|
},
|
||||||
|
Media: &mp4io.Media{
|
||||||
|
Header: &mp4io.MediaHeader{
|
||||||
|
TimeScale: 1000,
|
||||||
|
Duration: 0,
|
||||||
|
Language: 21956,
|
||||||
|
},
|
||||||
|
Info: &mp4io.MediaInfo{
|
||||||
|
Sample: stream.sample,
|
||||||
|
Data: &mp4io.DataInfo{
|
||||||
|
Refer: &mp4io.DataRefer{
|
||||||
|
Url: &mp4io.DataReferUrl{
|
||||||
|
Flags: 0x000001,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
switch codec.Type() {
|
||||||
|
case av.H264:
|
||||||
|
stream.sample.SyncSample = &mp4io.SyncSample{}
|
||||||
|
stream.timeScale = 90000
|
||||||
|
case av.AAC:
|
||||||
|
stream.timeScale = 8000
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.muxer = self
|
||||||
|
self.streams = append(self.streams, stream)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Stream) buildEsds(conf []byte) *FDummy {
|
||||||
|
esds := &mp4fio.ElemStreamDesc{DecConfig: conf}
|
||||||
|
|
||||||
|
b := make([]byte, esds.Len())
|
||||||
|
esds.Marshal(b)
|
||||||
|
|
||||||
|
esdsDummy := FDummy{
|
||||||
|
Data: b,
|
||||||
|
Tag_: mp4io.Tag(uint32(mp4io.ESDS)),
|
||||||
|
}
|
||||||
|
return &esdsDummy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Stream) buildHdlr() *FDummy {
|
||||||
|
hdlr := FDummy{
|
||||||
|
Data: []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x35, 0x68, 0x64, 0x6C, 0x72,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x69,
|
||||||
|
0x64, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x47, 0x6F, 0x6F, 0x64, 0x67, 0x61,
|
||||||
|
0x6D, 0x65, 0x20, 0x47, 0x4F, 0x20, 0x53, 0x65, 0x72, 0x76,
|
||||||
|
0x65, 0x72, 0x00, 0x00, 0x00},
|
||||||
|
|
||||||
|
Tag_: mp4io.Tag(uint32(mp4io.HDLR)),
|
||||||
|
}
|
||||||
|
return &hdlr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Stream) buildAudioHdlr() *FDummy {
|
||||||
|
hdlr := FDummy{
|
||||||
|
Data: []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x35, 0x68, 0x64, 0x6C, 0x72,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x6F,
|
||||||
|
0x75, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x42, 0x65, 0x6E, 0x74, 0x6F, 0x34,
|
||||||
|
0x20, 0x53, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x48, 0x61, 0x6E,
|
||||||
|
0x64, 0x6C, 0x65, 0x72, 0x00},
|
||||||
|
|
||||||
|
Tag_: mp4io.Tag(uint32(mp4io.HDLR)),
|
||||||
|
}
|
||||||
|
return &hdlr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Stream) buildEdts() *FDummy {
|
||||||
|
edts := FDummy{
|
||||||
|
Data: []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x30, 0x65, 0x64, 0x74, 0x73,
|
||||||
|
0x00, 0x00, 0x00, 0x28, 0x65, 0x6C, 0x73, 0x74, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x9B, 0x24, 0x00, 0x00, 0x02, 0x10, 0x00, 0x01, 0x00, 0x00,
|
||||||
|
},
|
||||||
|
Tag_: mp4io.Tag(0x65647473),
|
||||||
|
}
|
||||||
|
return &edts
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Header.TrackWidth = float64(width)
|
||||||
|
self.trackAtom.Header.TrackHeight = float64(height)
|
||||||
|
|
||||||
|
self.trackAtom.Media.Handler = &mp4io.HandlerRefer{
|
||||||
|
SubType: [4]byte{'v', 'i', 'd', 'e'},
|
||||||
|
Name: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'G', 'G', 0, 0, 0},
|
||||||
|
}
|
||||||
|
self.trackAtom.Media.Info.Video = &mp4io.VideoMediaInfo{
|
||||||
|
Flags: 0x000001,
|
||||||
|
}
|
||||||
|
self.codecString = fmt.Sprintf("avc1.%02X%02X%02X", codec.RecordInfo.AVCProfileIndication, codec.RecordInfo.ProfileCompatibility, codec.RecordInfo.AVCLevelIndication)
|
||||||
|
} 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: 16,
|
||||||
|
SampleRate: float64(codec.SampleRate()),
|
||||||
|
Unknowns: []mp4io.Atom{self.buildEsds(codec.MPEG4AudioConfigBytes())},
|
||||||
|
}
|
||||||
|
self.trackAtom.Header.Volume = 1
|
||||||
|
self.trackAtom.Header.AlternateGroup = 1
|
||||||
|
self.trackAtom.Header.Duration = 0
|
||||||
|
|
||||||
|
self.trackAtom.Media.Handler = &mp4io.HandlerRefer{
|
||||||
|
SubType: [4]byte{'s', 'o', 'u', 'n'},
|
||||||
|
Name: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'G', 'G', 0, 0, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.trackAtom.Media.Info.Sound = &mp4io.SoundMediaInfo{}
|
||||||
|
self.codecString = "mp4a.40.2"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("fmp4: codec type=%d invalid", self.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Muxer) WriteTrailer() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||||
|
element.streams = []*Stream{}
|
||||||
|
for _, stream := range streams {
|
||||||
|
if err = element.newStream(stream); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Muxer) GetInit(streams []av.CodecData) (string, []byte) {
|
||||||
|
moov := &mp4io.Movie{
|
||||||
|
Header: &mp4io.MovieHeader{
|
||||||
|
PreferredRate: 1,
|
||||||
|
PreferredVolume: 1,
|
||||||
|
Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
|
||||||
|
NextTrackId: 3,
|
||||||
|
Duration: 0,
|
||||||
|
TimeScale: 1000,
|
||||||
|
CreateTime: time0(),
|
||||||
|
ModifyTime: time0(),
|
||||||
|
PreviewTime: time0(),
|
||||||
|
PreviewDuration: time0(),
|
||||||
|
PosterTime: time0(),
|
||||||
|
SelectionTime: time0(),
|
||||||
|
SelectionDuration: time0(),
|
||||||
|
CurrentTime: time0(),
|
||||||
|
},
|
||||||
|
Unknowns: []mp4io.Atom{element.buildMvex()},
|
||||||
|
}
|
||||||
|
var meta string
|
||||||
|
for _, stream := range element.streams {
|
||||||
|
if err := stream.fillTrackAtom(); err != nil {
|
||||||
|
return meta, []byte{}
|
||||||
|
}
|
||||||
|
moov.Tracks = append(moov.Tracks, stream.trackAtom)
|
||||||
|
meta += stream.codecString + ","
|
||||||
|
}
|
||||||
|
meta = meta[:len(meta)-1]
|
||||||
|
ftypeData := []byte{0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x36, 0x00, 0x00, 0x00, 0x01, 0x69, 0x73, 0x6f, 0x36, 0x64, 0x61, 0x73, 0x68}
|
||||||
|
file := make([]byte, moov.Len()+len(ftypeData))
|
||||||
|
copy(file, ftypeData)
|
||||||
|
moov.Marshal(file[len(ftypeData):])
|
||||||
|
return meta, file
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Muxer) WritePacket(pkt av.Packet, GOP bool) (bool, []byte, error) {
|
||||||
|
stream := element.streams[pkt.Idx]
|
||||||
|
if GOP {
|
||||||
|
ts := time.Duration(0)
|
||||||
|
if stream.lastpkt != nil {
|
||||||
|
ts = pkt.Time - stream.lastpkt.Time
|
||||||
|
}
|
||||||
|
got, buf, err := stream.writePacketV3(pkt, ts, 5)
|
||||||
|
stream.lastpkt = &pkt
|
||||||
|
if err != nil {
|
||||||
|
return false, []byte{}, err
|
||||||
|
}
|
||||||
|
return got, buf, err
|
||||||
|
}
|
||||||
|
ts := time.Duration(0)
|
||||||
|
if stream.lastpkt != nil {
|
||||||
|
ts = pkt.Time - stream.lastpkt.Time
|
||||||
|
}
|
||||||
|
got, buf, err := stream.writePacketV2(pkt, ts, 5)
|
||||||
|
stream.lastpkt = &pkt
|
||||||
|
if err != nil {
|
||||||
|
return false, []byte{}, err
|
||||||
|
}
|
||||||
|
return got, buf, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Stream) writePacketV3(pkt av.Packet, rawdur time.Duration, maxFrames int) (bool, []byte, error) {
|
||||||
|
trackID := pkt.Idx + 1
|
||||||
|
var out []byte
|
||||||
|
var got bool
|
||||||
|
if element.sampleIndex > maxFrames && pkt.IsKeyFrame {
|
||||||
|
element.moof.Tracks[0].Run.DataOffset = uint32(element.moof.Len() + 8)
|
||||||
|
out = make([]byte, element.moof.Len()+len(element.buffer))
|
||||||
|
element.moof.Marshal(out)
|
||||||
|
pio.PutU32BE(element.buffer, uint32(len(element.buffer)))
|
||||||
|
copy(out[element.moof.Len():], element.buffer)
|
||||||
|
element.sampleIndex = 0
|
||||||
|
element.muxer.fragmentIndex++
|
||||||
|
got = true
|
||||||
|
}
|
||||||
|
if element.sampleIndex == 0 {
|
||||||
|
element.moof.Header = &mp4fio.MovieFragHeader{Seqnum: uint32(element.muxer.fragmentIndex + 1)}
|
||||||
|
element.moof.Tracks = []*mp4fio.TrackFrag{
|
||||||
|
&mp4fio.TrackFrag{
|
||||||
|
Header: &mp4fio.TrackFragHeader{
|
||||||
|
Data: []byte{0x00, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, uint8(trackID), 0x01, 0x01, 0x00, 0x00},
|
||||||
|
},
|
||||||
|
DecodeTime: &mp4fio.TrackFragDecodeTime{
|
||||||
|
Version: 1,
|
||||||
|
Flags: 0,
|
||||||
|
Time: uint64(element.dts),
|
||||||
|
},
|
||||||
|
Run: &mp4fio.TrackFragRun{
|
||||||
|
Flags: 0x000b05,
|
||||||
|
FirstSampleFlags: 0x02000000,
|
||||||
|
DataOffset: 0,
|
||||||
|
Entries: []mp4io.TrackFragRunEntry{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
element.buffer = []byte{0x00, 0x00, 0x00, 0x00, 0x6d, 0x64, 0x61, 0x74}
|
||||||
|
}
|
||||||
|
runEnrty := mp4io.TrackFragRunEntry{
|
||||||
|
Duration: uint32(element.timeToTs(rawdur)),
|
||||||
|
Size: uint32(len(pkt.Data)),
|
||||||
|
Cts: uint32(element.timeToTs(pkt.CompositionTime)),
|
||||||
|
}
|
||||||
|
element.moof.Tracks[0].Run.Entries = append(element.moof.Tracks[0].Run.Entries, runEnrty)
|
||||||
|
element.buffer = append(element.buffer, pkt.Data...)
|
||||||
|
element.sampleIndex++
|
||||||
|
element.dts += element.timeToTs(rawdur)
|
||||||
|
return got, out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Stream) writePacketV2(pkt av.Packet, rawdur time.Duration, maxFrames int) (bool, []byte, error) {
|
||||||
|
trackID := pkt.Idx + 1
|
||||||
|
if element.sampleIndex == 0 {
|
||||||
|
element.moof.Header = &mp4fio.MovieFragHeader{Seqnum: uint32(element.muxer.fragmentIndex + 1)}
|
||||||
|
element.moof.Tracks = []*mp4fio.TrackFrag{
|
||||||
|
&mp4fio.TrackFrag{
|
||||||
|
Header: &mp4fio.TrackFragHeader{
|
||||||
|
Data: []byte{0x00, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, uint8(trackID), 0x01, 0x01, 0x00, 0x00},
|
||||||
|
},
|
||||||
|
DecodeTime: &mp4fio.TrackFragDecodeTime{
|
||||||
|
Version: 1,
|
||||||
|
Flags: 0,
|
||||||
|
Time: uint64(element.dts),
|
||||||
|
},
|
||||||
|
Run: &mp4fio.TrackFragRun{
|
||||||
|
Flags: 0x000b05,
|
||||||
|
FirstSampleFlags: 0x02000000,
|
||||||
|
DataOffset: 0,
|
||||||
|
Entries: []mp4io.TrackFragRunEntry{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
element.buffer = []byte{0x00, 0x00, 0x00, 0x00, 0x6d, 0x64, 0x61, 0x74}
|
||||||
|
}
|
||||||
|
runEnrty := mp4io.TrackFragRunEntry{
|
||||||
|
Duration: uint32(element.timeToTs(rawdur)),
|
||||||
|
Size: uint32(len(pkt.Data)),
|
||||||
|
Cts: uint32(element.timeToTs(pkt.CompositionTime)),
|
||||||
|
}
|
||||||
|
element.moof.Tracks[0].Run.Entries = append(element.moof.Tracks[0].Run.Entries, runEnrty)
|
||||||
|
element.buffer = append(element.buffer, pkt.Data...)
|
||||||
|
element.sampleIndex++
|
||||||
|
element.dts += element.timeToTs(rawdur)
|
||||||
|
if element.sampleIndex > maxFrames { // Количество фреймов в пакете
|
||||||
|
element.moof.Tracks[0].Run.DataOffset = uint32(element.moof.Len() + 8)
|
||||||
|
file := make([]byte, element.moof.Len()+len(element.buffer))
|
||||||
|
element.moof.Marshal(file)
|
||||||
|
pio.PutU32BE(element.buffer, uint32(len(element.buffer)))
|
||||||
|
copy(file[element.moof.Len():], element.buffer)
|
||||||
|
element.sampleIndex = 0
|
||||||
|
element.muxer.fragmentIndex++
|
||||||
|
return true, file, nil
|
||||||
|
}
|
||||||
|
return false, []byte{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Muxer) buildMvex() *FDummy {
|
||||||
|
mvex := &FDummy{
|
||||||
|
Data: []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x38, 0x6D, 0x76, 0x65, 0x78,
|
||||||
|
0x00, 0x00, 0x00, 0x10, 0x6D, 0x65, 0x68, 0x64, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
Tag_: mp4io.Tag(0x6D766578),
|
||||||
|
}
|
||||||
|
for i := 1; i <= len(self.streams); i++ {
|
||||||
|
trex := self.buildTrex(i)
|
||||||
|
mvex.Data = append(mvex.Data, trex...)
|
||||||
|
}
|
||||||
|
|
||||||
|
pio.PutU32BE(mvex.Data, uint32(len(mvex.Data)))
|
||||||
|
return mvex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Muxer) buildTrex(trackId int) []byte {
|
||||||
|
return []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, uint8(trackId), 0x00, 0x00,
|
||||||
|
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00}
|
||||||
|
}
|
||||||
|
|
||||||
|
func time0() time.Time {
|
||||||
|
return time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package mp4f
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/format/mp4"
|
||||||
|
"github.com/Danile71/joy4/format/mp4/mp4io"
|
||||||
|
"github.com/Danile71/joy4/format/mp4f/mp4fio"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Stream struct {
|
||||||
|
av.CodecData
|
||||||
|
codecString string
|
||||||
|
trackAtom *mp4io.Track
|
||||||
|
idx int
|
||||||
|
lastpkt *av.Packet
|
||||||
|
timeScale int64
|
||||||
|
duration int64
|
||||||
|
muxer *Muxer
|
||||||
|
demuxer *mp4.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
|
||||||
|
moof mp4fio.MovieFrag
|
||||||
|
buffer []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (obj *Stream) timeToTs(tm time.Duration) int64 {
|
||||||
|
return int64(tm * time.Duration(obj.timeScale) / time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Stream) tsToTime(ts int64) time.Duration {
|
||||||
|
return time.Duration(ts) * time.Second / time.Duration(obj.timeScale)
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
package raw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/codec/h264parser"
|
||||||
|
"github.com/Danile71/joy4/utils/bits/pio"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Demuxer struct {
|
||||||
|
r io.ReadSeeker
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDemuxer(r io.ReadSeeker) *Demuxer {
|
||||||
|
return &Demuxer{
|
||||||
|
r: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Demuxer) readBytes() (data []byte, err error) {
|
||||||
|
size4 := make([]byte, 4)
|
||||||
|
|
||||||
|
// TODO: check read count
|
||||||
|
if _, err = d.r.Read(size4); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
size := pio.U32BE(size4)
|
||||||
|
data = make([]byte, size)
|
||||||
|
if _, err = d.r.Read(data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Demuxer) readI64() (data int64, err error) {
|
||||||
|
d8 := make([]byte, 8)
|
||||||
|
// TODO: check read count
|
||||||
|
if _, err = d.r.Read(d8); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = pio.I64BE(d8)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Demuxer) readI32() (data int32, err error) {
|
||||||
|
d4 := make([]byte, 4)
|
||||||
|
// TODO: check read count
|
||||||
|
if _, err = d.r.Read(d4); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = pio.I32BE(d4)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Demuxer) readU32() (data uint32, err error) {
|
||||||
|
d4 := make([]byte, 4)
|
||||||
|
// TODO: check read count
|
||||||
|
if _, err = d.r.Read(d4); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = pio.U32BE(d4)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Demuxer) readI8() (data int8, err error) {
|
||||||
|
d1 := make([]byte, 1)
|
||||||
|
// TODO: check read count
|
||||||
|
if _, err = d.r.Read(d1); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = int8(d1[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Demuxer) readU8() (data uint8, err error) {
|
||||||
|
d1 := make([]byte, 1)
|
||||||
|
// TODO: check read count
|
||||||
|
if _, err = d.r.Read(d1); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = d1[0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Demuxer) readBool() (data bool, err error) {
|
||||||
|
d1 := make([]byte, 1)
|
||||||
|
// TODO: check read count
|
||||||
|
if _, err = d.r.Read(d1); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = d1[0] == 0xFF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Demuxer) Streams() (streams []av.CodecData, err error) {
|
||||||
|
var codecType uint32
|
||||||
|
if codecType, err = d.readU32(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch av.CodecType(codecType) {
|
||||||
|
case av.H264:
|
||||||
|
var record []byte
|
||||||
|
if record, err = d.readBytes(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var codec h264parser.CodecData
|
||||||
|
if codec, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(record); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
streams = append(streams, codec)
|
||||||
|
case av.AAC:
|
||||||
|
err = fmt.Errorf("mp4: codec type=%v is not implement", codecType)
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("mp4: codec type=%v is not supported", codecType)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Demuxer) ReadPacket() (pkt av.Packet, err error) {
|
||||||
|
if pkt.IsKeyFrame, err = d.readBool(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkt.Idx, err = d.readI8(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var i64 int64
|
||||||
|
if i64, err = d.readI64(); err != nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
pkt.CompositionTime = time.Duration(i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i64, err = d.readI64(); err != nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
pkt.Time = time.Duration(i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkt.Data, err = d.readBytes(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package raw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/av/avutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var CodecTypes = []av.CodecType{av.H264, av.AAC}
|
||||||
|
|
||||||
|
func Handler(h *avutil.RegisterHandler) {
|
||||||
|
h.Ext = ".rec"
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,226 @@
|
|||||||
|
package raw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/codec/h264parser"
|
||||||
|
"github.com/Danile71/joy4/utils/bits/pio"
|
||||||
|
)
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///* AvRational */
|
||||||
|
//typedef struct _PACKTED_ RawRational {
|
||||||
|
// int32_t num; ///< Numerator
|
||||||
|
// int32_t den; ///< Denominator
|
||||||
|
//} RawRational;
|
||||||
|
//
|
||||||
|
//typedef struct _PACKTED_ RawHeader {
|
||||||
|
// /* codec context */
|
||||||
|
// struct {
|
||||||
|
// RawRational sample_aspect_ratio;
|
||||||
|
// int32_t width;
|
||||||
|
// int32_t height;
|
||||||
|
// int32_t format;
|
||||||
|
// int32_t codec_type; /*enum AVMediaType*/
|
||||||
|
// int32_t codec_id; /*enum AVCodecID*/
|
||||||
|
// } ccx;
|
||||||
|
//
|
||||||
|
// /* stream */
|
||||||
|
// struct {
|
||||||
|
// int64_t start_time;
|
||||||
|
// RawRational time_base;
|
||||||
|
// int64_t nb_frames;
|
||||||
|
// RawRational sample_aspect_ratio;
|
||||||
|
// RawRational r_frame_rate;
|
||||||
|
// RawRational avg_frame_rate;
|
||||||
|
// } st;
|
||||||
|
//} RawHeader;
|
||||||
|
|
||||||
|
func (m *Muxer) newStream(codec av.CodecData) (err error) {
|
||||||
|
switch codec.Type() {
|
||||||
|
case av.H264:
|
||||||
|
// videoCodec := codec.(av.VideoCodecData)
|
||||||
|
// videoCodec.Width()
|
||||||
|
// videoCodec.Height()
|
||||||
|
|
||||||
|
case av.AAC:
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("raw: codec type=%v is not supported", codec.Type())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream := &Stream{CodecData: codec, muxer: m}
|
||||||
|
|
||||||
|
m.streams = append(m.streams, stream)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Muxer) writeBytes(data []byte) (err error) {
|
||||||
|
size := make([]byte, 4)
|
||||||
|
pio.PutU32BE(size, uint32(len(data)))
|
||||||
|
|
||||||
|
// TODO: check write count
|
||||||
|
if _, err = m.bufw.Write(size); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = m.bufw.Write(data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Muxer) writeI64(data int64) (err error) {
|
||||||
|
data8 := make([]byte, 8)
|
||||||
|
pio.PutI64BE(data8, data)
|
||||||
|
// TODO: check write count
|
||||||
|
if _, err = m.bufw.Write(data8); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Muxer) writeI32(data int32) (err error) {
|
||||||
|
data4 := make([]byte, 4)
|
||||||
|
pio.PutI32BE(data4, data)
|
||||||
|
// TODO: check write count
|
||||||
|
if _, err = m.bufw.Write(data4); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Muxer) writeU32(data uint32) (err error) {
|
||||||
|
data4 := make([]byte, 4)
|
||||||
|
pio.PutU32BE(data4, data)
|
||||||
|
// TODO: check write count
|
||||||
|
if _, err = m.bufw.Write(data4); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Muxer) writeI8(data int8) (err error) {
|
||||||
|
// TODO: check write count
|
||||||
|
if _, err = m.bufw.Write([]byte{byte(data)}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Muxer) writeU8(data uint8) (err error) {
|
||||||
|
// TODO: check write count
|
||||||
|
if _, err = m.bufw.Write([]byte{data}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Muxer) writeBool(data bool) (err error) {
|
||||||
|
if data {
|
||||||
|
return m.writeU8(0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.writeU8(0x00)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Muxer) WriteHeader(codecs []av.CodecData) (err error) {
|
||||||
|
// m.streams = []*Stream{}
|
||||||
|
m.writeU32(uint32(len(codecs)))
|
||||||
|
for _, codec := range codecs {
|
||||||
|
if err = m.writeU32(uint32(codec.Type())); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch codec.Type() {
|
||||||
|
case av.H264:
|
||||||
|
h264 := codec.(h264parser.CodecData)
|
||||||
|
// write Record data
|
||||||
|
if err = m.writeBytes(h264.Record); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//{ // write RecordInfo
|
||||||
|
// size := uint32(h264.RecordInfo.Len())
|
||||||
|
// if err = m.writeU32(size); err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// buf := make([]byte, size)
|
||||||
|
// h264.RecordInfo.Marshal(buf)
|
||||||
|
// if _, err = m.bufw.Write(buf); err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
case av.AAC:
|
||||||
|
err = fmt.Errorf("mp4: codec type=%v is not implement", codec.Type())
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("mp4: codec type=%v is not supported", codec.Type())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
///* AvPacket */
|
||||||
|
//typedef struct _PACKTED_ RawPacket {
|
||||||
|
// int64_t pts;
|
||||||
|
// int64_t dts;
|
||||||
|
// int32_t flags;
|
||||||
|
// int64_t duration;
|
||||||
|
// int64_t pos;
|
||||||
|
// int32_t size;
|
||||||
|
//} RawPacket;
|
||||||
|
func (m *Muxer) WritePacket(pkt av.Packet) (err error) {
|
||||||
|
|
||||||
|
//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
|
||||||
|
|
||||||
|
m.writeBool(pkt.IsKeyFrame)
|
||||||
|
m.writeI8(pkt.Idx)
|
||||||
|
m.writeI64(int64(pkt.CompositionTime))
|
||||||
|
m.writeI64(int64(pkt.Time))
|
||||||
|
m.writeBytes(pkt.Data)
|
||||||
|
//stream := m.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 (m *Muxer) WriteTrailer() (err error) {
|
||||||
|
if err = m.bufw.Flush(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package raw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Stream struct {
|
||||||
|
av.CodecData
|
||||||
|
idx int
|
||||||
|
timeScale int64
|
||||||
|
muxer *Muxer
|
||||||
|
lastpkt *av.Packet
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
//writeFn := func(data interface{}) error {
|
||||||
|
// return binary.Write(self.muxer.w, binary.BigEndian, data)
|
||||||
|
//}
|
||||||
|
|
||||||
|
// int64_t pts;
|
||||||
|
// int64_t dts;
|
||||||
|
// int32_t flags;
|
||||||
|
// int64_t duration;
|
||||||
|
// int64_t pos;
|
||||||
|
// int32_t size;
|
||||||
|
|
||||||
|
// pts := self.timeToTs(rawdur)
|
||||||
|
//dts := pts
|
||||||
|
//writeFn(pts)
|
||||||
|
//writeFn(dts)
|
||||||
|
//writeFn(int32(0))
|
||||||
|
//writeFn(pts)
|
||||||
|
//writeFn(pts)
|
||||||
|
//writeFn(int32(0))
|
||||||
|
//writeFn(int32(len(pkt.Data)))
|
||||||
|
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 *Stream) timeToTs(tm time.Duration) int64 {
|
||||||
|
return int64(tm * time.Duration(self.timeScale) / time.Second)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
package sdp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
)
|
||||||
|
|
||||||
|
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":
|
||||||
|
switch val {
|
||||||
|
case "audio", "video", "track1":
|
||||||
|
media.Control = val
|
||||||
|
}
|
||||||
|
if strings.Contains(val, "trackID") {
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package sdp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
infos := Decode(`
|
||||||
|
v=0
|
||||||
|
o=- 1459325504777324 1 IN IP4 192.168.0.123
|
||||||
|
s=RTSP/RTP stream from Network Video Server
|
||||||
|
i=mpeg4cif
|
||||||
|
t=0 0
|
||||||
|
a=tool:LIVE555 Streaming Media v2009.09.28
|
||||||
|
a=type:broadcast
|
||||||
|
a=control:*
|
||||||
|
a=range:npt=0-
|
||||||
|
a=x-qt-text-nam:RTSP/RTP stream from Network Video Server
|
||||||
|
a=x-qt-text-inf:mpeg4cif
|
||||||
|
m=video 0 RTP/AVP 96
|
||||||
|
c=IN IP4 0.0.0.0
|
||||||
|
b=AS:300
|
||||||
|
a=rtpmap:96 H264/90000
|
||||||
|
a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AHpWoKA9k,aO48gA==
|
||||||
|
a=x-dimensions: 720, 480
|
||||||
|
a=x-framerate: 15
|
||||||
|
a=control:track1
|
||||||
|
m=audio 0 RTP/AVP 96
|
||||||
|
c=IN IP4 0.0.0.0
|
||||||
|
b=AS:256
|
||||||
|
a=rtpmap:96 MPEG4-GENERIC/16000/2
|
||||||
|
a=fmtp:96 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1408
|
||||||
|
a=control:track2
|
||||||
|
m=audio 0 RTP/AVP 0
|
||||||
|
c=IN IP4 0.0.0.0
|
||||||
|
b=AS:50
|
||||||
|
a=recvonly
|
||||||
|
a=control:rtsp://109.195.127.207:554/mpeg4cif/trackID=2
|
||||||
|
a=rtpmap:0 PCMU/8000
|
||||||
|
a=Media_header:MEDIAINFO=494D4B48010100000400010010710110401F000000FA000000000000000000000000000000000000;
|
||||||
|
a=appversion:1.0
|
||||||
|
`)
|
||||||
|
t.Logf("%v", infos)
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package rtsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/format/rtsp/sdp"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,296 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/codec/aacparser"
|
||||||
|
"github.com/Danile71/joy4/codec/h264parser"
|
||||||
|
"github.com/Danile71/joy4/format/ts/tsio"
|
||||||
|
"github.com/Danile71/joy4/utils/bits/pio"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
self.data = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/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
|
||||||
|
}
|
@ -0,0 +1,206 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/joy4/codec/aacparser"
|
||||||
|
"github.com/Danile71/joy4/codec/h264parser"
|
||||||
|
"github.com/Danile71/joy4/format/ts/tsio"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package ts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/joy4/av"
|
||||||
|
"github.com/Danile71/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
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,589 @@
|
|||||||
|
package tsio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Danile71/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
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package bits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBits(t *testing.T) {
|
||||||
|
rdata := []byte{0xf3, 0xb3, 0x45, 0x60}
|
||||||
|
rbuf := bytes.NewReader(rdata[:])
|
||||||
|
r := &Reader{R: rbuf}
|
||||||
|
var u32 uint
|
||||||
|
if u32, _ = r.ReadBits(4); u32 != 0xf {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if u32, _ = r.ReadBits(4); u32 != 0x3 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if u32, _ = r.ReadBits(2); u32 != 0x2 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if u32, _ = r.ReadBits(2); u32 != 0x3 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
b := make([]byte, 2)
|
||||||
|
if r.Read(b); b[0] != 0x34 || b[1] != 0x56 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
wbuf := &bytes.Buffer{}
|
||||||
|
w := &Writer{W: wbuf}
|
||||||
|
w.WriteBits(0xf, 4)
|
||||||
|
w.WriteBits(0x3, 4)
|
||||||
|
w.WriteBits(0x2, 2)
|
||||||
|
w.WriteBits(0x3, 2)
|
||||||
|
n, _ := w.Write([]byte{0x34, 0x56})
|
||||||
|
if n != 2 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
w.FlushBits()
|
||||||
|
wdata := wbuf.Bytes()
|
||||||
|
if wdata[0] != 0xf3 || wdata[1] != 0xb3 || wdata[2] != 0x45 || wdata[3] != 0x60 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
b = make([]byte, 8)
|
||||||
|
PutUInt64BE(b, 0x11223344, 32)
|
||||||
|
if b[0] != 0x11 || b[1] != 0x22 || b[2] != 0x33 || b[3] != 0x44 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package bufio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
buf [][]byte
|
||||||
|
R io.ReadSeeker
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReaderSize(r io.ReadSeeker, size int) *Reader {
|
||||||
|
buf := make([]byte, size*2)
|
||||||
|
return &Reader{
|
||||||
|
R: r,
|
||||||
|
buf: [][]byte{buf[0:size], buf[size:]},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Reader) ReadAt(b []byte, off int64) (n int, err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
package pio
|
||||||
|
|
||||||
|
var RecommendBufioSize = 1024*64
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
package pio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleVec() {
|
||||||
|
vec := [][]byte{[]byte{1,2,3}, []byte{4,5,6,7,8,9}, []byte{10,11,12,13}}
|
||||||
|
println(VecLen(vec))
|
||||||
|
|
||||||
|
vec = VecSlice(vec, 1, -1)
|
||||||
|
fmt.Println(vec)
|
||||||
|
|
||||||
|
vec = VecSlice(vec, 2, -1)
|
||||||
|
fmt.Println(vec)
|
||||||
|
|
||||||
|
vec = VecSlice(vec, 8, 8)
|
||||||
|
fmt.Println(vec)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue