35e93527812ab2fafabc60f06144d43a5b42dcd8
[WebKit-https.git] / Source / WebCore / platform / mediastream / mac / AudioTrackPrivateMediaStreamCocoa.cpp
1 /*
2  * Copyright (C) 2017 Apple 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''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "AudioTrackPrivateMediaStreamCocoa.h"
28
29 #include "AudioSampleBufferList.h"
30 #include "AudioSampleDataSource.h"
31 #include "AudioSession.h"
32 #include "CAAudioStreamDescription.h"
33 #include "Logging.h"
34
35 #include <pal/cf/CoreMediaSoftLink.h>
36 #include <pal/spi/cocoa/AudioToolboxSPI.h>
37 #include <wtf/Scope.h>
38
39 #if ENABLE(VIDEO_TRACK) && ENABLE(MEDIA_STREAM)
40
41 namespace WebCore {
42
43 AudioTrackPrivateMediaStreamCocoa::AudioTrackPrivateMediaStreamCocoa(MediaStreamTrackPrivate& track)
44     : AudioTrackPrivateMediaStream(track)
45 {
46     track.source().addObserver(*this);
47 }
48
49 AudioTrackPrivateMediaStreamCocoa::~AudioTrackPrivateMediaStreamCocoa()
50 {
51     streamTrack().source().removeObserver(*this);
52
53     if (m_dataSource)
54         m_dataSource->setPaused(true);
55
56     if (m_remoteIOUnit) {
57         AudioOutputUnitStop(m_remoteIOUnit);
58         AudioComponentInstanceDispose(m_remoteIOUnit);
59         m_remoteIOUnit = nullptr;
60     }
61
62     m_dataSource = nullptr;
63     m_inputDescription = nullptr;
64     m_outputDescription = nullptr;
65 }
66
67 void AudioTrackPrivateMediaStreamCocoa::playInternal()
68 {
69     if (m_isPlaying)
70         return;
71
72     if (m_remoteIOUnit) {
73         ASSERT(m_dataSource);
74         m_dataSource->setPaused(false);
75         if (!AudioOutputUnitStart(m_remoteIOUnit))
76             m_isPlaying = true;
77     }
78
79     m_autoPlay = !m_isPlaying;
80 }
81
82 void AudioTrackPrivateMediaStreamCocoa::play()
83 {
84     playInternal();
85 }
86
87 void AudioTrackPrivateMediaStreamCocoa::pause()
88 {
89     m_isPlaying = false;
90     m_autoPlay = false;
91
92     if (m_remoteIOUnit)
93         AudioOutputUnitStop(m_remoteIOUnit);
94     if (m_dataSource)
95         m_dataSource->setPaused(true);
96 }
97
98 void AudioTrackPrivateMediaStreamCocoa::setVolume(float volume)
99 {
100     m_volume = volume;
101     if (m_dataSource)
102         m_dataSource->setVolume(m_volume);
103 }
104
105 AudioComponentInstance AudioTrackPrivateMediaStreamCocoa::createAudioUnit(CAAudioStreamDescription& outputDescription)
106 {
107     AudioComponentInstance remoteIOUnit { nullptr };
108
109     AudioComponentDescription ioUnitDescription { kAudioUnitType_Output, 0, kAudioUnitManufacturer_Apple, 0, 0 };
110 #if PLATFORM(IOS)
111     ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
112 #else
113     ioUnitDescription.componentSubType = kAudioUnitSubType_DefaultOutput;
114 #endif
115
116     AudioComponent ioComponent = AudioComponentFindNext(nullptr, &ioUnitDescription);
117     ASSERT(ioComponent);
118     if (!ioComponent) {
119         ERROR_LOG(LOGIDENTIFIER, "unable to find remote IO unit component");
120         return nullptr;
121     }
122
123     OSStatus err = AudioComponentInstanceNew(ioComponent, &remoteIOUnit);
124     if (err) {
125         ERROR_LOG(LOGIDENTIFIER, "unable to open vpio unit, error = ", err, " (", (const char*)&err, ")");
126         return nullptr;
127     }
128
129 #if PLATFORM(IOS)
130     UInt32 param = 1;
131     err = AudioUnitSetProperty(remoteIOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &param, sizeof(param));
132     if (err) {
133         ERROR_LOG(LOGIDENTIFIER, "unable to enable vpio unit output, error = ", err, " (", (const char*)&err, ")");
134         return nullptr;
135     }
136 #endif
137
138     AURenderCallbackStruct callback = { inputProc, this };
139     err = AudioUnitSetProperty(remoteIOUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &callback, sizeof(callback));
140     if (err) {
141         ERROR_LOG(LOGIDENTIFIER, "unable to set vpio unit speaker proc, error = ", err, " (", (const char*)&err, ")");
142         return nullptr;
143     }
144
145     UInt32 size = sizeof(outputDescription.streamDescription());
146     err  = AudioUnitGetProperty(remoteIOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outputDescription.streamDescription(), &size);
147     if (err) {
148         ERROR_LOG(LOGIDENTIFIER, "unable to get input stream format, error = ", err, " (", (const char*)&err, ")");
149         return nullptr;
150     }
151
152     outputDescription.streamDescription().mSampleRate = AudioSession::sharedSession().sampleRate();
153
154     err = AudioUnitSetProperty(remoteIOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outputDescription.streamDescription(), sizeof(outputDescription.streamDescription()));
155     if (err) {
156         ERROR_LOG(LOGIDENTIFIER, "unable to set input stream format, error = ", err, " (", (const char*)&err, ")");
157         return nullptr;
158     }
159
160     err = AudioUnitInitialize(remoteIOUnit);
161     if (err) {
162         ERROR_LOG(LOGIDENTIFIER, "AudioUnitInitialize() failed, error = ", err, " (", (const char*)&err, ")");
163         return nullptr;
164     }
165
166     return remoteIOUnit;
167 }
168
169 void AudioTrackPrivateMediaStreamCocoa::audioSamplesAvailable(const MediaTime& sampleTime, const PlatformAudioData& audioData, const AudioStreamDescription& description, size_t sampleCount)
170 {
171     // This function is called on a background thread. The following protectedThis object ensures the object is not
172     // destroyed on the main thread before this function exits.
173     auto scopeExit = WTF::makeScopeExit([protectedThis = makeRef(*this)]() mutable {
174         callOnMainThread([protectedThis = WTFMove(protectedThis)] { });
175     });
176
177     ASSERT(description.platformDescription().type == PlatformDescription::CAAudioStreamBasicType);
178
179     if (!m_inputDescription || *m_inputDescription != description) {
180
181         m_inputDescription = nullptr;
182         m_outputDescription = nullptr;
183
184         if (m_remoteIOUnit) {
185             AudioOutputUnitStop(m_remoteIOUnit);
186             AudioComponentInstanceDispose(m_remoteIOUnit);
187             m_remoteIOUnit = nullptr;
188         }
189
190         CAAudioStreamDescription inputDescription = toCAAudioStreamDescription(description);
191         CAAudioStreamDescription outputDescription;
192
193         auto remoteIOUnit = createAudioUnit(outputDescription);
194         if (!remoteIOUnit)
195             return;
196
197         m_inputDescription = std::make_unique<CAAudioStreamDescription>(inputDescription);
198         m_outputDescription = std::make_unique<CAAudioStreamDescription>(outputDescription);
199
200         if (!m_dataSource)
201             m_dataSource = AudioSampleDataSource::create(description.sampleRate() * 2);
202
203         if (m_dataSource->setInputFormat(inputDescription) || m_dataSource->setOutputFormat(outputDescription)) {
204             AudioComponentInstanceDispose(remoteIOUnit);
205             return;
206         }
207
208         m_dataSource->setVolume(m_volume);
209         m_remoteIOUnit = remoteIOUnit;
210     }
211
212     m_dataSource->pushSamples(sampleTime, audioData, sampleCount);
213
214     if (m_autoPlay)
215         playInternal();
216 }
217
218 void AudioTrackPrivateMediaStreamCocoa::sourceStopped()
219 {
220     pause();
221 }
222
223 OSStatus AudioTrackPrivateMediaStreamCocoa::render(UInt32 sampleCount, AudioBufferList& ioData, UInt32 /*inBusNumber*/, const AudioTimeStamp& timeStamp, AudioUnitRenderActionFlags& actionFlags)
224 {
225     // This function is called on a high-priority background thread. The following protectedThis object ensures the object is not
226     // destroyed on the main thread before this function exits.
227     auto scopeExit = WTF::makeScopeExit([protectedThis = makeRef(*this)]() mutable {
228         callOnMainThread([protectedThis = WTFMove(protectedThis)] { });
229     });
230
231     if (!m_isPlaying || m_muted || !m_dataSource || streamTrack().muted() || streamTrack().ended() || !streamTrack().enabled()) {
232         AudioSampleBufferList::zeroABL(ioData, static_cast<size_t>(sampleCount * m_outputDescription->bytesPerFrame()));
233         actionFlags = kAudioUnitRenderAction_OutputIsSilence;
234         return 0;
235     }
236
237     m_dataSource->pullSamples(ioData, static_cast<size_t>(sampleCount), timeStamp.mSampleTime, timeStamp.mHostTime, AudioSampleDataSource::Copy);
238
239     return 0;
240 }
241
242 OSStatus AudioTrackPrivateMediaStreamCocoa::inputProc(void* userData, AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timeStamp, UInt32 inBusNumber, UInt32 sampleCount, AudioBufferList* ioData)
243 {
244     return static_cast<AudioTrackPrivateMediaStreamCocoa*>(userData)->render(sampleCount, *ioData, inBusNumber, *timeStamp, *actionFlags);
245 }
246
247
248 } // namespace WebCore
249
250 #endif // ENABLE(VIDEO_TRACK) && ENABLE(MEDIA_STREAM)