diff --git a/test/BUILD.gn b/test/BUILD.gn index 3d4b884f21..bb49e2ee82 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -21,6 +21,7 @@ group("test") { ":test_renderer", ":test_support", ":video_test_common", + "pc/e2e", "pc/e2e/api:peer_connection_quality_test_fixture_api", ] @@ -349,6 +350,7 @@ if (rtc_include_tests) { "../modules/video_coding:simulcast_test_fixture_impl", "../rtc_base:rtc_base_approved", "../test:single_threaded_task_queue", + "pc/e2e:e2e_unittests", "scenario:scenario_unittests", "//testing/gmock", "//testing/gtest", diff --git a/test/pc/e2e/BUILD.gn b/test/pc/e2e/BUILD.gn new file mode 100644 index 0000000000..c9d1f48984 --- /dev/null +++ b/test/pc/e2e/BUILD.gn @@ -0,0 +1,101 @@ +# Copyright (c) 2019 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("../../../webrtc.gni") + +group("e2e") { + testonly = true + + deps = [ + ":default_encoded_image_id_injector", + ":encoded_image_id_injector_api", + ":single_process_encoded_image_id_injector", + ] +} + +if (rtc_include_tests) { + group("e2e_unittests") { + testonly = true + + deps = [ + ":default_encoded_image_id_injector_unittest", + ":single_process_encoded_image_id_injector_unittest", + ] + } +} + +rtc_source_set("encoded_image_id_injector_api") { + visibility = [ "*" ] + sources = [ + "analyzer/video/encoded_image_id_injector.h", + ] + + deps = [ + "../../../api/video:encoded_image", + ] +} + +rtc_source_set("default_encoded_image_id_injector") { + visibility = [ "*" ] + sources = [ + "analyzer/video/default_encoded_image_id_injector.cc", + "analyzer/video/default_encoded_image_id_injector.h", + ] + + deps = [ + ":encoded_image_id_injector_api", + "../../../api/video:encoded_image", + "../../../rtc_base:checks", + "../../../rtc_base:criticalsection", + "//third_party/abseil-cpp/absl/memory:memory", + ] +} + +rtc_source_set("single_process_encoded_image_id_injector") { + visibility = [ "*" ] + sources = [ + "analyzer/video/single_process_encoded_image_id_injector.cc", + "analyzer/video/single_process_encoded_image_id_injector.h", + ] + + deps = [ + ":encoded_image_id_injector_api", + "../../../api/video:encoded_image", + "../../../rtc_base:checks", + "../../../rtc_base:criticalsection", + "//third_party/abseil-cpp/absl/memory:memory", + ] +} + +if (rtc_include_tests) { + rtc_source_set("single_process_encoded_image_id_injector_unittest") { + testonly = true + sources = [ + "analyzer/video/single_process_encoded_image_id_injector_unittest.cc", + ] + deps = [ + ":single_process_encoded_image_id_injector", + "../../../api/video:encoded_image", + "../../../rtc_base:rtc_base_approved", + "../../../test:test_support", + ] + } + + rtc_source_set("default_encoded_image_id_injector_unittest") { + testonly = true + sources = [ + "analyzer/video/default_encoded_image_id_injector_unittest.cc", + ] + deps = [ + ":default_encoded_image_id_injector", + "../../../api/video:encoded_image", + "../../../rtc_base:rtc_base_approved", + "../../../test:test_support", + ] + } +} diff --git a/test/pc/e2e/analyzer/video/default_encoded_image_id_injector.cc b/test/pc/e2e/analyzer/video/default_encoded_image_id_injector.cc new file mode 100644 index 0000000000..b0a5aacf7b --- /dev/null +++ b/test/pc/e2e/analyzer/video/default_encoded_image_id_injector.cc @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2019 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 "test/pc/e2e/analyzer/video/default_encoded_image_id_injector.h" + +#include + +#include "absl/memory/memory.h" +#include "api/video/encoded_image.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { +namespace { + +// The amount on which encoded image buffer will be expanded to inject frame id. +// This is 2 bytes for uint16_t frame id itself and 4 bytes for original length +// of the buffer. +constexpr int kEncodedImageBufferExpansion = 6; +constexpr size_t kInitialBufferSize = 2 * 1024; +// Count of coding entities for which buffers pools will be added on +// construction. +constexpr int kPreInitCodingEntitiesCount = 2; +constexpr size_t kBuffersPoolPerCodingEntity = 256; + +} // namespace + +DefaultEncodedImageIdInjector::DefaultEncodedImageIdInjector() { + for (size_t i = 0; + i < kPreInitCodingEntitiesCount * kBuffersPoolPerCodingEntity; ++i) { + bufs_pool_.push_back( + absl::make_unique>(kInitialBufferSize)); + } +} +DefaultEncodedImageIdInjector::~DefaultEncodedImageIdInjector() = default; + +EncodedImage DefaultEncodedImageIdInjector::InjectId(uint16_t id, + const EncodedImage& source, + int coding_entity_id) { + ExtendIfRequired(coding_entity_id); + + EncodedImage out = source; + std::vector* buffer = NextBuffer(); + if (buffer->size() < source.size() + kEncodedImageBufferExpansion) { + buffer->resize(source.size() + kEncodedImageBufferExpansion); + } + out.set_buffer(buffer->data(), buffer->size()); + out.set_size(source.size() + kEncodedImageBufferExpansion); + memcpy(&out.data()[kEncodedImageBufferExpansion], source.data(), + source.size()); + out.data()[0] = id & 0x00ff; + out.data()[1] = (id & 0xff00) >> 8; + out.data()[2] = source.size() & 0x00ff; + out.data()[3] = (source.size() & 0xff00) >> 8; + out.data()[4] = (source.size() & 0xff00) >> 16; + out.data()[5] = (source.size() & 0xff00) >> 24; + return out; +} + +std::pair DefaultEncodedImageIdInjector::ExtractId( + const EncodedImage& source, + int coding_entity_id) { + ExtendIfRequired(coding_entity_id); + + EncodedImage out = source; + std::vector* buffer = NextBuffer(); + if (buffer->size() < source.capacity() - kEncodedImageBufferExpansion) { + buffer->resize(source.capacity() - kEncodedImageBufferExpansion); + } + out.set_buffer(buffer->data(), buffer->size()); + + size_t source_pos = 0; + size_t out_pos = 0; + absl::optional id = absl::nullopt; + while (source_pos < source.size()) { + RTC_CHECK_LE(source_pos + kEncodedImageBufferExpansion, source.size()); + uint16_t next_id = + source.data()[source_pos] + (source.data()[source_pos + 1] << 8); + RTC_CHECK(!id || id.value() == next_id) + << "Different frames encoded into single encoded image: " << id.value() + << " vs " << next_id; + id = next_id; + uint32_t length = source.data()[source_pos + 2] + + (source.data()[source_pos + 3] << 8) + + (source.data()[source_pos + 3] << 16) + + (source.data()[source_pos + 3] << 24); + RTC_CHECK_LE(source_pos + kEncodedImageBufferExpansion + length, + source.size()); + memcpy(&out.data()[out_pos], + &source.data()[source_pos + kEncodedImageBufferExpansion], length); + source_pos += length + kEncodedImageBufferExpansion; + out_pos += length; + } + out.set_size(out_pos); + + return std::pair(id.value(), out); +} + +void DefaultEncodedImageIdInjector::ExtendIfRequired(int coding_entity_id) { + rtc::CritScope crit(&lock_); + if (coding_entities_.find(coding_entity_id) != coding_entities_.end()) { + // This entity is already known for this injector, so buffers are allocated. + return; + } + + // New coding entity. We need allocate extra buffers for this encoder/decoder + // We will put them into front of the queue to use them first. + coding_entities_.insert(coding_entity_id); + if (coding_entities_.size() <= kPreInitCodingEntitiesCount) { + // Buffers for the first kPreInitCodingEntitiesCount coding entities were + // allocated during construction. + return; + } + for (size_t i = 0; i < kBuffersPoolPerCodingEntity; ++i) { + bufs_pool_.push_front( + absl::make_unique>(kInitialBufferSize)); + } +} + +std::vector* DefaultEncodedImageIdInjector::NextBuffer() { + rtc::CritScope crit(&lock_); + // Get buffer from the front of the queue, return it to the caller and + // put in the back + std::vector* out = bufs_pool_.front().get(); + bufs_pool_.push_back(std::move(bufs_pool_.front())); + bufs_pool_.pop_front(); + return out; +} + +} // namespace test +} // namespace webrtc diff --git a/test/pc/e2e/analyzer/video/default_encoded_image_id_injector.h b/test/pc/e2e/analyzer/video/default_encoded_image_id_injector.h new file mode 100644 index 0000000000..1f19dc35bf --- /dev/null +++ b/test/pc/e2e/analyzer/video/default_encoded_image_id_injector.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2019 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. + */ + +#ifndef TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_ENCODED_IMAGE_ID_INJECTOR_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_ENCODED_IMAGE_ID_INJECTOR_H_ + +#include +#include +#include +#include +#include +#include + +#include "api/video/encoded_image.h" +#include "rtc_base/critical_section.h" +#include "test/pc/e2e/analyzer/video/encoded_image_id_injector.h" + +namespace webrtc { +namespace test { + +// Injects frame id into EncodedImage payload buffer. The payload buffer will +// be prepended in the injector with 2 bytes frame id and 4 bytes original +// buffer length. It is assumed, that frame's data can't be more then 2^32 +// bytes. In the decoder, frame id will be extracted and the length will be used +// to restore original buffer. +// +// The data in the EncodedImage on encoder side after injection will look like +// this: +// 4 bytes frame length +// _ _ _↓_ _ _ _________________ +// | | | original buffer | +// ¯↑¯ ¯ ¯ ¯ ¯ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ +// 2 bytes frame id +// +// But on decoder side multiple payloads can be concatenated into single +// EncodedImage in jitter buffer and its payload will look like this: +// _ _ _ _ _ _ _________ _ _ _ _ _ _ _________ _ _ _ _ _ _ _________ +// buf: | | | payload | | | payload | | | payload | +// ¯ ¯ ¯ ¯ ¯ ¯ ¯¯¯¯¯¯¯¯¯ ¯ ¯ ¯ ¯ ¯ ¯ ¯¯¯¯¯¯¯¯¯ ¯ ¯ ¯ ¯ ¯ ¯ ¯¯¯¯¯¯¯¯¯ +// To correctly restore such images we will extract id by this algorithm: +// 1. pos = 0 +// 2. Extract id from buf[pos] and buf[pos + 1] +// 3. Extract length from buf[pos + 2]..buf[pos + 5] +// 4. Copy |length| bytes starting from buf[pos + 6] to output buffer. +// 5. pos = pos + length + 6 +// 6. If pos < buf.length - go to the step 2. +// Also it will be checked, that all extracted ids are equals. +// +// Because EncodedImage doesn't take ownership of its buffer, injector will keep +// ownership of the buffers that will be used for EncodedImages with injected +// data. This is needed because there is no way to inform the injector that +// a buffer can be disposed. To address this issue injector will use a pool +// of buffers in round robin manner and will assume, that when it overlaps +// the buffer can be disposed. +// +// Because single injector can be used for different coding entities (encoders +// or decoders), it will store a |coding_entity_id| in the set for each +// coding entity seen and if the new one arrives, it will extend its buffers +// pool, adding 256 more buffers. During initialization injector will +// preallocate buffers for 2 coding entities, so 512 buffers with initial size +// 2KB. If in some point of time bigger buffer will be required, it will be also +// extended. +class DefaultEncodedImageIdInjector : public EncodedImageIdInjector, + public EncodedImageIdExtractor { + public: + DefaultEncodedImageIdInjector(); + ~DefaultEncodedImageIdInjector() override; + + EncodedImage InjectId(uint16_t id, + const EncodedImage& source, + int coding_entity_id) override; + std::pair ExtractId(const EncodedImage& source, + int coding_entity_id) override; + + private: + void ExtendIfRequired(int coding_entity_id) RTC_LOCKS_EXCLUDED(lock_); + std::vector* NextBuffer() RTC_LOCKS_EXCLUDED(lock_); + + // Because single injector will be used for all encoder and decoders in one + // peer and in case of the single process for all encoders and decoders in + // another peer, it can be called from different threads. So we need to ensure + // that buffers are given consecutively from pools and pool extension won't + // be interrupted by getting buffer in other thread. + rtc::CriticalSection lock_; + + // Store coding entities for which buffers pool have been already extended. + std::set coding_entities_ RTC_GUARDED_BY(lock_); + std::deque>> bufs_pool_ + RTC_GUARDED_BY(lock_); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_ENCODED_IMAGE_ID_INJECTOR_H_ diff --git a/test/pc/e2e/analyzer/video/default_encoded_image_id_injector_unittest.cc b/test/pc/e2e/analyzer/video/default_encoded_image_id_injector_unittest.cc new file mode 100644 index 0000000000..5886c8629c --- /dev/null +++ b/test/pc/e2e/analyzer/video/default_encoded_image_id_injector_unittest.cc @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2019 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 "test/pc/e2e/analyzer/video/default_encoded_image_id_injector.h" + +#include + +#include "api/video/encoded_image.h" +#include "rtc_base/buffer.h" +#include "test/gtest.h" + +namespace webrtc { +namespace test { +namespace { + +rtc::Buffer CreateBufferOfSizeNFilledWithValuesFromX(size_t n, uint8_t x) { + rtc::Buffer buffer(n); + for (size_t i = 0; i < n; ++i) { + buffer[i] = static_cast(x + i); + } + return buffer; +} + +} // namespace + +TEST(DefaultEncodedImageIdInjector, InjectExtract) { + DefaultEncodedImageIdInjector injector; + + rtc::Buffer buffer = CreateBufferOfSizeNFilledWithValuesFromX(10, 1); + + EncodedImage source(buffer.data(), 10, 10); + source.SetTimestamp(123456789); + + std::pair out = + injector.ExtractId(injector.InjectId(512, source, 1), 2); + ASSERT_EQ(out.first, 512); + ASSERT_EQ(out.second.size(), 10ul); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(out.second.data()[i], i + 1); + } +} + +TEST(DefaultEncodedImageIdInjector, Inject3Extract3) { + DefaultEncodedImageIdInjector injector; + + rtc::Buffer buffer1 = CreateBufferOfSizeNFilledWithValuesFromX(10, 1); + rtc::Buffer buffer2 = CreateBufferOfSizeNFilledWithValuesFromX(10, 11); + rtc::Buffer buffer3 = CreateBufferOfSizeNFilledWithValuesFromX(10, 21); + + // 1st frame + EncodedImage source1(buffer1.data(), 10, 10); + source1.SetTimestamp(123456710); + // 2nd frame 1st spatial layer + EncodedImage source2(buffer2.data(), 10, 10); + source2.SetTimestamp(123456720); + // 2nd frame 2nd spatial layer + EncodedImage source3(buffer3.data(), 10, 10); + source3.SetTimestamp(123456720); + + EncodedImage intermediate1 = injector.InjectId(510, source1, 1); + EncodedImage intermediate2 = injector.InjectId(520, source2, 1); + EncodedImage intermediate3 = injector.InjectId(520, source3, 1); + + // Extract ids in different order. + std::pair out3 = injector.ExtractId(intermediate3, 2); + std::pair out1 = injector.ExtractId(intermediate1, 2); + std::pair out2 = injector.ExtractId(intermediate2, 2); + + ASSERT_EQ(out1.first, 510); + ASSERT_EQ(out1.second.size(), 10ul); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(out1.second.data()[i], i + 1); + } + ASSERT_EQ(out2.first, 520); + ASSERT_EQ(out2.second.size(), 10ul); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(out2.second.data()[i], i + 11); + } + ASSERT_EQ(out3.first, 520); + ASSERT_EQ(out3.second.size(), 10ul); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(out3.second.data()[i], i + 21); + } +} + +TEST(DefaultEncodedImageIdInjector, InjectExtractFromConcatenated) { + DefaultEncodedImageIdInjector injector; + + rtc::Buffer buffer1 = CreateBufferOfSizeNFilledWithValuesFromX(10, 1); + rtc::Buffer buffer2 = CreateBufferOfSizeNFilledWithValuesFromX(10, 11); + rtc::Buffer buffer3 = CreateBufferOfSizeNFilledWithValuesFromX(10, 21); + + EncodedImage source1(buffer1.data(), 10, 10); + source1.SetTimestamp(123456710); + EncodedImage source2(buffer2.data(), 10, 10); + source2.SetTimestamp(123456710); + EncodedImage source3(buffer3.data(), 10, 10); + source3.SetTimestamp(123456710); + + // Inject id into 3 images with same frame id. + EncodedImage intermediate1 = injector.InjectId(512, source1, 1); + EncodedImage intermediate2 = injector.InjectId(512, source2, 1); + EncodedImage intermediate3 = injector.InjectId(512, source3, 1); + + // Concatenate them into single encoded image, like it can be done in jitter + // buffer. + size_t concatenated_length = + intermediate1.size() + intermediate2.size() + intermediate3.size(); + rtc::Buffer concatenated_buffer; + concatenated_buffer.AppendData(intermediate1.data(), intermediate1.size()); + concatenated_buffer.AppendData(intermediate2.data(), intermediate2.size()); + concatenated_buffer.AppendData(intermediate3.data(), intermediate3.size()); + EncodedImage concatenated(concatenated_buffer.data(), concatenated_length, + concatenated_length); + + // Extract frame id from concatenated image + std::pair out = injector.ExtractId(concatenated, 2); + + ASSERT_EQ(out.first, 512); + ASSERT_EQ(out.second.size(), 3 * 10ul); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(out.second.data()[i], i + 1); + ASSERT_EQ(out.second.data()[i + 10], i + 11); + ASSERT_EQ(out.second.data()[i + 20], i + 21); + } +} + +} // namespace test +} // namespace webrtc diff --git a/test/pc/e2e/analyzer/video/encoded_image_id_injector.h b/test/pc/e2e/analyzer/video/encoded_image_id_injector.h new file mode 100644 index 0000000000..952c23a24c --- /dev/null +++ b/test/pc/e2e/analyzer/video/encoded_image_id_injector.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 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. + */ + +#ifndef TEST_PC_E2E_ANALYZER_VIDEO_ENCODED_IMAGE_ID_INJECTOR_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_ENCODED_IMAGE_ID_INJECTOR_H_ + +#include +#include + +#include "api/video/encoded_image.h" + +namespace webrtc { +namespace test { + +// Injects frame id into EncodedImage on encoder side +class EncodedImageIdInjector { + public: + virtual ~EncodedImageIdInjector() = default; + + // Return encoded image with specified |id| injected into its payload. + // |coding_entity_id| is unique id of decoder or encoder. + virtual EncodedImage InjectId(uint16_t id, + const EncodedImage& source, + int coding_entity_id) = 0; +}; + +// Extracts frame id from EncodedImage on decoder side. +class EncodedImageIdExtractor { + public: + virtual ~EncodedImageIdExtractor() = default; + + // Returns encoded image id, extracted from payload and also encoded image + // with its original payload. For concatenated spatial layers it should be the + // same id. |coding_entity_id| is unique id of decoder or encoder. + virtual std::pair ExtractId( + const EncodedImage& source, + int coding_entity_id) = 0; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_ENCODED_IMAGE_ID_INJECTOR_H_ diff --git a/test/pc/e2e/analyzer/video/single_process_encoded_image_id_injector.cc b/test/pc/e2e/analyzer/video/single_process_encoded_image_id_injector.cc new file mode 100644 index 0000000000..0422797b35 --- /dev/null +++ b/test/pc/e2e/analyzer/video/single_process_encoded_image_id_injector.cc @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019 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 "test/pc/e2e/analyzer/video/single_process_encoded_image_id_injector.h" + +#include + +#include "absl/memory/memory.h" +#include "api/video/encoded_image.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { +namespace { + +// Number of bytes from the beginning of the EncodedImage buffer that will be +// used to store frame id and sub id. +constexpr size_t kUsedBufferSize = 3; + +} // namespace + +SingleProcessEncodedImageIdInjector::SingleProcessEncodedImageIdInjector() = + default; +SingleProcessEncodedImageIdInjector::~SingleProcessEncodedImageIdInjector() = + default; + +EncodedImage SingleProcessEncodedImageIdInjector::InjectId( + uint16_t id, + const EncodedImage& source, + int coding_entity_id) { + RTC_CHECK(source.size() >= kUsedBufferSize); + + ExtractionInfo info; + info.length = source.size(); + memcpy(info.origin_data, source.data(), kUsedBufferSize); + { + rtc::CritScope crit(&lock_); + // Will create new one if missed. + ExtractionInfoVector& ev = extraction_cache_[id]; + info.sub_id = ev.next_sub_id++; + ev.infos[info.sub_id] = std::move(info); + } + + EncodedImage out = source; + out.data()[0] = id & 0x00ff; + out.data()[1] = (id & 0xff00) >> 8; + out.data()[2] = info.sub_id; + return out; +} + +std::pair +SingleProcessEncodedImageIdInjector::ExtractId(const EncodedImage& source, + int coding_entity_id) { + EncodedImage out = source; + + size_t pos = 0; + absl::optional id = absl::nullopt; + while (pos < source.size()) { + // Extract frame id from first 2 bytes of the payload. + uint16_t next_id = source.data()[pos] + (source.data()[pos + 1] << 8); + // Extract frame sub id from second 2 byte of the payload. + uint16_t sub_id = source.data()[pos + 2]; + + RTC_CHECK(!id || id.value() == next_id) + << "Different frames encoded into single encoded image: " << id.value() + << " vs " << next_id; + id = next_id; + + ExtractionInfo info; + { + rtc::CritScope crit(&lock_); + auto ext_vector_it = extraction_cache_.find(next_id); + RTC_CHECK(ext_vector_it != extraction_cache_.end()) + << "Unknown frame id " << next_id; + + auto info_it = ext_vector_it->second.infos.find(sub_id); + RTC_CHECK(info_it != ext_vector_it->second.infos.end()) + << "Unknown sub id " << sub_id << " for frame " << next_id; + info = std::move(info_it->second); + ext_vector_it->second.infos.erase(info_it); + } + + memcpy(&out.data()[pos], info.origin_data, kUsedBufferSize); + pos += info.length; + } + out.set_size(pos); + + return std::pair(id.value(), out); +} + +SingleProcessEncodedImageIdInjector::ExtractionInfoVector:: + ExtractionInfoVector() = default; +SingleProcessEncodedImageIdInjector::ExtractionInfoVector:: + ~ExtractionInfoVector() = default; + +} // namespace test +} // namespace webrtc diff --git a/test/pc/e2e/analyzer/video/single_process_encoded_image_id_injector.h b/test/pc/e2e/analyzer/video/single_process_encoded_image_id_injector.h new file mode 100644 index 0000000000..3570f03053 --- /dev/null +++ b/test/pc/e2e/analyzer/video/single_process_encoded_image_id_injector.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 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. + */ + +#ifndef TEST_PC_E2E_ANALYZER_VIDEO_SINGLE_PROCESS_ENCODED_IMAGE_ID_INJECTOR_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_SINGLE_PROCESS_ENCODED_IMAGE_ID_INJECTOR_H_ + +#include +#include +#include +#include +#include + +#include "api/video/encoded_image.h" +#include "rtc_base/critical_section.h" +#include "test/pc/e2e/analyzer/video/encoded_image_id_injector.h" + +namespace webrtc { +namespace test { + +// Based on assumption that all call participants are in the same OS process +// and uses same QualityAnalyzingVideoContext to obtain EncodedImageIdInjector. +// +// To inject frame id into EncodedImage injector uses first 2 bytes of +// EncodedImage payload. Then it uses 3rd byte for frame sub id, that is +// required to distinguish different spatial layers. The origin data from these +// 3 bytes will be stored inside injector's internal storage and then will be +// restored during extraction phase. +// +// This injector won't add any extra overhead into EncodedImage payload and +// support frames with any size of payload. Also assumes that every EncodedImage +// payload size is greater or equals to 3 bytes +class SingleProcessEncodedImageIdInjector : public EncodedImageIdInjector, + public EncodedImageIdExtractor { + public: + SingleProcessEncodedImageIdInjector(); + ~SingleProcessEncodedImageIdInjector() override; + + // Id will be injected into EncodedImage buffer directly. This buffer won't + // be fully copied, so |source| image buffer will be also changed. + EncodedImage InjectId(uint16_t id, + const EncodedImage& source, + int coding_entity_id) override; + std::pair ExtractId(const EncodedImage& source, + int coding_entity_id) override; + + private: + // Contains data required to extract frame id from EncodedImage and restore + // original buffer. + struct ExtractionInfo { + // Frame sub id to distinguish encoded images for different spatial layers. + uint8_t sub_id = 0; + // Length of the origin buffer encoded image. + size_t length; + // Data from first 3 bytes of origin encoded image's payload. + uint8_t origin_data[3]; + }; + + struct ExtractionInfoVector { + ExtractionInfoVector(); + ~ExtractionInfoVector(); + + // Next sub id, that have to be used for this frame id. + uint8_t next_sub_id = 0; + std::map infos; + }; + + rtc::CriticalSection lock_; + // Stores a mapping from frame id to extraction info for spatial layers + // for this frame id. There can be a lot of them, because if frame was + // dropped we can't clean it up, because we won't receive a signal on + // decoder side about that frame. In such case it will be replaced + // when sub id will overlap. + std::map extraction_cache_ + RTC_GUARDED_BY(lock_); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_SINGLE_PROCESS_ENCODED_IMAGE_ID_INJECTOR_H_ diff --git a/test/pc/e2e/analyzer/video/single_process_encoded_image_id_injector_unittest.cc b/test/pc/e2e/analyzer/video/single_process_encoded_image_id_injector_unittest.cc new file mode 100644 index 0000000000..930a9d9f1c --- /dev/null +++ b/test/pc/e2e/analyzer/video/single_process_encoded_image_id_injector_unittest.cc @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019 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 "test/pc/e2e/analyzer/video/single_process_encoded_image_id_injector.h" + +#include + +#include "api/video/encoded_image.h" +#include "rtc_base/buffer.h" +#include "test/gtest.h" + +namespace webrtc { +namespace test { +namespace { + +rtc::Buffer CreateBufferOfSizeNFilledWithValuesFromX(size_t n, uint8_t x) { + rtc::Buffer buffer(n); + for (size_t i = 0; i < n; ++i) { + buffer[i] = static_cast(x + i); + } + return buffer; +} + +} // namespace + +TEST(SingleProcessEncodedImageIdInjector, InjectExtract) { + SingleProcessEncodedImageIdInjector injector; + + rtc::Buffer buffer = CreateBufferOfSizeNFilledWithValuesFromX(10, 1); + + EncodedImage source(buffer.data(), 10, 10); + source.SetTimestamp(123456789); + + std::pair out = + injector.ExtractId(injector.InjectId(512, source, 1), 2); + ASSERT_EQ(out.first, 512); + ASSERT_EQ(out.second.size(), 10ul); + ASSERT_EQ(out.second.capacity(), 10ul); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(out.second.data()[i], i + 1); + } +} + +TEST(SingleProcessEncodedImageIdInjector, Inject3Extract3) { + SingleProcessEncodedImageIdInjector injector; + + rtc::Buffer buffer1 = CreateBufferOfSizeNFilledWithValuesFromX(10, 1); + rtc::Buffer buffer2 = CreateBufferOfSizeNFilledWithValuesFromX(10, 11); + rtc::Buffer buffer3 = CreateBufferOfSizeNFilledWithValuesFromX(10, 21); + + // 1st frame + EncodedImage source1(buffer1.data(), 10, 10); + source1.SetTimestamp(123456710); + // 2nd frame 1st spatial layer + EncodedImage source2(buffer2.data(), 10, 10); + source2.SetTimestamp(123456720); + // 2nd frame 2nd spatial layer + EncodedImage source3(buffer3.data(), 10, 10); + source3.SetTimestamp(123456720); + + EncodedImage intermediate1 = injector.InjectId(510, source1, 1); + EncodedImage intermediate2 = injector.InjectId(520, source2, 1); + EncodedImage intermediate3 = injector.InjectId(520, source3, 1); + + // Extract ids in different order. + std::pair out3 = injector.ExtractId(intermediate3, 2); + std::pair out1 = injector.ExtractId(intermediate1, 2); + std::pair out2 = injector.ExtractId(intermediate2, 2); + + ASSERT_EQ(out1.first, 510); + ASSERT_EQ(out1.second.size(), 10ul); + ASSERT_EQ(out1.second.capacity(), 10ul); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(out1.second.data()[i], i + 1); + } + ASSERT_EQ(out2.first, 520); + ASSERT_EQ(out2.second.size(), 10ul); + ASSERT_EQ(out2.second.capacity(), 10ul); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(out2.second.data()[i], i + 11); + } + ASSERT_EQ(out3.first, 520); + ASSERT_EQ(out3.second.size(), 10ul); + ASSERT_EQ(out3.second.capacity(), 10ul); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(out3.second.data()[i], i + 21); + } +} + +TEST(SingleProcessEncodedImageIdInjector, InjectExtractFromConcatenated) { + SingleProcessEncodedImageIdInjector injector; + + rtc::Buffer buffer1 = CreateBufferOfSizeNFilledWithValuesFromX(10, 1); + rtc::Buffer buffer2 = CreateBufferOfSizeNFilledWithValuesFromX(10, 11); + rtc::Buffer buffer3 = CreateBufferOfSizeNFilledWithValuesFromX(10, 21); + + EncodedImage source1(buffer1.data(), 10, 10); + source1.SetTimestamp(123456710); + EncodedImage source2(buffer2.data(), 10, 10); + source2.SetTimestamp(123456710); + EncodedImage source3(buffer3.data(), 10, 10); + source3.SetTimestamp(123456710); + + // Inject id into 3 images with same frame id. + EncodedImage intermediate1 = injector.InjectId(512, source1, 1); + EncodedImage intermediate2 = injector.InjectId(512, source2, 1); + EncodedImage intermediate3 = injector.InjectId(512, source3, 1); + + // Concatenate them into single encoded image, like it can be done in jitter + // buffer. + size_t concatenated_length = + intermediate1.size() + intermediate2.size() + intermediate3.size(); + rtc::Buffer concatenated_buffer; + concatenated_buffer.AppendData(intermediate1.data(), intermediate1.size()); + concatenated_buffer.AppendData(intermediate2.data(), intermediate2.size()); + concatenated_buffer.AppendData(intermediate3.data(), intermediate3.size()); + EncodedImage concatenated(concatenated_buffer.data(), concatenated_length, + concatenated_length); + + // Extract frame id from concatenated image + std::pair out = injector.ExtractId(concatenated, 2); + + ASSERT_EQ(out.first, 512); + ASSERT_EQ(out.second.size(), 3 * 10ul); + ASSERT_EQ(out.second.capacity(), 3 * 10ul); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(out.second.data()[i], i + 1); + ASSERT_EQ(out.second.data()[i + 10], i + 11); + ASSERT_EQ(out.second.data()[i + 20], i + 21); + } +} + +} // namespace test +} // namespace webrtc