fixed the synchronized-audio corruption
This commit is contained in:
@@ -13,8 +13,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultSyncReadTimeout = 200 * time.Millisecond
|
||||
DefaultSyncBatchDuration = 10 * time.Millisecond
|
||||
DefaultSyncReadTimeout = 200 * time.Millisecond
|
||||
)
|
||||
|
||||
var ErrNativeSyncDifferentDomains = errors.New(
|
||||
@@ -22,15 +21,13 @@ var ErrNativeSyncDifferentDomains = errors.New(
|
||||
)
|
||||
|
||||
type SyncFactory struct {
|
||||
ReadTimeout time.Duration
|
||||
BatchDuration time.Duration
|
||||
open func(string, string, string) (localSyncSource, error)
|
||||
ReadTimeout time.Duration
|
||||
open func(string, string, string) (localSyncSource, error)
|
||||
}
|
||||
|
||||
type localSyncSource interface {
|
||||
NextSync(
|
||||
context.Context,
|
||||
uint64,
|
||||
time.Duration,
|
||||
) (source.Frame, source.AudioFrame, error)
|
||||
|
||||
@@ -41,7 +38,6 @@ type localSyncSource interface {
|
||||
type syncReader struct {
|
||||
source localSyncSource
|
||||
readTimeout time.Duration
|
||||
audioBatch uint64
|
||||
rateNumerator int64
|
||||
rateDenominator int64
|
||||
}
|
||||
@@ -118,25 +114,11 @@ func (f SyncFactory) OpenSync(
|
||||
if readTimeout <= 0 {
|
||||
readTimeout = DefaultSyncReadTimeout
|
||||
}
|
||||
batchDuration := f.BatchDuration
|
||||
if batchDuration <= 0 {
|
||||
batchDuration = DefaultSyncBatchDuration
|
||||
}
|
||||
audioRate := src.AudioRate()
|
||||
batch, err := audioBatchSize(audioRate.Num, audioRate.Den, batchDuration)
|
||||
if err != nil {
|
||||
_ = src.Close()
|
||||
return nil, &source.SourceError{
|
||||
Op: "calculate sync audio batch",
|
||||
Kind: source.ErrorKindInvalidConfig,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return &syncReader{
|
||||
source: src,
|
||||
readTimeout: readTimeout,
|
||||
audioBatch: batch,
|
||||
rateNumerator: audioRate.Num,
|
||||
rateDenominator: audioRate.Den,
|
||||
}, nil
|
||||
@@ -145,7 +127,7 @@ func (f SyncFactory) OpenSync(
|
||||
func (r *syncReader) ReadSync(
|
||||
ctx context.Context,
|
||||
) (playback.SyncFrame, error) {
|
||||
video, audio, err := r.source.NextSync(ctx, r.audioBatch, r.readTimeout)
|
||||
video, audio, err := r.source.NextSync(ctx, r.readTimeout)
|
||||
if err != nil {
|
||||
return playback.SyncFrame{}, err
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ type fakeLocalSyncSource struct {
|
||||
audio source.AudioFrame
|
||||
readErr error
|
||||
rate mxl.Rational
|
||||
batch uint64
|
||||
timeout time.Duration
|
||||
closed bool
|
||||
closeError error
|
||||
@@ -25,10 +24,8 @@ type fakeLocalSyncSource struct {
|
||||
|
||||
func (s *fakeLocalSyncSource) NextSync(
|
||||
_ context.Context,
|
||||
batch uint64,
|
||||
timeout time.Duration,
|
||||
) (source.Frame, source.AudioFrame, error) {
|
||||
s.batch = batch
|
||||
s.timeout = timeout
|
||||
return s.video, s.audio, s.readErr
|
||||
}
|
||||
@@ -79,23 +76,8 @@ func TestSyncFactoryUsesDefaultsAndForwardsFeeds(t *testing.T) {
|
||||
t.Fatalf("open args = %q %q %q", domain, videoUUID, audioUUID)
|
||||
}
|
||||
got := reader.(*syncReader)
|
||||
if got.readTimeout != DefaultSyncReadTimeout || got.audioBatch != 480 {
|
||||
t.Fatalf("reader timeout=%s batch=%d, want %s and 480", got.readTimeout, got.audioBatch, DefaultSyncReadTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncFactoryClosesSourceForInvalidAudioRate(t *testing.T) {
|
||||
fake := &fakeLocalSyncSource{rate: mxl.Rational{}}
|
||||
factory := SyncFactory{open: func(string, string, string) (localSyncSource, error) {
|
||||
return fake, nil
|
||||
}}
|
||||
video, audio := syncFeedConfigs()
|
||||
reader, err := factory.OpenSync(context.Background(), video, audio)
|
||||
if reader != nil {
|
||||
t.Fatal("OpenSync() reader is not nil")
|
||||
}
|
||||
if !errors.Is(err, ErrInvalidAudioBatch) || !fake.closed {
|
||||
t.Fatalf("OpenSync() error=%v closed=%t", err, fake.closed)
|
||||
if got.readTimeout != DefaultSyncReadTimeout {
|
||||
t.Fatalf("reader timeout=%s, want %s", got.readTimeout, DefaultSyncReadTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +90,7 @@ func TestSyncReaderConvertsPairWithoutCopying(t *testing.T) {
|
||||
rate: mxl.Rational{Num: 48_000, Den: 1},
|
||||
}
|
||||
reader := &syncReader{
|
||||
source: fake, readTimeout: 7 * time.Millisecond, audioBatch: 12,
|
||||
source: fake, readTimeout: 7 * time.Millisecond,
|
||||
rateNumerator: 48_000, rateDenominator: 1,
|
||||
}
|
||||
|
||||
@@ -116,8 +98,8 @@ func TestSyncReaderConvertsPairWithoutCopying(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if fake.batch != 12 || fake.timeout != 7*time.Millisecond {
|
||||
t.Fatalf("NextSync() batch=%d timeout=%s", fake.batch, fake.timeout)
|
||||
if fake.timeout != 7*time.Millisecond {
|
||||
t.Fatalf("NextSync() timeout=%s", fake.timeout)
|
||||
}
|
||||
if frame.Video.Index != 10 || frame.Audio.Index != 40 || frame.Audio.SampleRateNumerator != 48_000 {
|
||||
t.Fatalf("frame = %+v", frame)
|
||||
|
||||
+38
-17
@@ -598,8 +598,10 @@ func (s *SyncSource) Close() error {
|
||||
return s.inst.Close()
|
||||
}
|
||||
|
||||
// NextSync reads both at a synced timestamp. Returns video Frame + audio AudioFrame
|
||||
func (s *SyncSource) NextSync(ctx context.Context, audioBatch uint64, timeout time.Duration) (Frame, AudioFrame, error) {
|
||||
// NextSync reads one video frame and the audio interval between this video
|
||||
// timestamp and the next. Deriving the interval for every frame preserves
|
||||
// exact long-term timing for fractional video rates.
|
||||
func (s *SyncSource) NextSync(ctx context.Context, timeout time.Duration) (Frame, AudioFrame, error) {
|
||||
var timeouts int
|
||||
for {
|
||||
select {
|
||||
@@ -635,7 +637,30 @@ func (s *SyncSource) NextSync(ctx context.Context, audioBatch uint64, timeout ti
|
||||
}
|
||||
// read audio at the same timestamp
|
||||
aIdx := mxl.TimestampToIndex(s.aRate, ts)
|
||||
nextTimestamp := mxl.IndexToTimestamp(s.rate, s.idx+1)
|
||||
nextAudioIndex := mxl.TimestampToIndex(s.aRate, nextTimestamp)
|
||||
if nextAudioIndex <= aIdx {
|
||||
return Frame{}, AudioFrame{}, wrapError(
|
||||
"calculate synchronized audio interval",
|
||||
ErrorKindInvalidConfig,
|
||||
fmt.Errorf("invalid audio interval: %d..%d", aIdx, nextAudioIndex),
|
||||
)
|
||||
}
|
||||
audioBatch := nextAudioIndex - aIdx
|
||||
av, aerr := s.ar.GetSamples(aIdx, int(audioBatch), 50*time.Millisecond)
|
||||
if aerr != nil {
|
||||
kind := ErrorKindUnavailable
|
||||
if errors.Is(aerr, mxl.ErrTimeout) ||
|
||||
errors.Is(aerr, mxl.ErrOutOfRangeEarly) ||
|
||||
errors.Is(aerr, mxl.ErrOutOfRangeLate) {
|
||||
kind = ErrorKindTemporary
|
||||
}
|
||||
return Frame{}, AudioFrame{}, wrapError(
|
||||
"read synchronized audio",
|
||||
kind,
|
||||
aerr,
|
||||
)
|
||||
}
|
||||
vFrame := Frame{
|
||||
Index: g.Index, Width: s.width, Height: s.height,
|
||||
Stride: s.stride, Size: g.GrainSize,
|
||||
@@ -643,23 +668,19 @@ func (s *SyncSource) NextSync(ctx context.Context, audioBatch uint64, timeout ti
|
||||
}
|
||||
s.idx++
|
||||
|
||||
var aFrame AudioFrame
|
||||
if aerr == nil {
|
||||
samples := make([][]byte, s.chans)
|
||||
for ch := uint64(0); ch < s.chans; ch++ {
|
||||
f1, f2, _ := av.ChannelFragments(ch)
|
||||
if len(f2) > 0 {
|
||||
samples[ch] = append(f1, f2...)
|
||||
} else {
|
||||
samples[ch] = f1
|
||||
}
|
||||
}
|
||||
aFrame = AudioFrame{
|
||||
Index: aIdx, SampleCount: audioBatch,
|
||||
Channels: s.chans, Samples: samples,
|
||||
samples := make([][]byte, s.chans)
|
||||
for ch := uint64(0); ch < s.chans; ch++ {
|
||||
f1, f2, _ := av.ChannelFragments(ch)
|
||||
if len(f2) > 0 {
|
||||
samples[ch] = append(f1, f2...)
|
||||
} else {
|
||||
samples[ch] = f1
|
||||
}
|
||||
}
|
||||
// even if audio failed, video returns
|
||||
aFrame := AudioFrame{
|
||||
Index: aIdx, SampleCount: audioBatch,
|
||||
Channels: s.chans, Samples: samples,
|
||||
}
|
||||
return vFrame, aFrame, nil
|
||||
case errors.Is(err, mxl.ErrTimeout), errors.Is(err, mxl.ErrOutOfRangeEarly), errors.Is(err, mxl.ErrOutOfRangeLate):
|
||||
timeouts++
|
||||
|
||||
Reference in New Issue
Block a user