Audio recorded with MediaRecorder on Chrome missing duration - google-chrome

I am recording audio (oga/vorbis) files with MediaRecorder. When I record these file through Chrome I get problems: I cannot edit the files on ffmpeg and when I try to play them on Firefox it says they are corrupt (they do play fine on Chrome though).
Looking at their metadata on ffmpeg I get this:
Input #0, matroska,webm, from '91.oga':
Metadata:
encoder : Chrome
Duration: N/A, start: 0.000000, bitrate: N/A
Stream #0:0(eng): Audio: opus, 48000 Hz, mono, fltp (default)
[STREAM]
index=0
codec_name=opus
codec_long_name=Opus (Opus Interactive Audio Codec)
profile=unknown
codec_type=audio
codec_time_base=1/48000
codec_tag_string=[0][0][0][0]
codec_tag=0x0000
sample_fmt=fltp
sample_rate=48000
channels=1
channel_layout=mono
bits_per_sample=0
id=N/A
r_frame_rate=0/0
avg_frame_rate=0/0
time_base=1/1000
start_pts=0
start_time=0.000000
duration_ts=N/A
duration=N/A
bit_rate=N/A
max_bit_rate=N/A
bits_per_raw_sample=N/A
nb_frames=N/A
nb_read_frames=N/A
nb_read_packets=N/A
DISPOSITION:default=1
DISPOSITION:dub=0
DISPOSITION:original=0
DISPOSITION:comment=0
DISPOSITION:lyrics=0
DISPOSITION:karaoke=0
DISPOSITION:forced=0
DISPOSITION:hearing_impaired=0
DISPOSITION:visual_impaired=0
DISPOSITION:clean_effects=0
DISPOSITION:attached_pic=0
TAG:language=eng
[/STREAM]
[FORMAT]
filename=91.oga
nb_streams=1
nb_programs=0
format_name=matroska,webm
format_long_name=Matroska / WebM
start_time=0.000000
duration=N/A
size=7195
bit_rate=N/A
probe_score=100
TAG:encoder=Chrome
As you can see there are problems with the duration. I have looked at posts like this:
How can I add predefined length to audio recorded from MediaRecorder in Chrome?
But even trying that, I got errors when trying to chop and merge files.For example when running:
ffmpeg -f concat -i 89_inputs.txt -c copy final.oga
I get a lot of this:
[oga # 00000000006789c0] Non-monotonous DTS in output stream 0:0; previous: 57612, current: 1980; changing to 57613. This may result in incorrect timestamps in the output file.
[oga # 00000000006789c0] Non-monotonous DTS in output stream 0:0; previous: 57613, current: 2041; changing to 57614. This may result in incorrect timestamps in the output file.
DTS -442721849179034176, next:42521 st:0 invalid dropping
PTS -442721849179034176, next:42521 invalid dropping st:0
[oga # 00000000006789c0] Non-monotonous DTS in output stream 0:0; previous: 57614, current: 2041; changing to 57615. This may result in incorrect timestamps in the output file.
[oga # 00000000006789c0] Timestamps are unset in a packet for stream 0. This is deprecated and will stop working in the future. Fix your code to set the timestamps properly
DTS -442721849179031296, next:42521 st:0 invalid dropping
PTS -442721849179031296, next:42521 invalid dropping st:0
Does anyone know what we need to do to audio files recorded from Chrome for them to be useful? Or is there a problem with my setup?
Recorder js:
if (navigator.getUserMedia) {
console.log('getUserMedia supported.');
var constraints = { audio: true };
var chunks = [];
var onSuccess = function(stream) {
var mediaRecorder = new MediaRecorder(stream);
record.onclick = function() {
mediaRecorder.start();
console.log(mediaRecorder.state);
console.log("recorder started");
record.style.background = "red";
stop.disabled = false;
record.disabled = true;
var aud = document.getElementById("audioClip");
start = aud.currentTime;
}
stop.onclick = function() {
console.log(mediaRecorder.state);
console.log("Recording request sent.");
mediaRecorder.stop();
}
mediaRecorder.onstop = function(e) {
console.log("data available after MediaRecorder.stop() called.");
var audio = document.createElement('audio');
audio.setAttribute('controls', '');
audio.setAttribute('id', 'audioClip');
audio.controls = true;
var blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs="vorbis"' });
chunks = [];
var audioURL = window.URL.createObjectURL(blob);
audio.src = audioURL;
sendRecToPost(blob); // this just send the audio blob to the server by post
console.log("recorder stopped");
}

I found at the ffmpeg documentation that we can set metadata at the conversion using this option:
//-metadata[:metadata_specifier] key=value (output,per-metadata)
//Set a metadata key/value pair.
ffmpeg -i in.avi -metadata title="my title" out.flv
You can also test if the duration conversion limit works on your case:
//-t duration (input/output)
//When used as an input option (before -i), limit the duration of data read from the input file.
//When used as an output option (before an output url), stop writing the output after its duration reaches duration.

Related

MediaRecorder timeslice segments - only the first segment plays

I have the following on chrome latest:
var options = { mimeType: "video/webm;codecs=vp8" };
internalMediaRecorder = new MediaRecorder(internalStream, options);
internalMediaRecorder.ondataavailable = function (blob) {
// put blob.data into an array
var src = URL.createObjectURL(blobData.segment);
const $container = $("body");
const $video = $("<video id='" + blobData.ts + "-" + blob.data.size + "' controls src='" + src + "'></video>").css("max-width", "100%");
$container.prepend($video);
// if I stop/start the recorder, I get playable segments here, separated by unplayable mini-segments from onDataAvailable because I call stop right after processing a video. I can "approximate" desired behavior by doing this and then ignoring blobs that are less than some threshhold to ignore the "dead gap" segments.
}
internalMediaRecorder.start(segmentLengthInMs); // every 5s
I then compile an array of 5s segments - the blob data is available. However when I create a URL for each of these segments:
URL.createObjectURL(videoSegment)
Only the first video plays. Why is this?
UPDATE
If I stop/start the recorder in onDataAvailable, I get playable segments here, separated by unplayable mini-segments from onDataAvailable because I call stop right after processing a video. I can "approximate" desired behavior by doing this and then ignoring blobs that are less than some threshhold to ignore the "dead gap" segments. This smells like feet though and I'd like to get proper segmentation working if possible.
Its expected as per the spec
The UA MUST record stream in such a way that the original Tracks can be retrieved at playback time. When multiple Blobs are returned (because of timeslice or requestData()), the individual Blobs need not be playable, but the combination of all the Blobs from a completed recording MUST be playable.
The resulted blobs are not raw video data, they are encoded with requested MIME type. So you need merge all the blobs in correct order, to generate a playable video file.
var options = { mimeType: "video/webm;codecs=vp8" };
var recordedBlobs = [];
internalMediaRecorder = new MediaRecorder(internalStream, options);
internalMediaRecorder.ondataavailable = function (event) {
if (event.data && event.data.size > 0) {
recordedBlobs.push(event.data);
}
}
internalMediaRecorder.start(segmentLengthInMs); // every 5s
function play() {
var superBuffer = new Blob(recordedBlobs, {type: 'video/webm'});
videoElement.src = window.URL.createObjectURL(superBuffer);
}
See the demo

HTTP Header for Duration of a MP4 for HTML 5 video

I am trying to stream MP4 video as it is encoded from a webserver. I believe I used the appropriate flags, but it is not working correctly. When I download the video from my stream and open it with VLC, it properly shows the duration. Since a socket is not seekable, I assume it writes the metadata to end? My Chrome browser always shows 8 seconds duration. The first 8 seconds plays at the normal speed, but afterwards the pause button turns into play button and the video plays very fast, probably as fast as it is recieved. However the audio is played at normal speed. I tried document.getElementById('myVid').duration = 20000 but it is a readonly field.
I wonder, is there anyway to explicitly state the duration in HTTP headers or in any other way? I cannot find any documentation about it.
ffmpeg -i - -vcodec libx264 -acodec libvo_aacenc -ar 44100 -ac 2 -ab 128000 -f mp4 -movflags frag_keyframe+faststart pipe:1 -fflags +genpts -re -profile baseline -level 30 -preset fast
To close-voters, that thinks it is not programming related, I use it in my own server I coded, and I need to set the duration programatically via JavaScript or setting the HTTP header. I believe it may be related to both ffmpeg or http headers, that's why I posted it here.
app.get("/video/*", function(req,res){
res.writeHead(200, {
'Content-Type': 'video/mp4',
});
var dir = req.url.split("/").splice(2).join("/");
var buf = new Buffer(dir, 'base64');
var src = buf.toString();
var Transcoder = require('stream-transcoder');
var stream = fs.createReadStream(src);
// I added my own flags to this module, they are at below:
new Transcoder(stream)
.videoCodec('libx264')
.audioCodec("libvo_aacenc")
.sampleRate(44100)
.channels(2)
.audioBitrate(128 * 1000)
.format('mp4')
.on('finish', function() {
console.log("finished");
})
.stream().pipe(res);
});
exec function in that stream-transcoder module,
a.push("-fflags");
a.push("+genpts");
a.push("-re");
a.push("-profile");
a.push("baseline");
a.push("-level");
a.push("30");
a.push("-preset");
a.push("fast");
a.push("-strict");
a.push("experimental");
a.push("-frag_duration");
a.push("" + 2 * (1000 * 1000));
var child = spawn('ffmpeg', a, {
cwd: os.tmpdir()
});
I believe the X-Content-Duration header is what you need.
Mozilla documentation on X-Content-Duration*
* The documentation discusses the OGG format, but the principle applies to other video formats

HTML5 <audio> poor choice for LIVE streaming?

As discussed in a previous question, I have built a prototype (using MVC Web API, NAudio and NAudio.Lame) that is streaming live low quality audio after converting it to mp3. The source stream is PCM: 8K, 16-bit, mono and I'm making use of html5's audio tag.
On both Chrome and IE11 there is a 15-34 second delay (high-latency) before audio is heard from the browser which, I'm told, is unacceptable for our end users. Ideally the latency would be no more than 5 seconds. The delay occurs even when using the preload="none" attribute within my audio tag.
Looking more closely at the issue, it appears as though both browsers will not start playing audio until they have received ~32K of audio data. With that in mind, I can affect the delay by changing Lame's MP3 'bitrate' setting. However, if I reduce the delay (by sending more data to the browser for the same length of audio), I will introduce audio drop-outs later.
Examples:
If I use Lame's V0 encoding the delay is nearly 34 seconds which requires almost 0.5 MB of source audio.
If I use Lame's ABR_32 encoding, I can reduce the delay to 10-15 seconds but I will experience pauses and drop-outs throughout the listening session.
Questions:
Any ideas how I can minimize the start-up delay (latency)?
Should I continue investigating various Lame 'presets' in hopes of picking the "right" one?
Could it be that MP3 is not the best format for live streaming?
Would switching to Ogg/Vorbis (or Ogg/OPUS) help?
Do we need to abandon HTML5's audio tag and use Flash or a java applet?
Thanks.
You can not reduce the delay, since you have no control on the browser code and buffering size. HTML5 specification does not enforce any constraint, so I don't see any reason why it would improve.
You can however implement a solution with webaudio API (it's quite simple), where you handle streaming yourself.
If you can split your MP3's chunk in fixed size (so that each MP3 chunks size is known beforehand, or at least, at receive time), then you can have a live streaming in 20 lines of code. The chunk size will be your latency.
The key is to use AudioContext::decodeAudioData.
// Fix up prefixing
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var offset = 0;
var byteOffset = 0;
var minDecodeSize = 16384; // This is your chunk size
var request = new XMLHttpRequest();
request.onprogress = function(evt)
{
if (request.response)
{
var size = request.response.length - byteOffset;
if (size < minDecodeSize) return;
// In Chrome, XHR stream mode gives text, not ArrayBuffer.
// If in Firefox, you can get an ArrayBuffer as is
var buf;
if (request.response instanceof ArrayBuffer)
buf = request.response;
else
{
ab = new ArrayBuffer(size);
buf = new Uint8Array(ab);
for (var i = 0; i < size; i++)
buf[i] = request.response.charCodeAt(i + byteOffset) & 0xff;
}
byteOffset = request.response.length;
context.decodeAudioData(ab, function(buffer) {
playSound(buffer);
}, onError);
}
};
request.open('GET', url, true);
request.responseType = expectedType; // 'stream' in chrome, 'moz-chunked-arraybuffer' in firefox, 'ms-stream' in IE
request.overrideMimeType('text/plain; charset=x-user-defined');
request.send(null);
function playSound(buffer) {
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.start(offset); // play the source now
// note: on older systems, may have to use deprecated noteOn(time);
offset += buffer.duration;
}

Convert RTSP stream to MP4

I have a IP Camera that supports RTSP, and I need to display this stream to multiple clients using HTML5.
Since HTML Video tag doesn't support RTSP, I'm calling ffmpeg to encode it to a WEBM stream, but the result is very glitchy and distorts the original stream.
The command im using is the following:
ffmpeg -i my_RSTP_URL -vcodec libvpx -f webm -
To distribute the stream I'm using a Node.js instance that calls the rtsp stream via ffpmeg when needed.
The solution looks like such:
Camera --Via RSTP--> ffmpeg --Encodes to WEBM--> Node.js --Via HTML5 Video--> Client
Node.js code:
var request = require('request');
var http = require('http');
var child_process = require("child_process");
var stdouts = {};
http.createServer(function (req, resp) {
switch (params[0])
{
case "LIVE":
resp.writeHead(200, {'Content-Type': 'video/mp4', 'Connection': 'keep-alive'});
// Start ffmpeg
var ffmpeg = child_process.spawn("ffmpeg",[
"-i","my_RSTP_URL", // Capture offset
"-vcodec","libvpx", // vp8 encoding
"-f","webm", // File format
"-" // Output to STDOUT
]);
ffmpeg.on('exit', function()
{
console.log('ffmpeg terminado');
});
ffmpeg.on('error',function(e)
{
console.log(e);
})
ffmpeg.stdout.on('data',function(data)
{
console.log('datos'+data);
});
ffmpeg.stderr.on('data', function(data) {
console.log('stderr'+data);
});
stdouts[params[1]] = ffmpeg.stdout;
// Pipe the video output to the client response
ffmpeg.stdout.pipe(resp);
console.log("Initializing camera");
break;
}
}).listen(8088);
console.log('Server running at port 8088');
Am I using the wrong library codec? Or why Am I getting such a weird result?
It seems to me, this can help for https://github.com/kyriesent/node-rtsp-stream
Also I worked with this technology, you can visit repository on bitBucket: https://bitbucket.org/kaleniuk_ihor/neuro_vision/src/db_watch/
Оn the other hand, your code may not work because you did not install ffmpeg at the root of drive C.

How can I play .spx file by html5 ?

from html5 spec, it seem support spx:
http://dev.w3.org/html5/spec-preview/the-source-element.html
Using:
But from my trying, it can't play in both Firefox 17 and Chrome, could you help ?
I have found that speex.js on GitHub (https://github.com/jpemartins/speex.js) can solve your problem. With speex.js you can demux & decode speex file (*.spx or *.ogg) to wav on fly, which is supported by both Chrome/Firefox and many other modern browsers HTML5 ready.
In your html include thoese *.js file under https://github.com/jpemartins/speex.js/tree/master/public/js/lib
<script src="bitstring.js"></script>
<script src="pcmdata.min.js"></script>
<script src="speex.js"></script>
function below will do the trick to convert spx to wav codec:
/**
* #param bufSpx ArrayBuffer (Uint8Array) holding content of speex file (*.spx or *.ogg)
*/
function decodeFile(bufSpx) {
var stream, samples, st;
var ogg, header, err;
ogg = new Ogg(bufSpx, {file: true});
ogg.demux();
stream = ogg.bitstream();
header = Speex.parseHeader(ogg.frames[0]);
console.log(header);
comment = new SpeexComment(ogg.frames[1]);
console.log(comment.data);
st = new Speex({
quality: 8,
mode: header.mode,
rate: header.rate
});
samples = st.decode(stream, ogg.segments);
var waveData = PCMData.encode({
sampleRate: header.rate,
channelCount: header.nb_channels,
bytesPerSample: 2,
data: samples
});
// array buffer holding audio data in wav codec
var bufWav = Speex.util.str2ab(waveData);
// convert to a blob object
var blob = new Blob([bufWav], {type: "audio/wav"});
// return a "blob://" url which can be used as a href anywhere
return URL.createObjectURL(blob);
}
The spec says:
The type attribute gives the type of the media resource, to help the
user agent determine if it can play this media resource before
fetching it.
The spec itself does not specify any audio or video formats to be supported and support is up to individual browsers.
... and no browser supports .spx as far as I know.