|  | /* | 
|  | *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #include "modules/video_coding/h264_sps_pps_tracker.h" | 
|  |  | 
|  | #include <cstdint> | 
|  | #include <vector> | 
|  |  | 
|  | #include "api/array_view.h" | 
|  | #include "api/video/video_codec_type.h" | 
|  | #include "common_video/h264/h264_common.h" | 
|  | #include "modules/rtp_rtcp/source/rtp_video_header.h" | 
|  | #include "modules/video_coding/codecs/h264/include/h264_globals.h" | 
|  | #include "test/gmock.h" | 
|  | #include "test/gtest.h" | 
|  |  | 
|  | namespace webrtc { | 
|  | namespace video_coding { | 
|  | namespace { | 
|  |  | 
|  | using ::testing::ElementsAreArray; | 
|  | using ::testing::SizeIs; | 
|  |  | 
|  | const uint8_t start_code[] = {0, 0, 0, 1}; | 
|  |  | 
|  | ArrayView<const uint8_t> Bitstream( | 
|  | const H264SpsPpsTracker::FixedBitstream& fixed) { | 
|  | return fixed.bitstream; | 
|  | } | 
|  |  | 
|  | void ExpectSpsPpsIdr(const RTPVideoHeaderH264& codec_header, | 
|  | uint8_t sps_id, | 
|  | uint8_t pps_id) { | 
|  | bool contains_sps = false; | 
|  | bool contains_pps = false; | 
|  | bool contains_idr = false; | 
|  | for (const auto& nalu : codec_header.nalus) { | 
|  | if (nalu.type == H264::NaluType::kSps) { | 
|  | EXPECT_EQ(sps_id, nalu.sps_id); | 
|  | contains_sps = true; | 
|  | } else if (nalu.type == H264::NaluType::kPps) { | 
|  | EXPECT_EQ(sps_id, nalu.sps_id); | 
|  | EXPECT_EQ(pps_id, nalu.pps_id); | 
|  | contains_pps = true; | 
|  | } else if (nalu.type == H264::NaluType::kIdr) { | 
|  | EXPECT_EQ(pps_id, nalu.pps_id); | 
|  | contains_idr = true; | 
|  | } | 
|  | } | 
|  | EXPECT_TRUE(contains_sps); | 
|  | EXPECT_TRUE(contains_pps); | 
|  | EXPECT_TRUE(contains_idr); | 
|  | } | 
|  |  | 
|  | class H264VideoHeader : public RTPVideoHeader { | 
|  | public: | 
|  | H264VideoHeader() { | 
|  | codec = kVideoCodecH264; | 
|  | is_first_packet_in_frame = false; | 
|  | auto& h264_header = video_type_header.emplace<RTPVideoHeaderH264>(); | 
|  | h264_header.packetization_type = kH264SingleNalu; | 
|  | } | 
|  |  | 
|  | RTPVideoHeaderH264& h264() { | 
|  | return std::get<RTPVideoHeaderH264>(video_type_header); | 
|  | } | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class TestH264SpsPpsTracker : public ::testing::Test { | 
|  | public: | 
|  | void AddSps(H264VideoHeader* header, | 
|  | uint8_t sps_id, | 
|  | std::vector<uint8_t>* data) { | 
|  | NaluInfo info; | 
|  | info.type = H264::NaluType::kSps; | 
|  | info.sps_id = sps_id; | 
|  | info.pps_id = -1; | 
|  | data->push_back(H264::NaluType::kSps); | 
|  | data->push_back(sps_id);  // The sps data, just a single byte. | 
|  |  | 
|  | header->h264().nalus.push_back(info); | 
|  | } | 
|  |  | 
|  | void AddPps(H264VideoHeader* header, | 
|  | uint8_t sps_id, | 
|  | uint8_t pps_id, | 
|  | std::vector<uint8_t>* data) { | 
|  | NaluInfo info; | 
|  | info.type = H264::NaluType::kPps; | 
|  | info.sps_id = sps_id; | 
|  | info.pps_id = pps_id; | 
|  | data->push_back(H264::NaluType::kPps); | 
|  | data->push_back(pps_id);  // The pps data, just a single byte. | 
|  |  | 
|  | header->h264().nalus.push_back(info); | 
|  | } | 
|  |  | 
|  | void AddIdr(H264VideoHeader* header, int pps_id) { | 
|  | NaluInfo info; | 
|  | info.type = H264::NaluType::kIdr; | 
|  | info.sps_id = -1; | 
|  | info.pps_id = pps_id; | 
|  |  | 
|  | header->h264().nalus.push_back(info); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | H264SpsPpsTracker tracker_; | 
|  | }; | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, NoNalus) { | 
|  | uint8_t data[] = {1, 2, 3}; | 
|  | H264VideoHeader header; | 
|  | header.h264().packetization_type = kH264FuA; | 
|  |  | 
|  | H264SpsPpsTracker::FixedBitstream fixed = | 
|  | tracker_.CopyAndFixBitstream(data, &header); | 
|  |  | 
|  | EXPECT_EQ(fixed.action, H264SpsPpsTracker::kInsert); | 
|  | EXPECT_THAT(Bitstream(fixed), ElementsAreArray(data)); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, FuAFirstPacket) { | 
|  | uint8_t data[] = {1, 2, 3}; | 
|  | H264VideoHeader header; | 
|  | header.h264().packetization_type = kH264FuA; | 
|  | header.h264().nalus.resize(1); | 
|  | header.is_first_packet_in_frame = true; | 
|  |  | 
|  | H264SpsPpsTracker::FixedBitstream fixed = | 
|  | tracker_.CopyAndFixBitstream(data, &header); | 
|  |  | 
|  | EXPECT_EQ(fixed.action, H264SpsPpsTracker::kInsert); | 
|  | std::vector<uint8_t> expected; | 
|  | expected.insert(expected.end(), start_code, start_code + sizeof(start_code)); | 
|  | expected.insert(expected.end(), {1, 2, 3}); | 
|  | EXPECT_THAT(Bitstream(fixed), ElementsAreArray(expected)); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, StapAIncorrectSegmentLength) { | 
|  | uint8_t data[] = {0, 0, 2, 0}; | 
|  | H264VideoHeader header; | 
|  | header.h264().packetization_type = kH264StapA; | 
|  | header.is_first_packet_in_frame = true; | 
|  |  | 
|  | EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &header).action, | 
|  | H264SpsPpsTracker::kDrop); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, ConsecutiveStapA) { | 
|  | // When the GenericFrameDescriptor or DependencyDescriptor RTP header | 
|  | // extensions are used, we may receive a series of StapA packets where only | 
|  | // the first packet has is_first_packet_in_frame = true set. | 
|  | std::vector<uint8_t> data; | 
|  | H264VideoHeader first_header; | 
|  | first_header.h264().packetization_type = kH264StapA; | 
|  | first_header.is_first_packet_in_frame = true; | 
|  |  | 
|  | // SPS in first packet. | 
|  | data.insert(data.end(), {0});     // First byte is ignored | 
|  | data.insert(data.end(), {0, 2});  // Length of segment | 
|  | AddSps(&first_header, 13, &data); | 
|  | H264SpsPpsTracker::FixedBitstream first_fixed = | 
|  | tracker_.CopyAndFixBitstream(data, &first_header); | 
|  | EXPECT_THAT(first_fixed.action, H264SpsPpsTracker::kInsert); | 
|  |  | 
|  | H264VideoHeader second_header; | 
|  | second_header.h264().packetization_type = kH264StapA; | 
|  | second_header.is_first_packet_in_frame = false; | 
|  |  | 
|  | // PPS and IDR in second packet. | 
|  | data.insert(data.end(), {0, 2});  // Length of segment | 
|  | AddPps(&second_header, 13, 27, &data); | 
|  | data.insert(data.end(), {0, 5});  // Length of segment | 
|  | AddIdr(&second_header, 27); | 
|  | data.insert(data.end(), {1, 2, 3, 2, 1}); | 
|  |  | 
|  | H264SpsPpsTracker::FixedBitstream fixed = | 
|  | tracker_.CopyAndFixBitstream(data, &second_header); | 
|  |  | 
|  | EXPECT_THAT(fixed.action, H264SpsPpsTracker::kInsert); | 
|  | std::vector<uint8_t> expected; | 
|  | expected.insert(expected.end(), start_code, start_code + sizeof(start_code)); | 
|  | expected.insert(expected.end(), {H264::NaluType::kSps, 13}); | 
|  | expected.insert(expected.end(), start_code, start_code + sizeof(start_code)); | 
|  | expected.insert(expected.end(), {H264::NaluType::kPps, 27}); | 
|  | expected.insert(expected.end(), start_code, start_code + sizeof(start_code)); | 
|  | expected.insert(expected.end(), {1, 2, 3, 2, 1}); | 
|  | EXPECT_THAT(Bitstream(fixed), ElementsAreArray(expected)); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, SingleNaluInsertStartCode) { | 
|  | uint8_t data[] = {1, 2, 3}; | 
|  | H264VideoHeader header; | 
|  | header.h264().nalus.resize(1); | 
|  |  | 
|  | H264SpsPpsTracker::FixedBitstream fixed = | 
|  | tracker_.CopyAndFixBitstream(data, &header); | 
|  |  | 
|  | EXPECT_EQ(fixed.action, H264SpsPpsTracker::kInsert); | 
|  | std::vector<uint8_t> expected; | 
|  | expected.insert(expected.end(), start_code, start_code + sizeof(start_code)); | 
|  | expected.insert(expected.end(), {1, 2, 3}); | 
|  | EXPECT_THAT(Bitstream(fixed), ElementsAreArray(expected)); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, NoStartCodeInsertedForSubsequentFuAPacket) { | 
|  | std::vector<uint8_t> data = {1, 2, 3}; | 
|  | H264VideoHeader header; | 
|  | header.h264().packetization_type = kH264FuA; | 
|  | // Since no NALU begin in this packet the nalus are empty. | 
|  | header.h264().nalus.clear(); | 
|  |  | 
|  | H264SpsPpsTracker::FixedBitstream fixed = | 
|  | tracker_.CopyAndFixBitstream(data, &header); | 
|  |  | 
|  | EXPECT_EQ(fixed.action, H264SpsPpsTracker::kInsert); | 
|  | EXPECT_THAT(Bitstream(fixed), ElementsAreArray(data)); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, IdrFirstPacketNoSpsPpsInserted) { | 
|  | std::vector<uint8_t> data = {1, 2, 3}; | 
|  | H264VideoHeader header; | 
|  | header.is_first_packet_in_frame = true; | 
|  | AddIdr(&header, 0); | 
|  |  | 
|  | EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &header).action, | 
|  | H264SpsPpsTracker::kRequestKeyframe); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, IdrFirstPacketNoPpsInserted) { | 
|  | std::vector<uint8_t> data = {1, 2, 3}; | 
|  | H264VideoHeader header; | 
|  | header.is_first_packet_in_frame = true; | 
|  | AddSps(&header, 0, &data); | 
|  | AddIdr(&header, 0); | 
|  |  | 
|  | EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &header).action, | 
|  | H264SpsPpsTracker::kRequestKeyframe); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, IdrFirstPacketNoSpsInserted) { | 
|  | std::vector<uint8_t> data = {1, 2, 3}; | 
|  | H264VideoHeader header; | 
|  | header.is_first_packet_in_frame = true; | 
|  | AddPps(&header, 0, 0, &data); | 
|  | AddIdr(&header, 0); | 
|  |  | 
|  | EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &header).action, | 
|  | H264SpsPpsTracker::kRequestKeyframe); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, SpsPpsPacketThenIdrFirstPacket) { | 
|  | std::vector<uint8_t> data; | 
|  | H264VideoHeader sps_pps_header; | 
|  | // Insert SPS/PPS | 
|  | AddSps(&sps_pps_header, 0, &data); | 
|  | AddPps(&sps_pps_header, 0, 1, &data); | 
|  |  | 
|  | EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &sps_pps_header).action, | 
|  | H264SpsPpsTracker::kInsert); | 
|  |  | 
|  | // Insert first packet of the IDR | 
|  | H264VideoHeader idr_header; | 
|  | idr_header.is_first_packet_in_frame = true; | 
|  | AddIdr(&idr_header, 1); | 
|  | data = {1, 2, 3}; | 
|  |  | 
|  | H264SpsPpsTracker::FixedBitstream fixed = | 
|  | tracker_.CopyAndFixBitstream(data, &idr_header); | 
|  | EXPECT_EQ(fixed.action, H264SpsPpsTracker::kInsert); | 
|  |  | 
|  | std::vector<uint8_t> expected; | 
|  | expected.insert(expected.end(), start_code, start_code + sizeof(start_code)); | 
|  | expected.insert(expected.end(), {1, 2, 3}); | 
|  | EXPECT_THAT(Bitstream(fixed), ElementsAreArray(expected)); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, SpsPpsIdrInStapA) { | 
|  | std::vector<uint8_t> data; | 
|  | H264VideoHeader header; | 
|  | header.h264().packetization_type = kH264StapA; | 
|  | header.is_first_packet_in_frame = true;  // Always true for StapA | 
|  |  | 
|  | data.insert(data.end(), {0});     // First byte is ignored | 
|  | data.insert(data.end(), {0, 2});  // Length of segment | 
|  | AddSps(&header, 13, &data); | 
|  | data.insert(data.end(), {0, 2});  // Length of segment | 
|  | AddPps(&header, 13, 27, &data); | 
|  | data.insert(data.end(), {0, 5});  // Length of segment | 
|  | AddIdr(&header, 27); | 
|  | data.insert(data.end(), {1, 2, 3, 2, 1}); | 
|  |  | 
|  | H264SpsPpsTracker::FixedBitstream fixed = | 
|  | tracker_.CopyAndFixBitstream(data, &header); | 
|  |  | 
|  | EXPECT_THAT(fixed.action, H264SpsPpsTracker::kInsert); | 
|  |  | 
|  | std::vector<uint8_t> expected; | 
|  | expected.insert(expected.end(), start_code, start_code + sizeof(start_code)); | 
|  | expected.insert(expected.end(), {H264::NaluType::kSps, 13}); | 
|  | expected.insert(expected.end(), start_code, start_code + sizeof(start_code)); | 
|  | expected.insert(expected.end(), {H264::NaluType::kPps, 27}); | 
|  | expected.insert(expected.end(), start_code, start_code + sizeof(start_code)); | 
|  | expected.insert(expected.end(), {1, 2, 3, 2, 1}); | 
|  | EXPECT_THAT(Bitstream(fixed), ElementsAreArray(expected)); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, SpsPpsOutOfBand) { | 
|  | constexpr uint8_t kData[] = {1, 2, 3}; | 
|  |  | 
|  | // Generated by "ffmpeg -r 30 -f avfoundation -i "default" out.h264" on macos. | 
|  | // width: 320, height: 240 | 
|  | const std::vector<uint8_t> sps( | 
|  | {0x67, 0x7a, 0x00, 0x0d, 0xbc, 0xd9, 0x41, 0x41, 0xfa, 0x10, 0x00, 0x00, | 
|  | 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0xc0, 0xf1, 0x42, 0x99, 0x60}); | 
|  | const std::vector<uint8_t> pps({0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0}); | 
|  | tracker_.InsertSpsPpsNalus(sps, pps); | 
|  |  | 
|  | // Insert first packet of the IDR. | 
|  | H264VideoHeader idr_header; | 
|  | idr_header.is_first_packet_in_frame = true; | 
|  | AddIdr(&idr_header, 0); | 
|  | EXPECT_THAT(idr_header.h264().nalus, SizeIs(1)); | 
|  |  | 
|  | H264SpsPpsTracker::FixedBitstream fixed = | 
|  | tracker_.CopyAndFixBitstream(kData, &idr_header); | 
|  |  | 
|  | EXPECT_THAT(idr_header.h264().nalus, SizeIs(3)); | 
|  | EXPECT_EQ(idr_header.width, 320u); | 
|  | EXPECT_EQ(idr_header.height, 240u); | 
|  | ExpectSpsPpsIdr(idr_header.h264(), 0, 0); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, SpsPpsOutOfBandWrongNaluHeader) { | 
|  | constexpr uint8_t kData[] = {1, 2, 3}; | 
|  |  | 
|  | // Generated by "ffmpeg -r 30 -f avfoundation -i "default" out.h264" on macos. | 
|  | // Nalu headers manupilated afterwards. | 
|  | const std::vector<uint8_t> sps( | 
|  | {0xff, 0x7a, 0x00, 0x0d, 0xbc, 0xd9, 0x41, 0x41, 0xfa, 0x10, 0x00, 0x00, | 
|  | 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0xc0, 0xf1, 0x42, 0x99, 0x60}); | 
|  | const std::vector<uint8_t> pps({0xff, 0xeb, 0xe3, 0xcb, 0x22, 0xc0}); | 
|  | tracker_.InsertSpsPpsNalus(sps, pps); | 
|  |  | 
|  | // Insert first packet of the IDR. | 
|  | H264VideoHeader idr_header; | 
|  | idr_header.is_first_packet_in_frame = true; | 
|  | AddIdr(&idr_header, 0); | 
|  |  | 
|  | EXPECT_EQ(tracker_.CopyAndFixBitstream(kData, &idr_header).action, | 
|  | H264SpsPpsTracker::kRequestKeyframe); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, SpsPpsOutOfBandIncompleteNalu) { | 
|  | constexpr uint8_t kData[] = {1, 2, 3}; | 
|  |  | 
|  | // Generated by "ffmpeg -r 30 -f avfoundation -i "default" out.h264" on macos. | 
|  | // Nalus damaged afterwards. | 
|  | const std::vector<uint8_t> sps({0x67, 0x7a, 0x00, 0x0d, 0xbc, 0xd9}); | 
|  | const std::vector<uint8_t> pps({0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0}); | 
|  | tracker_.InsertSpsPpsNalus(sps, pps); | 
|  |  | 
|  | // Insert first packet of the IDR. | 
|  | H264VideoHeader idr_header; | 
|  | idr_header.is_first_packet_in_frame = true; | 
|  | AddIdr(&idr_header, 0); | 
|  |  | 
|  | EXPECT_EQ(tracker_.CopyAndFixBitstream(kData, &idr_header).action, | 
|  | H264SpsPpsTracker::kRequestKeyframe); | 
|  | } | 
|  |  | 
|  | TEST_F(TestH264SpsPpsTracker, SaveRestoreWidthHeight) { | 
|  | std::vector<uint8_t> data; | 
|  |  | 
|  | // Insert an SPS/PPS packet with width/height and make sure | 
|  | // that information is set on the first IDR packet. | 
|  | H264VideoHeader sps_pps_header; | 
|  | AddSps(&sps_pps_header, 0, &data); | 
|  | AddPps(&sps_pps_header, 0, 1, &data); | 
|  | sps_pps_header.width = 320; | 
|  | sps_pps_header.height = 240; | 
|  |  | 
|  | EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &sps_pps_header).action, | 
|  | H264SpsPpsTracker::kInsert); | 
|  |  | 
|  | H264VideoHeader idr_header; | 
|  | idr_header.is_first_packet_in_frame = true; | 
|  | AddIdr(&idr_header, 1); | 
|  | data.insert(data.end(), {1, 2, 3}); | 
|  |  | 
|  | EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &idr_header).action, | 
|  | H264SpsPpsTracker::kInsert); | 
|  |  | 
|  | EXPECT_EQ(idr_header.width, 320); | 
|  | EXPECT_EQ(idr_header.height, 240); | 
|  | } | 
|  |  | 
|  | }  // namespace video_coding | 
|  | }  // namespace webrtc |