Skip to content

Commit

Permalink
Merge pull request #190 from shiguredo/feature/high-bitrate
Browse files Browse the repository at this point in the history
高ビットレート音声対応
  • Loading branch information
Hexa authored Nov 12, 2024
2 parents 3ac7971 + 6cdb101 commit ddf1a33
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 84 deletions.
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

0 comments on commit ddf1a33

Please sign in to comment.