Line data Source code
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 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 : #include <stdio.h>
7 : #include <math.h>
8 : #include <string.h>
9 : #include "mozilla/Logging.h"
10 : #include "prdtoa.h"
11 : #include "AudioStream.h"
12 : #include "VideoUtils.h"
13 : #include "mozilla/Monitor.h"
14 : #include "mozilla/Mutex.h"
15 : #include "mozilla/Sprintf.h"
16 : #include <algorithm>
17 : #include "mozilla/Telemetry.h"
18 : #include "CubebUtils.h"
19 : #include "nsPrintfCString.h"
20 : #include "gfxPrefs.h"
21 : #include "AudioConverter.h"
22 :
23 : namespace mozilla {
24 :
25 : #undef LOG
26 : #undef LOGW
27 :
28 : LazyLogModule gAudioStreamLog("AudioStream");
29 : // For simple logs
30 : #define LOG(x, ...) MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Debug, ("%p " x, this, ##__VA_ARGS__))
31 : #define LOGW(x, ...) MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Warning, ("%p " x, this, ##__VA_ARGS__))
32 :
33 : /**
34 : * Keep a list of frames sent to the audio engine in each DataCallback along
35 : * with the playback rate at the moment. Since the playback rate and number of
36 : * underrun frames can vary in each callback. We need to keep the whole history
37 : * in order to calculate the playback position of the audio engine correctly.
38 : */
39 0 : class FrameHistory {
40 : struct Chunk {
41 : uint32_t servicedFrames;
42 : uint32_t totalFrames;
43 : uint32_t rate;
44 : };
45 :
46 : template <typename T>
47 0 : static T FramesToUs(uint32_t frames, int rate) {
48 0 : return static_cast<T>(frames) * USECS_PER_S / rate;
49 : }
50 : public:
51 0 : FrameHistory()
52 0 : : mBaseOffset(0), mBasePosition(0) {}
53 :
54 0 : void Append(uint32_t aServiced, uint32_t aUnderrun, uint32_t aRate) {
55 : /* In most case where playback rate stays the same and we don't underrun
56 : * frames, we are able to merge chunks to avoid lose of precision to add up
57 : * in compressing chunks into |mBaseOffset| and |mBasePosition|.
58 : */
59 0 : if (!mChunks.IsEmpty()) {
60 0 : Chunk& c = mChunks.LastElement();
61 : // 2 chunks (c1 and c2) can be merged when rate is the same and
62 : // adjacent frames are zero. That is, underrun frames in c1 are zero
63 : // or serviced frames in c2 are zero.
64 0 : if (c.rate == aRate &&
65 0 : (c.servicedFrames == c.totalFrames ||
66 : aServiced == 0)) {
67 0 : c.servicedFrames += aServiced;
68 0 : c.totalFrames += aServiced + aUnderrun;
69 0 : return;
70 : }
71 : }
72 0 : Chunk* p = mChunks.AppendElement();
73 0 : p->servicedFrames = aServiced;
74 0 : p->totalFrames = aServiced + aUnderrun;
75 0 : p->rate = aRate;
76 : }
77 :
78 : /**
79 : * @param frames The playback position in frames of the audio engine.
80 : * @return The playback position in microseconds of the audio engine,
81 : * adjusted by playback rate changes and underrun frames.
82 : */
83 0 : int64_t GetPosition(int64_t frames) {
84 : // playback position should not go backward.
85 0 : MOZ_ASSERT(frames >= mBaseOffset);
86 : while (true) {
87 0 : if (mChunks.IsEmpty()) {
88 0 : return mBasePosition;
89 : }
90 0 : const Chunk& c = mChunks[0];
91 0 : if (frames <= mBaseOffset + c.totalFrames) {
92 0 : uint32_t delta = frames - mBaseOffset;
93 0 : delta = std::min(delta, c.servicedFrames);
94 0 : return static_cast<int64_t>(mBasePosition) +
95 0 : FramesToUs<int64_t>(delta, c.rate);
96 : }
97 : // Since the playback position of the audio engine will not go backward,
98 : // we are able to compress chunks so that |mChunks| won't grow unlimitedly.
99 : // Note that we lose precision in converting integers into floats and
100 : // inaccuracy will accumulate over time. However, for a 24hr long,
101 : // sample rate = 44.1k file, the error will be less than 1 microsecond
102 : // after playing 24 hours. So we are fine with that.
103 0 : mBaseOffset += c.totalFrames;
104 0 : mBasePosition += FramesToUs<double>(c.servicedFrames, c.rate);
105 0 : mChunks.RemoveElementAt(0);
106 0 : }
107 : }
108 : private:
109 : AutoTArray<Chunk, 7> mChunks;
110 : int64_t mBaseOffset;
111 : double mBasePosition;
112 : };
113 :
114 0 : AudioStream::AudioStream(DataSource& aSource)
115 : : mMonitor("AudioStream")
116 : , mChannels(0)
117 : , mOutChannels(0)
118 : , mTimeStretcher(nullptr)
119 : , mDumpFile(nullptr)
120 : , mState(INITIALIZED)
121 0 : , mDataSource(aSource)
122 : {
123 0 : }
124 :
125 0 : AudioStream::~AudioStream()
126 : {
127 0 : LOG("deleted, state %d", mState);
128 0 : MOZ_ASSERT(mState == SHUTDOWN && !mCubebStream,
129 : "Should've called Shutdown() before deleting an AudioStream");
130 0 : if (mDumpFile) {
131 0 : fclose(mDumpFile);
132 : }
133 0 : if (mTimeStretcher) {
134 0 : soundtouch::destroySoundTouchObj(mTimeStretcher);
135 : }
136 0 : }
137 :
138 : size_t
139 0 : AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
140 : {
141 0 : size_t amount = aMallocSizeOf(this);
142 :
143 : // Possibly add in the future:
144 : // - mTimeStretcher
145 : // - mCubebStream
146 :
147 0 : return amount;
148 : }
149 :
150 0 : nsresult AudioStream::EnsureTimeStretcherInitializedUnlocked()
151 : {
152 0 : mMonitor.AssertCurrentThreadOwns();
153 0 : if (!mTimeStretcher) {
154 0 : mTimeStretcher = soundtouch::createSoundTouchObj();
155 0 : mTimeStretcher->setSampleRate(mAudioClock.GetInputRate());
156 0 : mTimeStretcher->setChannels(mOutChannels);
157 0 : mTimeStretcher->setPitch(1.0);
158 : }
159 0 : return NS_OK;
160 : }
161 :
162 0 : nsresult AudioStream::SetPlaybackRate(double aPlaybackRate)
163 : {
164 : // MUST lock since the rate transposer is used from the cubeb callback,
165 : // and rate changes can cause the buffer to be reallocated
166 0 : MonitorAutoLock mon(mMonitor);
167 :
168 0 : NS_ASSERTION(aPlaybackRate > 0.0,
169 : "Can't handle negative or null playbackrate in the AudioStream.");
170 : // Avoid instantiating the resampler if we are not changing the playback rate.
171 : // GetPreservesPitch/SetPreservesPitch don't need locking before calling
172 0 : if (aPlaybackRate == mAudioClock.GetPlaybackRate()) {
173 0 : return NS_OK;
174 : }
175 :
176 0 : if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
177 0 : return NS_ERROR_FAILURE;
178 : }
179 :
180 0 : mAudioClock.SetPlaybackRate(aPlaybackRate);
181 :
182 0 : if (mAudioClock.GetPreservesPitch()) {
183 0 : mTimeStretcher->setTempo(aPlaybackRate);
184 0 : mTimeStretcher->setRate(1.0f);
185 : } else {
186 0 : mTimeStretcher->setTempo(1.0f);
187 0 : mTimeStretcher->setRate(aPlaybackRate);
188 : }
189 0 : return NS_OK;
190 : }
191 :
192 0 : nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch)
193 : {
194 : // MUST lock since the rate transposer is used from the cubeb callback,
195 : // and rate changes can cause the buffer to be reallocated
196 0 : MonitorAutoLock mon(mMonitor);
197 :
198 : // Avoid instantiating the timestretcher instance if not needed.
199 0 : if (aPreservesPitch == mAudioClock.GetPreservesPitch()) {
200 0 : return NS_OK;
201 : }
202 :
203 0 : if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
204 0 : return NS_ERROR_FAILURE;
205 : }
206 :
207 0 : if (aPreservesPitch == true) {
208 0 : mTimeStretcher->setTempo(mAudioClock.GetPlaybackRate());
209 0 : mTimeStretcher->setRate(1.0f);
210 : } else {
211 0 : mTimeStretcher->setTempo(1.0f);
212 0 : mTimeStretcher->setRate(mAudioClock.GetPlaybackRate());
213 : }
214 :
215 0 : mAudioClock.SetPreservesPitch(aPreservesPitch);
216 :
217 0 : return NS_OK;
218 : }
219 :
220 0 : static void SetUint16LE(uint8_t* aDest, uint16_t aValue)
221 : {
222 0 : aDest[0] = aValue & 0xFF;
223 0 : aDest[1] = aValue >> 8;
224 0 : }
225 :
226 0 : static void SetUint32LE(uint8_t* aDest, uint32_t aValue)
227 : {
228 0 : SetUint16LE(aDest, aValue & 0xFFFF);
229 0 : SetUint16LE(aDest + 2, aValue >> 16);
230 0 : }
231 :
232 : static FILE*
233 0 : OpenDumpFile(uint32_t aChannels, uint32_t aRate)
234 : {
235 : /**
236 : * When MOZ_DUMP_AUDIO is set in the environment (to anything),
237 : * we'll drop a series of files in the current working directory named
238 : * dumped-audio-<nnn>.wav, one per AudioStream created, containing
239 : * the audio for the stream including any skips due to underruns.
240 : */
241 : static Atomic<int> gDumpedAudioCount(0);
242 :
243 0 : if (!getenv("MOZ_DUMP_AUDIO"))
244 0 : return nullptr;
245 : char buf[100];
246 0 : SprintfLiteral(buf, "dumped-audio-%d.wav", ++gDumpedAudioCount);
247 0 : FILE* f = fopen(buf, "wb");
248 0 : if (!f)
249 0 : return nullptr;
250 :
251 : uint8_t header[] = {
252 : // RIFF header
253 : 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45,
254 : // fmt chunk. We always write 16-bit samples.
255 : 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF,
256 : 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x00,
257 : // data chunk
258 : 0x64, 0x61, 0x74, 0x61, 0xFE, 0xFF, 0xFF, 0x7F
259 0 : };
260 : static const int CHANNEL_OFFSET = 22;
261 : static const int SAMPLE_RATE_OFFSET = 24;
262 : static const int BLOCK_ALIGN_OFFSET = 32;
263 0 : SetUint16LE(header + CHANNEL_OFFSET, aChannels);
264 0 : SetUint32LE(header + SAMPLE_RATE_OFFSET, aRate);
265 0 : SetUint16LE(header + BLOCK_ALIGN_OFFSET, aChannels * 2);
266 0 : fwrite(header, sizeof(header), 1, f);
267 :
268 0 : return f;
269 : }
270 :
271 : template <typename T>
272 : typename EnableIf<IsSame<T, int16_t>::value, void>::Type
273 : WriteDumpFileHelper(T* aInput, size_t aSamples, FILE* aFile) {
274 : fwrite(aInput, sizeof(T), aSamples, aFile);
275 : }
276 :
277 : template <typename T>
278 : typename EnableIf<IsSame<T, float>::value, void>::Type
279 0 : WriteDumpFileHelper(T* aInput, size_t aSamples, FILE* aFile) {
280 0 : AutoTArray<uint8_t, 1024*2> buf;
281 0 : buf.SetLength(aSamples*2);
282 0 : uint8_t* output = buf.Elements();
283 0 : for (uint32_t i = 0; i < aSamples; ++i) {
284 0 : SetUint16LE(output + i*2, int16_t(aInput[i]*32767.0f));
285 : }
286 0 : fwrite(output, 2, aSamples, aFile);
287 0 : fflush(aFile);
288 0 : }
289 :
290 : static void
291 0 : WriteDumpFile(FILE* aDumpFile, AudioStream* aStream, uint32_t aFrames,
292 : void* aBuffer)
293 : {
294 0 : if (!aDumpFile)
295 0 : return;
296 :
297 0 : uint32_t samples = aStream->GetOutChannels()*aFrames;
298 :
299 : using SampleT = AudioSampleTraits<AUDIO_OUTPUT_FORMAT>::Type;
300 0 : WriteDumpFileHelper(reinterpret_cast<SampleT*>(aBuffer), samples, aDumpFile);
301 : }
302 :
303 : template <AudioSampleFormat N>
304 : struct ToCubebFormat {
305 : static const cubeb_sample_format value = CUBEB_SAMPLE_FLOAT32NE;
306 : };
307 :
308 : template <>
309 : struct ToCubebFormat<AUDIO_FORMAT_S16> {
310 : static const cubeb_sample_format value = CUBEB_SAMPLE_S16NE;
311 : };
312 :
313 : template <typename Function, typename... Args>
314 0 : int AudioStream::InvokeCubeb(Function aFunction, Args&&... aArgs)
315 : {
316 0 : MonitorAutoUnlock mon(mMonitor);
317 0 : return aFunction(mCubebStream.get(), Forward<Args>(aArgs)...);
318 : }
319 :
320 : nsresult
321 0 : AudioStream::Init(uint32_t aNumChannels, uint32_t aChannelMap, uint32_t aRate,
322 : const dom::AudioChannel aAudioChannel)
323 : {
324 0 : auto startTime = TimeStamp::Now();
325 :
326 0 : LOG("%s channels: %d, rate: %d", __FUNCTION__, aNumChannels, aRate);
327 0 : mChannels = aNumChannels;
328 0 : mOutChannels = aNumChannels;
329 :
330 0 : mDumpFile = OpenDumpFile(aNumChannels, aRate);
331 :
332 : cubeb_stream_params params;
333 0 : params.rate = aRate;
334 0 : params.channels = mOutChannels;
335 0 : params.layout = CubebUtils::ConvertChannelMapToCubebLayout(aChannelMap);
336 : #if defined(__ANDROID__)
337 : #if defined(MOZ_B2G)
338 : params.stream_type = CubebUtils::ConvertChannelToCubebType(aAudioChannel);
339 : #else
340 : params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
341 : #endif
342 :
343 : if (params.stream_type == CUBEB_STREAM_TYPE_MAX) {
344 : return NS_ERROR_INVALID_ARG;
345 : }
346 : #endif
347 :
348 0 : params.format = ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value;
349 0 : mAudioClock.Init(aRate);
350 :
351 0 : cubeb* cubebContext = CubebUtils::GetCubebContext();
352 0 : if (!cubebContext) {
353 0 : NS_WARNING("Can't get cubeb context!");
354 0 : CubebUtils::ReportCubebStreamInitFailure(true);
355 0 : return NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR;
356 : }
357 :
358 0 : return OpenCubeb(cubebContext, params, startTime, CubebUtils::GetFirstStream());
359 : }
360 :
361 : nsresult
362 0 : AudioStream::OpenCubeb(cubeb* aContext, cubeb_stream_params& aParams,
363 : TimeStamp aStartTime, bool aIsFirst)
364 : {
365 0 : MOZ_ASSERT(aContext);
366 :
367 0 : cubeb_stream* stream = nullptr;
368 : /* Convert from milliseconds to frames. */
369 : uint32_t latency_frames =
370 0 : CubebUtils::GetCubebPlaybackLatencyInMilliseconds() * aParams.rate / 1000;
371 0 : if (cubeb_stream_init(aContext, &stream, "AudioStream",
372 : nullptr, nullptr, nullptr, &aParams,
373 : latency_frames,
374 : DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
375 0 : mCubebStream.reset(stream);
376 0 : CubebUtils::ReportCubebBackendUsed();
377 : } else {
378 0 : NS_WARNING(nsPrintfCString("AudioStream::OpenCubeb() %p failed to init cubeb", this).get());
379 0 : CubebUtils::ReportCubebStreamInitFailure(aIsFirst);
380 0 : return NS_ERROR_FAILURE;
381 : }
382 :
383 0 : TimeDuration timeDelta = TimeStamp::Now() - aStartTime;
384 0 : LOG("creation time %sfirst: %u ms", aIsFirst ? "" : "not ",
385 : (uint32_t) timeDelta.ToMilliseconds());
386 0 : Telemetry::Accumulate(aIsFirst ? Telemetry::AUDIOSTREAM_FIRST_OPEN_MS :
387 0 : Telemetry::AUDIOSTREAM_LATER_OPEN_MS, timeDelta.ToMilliseconds());
388 :
389 0 : return NS_OK;
390 : }
391 :
392 : void
393 0 : AudioStream::SetVolume(double aVolume)
394 : {
395 0 : MOZ_ASSERT(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
396 :
397 0 : if (cubeb_stream_set_volume(mCubebStream.get(), aVolume * CubebUtils::GetVolumeScale()) != CUBEB_OK) {
398 0 : NS_WARNING("Could not change volume on cubeb stream.");
399 : }
400 0 : }
401 :
402 : void
403 0 : AudioStream::Start()
404 : {
405 0 : MonitorAutoLock mon(mMonitor);
406 0 : MOZ_ASSERT(mState == INITIALIZED);
407 0 : mState = STARTED;
408 0 : auto r = InvokeCubeb(cubeb_stream_start);
409 0 : if (r != CUBEB_OK) {
410 0 : mState = ERRORED;
411 : }
412 0 : LOG("started, state %s", mState == STARTED ? "STARTED" : mState == DRAINED ? "DRAINED" : "ERRORED");
413 0 : }
414 :
415 : void
416 0 : AudioStream::Pause()
417 : {
418 0 : MonitorAutoLock mon(mMonitor);
419 0 : MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed.");
420 0 : MOZ_ASSERT(mState != STOPPED, "Already Pause()ed.");
421 0 : MOZ_ASSERT(mState != SHUTDOWN, "Already Shutdown()ed.");
422 :
423 : // Do nothing if we are already drained or errored.
424 0 : if (mState == DRAINED || mState == ERRORED) {
425 0 : return;
426 : }
427 :
428 0 : if (InvokeCubeb(cubeb_stream_stop) != CUBEB_OK) {
429 0 : mState = ERRORED;
430 0 : } else if (mState != DRAINED && mState != ERRORED) {
431 : // Don't transition to other states if we are already
432 : // drained or errored.
433 0 : mState = STOPPED;
434 : }
435 : }
436 :
437 : void
438 0 : AudioStream::Resume()
439 : {
440 0 : MonitorAutoLock mon(mMonitor);
441 0 : MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed.");
442 0 : MOZ_ASSERT(mState != STARTED, "Already Start()ed.");
443 0 : MOZ_ASSERT(mState != SHUTDOWN, "Already Shutdown()ed.");
444 :
445 : // Do nothing if we are already drained or errored.
446 0 : if (mState == DRAINED || mState == ERRORED) {
447 0 : return;
448 : }
449 :
450 0 : if (InvokeCubeb(cubeb_stream_start) != CUBEB_OK) {
451 0 : mState = ERRORED;
452 0 : } else if (mState != DRAINED && mState != ERRORED) {
453 : // Don't transition to other states if we are already
454 : // drained or errored.
455 0 : mState = STARTED;
456 : }
457 : }
458 :
459 : void
460 0 : AudioStream::Shutdown()
461 : {
462 0 : MonitorAutoLock mon(mMonitor);
463 0 : LOG("Shutdown, state %d", mState);
464 :
465 0 : if (mCubebStream) {
466 0 : MonitorAutoUnlock mon(mMonitor);
467 : // Force stop to put the cubeb stream in a stable state before deletion.
468 0 : cubeb_stream_stop(mCubebStream.get());
469 : // Must not try to shut down cubeb from within the lock! wasapi may still
470 : // call our callback after Pause()/stop()!?! Bug 996162
471 0 : mCubebStream.reset();
472 : }
473 :
474 0 : mState = SHUTDOWN;
475 0 : }
476 :
477 : int64_t
478 0 : AudioStream::GetPosition()
479 : {
480 0 : MonitorAutoLock mon(mMonitor);
481 0 : int64_t frames = GetPositionInFramesUnlocked();
482 0 : return frames >= 0 ? mAudioClock.GetPosition(frames) : -1;
483 : }
484 :
485 : int64_t
486 0 : AudioStream::GetPositionInFrames()
487 : {
488 0 : MonitorAutoLock mon(mMonitor);
489 0 : int64_t frames = GetPositionInFramesUnlocked();
490 0 : return frames >= 0 ? mAudioClock.GetPositionInFrames(frames) : -1;
491 : }
492 :
493 : int64_t
494 0 : AudioStream::GetPositionInFramesUnlocked()
495 : {
496 0 : mMonitor.AssertCurrentThreadOwns();
497 :
498 0 : if (mState == ERRORED) {
499 0 : return -1;
500 : }
501 :
502 0 : uint64_t position = 0;
503 0 : if (InvokeCubeb(cubeb_stream_get_position, &position) != CUBEB_OK) {
504 0 : return -1;
505 : }
506 0 : return std::min<uint64_t>(position, INT64_MAX);
507 : }
508 :
509 : bool
510 0 : AudioStream::IsValidAudioFormat(Chunk* aChunk)
511 : {
512 0 : if (aChunk->Rate() != mAudioClock.GetInputRate()) {
513 0 : LOGW("mismatched sample %u, mInRate=%u", aChunk->Rate(), mAudioClock.GetInputRate());
514 0 : return false;
515 : }
516 :
517 0 : if (aChunk->Channels() > 8) {
518 0 : return false;
519 : }
520 :
521 0 : return true;
522 : }
523 :
524 : void
525 0 : AudioStream::GetUnprocessed(AudioBufferWriter& aWriter)
526 : {
527 0 : mMonitor.AssertCurrentThreadOwns();
528 :
529 : // Flush the timestretcher pipeline, if we were playing using a playback rate
530 : // other than 1.0.
531 0 : if (mTimeStretcher && mTimeStretcher->numSamples()) {
532 0 : auto timeStretcher = mTimeStretcher;
533 0 : aWriter.Write([timeStretcher] (AudioDataValue* aPtr, uint32_t aFrames) {
534 0 : return timeStretcher->receiveSamples(aPtr, aFrames);
535 0 : }, aWriter.Available());
536 :
537 : // TODO: There might be still unprocessed samples in the stretcher.
538 : // We should either remove or flush them so they won't be in the output
539 : // next time we switch a playback rate other than 1.0.
540 0 : NS_WARNING_ASSERTION(
541 : mTimeStretcher->numUnprocessedSamples() == 0, "no samples");
542 : }
543 :
544 0 : while (aWriter.Available() > 0) {
545 0 : UniquePtr<Chunk> c = mDataSource.PopFrames(aWriter.Available());
546 0 : if (c->Frames() == 0) {
547 0 : break;
548 : }
549 0 : MOZ_ASSERT(c->Frames() <= aWriter.Available());
550 0 : if (IsValidAudioFormat(c.get())) {
551 0 : aWriter.Write(c->Data(), c->Frames());
552 : } else {
553 : // Write silence if invalid format.
554 0 : aWriter.WriteZeros(c->Frames());
555 : }
556 : }
557 0 : }
558 :
559 : void
560 0 : AudioStream::GetTimeStretched(AudioBufferWriter& aWriter)
561 : {
562 0 : mMonitor.AssertCurrentThreadOwns();
563 :
564 : // We need to call the non-locking version, because we already have the lock.
565 0 : if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
566 0 : return;
567 : }
568 :
569 : uint32_t toPopFrames =
570 0 : ceil(aWriter.Available() * mAudioClock.GetPlaybackRate());
571 :
572 0 : while (mTimeStretcher->numSamples() < aWriter.Available()) {
573 0 : UniquePtr<Chunk> c = mDataSource.PopFrames(toPopFrames);
574 0 : if (c->Frames() == 0) {
575 0 : break;
576 : }
577 0 : MOZ_ASSERT(c->Frames() <= toPopFrames);
578 0 : if (IsValidAudioFormat(c.get())) {
579 0 : mTimeStretcher->putSamples(c->Data(), c->Frames());
580 : } else {
581 : // Write silence if invalid format.
582 0 : AutoTArray<AudioDataValue, 1000> buf;
583 0 : auto size = CheckedUint32(mOutChannels) * c->Frames();
584 0 : if (!size.isValid()) {
585 : // The overflow should not happen in normal case.
586 0 : LOGW("Invalid member data: %d channels, %d frames", mOutChannels, c->Frames());
587 0 : return;
588 : }
589 0 : buf.SetLength(size.value());
590 0 : size = size * sizeof(AudioDataValue);
591 0 : if (!size.isValid()) {
592 0 : LOGW("The required memory size is too large.");
593 0 : return;
594 : }
595 0 : memset(buf.Elements(), 0, size.value());
596 0 : mTimeStretcher->putSamples(buf.Elements(), c->Frames());
597 : }
598 : }
599 :
600 0 : auto timeStretcher = mTimeStretcher;
601 0 : aWriter.Write([timeStretcher] (AudioDataValue* aPtr, uint32_t aFrames) {
602 0 : return timeStretcher->receiveSamples(aPtr, aFrames);
603 0 : }, aWriter.Available());
604 : }
605 :
606 : long
607 0 : AudioStream::DataCallback(void* aBuffer, long aFrames)
608 : {
609 0 : MonitorAutoLock mon(mMonitor);
610 0 : MOZ_ASSERT(mState != SHUTDOWN, "No data callback after shutdown");
611 :
612 : auto writer = AudioBufferWriter(
613 0 : reinterpret_cast<AudioDataValue*>(aBuffer), mOutChannels, aFrames);
614 :
615 0 : if (!strcmp(cubeb_get_backend_id(CubebUtils::GetCubebContext()), "winmm")) {
616 : // Don't consume audio data until Start() is called.
617 : // Expected only with cubeb winmm backend.
618 0 : if (mState == INITIALIZED) {
619 0 : NS_WARNING("data callback fires before cubeb_stream_start() is called");
620 0 : mAudioClock.UpdateFrameHistory(0, aFrames);
621 0 : return writer.WriteZeros(aFrames);
622 : }
623 : } else {
624 0 : MOZ_ASSERT(mState != INITIALIZED);
625 : }
626 :
627 : // NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN)
628 : // Bug 996162
629 :
630 0 : if (mAudioClock.GetInputRate() == mAudioClock.GetOutputRate()) {
631 0 : GetUnprocessed(writer);
632 : } else {
633 0 : GetTimeStretched(writer);
634 : }
635 :
636 : // Always send audible frames first, and silent frames later.
637 : // Otherwise it will break the assumption of FrameHistory.
638 0 : if (!mDataSource.Ended()) {
639 0 : mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), writer.Available());
640 0 : if (writer.Available() > 0) {
641 0 : LOGW("lost %d frames", writer.Available());
642 0 : writer.WriteZeros(writer.Available());
643 : }
644 : } else {
645 : // No more new data in the data source. Don't send silent frames so the
646 : // cubeb stream can start draining.
647 0 : mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), 0);
648 : }
649 :
650 0 : WriteDumpFile(mDumpFile, this, aFrames, aBuffer);
651 :
652 0 : return aFrames - writer.Available();
653 : }
654 :
655 : void
656 0 : AudioStream::StateCallback(cubeb_state aState)
657 : {
658 0 : MonitorAutoLock mon(mMonitor);
659 0 : MOZ_ASSERT(mState != SHUTDOWN, "No state callback after shutdown");
660 0 : LOG("StateCallback, mState=%d cubeb_state=%d", mState, aState);
661 0 : if (aState == CUBEB_STATE_DRAINED) {
662 0 : mState = DRAINED;
663 0 : mDataSource.Drained();
664 0 : } else if (aState == CUBEB_STATE_ERROR) {
665 0 : LOG("StateCallback() state %d cubeb error", mState);
666 0 : mState = ERRORED;
667 : }
668 0 : }
669 :
670 0 : AudioClock::AudioClock()
671 : : mOutRate(0),
672 : mInRate(0),
673 : mPreservesPitch(true),
674 0 : mFrameHistory(new FrameHistory())
675 0 : {}
676 :
677 0 : void AudioClock::Init(uint32_t aRate)
678 : {
679 0 : mOutRate = aRate;
680 0 : mInRate = aRate;
681 0 : }
682 :
683 0 : void AudioClock::UpdateFrameHistory(uint32_t aServiced, uint32_t aUnderrun)
684 : {
685 0 : mFrameHistory->Append(aServiced, aUnderrun, mOutRate);
686 0 : }
687 :
688 0 : int64_t AudioClock::GetPositionInFrames(int64_t aFrames) const
689 : {
690 0 : CheckedInt64 v = UsecsToFrames(GetPosition(aFrames), mInRate);
691 0 : return v.isValid() ? v.value() : -1;
692 : }
693 :
694 0 : int64_t AudioClock::GetPosition(int64_t frames) const
695 : {
696 0 : return mFrameHistory->GetPosition(frames);
697 : }
698 :
699 0 : void AudioClock::SetPlaybackRate(double aPlaybackRate)
700 : {
701 0 : mOutRate = static_cast<uint32_t>(mInRate / aPlaybackRate);
702 0 : }
703 :
704 0 : double AudioClock::GetPlaybackRate() const
705 : {
706 0 : return static_cast<double>(mInRate) / mOutRate;
707 : }
708 :
709 0 : void AudioClock::SetPreservesPitch(bool aPreservesPitch)
710 : {
711 0 : mPreservesPitch = aPreservesPitch;
712 0 : }
713 :
714 0 : bool AudioClock::GetPreservesPitch() const
715 : {
716 0 : return mPreservesPitch;
717 : }
718 :
719 : } // namespace mozilla
|