1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * the Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2007
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Chris Double <chris.double@double.co.nz>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "mozilla/dom/ContentChild.h"
41 : #include "mozilla/dom/PAudioChild.h"
42 : #include "mozilla/dom/AudioChild.h"
43 : #include "nsXULAppAPI.h"
44 : using namespace mozilla::dom;
45 :
46 : #include <stdio.h>
47 : #include <math.h>
48 : #include "prlog.h"
49 : #include "prmem.h"
50 : #include "prdtoa.h"
51 : #include "nsAutoPtr.h"
52 : #include "nsAudioStream.h"
53 : #include "nsAlgorithm.h"
54 : #include "VideoUtils.h"
55 : #include "mozilla/Mutex.h"
56 : extern "C" {
57 : #include "sydneyaudio/sydney_audio.h"
58 : }
59 : #include "mozilla/TimeStamp.h"
60 : #include "nsThreadUtils.h"
61 : #include "mozilla/Preferences.h"
62 :
63 : #if defined(MOZ_CUBEB)
64 : #include "nsAutoRef.h"
65 : #include "cubeb/cubeb.h"
66 : #endif
67 :
68 : using namespace mozilla;
69 :
70 : #if defined(XP_MACOSX)
71 : #define SA_PER_STREAM_VOLUME 1
72 : #endif
73 :
74 : // Android's audio backend is not available in content processes, so audio must
75 : // be remoted to the parent chrome process.
76 : #if defined(ANDROID)
77 : #define REMOTE_AUDIO 1
78 : #endif
79 :
80 : using mozilla::TimeStamp;
81 :
82 : #ifdef PR_LOGGING
83 : PRLogModuleInfo* gAudioStreamLog = nsnull;
84 : #endif
85 :
86 : #if defined(MOZ_CUBEB)
87 : static cubeb* gCubebContext;
88 : #endif
89 :
90 : static const PRUint32 FAKE_BUFFER_SIZE = 176400;
91 :
92 : // Number of milliseconds per second.
93 : static const PRInt64 MS_PER_S = 1000;
94 :
95 : class nsNativeAudioStream : public nsAudioStream
96 : {
97 : public:
98 : NS_DECL_ISUPPORTS
99 :
100 : ~nsNativeAudioStream();
101 : nsNativeAudioStream();
102 :
103 : nsresult Init(PRInt32 aNumChannels, PRInt32 aRate, SampleFormat aFormat);
104 : void Shutdown();
105 : nsresult Write(const void* aBuf, PRUint32 aFrames);
106 : PRUint32 Available();
107 : void SetVolume(double aVolume);
108 : void Drain();
109 : void Pause();
110 : void Resume();
111 : PRInt64 GetPosition();
112 : PRInt64 GetPositionInFrames();
113 : bool IsPaused();
114 : PRInt32 GetMinWriteSize();
115 :
116 : private:
117 :
118 : double mVolume;
119 : void* mAudioHandle;
120 :
121 : // True if this audio stream is paused.
122 : bool mPaused;
123 :
124 : // True if this stream has encountered an error.
125 : bool mInError;
126 :
127 : };
128 :
129 : #if defined(REMOTE_AUDIO)
130 : class nsRemotedAudioStream : public nsAudioStream
131 : {
132 : public:
133 : NS_DECL_ISUPPORTS
134 :
135 : nsRemotedAudioStream();
136 : ~nsRemotedAudioStream();
137 :
138 : nsresult Init(PRInt32 aNumChannels, PRInt32 aRate, SampleFormat aFormat);
139 : void Shutdown();
140 : nsresult Write(const void* aBuf, PRUint32 aFrames);
141 : PRUint32 Available();
142 : void SetVolume(double aVolume);
143 : void Drain();
144 : void Pause();
145 : void Resume();
146 : PRInt64 GetPosition();
147 : PRInt64 GetPositionInFrames();
148 : bool IsPaused();
149 : PRInt32 GetMinWriteSize();
150 :
151 : private:
152 : nsRefPtr<AudioChild> mAudioChild;
153 :
154 : PRInt32 mBytesPerFrame;
155 :
156 : // True if this audio stream is paused.
157 : bool mPaused;
158 :
159 : friend class AudioInitEvent;
160 : };
161 :
162 : class AudioInitEvent : public nsRunnable
163 : {
164 : public:
165 : AudioInitEvent(nsRemotedAudioStream* owner)
166 : {
167 : mOwner = owner;
168 : }
169 :
170 : NS_IMETHOD Run()
171 : {
172 : ContentChild * cpc = ContentChild::GetSingleton();
173 : NS_ASSERTION(cpc, "Content Protocol is NULL!");
174 : mOwner->mAudioChild = static_cast<AudioChild*>(cpc->SendPAudioConstructor(mOwner->mChannels,
175 : mOwner->mRate,
176 : mOwner->mFormat));
177 : return NS_OK;
178 : }
179 :
180 : nsRefPtr<nsRemotedAudioStream> mOwner;
181 : };
182 :
183 : class AudioWriteEvent : public nsRunnable
184 : {
185 : public:
186 : AudioWriteEvent(AudioChild* aChild,
187 : const void* aBuf,
188 : PRUint32 aNumberOfFrames,
189 : PRUint32 aBytesPerFrame)
190 : {
191 : mAudioChild = aChild;
192 : mBytesPerFrame = aBytesPerFrame;
193 : mBuffer.Assign((const char*)aBuf, aNumberOfFrames * aBytesPerFrame);
194 : }
195 :
196 : NS_IMETHOD Run()
197 : {
198 : if (!mAudioChild->IsIPCOpen())
199 : return NS_OK;
200 :
201 : mAudioChild->SendWrite(mBuffer, mBuffer.Length() / mBytesPerFrame);
202 : return NS_OK;
203 : }
204 :
205 : nsRefPtr<AudioChild> mAudioChild;
206 : nsCString mBuffer;
207 : PRUint32 mBytesPerFrame;
208 : };
209 :
210 : class AudioSetVolumeEvent : public nsRunnable
211 : {
212 : public:
213 : AudioSetVolumeEvent(AudioChild* aChild, double aVolume)
214 : {
215 : mAudioChild = aChild;
216 : mVolume = aVolume;
217 : }
218 :
219 : NS_IMETHOD Run()
220 : {
221 : if (!mAudioChild->IsIPCOpen())
222 : return NS_OK;
223 :
224 : mAudioChild->SendSetVolume(mVolume);
225 : return NS_OK;
226 : }
227 :
228 : nsRefPtr<AudioChild> mAudioChild;
229 : double mVolume;
230 : };
231 :
232 :
233 : class AudioMinWriteSizeEvent : public nsRunnable
234 : {
235 : public:
236 : AudioMinWriteSizeEvent(AudioChild* aChild)
237 : {
238 : mAudioChild = aChild;
239 : }
240 :
241 : NS_IMETHOD Run()
242 : {
243 : if (!mAudioChild->IsIPCOpen())
244 : return NS_OK;
245 :
246 : mAudioChild->SendMinWriteSize();
247 : return NS_OK;
248 : }
249 :
250 : nsRefPtr<AudioChild> mAudioChild;
251 : };
252 :
253 : class AudioDrainEvent : public nsRunnable
254 : {
255 : public:
256 : AudioDrainEvent(AudioChild* aChild)
257 : {
258 : mAudioChild = aChild;
259 : }
260 :
261 : NS_IMETHOD Run()
262 : {
263 : if (!mAudioChild->IsIPCOpen())
264 : return NS_OK;
265 :
266 : mAudioChild->SendDrain();
267 : return NS_OK;
268 : }
269 :
270 : nsRefPtr<AudioChild> mAudioChild;
271 : };
272 :
273 :
274 : class AudioPauseEvent : public nsRunnable
275 : {
276 : public:
277 : AudioPauseEvent(AudioChild* aChild, bool pause)
278 : {
279 : mAudioChild = aChild;
280 : mPause = pause;
281 : }
282 :
283 : NS_IMETHOD Run()
284 : {
285 : if (!mAudioChild->IsIPCOpen())
286 : return NS_OK;
287 :
288 : if (mPause)
289 : mAudioChild->SendPause();
290 : else
291 : mAudioChild->SendResume();
292 :
293 : return NS_OK;
294 : }
295 :
296 : nsRefPtr<AudioChild> mAudioChild;
297 : bool mPause;
298 : };
299 :
300 :
301 : class AudioShutdownEvent : public nsRunnable
302 : {
303 : public:
304 : AudioShutdownEvent(AudioChild* aChild)
305 : {
306 : mAudioChild = aChild;
307 : }
308 :
309 : NS_IMETHOD Run()
310 : {
311 : if (mAudioChild->IsIPCOpen())
312 : mAudioChild->SendShutdown();
313 : return NS_OK;
314 : }
315 :
316 : nsRefPtr<AudioChild> mAudioChild;
317 : };
318 : #endif
319 :
320 : #define PREF_VOLUME_SCALE "media.volume_scale"
321 : #define PREF_USE_CUBEB "media.use_cubeb"
322 :
323 : static mozilla::Mutex* gAudioPrefsLock = nsnull;
324 : static double gVolumeScale = 1.0;
325 : static bool gUseCubeb = false;
326 :
327 1405 : static int PrefChanged(const char* aPref, void* aClosure)
328 : {
329 1405 : if (strcmp(aPref, PREF_VOLUME_SCALE) == 0) {
330 2810 : nsAdoptingString value = Preferences::GetString(aPref);
331 2810 : mozilla::MutexAutoLock lock(*gAudioPrefsLock);
332 1405 : if (value.IsEmpty()) {
333 0 : gVolumeScale = 1.0;
334 : } else {
335 2810 : NS_ConvertUTF16toUTF8 utf8(value);
336 1405 : gVolumeScale = NS_MAX<double>(0, PR_strtod(utf8.get(), nsnull));
337 : }
338 0 : } else if (strcmp(aPref, PREF_USE_CUBEB) == 0) {
339 0 : bool value = Preferences::GetBool(aPref, false);
340 0 : mozilla::MutexAutoLock lock(*gAudioPrefsLock);
341 0 : gUseCubeb = value;
342 : }
343 1405 : return 0;
344 : }
345 :
346 0 : static double GetVolumeScale()
347 : {
348 0 : mozilla::MutexAutoLock lock(*gAudioPrefsLock);
349 0 : return gVolumeScale;
350 : }
351 :
352 : #if defined(MOZ_CUBEB)
353 : static bool GetUseCubeb()
354 : {
355 : mozilla::MutexAutoLock lock(*gAudioPrefsLock);
356 : return gUseCubeb;
357 : }
358 : #endif
359 :
360 1404 : void nsAudioStream::InitLibrary()
361 : {
362 : #ifdef PR_LOGGING
363 1404 : gAudioStreamLog = PR_NewLogModule("nsAudioStream");
364 : #endif
365 1404 : gAudioPrefsLock = new mozilla::Mutex("nsAudioStream::gAudioPrefsLock");
366 1404 : PrefChanged(PREF_VOLUME_SCALE, nsnull);
367 1404 : Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE);
368 : #if defined(MOZ_CUBEB)
369 : PrefChanged(PREF_USE_CUBEB, nsnull);
370 : Preferences::RegisterCallback(PrefChanged, PREF_USE_CUBEB);
371 : if (cubeb_init(&gCubebContext, "nsAudioStream") != 0) {
372 : NS_WARNING("cubeb_init failed");
373 : }
374 : #endif
375 1404 : }
376 :
377 1403 : void nsAudioStream::ShutdownLibrary()
378 : {
379 1403 : Preferences::UnregisterCallback(PrefChanged, PREF_VOLUME_SCALE);
380 : #if defined(MOZ_CUBEB)
381 : Preferences::UnregisterCallback(PrefChanged, PREF_USE_CUBEB);
382 : #endif
383 1403 : delete gAudioPrefsLock;
384 1403 : gAudioPrefsLock = nsnull;
385 :
386 : #if defined(MOZ_CUBEB)
387 : if (gCubebContext) {
388 : cubeb_destroy(gCubebContext);
389 : gCubebContext = nsnull;
390 : }
391 : #endif
392 1403 : }
393 :
394 : nsIThread *
395 0 : nsAudioStream::GetThread()
396 : {
397 0 : if (!mAudioPlaybackThread) {
398 0 : NS_NewThread(getter_AddRefs(mAudioPlaybackThread),
399 : nsnull,
400 0 : MEDIA_THREAD_STACK_SIZE);
401 : }
402 0 : return mAudioPlaybackThread;
403 : }
404 :
405 : class AsyncShutdownPlaybackThread : public nsRunnable
406 0 : {
407 : public:
408 0 : AsyncShutdownPlaybackThread(nsIThread* aThread) : mThread(aThread) {}
409 0 : NS_IMETHODIMP Run() { return mThread->Shutdown(); }
410 : private:
411 : nsCOMPtr<nsIThread> mThread;
412 : };
413 :
414 0 : nsAudioStream::~nsAudioStream()
415 : {
416 0 : if (mAudioPlaybackThread) {
417 0 : nsCOMPtr<nsIRunnable> event = new AsyncShutdownPlaybackThread(mAudioPlaybackThread);
418 0 : NS_DispatchToMainThread(event);
419 : }
420 0 : }
421 :
422 0 : nsNativeAudioStream::nsNativeAudioStream() :
423 : mVolume(1.0),
424 : mAudioHandle(0),
425 : mPaused(false),
426 0 : mInError(false)
427 : {
428 0 : }
429 :
430 0 : nsNativeAudioStream::~nsNativeAudioStream()
431 : {
432 0 : Shutdown();
433 0 : }
434 :
435 0 : NS_IMPL_THREADSAFE_ISUPPORTS0(nsNativeAudioStream)
436 :
437 0 : nsresult nsNativeAudioStream::Init(PRInt32 aNumChannels, PRInt32 aRate, SampleFormat aFormat)
438 : {
439 0 : mRate = aRate;
440 0 : mChannels = aNumChannels;
441 0 : mFormat = aFormat;
442 :
443 0 : if (sa_stream_create_pcm(reinterpret_cast<sa_stream_t**>(&mAudioHandle),
444 : NULL,
445 : SA_MODE_WRONLY,
446 : SA_PCM_FORMAT_S16_NE,
447 : aRate,
448 0 : aNumChannels) != SA_SUCCESS) {
449 0 : mAudioHandle = nsnull;
450 0 : mInError = true;
451 0 : PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsNativeAudioStream: sa_stream_create_pcm error"));
452 0 : return NS_ERROR_FAILURE;
453 : }
454 :
455 0 : if (sa_stream_open(static_cast<sa_stream_t*>(mAudioHandle)) != SA_SUCCESS) {
456 0 : sa_stream_destroy(static_cast<sa_stream_t*>(mAudioHandle));
457 0 : mAudioHandle = nsnull;
458 0 : mInError = true;
459 0 : PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsNativeAudioStream: sa_stream_open error"));
460 0 : return NS_ERROR_FAILURE;
461 : }
462 0 : mInError = false;
463 :
464 0 : return NS_OK;
465 : }
466 :
467 0 : void nsNativeAudioStream::Shutdown()
468 : {
469 0 : if (!mAudioHandle)
470 0 : return;
471 :
472 0 : sa_stream_destroy(static_cast<sa_stream_t*>(mAudioHandle));
473 0 : mAudioHandle = nsnull;
474 0 : mInError = true;
475 : }
476 :
477 0 : nsresult nsNativeAudioStream::Write(const void* aBuf, PRUint32 aFrames)
478 : {
479 0 : NS_ASSERTION(!mPaused, "Don't write audio when paused, you'll block");
480 :
481 0 : if (mInError)
482 0 : return NS_ERROR_FAILURE;
483 :
484 0 : PRUint32 samples = aFrames * mChannels;
485 0 : nsAutoArrayPtr<short> s_data(new short[samples]);
486 :
487 0 : if (s_data) {
488 0 : double scaled_volume = GetVolumeScale() * mVolume;
489 0 : switch (mFormat) {
490 : case FORMAT_U8: {
491 0 : const PRUint8* buf = static_cast<const PRUint8*>(aBuf);
492 0 : PRInt32 volume = PRInt32((1 << 16) * scaled_volume);
493 0 : for (PRUint32 i = 0; i < samples; ++i) {
494 0 : s_data[i] = short(((PRInt32(buf[i]) - 128) * volume) >> 8);
495 : }
496 0 : break;
497 : }
498 : case FORMAT_S16_LE: {
499 0 : const short* buf = static_cast<const short*>(aBuf);
500 0 : PRInt32 volume = PRInt32((1 << 16) * scaled_volume);
501 0 : for (PRUint32 i = 0; i < samples; ++i) {
502 0 : short s = buf[i];
503 : #if defined(IS_BIG_ENDIAN)
504 : s = ((s & 0x00ff) << 8) | ((s & 0xff00) >> 8);
505 : #endif
506 0 : s_data[i] = short((PRInt32(s) * volume) >> 16);
507 : }
508 0 : break;
509 : }
510 : case FORMAT_FLOAT32: {
511 0 : const float* buf = static_cast<const float*>(aBuf);
512 0 : for (PRUint32 i = 0; i < samples; ++i) {
513 0 : float scaled_value = floorf(0.5 + 32768 * buf[i] * scaled_volume);
514 0 : if (buf[i] < 0.0) {
515 0 : s_data[i] = (scaled_value < -32768.0) ?
516 : -32768 :
517 0 : short(scaled_value);
518 : } else {
519 0 : s_data[i] = (scaled_value > 32767.0) ?
520 : 32767 :
521 0 : short(scaled_value);
522 : }
523 : }
524 0 : break;
525 : }
526 : }
527 :
528 0 : if (sa_stream_write(static_cast<sa_stream_t*>(mAudioHandle),
529 0 : s_data.get(),
530 0 : samples * sizeof(short)) != SA_SUCCESS)
531 : {
532 0 : PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsNativeAudioStream: sa_stream_write error"));
533 0 : mInError = true;
534 0 : return NS_ERROR_FAILURE;
535 : }
536 : }
537 0 : return NS_OK;
538 : }
539 :
540 0 : PRUint32 nsNativeAudioStream::Available()
541 : {
542 : // If the audio backend failed to open, lie and say we'll accept some
543 : // data.
544 0 : if (mInError)
545 0 : return FAKE_BUFFER_SIZE;
546 :
547 0 : size_t s = 0;
548 0 : if (sa_stream_get_write_size(static_cast<sa_stream_t*>(mAudioHandle), &s) != SA_SUCCESS)
549 0 : return 0;
550 :
551 0 : return s / mChannels / sizeof(short);
552 : }
553 :
554 0 : void nsNativeAudioStream::SetVolume(double aVolume)
555 : {
556 0 : NS_ASSERTION(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
557 : #if defined(SA_PER_STREAM_VOLUME)
558 : if (sa_stream_set_volume_abs(static_cast<sa_stream_t*>(mAudioHandle), aVolume) != SA_SUCCESS) {
559 : PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsNativeAudioStream: sa_stream_set_volume_abs error"));
560 : mInError = true;
561 : }
562 : #else
563 0 : mVolume = aVolume;
564 : #endif
565 0 : }
566 :
567 0 : void nsNativeAudioStream::Drain()
568 : {
569 0 : NS_ASSERTION(!mPaused, "Don't drain audio when paused, it won't finish!");
570 :
571 0 : if (mInError)
572 0 : return;
573 :
574 0 : int r = sa_stream_drain(static_cast<sa_stream_t*>(mAudioHandle));
575 0 : if (r != SA_SUCCESS && r != SA_ERROR_INVALID) {
576 0 : PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsNativeAudioStream: sa_stream_drain error"));
577 0 : mInError = true;
578 : }
579 : }
580 :
581 0 : void nsNativeAudioStream::Pause()
582 : {
583 0 : if (mInError)
584 0 : return;
585 0 : mPaused = true;
586 0 : sa_stream_pause(static_cast<sa_stream_t*>(mAudioHandle));
587 : }
588 :
589 0 : void nsNativeAudioStream::Resume()
590 : {
591 0 : if (mInError)
592 0 : return;
593 0 : mPaused = false;
594 0 : sa_stream_resume(static_cast<sa_stream_t*>(mAudioHandle));
595 : }
596 :
597 0 : PRInt64 nsNativeAudioStream::GetPosition()
598 : {
599 0 : PRInt64 position = GetPositionInFrames();
600 0 : if (position >= 0) {
601 0 : return ((USECS_PER_S * position) / mRate);
602 : }
603 0 : return -1;
604 : }
605 :
606 0 : PRInt64 nsNativeAudioStream::GetPositionInFrames()
607 : {
608 0 : if (mInError) {
609 0 : return -1;
610 : }
611 :
612 0 : sa_position_t positionType = SA_POSITION_WRITE_SOFTWARE;
613 : #if defined(XP_WIN)
614 : positionType = SA_POSITION_WRITE_HARDWARE;
615 : #endif
616 0 : int64_t position = 0;
617 0 : if (sa_stream_get_position(static_cast<sa_stream_t*>(mAudioHandle),
618 0 : positionType, &position) == SA_SUCCESS) {
619 0 : return position / mChannels / sizeof(short);
620 : }
621 :
622 0 : return -1;
623 : }
624 :
625 0 : bool nsNativeAudioStream::IsPaused()
626 : {
627 0 : return mPaused;
628 : }
629 :
630 0 : PRInt32 nsNativeAudioStream::GetMinWriteSize()
631 : {
632 : size_t size;
633 : int r = sa_stream_get_min_write(static_cast<sa_stream_t*>(mAudioHandle),
634 0 : &size);
635 0 : if (r == SA_ERROR_NOT_SUPPORTED)
636 0 : return 1;
637 0 : else if (r != SA_SUCCESS || size > PR_INT32_MAX)
638 0 : return -1;
639 :
640 0 : return static_cast<PRInt32>(size / mChannels / sizeof(short));
641 : }
642 :
643 : #if defined(REMOTE_AUDIO)
644 : nsRemotedAudioStream::nsRemotedAudioStream()
645 : : mAudioChild(nsnull),
646 : mBytesPerFrame(0),
647 : mPaused(false)
648 : {}
649 :
650 : nsRemotedAudioStream::~nsRemotedAudioStream()
651 : {
652 : Shutdown();
653 : }
654 :
655 : NS_IMPL_THREADSAFE_ISUPPORTS0(nsRemotedAudioStream)
656 :
657 : nsresult
658 : nsRemotedAudioStream::Init(PRInt32 aNumChannels,
659 : PRInt32 aRate,
660 : SampleFormat aFormat)
661 : {
662 : mRate = aRate;
663 : mChannels = aNumChannels;
664 : mFormat = aFormat;
665 :
666 : switch (mFormat) {
667 : case FORMAT_U8: {
668 : mBytesPerFrame = sizeof(PRUint8) * mChannels;
669 : break;
670 : }
671 : case FORMAT_S16_LE: {
672 : mBytesPerFrame = sizeof(short) * mChannels;
673 : break;
674 : }
675 : case FORMAT_FLOAT32: {
676 : mBytesPerFrame = sizeof(float) * mChannels;
677 : }
678 : }
679 :
680 : nsCOMPtr<nsIRunnable> event = new AudioInitEvent(this);
681 : NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
682 : return NS_OK;
683 : }
684 :
685 : void
686 : nsRemotedAudioStream::Shutdown()
687 : {
688 : if (!mAudioChild)
689 : return;
690 : nsCOMPtr<nsIRunnable> event = new AudioShutdownEvent(mAudioChild);
691 : NS_DispatchToMainThread(event);
692 : mAudioChild = nsnull;
693 : }
694 :
695 : nsresult
696 : nsRemotedAudioStream::Write(const void* aBuf, PRUint32 aFrames)
697 : {
698 : if (!mAudioChild)
699 : return NS_ERROR_FAILURE;
700 : nsCOMPtr<nsIRunnable> event = new AudioWriteEvent(mAudioChild,
701 : aBuf,
702 : aFrames,
703 : mBytesPerFrame);
704 : NS_DispatchToMainThread(event);
705 : return NS_OK;
706 : }
707 :
708 : PRUint32
709 : nsRemotedAudioStream::Available()
710 : {
711 : return FAKE_BUFFER_SIZE;
712 : }
713 :
714 : PRInt32 nsRemotedAudioStream::GetMinWriteSize()
715 : {
716 : if (!mAudioChild)
717 : return -1;
718 : nsCOMPtr<nsIRunnable> event = new AudioMinWriteSizeEvent(mAudioChild);
719 : NS_DispatchToMainThread(event);
720 : return mAudioChild->WaitForMinWriteSize();
721 : }
722 :
723 : void
724 : nsRemotedAudioStream::SetVolume(double aVolume)
725 : {
726 : if (!mAudioChild)
727 : return;
728 : nsCOMPtr<nsIRunnable> event = new AudioSetVolumeEvent(mAudioChild, aVolume);
729 : NS_DispatchToMainThread(event);
730 : }
731 :
732 : void
733 : nsRemotedAudioStream::Drain()
734 : {
735 : if (!mAudioChild)
736 : return;
737 : nsCOMPtr<nsIRunnable> event = new AudioDrainEvent(mAudioChild);
738 : NS_DispatchToMainThread(event);
739 : mAudioChild->WaitForDrain();
740 : }
741 :
742 : void
743 : nsRemotedAudioStream::Pause()
744 : {
745 : mPaused = true;
746 : if (!mAudioChild)
747 : return;
748 : nsCOMPtr<nsIRunnable> event = new AudioPauseEvent(mAudioChild, true);
749 : NS_DispatchToMainThread(event);
750 : }
751 :
752 : void
753 : nsRemotedAudioStream::Resume()
754 : {
755 : mPaused = false;
756 : if (!mAudioChild)
757 : return;
758 : nsCOMPtr<nsIRunnable> event = new AudioPauseEvent(mAudioChild, false);
759 : NS_DispatchToMainThread(event);
760 : }
761 :
762 : PRInt64 nsRemotedAudioStream::GetPosition()
763 : {
764 : PRInt64 position = GetPositionInFrames();
765 : if (position >= 0) {
766 : return ((USECS_PER_S * position) / mRate);
767 : }
768 : return 0;
769 : }
770 :
771 : PRInt64
772 : nsRemotedAudioStream::GetPositionInFrames()
773 : {
774 : if(!mAudioChild)
775 : return 0;
776 :
777 : PRInt64 position = mAudioChild->GetLastKnownPosition();
778 : if (position == -1)
779 : return 0;
780 :
781 : PRInt64 time = mAudioChild->GetLastKnownPositionTimestamp();
782 : PRInt64 dt = PR_IntervalToMilliseconds(PR_IntervalNow() - time);
783 :
784 : return position + (mRate * dt / MS_PER_S);
785 : }
786 :
787 : bool
788 : nsRemotedAudioStream::IsPaused()
789 : {
790 : return mPaused;
791 : }
792 : #endif
793 :
794 : #if defined(MOZ_CUBEB)
795 : template <>
796 : class nsAutoRefTraits<cubeb_stream> : public nsPointerRefTraits<cubeb_stream>
797 : {
798 : public:
799 : static void Release(cubeb_stream* aStream) { cubeb_stream_destroy(aStream); }
800 : };
801 :
802 : class nsBufferedAudioStream : public nsAudioStream
803 : {
804 : public:
805 : NS_DECL_ISUPPORTS
806 :
807 : nsBufferedAudioStream();
808 : ~nsBufferedAudioStream();
809 :
810 : nsresult Init(PRInt32 aNumChannels, PRInt32 aRate, SampleFormat aFormat);
811 : void Shutdown();
812 : nsresult Write(const void* aBuf, PRUint32 aFrames);
813 : PRUint32 Available();
814 : void SetVolume(double aVolume);
815 : void Drain();
816 : void Pause();
817 : void Resume();
818 : PRInt64 GetPosition();
819 : PRInt64 GetPositionInFrames();
820 : bool IsPaused();
821 : PRInt32 GetMinWriteSize();
822 :
823 : private:
824 : static long DataCallback_S(cubeb_stream*, void* aThis, void* aBuffer, long aFrames)
825 : {
826 : return static_cast<nsBufferedAudioStream*>(aThis)->DataCallback(aBuffer, aFrames);
827 : }
828 :
829 : static int StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState)
830 : {
831 : return static_cast<nsBufferedAudioStream*>(aThis)->StateCallback(aState);
832 : }
833 :
834 : long DataCallback(void* aBuffer, long aFrames);
835 : int StateCallback(cubeb_state aState);
836 :
837 : // Shared implementation of underflow adjusted position calculation.
838 : // Caller must own the monitor.
839 : PRInt64 GetPositionInFramesUnlocked();
840 :
841 : // The monitor is held to protect all access to member variables. Write()
842 : // waits while mBuffer is full; DataCallback() notifies as it consumes
843 : // data from mBuffer. Drain() waits while mState is DRAINING;
844 : // StateCallback() notifies when mState is DRAINED.
845 : Monitor mMonitor;
846 :
847 : // Sum of silent frames written when DataCallback requests more frames
848 : // than are available in mBuffer.
849 : PRUint64 mLostFrames;
850 :
851 : // Temporary audio buffer. Filled by Write() and consumed by
852 : // DataCallback(). Once mBufferLimit is reached, Write() blocks until
853 : // sufficient space becomes available in mBuffer. The buffer and buffer
854 : // limit deal in bytes, not frames.
855 : nsTArray<PRUint8> mBuffer;
856 : PRUint32 mBufferLimit;
857 :
858 : // Software volume level. Applied during the servicing of DataCallback().
859 : double mVolume;
860 :
861 : // Owning reference to a cubeb_stream. cubeb_stream_destroy is called by
862 : // nsAutoRef's destructor.
863 : nsAutoRef<cubeb_stream> mCubebStream;
864 :
865 : PRUint32 mBytesPerFrame;
866 :
867 : enum StreamState {
868 : INITIALIZED, // Initialized, playback has not begun.
869 : STARTED, // Started by a call to Write() (iff INITIALIZED) or Resume().
870 : STOPPED, // Stopped by a call to Pause().
871 : DRAINING, // Drain requested. DataCallback will indicate end of stream
872 : // once the remaining contents of mBuffer are requested by
873 : // cubeb, after which StateCallback will indicate drain
874 : // completion.
875 : DRAINED // StateCallback has indicated that the drain is complete.
876 : };
877 :
878 : StreamState mState;
879 :
880 : // Arbitrary default stream latency. The higher this value, the longer stream
881 : // volume changes will take to become audible.
882 : static const unsigned int DEFAULT_LATENCY_MS = 100;
883 : };
884 : #endif
885 :
886 0 : nsAudioStream* nsAudioStream::AllocateStream()
887 : {
888 : #if defined(REMOTE_AUDIO)
889 : if (XRE_GetProcessType() == GeckoProcessType_Content) {
890 : return new nsRemotedAudioStream();
891 : }
892 : #endif
893 : #if defined(MOZ_CUBEB)
894 : if (GetUseCubeb()) {
895 : return new nsBufferedAudioStream();
896 : }
897 : #endif
898 0 : return new nsNativeAudioStream();
899 : }
900 :
901 : #if defined(MOZ_CUBEB)
902 : nsBufferedAudioStream::nsBufferedAudioStream()
903 : : mMonitor("nsBufferedAudioStream"), mLostFrames(0), mVolume(1.0),
904 : mBytesPerFrame(0), mState(INITIALIZED)
905 : {
906 : }
907 :
908 : nsBufferedAudioStream::~nsBufferedAudioStream()
909 : {
910 : Shutdown();
911 : }
912 :
913 : NS_IMPL_THREADSAFE_ISUPPORTS0(nsBufferedAudioStream)
914 :
915 : nsresult
916 : nsBufferedAudioStream::Init(PRInt32 aNumChannels, PRInt32 aRate, SampleFormat aFormat)
917 : {
918 : if (!gCubebContext || aNumChannels < 0 || aRate < 0) {
919 : return NS_ERROR_FAILURE;
920 : }
921 :
922 : mRate = aRate;
923 : mChannels = aNumChannels;
924 : mFormat = aFormat;
925 :
926 : cubeb_stream_params params;
927 : params.rate = aRate;
928 : params.channels = aNumChannels;
929 : switch (aFormat) {
930 : case FORMAT_S16_LE:
931 : params.format = CUBEB_SAMPLE_S16LE;
932 : mBytesPerFrame = sizeof(short) * aNumChannels;
933 : break;
934 : case FORMAT_FLOAT32:
935 : params.format = CUBEB_SAMPLE_FLOAT32LE;
936 : mBytesPerFrame = sizeof(float) * aNumChannels;
937 : break;
938 : default:
939 : return NS_ERROR_FAILURE;
940 : }
941 :
942 : {
943 : cubeb_stream* stream;
944 : if (cubeb_stream_init(gCubebContext, &stream, "nsBufferedAudioStream", params,
945 : DEFAULT_LATENCY_MS, DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
946 : mCubebStream.own(stream);
947 : }
948 : }
949 :
950 : if (!mCubebStream) {
951 : return NS_ERROR_FAILURE;
952 : }
953 :
954 : // Limit mBuffer to one second of audio. This value is arbitrary, and was
955 : // selected based on the observed behaviour of the existing nsAudioStream
956 : // implementations.
957 : mBufferLimit = aRate * mBytesPerFrame;
958 : NS_ABORT_IF_FALSE(mBufferLimit % mBytesPerFrame == 0, "Must buffer complete frames");
959 :
960 : // Pre-allocate the buffer. nsTArray::RemoveElementsAt shrinks the buffer
961 : // only if its length reaches zero, so allocator thrashing should be
962 : // minimal.
963 : mBuffer.SetCapacity(mBufferLimit);
964 :
965 : return NS_OK;
966 : }
967 :
968 : void
969 : nsBufferedAudioStream::Shutdown()
970 : {
971 : if (mCubebStream) {
972 : cubeb_stream_stop(mCubebStream);
973 : mCubebStream.reset();
974 : }
975 : }
976 :
977 : nsresult
978 : nsBufferedAudioStream::Write(const void* aBuf, PRUint32 aFrames)
979 : {
980 : MonitorAutoLock mon(mMonitor);
981 : if (!mCubebStream) {
982 : return NS_ERROR_FAILURE;
983 : }
984 : NS_ASSERTION(mState == INITIALIZED || mState == STARTED, "Stream write in unexpected state.");
985 :
986 : const PRUint8* src = static_cast<const PRUint8*>(aBuf);
987 : PRUint32 bytesToCopy = aFrames * mBytesPerFrame;
988 :
989 : while (bytesToCopy > 0) {
990 : NS_ABORT_IF_FALSE(mBuffer.Length() <= mBufferLimit, "Buffer invariant violated.");
991 :
992 : PRUint32 available = NS_MIN(bytesToCopy, mBufferLimit - mBuffer.Length());
993 : NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, "Must copy complete frames.");
994 :
995 : mBuffer.AppendElements(src, available);
996 : src += available;
997 : bytesToCopy -= available;
998 :
999 : if (mState != STARTED && cubeb_stream_start(mCubebStream) == CUBEB_OK) {
1000 : mState = STARTED;
1001 : }
1002 :
1003 : if (bytesToCopy > 0) {
1004 : mon.Wait();
1005 : }
1006 : }
1007 :
1008 : return NS_OK;
1009 : }
1010 :
1011 : PRUint32
1012 : nsBufferedAudioStream::Available()
1013 : {
1014 : MonitorAutoLock mon(mMonitor);
1015 : NS_ABORT_IF_FALSE(mBuffer.Length() <= mBufferLimit, "Buffer invariant violated.");
1016 : NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Buffer invariant violated.");
1017 : return (mBufferLimit - mBuffer.Length()) / mBytesPerFrame;
1018 : }
1019 :
1020 : PRInt32 nsBufferedAudioStream::GetMinWriteSize()
1021 : {
1022 : return 1;
1023 : }
1024 :
1025 : void
1026 : nsBufferedAudioStream::SetVolume(double aVolume)
1027 : {
1028 : MonitorAutoLock mon(mMonitor);
1029 : NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
1030 : mVolume = aVolume;
1031 : }
1032 :
1033 : void
1034 : nsBufferedAudioStream::Drain()
1035 : {
1036 : MonitorAutoLock mon(mMonitor);
1037 : if (mState != STARTED) {
1038 : return;
1039 : }
1040 : mState = DRAINING;
1041 : while (mState != DRAINED) {
1042 : mon.Wait();
1043 : }
1044 : }
1045 :
1046 : void
1047 : nsBufferedAudioStream::Pause()
1048 : {
1049 : MonitorAutoLock mon(mMonitor);
1050 : if (!mCubebStream || mState != STARTED) {
1051 : return;
1052 : }
1053 :
1054 : if (cubeb_stream_stop(mCubebStream) == CUBEB_OK) {
1055 : mState = STOPPED;
1056 : }
1057 : }
1058 :
1059 : void
1060 : nsBufferedAudioStream::Resume()
1061 : {
1062 : MonitorAutoLock mon(mMonitor);
1063 : if (!mCubebStream || mState != STOPPED) {
1064 : return;
1065 : }
1066 :
1067 : if (cubeb_stream_start(mCubebStream) == CUBEB_OK) {
1068 : mState = STARTED;
1069 : }
1070 : }
1071 :
1072 : PRInt64 nsBufferedAudioStream::GetPosition()
1073 : {
1074 : MonitorAutoLock mon(mMonitor);
1075 : PRInt64 frames = GetPositionInFramesUnlocked();
1076 : if (frames >= 0) {
1077 : return USECS_PER_S * frames / mRate;
1078 : }
1079 : return -1;
1080 : }
1081 :
1082 : PRInt64
1083 : nsBufferedAudioStream::GetPositionInFrames()
1084 : {
1085 : MonitorAutoLock mon(mMonitor);
1086 : return GetPositionInFramesUnlocked();
1087 : }
1088 :
1089 : PRInt64
1090 : nsBufferedAudioStream::GetPositionInFramesUnlocked()
1091 : {
1092 : mMonitor.AssertCurrentThreadOwns();
1093 :
1094 : if (!mCubebStream) {
1095 : return -1;
1096 : }
1097 :
1098 : uint64_t position = 0;
1099 : if (cubeb_stream_get_position(mCubebStream, &position) != CUBEB_OK) {
1100 : return -1;
1101 : }
1102 :
1103 : // Adjust the reported position by the number of silent frames written
1104 : // during stream underruns.
1105 : PRInt64 adjustedPosition = 0;
1106 : if (position >= mLostFrames) {
1107 : adjustedPosition = position - mLostFrames;
1108 : }
1109 : return adjustedPosition;
1110 : }
1111 :
1112 : bool
1113 : nsBufferedAudioStream::IsPaused()
1114 : {
1115 : MonitorAutoLock mon(mMonitor);
1116 : return mState == STOPPED;
1117 : }
1118 :
1119 : template<typename T>
1120 : void
1121 : SampleCopy(void* aDst, const PRUint8* aSrc, PRUint32 aSamples, double aVolume)
1122 : {
1123 : const T* src = reinterpret_cast<const T*>(aSrc);
1124 : double scaled_volume = GetVolumeScale() * aVolume;
1125 : T* dst = static_cast<T*>(aDst);
1126 : for (PRUint32 i = 0; i < aSamples; ++i) {
1127 : dst[i] = T(src[i] * scaled_volume);
1128 : }
1129 : }
1130 :
1131 : long
1132 : nsBufferedAudioStream::DataCallback(void* aBuffer, long aFrames)
1133 : {
1134 : MonitorAutoLock mon(mMonitor);
1135 : PRUint32 bytesWanted = aFrames * mBytesPerFrame;
1136 :
1137 : // Adjust bytesWanted to fit what is available in mBuffer.
1138 : PRUint32 available = NS_MIN(bytesWanted, mBuffer.Length());
1139 : NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, "Must copy complete frames");
1140 :
1141 : // Copy each sample from mBuffer to aBuffer, adjusting the volume during the copy.
1142 : PRUint32 samplesToCopy = available / mBytesPerFrame * mChannels;
1143 : switch (mFormat) {
1144 : case FORMAT_S16_LE:
1145 : SampleCopy<PRInt16>(aBuffer, mBuffer.Elements(), samplesToCopy, mVolume);
1146 : break;
1147 : case FORMAT_FLOAT32:
1148 : SampleCopy<float>(aBuffer, mBuffer.Elements(), samplesToCopy, mVolume);
1149 : break;
1150 : default:
1151 : return -1;
1152 : }
1153 :
1154 : // Remove copied data from the temporary audio buffer.
1155 : mBuffer.RemoveElementsAt(0, available);
1156 : NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames");
1157 :
1158 : // Notify any blocked Write() call that more space is available in mBuffer.
1159 : mon.NotifyAll();
1160 :
1161 : // Calculate remaining bytes requested by caller. If the stream is not
1162 : // draining an underrun has occurred, so fill the remaining buffer with
1163 : // silence.
1164 : bytesWanted -= available;
1165 : if (mState != DRAINING) {
1166 : memset(static_cast<PRUint8*>(aBuffer) + available, 0, bytesWanted);
1167 : mLostFrames += bytesWanted / mBytesPerFrame;
1168 : bytesWanted = 0;
1169 : }
1170 :
1171 : return aFrames - (bytesWanted / mBytesPerFrame);
1172 : }
1173 :
1174 : int
1175 : nsBufferedAudioStream::StateCallback(cubeb_state aState)
1176 : {
1177 : if (aState == CUBEB_STATE_DRAINED) {
1178 : MonitorAutoLock mon(mMonitor);
1179 : mState = DRAINED;
1180 : mon.NotifyAll();
1181 : }
1182 : return CUBEB_OK;
1183 : }
1184 : #endif
1185 :
|