Closed Bug 968109 Opened 11 years ago Closed 10 years ago

[MediaRecorder] Support recording media stream from OfflineAudioContext

Categories

(Core :: Audio/Video: Recording, defect)

defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla34

People

(Reporter: u459114, Assigned: jhlin)

Details

Attachments

(2 files, 4 obsolete files)

Ringtone editing app need none real-time recording to trim a audio clip as ring tone. One possible solution is to enable recording a media stream from offline audio context. Since we didn't handle offline mode in media recorder, buffer overflow may happen in this scenario.
Assignee: nobody → rlin
It look like OfflineAudioContext can't create an offline media stream for media recorder via CreateMediaStreamDestination.. With this API, it can output pcm raw data to renderedBuffer through OfflineAudioCompletionEvent.
How about have a new Web Audio node called OggCompressor to do the transcode task and produce the opus in Ogg audio blob data?
Component: Video/Audio → Web Audio
Well, it would be rather nice to be able to use _some_ MediaStream related Web Platform features, with an OfflineAudioContext. There does not seem to be something like an "isRealTime" flag on MediaStreams we could use to indicate that a particular MediaStream can be consumed as fast as possible, and therefore can be used with an OfflineAudioContext. Adding nodes to the Web Audio spec does not seem to me the best way to solve this.
The media stream can be the source for media element, recording source, webaudio. If the media stream can be non-realtime, would it break the world in API level?
Not really. We would only allow createMediaStreamSource with non-realtime stream (such as an offline audio context), and the resulting MediaStreamDestinationNode would provide a MediaStream that is also consumable in a non-realtime fashion. Then again, we would have to change a bunch of spec.
Hi roc, Is it possible to change the media stream behavior? MediaStream interface looks like we prefer it to be real-time.
Flags: needinfo?(roc)
By using OfflineAudioContext, we can generate an AudioBuffer with PCM raw samples. There is no way, on API level, to hand over that buffer to MediaRecorder for encoding. *If* we have this MediaEncoder API interface MediaEncoder { void encode(AudioBuffer); attribute EventHandler oncomplete; }; client can get a encode Blob from oncomplete.
Here is my suggestion 1. Provide encoding function in navigator.mozMediaEncoder for B2G privilege app. 2. Convert AudioSegment into RawTrackData to feed TrackEncoder. 3. generate opus(in OGG) encode data by MediaEncoder pipeline and return encoded Blob via oncomplete callback
(In reply to C.J. Ku[:CJKu] from comment #8) > Here is my suggestion > 1. Provide encoding function in navigator.mozMediaEncoder for B2G privilege > app. > 2. Convert AudioSegment into RawTrackData to feed TrackEncoder. type: AudioBuffer(from OfflineAudioContext) > 3. generate opus(in OGG) encode data by MediaEncoder pipeline and return > encoded Blob via oncomplete callback
another suggestion is to convert AudioBuffer into wav format at js side, save it as ringtone
Generate a ringtone wave files seems to be a short solution for this, but workable. 30sec wave file size would be 2.8M in 2ch, 48khz.
Hi Guys, As discussed with Sri (Media) PM, that the music trimmer has to be done in 1.4. let's discuss and break down the work. BR, Marvin
MediaStreams and OfflineAudioContexts do not mix. MediaStream has all sort of real time assumptions built into it, which is the reason that it's not possible to connect them to OfflineAudioContexts in any way. That was done quite deliberately. What is the format of the audio clip that you need to trim? If you have all of the original data in an AudioBuffer for example, then it should be possible to write it in the wave format without needing any additional APIs.
(In reply to :Ehsan Akhgari (needinfo? me!) (slow responsiveness, emailacopolypse) from comment #13) > MediaStreams and OfflineAudioContexts do not mix. MediaStream has all sort > of real time assumptions built into it, which is the reason that it's not > possible to connect them to OfflineAudioContexts in any way. That was done > quite deliberately. I don't think that's true actually. I think we could quite easily implement a solution that allows you to call createMediaStreamDestination in an OfflineAudioContext, get an "offline MediaStream", and create a MediaRecorder for that. However this would require spec work which would probably be quite controversial. Perhaps a better approach would be to add a method to AudioContext, createRecorder(), which creates a MediaRecorder object consuming the destination node of the AudioContext, and allow this to work on both online and offline AudioContexts. I think this would be easy for us to implement. I can propose it to public-audio and public-media-capture. Script could easily wrap an audioBuffer in a WAV file --- some of our test script already does that --- but it would be nice to do this properly.
Flags: needinfo?(roc)
(In reply to Robert O'Callahan (:roc) (Mozilla Corporation) from comment #14) > Perhaps a better approach would be to add a method to AudioContext, > createRecorder(), which creates a MediaRecorder object consuming the > destination node of the AudioContext, and allow this to work on both online > and offline AudioContexts. I think this would be easy for us to implement. I > can propose it to public-audio and public-media-capture. Actually we'd probably be better off adding a MediaRecorder(AudioContext) constructor, so that any changes to the constructors (e.g. adding new options) would be easy to make in one spec.
(In reply to comment #14) > (In reply to :Ehsan Akhgari (needinfo? me!) (slow responsiveness, > emailacopolypse) from comment #13) > > MediaStreams and OfflineAudioContexts do not mix. MediaStream has all sort > > of real time assumptions built into it, which is the reason that it's not > > possible to connect them to OfflineAudioContexts in any way. That was done > > quite deliberately. > > I don't think that's true actually. I think we could quite easily implement a > solution that allows you to call createMediaStreamDestination in an > OfflineAudioContext, get an "offline MediaStream", and create a MediaRecorder > for that. > > However this would require spec work which would probably be quite > controversial. Right, that's what I meant. :-) It's not an implementation problem in Gecko. > Perhaps a better approach would be to add a method to AudioContext, > createRecorder(), which creates a MediaRecorder object consuming the > destination node of the AudioContext, and allow this to work on both online and > offline AudioContexts. I think this would be easy for us to implement. I can > propose it to public-audio and public-media-capture. That (the ctor syntax in comment 15) sounds good. We should make sure that it's implementable by other engines, as I know that at least Safari has weird setups for some of their media stream types. > Script could easily wrap an audioBuffer in a WAV file --- some of our test > script already does that --- but it would be nice to do this properly. Yeah, well, and we need to encode to other formats anyways.
Perhaps MediaRecorder(AudioNode, optional output), so that output need not go to hardware in an online context. This may also allow optimization for offline contexts because the buffer for OfflineAudioContext oncomplete can be null. For an online AudioNode.context, this would be sugar for dest = AudioNode.context.createMediaStreamDestination(); AudioNode.connect(dest, output); MediaRecorder(dest.stream);
Add MediaRecorder(AudioContext) constructor also sounds good to me, too.
Component: Web Audio → Video/Audio
Component: Video/Audio → Video/Audio: Recording
No longer blocks: MediaRecording
Free it if anyone wants.
Assignee: rlin → nobody
WIP implementation to support recording audio from OfflineAudioContext. - new MediaRecorder constructor which takes Web Audio destination node as input source - modify recorder object to hold native media stream object rather than DOM wrapper - rename AfterTracksAdded() to InitEncoder() to make it more clear what the function does.
Assignee: nobody → jolin
Attachment #8468261 - Flags: feedback?(rlin)
Comment on attachment 8468261 [details] [diff] [review] Add constructor to support recording audio from OfflineAudioContext. Review of attachment 8468261 [details] [diff] [review]: ----------------------------------------------------------------- LGTM. :)
Attachment #8468261 - Flags: feedback?(rlin) → feedback+
Attachment #8468261 - Flags: feedback?(roc)
Comment on attachment 8468261 [details] [diff] [review] Add constructor to support recording audio from OfflineAudioContext. Review of attachment 8468261 [details] [diff] [review]: ----------------------------------------------------------------- Basically looks good! Glad it's so simple. Does it work? :-) Someone needs to get this added to the spec. Anyone volunteering for that? ::: dom/webidl/MediaRecorder.webidl @@ +12,5 @@ > > enum RecordingState { "inactive", "recording", "paused" }; > > +[Constructor(MediaStream stream, optional MediaRecorderOptions options), > + Constructor(AudioDestinationNode node, optional MediaRecorderOptions options)] See Karl's comment. We should take an AudioNode here.
Attachment #8468261 - Flags: feedback?(roc) → feedback+
(In reply to Robert O'Callahan (:roc) (Mozilla Corporation) from comment #22) > Comment on attachment 8468261 [details] [diff] [review] > Add constructor to support recording audio from OfflineAudioContext. > > Review of attachment 8468261 [details] [diff] [review]: > ----------------------------------------------------------------- > > Basically looks good! Glad it's so simple. Does it work? :-) Yes. I've run my test script on Firefox and FxOS. \o/ > > Someone needs to get this added to the spec. Anyone volunteering for that? Sure. Never done this before, where do I start? > > ::: dom/webidl/MediaRecorder.webidl > @@ +12,5 @@ > > > > enum RecordingState { "inactive", "recording", "paused" }; > > > > +[Constructor(MediaStream stream, optional MediaRecorderOptions options), > > + Constructor(AudioDestinationNode node, optional MediaRecorderOptions options)] > > See Karl's comment. We should take an AudioNode here. Okay.
(In reply to John Lin[:jolin][:jhlin] from comment #23) > (In reply to Robert O'Callahan (:roc) (Mozilla Corporation) from comment #22) > > Comment on attachment 8468261 [details] [diff] [review] > > Add constructor to support recording audio from OfflineAudioContext. > > > > Review of attachment 8468261 [details] [diff] [review]: > > ----------------------------------------------------------------- > > > > Basically looks good! Glad it's so simple. Does it work? :-) > > Yes. I've run my test script on Firefox and FxOS. \o/ > > > > > Someone needs to get this added to the spec. Anyone volunteering for that? > > Sure. Never done this before, where do I start? You could write this idea in mail and post to this mailist http://lists.w3.org/Archives/Public/public-media-capture/ > > > > ::: dom/webidl/MediaRecorder.webidl > > @@ +12,5 @@ > > > > > > enum RecordingState { "inactive", "recording", "paused" }; > > > > > > +[Constructor(MediaStream stream, optional MediaRecorderOptions options), > > > + Constructor(AudioDestinationNode node, optional MediaRecorderOptions options)] > > > > See Karl's comment. We should take an AudioNode here. > > Okay.
(In reply to Karl Tomlinson (:karlt) from comment #17) > Perhaps MediaRecorder(AudioNode, optional output), so that output need not > go to hardware in an online context. This may also allow optimization for > offline contexts because the buffer for OfflineAudioContext oncomplete can > be null. > > For an online AudioNode.context, this would be sugar for > > dest = AudioNode.context.createMediaStreamDestination(); > AudioNode.connect(dest, output); > MediaRecorder(dest.stream); Hi Karl, Just want to be sure that I understand it correctly: * |output| is an audio node * when supplied, the audio, besides being recorded by recorder, will also go to the its input * if undefined or null => the audio won't go to audio hardware (no sound) for online context => OfflineAudioCompletionEvent.renderedBuffer will be null for offline context Right?
Flags: needinfo?(karlt)
(In reply to John Lin[:jolin][:jhlin] from comment #25) > (In reply to Karl Tomlinson (:karlt) from comment #17) > > Perhaps MediaRecorder(AudioNode, optional output), I mean MediaRecorder(AudioNode node, optional unsigned long output = 0). Some AudioNodes have more than one output. The |output| parameter specifies which output is connected to the MediaRecorder. If |output| is not provided, then output number 0 from the AudioNode will be recorded. Compare http://webaudio.github.io/web-audio-api/#widl-AudioNode-connect-void-AudioNode-destination-unsigned-long-output-unsigned-long-input If the AudioNode output is also connected to other AudioNodes or AudioParams then it will still go to those other objects, as when not connected to the MediaRecorder. I'm not sure about passing undefined or null for an unsigned long parameter, but I guess the code generated from webidl will throw an exception. > => the audio won't go to audio hardware (no sound) for online context > => OfflineAudioCompletionEvent.renderedBuffer will be null for offline Whether any sound goes to hardware (online) or renderedBuffer (offline) depends on whether the AudioNode is connected to the AudioContext::destination. Creating a MediaRecorder does not affect this.
Flags: needinfo?(karlt)
(In reply to Karl Tomlinson (:karlt) from comment #26) > (In reply to John Lin[:jolin][:jhlin] from comment #25) > > (In reply to Karl Tomlinson (:karlt) from comment #17) > > > Perhaps MediaRecorder(AudioNode, optional output), > > I mean MediaRecorder(AudioNode node, optional unsigned long output = 0). > > Some AudioNodes have more than one output. The |output| parameter specifies > which output is connected to the MediaRecorder. If |output| is not > provided, then output number 0 from the AudioNode will be recorded. > > Compare > http://webaudio.github.io/web-audio-api/#widl-AudioNode-connect-void- > AudioNode-destination-unsigned-long-output-unsigned-long-input > > If the AudioNode output is also connected to other AudioNodes or AudioParams > then it will still go to those other objects, as when not connected to the > MediaRecorder. > > I'm not sure about passing undefined or null for an unsigned long parameter, > but I guess the code generated from webidl will throw an exception. > > > => the audio won't go to audio hardware (no sound) for online context > > => OfflineAudioCompletionEvent.renderedBuffer will be null for offline > > Whether any sound goes to hardware (online) or renderedBuffer (offline) > depends on whether the AudioNode is connected to the > AudioContext::destination. Creating a MediaRecorder does not affect this. Uh-oh, I COMPLETELY misunderstood it. :-$ Thanks a lot for the prompt response!
Code change according to Karl's comment. It now can record from audio node other than destination.
Attachment #8468261 - Attachment is obsolete: true
Attachment #8475040 - Flags: review?(roc)
Attachment #8475040 - Flags: feedback?(rlin)
Comment on attachment 8475040 [details] [diff] [review] Add constructor to support recording audio from OfflineAudioContext. Review of attachment 8475040 [details] [diff] [review]: ----------------------------------------------------------------- Please send that email to public-media-capture!
Attachment #8475040 - Flags: review?(roc) → review+
Comment on attachment 8475040 [details] [diff] [review] Add constructor to support recording audio from OfflineAudioContext. LGTM, Thanks. The MediaRecorder.webidl requires sr to review it.
Attachment #8475040 - Flags: feedback?(rlin) → feedback+
(In reply to Robert O'Callahan (:roc) (Mozilla Corporation) from comment #29) > Comment on attachment 8475040 [details] [diff] [review] > Add constructor to support recording audio from OfflineAudioContext. > > Review of attachment 8475040 [details] [diff] [review]: > ----------------------------------------------------------------- > > Please send that email to public-media-capture! Thanks! The message was sent out yesterday. I guess it didn't show up yet because I'm a 1st-time poster. :)
Couldn't figure out how to hide just one constructor behind [Pref] in WebIDL. Throw TypeError in the implementation to achieve same effect.
Attachment #8475040 - Attachment is obsolete: true
Attachment #8478884 - Flags: review?(bugs)
Attachment #8478884 - Flags: feedback?(ayang)
Some basic tests to check: - newly added constructor is hidden behind pref - recording works for various context and node types
Attachment #8478889 - Flags: review?(roc)
Attachment #8478889 - Flags: feedback?(ayang)
Comment on attachment 8478884 [details] [diff] [review] Hide the newly added constructor behind pref. Review of attachment 8478884 [details] [diff] [review]: ----------------------------------------------------------------- ::: content/media/MediaRecorder.cpp @@ +816,5 @@ > + return nullptr; > + } > + > + nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal.GetAsSupports()); > + if (!sgo) { I can't find where it is used. Do we need to check it?
Attachment #8478884 - Flags: feedback?(ayang) → feedback+
Attachment #8478889 - Flags: feedback?(ayang) → feedback+
Comment on attachment 8478884 [details] [diff] [review] Hide the newly added constructor behind pref. >+/* static */ already_AddRefed<MediaRecorder> >+MediaRecorder::Constructor(const GlobalObject& aGlobal, >+ AudioNode& aSrcAudioNode, >+ uint32_t aSrcOutput, >+ const MediaRecorderOptions& aInitDict, >+ ErrorResult& aRv) >+{ >+ // Only enable recording from audio node when pref is on. >+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); >+ bool enabled = false; >+ if (prefs == nullptr || >+ NS_FAILED(prefs->GetBoolPref("media.recorder.audio_node.enabled", &enabled)) || >+ !enabled) { >+ // Pretending that this constructor is not defined. >+ NS_NAMED_LITERAL_STRING(argStr, "Argument 1 of MediaRecorder.constructor"); >+ NS_NAMED_LITERAL_STRING(typeStr, "MediaStream"); >+ aRv.ThrowTypeError(MSG_DOES_NOT_IMPLEMENT_INTERFACE, &argStr, &typeStr); >+ return nullptr; >+ } Do we want a pref or permission check? I think pref is probably fine here, but it does mean the API is exposed everywhere, in all the apps when the pref is on. To check the pref value, please use Preferences::GetBool("media.recorder.audio_node.enabled", false) >+ >+ nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal.GetAsSupports()); >+ if (!sgo) { >+ aRv.Throw(NS_ERROR_FAILURE); >+ return nullptr; >+ } You don't use sgo for anything, so drop this stuff
Attachment #8478884 - Flags: review?(bugs) → review+
- address review comment - carry r+ - fix non-unified bustage
Attachment #8478884 - Attachment is obsolete: true
Attachment #8480382 - Flags: review+
- carry r+ - fix test failures caused by finishing test before all test cases are complete
Attachment #8478889 - Attachment is obsolete: true
Attachment #8480383 - Flags: review+
Keywords: checkin-needed
Status: NEW → RESOLVED
Closed: 10 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla34
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: