Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

高ビットレート音声対応 #190

Merged
merged 12 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ jobs:

- run: go fmt .

- name: Patch
run: make patch

- uses: dominikh/[email protected]
with:
version: "2023.1.6"
Expand Down
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

## develop

- [FIX] 高ビットレートの音声データの場合に、解析結果が送られてこない不具合を修正する
- @Hexa

### misc

## 2024.4.0
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ all: patch
go build -o bin/suzu cmd/suzu/main.go

patch:
patch -o oggwriter.go ./pion/oggwriter.go ./patch/oggwriter.go.patch
patch -o oggwriter.go ./_third_party/pion/oggwriter.go ./patch/oggwriter.go.patch
patch -o util.go ./_third_party/pion/util.go ./patch/util.go.patch


test:
Expand Down
26 changes: 20 additions & 6 deletions pion/oggwriter.go → _third_party/pion/oggwriter.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

// Package oggwriter implements OGG media container writer
package oggwriter

Expand All @@ -7,9 +10,9 @@ import (
"io"
"os"

"github.com/pion/randutil"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
"github.com/pion/webrtc/v4/internal/util"
)

const (
Expand Down Expand Up @@ -65,7 +68,7 @@ func NewWith(out io.Writer, sampleRate uint32, channelCount uint16) (*OggWriter,
stream: out,
sampleRate: sampleRate,
channelCount: channelCount,
serial: randutil.NewMathRandomGenerator().Uint32(),
serial: util.RandUint32(),
checksumTable: generateChecksumTable(),

// Timestamp and Granule MUST start from 1
Expand Down Expand Up @@ -146,22 +149,33 @@ const (

func (i *OggWriter) createPage(payload []uint8, headerType uint8, granulePos uint64, pageIndex uint32) []byte {
i.lastPayloadSize = len(payload)
page := make([]byte, pageHeaderSize+1+i.lastPayloadSize)
nSegments := (len(payload) / 255) + 1 // A segment can be at most 255 bytes long.

page := make([]byte, pageHeaderSize+i.lastPayloadSize+nSegments)

copy(page[0:], pageHeaderSignature) // page headers starts with 'OggS'
page[4] = 0 // Version
page[5] = headerType // 1 = continuation, 2 = beginning of stream, 4 = end of stream
binary.LittleEndian.PutUint64(page[6:], granulePos) // granule position
binary.LittleEndian.PutUint32(page[14:], i.serial) // Bitstream serial number
binary.LittleEndian.PutUint32(page[18:], pageIndex) // Page sequence number
page[26] = 1 // Number of segments in page, giving always 1 segment
page[27] = uint8(i.lastPayloadSize) // Segment Table inserting at 27th position since page header length is 27
copy(page[28:], payload) // inserting at 28th since Segment Table(1) + header length(27)
page[26] = uint8(nSegments) // Number of segments in page.

// Filling segment table with the lacing values.
// First (nSegments - 1) values will always be 255.
for i := 0; i < nSegments-1; i++ {
page[pageHeaderSize+i] = 255
}
// The last value will be the remainder.
page[pageHeaderSize+nSegments-1] = uint8(len(payload) % 255)

copy(page[pageHeaderSize+nSegments:], payload) // Payload goes after the segment table, so at pageHeaderSize+nSegments.

var checksum uint32
for index := range page {
checksum = (checksum << 8) ^ i.checksumTable[byte(checksum>>24)^page[index]]
}

binary.LittleEndian.PutUint32(page[22:], checksum) // Checksum - generating for page data and inserting at 22th position into 32 bits

return page
Expand Down
75 changes: 75 additions & 0 deletions _third_party/pion/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

// Package util provides auxiliary functions internally used in webrtc package
package util

import (
"errors"
"strings"

"github.com/pion/randutil"
)

const (
runesAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)

// Use global random generator to properly seed by crypto grade random.
var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals

// MathRandAlpha generates a mathematical random alphabet sequence of the requested length.
func MathRandAlpha(n int) string {
return globalMathRandomGenerator.GenerateString(n, runesAlpha)
}

// RandUint32 generates a mathematical random uint32.
func RandUint32() uint32 {
return globalMathRandomGenerator.Uint32()
}

// FlattenErrs flattens multiple errors into one
func FlattenErrs(errs []error) error {
errs2 := []error{}
for _, e := range errs {
if e != nil {
errs2 = append(errs2, e)
}
}
if len(errs2) == 0 {
return nil
}
return multiError(errs2)
}

type multiError []error //nolint:errname

func (me multiError) Error() string {
var errstrings []string

for _, err := range me {
if err != nil {
errstrings = append(errstrings, err.Error())
}
}

if len(errstrings) == 0 {
return "multiError must contain multiple error but is empty"
}

return strings.Join(errstrings, "\n")
}

func (me multiError) Is(err error) bool {
for _, e := range me {
if errors.Is(e, err) {
return true
}
if me2, ok := e.(multiError); ok { //nolint:errorlint
if me2.Is(err) {
return true
}
}
}
return false
}
13 changes: 7 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ require (
github.com/labstack/echo-contrib v0.17.1
github.com/labstack/echo/v4 v4.12.0
github.com/pion/randutil v0.1.0
github.com/pion/rtp v1.8.6
github.com/pion/rtp v1.8.9
github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
golang.org/x/net v0.24.0
golang.org/x/sync v0.7.0
golang.org/x/net v0.29.0
golang.org/x/sync v0.8.0
google.golang.org/api v0.176.1
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0
Expand Down Expand Up @@ -46,6 +46,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/pion/webrtc/v4 v4.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
Expand All @@ -59,10 +60,10 @@ require (
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/oauth2 v0.19.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
Expand Down
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ github.com/pion/rtp v1.8.5 h1:uYzINfaK+9yWs7r537z/Rc1SvT8ILjBcmDOpJcTB+OU=
github.com/pion/rtp v1.8.5/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/rtp v1.8.6 h1:MTmn/b0aWWsAzux2AmP8WGllusBVw4NPYPVFFd7jUPw=
github.com/pion/rtp v1.8.6/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/rtp v1.8.9 h1:E2HX740TZKaqdcPmf4pw6ZZuG8u5RlMMt+l3dxeu6Wk=
github.com/pion/rtp v1.8.9/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/webrtc/v4 v4.0.1 h1:6Unwc6JzoTsjxetcAIoWH81RUM4K5dBc1BbJGcF9WVE=
github.com/pion/webrtc/v4 v4.0.1/go.mod h1:SfNn8CcFxR6OUVjLXVslAQ3a3994JhyE3Hw1jAuqEto=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down Expand Up @@ -308,6 +312,8 @@ golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4=
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
Expand Down Expand Up @@ -362,6 +368,8 @@ golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
Expand All @@ -384,6 +392,8 @@ golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -408,6 +418,8 @@ golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=
Expand All @@ -416,6 +428,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
7 changes: 0 additions & 7 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,13 +326,6 @@ func opus2ogg(ctx context.Context, opusReader io.Reader, oggWriter io.Writer, sa
}
defer o.Close()

if err := o.writeHeaders(); err != nil {
if w, ok := oggWriter.(*io.PipeWriter); ok {
w.CloseWithError(err)
}
return err
}

var r io.Reader
if c.AudioStreamingHeader {
r, err = readPacketWithHeader(opusReader)
Expand Down
42 changes: 30 additions & 12 deletions oggwriter.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

// Package oggwriter implements OGG media container writer
package suzu

import (
Expand All @@ -6,7 +10,6 @@ import (
"io"
"os"

"github.com/pion/randutil"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
)
Expand All @@ -19,7 +22,6 @@ const (
idPageSignature = "OpusHead"
commentPageSignature = "OpusTags"
pageHeaderSignature = "OggS"
vendorName = "pion"
)

var (
Expand Down Expand Up @@ -65,21 +67,25 @@ func NewWith(out io.Writer, sampleRate uint32, channelCount uint16) (*OggWriter,
stream: out,
sampleRate: sampleRate,
channelCount: channelCount,
serial: randutil.NewMathRandomGenerator().Uint32(),
serial: RandUint32(),
checksumTable: generateChecksumTable(),

// Timestamp and Granule MUST start from 1
// Only headers can have 0 values
previousTimestamp: 1,
previousGranulePosition: 1,
}
if err := writer.writeHeaders(); err != nil {
return nil, err
}

return writer, nil
}

/*
ref: https://tools.ietf.org/html/rfc7845.html
https://git.xiph.org/?p=opus-tools.git;a=blob;f=src/opus_header.c#l219

Page 0 Pages 1 ... n Pages (n+1) ...
+------------+ +---+ +---+ ... +---+ +-----------+ +---------+ +--
| | | | | | | | | | | | |
Expand All @@ -95,6 +101,7 @@ func NewWith(out io.Writer, sampleRate uint32, channelCount uint16) (*OggWriter,
| ID header is contained on a single page
|
'Beginning Of Stream'

Figure 1: Example Packet Organization for a Logical Ogg Opus Stream
*/

Expand All @@ -119,11 +126,11 @@ func (i *OggWriter) writeHeaders() error {
i.pageIndex++

// Comment Header
oggCommentHeader := make([]byte, (8 + len(vendorName) + 4 + 4))
copy(oggCommentHeader[0:], commentPageSignature) // Magic Signature 'OpusTags'
binary.LittleEndian.PutUint32(oggCommentHeader[8:], uint32(len(vendorName))) // Vendor Length
copy(oggCommentHeader[12:], vendorName) // Vendor name 'pion'
binary.LittleEndian.PutUint32(oggCommentHeader[16:], 0) // User Comment List Length
oggCommentHeader := make([]byte, 21)
copy(oggCommentHeader[0:], commentPageSignature) // Magic Signature 'OpusTags'
binary.LittleEndian.PutUint32(oggCommentHeader[8:], 5) // Vendor Length
copy(oggCommentHeader[12:], "pion") // Vendor name 'pion'
binary.LittleEndian.PutUint32(oggCommentHeader[17:], 0) // User Comment List Length

// RFC specifies that the page where the CommentHeader completes should have a granule position of 0
data = i.createPage(oggCommentHeader, pageHeaderTypeContinuationOfStream, 0, i.pageIndex)
Expand All @@ -141,22 +148,33 @@ const (

func (i *OggWriter) createPage(payload []uint8, headerType uint8, granulePos uint64, pageIndex uint32) []byte {
i.lastPayloadSize = len(payload)
page := make([]byte, pageHeaderSize+1+i.lastPayloadSize)
nSegments := (len(payload) / 255) + 1 // A segment can be at most 255 bytes long.

page := make([]byte, pageHeaderSize+i.lastPayloadSize+nSegments)

copy(page[0:], pageHeaderSignature) // page headers starts with 'OggS'
page[4] = 0 // Version
page[5] = headerType // 1 = continuation, 2 = beginning of stream, 4 = end of stream
binary.LittleEndian.PutUint64(page[6:], granulePos) // granule position
binary.LittleEndian.PutUint32(page[14:], i.serial) // Bitstream serial number
binary.LittleEndian.PutUint32(page[18:], pageIndex) // Page sequence number
page[26] = 1 // Number of segments in page, giving always 1 segment
page[27] = uint8(i.lastPayloadSize) // Segment Table inserting at 27th position since page header length is 27
copy(page[28:], payload) // inserting at 28th since Segment Table(1) + header length(27)
page[26] = uint8(nSegments) // Number of segments in page.

// Filling segment table with the lacing values.
// First (nSegments - 1) values will always be 255.
for i := 0; i < nSegments-1; i++ {
page[pageHeaderSize+i] = 255
}
// The last value will be the remainder.
page[pageHeaderSize+nSegments-1] = uint8(len(payload) % 255)

copy(page[pageHeaderSize+nSegments:], payload) // Payload goes after the segment table, so at pageHeaderSize+nSegments.

var checksum uint32
for index := range page {
checksum = (checksum << 8) ^ i.checksumTable[byte(checksum>>24)^page[index]]
}

binary.LittleEndian.PutUint32(page[22:], checksum) // Checksum - generating for page data and inserting at 22th position into 32 bits

return page
Expand Down
Loading