[iOS] Switching cameras in a WebRTC call makes black frames being sent
[WebKit-https.git] / Source / WebCore / platform / mediastream / mac / RealtimeOutgoingVideoSource.cpp
1 /*
2  * Copyright (C) 2017 Apple Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted, provided that the following conditions
6  * are required to be met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Inc. nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. AND ITS CONTRIBUTORS BE LIABLE FOR
21  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "RealtimeOutgoingVideoSource.h"
31
32 #if USE(LIBWEBRTC)
33
34 #include "Logging.h"
35 #include <webrtc/api/video/i420_buffer.h>
36 #include <webrtc/common_video/include/corevideo_frame_buffer.h>
37 #include <webrtc/common_video/libyuv/include/webrtc_libyuv.h>
38 #include <webrtc/media/base/videoframe.h>
39 #include <wtf/MainThread.h>
40
41 #include "CoreMediaSoftLink.h"
42 #include "CoreVideoSoftLink.h"
43
44 namespace WebCore {
45
46 RealtimeOutgoingVideoSource::RealtimeOutgoingVideoSource(Ref<RealtimeMediaSource>&& videoSource)
47     : m_videoSource(WTFMove(videoSource))
48     , m_blackFrameTimer(*this, &RealtimeOutgoingVideoSource::sendOneBlackFrame)
49 {
50     m_videoSource->addObserver(*this);
51     initializeFromSource();
52 }
53
54 bool RealtimeOutgoingVideoSource::setSource(Ref<RealtimeMediaSource>&& newSource)
55 {
56     if (!m_initialSettings)
57         m_initialSettings = m_videoSource->settings();
58
59     m_videoSource->removeObserver(*this);
60     m_videoSource = WTFMove(newSource);
61     m_videoSource->addObserver(*this);
62
63     initializeFromSource();
64
65     return true;
66 }
67
68 void RealtimeOutgoingVideoSource::stop()
69 {
70     m_videoSource->removeObserver(*this);
71     m_blackFrameTimer.stop();
72     m_isStopped = true;
73 }
74
75 void RealtimeOutgoingVideoSource::updateBlackFramesSending()
76 {
77     if (!m_muted && m_enabled) {
78         if (m_blackFrameTimer.isActive())
79             m_blackFrameTimer.stop();
80         return;
81     }
82
83     sendBlackFramesIfNeeded();
84 }
85
86 void RealtimeOutgoingVideoSource::sourceMutedChanged()
87 {
88     ASSERT(m_muted != m_videoSource->muted());
89
90     m_muted = m_videoSource->muted();
91
92     updateBlackFramesSending();
93 }
94
95 void RealtimeOutgoingVideoSource::sourceEnabledChanged()
96 {
97     ASSERT(m_enabled != m_videoSource->enabled());
98
99     m_enabled = m_videoSource->enabled();
100
101     updateBlackFramesSending();
102 }
103
104 void RealtimeOutgoingVideoSource::initializeFromSource()
105 {
106     const auto& settings = m_videoSource->settings();
107     m_width = settings.width();
108     m_height = settings.height();
109
110     m_muted = m_videoSource->muted();
111     m_enabled = m_videoSource->enabled();
112
113     updateBlackFramesSending();
114 }
115
116 bool RealtimeOutgoingVideoSource::GetStats(Stats*)
117 {
118     return false;
119 }
120
121 void RealtimeOutgoingVideoSource::AddOrUpdateSink(rtc::VideoSinkInterface<webrtc::VideoFrame>* sink, const rtc::VideoSinkWants& sinkWants)
122 {
123     ASSERT(!sinkWants.black_frames);
124
125     if (sinkWants.rotation_applied)
126         m_shouldApplyRotation = true;
127
128     if (!m_sinks.contains(sink))
129         m_sinks.append(sink);
130
131     callOnMainThread([protectedThis = makeRef(*this)]() {
132         protectedThis->sendBlackFramesIfNeeded();
133     });
134 }
135
136 void RealtimeOutgoingVideoSource::RemoveSink(rtc::VideoSinkInterface<webrtc::VideoFrame>* sink)
137 {
138     m_sinks.removeFirst(sink);
139
140     if (m_sinks.size())
141         return;
142
143     callOnMainThread([protectedThis = makeRef(*this)]() {
144         if (protectedThis->m_blackFrameTimer.isActive())
145             protectedThis->m_blackFrameTimer.stop();
146     });
147 }
148
149 void RealtimeOutgoingVideoSource::sendBlackFramesIfNeeded()
150 {
151     if (m_blackFrameTimer.isActive())
152         return;
153
154     if (!m_sinks.size())
155         return;
156
157     if (!m_muted && m_enabled)
158         return;
159
160     if (!m_width || !m_height)
161         return;
162
163     if (!m_blackFrame) {
164         auto width = m_width;
165         auto height = m_height;
166         if (m_shouldApplyRotation && (m_currentRotation == webrtc::kVideoRotation_0 || m_currentRotation == webrtc::kVideoRotation_90))
167             std::swap(width, height);
168         auto frame = m_bufferPool.CreateBuffer(width, height);
169         ASSERT(frame);
170         if (!frame) {
171             RELEASE_LOG(WebRTC, "RealtimeOutgoingVideoSource::sendBlackFramesIfNeeded unable to send black frames");
172             return;
173         }
174         frame->SetToBlack();
175         m_blackFrame = WTFMove(frame);
176     }
177     sendOneBlackFrame();
178     m_blackFrameTimer.startRepeating(1_s);
179 }
180
181 void RealtimeOutgoingVideoSource::sendOneBlackFrame()
182 {
183     RELEASE_LOG(MediaStream, "RealtimeOutgoingVideoSource::sendOneBlackFrame");
184     sendFrame(rtc::scoped_refptr<webrtc::VideoFrameBuffer>(m_blackFrame));
185 }
186
187 void RealtimeOutgoingVideoSource::sendFrame(rtc::scoped_refptr<webrtc::VideoFrameBuffer>&& buffer)
188 {
189     webrtc::VideoFrame frame(buffer, 0, 0, m_shouldApplyRotation ? webrtc::kVideoRotation_0 : m_currentRotation);
190     for (auto* sink : m_sinks)
191         sink->OnFrame(frame);
192 }
193
194 void RealtimeOutgoingVideoSource::videoSampleAvailable(MediaSample& sample)
195 {
196     if (!m_sinks.size())
197         return;
198
199     if (m_muted || !m_enabled)
200         return;
201
202 #if !RELEASE_LOG_DISABLED
203     if (!(++m_numberOfFrames % 30))
204         RELEASE_LOG(MediaStream, "RealtimeOutgoingVideoSource::sendFrame %zu frame", m_numberOfFrames);
205 #endif
206
207     switch (sample.videoRotation()) {
208     case MediaSample::VideoRotation::None:
209         m_currentRotation = webrtc::kVideoRotation_0;
210         break;
211     case MediaSample::VideoRotation::UpsideDown:
212         m_currentRotation = webrtc::kVideoRotation_180;
213         break;
214     case MediaSample::VideoRotation::Right:
215         m_currentRotation = webrtc::kVideoRotation_90;
216         break;
217     case MediaSample::VideoRotation::Left:
218         m_currentRotation = webrtc::kVideoRotation_270;
219         break;
220     }
221
222     ASSERT(sample.platformSample().type == PlatformSample::CMSampleBufferType);
223     auto pixelBuffer = static_cast<CVPixelBufferRef>(CMSampleBufferGetImageBuffer(sample.platformSample().sample.cmSampleBuffer));
224     auto pixelFormatType = CVPixelBufferGetPixelFormatType(pixelBuffer);
225
226     if (pixelFormatType == kCVPixelFormatType_420YpCbCr8Planar || pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
227         rtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer = new rtc::RefCountedObject<webrtc::CoreVideoFrameBuffer>(pixelBuffer);
228         if (m_shouldApplyRotation && m_currentRotation != webrtc::kVideoRotation_0) {
229             // FIXME: We should make AVVideoCaptureSource handle the rotation whenever possible.
230             // This implementation is inefficient, we should rotate on the CMSampleBuffer directly instead of doing this double allocation.
231             auto rotatedBuffer = buffer->NativeToI420Buffer();
232             ASSERT(rotatedBuffer);
233             buffer = webrtc::I420Buffer::Rotate(*rotatedBuffer, m_currentRotation);
234         }
235         sendFrame(WTFMove(buffer));
236         return;
237     }
238
239     CVPixelBufferLockBaseAddress(pixelBuffer, 0);
240     auto* source = reinterpret_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0));
241
242     ASSERT(m_width);
243     ASSERT(m_height);
244
245     auto newBuffer = m_bufferPool.CreateBuffer(m_width, m_height);
246     ASSERT(newBuffer);
247     if (!newBuffer) {
248         RELEASE_LOG(WebRTC, "RealtimeOutgoingVideoSource::videoSampleAvailable unable to allocate buffer for conversion to YUV");
249         return;
250     }
251     if (pixelFormatType == kCVPixelFormatType_32BGRA)
252         webrtc::ConvertToI420(webrtc::kARGB, source, 0, 0, m_width, m_height, 0, webrtc::kVideoRotation_0, newBuffer);
253     else {
254         ASSERT(pixelFormatType == kCVPixelFormatType_32ARGB);
255         webrtc::ConvertToI420(webrtc::kBGRA, source, 0, 0, m_width, m_height, 0, webrtc::kVideoRotation_0, newBuffer);
256     }
257     CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
258     if (m_shouldApplyRotation && m_currentRotation != webrtc::kVideoRotation_0)
259         newBuffer = webrtc::I420Buffer::Rotate(*newBuffer, m_currentRotation);
260     sendFrame(WTFMove(newBuffer));
261 }
262
263 } // namespace WebCore
264
265 #endif // USE(LIBWEBRTC)