add ffmpeg/libav prober

This commit is contained in:
Leandro Moreira
2024-02-18 11:27:04 -03:00
parent c510b07a65
commit a0dc2b5508
21 changed files with 327 additions and 31 deletions
+29
View File
@@ -0,0 +1,29 @@
# Auto detect text files and perform LF normalization
* text=auto
# Collapse generated and vendored files on GitHub
AUTHORS linguist-generated merge=union
*.gen.* linguist-generated merge=ours
*.pb.go linguist-generated merge=ours
*.pb.gw.go linguist-generated merge=ours
go.sum linguist-generated merge=ours
go.mod linguist-generated
gen.sum linguist-generated merge=ours
vendor/* linguist-vendored
rules.mk linguist-vendored
*/vendor/* linguist-vendored
# doc
doc/* linguist-documentation
doc/Makefile linguist-documentation=false
# Reduce conflicts on markdown files
*.md merge=union
# A set of files you probably don't want in distribution
/.github export-ignore
/.githooks export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.gitmodules export-ignore
/tool/lint export-ignore
+3 -1
View File
@@ -17,6 +17,8 @@ coverage.out
# vendor/
tags
tmp/
.DS_Store
.vscode
.vscode
+26 -7
View File
@@ -1,5 +1,26 @@
# Platform is being enforced here to solve issues like:
# issue /usr/bin/ld: skipping incompatible /usr/local/lib/libavdevice.so when searching for -lavdevice
#
# The tools to check the compiled objects format showed:
# objdump -a /opt/srt_lib/lib/libsrt.so (file format elf64-littleaarch64)
# objdump -a /usr/local/lib/libavformat.so (file format elf64-little)
#
# Once the platform was fixed, the problem disappeared. Even though the configured platform
# is amd64, the final objects are x64, don't know why yet.
#
# FFmpeg/libAV is fixed on version 5.1.2 because go-astiav binding supports it.
# see https://github.com/asticode/go-astiav/issues/27
FROM --platform=linux/amd64 jrottenberg/ffmpeg:5.1.2-ubuntu2004 AS base
FROM golang:1.19
# TODO: copy only required files
COPY --from=base / /
# ffmpeg/libav libraries
ENV LD_LIBRARY_PATH="/usr/local/lib:/usr/lib:/usr/lib/x86_64-linux-gnu/"
ENV CGO_CFLAGS="-I/usr/local/include/"
ENV CGO_LDFLAGS="-L/usr/local/lib"
ENV WD=/usr/src/app
ENV SRT_VERSION="v1.5.3"
ENV SRT_FOLDER="/opt/srt_lib"
@@ -18,14 +39,12 @@ RUN \
make && \
make install
# To find where the srt.h and libsrt.so were you can
# find / -name srt.h
# find / -name libsrt.so
# inside the container docker run -it --rm -t <TAG_YOU_BUILT> bash
ENV GOPROXY=direct
ENV LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${SRT_FOLDER}/lib/"
ENV CGO_CFLAGS="-I${SRT_FOLDER}/include/"
ENV CGO_LDFLAGS="-L${SRT_FOLDER}/lib/"
# DO NOT ALTER THE ORDER OF THE FLAGS HERE, ffmpeg installs srt as well,
# but we want to use the SRT we just built.
ENV LD_LIBRARY_PATH="${SRT_FOLDER}/lib:$LD_LIBRARY_PATH"
ENV CGO_CFLAGS="-I${SRT_FOLDER}/include/ ${CGO_CFLAGS}"
ENV CGO_LDFLAGS="-L${SRT_FOLDER}/lib ${CGO_LDFLAGS}"
COPY . ./donut
WORKDIR ${WD}/donut
+26 -8
View File
@@ -1,5 +1,26 @@
# Platform is being enforced here to solve issues like:
# issue /usr/bin/ld: skipping incompatible /usr/local/lib/libavdevice.so when searching for -lavdevice
#
# The tools to check the compiled objects format showed:
# objdump -a /opt/srt_lib/lib/libsrt.so (file format elf64-littleaarch64)
# objdump -a /usr/local/lib/libavformat.so (file format elf64-little)
#
# Once the platform was fixed, the problem disappeared. Even though the configured platform
# is amd64, the final objects are x64, don't know why yet.
#
# FFmpeg/libAV is fixed on version 5.1.2 because go-astiav binding supports it.
# see https://github.com/asticode/go-astiav/issues/27
FROM --platform=linux/amd64 jrottenberg/ffmpeg:5.1.2-ubuntu2004 AS base
FROM golang:1.19
# TODO: copy only required files
COPY --from=base / /
# ffmpeg/libav libraries
ENV LD_LIBRARY_PATH="/usr/local/lib:/usr/lib:/usr/lib/x86_64-linux-gnu/"
ENV CGO_CFLAGS="-I/usr/local/include/"
ENV CGO_LDFLAGS="-L/usr/local/lib"
ENV WD=/usr/src/app
ENV SRT_VERSION="v1.5.3"
ENV SRT_FOLDER="/opt/srt_lib"
@@ -8,7 +29,6 @@ WORKDIR ${WD}
RUN apt-get clean && apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
tclsh pkg-config cmake libssl-dev build-essential git \
ffmpeg \
&& apt-get clean
RUN \
@@ -19,13 +39,11 @@ RUN \
make && \
make install
# To find where the srt.h and libsrt.so were you can
# find / -name srt.h
# find / -name libsrt.so
# inside the container docker run -it --rm -t <TAG_YOU_BUILT> bash
ENV GOPROXY=direct
ENV LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${SRT_FOLDER}/lib/"
ENV CGO_CFLAGS="-I${SRT_FOLDER}/include/"
ENV CGO_LDFLAGS="-L${SRT_FOLDER}/lib/"
# DO NOT ALTER THE ORDER OF THE FLAGS HERE, ffmpeg installs srt as well,
# but we want to use the SRT we just built.
ENV LD_LIBRARY_PATH="${SRT_FOLDER}/lib:$LD_LIBRARY_PATH"
ENV CGO_CFLAGS="-I${SRT_FOLDER}/include/ ${CGO_CFLAGS}"
ENV CGO_LDFLAGS="-L${SRT_FOLDER}/lib ${CGO_LDFLAGS}"
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2
+32
View File
@@ -51,3 +51,35 @@ You can try to use the [docker-compose](/README.md#run-using-docker-compose), bu
# For MacOS
CGO_LDFLAGS="-L$(brew --prefix srt)/lib -lsrt" CGO_CFLAGS="-I$(brew --prefix srt)/include/" go run main.go
```
## If you're seeing the error "could not determine kind of name for C.AV_CODEC"
Make sure you're using ffmpeg `"n5.1.2"` (via `make install-ffmpeg`), go-astiav@v0.12.0 only supports ffmpeg 5.0.
```
../../go/pkg/mod/github.com/asticode/go-astiav@v0.12.0/codec_context_flag.go:38:50: could not determine kind of name for C.AV_CODEC_FLAG2_DROP_FRAME_TIMECODE
../../go/pkg/mod/github.com/asticode/go-astiav@v0.12.0/codec_context_flag.go:21:51: could not determine kind of name for C.AV_CODEC_FLAG_TRUNCATED
```
## If you're seeing the error "issue /usr/bin/ld: skipping incompatible lib.so when searching for -lavdevice"
Fixing the docker platform fixed the problem. Even though the configured platform is amd64, the final objects are x64, don't know why yet.
```
# The tools to check the compiled objects format:
find / -name libsrt.so # to find the objects
objdump -a /opt/srt_lib/lib/libsrt.so
objdump -a /usr/local/lib/libavformat.so
```
Fixing the platform.
Dockerfile
```Dockerfile
FROM --platform=linux/amd64 jrottenberg/ffmpeg:5.1.2-ubuntu2004 AS base
```
docker-compose.yml
```yaml
platform: "linux/amd64"
```
+6 -2
View File
@@ -7,7 +7,7 @@ test:
docker compose stop test && docker compose down test && docker compose run --rm test
run-srt:
docker compose stop && docker compose down && docker compose build && docker compose up srt
docker compose stop && docker compose down && docker compose build srt && docker compose up srt
mac-run-local:
./scripts/mac_local_run.sh
@@ -21,4 +21,8 @@ html-local-coverage:
lint:
docker compose stop lint && docker compose down lint && docker compose run --rm lint
.PHONY: run
# INCOMPLETE from https://github.com/asticode/go-astiav/blob/master/Makefile
install-ffmpeg:
./scripts/install_local_ffmpeg.sh
.PHONY: run lint test run-srt mac-run-local mac-test-local html-local-coverage install-ffmpeg
+12
View File
@@ -5,6 +5,16 @@ services:
build:
context: .
working_dir: "/app"
# Platform is being enforced here to solve issues like:
# issue /usr/bin/ld: skipping incompatible /usr/local/lib/libavdevice.so when searching for -lavdevice
#
# The tools to check the compiled objects format showed:
# objdump -a /opt/srt_lib/lib/libsrt.so (file format elf64-littleaarch64)
# objdump -a /usr/local/lib/libavformat.so (file format elf64-little)
#
# Once the platform was fixed, the problem disappeared. Even though the configured platform
# is amd64, the final objects are x64, don't know why yet.
platform: "linux/amd64"
volumes:
- "./:/app/"
ports:
@@ -22,6 +32,7 @@ services:
context: .
dockerfile: Dockerfile-dev
working_dir: "/app"
platform: "linux/amd64"
volumes:
- "./:/app/"
command: "go test -v ./..."
@@ -31,6 +42,7 @@ services:
context: .
dockerfile: Dockerfile-dev
working_dir: "/app"
platform: "linux/amd64"
volumes:
- "./:/app/"
command: "golangci-lint run -v"
Generated
+2 -1
View File
@@ -3,6 +3,8 @@ module github.com/flavioribeiro/donut
go 1.19
require (
github.com/asticode/go-astiav v0.12.0
github.com/asticode/go-astikit v0.36.0
github.com/asticode/go-astisrt v0.3.0
github.com/asticode/go-astits v1.11.0
github.com/kelseyhightower/envconfig v1.4.0
@@ -14,7 +16,6 @@ require (
)
require (
github.com/asticode/go-astikit v0.36.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/pion/datachannel v1.5.2 // indirect
Generated
+2
View File
@@ -1,3 +1,5 @@
github.com/asticode/go-astiav v0.12.0 h1:tETfPhVpJrSyh3zvUOmDvebFaCoFpeATSaQAA7B50J8=
github.com/asticode/go-astiav v0.12.0/go.mod h1:phvUnSSlV91S/PELeLkDisYiRLOssxWOsj4oDrqM/54=
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
github.com/asticode/go-astikit v0.36.0 h1:WHSY88YT76D/XRbdp0lMLwfjyUGw8dygnbKKtbGNIG8=
github.com/asticode/go-astikit v0.36.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
@@ -0,0 +1,91 @@
package probers
import (
"fmt"
"github.com/asticode/go-astiav"
"github.com/asticode/go-astikit"
"github.com/flavioribeiro/donut/internal/entities"
"github.com/flavioribeiro/donut/internal/mapper"
"go.uber.org/fx"
"go.uber.org/zap"
)
type LibAVFFmpeg struct {
c *entities.Config
l *zap.SugaredLogger
m *mapper.Mapper
}
type ResultLibAVFFmpeg struct {
fx.Out
LibAVFFmpegProber DonutProber `group:"probers"`
}
// NewLibAVFFmpeg creates a new LibAVFFmpeg DonutProber
func NewLibAVFFmpeg(
c *entities.Config,
l *zap.SugaredLogger,
m *mapper.Mapper,
) ResultLibAVFFmpeg {
return ResultLibAVFFmpeg{
LibAVFFmpegProber: &LibAVFFmpeg{
c: c,
l: l,
m: m,
},
}
}
// Match returns true when the request is for an LibAVFFmpeg prober
func (c *LibAVFFmpeg) Match(req *entities.RequestParams) bool {
if req.SRTHost != "" {
return true
}
return false
}
// StreamInfo connects to the SRT stream and probe N packets to discovery the media properties.
func (c *LibAVFFmpeg) StreamInfo(req *entities.RequestParams) (*entities.StreamInfo, error) {
closer := astikit.NewCloser()
defer closer.Close()
var inputFormatContext *astiav.FormatContext
if inputFormatContext = astiav.AllocFormatContext(); inputFormatContext == nil {
return nil, entities.ErrFFmpegLibAVFormatContextIsNil
}
closer.Add(inputFormatContext.Free)
// TODO: add an UI element for sub-type (format) when input is srt:// (defaulting to mpeg-ts)
userProvidedInputFormat := "mpegts"
// We're assuming that SRT is carrying mpegts.
//
// ffmpeg -hide_banner -protocols # shows all protocols (SRT/RTMP)
// ffmpeg -hide_banner -formats # shows all formats (mpegts/webm/mov)
inputFormat := astiav.FindInputFormat(userProvidedInputFormat)
if inputFormat == nil {
return nil, fmt.Errorf("mpegts: %w", entities.ErrFFmpegLibAVNotFound)
}
inputURL := fmt.Sprintf("srt://%s:%d/%s", req.SRTHost, req.SRTPort, req.SRTStreamID)
if err := inputFormatContext.OpenInput(inputURL, inputFormat, nil); err != nil {
return nil, fmt.Errorf("error while inputFormatContext.OpenInput: %w", err)
}
if err := inputFormatContext.FindStreamInfo(nil); err != nil {
return nil, fmt.Errorf("error while inputFormatContext.FindStreamInfo %w", err)
}
streams := []entities.Stream{}
for _, is := range inputFormatContext.Streams() {
if is.CodecParameters().MediaType() != astiav.MediaTypeAudio &&
is.CodecParameters().MediaType() != astiav.MediaTypeVideo {
continue
}
streams = append(streams, c.m.FromLibAVStreamToEntityStream(is))
}
si := entities.StreamInfo{Streams: streams}
return &si, nil
}
+1 -1
View File
@@ -42,7 +42,7 @@ func NewSrtMpegTs(
// Match returns true when the request is for an SrtMpegTs prober
func (c *SrtMpegTs) Match(req *entities.RequestParams) bool {
if req.SRTHost != "" {
return true
return false
}
return false
}
+1
View File
@@ -80,6 +80,7 @@ type Stream struct {
Codec Codec
Type MediaType
Id uint16
Index uint16
}
type StreamInfo struct {
+14 -1
View File
@@ -1,17 +1,30 @@
package entities
import "errors"
import (
"errors"
"fmt"
)
var ErrHTTPGetOnly = errors.New("you must use http GET verb")
var ErrHTTPPostOnly = errors.New("you must use http POST verb")
var ErrMissingParamsOffer = errors.New("ParamsOffer must not be nil")
var ErrMissingSRTHost = errors.New("SRTHost must not be nil")
var ErrMissingSRTPort = errors.New("SRTPort must be valid")
var ErrMissingSRTStreamID = errors.New("SRTStreamID must not be empty")
var ErrMissingWebRTCSetup = errors.New("WebRTCController.SetupPeerConnection must be called first")
var ErrMissingRemoteOffer = errors.New("nil offer, in order to connect one must pass a valid offer")
var ErrMissingRequestParams = errors.New("RequestParams must not be nil")
var ErrMissingProcess = errors.New("there is no process running")
var ErrMissingProber = errors.New("there is no prober")
var ErrMissingStreamer = errors.New("there is no streamer")
var ErrMissingCompatibleStreams = errors.New("there is no compatible streams")
// FFmpeg/LibAV
var ErrFFMpegLibAV = errors.New("ffmpeg/libav error")
var ErrFFmpegLibAVNotFound = fmt.Errorf("%w input not found", ErrFFMpegLibAV)
var ErrFFmpegLibAVFormatContextIsNil = fmt.Errorf("%w format context is nil", ErrFFMpegLibAV)
var ErrFFmpegLibAVFormatContextOpenInputFailed = fmt.Errorf("%w format context open input has failed", ErrFFMpegLibAV)
var ErrFFmpegLibAVFindStreamInfo = fmt.Errorf("%w could not find stream info", ErrFFMpegLibAV)
+37
View File
@@ -3,6 +3,7 @@ package mapper
import (
"strings"
"github.com/asticode/go-astiav"
"github.com/asticode/go-astits"
"github.com/flavioribeiro/donut/internal/entities"
"github.com/pion/webrtc/v3"
@@ -157,3 +158,39 @@ func (m *Mapper) FromStreamToEntityMessage(st entities.Stream) entities.Message
Message: string(st.Codec),
}
}
func (m *Mapper) FromLibAVStreamToEntityStream(libavStream *astiav.Stream) entities.Stream {
st := entities.Stream{}
if libavStream.CodecParameters().MediaType() == astiav.MediaTypeAudio {
st.Type = entities.AudioType
} else if libavStream.CodecParameters().MediaType() == astiav.MediaTypeVideo {
st.Type = entities.VideoType
} else {
m.l.Info("[[[[TODO: mapper not implemented]]]] for ", libavStream.CodecParameters().MediaType())
st.Type = entities.UnknownType
}
// https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/codec_desc.c#L34
if libavStream.CodecParameters().CodecID().Name() == "h264" {
st.Codec = entities.H264
} else if libavStream.CodecParameters().CodecID().Name() == "h265" {
st.Codec = entities.H265
} else if libavStream.CodecParameters().CodecID().Name() == "hevc" {
st.Codec = entities.H265
} else if libavStream.CodecParameters().CodecID().Name() == "av1" {
st.Codec = entities.AV1
} else if libavStream.CodecParameters().CodecID().Name() == "aac" {
st.Codec = entities.AAC
} else if libavStream.CodecParameters().CodecID().Name() == "opus" {
st.Codec = entities.Opus
} else {
m.l.Info("[[[[TODO: mapper not implemented]]]] for ", libavStream.CodecParameters().CodecID().Name())
st.Codec = entities.UnknownCodec
}
st.Id = uint16(libavStream.ID())
st.Index = uint16(libavStream.Index())
return st
}
+4 -4
View File
@@ -27,8 +27,8 @@ var FFMPEG_LIVE_SRT_MPEG_TS_H264_AAC = testFFmpeg{
-c:a aac -b:a 96k -f mpegts srt://0.0.0.0:` + strconv.Itoa(output_port+0) + `?mode=listener&smoother=live&transtype=live
`,
expectedStreams: []entities.Stream{
entities.Stream{Id: uint16(256), Codec: entities.H264, Type: entities.VideoType},
entities.Stream{Id: uint16(257), Codec: entities.AAC, Type: entities.AudioType},
{Index: 0, Id: uint16(256), Codec: entities.H264, Type: entities.VideoType},
{Index: 1, Id: uint16(257), Codec: entities.AAC, Type: entities.AudioType},
},
output: FFmpegOutput{Host: "127.0.0.1", Port: output_port + 0},
}
@@ -41,8 +41,8 @@ var FFMPEG_LIVE_SRT_MPEG_TS_H265_AAC = testFFmpeg{
-c:a aac -b:a 96k -f mpegts srt://0.0.0.0:` + strconv.Itoa(output_port+1) + `?mode=listener&smoother=live&transtype=live
`,
expectedStreams: []entities.Stream{
entities.Stream{Id: uint16(256), Codec: entities.H265, Type: entities.VideoType},
entities.Stream{Id: uint16(257), Codec: entities.AAC, Type: entities.AudioType},
{Index: 0, Id: uint16(256), Codec: entities.H265, Type: entities.VideoType},
{Index: 1, Id: uint16(257), Codec: entities.AAC, Type: entities.AudioType},
},
output: FFmpegOutput{Host: "127.0.0.1", Port: output_port + 1},
}
+1
View File
@@ -46,6 +46,7 @@ func Dependencies(enableICEMux bool) fx.Option {
fx.Provide(controllers.NewWebRTCAPI),
fx.Provide(streamers.NewSRTMpegTSStreamer),
fx.Provide(probers.NewSrtMpegTs),
fx.Provide(probers.NewLibAVFFmpeg),
fx.Provide(engine.NewDonutEngineController),
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
set -e
PREFIX="/opt/ffmpeg"
# from https://github.com/asticode/go-astiav/blob/master/Makefile
version="n5.1.2"
srcPath="tmp/$(version)/src"
postCheckout=""
rm -rf $(srcPath)
mkdir -p $(srcPath)
git clone --depth 1 --branch $(version) https://github.com/FFmpeg/FFmpeg $(srcPath)
# TODO: install all required libraries (srt, rtmp, aac, x264...) and enable them.
cd $(srcPath) && ./configure --prefix=.. $(configure) \
--disable-htmlpages --disable-doc --disable-txtpages --disable-podpages --disable-manpages \
# --enable-gpl \
# --disable-ffmpeg --disable-ffplay --disable-ffprobe --enable-libopus \
# --enable-libsvtav1 --enable-libfdk-aac --enable-libopus \
# --enable-libfreetype --enable-libsrt --enable-librtmp \
# --enable-libvorbis --enable-libx265 --enable-libx264 --enable-libvpx
cd $(srcPath) && make
cd $(srcPath) && make install
+2 -2
View File
@@ -5,8 +5,8 @@ if ! brew list srt &>/dev/null; then
exit 1
fi
if ! brew list ffmpeg &>/dev/null; then
if ! ls tmp &>/dev/null; then
echo "ERROR you must install ffmpeg"
echo "brew install ffmpeg"
echo "make install-ffmpeg"
exit 1
fi
+2 -2
View File
@@ -1,7 +1,7 @@
#!/bin/bash
source ./scripts/mac_check_deps.sh
export CGO_LDFLAGS="-L$(brew --prefix srt)/lib -lsrt"
export CGO_CFLAGS="-I$(brew --prefix srt)/include/"
# deps
source ./scripts/setup_deps_flags.sh
go run -race main.go
+3 -2
View File
@@ -1,10 +1,11 @@
#!/bin/bash
source ./scripts/mac_check_deps.sh
export CGO_LDFLAGS="-L$(brew --prefix srt)/lib -lsrt"
export CGO_CFLAGS="-I$(brew --prefix srt)/include/"
# deps
source ./scripts/setup_deps_flags.sh
# For debugging:
# go test -v -p 1 ./...
# ref https://github.com/golang/go/issues/46959#issuecomment-1407594935
go test ./...
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# SRT deps
export CGO_LDFLAGS="-L$(brew --prefix srt)/lib"
export CGO_CFLAGS="-I$(brew --prefix srt)/include/"
export PKG_CONFIG_PATH="$(brew --prefix srt)/lib/pkgconfig"
# ffmpeg/libav deps
CGO_LDFLAGS="$CGO_LDFLAGS -L$(pwd)/tmp/n5.1.2/lib/"
CGO_CFLAGS="$CGO_CFLAGS -I$(pwd)/tmp/n5.1.2/include/"
PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$(pwd)/tmp/n5.1.2/lib/pkgconfig"