Compare commits

...

10 Commits

Author SHA1 Message Date
Lukas Herman
5d5001d0b4 Bring back x11 adapter for Linux build only
Original x11 adapter uses ~40% less CPU usage than kbinani/screenshot.
Ideally, we should migrate 100% to kbinani/screenshot. But, the CPU
usage difference is substantial. In the future, we should look deeper
into kbinani/screenshot and try to improve it.
2020-11-23 20:36:17 -05:00
Lukas Herman
c734c53b00 Revert "Create codeql-analysis.yml"
This reverts commit ce032411a7.
2020-11-21 14:38:31 -08:00
Lukas Herman
92ac89a620 Update README.md 2020-11-21 14:11:35 -08:00
Lukas Herman
ce032411a7 Create codeql-analysis.yml 2020-11-21 14:11:00 -08:00
Lukas Herman
3ea7120130 Remove some hardcoded microphone metadata
Changes:
  * Use malgo.DeviceInfo to get the microphone metadata
  * Move static buffering to the driver layer
2020-11-21 14:07:32 -08:00
Lukas Herman
282d0a4fb4 Migrate to kbinani/screenshot for screen adapter
https://github.com/kbinani/screenshot is a library to capture desktop
screen. It is cgo free for Windows, Linux, FreeBSD, OpenBSD, NetBSD, and
Solaris.
2020-11-21 13:40:35 -08:00
Lukas Herman
356eee19ce Remove pkg-config requirement for mmal 2020-11-21 20:57:26 +00:00
Lukas Herman
02d0cd3f44 Prioritize /dev/video0 on Linux camera 2020-11-21 12:44:42 -08:00
Lukas Herman
a8dbecff30 Fix crash in x264 encoder close 2020-11-21 11:42:21 -08:00
Lukas Herman
273457b370 Remove git LFS 2020-11-17 11:41:09 -08:00
10 changed files with 165 additions and 68 deletions

1
.gitattributes vendored
View File

@@ -1 +0,0 @@
*.gif filter=lfs diff=lfs merge=lfs -text

View File

@@ -85,7 +85,7 @@ func main() {
| :--------: | :---: | :-: | :-----: |
| Camera | ✔️ | ✔️ | ✔️ |
| Microphone | ✔️ | ✔️ | ✔️ |
| Screen | ✔️ | | |
| Screen | ✔️ | | |
By default, there's no media input registered. This decision was made to allow you to pay what you need. Therefore, you need to import the associated packages for the media inputs. For example, if you want to use a camera, you need to import the camera package as a side effect:
@@ -144,8 +144,7 @@ A free software library and application for encoding video streams into the H.26
A framework to enable H264 hardware encoding for Raspberry Pi or boards that use VideoCore GPUs.
* Package: [github.com/pion/mediadevices/pkg/codec/mmal](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/mmal)
* Installation:
* Raspbian: `export PKG_CONFIG_PATH=/opt/vc/lib/pkgconfig`
* Installation: no installation needed, mmal should come built in Raspberry Pi devices
#### openh264
A codec library which supports H.264 encoding and decoding. It is suitable for use in real time applications.

6
go.mod
View File

@@ -3,9 +3,13 @@ module github.com/pion/mediadevices
go 1.13
require (
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd // indirect
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539
github.com/gen2brain/malgo v0.10.24
github.com/gen2brain/malgo v0.10.25
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
github.com/lherman-cs/opus v0.0.2
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55 // indirect
github.com/pion/logging v0.2.2
github.com/pion/rtp v1.6.1
github.com/pion/webrtc/v2 v2.2.26

13
go.sum
View File

@@ -1,3 +1,5 @@
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd h1:u7K2oMFMd8APDV3fM1j2rO3U/XJf1g1qC3DDTKou8iM=
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539 h1:1aIqYfg9s9RETAJHGfVKZW4ok0b22p4QTwk8MsdRtPs=
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
@@ -7,8 +9,10 @@ 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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gen2brain/malgo v0.10.24 h1:q9TFP4lRYpK8UbH3XSa/SNnMwMLUZraRyZt2u+qKYxg=
github.com/gen2brain/malgo v0.10.24/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
github.com/gen2brain/malgo v0.10.25 h1:VRiYTBmBeHTCXD0wCg7XyLi6lJJBqND/XVmSEyrGkGc=
github.com/gen2brain/malgo v0.10.25/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrUWtAdksnEs=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
@@ -17,6 +21,8 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f h1:5hWo+DzJQSOBl6X+TDac0SPWffRonuRJ2///OYtYRT8=
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f/go.mod h1:f8GY5V3lRzakvEyr49P7hHRYoHtPr8zvj/7JodCoRzw=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -26,6 +32,8 @@ github.com/lherman-cs/opus v0.0.2 h1:fE9Du3NKXDBztqvoTd6P2y9eJ9vgIHahGK8yQostnhA
github.com/lherman-cs/opus v0.0.2/go.mod h1:v9KQvlDYMuvlwniumBVMlrB0VHQvyTgxNvaXjPmTmps=
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9 h1:tbuodUh2vuhOVZAdW3NEUvosFHUMJwUNl7jk/VSEiwc=
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55 h1:4BxFx5XCtXc+nFtXDGDW+Uu5sPtsAbvPh6RObj3fG9o=
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -111,6 +119,7 @@ golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=

View File

@@ -3,7 +3,8 @@
// Reference: https://github.com/raspberrypi/userland/tree/master/interface/mmal
package mmal
// #cgo pkg-config: mmal
// #cgo CFLAGS: -I/opt/vc/include
// #cgo LDFLAGS: -L/opt/vc/lib -lmmal -lmmal_core -lmmal_util -lmmal_vc_client -lbcm_host -lvcsm -lvcos
// #include "bridge.h"
import "C"
import (

View File

@@ -16,7 +16,7 @@ typedef struct Slice {
typedef struct Encoder {
x264_t *h;
x264_picture_t pic_in, pic_out;
x264_picture_t pic_in;
x264_param_t param;
} Encoder;
@@ -52,15 +52,21 @@ Encoder *enc_new(x264_param_t param, char *preset, int *rc) {
goto fail;
}
if (x264_picture_alloc(&e->pic_in, param.i_csp, param.i_width, param.i_height) < 0) {
x264_picture_t pic_in;
if (x264_picture_alloc(&pic_in, param.i_csp, param.i_width, param.i_height) < 0) {
*rc = ERR_ALLOC_PICTURE;
goto fail;
}
// FIXME: we use x264_picture_alloc to set the metadata only, we don't need the allocated memory
// to store the frame. Since we free the frame memory here, we don't need to call
// x264_picture_clean later.
e->pic_in = pic_in;
x264_picture_clean(&pic_in);
e->h = x264_encoder_open(&e->param);
if (!e->h) {
*rc = ERR_OPEN_ENGINE;
x264_picture_clean(&e->pic_in);
goto fail;
}
@@ -75,24 +81,24 @@ Slice enc_encode(Encoder *e, uint8_t *y, uint8_t *cb, uint8_t *cr, int *rc) {
x264_nal_t *nal;
int i_nal;
x264_picture_t pic_out;
e->pic_in.img.plane[0] = y;
e->pic_in.img.plane[1] = cb;
e->pic_in.img.plane[2] = cr;
int frame_size = x264_encoder_encode(e->h, &nal, &i_nal, &e->pic_in, &e->pic_out);
int frame_size = x264_encoder_encode(e->h, &nal, &i_nal, &e->pic_in, &pic_out);
Slice s = {.data_len = frame_size};
if (frame_size <= 0) {
*rc = ERR_ENCODE;
return s;
}
e->pic_in.i_pts++;
// e->pic_in.i_pts++;
s.data = nal->p_payload;
return s;
}
void enc_close(Encoder *e, int *rc) {
x264_encoder_close(e->h);
x264_picture_clean(&e->pic_in);
free(e);
}

View File

@@ -21,6 +21,7 @@ import (
const (
maxEmptyFrameCount = 5
prioritizedDevice = "video0"
)
var (
@@ -94,9 +95,14 @@ func init() {
discovered[reallink] = struct{}{}
cam := newCamera(device)
priority := driver.PriorityNormal
if label == prioritizedDevice {
priority = driver.PriorityHigh
}
driver.GetManager().Register(cam, driver.Info{
Label: label,
DeviceType: driver.Camera,
Priority: priority,
})
}
}

View File

@@ -37,12 +37,6 @@ type microphone struct {
func init() {
var err error
/*
backends := []malgo.Backend{
malgo.BackendPulseaudio,
malgo.BackendAlsa,
}
*/
ctx, err = malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) {
logger.Debugf("%v\n", message)
})
@@ -56,11 +50,18 @@ func init() {
}
for _, device := range devices {
// TODO: Detect default device and prioritize it
driver.GetManager().Register(newMicrophone(device), driver.Info{
Label: device.ID.String(),
DeviceType: driver.Microphone,
})
info, err := ctx.DeviceInfo(malgo.Capture, device.ID, malgo.Shared)
if err == nil {
priority := driver.PriorityNormal
if info.IsDefault > 0 {
priority = driver.PriorityHigh
}
driver.GetManager().Register(newMicrophone(info), driver.Info{
Label: device.ID.String(),
DeviceType: driver.Microphone,
Priority: priority,
})
}
}
// Decide which endian
@@ -133,7 +134,7 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
return nil, err
}
return audio.ReaderFunc(func() (wave.Audio, func(), error) {
var reader audio.Reader = audio.ReaderFunc(func() (wave.Audio, func(), error) {
chunk, ok := <-m.chunkChan
if !ok {
device.Stop()
@@ -145,7 +146,12 @@ func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
// FIXME: the decoder should also fill this information
decodedChunk.(*wave.Float32Interleaved).Size.SamplingRate = inputProp.SampleRate
return decodedChunk, func() {}, err
}), nil
})
// FIXME: The current audio detection and audio encoder can only work with a static latency. Since the latency from the driver
// can fluctuate, we need to stabilize it. Maybe there's a better way for doing this?
reader = audio.NewBuffer(int(inputProp.Latency.Seconds() * float64(inputProp.SampleRate)))(reader)
return reader, nil
}
func (m *microphone) Properties() []prop.Media {
@@ -159,46 +165,36 @@ func (m *microphone) Properties() []prop.Media {
}
for ch := m.MinChannels; ch <= m.MaxChannels; ch++ {
for sampleRate := m.MinSampleRate; sampleRate <= m.MaxSampleRate; sampleRate += sampleRateStep {
for i := 0; i < int(m.FormatCount); i++ {
format := m.Formats[i]
// FIXME: Currently support 48kHz only. We need to implement a resampler first.
// for sampleRate := m.MinSampleRate; sampleRate <= m.MaxSampleRate; sampleRate += sampleRateStep {
sampleRate := 48000
for i := 0; i < int(m.FormatCount); i++ {
format := m.Formats[i]
supportedProp := prop.Media{
Audio: prop.Audio{
ChannelCount: int(ch),
SampleRate: int(sampleRate),
IsBigEndian: isBigEndian,
// miniaudio only supports interleaved at the moment
IsInterleaved: true,
},
}
switch malgo.FormatType(format) {
case malgo.FormatF32:
supportedProp.SampleSize = 4
supportedProp.IsFloat = true
case malgo.FormatS16:
supportedProp.SampleSize = 2
supportedProp.IsFloat = false
}
supportedProps = append(supportedProps, supportedProp)
supportedProp := prop.Media{
Audio: prop.Audio{
ChannelCount: int(ch),
SampleRate: int(sampleRate),
IsBigEndian: isBigEndian,
// miniaudio only supports interleaved at the moment
IsInterleaved: true,
// FIXME: should change this to a less discrete value
Latency: time.Millisecond * 20,
},
}
}
}
// FIXME: remove this hardcoded value. Malgo doesn't support "ma_context_get_device_info" API yet. The above iterations
// will always return nothing as of now
supportedProps = append(supportedProps, prop.Media{
Audio: prop.Audio{
Latency: time.Millisecond * 20,
ChannelCount: 1,
SampleRate: 48000,
SampleSize: 4,
IsFloat: true,
IsBigEndian: isBigEndian,
IsInterleaved: true,
},
})
switch malgo.FormatType(format) {
case malgo.FormatF32:
supportedProp.SampleSize = 4
supportedProp.IsFloat = true
case malgo.FormatS16:
supportedProp.SampleSize = 2
supportedProp.IsFloat = false
}
supportedProps = append(supportedProps, supportedProp)
}
// }
}
return supportedProps
}

View File

@@ -1 +1,81 @@
// +build !linux
package screen
import (
"fmt"
"image"
"io"
"github.com/kbinani/screenshot"
"github.com/pion/mediadevices/pkg/driver"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
type screen struct {
displayIndex int
doneCh chan struct{}
}
func init() {
activeDisplays := screenshot.NumActiveDisplays()
for i := 0; i < activeDisplays; i++ {
priority := driver.PriorityNormal
if i == 0 {
priority = driver.PriorityHigh
}
s := newScreen(i)
driver.GetManager().Register(s, driver.Info{
Label: fmt.Sprint(i),
DeviceType: driver.Screen,
Priority: priority,
})
}
}
func newScreen(displayIndex int) *screen {
s := screen{
displayIndex: displayIndex,
}
return &s
}
func (s *screen) Open() error {
s.doneCh = make(chan struct{})
return nil
}
func (s *screen) Close() error {
close(s.doneCh)
return nil
}
func (s *screen) VideoRecord(selectedProp prop.Media) (video.Reader, error) {
r := video.ReaderFunc(func() (img image.Image, release func(), err error) {
select {
case <-s.doneCh:
return nil, nil, io.EOF
default:
}
img, err = screenshot.CaptureDisplay(s.displayIndex)
release = func() {}
return
})
return r, nil
}
func (s *screen) Properties() []prop.Media {
resolution := screenshot.GetDisplayBounds(s.displayIndex)
supportedProp := prop.Media{
Video: prop.Video{
Width: resolution.Dx(),
Height: resolution.Dy(),
FrameFormat: frame.FormatRGBA,
},
}
return []prop.Media{supportedProp}
}

View File

@@ -379,9 +379,6 @@ func newAudioTrackFromDriver(d driver.Driver, recorder driver.AudioRecorder, con
return nil, err
}
// FIXME: The current audio detection and audio encoder can only work with a static latency. Since the latency from the driver
// can fluctuate, we need to stabilize it. Maybe there's a better way for doing this?
reader = audio.NewBuffer(int(constraints.selectedMedia.Latency.Seconds() * float64(constraints.selectedMedia.SampleRate)))(reader)
return newAudioTrackFromReader(d, reader, selector), nil
}