@qvac/decoder-audio 0.2.10 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,119 +1,413 @@
1
1
  'use strict'
2
2
 
3
3
  const QvacResponse = require('@qvac/response')
4
+ const QvacLogger = require('@qvac/logging')
5
+ const ffmpeg = require('bare-ffmpeg')
4
6
  const BaseInference = require('@qvac/infer-base/WeightsProvider/BaseInference')
5
- const { GSTDecoderInterface } = require('./gstreamer')
6
- const createStreamAccumulator = require('./utils/createStreamAccumulator')
7
- const { FORMATS_NEEDING_DECODE, SUPPORTED_AUDIO_FORMATS } = require('./constants')
8
-
9
- const END_OF_INPUT = 'end of job'
10
7
 
11
8
  /**
12
- * GSTDecoder client implementation for the Whisper transcription model
9
+ * FFmpeg-based audio decoder (single-threaded)
13
10
  */
14
- class GSTDecoder extends BaseInference {
11
+ class FFmpegDecoder extends BaseInference {
12
+ SUPPORTED_AUDIO_FORMATS = {
13
+ s16le: {
14
+ format: null, // Will be set to ffmpeg.constants.sampleFormats.S16
15
+ byteLength: 2
16
+ },
17
+ f32le: {
18
+ format: null, // Will be set to ffmpeg.constants.sampleFormats.FLT
19
+ byteLength: 4
20
+ }
21
+ }
22
+
23
+ OUTPUT_CHANNEL_LAYOUT = null // Will be set to ffmpeg.constants.channelLayouts.MONO
15
24
  /**
16
- * Creates an instance of GSTDecoder.
17
- * @constructor
18
- * @param {Object} options - Constructor options
19
- * @param {Object} options.config - environment-specific inference setup configuration
20
- * @param {*} [args] - Additional arguments passed to BaseInference
25
+ * Creates an instance of FFmpegDecoder.
26
+ * @param {Object} config - Configuration options
27
+ * @param logger - Logger instance
28
+ * @param streamIndex - Index of the stream to decode. Default: 0
29
+ * @param inputBitrate - Input audio bitrate. Default: 192000
30
+ * @param audioFormat - Output audio format. Default: 's16le'
31
+ * @param args - Additional arguments passed to BaseInference
32
+ * @param {Object} [config.streamIndex] - Index of the stream to decode (default: 0)
33
+ * @param {number} [config.inputBitrate] - Input audio bitrate (default: 192000)
34
+ * @param {string} [config.audioFormat] - Output audio format (default: 'f32le')
35
+ * @param {number} [config.sampleRate] - Output sample rate (default: 16000)
36
+ * @param {Object} [config.logger] - Logger instance
21
37
  */
38
+ constructor ({
39
+ config = {},
40
+ logger = null,
41
+ streamIndex = 0,
42
+ inputBitrate = 192000,
43
+ audioFormat = 's16le',
44
+ ...args
45
+ }) {
46
+ super({ ...args, logger })
47
+
48
+ this.config = {
49
+ streamIndex: config.streamIndex || streamIndex,
50
+ inputBitrate: config.inputBitrate || inputBitrate,
51
+ audioFormat: config.audioFormat || audioFormat,
52
+ sampleRate: config.sampleRate || 16000
53
+ }
54
+
55
+ this.logger = new QvacLogger(logger)
56
+ this.isLoaded = false
57
+ this.currentJob = null
22
58
 
23
- constructor ({ config, ...args }) {
24
- super(args)
25
- this.config = config
59
+ // Encoder delay handling
60
+ this.samplesSkipped = 0
61
+ this.totalSkipSamples = 0
26
62
  }
27
63
 
64
+ /**
65
+ * Load and initialize the decoder
66
+ */
28
67
  async load () {
29
- const config = {
30
- audioFormat: this.config.audioFormat || 'encoded',
31
- sampleRate: this.config.sampleRate || 16000
68
+ if (this.isLoaded) {
69
+ this.logger.info('FFmpegDecoder already loaded')
70
+ return
32
71
  }
33
72
 
34
- this.logger.info('Loading GSTDecoder with config:', config)
35
- this.addon = this.createAddon(config)
73
+ this.logger.info('Loading FFmpegDecoder with config:', this.config)
36
74
 
37
- await this.addon.activate()
38
- this.logger.info('GSTDecoder activated successfully')
75
+ // Initialize format constants
76
+ this.SUPPORTED_AUDIO_FORMATS.s16le.format = ffmpeg.constants.sampleFormats.S16
77
+ this.SUPPORTED_AUDIO_FORMATS.f32le.format = ffmpeg.constants.sampleFormats.FLT
78
+ this.OUTPUT_CHANNEL_LAYOUT = ffmpeg.constants.channelLayouts.MONO
79
+
80
+ // Validate audio format
81
+ if (!this.SUPPORTED_AUDIO_FORMATS[this.config.audioFormat]) {
82
+ throw new Error(`Unsupported audio format: ${this.config.audioFormat}`)
83
+ }
84
+
85
+ this.isLoaded = true
86
+ this.logger.info('FFmpegDecoder loaded successfully')
39
87
  }
40
88
 
41
- async _runInternal (audioStream) {
89
+ /**
90
+ * Unload the decoder and clean up resources
91
+ */
92
+ async unload () {
93
+ if (!this.isLoaded) {
94
+ return
95
+ }
96
+
97
+ this.logger.info('Unloading FFmpegDecoder')
98
+
99
+ this.isLoaded = false
100
+ this.currentJob = null
101
+ this.logger.info('FFmpegDecoder unloaded')
102
+ }
103
+
104
+ /**
105
+ * Run the decoder on an audio stream
106
+ * @param {Readable} audioStream - Input audio stream
107
+ * @returns {QvacResponse} Response with decoded audio
108
+ */
109
+ async run (audioStream) {
110
+ if (!this.isLoaded) {
111
+ throw new Error('Decoder not loaded. Call load() first.')
112
+ }
113
+
42
114
  this.logger.info('Starting new audio stream processing')
43
- const response = new QvacResponse({
44
- cancelHandler: () => this.addon.cancel(jobId),
45
- pauseHandler: () => this.addon.pause(),
46
- continueHandler: () => this.addon.continue()
47
- })
48
115
 
49
- const jobId = await this.addon.append({
50
- type: 'audio',
51
- input: new Uint8Array().buffer,
52
- priority: 1
116
+ const response = new QvacResponse({
117
+ cancelHandler: () => this.stop(),
118
+ pauseHandler: () => this.pause(),
119
+ continueHandler: () => this.unpause()
53
120
  })
54
121
 
55
- this.logger.info('Created new job with ID:', jobId)
56
- this._saveJobToResponseMapping(jobId, response)
122
+ this.currentJob = {
123
+ response,
124
+ audioChunks: [],
125
+ isActive: true,
126
+ isPaused: false
127
+ }
57
128
 
58
- this._handleAudioStream(audioStream).catch(err => {
129
+ // Process the audio stream
130
+ this._processStream(audioStream).catch(err => {
59
131
  this.logger.error('Error processing audio stream:', err)
60
132
  response.failed(err)
61
133
  })
134
+
62
135
  return response
63
136
  }
64
137
 
65
- async _handleAudioStream (audioStream) {
66
- this.logger.info('Starting audio stream handling')
67
- const streamAccumulator = createStreamAccumulator({
68
- onChunk: async chunk => {
69
- this.logger.debug('Processing audio chunk of size:', chunk.byteLength)
70
- await this.addon.append({
71
- type: 'audio',
72
- input: chunk.buffer,
73
- priority: 1
74
- })
75
- },
76
- onFinish: async () => {
77
- this.logger.info('Audio stream finished, sending end of input')
78
- await this.addon.append({ type: END_OF_INPUT })
138
+ _getBufferSize (inputBitrate) {
139
+ const maxBufferSize = 1024 * 1024 // 1MB max
140
+ return Math.min((inputBitrate / 8) * 4, maxBufferSize)
141
+ }
142
+
143
+ _processFrame (decoder, raw, resampler, job) {
144
+ const OUTPUT_FORMAT = this.SUPPORTED_AUDIO_FORMATS[this.config.audioFormat].format
145
+ const OUTPUT_FORMAT_BYTE_LENGTH = this.SUPPORTED_AUDIO_FORMATS[this.config.audioFormat].byteLength
146
+ const OUTPUT_SAMPLE_RATE = this.config.sampleRate
147
+
148
+ while (decoder.receiveFrame(raw)) {
149
+ const output = new ffmpeg.Frame()
150
+ output.channelLayout = this.OUTPUT_CHANNEL_LAYOUT
151
+ output.format = OUTPUT_FORMAT
152
+ output.sampleRate = OUTPUT_SAMPLE_RATE
153
+ output.nbSamples = raw.nbSamples
154
+
155
+ const samples = new ffmpeg.Samples(
156
+ output.format,
157
+ output.channelLayout.nbChannels,
158
+ output.nbSamples
159
+ )
160
+ samples.fill(output)
161
+
162
+ const count = resampler.convert(raw, output)
163
+
164
+ // Handle encoder delay by skipping initial samples
165
+ if (this.samplesSkipped < this.totalSkipSamples) {
166
+ const samplesToSkip = Math.min(count, this.totalSkipSamples - this.samplesSkipped)
167
+ this.samplesSkipped += samplesToSkip
168
+ if (samplesToSkip >= count) continue // Skip entire frame
169
+
170
+ // Skip partial frame
171
+ const skipBytes = OUTPUT_FORMAT_BYTE_LENGTH * samplesToSkip * output.channelLayout.nbChannels
172
+ const length = OUTPUT_FORMAT_BYTE_LENGTH * (count - samplesToSkip) * output.channelLayout.nbChannels
173
+ const chunk = Buffer.from(samples.data.subarray(skipBytes, skipBytes + length))
174
+ job.response.updateOutput({ outputArray: chunk })
175
+ } else {
176
+ const length = OUTPUT_FORMAT_BYTE_LENGTH * count * output.channelLayout.nbChannels
177
+ const chunk = Buffer.from(samples.data.subarray(0, length))
178
+ job.response.updateOutput({ outputArray: chunk })
79
179
  }
80
- })
180
+ }
181
+ }
182
+
183
+ _processPacket (format, packet, raw, decoder, resampler, job) {
184
+ while (format.readFrame(packet)) {
185
+ decoder.sendPacket(packet)
186
+ this._processFrame(decoder, raw, resampler, job)
187
+ packet.unref()
188
+ }
189
+ }
190
+
191
+ _processFFmpegStream (format, stream, job) {
192
+ const OUTPUT_FORMAT = this.SUPPORTED_AUDIO_FORMATS[this.config.audioFormat].format
193
+ const OUTPUT_FORMAT_BYTE_LENGTH = this.SUPPORTED_AUDIO_FORMATS[this.config.audioFormat].byteLength
194
+ const OUTPUT_SAMPLE_RATE = this.config.sampleRate
195
+
196
+ this.logger.debug('[FFmpegDecoder] Stream codec:', stream.codec, stream.codecParameters)
197
+
198
+ const packet = new ffmpeg.Packet()
199
+ const raw = new ffmpeg.Frame()
200
+
201
+ const resampler = new ffmpeg.Resampler(
202
+ stream.codecParameters.sampleRate,
203
+ stream.codecParameters.channelLayout,
204
+ stream.codecParameters.format,
205
+ OUTPUT_SAMPLE_RATE,
206
+ this.OUTPUT_CHANNEL_LAYOUT,
207
+ OUTPUT_FORMAT
208
+ )
209
+
210
+ const decoder = stream.decoder()
211
+ decoder.open()
212
+
213
+ // Auto-detect encoder delay: lossy codecs need ~400ms skipped to remove artifacts
214
+ const codecName = stream.codec.name.toLowerCase()
215
+ const SKIP_MS = {
216
+ mp3: 400,
217
+ vorbis: 400,
218
+ opus: 150,
219
+ aac: 300
220
+ }
221
+
222
+ const skipMs = SKIP_MS[codecName] || 0
223
+ this.samplesSkipped = 0
224
+ this.totalSkipSamples = Math.floor((OUTPUT_SAMPLE_RATE * skipMs) / 1000)
225
+
226
+ if (this.totalSkipSamples > 0) {
227
+ this.logger.info(`[FFmpegDecoder] Skipping ${skipMs}ms (${this.totalSkipSamples} samples) for ${codecName} to remove encoder artifacts`)
228
+ }
229
+
230
+ this._processPacket(format, packet, raw, decoder, resampler, job)
231
+
232
+ // Flush resampler
233
+ const output = new ffmpeg.Frame()
234
+ output.channelLayout = this.OUTPUT_CHANNEL_LAYOUT
235
+ output.format = OUTPUT_FORMAT
236
+ output.sampleRate = OUTPUT_SAMPLE_RATE
237
+ output.nbSamples = 1024
238
+
239
+ const samples = new ffmpeg.Samples(
240
+ output.format,
241
+ output.channelLayout.nbChannels,
242
+ output.nbSamples
243
+ )
244
+ samples.fill(output)
245
+
246
+ let flushCount
247
+ while ((flushCount = resampler.flush(output)) > 0) {
248
+ const actualLength = OUTPUT_FORMAT_BYTE_LENGTH * flushCount * output.channelLayout.nbChannels
249
+ const chunk = Buffer.from(samples.data.subarray(0, actualLength))
250
+ job.response.updateOutput({ outputArray: chunk })
251
+ }
252
+
253
+ decoder.destroy()
254
+ }
255
+
256
+ async _collectStreamData (audioStream, job) {
257
+ const chunks = []
258
+ let totalBytes = 0
81
259
 
82
260
  for await (const chunk of audioStream) {
83
- await streamAccumulator.processData(chunk)
261
+ if (!job.isActive) {
262
+ this.logger.info('[FFmpegDecoder] Job cancelled, stopping stream collection')
263
+ break
264
+ }
265
+
266
+ while (job.isPaused) {
267
+ this.logger.debug('[FFmpegDecoder] Job is paused, waiting to resume...')
268
+ await new Promise(resolve => setTimeout(resolve, 100))
269
+ }
270
+
271
+ chunks.push(chunk)
272
+ totalBytes += chunk.length
273
+ this.logger.debug(`[FFmpegDecoder] Collected chunk, total bytes: ${totalBytes}`)
84
274
  }
85
275
 
86
- await streamAccumulator.finish()
87
- this.logger.info('Audio stream handling completed')
276
+ return Buffer.concat(chunks)
88
277
  }
89
278
 
90
- async getStatus () {
91
- const status = await this.addon.status()
92
- this.logger.debug('Current status:', status)
93
- return status
279
+ async _processStream (audioStream) {
280
+ const job = this.currentJob
281
+ if (!job.isActive) {
282
+ return
283
+ }
284
+
285
+ try {
286
+ this.logger.info('[FFmpegDecoder] Starting stream processing')
287
+
288
+ // Collect all audio data from stream
289
+ const audioBuffer = await this._collectStreamData(audioStream, job)
290
+ this.logger.info(`[FFmpegDecoder] Collected ${audioBuffer.length} bytes of audio data`)
291
+
292
+ if (!job.isActive) {
293
+ this.logger.info('[FFmpegDecoder] Job cancelled after data collection')
294
+ return
295
+ }
296
+
297
+ // Create FFmpeg IO context with the buffer
298
+ const bufferSize = this._getBufferSize(this.config.inputBitrate)
299
+ let bufferOffset = 0
300
+
301
+ const io = new ffmpeg.IOContext(bufferSize, {
302
+ onread: (buffer, requestedLen) => {
303
+ const remainingBytes = audioBuffer.length - bufferOffset
304
+ const bytesToRead = Math.min(requestedLen, remainingBytes)
305
+
306
+ if (bytesToRead <= 0) {
307
+ return 0 // EOF
308
+ }
309
+
310
+ audioBuffer.copy(buffer, 0, bufferOffset, bufferOffset + bytesToRead)
311
+ bufferOffset += bytesToRead
312
+
313
+ this.logger.debug(`[FFmpegDecoder] Read ${bytesToRead} bytes from buffer, offset now: ${bufferOffset}`)
314
+ return bytesToRead
315
+ },
316
+ onseek: (offset, whence) => {
317
+ const AVSEEK_SIZE = 0x10000
318
+
319
+ if (whence === AVSEEK_SIZE) {
320
+ return audioBuffer.length
321
+ }
322
+
323
+ let newOffset
324
+ if (whence === 0) {
325
+ newOffset = offset
326
+ } else if (whence === 1) {
327
+ newOffset = bufferOffset + offset
328
+ } else if (whence === 2) {
329
+ newOffset = audioBuffer.length + offset
330
+ } else {
331
+ return -1
332
+ }
333
+
334
+ if (newOffset < 0 || newOffset > audioBuffer.length) {
335
+ return -1
336
+ }
337
+
338
+ bufferOffset = newOffset
339
+ this.logger.debug(`[FFmpegDecoder] Seek to offset: ${bufferOffset}`)
340
+ return bufferOffset
341
+ }
342
+ })
343
+
344
+ this.logger.debug('[FFmpegDecoder] IOContext created')
345
+ const format = new ffmpeg.InputFormatContext(io)
346
+ this.logger.debug('[FFmpegDecoder] InputFormatContext created')
347
+
348
+ const streamIndex = this.config.streamIndex || 0
349
+ if (format.streams[streamIndex] === undefined) {
350
+ throw new Error('Stream index out of bounds')
351
+ }
352
+
353
+ // Process the stream and generate decoded output
354
+ this._processFFmpegStream(format, format.streams[streamIndex], job)
355
+
356
+ // Mark as complete
357
+ job.response.ended()
358
+ this.logger.info('[FFmpegDecoder] Stream processing completed successfully')
359
+ } catch (err) {
360
+ this.logger.error('Error processing audio stream:', err)
361
+ job.response.failed(err)
362
+ }
363
+
364
+ this.logger.info('Audio _processStream completed')
94
365
  }
95
366
 
96
- async unload () {
97
- if (this.addon) {
98
- this.logger.info('Destroying GSTDecoder addon')
99
- await this.addon.destroy()
100
- this.addon = null
367
+ /**
368
+ * Pause the current job
369
+ */
370
+ pause () {
371
+ if (this.currentJob) {
372
+ this.currentJob.isPaused = true
373
+ this.logger.debug('Decoder paused')
101
374
  }
375
+ return Promise.resolve()
102
376
  }
103
377
 
104
- createAddon (config) {
105
- this.logger.info('Creating new GSTDecoderInterface instance')
106
- return new GSTDecoderInterface(
107
- config,
108
- this._outputCallback.bind(this),
109
- this.logger.info.bind(this.logger)
110
- )
378
+ /**
379
+ * Unpause the current job
380
+ */
381
+ unpause () {
382
+ if (this.currentJob) {
383
+ this.currentJob.isPaused = false
384
+ this.logger.debug('Decoder unpaused')
385
+ }
386
+ return Promise.resolve()
111
387
  }
112
- }
113
388
 
114
- const FFmpegDecoder = require('./lib/ffmpeg/ffmpeg-decoder')
389
+ /**
390
+ * Stop the current job
391
+ */
392
+ stop () {
393
+ if (this.currentJob) {
394
+ this.currentJob.isActive = false
395
+ this.currentJob.response.finish()
396
+ this.logger.debug('Decoder stopped')
397
+ }
398
+ return Promise.resolve()
399
+ }
400
+
401
+ /**
402
+ * Get the current status
403
+ */
404
+ status () {
405
+ return {
406
+ loaded: this.isLoaded,
407
+ active: this.currentJob?.isActive || false,
408
+ paused: this.currentJob?.isPaused || false
409
+ }
410
+ }
411
+ }
115
412
 
116
- module.exports = GSTDecoder
117
- module.exports.FORMATS_NEEDING_DECODE = FORMATS_NEEDING_DECODE
118
- module.exports.SUPPORTED_AUDIO_FORMATS = SUPPORTED_AUDIO_FORMATS
119
- module.exports.FFmpegDecoder = FFmpegDecoder
413
+ module.exports = { FFmpegDecoder }
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@qvac/decoder-audio",
3
- "version": "0.2.10",
3
+ "version": "0.3.1",
4
4
  "description": "",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Tether",
7
7
  "type": "commonjs",
8
8
  "main": "index.js",
9
- "addon": true,
10
9
  "scripts": {
11
10
  "test:dts": "tsc index.d.ts --noEmit --esModuleInterop --skipLibCheck",
12
11
  "test:unit": "brittle-bare test/unit/*.test.js",
@@ -18,14 +17,10 @@
18
17
  "test:integration": "brittle-bare test/integration/*.test.js"
19
18
  },
20
19
  "files": [
21
- "binding.js",
22
20
  "index.js",
23
- "gstreamer.js",
24
21
  "constants.js",
25
22
  "constants.d.ts",
26
- "prebuilds",
27
23
  "utils",
28
- "lib",
29
24
  "index.d.ts",
30
25
  "test/mobile"
31
26
  ],
@@ -38,8 +33,6 @@
38
33
  "devDependencies": {
39
34
  "@types/node": "^22.14.1",
40
35
  "brittle": "^3.13.1",
41
- "cmake-bare": "^1.7.5",
42
- "cmake-vcpkg": "^1.1.0",
43
36
  "istanbul": "^0.4.5",
44
37
  "standard": "^17.1.2",
45
38
  "typescript": "^5.3.0"
package/utils/error.js CHANGED
@@ -40,7 +40,7 @@ addCodes({
40
40
  },
41
41
  [ERR_CODES.FAILED_TO_GET_STATUS]: {
42
42
  name: 'FAILED_TO_GET_STATUS',
43
- message: (message) => `Failed to get addon status, error: ${message}`
43
+ message: (message) => `Failed to get decoder status, error: ${message}`
44
44
  },
45
45
  [ERR_CODES.FAILED_TO_DESTROY]: {
46
46
  name: 'FAILED_TO_DESTROY',
package/binding.js DELETED
@@ -1,3 +0,0 @@
1
- 'use strict'
2
-
3
- module.exports = require.addon()
package/gstreamer.js DELETED
@@ -1,131 +0,0 @@
1
- 'use strict'
2
-
3
- const binding = require('./binding')
4
- const { QvacErrorDecoderAudio, ERR_CODES } = require('./utils/error')
5
-
6
- /**
7
- * An interface between Bare addon in C++ and JS runtime.
8
- */
9
- class GSTDecoderInterface {
10
- /**
11
- *
12
- * @param {Object} configurationParams - all the required configuration for inference setup
13
- * @param {Function} outputCb - to be called on any inference event ( started, new output, error, etc )
14
- * @param {Function} transitionCb - to be called on addon state changes (LISTENING, IDLE, STOPPED, etc )
15
- */
16
- constructor (configurationParams, outputCb, transitionCb = null) {
17
- this._handle = binding.createInstance(this, configurationParams, outputCb, transitionCb)
18
- }
19
-
20
- /**
21
- *
22
- * @param {Object} weightsData
23
- * @param {String} weightsData.filename
24
- * @param {Uint8Array} weightsData.contents
25
- * @param {Boolean} weightsData.completed
26
- */
27
- async loadWeights (weightsData) {
28
- try {
29
- binding.loadWeights(this._handle, weightsData)
30
- } catch (err) {
31
- throw new QvacErrorDecoderAudio(
32
- ERR_CODES.FAILED_TO_LOAD_WEIGHTS,
33
- err.message
34
- )
35
- }
36
- }
37
-
38
- /**
39
- * Moves addon to the LISTENING state after all the initialization is done
40
- */
41
- async activate () {
42
- try {
43
- binding.activate(this._handle)
44
- } catch (err) {
45
- throw new QvacErrorDecoderAudio(
46
- ERR_CODES.FAILED_TO_ACTIVATE,
47
- err.message
48
- )
49
- }
50
- }
51
-
52
- /**
53
- * Pauses current inference process
54
- */
55
- async pause () {
56
- try {
57
- binding.pause(this._handle)
58
- } catch (err) {
59
- throw new QvacErrorDecoderAudio(
60
- ERR_CODES.FAILED_TO_PAUSE,
61
- err.message
62
- )
63
- }
64
- }
65
-
66
- /**
67
- * Cancel a inference process by jobId, if no jobId is provided it cancel the whole queue
68
- */
69
- async cancel (jobId) {
70
- try {
71
- binding.cancel(this._handle, jobId)
72
- } catch (err) {
73
- throw new QvacErrorDecoderAudio(
74
- ERR_CODES.FAILED_TO_CANCEL,
75
- err.message
76
- )
77
- }
78
- }
79
-
80
- /**
81
- * Adds new input to the processing queue
82
- * @param {Object} data
83
- * @param {String} data.type
84
- * @param {String} data.input
85
- * @returns {Number} - job ID
86
- */
87
- async append (data) {
88
- try {
89
- return binding.append(this._handle, data)
90
- } catch (err) {
91
- throw new QvacErrorDecoderAudio(
92
- ERR_CODES.FAILED_TO_APPEND,
93
- err.message
94
- )
95
- }
96
- }
97
-
98
- /**
99
- * Addon process status
100
- * @returns {String}
101
- */
102
- async status () {
103
- try {
104
- return binding.status(this._handle)
105
- } catch (err) {
106
- throw new QvacErrorDecoderAudio(
107
- ERR_CODES.FAILED_TO_GET_STATUS,
108
- err.message
109
- )
110
- }
111
- }
112
-
113
- /**
114
- * Stops addon process and clears resources (including memory).
115
- */
116
- async destroy () {
117
- try {
118
- binding.destroyInstance(this._handle)
119
- this._handle = null
120
- } catch (err) {
121
- throw new QvacErrorDecoderAudio(
122
- ERR_CODES.FAILED_TO_DESTROY,
123
- err.message
124
- )
125
- }
126
- }
127
- }
128
-
129
- module.exports = {
130
- GSTDecoderInterface
131
- }