Skip to content

Commit

Permalink
recording: use MediaRecorder and ffmpeg plus more improvements
Browse files Browse the repository at this point in the history
- glitch free audio recording! (maybe)
- improved preroll management and disk streaming
- merged in audio-timeline package
- new streaming based on pre-sliced clips on disk
- export to 32bit wav (instead of 16bit)
- finish recording before closing window

closes #172
closes #153
  • Loading branch information
mmckegg committed Aug 2, 2016
1 parent be534cf commit a0e19dd
Show file tree
Hide file tree
Showing 21 changed files with 954 additions and 198 deletions.
3 changes: 1 addition & 2 deletions README.md
Expand Up @@ -103,12 +103,11 @@ https://www.youtube.com/watch?v=2oVcNaDpPz0

- [loop-grid](https://github.com/mmckegg/loop-grid)
- [audio-slot](https://github.com/mmckegg/audio-slot)
- [audio-timeline](https://github.com/mmckegg/audio-timeline)
- [wave-recorder](https://github.com/mmckegg/wave-recorder)
- [web-midi](https://github.com/mmckegg/web-midi)
- [bopper](https://github.com/wavejs/bopper)
- [micro-css](https://github.com/mmckegg/micro-css)
- [mercury](https://github.com/raynos/mercury)
- [mutant](https://github.com/mmckegg/mutant)
- [observ-fs](https://github.com/mmckegg/observ-fs)
- [observ-midi](https://github.com/mmckegg/observ-midi)
- [electron](https://github.com/atom/electron)
Expand Down
3 changes: 3 additions & 0 deletions bin/build-ffmpeg.sh
@@ -0,0 +1,3 @@
./configure --enable-static --disable-shared --disable-all --enable-ffmpeg --enable-avcodec --enable-avformat --enable-avutil --enable-swresample --enable-swscale --enable-avfilter --disable-network --disable-d3d11va --disable-dxva2 --disable-vaapi --disable-vda --disable-vdpau --enable-decoder=opus --enable-demuxer=matroska --enable-protocol=file --enable-protocol=pipe --disable-bzlib --disable-iconv --disable-libxcb --disable-lzma --disable-sdl --disable-securetransport --disable-xlib --disable-zlib --enable-filter=aresample --enable-encoder=pcm_f32le --enable-muxer=wav --enable-muxer=segment --enable-libopus && make

# build for windows: https://github.com/rdp/ffmpeg-windows-build-helpers
Binary file added bin/ffmpeg-darwin
Binary file not shown.
Binary file added bin/ffmpeg-linux
Binary file not shown.
Binary file added bin/ffmpeg-win32.exe
Binary file not shown.
12 changes: 12 additions & 0 deletions index.js
Expand Up @@ -31,6 +31,18 @@ MidiStream.watchPortNames(function (ports) {
midiPorts.set(ports)
})

var closing = false
window.onbeforeunload = function (e) {
// ensure recording is saved on close
if (!closing && window.currentProject && window.currentProject.actions.prepareToClose) {
window.currentProject.actions.prepareToClose(function () {
closing = true
electron.remote.getCurrentWindow().close()
})
return false
}
}

// create root context
var audioContext = new global.AudioContext()
var nodes = require('./nodes')
Expand Down
107 changes: 107 additions & 0 deletions lib/record-to-disk.js
@@ -0,0 +1,107 @@
var ReadableBlobStream = require('readable-blob-stream')
var spawn = require('child_process').spawn
var fs = require('fs')
var join = require('path').join
var getExt = require('path').extname
var getBaseName = require('path').basename
var getDirName = require('path').dirname
var ffmpeg = join(__dirname, '..', 'bin', 'ffmpeg-' + process.platform)

if (process.platform === 'win32') {
ffmpeg += '.exe'
}

module.exports = recordToDisk

function recordToDisk (path, mediaStream) {
var chunkDuration = 0.5 // seconds
var mediaRecorder = new window.MediaRecorder(mediaStream, { mimeType: 'audio/webm' })
var onFinish = null
mediaRecorder.chunkCount = 0
mediaRecorder.start(chunkDuration * 1000)

var output = WaveWriter(path, function () {
if (onFinish) {
onFinish.apply(this, arguments)
}
})

mediaRecorder.ondataavailable = function (e) {
mediaRecorder.chunkCount += 1
var stream = ReadableBlobStream(e.data)
stream.pipe(output, {end: false})
}

return function stopRecording (cb) {
onFinish = cb
mediaRecorder.requestData()
mediaRecorder.stop()

setTimeout(function () {
output.end()
}, 200)
}
}

function WaveWriter (outputFile, onDone) {
var ext = getExt(outputFile)
var base = getBaseName(outputFile, ext)
var dir = getDirName(outputFile)
var segmentLength = 30

var outputInfo = {
sampleRate: 48000,
recordedAt: Date.now(),
duration: 0,
channels: 2,
segments: []
}

writeOutputInfo()

var child = spawn(ffmpeg, [
'-i', '-',
'-acodec', 'pcm_f32le',
'-f', 'segment',
'-segment_list', 'pipe:1',
'-segment_list_type', 'csv',
'-segment_time', segmentLength,
join(dir, base + '-%05d.wav')
])

// child.stderr.on('data', function (c) {
// console.log(c.toString())
// })

child.stdout.on('data', function (data) {
if (outputInfo.segments.length) {
// ensure correct segment length time
outputInfo.segments[outputInfo.segments.length - 1].duration = segmentLength
}
var parts = data.toString().trim().split(',')
var duration = parseFloat(parts[2]) - parseFloat(parts[1])
outputInfo.segments.push({src: './' + parts[0], duration: duration})
outputInfo.duration = outputInfo.segments.reduce((r, s) => r + s.duration, 0)
writeOutputInfo()
})

if (onDone) {
child.on('exit', function (code) {
if (code === 0) {
setTimeout(function () {
onDone(null, outputInfo)
}, 50)
} else {
onDone(new Error('Recording error'))
}
})
}

return child.stdin

// scoped

function writeOutputInfo () {
fs.writeFile(outputFile, JSON.stringify(outputInfo))
}
}

0 comments on commit a0e19dd

Please sign in to comment.