driver done

This commit is contained in:
TonyChyi 2022-09-28 14:14:26 +08:00
parent 64584cde86
commit 095b52fb5d
5 changed files with 311 additions and 18 deletions

92
drivers/audio/wavfifo.go Normal file
View 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
View 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
View 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
}

2
go.mod
View File

@ -3,6 +3,7 @@ module git.sense-t.eu.org/ACE/ace
go 1.19
require (
github.com/jbuchbinder/gopnm v0.0.0-20220507095634-e31f54490ce0
github.com/pion/mediadevices v0.3.11
github.com/urfave/cli/v2 v2.11.2
)
@ -50,7 +51,6 @@ require (
require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/digitalocean/go-qemu v0.0.0-20220804221245-2002801203aa
github.com/gin-contrib/static v0.0.1
github.com/gin-gonic/gin v1.8.1
github.com/pion/webrtc/v3 v3.1.43
github.com/russross/blackfriday/v2 v2.1.0 // indirect

19
go.sum
View File

@ -16,27 +16,20 @@ github.com/gen2brain/malgo v0.10.35/go.mod h1:zHSUNZAXfCeNsZou0RtQ6Zk7gDYLIcKOrU
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@ -54,8 +47,9 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jbuchbinder/gopnm v0.0.0-20220507095634-e31f54490ce0 h1:9GwwkVzUn1vRWAQ8GRu7UOaoM+FZGnvw88DsjyiqfXc=
github.com/jbuchbinder/gopnm v0.0.0-20220507095634-e31f54490ce0/go.mod h1:6U0E76+sB1jTuSSXJjePtLd44vExeoYThOWgOoXo3x8=
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4=
@ -67,17 +61,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@ -147,15 +138,12 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/urfave/cli/v2 v2.11.2 h1:FVfNg4m3vbjbBpLYxW//WjxUoHvJ9TlppXcqY9Q9ZfA=
@ -204,7 +192,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -225,7 +212,6 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
@ -259,7 +245,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=