|
|
|
@@ -4,11 +4,14 @@ import (
|
|
|
|
"bytes"
|
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
|
|
|
"encoding/binary"
|
|
|
|
"encoding/gob"
|
|
|
|
"encoding/gob"
|
|
|
|
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"fmt"
|
|
|
|
"github.com/deepch/vdk/av"
|
|
|
|
"github.com/deepch/vdk/av"
|
|
|
|
"github.com/deepch/vdk/codec/aacparser"
|
|
|
|
"github.com/deepch/vdk/codec/aacparser"
|
|
|
|
"github.com/deepch/vdk/codec/h264parser"
|
|
|
|
"github.com/deepch/vdk/codec/h264parser"
|
|
|
|
"github.com/deepch/vdk/format/mp4"
|
|
|
|
"github.com/deepch/vdk/format/mp4"
|
|
|
|
|
|
|
|
"github.com/moby/sys/mountinfo"
|
|
|
|
|
|
|
|
"github.com/shirou/gopsutil/v3/disk"
|
|
|
|
"os"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"strings"
|
|
|
|
@@ -16,7 +19,7 @@ import (
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var MIME = []byte{11, 22, 111, 222, 11, 22, 111, 222}
|
|
|
|
var MIME = []byte{11, 22, 111, 222, 11, 22, 111, 222}
|
|
|
|
var listTag = []string{"{host_name}", "{stream_name}", "{channel_name}", "{stream_id}", "{channel_id}", "{start_year}", "{start_month}", "{start_day}", "{start_minute}", "{start_second}", "{start_millisecond}", "{start_unix_second}", "{start_unix_millisecond}", "{start_time}", "{start_pts}", "{end_year}", "{end_month}", "{end_day}", "{end_minute}", "{end_second}", "{end_millisecond}", "{start_unix_second}", "{start_unix_millisecond}", "{end_time}", "{end_pts}", "{duration_second}", "{duration_millisecond}"}
|
|
|
|
var listTag = []string{"{server_id}", "{host_name}", "{host_name_short}", "{host_name_long}", "{stream_name}", "{channel_name}", "{stream_id}", "{channel_id}", "{start_year}", "{start_month}", "{start_day}", "{start_minute}", "{start_second}", "{start_millisecond}", "{start_unix_second}", "{start_unix_millisecond}", "{start_time}", "{start_pts}", "{end_year}", "{end_month}", "{end_day}", "{end_minute}", "{end_second}", "{end_millisecond}", "{start_unix_second}", "{start_unix_millisecond}", "{end_time}", "{end_pts}", "{duration_second}", "{duration_millisecond}"}
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
const (
|
|
|
|
MP4 = "mp4"
|
|
|
|
MP4 = "mp4"
|
|
|
|
@@ -24,19 +27,20 @@ const (
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type Muxer struct {
|
|
|
|
type Muxer struct {
|
|
|
|
muxer *mp4.Muxer
|
|
|
|
muxer *mp4.Muxer
|
|
|
|
format string
|
|
|
|
format string
|
|
|
|
limit int
|
|
|
|
limit int
|
|
|
|
d *os.File
|
|
|
|
d *os.File
|
|
|
|
m *os.File
|
|
|
|
m *os.File
|
|
|
|
dur time.Duration
|
|
|
|
dur time.Duration
|
|
|
|
h int
|
|
|
|
h int
|
|
|
|
gof *Gof
|
|
|
|
gof *Gof
|
|
|
|
patch string
|
|
|
|
patch string
|
|
|
|
start, end time.Time
|
|
|
|
mpoint []string
|
|
|
|
pstart, pend time.Duration
|
|
|
|
start, end time.Time
|
|
|
|
started bool
|
|
|
|
pstart, pend time.Duration
|
|
|
|
hostName, streamName, channelName, streamID, channelID string
|
|
|
|
started bool
|
|
|
|
|
|
|
|
serverID, streamName, channelName, streamID, channelID, hostname string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type Gof struct {
|
|
|
|
type Gof struct {
|
|
|
|
@@ -64,18 +68,21 @@ func init() {
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func NewMuxer(hostName, streamName, channelName, streamID, channelID, patch string, format string, limit int) (m *Muxer, err error) {
|
|
|
|
func NewMuxer(serverID, streamName, channelName, streamID, channelID string, mpoint []string, patch, format string, limit int) (m *Muxer, err error) {
|
|
|
|
|
|
|
|
hostname, _ := os.Hostname()
|
|
|
|
m = &Muxer{
|
|
|
|
m = &Muxer{
|
|
|
|
|
|
|
|
mpoint: mpoint,
|
|
|
|
patch: patch,
|
|
|
|
patch: patch,
|
|
|
|
h: -1,
|
|
|
|
h: -1,
|
|
|
|
gof: &Gof{},
|
|
|
|
gof: &Gof{},
|
|
|
|
format: format,
|
|
|
|
format: format,
|
|
|
|
limit: limit,
|
|
|
|
limit: limit,
|
|
|
|
hostName: hostName,
|
|
|
|
serverID: serverID,
|
|
|
|
streamName: streamName,
|
|
|
|
streamName: streamName,
|
|
|
|
channelName: channelName,
|
|
|
|
channelName: channelName,
|
|
|
|
streamID: streamID,
|
|
|
|
streamID: streamID,
|
|
|
|
channelID: channelID,
|
|
|
|
channelID: channelID,
|
|
|
|
|
|
|
|
hostname: hostname,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -83,7 +90,7 @@ func NewMuxer(hostName, streamName, channelName, streamID, channelID, patch stri
|
|
|
|
func (m *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
|
|
|
func (m *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
|
|
|
m.gof.Streams = streams
|
|
|
|
m.gof.Streams = streams
|
|
|
|
if m.format == MP4 {
|
|
|
|
if m.format == MP4 {
|
|
|
|
m.OpenMP4()
|
|
|
|
return m.OpenMP4()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
@@ -111,11 +118,15 @@ func (m *Muxer) WritePacket(pkt av.Packet) (err error) {
|
|
|
|
func (m *Muxer) writePacketMP4(pkt av.Packet) (err error) {
|
|
|
|
func (m *Muxer) writePacketMP4(pkt av.Packet) (err error) {
|
|
|
|
if pkt.IsKeyFrame && m.dur > time.Duration(m.limit)*time.Second {
|
|
|
|
if pkt.IsKeyFrame && m.dur > time.Duration(m.limit)*time.Second {
|
|
|
|
m.pstart = pkt.Time
|
|
|
|
m.pstart = pkt.Time
|
|
|
|
m.OpenMP4()
|
|
|
|
if err = m.OpenMP4(); err != nil {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
m.dur = 0
|
|
|
|
m.dur = 0
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m.dur += pkt.Duration
|
|
|
|
m.dur += pkt.Duration
|
|
|
|
m.pend = pkt.Time
|
|
|
|
m.pend = pkt.Time
|
|
|
|
|
|
|
|
|
|
|
|
return m.muxer.WritePacket(pkt)
|
|
|
|
return m.muxer.WritePacket(pkt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -186,7 +197,15 @@ func (m *Muxer) OpenNVR() (err error) {
|
|
|
|
func (m *Muxer) OpenMP4() (err error) {
|
|
|
|
func (m *Muxer) OpenMP4() (err error) {
|
|
|
|
m.WriteTrailer()
|
|
|
|
m.WriteTrailer()
|
|
|
|
m.start = time.Now().UTC()
|
|
|
|
m.start = time.Now().UTC()
|
|
|
|
if m.d, err = os.CreateTemp("", "rtspvideo.*.mp4"); err != nil {
|
|
|
|
|
|
|
|
|
|
|
|
p, err := m.filePatch()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = os.MkdirAll(filepath.Dir(p), 0755); err != nil {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.d, err = os.Create(filepath.Dir(p) + "/tmp.mp4"); err != nil {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m.muxer = mp4.NewMuxer(m.d)
|
|
|
|
m.muxer = mp4.NewMuxer(m.d)
|
|
|
|
@@ -197,13 +216,40 @@ func (m *Muxer) OpenMP4() (err error) {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (m *Muxer) filePatch() string {
|
|
|
|
func (m *Muxer) filePatch() (string, error) {
|
|
|
|
ts := m.patch
|
|
|
|
var (
|
|
|
|
|
|
|
|
mu = float64(100)
|
|
|
|
|
|
|
|
ui = -1
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for i, i2 := range m.mpoint {
|
|
|
|
|
|
|
|
if m, err := mountinfo.Mounted(i2); err == nil && m {
|
|
|
|
|
|
|
|
if d, err := disk.Usage(i2); err == nil {
|
|
|
|
|
|
|
|
if d.UsedPercent < mu {
|
|
|
|
|
|
|
|
ui = i
|
|
|
|
|
|
|
|
mu = d.UsedPercent
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ui == -1 {
|
|
|
|
|
|
|
|
return "", errors.New("not mount ready")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ts := filepath.Join(m.mpoint[ui], m.patch)
|
|
|
|
m.end = time.Now().UTC()
|
|
|
|
m.end = time.Now().UTC()
|
|
|
|
|
|
|
|
|
|
|
|
for _, s := range listTag {
|
|
|
|
for _, s := range listTag {
|
|
|
|
switch s {
|
|
|
|
switch s {
|
|
|
|
|
|
|
|
case "{server_id}":
|
|
|
|
|
|
|
|
ts = strings.Replace(ts, "{server_id}", m.serverID, -1)
|
|
|
|
case "{host_name}":
|
|
|
|
case "{host_name}":
|
|
|
|
ts = strings.Replace(ts, "{host_name}", m.hostName, -1)
|
|
|
|
ts = strings.Replace(ts, "{host_name}", m.hostname, -1)
|
|
|
|
|
|
|
|
case "{host_name_short}":
|
|
|
|
|
|
|
|
ts = strings.Replace(ts, "{host_name_short}", m.hostname, -1)
|
|
|
|
|
|
|
|
case "{host_name_long}":
|
|
|
|
|
|
|
|
ts = strings.Replace(ts, "{host_name_long}", m.hostname, -1)
|
|
|
|
case "{stream_name}":
|
|
|
|
case "{stream_name}":
|
|
|
|
ts = strings.Replace(ts, "{stream_name}", m.streamName, -1)
|
|
|
|
ts = strings.Replace(ts, "{stream_name}", m.streamName, -1)
|
|
|
|
case "{channel_name}":
|
|
|
|
case "{channel_name}":
|
|
|
|
@@ -215,13 +261,13 @@ func (m *Muxer) filePatch() string {
|
|
|
|
case "{start_year}":
|
|
|
|
case "{start_year}":
|
|
|
|
ts = strings.Replace(ts, "{start_year}", fmt.Sprintf("%d", m.start.Year()), -1)
|
|
|
|
ts = strings.Replace(ts, "{start_year}", fmt.Sprintf("%d", m.start.Year()), -1)
|
|
|
|
case "{start_month}":
|
|
|
|
case "{start_month}":
|
|
|
|
ts = strings.Replace(ts, "{start_month}", fmt.Sprintf("%02d", int(m.start.Month())), -1)
|
|
|
|
ts = strings.Replace(ts, "{start_month}", fmt.Sprintf("%d", int(m.start.Month())), -1)
|
|
|
|
case "{start_day}":
|
|
|
|
case "{start_day}":
|
|
|
|
ts = strings.Replace(ts, "{start_day}", fmt.Sprintf("%02d", m.start.Day()), -1)
|
|
|
|
ts = strings.Replace(ts, "{start_day}", fmt.Sprintf("%d", m.start.Day()), -1)
|
|
|
|
case "{start_minute}":
|
|
|
|
case "{start_minute}":
|
|
|
|
ts = strings.Replace(ts, "{start_minute}", fmt.Sprintf("%02d", m.start.Minute()), -1)
|
|
|
|
ts = strings.Replace(ts, "{start_minute}", fmt.Sprintf("%d", m.start.Minute()), -1)
|
|
|
|
case "{start_second}":
|
|
|
|
case "{start_second}":
|
|
|
|
ts = strings.Replace(ts, "{start_second}", fmt.Sprintf("%02d", m.start.Second()), -1)
|
|
|
|
ts = strings.Replace(ts, "{start_second}", fmt.Sprintf("%d", m.start.Second()), -1)
|
|
|
|
case "{start_millisecond}":
|
|
|
|
case "{start_millisecond}":
|
|
|
|
ts = strings.Replace(ts, "{start_millisecond}", fmt.Sprintf("%d", m.start.Nanosecond()/1000/1000), -1)
|
|
|
|
ts = strings.Replace(ts, "{start_millisecond}", fmt.Sprintf("%d", m.start.Nanosecond()/1000/1000), -1)
|
|
|
|
case "{start_unix_millisecond}":
|
|
|
|
case "{start_unix_millisecond}":
|
|
|
|
@@ -235,13 +281,13 @@ func (m *Muxer) filePatch() string {
|
|
|
|
case "{end_year}":
|
|
|
|
case "{end_year}":
|
|
|
|
ts = strings.Replace(ts, "{end_year}", fmt.Sprintf("%d", m.end.Year()), -1)
|
|
|
|
ts = strings.Replace(ts, "{end_year}", fmt.Sprintf("%d", m.end.Year()), -1)
|
|
|
|
case "{end_month}":
|
|
|
|
case "{end_month}":
|
|
|
|
ts = strings.Replace(ts, "{end_month}", fmt.Sprintf("%02d", int(m.end.Month())), -1)
|
|
|
|
ts = strings.Replace(ts, "{end_month}", fmt.Sprintf("%d", int(m.end.Month())), -1)
|
|
|
|
case "{end_day}":
|
|
|
|
case "{end_day}":
|
|
|
|
ts = strings.Replace(ts, "{end_day}", fmt.Sprintf("%02d", m.end.Day()), -1)
|
|
|
|
ts = strings.Replace(ts, "{end_day}", fmt.Sprintf("%d", m.end.Day()), -1)
|
|
|
|
case "{end_minute}":
|
|
|
|
case "{end_minute}":
|
|
|
|
ts = strings.Replace(ts, "{end_minute}", fmt.Sprintf("%02d", m.end.Minute()), -1)
|
|
|
|
ts = strings.Replace(ts, "{end_minute}", fmt.Sprintf("%d", m.end.Minute()), -1)
|
|
|
|
case "{end_second}":
|
|
|
|
case "{end_second}":
|
|
|
|
ts = strings.Replace(ts, "{end_second}", fmt.Sprintf("%02d", m.end.Second()), -1)
|
|
|
|
ts = strings.Replace(ts, "{end_second}", fmt.Sprintf("%d", m.end.Second()), -1)
|
|
|
|
case "{end_millisecond}":
|
|
|
|
case "{end_millisecond}":
|
|
|
|
ts = strings.Replace(ts, "{end_millisecond}", fmt.Sprintf("%d", m.start.Nanosecond()/1000/1000), -1)
|
|
|
|
ts = strings.Replace(ts, "{end_millisecond}", fmt.Sprintf("%d", m.start.Nanosecond()/1000/1000), -1)
|
|
|
|
case "{end_unix_millisecond}":
|
|
|
|
case "{end_unix_millisecond}":
|
|
|
|
@@ -259,7 +305,7 @@ func (m *Muxer) filePatch() string {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ts
|
|
|
|
return ts, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (m *Muxer) WriteTrailer() (err error) {
|
|
|
|
func (m *Muxer) WriteTrailer() (err error) {
|
|
|
|
@@ -271,12 +317,15 @@ func (m *Muxer) WriteTrailer() (err error) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if m.d != nil {
|
|
|
|
if m.d != nil {
|
|
|
|
if m.format == MP4 {
|
|
|
|
if m.format == MP4 {
|
|
|
|
p := m.filePatch()
|
|
|
|
p, err := m.filePatch()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
if err = os.MkdirAll(filepath.Dir(p), 0755); err != nil {
|
|
|
|
if err = os.MkdirAll(filepath.Dir(p), 0755); err != nil {
|
|
|
|
return
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err = os.Rename(m.d.Name(), p); err != nil {
|
|
|
|
if err = os.Rename(m.d.Name(), p); err != nil {
|
|
|
|
return
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = m.d.Close()
|
|
|
|
err = m.d.Close()
|
|
|
|
|