This commit is contained in:
Sense T 2022-10-02 11:29:57 +00:00
parent b945218c85
commit 708732a37b
8 changed files with 76 additions and 281 deletions

View File

@ -47,7 +47,7 @@ func NewConfig() *Config {
} }
func genconf(c *cli.Context) error { func genconf(c *cli.Context) error {
f, err := os.OpenFile(c.String("config"), os.O_CREATE|os.O_WRONLY, 0644) f, err := os.OpenFile(c.String("config"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil { if err != nil {
return err return err
} }

View File

@ -11,7 +11,7 @@ import (
// Skip riff header and `fmt ` just 16 bytes // Skip riff header and `fmt ` just 16 bytes
const ( const (
FmtHeaderOffset = 0x0c FmtHeaderOffset = 0x0c // 12
FmtHeaderIDSize = 4 FmtHeaderIDSize = 4
FmtHeaderChunkSizeSize = 4 FmtHeaderChunkSizeSize = 4
FmtHeaderSizeDefault = 16 FmtHeaderSizeDefault = 16
@ -49,8 +49,8 @@ func DefaultHeader() *WavHeader {
func (w *WavHeader) Parse(f io.Reader) error { func (w *WavHeader) Parse(f io.Reader) error {
// skip headers // skip headers
var headers [FmtHeaderOffset]byte var _headers [FmtHeaderOffset]byte
if _, err := f.Read(headers[:]); err != nil { if _, err := f.Read(_headers[:]); err != nil {
return err return err
} }
@ -58,7 +58,7 @@ func (w *WavHeader) Parse(f io.Reader) error {
if _, err := f.Read(id[:]); err != nil { if _, err := f.Read(id[:]); err != nil {
return err return err
} }
if bytes.Equal(id[:], []byte{'f', 'm', 't', '0'}[:]) { if bytes.Equal(id[:], []byte("fmt ")) {
return errors.New("bad header") return errors.New("bad header")
} }
w.ID = id w.ID = id

View File

@ -1,98 +0,0 @@
package audiodriver
import (
"context"
"encoding/binary"
"io"
"time"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/mediadevices/pkg/wave"
"github.com/sirupsen/logrus"
)
const (
DataChunkIDSize = 4
DataChunkSizeSize = 4
)
// BufferSize for pcm bytes
const BufferSize = 512
const BitsPerByte = 8
type WavFIFODriver struct {
PCM <-chan [BufferSize]byte
WaveHeader *WavHeader
closed <-chan struct{}
cancel func()
}
func New() *WavFIFODriver {
return &WavFIFODriver{}
}
func (w *WavFIFODriver) Open() error {
defer logrus.Debug("device opened")
ctx, cancel := context.WithCancel(context.Background())
w.closed = ctx.Done()
w.cancel = cancel
return nil
}
func (w *WavFIFODriver) Close() error {
w.cancel()
return nil
}
func (w *WavFIFODriver) Properties() []prop.Media {
logrus.Debugf("wave header: %v", w.WaveHeader)
return []prop.Media{
{
Audio: prop.Audio{
SampleRate: int(w.WaveHeader.SampleRate),
ChannelCount: int(w.WaveHeader.NumChannels),
SampleSize: int(w.WaveHeader.BitsPerSample),
Latency: w.WaveHeader.GetLatnecy(),
IsFloat: false, // just 8bit or 16bit with qemu
IsBigEndian: false, // qemu should be little endian
IsInterleaved: true,
},
},
}
}
func (w *WavFIFODriver) AudioRecord(p prop.Media) (audio.Reader, error) {
logrus.Debug(p)
reader := func() (wave.Audio, func(), error) {
a := wave.NewInt16Interleaved(wave.ChunkInfo{
Len: BufferSize / int(p.SampleSize/BitsPerByte),
Channels: p.ChannelCount,
SamplingRate: p.SampleRate,
})
select {
case <-w.closed:
return nil, func() {}, io.EOF
case pcmData, ok := <-w.PCM:
logrus.Debug("got %d bytes pcm data", len(pcmData))
if !ok {
return nil, func() {}, io.ErrClosedPipe
}
copy(a.Data, bytesTo16BitSamples(pcmData[:]))
case <-time.After(p.Latency):
}
return a, func() {}, nil
}
return audio.ReaderFunc(reader), nil
}
func bytesTo16BitSamples(b []byte) []int16 {
samples := make([]int16, 0)
for i := 0; i < len(b); i += 2 {
sample := binary.LittleEndian.Uint16(b[i : i+1])
samples = append(samples, int16(sample))
}
return samples
}

View File

@ -1,118 +0,0 @@
package audiodriver
import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"io"
"time"
)
// Skip riff header and `fmt ` just 16 bytes
const (
FmtHeaderOffset = 0x0c
FmtHeaderIDSize = 4
FmtHeaderChunkSizeSize = 4
FmtHeaderSizeDefault = 16
)
type WavHeader struct {
ID [4]byte
Size uint32
AudioFormat uint16
NumChannels uint16
SampleRate uint32
ByteRate uint32
BlockAlign uint16
BitsPerSample uint16
}
func NewHeader(f io.Reader) (*WavHeader, error) {
w := &WavHeader{}
if err := w.Parse(f); err != nil {
return nil, err
}
return w, nil
}
func DefaultHeader() *WavHeader {
return &WavHeader{
Size: uint32(FmtHeaderSizeDefault),
AudioFormat: 1,
NumChannels: 2,
SampleRate: 48000, // opus only support 48kHz
BlockAlign: 4,
BitsPerSample: 16,
}
}
func (w *WavHeader) Parse(f io.Reader) error {
// skip headers
var headers [FmtHeaderOffset]byte
if _, err := f.Read(headers[:]); err != nil {
return err
}
var id [4]byte
if _, err := f.Read(id[:]); err != nil {
return err
}
if bytes.Equal(id[:], []byte{'f', 'm', 't', '0'}[:]) {
return errors.New("bad header")
}
w.ID = id
var size [4]byte
if _, err := f.Read(size[:]); err != nil {
return err
}
w.Size = binary.LittleEndian.Uint32(size[:])
var af [2]byte
if _, err := f.Read(af[:]); err != nil {
return err
}
w.AudioFormat = binary.LittleEndian.Uint16(af[:])
var nc [2]byte
if _, err := f.Read(nc[:]); err != nil {
return err
}
w.NumChannels = binary.LittleEndian.Uint16(nc[:])
var sr [4]byte
if _, err := f.Read(sr[:]); err != nil {
return err
}
w.SampleRate = binary.LittleEndian.Uint32(sr[:])
var br [4]byte
if _, err := f.Read(br[:]); err != nil {
return err
}
w.ByteRate = binary.LittleEndian.Uint32(br[:])
var ba [2]byte
if _, err := f.Read(ba[:]); err != nil {
return err
}
w.BlockAlign = binary.LittleEndian.Uint16(ba[:])
var bps [2]byte
if _, err := f.Read(bps[:]); err != nil {
return err
}
w.BitsPerSample = binary.LittleEndian.Uint16(bps[:])
return nil
}
func (w *WavHeader) String() string {
b, _ := json.Marshal(w)
return string(b)
}
func (w *WavHeader) GetLatnecy() time.Duration {
return time.Millisecond * time.Duration(BufferSize) / time.Duration(w.SampleRate) / time.Duration(w.NumChannels)
}

View File

@ -6,7 +6,6 @@ import (
"github.com/pion/mediadevices/pkg/codec/opus" "github.com/pion/mediadevices/pkg/codec/opus"
"github.com/pion/mediadevices/pkg/codec/x264" "github.com/pion/mediadevices/pkg/codec/x264"
"github.com/pion/mediadevices/pkg/driver" "github.com/pion/mediadevices/pkg/driver"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -24,7 +23,7 @@ func New(o *Options) (*Connection, error) {
connection := &Connection{ connection := &Connection{
option: o, option: o,
} }
codecSelector, err := setupCodec(o.Video.BPS) codecSelector, err := setupCodec(o.VideoBPS)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -45,10 +44,7 @@ func New(o *Options) (*Connection, error) {
} }
s, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{ s, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(mtc *mediadevices.MediaTrackConstraints) { Video: func(mtc *mediadevices.MediaTrackConstraints) {},
mtc.Height = prop.Int(o.Video.Height)
mtc.Width = prop.Int(o.Video.Width)
},
Audio: func(mtc *mediadevices.MediaTrackConstraints) {}, Audio: func(mtc *mediadevices.MediaTrackConstraints) {},
Codec: codecSelector, Codec: codecSelector,
}) })

View File

@ -2,11 +2,7 @@ package webrtcconnection
type Options struct { type Options struct {
STUNServers []string `yaml:"stun_servers"` STUNServers []string `yaml:"stun_servers"`
Video struct { VideoBPS int `yaml:"video_bps"`
Height int `yaml:"height"`
Width int `yaml:"width"`
BPS int `yaml:"bps"`
} `yaml:"video"`
} }
func ExampleOptions() *Options { func ExampleOptions() *Options {
@ -15,9 +11,7 @@ func ExampleOptions() *Options {
"stun:stun.l.google.com:19302", "stun:stun.l.google.com:19302",
"stun:wetofu.me:3478", "stun:wetofu.me:3478",
}, },
VideoBPS: 500_000,
} }
options.Video.BPS = 500_000
options.Video.Height = 768
options.Video.Width = 1024
return options return options
} }

View File

@ -1,8 +1,10 @@
package qemuserver package qemuserver
import ( import (
"fmt"
"net/url" "net/url"
"os" "os"
"path"
"syscall" "syscall"
"time" "time"
) )
@ -11,26 +13,46 @@ type Options struct {
QmpAddress string `yaml:"address"` QmpAddress string `yaml:"address"`
Timeout time.Duration `yaml:"timeout"` Timeout time.Duration `yaml:"timeout"`
Name string `yaml:"name"` Name string `yaml:"name"`
VNCAddress string `yaml:"vnc"` Audio AudioOptions `yaml:"audio_options"`
AudioPipe string `yaml:"audio_pipe"` Video VideoOptions `yaml:"video_options"`
AudioDevice string `yaml:"audio_device"` }
type AudioOptions struct {
Device string `yaml:"device"`
BufferSize uint16 `yaml:"buffer_size"` // in bytes
}
type VideoOptions struct {
Height int `yaml:"height"`
Width int `yaml:"width"`
FPS float32 `yaml:"fps"`
} }
func ExampleOptions() *Options { func ExampleOptions() *Options {
return &Options{ return &Options{
QmpAddress: (&url.URL{ QmpAddress: (&url.URL{
Scheme: "unix", Scheme: "tcp",
Path: "/tmp/qemu.sock", Host: "localhost:4444",
}).String(), }).String(),
Timeout: time.Duration(60 * time.Second), Timeout: time.Duration(30 * time.Second),
Name: "ace-qemu", Name: "ace-qemu",
VNCAddress: "localhost:5900", Audio: AudioOptions{
AudioPipe: "/tmp/audio", Device: "snd0",
AudioDevice: "snd0", BufferSize: 2048,
},
Video: VideoOptions{
Height: 1024,
Width: 768,
FPS: 60,
},
} }
} }
func (o *Options) MakeFIFO() error { func (o *Options) MakeFIFO() (string, error) {
os.Remove(o.AudioPipe) path := path.Join(
return syscall.Mkfifo(o.AudioPipe, 0600) os.TempDir(),
fmt.Sprintf("%s-%s-audio", o.Name, o.Audio.Device),
)
os.Remove(path)
return path, syscall.Mkfifo(path, 0600)
} }

View File

@ -9,7 +9,6 @@ import (
"git.sense-t.eu.org/ACE/ace/drivers/audio" "git.sense-t.eu.org/ACE/ace/drivers/audio"
"git.sense-t.eu.org/ACE/ace/drivers/video" "git.sense-t.eu.org/ACE/ace/drivers/video"
"git.sense-t.eu.org/ACE/ace/lib/audiodriver"
"git.sense-t.eu.org/ACE/ace/lib/qemuconnection" "git.sense-t.eu.org/ACE/ace/lib/qemuconnection"
"github.com/digitalocean/go-qemu/qemu" "github.com/digitalocean/go-qemu/qemu"
"github.com/digitalocean/go-qemu/qmp" "github.com/digitalocean/go-qemu/qmp"
@ -36,15 +35,11 @@ type Server struct {
var DefaultServer *Server var DefaultServer *Server
func NewServer(o *Options) (*Server, error) { func NewServer(o *Options) (*Server, error) {
if err := o.MakeFIFO(); err != nil {
return nil, err
}
server := &Server{ server := &Server{
options: o, options: o,
audioHeader: make(chan *audio.WavHeader, 1), audioHeader: make(chan *audio.WavHeader, 1),
pcm: make(chan []byte), pcm: make(chan []byte),
ppm: make(chan io.ReadCloser), ppm: make(chan io.ReadCloser, 60), // to be configured
} }
u, err := url.Parse(o.QmpAddress) u, err := url.Parse(o.QmpAddress)
@ -77,7 +72,7 @@ func NewServer(o *Options) (*Server, error) {
audio := &audio.PCMStreamDriver{ audio := &audio.PCMStreamDriver{
PCM: server.pcm, PCM: server.pcm,
WaveHeader: waveHeader, WaveHeader: waveHeader,
BufferSizeByBytes: 2048, // to be configured BufferSizeByBytes: o.Audio.BufferSize, // to be configured
} }
if err := driver.GetManager().Register( if err := driver.GetManager().Register(
audio, audio,
@ -91,9 +86,9 @@ func NewServer(o *Options) (*Server, error) {
} }
video := &video.PPMStreamDriver{ video := &video.PPMStreamDriver{
Height: 768, Height: o.Video.Height,
Width: 1024, Width: o.Video.Width,
FPS: 60, FPS: o.Video.FPS,
PPMImage: server.ppm, PPMImage: server.ppm,
} }
if err := driver.GetManager().Register( if err := driver.GetManager().Register(
@ -112,9 +107,26 @@ func NewServer(o *Options) (*Server, error) {
func (s *Server) Run() error { func (s *Server) Run() error {
logrus.Debug("qemu server running") logrus.Debug("qemu server running")
path, err := s.options.MakeFIFO()
if err != nil {
return err
}
go func() { go func() {
f, err := os.Open(s.options.AudioPipe) logrus.Debug("screen capture start")
defer close(s.ppm)
for range time.Tick(time.Second / time.Duration(s.options.Video.FPS)) { // to be configured
ppm, err := s.qemu.ScreenDump()
if err != nil {
logrus.Error(err)
continue
}
s.ppm <- ppm
}
}()
go func() {
f, err := os.Open(path)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -123,9 +135,9 @@ func (s *Server) Run() error {
logrus.Debug("skip wave headers, to the PCM!") logrus.Debug("skip wave headers, to the PCM!")
// skip to pcm data, for 44 bytes. // skip to pcm data, for 44 bytes.
var _dataChunkHeader [audiodriver.FmtHeaderOffset + var _dataChunkHeader [audio.FmtHeaderOffset +
audiodriver.FmtHeaderIDSize + audiodriver.FmtHeaderChunkSizeSize + waveHeaderSize + audio.FmtHeaderIDSize + audio.FmtHeaderChunkSizeSize + waveHeaderSize +
audiodriver.DataChunkIDSize + audiodriver.DataChunkSizeSize]byte audio.DataChunkIDSize + audio.DataChunkSizeSize]byte
if _, err := f.Read(_dataChunkHeader[:]); err != nil { if _, err := f.Read(_dataChunkHeader[:]); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -139,7 +151,7 @@ func (s *Server) Run() error {
} }
select { select {
case s.pcm <- b: case s.pcm <- b:
case <-time.After(waveHeader.GetLatnecy(2048)): case <-time.After(waveHeader.GetLatnecy(s.options.Audio.BufferSize)):
} }
} }
}() }()
@ -151,8 +163,8 @@ func (s *Server) Run() error {
Args: map[string]string{ Args: map[string]string{
"command-line": fmt.Sprintf( "command-line": fmt.Sprintf(
"wavcapture %s %s %d %d %d", "wavcapture %s %s %d %d %d",
s.options.AudioPipe, path,
s.options.AudioDevice, s.options.Audio.Device,
waveHeader.SampleRate, waveHeader.SampleRate,
waveHeader.BitsPerSample, waveHeader.BitsPerSample,
waveHeader.NumChannels, waveHeader.NumChannels,
@ -164,19 +176,6 @@ func (s *Server) Run() error {
logrus.Debug("audio capture set") logrus.Debug("audio capture set")
}() }()
go func() {
logrus.Debug("screen capture start")
defer close(s.ppm)
for range time.Tick(time.Second / 60) { // to be configured
ppm, err := s.qemu.ScreenDump()
if err != nil {
logrus.Error(err)
continue
}
s.ppm <- ppm
}
}()
select {} select {}
} }