driver done
This commit is contained in:
92
drivers/audio/wavfifo.go
Normal file
92
drivers/audio/wavfifo.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package audio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"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 BitsPerByte = 8
|
||||
|
||||
type PCMStreamDriver struct {
|
||||
PCM <-chan []byte
|
||||
BufferSizeByBytes uint16
|
||||
WaveHeader *WavHeader
|
||||
closed <-chan struct{}
|
||||
cancel func()
|
||||
}
|
||||
|
||||
func (w *PCMStreamDriver) Open() error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
w.closed = ctx.Done()
|
||||
w.cancel = cancel
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *PCMStreamDriver) Close() error {
|
||||
w.cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *PCMStreamDriver) 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(w.BufferSizeByBytes),
|
||||
IsFloat: false, // just 8bit or 16bit with qemu
|
||||
IsBigEndian: false, // qemu should be little endian
|
||||
IsInterleaved: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *PCMStreamDriver) AudioRecord(p prop.Media) (audio.Reader, error) {
|
||||
logrus.Debug(p)
|
||||
chunkInfo := wave.ChunkInfo{
|
||||
Len: int(w.BufferSizeByBytes) / int(p.SampleSize/BitsPerByte),
|
||||
Channels: p.ChannelCount,
|
||||
SamplingRate: p.SampleRate,
|
||||
}
|
||||
|
||||
reader := func() (wave.Audio, func(), error) {
|
||||
a := wave.NewInt16Interleaved(chunkInfo)
|
||||
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[:]))
|
||||
}
|
||||
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
|
||||
}
|
124
drivers/audio/wavheader.go
Normal file
124
drivers/audio/wavheader.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package audio
|
||||
|
||||
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(bufferSizeByBytes uint16) time.Duration {
|
||||
bytesPerSample := w.BitsPerSample / BitsPerByte
|
||||
bufferLength := bufferSizeByBytes / bytesPerSample
|
||||
|
||||
return time.Second *
|
||||
time.Duration(bufferLength) /
|
||||
time.Duration(w.NumChannels) /
|
||||
time.Duration(w.SampleRate)
|
||||
}
|
92
drivers/video/ppm.go
Normal file
92
drivers/video/ppm.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package video
|
||||
|
||||
import (
|
||||
"context"
|
||||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/frame"
|
||||
"github.com/pion/mediadevices/pkg/io/video"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
_ "github.com/jbuchbinder/gopnm"
|
||||
)
|
||||
|
||||
const DefaultFPS float32 = 60.0
|
||||
|
||||
type PPMStreamDriver struct {
|
||||
Height, Width int
|
||||
FPS float32
|
||||
PPMImage <-chan io.ReadCloser
|
||||
closed <-chan struct{}
|
||||
cancel func()
|
||||
}
|
||||
|
||||
func (v *PPMStreamDriver) Open() error {
|
||||
defer logrus.Debug("device opened")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
v.closed = ctx.Done()
|
||||
v.cancel = cancel
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *PPMStreamDriver) Close() error {
|
||||
v.cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *PPMStreamDriver) Properties() []prop.Media {
|
||||
return []prop.Media{
|
||||
{
|
||||
Video: prop.Video{
|
||||
Width: v.Width,
|
||||
Height: v.Height,
|
||||
FrameRate: v.FPS,
|
||||
FrameFormat: frame.FormatYUYV,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v *PPMStreamDriver) VideoRecord(p prop.Media) (video.Reader, error) {
|
||||
r := video.ReaderFunc(func() (img image.Image, release func(), err error) {
|
||||
canvas := image.NewYCbCr(
|
||||
image.Rect(0, 0, p.Width, p.Height),
|
||||
image.YCbCrSubsampleRatio420,
|
||||
)
|
||||
|
||||
select {
|
||||
case <-v.closed:
|
||||
return nil, func() {}, io.EOF
|
||||
case ppmF, ok := <-v.PPMImage:
|
||||
if !ok {
|
||||
return nil, func() {}, io.ErrClosedPipe
|
||||
}
|
||||
defer ppmF.Close()
|
||||
|
||||
img, _, err := image.Decode(ppmF)
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
|
||||
// draw input image in middle of canvas
|
||||
offsetX := (canvas.Bounds().Dx() - img.Bounds().Dx()) / 2
|
||||
offsetY := (canvas.Bounds().Dy() - img.Bounds().Dy()) / 2
|
||||
for x := 0; x < img.Bounds().Dx(); x++ {
|
||||
for y := 0; y < img.Bounds().Dy(); y++ {
|
||||
r, g, b, _ := img.At(x, y).RGBA()
|
||||
Y, Cb, Cr := color.RGBToYCbCr(uint8(r), uint8(g), uint8(b))
|
||||
|
||||
canvas.Y[canvas.YOffset(offsetX+x, offsetY+y)] = Y
|
||||
canvas.Cb[canvas.COffset(offsetX+x, offsetY+y)] = Cb
|
||||
canvas.Cr[canvas.COffset(offsetX+x, offsetY+y)] = Cr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return canvas, func() {}, nil
|
||||
})
|
||||
return r, nil
|
||||
}
|
Reference in New Issue
Block a user