Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
1 change: 0 additions & 1 deletion index.js

This file was deleted.

33 changes: 33 additions & 0 deletions lib/pcm.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export declare type PcmOptions = {
stereo?: boolean;
sampleRate?: number;
ffmpegPath?: string;
};
export declare type PcmSampleCallback = (value: number, channel: number) => void;
export declare type PcmEndCallback = (err: string | null, output: string | null) => void;
/**
* Takes a file containing an audio stream and returns raw PCM data
* asynchronously using ffmpeg.
* @param {String} filename file to extract audio from.
* @param {PcmOptions} options optional object holding boolean stereo and integer
* sampleRate parameters.
* @param {PcmSampleCallback} sampleCallback(sample, channel) called each time a sample
* is read. sample ranges from [-1.0...1.0] and channel is 0 for left
* channel, 1 for right channel.
* @param {PcmEndCallback} endCallback(err, output) called when all samples have been
* read or an error occurred. err is the ffmpeg error output or null,
* output is the ffmpeg output on success.
*/
export declare const getPcmData: (filename: string, options: PcmOptions | null | undefined, sampleCallback: PcmSampleCallback, endCallback: PcmEndCallback) => void;
/**
* Takes a file containing an audio stream and returns a stream that will emit
* raw PCM data asynchronously using ffmpeg.
* @param {String} filename file to extract audio from.
* @param {PcmOptions} options optional object holding boolean stereo and integer
* sampleRate parameters.
* @returns {Stream} readable stream. Emits a data event with (sample, channel)
* parameters. sample ranges from [-1.0...1.0] and channel is 0 for
* left channel, 1 for right channel. Also emits an end event with err
* and output string parameters.
*/
export declare const getPcmStream: (filename: string, options: PcmOptions) => any;
148 changes: 70 additions & 78 deletions lib/pcm.js
Original file line number Diff line number Diff line change
@@ -1,101 +1,93 @@
var spawn = require('child_process').spawn;
var stream = require('stream');

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPcmStream = exports.getPcmData = void 0;
let spawn = require('child_process').spawn;
let stream = require('stream');
/**
* Takes a file containing an audio stream and returns raw PCM data
* asynchronously using ffmpeg.
* @param {String} filename file to extract audio from.
* @param {Object} options optional object holding boolean stereo and integer
* @param {PcmOptions} options optional object holding boolean stereo and integer
* sampleRate parameters.
* @param {Function} sampleCallback(sample, channel) called each time a sample
* @param {PcmSampleCallback} sampleCallback(sample, channel) called each time a sample
* is read. sample ranges from [-1.0...1.0] and channel is 0 for left
* channel, 1 for right channel.
* @param {Function} endCallback(err, output) called when all samples have been
* read or an error occurred. err is the ffmpeg error output or null,
* @param {PcmEndCallback} endCallback(err, output) called when all samples have been
* read or an error occurred. err is the ffmpeg error output or null,
* output is the ffmpeg output on success.
*/
exports.getPcmData = function(filename, options, sampleCallback, endCallback) {
var outputStr = '';
var oddByte = null;
var channel = 0;
var gotData = false;

options = options || {};
var channels = 2;
if (typeof options.stereo !== 'undefined')
channels = (options.stereo) ? 2 : 1;
var sampleRate = 44100;
if (typeof options.sampleRate !== 'undefined')
sampleRate = options.sampleRate;
var ffmpegPath = 'ffmpeg';
if (typeof options.ffmpegPath !== 'undefined')
ffmpegPath = options.ffmpegPath;

// Extract signed 16-bit little endian PCM data with ffmpeg and pipe to
// stdout
var ffmpeg = spawn(ffmpegPath, ['-i',filename,'-f','s16le','-ac',channels,
'-acodec','pcm_s16le','-ar',sampleRate,'-y','pipe:1']);

ffmpeg.stdout.on('data', function(data) {
gotData = true;

var value;
var i = 0;
var dataLen = data.length;

// If there is a leftover byte from the previous block, combine it with the
// first byte from this block
if (oddByte !== null) {
value = ((data.readInt8(i++, true) << 8) | oddByte) / 32767.0;
sampleCallback(value, channel);
channel = ++channel % 2;
const getPcmData = (filename, options, sampleCallback, endCallback) => {
let outputStr = '';
let oddByte = null;
let channel = 0;
let gotData = false;
options = options || {};
let channels = 2;
if ((options === null || options === void 0 ? void 0 : options.stereo) !== undefined) {
channels = (options.stereo) ? 2 : 1;
}

for (; i < dataLen; i += 2) {
value = data.readInt16LE(i, true) / 32767.0;
sampleCallback(value, channel);
channel = ++channel % 2;
let sampleRate = 44100;
if (options === null || options === void 0 ? void 0 : options.sampleRate) {
sampleRate = options.sampleRate;
}

oddByte = (i < dataLen) ? data.readUInt8(i, true) : null;
});

ffmpeg.stderr.on('data', function(data) {
// Text info from ffmpeg is output to stderr
outputStr += data.toString();
});

ffmpeg.stderr.on('end', function() {
if (gotData)
endCallback(null, outputStr);
else
endCallback(outputStr, null);
});
let ffmpegPath = 'ffmpeg';
if (options === null || options === void 0 ? void 0 : options.ffmpegPath) {
ffmpegPath = options.ffmpegPath;
}
// Extract signed 16-bit little endian PCM data with ffmpeg and pipe to
// stdout
let ffmpeg = spawn(ffmpegPath, ['-i', filename, '-f', 's16le', '-ac', channels,
'-acodec', 'pcm_s16le', '-ar', sampleRate, '-y', 'pipe:1']);
ffmpeg.stdout.on('data', function (data) {
gotData = true;
let value;
let i = 0;
let dataLen = data.length;
// If there is a leftover byte from the previous block, combine it with the
// first byte from this block
if (oddByte !== null) {
value = ((data.readInt8(i++) << 8) | oddByte) / 32767.0;
sampleCallback(value, channel);
channel = ++channel % 2;
}
for (; i < dataLen; i += 2) {
value = data.readInt16LE(i) / 32767.0;
sampleCallback(value, channel);
channel = ++channel % 2;
}
oddByte = (i < dataLen) ? data.readUInt8(i) : null;
});
ffmpeg.stderr.on('data', function (data) {
// Text info from ffmpeg is output to stderr
outputStr += data.toString();
});
ffmpeg.stderr.on('end', function () {
if (gotData)
endCallback(null, outputStr);
else
endCallback(outputStr, null);
});
};

exports.getPcmData = getPcmData;
/**
* Takes a file containing an audio stream and returns a stream that will emit
* raw PCM data asynchronously using ffmpeg.
* @param {String} filename file to extract audio from.
* @param {Object} options optional object holding boolean stereo and integer
* @param {PcmOptions} options optional object holding boolean stereo and integer
* sampleRate parameters.
* @returns {Stream} readable stream. Emits a data event with (sample, channel)
* parameters. sample ranges from [-1.0...1.0] and channel is 0 for
* left channel, 1 for right channel. Also emits an end event with err
* and output string parameters.
*/
exports.getPcmStream = function(filename, options) {
var sampleStream = new stream.Stream();
sampleStream.readable = true;

exports.getPcmData(filename, options,
function(sample, channel) {
sampleStream.emit('data', sample, channel);
},
function(err, output) {
sampleStream.emit('end', err, output);
}
);

return sampleStream;
const getPcmStream = (filename, options) => {
let sampleStream = new stream.Stream();
sampleStream.readable = true;
(0, exports.getPcmData)(filename, options, function (sample, channel) {
sampleStream.emit('data', sample, channel);
}, function (err, output) {
sampleStream.emit('end', err, output);
});
return sampleStream;
};
exports.getPcmStream = getPcmStream;
49 changes: 49 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
{
"name": "pcm",
"version": "1.0.3",
"main": "./index",
"dependencies": {
"version": "1.0.4",
"main": "./lib/pcm.js",
"scripts": {
"compile": "npx tsc"
},
"devDependencies": {
}
"@types/node": "^18.11.3",
"typescript": "^4.8.4"
},
"files": [
"examples/**/*",
"lib/**/*",
"misc/**/*"
]
}
117 changes: 117 additions & 0 deletions src/pcm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
let spawn = require('child_process').spawn;
let stream = require('stream');

export type PcmOptions = {
stereo?: boolean,
sampleRate?: number,
ffmpegPath?: string,
}

export type PcmSampleCallback = (value: number, channel: number) => void;
export type PcmEndCallback = (err: string | null, output: string | null) => void;

/**
* Takes a file containing an audio stream and returns raw PCM data
* asynchronously using ffmpeg.
* @param {String} filename file to extract audio from.
* @param {PcmOptions} options optional object holding boolean stereo and integer
* sampleRate parameters.
* @param {PcmSampleCallback} sampleCallback(sample, channel) called each time a sample
* is read. sample ranges from [-1.0...1.0] and channel is 0 for left
* channel, 1 for right channel.
* @param {PcmEndCallback} endCallback(err, output) called when all samples have been
* read or an error occurred. err is the ffmpeg error output or null,
* output is the ffmpeg output on success.
*/
export const getPcmData = (
filename: string,
options: PcmOptions | null | undefined,
sampleCallback: PcmSampleCallback,
endCallback: PcmEndCallback,
) => {
let outputStr = '';
let oddByte: number | null = null;
let channel = 0;
let gotData = false;

options = options || {};
let channels = 2;
if (options?.stereo !== undefined) {
channels = (options.stereo) ? 2 : 1;
}
let sampleRate = 44100;
if (options?.sampleRate) {
sampleRate = options.sampleRate;
}
let ffmpegPath = 'ffmpeg';
if (options?.ffmpegPath) {
ffmpegPath = options.ffmpegPath;
}

// Extract signed 16-bit little endian PCM data with ffmpeg and pipe to
// stdout
let ffmpeg = spawn(ffmpegPath, ['-i',filename,'-f','s16le','-ac',channels,
'-acodec','pcm_s16le','-ar',sampleRate,'-y','pipe:1']);

ffmpeg.stdout.on('data', function(data: Buffer) {
gotData = true;

let value;
let i = 0;
let dataLen = data.length;

// If there is a leftover byte from the previous block, combine it with the
// first byte from this block
if (oddByte !== null) {
value = ((data.readInt8(i++) << 8) | oddByte) / 32767.0;
sampleCallback(value, channel);
channel = ++channel % 2;
}

for (; i < dataLen; i += 2) {
value = data.readInt16LE(i) / 32767.0;
sampleCallback(value, channel);
channel = ++channel % 2;
}

oddByte = (i < dataLen) ? data.readUInt8(i) : null;
});

ffmpeg.stderr.on('data', function(data: Buffer) {
// Text info from ffmpeg is output to stderr
outputStr += data.toString();
});

ffmpeg.stderr.on('end', function() {
if (gotData)
endCallback(null, outputStr);
else
endCallback(outputStr, null);
});
};

/**
* Takes a file containing an audio stream and returns a stream that will emit
* raw PCM data asynchronously using ffmpeg.
* @param {String} filename file to extract audio from.
* @param {PcmOptions} options optional object holding boolean stereo and integer
* sampleRate parameters.
* @returns {Stream} readable stream. Emits a data event with (sample, channel)
* parameters. sample ranges from [-1.0...1.0] and channel is 0 for
* left channel, 1 for right channel. Also emits an end event with err
* and output string parameters.
*/
export const getPcmStream = (filename: string, options: PcmOptions) => {
let sampleStream = new stream.Stream();
sampleStream.readable = true;

getPcmData(filename, options,
function(sample, channel) {
sampleStream.emit('data', sample, channel);
},
function(err, output) {
sampleStream.emit('end', err, output);
}
);
return sampleStream;
};
Loading