From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../objc/unittests/ObjCVideoTrackSource_xctest.mm | 469 ++++++++++++++++ .../objc/unittests/RTCAudioDeviceModule_xctest.mm | 618 +++++++++++++++++++++ .../sdk/objc/unittests/RTCAudioDevice_xctest.mm | 129 +++++ .../sdk/objc/unittests/RTCAudioSessionTest.mm | 317 +++++++++++ .../sdk/objc/unittests/RTCCVPixelBuffer_xctest.mm | 461 +++++++++++++++ .../sdk/objc/unittests/RTCCallbackLogger_xctest.m | 244 ++++++++ .../objc/unittests/RTCCameraVideoCapturerTests.mm | 560 +++++++++++++++++++ .../sdk/objc/unittests/RTCCertificateTest.mm | 73 +++ .../sdk/objc/unittests/RTCConfigurationTest.mm | 162 ++++++ .../unittests/RTCDataChannelConfigurationTest.mm | 51 ++ ...RTCDoNotPutCPlusPlusInFrameworkHeaders_xctest.m | 30 + .../sdk/objc/unittests/RTCEncodedImage_xctest.mm | 55 ++ .../objc/unittests/RTCFileVideoCapturer_xctest.mm | 114 ++++ .../objc/unittests/RTCH264ProfileLevelId_xctest.m | 48 ++ .../sdk/objc/unittests/RTCIceCandidateTest.mm | 61 ++ .../sdk/objc/unittests/RTCIceServerTest.mm | 136 +++++ .../sdk/objc/unittests/RTCMTLVideoView_xctest.m | 299 ++++++++++ .../sdk/objc/unittests/RTCMediaConstraintsTest.mm | 58 ++ .../objc/unittests/RTCNV12TextureCache_xctest.m | 55 ++ .../RTCPeerConnectionFactoryBuilderTest.mm | 72 +++ .../unittests/RTCPeerConnectionFactory_xctest.m | 380 +++++++++++++ .../sdk/objc/unittests/RTCPeerConnectionTest.mm | 204 +++++++ .../objc/unittests/RTCSessionDescriptionTest.mm | 122 ++++ .../libwebrtc/sdk/objc/unittests/RTCTracingTest.mm | 41 ++ .../libwebrtc/sdk/objc/unittests/audio_short16.pcm | Bin 0 -> 643632 bytes .../libwebrtc/sdk/objc/unittests/audio_short44.pcm | Bin 0 -> 1774010 bytes .../libwebrtc/sdk/objc/unittests/audio_short48.pcm | Bin 0 -> 1930896 bytes .../sdk/objc/unittests/avformatmappertests.mm | 243 ++++++++ .../libwebrtc/sdk/objc/unittests/foreman.mp4 | Bin 0 -> 546651 bytes .../sdk/objc/unittests/frame_buffer_helpers.h | 22 + .../sdk/objc/unittests/frame_buffer_helpers.mm | 126 +++++ third_party/libwebrtc/sdk/objc/unittests/main.mm | 24 + .../sdk/objc/unittests/nalu_rewriter_xctest.mm | 374 +++++++++++++ .../unittests/objc_video_decoder_factory_tests.mm | 107 ++++ .../unittests/objc_video_encoder_factory_tests.mm | 148 +++++ .../sdk/objc/unittests/scoped_cftyperef_tests.mm | 111 ++++ 36 files changed, 5914 insertions(+) create mode 100644 third_party/libwebrtc/sdk/objc/unittests/ObjCVideoTrackSource_xctest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCAudioDeviceModule_xctest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCAudioDevice_xctest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCAudioSessionTest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCCVPixelBuffer_xctest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCCallbackLogger_xctest.m create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCCameraVideoCapturerTests.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCCertificateTest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCConfigurationTest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCDataChannelConfigurationTest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCDoNotPutCPlusPlusInFrameworkHeaders_xctest.m create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCEncodedImage_xctest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCFileVideoCapturer_xctest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCH264ProfileLevelId_xctest.m create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCIceCandidateTest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCIceServerTest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCMTLVideoView_xctest.m create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCMediaConstraintsTest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCNV12TextureCache_xctest.m create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionFactoryBuilderTest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionFactory_xctest.m create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionTest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCSessionDescriptionTest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/RTCTracingTest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/audio_short16.pcm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/audio_short44.pcm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/audio_short48.pcm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/avformatmappertests.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/foreman.mp4 create mode 100644 third_party/libwebrtc/sdk/objc/unittests/frame_buffer_helpers.h create mode 100644 third_party/libwebrtc/sdk/objc/unittests/frame_buffer_helpers.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/main.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/nalu_rewriter_xctest.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/objc_video_decoder_factory_tests.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/objc_video_encoder_factory_tests.mm create mode 100644 third_party/libwebrtc/sdk/objc/unittests/scoped_cftyperef_tests.mm (limited to 'third_party/libwebrtc/sdk/objc/unittests') diff --git a/third_party/libwebrtc/sdk/objc/unittests/ObjCVideoTrackSource_xctest.mm b/third_party/libwebrtc/sdk/objc/unittests/ObjCVideoTrackSource_xctest.mm new file mode 100644 index 0000000000..4c8bf348f4 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/ObjCVideoTrackSource_xctest.mm @@ -0,0 +1,469 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#include "sdk/objc/native/src/objc_video_track_source.h" + +#import "api/video_frame_buffer/RTCNativeI420Buffer+Private.h" +#import "base/RTCVideoFrame.h" +#import "base/RTCVideoFrameBuffer.h" +#import "components/video_frame_buffer/RTCCVPixelBuffer.h" +#import "frame_buffer_helpers.h" + +#include "api/scoped_refptr.h" +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "media/base/fake_video_renderer.h" +#include "sdk/objc/native/api/video_frame.h" + +typedef void (^VideoSinkCallback)(RTC_OBJC_TYPE(RTCVideoFrame) *); + +namespace { + +class ObjCCallbackVideoSink : public rtc::VideoSinkInterface { + public: + ObjCCallbackVideoSink(VideoSinkCallback callback) : callback_(callback) {} + + void OnFrame(const webrtc::VideoFrame &frame) override { + callback_(NativeToObjCVideoFrame(frame)); + } + + private: + VideoSinkCallback callback_; +}; + +} // namespace + +@interface ObjCVideoTrackSourceTests : XCTestCase +@end + +@implementation ObjCVideoTrackSourceTests { + rtc::scoped_refptr _video_source; +} + +- (void)setUp { + _video_source = rtc::make_ref_counted(); +} + +- (void)tearDown { + _video_source = NULL; +} + +- (void)testOnCapturedFrameAdaptsFrame { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + + RTC_OBJC_TYPE(RTCVideoFrame) *frame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + + cricket::FakeVideoRenderer *video_renderer = new cricket::FakeVideoRenderer(); + const rtc::VideoSinkWants video_sink_wants; + rtc::VideoSourceInterface *video_source_interface = _video_source.get(); + video_source_interface->AddOrUpdateSink(video_renderer, video_sink_wants); + + _video_source->OnOutputFormatRequest(640, 360, 30); + _video_source->OnCapturedFrame(frame); + + XCTAssertEqual(video_renderer->num_rendered_frames(), 1); + XCTAssertEqual(video_renderer->width(), 360); + XCTAssertEqual(video_renderer->height(), 640); + + CVBufferRelease(pixelBufferRef); +} + +- (void)testOnCapturedFrameAdaptsFrameWithAlignment { + // Requesting to adapt 1280x720 to 912x514 gives 639x360 without alignment. The 639 causes issues + // with some hardware encoders (e.g. HEVC) so in this test we verify that the alignment is set and + // respected. + + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + + RTC_OBJC_TYPE(RTCVideoFrame) *frame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + + cricket::FakeVideoRenderer *video_renderer = new cricket::FakeVideoRenderer(); + const rtc::VideoSinkWants video_sink_wants; + rtc::VideoSourceInterface *video_source_interface = _video_source.get(); + video_source_interface->AddOrUpdateSink(video_renderer, video_sink_wants); + + _video_source->OnOutputFormatRequest(912, 514, 30); + _video_source->OnCapturedFrame(frame); + + XCTAssertEqual(video_renderer->num_rendered_frames(), 1); + XCTAssertEqual(video_renderer->width(), 360); + XCTAssertEqual(video_renderer->height(), 640); + + CVBufferRelease(pixelBufferRef); +} + +- (void)testOnCapturedFrameAdaptationResultsInCommonResolutions { + // Some of the most common resolutions used in the wild are 640x360, 480x270 and 320x180. + // Make sure that we properly scale down to exactly these resolutions. + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + + RTC_OBJC_TYPE(RTCVideoFrame) *frame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + + cricket::FakeVideoRenderer *video_renderer = new cricket::FakeVideoRenderer(); + const rtc::VideoSinkWants video_sink_wants; + rtc::VideoSourceInterface *video_source_interface = _video_source.get(); + video_source_interface->AddOrUpdateSink(video_renderer, video_sink_wants); + + _video_source->OnOutputFormatRequest(640, 360, 30); + _video_source->OnCapturedFrame(frame); + + XCTAssertEqual(video_renderer->num_rendered_frames(), 1); + XCTAssertEqual(video_renderer->width(), 360); + XCTAssertEqual(video_renderer->height(), 640); + + _video_source->OnOutputFormatRequest(480, 270, 30); + _video_source->OnCapturedFrame(frame); + + XCTAssertEqual(video_renderer->num_rendered_frames(), 2); + XCTAssertEqual(video_renderer->width(), 270); + XCTAssertEqual(video_renderer->height(), 480); + + _video_source->OnOutputFormatRequest(320, 180, 30); + _video_source->OnCapturedFrame(frame); + + XCTAssertEqual(video_renderer->num_rendered_frames(), 3); + XCTAssertEqual(video_renderer->width(), 180); + XCTAssertEqual(video_renderer->height(), 320); + + CVBufferRelease(pixelBufferRef); +} + +- (void)testOnCapturedFrameWithoutAdaptation { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 360, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + RTC_OBJC_TYPE(RTCVideoFrame) *frame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"]; + ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) { + XCTAssertEqual(frame.width, outputFrame.width); + XCTAssertEqual(frame.height, outputFrame.height); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer; + XCTAssertEqual(buffer.cropX, outputBuffer.cropX); + XCTAssertEqual(buffer.cropY, outputBuffer.cropY); + XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer); + + [callbackExpectation fulfill]; + }); + + const rtc::VideoSinkWants video_sink_wants; + rtc::VideoSourceInterface *video_source_interface = _video_source.get(); + video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants); + + _video_source->OnOutputFormatRequest(640, 360, 30); + _video_source->OnCapturedFrame(frame); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; + CVBufferRelease(pixelBufferRef); +} + +- (void)testOnCapturedFrameCVPixelBufferNeedsAdaptation { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + RTC_OBJC_TYPE(RTCVideoFrame) *frame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"]; + ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) { + XCTAssertEqual(outputFrame.width, 360); + XCTAssertEqual(outputFrame.height, 640); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer; + XCTAssertEqual(outputBuffer.cropX, 0); + XCTAssertEqual(outputBuffer.cropY, 0); + XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer); + + [callbackExpectation fulfill]; + }); + + const rtc::VideoSinkWants video_sink_wants; + rtc::VideoSourceInterface *video_source_interface = _video_source.get(); + video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants); + + _video_source->OnOutputFormatRequest(640, 360, 30); + _video_source->OnCapturedFrame(frame); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; + CVBufferRelease(pixelBufferRef); +} + +- (void)testOnCapturedFrameCVPixelBufferNeedsCropping { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 380, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + RTC_OBJC_TYPE(RTCVideoFrame) *frame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"]; + ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) { + XCTAssertEqual(outputFrame.width, 360); + XCTAssertEqual(outputFrame.height, 640); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer; + XCTAssertEqual(outputBuffer.cropX, 10); + XCTAssertEqual(outputBuffer.cropY, 0); + XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer); + + [callbackExpectation fulfill]; + }); + + const rtc::VideoSinkWants video_sink_wants; + rtc::VideoSourceInterface *video_source_interface = _video_source.get(); + video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants); + + _video_source->OnOutputFormatRequest(640, 360, 30); + _video_source->OnCapturedFrame(frame); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; + CVBufferRelease(pixelBufferRef); +} + +- (void)testOnCapturedFramePreAdaptedCVPixelBufferNeedsAdaptation { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + // Create a frame that's already adapted down. + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef + adaptedWidth:640 + adaptedHeight:360 + cropWidth:720 + cropHeight:1280 + cropX:0 + cropY:0]; + RTC_OBJC_TYPE(RTCVideoFrame) *frame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"]; + ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) { + XCTAssertEqual(outputFrame.width, 480); + XCTAssertEqual(outputFrame.height, 270); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer; + XCTAssertEqual(outputBuffer.cropX, 0); + XCTAssertEqual(outputBuffer.cropY, 0); + XCTAssertEqual(outputBuffer.cropWidth, 640); + XCTAssertEqual(outputBuffer.cropHeight, 360); + XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer); + + [callbackExpectation fulfill]; + }); + + const rtc::VideoSinkWants video_sink_wants; + rtc::VideoSourceInterface *video_source_interface = _video_source.get(); + video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants); + + _video_source->OnOutputFormatRequest(480, 270, 30); + _video_source->OnCapturedFrame(frame); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; + CVBufferRelease(pixelBufferRef); +} + +- (void)testOnCapturedFramePreCroppedCVPixelBufferNeedsCropping { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 380, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef + adaptedWidth:370 + adaptedHeight:640 + cropWidth:370 + cropHeight:640 + cropX:10 + cropY:0]; + RTC_OBJC_TYPE(RTCVideoFrame) *frame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"]; + ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) { + XCTAssertEqual(outputFrame.width, 360); + XCTAssertEqual(outputFrame.height, 640); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer; + XCTAssertEqual(outputBuffer.cropX, 14); + XCTAssertEqual(outputBuffer.cropY, 0); + XCTAssertEqual(outputBuffer.cropWidth, 360); + XCTAssertEqual(outputBuffer.cropHeight, 640); + XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer); + + [callbackExpectation fulfill]; + }); + + const rtc::VideoSinkWants video_sink_wants; + rtc::VideoSourceInterface *video_source_interface = _video_source.get(); + video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants); + + _video_source->OnOutputFormatRequest(640, 360, 30); + _video_source->OnCapturedFrame(frame); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; + CVBufferRelease(pixelBufferRef); +} + +- (void)testOnCapturedFrameSmallerPreCroppedCVPixelBufferNeedsCropping { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 380, 640, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef + adaptedWidth:300 + adaptedHeight:640 + cropWidth:300 + cropHeight:640 + cropX:40 + cropY:0]; + RTC_OBJC_TYPE(RTCVideoFrame) *frame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"]; + ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) { + XCTAssertEqual(outputFrame.width, 300); + XCTAssertEqual(outputFrame.height, 534); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *outputBuffer = outputFrame.buffer; + XCTAssertEqual(outputBuffer.cropX, 40); + XCTAssertEqual(outputBuffer.cropY, 52); + XCTAssertEqual(outputBuffer.cropWidth, 300); + XCTAssertEqual(outputBuffer.cropHeight, 534); + XCTAssertEqual(buffer.pixelBuffer, outputBuffer.pixelBuffer); + + [callbackExpectation fulfill]; + }); + + const rtc::VideoSinkWants video_sink_wants; + rtc::VideoSourceInterface *video_source_interface = _video_source.get(); + video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants); + + _video_source->OnOutputFormatRequest(640, 360, 30); + _video_source->OnCapturedFrame(frame); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; + CVBufferRelease(pixelBufferRef); +} + +- (void)testOnCapturedFrameI420BufferNeedsAdaptation { + rtc::scoped_refptr i420Buffer = CreateI420Gradient(720, 1280); + RTC_OBJC_TYPE(RTCI420Buffer) *buffer = + [[RTC_OBJC_TYPE(RTCI420Buffer) alloc] initWithFrameBuffer:i420Buffer]; + RTC_OBJC_TYPE(RTCVideoFrame) *frame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"]; + ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) { + XCTAssertEqual(outputFrame.width, 360); + XCTAssertEqual(outputFrame.height, 640); + + RTC_OBJC_TYPE(RTCI420Buffer) *outputBuffer = (RTC_OBJC_TYPE(RTCI420Buffer) *)outputFrame.buffer; + + double psnr = I420PSNR(*[buffer nativeI420Buffer], *[outputBuffer nativeI420Buffer]); + XCTAssertEqual(psnr, webrtc::kPerfectPSNR); + + [callbackExpectation fulfill]; + }); + + const rtc::VideoSinkWants video_sink_wants; + rtc::VideoSourceInterface *video_source_interface = _video_source.get(); + video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants); + + _video_source->OnOutputFormatRequest(640, 360, 30); + _video_source->OnCapturedFrame(frame); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; +} + +- (void)testOnCapturedFrameI420BufferNeedsCropping { + rtc::scoped_refptr i420Buffer = CreateI420Gradient(380, 640); + RTC_OBJC_TYPE(RTCI420Buffer) *buffer = + [[RTC_OBJC_TYPE(RTCI420Buffer) alloc] initWithFrameBuffer:i420Buffer]; + RTC_OBJC_TYPE(RTCVideoFrame) *frame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:buffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"videoSinkCallback"]; + ObjCCallbackVideoSink callback_video_sink(^void(RTC_OBJC_TYPE(RTCVideoFrame) * outputFrame) { + XCTAssertEqual(outputFrame.width, 360); + XCTAssertEqual(outputFrame.height, 640); + + RTC_OBJC_TYPE(RTCI420Buffer) *outputBuffer = (RTC_OBJC_TYPE(RTCI420Buffer) *)outputFrame.buffer; + + double psnr = I420PSNR(*[buffer nativeI420Buffer], *[outputBuffer nativeI420Buffer]); + XCTAssertGreaterThanOrEqual(psnr, 40); + + [callbackExpectation fulfill]; + }); + + const rtc::VideoSinkWants video_sink_wants; + rtc::VideoSourceInterface *video_source_interface = _video_source.get(); + video_source_interface->AddOrUpdateSink(&callback_video_sink, video_sink_wants); + + _video_source->OnOutputFormatRequest(640, 360, 30); + _video_source->OnCapturedFrame(frame); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCAudioDeviceModule_xctest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCAudioDeviceModule_xctest.mm new file mode 100644 index 0000000000..f7439f39f9 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCAudioDeviceModule_xctest.mm @@ -0,0 +1,618 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#include + +#if defined(WEBRTC_IOS) +#import "sdk/objc/native/api/audio_device_module.h" +#endif + +#include "api/scoped_refptr.h" + +typedef int32_t(^NeedMorePlayDataBlock)(const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + void* audioSamples, + size_t& nSamplesOut, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms); + +typedef int32_t(^RecordedDataIsAvailableBlock)(const void* audioSamples, + const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + const uint32_t totalDelayMS, + const int32_t clockDrift, + const uint32_t currentMicLevel, + const bool keyPressed, + uint32_t& newMicLevel); + + +// This class implements the AudioTransport API and forwards all methods to the appropriate blocks. +class MockAudioTransport : public webrtc::AudioTransport { +public: + MockAudioTransport() {} + ~MockAudioTransport() override {} + + void expectNeedMorePlayData(NeedMorePlayDataBlock block) { + needMorePlayDataBlock = block; + } + + void expectRecordedDataIsAvailable(RecordedDataIsAvailableBlock block) { + recordedDataIsAvailableBlock = block; + } + + int32_t NeedMorePlayData(const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + void* audioSamples, + size_t& nSamplesOut, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) override { + return needMorePlayDataBlock(nSamples, + nBytesPerSample, + nChannels, + samplesPerSec, + audioSamples, + nSamplesOut, + elapsed_time_ms, + ntp_time_ms); + } + + int32_t RecordedDataIsAvailable(const void* audioSamples, + const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + const uint32_t totalDelayMS, + const int32_t clockDrift, + const uint32_t currentMicLevel, + const bool keyPressed, + uint32_t& newMicLevel) override { + return recordedDataIsAvailableBlock(audioSamples, + nSamples, + nBytesPerSample, + nChannels, + samplesPerSec, + totalDelayMS, + clockDrift, + currentMicLevel, + keyPressed, + newMicLevel); + } + + void PullRenderData(int bits_per_sample, + int sample_rate, + size_t number_of_channels, + size_t number_of_frames, + void* audio_data, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) override {} + + private: + NeedMorePlayDataBlock needMorePlayDataBlock; + RecordedDataIsAvailableBlock recordedDataIsAvailableBlock; +}; + +// Number of callbacks (input or output) the tests waits for before we set +// an event indicating that the test was OK. +static const NSUInteger kNumCallbacks = 10; +// Max amount of time we wait for an event to be set while counting callbacks. +static const NSTimeInterval kTestTimeOutInSec = 20.0; +// Number of bits per PCM audio sample. +static const NSUInteger kBitsPerSample = 16; +// Number of bytes per PCM audio sample. +static const NSUInteger kBytesPerSample = kBitsPerSample / 8; +// Average number of audio callbacks per second assuming 10ms packet size. +static const NSUInteger kNumCallbacksPerSecond = 100; +// Play out a test file during this time (unit is in seconds). +static const NSUInteger kFilePlayTimeInSec = 15; +// Run the full-duplex test during this time (unit is in seconds). +// Note that first `kNumIgnoreFirstCallbacks` are ignored. +static const NSUInteger kFullDuplexTimeInSec = 10; +// Wait for the callback sequence to stabilize by ignoring this amount of the +// initial callbacks (avoids initial FIFO access). +// Only used in the RunPlayoutAndRecordingInFullDuplex test. +static const NSUInteger kNumIgnoreFirstCallbacks = 50; + +@interface RTCAudioDeviceModuleTests : XCTestCase { + bool _testEnabled; + rtc::scoped_refptr audioDeviceModule; + MockAudioTransport mock; +} + +@property(nonatomic, assign) webrtc::AudioParameters playoutParameters; +@property(nonatomic, assign) webrtc::AudioParameters recordParameters; + +@end + +@implementation RTCAudioDeviceModuleTests + +@synthesize playoutParameters; +@synthesize recordParameters; + +- (void)setUp { + [super setUp]; +#if defined(WEBRTC_IOS) && TARGET_OS_SIMULATOR + // TODO(peterhanspers): Reenable these tests on simulator. + // See bugs.webrtc.org/7812 + _testEnabled = false; + if (::getenv("WEBRTC_IOS_RUN_AUDIO_TESTS") != nullptr) { + _testEnabled = true; + } +#else + _testEnabled = true; +#endif + + audioDeviceModule = webrtc::CreateAudioDeviceModule(); + XCTAssertEqual(0, audioDeviceModule->Init()); + XCTAssertEqual(0, audioDeviceModule->GetPlayoutAudioParameters(&playoutParameters)); + XCTAssertEqual(0, audioDeviceModule->GetRecordAudioParameters(&recordParameters)); +} + +- (void)tearDown { + XCTAssertEqual(0, audioDeviceModule->Terminate()); + audioDeviceModule = nullptr; + [super tearDown]; +} + +- (void)startPlayout { + XCTAssertFalse(audioDeviceModule->Playing()); + XCTAssertEqual(0, audioDeviceModule->InitPlayout()); + XCTAssertTrue(audioDeviceModule->PlayoutIsInitialized()); + XCTAssertEqual(0, audioDeviceModule->StartPlayout()); + XCTAssertTrue(audioDeviceModule->Playing()); +} + +- (void)stopPlayout { + XCTAssertEqual(0, audioDeviceModule->StopPlayout()); + XCTAssertFalse(audioDeviceModule->Playing()); +} + +- (void)startRecording{ + XCTAssertFalse(audioDeviceModule->Recording()); + XCTAssertEqual(0, audioDeviceModule->InitRecording()); + XCTAssertTrue(audioDeviceModule->RecordingIsInitialized()); + XCTAssertEqual(0, audioDeviceModule->StartRecording()); + XCTAssertTrue(audioDeviceModule->Recording()); +} + +- (void)stopRecording{ + XCTAssertEqual(0, audioDeviceModule->StopRecording()); + XCTAssertFalse(audioDeviceModule->Recording()); +} + +- (NSURL*)fileURLForSampleRate:(int)sampleRate { + XCTAssertTrue(sampleRate == 48000 || sampleRate == 44100 || sampleRate == 16000); + NSString *filename = [NSString stringWithFormat:@"audio_short%d", sampleRate / 1000]; + NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:@"pcm"]; + XCTAssertNotNil(url); + + return url; +} + +#pragma mark - Tests + +- (void)testConstructDestruct { + XCTSkipIf(!_testEnabled); + // Using the test fixture to create and destruct the audio device module. +} + +- (void)testInitTerminate { + XCTSkipIf(!_testEnabled); + // Initialization is part of the test fixture. + XCTAssertTrue(audioDeviceModule->Initialized()); + XCTAssertEqual(0, audioDeviceModule->Terminate()); + XCTAssertFalse(audioDeviceModule->Initialized()); +} + +// Tests that playout can be initiated, started and stopped. No audio callback +// is registered in this test. +- (void)testStartStopPlayout { + XCTSkipIf(!_testEnabled); + [self startPlayout]; + [self stopPlayout]; + [self startPlayout]; + [self stopPlayout]; +} + +// Tests that recording can be initiated, started and stopped. No audio callback +// is registered in this test. +- (void)testStartStopRecording { + XCTSkipIf(!_testEnabled); + [self startRecording]; + [self stopRecording]; + [self startRecording]; + [self stopRecording]; +} +// Verify that calling StopPlayout() will leave us in an uninitialized state +// which will require a new call to InitPlayout(). This test does not call +// StartPlayout() while being uninitialized since doing so will hit a +// RTC_DCHECK. +- (void)testStopPlayoutRequiresInitToRestart { + XCTSkipIf(!_testEnabled); + XCTAssertEqual(0, audioDeviceModule->InitPlayout()); + XCTAssertEqual(0, audioDeviceModule->StartPlayout()); + XCTAssertEqual(0, audioDeviceModule->StopPlayout()); + XCTAssertFalse(audioDeviceModule->PlayoutIsInitialized()); +} + +// Verify that we can create two ADMs and start playing on the second ADM. +// Only the first active instance shall activate an audio session and the +// last active instance shall deactivate the audio session. The test does not +// explicitly verify correct audio session calls but instead focuses on +// ensuring that audio starts for both ADMs. +- (void)testStartPlayoutOnTwoInstances { + XCTSkipIf(!_testEnabled); + // Create and initialize a second/extra ADM instance. The default ADM is + // created by the test harness. + rtc::scoped_refptr secondAudioDeviceModule = + webrtc::CreateAudioDeviceModule(); + XCTAssertNotEqual(secondAudioDeviceModule.get(), nullptr); + XCTAssertEqual(0, secondAudioDeviceModule->Init()); + + // Start playout for the default ADM but don't wait here. Instead use the + // upcoming second stream for that. We set the same expectation on number + // of callbacks as for the second stream. + mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + void *audioSamples, + size_t &nSamplesOut, + int64_t *elapsed_time_ms, + int64_t *ntp_time_ms) { + nSamplesOut = nSamples; + XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); + XCTAssertEqual(nBytesPerSample, kBytesPerSample); + XCTAssertEqual(nChannels, self.playoutParameters.channels()); + XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); + XCTAssertNotEqual((void*)NULL, audioSamples); + + return 0; + }); + + XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); + [self startPlayout]; + + // Initialize playout for the second ADM. If all is OK, the second ADM shall + // reuse the audio session activated when the first ADM started playing. + // This call will also ensure that we avoid a problem related to initializing + // two different audio unit instances back to back (see webrtc:5166 for + // details). + XCTAssertEqual(0, secondAudioDeviceModule->InitPlayout()); + XCTAssertTrue(secondAudioDeviceModule->PlayoutIsInitialized()); + + // Start playout for the second ADM and verify that it starts as intended. + // Passing this test ensures that initialization of the second audio unit + // has been done successfully and that there is no conflict with the already + // playing first ADM. + XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; + __block int num_callbacks = 0; + + MockAudioTransport mock2; + mock2.expectNeedMorePlayData(^int32_t(const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + void *audioSamples, + size_t &nSamplesOut, + int64_t *elapsed_time_ms, + int64_t *ntp_time_ms) { + nSamplesOut = nSamples; + XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); + XCTAssertEqual(nBytesPerSample, kBytesPerSample); + XCTAssertEqual(nChannels, self.playoutParameters.channels()); + XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); + XCTAssertNotEqual((void*)NULL, audioSamples); + if (++num_callbacks == kNumCallbacks) { + [playoutExpectation fulfill]; + } + + return 0; + }); + + XCTAssertEqual(0, secondAudioDeviceModule->RegisterAudioCallback(&mock2)); + XCTAssertEqual(0, secondAudioDeviceModule->StartPlayout()); + XCTAssertTrue(secondAudioDeviceModule->Playing()); + [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; + [self stopPlayout]; + XCTAssertEqual(0, secondAudioDeviceModule->StopPlayout()); + XCTAssertFalse(secondAudioDeviceModule->Playing()); + XCTAssertFalse(secondAudioDeviceModule->PlayoutIsInitialized()); + + XCTAssertEqual(0, secondAudioDeviceModule->Terminate()); +} + +// Start playout and verify that the native audio layer starts asking for real +// audio samples to play out using the NeedMorePlayData callback. +- (void)testStartPlayoutVerifyCallbacks { + XCTSkipIf(!_testEnabled); + XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; + __block int num_callbacks = 0; + mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + void *audioSamples, + size_t &nSamplesOut, + int64_t *elapsed_time_ms, + int64_t *ntp_time_ms) { + nSamplesOut = nSamples; + XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); + XCTAssertEqual(nBytesPerSample, kBytesPerSample); + XCTAssertEqual(nChannels, self.playoutParameters.channels()); + XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); + XCTAssertNotEqual((void*)NULL, audioSamples); + if (++num_callbacks == kNumCallbacks) { + [playoutExpectation fulfill]; + } + return 0; + }); + + XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); + + [self startPlayout]; + [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; + [self stopPlayout]; +} + +// Start recording and verify that the native audio layer starts feeding real +// audio samples via the RecordedDataIsAvailable callback. +- (void)testStartRecordingVerifyCallbacks { + XCTSkipIf(!_testEnabled); + XCTestExpectation *recordExpectation = + [self expectationWithDescription:@"RecordedDataIsAvailable"]; + __block int num_callbacks = 0; + + mock.expectRecordedDataIsAvailable(^(const void* audioSamples, + const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + const uint32_t totalDelayMS, + const int32_t clockDrift, + const uint32_t currentMicLevel, + const bool keyPressed, + uint32_t& newMicLevel) { + XCTAssertNotEqual((void*)NULL, audioSamples); + XCTAssertEqual(nSamples, self.recordParameters.frames_per_10ms_buffer()); + XCTAssertEqual(nBytesPerSample, kBytesPerSample); + XCTAssertEqual(nChannels, self.recordParameters.channels()); + XCTAssertEqual((int)samplesPerSec, self.recordParameters.sample_rate()); + XCTAssertEqual(0, clockDrift); + XCTAssertEqual(0u, currentMicLevel); + XCTAssertFalse(keyPressed); + if (++num_callbacks == kNumCallbacks) { + [recordExpectation fulfill]; + } + + return 0; + }); + + XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); + [self startRecording]; + [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; + [self stopRecording]; +} + +// Start playout and recording (full-duplex audio) and verify that audio is +// active in both directions. +- (void)testStartPlayoutAndRecordingVerifyCallbacks { + XCTSkipIf(!_testEnabled); + XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; + __block NSUInteger callbackCount = 0; + + XCTestExpectation *recordExpectation = + [self expectationWithDescription:@"RecordedDataIsAvailable"]; + recordExpectation.expectedFulfillmentCount = kNumCallbacks; + + mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + void *audioSamples, + size_t &nSamplesOut, + int64_t *elapsed_time_ms, + int64_t *ntp_time_ms) { + nSamplesOut = nSamples; + XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); + XCTAssertEqual(nBytesPerSample, kBytesPerSample); + XCTAssertEqual(nChannels, self.playoutParameters.channels()); + XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); + XCTAssertNotEqual((void*)NULL, audioSamples); + if (callbackCount++ >= kNumCallbacks) { + [playoutExpectation fulfill]; + } + + return 0; + }); + + mock.expectRecordedDataIsAvailable(^(const void* audioSamples, + const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + const uint32_t totalDelayMS, + const int32_t clockDrift, + const uint32_t currentMicLevel, + const bool keyPressed, + uint32_t& newMicLevel) { + XCTAssertNotEqual((void*)NULL, audioSamples); + XCTAssertEqual(nSamples, self.recordParameters.frames_per_10ms_buffer()); + XCTAssertEqual(nBytesPerSample, kBytesPerSample); + XCTAssertEqual(nChannels, self.recordParameters.channels()); + XCTAssertEqual((int)samplesPerSec, self.recordParameters.sample_rate()); + XCTAssertEqual(0, clockDrift); + XCTAssertEqual(0u, currentMicLevel); + XCTAssertFalse(keyPressed); + [recordExpectation fulfill]; + + return 0; + }); + + XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); + [self startPlayout]; + [self startRecording]; + [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; + [self stopRecording]; + [self stopPlayout]; +} + +// Start playout and read audio from an external PCM file when the audio layer +// asks for data to play out. Real audio is played out in this test but it does +// not contain any explicit verification that the audio quality is perfect. +- (void)testRunPlayoutWithFileAsSource { + XCTSkipIf(!_testEnabled); + XCTAssertEqual(1u, playoutParameters.channels()); + + // Using XCTestExpectation to count callbacks is very slow. + XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; + const int expectedCallbackCount = kFilePlayTimeInSec * kNumCallbacksPerSecond; + __block int callbackCount = 0; + + NSURL *fileURL = [self fileURLForSampleRate:playoutParameters.sample_rate()]; + NSInputStream *inputStream = [[NSInputStream alloc] initWithURL:fileURL]; + + mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + void *audioSamples, + size_t &nSamplesOut, + int64_t *elapsed_time_ms, + int64_t *ntp_time_ms) { + [inputStream read:(uint8_t *)audioSamples maxLength:nSamples*nBytesPerSample*nChannels]; + nSamplesOut = nSamples; + if (callbackCount++ == expectedCallbackCount) { + [playoutExpectation fulfill]; + } + + return 0; + }); + + XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); + [self startPlayout]; + NSTimeInterval waitTimeout = kFilePlayTimeInSec * 2.0; + [self waitForExpectationsWithTimeout:waitTimeout handler:nil]; + [self stopPlayout]; +} + +- (void)testDevices { + XCTSkipIf(!_testEnabled); + // Device enumeration is not supported. Verify fixed values only. + XCTAssertEqual(1, audioDeviceModule->PlayoutDevices()); + XCTAssertEqual(1, audioDeviceModule->RecordingDevices()); +} + +// Start playout and recording and store recorded data in an intermediate FIFO +// buffer from which the playout side then reads its samples in the same order +// as they were stored. Under ideal circumstances, a callback sequence would +// look like: ...+-+-+-+-+-+-+-..., where '+' means 'packet recorded' and '-' +// means 'packet played'. Under such conditions, the FIFO would only contain +// one packet on average. However, under more realistic conditions, the size +// of the FIFO will vary more due to an unbalance between the two sides. +// This test tries to verify that the device maintains a balanced callback- +// sequence by running in loopback for ten seconds while measuring the size +// (max and average) of the FIFO. The size of the FIFO is increased by the +// recording side and decreased by the playout side. +// TODO(henrika): tune the final test parameters after running tests on several +// different devices. +- (void)testRunPlayoutAndRecordingInFullDuplex { + XCTSkipIf(!_testEnabled); + XCTAssertEqual(recordParameters.channels(), playoutParameters.channels()); + XCTAssertEqual(recordParameters.sample_rate(), playoutParameters.sample_rate()); + + XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; + __block NSUInteger playoutCallbacks = 0; + NSUInteger expectedPlayoutCallbacks = kFullDuplexTimeInSec * kNumCallbacksPerSecond; + + // FIFO queue and measurements + NSMutableArray *fifoBuffer = [NSMutableArray arrayWithCapacity:20]; + __block NSUInteger fifoMaxSize = 0; + __block NSUInteger fifoTotalWrittenElements = 0; + __block NSUInteger fifoWriteCount = 0; + + mock.expectRecordedDataIsAvailable(^(const void* audioSamples, + const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + const uint32_t totalDelayMS, + const int32_t clockDrift, + const uint32_t currentMicLevel, + const bool keyPressed, + uint32_t& newMicLevel) { + if (fifoWriteCount++ < kNumIgnoreFirstCallbacks) { + return 0; + } + + NSData *data = [NSData dataWithBytes:audioSamples length:nSamples*nBytesPerSample*nChannels]; + @synchronized(fifoBuffer) { + [fifoBuffer addObject:data]; + fifoMaxSize = MAX(fifoMaxSize, fifoBuffer.count); + fifoTotalWrittenElements += fifoBuffer.count; + } + + return 0; + }); + + mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, + const size_t nBytesPerSample, + const size_t nChannels, + const uint32_t samplesPerSec, + void *audioSamples, + size_t &nSamplesOut, + int64_t *elapsed_time_ms, + int64_t *ntp_time_ms) { + nSamplesOut = nSamples; + NSData *data; + @synchronized(fifoBuffer) { + data = fifoBuffer.firstObject; + if (data) { + [fifoBuffer removeObjectAtIndex:0]; + } + } + + if (data) { + memcpy(audioSamples, (char*) data.bytes, data.length); + } else { + memset(audioSamples, 0, nSamples*nBytesPerSample*nChannels); + } + + if (playoutCallbacks++ == expectedPlayoutCallbacks) { + [playoutExpectation fulfill]; + } + return 0; + }); + + XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); + [self startRecording]; + [self startPlayout]; + NSTimeInterval waitTimeout = kFullDuplexTimeInSec * 2.0; + [self waitForExpectationsWithTimeout:waitTimeout handler:nil]; + + size_t fifoAverageSize = + (fifoTotalWrittenElements == 0) + ? 0.0 + : 0.5 + (double)fifoTotalWrittenElements / (fifoWriteCount - kNumIgnoreFirstCallbacks); + + [self stopPlayout]; + [self stopRecording]; + XCTAssertLessThan(fifoAverageSize, 10u); + XCTAssertLessThan(fifoMaxSize, 20u); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCAudioDevice_xctest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCAudioDevice_xctest.mm new file mode 100644 index 0000000000..eec9e17a17 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCAudioDevice_xctest.mm @@ -0,0 +1,129 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#include + +#include "api/task_queue/default_task_queue_factory.h" + +#import "sdk/objc/components/audio/RTCAudioSession+Private.h" +#import "sdk/objc/native/api/audio_device_module.h" +#import "sdk/objc/native/src/audio/audio_device_ios.h" + +@interface RTCAudioDeviceTests : XCTestCase { + bool _testEnabled; + rtc::scoped_refptr _audioDeviceModule; + std::unique_ptr _audio_device; +} + +@property(nonatomic) RTC_OBJC_TYPE(RTCAudioSession) * audioSession; + +@end + +@implementation RTCAudioDeviceTests + +@synthesize audioSession = _audioSession; + +- (void)setUp { + [super setUp]; +#if defined(WEBRTC_IOS) && TARGET_OS_SIMULATOR + // TODO(peterhanspers): Reenable these tests on simulator. + // See bugs.webrtc.org/7812 + _testEnabled = false; + if (::getenv("WEBRTC_IOS_RUN_AUDIO_TESTS") != nullptr) { + _testEnabled = true; + } +#else + _testEnabled = true; +#endif + + _audioDeviceModule = webrtc::CreateAudioDeviceModule(); + _audio_device.reset(new webrtc::ios_adm::AudioDeviceIOS(/*bypass_voice_processing=*/false)); + self.audioSession = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; + + NSError *error = nil; + [self.audioSession lockForConfiguration]; + [self.audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:0 error:&error]; + XCTAssertNil(error); + + [self.audioSession setMode:AVAudioSessionModeVoiceChat error:&error]; + XCTAssertNil(error); + + [self.audioSession setActive:YES error:&error]; + XCTAssertNil(error); + + [self.audioSession unlockForConfiguration]; +} + +- (void)tearDown { + _audio_device->Terminate(); + _audio_device.reset(nullptr); + _audioDeviceModule = nullptr; + [self.audioSession notifyDidEndInterruptionWithShouldResumeSession:NO]; + + [super tearDown]; +} + +// Verifies that the AudioDeviceIOS is_interrupted_ flag is reset correctly +// after an iOS AVAudioSessionInterruptionTypeEnded notification event. +// AudioDeviceIOS listens to RTC_OBJC_TYPE(RTCAudioSession) interrupted notifications by: +// - In AudioDeviceIOS.InitPlayOrRecord registers its audio_session_observer_ +// callback with RTC_OBJC_TYPE(RTCAudioSession)'s delegate list. +// - When RTC_OBJC_TYPE(RTCAudioSession) receives an iOS audio interrupted notification, it +// passes the notification to callbacks in its delegate list which sets +// AudioDeviceIOS's is_interrupted_ flag to true. +// - When AudioDeviceIOS.ShutdownPlayOrRecord is called, its +// audio_session_observer_ callback is removed from RTCAudioSessions's +// delegate list. +// So if RTC_OBJC_TYPE(RTCAudioSession) receives an iOS end audio interruption notification, +// AudioDeviceIOS is not notified as its callback is not in RTC_OBJC_TYPE(RTCAudioSession)'s +// delegate list. This causes AudioDeviceIOS's is_interrupted_ flag to be in +// the wrong (true) state and the audio session will ignore audio changes. +// As RTC_OBJC_TYPE(RTCAudioSession) keeps its own interrupted state, the fix is to initialize +// AudioDeviceIOS's is_interrupted_ flag to RTC_OBJC_TYPE(RTCAudioSession)'s isInterrupted +// flag in AudioDeviceIOS.InitPlayOrRecord. +- (void)testInterruptedAudioSession { + XCTSkipIf(!_testEnabled); + XCTAssertTrue(self.audioSession.isActive); + XCTAssertTrue([self.audioSession.category isEqual:AVAudioSessionCategoryPlayAndRecord] || + [self.audioSession.category isEqual:AVAudioSessionCategoryPlayback]); + XCTAssertEqual(AVAudioSessionModeVoiceChat, self.audioSession.mode); + + std::unique_ptr task_queue_factory = + webrtc::CreateDefaultTaskQueueFactory(); + std::unique_ptr audio_buffer; + audio_buffer.reset(new webrtc::AudioDeviceBuffer(task_queue_factory.get())); + _audio_device->AttachAudioBuffer(audio_buffer.get()); + XCTAssertEqual(webrtc::AudioDeviceGeneric::InitStatus::OK, _audio_device->Init()); + XCTAssertEqual(0, _audio_device->InitPlayout()); + XCTAssertEqual(0, _audio_device->StartPlayout()); + + // Force interruption. + [self.audioSession notifyDidBeginInterruption]; + + // Wait for notification to propagate. + rtc::ThreadManager::ProcessAllMessageQueuesForTesting(); + XCTAssertTrue(_audio_device->IsInterrupted()); + + // Force it for testing. + _audio_device->StopPlayout(); + + [self.audioSession notifyDidEndInterruptionWithShouldResumeSession:YES]; + // Wait for notification to propagate. + rtc::ThreadManager::ProcessAllMessageQueuesForTesting(); + XCTAssertTrue(_audio_device->IsInterrupted()); + + _audio_device->Init(); + _audio_device->InitPlayout(); + XCTAssertFalse(_audio_device->IsInterrupted()); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCAudioSessionTest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCAudioSessionTest.mm new file mode 100644 index 0000000000..d7cfc9ed04 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCAudioSessionTest.mm @@ -0,0 +1,317 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import +#import + +#include + +#include "rtc_base/event.h" +#include "rtc_base/gunit.h" + +#import "components/audio/RTCAudioSession+Private.h" + +#import "components/audio/RTCAudioSession.h" +#import "components/audio/RTCAudioSessionConfiguration.h" + +@interface RTC_OBJC_TYPE (RTCAudioSession) +(UnitTesting) + + @property(nonatomic, + readonly) std::vector<__weak id > delegates; + +- (instancetype)initWithAudioSession:(id)audioSession; + +@end + +@interface MockAVAudioSession : NSObject + +@property (nonatomic, readwrite, assign) float outputVolume; + +@end + +@implementation MockAVAudioSession +@synthesize outputVolume = _outputVolume; +@end + +@interface RTCAudioSessionTestDelegate : NSObject + +@property (nonatomic, readonly) float outputVolume; + +@end + +@implementation RTCAudioSessionTestDelegate + +@synthesize outputVolume = _outputVolume; + +- (instancetype)init { + if (self = [super init]) { + _outputVolume = -1; + } + return self; +} + +- (void)audioSessionDidBeginInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session { +} + +- (void)audioSessionDidEndInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session + shouldResumeSession:(BOOL)shouldResumeSession { +} + +- (void)audioSessionDidChangeRoute:(RTC_OBJC_TYPE(RTCAudioSession) *)session + reason:(AVAudioSessionRouteChangeReason)reason + previousRoute:(AVAudioSessionRouteDescription *)previousRoute { +} + +- (void)audioSessionMediaServerTerminated:(RTC_OBJC_TYPE(RTCAudioSession) *)session { +} + +- (void)audioSessionMediaServerReset:(RTC_OBJC_TYPE(RTCAudioSession) *)session { +} + +- (void)audioSessionShouldConfigure:(RTC_OBJC_TYPE(RTCAudioSession) *)session { +} + +- (void)audioSessionShouldUnconfigure:(RTC_OBJC_TYPE(RTCAudioSession) *)session { +} + +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + didChangeOutputVolume:(float)outputVolume { + _outputVolume = outputVolume; +} + +@end + +// A delegate that adds itself to the audio session on init and removes itself +// in its dealloc. +@interface RTCTestRemoveOnDeallocDelegate : RTCAudioSessionTestDelegate +@end + +@implementation RTCTestRemoveOnDeallocDelegate + +- (instancetype)init { + if (self = [super init]) { + RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; + [session addDelegate:self]; + } + return self; +} + +- (void)dealloc { + RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; + [session removeDelegate:self]; +} + +@end + +@interface RTCAudioSessionTest : XCTestCase + +@end + +@implementation RTCAudioSessionTest + +- (void)testAddAndRemoveDelegates { + RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; + NSMutableArray *delegates = [NSMutableArray array]; + const size_t count = 5; + for (size_t i = 0; i < count; ++i) { + RTCAudioSessionTestDelegate *delegate = + [[RTCAudioSessionTestDelegate alloc] init]; + [session addDelegate:delegate]; + [delegates addObject:delegate]; + EXPECT_EQ(i + 1, session.delegates.size()); + } + [delegates enumerateObjectsUsingBlock:^(RTCAudioSessionTestDelegate *obj, + NSUInteger idx, + BOOL *stop) { + [session removeDelegate:obj]; + }]; + EXPECT_EQ(0u, session.delegates.size()); +} + +- (void)testPushDelegate { + RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; + NSMutableArray *delegates = [NSMutableArray array]; + const size_t count = 2; + for (size_t i = 0; i < count; ++i) { + RTCAudioSessionTestDelegate *delegate = + [[RTCAudioSessionTestDelegate alloc] init]; + [session addDelegate:delegate]; + [delegates addObject:delegate]; + } + // Test that it gets added to the front of the list. + RTCAudioSessionTestDelegate *pushedDelegate = + [[RTCAudioSessionTestDelegate alloc] init]; + [session pushDelegate:pushedDelegate]; + EXPECT_TRUE(pushedDelegate == session.delegates[0]); + + // Test that it stays at the front of the list. + for (size_t i = 0; i < count; ++i) { + RTCAudioSessionTestDelegate *delegate = + [[RTCAudioSessionTestDelegate alloc] init]; + [session addDelegate:delegate]; + [delegates addObject:delegate]; + } + EXPECT_TRUE(pushedDelegate == session.delegates[0]); + + // Test that the next one goes to the front too. + pushedDelegate = [[RTCAudioSessionTestDelegate alloc] init]; + [session pushDelegate:pushedDelegate]; + EXPECT_TRUE(pushedDelegate == session.delegates[0]); +} + +// Tests that delegates added to the audio session properly zero out. This is +// checking an implementation detail (that vectors of __weak work as expected). +- (void)testZeroingWeakDelegate { + RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; + @autoreleasepool { + // Add a delegate to the session. There should be one delegate at this + // point. + RTCAudioSessionTestDelegate *delegate = + [[RTCAudioSessionTestDelegate alloc] init]; + [session addDelegate:delegate]; + EXPECT_EQ(1u, session.delegates.size()); + EXPECT_TRUE(session.delegates[0]); + } + // The previously created delegate should've de-alloced, leaving a nil ptr. + EXPECT_FALSE(session.delegates[0]); + RTCAudioSessionTestDelegate *delegate = + [[RTCAudioSessionTestDelegate alloc] init]; + [session addDelegate:delegate]; + // On adding a new delegate, nil ptrs should've been cleared. + EXPECT_EQ(1u, session.delegates.size()); + EXPECT_TRUE(session.delegates[0]); +} + +// Tests that we don't crash when removing delegates in dealloc. +// Added as a regression test. +- (void)testRemoveDelegateOnDealloc { + @autoreleasepool { + RTCTestRemoveOnDeallocDelegate *delegate = + [[RTCTestRemoveOnDeallocDelegate alloc] init]; + EXPECT_TRUE(delegate); + } + RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; + EXPECT_EQ(0u, session.delegates.size()); +} + +- (void)testAudioSessionActivation { + RTC_OBJC_TYPE(RTCAudioSession) *audioSession = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; + EXPECT_EQ(0, audioSession.activationCount); + [audioSession audioSessionDidActivate:[AVAudioSession sharedInstance]]; + EXPECT_EQ(1, audioSession.activationCount); + [audioSession audioSessionDidDeactivate:[AVAudioSession sharedInstance]]; + EXPECT_EQ(0, audioSession.activationCount); +} + +// TODO(b/298960678): Fix crash when running the test on simulators. +- (void)DISABLED_testConfigureWebRTCSession { + NSError *error = nil; + + void (^setActiveBlock)(NSInvocation *invocation) = ^(NSInvocation *invocation) { + __autoreleasing NSError **retError; + [invocation getArgument:&retError atIndex:4]; + *retError = [NSError errorWithDomain:@"AVAudioSession" + code:AVAudioSessionErrorCodeCannotInterruptOthers + userInfo:nil]; + BOOL failure = NO; + [invocation setReturnValue:&failure]; + }; + + id mockAVAudioSession = OCMPartialMock([AVAudioSession sharedInstance]); + OCMStub([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES + withOptions:0 + error:([OCMArg anyObjectRef])]) + .andDo(setActiveBlock); + + id mockAudioSession = OCMPartialMock([RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]); + OCMStub([mockAudioSession session]).andReturn(mockAVAudioSession); + + RTC_OBJC_TYPE(RTCAudioSession) *audioSession = mockAudioSession; + EXPECT_EQ(0, audioSession.activationCount); + [audioSession lockForConfiguration]; + // configureWebRTCSession is forced to fail in the above mock interface, + // so activationCount should remain 0 + OCMExpect([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES + withOptions:0 + error:([OCMArg anyObjectRef])]) + .andDo(setActiveBlock); + OCMExpect([mockAudioSession session]).andReturn(mockAVAudioSession); + EXPECT_FALSE([audioSession configureWebRTCSession:&error]); + EXPECT_EQ(0, audioSession.activationCount); + + id session = audioSession.session; + EXPECT_EQ(session, mockAVAudioSession); + EXPECT_EQ(NO, [mockAVAudioSession setActive:YES withOptions:0 error:&error]); + [audioSession unlockForConfiguration]; + + // The -Wunused-value is a workaround for https://bugs.llvm.org/show_bug.cgi?id=45245 + _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wunused-value\""); + OCMVerify([mockAudioSession session]); + OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES withOptions:0 error:&error]); + OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:NO withOptions:0 error:&error]); + _Pragma("clang diagnostic pop"); + + [mockAVAudioSession stopMocking]; + [mockAudioSession stopMocking]; +} + +// TODO(b/298960678): Fix crash when running the test on simulators. +- (void)DISABLED_testConfigureWebRTCSessionWithoutLocking { + NSError *error = nil; + + id mockAVAudioSession = OCMPartialMock([AVAudioSession sharedInstance]); + id mockAudioSession = OCMPartialMock([RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]); + OCMStub([mockAudioSession session]).andReturn(mockAVAudioSession); + + RTC_OBJC_TYPE(RTCAudioSession) *audioSession = mockAudioSession; + + std::unique_ptr thread = rtc::Thread::Create(); + EXPECT_TRUE(thread); + EXPECT_TRUE(thread->Start()); + + rtc::Event waitLock; + rtc::Event waitCleanup; + constexpr webrtc::TimeDelta timeout = webrtc::TimeDelta::Seconds(5); + thread->PostTask([audioSession, &waitLock, &waitCleanup, timeout] { + [audioSession lockForConfiguration]; + waitLock.Set(); + waitCleanup.Wait(timeout); + [audioSession unlockForConfiguration]; + }); + + waitLock.Wait(timeout); + [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:0 error:&error]; + EXPECT_TRUE(error != nil); + EXPECT_EQ(error.domain, kRTCAudioSessionErrorDomain); + EXPECT_EQ(error.code, kRTCAudioSessionErrorLockRequired); + waitCleanup.Set(); + thread->Stop(); + + [mockAVAudioSession stopMocking]; + [mockAudioSession stopMocking]; +} + +- (void)testAudioVolumeDidNotify { + MockAVAudioSession *mockAVAudioSession = [[MockAVAudioSession alloc] init]; + RTC_OBJC_TYPE(RTCAudioSession) *session = + [[RTC_OBJC_TYPE(RTCAudioSession) alloc] initWithAudioSession:mockAVAudioSession]; + RTCAudioSessionTestDelegate *delegate = + [[RTCAudioSessionTestDelegate alloc] init]; + [session addDelegate:delegate]; + + float expectedVolume = 0.75; + mockAVAudioSession.outputVolume = expectedVolume; + + EXPECT_EQ(expectedVolume, delegate.outputVolume); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCCVPixelBuffer_xctest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCCVPixelBuffer_xctest.mm new file mode 100644 index 0000000000..cf759c5243 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCCVPixelBuffer_xctest.mm @@ -0,0 +1,461 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import "components/video_frame_buffer/RTCCVPixelBuffer.h" + +#import "api/video_frame_buffer/RTCNativeI420Buffer+Private.h" +#import "base/RTCVideoFrame.h" +#import "base/RTCVideoFrameBuffer.h" +#import "frame_buffer_helpers.h" + +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "third_party/libyuv/include/libyuv.h" + +namespace { + +struct ToI420WithCropAndScaleSetting { + int inputWidth; + int inputHeight; + int offsetX; + int offsetY; + int cropWidth; + int cropHeight; + int scaleWidth; + int scaleHeight; +}; + +constexpr const ToI420WithCropAndScaleSetting kToI420WithCropAndScaleSettings[] = { + ToI420WithCropAndScaleSetting{ + .inputWidth = 640, + .inputHeight = 360, + .offsetX = 0, + .offsetY = 0, + .cropWidth = 640, + .cropHeight = 360, + .scaleWidth = 320, + .scaleHeight = 180, + }, + ToI420WithCropAndScaleSetting{ + .inputWidth = 640, + .inputHeight = 360, + .offsetX = 160, + .offsetY = 90, + .cropWidth = 160, + .cropHeight = 90, + .scaleWidth = 320, + .scaleHeight = 180, + }, +}; + +} // namespace + +@interface RTCCVPixelBufferTests : XCTestCase +@end + +@implementation RTCCVPixelBufferTests { +} + +- (void)testRequiresCroppingNoCrop { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + + XCTAssertFalse([buffer requiresCropping]); + + CVBufferRelease(pixelBufferRef); +} + +- (void)testRequiresCroppingWithCrop { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + RTC_OBJC_TYPE(RTCCVPixelBuffer) *croppedBuffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef + adaptedWidth:720 + adaptedHeight:1280 + cropWidth:360 + cropHeight:640 + cropX:100 + cropY:100]; + + XCTAssertTrue([croppedBuffer requiresCropping]); + + CVBufferRelease(pixelBufferRef); +} + +- (void)testRequiresScalingNoScale { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + XCTAssertFalse([buffer requiresScalingToWidth:720 height:1280]); + + CVBufferRelease(pixelBufferRef); +} + +- (void)testRequiresScalingWithScale { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + XCTAssertTrue([buffer requiresScalingToWidth:360 height:640]); + + CVBufferRelease(pixelBufferRef); +} + +- (void)testRequiresScalingWithScaleAndMatchingCrop { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef + adaptedWidth:720 + adaptedHeight:1280 + cropWidth:360 + cropHeight:640 + cropX:100 + cropY:100]; + XCTAssertFalse([buffer requiresScalingToWidth:360 height:640]); + + CVBufferRelease(pixelBufferRef); +} + +- (void)testBufferSize_NV12 { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 720, 1280, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + XCTAssertEqual([buffer bufferSizeForCroppingAndScalingToWidth:360 height:640], 576000); + + CVBufferRelease(pixelBufferRef); +} + +- (void)testBufferSize_RGB { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate(NULL, 720, 1280, kCVPixelFormatType_32BGRA, NULL, &pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + XCTAssertEqual([buffer bufferSizeForCroppingAndScalingToWidth:360 height:640], 0); + + CVBufferRelease(pixelBufferRef); +} + +- (void)testCropAndScale_NV12 { + [self cropAndScaleTestWithNV12]; +} + +- (void)testCropAndScaleNoOp_NV12 { + [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + outputSize:CGSizeMake(720, 1280)]; +} + +- (void)testCropAndScale_NV12FullToVideo { + [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange + outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]; +} + +- (void)testCropAndScaleZeroSizeFrame_NV12 { + [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + outputSize:CGSizeMake(0, 0)]; +} + +- (void)testCropAndScaleToSmallFormat_NV12 { + [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + outputSize:CGSizeMake(148, 320)]; +} + +- (void)testCropAndScaleToOddFormat_NV12 { + [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + outputSize:CGSizeMake(361, 640)]; +} + +- (void)testCropAndScale_32BGRA { + [self cropAndScaleTestWithRGBPixelFormat:kCVPixelFormatType_32BGRA]; +} + +- (void)testCropAndScale_32ARGB { + [self cropAndScaleTestWithRGBPixelFormat:kCVPixelFormatType_32ARGB]; +} + +- (void)testCropAndScaleWithSmallCropInfo_32ARGB { + [self cropAndScaleTestWithRGBPixelFormat:kCVPixelFormatType_32ARGB cropX:2 cropY:3]; +} + +- (void)testCropAndScaleWithLargeCropInfo_32ARGB { + [self cropAndScaleTestWithRGBPixelFormat:kCVPixelFormatType_32ARGB cropX:200 cropY:300]; +} + +- (void)testToI420_NV12 { + [self toI420WithPixelFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]; +} + +- (void)testToI420_32BGRA { + [self toI420WithPixelFormat:kCVPixelFormatType_32BGRA]; +} + +- (void)testToI420_32ARGB { + [self toI420WithPixelFormat:kCVPixelFormatType_32ARGB]; +} + +- (void)testToI420WithCropAndScale_NV12 { + for (const auto &setting : kToI420WithCropAndScaleSettings) { + [self toI420WithCropAndScaleWithPixelFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + setting:setting]; + } +} + +- (void)testToI420WithCropAndScale_32BGRA { + for (const auto &setting : kToI420WithCropAndScaleSettings) { + [self toI420WithCropAndScaleWithPixelFormat:kCVPixelFormatType_32BGRA setting:setting]; + } +} + +- (void)testToI420WithCropAndScale_32ARGB { + for (const auto &setting : kToI420WithCropAndScaleSettings) { + [self toI420WithCropAndScaleWithPixelFormat:kCVPixelFormatType_32ARGB setting:setting]; + } +} + +- (void)testScaleBufferTest { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, 1920, 1080, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, NULL, &pixelBufferRef); + + rtc::scoped_refptr i420Buffer = CreateI420Gradient(1920, 1080); + CopyI420BufferToCVPixelBuffer(i420Buffer, pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + + XCTAssertEqual(buffer.width, 1920); + XCTAssertEqual(buffer.height, 1080); + XCTAssertEqual(buffer.cropX, 0); + XCTAssertEqual(buffer.cropY, 0); + XCTAssertEqual(buffer.cropWidth, 1920); + XCTAssertEqual(buffer.cropHeight, 1080); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer2 = + (RTC_OBJC_TYPE(RTCCVPixelBuffer) *)[buffer cropAndScaleWith:320 + offsetY:180 + cropWidth:1280 + cropHeight:720 + scaleWidth:960 + scaleHeight:540]; + + XCTAssertEqual(buffer2.width, 960); + XCTAssertEqual(buffer2.height, 540); + XCTAssertEqual(buffer2.cropX, 320); + XCTAssertEqual(buffer2.cropY, 180); + XCTAssertEqual(buffer2.cropWidth, 1280); + XCTAssertEqual(buffer2.cropHeight, 720); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer3 = + (RTC_OBJC_TYPE(RTCCVPixelBuffer) *)[buffer2 cropAndScaleWith:240 + offsetY:135 + cropWidth:480 + cropHeight:270 + scaleWidth:320 + scaleHeight:180]; + + XCTAssertEqual(buffer3.width, 320); + XCTAssertEqual(buffer3.height, 180); + XCTAssertEqual(buffer3.cropX, 640); + XCTAssertEqual(buffer3.cropY, 360); + XCTAssertEqual(buffer3.cropWidth, 640); + XCTAssertEqual(buffer3.cropHeight, 360); + + CVBufferRelease(pixelBufferRef); +} + +#pragma mark - Shared test code + +- (void)cropAndScaleTestWithNV12 { + [self cropAndScaleTestWithNV12InputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + outputFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]; +} + +- (void)cropAndScaleTestWithNV12InputFormat:(OSType)inputFormat outputFormat:(OSType)outputFormat { + [self cropAndScaleTestWithNV12InputFormat:(OSType)inputFormat + outputFormat:(OSType)outputFormat + outputSize:CGSizeMake(360, 640)]; +} + +- (void)cropAndScaleTestWithNV12InputFormat:(OSType)inputFormat + outputFormat:(OSType)outputFormat + outputSize:(CGSize)outputSize { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate(NULL, 720, 1280, inputFormat, NULL, &pixelBufferRef); + + rtc::scoped_refptr i420Buffer = CreateI420Gradient(720, 1280); + CopyI420BufferToCVPixelBuffer(i420Buffer, pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + XCTAssertEqual(buffer.width, 720); + XCTAssertEqual(buffer.height, 1280); + + CVPixelBufferRef outputPixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, outputSize.width, outputSize.height, outputFormat, NULL, &outputPixelBufferRef); + + std::vector frameScaleBuffer; + if ([buffer requiresScalingToWidth:outputSize.width height:outputSize.height]) { + int size = + [buffer bufferSizeForCroppingAndScalingToWidth:outputSize.width height:outputSize.height]; + frameScaleBuffer.resize(size); + } else { + frameScaleBuffer.clear(); + } + frameScaleBuffer.shrink_to_fit(); + + [buffer cropAndScaleTo:outputPixelBufferRef withTempBuffer:frameScaleBuffer.data()]; + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *scaledBuffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:outputPixelBufferRef]; + XCTAssertEqual(scaledBuffer.width, outputSize.width); + XCTAssertEqual(scaledBuffer.height, outputSize.height); + + if (outputSize.width > 0 && outputSize.height > 0) { + RTC_OBJC_TYPE(RTCI420Buffer) *originalBufferI420 = [buffer toI420]; + RTC_OBJC_TYPE(RTCI420Buffer) *scaledBufferI420 = [scaledBuffer toI420]; + double psnr = + I420PSNR(*[originalBufferI420 nativeI420Buffer], *[scaledBufferI420 nativeI420Buffer]); + XCTAssertEqual(psnr, webrtc::kPerfectPSNR); + } + + CVBufferRelease(pixelBufferRef); +} + +- (void)cropAndScaleTestWithRGBPixelFormat:(OSType)pixelFormat { + [self cropAndScaleTestWithRGBPixelFormat:pixelFormat cropX:0 cropY:0]; +} + +- (void)cropAndScaleTestWithRGBPixelFormat:(OSType)pixelFormat cropX:(int)cropX cropY:(int)cropY { + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate(NULL, 720, 1280, pixelFormat, NULL, &pixelBufferRef); + + DrawGradientInRGBPixelBuffer(pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] + initWithPixelBuffer:pixelBufferRef + adaptedWidth:CVPixelBufferGetWidth(pixelBufferRef) + adaptedHeight:CVPixelBufferGetHeight(pixelBufferRef) + cropWidth:CVPixelBufferGetWidth(pixelBufferRef) - cropX + cropHeight:CVPixelBufferGetHeight(pixelBufferRef) - cropY + cropX:cropX + cropY:cropY]; + + XCTAssertEqual(buffer.width, 720); + XCTAssertEqual(buffer.height, 1280); + + CVPixelBufferRef outputPixelBufferRef = NULL; + CVPixelBufferCreate(NULL, 360, 640, pixelFormat, NULL, &outputPixelBufferRef); + [buffer cropAndScaleTo:outputPixelBufferRef withTempBuffer:NULL]; + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *scaledBuffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:outputPixelBufferRef]; + XCTAssertEqual(scaledBuffer.width, 360); + XCTAssertEqual(scaledBuffer.height, 640); + + RTC_OBJC_TYPE(RTCI420Buffer) *originalBufferI420 = [buffer toI420]; + RTC_OBJC_TYPE(RTCI420Buffer) *scaledBufferI420 = [scaledBuffer toI420]; + double psnr = + I420PSNR(*[originalBufferI420 nativeI420Buffer], *[scaledBufferI420 nativeI420Buffer]); + XCTAssertEqual(psnr, webrtc::kPerfectPSNR); + + CVBufferRelease(pixelBufferRef); +} + +- (void)toI420WithPixelFormat:(OSType)pixelFormat { + rtc::scoped_refptr i420Buffer = CreateI420Gradient(360, 640); + + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate(NULL, 360, 640, pixelFormat, NULL, &pixelBufferRef); + + CopyI420BufferToCVPixelBuffer(i420Buffer, pixelBufferRef); + + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + RTC_OBJC_TYPE(RTCI420Buffer) *fromCVPixelBuffer = [buffer toI420]; + + double psnr = I420PSNR(*i420Buffer, *[fromCVPixelBuffer nativeI420Buffer]); + double target = webrtc::kPerfectPSNR; + if (pixelFormat != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) { + // libyuv's I420ToRGB functions seem to lose some quality. + target = 19.0; + } + XCTAssertGreaterThanOrEqual(psnr, target); + + CVBufferRelease(pixelBufferRef); +} + +- (void)toI420WithCropAndScaleWithPixelFormat:(OSType)pixelFormat + setting:(const ToI420WithCropAndScaleSetting &)setting { + rtc::scoped_refptr i420Buffer = + CreateI420Gradient(setting.inputWidth, setting.inputHeight); + + CVPixelBufferRef pixelBufferRef = NULL; + CVPixelBufferCreate( + NULL, setting.inputWidth, setting.inputHeight, pixelFormat, NULL, &pixelBufferRef); + + CopyI420BufferToCVPixelBuffer(i420Buffer, pixelBufferRef); + + RTC_OBJC_TYPE(RTCI420Buffer) *objcI420Buffer = + [[RTC_OBJC_TYPE(RTCI420Buffer) alloc] initWithFrameBuffer:i420Buffer]; + RTC_OBJC_TYPE(RTCI420Buffer) *scaledObjcI420Buffer = + (RTC_OBJC_TYPE(RTCI420Buffer) *)[objcI420Buffer cropAndScaleWith:setting.offsetX + offsetY:setting.offsetY + cropWidth:setting.cropWidth + cropHeight:setting.cropHeight + scaleWidth:setting.scaleWidth + scaleHeight:setting.scaleHeight]; + RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]; + id scaledBuffer = + [buffer cropAndScaleWith:setting.offsetX + offsetY:setting.offsetY + cropWidth:setting.cropWidth + cropHeight:setting.cropHeight + scaleWidth:setting.scaleWidth + scaleHeight:setting.scaleHeight]; + XCTAssertTrue([scaledBuffer isKindOfClass:[RTC_OBJC_TYPE(RTCCVPixelBuffer) class]]); + + RTC_OBJC_TYPE(RTCI420Buffer) *fromCVPixelBuffer = [scaledBuffer toI420]; + + double psnr = + I420PSNR(*[scaledObjcI420Buffer nativeI420Buffer], *[fromCVPixelBuffer nativeI420Buffer]); + double target = webrtc::kPerfectPSNR; + if (pixelFormat != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) { + // libyuv's I420ToRGB functions seem to lose some quality. + target = 19.0; + } + XCTAssertGreaterThanOrEqual(psnr, target); + + CVBufferRelease(pixelBufferRef); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCCallbackLogger_xctest.m b/third_party/libwebrtc/sdk/objc/unittests/RTCCallbackLogger_xctest.m new file mode 100644 index 0000000000..1b6fb1c07b --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCCallbackLogger_xctest.m @@ -0,0 +1,244 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import "api/logging/RTCCallbackLogger.h" + +#import + +@interface RTCCallbackLoggerTests : XCTestCase + +@property(nonatomic, strong) RTC_OBJC_TYPE(RTCCallbackLogger) * logger; + +@end + +@implementation RTCCallbackLoggerTests + +@synthesize logger; + +- (void)setUp { + self.logger = [[RTC_OBJC_TYPE(RTCCallbackLogger) alloc] init]; +} + +- (void)tearDown { + self.logger = nil; +} + +- (void)testDefaultSeverityLevel { + XCTAssertEqual(self.logger.severity, RTCLoggingSeverityInfo); +} + +- (void)testCallbackGetsCalledForAppropriateLevel { + self.logger.severity = RTCLoggingSeverityWarning; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"callbackWarning"]; + + [self.logger start:^(NSString *message) { + XCTAssertTrue([message hasSuffix:@"Horrible error\n"]); + [callbackExpectation fulfill]; + }]; + + RTCLogError("Horrible error"); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; +} + +- (void)testCallbackWithSeverityGetsCalledForAppropriateLevel { + self.logger.severity = RTCLoggingSeverityWarning; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"callbackWarning"]; + + [self.logger + startWithMessageAndSeverityHandler:^(NSString *message, RTCLoggingSeverity severity) { + XCTAssertTrue([message hasSuffix:@"Horrible error\n"]); + XCTAssertEqual(severity, RTCLoggingSeverityError); + [callbackExpectation fulfill]; + }]; + + RTCLogError("Horrible error"); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; +} + +- (void)testCallbackDoesNotGetCalledForOtherLevels { + self.logger.severity = RTCLoggingSeverityError; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"callbackError"]; + + [self.logger start:^(NSString *message) { + XCTAssertTrue([message hasSuffix:@"Horrible error\n"]); + [callbackExpectation fulfill]; + }]; + + RTCLogInfo("Just some info"); + RTCLogWarning("Warning warning"); + RTCLogError("Horrible error"); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; +} + +- (void)testCallbackWithSeverityDoesNotGetCalledForOtherLevels { + self.logger.severity = RTCLoggingSeverityError; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"callbackError"]; + + [self.logger + startWithMessageAndSeverityHandler:^(NSString *message, RTCLoggingSeverity severity) { + XCTAssertTrue([message hasSuffix:@"Horrible error\n"]); + XCTAssertEqual(severity, RTCLoggingSeverityError); + [callbackExpectation fulfill]; + }]; + + RTCLogInfo("Just some info"); + RTCLogWarning("Warning warning"); + RTCLogError("Horrible error"); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; +} + +- (void)testCallbackDoesNotgetCalledForSeverityNone { + self.logger.severity = RTCLoggingSeverityNone; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"unexpectedCallback"]; + + [self.logger start:^(NSString *message) { + [callbackExpectation fulfill]; + XCTAssertTrue(false); + }]; + + RTCLogInfo("Just some info"); + RTCLogWarning("Warning warning"); + RTCLogError("Horrible error"); + + XCTWaiter *waiter = [[XCTWaiter alloc] init]; + XCTWaiterResult result = [waiter waitForExpectations:@[ callbackExpectation ] timeout:1.0]; + XCTAssertEqual(result, XCTWaiterResultTimedOut); +} + +- (void)testCallbackWithSeverityDoesNotgetCalledForSeverityNone { + self.logger.severity = RTCLoggingSeverityNone; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"unexpectedCallback"]; + + [self.logger + startWithMessageAndSeverityHandler:^(NSString *message, RTCLoggingSeverity severity) { + [callbackExpectation fulfill]; + XCTAssertTrue(false); + }]; + + RTCLogInfo("Just some info"); + RTCLogWarning("Warning warning"); + RTCLogError("Horrible error"); + + XCTWaiter *waiter = [[XCTWaiter alloc] init]; + XCTWaiterResult result = [waiter waitForExpectations:@[ callbackExpectation ] timeout:1.0]; + XCTAssertEqual(result, XCTWaiterResultTimedOut); +} + +- (void)testStartingWithNilCallbackDoesNotCrash { + [self.logger start:nil]; + + RTCLogError("Horrible error"); +} + +- (void)testStartingWithNilCallbackWithSeverityDoesNotCrash { + [self.logger startWithMessageAndSeverityHandler:nil]; + + RTCLogError("Horrible error"); +} + +- (void)testStopCallbackLogger { + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"stopped"]; + + [self.logger start:^(NSString *message) { + [callbackExpectation fulfill]; + }]; + + [self.logger stop]; + + RTCLogInfo("Just some info"); + + XCTWaiter *waiter = [[XCTWaiter alloc] init]; + XCTWaiterResult result = [waiter waitForExpectations:@[ callbackExpectation ] timeout:1.0]; + XCTAssertEqual(result, XCTWaiterResultTimedOut); +} + +- (void)testStopCallbackWithSeverityLogger { + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"stopped"]; + + [self.logger + startWithMessageAndSeverityHandler:^(NSString *message, RTCLoggingSeverity loggingServerity) { + [callbackExpectation fulfill]; + }]; + + [self.logger stop]; + + RTCLogInfo("Just some info"); + + XCTWaiter *waiter = [[XCTWaiter alloc] init]; + XCTWaiterResult result = [waiter waitForExpectations:@[ callbackExpectation ] timeout:1.0]; + XCTAssertEqual(result, XCTWaiterResultTimedOut); +} + +- (void)testDestroyingCallbackLogger { + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"destroyed"]; + + [self.logger start:^(NSString *message) { + [callbackExpectation fulfill]; + }]; + + self.logger = nil; + + RTCLogInfo("Just some info"); + + XCTWaiter *waiter = [[XCTWaiter alloc] init]; + XCTWaiterResult result = [waiter waitForExpectations:@[ callbackExpectation ] timeout:1.0]; + XCTAssertEqual(result, XCTWaiterResultTimedOut); +} + +- (void)testDestroyingCallbackWithSeverityLogger { + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"destroyed"]; + + [self.logger + startWithMessageAndSeverityHandler:^(NSString *message, RTCLoggingSeverity loggingServerity) { + [callbackExpectation fulfill]; + }]; + + self.logger = nil; + + RTCLogInfo("Just some info"); + + XCTWaiter *waiter = [[XCTWaiter alloc] init]; + XCTWaiterResult result = [waiter waitForExpectations:@[ callbackExpectation ] timeout:1.0]; + XCTAssertEqual(result, XCTWaiterResultTimedOut); +} + +- (void)testCallbackWithSeverityLoggerCannotStartTwice { + self.logger.severity = RTCLoggingSeverityWarning; + + XCTestExpectation *callbackExpectation = [self expectationWithDescription:@"callbackWarning"]; + + [self.logger + startWithMessageAndSeverityHandler:^(NSString *message, RTCLoggingSeverity loggingServerity) { + XCTAssertTrue([message hasSuffix:@"Horrible error\n"]); + XCTAssertEqual(loggingServerity, RTCLoggingSeverityError); + [callbackExpectation fulfill]; + }]; + + [self.logger start:^(NSString *message) { + [callbackExpectation fulfill]; + XCTAssertTrue(false); + }]; + + RTCLogError("Horrible error"); + + [self waitForExpectations:@[ callbackExpectation ] timeout:10.0]; +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCCameraVideoCapturerTests.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCCameraVideoCapturerTests.mm new file mode 100644 index 0000000000..6a117a3546 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCCameraVideoCapturerTests.mm @@ -0,0 +1,560 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#if TARGET_OS_IPHONE +#import +#endif + +#import "base/RTCVideoFrame.h" +#import "components/capturer/RTCCameraVideoCapturer.h" +#import "helpers/AVCaptureSession+DevicePosition.h" +#import "helpers/RTCDispatcher.h" +#import "helpers/scoped_cftyperef.h" + +#define WAIT(timeoutMs) \ + do { \ + id expectation = [[XCTestExpectation alloc] initWithDescription:@"Dummy"]; \ + XCTWaiterResult res = [XCTWaiter waitForExpectations:@[ expectation ] \ + timeout:timeoutMs / 1000.0]; \ + XCTAssertEqual(XCTWaiterResultTimedOut, res); \ + } while (false); + +#if TARGET_OS_IPHONE +// Helper method. +CMSampleBufferRef createTestSampleBufferRef() { + + // This image is already in the testing bundle. + UIImage *image = [UIImage imageNamed:@"Default.png"]; + CGSize size = image.size; + CGImageRef imageRef = [image CGImage]; + + CVPixelBufferRef pixelBuffer = nullptr; + CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32ARGB, nil, + &pixelBuffer); + + CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + // We don't care about bitsPerComponent and bytesPerRow so arbitrary value of 8 for both. + CGContextRef context = CGBitmapContextCreate(nil, size.width, size.height, 8, 8 * size.width, + rgbColorSpace, kCGImageAlphaPremultipliedFirst); + + CGContextDrawImage( + context, CGRectMake(0, 0, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)), imageRef); + + CGColorSpaceRelease(rgbColorSpace); + CGContextRelease(context); + + // We don't really care about the timing. + CMSampleTimingInfo timing = {kCMTimeInvalid, kCMTimeInvalid, kCMTimeInvalid}; + CMVideoFormatDescriptionRef description = nullptr; + CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &description); + + CMSampleBufferRef sampleBuffer = nullptr; + CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, YES, NULL, NULL, description, + &timing, &sampleBuffer); + CFRelease(pixelBuffer); + + return sampleBuffer; + +} +#endif +@interface RTC_OBJC_TYPE (RTCCameraVideoCapturer) +(Tests) - + (instancetype)initWithDelegate + : (__weak id)delegate captureSession + : (AVCaptureSession *)captureSession; +@end + +@interface RTCCameraVideoCapturerTests : XCTestCase +@property(nonatomic, strong) id delegateMock; +@property(nonatomic, strong) id deviceMock; +@property(nonatomic, strong) id captureConnectionMock; +@property(nonatomic, strong) RTC_OBJC_TYPE(RTCCameraVideoCapturer) * capturer; +@end + +@implementation RTCCameraVideoCapturerTests +@synthesize delegateMock = _delegateMock; +@synthesize deviceMock = _deviceMock; +@synthesize captureConnectionMock = _captureConnectionMock; +@synthesize capturer = _capturer; + +- (void)setUp { + self.delegateMock = OCMProtocolMock(@protocol(RTC_OBJC_TYPE(RTCVideoCapturerDelegate))); + self.captureConnectionMock = OCMClassMock([AVCaptureConnection class]); + self.capturer = + [[RTC_OBJC_TYPE(RTCCameraVideoCapturer) alloc] initWithDelegate:self.delegateMock]; + self.deviceMock = [RTCCameraVideoCapturerTests createDeviceMock]; +} + +- (void)tearDown { + [self.delegateMock stopMocking]; + [self.deviceMock stopMocking]; + self.delegateMock = nil; + self.deviceMock = nil; + self.capturer = nil; +} + +#pragma mark - utils + ++ (id)createDeviceMock { + return OCMClassMock([AVCaptureDevice class]); +} + +#pragma mark - test cases + +- (void)testSetupSession { + AVCaptureSession *session = self.capturer.captureSession; + XCTAssertTrue(session != nil); + +#if TARGET_OS_IPHONE + XCTAssertEqual(session.sessionPreset, AVCaptureSessionPresetInputPriority); + XCTAssertEqual(session.usesApplicationAudioSession, NO); +#endif + XCTAssertEqual(session.outputs.count, 1u); +} + +- (void)testSetupSessionOutput { + AVCaptureVideoDataOutput *videoOutput = self.capturer.captureSession.outputs[0]; + XCTAssertEqual(videoOutput.alwaysDiscardsLateVideoFrames, NO); + XCTAssertEqual(videoOutput.sampleBufferDelegate, self.capturer); +} + +- (void)testSupportedFormatsForDevice { + // given + id validFormat1 = OCMClassMock([AVCaptureDeviceFormat class]); + CMVideoFormatDescriptionRef format; + + // We don't care about width and heigth so arbitrary 123 and 456 values. + int width = 123; + int height = 456; + CMVideoFormatDescriptionCreate(nil, kCVPixelFormatType_420YpCbCr8PlanarFullRange, width, height, + nil, &format); + OCMStub([validFormat1 formatDescription]).andReturn(format); + + id validFormat2 = OCMClassMock([AVCaptureDeviceFormat class]); + CMVideoFormatDescriptionCreate(nil, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, width, + height, nil, &format); + OCMStub([validFormat2 formatDescription]).andReturn(format); + + id invalidFormat = OCMClassMock([AVCaptureDeviceFormat class]); + CMVideoFormatDescriptionCreate(nil, kCVPixelFormatType_422YpCbCr8_yuvs, width, height, nil, + &format); + OCMStub([invalidFormat formatDescription]).andReturn(format); + + NSArray *formats = @[ validFormat1, validFormat2, invalidFormat ]; + OCMStub([self.deviceMock formats]).andReturn(formats); + + // when + NSArray *supportedFormats = + [RTC_OBJC_TYPE(RTCCameraVideoCapturer) supportedFormatsForDevice:self.deviceMock]; + + // then + XCTAssertEqual(supportedFormats.count, 3u); + XCTAssertTrue([supportedFormats containsObject:validFormat1]); + XCTAssertTrue([supportedFormats containsObject:validFormat2]); + XCTAssertTrue([supportedFormats containsObject:invalidFormat]); + + // cleanup + [validFormat1 stopMocking]; + [validFormat2 stopMocking]; + [invalidFormat stopMocking]; + validFormat1 = nil; + validFormat2 = nil; + invalidFormat = nil; +} + +- (void)testDelegateCallbackNotCalledWhenInvalidBuffer { + // given + CMSampleBufferRef sampleBuffer = nullptr; + [[self.delegateMock reject] capturer:[OCMArg any] didCaptureVideoFrame:[OCMArg any]]; + + // when + [self.capturer captureOutput:self.capturer.captureSession.outputs[0] + didOutputSampleBuffer:sampleBuffer + fromConnection:self.captureConnectionMock]; + + // then + [self.delegateMock verify]; +} + +- (void)testDelegateCallbackWithValidBufferAndOrientationUpdate { +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Setting orientation on UIDevice is not supported"); + [UIDevice.currentDevice setValue:@(UIDeviceOrientationPortraitUpsideDown) forKey:@"orientation"]; + CMSampleBufferRef sampleBuffer = createTestSampleBufferRef(); + + // then + [[self.delegateMock expect] capturer:self.capturer + didCaptureVideoFrame:[OCMArg checkWithBlock:^BOOL(RTC_OBJC_TYPE(RTCVideoFrame) * + expectedFrame) { + XCTAssertEqual(expectedFrame.rotation, RTCVideoRotation_270); + return YES; + }]]; + + // when + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center postNotificationName:UIDeviceOrientationDidChangeNotification object:nil]; + + // We need to wait for the dispatch to finish. + WAIT(1000); + + [self.capturer captureOutput:self.capturer.captureSession.outputs[0] + didOutputSampleBuffer:sampleBuffer + fromConnection:self.captureConnectionMock]; + + [self.delegateMock verify]; + CFRelease(sampleBuffer); +#endif +} + +// The XCTest framework considers functions that don't take arguments tests. This is a helper. +- (void)testRotationCamera:(AVCaptureDevicePosition)camera + withOrientation:(UIDeviceOrientation)deviceOrientation { +#if TARGET_OS_IPHONE + // Mock the AVCaptureConnection as we will get the camera position from the connection's + // input ports. + AVCaptureDeviceInput *inputPortMock = OCMClassMock([AVCaptureDeviceInput class]); + AVCaptureInputPort *captureInputPort = OCMClassMock([AVCaptureInputPort class]); + NSArray *inputPortsArrayMock = @[captureInputPort]; + AVCaptureDevice *captureDeviceMock = OCMClassMock([AVCaptureDevice class]); + OCMStub(((AVCaptureConnection *)self.captureConnectionMock).inputPorts). + andReturn(inputPortsArrayMock); + OCMStub(captureInputPort.input).andReturn(inputPortMock); + OCMStub(inputPortMock.device).andReturn(captureDeviceMock); + OCMStub(captureDeviceMock.position).andReturn(camera); + + XCTExpectFailure(@"Setting orientation on UIDevice is not supported"); + [UIDevice.currentDevice setValue:@(deviceOrientation) forKey:@"orientation"]; + + CMSampleBufferRef sampleBuffer = createTestSampleBufferRef(); + + [[self.delegateMock expect] capturer:self.capturer + didCaptureVideoFrame:[OCMArg checkWithBlock:^BOOL(RTC_OBJC_TYPE(RTCVideoFrame) * + expectedFrame) { + if (camera == AVCaptureDevicePositionFront) { + if (deviceOrientation == UIDeviceOrientationLandscapeLeft) { + XCTAssertEqual(expectedFrame.rotation, RTCVideoRotation_180); + } else if (deviceOrientation == UIDeviceOrientationLandscapeRight) { + XCTAssertEqual(expectedFrame.rotation, RTCVideoRotation_0); + } + } else if (camera == AVCaptureDevicePositionBack) { + if (deviceOrientation == UIDeviceOrientationLandscapeLeft) { + XCTAssertEqual(expectedFrame.rotation, RTCVideoRotation_0); + } else if (deviceOrientation == UIDeviceOrientationLandscapeRight) { + XCTAssertEqual(expectedFrame.rotation, RTCVideoRotation_180); + } + } + return YES; + }]]; + + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center postNotificationName:UIDeviceOrientationDidChangeNotification object:nil]; + + // We need to wait for the dispatch to finish. + WAIT(1000); + + [self.capturer captureOutput:self.capturer.captureSession.outputs[0] + didOutputSampleBuffer:sampleBuffer + fromConnection:self.captureConnectionMock]; + + [self.delegateMock verify]; + + CFRelease(sampleBuffer); +#endif +} + +- (void)testRotationCameraBackLandscapeLeft { + [self testRotationCamera:AVCaptureDevicePositionBack + withOrientation:UIDeviceOrientationLandscapeLeft]; +} + +- (void)testRotationCameraFrontLandscapeLeft { + [self testRotationCamera:AVCaptureDevicePositionFront + withOrientation:UIDeviceOrientationLandscapeLeft]; +} + +- (void)testRotationCameraBackLandscapeRight { + [self testRotationCamera:AVCaptureDevicePositionBack + withOrientation:UIDeviceOrientationLandscapeRight]; +} + +- (void)testRotationCameraFrontLandscapeRight { + [self testRotationCamera:AVCaptureDevicePositionFront + withOrientation:UIDeviceOrientationLandscapeRight]; +} + +- (void)setExif:(CMSampleBufferRef)sampleBuffer { + rtc::ScopedCFTypeRef exif(CFDictionaryCreateMutable( + kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + CFDictionarySetValue(exif.get(), CFSTR("LensModel"), CFSTR("iPhone SE back camera 4.15mm f/2.2")); + CMSetAttachment(sampleBuffer, CFSTR("{Exif}"), exif.get(), kCMAttachmentMode_ShouldPropagate); +} + +- (void)testRotationFrame { +#if TARGET_OS_IPHONE + // Mock the AVCaptureConnection as we will get the camera position from the connection's + // input ports. + AVCaptureDeviceInput *inputPortMock = OCMClassMock([AVCaptureDeviceInput class]); + AVCaptureInputPort *captureInputPort = OCMClassMock([AVCaptureInputPort class]); + NSArray *inputPortsArrayMock = @[captureInputPort]; + AVCaptureDevice *captureDeviceMock = OCMClassMock([AVCaptureDevice class]); + OCMStub(((AVCaptureConnection *)self.captureConnectionMock).inputPorts). + andReturn(inputPortsArrayMock); + OCMStub(captureInputPort.input).andReturn(inputPortMock); + OCMStub(inputPortMock.device).andReturn(captureDeviceMock); + OCMStub(captureDeviceMock.position).andReturn(AVCaptureDevicePositionFront); + + XCTExpectFailure(@"Setting orientation on UIDevice is not supported"); + [UIDevice.currentDevice setValue:@(UIDeviceOrientationLandscapeLeft) forKey:@"orientation"]; + + CMSampleBufferRef sampleBuffer = createTestSampleBufferRef(); + + [[self.delegateMock expect] capturer:self.capturer + didCaptureVideoFrame:[OCMArg checkWithBlock:^BOOL(RTC_OBJC_TYPE(RTCVideoFrame) * + expectedFrame) { + // Front camera and landscape left should return 180. But the frame's exif + // we add below says its from the back camera, so rotation should be 0. + XCTAssertEqual(expectedFrame.rotation, RTCVideoRotation_0); + return YES; + }]]; + + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center postNotificationName:UIDeviceOrientationDidChangeNotification object:nil]; + + // We need to wait for the dispatch to finish. + WAIT(1000); + + [self setExif:sampleBuffer]; + + [self.capturer captureOutput:self.capturer.captureSession.outputs[0] + didOutputSampleBuffer:sampleBuffer + fromConnection:self.captureConnectionMock]; + + [self.delegateMock verify]; + CFRelease(sampleBuffer); +#endif +} + +- (void)testImageExif { +#if TARGET_OS_IPHONE + CMSampleBufferRef sampleBuffer = createTestSampleBufferRef(); + [self setExif:sampleBuffer]; + + AVCaptureDevicePosition cameraPosition = [AVCaptureSession + devicePositionForSampleBuffer:sampleBuffer]; + XCTAssertEqual(cameraPosition, AVCaptureDevicePositionBack); +#endif +} + +@end + +@interface RTCCameraVideoCapturerTestsWithMockedCaptureSession : XCTestCase +@property(nonatomic, strong) id delegateMock; +@property(nonatomic, strong) id deviceMock; +@property(nonatomic, strong) id captureSessionMock; +@property(nonatomic, strong) RTC_OBJC_TYPE(RTCCameraVideoCapturer) * capturer; +@end + +@implementation RTCCameraVideoCapturerTestsWithMockedCaptureSession +@synthesize delegateMock = _delegateMock; +@synthesize deviceMock = _deviceMock; +@synthesize captureSessionMock = _captureSessionMock; +@synthesize capturer = _capturer; + +- (void)setUp { + self.captureSessionMock = OCMStrictClassMock([AVCaptureSession class]); + OCMStub([self.captureSessionMock setSessionPreset:[OCMArg any]]); + OCMStub([self.captureSessionMock setUsesApplicationAudioSession:NO]); + OCMStub([self.captureSessionMock canAddOutput:[OCMArg any]]).andReturn(YES); + OCMStub([self.captureSessionMock addOutput:[OCMArg any]]); + OCMStub([self.captureSessionMock beginConfiguration]); + OCMStub([self.captureSessionMock commitConfiguration]); + self.delegateMock = OCMProtocolMock(@protocol(RTC_OBJC_TYPE(RTCVideoCapturerDelegate))); + self.capturer = + [[RTC_OBJC_TYPE(RTCCameraVideoCapturer) alloc] initWithDelegate:self.delegateMock + captureSession:self.captureSessionMock]; + self.deviceMock = [RTCCameraVideoCapturerTests createDeviceMock]; +} + +- (void)tearDown { + [self.delegateMock stopMocking]; + [self.deviceMock stopMocking]; + self.delegateMock = nil; + self.deviceMock = nil; + self.capturer = nil; + self.captureSessionMock = nil; +} + +#pragma mark - test cases + +- (void)testStartingAndStoppingCapture { + id expectedDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + id captureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([captureDeviceInputMock deviceInputWithDevice:self.deviceMock error:[OCMArg setTo:nil]]) + .andReturn(expectedDeviceInputMock); + + OCMStub([self.deviceMock lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES); + OCMStub([self.deviceMock unlockForConfiguration]); + OCMStub([_captureSessionMock canAddInput:expectedDeviceInputMock]).andReturn(YES); + OCMStub([_captureSessionMock inputs]).andReturn(@[ expectedDeviceInputMock ]); + OCMStub([_captureSessionMock removeInput:expectedDeviceInputMock]); + + // Set expectation that the capture session should be started with correct device. + OCMExpect([_captureSessionMock addInput:expectedDeviceInputMock]); + OCMExpect([_captureSessionMock startRunning]); + OCMExpect([_captureSessionMock stopRunning]); + + id format = OCMClassMock([AVCaptureDeviceFormat class]); + [self.capturer startCaptureWithDevice:self.deviceMock format:format fps:30]; + [self.capturer stopCapture]; + + // Start capture code is dispatched async. + OCMVerifyAllWithDelay(_captureSessionMock, 15); +} + +- (void)testStartCaptureFailingToLockForConfiguration { + // The captureSessionMock is a strict mock, so this test will crash if the startCapture + // method does not return when failing to lock for configuration. + OCMExpect([self.deviceMock lockForConfiguration:[OCMArg setTo:nil]]).andReturn(NO); + + id format = OCMClassMock([AVCaptureDeviceFormat class]); + [self.capturer startCaptureWithDevice:self.deviceMock format:format fps:30]; + + // Start capture code is dispatched async. + OCMVerifyAllWithDelay(self.deviceMock, 15); +} + +- (void)testStartingAndStoppingCaptureWithCallbacks { + id expectedDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + id captureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([captureDeviceInputMock deviceInputWithDevice:self.deviceMock error:[OCMArg setTo:nil]]) + .andReturn(expectedDeviceInputMock); + + OCMStub([self.deviceMock lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES); + OCMStub([self.deviceMock unlockForConfiguration]); + OCMStub([_captureSessionMock canAddInput:expectedDeviceInputMock]).andReturn(YES); + OCMStub([_captureSessionMock inputs]).andReturn(@[ expectedDeviceInputMock ]); + OCMStub([_captureSessionMock removeInput:expectedDeviceInputMock]); + + // Set expectation that the capture session should be started with correct device. + OCMExpect([_captureSessionMock addInput:expectedDeviceInputMock]); + OCMExpect([_captureSessionMock startRunning]); + OCMExpect([_captureSessionMock stopRunning]); + + dispatch_semaphore_t completedStopSemaphore = dispatch_semaphore_create(0); + + __block BOOL completedStart = NO; + id format = OCMClassMock([AVCaptureDeviceFormat class]); + [self.capturer startCaptureWithDevice:self.deviceMock + format:format + fps:30 + completionHandler:^(NSError *error) { + XCTAssertEqual(error, nil); + completedStart = YES; + }]; + + __block BOOL completedStop = NO; + [self.capturer stopCaptureWithCompletionHandler:^{ + completedStop = YES; + dispatch_semaphore_signal(completedStopSemaphore); + }]; + + dispatch_semaphore_wait(completedStopSemaphore, + dispatch_time(DISPATCH_TIME_NOW, 15.0 * NSEC_PER_SEC)); + OCMVerifyAllWithDelay(_captureSessionMock, 15); + XCTAssertTrue(completedStart); + XCTAssertTrue(completedStop); +} + +- (void)testStartCaptureFailingToLockForConfigurationWithCallback { + id expectedDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + id captureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([captureDeviceInputMock deviceInputWithDevice:self.deviceMock error:[OCMArg setTo:nil]]) + .andReturn(expectedDeviceInputMock); + + id errorMock = OCMClassMock([NSError class]); + + OCMStub([self.deviceMock lockForConfiguration:[OCMArg setTo:errorMock]]).andReturn(NO); + OCMStub([_captureSessionMock canAddInput:expectedDeviceInputMock]).andReturn(YES); + OCMStub([self.deviceMock unlockForConfiguration]); + + OCMExpect([_captureSessionMock addInput:expectedDeviceInputMock]); + + dispatch_semaphore_t completedStartSemaphore = dispatch_semaphore_create(0); + __block NSError *callbackError = nil; + + id format = OCMClassMock([AVCaptureDeviceFormat class]); + [self.capturer startCaptureWithDevice:self.deviceMock + format:format + fps:30 + completionHandler:^(NSError *error) { + callbackError = error; + dispatch_semaphore_signal(completedStartSemaphore); + }]; + + long ret = dispatch_semaphore_wait(completedStartSemaphore, + dispatch_time(DISPATCH_TIME_NOW, 15.0 * NSEC_PER_SEC)); + XCTAssertEqual(ret, 0); + XCTAssertEqual(callbackError, errorMock); +} + +// TODO(crbug.com/webrtc/14829): Test is disabled on iOS < 16 and broken on iOS 16. +- (void)DISABLED_testStartCaptureSetsOutputDimensionsInvalidPixelFormat { + id expectedDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + id captureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([captureDeviceInputMock deviceInputWithDevice:_deviceMock error:[OCMArg setTo:nil]]) + .andReturn(expectedDeviceInputMock); + + OCMStub([_deviceMock lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES); + OCMStub([_deviceMock unlockForConfiguration]); + OCMStub([_captureSessionMock canAddInput:expectedDeviceInputMock]).andReturn(YES); + OCMStub([_captureSessionMock addInput:expectedDeviceInputMock]); + OCMStub([_captureSessionMock inputs]).andReturn(@[ expectedDeviceInputMock ]); + OCMStub([_captureSessionMock removeInput:expectedDeviceInputMock]); + OCMStub([_captureSessionMock startRunning]); + OCMStub([_captureSessionMock stopRunning]); + + id deviceFormatMock = OCMClassMock([AVCaptureDeviceFormat class]); + CMVideoFormatDescriptionRef formatDescription; + + int width = 110; + int height = 220; + FourCharCode pixelFormat = 0x18000000; + CMVideoFormatDescriptionCreate(nil, pixelFormat, width, height, nil, &formatDescription); + OCMStub([deviceFormatMock formatDescription]).andReturn(formatDescription); + + [_capturer startCaptureWithDevice:_deviceMock format:deviceFormatMock fps:30]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"StopCompletion"]; + [_capturer stopCaptureWithCompletionHandler:^(void) { + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:15 handler:nil]; + + OCMVerify([_captureSessionMock + addOutput:[OCMArg checkWithBlock:^BOOL(AVCaptureVideoDataOutput *output) { + if (@available(iOS 16, *)) { + XCTAssertEqual(width, [output.videoSettings[(id)kCVPixelBufferWidthKey] intValue]); + XCTAssertEqual(height, [output.videoSettings[(id)kCVPixelBufferHeightKey] intValue]); + } else { + XCTAssertEqual(0, [output.videoSettings[(id)kCVPixelBufferWidthKey] intValue]); + XCTAssertEqual(0, [output.videoSettings[(id)kCVPixelBufferHeightKey] intValue]); + } + XCTAssertEqual( + (FourCharCode)kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, + [output.videoSettings[(id)kCVPixelBufferPixelFormatTypeKey] unsignedIntValue]); + return YES; + }]]); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCCertificateTest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCCertificateTest.mm new file mode 100644 index 0000000000..bc1347336c --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCCertificateTest.mm @@ -0,0 +1,73 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#include + +#include "rtc_base/gunit.h" + +#import "api/peerconnection/RTCConfiguration+Private.h" +#import "api/peerconnection/RTCConfiguration.h" +#import "api/peerconnection/RTCIceServer.h" +#import "api/peerconnection/RTCMediaConstraints.h" +#import "api/peerconnection/RTCPeerConnection.h" +#import "api/peerconnection/RTCPeerConnectionFactory.h" +#import "helpers/NSString+StdString.h" + +@interface RTCCertificateTest : XCTestCase +@end + +@implementation RTCCertificateTest + +- (void)testCertificateIsUsedInConfig { + RTC_OBJC_TYPE(RTCConfiguration) *originalConfig = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + + NSArray *urlStrings = @[ @"stun:stun1.example.net" ]; + RTC_OBJC_TYPE(RTCIceServer) *server = + [[RTC_OBJC_TYPE(RTCIceServer) alloc] initWithURLStrings:urlStrings]; + originalConfig.iceServers = @[ server ]; + + // Generate a new certificate. + RTC_OBJC_TYPE(RTCCertificate) *originalCertificate = [RTC_OBJC_TYPE(RTCCertificate) + generateCertificateWithParams:@{@"expires" : @100000, @"name" : @"RSASSA-PKCS1-v1_5"}]; + + // Store certificate in configuration. + originalConfig.certificate = originalCertificate; + + RTC_OBJC_TYPE(RTCMediaConstraints) *contraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} + optionalConstraints:nil]; + RTC_OBJC_TYPE(RTCPeerConnectionFactory) *factory = + [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + + // Create PeerConnection with this certificate. + RTC_OBJC_TYPE(RTCPeerConnection) *peerConnection = + [factory peerConnectionWithConfiguration:originalConfig constraints:contraints delegate:nil]; + + // Retrieve certificate from the configuration. + RTC_OBJC_TYPE(RTCConfiguration) *retrievedConfig = peerConnection.configuration; + + // Extract PEM strings from original certificate. + std::string originalPrivateKeyField = [[originalCertificate private_key] UTF8String]; + std::string originalCertificateField = [[originalCertificate certificate] UTF8String]; + + // Extract PEM strings from certificate retrieved from configuration. + RTC_OBJC_TYPE(RTCCertificate) *retrievedCertificate = retrievedConfig.certificate; + std::string retrievedPrivateKeyField = [[retrievedCertificate private_key] UTF8String]; + std::string retrievedCertificateField = [[retrievedCertificate certificate] UTF8String]; + + // Check that the original certificate and retrieved certificate match. + EXPECT_EQ(originalPrivateKeyField, retrievedPrivateKeyField); + EXPECT_EQ(retrievedCertificateField, retrievedCertificateField); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCConfigurationTest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCConfigurationTest.mm new file mode 100644 index 0000000000..18cc97191e --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCConfigurationTest.mm @@ -0,0 +1,162 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#include + +#include "rtc_base/gunit.h" + +#import "api/peerconnection/RTCConfiguration+Private.h" +#import "api/peerconnection/RTCConfiguration.h" +#import "api/peerconnection/RTCIceServer.h" +#import "helpers/NSString+StdString.h" + +@interface RTCConfigurationTest : XCTestCase +@end + +@implementation RTCConfigurationTest + +- (void)testConversionToNativeConfiguration { + NSArray *urlStrings = @[ @"stun:stun1.example.net" ]; + RTC_OBJC_TYPE(RTCIceServer) *server = + [[RTC_OBJC_TYPE(RTCIceServer) alloc] initWithURLStrings:urlStrings]; + + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + config.iceServers = @[ server ]; + config.iceTransportPolicy = RTCIceTransportPolicyRelay; + config.bundlePolicy = RTCBundlePolicyMaxBundle; + config.rtcpMuxPolicy = RTCRtcpMuxPolicyNegotiate; + config.tcpCandidatePolicy = RTCTcpCandidatePolicyDisabled; + config.candidateNetworkPolicy = RTCCandidateNetworkPolicyLowCost; + const int maxPackets = 60; + const int timeout = 1; + const int interval = 2; + config.audioJitterBufferMaxPackets = maxPackets; + config.audioJitterBufferFastAccelerate = YES; + config.iceConnectionReceivingTimeout = timeout; + config.iceBackupCandidatePairPingInterval = interval; + config.continualGatheringPolicy = + RTCContinualGatheringPolicyGatherContinually; + config.shouldPruneTurnPorts = YES; + config.cryptoOptions = + [[RTC_OBJC_TYPE(RTCCryptoOptions) alloc] initWithSrtpEnableGcmCryptoSuites:YES + srtpEnableAes128Sha1_32CryptoCipher:YES + srtpEnableEncryptedRtpHeaderExtensions:YES + sframeRequireFrameEncryption:YES]; + config.rtcpAudioReportIntervalMs = 2500; + config.rtcpVideoReportIntervalMs = 3750; + + std::unique_ptr + nativeConfig([config createNativeConfiguration]); + EXPECT_TRUE(nativeConfig.get()); + EXPECT_EQ(1u, nativeConfig->servers.size()); + webrtc::PeerConnectionInterface::IceServer nativeServer = + nativeConfig->servers.front(); + EXPECT_EQ(1u, nativeServer.urls.size()); + EXPECT_EQ("stun:stun1.example.net", nativeServer.urls.front()); + + EXPECT_EQ(webrtc::PeerConnectionInterface::kRelay, nativeConfig->type); + EXPECT_EQ(webrtc::PeerConnectionInterface::kBundlePolicyMaxBundle, + nativeConfig->bundle_policy); + EXPECT_EQ(webrtc::PeerConnectionInterface::kRtcpMuxPolicyNegotiate, + nativeConfig->rtcp_mux_policy); + EXPECT_EQ(webrtc::PeerConnectionInterface::kTcpCandidatePolicyDisabled, + nativeConfig->tcp_candidate_policy); + EXPECT_EQ(webrtc::PeerConnectionInterface::kCandidateNetworkPolicyLowCost, + nativeConfig->candidate_network_policy); + EXPECT_EQ(maxPackets, nativeConfig->audio_jitter_buffer_max_packets); + EXPECT_EQ(true, nativeConfig->audio_jitter_buffer_fast_accelerate); + EXPECT_EQ(timeout, nativeConfig->ice_connection_receiving_timeout); + EXPECT_EQ(interval, nativeConfig->ice_backup_candidate_pair_ping_interval); + EXPECT_EQ(webrtc::PeerConnectionInterface::GATHER_CONTINUALLY, + nativeConfig->continual_gathering_policy); + EXPECT_EQ(true, nativeConfig->prune_turn_ports); + EXPECT_EQ(true, nativeConfig->crypto_options->srtp.enable_gcm_crypto_suites); + EXPECT_EQ(true, nativeConfig->crypto_options->srtp.enable_aes128_sha1_32_crypto_cipher); + EXPECT_EQ(true, nativeConfig->crypto_options->srtp.enable_encrypted_rtp_header_extensions); + EXPECT_EQ(true, nativeConfig->crypto_options->sframe.require_frame_encryption); + EXPECT_EQ(2500, nativeConfig->audio_rtcp_report_interval_ms()); + EXPECT_EQ(3750, nativeConfig->video_rtcp_report_interval_ms()); +} + +- (void)testNativeConversionToConfiguration { + NSArray *urlStrings = @[ @"stun:stun1.example.net" ]; + RTC_OBJC_TYPE(RTCIceServer) *server = + [[RTC_OBJC_TYPE(RTCIceServer) alloc] initWithURLStrings:urlStrings]; + + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + config.iceServers = @[ server ]; + config.iceTransportPolicy = RTCIceTransportPolicyRelay; + config.bundlePolicy = RTCBundlePolicyMaxBundle; + config.rtcpMuxPolicy = RTCRtcpMuxPolicyNegotiate; + config.tcpCandidatePolicy = RTCTcpCandidatePolicyDisabled; + config.candidateNetworkPolicy = RTCCandidateNetworkPolicyLowCost; + const int maxPackets = 60; + const int timeout = 1; + const int interval = 2; + config.audioJitterBufferMaxPackets = maxPackets; + config.audioJitterBufferFastAccelerate = YES; + config.iceConnectionReceivingTimeout = timeout; + config.iceBackupCandidatePairPingInterval = interval; + config.continualGatheringPolicy = + RTCContinualGatheringPolicyGatherContinually; + config.shouldPruneTurnPorts = YES; + config.cryptoOptions = + [[RTC_OBJC_TYPE(RTCCryptoOptions) alloc] initWithSrtpEnableGcmCryptoSuites:YES + srtpEnableAes128Sha1_32CryptoCipher:NO + srtpEnableEncryptedRtpHeaderExtensions:NO + sframeRequireFrameEncryption:NO]; + config.rtcpAudioReportIntervalMs = 1500; + config.rtcpVideoReportIntervalMs = 2150; + + webrtc::PeerConnectionInterface::RTCConfiguration *nativeConfig = + [config createNativeConfiguration]; + RTC_OBJC_TYPE(RTCConfiguration) *newConfig = + [[RTC_OBJC_TYPE(RTCConfiguration) alloc] initWithNativeConfiguration:*nativeConfig]; + EXPECT_EQ([config.iceServers count], newConfig.iceServers.count); + RTC_OBJC_TYPE(RTCIceServer) *newServer = newConfig.iceServers[0]; + RTC_OBJC_TYPE(RTCIceServer) *origServer = config.iceServers[0]; + EXPECT_EQ(origServer.urlStrings.count, server.urlStrings.count); + std::string origUrl = origServer.urlStrings.firstObject.UTF8String; + std::string url = newServer.urlStrings.firstObject.UTF8String; + EXPECT_EQ(origUrl, url); + + EXPECT_EQ(config.iceTransportPolicy, newConfig.iceTransportPolicy); + EXPECT_EQ(config.bundlePolicy, newConfig.bundlePolicy); + EXPECT_EQ(config.rtcpMuxPolicy, newConfig.rtcpMuxPolicy); + EXPECT_EQ(config.tcpCandidatePolicy, newConfig.tcpCandidatePolicy); + EXPECT_EQ(config.candidateNetworkPolicy, newConfig.candidateNetworkPolicy); + EXPECT_EQ(config.audioJitterBufferMaxPackets, newConfig.audioJitterBufferMaxPackets); + EXPECT_EQ(config.audioJitterBufferFastAccelerate, newConfig.audioJitterBufferFastAccelerate); + EXPECT_EQ(config.iceConnectionReceivingTimeout, newConfig.iceConnectionReceivingTimeout); + EXPECT_EQ(config.iceBackupCandidatePairPingInterval, + newConfig.iceBackupCandidatePairPingInterval); + EXPECT_EQ(config.continualGatheringPolicy, newConfig.continualGatheringPolicy); + EXPECT_EQ(config.shouldPruneTurnPorts, newConfig.shouldPruneTurnPorts); + EXPECT_EQ(config.cryptoOptions.srtpEnableGcmCryptoSuites, + newConfig.cryptoOptions.srtpEnableGcmCryptoSuites); + EXPECT_EQ(config.cryptoOptions.srtpEnableAes128Sha1_32CryptoCipher, + newConfig.cryptoOptions.srtpEnableAes128Sha1_32CryptoCipher); + EXPECT_EQ(config.cryptoOptions.srtpEnableEncryptedRtpHeaderExtensions, + newConfig.cryptoOptions.srtpEnableEncryptedRtpHeaderExtensions); + EXPECT_EQ(config.cryptoOptions.sframeRequireFrameEncryption, + newConfig.cryptoOptions.sframeRequireFrameEncryption); + EXPECT_EQ(config.rtcpAudioReportIntervalMs, newConfig.rtcpAudioReportIntervalMs); + EXPECT_EQ(config.rtcpVideoReportIntervalMs, newConfig.rtcpVideoReportIntervalMs); +} + +- (void)testDefaultValues { + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + EXPECT_EQ(config.cryptoOptions, nil); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCDataChannelConfigurationTest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCDataChannelConfigurationTest.mm new file mode 100644 index 0000000000..ccebd74198 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCDataChannelConfigurationTest.mm @@ -0,0 +1,51 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#include "rtc_base/gunit.h" + +#import "api/peerconnection/RTCDataChannelConfiguration+Private.h" +#import "api/peerconnection/RTCDataChannelConfiguration.h" +#import "helpers/NSString+StdString.h" + +@interface RTCDataChannelConfigurationTest : XCTestCase +@end + +@implementation RTCDataChannelConfigurationTest + +- (void)testConversionToNativeDataChannelInit { + BOOL isOrdered = NO; + int maxPacketLifeTime = 5; + int maxRetransmits = 4; + BOOL isNegotiated = YES; + int channelId = 4; + NSString *protocol = @"protocol"; + + RTC_OBJC_TYPE(RTCDataChannelConfiguration) *dataChannelConfig = + [[RTC_OBJC_TYPE(RTCDataChannelConfiguration) alloc] init]; + dataChannelConfig.isOrdered = isOrdered; + dataChannelConfig.maxPacketLifeTime = maxPacketLifeTime; + dataChannelConfig.maxRetransmits = maxRetransmits; + dataChannelConfig.isNegotiated = isNegotiated; + dataChannelConfig.channelId = channelId; + dataChannelConfig.protocol = protocol; + + webrtc::DataChannelInit nativeInit = dataChannelConfig.nativeDataChannelInit; + EXPECT_EQ(isOrdered, nativeInit.ordered); + EXPECT_EQ(maxPacketLifeTime, nativeInit.maxRetransmitTime); + EXPECT_EQ(maxRetransmits, nativeInit.maxRetransmits); + EXPECT_EQ(isNegotiated, nativeInit.negotiated); + EXPECT_EQ(channelId, nativeInit.id); + EXPECT_EQ(protocol.stdString, nativeInit.protocol); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCDoNotPutCPlusPlusInFrameworkHeaders_xctest.m b/third_party/libwebrtc/sdk/objc/unittests/RTCDoNotPutCPlusPlusInFrameworkHeaders_xctest.m new file mode 100644 index 0000000000..02bef9bfb7 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCDoNotPutCPlusPlusInFrameworkHeaders_xctest.m @@ -0,0 +1,30 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +#import + +@interface RTCDoNotPutCPlusPlusInFrameworkHeaders : XCTestCase +@end + +@implementation RTCDoNotPutCPlusPlusInFrameworkHeaders + +- (void)testNoCPlusPlusInFrameworkHeaders { + NSString *fullPath = [NSString stringWithFormat:@"%s", __FILE__]; + NSString *extension = fullPath.pathExtension; + + XCTAssertEqualObjects( + @"m", extension, @"Do not rename %@. It should end with .m.", fullPath.lastPathComponent); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCEncodedImage_xctest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCEncodedImage_xctest.mm new file mode 100644 index 0000000000..84804fee87 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCEncodedImage_xctest.mm @@ -0,0 +1,55 @@ +/* + * Copyright 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import "api/peerconnection/RTCEncodedImage+Private.h" + +#import + +@interface RTCEncodedImageTests : XCTestCase +@end + +@implementation RTCEncodedImageTests + +- (void)testInitializedWithNativeEncodedImage { + const auto encoded_data = webrtc::EncodedImageBuffer::Create(); + webrtc::EncodedImage encoded_image; + encoded_image.SetEncodedData(encoded_data); + + RTC_OBJC_TYPE(RTCEncodedImage) *encodedImage = + [[RTC_OBJC_TYPE(RTCEncodedImage) alloc] initWithNativeEncodedImage:encoded_image]; + + XCTAssertEqual([encodedImage nativeEncodedImage].GetEncodedData(), encoded_data); +} + +- (void)testInitWithNSData { + NSData *bufferData = [NSData data]; + RTC_OBJC_TYPE(RTCEncodedImage) *encodedImage = [[RTC_OBJC_TYPE(RTCEncodedImage) alloc] init]; + encodedImage.buffer = bufferData; + + webrtc::EncodedImage result_encoded_image = [encodedImage nativeEncodedImage]; + XCTAssertTrue(result_encoded_image.GetEncodedData() != nullptr); + XCTAssertEqual(result_encoded_image.GetEncodedData()->data(), bufferData.bytes); +} + +- (void)testRetainsNativeEncodedImage { + RTC_OBJC_TYPE(RTCEncodedImage) * encodedImage; + { + const auto encoded_data = webrtc::EncodedImageBuffer::Create(); + webrtc::EncodedImage encoded_image; + encoded_image.SetEncodedData(encoded_data); + encodedImage = + [[RTC_OBJC_TYPE(RTCEncodedImage) alloc] initWithNativeEncodedImage:encoded_image]; + } + webrtc::EncodedImage result_encoded_image = [encodedImage nativeEncodedImage]; + XCTAssertTrue(result_encoded_image.GetEncodedData() != nullptr); + XCTAssertTrue(result_encoded_image.GetEncodedData()->data() != nullptr); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCFileVideoCapturer_xctest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCFileVideoCapturer_xctest.mm new file mode 100644 index 0000000000..2407c88c1a --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCFileVideoCapturer_xctest.mm @@ -0,0 +1,114 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import "components/capturer/RTCFileVideoCapturer.h" + +#import + +#include "rtc_base/gunit.h" + +NSString *const kTestFileName = @"foreman.mp4"; +static const int kTestTimeoutMs = 5 * 1000; // 5secs. + +@interface MockCapturerDelegate : NSObject + +@property(nonatomic, assign) NSInteger capturedFramesCount; + +@end + +@implementation MockCapturerDelegate +@synthesize capturedFramesCount = _capturedFramesCount; + +- (void)capturer:(RTC_OBJC_TYPE(RTCVideoCapturer) *)capturer + didCaptureVideoFrame:(RTC_OBJC_TYPE(RTCVideoFrame) *)frame { + self.capturedFramesCount++; +} + +@end + +NS_CLASS_AVAILABLE_IOS(10) +@interface RTCFileVideoCapturerTests : XCTestCase + +@property(nonatomic, strong) RTC_OBJC_TYPE(RTCFileVideoCapturer) * capturer; +@property(nonatomic, strong) MockCapturerDelegate *mockDelegate; + +@end + +@implementation RTCFileVideoCapturerTests +@synthesize capturer = _capturer; +@synthesize mockDelegate = _mockDelegate; + +- (void)setUp { + self.mockDelegate = [[MockCapturerDelegate alloc] init]; + self.capturer = [[RTC_OBJC_TYPE(RTCFileVideoCapturer) alloc] initWithDelegate:self.mockDelegate]; +} + +- (void)tearDown { + self.capturer = nil; + self.mockDelegate = nil; +} + +- (void)testCaptureWhenFileNotInBundle { + __block BOOL errorOccured = NO; + + RTCFileVideoCapturerErrorBlock errorBlock = ^void(NSError *error) { + errorOccured = YES; + }; + + [self.capturer startCapturingFromFileNamed:@"not_in_bundle.mov" onError:errorBlock]; + ASSERT_TRUE_WAIT(errorOccured, kTestTimeoutMs); +} + +- (void)testSecondStartCaptureCallFails { + __block BOOL secondError = NO; + + RTCFileVideoCapturerErrorBlock firstErrorBlock = ^void(NSError *error) { + // This block should never be called. + NSLog(@"Error: %@", [error userInfo]); + ASSERT_TRUE(false); + }; + + RTCFileVideoCapturerErrorBlock secondErrorBlock = ^void(NSError *error) { + secondError = YES; + }; + + [self.capturer startCapturingFromFileNamed:kTestFileName onError:firstErrorBlock]; + [self.capturer startCapturingFromFileNamed:kTestFileName onError:secondErrorBlock]; + + ASSERT_TRUE_WAIT(secondError, kTestTimeoutMs); +} + +- (void)testStartStopCapturer { +#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) + if (@available(iOS 10, *)) { + [self.capturer startCapturingFromFileNamed:kTestFileName onError:nil]; + + __block BOOL done = NO; + __block NSInteger capturedFrames = -1; + NSInteger capturedFramesAfterStop = -1; + + // We're dispatching the `stopCapture` with delay to ensure the capturer has + // had the chance to capture several frames. + dispatch_time_t captureDelay = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); // 2secs. + dispatch_after(captureDelay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + capturedFrames = self.mockDelegate.capturedFramesCount; + [self.capturer stopCapture]; + done = YES; + }); + WAIT(done, kTestTimeoutMs); + + capturedFramesAfterStop = self.mockDelegate.capturedFramesCount; + ASSERT_TRUE(capturedFrames != -1); + ASSERT_EQ(capturedFrames, capturedFramesAfterStop); + } +#endif +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCH264ProfileLevelId_xctest.m b/third_party/libwebrtc/sdk/objc/unittests/RTCH264ProfileLevelId_xctest.m new file mode 100644 index 0000000000..ec9dc41796 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCH264ProfileLevelId_xctest.m @@ -0,0 +1,48 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import "components/video_codec/RTCH264ProfileLevelId.h" + +#import + +@interface RTCH264ProfileLevelIdTests : XCTestCase + +@end + +static NSString *level31ConstrainedHigh = @"640c1f"; +static NSString *level31ConstrainedBaseline = @"42e01f"; + +@implementation RTCH264ProfileLevelIdTests + +- (void)testInitWithString { + RTC_OBJC_TYPE(RTCH264ProfileLevelId) *profileLevelId = + [[RTC_OBJC_TYPE(RTCH264ProfileLevelId) alloc] initWithHexString:level31ConstrainedHigh]; + XCTAssertEqual(profileLevelId.profile, RTCH264ProfileConstrainedHigh); + XCTAssertEqual(profileLevelId.level, RTCH264Level3_1); + + profileLevelId = + [[RTC_OBJC_TYPE(RTCH264ProfileLevelId) alloc] initWithHexString:level31ConstrainedBaseline]; + XCTAssertEqual(profileLevelId.profile, RTCH264ProfileConstrainedBaseline); + XCTAssertEqual(profileLevelId.level, RTCH264Level3_1); +} + +- (void)testInitWithProfileAndLevel { + RTC_OBJC_TYPE(RTCH264ProfileLevelId) *profileLevelId = + [[RTC_OBJC_TYPE(RTCH264ProfileLevelId) alloc] initWithProfile:RTCH264ProfileConstrainedHigh + level:RTCH264Level3_1]; + XCTAssertEqualObjects(profileLevelId.hexString, level31ConstrainedHigh); + + profileLevelId = [[RTC_OBJC_TYPE(RTCH264ProfileLevelId) alloc] + initWithProfile:RTCH264ProfileConstrainedBaseline + level:RTCH264Level3_1]; + XCTAssertEqualObjects(profileLevelId.hexString, level31ConstrainedBaseline); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCIceCandidateTest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCIceCandidateTest.mm new file mode 100644 index 0000000000..d781488286 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCIceCandidateTest.mm @@ -0,0 +1,61 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#include + +#include "rtc_base/gunit.h" + +#import "api/peerconnection/RTCIceCandidate+Private.h" +#import "api/peerconnection/RTCIceCandidate.h" +#import "helpers/NSString+StdString.h" + +@interface RTCIceCandidateTest : XCTestCase +@end + +@implementation RTCIceCandidateTest + +- (void)testCandidate { + NSString *sdp = @"candidate:4025901590 1 udp 2122265343 " + "fdff:2642:12a6:fe38:c001:beda:fcf9:51aa " + "59052 typ host generation 0"; + + RTC_OBJC_TYPE(RTCIceCandidate) *candidate = + [[RTC_OBJC_TYPE(RTCIceCandidate) alloc] initWithSdp:sdp sdpMLineIndex:0 sdpMid:@"audio"]; + + std::unique_ptr nativeCandidate = + candidate.nativeCandidate; + EXPECT_EQ("audio", nativeCandidate->sdp_mid()); + EXPECT_EQ(0, nativeCandidate->sdp_mline_index()); + + std::string sdpString; + nativeCandidate->ToString(&sdpString); + EXPECT_EQ(sdp.stdString, sdpString); +} + +- (void)testInitFromNativeCandidate { + std::string sdp("candidate:4025901590 1 udp 2122265343 " + "fdff:2642:12a6:fe38:c001:beda:fcf9:51aa " + "59052 typ host generation 0"); + std::unique_ptr nativeCandidate( + webrtc::CreateIceCandidate("audio", 0, sdp, nullptr)); + + RTC_OBJC_TYPE(RTCIceCandidate) *iceCandidate = + [[RTC_OBJC_TYPE(RTCIceCandidate) alloc] initWithNativeCandidate:nativeCandidate.get()]; + EXPECT_NE(nativeCandidate.get(), iceCandidate.nativeCandidate.get()); + EXPECT_TRUE([@"audio" isEqualToString:iceCandidate.sdpMid]); + EXPECT_EQ(0, iceCandidate.sdpMLineIndex); + + EXPECT_EQ(sdp, iceCandidate.sdp.stdString); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCIceServerTest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCIceServerTest.mm new file mode 100644 index 0000000000..772653c4dc --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCIceServerTest.mm @@ -0,0 +1,136 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#include + +#include "rtc_base/gunit.h" + +#import "api/peerconnection/RTCIceServer+Private.h" +#import "api/peerconnection/RTCIceServer.h" +#import "helpers/NSString+StdString.h" + +@interface RTCIceServerTest : XCTestCase +@end + +@implementation RTCIceServerTest + +- (void)testOneURLServer { + RTC_OBJC_TYPE(RTCIceServer) *server = + [[RTC_OBJC_TYPE(RTCIceServer) alloc] initWithURLStrings:@[ @"stun:stun1.example.net" ]]; + + webrtc::PeerConnectionInterface::IceServer iceStruct = server.nativeServer; + EXPECT_EQ(1u, iceStruct.urls.size()); + EXPECT_EQ("stun:stun1.example.net", iceStruct.urls.front()); + EXPECT_EQ("", iceStruct.username); + EXPECT_EQ("", iceStruct.password); +} + +- (void)testTwoURLServer { + RTC_OBJC_TYPE(RTCIceServer) *server = [[RTC_OBJC_TYPE(RTCIceServer) alloc] + initWithURLStrings:@[ @"turn1:turn1.example.net", @"turn2:turn2.example.net" ]]; + + webrtc::PeerConnectionInterface::IceServer iceStruct = server.nativeServer; + EXPECT_EQ(2u, iceStruct.urls.size()); + EXPECT_EQ("turn1:turn1.example.net", iceStruct.urls.front()); + EXPECT_EQ("turn2:turn2.example.net", iceStruct.urls.back()); + EXPECT_EQ("", iceStruct.username); + EXPECT_EQ("", iceStruct.password); +} + +- (void)testPasswordCredential { + RTC_OBJC_TYPE(RTCIceServer) *server = + [[RTC_OBJC_TYPE(RTCIceServer) alloc] initWithURLStrings:@[ @"turn1:turn1.example.net" ] + username:@"username" + credential:@"credential"]; + webrtc::PeerConnectionInterface::IceServer iceStruct = server.nativeServer; + EXPECT_EQ(1u, iceStruct.urls.size()); + EXPECT_EQ("turn1:turn1.example.net", iceStruct.urls.front()); + EXPECT_EQ("username", iceStruct.username); + EXPECT_EQ("credential", iceStruct.password); +} + +- (void)testHostname { + RTC_OBJC_TYPE(RTCIceServer) *server = + [[RTC_OBJC_TYPE(RTCIceServer) alloc] initWithURLStrings:@[ @"turn1:turn1.example.net" ] + username:@"username" + credential:@"credential" + tlsCertPolicy:RTCTlsCertPolicySecure + hostname:@"hostname"]; + webrtc::PeerConnectionInterface::IceServer iceStruct = server.nativeServer; + EXPECT_EQ(1u, iceStruct.urls.size()); + EXPECT_EQ("turn1:turn1.example.net", iceStruct.urls.front()); + EXPECT_EQ("username", iceStruct.username); + EXPECT_EQ("credential", iceStruct.password); + EXPECT_EQ("hostname", iceStruct.hostname); +} + +- (void)testTlsAlpnProtocols { + RTC_OBJC_TYPE(RTCIceServer) *server = + [[RTC_OBJC_TYPE(RTCIceServer) alloc] initWithURLStrings:@[ @"turn1:turn1.example.net" ] + username:@"username" + credential:@"credential" + tlsCertPolicy:RTCTlsCertPolicySecure + hostname:@"hostname" + tlsAlpnProtocols:@[ @"proto1", @"proto2" ]]; + webrtc::PeerConnectionInterface::IceServer iceStruct = server.nativeServer; + EXPECT_EQ(1u, iceStruct.urls.size()); + EXPECT_EQ("turn1:turn1.example.net", iceStruct.urls.front()); + EXPECT_EQ("username", iceStruct.username); + EXPECT_EQ("credential", iceStruct.password); + EXPECT_EQ("hostname", iceStruct.hostname); + EXPECT_EQ(2u, iceStruct.tls_alpn_protocols.size()); +} + +- (void)testTlsEllipticCurves { + RTC_OBJC_TYPE(RTCIceServer) *server = + [[RTC_OBJC_TYPE(RTCIceServer) alloc] initWithURLStrings:@[ @"turn1:turn1.example.net" ] + username:@"username" + credential:@"credential" + tlsCertPolicy:RTCTlsCertPolicySecure + hostname:@"hostname" + tlsAlpnProtocols:@[ @"proto1", @"proto2" ] + tlsEllipticCurves:@[ @"curve1", @"curve2" ]]; + webrtc::PeerConnectionInterface::IceServer iceStruct = server.nativeServer; + EXPECT_EQ(1u, iceStruct.urls.size()); + EXPECT_EQ("turn1:turn1.example.net", iceStruct.urls.front()); + EXPECT_EQ("username", iceStruct.username); + EXPECT_EQ("credential", iceStruct.password); + EXPECT_EQ("hostname", iceStruct.hostname); + EXPECT_EQ(2u, iceStruct.tls_alpn_protocols.size()); + EXPECT_EQ(2u, iceStruct.tls_elliptic_curves.size()); +} + +- (void)testInitFromNativeServer { + webrtc::PeerConnectionInterface::IceServer nativeServer; + nativeServer.username = "username"; + nativeServer.password = "password"; + nativeServer.urls.push_back("stun:stun.example.net"); + nativeServer.hostname = "hostname"; + nativeServer.tls_alpn_protocols.push_back("proto1"); + nativeServer.tls_alpn_protocols.push_back("proto2"); + nativeServer.tls_elliptic_curves.push_back("curve1"); + nativeServer.tls_elliptic_curves.push_back("curve2"); + + RTC_OBJC_TYPE(RTCIceServer) *iceServer = + [[RTC_OBJC_TYPE(RTCIceServer) alloc] initWithNativeServer:nativeServer]; + EXPECT_EQ(1u, iceServer.urlStrings.count); + EXPECT_EQ("stun:stun.example.net", + [NSString stdStringForString:iceServer.urlStrings.firstObject]); + EXPECT_EQ("username", [NSString stdStringForString:iceServer.username]); + EXPECT_EQ("password", [NSString stdStringForString:iceServer.credential]); + EXPECT_EQ("hostname", [NSString stdStringForString:iceServer.hostname]); + EXPECT_EQ(2u, iceServer.tlsAlpnProtocols.count); + EXPECT_EQ(2u, iceServer.tlsEllipticCurves.count); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCMTLVideoView_xctest.m b/third_party/libwebrtc/sdk/objc/unittests/RTCMTLVideoView_xctest.m new file mode 100644 index 0000000000..587a6b588f --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCMTLVideoView_xctest.m @@ -0,0 +1,299 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +#import "components/renderer/metal/RTCMTLVideoView.h" + +#import "api/video_frame_buffer/RTCNativeI420Buffer.h" +#import "base/RTCVideoFrameBuffer.h" +#import "components/renderer/metal/RTCMTLNV12Renderer.h" +#import "components/video_frame_buffer/RTCCVPixelBuffer.h" + +static size_t kBufferWidth = 200; +static size_t kBufferHeight = 200; + +// Extension of RTC_OBJC_TYPE(RTCMTLVideoView) for testing purposes. +@interface RTC_OBJC_TYPE (RTCMTLVideoView) +(Testing) + + @property(nonatomic, readonly) MTKView *metalView; + ++ (BOOL)isMetalAvailable; ++ (UIView *)createMetalView:(CGRect)frame; ++ (id)createNV12Renderer; ++ (id)createI420Renderer; +- (void)drawInMTKView:(id)view; +@end + +@interface RTCMTLVideoViewTests : XCTestCase +@property(nonatomic, strong) id classMock; +@property(nonatomic, strong) id rendererNV12Mock; +@property(nonatomic, strong) id rendererI420Mock; +@property(nonatomic, strong) id frameMock; +@end + +@implementation RTCMTLVideoViewTests + +@synthesize classMock = _classMock; +@synthesize rendererNV12Mock = _rendererNV12Mock; +@synthesize rendererI420Mock = _rendererI420Mock; +@synthesize frameMock = _frameMock; + +- (void)setUp { + self.classMock = OCMClassMock([RTC_OBJC_TYPE(RTCMTLVideoView) class]); + [self startMockingNilView]; +} + +- (void)tearDown { + [self.classMock stopMocking]; + [self.rendererI420Mock stopMocking]; + [self.rendererNV12Mock stopMocking]; + [self.frameMock stopMocking]; + self.classMock = nil; + self.rendererI420Mock = nil; + self.rendererNV12Mock = nil; + self.frameMock = nil; +} + +- (id)frameMockWithCVPixelBuffer:(BOOL)hasCVPixelBuffer { + id frameMock = OCMClassMock([RTC_OBJC_TYPE(RTCVideoFrame) class]); + if (hasCVPixelBuffer) { + CVPixelBufferRef pixelBufferRef; + CVPixelBufferCreate(kCFAllocatorDefault, + kBufferWidth, + kBufferHeight, + kCVPixelFormatType_420YpCbCr8Planar, + nil, + &pixelBufferRef); + OCMStub([frameMock buffer]) + .andReturn([[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]); + } else { + OCMStub([frameMock buffer]) + .andReturn([[RTC_OBJC_TYPE(RTCI420Buffer) alloc] initWithWidth:kBufferWidth + height:kBufferHeight]); + } + OCMStub([((RTC_OBJC_TYPE(RTCVideoFrame) *)frameMock) width]).andReturn(kBufferWidth); + OCMStub([((RTC_OBJC_TYPE(RTCVideoFrame) *)frameMock) height]).andReturn(kBufferHeight); + OCMStub([frameMock timeStampNs]).andReturn(arc4random_uniform(INT_MAX)); + return frameMock; +} + +- (id)rendererMockWithSuccessfulSetup:(BOOL)success { + id rendererMock = OCMClassMock([RTCMTLRenderer class]); + OCMStub([rendererMock addRenderingDestination:[OCMArg any]]).andReturn(success); + return rendererMock; +} + +- (void)startMockingNilView { + // Use OCMock 2 syntax here until OCMock is upgraded to 3.4 + [[[self.classMock stub] andReturn:nil] createMetalView:CGRectZero]; +} + +#pragma mark - Test cases + +- (void)testInitAssertsIfMetalUnavailabe { + // given + OCMStub([self.classMock isMetalAvailable]).andReturn(NO); + + // when + BOOL asserts = NO; + @try { + RTC_OBJC_TYPE(RTCMTLVideoView) *realView = + [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectZero]; + (void)realView; + } @catch (NSException *ex) { + asserts = YES; + } + + XCTAssertTrue(asserts); +} + +- (void)testRTCVideoRenderNilFrameCallback { + // given + OCMStub([self.classMock isMetalAvailable]).andReturn(YES); + + RTC_OBJC_TYPE(RTCMTLVideoView) *realView = + [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; + self.frameMock = OCMClassMock([RTC_OBJC_TYPE(RTCVideoFrame) class]); + + [[self.frameMock reject] buffer]; + [[self.classMock reject] createNV12Renderer]; + [[self.classMock reject] createI420Renderer]; + + // when + [realView renderFrame:nil]; + [realView drawInMTKView:realView.metalView]; + + // then + [self.frameMock verify]; + [self.classMock verify]; +} + +- (void)testRTCVideoRenderFrameCallbackI420 { + // given + OCMStub([self.classMock isMetalAvailable]).andReturn(YES); + self.rendererI420Mock = [self rendererMockWithSuccessfulSetup:YES]; + self.frameMock = [self frameMockWithCVPixelBuffer:NO]; + + OCMExpect([self.rendererI420Mock drawFrame:self.frameMock]); + OCMExpect([self.classMock createI420Renderer]).andReturn(self.rendererI420Mock); + [[self.classMock reject] createNV12Renderer]; + + RTC_OBJC_TYPE(RTCMTLVideoView) *realView = + [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; + + // when + [realView renderFrame:self.frameMock]; + [realView drawInMTKView:realView.metalView]; + + // then + [self.rendererI420Mock verify]; + [self.classMock verify]; +} + +- (void)testRTCVideoRenderFrameCallbackNV12 { + // given + OCMStub([self.classMock isMetalAvailable]).andReturn(YES); + self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; + self.frameMock = [self frameMockWithCVPixelBuffer:YES]; + + OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); + OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); + [[self.classMock reject] createI420Renderer]; + + RTC_OBJC_TYPE(RTCMTLVideoView) *realView = + [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; + + // when + [realView renderFrame:self.frameMock]; + [realView drawInMTKView:realView.metalView]; + + // then + [self.rendererNV12Mock verify]; + [self.classMock verify]; +} + +- (void)testRTCVideoRenderWorksAfterReconstruction { + OCMStub([self.classMock isMetalAvailable]).andReturn(YES); + self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; + self.frameMock = [self frameMockWithCVPixelBuffer:YES]; + + OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); + OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); + [[self.classMock reject] createI420Renderer]; + + RTC_OBJC_TYPE(RTCMTLVideoView) *realView = + [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; + + [realView renderFrame:self.frameMock]; + [realView drawInMTKView:realView.metalView]; + [self.rendererNV12Mock verify]; + [self.classMock verify]; + + // Recreate view. + realView = [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; + OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); + // View hould reinit renderer. + OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); + + [realView renderFrame:self.frameMock]; + [realView drawInMTKView:realView.metalView]; + [self.rendererNV12Mock verify]; + [self.classMock verify]; +} + +- (void)testDontRedrawOldFrame { + OCMStub([self.classMock isMetalAvailable]).andReturn(YES); + self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; + self.frameMock = [self frameMockWithCVPixelBuffer:YES]; + + OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); + OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); + [[self.classMock reject] createI420Renderer]; + + RTC_OBJC_TYPE(RTCMTLVideoView) *realView = + [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; + [realView renderFrame:self.frameMock]; + [realView drawInMTKView:realView.metalView]; + + [self.rendererNV12Mock verify]; + [self.classMock verify]; + + [[self.rendererNV12Mock reject] drawFrame:[OCMArg any]]; + + [realView renderFrame:self.frameMock]; + [realView drawInMTKView:realView.metalView]; + + [self.rendererNV12Mock verify]; +} + +- (void)testDoDrawNewFrame { + OCMStub([self.classMock isMetalAvailable]).andReturn(YES); + self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; + self.frameMock = [self frameMockWithCVPixelBuffer:YES]; + + OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); + OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); + [[self.classMock reject] createI420Renderer]; + + RTC_OBJC_TYPE(RTCMTLVideoView) *realView = + [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; + [realView renderFrame:self.frameMock]; + [realView drawInMTKView:realView.metalView]; + + [self.rendererNV12Mock verify]; + [self.classMock verify]; + + // Get new frame. + self.frameMock = [self frameMockWithCVPixelBuffer:YES]; + OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); + + [realView renderFrame:self.frameMock]; + [realView drawInMTKView:realView.metalView]; + + [self.rendererNV12Mock verify]; +} + +- (void)testReportsSizeChangesToDelegate { + OCMStub([self.classMock isMetalAvailable]).andReturn(YES); + + id delegateMock = OCMProtocolMock(@protocol(RTC_OBJC_TYPE(RTCVideoViewDelegate))); + CGSize size = CGSizeMake(640, 480); + OCMExpect([delegateMock videoView:[OCMArg any] didChangeVideoSize:size]); + + RTC_OBJC_TYPE(RTCMTLVideoView) *realView = + [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; + realView.delegate = delegateMock; + [realView setSize:size]; + + // Delegate method is invoked with a dispatch_async. + OCMVerifyAllWithDelay(delegateMock, 1); +} + +// TODO(b/298960678): Fix test expectations. +- (void)DISABLED_testSetContentMode { + OCMStub([self.classMock isMetalAvailable]).andReturn(YES); + id metalKitView = OCMClassMock([MTKView class]); + [[[[self.classMock stub] ignoringNonObjectArgs] andReturn:metalKitView] + createMetalView:CGRectZero]; + OCMExpect([metalKitView setContentMode:UIViewContentModeScaleAspectFill]); + + RTC_OBJC_TYPE(RTCMTLVideoView) *realView = [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] init]; + [realView setVideoContentMode:UIViewContentModeScaleAspectFill]; + + OCMVerifyAll(metalKitView); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCMediaConstraintsTest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCMediaConstraintsTest.mm new file mode 100644 index 0000000000..6ed7859ba1 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCMediaConstraintsTest.mm @@ -0,0 +1,58 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#include + +#include "rtc_base/gunit.h" + +#import "api/peerconnection/RTCMediaConstraints+Private.h" +#import "api/peerconnection/RTCMediaConstraints.h" +#import "helpers/NSString+StdString.h" + +@interface RTCMediaConstraintsTests : XCTestCase +@end + +@implementation RTCMediaConstraintsTests + +- (void)testMediaConstraints { + NSDictionary *mandatory = @{@"key1": @"value1", @"key2": @"value2"}; + NSDictionary *optional = @{@"key3": @"value3", @"key4": @"value4"}; + + RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:mandatory + optionalConstraints:optional]; + std::unique_ptr nativeConstraints = + [constraints nativeConstraints]; + + webrtc::MediaConstraints::Constraints nativeMandatory = nativeConstraints->GetMandatory(); + [self expectConstraints:mandatory inNativeConstraints:nativeMandatory]; + + webrtc::MediaConstraints::Constraints nativeOptional = nativeConstraints->GetOptional(); + [self expectConstraints:optional inNativeConstraints:nativeOptional]; +} + +- (void)expectConstraints:(NSDictionary *)constraints + inNativeConstraints:(webrtc::MediaConstraints::Constraints)nativeConstraints { + EXPECT_EQ(constraints.count, nativeConstraints.size()); + + for (NSString *key in constraints) { + NSString *value = [constraints objectForKey:key]; + + std::string nativeValue; + bool found = nativeConstraints.FindFirst(key.stdString, &nativeValue); + EXPECT_TRUE(found); + EXPECT_EQ(value.stdString, nativeValue); + } +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCNV12TextureCache_xctest.m b/third_party/libwebrtc/sdk/objc/unittests/RTCNV12TextureCache_xctest.m new file mode 100644 index 0000000000..7bdc538f67 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCNV12TextureCache_xctest.m @@ -0,0 +1,55 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import +#import +#import + +#import "base/RTCVideoFrame.h" +#import "base/RTCVideoFrameBuffer.h" +#import "components/renderer/opengl/RTCNV12TextureCache.h" +#import "components/video_frame_buffer/RTCCVPixelBuffer.h" + +@interface RTCNV12TextureCacheTests : XCTestCase +@end + +@implementation RTCNV12TextureCacheTests { + EAGLContext *_glContext; + RTCNV12TextureCache *_nv12TextureCache; +} + +- (void)setUp { + [super setUp]; + _glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; + if (!_glContext) { + _glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + } + _nv12TextureCache = [[RTCNV12TextureCache alloc] initWithContext:_glContext]; +} + +- (void)tearDown { + _nv12TextureCache = nil; + _glContext = nil; + [super tearDown]; +} + +- (void)testNV12TextureCacheDoesNotCrashOnEmptyFrame { + CVPixelBufferRef nullPixelBuffer = NULL; + RTC_OBJC_TYPE(RTCCVPixelBuffer) *badFrameBuffer = + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:nullPixelBuffer]; + RTC_OBJC_TYPE(RTCVideoFrame) *badFrame = + [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:badFrameBuffer + rotation:RTCVideoRotation_0 + timeStampNs:0]; + [_nv12TextureCache uploadFrameToTextures:badFrame]; +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionFactoryBuilderTest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionFactoryBuilderTest.mm new file mode 100644 index 0000000000..5ba5a52a53 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionFactoryBuilderTest.mm @@ -0,0 +1,72 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import +#ifdef __cplusplus +extern "C" { +#endif +#import +#ifdef __cplusplus +} +#endif +#import "api/peerconnection/RTCPeerConnectionFactory+Native.h" +#import "api/peerconnection/RTCPeerConnectionFactoryBuilder+DefaultComponents.h" +#import "api/peerconnection/RTCPeerConnectionFactoryBuilder.h" + +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/video_codecs/video_decoder_factory.h" +#include "api/video_codecs/video_encoder_factory.h" +#include "modules/audio_device/include/audio_device.h" +#include "modules/audio_processing/include/audio_processing.h" + +#include "rtc_base/gunit.h" +#include "rtc_base/system/unused.h" + +@interface RTCPeerConnectionFactoryBuilderTests : XCTestCase +@end + +@implementation RTCPeerConnectionFactoryBuilderTests + +- (void)testBuilder { + id factoryMock = OCMStrictClassMock([RTC_OBJC_TYPE(RTCPeerConnectionFactory) class]); + OCMExpect([factoryMock alloc]).andReturn(factoryMock); + RTC_UNUSED([[[[factoryMock expect] andReturn:factoryMock] ignoringNonObjectArgs] + initWithNativeAudioEncoderFactory:nullptr + nativeAudioDecoderFactory:nullptr + nativeVideoEncoderFactory:nullptr + nativeVideoDecoderFactory:nullptr + audioDeviceModule:nullptr + audioProcessingModule:nullptr]); + RTCPeerConnectionFactoryBuilder* builder = [[RTCPeerConnectionFactoryBuilder alloc] init]; + RTC_OBJC_TYPE(RTCPeerConnectionFactory)* peerConnectionFactory = + [builder createPeerConnectionFactory]; + EXPECT_TRUE(peerConnectionFactory != nil); + OCMVerifyAll(factoryMock); +} + +- (void)testDefaultComponentsBuilder { + id factoryMock = OCMStrictClassMock([RTC_OBJC_TYPE(RTCPeerConnectionFactory) class]); + OCMExpect([factoryMock alloc]).andReturn(factoryMock); + RTC_UNUSED([[[[factoryMock expect] andReturn:factoryMock] ignoringNonObjectArgs] + initWithNativeAudioEncoderFactory:nullptr + nativeAudioDecoderFactory:nullptr + nativeVideoEncoderFactory:nullptr + nativeVideoDecoderFactory:nullptr + audioDeviceModule:nullptr + audioProcessingModule:nullptr]); + RTCPeerConnectionFactoryBuilder* builder = [RTCPeerConnectionFactoryBuilder defaultBuilder]; + RTC_OBJC_TYPE(RTCPeerConnectionFactory)* peerConnectionFactory = + [builder createPeerConnectionFactory]; + EXPECT_TRUE(peerConnectionFactory != nil); + OCMVerifyAll(factoryMock); +} +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionFactory_xctest.m b/third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionFactory_xctest.m new file mode 100644 index 0000000000..56c74971b6 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionFactory_xctest.m @@ -0,0 +1,380 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import "api/peerconnection/RTCAudioSource.h" +#import "api/peerconnection/RTCConfiguration.h" +#import "api/peerconnection/RTCDataChannel.h" +#import "api/peerconnection/RTCDataChannelConfiguration.h" +#import "api/peerconnection/RTCMediaConstraints.h" +#import "api/peerconnection/RTCMediaStreamTrack.h" +#import "api/peerconnection/RTCPeerConnection.h" +#import "api/peerconnection/RTCPeerConnectionFactory.h" +#import "api/peerconnection/RTCRtpReceiver.h" +#import "api/peerconnection/RTCRtpSender.h" +#import "api/peerconnection/RTCRtpTransceiver.h" +#import "api/peerconnection/RTCSessionDescription.h" +#import "api/peerconnection/RTCVideoSource.h" +#import "rtc_base/system/unused.h" + +#import + +@interface RTCPeerConnectionFactoryTests : XCTestCase +@end + +@implementation RTCPeerConnectionFactoryTests + +- (void)testPeerConnectionLifetime { + @autoreleasepool { + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + + RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} + optionalConstraints:nil]; + + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection; + + @autoreleasepool { + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + peerConnection = + [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; + [peerConnection close]; + factory = nil; + } + peerConnection = nil; + } + + XCTAssertTrue(true, @"Expect test does not crash"); +} + +- (void)testMediaStreamLifetime { + @autoreleasepool { + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCMediaStream) * mediaStream; + + @autoreleasepool { + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + mediaStream = [factory mediaStreamWithStreamId:@"mediaStream"]; + factory = nil; + } + mediaStream = nil; + RTC_UNUSED(mediaStream); + } + + XCTAssertTrue(true, "Expect test does not crash"); +} + +- (void)testDataChannelLifetime { + @autoreleasepool { + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} + optionalConstraints:nil]; + RTC_OBJC_TYPE(RTCDataChannelConfiguration) *dataChannelConfig = + [[RTC_OBJC_TYPE(RTCDataChannelConfiguration) alloc] init]; + + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection; + RTC_OBJC_TYPE(RTCDataChannel) * dataChannel; + + @autoreleasepool { + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + peerConnection = + [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; + dataChannel = + [peerConnection dataChannelForLabel:@"test_channel" configuration:dataChannelConfig]; + XCTAssertNotNil(dataChannel); + [peerConnection close]; + peerConnection = nil; + factory = nil; + } + dataChannel = nil; + } + + XCTAssertTrue(true, "Expect test does not crash"); +} + +- (void)testRTCRtpTransceiverLifetime { + @autoreleasepool { + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + config.sdpSemantics = RTCSdpSemanticsUnifiedPlan; + RTC_OBJC_TYPE(RTCMediaConstraints) *contraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} + optionalConstraints:nil]; + RTC_OBJC_TYPE(RTCRtpTransceiverInit) *init = + [[RTC_OBJC_TYPE(RTCRtpTransceiverInit) alloc] init]; + + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection; + RTC_OBJC_TYPE(RTCRtpTransceiver) * tranceiver; + + @autoreleasepool { + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + peerConnection = + [factory peerConnectionWithConfiguration:config constraints:contraints delegate:nil]; + tranceiver = [peerConnection addTransceiverOfType:RTCRtpMediaTypeAudio init:init]; + XCTAssertNotNil(tranceiver); + [peerConnection close]; + peerConnection = nil; + factory = nil; + } + tranceiver = nil; + } + + XCTAssertTrue(true, "Expect test does not crash"); +} + +- (void)testRTCRtpSenderLifetime { + @autoreleasepool { + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + config.sdpSemantics = RTCSdpSemanticsPlanB; + RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} + optionalConstraints:nil]; + + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection; + RTC_OBJC_TYPE(RTCRtpSender) * sender; + + @autoreleasepool { + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + peerConnection = + [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; + sender = [peerConnection senderWithKind:kRTCMediaStreamTrackKindVideo streamId:@"stream"]; + XCTAssertNotNil(sender); + [peerConnection close]; + peerConnection = nil; + factory = nil; + } + sender = nil; + } + + XCTAssertTrue(true, "Expect test does not crash"); +} + +- (void)testRTCRtpReceiverLifetime { + @autoreleasepool { + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + config.sdpSemantics = RTCSdpSemanticsPlanB; + RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} + optionalConstraints:nil]; + + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCPeerConnection) * pc1; + RTC_OBJC_TYPE(RTCPeerConnection) * pc2; + + NSArray *receivers1; + NSArray *receivers2; + + @autoreleasepool { + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + pc1 = [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; + [pc1 senderWithKind:kRTCMediaStreamTrackKindAudio streamId:@"stream"]; + + pc2 = [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; + [pc2 senderWithKind:kRTCMediaStreamTrackKindAudio streamId:@"stream"]; + + NSTimeInterval negotiationTimeout = 15; + XCTAssertTrue([self negotiatePeerConnection:pc1 + withPeerConnection:pc2 + negotiationTimeout:negotiationTimeout]); + + XCTAssertEqual(pc1.signalingState, RTCSignalingStateStable); + XCTAssertEqual(pc2.signalingState, RTCSignalingStateStable); + + receivers1 = pc1.receivers; + receivers2 = pc2.receivers; + XCTAssertTrue(receivers1.count > 0); + XCTAssertTrue(receivers2.count > 0); + [pc1 close]; + [pc2 close]; + pc1 = nil; + pc2 = nil; + factory = nil; + } + receivers1 = nil; + receivers2 = nil; + } + + XCTAssertTrue(true, "Expect test does not crash"); +} + +- (void)testAudioSourceLifetime { + @autoreleasepool { + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCAudioSource) * audioSource; + + @autoreleasepool { + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + audioSource = [factory audioSourceWithConstraints:nil]; + XCTAssertNotNil(audioSource); + factory = nil; + } + audioSource = nil; + } + + XCTAssertTrue(true, "Expect test does not crash"); +} + +- (void)testVideoSourceLifetime { + @autoreleasepool { + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCVideoSource) * videoSource; + + @autoreleasepool { + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + videoSource = [factory videoSource]; + XCTAssertNotNil(videoSource); + factory = nil; + } + videoSource = nil; + } + + XCTAssertTrue(true, "Expect test does not crash"); +} + +- (void)testAudioTrackLifetime { + @autoreleasepool { + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCAudioTrack) * audioTrack; + + @autoreleasepool { + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + audioTrack = [factory audioTrackWithTrackId:@"audioTrack"]; + XCTAssertNotNil(audioTrack); + factory = nil; + } + audioTrack = nil; + } + + XCTAssertTrue(true, "Expect test does not crash"); +} + +- (void)testVideoTrackLifetime { + @autoreleasepool { + RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + RTC_OBJC_TYPE(RTCVideoTrack) * videoTrack; + + @autoreleasepool { + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + videoTrack = [factory videoTrackWithSource:[factory videoSource] trackId:@"videoTrack"]; + XCTAssertNotNil(videoTrack); + factory = nil; + } + videoTrack = nil; + } + + XCTAssertTrue(true, "Expect test does not crash"); +} + +- (void)testRollback { + @autoreleasepool { + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + config.sdpSemantics = RTCSdpSemanticsUnifiedPlan; + RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{ + kRTCMediaConstraintsOfferToReceiveAudio : kRTCMediaConstraintsValueTrue + } + optionalConstraints:nil]; + + __block RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; + __block RTC_OBJC_TYPE(RTCPeerConnection) * pc1; + RTC_OBJC_TYPE(RTCSessionDescription) *rollback = + [[RTC_OBJC_TYPE(RTCSessionDescription) alloc] initWithType:RTCSdpTypeRollback sdp:@""]; + + @autoreleasepool { + factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + pc1 = [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; + dispatch_semaphore_t negotiatedSem = dispatch_semaphore_create(0); + [pc1 offerForConstraints:constraints + completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * offer, NSError * error) { + XCTAssertNil(error); + XCTAssertNotNil(offer); + + __weak RTC_OBJC_TYPE(RTCPeerConnection) *weakPC1 = pc1; + [pc1 setLocalDescription:offer + completionHandler:^(NSError *error) { + XCTAssertNil(error); + [weakPC1 setLocalDescription:rollback + completionHandler:^(NSError *error) { + XCTAssertNil(error); + }]; + }]; + NSTimeInterval negotiationTimeout = 15; + dispatch_semaphore_wait( + negotiatedSem, + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(negotiationTimeout * NSEC_PER_SEC))); + + XCTAssertEqual(pc1.signalingState, RTCSignalingStateStable); + + [pc1 close]; + pc1 = nil; + factory = nil; + }]; + } + + XCTAssertTrue(true, "Expect test does not crash"); + } +} + +- (bool)negotiatePeerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)pc1 + withPeerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)pc2 + negotiationTimeout:(NSTimeInterval)timeout { + __weak RTC_OBJC_TYPE(RTCPeerConnection) *weakPC1 = pc1; + __weak RTC_OBJC_TYPE(RTCPeerConnection) *weakPC2 = pc2; + RTC_OBJC_TYPE(RTCMediaConstraints) *sdpConstraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{ + kRTCMediaConstraintsOfferToReceiveAudio : kRTCMediaConstraintsValueTrue + } + optionalConstraints:nil]; + + dispatch_semaphore_t negotiatedSem = dispatch_semaphore_create(0); + [weakPC1 offerForConstraints:sdpConstraints + completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * offer, NSError * error) { + XCTAssertNil(error); + XCTAssertNotNil(offer); + [weakPC1 + setLocalDescription:offer + completionHandler:^(NSError *error) { + XCTAssertNil(error); + [weakPC2 + setRemoteDescription:offer + completionHandler:^(NSError *error) { + XCTAssertNil(error); + [weakPC2 + answerForConstraints:sdpConstraints + completionHandler:^( + RTC_OBJC_TYPE(RTCSessionDescription) * answer, + NSError * error) { + XCTAssertNil(error); + XCTAssertNotNil(answer); + [weakPC2 + setLocalDescription:answer + completionHandler:^(NSError *error) { + XCTAssertNil(error); + [weakPC1 + setRemoteDescription:answer + completionHandler:^(NSError *error) { + XCTAssertNil(error); + dispatch_semaphore_signal(negotiatedSem); + }]; + }]; + }]; + }]; + }]; + }]; + + return 0 == + dispatch_semaphore_wait(negotiatedSem, + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC))); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionTest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionTest.mm new file mode 100644 index 0000000000..9ca8403559 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCPeerConnectionTest.mm @@ -0,0 +1,204 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#include +#include + +#include "rtc_base/gunit.h" + +#import "api/peerconnection/RTCConfiguration+Private.h" +#import "api/peerconnection/RTCConfiguration.h" +#import "api/peerconnection/RTCCryptoOptions.h" +#import "api/peerconnection/RTCIceCandidate.h" +#import "api/peerconnection/RTCIceServer.h" +#import "api/peerconnection/RTCMediaConstraints.h" +#import "api/peerconnection/RTCPeerConnection.h" +#import "api/peerconnection/RTCPeerConnectionFactory+Native.h" +#import "api/peerconnection/RTCPeerConnectionFactory.h" +#import "api/peerconnection/RTCSessionDescription.h" +#import "helpers/NSString+StdString.h" + +@interface RTCPeerConnectionTests : XCTestCase +@end + +@implementation RTCPeerConnectionTests + +- (void)testConfigurationGetter { + NSArray *urlStrings = @[ @"stun:stun1.example.net" ]; + RTC_OBJC_TYPE(RTCIceServer) *server = + [[RTC_OBJC_TYPE(RTCIceServer) alloc] initWithURLStrings:urlStrings]; + + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + config.sdpSemantics = RTCSdpSemanticsUnifiedPlan; + config.iceServers = @[ server ]; + config.iceTransportPolicy = RTCIceTransportPolicyRelay; + config.bundlePolicy = RTCBundlePolicyMaxBundle; + config.rtcpMuxPolicy = RTCRtcpMuxPolicyNegotiate; + config.tcpCandidatePolicy = RTCTcpCandidatePolicyDisabled; + config.candidateNetworkPolicy = RTCCandidateNetworkPolicyLowCost; + const int maxPackets = 60; + const int timeout = 1500; + const int interval = 2000; + config.audioJitterBufferMaxPackets = maxPackets; + config.audioJitterBufferFastAccelerate = YES; + config.iceConnectionReceivingTimeout = timeout; + config.iceBackupCandidatePairPingInterval = interval; + config.continualGatheringPolicy = + RTCContinualGatheringPolicyGatherContinually; + config.shouldPruneTurnPorts = YES; + config.activeResetSrtpParams = YES; + config.cryptoOptions = + [[RTC_OBJC_TYPE(RTCCryptoOptions) alloc] initWithSrtpEnableGcmCryptoSuites:YES + srtpEnableAes128Sha1_32CryptoCipher:YES + srtpEnableEncryptedRtpHeaderExtensions:NO + sframeRequireFrameEncryption:NO]; + + RTC_OBJC_TYPE(RTCMediaConstraints) *contraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} + optionalConstraints:nil]; + RTC_OBJC_TYPE(RTCPeerConnectionFactory) *factory = + [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + + RTC_OBJC_TYPE(RTCConfiguration) * newConfig; + @autoreleasepool { + RTC_OBJC_TYPE(RTCPeerConnection) *peerConnection = + [factory peerConnectionWithConfiguration:config constraints:contraints delegate:nil]; + newConfig = peerConnection.configuration; + + EXPECT_TRUE([peerConnection setBweMinBitrateBps:[NSNumber numberWithInt:100000] + currentBitrateBps:[NSNumber numberWithInt:5000000] + maxBitrateBps:[NSNumber numberWithInt:500000000]]); + EXPECT_FALSE([peerConnection setBweMinBitrateBps:[NSNumber numberWithInt:2] + currentBitrateBps:[NSNumber numberWithInt:1] + maxBitrateBps:nil]); + } + + EXPECT_EQ([config.iceServers count], [newConfig.iceServers count]); + RTC_OBJC_TYPE(RTCIceServer) *newServer = newConfig.iceServers[0]; + RTC_OBJC_TYPE(RTCIceServer) *origServer = config.iceServers[0]; + std::string origUrl = origServer.urlStrings.firstObject.UTF8String; + std::string url = newServer.urlStrings.firstObject.UTF8String; + EXPECT_EQ(origUrl, url); + + EXPECT_EQ(config.iceTransportPolicy, newConfig.iceTransportPolicy); + EXPECT_EQ(config.bundlePolicy, newConfig.bundlePolicy); + EXPECT_EQ(config.rtcpMuxPolicy, newConfig.rtcpMuxPolicy); + EXPECT_EQ(config.tcpCandidatePolicy, newConfig.tcpCandidatePolicy); + EXPECT_EQ(config.candidateNetworkPolicy, newConfig.candidateNetworkPolicy); + EXPECT_EQ(config.audioJitterBufferMaxPackets, newConfig.audioJitterBufferMaxPackets); + EXPECT_EQ(config.audioJitterBufferFastAccelerate, newConfig.audioJitterBufferFastAccelerate); + EXPECT_EQ(config.iceConnectionReceivingTimeout, newConfig.iceConnectionReceivingTimeout); + EXPECT_EQ(config.iceBackupCandidatePairPingInterval, + newConfig.iceBackupCandidatePairPingInterval); + EXPECT_EQ(config.continualGatheringPolicy, newConfig.continualGatheringPolicy); + EXPECT_EQ(config.shouldPruneTurnPorts, newConfig.shouldPruneTurnPorts); + EXPECT_EQ(config.activeResetSrtpParams, newConfig.activeResetSrtpParams); + EXPECT_EQ(config.cryptoOptions.srtpEnableGcmCryptoSuites, + newConfig.cryptoOptions.srtpEnableGcmCryptoSuites); + EXPECT_EQ(config.cryptoOptions.srtpEnableAes128Sha1_32CryptoCipher, + newConfig.cryptoOptions.srtpEnableAes128Sha1_32CryptoCipher); + EXPECT_EQ(config.cryptoOptions.srtpEnableEncryptedRtpHeaderExtensions, + newConfig.cryptoOptions.srtpEnableEncryptedRtpHeaderExtensions); + EXPECT_EQ(config.cryptoOptions.sframeRequireFrameEncryption, + newConfig.cryptoOptions.sframeRequireFrameEncryption); +} + +- (void)testWithDependencies { + NSArray *urlStrings = @[ @"stun:stun1.example.net" ]; + RTC_OBJC_TYPE(RTCIceServer) *server = + [[RTC_OBJC_TYPE(RTCIceServer) alloc] initWithURLStrings:urlStrings]; + + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + config.sdpSemantics = RTCSdpSemanticsUnifiedPlan; + config.iceServers = @[ server ]; + RTC_OBJC_TYPE(RTCMediaConstraints) *contraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} + optionalConstraints:nil]; + RTC_OBJC_TYPE(RTCPeerConnectionFactory) *factory = + [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + + std::unique_ptr pc_dependencies = + std::make_unique(nullptr); + @autoreleasepool { + RTC_OBJC_TYPE(RTCPeerConnection) *peerConnection = + [factory peerConnectionWithDependencies:config + constraints:contraints + dependencies:std::move(pc_dependencies) + delegate:nil]; + ASSERT_NE(peerConnection, nil); + } +} + +- (void)testWithInvalidSDP { + RTC_OBJC_TYPE(RTCPeerConnectionFactory) *factory = + [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + config.sdpSemantics = RTCSdpSemanticsUnifiedPlan; + RTC_OBJC_TYPE(RTCMediaConstraints) *contraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} + optionalConstraints:nil]; + RTC_OBJC_TYPE(RTCPeerConnection) *peerConnection = + [factory peerConnectionWithConfiguration:config constraints:contraints delegate:nil]; + + dispatch_semaphore_t negotiatedSem = dispatch_semaphore_create(0); + [peerConnection setRemoteDescription:[[RTC_OBJC_TYPE(RTCSessionDescription) alloc] + initWithType:RTCSdpTypeOffer + sdp:@"invalid"] + completionHandler:^(NSError *error) { + ASSERT_NE(error, nil); + if (error != nil) { + dispatch_semaphore_signal(negotiatedSem); + } + }]; + + NSTimeInterval timeout = 5; + ASSERT_EQ( + 0, + dispatch_semaphore_wait(negotiatedSem, + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)))); + [peerConnection close]; +} + +- (void)testWithInvalidIceCandidate { + RTC_OBJC_TYPE(RTCPeerConnectionFactory) *factory = + [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; + + RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; + config.sdpSemantics = RTCSdpSemanticsUnifiedPlan; + RTC_OBJC_TYPE(RTCMediaConstraints) *contraints = + [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} + optionalConstraints:nil]; + RTC_OBJC_TYPE(RTCPeerConnection) *peerConnection = + [factory peerConnectionWithConfiguration:config constraints:contraints delegate:nil]; + + dispatch_semaphore_t negotiatedSem = dispatch_semaphore_create(0); + [peerConnection addIceCandidate:[[RTC_OBJC_TYPE(RTCIceCandidate) alloc] initWithSdp:@"invalid" + sdpMLineIndex:-1 + sdpMid:nil] + completionHandler:^(NSError *error) { + ASSERT_NE(error, nil); + if (error != nil) { + dispatch_semaphore_signal(negotiatedSem); + } + }]; + + NSTimeInterval timeout = 5; + ASSERT_EQ( + 0, + dispatch_semaphore_wait(negotiatedSem, + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)))); + [peerConnection close]; +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCSessionDescriptionTest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCSessionDescriptionTest.mm new file mode 100644 index 0000000000..70c82f78ce --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCSessionDescriptionTest.mm @@ -0,0 +1,122 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#include "rtc_base/gunit.h" + +#import "api/peerconnection/RTCSessionDescription+Private.h" +#import "api/peerconnection/RTCSessionDescription.h" +#import "helpers/NSString+StdString.h" + +@interface RTCSessionDescriptionTests : XCTestCase +@end + +@implementation RTCSessionDescriptionTests + +/** + * Test conversion of an Objective-C RTC_OBJC_TYPE(RTCSessionDescription) to a native + * SessionDescriptionInterface (based on the types and SDP strings being equal). + */ +- (void)testSessionDescriptionConversion { + RTC_OBJC_TYPE(RTCSessionDescription) *description = + [[RTC_OBJC_TYPE(RTCSessionDescription) alloc] initWithType:RTCSdpTypeAnswer sdp:[self sdp]]; + + std::unique_ptr nativeDescription = + description.nativeDescription; + + EXPECT_EQ(RTCSdpTypeAnswer, + [RTC_OBJC_TYPE(RTCSessionDescription) typeForStdString:nativeDescription->type()]); + + std::string sdp; + nativeDescription->ToString(&sdp); + EXPECT_EQ([self sdp].stdString, sdp); +} + +- (void)testInitFromNativeSessionDescription { + webrtc::SessionDescriptionInterface *nativeDescription; + + nativeDescription = webrtc::CreateSessionDescription( + webrtc::SessionDescriptionInterface::kAnswer, + [self sdp].stdString, + nullptr); + + RTC_OBJC_TYPE(RTCSessionDescription) *description = + [[RTC_OBJC_TYPE(RTCSessionDescription) alloc] initWithNativeDescription:nativeDescription]; + EXPECT_EQ(webrtc::SessionDescriptionInterface::kAnswer, + [RTC_OBJC_TYPE(RTCSessionDescription) stdStringForType:description.type]); + EXPECT_TRUE([[self sdp] isEqualToString:description.sdp]); +} + +- (NSString *)sdp { + return @"v=0\r\n" + "o=- 5319989746393411314 2 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "t=0 0\r\n" + "a=group:BUNDLE audio video\r\n" + "a=msid-semantic: WMS ARDAMS\r\n" + "m=audio 9 UDP/TLS/RTP/SAVPF 111 103 9 0 8 126\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=rtcp:9 IN IP4 0.0.0.0\r\n" + "a=ice-ufrag:f3o+0HG7l9nwIWFY\r\n" + "a=ice-pwd:VDctmJNCptR2TB7+meDpw7w5\r\n" + "a=fingerprint:sha-256 A9:D5:8D:A8:69:22:39:60:92:AD:94:1A:22:2D:5E:" + "A5:4A:A9:18:C2:35:5D:46:5E:59:BD:1C:AF:38:9F:E6:E1\r\n" + "a=setup:active\r\n" + "a=mid:audio\r\n" + "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/" + "abs-send-time\r\n" + "a=sendrecv\r\n" + "a=rtcp-mux\r\n" + "a=rtpmap:111 opus/48000/2\r\n" + "a=fmtp:111 minptime=10;useinbandfec=1\r\n" + "a=rtpmap:103 ISAC/16000\r\n" + "a=rtpmap:9 G722/8000\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:126 telephone-event/8000\r\n" + "a=maxptime:60\r\n" + "a=ssrc:1504474588 cname:V+FdIC5AJpxLhdYQ\r\n" + "a=ssrc:1504474588 msid:ARDAMS ARDAMSa0\r\n" + "m=video 9 UDP/TLS/RTP/SAVPF 100 116 117 96\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=rtcp:9 IN IP4 0.0.0.0\r\n" + "a=ice-ufrag:f3o+0HG7l9nwIWFY\r\n" + "a=ice-pwd:VDctmJNCptR2TB7+meDpw7w5\r\n" + "a=fingerprint:sha-256 A9:D5:8D:A8:69:22:39:60:92:AD:94:1A:22:2D:5E:" + "A5:4A:A9:18:C2:35:5D:46:5E:59:BD:1C:AF:38:9F:E6:E1\r\n" + "a=setup:active\r\n" + "a=mid:video\r\n" + "a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n" + "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/" + "abs-send-time\r\n" + "a=extmap:4 urn:3gpp:video-orientation\r\n" + "a=sendrecv\r\n" + "a=rtcp-mux\r\n" + "a=rtpmap:100 VP8/90000\r\n" + "a=rtcp-fb:100 ccm fir\r\n" + "a=rtcp-fb:100 goog-lntf\r\n" + "a=rtcp-fb:100 nack\r\n" + "a=rtcp-fb:100 nack pli\r\n" + "a=rtcp-fb:100 goog-remb\r\n" + "a=rtpmap:116 red/90000\r\n" + "a=rtpmap:117 ulpfec/90000\r\n" + "a=rtpmap:96 rtx/90000\r\n" + "a=fmtp:96 apt=100\r\n" + "a=ssrc-group:FID 498297514 1644357692\r\n" + "a=ssrc:498297514 cname:V+FdIC5AJpxLhdYQ\r\n" + "a=ssrc:498297514 msid:ARDAMS ARDAMSv0\r\n" + "a=ssrc:1644357692 cname:V+FdIC5AJpxLhdYQ\r\n" + "a=ssrc:1644357692 msid:ARDAMS ARDAMSv0\r\n"; +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/RTCTracingTest.mm b/third_party/libwebrtc/sdk/objc/unittests/RTCTracingTest.mm new file mode 100644 index 0000000000..ff93047bdf --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/RTCTracingTest.mm @@ -0,0 +1,41 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#include + +#include "rtc_base/gunit.h" + +#import "api/peerconnection/RTCTracing.h" +#import "helpers/NSString+StdString.h" + +@interface RTCTracingTests : XCTestCase +@end + +@implementation RTCTracingTests + +- (NSString *)documentsFilePathForFileName:(NSString *)fileName { + NSParameterAssert(fileName.length); + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirPath = paths.firstObject; + NSString *filePath = + [documentsDirPath stringByAppendingPathComponent:fileName]; + return filePath; +} + +- (void)testTracingTestNoInitialization { + NSString *filePath = [self documentsFilePathForFileName:@"webrtc-trace.txt"]; + EXPECT_EQ(NO, RTCStartInternalCapture(filePath)); + RTCStopInternalCapture(); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/audio_short16.pcm b/third_party/libwebrtc/sdk/objc/unittests/audio_short16.pcm new file mode 100644 index 0000000000..15a0f1811c Binary files /dev/null and b/third_party/libwebrtc/sdk/objc/unittests/audio_short16.pcm differ diff --git a/third_party/libwebrtc/sdk/objc/unittests/audio_short44.pcm b/third_party/libwebrtc/sdk/objc/unittests/audio_short44.pcm new file mode 100644 index 0000000000..011cdce959 Binary files /dev/null and b/third_party/libwebrtc/sdk/objc/unittests/audio_short44.pcm differ diff --git a/third_party/libwebrtc/sdk/objc/unittests/audio_short48.pcm b/third_party/libwebrtc/sdk/objc/unittests/audio_short48.pcm new file mode 100644 index 0000000000..06fd8261cd Binary files /dev/null and b/third_party/libwebrtc/sdk/objc/unittests/audio_short48.pcm differ diff --git a/third_party/libwebrtc/sdk/objc/unittests/avformatmappertests.mm b/third_party/libwebrtc/sdk/objc/unittests/avformatmappertests.mm new file mode 100644 index 0000000000..35e95a8c22 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/avformatmappertests.mm @@ -0,0 +1,243 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#include "rtc_base/gunit.h" + +// Width and height don't play any role so lets use predefined values throughout +// the tests. +static const int kFormatWidth = 789; +static const int kFormatHeight = 987; + +// Hardcoded framrate to be used throughout the tests. +static const int kFramerate = 30; + +// Same width and height is used so it's ok to expect same cricket::VideoFormat +static cricket::VideoFormat expectedFormat = + cricket::VideoFormat(kFormatWidth, + kFormatHeight, + cricket::VideoFormat::FpsToInterval(kFramerate), + cricket::FOURCC_NV12); + +// Mock class for AVCaptureDeviceFormat. +// Custom implementation needed because OCMock cannot handle the +// CMVideoDescriptionRef mocking. +@interface AVCaptureDeviceFormatMock : NSObject + +@property (nonatomic, assign) CMVideoFormatDescriptionRef format; +@property (nonatomic, strong) OCMockObject *rangeMock; + +- (instancetype)initWithMediaSubtype:(FourCharCode)subtype + minFps:(float)minFps + maxFps:(float)maxFps; ++ (instancetype)validFormat; ++ (instancetype)invalidFpsFormat; ++ (instancetype)invalidMediaSubtypeFormat; + +@end + +@implementation AVCaptureDeviceFormatMock + +@synthesize format = _format; +@synthesize rangeMock = _rangeMock; + +- (instancetype)initWithMediaSubtype:(FourCharCode)subtype + minFps:(float)minFps + maxFps:(float)maxFps { + if (self = [super init]) { + CMVideoFormatDescriptionCreate(nil, subtype, kFormatWidth, kFormatHeight, + nil, &_format); + // We can use OCMock for the range. + _rangeMock = [OCMockObject mockForClass:[AVFrameRateRange class]]; + [[[_rangeMock stub] andReturnValue:@(minFps)] minFrameRate]; + [[[_rangeMock stub] andReturnValue:@(maxFps)] maxFrameRate]; + } + + return self; +} + ++ (instancetype)validFormat { + AVCaptureDeviceFormatMock *instance = [[AVCaptureDeviceFormatMock alloc] + initWithMediaSubtype:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + minFps:0.0 + maxFps:30.0]; + return instance; +} + ++ (instancetype)invalidFpsFormat { + AVCaptureDeviceFormatMock *instance = [[AVCaptureDeviceFormatMock alloc] + initWithMediaSubtype:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + minFps:0.0 + maxFps:22.0]; + return instance; +} + ++ (instancetype)invalidMediaSubtypeFormat { + AVCaptureDeviceFormatMock *instance = [[AVCaptureDeviceFormatMock alloc] + initWithMediaSubtype:kCVPixelFormatType_420YpCbCr8Planar + minFps:0.0 + maxFps:60.0]; + return instance; +} + +- (void)dealloc { + if (_format != nil) { + CFRelease(_format); + _format = nil; + } +} + +// Redefinition of AVCaptureDevice methods we want to mock. +- (CMVideoFormatDescriptionRef)formatDescription { + return self.format; +} + +- (NSArray *)videoSupportedFrameRateRanges { + return @[ self.rangeMock ]; +} + +@end + +TEST(AVFormatMapperTest, SuportedCricketFormatsWithInvalidFramerateFormats) { + // given + id mockDevice = OCMClassMock([AVCaptureDevice class]); + + // Valid media subtype, invalid framerate + AVCaptureDeviceFormatMock* mock = + [AVCaptureDeviceFormatMock invalidFpsFormat]; + OCMStub([mockDevice formats]).andReturn(@[ mock ]); + + // when + std::set result = + webrtc::GetSupportedVideoFormatsForDevice(mockDevice); + + // then + EXPECT_TRUE(result.empty()); +} + +TEST(AVFormatMapperTest, SuportedCricketFormatsWithInvalidFormats) { + // given + id mockDevice = OCMClassMock([AVCaptureDevice class]); + + // Invalid media subtype, valid framerate + AVCaptureDeviceFormatMock* mock = + [AVCaptureDeviceFormatMock invalidMediaSubtypeFormat]; + OCMStub([mockDevice formats]).andReturn(@[ mock ]); + + // when + std::set result = + webrtc::GetSupportedVideoFormatsForDevice(mockDevice); + + // then + EXPECT_TRUE(result.empty()); +} + +TEST(AVFormatMapperTest, SuportedCricketFormats) { + // given + id mockDevice = OCMClassMock([AVCaptureDevice class]); + + // valid media subtype, valid framerate + AVCaptureDeviceFormatMock* mock = [AVCaptureDeviceFormatMock validFormat]; + OCMStub([mockDevice formats]).andReturn(@[ mock ]); + + // when + std::set result = + webrtc::GetSupportedVideoFormatsForDevice(mockDevice); + + // then + EXPECT_EQ(1u, result.size()); + // make sure the set has the expected format + EXPECT_EQ(expectedFormat, *result.begin()); +} + +TEST(AVFormatMapperTest, MediaSubtypePreference) { + // given + id mockDevice = OCMClassMock([AVCaptureDevice class]); + + // valid media subtype, valid framerate + AVCaptureDeviceFormatMock* mockOne = [[AVCaptureDeviceFormatMock alloc] + initWithMediaSubtype:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange + minFps:0.0 + maxFps:30.0]; + // valid media subtype, valid framerate. + // This media subtype should be the preffered one. + AVCaptureDeviceFormatMock* mockTwo = [[AVCaptureDeviceFormatMock alloc] + initWithMediaSubtype:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + minFps:0.0 + maxFps:30.0]; + OCMStub([mockDevice lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES); + OCMStub([mockDevice unlockForConfiguration]); + NSArray* array = @[ mockOne, mockTwo ]; + OCMStub([mockDevice formats]).andReturn(array); + + // to verify + OCMExpect([mockDevice setActiveFormat:(AVCaptureDeviceFormat*)mockTwo]); + OCMExpect( + [mockDevice setActiveVideoMinFrameDuration:CMTimeMake(1, kFramerate)]); + + // when + bool resultFormat = + webrtc::SetFormatForCaptureDevice(mockDevice, nil, expectedFormat); + + // then + EXPECT_TRUE(resultFormat); + [mockDevice verify]; +} + +TEST(AVFormatMapperTest, SetFormatWhenDeviceCannotLock) { + // given + id mockDevice = OCMClassMock([AVCaptureDevice class]); + [[[mockDevice stub] andReturnValue:@(NO)] + lockForConfiguration:[OCMArg setTo:nil]]; + [[[mockDevice stub] andReturn:@[]] formats]; + + // when + bool resultFormat = webrtc::SetFormatForCaptureDevice(mockDevice, nil, + cricket::VideoFormat()); + + // then + EXPECT_FALSE(resultFormat); +} + +TEST(AVFormatMapperTest, SetFormatWhenFormatIsIncompatible) { + // given + id mockDevice = OCMClassMock([AVCaptureDevice class]); + OCMStub([mockDevice formats]).andReturn(@[]); + OCMStub([mockDevice lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES); + NSException* testException = + [NSException exceptionWithName:@"Test exception" + reason:@"Raised from unit tests" + userInfo:nil]; + OCMStub([mockDevice setActiveFormat:[OCMArg any]]).andThrow(testException); + OCMExpect([mockDevice unlockForConfiguration]); + + // when + bool resultFormat = webrtc::SetFormatForCaptureDevice(mockDevice, nil, + cricket::VideoFormat()); + + // then + EXPECT_FALSE(resultFormat); + + // TODO(denicija): Remove try-catch when Chromium rolls this change: + // https://github.com/erikdoe/ocmock/commit/de1419415581dc307045e54bfe9c98c86efea96b + // Without it, stubbed exceptions are being re-raised on [mock verify]. + // More information here: + //https://github.com/erikdoe/ocmock/issues/241 + @try { + [mockDevice verify]; + } @catch (NSException* exception) { + if ([exception.reason isEqual:testException.reason]) { + // Nothing dangerous here + EXPECT_TRUE([exception.reason isEqualToString:exception.reason]); + } + } +} diff --git a/third_party/libwebrtc/sdk/objc/unittests/foreman.mp4 b/third_party/libwebrtc/sdk/objc/unittests/foreman.mp4 new file mode 100644 index 0000000000..ccffbf4722 Binary files /dev/null and b/third_party/libwebrtc/sdk/objc/unittests/foreman.mp4 differ diff --git a/third_party/libwebrtc/sdk/objc/unittests/frame_buffer_helpers.h b/third_party/libwebrtc/sdk/objc/unittests/frame_buffer_helpers.h new file mode 100644 index 0000000000..76c0d15c7e --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/frame_buffer_helpers.h @@ -0,0 +1,22 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#include "api/video/i420_buffer.h" + +void DrawGradientInRGBPixelBuffer(CVPixelBufferRef pixelBuffer); + +rtc::scoped_refptr CreateI420Gradient(int width, + int height); + +void CopyI420BufferToCVPixelBuffer( + rtc::scoped_refptr i420Buffer, + CVPixelBufferRef pixelBuffer); diff --git a/third_party/libwebrtc/sdk/objc/unittests/frame_buffer_helpers.mm b/third_party/libwebrtc/sdk/objc/unittests/frame_buffer_helpers.mm new file mode 100644 index 0000000000..98b86c54c0 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/frame_buffer_helpers.mm @@ -0,0 +1,126 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "sdk/objc/unittests/frame_buffer_helpers.h" + +#include "third_party/libyuv/include/libyuv.h" + +void DrawGradientInRGBPixelBuffer(CVPixelBufferRef pixelBuffer) { + CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); + void* baseAddr = CVPixelBufferGetBaseAddress(pixelBuffer); + size_t width = CVPixelBufferGetWidth(pixelBuffer); + size_t height = CVPixelBufferGetHeight(pixelBuffer); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + int byteOrder = CVPixelBufferGetPixelFormatType(pixelBuffer) == kCVPixelFormatType_32ARGB ? + kCGBitmapByteOrder32Little : + 0; + CGContextRef cgContext = CGBitmapContextCreate(baseAddr, + width, + height, + 8, + CVPixelBufferGetBytesPerRow(pixelBuffer), + colorSpace, + byteOrder | kCGImageAlphaNoneSkipLast); + + // Create a gradient + CGFloat colors[] = { + 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, + }; + CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, NULL, 4); + + CGContextDrawLinearGradient( + cgContext, gradient, CGPointMake(0, 0), CGPointMake(width, height), 0); + CGGradientRelease(gradient); + + CGImageRef cgImage = CGBitmapContextCreateImage(cgContext); + CGContextRelease(cgContext); + CGImageRelease(cgImage); + CGColorSpaceRelease(colorSpace); + + CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); +} + +rtc::scoped_refptr CreateI420Gradient(int width, int height) { + rtc::scoped_refptr buffer(webrtc::I420Buffer::Create(width, height)); + // Initialize with gradient, Y = 128(x/w + y/h), U = 256 x/w, V = 256 y/h + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + buffer->MutableDataY()[x + y * width] = 128 * (x * height + y * width) / (width * height); + } + } + int chroma_width = buffer->ChromaWidth(); + int chroma_height = buffer->ChromaHeight(); + for (int x = 0; x < chroma_width; x++) { + for (int y = 0; y < chroma_height; y++) { + buffer->MutableDataU()[x + y * chroma_width] = 255 * x / (chroma_width - 1); + buffer->MutableDataV()[x + y * chroma_width] = 255 * y / (chroma_height - 1); + } + } + return buffer; +} + +void CopyI420BufferToCVPixelBuffer(rtc::scoped_refptr i420Buffer, + CVPixelBufferRef pixelBuffer) { + CVPixelBufferLockBaseAddress(pixelBuffer, 0); + + const OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer); + if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange || + pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) { + // NV12 + uint8_t* dstY = static_cast(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)); + const int dstYStride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); + uint8_t* dstUV = static_cast(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1)); + const int dstUVStride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); + + libyuv::I420ToNV12(i420Buffer->DataY(), + i420Buffer->StrideY(), + i420Buffer->DataU(), + i420Buffer->StrideU(), + i420Buffer->DataV(), + i420Buffer->StrideV(), + dstY, + dstYStride, + dstUV, + dstUVStride, + i420Buffer->width(), + i420Buffer->height()); + } else { + uint8_t* dst = static_cast(CVPixelBufferGetBaseAddress(pixelBuffer)); + const int bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer); + + if (pixelFormat == kCVPixelFormatType_32BGRA) { + // Corresponds to libyuv::FOURCC_ARGB + libyuv::I420ToARGB(i420Buffer->DataY(), + i420Buffer->StrideY(), + i420Buffer->DataU(), + i420Buffer->StrideU(), + i420Buffer->DataV(), + i420Buffer->StrideV(), + dst, + bytesPerRow, + i420Buffer->width(), + i420Buffer->height()); + } else if (pixelFormat == kCVPixelFormatType_32ARGB) { + // Corresponds to libyuv::FOURCC_BGRA + libyuv::I420ToBGRA(i420Buffer->DataY(), + i420Buffer->StrideY(), + i420Buffer->DataU(), + i420Buffer->StrideU(), + i420Buffer->DataV(), + i420Buffer->StrideV(), + dst, + bytesPerRow, + i420Buffer->width(), + i420Buffer->height()); + } + } + + CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); +} diff --git a/third_party/libwebrtc/sdk/objc/unittests/main.mm b/third_party/libwebrtc/sdk/objc/unittests/main.mm new file mode 100644 index 0000000000..9c513762c1 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/main.mm @@ -0,0 +1,24 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import +#include "rtc_base/thread.h" +#include "test/ios/coverage_util_ios.h" + +int main(int argc, char* argv[]) { + rtc::test::ConfigureCoverageReportPath(); + + rtc::AutoThread main_thread; + + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, nil); + } +} diff --git a/third_party/libwebrtc/sdk/objc/unittests/nalu_rewriter_xctest.mm b/third_party/libwebrtc/sdk/objc/unittests/nalu_rewriter_xctest.mm new file mode 100644 index 0000000000..82da549bb6 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/nalu_rewriter_xctest.mm @@ -0,0 +1,374 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "common_video/h264/h264_common.h" +#include "components/video_codec/nalu_rewriter.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/gunit.h" + +#import + +#if TARGET_OS_IPHONE +#import +#import +#endif + +@interface NaluRewriterTests : XCTestCase + +@end + +static const uint8_t NALU_TEST_DATA_0[] = {0xAA, 0xBB, 0xCC}; +static const uint8_t NALU_TEST_DATA_1[] = {0xDE, 0xAD, 0xBE, 0xEF}; + +// clang-format off +static const uint8_t SPS_PPS_BUFFER[] = { + // SPS nalu. + 0x00, 0x00, 0x00, 0x01, 0x27, 0x42, 0x00, 0x1E, 0xAB, 0x40, 0xF0, 0x28, + 0xD3, 0x70, 0x20, 0x20, 0x20, 0x20, + // PPS nalu. + 0x00, 0x00, 0x00, 0x01, 0x28, 0xCE, 0x3C, 0x30}; +// clang-format on + +@implementation NaluRewriterTests + +- (void)testCreateVideoFormatDescription { + CMVideoFormatDescriptionRef description = + webrtc::CreateVideoFormatDescription(SPS_PPS_BUFFER, arraysize(SPS_PPS_BUFFER)); + XCTAssertTrue(description); + if (description) { + CFRelease(description); + description = nullptr; + } + + // clang-format off + const uint8_t sps_pps_not_at_start_buffer[] = { + // Add some non-SPS/PPS NALUs at the beginning + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x01, + 0xAB, 0x33, 0x21, + // SPS nalu. + 0x00, 0x00, 0x01, 0x27, 0x42, 0x00, 0x1E, 0xAB, 0x40, 0xF0, 0x28, 0xD3, + 0x70, 0x20, 0x20, 0x20, 0x20, + // PPS nalu. + 0x00, 0x00, 0x01, 0x28, 0xCE, 0x3C, 0x30}; + // clang-format on + description = webrtc::CreateVideoFormatDescription(sps_pps_not_at_start_buffer, + arraysize(sps_pps_not_at_start_buffer)); + + XCTAssertTrue(description); + + if (description) { + CFRelease(description); + description = nullptr; + } + + const uint8_t other_buffer[] = {0x00, 0x00, 0x00, 0x01, 0x28}; + XCTAssertFalse(webrtc::CreateVideoFormatDescription(other_buffer, arraysize(other_buffer))); +} + +- (void)testReadEmptyInput { + const uint8_t annex_b_test_data[] = {0x00}; + webrtc::AnnexBBufferReader reader(annex_b_test_data, 0); + const uint8_t* nalu = nullptr; + size_t nalu_length = 0; + XCTAssertEqual(0u, reader.BytesRemaining()); + XCTAssertFalse(reader.ReadNalu(&nalu, &nalu_length)); + XCTAssertEqual(nullptr, nalu); + XCTAssertEqual(0u, nalu_length); +} + +- (void)testReadSingleNalu { + const uint8_t annex_b_test_data[] = {0x00, 0x00, 0x00, 0x01, 0xAA}; + webrtc::AnnexBBufferReader reader(annex_b_test_data, arraysize(annex_b_test_data)); + const uint8_t* nalu = nullptr; + size_t nalu_length = 0; + XCTAssertEqual(arraysize(annex_b_test_data), reader.BytesRemaining()); + XCTAssertTrue(reader.ReadNalu(&nalu, &nalu_length)); + XCTAssertEqual(annex_b_test_data + 4, nalu); + XCTAssertEqual(1u, nalu_length); + XCTAssertEqual(0u, reader.BytesRemaining()); + XCTAssertFalse(reader.ReadNalu(&nalu, &nalu_length)); + XCTAssertEqual(nullptr, nalu); + XCTAssertEqual(0u, nalu_length); +} + +- (void)testReadSingleNalu3ByteHeader { + const uint8_t annex_b_test_data[] = {0x00, 0x00, 0x01, 0xAA}; + webrtc::AnnexBBufferReader reader(annex_b_test_data, arraysize(annex_b_test_data)); + const uint8_t* nalu = nullptr; + size_t nalu_length = 0; + XCTAssertEqual(arraysize(annex_b_test_data), reader.BytesRemaining()); + XCTAssertTrue(reader.ReadNalu(&nalu, &nalu_length)); + XCTAssertEqual(annex_b_test_data + 3, nalu); + XCTAssertEqual(1u, nalu_length); + XCTAssertEqual(0u, reader.BytesRemaining()); + XCTAssertFalse(reader.ReadNalu(&nalu, &nalu_length)); + XCTAssertEqual(nullptr, nalu); + XCTAssertEqual(0u, nalu_length); +} + +- (void)testReadMissingNalu { + // clang-format off + const uint8_t annex_b_test_data[] = {0x01, + 0x00, 0x01, + 0x00, 0x00, 0x00, 0xFF}; + // clang-format on + webrtc::AnnexBBufferReader reader(annex_b_test_data, arraysize(annex_b_test_data)); + const uint8_t* nalu = nullptr; + size_t nalu_length = 0; + XCTAssertEqual(0u, reader.BytesRemaining()); + XCTAssertFalse(reader.ReadNalu(&nalu, &nalu_length)); + XCTAssertEqual(nullptr, nalu); + XCTAssertEqual(0u, nalu_length); +} + +- (void)testReadMultipleNalus { + // clang-format off + const uint8_t annex_b_test_data[] = {0x00, 0x00, 0x00, 0x01, 0xFF, + 0x01, + 0x00, 0x01, + 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x01, 0xAA, 0xBB}; + // clang-format on + webrtc::AnnexBBufferReader reader(annex_b_test_data, arraysize(annex_b_test_data)); + const uint8_t* nalu = nullptr; + size_t nalu_length = 0; + XCTAssertEqual(arraysize(annex_b_test_data), reader.BytesRemaining()); + XCTAssertTrue(reader.ReadNalu(&nalu, &nalu_length)); + XCTAssertEqual(annex_b_test_data + 4, nalu); + XCTAssertEqual(8u, nalu_length); + XCTAssertEqual(5u, reader.BytesRemaining()); + XCTAssertTrue(reader.ReadNalu(&nalu, &nalu_length)); + XCTAssertEqual(annex_b_test_data + 15, nalu); + XCTAssertEqual(2u, nalu_length); + XCTAssertEqual(0u, reader.BytesRemaining()); + XCTAssertFalse(reader.ReadNalu(&nalu, &nalu_length)); + XCTAssertEqual(nullptr, nalu); + XCTAssertEqual(0u, nalu_length); +} + +- (void)testEmptyOutputBuffer { + const uint8_t expected_buffer[] = {0x00}; + const size_t buffer_size = 1; + std::unique_ptr buffer(new uint8_t[buffer_size]); + memset(buffer.get(), 0, buffer_size); + webrtc::AvccBufferWriter writer(buffer.get(), 0); + XCTAssertEqual(0u, writer.BytesRemaining()); + XCTAssertFalse(writer.WriteNalu(NALU_TEST_DATA_0, arraysize(NALU_TEST_DATA_0))); + XCTAssertEqual(0, memcmp(expected_buffer, buffer.get(), arraysize(expected_buffer))); +} + +- (void)testWriteSingleNalu { + const uint8_t expected_buffer[] = { + 0x00, 0x00, 0x00, 0x03, 0xAA, 0xBB, 0xCC, + }; + const size_t buffer_size = arraysize(NALU_TEST_DATA_0) + 4; + std::unique_ptr buffer(new uint8_t[buffer_size]); + webrtc::AvccBufferWriter writer(buffer.get(), buffer_size); + XCTAssertEqual(buffer_size, writer.BytesRemaining()); + XCTAssertTrue(writer.WriteNalu(NALU_TEST_DATA_0, arraysize(NALU_TEST_DATA_0))); + XCTAssertEqual(0u, writer.BytesRemaining()); + XCTAssertFalse(writer.WriteNalu(NALU_TEST_DATA_1, arraysize(NALU_TEST_DATA_1))); + XCTAssertEqual(0, memcmp(expected_buffer, buffer.get(), arraysize(expected_buffer))); +} + +- (void)testWriteMultipleNalus { + // clang-format off + const uint8_t expected_buffer[] = { + 0x00, 0x00, 0x00, 0x03, 0xAA, 0xBB, 0xCC, + 0x00, 0x00, 0x00, 0x04, 0xDE, 0xAD, 0xBE, 0xEF + }; + // clang-format on + const size_t buffer_size = arraysize(NALU_TEST_DATA_0) + arraysize(NALU_TEST_DATA_1) + 8; + std::unique_ptr buffer(new uint8_t[buffer_size]); + webrtc::AvccBufferWriter writer(buffer.get(), buffer_size); + XCTAssertEqual(buffer_size, writer.BytesRemaining()); + XCTAssertTrue(writer.WriteNalu(NALU_TEST_DATA_0, arraysize(NALU_TEST_DATA_0))); + XCTAssertEqual(buffer_size - (arraysize(NALU_TEST_DATA_0) + 4), writer.BytesRemaining()); + XCTAssertTrue(writer.WriteNalu(NALU_TEST_DATA_1, arraysize(NALU_TEST_DATA_1))); + XCTAssertEqual(0u, writer.BytesRemaining()); + XCTAssertEqual(0, memcmp(expected_buffer, buffer.get(), arraysize(expected_buffer))); +} + +- (void)testOverflow { + const uint8_t expected_buffer[] = {0x00, 0x00, 0x00}; + const size_t buffer_size = arraysize(NALU_TEST_DATA_0); + std::unique_ptr buffer(new uint8_t[buffer_size]); + memset(buffer.get(), 0, buffer_size); + webrtc::AvccBufferWriter writer(buffer.get(), buffer_size); + XCTAssertEqual(buffer_size, writer.BytesRemaining()); + XCTAssertFalse(writer.WriteNalu(NALU_TEST_DATA_0, arraysize(NALU_TEST_DATA_0))); + XCTAssertEqual(buffer_size, writer.BytesRemaining()); + XCTAssertEqual(0, memcmp(expected_buffer, buffer.get(), arraysize(expected_buffer))); +} + +- (void)testH264AnnexBBufferToCMSampleBuffer { + // clang-format off + const uint8_t annex_b_test_data[] = { + 0x00, + 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes + 0x00, 0x00, 0x01, + 0xAA, 0xFF, // second chunk, 2 bytes + 0x00, 0x00, 0x01, + 0xBB}; // third chunk, 1 byte, will not fit into output array + + const uint8_t expected_cmsample_data[] = { + 0x00, 0x00, 0x00, 0x04, + 0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes + 0x00, 0x00, 0x00, 0x02, + 0xAA, 0xFF}; // second chunk, 2 bytes + // clang-format on + + CMMemoryPoolRef memory_pool = CMMemoryPoolCreate(nil); + CMSampleBufferRef out_sample_buffer = nil; + CMVideoFormatDescriptionRef description = [self createDescription]; + + Boolean result = webrtc::H264AnnexBBufferToCMSampleBuffer(annex_b_test_data, + arraysize(annex_b_test_data), + description, + &out_sample_buffer, + memory_pool); + + XCTAssertTrue(result); + + XCTAssertEqual(description, CMSampleBufferGetFormatDescription(out_sample_buffer)); + + char* data_ptr = nullptr; + CMBlockBufferRef block_buffer = CMSampleBufferGetDataBuffer(out_sample_buffer); + size_t block_buffer_size = CMBlockBufferGetDataLength(block_buffer); + CMBlockBufferGetDataPointer(block_buffer, 0, nullptr, nullptr, &data_ptr); + XCTAssertEqual(block_buffer_size, arraysize(annex_b_test_data)); + + int data_comparison_result = + memcmp(expected_cmsample_data, data_ptr, arraysize(expected_cmsample_data)); + + XCTAssertEqual(0, data_comparison_result); + + if (description) { + CFRelease(description); + description = nullptr; + } + + CMMemoryPoolInvalidate(memory_pool); + CFRelease(memory_pool); +} + +- (void)testH264CMSampleBufferToAnnexBBuffer { + // clang-format off + const uint8_t cmsample_data[] = { + 0x00, 0x00, 0x00, 0x04, + 0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes + 0x00, 0x00, 0x00, 0x02, + 0xAA, 0xFF}; // second chunk, 2 bytes + + const uint8_t expected_annex_b_data[] = { + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes + 0x00, 0x00, 0x00, 0x01, + 0xAA, 0xFF}; // second chunk, 2 bytes + // clang-format on + + rtc::Buffer annexb_buffer(arraysize(cmsample_data)); + CMSampleBufferRef sample_buffer = + [self createCMSampleBufferRef:(void*)cmsample_data cmsampleSize:arraysize(cmsample_data)]; + + Boolean result = webrtc::H264CMSampleBufferToAnnexBBuffer(sample_buffer, + /* is_keyframe */ false, + &annexb_buffer); + + XCTAssertTrue(result); + + XCTAssertEqual(arraysize(expected_annex_b_data), annexb_buffer.size()); + + int data_comparison_result = + memcmp(expected_annex_b_data, annexb_buffer.data(), arraysize(expected_annex_b_data)); + + XCTAssertEqual(0, data_comparison_result); +} + +- (void)testH264CMSampleBufferToAnnexBBufferWithKeyframe { + // clang-format off + const uint8_t cmsample_data[] = { + 0x00, 0x00, 0x00, 0x04, + 0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes + 0x00, 0x00, 0x00, 0x02, + 0xAA, 0xFF}; // second chunk, 2 bytes + + const uint8_t expected_annex_b_data[] = { + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0xFF, // first chunk, 4 bytes + 0x00, 0x00, 0x00, 0x01, + 0xAA, 0xFF}; // second chunk, 2 bytes + // clang-format on + + rtc::Buffer annexb_buffer(arraysize(cmsample_data)); + CMSampleBufferRef sample_buffer = + [self createCMSampleBufferRef:(void*)cmsample_data cmsampleSize:arraysize(cmsample_data)]; + + Boolean result = webrtc::H264CMSampleBufferToAnnexBBuffer(sample_buffer, + /* is_keyframe */ true, + &annexb_buffer); + + XCTAssertTrue(result); + + XCTAssertEqual(arraysize(SPS_PPS_BUFFER) + arraysize(expected_annex_b_data), + annexb_buffer.size()); + + XCTAssertEqual(0, memcmp(SPS_PPS_BUFFER, annexb_buffer.data(), arraysize(SPS_PPS_BUFFER))); + + XCTAssertEqual(0, + memcmp(expected_annex_b_data, + annexb_buffer.data() + arraysize(SPS_PPS_BUFFER), + arraysize(expected_annex_b_data))); +} + +- (CMVideoFormatDescriptionRef)createDescription { + CMVideoFormatDescriptionRef description = + webrtc::CreateVideoFormatDescription(SPS_PPS_BUFFER, arraysize(SPS_PPS_BUFFER)); + XCTAssertTrue(description); + return description; +} + +- (CMSampleBufferRef)createCMSampleBufferRef:(void*)cmsampleData cmsampleSize:(size_t)cmsampleSize { + CMSampleBufferRef sample_buffer = nil; + OSStatus status; + + CMVideoFormatDescriptionRef description = [self createDescription]; + CMBlockBufferRef block_buffer = nullptr; + + status = CMBlockBufferCreateWithMemoryBlock(nullptr, + cmsampleData, + cmsampleSize, + nullptr, + nullptr, + 0, + cmsampleSize, + kCMBlockBufferAssureMemoryNowFlag, + &block_buffer); + XCTAssertEqual(kCMBlockBufferNoErr, status); + + status = CMSampleBufferCreate(nullptr, + block_buffer, + true, + nullptr, + nullptr, + description, + 1, + 0, + nullptr, + 0, + nullptr, + &sample_buffer); + XCTAssertEqual(noErr, status); + + return sample_buffer; +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/objc_video_decoder_factory_tests.mm b/third_party/libwebrtc/sdk/objc/unittests/objc_video_decoder_factory_tests.mm new file mode 100644 index 0000000000..f44d831d29 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/objc_video_decoder_factory_tests.mm @@ -0,0 +1,107 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import +#import + +#include "sdk/objc/native/src/objc_video_decoder_factory.h" + +#import "base/RTCMacros.h" +#import "base/RTCVideoDecoder.h" +#import "base/RTCVideoDecoderFactory.h" +#include "media/base/codec.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "rtc_base/gunit.h" + +id CreateDecoderFactoryReturning(int return_code) { + id decoderMock = OCMProtocolMock(@protocol(RTC_OBJC_TYPE(RTCVideoDecoder))); + OCMStub([decoderMock startDecodeWithNumberOfCores:1]).andReturn(return_code); + OCMStub([decoderMock decode:[OCMArg any] + missingFrames:NO + codecSpecificInfo:[OCMArg any] + renderTimeMs:0]) + .andReturn(return_code); + OCMStub([decoderMock releaseDecoder]).andReturn(return_code); + + id decoderFactoryMock = OCMProtocolMock(@protocol(RTC_OBJC_TYPE(RTCVideoDecoderFactory))); + RTC_OBJC_TYPE(RTCVideoCodecInfo)* supported = + [[RTC_OBJC_TYPE(RTCVideoCodecInfo) alloc] initWithName:@"H264" parameters:nil]; + OCMStub([decoderFactoryMock supportedCodecs]).andReturn(@[ supported ]); + OCMStub([decoderFactoryMock createDecoder:[OCMArg any]]).andReturn(decoderMock); + return decoderFactoryMock; +} + +id CreateOKDecoderFactory() { + return CreateDecoderFactoryReturning(WEBRTC_VIDEO_CODEC_OK); +} + +id CreateErrorDecoderFactory() { + return CreateDecoderFactoryReturning(WEBRTC_VIDEO_CODEC_ERROR); +} + +std::unique_ptr GetObjCDecoder( + id factory) { + webrtc::ObjCVideoDecoderFactory decoder_factory(factory); + return decoder_factory.CreateVideoDecoder(webrtc::SdpVideoFormat(cricket::kH264CodecName)); +} + +#pragma mark - + +@interface ObjCVideoDecoderFactoryTests : XCTestCase +@end + +@implementation ObjCVideoDecoderFactoryTests + +- (void)testConfigureReturnsTrueOnSuccess { + std::unique_ptr decoder = GetObjCDecoder(CreateOKDecoderFactory()); + + webrtc::VideoDecoder::Settings settings; + EXPECT_TRUE(decoder->Configure(settings)); +} + +- (void)testConfigureReturnsFalseOnFail { + std::unique_ptr decoder = GetObjCDecoder(CreateErrorDecoderFactory()); + + webrtc::VideoDecoder::Settings settings; + EXPECT_FALSE(decoder->Configure(settings)); +} + +- (void)testDecodeReturnsOKOnSuccess { + std::unique_ptr decoder = GetObjCDecoder(CreateOKDecoderFactory()); + + webrtc::EncodedImage encoded_image; + encoded_image.SetEncodedData(webrtc::EncodedImageBuffer::Create()); + + EXPECT_EQ(decoder->Decode(encoded_image, false, 0), WEBRTC_VIDEO_CODEC_OK); +} + +- (void)testDecodeReturnsErrorOnFail { + std::unique_ptr decoder = GetObjCDecoder(CreateErrorDecoderFactory()); + + webrtc::EncodedImage encoded_image; + encoded_image.SetEncodedData(webrtc::EncodedImageBuffer::Create()); + + EXPECT_EQ(decoder->Decode(encoded_image, false, 0), WEBRTC_VIDEO_CODEC_ERROR); +} + +- (void)testReleaseDecodeReturnsOKOnSuccess { + std::unique_ptr decoder = GetObjCDecoder(CreateOKDecoderFactory()); + + EXPECT_EQ(decoder->Release(), WEBRTC_VIDEO_CODEC_OK); +} + +- (void)testReleaseDecodeReturnsErrorOnFail { + std::unique_ptr decoder = GetObjCDecoder(CreateErrorDecoderFactory()); + + EXPECT_EQ(decoder->Release(), WEBRTC_VIDEO_CODEC_ERROR); +} +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/objc_video_encoder_factory_tests.mm b/third_party/libwebrtc/sdk/objc/unittests/objc_video_encoder_factory_tests.mm new file mode 100644 index 0000000000..9a4fee2e95 --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/objc_video_encoder_factory_tests.mm @@ -0,0 +1,148 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import +#import + +#include "sdk/objc/native/src/objc_video_encoder_factory.h" + +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/video_encoder.h" +#import "base/RTCVideoEncoder.h" +#import "base/RTCVideoEncoderFactory.h" +#import "base/RTCVideoFrameBuffer.h" +#import "components/video_frame_buffer/RTCCVPixelBuffer.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "rtc_base/gunit.h" +#include "sdk/objc/native/src/objc_frame_buffer.h" + +id CreateEncoderFactoryReturning(int return_code) { + id encoderMock = OCMProtocolMock(@protocol(RTC_OBJC_TYPE(RTCVideoEncoder))); + OCMStub([encoderMock startEncodeWithSettings:[OCMArg any] numberOfCores:1]) + .andReturn(return_code); + OCMStub([encoderMock encode:[OCMArg any] codecSpecificInfo:[OCMArg any] frameTypes:[OCMArg any]]) + .andReturn(return_code); + OCMStub([encoderMock releaseEncoder]).andReturn(return_code); + OCMStub([encoderMock setBitrate:0 framerate:0]).andReturn(return_code); + + id encoderFactoryMock = OCMProtocolMock(@protocol(RTC_OBJC_TYPE(RTCVideoEncoderFactory))); + RTC_OBJC_TYPE(RTCVideoCodecInfo)* supported = + [[RTC_OBJC_TYPE(RTCVideoCodecInfo) alloc] initWithName:@"H264" parameters:nil]; + OCMStub([encoderFactoryMock supportedCodecs]).andReturn(@[ supported ]); + OCMStub([encoderFactoryMock implementations]).andReturn(@[ supported ]); + OCMStub([encoderFactoryMock createEncoder:[OCMArg any]]).andReturn(encoderMock); + return encoderFactoryMock; +} + +id CreateOKEncoderFactory() { + return CreateEncoderFactoryReturning(WEBRTC_VIDEO_CODEC_OK); +} + +id CreateErrorEncoderFactory() { + return CreateEncoderFactoryReturning(WEBRTC_VIDEO_CODEC_ERROR); +} + +std::unique_ptr GetObjCEncoder( + id factory) { + webrtc::ObjCVideoEncoderFactory encoder_factory(factory); + webrtc::SdpVideoFormat format("H264"); + return encoder_factory.CreateVideoEncoder(format); +} + +#pragma mark - + +@interface ObjCVideoEncoderFactoryTests : XCTestCase +@end + +@implementation ObjCVideoEncoderFactoryTests + +- (void)testInitEncodeReturnsOKOnSuccess { + std::unique_ptr encoder = GetObjCEncoder(CreateOKEncoderFactory()); + + auto* settings = new webrtc::VideoCodec(); + const webrtc::VideoEncoder::Capabilities kCapabilities(false); + EXPECT_EQ(encoder->InitEncode(settings, webrtc::VideoEncoder::Settings(kCapabilities, 1, 0)), + WEBRTC_VIDEO_CODEC_OK); +} + +- (void)testInitEncodeReturnsErrorOnFail { + std::unique_ptr encoder = GetObjCEncoder(CreateErrorEncoderFactory()); + + auto* settings = new webrtc::VideoCodec(); + const webrtc::VideoEncoder::Capabilities kCapabilities(false); + EXPECT_EQ(encoder->InitEncode(settings, webrtc::VideoEncoder::Settings(kCapabilities, 1, 0)), + WEBRTC_VIDEO_CODEC_ERROR); +} + +- (void)testEncodeReturnsOKOnSuccess { + std::unique_ptr encoder = GetObjCEncoder(CreateOKEncoderFactory()); + + CVPixelBufferRef pixel_buffer; + CVPixelBufferCreate(kCFAllocatorDefault, 640, 480, kCVPixelFormatType_32ARGB, nil, &pixel_buffer); + rtc::scoped_refptr buffer = + rtc::make_ref_counted( + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixel_buffer]); + webrtc::VideoFrame frame = webrtc::VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_rotation(webrtc::kVideoRotation_0) + .set_timestamp_us(0) + .build(); + std::vector frame_types; + + EXPECT_EQ(encoder->Encode(frame, &frame_types), WEBRTC_VIDEO_CODEC_OK); +} + +- (void)testEncodeReturnsErrorOnFail { + std::unique_ptr encoder = GetObjCEncoder(CreateErrorEncoderFactory()); + + CVPixelBufferRef pixel_buffer; + CVPixelBufferCreate(kCFAllocatorDefault, 640, 480, kCVPixelFormatType_32ARGB, nil, &pixel_buffer); + rtc::scoped_refptr buffer = + rtc::make_ref_counted( + [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixel_buffer]); + webrtc::VideoFrame frame = webrtc::VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_rotation(webrtc::kVideoRotation_0) + .set_timestamp_us(0) + .build(); + std::vector frame_types; + + EXPECT_EQ(encoder->Encode(frame, &frame_types), WEBRTC_VIDEO_CODEC_ERROR); +} + +- (void)testReleaseEncodeReturnsOKOnSuccess { + std::unique_ptr encoder = GetObjCEncoder(CreateOKEncoderFactory()); + + EXPECT_EQ(encoder->Release(), WEBRTC_VIDEO_CODEC_OK); +} + +- (void)testReleaseEncodeReturnsErrorOnFail { + std::unique_ptr encoder = GetObjCEncoder(CreateErrorEncoderFactory()); + + EXPECT_EQ(encoder->Release(), WEBRTC_VIDEO_CODEC_ERROR); +} + +- (void)testGetSupportedFormats { + webrtc::ObjCVideoEncoderFactory encoder_factory(CreateOKEncoderFactory()); + std::vector supportedFormats = encoder_factory.GetSupportedFormats(); + EXPECT_EQ(supportedFormats.size(), 1u); + EXPECT_EQ(supportedFormats[0].name, "H264"); +} + +- (void)testGetImplementations { + webrtc::ObjCVideoEncoderFactory encoder_factory(CreateOKEncoderFactory()); + std::vector supportedFormats = encoder_factory.GetImplementations(); + EXPECT_EQ(supportedFormats.size(), 1u); + EXPECT_EQ(supportedFormats[0].name, "H264"); +} + +@end diff --git a/third_party/libwebrtc/sdk/objc/unittests/scoped_cftyperef_tests.mm b/third_party/libwebrtc/sdk/objc/unittests/scoped_cftyperef_tests.mm new file mode 100644 index 0000000000..a354410ede --- /dev/null +++ b/third_party/libwebrtc/sdk/objc/unittests/scoped_cftyperef_tests.mm @@ -0,0 +1,111 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#include "sdk/objc/helpers/scoped_cftyperef.h" + +namespace { +struct TestType { + TestType() : has_value(true) {} + TestType(bool b) : has_value(b) {} + explicit operator bool() { return has_value; } + bool has_value; + int retain_count = 0; +}; + +typedef TestType* TestTypeRef; + +struct TestTypeTraits { + static TestTypeRef InvalidValue() { return TestTypeRef(false); } + static void Release(TestTypeRef t) { t->retain_count--; } + static TestTypeRef Retain(TestTypeRef t) { + t->retain_count++; + return t; + } +}; +} // namespace + +using ScopedTestType = rtc::internal::ScopedTypeRef; + +// In these tests we sometime introduce variables just to +// observe side-effects. Ignore the compilers complaints. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-variable" + +@interface ScopedTypeRefTests : XCTestCase +@end + +@implementation ScopedTypeRefTests + +- (void)testShouldNotRetainByDefault { + TestType a; + ScopedTestType ref(&a); + XCTAssertEqual(0, a.retain_count); +} + +- (void)testShouldRetainWithPolicy { + TestType a; + ScopedTestType ref(&a, rtc::RetainPolicy::RETAIN); + XCTAssertEqual(1, a.retain_count); +} + +- (void)testShouldReleaseWhenLeavingScope { + TestType a; + XCTAssertEqual(0, a.retain_count); + { + ScopedTestType ref(&a, rtc::RetainPolicy::RETAIN); + XCTAssertEqual(1, a.retain_count); + } + XCTAssertEqual(0, a.retain_count); +} + +- (void)testShouldBeCopyable { + TestType a; + XCTAssertEqual(0, a.retain_count); + { + ScopedTestType ref1(&a, rtc::RetainPolicy::RETAIN); + XCTAssertEqual(1, a.retain_count); + ScopedTestType ref2 = ref1; + XCTAssertEqual(2, a.retain_count); + } + XCTAssertEqual(0, a.retain_count); +} + +- (void)testCanReleaseOwnership { + TestType a; + XCTAssertEqual(0, a.retain_count); + { + ScopedTestType ref(&a, rtc::RetainPolicy::RETAIN); + XCTAssertEqual(1, a.retain_count); + TestTypeRef b = ref.release(); + } + XCTAssertEqual(1, a.retain_count); +} + +- (void)testShouldBeTestableForTruthiness { + ScopedTestType ref; + XCTAssertFalse(ref); + TestType a; + ref = &a; + XCTAssertTrue(ref); + ref.release(); + XCTAssertFalse(ref); +} + +- (void)testShouldProvideAccessToWrappedType { + TestType a; + ScopedTestType ref(&a); + XCTAssertEqual(&(a.retain_count), &(ref->retain_count)); +} + +@end + +#pragma clang diagnostic pop -- cgit v1.2.3