Skip to content

Commit

Permalink
Microphone support
Browse files Browse the repository at this point in the history
Signed-off-by: Rick Waldron <waldron.rick@gmail.com>
  • Loading branch information
rwaldron committed Apr 13, 2016
1 parent 2384531 commit dedca19
Show file tree
Hide file tree
Showing 8 changed files with 356 additions and 4 deletions.
50 changes: 50 additions & 0 deletions arecord.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
```
Usage: arecord [OPTION]... [FILE]...
-h, --help help
--version print current version
-l, --list-devices list all soundcards and digital audio devices
-L, --list-pcms list device names
-D, --device=NAME select PCM by name
-q, --quiet quiet mode
-t, --file-type TYPE file type (voc, wav, raw or au)
-c, --channels=# channels
-f, --format=FORMAT sample format (case insensitive)
-r, --rate=# sample rate
-d, --duration=# interrupt after # seconds
-M, --mmap mmap stream
-N, --nonblock nonblocking mode
-F, --period-time=# distance between interrupts is # microseconds
-B, --buffer-time=# buffer duration is # microseconds
--period-size=# distance between interrupts is # frames
--buffer-size=# buffer duration is # frames
-A, --avail-min=# min available space for wakeup is # microseconds
-R, --start-delay=# delay for automatic PCM start is # microseconds
(relative to buffer size if <= 0)
-T, --stop-delay=# delay for automatic PCM stop is # microseconds from xrun
-v, --verbose show PCM structure and setup (accumulative)
-V, --vumeter=TYPE enable VU meter (TYPE: mono or stereo)
-I, --separate-channels one file for each channel
-i, --interactive allow interactive operation from stdin
-m, --chmap=ch1,ch2,.. Give the channel map to override or follow
--disable-resample disable automatic rate resample
--disable-channels disable automatic channel conversions
--disable-format disable automatic format conversions
--disable-softvol disable software volume control (softvol)
--test-position test ring buffer position
--test-coef=# test coefficient for ring buffer position (default 8)
expression for validation is: coef * (buffer_size / 2)
--test-nowait do not wait for ring buffer - eats whole CPU
--max-file-time=# start another output file when the old file has recorded
for this many seconds
--process-id-file write the process ID here
--use-strftime apply the strftime facility to the output file name
--dump-hw-params dump hw_params of the device
--fatal-errors treat all errors as fatal
Recognized sample formats are: S8 U8 S16_LE S16_BE U16_LE U16_BE S24_LE S24_BE U24_LE U24_BE S32_LE S32_BE U32_LE U32_BE FLOAT_LE FLOAT_BE FLOAT64_LE FLOAT64_BE IEC958_SUBFRAME_LE IEC958_SUBFRAME_BE MU_LAW A_LAW IMA_ADPCM MPEG GSM SPECIAL S24_3LE S24_3BE U24_3LE U24_3BE S20_3LE S20_3BE U20_3LE U20_3BE S18_3LE S18_3BE U18_3LE U18_3BE G723_24 G723_24_1B G723_40 G723_40_1B DSD_U8 DSD_U16_LE
Some of these may not be available on selected hardware
The available format shortcuts are:
-f cd (16 bit little endian, 44100, stereo)
-f cdr (16 bit big endian, 44100, stereo)
-f dat (16 bit little endian, 48000, stereo)
```
4 changes: 2 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require('./array-includes-shim');

var Camera = require('./camera');
var Microphone = require('./microphone');
var Player = require('./player');
var Speaker = require('./speaker');

Expand All @@ -18,6 +19,5 @@ module.exports = {
install('madplay');
return new Speaker(args);
},
// TODO...
Microphone: null,
Microphone: Microphone,
};
2 changes: 2 additions & 0 deletions lib/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ var cp = require('child_process');

var profile = '/etc/profile';
var installed = {
aplay: true,
arecord: true,
espeak: false,
fswebcam: true,
ffmpeg: true,
Expand Down
204 changes: 204 additions & 0 deletions lib/microphone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// Dependencies: Built-in
var cp = require('child_process');
var Emitter = require('events').EventEmitter;

// Dependencies: Internal
var CaptureStream = require('./capture-stream');

// Program specific
var priv = new WeakMap();

function Microphone(options) {
Emitter.call(this);

options = options || {};

var state = {
arecord: null,
aplay: null,
currentTime: 0,
isListening: false,
interval: null,
cs: new CaptureStream(),
time: null,
};

priv.set(this, state);

Object.defineProperties(this, {
currentTime: {
get: () => Number(state.currentTime.toFixed(3)),
},
isListening: {
get: () => state.isListening,
},
});
}

Microphone.prototype = Object.create(Emitter.prototype, {
constructor: {
value: Microphone
}
});

var defaults = ['-f', 'cd', '-c', '1', '-r', '41100', '-D', 'plughw:0,0'];

Microphone.prototype.listen = function(options) {
var state = priv.get(this);
var args = [];
var offset = null;
var time = 0;

if (state.isListening) {
return state.cs;
}

options = options || {};

if (Array.isArray(options)) {
// mic.listen(['-f', 'cd', '-c', 1, '-r', 41100 ]);
args = options;
} else {
// Don't need to check for null, since those are handled above
if (typeof options === 'object') {
// mic.listen({
// ...
// });
//
args = Object.keys(options).reduce((accum, key) => {
var value = options[key];
var option = '';

if (key.length === 1) {
// mic.listen({
// c: 1,
// f: 'cd',
// });
//
option = '-';
}

// mic.listen({
// '-c': 1,
// '-f': 'cd',
// });
//
option += key;

accum.push(option, value);

return accum;
}, []);
}
}

if (args.length === 0) {
args = defaults.slice();
}

if (state.arecord === null) {
state.isListening = true;
state.currentTime = 0;
state.startTime = Date.now();
state.interval = setInterval(() => {
var now = Date.now();
if (offset === null) {
offset = now - state.startTime;
}
state.currentTime = time + (now - state.startTime - offset) / 1000;

this.emit('timeupdate', this.currentTime);
}, 100);

// Apply some reasonable defaults...
Object.keys(arecord.options).forEach(key => {
if (!args.includes(key)) {
args.push(key, arecord.options[key]);
}
});

state.arecord = cp.spawn('arecord', args);

if (this.debug) {
state.arecord.stderr.on('data', (data) => {
var lines = data.toString().split('\n').filter(Boolean).map(line => line.trim());
lines.forEach(line => {
console.error(line);
});
});
}

state.arecord.stdout.on('data', (data) => {
state.cs.push(data);
this.emit('data', data);
this.emit('timeupdate', this.currentTime);
});

state.arecord.on('close', (code, signal) => {
if (code !== null && signal === null) {
if (state.interval) {
clearInterval(state.interval);
}
state.arecord = null;
state.currentTime = 0;
state.startTime = 0;
state.isListening = false;

this.emit('close');
}
});

this.emit('listen');
}

return state.cs;
};

Microphone.prototype.monitor = function(catpureStream) {
var state = priv.get(this);

if (state.isListening) {
state.aplay = cp.spawn('aplay', ['-D', 'plughw:0,0']);
}

if (catpureStream) {
return catpureStream.pipe(state.aplay.stdin);
} else {
return state.cs.pipe(state.aplay.stdin);
}
};

Microphone.prototype.stop = function() {
var state = priv.get(this);
if (!state.isListening) {
return this;
}
state.isListening = false;
if (state.interval) {
clearInterval(state.interval);
}
if (state.arecord) {
state.arecord.kill('SIGTERM');
state.arecord = null;
}
if (state.aplay) {
state.aplay.kill('SIGTERM');
state.aplay = null;
}
this.emit('stop');
return this;
};


var arecord = {
options: {
'-f': 'cd',
'-c': 1,
'-r': 41100,
'-D': 'plughw:0,0',
},
};

arecord.keys = Object.keys(arecord.options);

module.exports = Microphone;
5 changes: 3 additions & 2 deletions test/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@
"Writable": true,
"bindings": true,
"sinon": true,
"MemoryStream": true,
"CaptureStream": true,
"av": true,
"Camera": true,
"Player": true,
"Speaker": true
"Speaker": true,
"Microphone": true
}
}
1 change: 1 addition & 0 deletions test/common/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ global.sinon = require('sinon');
global.av = require('../../lib/index');
global.CaptureStream = require('../../lib/capture-stream');
global.Camera = require('../../lib/camera');
global.Microphone = require('../../lib/microphone');
global.Player = require('../../lib/player');
global.Speaker = require('../../lib/speaker');

Expand Down
2 changes: 2 additions & 0 deletions test/unit/camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ exports['av.Camera'] = {
});

capture.on('end', function() {
cam.stop();
test.done();
});

Expand All @@ -69,6 +70,7 @@ exports['av.Camera'] = {

writable.on('pipe', () => {
test.ok(true);
cam.stop();
test.done();
});

Expand Down

0 comments on commit dedca19

Please sign in to comment.