mirror of
https://github.com/comma-hacks/webrtc.git
synced 2025-09-26 20:21:26 +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))
|
||||
}
|
||||
} else if connectionState.String() == "connected" {
|
||||
astiav.SetLogLevel(astiav.LogLevelDebug)
|
||||
astiav.SetLogLevel(astiav.LogLevelError)
|
||||
astiav.SetLogCallback(func(l astiav.LogLevel, fmt, msg, parent string) {
|
||||
log.Printf("ffmpeg log: %s (level: %d)\n", strings.TrimSpace(msg), l)
|
||||
})
|
||||
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()
|
||||
defer visionTrack.Stop()
|
||||
|
||||
for visionTrack != nil {
|
||||
var rtpPacket []byte
|
||||
for frame := range visionTrack.Frame {
|
||||
// Do something with decoded frame
|
||||
fmt.Println(frame.Roll)
|
||||
|
||||
// so right now frame is a raw decoded AVFrame
|
||||
// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libavutil/frame.h#L317
|
||||
// avframe := frame.Frame
|
||||
avframe := frame.Frame
|
||||
|
||||
// we need to:
|
||||
// 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
|
||||
var rtpPacket []byte
|
||||
// 3. write the RTP packet to the videoTrack
|
||||
fmt.Println(len(outFrame.Data()))
|
||||
|
||||
// 3. write the RTP packet to the videoTrack
|
||||
if _, err = videoTrack.Write(rtpPacket); err != nil {
|
||||
if errors.Is(err, io.ErrClosedPipe) {
|
||||
// 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_CXXFLAGS="-I$PWD/go-astiav/tmp/n4.4.1/include/"
|
||||
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 PKG_CONFIG_PATH="$PWD/go-astiav/tmp/n4.4.1/lib/pkgconfig"
|
||||
go run .
|
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
const V4L2_BUF_FLAG_KEYFRAME = uint32(8)
|
||||
|
||||
type Stream struct {
|
||||
type VisionIpcTrackDecoderStream struct {
|
||||
decCodec *astiav.Codec
|
||||
decCodecContext *astiav.CodecContext
|
||||
inputStream *astiav.Stream
|
||||
@@ -22,7 +22,7 @@ type Stream struct {
|
||||
|
||||
type VisionIpcTrack struct {
|
||||
name string
|
||||
stream *Stream
|
||||
stream *VisionIpcTrackDecoderStream
|
||||
context *zmq.Context
|
||||
subscriber *zmq.Socket
|
||||
lastIdx int64
|
||||
@@ -64,7 +64,7 @@ func NewVisionIpcTrack(name string) (track *VisionIpcTrack, err error) {
|
||||
subscriber.Connect(GetServiceURI(name))
|
||||
|
||||
// Create stream
|
||||
s := &Stream{inputStream: nil}
|
||||
s := &VisionIpcTrackDecoderStream{inputStream: nil}
|
||||
|
||||
// Find decoder
|
||||
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")
|
||||
}
|
||||
|
||||
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
|
||||
if err := s.decCodecContext.Open(s.decCodec, nil); err != nil {
|
||||
log.Fatal(fmt.Errorf("main: opening codec context failed: %w", err))
|
||||
@@ -107,6 +113,30 @@ func NewVisionIpcTrack(name string) (track *VisionIpcTrack, err error) {
|
||||
}, 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() {
|
||||
if !Open {
|
||||
return
|
||||
|
Reference in New Issue
Block a user