ogg-opus-decoder
Advanced tools
Comparing version 1.6.6 to 1.6.7
{ | ||
"name": "ogg-opus-decoder", | ||
"version": "1.6.6", | ||
"version": "1.6.7", | ||
"description": "Web Assembly streaming Ogg Opus decoder", | ||
@@ -5,0 +5,0 @@ "type": "module", |
# `ogg-opus-decoder` | ||
`ogg-opus-decoder` is a Web Assembly Ogg Opus audio decoder. | ||
* 108.2 KiB minified bundle size | ||
* 108.0 KiB minified bundle size | ||
* Browser and NodeJS support | ||
@@ -6,0 +6,0 @@ * Built in Web Worker support |
@@ -14,102 +14,5 @@ import { WASMAudioDecoderCommon } from "@wasm-audio-decoders/common"; | ||
samples, | ||
data, | ||
} from "codec-parser"; | ||
class DecoderState { | ||
constructor(instance) { | ||
this._instance = instance; | ||
this._sampleRate = this._instance._sampleRate; | ||
this._decoderOperations = []; | ||
this._errors = []; | ||
this._decoded = []; | ||
this._channelsDecoded = 0; | ||
this._totalSamples = 0; | ||
} | ||
get decoded() { | ||
return this._instance.ready | ||
.then(() => Promise.all(this._decoderOperations)) | ||
.then(() => [ | ||
this._errors, | ||
this._decoded, | ||
this._channelsDecoded, | ||
this._totalSamples, | ||
this._sampleRate, | ||
]); | ||
} | ||
async _instantiateDecoder(header) { | ||
this._preSkip = header[preSkip]; | ||
this._instance._decoder = new this._instance._decoderClass({ | ||
channels: header[channels], | ||
streamCount: header[streamCount], | ||
coupledStreamCount: header[coupledStreamCount], | ||
channelMappingTable: header[channelMappingTable], | ||
preSkip: Math.round((this._preSkip / 48000) * this._sampleRate), | ||
sampleRate: this._sampleRate, | ||
forceStereo: this._instance._forceStereo, | ||
}); | ||
this._instance._ready = this._instance._decoder.ready; | ||
} | ||
async _sendToDecoder(oggPage) { | ||
const dataFrames = oggPage[codecFrames].map((f) => f.data); | ||
const { channelData, samplesDecoded, errors } = | ||
await this._instance._decoder.decodeFrames(dataFrames); | ||
this._totalSamples += samplesDecoded; | ||
if ( | ||
this._beginningSampleOffset === undefined && | ||
Number(oggPage[absoluteGranulePosition]) > -1 | ||
) { | ||
this._beginningSampleOffset = | ||
oggPage[absoluteGranulePosition] - | ||
BigInt(oggPage[samples]) + | ||
BigInt(this._preSkip); | ||
} | ||
// in cases where BigInt isn't supported, don't do any absoluteGranulePosition logic (i.e. old iOS versions) | ||
if (oggPage[isLastPage] && oggPage[absoluteGranulePosition] !== undefined) { | ||
const totalDecodedSamples = | ||
(this._totalSamples / this._sampleRate) * 48000; | ||
const totalOggSamples = Number( | ||
oggPage[absoluteGranulePosition] - this._beginningSampleOffset, | ||
); | ||
// trim any extra samples that are decoded beyond the absoluteGranulePosition, relative to where we started in the stream | ||
const samplesToTrim = Math.round( | ||
((totalDecodedSamples - totalOggSamples) / 48000) * this._sampleRate, | ||
); | ||
for (let i = 0; i < channelData.length; i++) | ||
channelData[i] = channelData[i].subarray( | ||
0, | ||
samplesDecoded - samplesToTrim, | ||
); | ||
this._totalSamples -= samplesToTrim; | ||
} | ||
this._decoded.push(channelData); | ||
this._errors = this._errors.concat(errors); | ||
this._channelsDecoded = channelData.length; | ||
} | ||
async _decode(oggPage) { | ||
const frames = oggPage[codecFrames]; | ||
if (frames.length) { | ||
if (!this._instance._decoder && frames[0][header]) | ||
this._instantiateDecoder(frames[0][header]); | ||
await this._instance.ready; | ||
this._decoderOperations.push(this._sendToDecoder(oggPage)); | ||
} | ||
} | ||
} | ||
export default class OggOpusDecoder { | ||
@@ -146,2 +49,20 @@ constructor(options = {}) { | ||
async _instantiateDecoder(header) { | ||
this._totalSamplesDecoded = 0; | ||
this._preSkip = header[preSkip]; | ||
this._channels = this._forceStereo ? 2 : header[channels]; | ||
this._beginningSampleOffset = null; | ||
this._decoder = new this._decoderClass({ | ||
channels: header[channels], | ||
streamCount: header[streamCount], | ||
coupledStreamCount: header[coupledStreamCount], | ||
channelMappingTable: header[channelMappingTable], | ||
preSkip: Math.round((this._preSkip / 48000) * this._sampleRate), | ||
sampleRate: this._sampleRate, | ||
forceStereo: this._forceStereo, | ||
}); | ||
await this._decoder.ready; | ||
} | ||
get ready() { | ||
@@ -159,42 +80,106 @@ return this._ready; | ||
async _flush(decoderState) { | ||
for (const oggPage of this._codecParser.flush()) { | ||
decoderState._decode(oggPage); | ||
} | ||
async _decode(oggPages) { | ||
let allErrors = [], | ||
allChannelData = [], | ||
samplesThisDecode = 0; | ||
const decoded = await decoderState.decoded; | ||
this._init(); | ||
for await (const oggPage of oggPages) { | ||
// only decode Ogg pages that have codec frames | ||
const frames = oggPage[codecFrames].map((f) => f[data]); | ||
return decoded; | ||
} | ||
if (frames.length) { | ||
// wait until there is an Opus header before instantiating | ||
if (!this._decoder) | ||
await this._instantiateDecoder(oggPage[codecFrames][0][header]); | ||
async _decode(oggOpusData, decoderState) { | ||
for (const oggPage of this._codecParser.parseChunk(oggOpusData)) { | ||
decoderState._decode(oggPage); | ||
const { channelData, samplesDecoded, errors } = | ||
await this._decoder.decodeFrames(frames); | ||
this._totalSamplesDecoded += samplesDecoded; | ||
// record beginning sample offset for absoluteGranulePosition logic | ||
if ( | ||
this._beginningSampleOffset === null && | ||
Number(oggPage[absoluteGranulePosition]) > -1 | ||
) { | ||
this._beginningSampleOffset = | ||
oggPage[absoluteGranulePosition] - | ||
BigInt(oggPage[samples]) + | ||
BigInt(this._preSkip); | ||
} | ||
if (oggPage[isLastPage]) { | ||
// in cases where BigInt isn't supported, don't do any absoluteGranulePosition logic (i.e. old iOS versions) | ||
if (oggPage[absoluteGranulePosition] !== undefined) { | ||
const totalDecodedSamples_48000 = | ||
(this._totalSamplesDecoded / this._sampleRate) * 48000; | ||
const totalOggSamples_48000 = Number( | ||
oggPage[absoluteGranulePosition] - this._beginningSampleOffset, | ||
); | ||
// trim any extra samples that are decoded beyond the absoluteGranulePosition, relative to where we started in the stream | ||
const samplesToTrim = Math.round( | ||
((totalDecodedSamples_48000 - totalOggSamples_48000) / 48000) * | ||
this._sampleRate, | ||
); | ||
for (let i = 0; i < channelData.length; i++) { | ||
channelData[i] = channelData[i].subarray( | ||
0, | ||
samplesDecoded - samplesToTrim, | ||
); | ||
} | ||
samplesThisDecode -= samplesToTrim; | ||
} | ||
// reached the end of an ogg stream, reset the decoder | ||
this._init(); | ||
} | ||
allErrors.push(...errors); | ||
allChannelData.push(channelData); | ||
samplesThisDecode += samplesDecoded; | ||
} | ||
} | ||
return decoderState.decoded; | ||
return [ | ||
allErrors, | ||
allChannelData, | ||
this._channels, | ||
samplesThisDecode, | ||
this._sampleRate, | ||
16, | ||
]; | ||
} | ||
_parse(oggOpusData) { | ||
return [...this._codecParser.parseChunk(oggOpusData)]; | ||
} | ||
_flush() { | ||
return [...this._codecParser.flush()]; | ||
} | ||
async decode(oggOpusData) { | ||
return WASMAudioDecoderCommon.getDecodedAudioMultiChannel( | ||
...(await this._decode(oggOpusData, new DecoderState(this))), | ||
); | ||
const decoded = await this._decode(this._parse(oggOpusData)); | ||
return WASMAudioDecoderCommon.getDecodedAudioMultiChannel(...decoded); | ||
} | ||
async decodeFile(oggOpusData) { | ||
const decoderState = new DecoderState(this); | ||
const decoded = await this._decode([ | ||
...this._parse(oggOpusData), | ||
...this._flush(), | ||
]); | ||
this._init(); | ||
return WASMAudioDecoderCommon.getDecodedAudioMultiChannel( | ||
...(await this._decode(oggOpusData, decoderState).then(() => | ||
this._flush(decoderState), | ||
)), | ||
); | ||
return WASMAudioDecoderCommon.getDecodedAudioMultiChannel(...decoded); | ||
} | ||
async flush() { | ||
return WASMAudioDecoderCommon.getDecodedAudioMultiChannel( | ||
...(await this._flush(new DecoderState(this))), | ||
); | ||
const decoded = await this._decode(this._flush()); | ||
this._init(); | ||
return WASMAudioDecoderCommon.getDecodedAudioMultiChannel(...decoded); | ||
} | ||
} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
193224
732