From e75f204b06d597203c7eefd583aeb7820f32344b Mon Sep 17 00:00:00 2001 From: palmkvist Date: Wed, 28 Sep 2016 06:19:48 -0700 Subject: [PATCH] Expose Ivf logging through the native API BUG=webrtc:6300 Review-Url: https://codereview.webrtc.org/2303273002 Cr-Commit-Position: refs/heads/master@{#14419} --- webrtc/media/engine/fakewebrtccall.cc | 13 +++ webrtc/media/engine/fakewebrtccall.h | 9 +- .../video_coding/utility/ivf_file_writer.cc | 98 +++++++++------- .../video_coding/utility/ivf_file_writer.h | 28 +++-- .../utility/ivf_file_writer_unittest.cc | 106 +++++++----------- webrtc/test/fake_encoder.cc | 7 +- webrtc/video/end_to_end_tests.cc | 102 +++++++++++++++++ webrtc/video/screenshare_loopback.cc | 29 ++++- webrtc/video/video_loopback.cc | 29 ++++- webrtc/video/video_quality_test.cc | 36 +++++- webrtc/video/video_quality_test.h | 7 ++ webrtc/video/video_receive_stream.cc | 34 ++++-- webrtc/video/video_receive_stream.h | 11 +- webrtc/video/video_send_stream.cc | 67 +++++++---- webrtc/video/video_send_stream.h | 10 ++ webrtc/video_receive_stream.h | 12 ++ webrtc/video_send_stream.h | 18 +++ 17 files changed, 449 insertions(+), 167 deletions(-) diff --git a/webrtc/media/engine/fakewebrtccall.cc b/webrtc/media/engine/fakewebrtccall.cc index 9670f13710..9515505af2 100644 --- a/webrtc/media/engine/fakewebrtccall.cc +++ b/webrtc/media/engine/fakewebrtccall.cc @@ -15,6 +15,7 @@ #include "webrtc/api/call/audio_sink.h" #include "webrtc/base/checks.h" +#include "webrtc/base/platform_file.h" #include "webrtc/base/gunit.h" #include "webrtc/media/base/rtputils.h" @@ -182,6 +183,13 @@ webrtc::VideoSendStream::Stats FakeVideoSendStream::GetStats() { return stats_; } +void FakeVideoSendStream::EnableEncodedFrameRecording( + const std::vector& files, + size_t byte_limit) { + for (rtc::PlatformFile file : files) + rtc::ClosePlatformFile(file); +} + void FakeVideoSendStream::ReconfigureVideoEncoder( webrtc::VideoEncoderConfig config) { if (config.encoder_specific_settings != NULL) { @@ -258,6 +266,11 @@ void FakeVideoReceiveStream::SetStats( stats_ = stats; } +void FakeVideoReceiveStream::EnableEncodedFrameRecording(rtc::PlatformFile file, + size_t byte_limit) { + rtc::ClosePlatformFile(file); +} + FakeCall::FakeCall(const webrtc::Call::Config& config) : config_(config), audio_network_state_(webrtc::kNetworkUp), diff --git a/webrtc/media/engine/fakewebrtccall.h b/webrtc/media/engine/fakewebrtccall.h index 29142c18b7..170e6fce91 100644 --- a/webrtc/media/engine/fakewebrtccall.h +++ b/webrtc/media/engine/fakewebrtccall.h @@ -21,6 +21,7 @@ #define WEBRTC_MEDIA_ENGINE_FAKEWEBRTCCALL_H_ #include +#include #include #include "webrtc/api/call/audio_receive_stream.h" @@ -123,6 +124,9 @@ class FakeVideoSendStream final return num_encoder_reconfigurations_; } + void EnableEncodedFrameRecording(const std::vector& files, + size_t byte_limit) override; + private: // rtc::VideoSinkInterface implementation. void OnFrame(const webrtc::VideoFrame& frame) override; @@ -162,6 +166,9 @@ class FakeVideoReceiveStream final : public webrtc::VideoReceiveStream { void SetStats(const webrtc::VideoReceiveStream::Stats& stats); + void EnableEncodedFrameRecording(rtc::PlatformFile file, + size_t byte_limit) override; + private: // webrtc::VideoReceiveStream implementation. void Start() override; @@ -257,4 +264,4 @@ class FakeCall final : public webrtc::Call, public webrtc::PacketReceiver { }; } // namespace cricket -#endif // TALK_MEDIA_WEBRTC_WEBRTCVIDEOENGINE2_UNITTEST_H_ +#endif // WEBRTC_MEDIA_ENGINE_FAKEWEBRTCCALL_H_ diff --git a/webrtc/modules/video_coding/utility/ivf_file_writer.cc b/webrtc/modules/video_coding/utility/ivf_file_writer.cc index a80cf9bf6b..4198dfea49 100644 --- a/webrtc/modules/video_coding/utility/ivf_file_writer.cc +++ b/webrtc/modules/video_coding/utility/ivf_file_writer.cc @@ -10,48 +10,47 @@ #include "webrtc/modules/video_coding/utility/ivf_file_writer.h" +#include +#include + #include "webrtc/base/checks.h" #include "webrtc/base/logging.h" #include "webrtc/modules/rtp_rtcp/source/byte_io.h" +// TODO(palmkvist): make logging more informative in the absence of a file name +// (or get one) + namespace webrtc { -IvfFileWriter::IvfFileWriter(const std::string& file_name, - std::unique_ptr file, - VideoCodecType codec_type) - : codec_type_(codec_type), +const size_t kIvfHeaderSize = 32; + +IvfFileWriter::IvfFileWriter(rtc::File file, size_t byte_limit) + : codec_type_(kVideoCodecUnknown), + bytes_written_(0), + byte_limit_(byte_limit), num_frames_(0), width_(0), height_(0), last_timestamp_(-1), using_capture_timestamps_(false), - file_name_(file_name), - file_(std::move(file)) {} + file_(std::move(file)) { + RTC_DCHECK(byte_limit == 0 || kIvfHeaderSize <= byte_limit) + << "The byte_limit is too low, not even the header will fit."; +} IvfFileWriter::~IvfFileWriter() { Close(); } -const size_t kIvfHeaderSize = 32; - -std::unique_ptr IvfFileWriter::Open(const std::string& file_name, - VideoCodecType codec_type) { - std::unique_ptr file_writer; - std::unique_ptr file(FileWrapper::Create()); - if (!file->OpenFile(file_name.c_str(), false)) - return file_writer; - - file_writer.reset(new IvfFileWriter( - file_name, std::unique_ptr(std::move(file)), codec_type)); - if (!file_writer->WriteHeader()) - file_writer.reset(); - - return file_writer; +std::unique_ptr IvfFileWriter::Wrap(rtc::File file, + size_t byte_limit) { + return std::unique_ptr( + new IvfFileWriter(std::move(file), byte_limit)); } bool IvfFileWriter::WriteHeader() { - if (file_->Rewind() != 0) { - LOG(LS_WARNING) << "Unable to rewind output file " << file_name_; + if (!file_.Seek(0)) { + LOG(LS_WARNING) << "Unable to rewind ivf output file."; return false; } @@ -98,21 +97,28 @@ bool IvfFileWriter::WriteHeader() { static_cast(num_frames_)); ByteWriter::WriteLittleEndian(&ivf_header[28], 0); // Reserved. - if (!file_->Write(ivf_header, kIvfHeaderSize)) { - LOG(LS_ERROR) << "Unable to write IVF header for file " << file_name_; + if (file_.Write(ivf_header, kIvfHeaderSize) < kIvfHeaderSize) { + LOG(LS_ERROR) << "Unable to write IVF header for ivf output file."; return false; } + if (bytes_written_ < kIvfHeaderSize) { + bytes_written_ = kIvfHeaderSize; + } + return true; } -bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image) { +bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image, + VideoCodecType codec_type) { width_ = encoded_image._encodedWidth; height_ = encoded_image._encodedHeight; RTC_CHECK_GT(width_, 0); RTC_CHECK_GT(height_, 0); using_capture_timestamps_ = encoded_image._timeStamp == 0; + codec_type_ = codec_type; + if (!WriteHeader()) return false; @@ -130,20 +136,22 @@ bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image) { default: codec_name = "Unknown"; } - LOG(LS_WARNING) << "Created IVF file " << file_name_ - << " for codec data of type " << codec_name + LOG(LS_WARNING) << "Created IVF file for codec data of type " << codec_name << " at resolution " << width_ << " x " << height_ << ", using " << (using_capture_timestamps_ ? "1" : "90") << "kHz clock resolution."; return true; } -bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image) { - RTC_DCHECK(file_->is_open()); - - if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image)) +bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image, + VideoCodecType codec_type) { + if (!file_.IsOpen()) return false; + if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image, codec_type)) + return false; + RTC_DCHECK_EQ(codec_type_, codec_type); + if ((encoded_image._encodedWidth > 0 || encoded_image._encodedHeight > 0) && (encoded_image._encodedHeight != height_ || encoded_image._encodedWidth != width_)) { @@ -163,35 +171,41 @@ bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image) { last_timestamp_ = timestamp; const size_t kFrameHeaderSize = 12; + if (byte_limit_ != 0 && + bytes_written_ + kFrameHeaderSize + encoded_image._length > byte_limit_) { + LOG(LS_WARNING) << "Closing IVF file due to reaching size limit: " + << byte_limit_ << " bytes."; + Close(); + return false; + } uint8_t frame_header[kFrameHeaderSize] = {}; ByteWriter::WriteLittleEndian( &frame_header[0], static_cast(encoded_image._length)); ByteWriter::WriteLittleEndian(&frame_header[4], timestamp); - if (!file_->Write(frame_header, kFrameHeaderSize) || - !file_->Write(encoded_image._buffer, encoded_image._length)) { - LOG(LS_ERROR) << "Unable to write frame to file " << file_name_; + if (file_.Write(frame_header, kFrameHeaderSize) < kFrameHeaderSize || + file_.Write(encoded_image._buffer, encoded_image._length) < + encoded_image._length) { + LOG(LS_ERROR) << "Unable to write frame to file."; return false; } + bytes_written_ += kFrameHeaderSize + encoded_image._length; + ++num_frames_; return true; } bool IvfFileWriter::Close() { - if (!file_->is_open()) + if (!file_.IsOpen()) return false; if (num_frames_ == 0) { - // No frame written to file, close and remove it entirely if possible. - file_->CloseFile(); - if (remove(file_name_.c_str()) != 0) - LOG(LS_WARNING) << "Failed to remove empty IVF file " << file_name_; - + file_.Close(); return true; } bool ret = WriteHeader(); - file_->CloseFile(); + file_.Close(); return ret; } diff --git a/webrtc/modules/video_coding/utility/ivf_file_writer.h b/webrtc/modules/video_coding/utility/ivf_file_writer.h index 25d68a2803..b556111aa5 100644 --- a/webrtc/modules/video_coding/utility/ivf_file_writer.h +++ b/webrtc/modules/video_coding/utility/ivf_file_writer.h @@ -15,38 +15,42 @@ #include #include "webrtc/base/constructormagic.h" +#include "webrtc/base/file.h" #include "webrtc/base/timeutils.h" #include "webrtc/modules/include/module_common_types.h" #include "webrtc/video_frame.h" -#include "webrtc/system_wrappers/include/file_wrapper.h" namespace webrtc { class IvfFileWriter { public: + // Takes ownership of the file, which will be closed either through + // Close or ~IvfFileWriter. If writing a frame would take the file above the + // |byte_limit| the file will be closed, the write (and all future writes) + // will fail. A |byte_limit| of 0 is equivalent to no limit. + static std::unique_ptr Wrap(rtc::File file, size_t byte_limit); ~IvfFileWriter(); - static std::unique_ptr Open(const std::string& file_name, - VideoCodecType codec_type); - bool WriteFrame(const EncodedImage& encoded_image); + bool WriteFrame(const EncodedImage& encoded_image, VideoCodecType codec_type); bool Close(); private: - IvfFileWriter(const std::string& path_name, - std::unique_ptr file, - VideoCodecType codec_type); - bool WriteHeader(); - bool InitFromFirstFrame(const EncodedImage& encoded_image); + explicit IvfFileWriter(rtc::File file, size_t byte_limit); - const VideoCodecType codec_type_; + bool WriteHeader(); + bool InitFromFirstFrame(const EncodedImage& encoded_image, + VideoCodecType codec_type); + + VideoCodecType codec_type_; + size_t bytes_written_; + size_t byte_limit_; size_t num_frames_; uint16_t width_; uint16_t height_; int64_t last_timestamp_; bool using_capture_timestamps_; rtc::TimestampWrapAroundHandler wrap_handler_; - const std::string file_name_; - std::unique_ptr file_; + rtc::File file_; RTC_DISALLOW_COPY_AND_ASSIGN(IvfFileWriter); }; diff --git a/webrtc/modules/video_coding/utility/ivf_file_writer_unittest.cc b/webrtc/modules/video_coding/utility/ivf_file_writer_unittest.cc index f22aff78cd..e5da48c659 100644 --- a/webrtc/modules/video_coding/utility/ivf_file_writer_unittest.cc +++ b/webrtc/modules/video_coding/utility/ivf_file_writer_unittest.cc @@ -26,23 +26,18 @@ namespace { static const int kHeaderSize = 32; static const int kFrameHeaderSize = 12; static uint8_t dummy_payload[4] = {0, 1, 2, 3}; -static const int kMaxFileRetries = 5; } // namespace class IvfFileWriterTest : public ::testing::Test { protected: void SetUp() override { - const uint64_t start_id = rtc::CreateRandomId64(); - uint64_t id = start_id; - do { - std::ostringstream oss; - oss << test::OutputPath() << "ivf_test_file_" << id++ << ".ivf"; - file_name_ = oss.str(); - } while ((id - start_id) < 100u && FileExists(false)); - ASSERT_LT(id - start_id, 100u); + file_name_ = + webrtc::test::TempFilename(webrtc::test::OutputPath(), "test_file"); } + void TearDown() override { rtc::RemoveFile(file_name_); } - bool WriteDummyTestFrames(int width, + bool WriteDummyTestFrames(VideoCodecType codec_type, + int width, int height, int num_frames, bool use_capture_tims_ms) { @@ -57,21 +52,21 @@ class IvfFileWriterTest : public ::testing::Test { } else { frame._timeStamp = i; } - if (!file_writer_->WriteFrame(frame)) + if (!file_writer_->WriteFrame(frame, codec_type)) return false; } return true; } - void VerifyIvfHeader(FileWrapper* file, + void VerifyIvfHeader(rtc::File* file, const uint8_t fourcc[4], int width, int height, uint32_t num_frames, bool use_capture_tims_ms) { - ASSERT_TRUE(file->is_open()); + ASSERT_TRUE(file->IsOpen()); uint8_t data[kHeaderSize]; - ASSERT_EQ(kHeaderSize, file->Read(data, kHeaderSize)); + ASSERT_EQ(static_cast(kHeaderSize), file->Read(data, kHeaderSize)); uint8_t dkif[4] = {'D', 'K', 'I', 'F'}; EXPECT_EQ(0, memcmp(dkif, data, 4)); @@ -87,11 +82,12 @@ class IvfFileWriterTest : public ::testing::Test { EXPECT_EQ(0u, ByteReader::ReadLittleEndian(&data[28])); } - void VerifyDummyTestFrames(FileWrapper* file, uint32_t num_frames) { + void VerifyDummyTestFrames(rtc::File* file, uint32_t num_frames) { const int kMaxFrameSize = 4; for (uint32_t i = 1; i <= num_frames; ++i) { uint8_t frame_header[kFrameHeaderSize]; - ASSERT_EQ(kFrameHeaderSize, file->Read(frame_header, kFrameHeaderSize)); + ASSERT_EQ(static_cast(kFrameHeaderSize), + file->Read(frame_header, kFrameHeaderSize)); uint32_t frame_length = ByteReader::ReadLittleEndian(&frame_header[0]); EXPECT_EQ(i % 4, frame_length); @@ -109,67 +105,27 @@ class IvfFileWriterTest : public ::testing::Test { void RunBasicFileStructureTest(VideoCodecType codec_type, const uint8_t fourcc[4], bool use_capture_tims_ms) { - file_writer_ = IvfFileWriter::Open(file_name_, codec_type); + file_writer_ = IvfFileWriter::Wrap(rtc::File::Open(file_name_), 0); ASSERT_TRUE(file_writer_.get()); const int kWidth = 320; const int kHeight = 240; const int kNumFrames = 257; - EXPECT_TRUE( - WriteDummyTestFrames(kWidth, kHeight, kNumFrames, use_capture_tims_ms)); + ASSERT_TRUE(WriteDummyTestFrames(codec_type, kWidth, kHeight, kNumFrames, + use_capture_tims_ms)); EXPECT_TRUE(file_writer_->Close()); - std::unique_ptr out_file(FileWrapper::Create()); - ASSERT_TRUE(out_file->OpenFile(file_name_.c_str(), true)); - VerifyIvfHeader(out_file.get(), fourcc, kWidth, kHeight, kNumFrames, + rtc::File out_file = rtc::File::Open(file_name_); + VerifyIvfHeader(&out_file, fourcc, kWidth, kHeight, kNumFrames, use_capture_tims_ms); - VerifyDummyTestFrames(out_file.get(), kNumFrames); + VerifyDummyTestFrames(&out_file, kNumFrames); - out_file->CloseFile(); - - bool file_removed = false; - for (int i = 0; i < kMaxFileRetries; ++i) { - file_removed = remove(file_name_.c_str()) == 0; - if (file_removed) - break; - - // Couldn't remove file for some reason, wait a sec and try again. - rtc::Thread::SleepMs(1000); - } - EXPECT_TRUE(file_removed); - } - - // Check whether file exists or not, and if it does not meet expectation, - // wait a bit and check again, up to kMaxFileRetries times. This is an ugly - // hack to avoid flakiness on certain operating systems where antivirus - // software may unexpectedly lock files and keep them from disappearing or - // being reused. - bool FileExists(bool expected) { - bool file_exists = expected; - std::unique_ptr file_wrapper; - int iterations = 0; - do { - if (file_wrapper.get() != nullptr) - rtc::Thread::SleepMs(1000); - file_wrapper.reset(FileWrapper::Create()); - file_exists = file_wrapper->OpenFile(file_name_.c_str(), true); - file_wrapper->CloseFile(); - } while (file_exists != expected && ++iterations < kMaxFileRetries); - return file_exists; + out_file.Close(); } std::string file_name_; std::unique_ptr file_writer_; }; -TEST_F(IvfFileWriterTest, RemovesUnusedFile) { - file_writer_ = IvfFileWriter::Open(file_name_, kVideoCodecVP8); - ASSERT_TRUE(file_writer_.get() != nullptr); - EXPECT_TRUE(FileExists(true)); - EXPECT_TRUE(file_writer_->Close()); - EXPECT_FALSE(FileExists(false)); - EXPECT_FALSE(file_writer_->Close()); // Can't close twice. -} - TEST_F(IvfFileWriterTest, WritesBasicVP8FileNtpTimestamp) { const uint8_t fourcc[4] = {'V', 'P', '8', '0'}; RunBasicFileStructureTest(kVideoCodecVP8, fourcc, false); @@ -200,4 +156,28 @@ TEST_F(IvfFileWriterTest, WritesBasicH264FileMsTimestamp) { RunBasicFileStructureTest(kVideoCodecH264, fourcc, true); } +TEST_F(IvfFileWriterTest, ClosesWhenReachesLimit) { + const uint8_t fourcc[4] = {'V', 'P', '8', '0'}; + const int kWidth = 320; + const int kHeight = 240; + const int kNumFramesToWrite = 2; + const int kNumFramesToFit = 1; + + file_writer_ = IvfFileWriter::Wrap( + rtc::File::Open(file_name_), + kHeaderSize + + kNumFramesToFit * (kFrameHeaderSize + sizeof(dummy_payload))); + ASSERT_TRUE(file_writer_.get()); + + ASSERT_FALSE(WriteDummyTestFrames(kVideoCodecVP8, kWidth, kHeight, + kNumFramesToWrite, true)); + ASSERT_FALSE(file_writer_->Close()); + + rtc::File out_file = rtc::File::Open(file_name_); + VerifyIvfHeader(&out_file, fourcc, kWidth, kHeight, kNumFramesToFit, true); + VerifyDummyTestFrames(&out_file, kNumFramesToFit); + + out_file.Close(); +} + } // namespace webrtc diff --git a/webrtc/test/fake_encoder.cc b/webrtc/test/fake_encoder.cc index 29f844c00d..d015ab8913 100644 --- a/webrtc/test/fake_encoder.cc +++ b/webrtc/test/fake_encoder.cc @@ -10,6 +10,8 @@ #include "webrtc/test/fake_encoder.h" +#include + #include "testing/gtest/include/gtest/gtest.h" #include "webrtc/base/checks.h" @@ -193,7 +195,10 @@ EncodedImageCallback::Result FakeH264Encoder::OnEncodedImage( ++fragment_counter; } } - return callback_->OnEncodedImage(encoded_image, NULL, &fragmentation); + CodecSpecificInfo specifics; + memset(&specifics, 0, sizeof(specifics)); + specifics.codecType = kVideoCodecH264; + return callback_->OnEncodedImage(encoded_image, &specifics, &fragmentation); } DelayedEncoder::DelayedEncoder(Clock* clock, int delay_ms) diff --git a/webrtc/video/end_to_end_tests.cc b/webrtc/video/end_to_end_tests.cc index b3d667ef72..021ec3a16f 100644 --- a/webrtc/video/end_to_end_tests.cc +++ b/webrtc/video/end_to_end_tests.cc @@ -19,6 +19,7 @@ #include "webrtc/base/checks.h" #include "webrtc/base/event.h" +#include "webrtc/base/file.h" #include "webrtc/base/optional.h" #include "webrtc/base/rate_limiter.h" #include "webrtc/call.h" @@ -3750,4 +3751,105 @@ TEST_F(EndToEndTest, TransportSeqNumOnAudioAndVideo) { RunBaseTest(&test); } + +class EndToEndLogTest : public EndToEndTest { + void SetUp() { paths_.clear(); } + void TearDown() { + for (const auto& path : paths_) { + rtc::RemoveFile(path); + } + } + + public: + int AddFile() { + paths_.push_back(test::TempFilename(test::OutputPath(), "test_file")); + return static_cast(paths_.size()) - 1; + } + + rtc::PlatformFile OpenFile(int idx) { + return rtc::OpenPlatformFile(paths_[idx]); + } + + void LogSend(bool open) { + if (open) { + video_send_stream_->EnableEncodedFrameRecording( + std::vector(1, OpenFile(AddFile())), 0); + } else { + video_send_stream_->DisableEncodedFrameRecording(); + } + } + void LogReceive(bool open) { + if (open) { + video_receive_streams_[0]->EnableEncodedFrameRecording( + OpenFile(AddFile()), 0); + } else { + video_receive_streams_[0]->DisableEncodedFrameRecording(); + } + } + + std::vector paths_; +}; + +TEST_F(EndToEndLogTest, LogsEncodedFramesWhenRequested) { + static const int kNumFramesToRecord = 10; + class LogEncodingObserver : public test::EndToEndTest, + public EncodedFrameObserver { + public: + explicit LogEncodingObserver(EndToEndLogTest* fixture) + : EndToEndTest(kDefaultTimeoutMs), + fixture_(fixture), + recorded_frames_(0) {} + + void PerformTest() override { + fixture_->LogSend(true); + fixture_->LogReceive(true); + ASSERT_TRUE(Wait()) << "Timed out while waiting for frame logging."; + } + + void ModifyVideoConfigs( + VideoSendStream::Config* send_config, + std::vector* receive_configs, + VideoEncoderConfig* encoder_config) override { + encoder_.reset(VideoEncoder::Create(VideoEncoder::kVp8)); + decoder_.reset(VP8Decoder::Create()); + + send_config->post_encode_callback = this; + send_config->encoder_settings.payload_name = "VP8"; + send_config->encoder_settings.encoder = encoder_.get(); + + (*receive_configs)[0].decoders.resize(1); + (*receive_configs)[0].decoders[0].payload_type = + send_config->encoder_settings.payload_type; + (*receive_configs)[0].decoders[0].payload_name = + send_config->encoder_settings.payload_name; + (*receive_configs)[0].decoders[0].decoder = decoder_.get(); + } + + void EncodedFrameCallback(const EncodedFrame& encoded_frame) override { + rtc::CritScope lock(&crit_); + if (recorded_frames_++ > kNumFramesToRecord) { + fixture_->LogSend(false); + fixture_->LogReceive(false); + rtc::File send_file(fixture_->OpenFile(0)); + rtc::File receive_file(fixture_->OpenFile(1)); + uint8_t out[100]; + // If logging has worked correctly neither file should be empty, i.e. + // we should be able to read something from them. + EXPECT_LT(0u, send_file.Read(out, 100)); + EXPECT_LT(0u, receive_file.Read(out, 100)); + observation_complete_.Set(); + } + } + + private: + EndToEndLogTest* const fixture_; + std::unique_ptr encoder_; + std::unique_ptr decoder_; + rtc::CriticalSection crit_; + int recorded_frames_ GUARDED_BY(crit_); + } test(this); + + RunBaseTest(&test); +} + } // namespace webrtc diff --git a/webrtc/video/screenshare_loopback.cc b/webrtc/video/screenshare_loopback.cc index 82e51dd939..2d1e1d3468 100644 --- a/webrtc/video/screenshare_loopback.cc +++ b/webrtc/video/screenshare_loopback.cc @@ -172,6 +172,14 @@ std::string SL1() { return static_cast(FLAGS_sl1); } +DEFINE_string(encoded_frame_path, + "", + "The base path for encoded frame logs. Created files will have " + "the form ..(recv|send.).ivf"); +std::string EncodedFramePath() { + return static_cast(FLAGS_encoded_frame_path); +} + DEFINE_bool(logs, false, "print logs to stderr"); DEFINE_bool(send_side_bwe, true, "Use send-side bandwidth estimation"); @@ -224,12 +232,21 @@ void Loopback() { call_bitrate_config.max_bitrate_bps = flags::MaxBitrateKbps() * 1000; VideoQualityTest::Params params; - params.common = {flags::Width(), flags::Height(), flags::Fps(), - flags::MinBitrateKbps() * 1000, flags::TargetBitrateKbps() * 1000, - flags::MaxBitrateKbps() * 1000, false, flags::Codec(), - flags::NumTemporalLayers(), flags::SelectedTL(), - flags::MinTransmitBitrateKbps() * 1000, flags::FLAGS_send_side_bwe, - false, call_bitrate_config}; + params.common = {flags::Width(), + flags::Height(), + flags::Fps(), + flags::MinBitrateKbps() * 1000, + flags::TargetBitrateKbps() * 1000, + flags::MaxBitrateKbps() * 1000, + false, + flags::Codec(), + flags::NumTemporalLayers(), + flags::SelectedTL(), + flags::MinTransmitBitrateKbps() * 1000, + flags::FLAGS_send_side_bwe, + false, + flags::EncodedFramePath(), + call_bitrate_config}; params.screenshare = {true, flags::SlideChangeInterval(), flags::ScrollDuration()}; params.analyzer = {"screenshare", 0.0, 0.0, flags::DurationSecs(), diff --git a/webrtc/video/video_loopback.cc b/webrtc/video/video_loopback.cc index 10998c8867..da73640b44 100644 --- a/webrtc/video/video_loopback.cc +++ b/webrtc/video/video_loopback.cc @@ -183,6 +183,14 @@ std::string SL1() { return static_cast(FLAGS_sl1); } +DEFINE_string(encoded_frame_path, + "", + "The base path for encoded frame logs. Created files will have " + "the form ..(recv|send.).ivf"); +std::string EncodedFramePath() { + return static_cast(FLAGS_encoded_frame_path); +} + DEFINE_bool(logs, false, "print logs to stderr"); DEFINE_bool(send_side_bwe, true, "Use send-side bandwidth estimation"); @@ -230,12 +238,21 @@ void Loopback() { call_bitrate_config.max_bitrate_bps = flags::MaxBitrateKbps() * 1000; VideoQualityTest::Params params; - params.common = {flags::Width(), flags::Height(), flags::Fps(), - flags::MinBitrateKbps() * 1000, flags::TargetBitrateKbps() * 1000, - flags::MaxBitrateKbps() * 1000, flags::FLAGS_suspend_below_min_bitrate, - flags::Codec(), flags::NumTemporalLayers(), flags::SelectedTL(), - 0, // No min transmit bitrate. - flags::FLAGS_send_side_bwe, flags::FLAGS_use_fec, call_bitrate_config}; + params.common = {flags::Width(), + flags::Height(), + flags::Fps(), + flags::MinBitrateKbps() * 1000, + flags::TargetBitrateKbps() * 1000, + flags::MaxBitrateKbps() * 1000, + flags::FLAGS_suspend_below_min_bitrate, + flags::Codec(), + flags::NumTemporalLayers(), + flags::SelectedTL(), + 0, // No min transmit bitrate. + flags::FLAGS_send_side_bwe, + flags::FLAGS_use_fec, + flags::EncodedFramePath(), + call_bitrate_config}; params.video = {flags::Clip()}; params.analyzer = {"video", 0.0, 0.0, flags::DurationSecs(), flags::OutputFilename(), flags::GraphTitle()}; diff --git a/webrtc/video/video_quality_test.cc b/webrtc/video/video_quality_test.cc index f9440ad86c..ea0e0d9f79 100644 --- a/webrtc/video/video_quality_test.cc +++ b/webrtc/video/video_quality_test.cc @@ -22,6 +22,7 @@ #include "webrtc/base/event.h" #include "webrtc/base/format_macros.h" #include "webrtc/base/optional.h" +#include "webrtc/base/platform_file.h" #include "webrtc/base/timeutils.h" #include "webrtc/call.h" #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" @@ -798,7 +799,8 @@ class VideoAnalyzer : public PacketReceiver, rtc::Event done_; }; -VideoQualityTest::VideoQualityTest() : clock_(Clock::GetRealTimeClock()) {} +VideoQualityTest::VideoQualityTest() + : clock_(Clock::GetRealTimeClock()), receive_logs_(0), send_logs_(0) {} void VideoQualityTest::TestBody() {} @@ -1188,6 +1190,8 @@ void VideoQualityTest::RunWithAnalyzer(const Params& params) { rtc::VideoSinkWants wants; capturer_->AddOrUpdateSink(analyzer.InputInterface(), wants); + StartEncodedFrameLogs(video_send_stream_); + StartEncodedFrameLogs(video_receive_streams_[0]); video_send_stream_->Start(); for (VideoReceiveStream* receive_stream : video_receive_streams_) receive_stream->Start(); @@ -1311,12 +1315,15 @@ void VideoQualityTest::RunWithRenderers(const Params& params) { if (params_.audio_video_sync) audio_config.sync_group = kSyncGroup; - audio_receive_stream =call->CreateAudioReceiveStream(audio_config); + audio_receive_stream = call->CreateAudioReceiveStream(audio_config); const CodecInst kOpusInst = {120, "OPUS", 48000, 960, 2, 64000}; EXPECT_EQ(0, voe.codec->SetSendCodec(voe.send_channel_id, kOpusInst)); } + StartEncodedFrameLogs(video_receive_stream); + StartEncodedFrameLogs(video_send_stream_); + // Start sending and receiving video. video_receive_stream->Start(); video_send_stream_->Start(); @@ -1364,4 +1371,29 @@ void VideoQualityTest::RunWithRenderers(const Params& params) { DestroyVoiceEngine(&voe); } +void VideoQualityTest::StartEncodedFrameLogs(VideoSendStream* stream) { + if (!params_.common.encoded_frame_base_path.empty()) { + std::ostringstream str; + str << send_logs_++; + std::string prefix = + params_.common.encoded_frame_base_path + "." + str.str() + ".send."; + stream->EnableEncodedFrameRecording( + std::vector( + {rtc::CreatePlatformFile(prefix + "1.ivf"), + rtc::CreatePlatformFile(prefix + "2.ivf"), + rtc::CreatePlatformFile(prefix + "3.ivf")}), + 10000000); + } +} +void VideoQualityTest::StartEncodedFrameLogs(VideoReceiveStream* stream) { + if (!params_.common.encoded_frame_base_path.empty()) { + std::ostringstream str; + str << receive_logs_++; + std::string path = + params_.common.encoded_frame_base_path + "." + str.str() + ".recv.ivf"; + stream->EnableEncodedFrameRecording(rtc::CreatePlatformFile(path), + 10000000); + } +} + } // namespace webrtc diff --git a/webrtc/video/video_quality_test.h b/webrtc/video/video_quality_test.h index 805a559af7..cd4939de7f 100644 --- a/webrtc/video/video_quality_test.h +++ b/webrtc/video/video_quality_test.h @@ -41,6 +41,7 @@ class VideoQualityTest : public test::CallTest { int min_transmit_bps; bool send_side_bwe; bool fec; + std::string encoded_frame_base_path; Call::Config::BitrateConfig call_bitrate_config; } common; @@ -107,6 +108,9 @@ class VideoQualityTest : public test::CallTest { void SetupCommon(Transport* send_transport, Transport* recv_transport); void SetupScreenshare(); + void StartEncodedFrameLogs(VideoSendStream* stream); + void StartEncodedFrameLogs(VideoReceiveStream* stream); + // We need a more general capturer than the FrameGeneratorCapturer. std::unique_ptr capturer_; std::unique_ptr trace_to_stderr_; @@ -114,6 +118,9 @@ class VideoQualityTest : public test::CallTest { std::unique_ptr encoder_; Clock* const clock_; + int receive_logs_; + int send_logs_; + Params params_; }; diff --git a/webrtc/video/video_receive_stream.cc b/webrtc/video/video_receive_stream.cc index 01568cb3df..6e9633f1b2 100644 --- a/webrtc/video/video_receive_stream.cc +++ b/webrtc/video/video_receive_stream.cc @@ -31,8 +31,6 @@ namespace webrtc { -static const bool kEnableFrameRecording = false; - static bool UseSendSideBwe(const VideoReceiveStream::Config& config) { if (!config.rtp.transport_cc) return false; @@ -365,16 +363,12 @@ EncodedImageCallback::Result VideoReceiveStream::OnEncodedImage( EncodedFrame(encoded_image._buffer, encoded_image._length, encoded_image._frameType)); } - if (kEnableFrameRecording) { - if (!ivf_writer_.get()) { - RTC_DCHECK(codec_specific_info); - std::ostringstream oss; - oss << "receive_bitstream_ssrc_" << config_.rtp.remote_ssrc << ".ivf"; - ivf_writer_ = - IvfFileWriter::Open(oss.str(), codec_specific_info->codecType); - } + { + rtc::CritScope lock(&ivf_writer_lock_); if (ivf_writer_.get()) { - bool ok = ivf_writer_->WriteFrame(encoded_image); + RTC_DCHECK(codec_specific_info); + bool ok = ivf_writer_->WriteFrame(encoded_image, + codec_specific_info->codecType); RTC_DCHECK(ok); } } @@ -397,6 +391,24 @@ void VideoReceiveStream::SendNack( rtp_stream_receiver_.RequestPacketRetransmit(sequence_numbers); } +void VideoReceiveStream::EnableEncodedFrameRecording(rtc::PlatformFile file, + size_t byte_limit) { + { + rtc::CritScope lock(&ivf_writer_lock_); + if (file == rtc::kInvalidPlatformFileValue) { + ivf_writer_.reset(); + } else { + ivf_writer_ = IvfFileWriter::Wrap(rtc::File(file), byte_limit); + } + } + + if (file != rtc::kInvalidPlatformFileValue) { + // Make a keyframe appear as early as possible in the logs, to give actually + // decodable output. + RequestKeyFrame(); + } +} + void VideoReceiveStream::RequestKeyFrame() { rtp_stream_receiver_.RequestKeyFrame(); } diff --git a/webrtc/video/video_receive_stream.h b/webrtc/video/video_receive_stream.h index 3cffb4b385..416cfe993c 100644 --- a/webrtc/video/video_receive_stream.h +++ b/webrtc/video/video_receive_stream.h @@ -84,6 +84,14 @@ class VideoReceiveStream : public webrtc::VideoReceiveStream, // Implements KeyFrameRequestSender. void RequestKeyFrame() override; + // Takes ownership of the file, is responsible for closing it later. + // Calling this method will close and finalize any current log. + // Giving rtc::kInvalidPlatformFileValue disables logging. + // If a frame to be written would make the log too large the write fails and + // the log is closed and finalized. A |byte_limit| of 0 means no limit. + void EnableEncodedFrameRecording(rtc::PlatformFile file, + size_t byte_limit) override; + private: static bool DecodeThreadFunction(void* ptr); void Decode(); @@ -105,7 +113,8 @@ class VideoReceiveStream : public webrtc::VideoReceiveStream, std::unique_ptr video_stream_decoder_; RtpStreamsSynchronizer rtp_stream_sync_; - std::unique_ptr ivf_writer_; + rtc::CriticalSection ivf_writer_lock_; + std::unique_ptr ivf_writer_ GUARDED_BY(ivf_writer_lock_); }; } // namespace internal } // namespace webrtc diff --git a/webrtc/video/video_send_stream.cc b/webrtc/video/video_send_stream.cc index c8541fed12..c998272aa9 100644 --- a/webrtc/video/video_send_stream.cc +++ b/webrtc/video/video_send_stream.cc @@ -15,7 +15,9 @@ #include #include +#include "webrtc/common_types.h" #include "webrtc/base/checks.h" +#include "webrtc/base/file.h" #include "webrtc/base/logging.h" #include "webrtc/base/trace_event.h" #include "webrtc/modules/bitrate_controller/include/bitrate_controller.h" @@ -267,6 +269,9 @@ class VideoSendStreamImpl : public webrtc::BitrateAllocatorObserver, VideoSendStream::RtpStateMap GetRtpStates() const; + void EnableEncodedFrameRecording(const std::vector& files, + size_t byte_limit); + private: class CheckEncoderActivityTask; class EncoderReconfiguredTask; @@ -316,9 +321,9 @@ class VideoSendStreamImpl : public webrtc::BitrateAllocatorObserver, BitrateAllocator* const bitrate_allocator_; VieRemb* const remb_; - static const bool kEnableFrameRecording = false; - static const int kMaxLayers = 3; - std::unique_ptr file_writers_[kMaxLayers]; + rtc::CriticalSection ivf_writers_crit_; + std::unique_ptr file_writers_[kMaxSimulcastStreams] GUARDED_BY( + ivf_writers_crit_); int max_padding_bitrate_; int encoder_min_bitrate_bps_; @@ -615,6 +620,12 @@ bool VideoSendStream::DeliverRtcp(const uint8_t* packet, size_t length) { return send_stream_->DeliverRtcp(packet, length); } +void VideoSendStream::EnableEncodedFrameRecording( + const std::vector& files, + size_t byte_limit) { + send_stream_->EnableEncodedFrameRecording(files, byte_limit); +} + VideoSendStreamImpl::VideoSendStreamImpl( SendStatisticsProxy* stats_proxy, rtc::TaskQueue* worker_queue, @@ -891,25 +902,16 @@ EncodedImageCallback::Result VideoSendStreamImpl::OnEncodedImage( EncodedImageCallback::Result result = payload_router_.OnEncodedImage( encoded_image, codec_specific_info, fragmentation); - if (kEnableFrameRecording) { - int layer = codec_specific_info->codecType == kVideoCodecVP8 - ? codec_specific_info->codecSpecific.VP8.simulcastIdx - : 0; - IvfFileWriter* file_writer; - { - if (file_writers_[layer] == nullptr) { - std::ostringstream oss; - oss << "send_bitstream_ssrc"; - for (uint32_t ssrc : config_->rtp.ssrcs) - oss << "_" << ssrc; - oss << "_layer" << layer << ".ivf"; - file_writers_[layer] = - IvfFileWriter::Open(oss.str(), codec_specific_info->codecType); - } - file_writer = file_writers_[layer].get(); - } - if (file_writer) { - bool ok = file_writer->WriteFrame(encoded_image); + RTC_DCHECK(codec_specific_info); + + int layer = codec_specific_info->codecType == kVideoCodecVP8 + ? codec_specific_info->codecSpecific.VP8.simulcastIdx + : 0; + { + rtc::CritScope lock(&ivf_writers_crit_); + if (file_writers_[layer].get()) { + bool ok = file_writers_[layer]->WriteFrame( + encoded_image, codec_specific_info->codecType); RTC_DCHECK(ok); } } @@ -1064,6 +1066,27 @@ uint32_t VideoSendStreamImpl::OnBitrateUpdated(uint32_t bitrate_bps, return protection_bitrate; } +void VideoSendStreamImpl::EnableEncodedFrameRecording( + const std::vector& files, + size_t byte_limit) { + { + rtc::CritScope lock(&ivf_writers_crit_); + for (unsigned int i = 0; i < kMaxSimulcastStreams; ++i) { + if (i < files.size()) { + file_writers_[i] = IvfFileWriter::Wrap(rtc::File(files[i]), byte_limit); + } else { + file_writers_[i].reset(); + } + } + } + + if (!files.empty()) { + // Make a keyframe appear as early as possible in the logs, to give actually + // decodable output. + vie_encoder_->SendKeyFrame(); + } +} + int VideoSendStreamImpl::ProtectionRequest( const FecProtectionParams* delta_params, const FecProtectionParams* key_params, diff --git a/webrtc/video/video_send_stream.h b/webrtc/video/video_send_stream.h index dd5081d0d2..58433ad35b 100644 --- a/webrtc/video/video_send_stream.h +++ b/webrtc/video/video_send_stream.h @@ -79,6 +79,16 @@ class VideoSendStream : public webrtc::VideoSendStream { Stats GetStats() override; typedef std::map RtpStateMap; + + // Takes ownership of each file, is responsible for closing them later. + // Calling this method will close and finalize any current logs. + // Giving rtc::kInvalidPlatformFileValue in any position disables logging + // for the corresponding stream. + // If a frame to be written would make the log too large the write fails and + // the log is closed and finalized. A |byte_limit| of 0 means no limit. + void EnableEncodedFrameRecording(const std::vector& files, + size_t byte_limit) override; + RtpStateMap StopPermanentlyAndGetRtpStates(); private: diff --git a/webrtc/video_receive_stream.h b/webrtc/video_receive_stream.h index 0adcf3fc0d..def103f195 100644 --- a/webrtc/video_receive_stream.h +++ b/webrtc/video_receive_stream.h @@ -16,6 +16,7 @@ #include #include +#include "webrtc/base/platform_file.h" #include "webrtc/common_types.h" #include "webrtc/common_video/include/frame_callback.h" #include "webrtc/config.h" @@ -208,6 +209,17 @@ class VideoReceiveStream { // TODO(pbos): Add info on currently-received codec to Stats. virtual Stats GetStats() const = 0; + // Takes ownership of the file, is responsible for closing it later. + // Calling this method will close and finalize any current log. + // Giving rtc::kInvalidPlatformFileValue disables logging. + // If a frame to be written would make the log too large the write fails and + // the log is closed and finalized. A |byte_limit| of 0 means no limit. + virtual void EnableEncodedFrameRecording(rtc::PlatformFile file, + size_t byte_limit) = 0; + inline void DisableEncodedFrameRecording() { + EnableEncodedFrameRecording(rtc::kInvalidPlatformFileValue, 0); + } + protected: virtual ~VideoReceiveStream() {} }; diff --git a/webrtc/video_send_stream.h b/webrtc/video_send_stream.h index 2948d46874..e987784c83 100644 --- a/webrtc/video_send_stream.h +++ b/webrtc/video_send_stream.h @@ -13,8 +13,10 @@ #include #include +#include #include +#include "webrtc/base/platform_file.h" #include "webrtc/common_types.h" #include "webrtc/common_video/include/frame_callback.h" #include "webrtc/config.h" @@ -192,6 +194,22 @@ class VideoSendStream { virtual Stats GetStats() = 0; + // Takes ownership of each file, is responsible for closing them later. + // Calling this method will close and finalize any current logs. + // Some codecs produce multiple streams (VP8 only at present), each of these + // streams will log to a separate file. kMaxSimulcastStreams in common_types.h + // gives the max number of such streams. If there is no file for a stream, or + // the file is rtc::kInvalidPlatformFileValue, frames from that stream will + // not be logged. + // If a frame to be written would make the log too large the write fails and + // the log is closed and finalized. A |byte_limit| of 0 means no limit. + virtual void EnableEncodedFrameRecording( + const std::vector& files, + size_t byte_limit) = 0; + inline void DisableEncodedFrameRecording() { + EnableEncodedFrameRecording(std::vector(), 0); + } + protected: virtual ~VideoSendStream() {} };