Skip to content

Commit

Permalink
support AC3 in fMP4 and MPEG-TS (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
aler9 authored Sep 19, 2023
1 parent 5042498 commit 74d6902
Show file tree
Hide file tree
Showing 33 changed files with 1,106 additions and 64 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Definitions and functions shared between [gortsplib](https://github.com/bluenvir
|ISO 14496-3, Coding of audio-visual objects, Part 3, Audio|MPEG-4 Audio codec|
|[RFC6716, Definition of the Opus Audio Codec](https://datatracker.ietf.org/doc/html/rfc6716)|Opus codec|
|[Opus in MP4/ISOBMFF](https://opus-codec.org/docs/opus_in_isobmff.html)|Opus inside MP4|
|[ATSC Standard: Digital Audio Compression (AC-3, E-AC-3)](http://www.atsc.org/wp-content/uploads/2015/03/A52-201212-17.pdf)|AC-3 codec|
|[ETSI TS 102 366](https://www.etsi.org/deliver/etsi_ts/102300_102399/102366/01.04.01_60/ts_102366v010401p.pdf)|AC-3 inside MP4|
|ISO 14496-1, Coding of audio-visual objects, Part 1, Systems|MP4 format|
|ISO 14496-12, Coding of audio-visual objects, Part 12, ISO base media file format|MP4 format|
|ISO 14496-14, Coding of audio-visual objects, Part 14, MP4 file format|MP4 format|
Expand Down
7 changes: 7 additions & 0 deletions pkg/codecs/ac3/ac3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Package ac3 contains utilities to work with the AC-3 codec.
package ac3

const (
// SamplesPerFrame is the number of samples contained inside a frame.
SamplesPerFrame = 1536
)
74 changes: 74 additions & 0 deletions pkg/codecs/ac3/bsi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package ac3

import (
"fmt"

"github.com/bluenviron/mediacommon/pkg/bits"
)

// BSI is a Bit Stream Information.
// Specification: ATSC, AC-3, Table 5.2
type BSI struct {
Bsid uint8
Bsmod uint8
Acmod uint8
LfeOn bool
}

// Unmarshal decodes a BSI.
func (b *BSI) Unmarshal(buf []byte) error {
if len(buf) < 2 {
return fmt.Errorf("not enough bits")
}

b.Bsid = buf[0] >> 3
if b.Bsid != 0x08 {
return fmt.Errorf("invalid bsid")
}

b.Bsmod = buf[0] & 0b111

buf = buf[1:]
pos := 0

tmp := bits.ReadBitsUnsafe(buf, &pos, 3)
b.Acmod = uint8(tmp)

if ((b.Acmod & 0x1) != 0) && (b.Acmod != 0x1) {
bits.ReadBitsUnsafe(buf, &pos, 2) // cmixlev
}

if (b.Acmod & 0x4) != 0 {
bits.ReadBitsUnsafe(buf, &pos, 2) // surmixlev
}

if b.Acmod == 0x2 {
bits.ReadBitsUnsafe(buf, &pos, 2) // dsurmod
}

b.LfeOn = bits.ReadFlagUnsafe(buf, &pos)

return nil
}

// ChannelCount returns the channel count.
func (b BSI) ChannelCount() int {
var n int
switch b.Acmod {
case 0b001:
n = 1
case 0b010, 0b000:
n = 2
case 0b011, 0b100:
n = 3
case 0b101, 0b110:
n = 4
default:
n = 5
}

if b.LfeOn {
return n + 1
}
return n
}
25 changes: 25 additions & 0 deletions pkg/codecs/ac3/bsi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ac3

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestBSIUnmarshal(t *testing.T) {
for _, ca := range ac3Cases {
t.Run(ca.name, func(t *testing.T) {
var bsi BSI
err := bsi.Unmarshal(ca.enc[5:])
require.NoError(t, err)
require.Equal(t, ca.bsi, bsi)
})
}
}

func FuzzBSIUnmarshal(f *testing.F) {
f.Fuzz(func(t *testing.T, b []byte) {
var bsi BSI
bsi.Unmarshal(b) //nolint:errcheck
})
}
94 changes: 94 additions & 0 deletions pkg/codecs/ac3/sync_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package ac3

import (
"fmt"
)

// ATSC, AC-3, Table 5.18
var frameSizes = [][]int{
{64, 69, 96},
{64, 70, 96},
{80, 87, 120},
{80, 88, 120},
{96, 104, 144},
{96, 105, 144},
{112, 121, 168},
{112, 122, 168},
{128, 139, 192},
{128, 140, 192},
{160, 174, 240},
{160, 175, 240},
{192, 208, 288},
{192, 209, 288},
{224, 243, 336},
{224, 244, 336},
{256, 278, 384},
{256, 279, 384},
{320, 348, 480},
{320, 349, 480},
{384, 417, 576},
{384, 418, 576},
{448, 487, 672},
{448, 488, 672},
{512, 557, 768},
{512, 558, 768},
{640, 696, 960},
{640, 697, 960},
{768, 835, 1152},
{768, 836, 1152},
{896, 975, 1344},
{896, 976, 1344},
{1024, 1114, 1536},
{1024, 1115, 1536},
{1152, 1253, 1728},
{1152, 1254, 1728},
{1280, 1393, 1920},
{1280, 1394, 1920},
}

// SyncInfo is a synchronization information.
// Specification: ATSC, AC-3, Table 5.1
type SyncInfo struct {
Fscod uint8
Frmsizecod uint8
}

// Unmarshal decodes a SyncInfo.
func (s *SyncInfo) Unmarshal(frame []byte) error {
if len(frame) < 5 {
return fmt.Errorf("not enough bits")
}

if frame[0] != 0x0B || frame[1] != 0x77 {
return fmt.Errorf("invalid sync word")
}

s.Fscod = frame[4] >> 6
if s.Fscod >= 3 {
return fmt.Errorf("invalid fscod")
}

s.Frmsizecod = frame[4] & 0x3f
if s.Frmsizecod >= 38 {
return fmt.Errorf("invalid frmsizecod")
}

return nil
}

// FrameSize returns the frame size.
func (s SyncInfo) FrameSize() int {
return frameSizes[s.Frmsizecod][s.Fscod] * 2
}

// SampleRate returns the frame sample rate.
func (s SyncInfo) SampleRate() int {
switch s.Fscod {
case 0:
return 48000
case 1:
return 44100
default:
return 32000
}
}
Loading

0 comments on commit 74d6902

Please sign in to comment.