Skip to content

Commit 58e2b6c

Browse files
committed
feat: add support for chained streams in decoders
1 parent e2ea5eb commit 58e2b6c

File tree

6 files changed

+73
-0
lines changed

6 files changed

+73
-0
lines changed

lib/decoder/file-decoder.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface FileDecoderOptions extends DecoderOptions {
1313
* @emits metadata When a metadata block is received, the event will be fired
1414
* @emits format When the decoder knows the exact format of the flac
1515
* @emits flac-error When there is an error on the decoding
16+
* @emits end-link When chained decoding is enabled and a link finishes
1617
*/
1718
export default class FileDecoder extends Readable implements BaseDecoder {
1819
constructor(options: FileDecoderOptions);

lib/decoder/file-decoder.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ class FileDecoder extends Readable {
3131
this._builder.setMetadataRespond(type)
3232
}
3333
}
34+
35+
if (options.isChainedStream) {
36+
this._builder.setDecodeChainedStream(true)
37+
}
3438
}
3539

3640
get processedSamples() {
@@ -148,6 +152,10 @@ class FileDecoder extends Readable {
148152
await this._dec.finishAsync()
149153
this.push(null)
150154
this._dec = null
155+
} else if (decState === flac.Decoder.State.END_OF_LINK) {
156+
this._debug('End of link reached')
157+
await this._dec.finishLinkAsync()
158+
this.emit('end-link')
151159
} else if (!couldProcess && !this.destroyed) {
152160
this._throwDecoderError()
153161
return

lib/decoder/interfaces.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { format } from '../api.js'
44
export interface DecoderOptions {
55
/** If set to true, the input must be an Ogg/FLAC stream. */
66
isOggStream?: boolean
7+
/** Enables chaining decoding. Emits `end-link` when a link is finished. */
8+
isChainedStream?: boolean
79
/**
810
* If set to true, it will emit the `metadata` for each metadata block.
911
* If set to an array, it will only emit `metadata` for the types

lib/decoder/stream-decoder.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { BaseDecoder, DecoderOptions, DecoderPosition } from './interfaces.js'
77
* @emits metadata When a metadata block is received, the event will be fired
88
* @emits format When the decoder knows the exact format of the flac
99
* @emits flac-error When there is an error on the decoding
10+
* @emits end-link When chained decoding is enabled and a link finishes
1011
*/
1112
export default class StreamDecoder extends Transform implements BaseDecoder {
1213
constructor(props: DecoderOptions)

lib/decoder/stream-decoder.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ class StreamDecoder extends stream.Transform {
3131
this._builder.setMetadataRespond(type)
3232
}
3333
}
34+
35+
if (options.isChainedStream) {
36+
this._builder.setDecodeChainedStream(true)
37+
}
3438
}
3539

3640
get processedSamples() {
@@ -166,6 +170,14 @@ class StreamDecoder extends stream.Transform {
166170
}
167171
this._readCallback = null
168172

173+
// if chained is enabled and the decoder is in end of link, then finish link
174+
// and prepare the decoder for the next link
175+
if (this._dec.getState() === flac.Decoder.State.END_OF_LINK) {
176+
this._debug('End of link reached')
177+
await this._dec.finishLinkAsync()
178+
this.emit('end-link')
179+
}
180+
169181
// if the call has been blocked, then it does not need to do anything else
170182
if (this._hasBeenBlocked) {
171183
this._debug('processSingle was blocked, stopping loop for this processing')

test/encode-decode/js-streams.test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,29 @@ describe('encode & decode: js streams', () => {
133133
comparePCM(okData, raw, 24)
134134
})
135135

136+
it('decode using stream (chained ogg)', async () => {
137+
const input = fs.createReadStream(pathForFile('chained.oga'))
138+
const dec = new StreamDecoder({ outputAs32: false, isOggStream: true, isChainedStream: true })
139+
const chunks = []
140+
let endLinks = 0
141+
let metadata = 0
142+
143+
input.pipe(dec)
144+
dec.on('data', (chunk) => chunks.push(chunk))
145+
dec.on('end-link', () => { endLinks += 1 })
146+
dec.on('metadata', () => { metadata += 1 })
147+
await events.once(dec, 'end')
148+
149+
const raw = Buffer.concat(chunks)
150+
expect(raw).toHaveLength(totalSamples * 3 * 2 * 3)
151+
expect(dec.processedSamples).toStrictEqual(totalSamples * 3)
152+
expect(endLinks).toBe(2)
153+
expect(metadata).toBe(3)
154+
comparePCM(okData, raw.subarray(0, okData.byteLength), 24)
155+
comparePCM(okData, raw.subarray(okData.byteLength, okData.byteLength * 2), 24)
156+
comparePCM(okData, raw.subarray(okData.byteLength * 2), 24)
157+
})
158+
136159
it('encode using stream and file-bit input', async () => {
137160
const output = fs.createWriteStream(tmpFile.path)
138161
const enc = new StreamEncoder({
@@ -442,6 +465,32 @@ describe('encode & decode: js streams', () => {
442465
comparePCM(okData, raw, 24)
443466
})
444467

468+
it('decode using file (chained ogg)', async () => {
469+
const dec = new FileDecoder({
470+
outputAs32: false,
471+
file: pathForFile('chained.oga'),
472+
isOggStream: true,
473+
isChainedStream: true,
474+
})
475+
const chunks = []
476+
let endLinks = 0
477+
let metadata = 0
478+
479+
dec.on('data', (chunk) => chunks.push(chunk))
480+
dec.on('end-link', () => { endLinks += 1 })
481+
dec.on('metadata', () => { metadata += 1 })
482+
await events.once(dec, 'end')
483+
484+
const raw = Buffer.concat(chunks)
485+
expect(raw).toHaveLength(totalSamples * 3 * 2 * 3)
486+
expect(dec.processedSamples).toStrictEqual(totalSamples * 3)
487+
expect(endLinks).toBe(2)
488+
expect(metadata).toBe(3)
489+
comparePCM(okData, raw.subarray(0, okData.byteLength), 24)
490+
comparePCM(okData, raw.subarray(okData.byteLength, okData.byteLength * 2), 24)
491+
comparePCM(okData, raw.subarray(okData.byteLength * 2), 24)
492+
})
493+
445494
it('encode using file and 24-bit input', async () => {
446495
const file = tmpFile.path
447496
const enc = new FileEncoder({

0 commit comments

Comments
 (0)