| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| |
| #include <string> |
| |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/time/time.h" |
| #include "media/audio/simple_sources.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/video_color_space.h" |
| #include "media/base/video_frame.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h" |
| #include "third_party/blink/public/web/web_heap.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h" |
| #include "third_party/blink/renderer/core/testing/scoped_mock_overlay_scrollbars.h" |
| #include "third_party/blink/renderer/modules/mediarecorder/fake_encoded_video_frame.h" |
| #include "third_party/blink/renderer/modules/mediarecorder/media_recorder.h" |
| #include "third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h" |
| #include "third_party/blink/renderer/modules/mediastream/mock_media_stream_registry.h" |
| #include "third_party/blink/renderer/modules/mediastream/mock_media_stream_video_source.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/heap/thread_state.h" |
| #include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| |
| using base::test::RunOnceClosure; |
| using ::testing::_; |
| using ::testing::AtLeast; |
| using ::testing::Ge; |
| using ::testing::Gt; |
| using ::testing::InSequence; |
| using ::testing::Invoke; |
| using ::testing::Lt; |
| using ::testing::Mock; |
| using ::testing::Return; |
| using ::testing::TestWithParam; |
| using ::testing::ValuesIn; |
| |
| namespace blink { |
| |
| static const std::string kTestVideoTrackId = "video_track_id"; |
| static const std::string kTestAudioTrackId = "audio_track_id"; |
| static const int kTestAudioChannels = 2; |
| static const int kTestAudioSampleRate = 48000; |
| static const int kTestAudioBufferDurationMs = 10; |
| // Opus works with 60ms buffers, so 6 MediaStreamAudioTrack Buffers are needed |
| // to encode one output buffer. |
| static const int kRatioOpusToTestAudioBuffers = 6; |
| |
| struct MediaRecorderTestParams { |
| const bool has_video; |
| const bool has_audio; |
| const char* const mime_type; |
| const char* const codecs; |
| const bool encoder_supports_alpha; |
| }; |
| |
| // Array of valid combinations of video/audio/codecs and expected collected |
| // encoded sizes to use for parameterizing MediaRecorderHandlerTest. |
| static const MediaRecorderTestParams kMediaRecorderTestParams[] = { |
| {true, false, "video/webm", "vp8", true}, |
| {true, false, "video/webm", "vp9", true}, |
| #if BUILDFLAG(RTC_USE_H264) |
| {true, false, "video/x-matroska", "avc1", false}, |
| #endif |
| {false, true, "audio/webm", "opus", true}, |
| {false, true, "audio/webm", "", true}, // Should default to opus. |
| {false, true, "audio/webm", "pcm", true}, |
| {true, true, "video/webm", "vp9,opus", true}, |
| }; |
| |
| MediaStream* CreateMediaStream(V8TestingScope& scope) { |
| auto* source = MakeGarbageCollected<MediaStreamSource>( |
| "sourceId", MediaStreamSource::kTypeAudio, "sourceName", false); |
| auto* component = |
| MakeGarbageCollected<MediaStreamComponent>("audioTrack", source); |
| |
| auto* track = MakeGarbageCollected<MediaStreamTrack>( |
| scope.GetExecutionContext(), component); |
| |
| HeapVector<Member<MediaStreamTrack>> tracks; |
| tracks.push_back(track); |
| |
| MediaStream* stream = |
| MediaStream::Create(scope.GetExecutionContext(), tracks); |
| |
| return stream; |
| } |
| |
| class MockMediaRecorder : public MediaRecorder { |
| public: |
| MockMediaRecorder(V8TestingScope& scope) |
| : MediaRecorder(scope.GetExecutionContext(), |
| CreateMediaStream(scope), |
| MediaRecorderOptions::Create(), |
| scope.GetExceptionState()) {} |
| virtual ~MockMediaRecorder() = default; |
| |
| MOCK_METHOD4(WriteData, void(const char*, size_t, bool, double)); |
| MOCK_METHOD1(OnError, void(const String& message)); |
| }; |
| |
| class MediaRecorderHandlerFixture : public ScopedMockOverlayScrollbars { |
| public: |
| MediaRecorderHandlerFixture(bool has_video, bool has_audio) |
| : has_video_(has_video), |
| has_audio_(has_audio), |
| media_recorder_handler_(MakeGarbageCollected<MediaRecorderHandler>( |
| scheduler::GetSingleThreadTaskRunnerForTesting())), |
| audio_source_(kTestAudioChannels, |
| 440 /* freq */, |
| kTestAudioSampleRate) { |
| EXPECT_FALSE(media_recorder_handler_->recording_); |
| |
| registry_.Init(); |
| } |
| |
| ~MediaRecorderHandlerFixture() { |
| registry_.reset(); |
| ThreadState::Current()->CollectAllGarbageForTesting(); |
| } |
| |
| bool recording() const { return media_recorder_handler_->recording_; } |
| bool hasVideoRecorders() const { |
| return !media_recorder_handler_->video_recorders_.IsEmpty(); |
| } |
| bool hasAudioRecorders() const { |
| return !media_recorder_handler_->audio_recorders_.IsEmpty(); |
| } |
| |
| void OnVideoFrameForTesting(scoped_refptr<media::VideoFrame> frame) { |
| media_recorder_handler_->OnVideoFrameForTesting(std::move(frame), |
| base::TimeTicks::Now()); |
| } |
| |
| void OnEncodedVideoForTesting(const media::WebmMuxer::VideoParameters& params, |
| std::string encoded_data, |
| std::string encoded_alpha, |
| base::TimeTicks timestamp, |
| bool is_key_frame) { |
| media_recorder_handler_->OnEncodedVideo(params, encoded_data, encoded_alpha, |
| timestamp, is_key_frame); |
| } |
| |
| void OnAudioBusForTesting(const media::AudioBus& audio_bus) { |
| media_recorder_handler_->OnAudioBusForTesting(audio_bus, |
| base::TimeTicks::Now()); |
| } |
| void SetAudioFormatForTesting(const media::AudioParameters& params) { |
| media_recorder_handler_->SetAudioFormatForTesting(params); |
| } |
| |
| void AddVideoTrack() { |
| video_source_ = registry_.AddVideoTrack(kTestVideoTrackId); |
| } |
| |
| void AddTracks() { |
| // Avoid issues with non-parameterized tests by calling this outside of ctr. |
| if (has_video_) |
| AddVideoTrack(); |
| if (has_audio_) |
| registry_.AddAudioTrack(kTestAudioTrackId); |
| } |
| |
| void ForceOneErrorInWebmMuxer() { |
| media_recorder_handler_->webm_muxer_->ForceOneLibWebmErrorForTesting(); |
| } |
| |
| std::unique_ptr<media::AudioBus> NextAudioBus() { |
| std::unique_ptr<media::AudioBus> bus(media::AudioBus::Create( |
| kTestAudioChannels, |
| kTestAudioSampleRate * kTestAudioBufferDurationMs / 1000)); |
| audio_source_.OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), 0, |
| bus.get()); |
| return bus; |
| } |
| |
| ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_; |
| |
| MockMediaStreamRegistry registry_; |
| |
| bool has_video_; |
| |
| bool has_audio_; |
| |
| // The Class under test. Needs to be scoped_ptr to force its destruction. |
| Persistent<MediaRecorderHandler> media_recorder_handler_; |
| |
| // For generating test AudioBuses |
| media::SineWaveAudioSource audio_source_; |
| |
| MockMediaStreamVideoSource* video_source_ = nullptr; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MediaRecorderHandlerFixture); |
| }; |
| |
| class MediaRecorderHandlerTest : public TestWithParam<MediaRecorderTestParams>, |
| public MediaRecorderHandlerFixture { |
| public: |
| MediaRecorderHandlerTest() |
| : MediaRecorderHandlerFixture(GetParam().has_video, |
| GetParam().has_audio) {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MediaRecorderHandlerTest); |
| }; |
| |
| // Checks that canSupportMimeType() works as expected, by sending supported |
| // combinations and unsupported ones. |
| TEST_P(MediaRecorderHandlerTest, CanSupportMimeType) { |
| const String unsupported_mime_type("video/mpeg"); |
| EXPECT_FALSE(media_recorder_handler_->CanSupportMimeType( |
| unsupported_mime_type, String())); |
| |
| const String mime_type_video("video/webm"); |
| EXPECT_TRUE( |
| media_recorder_handler_->CanSupportMimeType(mime_type_video, String())); |
| const String mime_type_video_uppercase("video/WEBM"); |
| EXPECT_TRUE(media_recorder_handler_->CanSupportMimeType( |
| mime_type_video_uppercase, String())); |
| const String example_good_codecs_1("vp8"); |
| EXPECT_TRUE(media_recorder_handler_->CanSupportMimeType( |
| mime_type_video, example_good_codecs_1)); |
| const String example_good_codecs_2("vp9,opus"); |
| EXPECT_TRUE(media_recorder_handler_->CanSupportMimeType( |
| mime_type_video, example_good_codecs_2)); |
| const String example_good_codecs_3("VP9,opus"); |
| EXPECT_TRUE(media_recorder_handler_->CanSupportMimeType( |
| mime_type_video, example_good_codecs_3)); |
| const String example_good_codecs_4("H264"); |
| #if BUILDFLAG(RTC_USE_H264) |
| EXPECT_TRUE(media_recorder_handler_->CanSupportMimeType( |
| mime_type_video, example_good_codecs_4)); |
| #else |
| EXPECT_FALSE(media_recorder_handler_->CanSupportMimeType( |
| mime_type_video, example_good_codecs_4)); |
| #endif |
| |
| const String example_unsupported_codecs_1("daala"); |
| EXPECT_FALSE(media_recorder_handler_->CanSupportMimeType( |
| mime_type_video, example_unsupported_codecs_1)); |
| |
| const String mime_type_audio("audio/webm"); |
| EXPECT_TRUE( |
| media_recorder_handler_->CanSupportMimeType(mime_type_audio, String())); |
| const String example_good_codecs_5("opus"); |
| EXPECT_TRUE(media_recorder_handler_->CanSupportMimeType( |
| mime_type_audio, example_good_codecs_5)); |
| const String example_good_codecs_6("OpUs"); |
| EXPECT_TRUE(media_recorder_handler_->CanSupportMimeType( |
| mime_type_audio, example_good_codecs_6)); |
| const String example_good_codecs_7("pcm"); |
| EXPECT_TRUE(media_recorder_handler_->CanSupportMimeType( |
| mime_type_audio, example_good_codecs_7)); |
| |
| const String example_unsupported_codecs_2("vorbis"); |
| EXPECT_FALSE(media_recorder_handler_->CanSupportMimeType( |
| mime_type_audio, example_unsupported_codecs_2)); |
| } |
| |
| // Checks that it uses the specified bitrate mode. |
| TEST_P(MediaRecorderHandlerTest, SupportsBitrateMode) { |
| AddTracks(); |
| V8TestingScope scope; |
| auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope); |
| |
| const String mime_type(GetParam().mime_type); |
| const String codecs(GetParam().codecs); |
| |
| EXPECT_TRUE(media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), mime_type, codecs, 0, 0, |
| AudioTrackRecorder::BitrateMode::VARIABLE)); |
| EXPECT_EQ(media_recorder_handler_->AudioBitrateMode(), |
| AudioTrackRecorder::BitrateMode::VARIABLE); |
| |
| EXPECT_TRUE(media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), mime_type, codecs, 0, 0, |
| AudioTrackRecorder::BitrateMode::CONSTANT)); |
| EXPECT_EQ(media_recorder_handler_->AudioBitrateMode(), |
| AudioTrackRecorder::BitrateMode::CONSTANT); |
| } |
| |
| // Checks that the initialization-destruction sequence works fine. |
| TEST_P(MediaRecorderHandlerTest, InitializeStartStop) { |
| AddTracks(); |
| V8TestingScope scope; |
| auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope); |
| const String mime_type(GetParam().mime_type); |
| const String codecs(GetParam().codecs); |
| EXPECT_TRUE(media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), mime_type, codecs, 0, 0, |
| AudioTrackRecorder::BitrateMode::VARIABLE)); |
| EXPECT_FALSE(recording()); |
| EXPECT_FALSE(hasVideoRecorders()); |
| EXPECT_FALSE(hasAudioRecorders()); |
| |
| EXPECT_TRUE(media_recorder_handler_->Start(0)); |
| EXPECT_TRUE(recording()); |
| |
| EXPECT_TRUE(hasVideoRecorders() || !GetParam().has_video); |
| EXPECT_TRUE(hasAudioRecorders() || !GetParam().has_audio); |
| |
| media_recorder_handler_->Stop(); |
| EXPECT_FALSE(recording()); |
| EXPECT_FALSE(hasVideoRecorders()); |
| EXPECT_FALSE(hasAudioRecorders()); |
| |
| // Expect a last call on destruction. |
| EXPECT_CALL(*recorder, WriteData(_, _, true, _)).Times(1); |
| media_recorder_handler_ = nullptr; |
| } |
| |
| // Sends 2 opaque frames and 1 transparent frame and expects them as WebM |
| // contained encoded data in writeData(). |
| TEST_P(MediaRecorderHandlerTest, EncodeVideoFrames) { |
| // Video-only test. |
| if (GetParam().has_audio) |
| return; |
| |
| AddTracks(); |
| |
| V8TestingScope scope; |
| auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope); |
| const String mime_type(GetParam().mime_type); |
| const String codecs(GetParam().codecs); |
| EXPECT_TRUE(media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), mime_type, codecs, 0, 0, |
| AudioTrackRecorder::BitrateMode::VARIABLE)); |
| EXPECT_TRUE(media_recorder_handler_->Start(0)); |
| |
| InSequence s; |
| const scoped_refptr<media::VideoFrame> video_frame = |
| media::VideoFrame::CreateBlackFrame(gfx::Size(160, 80)); |
| |
| { |
| const size_t kEncodedSizeThreshold = 16; |
| base::RunLoop run_loop; |
| // writeData() is pinged a number of times as the WebM header is written; |
| // the last time it is called it has the encoded data. |
| EXPECT_CALL(*recorder, WriteData(_, Lt(kEncodedSizeThreshold), _, _)) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(*recorder, WriteData(_, Gt(kEncodedSizeThreshold), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceClosure(run_loop.QuitClosure())); |
| |
| OnVideoFrameForTesting(video_frame); |
| run_loop.Run(); |
| } |
| Mock::VerifyAndClearExpectations(this); |
| |
| { |
| const size_t kEncodedSizeThreshold = 12; |
| base::RunLoop run_loop; |
| // The second time around writeData() is called a number of times to write |
| // the WebM frame header, and then is pinged with the encoded data. |
| EXPECT_CALL(*recorder, WriteData(_, Lt(kEncodedSizeThreshold), _, _)) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(*recorder, WriteData(_, Gt(kEncodedSizeThreshold), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceClosure(run_loop.QuitClosure())); |
| |
| OnVideoFrameForTesting(video_frame); |
| run_loop.Run(); |
| } |
| Mock::VerifyAndClearExpectations(this); |
| |
| { |
| const scoped_refptr<media::VideoFrame> alpha_frame = |
| media::VideoFrame::CreateTransparentFrame(gfx::Size(160, 80)); |
| const size_t kEncodedSizeThreshold = 16; |
| EXPECT_EQ(4u, media::VideoFrame::NumPlanes(alpha_frame->format())); |
| base::RunLoop run_loop; |
| // The second time around writeData() is called a number of times to write |
| // the WebM frame header, and then is pinged with the encoded data. |
| EXPECT_CALL(*recorder, WriteData(_, Lt(kEncodedSizeThreshold), _, _)) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(*recorder, WriteData(_, Gt(kEncodedSizeThreshold), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceClosure(run_loop.QuitClosure())); |
| if (GetParam().encoder_supports_alpha) { |
| EXPECT_CALL(*recorder, WriteData(_, Lt(kEncodedSizeThreshold), _, _)) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(*recorder, WriteData(_, Gt(kEncodedSizeThreshold), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceClosure(run_loop.QuitClosure())); |
| } |
| |
| OnVideoFrameForTesting(alpha_frame); |
| run_loop.Run(); |
| } |
| |
| media_recorder_handler_->Stop(); |
| |
| // Expect a last call on destruction. |
| EXPECT_CALL(*recorder, WriteData(_, _, true, _)).Times(1); |
| media_recorder_handler_ = nullptr; |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| MediaRecorderHandlerTest, |
| ValuesIn(kMediaRecorderTestParams)); |
| |
| // Sends 2 frames and expect them as WebM (or MKV) contained encoded audio data |
| // in writeData(). |
| TEST_P(MediaRecorderHandlerTest, OpusEncodeAudioFrames) { |
| // Audio-only test. |
| if (GetParam().has_video) |
| return; |
| |
| AddTracks(); |
| |
| V8TestingScope scope; |
| auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope); |
| |
| const String mime_type(GetParam().mime_type); |
| const String codecs(GetParam().codecs); |
| EXPECT_TRUE(media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), mime_type, codecs, 0, 0, |
| AudioTrackRecorder::BitrateMode::VARIABLE)); |
| EXPECT_TRUE(media_recorder_handler_->Start(0)); |
| |
| InSequence s; |
| const std::unique_ptr<media::AudioBus> audio_bus1 = NextAudioBus(); |
| const std::unique_ptr<media::AudioBus> audio_bus2 = NextAudioBus(); |
| |
| media::AudioParameters params( |
| media::AudioParameters::AUDIO_PCM_LINEAR, media::CHANNEL_LAYOUT_STEREO, |
| kTestAudioSampleRate, |
| kTestAudioSampleRate * kTestAudioBufferDurationMs / 1000); |
| SetAudioFormatForTesting(params); |
| |
| const size_t kEncodedSizeThreshold = 24; |
| { |
| base::RunLoop run_loop; |
| // writeData() is pinged a number of times as the WebM header is written; |
| // the last time it is called it has the encoded data. |
| EXPECT_CALL(*recorder, WriteData(_, Lt(kEncodedSizeThreshold), _, _)) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(*recorder, WriteData(_, Gt(kEncodedSizeThreshold), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceClosure(run_loop.QuitClosure())); |
| |
| for (int i = 0; i < kRatioOpusToTestAudioBuffers; ++i) |
| OnAudioBusForTesting(*audio_bus1); |
| run_loop.Run(); |
| } |
| Mock::VerifyAndClearExpectations(this); |
| |
| { |
| base::RunLoop run_loop; |
| // The second time around writeData() is called a number of times to write |
| // the WebM frame header, and then is pinged with the encoded data. |
| EXPECT_CALL(*recorder, WriteData(_, Lt(kEncodedSizeThreshold), _, _)) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(*recorder, WriteData(_, Gt(kEncodedSizeThreshold), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceClosure(run_loop.QuitClosure())); |
| |
| for (int i = 0; i < kRatioOpusToTestAudioBuffers; ++i) |
| OnAudioBusForTesting(*audio_bus2); |
| run_loop.Run(); |
| } |
| |
| media_recorder_handler_->Stop(); |
| |
| // Expect a last call on destruction. |
| EXPECT_CALL(*recorder, WriteData(_, _, true, _)).Times(1); |
| media_recorder_handler_ = nullptr; |
| } |
| |
| // Starts up recording and forces a WebmMuxer's libwebm error. |
| TEST_P(MediaRecorderHandlerTest, WebmMuxerErrorWhileEncoding) { |
| // Video-only test: Audio would be very similar. |
| if (GetParam().has_audio) |
| return; |
| |
| AddTracks(); |
| |
| V8TestingScope scope; |
| auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope); |
| |
| const String mime_type(GetParam().mime_type); |
| const String codecs(GetParam().codecs); |
| EXPECT_TRUE(media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), mime_type, codecs, 0, 0, |
| AudioTrackRecorder::BitrateMode::VARIABLE)); |
| EXPECT_TRUE(media_recorder_handler_->Start(0)); |
| |
| InSequence s; |
| const scoped_refptr<media::VideoFrame> video_frame = |
| media::VideoFrame::CreateBlackFrame(gfx::Size(160, 80)); |
| |
| { |
| const size_t kEncodedSizeThreshold = 16; |
| base::RunLoop run_loop; |
| EXPECT_CALL(*recorder, WriteData(_, _, _, _)).Times(AtLeast(1)); |
| EXPECT_CALL(*recorder, WriteData(_, Gt(kEncodedSizeThreshold), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceClosure(run_loop.QuitClosure())); |
| |
| OnVideoFrameForTesting(video_frame); |
| run_loop.Run(); |
| } |
| |
| ForceOneErrorInWebmMuxer(); |
| |
| { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*recorder, WriteData(_, _, _, _)).Times(0); |
| EXPECT_CALL(*recorder, OnError(_)) |
| .Times(1) |
| .WillOnce(RunOnceClosure(run_loop.QuitClosure())); |
| |
| OnVideoFrameForTesting(video_frame); |
| run_loop.Run(); |
| } |
| |
| // Expect a last call on destruction. |
| EXPECT_CALL(*recorder, WriteData(_, _, true, _)).Times(1); |
| media_recorder_handler_ = nullptr; |
| } |
| |
| // Checks the ActualMimeType() versus the expected. |
| TEST_P(MediaRecorderHandlerTest, ActualMimeType) { |
| AddTracks(); |
| |
| V8TestingScope scope; |
| auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope); |
| |
| const String mime_type(GetParam().mime_type); |
| const String codecs(GetParam().codecs); |
| EXPECT_TRUE(media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), mime_type, codecs, 0, 0, |
| AudioTrackRecorder::BitrateMode::VARIABLE)); |
| |
| StringBuilder actual_mime_type; |
| actual_mime_type.Append(GetParam().mime_type); |
| actual_mime_type.Append(";codecs="); |
| if (strlen(GetParam().codecs) != 0u) |
| actual_mime_type.Append(GetParam().codecs); |
| else if (GetParam().has_video) |
| actual_mime_type.Append("vp8"); |
| else if (GetParam().has_audio) |
| actual_mime_type.Append("opus"); |
| |
| EXPECT_EQ(media_recorder_handler_->ActualMimeType(), |
| actual_mime_type.ToString()); |
| |
| // Expect a last call on destruction. |
| EXPECT_CALL(*recorder, WriteData(_, _, true, _)).Times(1); |
| media_recorder_handler_ = nullptr; |
| } |
| |
| TEST_P(MediaRecorderHandlerTest, PauseRecorderForVideo) { |
| // Video-only test: Audio would be very similar. |
| if (GetParam().has_audio) |
| return; |
| |
| AddTracks(); |
| |
| V8TestingScope scope; |
| auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope); |
| |
| const String mime_type(GetParam().mime_type); |
| const String codecs(GetParam().codecs); |
| |
| EXPECT_TRUE(media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), mime_type, codecs, 0, 0, |
| AudioTrackRecorder::BitrateMode::VARIABLE)); |
| EXPECT_TRUE(media_recorder_handler_->Start(0)); |
| |
| Mock::VerifyAndClearExpectations(recorder); |
| media_recorder_handler_->Pause(); |
| |
| EXPECT_CALL(*recorder, WriteData).Times(AtLeast(1)); |
| media::WebmMuxer::VideoParameters params(gfx::Size(), 1, media::kCodecVP9, |
| gfx::ColorSpace()); |
| OnEncodedVideoForTesting(params, "vp9 frame", "", base::TimeTicks::Now(), |
| true); |
| |
| // Expect a last call on destruction. |
| EXPECT_CALL(*recorder, WriteData(_, _, true, _)).Times(1); |
| media_recorder_handler_ = nullptr; |
| } |
| |
| TEST_P(MediaRecorderHandlerTest, StartStopStartRecorderForVideo) { |
| // Video-only test: Audio would be very similar. |
| if (GetParam().has_audio) |
| return; |
| |
| AddTracks(); |
| |
| V8TestingScope scope; |
| auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope); |
| |
| const String mime_type(GetParam().mime_type); |
| const String codecs(GetParam().codecs); |
| |
| EXPECT_TRUE(media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), mime_type, codecs, 0, 0, |
| AudioTrackRecorder::BitrateMode::VARIABLE)); |
| EXPECT_TRUE(media_recorder_handler_->Start(0)); |
| media_recorder_handler_->Stop(); |
| |
| Mock::VerifyAndClearExpectations(recorder); |
| EXPECT_TRUE(media_recorder_handler_->Start(0)); |
| |
| EXPECT_CALL(*recorder, WriteData).Times(AtLeast(1)); |
| media::WebmMuxer::VideoParameters params(gfx::Size(), 1, media::kCodecVP9, |
| gfx::ColorSpace()); |
| OnEncodedVideoForTesting(params, "vp9 frame", "", base::TimeTicks::Now(), |
| true); |
| |
| // Expect a last call on destruction. |
| EXPECT_CALL(*recorder, WriteData(_, _, true, _)).Times(1); |
| media_recorder_handler_ = nullptr; |
| } |
| |
| #if BUILDFLAG(RTC_USE_H264) |
| |
| struct H264ProfileTestParams { |
| const bool has_audio; |
| const char* const mime_type; |
| const char* const codecs; |
| }; |
| |
| static const H264ProfileTestParams kH264ProfileTestParams[] = { |
| {false, "video/x-matroska", "avc1.42000c"}, // H264PROFILE_BASELINE |
| {false, "video/x-matroska", "avc1.4d000c"}, // H264PROFILE_MAIN |
| {false, "video/x-matroska", "avc1.64000c"}, // H264PROFILE_HIGH |
| {false, "video/x-matroska", "avc1.640029"}, |
| {false, "video/x-matroska", "avc1.640034"}, |
| {true, "video/x-matroska", "avc1.64000c,pcm"}, |
| }; |
| |
| class MediaRecorderHandlerH264ProfileTest |
| : public TestWithParam<H264ProfileTestParams>, |
| public MediaRecorderHandlerFixture { |
| public: |
| MediaRecorderHandlerH264ProfileTest() |
| : MediaRecorderHandlerFixture(true, GetParam().has_audio) {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MediaRecorderHandlerH264ProfileTest); |
| }; |
| |
| TEST_P(MediaRecorderHandlerH264ProfileTest, ActualMimeType) { |
| AddTracks(); |
| |
| V8TestingScope scope; |
| auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope); |
| |
| const String mime_type(GetParam().mime_type); |
| const String codecs(GetParam().codecs); |
| EXPECT_TRUE(media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), mime_type, codecs, 0, 0, |
| AudioTrackRecorder::BitrateMode::VARIABLE)); |
| |
| String actual_mime_type = |
| String(GetParam().mime_type) + ";codecs=" + GetParam().codecs; |
| |
| EXPECT_EQ(media_recorder_handler_->ActualMimeType(), actual_mime_type); |
| |
| media_recorder_handler_ = nullptr; |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| MediaRecorderHandlerH264ProfileTest, |
| ValuesIn(kH264ProfileTestParams)); |
| |
| #endif |
| |
| struct MediaRecorderPassthroughTestParams { |
| const char* mime_type; |
| media::VideoCodec codec; |
| }; |
| |
| static const MediaRecorderPassthroughTestParams |
| kMediaRecorderPassthroughTestParams[] = { |
| {"video/webm;codecs=vp8", media::kCodecVP8}, |
| {"video/webm;codecs=vp9", media::kCodecVP9}, |
| #if BUILDFLAG(RTC_USE_H264) |
| {"video/x-matroska;codecs=avc1", media::kCodecH264}, |
| #endif |
| }; |
| |
| class MediaRecorderHandlerPassthroughTest |
| : public TestWithParam<MediaRecorderPassthroughTestParams>, |
| public ScopedMockOverlayScrollbars { |
| public: |
| MediaRecorderHandlerPassthroughTest() { |
| registry_.Init(); |
| video_source_ = registry_.AddVideoTrack(kTestVideoTrackId); |
| ON_CALL(*video_source_, SupportsEncodedOutput).WillByDefault(Return(true)); |
| media_recorder_handler_ = MakeGarbageCollected<MediaRecorderHandler>( |
| scheduler::GetSingleThreadTaskRunnerForTesting()); |
| EXPECT_FALSE(media_recorder_handler_->recording_); |
| } |
| |
| ~MediaRecorderHandlerPassthroughTest() { |
| registry_.reset(); |
| media_recorder_handler_ = nullptr; |
| WebHeap::CollectAllGarbageForTesting(); |
| } |
| |
| void OnVideoFrameForTesting(scoped_refptr<EncodedVideoFrame> frame) { |
| media_recorder_handler_->OnEncodedVideoFrameForTesting( |
| std::move(frame), base::TimeTicks::Now()); |
| } |
| |
| ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_; |
| MockMediaStreamRegistry registry_; |
| MockMediaStreamVideoSource* video_source_ = nullptr; |
| Persistent<MediaRecorderHandler> media_recorder_handler_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MediaRecorderHandlerPassthroughTest); |
| }; |
| |
| TEST_P(MediaRecorderHandlerPassthroughTest, PassesThrough) { |
| // Setup the mock video source to allow for passthrough recording. |
| EXPECT_CALL(*video_source_, OnEncodedSinkEnabled); |
| EXPECT_CALL(*video_source_, OnEncodedSinkDisabled); |
| |
| V8TestingScope scope; |
| auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope); |
| media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), "", "", 0, 0, |
| AudioTrackRecorder::BitrateMode::VARIABLE); |
| media_recorder_handler_->Start(0); |
| |
| const size_t kFrameSize = 42; |
| auto frame = FakeEncodedVideoFrame::Builder() |
| .WithKeyFrame(true) |
| .WithCodec(GetParam().codec) |
| .WithData(std::string(kFrameSize, 'P')) |
| .BuildRefPtr(); |
| { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*recorder, WriteData(_, _, _, _)).Times(AtLeast(1)); |
| EXPECT_CALL(*recorder, WriteData(_, Ge(kFrameSize), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceClosure(run_loop.QuitClosure())); |
| OnVideoFrameForTesting(frame); |
| run_loop.Run(); |
| } |
| EXPECT_EQ(media_recorder_handler_->ActualMimeType(), GetParam().mime_type); |
| Mock::VerifyAndClearExpectations(this); |
| |
| media_recorder_handler_->Stop(); |
| |
| // Expect a last call on destruction. |
| EXPECT_CALL(*recorder, WriteData(_, _, true, _)).Times(1); |
| media_recorder_handler_ = nullptr; |
| } |
| |
| TEST_F(MediaRecorderHandlerPassthroughTest, ErrorsOutOnCodecSwitch) { |
| V8TestingScope scope; |
| auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope); |
| EXPECT_TRUE(media_recorder_handler_->Initialize( |
| recorder, registry_.test_stream(), "", "", 0, 0, |
| AudioTrackRecorder::BitrateMode::VARIABLE)); |
| EXPECT_TRUE(media_recorder_handler_->Start(0)); |
| |
| // NOTE, Asan: the prototype of WriteData which has a const char* as data |
| // ptr plays badly with gmock which tries to interpret it as a null-terminated |
| // string. However, it points to binary data which causes gmock to overrun the |
| // bounds of buffers and this manifests as an ASAN crash. |
| // The expectation here works around this issue. |
| EXPECT_CALL(*recorder, WriteData).Times(AtLeast(1)); |
| |
| EXPECT_CALL(*recorder, OnError).WillOnce(Invoke([&](const String&) { |
| // Simulate MediaRecorder behavior which is to Stop() the handler on error. |
| media_recorder_handler_->Stop(); |
| })); |
| OnVideoFrameForTesting(FakeEncodedVideoFrame::Builder() |
| .WithKeyFrame(true) |
| .WithCodec(media::kCodecVP8) |
| .WithData(std::string("vp8 frame")) |
| .BuildRefPtr()); |
| // Switch to VP9 frames. This is expected to cause the call to OnError |
| // above. |
| OnVideoFrameForTesting(FakeEncodedVideoFrame::Builder() |
| .WithKeyFrame(true) |
| .WithCodec(media::kCodecVP9) |
| .WithData(std::string("vp9 frame")) |
| .BuildRefPtr()); |
| // Send one more frame to verify that continued frame of different codec |
| // transfer doesn't crash the media recorder. |
| OnVideoFrameForTesting(FakeEncodedVideoFrame::Builder() |
| .WithKeyFrame(true) |
| .WithCodec(media::kCodecVP8) |
| .WithData(std::string("vp8 frame")) |
| .BuildRefPtr()); |
| platform_->RunUntilIdle(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| MediaRecorderHandlerPassthroughTest, |
| ValuesIn(kMediaRecorderPassthroughTestParams)); |
| |
| } // namespace blink |