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