mirror of
https://github.com/comma-hacks/webrtc.git
synced 2025-10-04 07:46:24 +08:00
transcoding
This commit is contained in:
98
encoder.go
Normal file
98
encoder.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/asticode/go-astiav"
|
||||||
|
"github.com/asticode/go-astikit"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EncoderStream struct {
|
||||||
|
encCodec *astiav.Codec
|
||||||
|
encCodecContext *astiav.CodecContext
|
||||||
|
encPkt *astiav.Packet
|
||||||
|
}
|
||||||
|
|
||||||
|
type Encoder struct {
|
||||||
|
Stream *EncoderStream
|
||||||
|
Closer *astikit.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
type EncoderParams struct {
|
||||||
|
Height int
|
||||||
|
Width int
|
||||||
|
TimeBase astiav.Rational
|
||||||
|
AspectRatio astiav.Rational
|
||||||
|
PixelFormat astiav.PixelFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEncoder(params EncoderParams) (ts *Encoder, err error) {
|
||||||
|
ts = &Encoder{}
|
||||||
|
s := &EncoderStream{}
|
||||||
|
c := astikit.NewCloser()
|
||||||
|
ts.Closer = c
|
||||||
|
ts.Stream = s
|
||||||
|
|
||||||
|
// Get codec id
|
||||||
|
codecID := astiav.CodecIDMpeg4
|
||||||
|
|
||||||
|
// Find encoder
|
||||||
|
if s.encCodec = astiav.FindEncoder(codecID); s.encCodec == nil {
|
||||||
|
err = errors.New("main: codec is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alloc codec context
|
||||||
|
if s.encCodecContext = astiav.AllocCodecContext(s.encCodec); s.encCodecContext == nil {
|
||||||
|
err = errors.New("main: codec context is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Add(s.encCodecContext.Free)
|
||||||
|
|
||||||
|
s.encCodecContext.SetHeight(params.Height)
|
||||||
|
s.encCodecContext.SetPixelFormat(params.PixelFormat)
|
||||||
|
s.encCodecContext.SetBitRate(1000)
|
||||||
|
s.encCodecContext.SetSampleAspectRatio(params.AspectRatio)
|
||||||
|
s.encCodecContext.SetTimeBase(params.TimeBase)
|
||||||
|
s.encCodecContext.SetWidth(params.Width)
|
||||||
|
|
||||||
|
// Open codec context
|
||||||
|
if err = s.encCodecContext.Open(s.encCodec, nil); err != nil {
|
||||||
|
err = fmt.Errorf("encoder: opening codec context failed: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate packet
|
||||||
|
s.encPkt = astiav.AllocPacket()
|
||||||
|
c.Add(s.encPkt.Free)
|
||||||
|
|
||||||
|
return ts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *Encoder) Close() {
|
||||||
|
ts.Closer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *Encoder) Encode(f *astiav.Frame) (pkt *astiav.Packet, err error) {
|
||||||
|
s := ts.Stream
|
||||||
|
|
||||||
|
// Unref packet
|
||||||
|
s.encPkt.Unref()
|
||||||
|
|
||||||
|
log.Println(len(f.Data()))
|
||||||
|
|
||||||
|
// Send frame
|
||||||
|
if err = s.encCodecContext.SendFrame(f); err != nil {
|
||||||
|
err = fmt.Errorf("encoder: sending frame failed: %w", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.encCodecContext.ReceivePacket(s.encPkt); err != nil {
|
||||||
|
log.Fatal(fmt.Errorf("encoder: receiving frame failed: %w", err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.encPkt, nil
|
||||||
|
}
|
Submodule go-astiav updated: 0f5fd94d5d...f5bf3b5e98
33
main.go
33
main.go
@@ -58,29 +58,52 @@ func ReplaceTrack(prefix string, peerConnection *webrtc.PeerConnection) {
|
|||||||
log.Println(fmt.Errorf("main: peer connection closed due to error: %w", err))
|
log.Println(fmt.Errorf("main: peer connection closed due to error: %w", err))
|
||||||
}
|
}
|
||||||
} else if connectionState.String() == "connected" {
|
} else if connectionState.String() == "connected" {
|
||||||
astiav.SetLogLevel(astiav.LogLevelDebug)
|
astiav.SetLogLevel(astiav.LogLevelError)
|
||||||
astiav.SetLogCallback(func(l astiav.LogLevel, fmt, msg, parent string) {
|
astiav.SetLogCallback(func(l astiav.LogLevel, fmt, msg, parent string) {
|
||||||
log.Printf("ffmpeg log: %s (level: %d)\n", strings.TrimSpace(msg), l)
|
log.Printf("ffmpeg log: %s (level: %d)\n", strings.TrimSpace(msg), l)
|
||||||
})
|
})
|
||||||
go func() {
|
go func() {
|
||||||
|
encoderParams := EncoderParams{
|
||||||
|
Width: visionTrack.Width(),
|
||||||
|
Height: visionTrack.Height(),
|
||||||
|
TimeBase: visionTrack.TimeBase(),
|
||||||
|
AspectRatio: visionTrack.AspectRatio(),
|
||||||
|
PixelFormat: visionTrack.PixelFormat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder, err := NewEncoder(encoderParams)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(fmt.Errorf("main: creating track failed: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer encoder.Close()
|
||||||
|
|
||||||
go visionTrack.Start()
|
go visionTrack.Start()
|
||||||
defer visionTrack.Stop()
|
defer visionTrack.Stop()
|
||||||
|
|
||||||
for visionTrack != nil {
|
for visionTrack != nil {
|
||||||
|
var rtpPacket []byte
|
||||||
for frame := range visionTrack.Frame {
|
for frame := range visionTrack.Frame {
|
||||||
// Do something with decoded frame
|
// Do something with decoded frame
|
||||||
fmt.Println(frame.Roll)
|
fmt.Println(frame.Roll)
|
||||||
|
|
||||||
// so right now frame is a raw decoded AVFrame
|
// so right now frame is a raw decoded AVFrame
|
||||||
// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libavutil/frame.h#L317
|
// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libavutil/frame.h#L317
|
||||||
// avframe := frame.Frame
|
avframe := frame.Frame
|
||||||
|
|
||||||
// we need to:
|
// we need to:
|
||||||
// 1. transcode to h264 with adaptive bitrate using astiav
|
// 1. transcode to h264 with adaptive bitrate using astiav
|
||||||
|
outFrame, err := encoder.Encode(avframe)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(fmt.Errorf("encode error: %w", err))
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// 2. create an RTP packet with h264 data inside using pion's rtp packetizer
|
// 2. create an RTP packet with h264 data inside using pion's rtp packetizer
|
||||||
var rtpPacket []byte
|
fmt.Println(len(outFrame.Data()))
|
||||||
// 3. write the RTP packet to the videoTrack
|
|
||||||
|
|
||||||
|
// 3. write the RTP packet to the videoTrack
|
||||||
if _, err = videoTrack.Write(rtpPacket); err != nil {
|
if _, err = videoTrack.Write(rtpPacket); err != nil {
|
||||||
if errors.Is(err, io.ErrClosedPipe) {
|
if errors.Is(err, io.ErrClosedPipe) {
|
||||||
// The peerConnection has been closed.
|
// The peerConnection has been closed.
|
||||||
|
4
run.sh
4
run.sh
@@ -1,4 +1,4 @@
|
|||||||
export CGO_LDFLAGS="-L$PWD/go-astiav/tmp/n4.4.1/lib/"
|
export CGO_LDFLAGS="-L$PWD/go-astiav/tmp/n4.4.1/lib"
|
||||||
export CGO_CXXFLAGS="-I$PWD/go-astiav/tmp/n4.4.1/include/"
|
export CGO_CXXFLAGS="-I$PWD/go-astiav/tmp/n4.4.1/include"
|
||||||
export PKG_CONFIG_PATH="$PWD/go-astiav/tmp/n4.4.1/lib/pkgconfig"
|
export PKG_CONFIG_PATH="$PWD/go-astiav/tmp/n4.4.1/lib/pkgconfig"
|
||||||
go run .
|
go run .
|
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
const V4L2_BUF_FLAG_KEYFRAME = uint32(8)
|
const V4L2_BUF_FLAG_KEYFRAME = uint32(8)
|
||||||
|
|
||||||
type Stream struct {
|
type VisionIpcTrackDecoderStream struct {
|
||||||
decCodec *astiav.Codec
|
decCodec *astiav.Codec
|
||||||
decCodecContext *astiav.CodecContext
|
decCodecContext *astiav.CodecContext
|
||||||
inputStream *astiav.Stream
|
inputStream *astiav.Stream
|
||||||
@@ -22,7 +22,7 @@ type Stream struct {
|
|||||||
|
|
||||||
type VisionIpcTrack struct {
|
type VisionIpcTrack struct {
|
||||||
name string
|
name string
|
||||||
stream *Stream
|
stream *VisionIpcTrackDecoderStream
|
||||||
context *zmq.Context
|
context *zmq.Context
|
||||||
subscriber *zmq.Socket
|
subscriber *zmq.Socket
|
||||||
lastIdx int64
|
lastIdx int64
|
||||||
@@ -64,7 +64,7 @@ func NewVisionIpcTrack(name string) (track *VisionIpcTrack, err error) {
|
|||||||
subscriber.Connect(GetServiceURI(name))
|
subscriber.Connect(GetServiceURI(name))
|
||||||
|
|
||||||
// Create stream
|
// Create stream
|
||||||
s := &Stream{inputStream: nil}
|
s := &VisionIpcTrackDecoderStream{inputStream: nil}
|
||||||
|
|
||||||
// Find decoder
|
// Find decoder
|
||||||
if s.decCodec = astiav.FindDecoder(astiav.CodecIDHevc); s.decCodec == nil {
|
if s.decCodec = astiav.FindDecoder(astiav.CodecIDHevc); s.decCodec == nil {
|
||||||
@@ -76,6 +76,12 @@ func NewVisionIpcTrack(name string) (track *VisionIpcTrack, err error) {
|
|||||||
return nil, errors.New("main: codec context is nil")
|
return nil, errors.New("main: codec context is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.decCodecContext.SetHeight(track.Height())
|
||||||
|
s.decCodecContext.SetPixelFormat(track.PixelFormat())
|
||||||
|
s.decCodecContext.SetSampleAspectRatio(track.AspectRatio())
|
||||||
|
s.decCodecContext.SetTimeBase(track.TimeBase())
|
||||||
|
s.decCodecContext.SetWidth(track.Width())
|
||||||
|
|
||||||
// Open codec context
|
// Open codec context
|
||||||
if err := s.decCodecContext.Open(s.decCodec, nil); err != nil {
|
if err := s.decCodecContext.Open(s.decCodec, nil); err != nil {
|
||||||
log.Fatal(fmt.Errorf("main: opening codec context failed: %w", err))
|
log.Fatal(fmt.Errorf("main: opening codec context failed: %w", err))
|
||||||
@@ -107,6 +113,30 @@ func NewVisionIpcTrack(name string) (track *VisionIpcTrack, err error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *VisionIpcTrack) TimeBase() (t astiav.Rational) {
|
||||||
|
return astiav.NewRational(v.FrameRate(), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VisionIpcTrack) FrameRate() int {
|
||||||
|
return 20
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VisionIpcTrack) Height() int {
|
||||||
|
return 1208
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VisionIpcTrack) Width() int {
|
||||||
|
return 1928
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VisionIpcTrack) AspectRatio() (t astiav.Rational) {
|
||||||
|
return astiav.NewRational(151, 241)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VisionIpcTrack) PixelFormat() (f astiav.PixelFormat) {
|
||||||
|
return astiav.PixelFormatYuv420P
|
||||||
|
}
|
||||||
|
|
||||||
func (v *VisionIpcTrack) Stop() {
|
func (v *VisionIpcTrack) Stop() {
|
||||||
if !Open {
|
if !Open {
|
||||||
return
|
return
|
||||||
|
Reference in New Issue
Block a user