Absolute seek (#46)
* frame: Add frame.SampleNumber() * flac/seek: Make stream.Seek seek to absolute sample numbers * seek/test: Update test case for absolute seeking * Update flac.go Co-authored-by: Robin <mewmew@users.noreply.github.com> * Update flac.go Co-authored-by: Robin <mewmew@users.noreply.github.com> * Update flac.go Co-authored-by: Robin <mewmew@users.noreply.github.com> * Update flac.go Co-authored-by: Robin <mewmew@users.noreply.github.com> * Update flac_test.go Co-authored-by: Robin <mewmew@users.noreply.github.com> * Update frame/frame.go Co-authored-by: Robin <mewmew@users.noreply.github.com> * flac: Improve code styling * flac: Add ErrNoSeeker documentation comment * flac: Make searchFromStart return an error instead of panic * Update flac.go Co-authored-by: Robin <mewmew@users.noreply.github.com> * Update flac.go Co-authored-by: Robin <mewmew@users.noreply.github.com> Co-authored-by: Robin <mewmew@users.noreply.github.com>
This commit is contained in:
parent
bb157a5067
commit
11466d8af8
144
flac.go
144
flac.go
|
@ -110,10 +110,9 @@ func NewSeek(rs io.ReadSeeker) (stream *Stream, err error) {
|
|||
if err != nil {
|
||||
if err != meta.ErrReservedType {
|
||||
return stream, err
|
||||
} else {
|
||||
if err = block.Skip(); err != nil {
|
||||
return stream, err
|
||||
}
|
||||
}
|
||||
if err = block.Skip(); err != nil {
|
||||
return stream, err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,8 +133,13 @@ var (
|
|||
// id3Signature marks the beginning of an ID3 stream, used to skip over ID3 data.
|
||||
id3Signature = []byte("ID3")
|
||||
|
||||
ErrInvalidSeek = errors.New("stream.Seek: invalid seek out of stream")
|
||||
ErrNoSeeker = errors.New("stream.Seek: reader does not implement io.Seeker")
|
||||
// ErrNoSeeker reports that flac.NewSeek was called with an io.Reader not
|
||||
// implementing io.Seeker, and thus does not allow for seeking.
|
||||
ErrNoSeeker = errors.New("stream.Seek: reader does not implement io.Seeker")
|
||||
|
||||
// ErrNoSeektable reports that no seektable has been generated. Therefore,
|
||||
// it is not possible to seek in the stream.
|
||||
ErrNoSeektable = errors.New("stream.searchFromStart: no seektable exists")
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -305,20 +309,10 @@ func (stream *Stream) ParseNext() (f *frame.Frame, err error) {
|
|||
return frame.Parse(stream.r)
|
||||
}
|
||||
|
||||
// Seek to a specific sample number in the flac stream.
|
||||
//
|
||||
// sample is valid if:
|
||||
// whence == io.SeekEnd and sample is negative
|
||||
// whence == io.SeekStart and sample is positive
|
||||
// whence == io.SeekCurrent and sample + current sample > 0 and < stream.Info.NSamples
|
||||
//
|
||||
// If sample does not match one of the above conditions then the result will
|
||||
// probably be seeking to the beginning or very end of the data and no error
|
||||
// will be returned.
|
||||
//
|
||||
// The returned value, result, represents the closest match to sampleNum from the seek table.
|
||||
// Note that result will always be >= sampleNum
|
||||
func (stream *Stream) Seek(sampleNum int64, whence int) (result int64, err error) {
|
||||
// Seek seeks to the frame containing the given absolute sample number. The
|
||||
// return value specifies the first sample number of the frame containing
|
||||
// sampleNum.
|
||||
func (stream *Stream) Seek(sampleNum uint64) (uint64, error) {
|
||||
if stream.seekTable == nil && stream.seekTableSize > 0 {
|
||||
if err := stream.makeSeekTable(); err != nil {
|
||||
return 0, err
|
||||
|
@ -327,62 +321,54 @@ func (stream *Stream) Seek(sampleNum int64, whence int) (result int64, err error
|
|||
|
||||
rs := stream.r.(io.ReadSeeker)
|
||||
|
||||
var point meta.SeekPoint
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
point = stream.searchFromStart(sampleNum)
|
||||
case io.SeekCurrent:
|
||||
point, err = stream.searchFromCurrent(sampleNum, rs)
|
||||
case io.SeekEnd:
|
||||
point = stream.searchFromEnd(sampleNum)
|
||||
default:
|
||||
return 0, ErrInvalidSeek
|
||||
isBiggerThanStream := stream.Info.NSamples != 0 && sampleNum > stream.Info.NSamples
|
||||
if isBiggerThanStream || sampleNum < 0 {
|
||||
return 0, fmt.Errorf("unable to seek to sample number %d", sampleNum)
|
||||
}
|
||||
|
||||
point, err := stream.searchFromStart(sampleNum)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = rs.Seek(stream.dataStart+int64(point.Offset), io.SeekStart)
|
||||
return int64(point.SampleNum), err
|
||||
}
|
||||
|
||||
func (stream *Stream) searchFromCurrent(sample int64, rs io.ReadSeeker) (p meta.SeekPoint, err error) {
|
||||
o, err := rs.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return p, err
|
||||
if _, err := rs.Seek(stream.dataStart+int64(point.Offset), io.SeekStart); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
offset := o - stream.dataStart
|
||||
for _, p = range stream.seekTable.Points {
|
||||
if int64(p.Offset) >= offset {
|
||||
return stream.searchFromStart(int64(p.SampleNum) + sample), nil
|
||||
for {
|
||||
// Record seek offset to start of frame.
|
||||
offset, err := rs.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
frame, err := stream.ParseNext()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if frame.SampleNumber()+uint64(frame.BlockSize) >= sampleNum {
|
||||
// Restore seek offset to the start of the frame containing the specified sample number.
|
||||
_, err := rs.Seek(offset, io.SeekStart)
|
||||
return frame.SampleNumber(), err
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// searchFromEnd expects sample to be negative.
|
||||
// If it is positive, it's ok, the last seek point will be returned.
|
||||
func (stream *Stream) searchFromEnd(sample int64) (p meta.SeekPoint) {
|
||||
return stream.searchFromStart(int64(stream.Info.NSamples) + sample)
|
||||
}
|
||||
// TODO(_): Utilize binary search.
|
||||
|
||||
func (stream *Stream) searchFromStart(sample int64) (p meta.SeekPoint) {
|
||||
var last meta.SeekPoint
|
||||
var i int
|
||||
for i, p = range stream.seekTable.Points {
|
||||
if int64(p.SampleNum) >= sample {
|
||||
if i == 0 {
|
||||
return p
|
||||
}
|
||||
return last
|
||||
}
|
||||
last = p
|
||||
//
|
||||
func (stream *Stream) searchFromStart(sample uint64) (meta.SeekPoint, error) {
|
||||
if len(stream.seekTable.Points) == 0 {
|
||||
return meta.SeekPoint{}, ErrNoSeektable
|
||||
}
|
||||
return p
|
||||
prev := stream.seekTable.Points[0]
|
||||
for _, p := range stream.seekTable.Points {
|
||||
if p.SampleNum+uint64(p.NSamples) >= sample {
|
||||
return prev, nil
|
||||
}
|
||||
prev = p
|
||||
}
|
||||
return prev
|
||||
}
|
||||
|
||||
// makeSeekTable creates a seek table with seek points to each frame of the FLAC stream.
|
||||
func (stream *Stream) makeSeekTable() (err error) {
|
||||
rs, ok := stream.r.(io.ReadSeeker)
|
||||
if !ok {
|
||||
|
@ -401,23 +387,21 @@ func (stream *Stream) makeSeekTable() (err error) {
|
|||
|
||||
var i int
|
||||
var sampleNum uint64
|
||||
var tmp []meta.SeekPoint
|
||||
var points []meta.SeekPoint
|
||||
for {
|
||||
f, err := stream.ParseNext()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Record seek offset to start of frame.
|
||||
o, err := rs.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmp = append(tmp, meta.SeekPoint{
|
||||
f, err := stream.ParseNext()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
points = append(points, meta.SeekPoint{
|
||||
SampleNum: sampleNum,
|
||||
Offset: uint64(o - stream.dataStart),
|
||||
NSamples: f.BlockSize,
|
||||
|
@ -427,18 +411,6 @@ func (stream *Stream) makeSeekTable() (err error) {
|
|||
i++
|
||||
}
|
||||
|
||||
// reduce the number of seek points down to the specified resolution
|
||||
m := 1
|
||||
if len(tmp) > stream.seekTableSize {
|
||||
m = len(tmp) / stream.seekTableSize
|
||||
}
|
||||
points := make([]meta.SeekPoint, 0, stream.seekTableSize+1)
|
||||
for i, p := range tmp {
|
||||
if i%m == 0 {
|
||||
points = append(points, p)
|
||||
}
|
||||
}
|
||||
|
||||
stream.seekTable = &meta.SeekTable{Points: points}
|
||||
|
||||
_, err = rs.Seek(pos, io.SeekStart)
|
||||
|
|
29
flac_test.go
29
flac_test.go
|
@ -37,20 +37,17 @@ func TestSeek(t *testing.T) {
|
|||
// {SampleNum:40960 Offset:89596 NSamples:2723}
|
||||
|
||||
testPos := []struct {
|
||||
seek int64
|
||||
whence int
|
||||
expected int64
|
||||
seek uint64
|
||||
expected uint64
|
||||
err string
|
||||
}{
|
||||
{seek: 0, whence: io.SeekStart, expected: 0},
|
||||
{seek: 9000, whence: io.SeekStart, expected: 8192},
|
||||
{seek: 0, whence: io.SeekStart, expected: 0},
|
||||
{seek: -6000, whence: io.SeekEnd, expected: 36864},
|
||||
{seek: -8000, whence: io.SeekCurrent, expected: 32768},
|
||||
{seek: 8000, whence: io.SeekCurrent, expected: 40960},
|
||||
{seek: 0, whence: io.SeekEnd, expected: 40960},
|
||||
{seek: 50000, whence: io.SeekStart, expected: 40960},
|
||||
{seek: 100, whence: io.SeekEnd, expected: 40960},
|
||||
{seek: -100, whence: io.SeekStart, expected: 0},
|
||||
{seek: 0, expected: 0},
|
||||
{seek: 9000, expected: 8192},
|
||||
{seek: 0, expected: 0},
|
||||
{seek: 8000, expected: 4096},
|
||||
{seek: 0, expected: 0},
|
||||
{seek: 50000, expected: 0, err: "unable to seek to sample number 50000"},
|
||||
{seek: 100, expected: 0},
|
||||
}
|
||||
|
||||
stream, err := flac.NewSeek(f)
|
||||
|
@ -60,9 +57,11 @@ func TestSeek(t *testing.T) {
|
|||
|
||||
for i, pos := range testPos {
|
||||
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
||||
p, err := stream.Seek(pos.seek, pos.whence)
|
||||
p, err := stream.Seek(pos.seek)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if err.Error() != pos.err {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if p != pos.expected {
|
||||
|
|
|
@ -624,6 +624,14 @@ func (frame *Frame) correlate() {
|
|||
}
|
||||
}
|
||||
|
||||
// SampleNumber returns the first sample number contained within the frame.
|
||||
func (frame *Frame) SampleNumber() uint64 {
|
||||
if frame.HasFixedBlockSize {
|
||||
return frame.Num * uint64(frame.BlockSize)
|
||||
}
|
||||
return frame.Num
|
||||
}
|
||||
|
||||
// unexpected returns io.ErrUnexpectedEOF if err is io.EOF, and returns err
|
||||
// otherwise.
|
||||
func unexpected(err error) error {
|
||||
|
|
Loading…
Reference in a new issue