1 /*
2  * Copyright (C) 2011, Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1.  Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2.  Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
23  * DAMAGE.
24  */
25 
26 #include "third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.h"
27 
28 #include "third_party/blink/public/platform/task_type.h"
29 #include "third_party/blink/renderer/bindings/modules/v8/v8_media_element_audio_source_options.h"
30 #include "third_party/blink/renderer/core/frame/deprecation.h"
31 #include "third_party/blink/renderer/core/html/media/html_media_element.h"
32 #include "third_party/blink/renderer/core/inspector/console_message.h"
33 #include "third_party/blink/renderer/modules/webaudio/audio_context.h"
34 #include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
35 #include "third_party/blink/renderer/platform/audio/audio_utilities.h"
36 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
37 #include "third_party/blink/renderer/platform/heap/heap.h"
38 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
39 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
40 
41 namespace blink {
42 
43 class MediaElementAudioSourceHandlerLocker final {
44   STACK_ALLOCATED();
45 
46  public:
MediaElementAudioSourceHandlerLocker(MediaElementAudioSourceHandler & lockable)47   MediaElementAudioSourceHandlerLocker(MediaElementAudioSourceHandler& lockable)
48       : lockable_(lockable) {
49     lockable_.lock();
50   }
~MediaElementAudioSourceHandlerLocker()51   ~MediaElementAudioSourceHandlerLocker() { lockable_.unlock(); }
52 
53  private:
54   MediaElementAudioSourceHandler& lockable_;
55 
56   DISALLOW_COPY_AND_ASSIGN(MediaElementAudioSourceHandlerLocker);
57 };
58 
MediaElementAudioSourceHandler(AudioNode & node,HTMLMediaElement & media_element)59 MediaElementAudioSourceHandler::MediaElementAudioSourceHandler(
60     AudioNode& node,
61     HTMLMediaElement& media_element)
62     : AudioHandler(kNodeTypeMediaElementAudioSource,
63                    node,
64                    node.context()->sampleRate()),
65       media_element_(media_element),
66       source_number_of_channels_(0),
67       source_sample_rate_(0),
68       is_origin_tainted_(false) {
69   DCHECK(IsMainThread());
70   // Default to stereo. This could change depending on what the media element
71   // .src is set to.
72   AddOutput(2);
73 
74   if (Context()->GetExecutionContext()) {
75     task_runner_ = Context()->GetExecutionContext()->GetTaskRunner(
76         TaskType::kMediaElementEvent);
77   }
78 
79   Initialize();
80 }
81 
82 scoped_refptr<MediaElementAudioSourceHandler>
Create(AudioNode & node,HTMLMediaElement & media_element)83 MediaElementAudioSourceHandler::Create(AudioNode& node,
84                                        HTMLMediaElement& media_element) {
85   return base::AdoptRef(
86       new MediaElementAudioSourceHandler(node, media_element));
87 }
88 
~MediaElementAudioSourceHandler()89 MediaElementAudioSourceHandler::~MediaElementAudioSourceHandler() {
90   Uninitialize();
91 }
92 
MediaElement() const93 HTMLMediaElement* MediaElementAudioSourceHandler::MediaElement() const {
94   return media_element_.Get();
95 }
96 
Dispose()97 void MediaElementAudioSourceHandler::Dispose() {
98   AudioHandler::Dispose();
99 }
100 
SetFormat(uint32_t number_of_channels,float source_sample_rate)101 void MediaElementAudioSourceHandler::SetFormat(uint32_t number_of_channels,
102                                                float source_sample_rate) {
103   DCHECK(MediaElement());
104   bool is_tainted = WouldTaintOrigin();
105 
106   if (is_tainted) {
107     PrintCorsMessage(MediaElement()->currentSrc().GetString());
108   }
109 
110   {
111     // Make sure |is_origin_tainted_| matches |is_tainted|.  But need to
112     // synchronize with process() to set this.
113     MediaElementAudioSourceHandlerLocker locker(*this);
114     is_origin_tainted_ = is_tainted;
115   }
116 
117   if (number_of_channels != source_number_of_channels_ ||
118       source_sample_rate != source_sample_rate_) {
119     if (!number_of_channels ||
120         number_of_channels > BaseAudioContext::MaxNumberOfChannels() ||
121         !audio_utilities::IsValidAudioBufferSampleRate(source_sample_rate)) {
122       // process() will generate silence for these uninitialized values.
123       DLOG(ERROR) << "setFormat(" << number_of_channels << ", "
124                   << source_sample_rate << ") - unhandled format change";
125       // Synchronize with process().
126       MediaElementAudioSourceHandlerLocker locker(*this);
127       source_number_of_channels_ = 0;
128       source_sample_rate_ = 0;
129       return;
130     }
131 
132     // Synchronize with process() to protect |source_number_of_channels_|,
133     // |source_sample_rate_|, |multi_channel_resampler_|.
134     MediaElementAudioSourceHandlerLocker locker(*this);
135 
136     source_number_of_channels_ = number_of_channels;
137     source_sample_rate_ = source_sample_rate;
138 
139     if (source_sample_rate != Context()->sampleRate()) {
140       double scale_factor = source_sample_rate / Context()->sampleRate();
141       multi_channel_resampler_.reset(new MediaMultiChannelResampler(
142           number_of_channels, scale_factor,
143           audio_utilities::kRenderQuantumFrames,
144           CrossThreadBindRepeating(
145               &MediaElementAudioSourceHandler::ProvideResamplerInput,
146               CrossThreadUnretained(this))));
147     } else {
148       // Bypass resampling.
149       multi_channel_resampler_.reset();
150     }
151 
152     {
153       // The context must be locked when changing the number of output channels.
154       BaseAudioContext::GraphAutoLocker context_locker(Context());
155 
156       // Do any necesssary re-configuration to the output's number of channels.
157       Output(0).SetNumberOfChannels(number_of_channels);
158     }
159   }
160 }
161 
WouldTaintOrigin()162 bool MediaElementAudioSourceHandler::WouldTaintOrigin() {
163   DCHECK(MediaElement());
164   return MediaElement()->GetWebMediaPlayer()->WouldTaintOrigin();
165 }
166 
PrintCorsMessage(const String & message)167 void MediaElementAudioSourceHandler::PrintCorsMessage(const String& message) {
168   if (Context()->GetExecutionContext()) {
169     Context()->GetExecutionContext()->AddConsoleMessage(
170         MakeGarbageCollected<ConsoleMessage>(
171             mojom::ConsoleMessageSource::kSecurity,
172             mojom::ConsoleMessageLevel::kInfo,
173             "MediaElementAudioSource outputs zeroes due to "
174             "CORS access restrictions for " +
175                 message));
176   }
177 }
178 
ProvideResamplerInput(int resampler_frame_delay,AudioBus * dest)179 void MediaElementAudioSourceHandler::ProvideResamplerInput(
180     int resampler_frame_delay,
181     AudioBus* dest) {
182   DCHECK(Context()->IsAudioThread());
183   DCHECK(MediaElement());
184   DCHECK(dest);
185   MediaElement()->GetAudioSourceProvider().ProvideInput(dest, dest->length());
186 }
187 
Process(uint32_t number_of_frames)188 void MediaElementAudioSourceHandler::Process(uint32_t number_of_frames) {
189   AudioBus* output_bus = Output(0).Bus();
190 
191   // Use a tryLock() to avoid contention in the real-time audio thread.
192   // If we fail to acquire the lock then the HTMLMediaElement must be in the
193   // middle of reconfiguring its playback engine, so we output silence in this
194   // case.
195   MutexTryLocker try_locker(process_lock_);
196   if (try_locker.Locked()) {
197     if (!MediaElement() || !source_sample_rate_) {
198       output_bus->Zero();
199       return;
200     }
201 
202     // TODO(crbug.com/811516): Although OnSetFormat() requested the output bus
203     // channels, the actual channel count might have not been changed yet.
204     // Output silence for such case until the channel count is resolved.
205     if (source_number_of_channels_ != output_bus->NumberOfChannels()) {
206       output_bus->Zero();
207       return;
208     }
209 
210     AudioSourceProvider& provider = MediaElement()->GetAudioSourceProvider();
211     // Grab data from the provider so that the element continues to make
212     // progress, even if we're going to output silence anyway.
213     if (multi_channel_resampler_.get()) {
214       DCHECK_NE(source_sample_rate_, Context()->sampleRate());
215       multi_channel_resampler_->Resample(number_of_frames, output_bus);
216     } else {
217       // Bypass the resampler completely if the source is at the context's
218       // sample-rate.
219       DCHECK_EQ(source_sample_rate_, Context()->sampleRate());
220       provider.ProvideInput(output_bus, number_of_frames);
221     }
222     // Output silence if we don't have access to the element.
223     if (is_origin_tainted_) {
224       output_bus->Zero();
225     }
226   } else {
227     // We failed to acquire the lock.
228     output_bus->Zero();
229   }
230 }
231 
lock()232 void MediaElementAudioSourceHandler::lock() {
233   process_lock_.lock();
234 }
235 
unlock()236 void MediaElementAudioSourceHandler::unlock() {
237   process_lock_.unlock();
238 }
239 
240 // ----------------------------------------------------------------
241 
MediaElementAudioSourceNode(AudioContext & context,HTMLMediaElement & media_element)242 MediaElementAudioSourceNode::MediaElementAudioSourceNode(
243     AudioContext& context,
244     HTMLMediaElement& media_element)
245     : AudioNode(context), media_element_(&media_element) {
246   SetHandler(MediaElementAudioSourceHandler::Create(*this, media_element));
247 }
248 
Create(AudioContext & context,HTMLMediaElement & media_element,ExceptionState & exception_state)249 MediaElementAudioSourceNode* MediaElementAudioSourceNode::Create(
250     AudioContext& context,
251     HTMLMediaElement& media_element,
252     ExceptionState& exception_state) {
253   DCHECK(IsMainThread());
254 
255   // First check if this media element already has a source node.
256   if (media_element.AudioSourceNode()) {
257     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
258                                       "HTMLMediaElement already connected "
259                                       "previously to a different "
260                                       "MediaElementSourceNode.");
261     return nullptr;
262   }
263 
264   MediaElementAudioSourceNode* node =
265       MakeGarbageCollected<MediaElementAudioSourceNode>(context, media_element);
266 
267   if (node) {
268     media_element.SetAudioSourceNode(node);
269     // context keeps reference until node is disconnected
270     context.NotifySourceNodeStartedProcessing(node);
271     if (!context.HasRealtimeConstraint()) {
272       Deprecation::CountDeprecation(
273           node->GetExecutionContext(),
274           WebFeature::kMediaElementSourceOnOfflineContext);
275     }
276   }
277 
278   return node;
279 }
280 
Create(AudioContext * context,const MediaElementAudioSourceOptions * options,ExceptionState & exception_state)281 MediaElementAudioSourceNode* MediaElementAudioSourceNode::Create(
282     AudioContext* context,
283     const MediaElementAudioSourceOptions* options,
284     ExceptionState& exception_state) {
285   return Create(*context, *options->mediaElement(), exception_state);
286 }
287 
Trace(Visitor * visitor)288 void MediaElementAudioSourceNode::Trace(Visitor* visitor) {
289   visitor->Trace(media_element_);
290   AudioSourceProviderClient::Trace(visitor);
291   AudioNode::Trace(visitor);
292 }
293 
294 MediaElementAudioSourceHandler&
GetMediaElementAudioSourceHandler() const295 MediaElementAudioSourceNode::GetMediaElementAudioSourceHandler() const {
296   return static_cast<MediaElementAudioSourceHandler&>(Handler());
297 }
298 
mediaElement() const299 HTMLMediaElement* MediaElementAudioSourceNode::mediaElement() const {
300   return media_element_;
301 }
302 
SetFormat(uint32_t number_of_channels,float sample_rate)303 void MediaElementAudioSourceNode::SetFormat(uint32_t number_of_channels,
304                                             float sample_rate) {
305   GetMediaElementAudioSourceHandler().SetFormat(number_of_channels,
306                                                 sample_rate);
307 }
308 
lock()309 void MediaElementAudioSourceNode::lock() {
310   GetMediaElementAudioSourceHandler().lock();
311 }
312 
unlock()313 void MediaElementAudioSourceNode::unlock() {
314   GetMediaElementAudioSourceHandler().unlock();
315 }
316 
ReportDidCreate()317 void MediaElementAudioSourceNode::ReportDidCreate() {
318   GraphTracer().DidCreateAudioNode(this);
319 }
320 
ReportWillBeDestroyed()321 void MediaElementAudioSourceNode::ReportWillBeDestroyed() {
322   GraphTracer().WillDestroyAudioNode(this);
323 }
324 
325 }  // namespace blink
326