divide the program to 3 parts: ingest, storage, reader; ingest - gets packets from rtsp and send it to storage; storage - converts got packets to custom files; reader - reads custom files and creates containers (e.g. .ts).
This commit is contained in:
parent
883053a85c
commit
b37efdf403
@ -1,20 +1,276 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"reader/pkg/handlers"
|
||||
"os"
|
||||
"reader/internal/processor"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||
)
|
||||
|
||||
func main() {
|
||||
port := 8080
|
||||
//port := 8080
|
||||
//
|
||||
//http.HandleFunc("GET /download", handlers.Download) // example request: {"date": "07-03-2025", "start_time": "16-43", "end_time": "16-44"}
|
||||
//http.HandleFunc("GET /hls/", handlers.HLS)
|
||||
//
|
||||
//log.Println("Starting server on:")
|
||||
//log.Printf("Serving on HTTP port: %d\n", port)
|
||||
//
|
||||
//log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
|
||||
|
||||
http.HandleFunc("GET /download", handlers.Download) // example request: {"date": "07-03-2025", "start_time": "16-43", "end_time": "16-44"}
|
||||
http.HandleFunc("GET /hls/", handlers.HLS)
|
||||
// Интерпретируем типы пакетов:
|
||||
const (
|
||||
PacketTypeH264 = 1
|
||||
PacketTypeLPCM = 2
|
||||
)
|
||||
|
||||
log.Println("Starting server on:")
|
||||
log.Printf("Serving on HTTP port: %d\n", port)
|
||||
var (
|
||||
h264 string
|
||||
g711 string
|
||||
)
|
||||
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
|
||||
// InterleavedPacket описывает пакет, который может быть либо H264, либо G711.
|
||||
type InterleavedPacket struct {
|
||||
Type byte
|
||||
Pts int64
|
||||
H264AUs [][]byte // для H264
|
||||
LPCMSamples []byte // для G711
|
||||
}
|
||||
|
||||
// Segment содержит строки Start и Duration, а также набор пакетов.
|
||||
type Segment struct {
|
||||
Start string
|
||||
Duration string
|
||||
Packets []InterleavedPacket
|
||||
}
|
||||
|
||||
// readString читает строку: сначала длину (int32), затем данные строки.
|
||||
func readString(r io.Reader) (string, error) {
|
||||
var length int32
|
||||
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := make([]byte, length)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// readHeaderSegment читает сегмент, записанный функцией WriteHeader (с Start и Duration).
|
||||
func readHeaderSegment(r io.Reader) (Segment, error) {
|
||||
var seg Segment
|
||||
start, err := readString(r)
|
||||
if err != nil {
|
||||
return seg, err
|
||||
}
|
||||
seg.Start = start
|
||||
|
||||
duration, err := readString(r)
|
||||
if err != nil {
|
||||
return seg, err
|
||||
}
|
||||
seg.Duration = duration
|
||||
|
||||
return seg, nil
|
||||
}
|
||||
|
||||
// readPacket читает один interleaved пакет.
|
||||
func readPacket(r io.Reader) (InterleavedPacket, error) {
|
||||
var pkt InterleavedPacket
|
||||
|
||||
// Читаем тип пакета (1 байт).
|
||||
typeByte := make([]byte, 1)
|
||||
if _, err := io.ReadFull(r, typeByte); err != nil {
|
||||
return pkt, err
|
||||
}
|
||||
pkt.Type = typeByte[0]
|
||||
|
||||
// Читаем pts (int64).
|
||||
if err := binary.Read(r, binary.LittleEndian, &pkt.Pts); err != nil {
|
||||
return pkt, err
|
||||
}
|
||||
|
||||
// В зависимости от типа, читаем данные.
|
||||
if pkt.Type == PacketTypeH264 {
|
||||
var numAUs int32
|
||||
if err := binary.Read(r, binary.LittleEndian, &numAUs); err != nil {
|
||||
return pkt, err
|
||||
}
|
||||
var auList [][]byte
|
||||
for i := 0; i < int(numAUs); i++ {
|
||||
var auLen int32
|
||||
if err := binary.Read(r, binary.LittleEndian, &auLen); err != nil {
|
||||
return pkt, err
|
||||
}
|
||||
auData := make([]byte, auLen)
|
||||
if _, err := io.ReadFull(r, auData); err != nil {
|
||||
return pkt, err
|
||||
}
|
||||
auList = append(auList, auData)
|
||||
}
|
||||
pkt.H264AUs = auList
|
||||
} else if pkt.Type == PacketTypeLPCM {
|
||||
var auLen int32
|
||||
if err := binary.Read(r, binary.LittleEndian, &auLen); err != nil {
|
||||
return pkt, err
|
||||
}
|
||||
auData := make([]byte, auLen)
|
||||
if _, err := io.ReadFull(r, auData); err != nil {
|
||||
return pkt, err
|
||||
}
|
||||
pkt.LPCMSamples = auData
|
||||
} else {
|
||||
return pkt, fmt.Errorf("неизвестный тип пакета: %d", pkt.Type)
|
||||
}
|
||||
|
||||
return pkt, nil
|
||||
}
|
||||
|
||||
// readPacketSegment читает сегмент, записанный функцией WriteInterleavedPacket:
|
||||
// сначала число пакетов (int32), затем каждый пакет.
|
||||
func readPacketSegment(r io.Reader) ([]InterleavedPacket, error) {
|
||||
var numPackets int32
|
||||
if err := binary.Read(r, binary.LittleEndian, &numPackets); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var packets []InterleavedPacket
|
||||
for i := 0; i < int(numPackets); i++ {
|
||||
pkt, err := readPacket(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
packets = append(packets, pkt)
|
||||
}
|
||||
return packets, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
filename := "/home/psa/GoRepository/data/camera44-center-skver_temnika/12-10-00_21-03-2025.insit"
|
||||
|
||||
segment := Segment{}
|
||||
|
||||
// Открываем файл для чтения.
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Ошибка открытия файла:", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Читаем streamID (первые 4 байта — длина, затем сами байты).
|
||||
var streamIDLen int32
|
||||
if err := binary.Read(f, binary.LittleEndian, &streamIDLen); err != nil {
|
||||
fmt.Println("Ошибка чтения streamID длины:", err)
|
||||
return
|
||||
}
|
||||
streamIDBytes := make([]byte, streamIDLen)
|
||||
if _, err := io.ReadFull(f, streamIDBytes); err != nil {
|
||||
fmt.Println("Ошибка чтения streamID:", err)
|
||||
return
|
||||
}
|
||||
streamID := string(streamIDBytes)
|
||||
fmt.Println("Stream ID:", streamID)
|
||||
|
||||
// Теперь в файле идут сегменты. Первый сегмент — заголовочный, далее — сегменты с пакетами.
|
||||
// Читаем первый сегмент как заголовочный.
|
||||
var segLen int32
|
||||
if err := binary.Read(f, binary.LittleEndian, &segLen); err != nil {
|
||||
fmt.Println("Ошибка чтения длины заголовочного сегмента:", err)
|
||||
return
|
||||
}
|
||||
segData := make([]byte, segLen)
|
||||
if _, err := io.ReadFull(f, segData); err != nil {
|
||||
fmt.Println("Ошибка чтения заголовочного сегмента:", err)
|
||||
return
|
||||
}
|
||||
headerReader := bytes.NewReader(segData)
|
||||
headerSeg, err := readHeaderSegment(headerReader)
|
||||
if err != nil {
|
||||
fmt.Println("Ошибка чтения заголовочного сегмента:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("Заголовочный сегмент:")
|
||||
fmt.Println("\tStart:", headerSeg.Start)
|
||||
fmt.Println("\tDuration:", headerSeg.Duration)
|
||||
|
||||
segment = headerSeg
|
||||
|
||||
// Setup MPEG-TS muxer.
|
||||
var h264Format format.H264
|
||||
var aacFormat *format.MPEG4Audio
|
||||
|
||||
h264Format.PayloadTyp = 96
|
||||
h264Format.PacketizationMode = 1
|
||||
|
||||
currentMpegtsMuxer := processor.MpegtsMuxer{
|
||||
FileName: segment.Start + "_videoFragment" + "_0" + ".ts",
|
||||
H264Format: &h264Format,
|
||||
Mpeg4AudioFormat: aacFormat,
|
||||
}
|
||||
|
||||
err = currentMpegtsMuxer.Initialize()
|
||||
if err != nil {
|
||||
fmt.Printf("[%v-%v]: init muxer error: %w\n", h264, g711, err)
|
||||
}
|
||||
|
||||
// Читаем последующие сегменты, содержащие пакеты.
|
||||
segmentIndex := 1
|
||||
for {
|
||||
var segLen int32
|
||||
err := binary.Read(f, binary.LittleEndian, &segLen)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break // достигнут конец файла
|
||||
}
|
||||
fmt.Println("Ошибка чтения длины сегмента:", err)
|
||||
return
|
||||
}
|
||||
segData = make([]byte, segLen)
|
||||
if _, err := io.ReadFull(f, segData); err != nil {
|
||||
fmt.Println("Ошибка чтения данных сегмента:", err)
|
||||
return
|
||||
}
|
||||
packetReader := bytes.NewReader(segData)
|
||||
packets, err := readPacketSegment(packetReader)
|
||||
if err != nil {
|
||||
fmt.Println("Ошибка чтения сегмента пакетов:", err)
|
||||
return
|
||||
}
|
||||
|
||||
segment.Packets = append(segment.Packets, packets...)
|
||||
|
||||
fmt.Printf("Сегмент пакетов #%d:\n", segmentIndex)
|
||||
for _, pkt := range packets {
|
||||
switch pkt.Type {
|
||||
case PacketTypeH264:
|
||||
h264 = "h264"
|
||||
err = currentMpegtsMuxer.WriteH264(pkt.Pts, pkt.H264AUs)
|
||||
if err != nil {
|
||||
log.Printf("write h264 packet error: %v\n", err)
|
||||
}
|
||||
case PacketTypeLPCM:
|
||||
g711 = "g711"
|
||||
// Convert G711 to AAC.
|
||||
au, err := processor.ConvertLPCMToAAC(pkt.LPCMSamples)
|
||||
if err != nil {
|
||||
log.Printf("converting to AAC frame error: %v\n", err)
|
||||
}
|
||||
|
||||
// Encode the access unit into MPEG-TS.
|
||||
err = currentMpegtsMuxer.WriteAAC([][]byte{au}, pkt.Pts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
segmentIndex++
|
||||
}
|
||||
|
||||
currentMpegtsMuxer.Close()
|
||||
}
|
||||
|
@ -1,5 +1,23 @@
|
||||
module reader
|
||||
|
||||
go 1.23.6
|
||||
go 1.24.1
|
||||
|
||||
require git.insit.tech/sas/rtsp_proxy v0.0.0-20250310124520-82fa76149f4e // indirect
|
||||
require (
|
||||
git.insit.tech/psa/rtsp_reader-writer/writer v0.0.0-20250321123217-883053a85cbc
|
||||
github.com/bluenviron/gortsplib/v4 v4.12.3
|
||||
github.com/bluenviron/mediacommon v1.14.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/asticode/go-astikit v0.52.0 // indirect
|
||||
github.com/asticode/go-astits v1.13.0 // indirect
|
||||
github.com/bluenviron/mediacommon/v2 v2.0.0 // indirect
|
||||
github.com/gen2brain/aac-go v0.0.0-20230119102159-ef1e76509d21 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.15 // indirect
|
||||
github.com/pion/rtp v1.8.13 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.11 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
)
|
||||
|
@ -1,2 +1,49 @@
|
||||
git.insit.tech/sas/rtsp_proxy v0.0.0-20250310124520-82fa76149f4e h1:JeOZvcZA4JHfoBG5ES9tpSHrhOj1jmjFzSJAyKIjApU=
|
||||
git.insit.tech/sas/rtsp_proxy v0.0.0-20250310124520-82fa76149f4e/go.mod h1:9Yw6g7jcG9fIkWh6FGXSWTyZs4d7EQWaRhdKnnH2TvA=
|
||||
git.insit.tech/psa/rtsp_reader-writer/writer v0.0.0-20250321123217-883053a85cbc h1:dhJVA/B+q3CWBhI1ZBPhClAbY875j7+Ru6XIWimxjW8=
|
||||
git.insit.tech/psa/rtsp_reader-writer/writer v0.0.0-20250321123217-883053a85cbc/go.mod h1:hY3OlYFPtsZ/fX/BVUPkjIHNPbfIpzgujKn9TKftI+E=
|
||||
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
||||
github.com/asticode/go-astikit v0.52.0 h1:kTl2XjgiVQhUl1H7kim7NhmTtCMwVBbPrXKqhQhbk8Y=
|
||||
github.com/asticode/go-astikit v0.52.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE=
|
||||
github.com/asticode/go-astits v1.13.0 h1:XOgkaadfZODnyZRR5Y0/DWkA9vrkLLPLeeOvDwfKZ1c=
|
||||
github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
|
||||
github.com/bluenviron/gortsplib/v4 v4.12.3 h1:3EzbyGb5+MIOJQYiWytRegFEP4EW5paiyTrscQj63WE=
|
||||
github.com/bluenviron/gortsplib/v4 v4.12.3/go.mod h1:SkZPdaMNr+IvHt2PKRjUXxZN6FDutmSZn4eT0GmF0sk=
|
||||
github.com/bluenviron/mediacommon v1.14.0 h1:lWCwOBKNKgqmspRpwpvvg3CidYm+XOc2+z/Jw7LM5dQ=
|
||||
github.com/bluenviron/mediacommon v1.14.0/go.mod h1:z5LP9Tm1ZNfQV5Co54PyOzaIhGMusDfRKmh42nQSnyo=
|
||||
github.com/bluenviron/mediacommon/v2 v2.0.0 h1:JinZ9v2x6QeAOzA0cDA6aFe8vQuCrU8OyWEhG2iNzwY=
|
||||
github.com/bluenviron/mediacommon/v2 v2.0.0/go.mod h1:iHEz1SFIet6zBwAQoh1a92vTQ3dV3LpVFbom6/SLz3k=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gen2brain/aac-go v0.0.0-20230119102159-ef1e76509d21 h1:yfrARW/aVlqKORCdKrYdU0PZUKPqQvYEUQBKfVlNa9Q=
|
||||
github.com/gen2brain/aac-go v0.0.0-20230119102159-ef1e76509d21/go.mod h1:HZqGD/LXHB1VCGUGNzuyxSsD12f3KjbJbvImAmoK/mM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
||||
github.com/pion/rtp v1.8.13 h1:8uSUPpjSL4OlwZI8Ygqu7+h2p9NPFB+yAZ461Xn5sNg=
|
||||
github.com/pion/rtp v1.8.13/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4=
|
||||
github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI=
|
||||
github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
||||
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/youpy/go-riff v0.1.0 h1:vZO/37nI4tIET8tQI0Qn0Y79qQh99aEpponTPiPut7k=
|
||||
github.com/youpy/go-riff v0.1.0/go.mod h1:83nxdDV4Z9RzrTut9losK7ve4hUnxUR8ASSz4BsKXwQ=
|
||||
github.com/youpy/go-wav v0.3.2 h1:NLM8L/7yZ0Bntadw/0h95OyUsen+DQIVf9gay+SUsMU=
|
||||
github.com/youpy/go-wav v0.3.2/go.mod h1:0FCieAXAeSdcxFfwLpRuEo0PFmAoc+8NU34h7TUvk50=
|
||||
github.com/zaf/g711 v1.4.0 h1:XZYkjjiAg9QTBnHqEg37m2I9q3IIDv5JRYXs2N8ma7c=
|
||||
github.com/zaf/g711 v1.4.0/go.mod h1:eCDXt3dSp/kYYAoooba7ukD/Q75jvAaS4WOMr0l1Roo=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
1
reader/internal/processor/file2.go
Normal file
1
reader/internal/processor/file2.go
Normal file
@ -0,0 +1 @@
|
||||
package processor
|
42
reader/internal/processor/g711_to_aac.go
Normal file
42
reader/internal/processor/g711_to_aac.go
Normal file
@ -0,0 +1,42 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/g711"
|
||||
"github.com/gen2brain/aac-go"
|
||||
)
|
||||
|
||||
// ConvertG711ToLPCM converts G711 to LPCM.
|
||||
func ConvertG711ToLPCM(g711Samples []byte, mulaw bool) []byte {
|
||||
var pcmSamples []byte
|
||||
if mulaw {
|
||||
pcmSamples = g711.DecodeMulaw(g711Samples)
|
||||
} else {
|
||||
pcmSamples = g711.DecodeAlaw(g711Samples)
|
||||
}
|
||||
|
||||
return pcmSamples
|
||||
}
|
||||
|
||||
// ConvertLPCMToAAC converts G711 to AAC.
|
||||
func ConvertLPCMToAAC(pcmSamples []byte) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
opts := &aac.Options{
|
||||
SampleRate: 8000, // Исходная частота G711
|
||||
NumChannels: 1,
|
||||
}
|
||||
|
||||
enc, err := aac.NewEncoder(&buf, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating encoder: %v", err)
|
||||
}
|
||||
defer enc.Close()
|
||||
|
||||
r := bytes.NewReader(pcmSamples)
|
||||
if err := enc.Encode(r); err != nil {
|
||||
return nil, fmt.Errorf("error encoding: %v", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
133
reader/internal/processor/h264-aac_muxer.go
Normal file
133
reader/internal/processor/h264-aac_muxer.go
Normal file
@ -0,0 +1,133 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
||||
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
||||
)
|
||||
|
||||
func multiplyAndDivide(v, m, d int64) int64 {
|
||||
secs := v / d
|
||||
dec := v % d
|
||||
return (secs*m + dec*m/d)
|
||||
}
|
||||
|
||||
// MpegtsMuxer allows to save a H264 / MPEG-4 audio stream into a MPEG-TS file.
|
||||
type MpegtsMuxer struct {
|
||||
FileName string
|
||||
H264Format *format.H264
|
||||
Mpeg4AudioFormat *format.MPEG4Audio
|
||||
|
||||
f *os.File
|
||||
b *bufio.Writer
|
||||
w *mpegts.Writer
|
||||
h264Track *mpegts.Track
|
||||
mpeg4AudioTrack *mpegts.Track
|
||||
dtsExtractor *h264.DTSExtractor2
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Initialize initializes a MpegtsMuxer.
|
||||
func (e *MpegtsMuxer) Initialize() error {
|
||||
var err error
|
||||
e.f, err = os.Create(e.FileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.b = bufio.NewWriter(e.f)
|
||||
|
||||
e.h264Track = &mpegts.Track{
|
||||
Codec: &mpegts.CodecH264{},
|
||||
}
|
||||
|
||||
e.mpeg4AudioTrack = &mpegts.Track{
|
||||
Codec: &mpegts.CodecMPEG4Audio{
|
||||
Config: mpeg4audio.Config{},
|
||||
},
|
||||
}
|
||||
|
||||
e.w = mpegts.NewWriter(e.b, []*mpegts.Track{e.h264Track, e.mpeg4AudioTrack})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes all the MpegtsMuxer resources.
|
||||
func (e *MpegtsMuxer) Close() {
|
||||
e.b.Flush()
|
||||
e.f.Close()
|
||||
}
|
||||
|
||||
// WriteH264 writes a H264 access unit into MPEG-TS.
|
||||
func (e *MpegtsMuxer) WriteH264(pts int64, au [][]byte) error {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
var filteredAU [][]byte
|
||||
|
||||
nonIDRPresent := false
|
||||
idrPresent := false
|
||||
|
||||
for _, nalu := range au {
|
||||
typ := h264.NALUType(nalu[0] & 0x1F)
|
||||
switch typ {
|
||||
case h264.NALUTypeSPS:
|
||||
e.H264Format.SPS = nalu
|
||||
continue
|
||||
|
||||
case h264.NALUTypePPS:
|
||||
e.H264Format.PPS = nalu
|
||||
continue
|
||||
|
||||
case h264.NALUTypeAccessUnitDelimiter:
|
||||
continue
|
||||
|
||||
case h264.NALUTypeIDR:
|
||||
idrPresent = true
|
||||
|
||||
case h264.NALUTypeNonIDR:
|
||||
nonIDRPresent = true
|
||||
}
|
||||
|
||||
filteredAU = append(filteredAU, nalu)
|
||||
}
|
||||
|
||||
au = filteredAU
|
||||
|
||||
if au == nil || (!nonIDRPresent && !idrPresent) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add SPS and PPS before access unit that contains an IDR.
|
||||
if idrPresent {
|
||||
au = append([][]byte{e.H264Format.SPS, e.H264Format.PPS}, au...)
|
||||
}
|
||||
|
||||
if e.dtsExtractor == nil {
|
||||
// Skip samples silently until we find one with an IDR.
|
||||
if !idrPresent {
|
||||
return nil
|
||||
}
|
||||
e.dtsExtractor = h264.NewDTSExtractor2()
|
||||
}
|
||||
|
||||
dts, err := e.dtsExtractor.Extract(au, pts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Encode into MPEG-TS.
|
||||
return e.w.WriteH2642(e.h264Track, pts, dts, au)
|
||||
}
|
||||
|
||||
// WriteAAC writes MPEG-4 audio access units into MPEG-TS.
|
||||
func (e *MpegtsMuxer) WriteAAC(aus [][]byte, pts int64) error {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
return e.w.WriteMPEG4Audio(e.mpeg4AudioTrack, multiplyAndDivide(pts, 90000, int64(8000)), aus)
|
||||
}
|
@ -9,7 +9,7 @@ require (
|
||||
github.com/bluenviron/mediacommon v1.14.0
|
||||
github.com/bluenviron/mediacommon/v2 v2.0.0
|
||||
github.com/gen2brain/aac-go v0.0.0-20230119102159-ef1e76509d21
|
||||
github.com/hraban/opus v0.0.0-20230925203106-0188a62cb302
|
||||
github.com/golang/snappy v1.0.0
|
||||
github.com/pion/rtp v1.8.12
|
||||
github.com/zaf/g711 v1.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -22,12 +22,12 @@ github.com/gen2brain/aac-go v0.0.0-20230119102159-ef1e76509d21 h1:yfrARW/aVlqKOR
|
||||
github.com/gen2brain/aac-go v0.0.0-20230119102159-ef1e76509d21/go.mod h1:HZqGD/LXHB1VCGUGNzuyxSsD12f3KjbJbvImAmoK/mM=
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grafov/m3u8 v0.12.1 h1:DuP1uA1kvRRmGNAZ0m+ObLv1dvrfNO0TPx0c/enNk0s=
|
||||
github.com/grafov/m3u8 v0.12.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080=
|
||||
github.com/hraban/opus v0.0.0-20230925203106-0188a62cb302 h1:K7bmEmIesLcvCW0Ic2rCk6LtP5++nTnPmrO8mg5umlA=
|
||||
github.com/hraban/opus v0.0.0-20230925203106-0188a62cb302/go.mod h1:YQQXrWHN3JEvCtw5ImyTCcPeU/ZLo/YMA+TpB64XdrU=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||
|
@ -1,36 +1,36 @@
|
||||
#camera_1: rtsp://intercom-video-1.insit.ru/camera01-centr-pl_slavy
|
||||
camera_2: rtsp://intercom-video-1.insit.ru/camera03-centr-administraciya
|
||||
#camera_2: rtsp://intercom-video-1.insit.ru/camera03-centr-administraciya
|
||||
#camera_3: rtsp://intercom-video-1.insit.ru/camera08-centr-skver_kalinina
|
||||
camera_4: rtsp://intercom-video-1.insit.ru/camera04-centr-pobedy
|
||||
camera_5: rtsp://intercom-video-1.insit.ru/camera11-centr-dk_kirova
|
||||
camera_6: rtsp://intercom-video-1.insit.ru/camera13-centr-pobedy_sutyagina
|
||||
camera_7: rtsp://intercom-video-1.insit.ru/camera09-centr-nalogovaya
|
||||
#camera_4: rtsp://intercom-video-1.insit.ru/camera04-centr-pobedy
|
||||
#camera_5: rtsp://intercom-video-1.insit.ru/camera11-centr-dk_kirova
|
||||
#camera_6: rtsp://intercom-video-1.insit.ru/camera13-centr-pobedy_sutyagina
|
||||
#camera_7: rtsp://intercom-video-1.insit.ru/camera09-centr-nalogovaya
|
||||
#camera_8: rtsp://intercom-video-1.insit.ru/camera59-centr-prktslavy
|
||||
camera_9: rtsp://intercom-video-1.insit.ru/camera10-centr-kommunisticheskiy_ilycha
|
||||
camera_10: rtsp://intercom-video-1.insit.ru/camera16-centr-sportschool2
|
||||
camera_11: rtsp://intercom-video-1.insit.ru/camera40-center-kommunisticheskiy
|
||||
camera_12: rtsp://intercom-video-1.insit.ru/camera12-centr-skver_pavshih_geroev
|
||||
camera_13: rtsp://intercom-video-1.insit.ru/camera39-center-kommunisticheskiy
|
||||
camera_14: rtsp://intercom-video-1.insit.ru/camera14-centr-stadion_himik
|
||||
camera_15: rtsp://intercom-video-1.insit.ru/camera41-center-kuznecova_borby
|
||||
camera_16: rtsp://intercom-video-1.insit.ru/camera83-gorod-Kommunistichesky
|
||||
camera_17: rtsp://intercom-video-1.insit.ru/camera54-centr-kirova-kalinina
|
||||
camera_18: rtsp://intercom-video-1.insit.ru/camera53-centr-pobedy_slavy
|
||||
#camera_9: rtsp://intercom-video-1.insit.ru/camera10-centr-kommunisticheskiy_ilycha
|
||||
#camera_10: rtsp://intercom-video-1.insit.ru/camera16-centr-sportschool2
|
||||
#camera_11: rtsp://intercom-video-1.insit.ru/camera40-center-kommunisticheskiy
|
||||
#camera_12: rtsp://intercom-video-1.insit.ru/camera12-centr-skver_pavshih_geroev
|
||||
#camera_13: rtsp://intercom-video-1.insit.ru/camera39-center-kommunisticheskiy
|
||||
#camera_14: rtsp://intercom-video-1.insit.ru/camera14-centr-stadion_himik
|
||||
#camera_15: rtsp://intercom-video-1.insit.ru/camera41-center-kuznecova_borby
|
||||
#camera_16: rtsp://intercom-video-1.insit.ru/camera83-gorod-Kommunistichesky
|
||||
#camera_17: rtsp://intercom-video-1.insit.ru/camera54-centr-kirova-kalinina
|
||||
#camera_18: rtsp://intercom-video-1.insit.ru/camera53-centr-pobedy_slavy
|
||||
camera_19: rtsp://intercom-video-1.insit.ru/camera44-center-skver_temnika
|
||||
camera_20: rtsp://intercom-video-1.insit.ru/camera28-oktyabrskiy-severnaya23a
|
||||
camera_21: rtsp://intercom-video-1.insit.ru/dp-qamumnrlkizuypnetljzzkjqamdoti
|
||||
camera_22: rtsp://intercom-video-1.insit.ru/dp-ohusuxzcvzsnpzzvkpyhddnwxuyeyc
|
||||
camera_23: rtsp://intercom-video-1.insit.ru/dp-bflbwjulvgfzurmcpejklrfvqairns
|
||||
camera_24: rtsp://intercom-video-1.insit.ru/dp-ajiymmjyytokybrpganfcxlfyjcdbgezphn
|
||||
camera_25: rtsp://intercom-video-2.insit.ru/dp-pobedi6a-ii-2125126423
|
||||
camera_26: rtsp://intercom-video-1.insit.ru/dp-pobedi11-ii-2108117729
|
||||
camera_27: rtsp://intercom-video-2.insit.ru/dp-pobedi11-i-2108117197
|
||||
camera_28: rtsp://intercom-video-2.insit.ru/dp-nfhapwbfjpqkmaymfeipraxtzcpedk
|
||||
camera_29: rtsp://intercom-video-1.insit.ru/dp-swcixufwlheiwwrcvsrbkmhqzqvbxz
|
||||
#camera_20: rtsp://intercom-video-1.insit.ru/camera28-oktyabrskiy-severnaya23a
|
||||
#camera_21: rtsp://intercom-video-1.insit.ru/dp-qamumnrlkizuypnetljzzkjqamdoti
|
||||
#camera_22: rtsp://intercom-video-1.insit.ru/dp-ohusuxzcvzsnpzzvkpyhddnwxuyeyc
|
||||
#camera_23: rtsp://intercom-video-1.insit.ru/dp-bflbwjulvgfzurmcpejklrfvqairns
|
||||
#camera_24: rtsp://intercom-video-1.insit.ru/dp-ajiymmjyytokybrpganfcxlfyjcdbgezphn
|
||||
#camera_25: rtsp://intercom-video-2.insit.ru/dp-pobedi6a-ii-2125126423
|
||||
#camera_26: rtsp://intercom-video-1.insit.ru/dp-pobedi11-ii-2108117729
|
||||
#camera_27: rtsp://intercom-video-2.insit.ru/dp-pobedi11-i-2108117197
|
||||
#camera_28: rtsp://intercom-video-2.insit.ru/dp-nfhapwbfjpqkmaymfeipraxtzcpedk
|
||||
#camera_29: rtsp://intercom-video-1.insit.ru/dp-swcixufwlheiwwrcvsrbkmhqzqvbxz
|
||||
#camera_30: rtsp://intercom-video-1.insit.ru/dp-nerrjszqrbhjvqmfxskunejafdiihj
|
||||
camera_31: rtsp://intercom-video-1.insit.ru/dp-aiwukyujwonohpjyzeniispqqullyr
|
||||
camera_32: rtsp://intercom-video-1.insit.ru/dp-woxvkbynctgfbuztsalttgburbpvjf
|
||||
camera_33: rtsp://intercom-video-1.insit.ru/dp-fdzbasqehtptsuhxnjeqqnlrixfahcgvlcr
|
||||
camera_34: rtsp://intercom-video-1.insit.ru/dp-exyeqscyamrbkwkjifagouyprtsdoe
|
||||
camera_35: rtsp://intercom-video-2.insit.ru/dp-sutyagina3a-iv-uujtwbsjekv
|
||||
camera_36: rtsp://intercom-video-1.insit.ru/dp-wyshispseamhqmnhkqwkbarshnrvni
|
||||
#camera_31: rtsp://intercom-video-1.insit.ru/dp-aiwukyujwonohpjyzeniispqqullyr
|
||||
#camera_32: rtsp://intercom-video-1.insit.ru/dp-woxvkbynctgfbuztsalttgburbpvjf
|
||||
#camera_33: rtsp://intercom-video-1.insit.ru/dp-fdzbasqehtptsuhxnjeqqnlrixfahcgvlcr
|
||||
#camera_34: rtsp://intercom-video-1.insit.ru/dp-exyeqscyamrbkwkjifagouyprtsdoe
|
||||
#camera_35: rtsp://intercom-video-2.insit.ru/dp-sutyagina3a-iv-uujtwbsjekv
|
||||
#camera_36: rtsp://intercom-video-1.insit.ru/dp-wyshispseamhqmnhkqwkbarshnrvni
|
@ -63,7 +63,7 @@ func (e *MpegtsMuxer) Close() {
|
||||
}
|
||||
|
||||
// WriteH264 writes a H264 access unit into MPEG-TS.
|
||||
func (e *MpegtsMuxer) WriteH264(au [][]byte, pts int64) error {
|
||||
func (e *MpegtsMuxer) WriteH264(pts int64, au [][]byte) error {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -108,7 +110,7 @@ func RTSP(dir string, period int, link string) error {
|
||||
} else {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
/*
|
||||
h265Format, h265Media, err := formats.FindH265Format(desc)
|
||||
if h265Format != nil {
|
||||
log.Println("[h265]: format found")
|
||||
@ -156,7 +158,7 @@ func RTSP(dir string, period int, link string) error {
|
||||
} else {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
*/
|
||||
// Start program according to gotten formats.
|
||||
switch {
|
||||
case videoFormat == "AV1" && audioFormat == "":
|
||||
@ -339,7 +341,7 @@ func RTSP(dir string, period int, link string) error {
|
||||
}
|
||||
|
||||
// Encode the access unit into MPEG-TS.
|
||||
err = currentMpegtsMuxer.WriteH264(au, pts)
|
||||
err = currentMpegtsMuxer.WriteH264(pts, au)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -407,25 +409,52 @@ func RTSP(dir string, period int, link string) error {
|
||||
log.Printf("[%v-%v]: create decoder error: %v\n", videoFormat, audioFormat, err)
|
||||
}
|
||||
|
||||
// Setup MPEG-TS muxer.
|
||||
var aacFormat *format.MPEG4Audio
|
||||
//// Setup MPEG-TS muxer.
|
||||
//var aacFormat *format.MPEG4Audio
|
||||
//
|
||||
//currentMpegtsMuxer := formats.MpegtsMuxer{
|
||||
// FileName: fn.SetNumNTime(),
|
||||
// H264Format: h264Format,
|
||||
// Mpeg4AudioFormat: aacFormat,
|
||||
//}
|
||||
|
||||
currentMpegtsMuxer := formats.MpegtsMuxer{
|
||||
FileName: fn.SetNumNTime(),
|
||||
H264Format: h264Format,
|
||||
Mpeg4AudioFormat: aacFormat,
|
||||
}
|
||||
|
||||
err = currentMpegtsMuxer.Initialize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%v-%v]: init muxer error: %w\n", videoFormat, audioFormat, err)
|
||||
}
|
||||
//err = currentMpegtsMuxer.Initialize()
|
||||
//if err != nil {
|
||||
// return fmt.Errorf("[%v-%v]: init muxer error: %w\n", videoFormat, audioFormat, err)
|
||||
//}
|
||||
|
||||
// Setup all medias.
|
||||
err = c.SetupAll(desc.BaseURL, desc.Medias)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%v-%v]: setup media error: %w\n", videoFormat, audioFormat, err)
|
||||
}
|
||||
///////////////////////////////////////////////////////////
|
||||
file, err := os.Create(
|
||||
dir + "/data/" + cuttedURI + "/" + time.Now().Format("15-04-05_02-01-2006") + ".insit")
|
||||
if err != nil {
|
||||
fmt.Println("Ошибка создания файла:", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
seg := storage.Segment{
|
||||
Start: time.Now().Format("15-04-05_02-01-2006"),
|
||||
Duration: strconv.Itoa(period),
|
||||
Packets: storage.InterleavedPacket{},
|
||||
}
|
||||
|
||||
// Записываем заголовок файла (например, streamID).
|
||||
streamID := cuttedURI
|
||||
if err := binary.Write(file, binary.LittleEndian, int32(len(streamID))); err != nil {
|
||||
fmt.Println("Ошибка записи заголовка:", err)
|
||||
}
|
||||
if _, err := file.Write([]byte(streamID)); err != nil {
|
||||
fmt.Println("Ошибка записи streamID:", err)
|
||||
}
|
||||
|
||||
err = storage.WriteHeader(file, seg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%v-%v]: write header error: %w", videoFormat, audioFormat, err)
|
||||
}
|
||||
|
||||
// Process input rtp packets.
|
||||
c.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
|
||||
@ -438,11 +467,23 @@ func RTSP(dir string, period int, link string) error {
|
||||
return
|
||||
}
|
||||
|
||||
// Encode the access unit into MPEG-TS.
|
||||
err = currentMpegtsMuxer.WriteH264(au, pts)
|
||||
if err != nil {
|
||||
if au != nil {
|
||||
|
||||
seg.Packets.Type = storage.PacketTypeH264
|
||||
seg.Packets.Pts = pts
|
||||
seg.Packets.H264AUs = au
|
||||
|
||||
// Записываем сегмент с interleaved пакетами.
|
||||
if err := storage.WriteInterleavedPacket(file, seg); err != nil {
|
||||
fmt.Println("Ошибка записи сегмента:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
//// Encode the access unit into MPEG-TS.
|
||||
//err = currentMpegtsMuxer.WriteH264(pts, au)
|
||||
//if err != nil {
|
||||
// return
|
||||
//}
|
||||
|
||||
case *format.G711:
|
||||
// Process G711 flow and returns PTS and AU.
|
||||
@ -452,18 +493,33 @@ func RTSP(dir string, period int, link string) error {
|
||||
return
|
||||
}
|
||||
|
||||
// Convert G711 to AAC.
|
||||
au, err = formats.ConvertG711ToAAC(au, f.MULaw)
|
||||
if err != nil {
|
||||
log.Printf("[%v-%v]: converting to AAC frame error: %v\n", videoFormat, audioFormat, err)
|
||||
}
|
||||
if au != nil {
|
||||
|
||||
// Encode the access unit into MPEG-TS.
|
||||
err = currentMpegtsMuxer.WriteAAC([][]byte{au}, pts)
|
||||
if err != nil {
|
||||
lpcmSamples := formats.ConvertG711ToLPCM(au, f.MULaw)
|
||||
|
||||
seg.Packets.Type = storage.PacketTypeH264
|
||||
seg.Packets.Pts = pts
|
||||
seg.Packets.LPCMSamples = lpcmSamples
|
||||
|
||||
// Записываем сегмент с interleaved пакетами.
|
||||
if err := storage.WriteInterleavedPacket(file, seg); err != nil {
|
||||
fmt.Println("Ошибка записи сегмента:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//// Convert G711 to AAC.
|
||||
//au, err = formats.ConvertLPCMToAAC(lpcmSamples)
|
||||
//if err != nil {
|
||||
// log.Printf("[%v-%v]: converting to AAC frame error: %v\n", videoFormat, audioFormat, err)
|
||||
//}
|
||||
//
|
||||
//// Encode the access unit into MPEG-TS.
|
||||
//err = currentMpegtsMuxer.WriteAAC([][]byte{au}, pts)
|
||||
//if err != nil {
|
||||
// return
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// Start playing.
|
||||
@ -479,6 +535,7 @@ func RTSP(dir string, period int, link string) error {
|
||||
// Rotate files.
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
/*
|
||||
// Logic for rotation files.
|
||||
currentMpegtsMuxer.Close()
|
||||
currentMpegtsMuxer.FileName = fn.SetNumNTime()
|
||||
@ -488,12 +545,41 @@ func RTSP(dir string, period int, link string) error {
|
||||
log.Printf("[%v-%v]: init muxer error: %v\n", videoFormat, audioFormat, err)
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
file.Close()
|
||||
|
||||
file, err = os.Create(
|
||||
dir + "/data/" + cuttedURI + "/" + time.Now().Format("15-04-05_02-01-2006") + ".insit")
|
||||
if err != nil {
|
||||
fmt.Println("Ошибка создания файла:", err)
|
||||
}
|
||||
|
||||
seg = storage.Segment{
|
||||
Start: time.Now().Format("15-04-05_02-01-2006"),
|
||||
Duration: strconv.Itoa(period),
|
||||
Packets: storage.InterleavedPacket{},
|
||||
}
|
||||
|
||||
// Записываем заголовок файла (например, streamID).
|
||||
streamID = cuttedURI
|
||||
if err := binary.Write(file, binary.LittleEndian, int32(len(streamID))); err != nil {
|
||||
fmt.Println("Ошибка записи заголовка:", err)
|
||||
}
|
||||
if _, err := file.Write([]byte(streamID)); err != nil {
|
||||
fmt.Println("Ошибка записи streamID:", err)
|
||||
}
|
||||
|
||||
err = storage.WriteHeader(file, seg)
|
||||
if err != nil {
|
||||
log.Printf("[%v-%v]: write header error: %w", videoFormat, audioFormat, err)
|
||||
}
|
||||
|
||||
log.Printf("[%v-%v]: new file for recording created: %v",
|
||||
videoFormat, audioFormat, currentMpegtsMuxer.FileName)
|
||||
videoFormat, audioFormat, seg.Start+".insit")
|
||||
}
|
||||
}()
|
||||
|
||||
/*
|
||||
panic(c.Wait())
|
||||
|
||||
case videoFormat == "H264" && audioFormat == "":
|
||||
@ -568,7 +654,7 @@ func RTSP(dir string, period int, link string) error {
|
||||
}
|
||||
|
||||
log.Println("New file for recording created:", currentMpegtsMuxer.fileName)
|
||||
*/
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
@ -649,7 +735,7 @@ func RTSP(dir string, period int, link string) error {
|
||||
}
|
||||
|
||||
log.Println("New file for recording created:", currentMpegtsMuxer.fileName)
|
||||
*/
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
@ -708,7 +794,7 @@ func RTSP(dir string, period int, link string) error {
|
||||
}
|
||||
|
||||
log.Println("New file for recording created:", currentMpegtsMuxer.fileName)
|
||||
*/
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
@ -767,7 +853,7 @@ func RTSP(dir string, period int, link string) error {
|
||||
}
|
||||
|
||||
log.Println("New file for recording created:", currentMpegtsMuxer.fileName)
|
||||
*/
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
@ -828,7 +914,7 @@ func RTSP(dir string, period int, link string) error {
|
||||
}
|
||||
|
||||
log.Println("New file for recording created:", currentMpegtsMuxer.fileName)
|
||||
*/
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
@ -887,7 +973,7 @@ func RTSP(dir string, period int, link string) error {
|
||||
}
|
||||
|
||||
log.Println("New file for recording created:", currentMpegtsMuxer.fileName)
|
||||
*/
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
@ -954,7 +1040,7 @@ func RTSP(dir string, period int, link string) error {
|
||||
}
|
||||
|
||||
log.Println("New file for recording created:", currentMpegtsMuxer.fileName)
|
||||
*/
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
@ -1021,11 +1107,12 @@ func RTSP(dir string, period int, link string) error {
|
||||
}
|
||||
|
||||
log.Println("New file for recording created:", currentMpegtsMuxer.fileName)
|
||||
*/
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
*/
|
||||
panic(c.Wait())
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ func CutURI(URI string) (CutterURI string) {
|
||||
}
|
||||
|
||||
// CreateFileName creates FileName structure.
|
||||
func CreateFileName(dir string, resolutions []string, URI string, period int) *protos.FileName {
|
||||
func CreateFileName(dir string, resolutions []string, cuttedURI string, period int) *protos.FileName {
|
||||
fn := protos.FileName{
|
||||
Path: dir + "/data/" + URI + "/" + resolutions[0],
|
||||
Path: dir + "/data/" + cuttedURI + "/" + resolutions[0],
|
||||
TimeNow: time.Now().Format("15-04-05_02-01-2006"),
|
||||
Name: "videoFragment",
|
||||
Number: -1,
|
||||
|
@ -1,19 +1,23 @@
|
||||
package storage
|
||||
|
||||
import "time"
|
||||
const (
|
||||
PacketTypeH264 = 1
|
||||
PacketTypeLPCM = 2
|
||||
)
|
||||
|
||||
// InterleavedPacket представляет пакет, который может быть либо H264, либо G711.
|
||||
type InterleavedPacket struct {
|
||||
// Type: 1 для H264, 2 для G711.
|
||||
Type byte
|
||||
Pts int64
|
||||
|
||||
H264AUs [][]byte
|
||||
LPCMSamples []byte
|
||||
}
|
||||
|
||||
// Segment содержит строковые поля Start и Duration, а также набор interleaved пакетов.
|
||||
type Segment struct {
|
||||
Start time.Time
|
||||
Duration time.Duration
|
||||
|
||||
H264 byte
|
||||
G711 byte
|
||||
|
||||
SPS []byte
|
||||
PPS []byte
|
||||
}
|
||||
|
||||
type StreamRecord struct {
|
||||
ID string
|
||||
Segments []Segment
|
||||
Start string
|
||||
Duration string
|
||||
Packets InterleavedPacket
|
||||
}
|
||||
|
@ -1 +1,162 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang/snappy"
|
||||
)
|
||||
|
||||
// writeString записывает строку: сначала длину (int32), затем байты строки.
|
||||
func writeString(w io.Writer, s string) error {
|
||||
if err := binary.Write(w, binary.LittleEndian, int32(len(s))); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := w.Write([]byte(s))
|
||||
return err
|
||||
}
|
||||
|
||||
// WritePacket записывает один interleaved пакет в writer.
|
||||
func WritePacket(w io.Writer, pkt InterleavedPacket) error {
|
||||
// Записываем тип пакета (1 байт).
|
||||
if err := binary.Write(w, binary.LittleEndian, pkt.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
// Записываем pts.
|
||||
if err := binary.Write(w, binary.LittleEndian, pkt.Pts); err != nil {
|
||||
return err
|
||||
}
|
||||
// В зависимости от типа пакета записываем данные access unit.
|
||||
if pkt.Type == PacketTypeH264 {
|
||||
// Для H264 AU — [][]byte.
|
||||
numAUs := int32(len(pkt.H264AUs))
|
||||
if err := binary.Write(w, binary.LittleEndian, numAUs); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, au := range pkt.H264AUs {
|
||||
// Сначала длина AU.
|
||||
if err := binary.Write(w, binary.LittleEndian, int32(len(au))); err != nil {
|
||||
return err
|
||||
}
|
||||
// Затем сами данные.
|
||||
if _, err := w.Write(au); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if pkt.Type == PacketTypeLPCM {
|
||||
// Для G711 AU — []byte.
|
||||
if err := binary.Write(w, binary.LittleEndian, int32(len(pkt.LPCMSamples))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(pkt.LPCMSamples); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("неизвестный тип пакета: %d", pkt.Type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteHeader writes header to a file.
|
||||
func WriteHeader(w io.Writer, seg Segment) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Записываем строки Start и Duration.
|
||||
if err := writeString(&buf, seg.Start); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeString(&buf, seg.Duration); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Сначала записываем длину сегмента, затем данные сегмента.
|
||||
segData := buf.Bytes()
|
||||
if err := binary.Write(w, binary.LittleEndian, int32(len(segData))); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := w.Write(segData)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteInterleavedPacket записывает один сегмент в writer. Сначала собирается содержимое сегмента в буфер,
|
||||
// затем записывается его длина (int32) и данные.
|
||||
func WriteInterleavedPacket(w io.Writer, seg Segment) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Записываем количество пакетов.
|
||||
if err := binary.Write(&buf, binary.LittleEndian, int32(1)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Для каждого пакета записываем данные.
|
||||
if err := WritePacket(&buf, seg.Packets); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Сначала записываем длину сегмента, затем данные сегмента.
|
||||
segData := buf.Bytes()
|
||||
if err := binary.Write(w, binary.LittleEndian, int32(len(segData))); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := w.Write(segData)
|
||||
return err
|
||||
}
|
||||
|
||||
func main() {
|
||||
now := time.Now()
|
||||
// Пример сегмента с interleaved пакетами.
|
||||
seg := Segment{
|
||||
Start: now.Format(time.RFC3339),
|
||||
Duration: "1m", // длительность сегмента в виде строки
|
||||
Packets: InterleavedPacket{
|
||||
|
||||
Type: PacketTypeH264,
|
||||
Pts: now.UnixNano(),
|
||||
H264AUs: [][]byte{
|
||||
[]byte{0x00, 0x01, 0x02},
|
||||
[]byte{0x03, 0x04},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Открываем файл для записи.
|
||||
f, err := os.Create("stream_interleaved.bin")
|
||||
if err != nil {
|
||||
fmt.Println("Ошибка создания файла:", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Оборачиваем writer через snappy для быстрой компрессии (если требуется).
|
||||
writer := snappy.NewBufferedWriter(f)
|
||||
defer writer.Close()
|
||||
|
||||
// Записываем заголовок файла (например, streamID).
|
||||
streamID := "example_stream"
|
||||
if err := binary.Write(writer, binary.LittleEndian, int32(len(streamID))); err != nil {
|
||||
fmt.Println("Ошибка записи заголовка:", err)
|
||||
return
|
||||
}
|
||||
if _, err := writer.Write([]byte(streamID)); err != nil {
|
||||
fmt.Println("Ошибка записи streamID:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Записываем сегмент с interleaved пакетами.
|
||||
if err := WriteInterleavedPacket(writer, seg); err != nil {
|
||||
fmt.Println("Ошибка записи сегмента:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Обязательно делаем Flush, чтобы данные точно записались.
|
||||
if err := writer.Flush(); err != nil {
|
||||
fmt.Println("Ошибка при сбросе данных:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Сегмент с interleaved пакетами успешно записан.")
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user