Skip to content

Commit 4a8e016

Browse files
Rewrite CMSampleBufParser to fix #38 (#41)
* added testcase to repro TTAS FEED packet issue * fixed broken TTAS * add checks for sample buffers without sample data in consumers Co-authored-by: DanielPaulusContentful <[email protected]>
1 parent b36acdc commit 4a8e016

File tree

5 files changed

+107
-66
lines changed

5 files changed

+107
-66
lines changed

screencapture/coremedia/avfilewriter.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ func (avfw AVFileWriter) consumeVideo(buf CMSampleBuffer) error {
4444
return err
4545
}
4646
}
47+
if !buf.HasSampleData() {
48+
return nil
49+
}
4750
return avfw.writeNalus(buf.SampleData)
4851
}
4952

@@ -73,6 +76,9 @@ func (avfw AVFileWriter) writeNalu(naluBytes []byte) error {
7376
}
7477

7578
func (avfw AVFileWriter) consumeAudio(buffer CMSampleBuffer) error {
79+
if !buffer.HasSampleData() {
80+
return nil
81+
}
7682
_, err := avfw.wavFileWriter.Write(buffer.SampleData)
7783
if err != nil {
7884
return err

screencapture/coremedia/cmsamplebuf.go

Lines changed: 65 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package coremedia
22

33
import (
44
"encoding/binary"
5+
"encoding/hex"
56
"fmt"
67

78
"github.com/danielpaulus/quicktime_video_hack/screencapture/common"
@@ -44,6 +45,10 @@ func (info CMSampleTimingInfo) String() string {
4445
info.Duration, info.PresentationTimeStamp, info.DecodeTimeStamp)
4546
}
4647

48+
func (buffer CMSampleBuffer) HasSampleData() bool {
49+
return buffer.SampleData != nil
50+
}
51+
4752
//CMSampleBuffer represents the CoreMedia class used to exchange AV SampleData and contains meta information like timestamps or
4853
//optional FormatDescriptors
4954
type CMSampleBuffer struct {
@@ -98,75 +103,69 @@ func NewCMSampleBufferFromBytes(data []byte, mediaType uint32) (CMSampleBuffer,
98103
if length > len(data) {
99104
return sbuffer, fmt.Errorf("less data (%d bytes) in buffer than expected (%d bytes)", len(data), length)
100105
}
101-
102-
_, remainingBytes, err = common.ParseLengthAndMagic(remainingBytes, opts)
103-
if err != nil {
104-
return sbuffer, err
105-
}
106-
cmtime, err := NewCMTimeFromBytes(remainingBytes)
107-
if err != nil {
108-
return sbuffer, err
109-
}
110-
sbuffer.OutputPresentationTimestamp = cmtime
111-
sbuffer.SampleTimingInfoArray, remainingBytes, err = parseStia(remainingBytes[24:])
112-
if err != nil {
113-
return sbuffer, err
114-
}
115-
116-
length, remainingBytes, err = common.ParseLengthAndMagic(remainingBytes, sdat)
117-
if err != nil {
118-
return sbuffer, err
119-
}
120-
sbuffer.SampleData = remainingBytes[:length-8]
121-
length, remainingBytes, err = common.ParseLengthAndMagic(remainingBytes[length-8:], nsmp)
122-
if err != nil {
123-
return sbuffer, err
124-
}
125-
if length != 12 {
126-
return sbuffer, fmt.Errorf("invalid length for nsmp %d, should be 12", length)
127-
}
128-
sbuffer.NumSamples = int(binary.LittleEndian.Uint32(remainingBytes))
129-
130-
sbuffer.SampleSizes, remainingBytes, err = parseSampleSizeArray(remainingBytes[4:])
131-
if err != nil {
132-
return sbuffer, err
133-
}
134-
135-
//audio buffers usually end after samplesize
136-
if len(remainingBytes) == 0 {
137-
return sbuffer, nil
138-
}
139-
if binary.LittleEndian.Uint32(remainingBytes[4:]) == FormatDescriptorMagic {
140-
sbuffer.HasFormatDescription = true
141-
fdscLength := binary.LittleEndian.Uint32(remainingBytes)
142-
sbuffer.FormatDescription, err = NewFormatDescriptorFromBytes(remainingBytes[:fdscLength])
143-
if err != nil {
144-
return sbuffer, err
106+
for len(remainingBytes) > 0 {
107+
switch binary.LittleEndian.Uint32(remainingBytes[4:]) {
108+
case opts:
109+
cmtime, err := NewCMTimeFromBytes(remainingBytes[8:])
110+
if err != nil {
111+
return sbuffer, err
112+
}
113+
sbuffer.OutputPresentationTimestamp = cmtime
114+
remainingBytes = remainingBytes[32:]
115+
case stia:
116+
sbuffer.SampleTimingInfoArray, remainingBytes, err = parseStia(remainingBytes)
117+
if err != nil {
118+
return sbuffer, err
119+
}
120+
case sdat:
121+
length, remainingBytes, err = common.ParseLengthAndMagic(remainingBytes, sdat)
122+
if err != nil {
123+
return sbuffer, err
124+
}
125+
sbuffer.SampleData = remainingBytes[:length-8]
126+
remainingBytes = remainingBytes[length-8:]
127+
case nsmp:
128+
129+
length, remainingBytes, err = common.ParseLengthAndMagic(remainingBytes, nsmp)
130+
if err != nil {
131+
return sbuffer, err
132+
}
133+
if length != 12 {
134+
return sbuffer, fmt.Errorf("invalid length for nsmp %d, should be 12", length)
135+
}
136+
sbuffer.NumSamples = int(binary.LittleEndian.Uint32(remainingBytes))
137+
remainingBytes = remainingBytes[4:]
138+
case ssiz:
139+
sbuffer.SampleSizes, remainingBytes, err = parseSampleSizeArray(remainingBytes)
140+
if err != nil {
141+
return sbuffer, err
142+
}
143+
case FormatDescriptorMagic:
144+
sbuffer.HasFormatDescription = true
145+
fdscLength := binary.LittleEndian.Uint32(remainingBytes)
146+
sbuffer.FormatDescription, err = NewFormatDescriptorFromBytes(remainingBytes[:fdscLength])
147+
if err != nil {
148+
return sbuffer, err
149+
}
150+
remainingBytes = remainingBytes[fdscLength:]
151+
case satt:
152+
attachmentsLength := binary.LittleEndian.Uint32(remainingBytes)
153+
sbuffer.Attachments, err = NewIndexDictFromBytesWithCustomMarker(remainingBytes[:attachmentsLength], satt)
154+
if err != nil {
155+
return sbuffer, err
156+
}
157+
remainingBytes = remainingBytes[attachmentsLength:]
158+
case sary:
159+
saryLength := binary.LittleEndian.Uint32(remainingBytes)
160+
sbuffer.Sary, err = NewIndexDictFromBytes(remainingBytes[8:saryLength])
161+
remainingBytes = remainingBytes[saryLength:]
162+
default:
163+
unknownMagic := string(remainingBytes[4:8])
164+
return sbuffer, fmt.Errorf("unknown magic type:%s (0x%x), cannot parse value %s", unknownMagic, remainingBytes[4:8], hex.Dump(remainingBytes))
145165
}
146-
remainingBytes = remainingBytes[fdscLength:]
147-
}
148-
//audio buffers usually end after samplesize
149-
if len(remainingBytes) == 0 {
150-
return sbuffer, nil
151-
}
152166

153-
attachmentsLength := binary.LittleEndian.Uint32(remainingBytes)
154-
sbuffer.Attachments, err = NewIndexDictFromBytesWithCustomMarker(remainingBytes[:attachmentsLength], satt)
155-
if err != nil {
156-
return sbuffer, err
157-
}
158-
remainingBytes = remainingBytes[attachmentsLength:]
159-
saryLength := binary.LittleEndian.Uint32(remainingBytes)
160-
if binary.LittleEndian.Uint32(remainingBytes[4:]) != sary {
161-
return sbuffer, fmt.Errorf("wrong magic, expected sary got:%x", remainingBytes[4:8])
162-
}
163-
sbuffer.Sary, err = NewIndexDictFromBytes(remainingBytes[8:saryLength])
164-
if err != nil {
165-
return sbuffer, err
166-
}
167-
if len(remainingBytes[saryLength:]) != 0 {
168-
return sbuffer, fmt.Errorf("CmSampleBuf should have been read completely but still contains bytes: %x", remainingBytes[saryLength:])
169167
}
168+
170169
return sbuffer, nil
171170
}
172171

screencapture/coremedia/cmsamplebuf_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package coremedia_test
22

33
import (
4+
"encoding/binary"
45
"io/ioutil"
56
"log"
67
"testing"
@@ -9,6 +10,35 @@ import (
910
"github.com/stretchr/testify/assert"
1011
)
1112

13+
func TestFeedNoSdat(t *testing.T) {
14+
dat, err := ioutil.ReadFile("../packet/fixtures/asyn-feed-ttas-only")
15+
if err != nil {
16+
log.Fatal(err)
17+
}
18+
sbufPacket, err := coremedia.NewCMSampleBufferFromBytesVideo(dat[20:])
19+
20+
if assert.NoError(t, err) {
21+
assert.Equal(t, false, sbufPacket.HasFormatDescription)
22+
23+
}
24+
print(sbufPacket.String())
25+
}
26+
27+
func TestUnknownMagic(t *testing.T) {
28+
dat, err := ioutil.ReadFile("../packet/fixtures/asyn-feed-ttas-only")
29+
if err != nil {
30+
log.Fatal(err)
31+
}
32+
binary.LittleEndian.PutUint32(dat[32:], 0x75756c6c)
33+
34+
_, err = coremedia.NewCMSampleBufferFromBytesVideo(dat[20:])
35+
36+
if assert.Error(t, err) {
37+
assert.Contains(t, err.Error(), "lluu")
38+
}
39+
40+
}
41+
1242
func TestCMSampleBuffer(t *testing.T) {
1343
dat, err := ioutil.ReadFile("../packet/fixtures/asyn-feed")
1444
if err != nil {

screencapture/gstadapter/gst_adapter_linux.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ func checkElem(e *gst.Element, name string) {
227227
//Consume will transfer AV data into a Gstreamer AppSrc
228228
func (gsta *GstAdapter) Consume(buf coremedia.CMSampleBuffer) error {
229229
if buf.MediaType == coremedia.MediaTypeSound {
230+
if !buf.HasSampleData() {
231+
return nil
232+
}
230233
if gsta.firstAudioSample {
231234
gsta.firstAudioSample = false
232235
gsta.sendWavHeader()
@@ -252,6 +255,9 @@ func (gsta *GstAdapter) Consume(buf coremedia.CMSampleBuffer) error {
252255
return err
253256
}
254257
}
258+
if !buf.HasSampleData() {
259+
return nil
260+
}
255261
gsta.writeNalus(buf)
256262

257263
return nil
202 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)