From 54c8ca0112dc8ecdef587f2e62ec23ccfbf958f7 Mon Sep 17 00:00:00 2001 From: Alex X Date: Tue, 21 May 2024 17:48:22 +0300 Subject: [PATCH] Add wav format to magic producer --- pkg/magic/producer.go | 4 ++ pkg/wav/wav.go | 127 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 pkg/wav/wav.go diff --git a/pkg/magic/producer.go b/pkg/magic/producer.go index 5728da33e..c49fe8bfe 100644 --- a/pkg/magic/producer.go +++ b/pkg/magic/producer.go @@ -14,6 +14,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/magic/mjpeg" "github.com/AlexxIT/go2rtc/pkg/mpegts" "github.com/AlexxIT/go2rtc/pkg/multipart" + "github.com/AlexxIT/go2rtc/pkg/wav" ) func Open(r io.Reader) (core.Producer, error) { @@ -28,6 +29,9 @@ func Open(r io.Reader) (core.Producer, error) { case string(b) == annexb.StartCode: return bitstream.Open(rd) + case string(b) == wav.FourCC: + return wav.Open(rd) + case bytes.HasPrefix(b, []byte{0xFF, 0xD8}): return mjpeg.Open(rd) diff --git a/pkg/wav/wav.go b/pkg/wav/wav.go new file mode 100644 index 000000000..5f572bd64 --- /dev/null +++ b/pkg/wav/wav.go @@ -0,0 +1,127 @@ +package wav + +import ( + "bufio" + "encoding/binary" + "errors" + "io" + + "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/pion/rtp" +) + +const FourCC = "RIFF" + +func Open(r io.Reader) (*Producer, error) { + // https://en.wikipedia.org/wiki/WAV + // https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html + rd := bufio.NewReaderSize(r, core.BufferSize) + + // skip Master RIFF chunk + if _, err := rd.Discard(12); err != nil { + return nil, err + } + + codec := &core.Codec{} + + for { + chunkID, data, err := readChunk(rd) + if err != nil { + return nil, err + } + + if chunkID == "data" { + break + } + + if chunkID == "fmt " { + // https://audiocoding.cc/articles/2008-05-22-wav-file-structure/wav_formats.txt + switch data[0] { + case 1: + codec.Name = core.CodecPCML + case 6: + codec.Name = core.CodecPCMA + case 7: + codec.Name = core.CodecPCMU + } + + codec.Channels = uint16(data[2]) + codec.ClockRate = binary.LittleEndian.Uint32(data[4:]) + } + } + + if codec.Name == "" { + return nil, errors.New("waw: unsupported codec") + } + + prod := &Producer{rd: rd, cl: r.(io.Closer)} + prod.Type = "WAV producer" + prod.Medias = []*core.Media{ + { + Kind: core.KindAudio, + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{codec}, + }, + } + return prod, nil +} + +type Producer struct { + core.SuperProducer + rd *bufio.Reader + cl io.Closer +} + +func (c *Producer) Start() error { + var seq uint16 + var ts uint32 + + const PacketSize = 0.040 * 8000 // 40ms + + for { + payload := make([]byte, PacketSize) + if _, err := io.ReadFull(c.rd, payload); err != nil { + return err + } + + c.Recv += PacketSize + + if len(c.Receivers) == 0 { + continue + } + + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: 2, + Marker: true, + SequenceNumber: seq, + Timestamp: ts, + }, + Payload: payload, + } + c.Receivers[0].WriteRTP(pkt) + + seq++ + ts += PacketSize + } +} + +func (c *Producer) Stop() error { + _ = c.SuperProducer.Close() + return c.cl.Close() +} + +func readChunk(r io.Reader) (chunkID string, data []byte, err error) { + b := make([]byte, 8) + if _, err = io.ReadFull(r, b); err != nil { + return + } + + if chunkID = string(b[:4]); chunkID != "data" { + size := binary.LittleEndian.Uint32(b[4:]) + data = make([]byte, size) + _, err = io.ReadFull(r, data) + } + + return +}