Added th opportunity to get more formats.

This commit is contained in:
Сергей Петров 2025-03-20 09:58:31 +05:00
parent b26bcb9f69
commit c59702fcff
23 changed files with 2241 additions and 281 deletions

View File

@ -19,10 +19,10 @@ func main() {
// Connect to each camera. // Connect to each camera.
for _, link := range c { for _, link := range c {
log.Printf("start recording on camera: %s\n", link) log.Printf("process camera:\n %s\n", link)
go func() { go func() {
err = rtsp.RTSP(*directory, 60, link) err = rtsp.StartWriter(*directory, 60, link)
if err != nil { if err != nil {
log.Printf("procRTSP function error for camera %s: %s", link, err.Error()) log.Printf("procRTSP function error for camera %s: %s", link, err.Error())
} }

View File

@ -6,16 +6,18 @@ require (
git.insit.tech/sas/rtsp_proxy v0.0.0-20250310124520-82fa76149f4e git.insit.tech/sas/rtsp_proxy v0.0.0-20250310124520-82fa76149f4e
github.com/Eyevinn/mp4ff v0.47.0 github.com/Eyevinn/mp4ff v0.47.0
github.com/bluenviron/gortsplib/v4 v4.12.3 github.com/bluenviron/gortsplib/v4 v4.12.3
github.com/bluenviron/mediacommon v1.14.0
github.com/bluenviron/mediacommon/v2 v2.0.0 github.com/bluenviron/mediacommon/v2 v2.0.0
github.com/gen2brain/aac-go v0.0.0-20230119102159-ef1e76509d21 github.com/gen2brain/aac-go v0.0.0-20230119102159-ef1e76509d21
github.com/hraban/opus v0.0.0-20230925203106-0188a62cb302
github.com/pion/rtp v1.8.12 github.com/pion/rtp v1.8.12
github.com/zaf/g711 v1.4.0 github.com/zaf/g711 v1.4.0
gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/asticode/go-astikit v0.52.0 // indirect github.com/asticode/go-astikit v0.52.0 // indirect
github.com/asticode/go-astits v1.13.0 // indirect github.com/asticode/go-astits v1.13.0 // indirect
github.com/bluenviron/mediacommon v1.14.0 // indirect
github.com/cyub/ringbuffer v0.0.0-20221202135829-35445cc89929 // indirect github.com/cyub/ringbuffer v0.0.0-20221202135829-35445cc89929 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/grafov/m3u8 v0.12.1 // indirect github.com/grafov/m3u8 v0.12.1 // indirect
@ -24,5 +26,4 @@ require (
github.com/pion/sdp/v3 v3.0.10 // indirect github.com/pion/sdp/v3 v3.0.10 // indirect
golang.org/x/net v0.37.0 // indirect golang.org/x/net v0.37.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@ -26,6 +26,8 @@ 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/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 h1:DuP1uA1kvRRmGNAZ0m+ObLv1dvrfNO0TPx0c/enNk0s=
github.com/grafov/m3u8 v0.12.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= 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 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= 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 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
@ -56,6 +58,7 @@ 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/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 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,36 +1,36 @@
#camera_1: rtsp://intercom-video-1.insit.ru/camera01-centr-pl_slavy #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_3: rtsp://intercom-video-1.insit.ru/camera08-centr-skver_kalinina
#camera_4: rtsp://intercom-video-1.insit.ru/camera04-centr-pobedy camera_4: rtsp://intercom-video-1.insit.ru/camera04-centr-pobedy
#camera_5: rtsp://intercom-video-1.insit.ru/camera11-centr-dk_kirova 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_6: rtsp://intercom-video-1.insit.ru/camera13-centr-pobedy_sutyagina
#camera_7: rtsp://intercom-video-1.insit.ru/camera09-centr-nalogovaya camera_7: rtsp://intercom-video-1.insit.ru/camera09-centr-nalogovaya
#camera_8: rtsp://intercom-video-1.insit.ru/camera59-centr-prktslavy #camera_8: rtsp://intercom-video-1.insit.ru/camera59-centr-prktslavy
#camera_9: rtsp://intercom-video-1.insit.ru/camera10-centr-kommunisticheskiy_ilycha camera_9: rtsp://intercom-video-1.insit.ru/camera10-centr-kommunisticheskiy_ilycha
#camera_10: rtsp://intercom-video-1.insit.ru/camera16-centr-sportschool2 camera_10: rtsp://intercom-video-1.insit.ru/camera16-centr-sportschool2
#camera_11: rtsp://intercom-video-1.insit.ru/camera40-center-kommunisticheskiy 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_12: rtsp://intercom-video-1.insit.ru/camera12-centr-skver_pavshih_geroev
#camera_13: rtsp://intercom-video-1.insit.ru/camera39-center-kommunisticheskiy camera_13: rtsp://intercom-video-1.insit.ru/camera39-center-kommunisticheskiy
#camera_14: rtsp://intercom-video-1.insit.ru/camera14-centr-stadion_himik 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_15: rtsp://intercom-video-1.insit.ru/camera41-center-kuznecova_borby
#camera_16: rtsp://intercom-video-1.insit.ru/camera83-gorod-Kommunistichesky camera_16: rtsp://intercom-video-1.insit.ru/camera83-gorod-Kommunistichesky
#camera_17: rtsp://intercom-video-1.insit.ru/camera54-centr-kirova-kalinina 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_18: rtsp://intercom-video-1.insit.ru/camera53-centr-pobedy_slavy
#camera_19: rtsp://intercom-video-1.insit.ru/camera44-center-skver_temnika camera_19: rtsp://intercom-video-1.insit.ru/camera44-center-skver_temnika
#camera_20: rtsp://intercom-video-1.insit.ru/camera28-oktyabrskiy-severnaya23a camera_20: rtsp://intercom-video-1.insit.ru/camera28-oktyabrskiy-severnaya23a
#camera_21: rtsp://intercom-video-1.insit.ru/dp-qamumnrlkizuypnetljzzkjqamdoti camera_21: rtsp://intercom-video-1.insit.ru/dp-qamumnrlkizuypnetljzzkjqamdoti
#camera_22: rtsp://intercom-video-1.insit.ru/dp-ohusuxzcvzsnpzzvkpyhddnwxuyeyc camera_22: rtsp://intercom-video-1.insit.ru/dp-ohusuxzcvzsnpzzvkpyhddnwxuyeyc
#camera_23: rtsp://intercom-video-1.insit.ru/dp-bflbwjulvgfzurmcpejklrfvqairns camera_23: rtsp://intercom-video-1.insit.ru/dp-bflbwjulvgfzurmcpejklrfvqairns
#camera_24: rtsp://intercom-video-1.insit.ru/dp-ajiymmjyytokybrpganfcxlfyjcdbgezphn camera_24: rtsp://intercom-video-1.insit.ru/dp-ajiymmjyytokybrpganfcxlfyjcdbgezphn
#camera_25: rtsp://intercom-video-2.insit.ru/dp-pobedi6a-ii-2125126423 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_26: rtsp://intercom-video-1.insit.ru/dp-pobedi11-ii-2108117729
#camera_27: rtsp://intercom-video-2.insit.ru/dp-pobedi11-i-2108117197 camera_27: rtsp://intercom-video-2.insit.ru/dp-pobedi11-i-2108117197
#camera_28: rtsp://intercom-video-2.insit.ru/dp-nfhapwbfjpqkmaymfeipraxtzcpedk camera_28: rtsp://intercom-video-2.insit.ru/dp-nfhapwbfjpqkmaymfeipraxtzcpedk
#camera_29: rtsp://intercom-video-1.insit.ru/dp-swcixufwlheiwwrcvsrbkmhqzqvbxz camera_29: rtsp://intercom-video-1.insit.ru/dp-swcixufwlheiwwrcvsrbkmhqzqvbxz
#camera_30: rtsp://intercom-video-1.insit.ru/dp-nerrjszqrbhjvqmfxskunejafdiihj #camera_30: rtsp://intercom-video-1.insit.ru/dp-nerrjszqrbhjvqmfxskunejafdiihj
#camera_31: rtsp://intercom-video-1.insit.ru/dp-aiwukyujwonohpjyzeniispqqullyr camera_31: rtsp://intercom-video-1.insit.ru/dp-aiwukyujwonohpjyzeniispqqullyr
#camera_32: rtsp://intercom-video-1.insit.ru/dp-woxvkbynctgfbuztsalttgburbpvjf camera_32: rtsp://intercom-video-1.insit.ru/dp-woxvkbynctgfbuztsalttgburbpvjf
#camera_33: rtsp://intercom-video-1.insit.ru/dp-fdzbasqehtptsuhxnjeqqnlrixfahcgvlcr camera_33: rtsp://intercom-video-1.insit.ru/dp-fdzbasqehtptsuhxnjeqqnlrixfahcgvlcr
#camera_34: rtsp://intercom-video-1.insit.ru/dp-exyeqscyamrbkwkjifagouyprtsdoe camera_34: rtsp://intercom-video-1.insit.ru/dp-exyeqscyamrbkwkjifagouyprtsdoe
#camera_35: rtsp://intercom-video-2.insit.ru/dp-sutyagina3a-iv-uujtwbsjekv camera_35: rtsp://intercom-video-2.insit.ru/dp-sutyagina3a-iv-uujtwbsjekv
camera_36: rtsp://intercom-video-1.insit.ru/dp-wyshispseamhqmnhkqwkbarshnrvni camera_36: rtsp://intercom-video-1.insit.ru/dp-wyshispseamhqmnhkqwkbarshnrvni

View File

@ -0,0 +1,48 @@
package formats
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio"
"github.com/pion/rtp"
)
// FindAACFormat finds the AAC media and format.
func FindAACFormat(desc *description.Session) (*format.MPEG4Audio, *description.Media, error) {
var aacFormat *format.MPEG4Audio
aacMedia := desc.FindFormat(&aacFormat)
if aacMedia == nil {
return nil, nil, errors.New("media AAC not found")
}
return aacFormat, aacMedia, nil
}
// ProcessAAC processes AAC flow and returns PTS and AUS.
func ProcessAAC(
c *gortsplib.Client,
aacMedia *description.Media,
aacRTPDec *rtpmpeg4audio.Decoder,
pkt *rtp.Packet,
t string,
) (
int64,
[][]byte,
error) {
// Decode timestamp.
pts, ok := c.PacketPTS2(aacMedia, pkt)
if !ok {
return 0, nil, fmt.Errorf("[%v]: waiting for timestamp\n", t)
}
// Extract access unit from RTP packets.
aus, err := aacRTPDec.Decode(pkt)
if err != nil {
return 0, nil, fmt.Errorf("[%v]: decoding RTP packet error: %w", t, err)
}
return pts, aus, nil
}

View File

@ -0,0 +1,71 @@
package formats
import (
"C"
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/av1"
"github.com/pion/rtp"
"image"
)
// FindAV1Format finds the AV1 media and format.
func FindAV1Format(desc *description.Session) (*format.AV1, *description.Media, error) {
var av1Format *format.AV1
av1Media := desc.FindFormat(&av1Format)
if av1Media == nil {
return nil, nil, errors.New("media AV1 not found")
}
return av1Format, av1Media, nil
}
// ProcessAV1 processes AV1 flow and returns PTS and IMG.
func ProcessAV1(
c *gortsplib.Client,
av1Media *description.Media,
av1RTPDec *rtpav1.Decoder,
pkt *rtp.Packet,
av1Dec *AV1Decoder,
firstRandomReceived bool,
t string,
) (
int64,
*image.RGBA,
error) {
// Decode timestamp.
pts, ok := c.PacketPTS2(av1Media, pkt)
if !ok {
return 0, nil, fmt.Errorf("[%v]: waiting for timestamp\n", t)
}
// Extract AV1 temporal units from RTP packets.
tu, err := av1RTPDec.Decode(pkt)
if err != rtpav1.ErrNonStartingPacketAndNoPrevious && err != rtpav1.ErrMorePacketsNeeded {
return 0, nil, fmt.Errorf("[%v]: decoding RTP packet error: %w", t, err)
}
// Wait for a random access unit.
IsRandomAccess2, _ := av1.IsRandomAccess(tu)
if !firstRandomReceived && !IsRandomAccess2 {
return 0, nil, fmt.Errorf("[%v]: waiting for a random access unit\n", t)
}
firstRandomReceived = true
// Convert AV1 temporal units into RGBA frames.
img, err := av1Dec.Decode(tu)
if err != nil {
return 0, nil, fmt.Errorf("[%v]: convert into RGBA frames error\n", t)
}
// Wait for a frame.
if img == nil {
return 0, nil, fmt.Errorf("[%v]: frame not found\n", t)
}
return pts, img, nil
}

View File

@ -0,0 +1,161 @@
package formats
import (
"fmt"
"image"
"runtime"
"unsafe"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/av1"
)
// #cgo pkg-config: libavcodec libavutil libswscale
// #include <libavcodec/avcodec.h>
// #include <libavutil/imgutils.h>
// #include <libswscale/swscale.h>
import "C"
func frameDataAV1(frame *C.AVFrame) **C.uint8_t {
return (**C.uint8_t)(unsafe.Pointer(&frame.data[0]))
}
func frameLineSizeAV1(frame *C.AVFrame) *C.int {
return (*C.int)(unsafe.Pointer(&frame.linesize[0]))
}
// AV1Decoder is a wrapper around FFmpeg's AV1 decoder.
type AV1Decoder struct {
codecCtx *C.AVCodecContext
yuv420Frame *C.AVFrame
rgbaFrame *C.AVFrame
rgbaFramePtr []uint8
swsCtx *C.struct_SwsContext
}
// Initialize initializes a AV1Decoder.
func (d *AV1Decoder) Initialize() error {
codec := C.avcodec_find_decoder(C.AV_CODEC_ID_AV1)
if codec == nil {
return fmt.Errorf("avcodec_find_decoder() failed")
}
d.codecCtx = C.avcodec_alloc_context3(codec)
if d.codecCtx == nil {
return fmt.Errorf("avcodec_alloc_context3() failed")
}
res := C.avcodec_open2(d.codecCtx, codec, nil)
if res < 0 {
C.avcodec_close(d.codecCtx)
return fmt.Errorf("avcodec_open2() failed")
}
d.yuv420Frame = C.av_frame_alloc()
if d.yuv420Frame == nil {
C.avcodec_close(d.codecCtx)
return fmt.Errorf("av_frame_alloc() failed")
}
return nil
}
// Close closes the decoder.
func (d *AV1Decoder) Close() {
if d.swsCtx != nil {
C.sws_freeContext(d.swsCtx)
}
if d.rgbaFrame != nil {
C.av_frame_free(&d.rgbaFrame)
}
C.av_frame_free(&d.yuv420Frame)
C.avcodec_close(d.codecCtx)
}
func (d *AV1Decoder) reinitDynamicStuff() error {
if d.swsCtx != nil {
C.sws_freeContext(d.swsCtx)
}
if d.rgbaFrame != nil {
C.av_frame_free(&d.rgbaFrame)
}
d.rgbaFrame = C.av_frame_alloc()
if d.rgbaFrame == nil {
return fmt.Errorf("av_frame_alloc() failed")
}
d.rgbaFrame.format = C.AV_PIX_FMT_RGBA
d.rgbaFrame.width = d.yuv420Frame.width
d.rgbaFrame.height = d.yuv420Frame.height
d.rgbaFrame.color_range = C.AVCOL_RANGE_JPEG
res := C.av_frame_get_buffer(d.rgbaFrame, 1)
if res < 0 {
return fmt.Errorf("av_frame_get_buffer() failed")
}
d.swsCtx = C.sws_getContext(d.yuv420Frame.width, d.yuv420Frame.height, int32(d.yuv420Frame.format),
d.rgbaFrame.width, d.rgbaFrame.height, (int32)(d.rgbaFrame.format), C.SWS_BILINEAR, nil, nil, nil)
if d.swsCtx == nil {
return fmt.Errorf("sws_getContext() failed")
}
rgbaFrameSize := C.av_image_get_buffer_size((int32)(d.rgbaFrame.format), d.rgbaFrame.width, d.rgbaFrame.height, 1)
d.rgbaFramePtr = (*[1 << 30]uint8)(unsafe.Pointer(d.rgbaFrame.data[0]))[:rgbaFrameSize:rgbaFrameSize]
return nil
}
// Decode decodes a RGBA image from AV1.
func (d *AV1Decoder) Decode(tu [][]byte) (*image.RGBA, error) {
// encode temporal unit into bytestream
bs, err := av1.Bitstream(tu).Marshal()
if err != nil {
return nil, err
}
// send temporal unit to decoder
var pkt C.AVPacket
ptr := &bs[0]
var p runtime.Pinner
p.Pin(ptr)
pkt.data = (*C.uint8_t)(ptr)
pkt.size = (C.int)(len(bs))
res := C.avcodec_send_packet(d.codecCtx, &pkt)
p.Unpin()
if res < 0 {
return nil, nil
}
// receive frame if available
res = C.avcodec_receive_frame(d.codecCtx, d.yuv420Frame)
if res < 0 {
return nil, nil
}
// if frame size has changed, allocate needed objects
if d.rgbaFrame == nil || d.rgbaFrame.width != d.yuv420Frame.width || d.rgbaFrame.height != d.yuv420Frame.height {
err := d.reinitDynamicStuff()
if err != nil {
return nil, err
}
}
// convert color space from YUV420 to RGBA
res = C.sws_scale(d.swsCtx, frameDataAV1(d.yuv420Frame), frameLineSizeAV1(d.yuv420Frame),
0, d.yuv420Frame.height, frameDataAV1(d.rgbaFrame), frameLineSizeAV1(d.rgbaFrame))
if res < 0 {
return nil, fmt.Errorf("sws_scale() failed")
}
// embed frame into an image.RGBA
return &image.RGBA{
Pix: d.rgbaFramePtr,
Stride: 4 * (int)(d.rgbaFrame.width),
Rect: image.Rectangle{
Max: image.Point{(int)(d.rgbaFrame.width), (int)(d.rgbaFrame.height)},
},
}, nil
}

View File

@ -1,4 +1,4 @@
package rtsp package formats
import ( import (
"errors" "errors"
@ -8,11 +8,10 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm" "github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm"
"github.com/pion/rtp" "github.com/pion/rtp"
"log"
) )
// CheckG711Format finds the G711 media and format. // FindG711Format finds the G711 media and format.
func CheckG711Format(desc *description.Session) (*format.G711, *description.Media, error) { func FindG711Format(desc *description.Session) (*format.G711, *description.Media, error) {
var g711Format *format.G711 var g711Format *format.G711
g711Media := desc.FindFormat(&g711Format) g711Media := desc.FindFormat(&g711Format)
if g711Media == nil { if g711Media == nil {
@ -22,20 +21,20 @@ func CheckG711Format(desc *description.Session) (*format.G711, *description.Medi
return g711Format, g711Media, nil return g711Format, g711Media, nil
} }
// ProcG711 processes G711 flow and returns PTS and AU. // ProcessG711 processes G711 flow and returns PTS and AU.
func ProcG711(c *gortsplib.Client, g711Media *description.Media, g711RTPDec *rtplpcm.Decoder, pkt *rtp.Packet) ( func ProcessG711(c *gortsplib.Client, g711Media *description.Media, g711RTPDec *rtplpcm.Decoder, pkt *rtp.Packet, t string,
) (
int64, []byte, error) { int64, []byte, error) {
// Decode timestamp. // Decode timestamp.
pts, ok := c.PacketPTS2(g711Media, pkt) pts, ok := c.PacketPTS2(g711Media, pkt)
if !ok { if !ok {
log.Printf("waiting for timestamp\n") return 0, nil, fmt.Errorf("[%v]: waiting for timestamp\n", t)
return 0, nil, nil
} }
// Extract access unit from RTP packets. // Extract access unit from RTP packets.
au, err := g711RTPDec.Decode(pkt) au, err := g711RTPDec.Decode(pkt)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("decoding G711 RTP packet: %w", err) return 0, nil, fmt.Errorf("[%v]: decoding RTP packet error: %w", t, err)
} }
return pts, au, nil return pts, au, nil

View File

@ -1,4 +1,4 @@
package rtsp package formats
import ( import (
"bytes" "bytes"
@ -7,7 +7,7 @@ import (
"github.com/gen2brain/aac-go" "github.com/gen2brain/aac-go"
) )
// ConvertG711ToAAC converts PCM to AAC. // ConvertG711ToAAC converts G711 to AAC.
func ConvertG711ToAAC(g711Samples []byte, mulaw bool) ([]byte, error) { func ConvertG711ToAAC(g711Samples []byte, mulaw bool) ([]byte, error) {
var pcmSamples []byte var pcmSamples []byte
if mulaw { if mulaw {

View File

@ -1,13 +1,14 @@
package converter package formats
import ( import (
"bufio" "bufio"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"os" "os"
"sync" "sync"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts" "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
) )
func multiplyAndDivide(v, m, d int64) int64 { func multiplyAndDivide(v, m, d int64) int64 {
@ -22,19 +23,18 @@ type MpegtsMuxer struct {
H264Format *format.H264 H264Format *format.H264
Mpeg4AudioFormat *format.MPEG4Audio Mpeg4AudioFormat *format.MPEG4Audio
f *os.File f *os.File
b *bufio.Writer b *bufio.Writer
w *mpegts.Writer w *mpegts.Writer
h264Track *mpegts.Track h264Track *mpegts.Track
// mpeg4AudioTrack *mpegts.Track mpeg4AudioTrack *mpegts.Track
dtsExtractor *h264.DTSExtractor dtsExtractor *h264.DTSExtractor2
mutex sync.Mutex mutex sync.Mutex
} }
// Initialize initializes a MpegtsMuxer. // Initialize initializes a MpegtsMuxer.
func (e *MpegtsMuxer) Initialize() error { func (e *MpegtsMuxer) Initialize() error {
var err error var err error
e.f, err = os.Create(e.FileName) e.f, err = os.Create(e.FileName)
if err != nil { if err != nil {
return err return err
@ -45,27 +45,21 @@ func (e *MpegtsMuxer) Initialize() error {
Codec: &mpegts.CodecH264{}, Codec: &mpegts.CodecH264{},
} }
//e.mpeg4AudioTrack = &mpegts.Track{ e.mpeg4AudioTrack = &mpegts.Track{
// Codec: &mpegts.CodecMPEG4Audio{ Codec: &mpegts.CodecMPEG4Audio{
// // Config: *e.mpeg4AudioFormat.Config, Config: mpeg4audio.Config{},
// }, },
//} }
e.w = mpegts.NewWriter(e.b, []*mpegts.Track{e.h264Track}) // add e.mpeg4AudioTrack e.w = mpegts.NewWriter(e.b, []*mpegts.Track{e.h264Track, e.mpeg4AudioTrack})
return nil return nil
} }
// Close closes all the MpegtsMuxer resources. // Close closes all the MpegtsMuxer resources.
func (e *MpegtsMuxer) Close() { func (e *MpegtsMuxer) Close() {
err := e.b.Flush() e.b.Flush()
if err != nil { e.f.Close()
panic(err)
}
err = e.f.Close()
if err != nil {
panic(err)
}
} }
// WriteH264 writes a H264 access unit into MPEG-TS. // WriteH264 writes a H264 access unit into MPEG-TS.
@ -79,29 +73,27 @@ func (e *MpegtsMuxer) WriteH264(au [][]byte, pts int64) error {
idrPresent := false idrPresent := false
for _, nalu := range au { for _, nalu := range au {
if len(nalu) != 0 { typ := h264.NALUType(nalu[0] & 0x1F)
typ := h264.NALUType(nalu[0] & 0x1F) switch typ {
switch typ { case h264.NALUTypeSPS:
case h264.NALUTypeSPS: e.H264Format.SPS = nalu
e.H264Format.SPS = nalu continue
continue
case h264.NALUTypePPS: case h264.NALUTypePPS:
e.H264Format.PPS = nalu e.H264Format.PPS = nalu
continue continue
case h264.NALUTypeAccessUnitDelimiter: case h264.NALUTypeAccessUnitDelimiter:
continue continue
case h264.NALUTypeIDR: case h264.NALUTypeIDR:
idrPresent = true idrPresent = true
case h264.NALUTypeNonIDR: case h264.NALUTypeNonIDR:
nonIDRPresent = true nonIDRPresent = true
}
filteredAU = append(filteredAU, nalu)
} }
filteredAU = append(filteredAU, nalu)
} }
au = filteredAU au = filteredAU
@ -120,7 +112,7 @@ func (e *MpegtsMuxer) WriteH264(au [][]byte, pts int64) error {
if !idrPresent { if !idrPresent {
return nil return nil
} }
e.dtsExtractor = h264.NewDTSExtractor() e.dtsExtractor = h264.NewDTSExtractor2()
} }
dts, err := e.dtsExtractor.Extract(au, pts) dts, err := e.dtsExtractor.Extract(au, pts)
@ -129,14 +121,13 @@ func (e *MpegtsMuxer) WriteH264(au [][]byte, pts int64) error {
} }
// Encode into MPEG-TS. // Encode into MPEG-TS.
return e.w.WriteH264(e.h264Track, pts, dts, au) return e.w.WriteH2642(e.h264Track, pts, dts, au)
} }
// writeMPEG4Audio writes MPEG-4 audio access units into MPEG-TS. // WriteAAC writes MPEG-4 audio access units into MPEG-TS.
//func (e *MpegtsMuxer) writeMPEG4Audio(aus [][]byte, pts int64) error { func (e *MpegtsMuxer) WriteAAC(aus [][]byte, pts int64) error {
// e.mutex.Lock() e.mutex.Lock()
// defer e.mutex.Unlock() defer e.mutex.Unlock()
//
// return e.w.WriteMPEG4Audio( return e.w.WriteMPEG4Audio(e.mpeg4AudioTrack, multiplyAndDivide(pts, 90000, int64(8000)), aus)
// e.mpeg4AudioTrack, multiplyAndDivide(pts, 90000, int64(e.Mpeg4AudioFormat.ClockRate())), aus) }
//}

View File

@ -0,0 +1,96 @@
package formats
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtph264"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
"github.com/pion/rtp"
"image"
)
// FindH264Format finds the H264 media and format.
func FindH264Format(desc *description.Session) (*format.H264, *description.Media, error) {
var h264Format *format.H264
h264Media := desc.FindFormat(&h264Format)
if h264Media == nil {
return nil, nil, errors.New("media H264 not found")
}
return h264Format, h264Media, nil
}
// ProcessH264 processes H264 flow and returns PTS and AU.
func ProcessH264(
c *gortsplib.Client,
h264Media *description.Media,
h264RTPDec *rtph264.Decoder,
pkt *rtp.Packet,
t string,
) (
int64,
[][]byte,
error) {
// Decode timestamp.
pts, ok := c.PacketPTS2(h264Media, pkt)
if !ok {
return 0, nil, fmt.Errorf("[%v]: waiting for timestamp\n", t)
}
// Extract access unit from RTP packets.
au, err := h264RTPDec.Decode(pkt)
if err != nil {
if err != rtph264.ErrNonStartingPacketAndNoPrevious && err != rtph264.ErrMorePacketsNeeded {
return 0, nil, fmt.Errorf("[%v]: decoding RTP packet error: %w", t, err)
}
}
return pts, au, nil
}
// ProcessH264RGBA processes H264 flow and returns PTS and IMG.
func ProcessH264RGBA(
c *gortsplib.Client,
h264Media *description.Media,
h264RTPDec *rtph264.Decoder,
h264Dec *H264Decoder,
pkt *rtp.Packet,
firstRandomAccess bool,
t string,
) (
int64,
*image.RGBA,
error) {
// Decode timestamp.
pts, ok := c.PacketPTS2(h264Media, pkt)
if !ok {
return 0, nil, fmt.Errorf("[%v]: waiting for timestamp\n", t)
}
// Extract access units from RTP packets.
au, err := h264RTPDec.Decode(pkt)
if err != rtph264.ErrNonStartingPacketAndNoPrevious && err != rtph264.ErrMorePacketsNeeded {
return 0, nil, fmt.Errorf("[%v]: decoding RTP packet error: %w", t, err)
}
// Wait for a random access unit.
if !firstRandomAccess && !h264.IsRandomAccess(au) {
return 0, nil, nil
}
firstRandomAccess = true
// Convert H264 access units into RGBA frames.
img, err := h264Dec.Decode(au)
if err != nil {
return 0, nil, fmt.Errorf("[%v]: convert into RGBA frames error\n", t)
}
// Wait for a frame.
if img == nil {
return 0, nil, fmt.Errorf("[%v]: frame not found\n", t)
}
return pts, img, nil
}

View File

@ -0,0 +1,161 @@
package formats
import (
"fmt"
"image"
"runtime"
"unsafe"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
)
// #cgo pkg-config: libavcodec libavutil libswscale
// #include <libavcodec/avcodec.h>
// #include <libavutil/imgutils.h>
// #include <libswscale/swscale.h>
import "C"
func frameDataH264(frame *C.AVFrame) **C.uint8_t {
return (**C.uint8_t)(unsafe.Pointer(&frame.data[0]))
}
func frameLineSizeH264(frame *C.AVFrame) *C.int {
return (*C.int)(unsafe.Pointer(&frame.linesize[0]))
}
// H264Decoder is a wrapper around FFmpeg's H264 decoder.
type H264Decoder struct {
codecCtx *C.AVCodecContext
yuv420Frame *C.AVFrame
rgbaFrame *C.AVFrame
rgbaFramePtr []uint8
swsCtx *C.struct_SwsContext
}
// Initialize initializes a H264Decoder.
func (d *H264Decoder) Initialize() error {
codec := C.avcodec_find_decoder(C.AV_CODEC_ID_H264)
if codec == nil {
return fmt.Errorf("avcodec_find_decoder() failed")
}
d.codecCtx = C.avcodec_alloc_context3(codec)
if d.codecCtx == nil {
return fmt.Errorf("avcodec_alloc_context3() failed")
}
res := C.avcodec_open2(d.codecCtx, codec, nil)
if res < 0 {
C.avcodec_close(d.codecCtx)
return fmt.Errorf("avcodec_open2() failed")
}
d.yuv420Frame = C.av_frame_alloc()
if d.yuv420Frame == nil {
C.avcodec_close(d.codecCtx)
return fmt.Errorf("av_frame_alloc() failed")
}
return nil
}
// Close closes the decoder.
func (d *H264Decoder) Close() {
if d.swsCtx != nil {
C.sws_freeContext(d.swsCtx)
}
if d.rgbaFrame != nil {
C.av_frame_free(&d.rgbaFrame)
}
C.av_frame_free(&d.yuv420Frame)
C.avcodec_close(d.codecCtx)
}
func (d *H264Decoder) reinitDynamicStuff() error {
if d.swsCtx != nil {
C.sws_freeContext(d.swsCtx)
}
if d.rgbaFrame != nil {
C.av_frame_free(&d.rgbaFrame)
}
d.rgbaFrame = C.av_frame_alloc()
if d.rgbaFrame == nil {
return fmt.Errorf("av_frame_alloc() failed")
}
d.rgbaFrame.format = C.AV_PIX_FMT_RGBA
d.rgbaFrame.width = d.yuv420Frame.width
d.rgbaFrame.height = d.yuv420Frame.height
d.rgbaFrame.color_range = C.AVCOL_RANGE_JPEG
res := C.av_frame_get_buffer(d.rgbaFrame, 1)
if res < 0 {
return fmt.Errorf("av_frame_get_buffer() failed")
}
d.swsCtx = C.sws_getContext(d.yuv420Frame.width, d.yuv420Frame.height, int32(d.yuv420Frame.format),
d.rgbaFrame.width, d.rgbaFrame.height, (int32)(d.rgbaFrame.format), C.SWS_BILINEAR, nil, nil, nil)
if d.swsCtx == nil {
return fmt.Errorf("sws_getContext() failed")
}
rgbaFrameSize := C.av_image_get_buffer_size((int32)(d.rgbaFrame.format), d.rgbaFrame.width, d.rgbaFrame.height, 1)
d.rgbaFramePtr = (*[1 << 30]uint8)(unsafe.Pointer(d.rgbaFrame.data[0]))[:rgbaFrameSize:rgbaFrameSize]
return nil
}
// Decode decodes a RGBA image from H264.
func (d *H264Decoder) Decode(au [][]byte) (*image.RGBA, error) {
// Encode access unit into Annex-B.
annexb, err := h264.AnnexB(au).Marshal()
if err != nil {
return nil, err
}
// Send access unit to decoder.
var pkt C.AVPacket
ptr := &annexb[0]
var p runtime.Pinner
p.Pin(ptr)
pkt.data = (*C.uint8_t)(ptr)
pkt.size = (C.int)(len(annexb))
res := C.avcodec_send_packet(d.codecCtx, &pkt)
p.Unpin()
if res < 0 {
return nil, nil
}
// Receive frame if available.
res = C.avcodec_receive_frame(d.codecCtx, d.yuv420Frame)
if res < 0 {
return nil, nil
}
// If frame size has changed, allocate needed objects.
if d.rgbaFrame == nil || d.rgbaFrame.width != d.yuv420Frame.width || d.rgbaFrame.height != d.yuv420Frame.height {
err := d.reinitDynamicStuff()
if err != nil {
return nil, err
}
}
// Convert color space from YUV420 to RGBA.
res = C.sws_scale(d.swsCtx, frameDataH264(d.yuv420Frame), frameLineSizeH264(d.yuv420Frame),
0, d.yuv420Frame.height, frameDataH264(d.rgbaFrame), frameLineSizeH264(d.rgbaFrame))
if res < 0 {
return nil, fmt.Errorf("sws_scale() failed")
}
// Embed frame into an image.RGBA.
return &image.RGBA{
Pix: d.rgbaFramePtr,
Stride: 4 * (int)(d.rgbaFrame.width),
Rect: image.Rectangle{
Max: image.Point{(int)(d.rgbaFrame.width), (int)(d.rgbaFrame.height)},
},
}, nil
}

View File

@ -0,0 +1,69 @@
package formats
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtph265"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
"github.com/pion/rtp"
"image"
)
// FindH265Format finds the H265 media and format.
func FindH265Format(desc *description.Session) (*format.H265, *description.Media, error) {
var h265Format *format.H265
h265Media := desc.FindFormat(&h265Format)
if h265Media == nil {
return nil, nil, errors.New("media H265 not found")
}
return h265Format, h265Media, nil
}
// ProcessH265RGBA processes H265 flow and returns PTS and IMG.
func ProcessH265RGBA(
c *gortsplib.Client,
h265Media *description.Media,
h265RTPDec *rtph265.Decoder,
h265Dec *H265Decoder,
pkt *rtp.Packet,
firstRandomAccess bool,
t string,
) (
int64,
*image.RGBA,
error) {
// Decode timestamp.
pts, ok := c.PacketPTS2(h265Media, pkt)
if !ok {
return 0, nil, fmt.Errorf("[%v]: waiting for timestamp\n", t)
}
// Extract access units from RTP packets.
au, err := h265RTPDec.Decode(pkt)
if err != rtph265.ErrNonStartingPacketAndNoPrevious && err != rtph265.ErrMorePacketsNeeded {
return 0, nil, fmt.Errorf("[%v]: decoding RTP packet error: %w", t, err)
}
// Wait for a random access unit.
if !firstRandomAccess && !h265.IsRandomAccess(au) {
return 0, nil, nil
}
firstRandomAccess = true
// Convert H265 access units into RGBA frames.
img, err := h265Dec.Decode(au)
if err != nil {
return 0, nil, fmt.Errorf("[%v]: convert into RGBA frames error\n", t)
}
// Wait for a frame.
if img == nil {
return 0, nil, fmt.Errorf("[%v]: frame not found\n", t)
}
return pts, img, nil
}

View File

@ -0,0 +1,161 @@
package formats
import (
"fmt"
"image"
"runtime"
"unsafe"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
)
// #cgo pkg-config: libavcodec libavutil libswscale
// #include <libavcodec/avcodec.h>
// #include <libavutil/imgutils.h>
// #include <libswscale/swscale.h>
import "C"
func frameDataH265(frame *C.AVFrame) **C.uint8_t {
return (**C.uint8_t)(unsafe.Pointer(&frame.data[0]))
}
func frameLineSizeH265(frame *C.AVFrame) *C.int {
return (*C.int)(unsafe.Pointer(&frame.linesize[0]))
}
// H265Decoder is a wrapper around FFmpeg's H265 decoder.
type H265Decoder struct {
codecCtx *C.AVCodecContext
yuv420Frame *C.AVFrame
rgbaFrame *C.AVFrame
rgbaFramePtr []uint8
swsCtx *C.struct_SwsContext
}
// Initialize initializes a H265Decoder.
func (d *H265Decoder) Initialize() error {
codec := C.avcodec_find_decoder(C.AV_CODEC_ID_H265)
if codec == nil {
return fmt.Errorf("avcodec_find_decoder() failed")
}
d.codecCtx = C.avcodec_alloc_context3(codec)
if d.codecCtx == nil {
return fmt.Errorf("avcodec_alloc_context3() failed")
}
res := C.avcodec_open2(d.codecCtx, codec, nil)
if res < 0 {
C.avcodec_close(d.codecCtx)
return fmt.Errorf("avcodec_open2() failed")
}
d.yuv420Frame = C.av_frame_alloc()
if d.yuv420Frame == nil {
C.avcodec_close(d.codecCtx)
return fmt.Errorf("av_frame_alloc() failed")
}
return nil
}
// Close closes the decoder.
func (d *H265Decoder) Close() {
if d.swsCtx != nil {
C.sws_freeContext(d.swsCtx)
}
if d.rgbaFrame != nil {
C.av_frame_free(&d.rgbaFrame)
}
C.av_frame_free(&d.yuv420Frame)
C.avcodec_close(d.codecCtx)
}
func (d *H265Decoder) reinitDynamicStuff() error {
if d.swsCtx != nil {
C.sws_freeContext(d.swsCtx)
}
if d.rgbaFrame != nil {
C.av_frame_free(&d.rgbaFrame)
}
d.rgbaFrame = C.av_frame_alloc()
if d.rgbaFrame == nil {
return fmt.Errorf("av_frame_alloc() failed")
}
d.rgbaFrame.format = C.AV_PIX_FMT_RGBA
d.rgbaFrame.width = d.yuv420Frame.width
d.rgbaFrame.height = d.yuv420Frame.height
d.rgbaFrame.color_range = C.AVCOL_RANGE_JPEG
res := C.av_frame_get_buffer(d.rgbaFrame, 1)
if res < 0 {
return fmt.Errorf("av_frame_get_buffer() failed")
}
d.swsCtx = C.sws_getContext(d.yuv420Frame.width, d.yuv420Frame.height, int32(d.yuv420Frame.format),
d.rgbaFrame.width, d.rgbaFrame.height, (int32)(d.rgbaFrame.format), C.SWS_BILINEAR, nil, nil, nil)
if d.swsCtx == nil {
return fmt.Errorf("sws_getContext() failed")
}
rgbaFrameSize := C.av_image_get_buffer_size((int32)(d.rgbaFrame.format), d.rgbaFrame.width, d.rgbaFrame.height, 1)
d.rgbaFramePtr = (*[1 << 30]uint8)(unsafe.Pointer(d.rgbaFrame.data[0]))[:rgbaFrameSize:rgbaFrameSize]
return nil
}
// Decode decodes a RGBA image from H265.
func (d *H265Decoder) Decode(au [][]byte) (*image.RGBA, error) {
// encode access unit into Annex-B
annexb, err := h264.AnnexB(au).Marshal()
if err != nil {
return nil, err
}
// send access unit to decoder
var pkt C.AVPacket
ptr := &annexb[0]
var p runtime.Pinner
p.Pin(ptr)
pkt.data = (*C.uint8_t)(ptr)
pkt.size = (C.int)(len(annexb))
res := C.avcodec_send_packet(d.codecCtx, &pkt)
p.Unpin()
if res < 0 {
return nil, nil
}
// receive frame if available
res = C.avcodec_receive_frame(d.codecCtx, d.yuv420Frame)
if res < 0 {
return nil, nil
}
// if frame size has changed, reallocate needed objects
if d.rgbaFrame == nil || d.rgbaFrame.width != d.yuv420Frame.width || d.rgbaFrame.height != d.yuv420Frame.height {
err := d.reinitDynamicStuff()
if err != nil {
return nil, err
}
}
// convert color space from YUV420 to RGBA
res = C.sws_scale(d.swsCtx, frameDataH265(d.yuv420Frame), frameLineSizeH265(d.yuv420Frame),
0, d.yuv420Frame.height, frameDataH265(d.rgbaFrame), frameLineSizeH265(d.rgbaFrame))
if res < 0 {
return nil, fmt.Errorf("sws_scale() failed")
}
// embed frame into an image.RGBA
return &image.RGBA{
Pix: d.rgbaFramePtr,
Stride: 4 * (int)(d.rgbaFrame.width),
Rect: image.Rectangle{
Max: image.Point{(int)(d.rgbaFrame.width), (int)(d.rgbaFrame.height)},
},
}, nil
}

View File

@ -0,0 +1,48 @@
package formats
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm"
"github.com/pion/rtp"
)
// FindLPCMFormat finds the LPCM media and format.
func FindLPCMFormat(desc *description.Session) (*format.LPCM, *description.Media, error) {
var lpcmFormat *format.LPCM
lpcmMedia := desc.FindFormat(&lpcmFormat)
if lpcmMedia == nil {
return nil, nil, errors.New("media LPCM not found")
}
return lpcmFormat, lpcmMedia, nil
}
// ProcessLPCM processes LPCM flow and returns PTS and SAMPLES.
func ProcessLPCM(
c *gortsplib.Client,
lpcmMedia *description.Media,
lpcmRTPDec *rtplpcm.Decoder,
pkt *rtp.Packet,
t string,
) (
int64,
[]byte,
error) {
// Decode timestamp.
pts, ok := c.PacketPTS2(lpcmMedia, pkt)
if !ok {
return 0, nil, fmt.Errorf("[%v]: waiting for timestamp\n", t)
}
// Extract LPCM samples from RTP packets.
samples, err := lpcmRTPDec.Decode(pkt)
if err != nil {
return 0, nil, fmt.Errorf("[%v]: decoding RTP packet error: %w", t, err)
}
return pts, samples, err
}

View File

@ -0,0 +1,57 @@
package formats
import (
"bytes"
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg"
"github.com/pion/rtp"
"image"
"image/jpeg"
)
// FindMJPEGFormat finds the MJPEG media and format.
func FindMJPEGFormat(desc *description.Session) (*format.MJPEG, *description.Media, error) {
var mjpegFormat *format.MJPEG
mjpegMedia := desc.FindFormat(&mjpegFormat)
if mjpegMedia == nil {
return nil, nil, errors.New("media MJPEG not found")
}
return mjpegFormat, mjpegMedia, nil
}
// ProcessMJPEGRGBA processes MJPEG flow and returns PTS and IMG.
func ProcessMJPEGRGBA(
c *gortsplib.Client,
mjpegMedia *description.Media,
mjpegRTPDec *rtpmjpeg.Decoder,
pkt *rtp.Packet,
t string,
) (
int64,
image.Image,
error) {
// Decode timestamp.
pts, ok := c.PacketPTS2(mjpegMedia, pkt)
if !ok {
return 0, nil, fmt.Errorf("[%v]: waiting for timestamp\n", t)
}
// Extract JPEG images from RTP packets.
enc, err := mjpegRTPDec.Decode(pkt)
if err != rtpmjpeg.ErrNonStartingPacketAndNoPrevious && err != rtpmjpeg.ErrMorePacketsNeeded {
return 0, nil, fmt.Errorf("[%v]: decoding RTP packet error: %w", t, err)
}
// Convert JPEG images into RGBA frames.
img, err := jpeg.Decode(bytes.NewReader(enc))
if err != nil {
return 0, nil, fmt.Errorf("[%v]: convert into RGBA frames error\n", t)
}
return pts, img, nil
}

View File

@ -0,0 +1,48 @@
package formats
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio"
"github.com/pion/rtp"
)
// FindOPUSFormat finds the OPUS media and format.
func FindOPUSFormat(desc *description.Session) (*format.Opus, *description.Media, error) {
var opusFormat *format.Opus
opusMedia := desc.FindFormat(&opusFormat)
if opusMedia == nil {
return nil, nil, errors.New("media OPUS not found")
}
return opusFormat, opusMedia, nil
}
// ProcessOPUS processes OPUS flow and returns PTS and OP.
func ProcessOPUS(
c *gortsplib.Client,
opusMedia *description.Media,
opusRTPDec *rtpsimpleaudio.Decoder,
pkt *rtp.Packet,
t string,
) (
int64,
[]byte,
error) {
// Decode timestamp.
pts, ok := c.PacketPTS2(opusMedia, pkt)
if !ok {
return 0, nil, fmt.Errorf("[%v]: waiting for timestamp\n", t)
}
// Extract Opus packets from RTP packets.
op, err := opusRTPDec.Decode(pkt)
if err != nil {
return 0, nil, fmt.Errorf("[%v]: decoding RTP packet error: %w", t, err)
}
return pts, op, nil
}

View File

@ -0,0 +1,153 @@
package formats
import (
"fmt"
"image"
"runtime"
"unsafe"
)
// #cgo pkg-config: libavcodec libavutil libswscale
// #include <libavcodec/avcodec.h>
// #include <libavutil/imgutils.h>
// #include <libswscale/swscale.h>
import "C"
func frameDataVP(frame *C.AVFrame) **C.uint8_t {
return (**C.uint8_t)(unsafe.Pointer(&frame.data[0]))
}
func frameLineSizeVP(frame *C.AVFrame) *C.int {
return (*C.int)(unsafe.Pointer(&frame.linesize[0]))
}
// VP8Decoder is a wrapper around FFmpeg's VP8 decoder.
type VPDecoder struct {
codecCtx *C.AVCodecContext
yuv420Frame *C.AVFrame
rgbaFrame *C.AVFrame
rgbaFramePtr []uint8
swsCtx *C.struct_SwsContext
}
// Initialize initializes a VP8Decoder.
func (d *VPDecoder) Initialize() error {
codec := C.avcodec_find_decoder(C.AV_CODEC_ID_VP8)
if codec == nil {
return fmt.Errorf("avcodec_find_decoder() failed")
}
d.codecCtx = C.avcodec_alloc_context3(codec)
if d.codecCtx == nil {
return fmt.Errorf("avcodec_alloc_context3() failed")
}
res := C.avcodec_open2(d.codecCtx, codec, nil)
if res < 0 {
C.avcodec_close(d.codecCtx)
return fmt.Errorf("avcodec_open2() failed")
}
d.yuv420Frame = C.av_frame_alloc()
if d.yuv420Frame == nil {
C.avcodec_close(d.codecCtx)
return fmt.Errorf("av_frame_alloc() failed")
}
return nil
}
// Close closes the decoder.
func (d *VPDecoder) Close() {
if d.swsCtx != nil {
C.sws_freeContext(d.swsCtx)
}
if d.rgbaFrame != nil {
C.av_frame_free(&d.rgbaFrame)
}
C.av_frame_free(&d.yuv420Frame)
C.avcodec_close(d.codecCtx)
}
func (d *VPDecoder) reinitDynamicStuff() error {
if d.swsCtx != nil {
C.sws_freeContext(d.swsCtx)
}
if d.rgbaFrame != nil {
C.av_frame_free(&d.rgbaFrame)
}
d.rgbaFrame = C.av_frame_alloc()
if d.rgbaFrame == nil {
return fmt.Errorf("av_frame_alloc() failed")
}
d.rgbaFrame.format = C.AV_PIX_FMT_RGBA
d.rgbaFrame.width = d.yuv420Frame.width
d.rgbaFrame.height = d.yuv420Frame.height
d.rgbaFrame.color_range = C.AVCOL_RANGE_JPEG
res := C.av_frame_get_buffer(d.rgbaFrame, 1)
if res < 0 {
return fmt.Errorf("av_frame_get_buffer() failed")
}
d.swsCtx = C.sws_getContext(d.yuv420Frame.width, d.yuv420Frame.height, int32(d.yuv420Frame.format),
d.rgbaFrame.width, d.rgbaFrame.height, (int32)(d.rgbaFrame.format), C.SWS_BILINEAR, nil, nil, nil)
if d.swsCtx == nil {
return fmt.Errorf("sws_getContext() failed")
}
rgbaFrameSize := C.av_image_get_buffer_size((int32)(d.rgbaFrame.format), d.rgbaFrame.width, d.rgbaFrame.height, 1)
d.rgbaFramePtr = (*[1 << 30]uint8)(unsafe.Pointer(d.rgbaFrame.data[0]))[:rgbaFrameSize:rgbaFrameSize]
return nil
}
// Decode decodes a RGBA image from VP8.
func (d *VPDecoder) Decode(au []byte) (*image.RGBA, error) {
// send access unit to decoder
var pkt C.AVPacket
ptr := &au[0]
var p runtime.Pinner
p.Pin(ptr)
pkt.data = (*C.uint8_t)(ptr)
pkt.size = (C.int)(len(au))
res := C.avcodec_send_packet(d.codecCtx, &pkt)
p.Unpin()
if res < 0 {
return nil, nil
}
// receive frame if available
res = C.avcodec_receive_frame(d.codecCtx, d.yuv420Frame)
if res < 0 {
return nil, nil
}
// if frame size has changed, allocate needed objects
if d.rgbaFrame == nil || d.rgbaFrame.width != d.yuv420Frame.width || d.rgbaFrame.height != d.yuv420Frame.height {
err := d.reinitDynamicStuff()
if err != nil {
return nil, err
}
}
// convert color space from YUV420 to RGBA
res = C.sws_scale(d.swsCtx, frameDataVP(d.yuv420Frame), frameLineSizeVP(d.yuv420Frame),
0, d.yuv420Frame.height, frameDataVP(d.rgbaFrame), frameLineSizeVP(d.rgbaFrame))
if res < 0 {
return nil, fmt.Errorf("sws_scale() failed")
}
// embed frame into an image.RGBA
return &image.RGBA{
Pix: d.rgbaFramePtr,
Stride: 4 * (int)(d.rgbaFrame.width),
Rect: image.Rectangle{
Max: image.Point{(int)(d.rgbaFrame.width), (int)(d.rgbaFrame.height)},
},
}, nil
}

View File

@ -0,0 +1,61 @@
package formats
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8"
"github.com/pion/rtp"
"image"
)
// FindVP8Format finds the VP8 media and format.
func FindVP8Format(desc *description.Session) (*format.VP8, *description.Media, error) {
var vp8Format *format.VP8
vp8Media := desc.FindFormat(&vp8Format)
if vp8Media == nil {
return nil, nil, errors.New("media VP8 not found")
}
return vp8Format, vp8Media, nil
}
// ProcessVP8RGBA processes VP8 flow and returns PTS and IMG.
func ProcessVP8RGBA(
c *gortsplib.Client,
vp8Media *description.Media,
vp8RTPDec *rtpvp8.Decoder,
vp8Dec *VPDecoder,
pkt *rtp.Packet,
t string,
) (
int64,
*image.RGBA,
error) {
// Decode timestamp.
pts, ok := c.PacketPTS2(vp8Media, pkt)
if !ok {
return 0, nil, fmt.Errorf("[%v]: waiting for timestamp\n", t)
}
// Extract access units from RTP packets.
au, err := vp8RTPDec.Decode(pkt)
if err != rtpvp8.ErrNonStartingPacketAndNoPrevious && err != rtpvp8.ErrMorePacketsNeeded {
return 0, nil, fmt.Errorf("[%v]: decoding RTP packet error: %w", t, err)
}
// Convert VP8 access units into RGBA frames.
img, err := vp8Dec.Decode(au)
if err != nil {
return 0, nil, fmt.Errorf("[%v]: convert into RGBA frames error\n", t)
}
// Wait for a frame.
if img == nil {
return 0, nil, fmt.Errorf("[%v]: frame not found\n", t)
}
return pts, img, nil
}

View File

@ -0,0 +1,62 @@
package formats
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9"
"github.com/pion/rtp"
"image"
)
// FindVP9Format finds the VP9 media and format.
func FindVP9Format(desc *description.Session) (*format.VP9, *description.Media, error) {
var vp9Format *format.VP9
vp9Media := desc.FindFormat(&vp9Format)
if vp9Media == nil {
return nil, nil, errors.New("media VP9 not found")
}
return vp9Format, vp9Media, nil
}
// ProcessVP9RGBA processes VP9 flow and returns PTS and IMG.
func ProcessVP9RGBA(
c *gortsplib.Client,
vp9Media *description.Media,
vp9RTPDec *rtpvp9.Decoder,
vp9Dec *VPDecoder,
pkt *rtp.Packet,
t string,
) (
int64,
*image.RGBA,
error) {
// Decode timestamp.
pts, ok := c.PacketPTS2(vp9Media, pkt)
if !ok {
return 0, nil, fmt.Errorf("[%v]: waiting for timestamp\n", t)
}
// Extract access units from RTP packets.
au, err := vp9RTPDec.Decode(pkt)
if err != rtpvp8.ErrNonStartingPacketAndNoPrevious && err != rtpvp8.ErrMorePacketsNeeded {
return 0, nil, fmt.Errorf("[%v]: decoding RTP packet error: %w", t, err)
}
// Convert VP8 access units into RGBA frames.
img, err := vp9Dec.Decode(au)
if err != nil {
return 0, nil, fmt.Errorf("[%v]: convert into RGBA frames error\n", t)
}
// Wait for a frame.
if img == nil {
return 0, nil, fmt.Errorf("[%v]: frame not found\n", t)
}
return pts, img, nil
}

View File

@ -1,43 +0,0 @@
package rtsp
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtph264"
"github.com/pion/rtp"
"log"
)
// CheckH264Format finds the H264 media and format.
func CheckH264Format(desc *description.Session) (*format.H264, *description.Media, error) {
var h264Format *format.H264
h264Media := desc.FindFormat(&h264Format)
if h264Media == nil {
return nil, nil, errors.New("media H264 not found")
}
return h264Format, h264Media, nil
}
// ProcH264 processes H264 flow and returns PTS and AU.
func ProcH264(c *gortsplib.Client, h264Media *description.Media, h264RTPDec *rtph264.Decoder, pkt *rtp.Packet) (
int64, [][]byte, error) {
// Decode timestamp.
pts, ok := c.PacketPTS2(h264Media, pkt)
if !ok {
log.Printf("waiting for timestamp\n")
return 0, nil, nil
}
// Extract access unit from RTP packets.
au, err := h264RTPDec.Decode(pkt)
if err != nil {
if err != rtph264.ErrNonStartingPacketAndNoPrevious && err != rtph264.ErrMorePacketsNeeded {
return 0, nil, fmt.Errorf("decoding H264 RTP packet: %w", err)
}
}
return pts, au, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,7 @@ func WaitPeriod(period int) {
} }
func FindResolution(Body []byte) string { func FindResolution(Body []byte) string {
split := strings.Split(string(Body), "\n") split := strings.Split(string(Body), "\r\n")
for _, line := range split { for _, line := range split {
if strings.Contains(line, "a=x-dimensions:") { if strings.Contains(line, "a=x-dimensions:") {
s, _ := strings.CutPrefix(line, "a=x-dimensions:") s, _ := strings.CutPrefix(line, "a=x-dimensions:")