Skip to content
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
in a wave child of an audio sample entry becomes reachable as `Wave.Esds`.
Children that are not well-formed boxes (such as the spec-mandated
terminator atom when written with size zero) are preserved verbatim
- `WaveBox.GetChildren`, so a wave box satisfies the `ContainerBox` interface
- `DecoderConfigDescriptor.StreamTypeValue` and `UpStream` decompose the
packed streamType byte, and named constants cover the common stream types
(ISO/IEC 14496-1 Table 6) and object type indications (mp4ra.org), so
Expand All @@ -30,6 +31,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- `Info()` escapes control characters in box types as `\xNN`, so a type such
as the four zero bytes of the QuickTime wave terminator atom stays visible
- audio sample entries whose reserved bytes claim a QuickTime version whose
layout does not parse fall back to the plain ISO interpretation, so
previously decodable files keep decoding; the overlaid QuickTime bytes
Expand Down
35 changes: 33 additions & 2 deletions mp4/infodumper.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,51 @@ func fixStartingCopyrightChar(boxType string) string {
// © is 0xa9 in latin1 (and in Apple boxes/atoms)
// In UTF-8 it is two bytes: 0xc2 0xa9
bType := []byte(boxType)
if bType[0] == 0xa9 {
if len(bType) > 0 && bType[0] == 0xa9 {
bType = append([]byte{0xc2}, bType...)
}
return string(bType)
}

// displayBoxType - make a box type safe to print. Control characters are
// escaped as \xNN, so that a type such as the four zero bytes of the
// QuickTime wave terminator atom stays visible in the output.
func displayBoxType(boxType string) string {
boxType = fixStartingCopyrightChar(boxType)
nrControlChars := 0
for i := 0; i < len(boxType); i++ {
if isControlChar(boxType[i]) {
nrControlChars++
}
}
if nrControlChars == 0 {
return boxType
}
var sb strings.Builder
sb.Grow(len(boxType) + 3*nrControlChars)
for i := 0; i < len(boxType); i++ {
if c := boxType[i]; isControlChar(c) {
fmt.Fprintf(&sb, "\\x%02x", c)
} else {
sb.WriteByte(c)
}
}
return sb.String()
}

// isControlChar - C0 control character or DEL
func isControlChar(c byte) bool {
return c < 0x20 || c == 0x7f
}

// newInfoDumper - make an infoDumper with indent
// write version if >= 0
// set Version to -1 if not present for box
// set Version to -2 for sample group entries
// set Version to -3 for descriptors
func newInfoDumper(w io.Writer, indent string, b boxLike, version int, flags uint32) *infoDumper {
bd := infoDumper{w, indent, b, nil}
utf8BoxType := fixStartingCopyrightChar(b.Type())
utf8BoxType := displayBoxType(b.Type())
switch {
case version >= 0:
bd.write("[%s] size=%d version=%d flags=%06x", utf8BoxType, b.Size(), version, flags)
Expand Down
14 changes: 13 additions & 1 deletion mp4/wave.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,19 @@ type WaveBox struct {
Frma *FrmaBox
Esds *EsdsBox
Children []Box
RawTail []byte
// RawTail holds the bytes from the first position where a well-formed box
// header could not be read to the end of the wave payload. Since the
// terminator atom comes last, this is normally either empty or just that
// atom, but a malformed atom anywhere in the payload puts everything from
// it onwards here, so any boxes after it are not decoded into Children.
// Encode writes RawTail verbatim after the children.
RawTail []byte
}

// GetChildren - list of child boxes. Note that the bytes in RawTail are not
// part of the children, so a generic container encode would drop them.
func (b *WaveBox) GetChildren() []Box {
return b.Children
}

// AddChild - add a child box
Expand Down
31 changes: 31 additions & 0 deletions mp4/wave_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mp4_test
import (
"bytes"
"encoding/binary"
"strings"
"testing"

"github.com/Eyevinn/mp4ff/mp4"
Expand Down Expand Up @@ -63,6 +64,36 @@ func TestWaveBox(t *testing.T) {
}
}

func TestWaveBoxIsContainer(t *testing.T) {
wave := &mp4.WaveBox{}
var container mp4.ContainerBox = wave // wave must satisfy the container interface
frma := &mp4.FrmaBox{DataFormat: "mp4a"}
wave.AddChild(frma)
children := container.GetChildren()
if len(children) != 1 || children[0] != frma {
t.Errorf("GetChildren gave %v, wanted the single added frma", children)
}
}

func TestWaveBoxTerminatorInInfo(t *testing.T) {
terminator := waveChildBytes(string([]byte{0, 0, 0, 0}), nil)
box, err := mp4.DecodeBox(0, bytes.NewReader(waveBytes(terminator)))
if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
if err := box.Info(&buf, "all:1", "", " "); err != nil {
t.Fatal(err)
}
info := buf.String()
if strings.ContainsRune(info, 0) {
t.Errorf("info output has a raw NUL byte in a box type: %q", info)
}
if !strings.Contains(info, `[\x00\x00\x00\x00]`) {
t.Errorf("terminator box type not escaped in info output: %q", info)
}
}

func TestWaveBoxMalformedEsds(t *testing.T) {
badEsds := waveChildBytes("esds", []byte{0, 0}) // truncated esds body
data := waveBytes(badEsds)
Expand Down
Loading