132 lines
2.8 KiB
Go
132 lines
2.8 KiB
Go
|
package audiodriver
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"io"
|
||
|
"os"
|
||
|
"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 {
|
||
|
FIFOFile string
|
||
|
f *os.File
|
||
|
waveHeader *WavHeader
|
||
|
closed <-chan struct{}
|
||
|
cancel func()
|
||
|
}
|
||
|
|
||
|
func New(fifoname string) *WavFIFODriver {
|
||
|
return &WavFIFODriver{
|
||
|
FIFOFile: fifoname,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (w *WavFIFODriver) Open() error {
|
||
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
w.closed = ctx.Done()
|
||
|
w.cancel = cancel
|
||
|
|
||
|
f, err := os.Open(w.FIFOFile)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
w.f = f
|
||
|
w.waveHeader = &WavHeader{}
|
||
|
return w.waveHeader.Parse(f)
|
||
|
}
|
||
|
|
||
|
func (w *WavFIFODriver) Close() error {
|
||
|
defer w.cancel()
|
||
|
return w.f.Close()
|
||
|
}
|
||
|
|
||
|
func (w *WavFIFODriver) Properties() []prop.Media {
|
||
|
return []prop.Media{
|
||
|
{
|
||
|
Audio: prop.Audio{
|
||
|
SampleRate: int(w.waveHeader.SampleRate),
|
||
|
ChannelCount: int(w.waveHeader.NumChannels),
|
||
|
Latency: time.Millisecond * time.Duration(BufferSize) / time.Duration(w.waveHeader.SampleRate) / time.Duration(w.waveHeader.NumChannels),
|
||
|
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) {
|
||
|
offset := FmtHeaderOffset + FmtHeaderIDSize + FmtHeaderChunkSizeSize + int64(w.waveHeader.Size) + DataChunkIDSize + DataChunkSizeSize
|
||
|
if _, err := w.f.Seek(
|
||
|
offset,
|
||
|
io.SeekStart,
|
||
|
); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var pcm <-chan [BufferSize]byte
|
||
|
go func() {
|
||
|
samples := make(chan [BufferSize]byte, BufferSize)
|
||
|
defer close(samples)
|
||
|
|
||
|
pcm = samples
|
||
|
for {
|
||
|
var b [BufferSize]byte
|
||
|
if _, err := w.f.Read(b[:]); err != nil {
|
||
|
if errors.Is(err, io.EOF) {
|
||
|
return
|
||
|
}
|
||
|
logrus.Errorf("read audio stream error: %v", err)
|
||
|
continue
|
||
|
}
|
||
|
samples <- b
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
reader := func() (wave.Audio, func(), error) {
|
||
|
select {
|
||
|
case <-w.closed:
|
||
|
return nil, func() {}, io.EOF
|
||
|
case pcmData, ok := <-pcm:
|
||
|
if !ok {
|
||
|
return nil, func() {}, io.ErrClosedPipe
|
||
|
}
|
||
|
|
||
|
a := wave.NewInt16Interleaved(wave.ChunkInfo{
|
||
|
Len: BufferSize / int(w.waveHeader.BitsPerSample/BitsPerByte),
|
||
|
Channels: p.ChannelCount,
|
||
|
SamplingRate: p.SampleRate,
|
||
|
})
|
||
|
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
|
||
|
}
|