1 /*
2 * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "modules/video_coding/h264_sps_pps_tracker.h"
12
13 #include <string.h>
14
15 #include <vector>
16
17 #include "absl/types/variant.h"
18 #include "common_video/h264/h264_common.h"
19 #include "modules/rtp_rtcp/source/rtp_video_header.h"
20 #include "modules/video_coding/codecs/h264/include/h264_globals.h"
21 #include "modules/video_coding/packet.h"
22 #include "test/gmock.h"
23 #include "test/gtest.h"
24
25 namespace webrtc {
26 namespace video_coding {
27 namespace {
28
29 using ::testing::ElementsAreArray;
30
31 const uint8_t start_code[] = {0, 0, 0, 1};
32
Bitstream(const H264SpsPpsTracker::FixedBitstream & fixed)33 rtc::ArrayView<const uint8_t> Bitstream(
34 const H264SpsPpsTracker::FixedBitstream& fixed) {
35 return fixed.bitstream;
36 }
37
ExpectSpsPpsIdr(const RTPVideoHeaderH264 & codec_header,uint8_t sps_id,uint8_t pps_id)38 void ExpectSpsPpsIdr(const RTPVideoHeaderH264& codec_header,
39 uint8_t sps_id,
40 uint8_t pps_id) {
41 bool contains_sps = false;
42 bool contains_pps = false;
43 bool contains_idr = false;
44 for (const auto& nalu : codec_header.nalus) {
45 if (nalu.type == H264::NaluType::kSps) {
46 EXPECT_EQ(sps_id, nalu.sps_id);
47 contains_sps = true;
48 } else if (nalu.type == H264::NaluType::kPps) {
49 EXPECT_EQ(sps_id, nalu.sps_id);
50 EXPECT_EQ(pps_id, nalu.pps_id);
51 contains_pps = true;
52 } else if (nalu.type == H264::NaluType::kIdr) {
53 EXPECT_EQ(pps_id, nalu.pps_id);
54 contains_idr = true;
55 }
56 }
57 EXPECT_TRUE(contains_sps);
58 EXPECT_TRUE(contains_pps);
59 EXPECT_TRUE(contains_idr);
60 }
61
62 class H264VideoHeader : public RTPVideoHeader {
63 public:
H264VideoHeader()64 H264VideoHeader() {
65 codec = kVideoCodecH264;
66 is_first_packet_in_frame = false;
67 auto& h264_header = video_type_header.emplace<RTPVideoHeaderH264>();
68 h264_header.nalus_length = 0;
69 h264_header.packetization_type = kH264SingleNalu;
70 }
71
h264()72 RTPVideoHeaderH264& h264() {
73 return absl::get<RTPVideoHeaderH264>(video_type_header);
74 }
75 };
76
77 } // namespace
78
79 class TestH264SpsPpsTracker : public ::testing::Test {
80 public:
AddSps(H264VideoHeader * header,uint8_t sps_id,std::vector<uint8_t> * data)81 void AddSps(H264VideoHeader* header,
82 uint8_t sps_id,
83 std::vector<uint8_t>* data) {
84 NaluInfo info;
85 info.type = H264::NaluType::kSps;
86 info.sps_id = sps_id;
87 info.pps_id = -1;
88 data->push_back(H264::NaluType::kSps);
89 data->push_back(sps_id); // The sps data, just a single byte.
90
91 header->h264().nalus[header->h264().nalus_length++] = info;
92 }
93
AddPps(H264VideoHeader * header,uint8_t sps_id,uint8_t pps_id,std::vector<uint8_t> * data)94 void AddPps(H264VideoHeader* header,
95 uint8_t sps_id,
96 uint8_t pps_id,
97 std::vector<uint8_t>* data) {
98 NaluInfo info;
99 info.type = H264::NaluType::kPps;
100 info.sps_id = sps_id;
101 info.pps_id = pps_id;
102 data->push_back(H264::NaluType::kPps);
103 data->push_back(pps_id); // The pps data, just a single byte.
104
105 header->h264().nalus[header->h264().nalus_length++] = info;
106 }
107
AddIdr(H264VideoHeader * header,int pps_id)108 void AddIdr(H264VideoHeader* header, int pps_id) {
109 NaluInfo info;
110 info.type = H264::NaluType::kIdr;
111 info.sps_id = -1;
112 info.pps_id = pps_id;
113
114 header->h264().nalus[header->h264().nalus_length++] = info;
115 }
116
117 protected:
118 H264SpsPpsTracker tracker_;
119 };
120
TEST_F(TestH264SpsPpsTracker,NoNalus)121 TEST_F(TestH264SpsPpsTracker, NoNalus) {
122 uint8_t data[] = {1, 2, 3};
123 H264VideoHeader header;
124 header.h264().packetization_type = kH264FuA;
125
126 H264SpsPpsTracker::FixedBitstream fixed =
127 tracker_.CopyAndFixBitstream(data, &header);
128
129 EXPECT_EQ(fixed.action, H264SpsPpsTracker::kInsert);
130 EXPECT_THAT(Bitstream(fixed), ElementsAreArray(data));
131 }
132
TEST_F(TestH264SpsPpsTracker,FuAFirstPacket)133 TEST_F(TestH264SpsPpsTracker, FuAFirstPacket) {
134 uint8_t data[] = {1, 2, 3};
135 H264VideoHeader header;
136 header.h264().packetization_type = kH264FuA;
137 header.h264().nalus_length = 1;
138 header.is_first_packet_in_frame = true;
139
140 H264SpsPpsTracker::FixedBitstream fixed =
141 tracker_.CopyAndFixBitstream(data, &header);
142
143 EXPECT_EQ(fixed.action, H264SpsPpsTracker::kInsert);
144 std::vector<uint8_t> expected;
145 expected.insert(expected.end(), start_code, start_code + sizeof(start_code));
146 expected.insert(expected.end(), {1, 2, 3});
147 EXPECT_THAT(Bitstream(fixed), ElementsAreArray(expected));
148 }
149
TEST_F(TestH264SpsPpsTracker,StapAIncorrectSegmentLength)150 TEST_F(TestH264SpsPpsTracker, StapAIncorrectSegmentLength) {
151 uint8_t data[] = {0, 0, 2, 0};
152 H264VideoHeader header;
153 header.h264().packetization_type = kH264StapA;
154 header.is_first_packet_in_frame = true;
155
156 EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &header).action,
157 H264SpsPpsTracker::kDrop);
158 }
159
TEST_F(TestH264SpsPpsTracker,SingleNaluInsertStartCode)160 TEST_F(TestH264SpsPpsTracker, SingleNaluInsertStartCode) {
161 uint8_t data[] = {1, 2, 3};
162 H264VideoHeader header;
163 header.h264().nalus_length = 1;
164
165 H264SpsPpsTracker::FixedBitstream fixed =
166 tracker_.CopyAndFixBitstream(data, &header);
167
168 EXPECT_EQ(fixed.action, H264SpsPpsTracker::kInsert);
169 std::vector<uint8_t> expected;
170 expected.insert(expected.end(), start_code, start_code + sizeof(start_code));
171 expected.insert(expected.end(), {1, 2, 3});
172 EXPECT_THAT(Bitstream(fixed), ElementsAreArray(expected));
173 }
174
TEST_F(TestH264SpsPpsTracker,NoStartCodeInsertedForSubsequentFuAPacket)175 TEST_F(TestH264SpsPpsTracker, NoStartCodeInsertedForSubsequentFuAPacket) {
176 std::vector<uint8_t> data = {1, 2, 3};
177 H264VideoHeader header;
178 header.h264().packetization_type = kH264FuA;
179 // Since no NALU begin in this packet the nalus_length is zero.
180 header.h264().nalus_length = 0;
181
182 H264SpsPpsTracker::FixedBitstream fixed =
183 tracker_.CopyAndFixBitstream(data, &header);
184
185 EXPECT_EQ(fixed.action, H264SpsPpsTracker::kInsert);
186 EXPECT_THAT(Bitstream(fixed), ElementsAreArray(data));
187 }
188
TEST_F(TestH264SpsPpsTracker,IdrFirstPacketNoSpsPpsInserted)189 TEST_F(TestH264SpsPpsTracker, IdrFirstPacketNoSpsPpsInserted) {
190 std::vector<uint8_t> data = {1, 2, 3};
191 H264VideoHeader header;
192 header.is_first_packet_in_frame = true;
193 AddIdr(&header, 0);
194
195 EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &header).action,
196 H264SpsPpsTracker::kRequestKeyframe);
197 }
198
TEST_F(TestH264SpsPpsTracker,IdrFirstPacketNoPpsInserted)199 TEST_F(TestH264SpsPpsTracker, IdrFirstPacketNoPpsInserted) {
200 std::vector<uint8_t> data = {1, 2, 3};
201 H264VideoHeader header;
202 header.is_first_packet_in_frame = true;
203 AddSps(&header, 0, &data);
204 AddIdr(&header, 0);
205
206 EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &header).action,
207 H264SpsPpsTracker::kRequestKeyframe);
208 }
209
TEST_F(TestH264SpsPpsTracker,IdrFirstPacketNoSpsInserted)210 TEST_F(TestH264SpsPpsTracker, IdrFirstPacketNoSpsInserted) {
211 std::vector<uint8_t> data = {1, 2, 3};
212 H264VideoHeader header;
213 header.is_first_packet_in_frame = true;
214 AddPps(&header, 0, 0, &data);
215 AddIdr(&header, 0);
216
217 EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &header).action,
218 H264SpsPpsTracker::kRequestKeyframe);
219 }
220
TEST_F(TestH264SpsPpsTracker,SpsPpsPacketThenIdrFirstPacket)221 TEST_F(TestH264SpsPpsTracker, SpsPpsPacketThenIdrFirstPacket) {
222 std::vector<uint8_t> data;
223 H264VideoHeader sps_pps_header;
224 // Insert SPS/PPS
225 AddSps(&sps_pps_header, 0, &data);
226 AddPps(&sps_pps_header, 0, 1, &data);
227
228 EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &sps_pps_header).action,
229 H264SpsPpsTracker::kInsert);
230
231 // Insert first packet of the IDR
232 H264VideoHeader idr_header;
233 idr_header.is_first_packet_in_frame = true;
234 AddIdr(&idr_header, 1);
235 data = {1, 2, 3};
236
237 H264SpsPpsTracker::FixedBitstream fixed =
238 tracker_.CopyAndFixBitstream(data, &idr_header);
239 EXPECT_EQ(fixed.action, H264SpsPpsTracker::kInsert);
240
241 std::vector<uint8_t> expected;
242 expected.insert(expected.end(), start_code, start_code + sizeof(start_code));
243 expected.insert(expected.end(), {1, 2, 3});
244 EXPECT_THAT(Bitstream(fixed), ElementsAreArray(expected));
245 }
246
TEST_F(TestH264SpsPpsTracker,SpsPpsIdrInStapA)247 TEST_F(TestH264SpsPpsTracker, SpsPpsIdrInStapA) {
248 std::vector<uint8_t> data;
249 H264VideoHeader header;
250 header.h264().packetization_type = kH264StapA;
251 header.is_first_packet_in_frame = true; // Always true for StapA
252
253 data.insert(data.end(), {0}); // First byte is ignored
254 data.insert(data.end(), {0, 2}); // Length of segment
255 AddSps(&header, 13, &data);
256 data.insert(data.end(), {0, 2}); // Length of segment
257 AddPps(&header, 13, 27, &data);
258 data.insert(data.end(), {0, 5}); // Length of segment
259 AddIdr(&header, 27);
260 data.insert(data.end(), {1, 2, 3, 2, 1});
261
262 H264SpsPpsTracker::FixedBitstream fixed =
263 tracker_.CopyAndFixBitstream(data, &header);
264
265 EXPECT_THAT(fixed.action, H264SpsPpsTracker::kInsert);
266
267 std::vector<uint8_t> expected;
268 expected.insert(expected.end(), start_code, start_code + sizeof(start_code));
269 expected.insert(expected.end(), {H264::NaluType::kSps, 13});
270 expected.insert(expected.end(), start_code, start_code + sizeof(start_code));
271 expected.insert(expected.end(), {H264::NaluType::kPps, 27});
272 expected.insert(expected.end(), start_code, start_code + sizeof(start_code));
273 expected.insert(expected.end(), {1, 2, 3, 2, 1});
274 EXPECT_THAT(Bitstream(fixed), ElementsAreArray(expected));
275 }
276
TEST_F(TestH264SpsPpsTracker,SpsPpsOutOfBand)277 TEST_F(TestH264SpsPpsTracker, SpsPpsOutOfBand) {
278 constexpr uint8_t kData[] = {1, 2, 3};
279
280 // Generated by "ffmpeg -r 30 -f avfoundation -i "default" out.h264" on macos.
281 // width: 320, height: 240
282 const std::vector<uint8_t> sps(
283 {0x67, 0x7a, 0x00, 0x0d, 0xbc, 0xd9, 0x41, 0x41, 0xfa, 0x10, 0x00, 0x00,
284 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0xc0, 0xf1, 0x42, 0x99, 0x60});
285 const std::vector<uint8_t> pps({0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0});
286 tracker_.InsertSpsPpsNalus(sps, pps);
287
288 // Insert first packet of the IDR.
289 H264VideoHeader idr_header;
290 idr_header.is_first_packet_in_frame = true;
291 AddIdr(&idr_header, 0);
292 EXPECT_EQ(idr_header.h264().nalus_length, 1u);
293
294 H264SpsPpsTracker::FixedBitstream fixed =
295 tracker_.CopyAndFixBitstream(kData, &idr_header);
296
297 EXPECT_EQ(idr_header.h264().nalus_length, 3u);
298 EXPECT_EQ(idr_header.width, 320u);
299 EXPECT_EQ(idr_header.height, 240u);
300 ExpectSpsPpsIdr(idr_header.h264(), 0, 0);
301 }
302
TEST_F(TestH264SpsPpsTracker,SpsPpsOutOfBandWrongNaluHeader)303 TEST_F(TestH264SpsPpsTracker, SpsPpsOutOfBandWrongNaluHeader) {
304 constexpr uint8_t kData[] = {1, 2, 3};
305
306 // Generated by "ffmpeg -r 30 -f avfoundation -i "default" out.h264" on macos.
307 // Nalu headers manupilated afterwards.
308 const std::vector<uint8_t> sps(
309 {0xff, 0x7a, 0x00, 0x0d, 0xbc, 0xd9, 0x41, 0x41, 0xfa, 0x10, 0x00, 0x00,
310 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0xc0, 0xf1, 0x42, 0x99, 0x60});
311 const std::vector<uint8_t> pps({0xff, 0xeb, 0xe3, 0xcb, 0x22, 0xc0});
312 tracker_.InsertSpsPpsNalus(sps, pps);
313
314 // Insert first packet of the IDR.
315 H264VideoHeader idr_header;
316 idr_header.is_first_packet_in_frame = true;
317 AddIdr(&idr_header, 0);
318
319 EXPECT_EQ(tracker_.CopyAndFixBitstream(kData, &idr_header).action,
320 H264SpsPpsTracker::kRequestKeyframe);
321 }
322
TEST_F(TestH264SpsPpsTracker,SpsPpsOutOfBandIncompleteNalu)323 TEST_F(TestH264SpsPpsTracker, SpsPpsOutOfBandIncompleteNalu) {
324 constexpr uint8_t kData[] = {1, 2, 3};
325
326 // Generated by "ffmpeg -r 30 -f avfoundation -i "default" out.h264" on macos.
327 // Nalus damaged afterwards.
328 const std::vector<uint8_t> sps({0x67, 0x7a, 0x00, 0x0d, 0xbc, 0xd9});
329 const std::vector<uint8_t> pps({0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0});
330 tracker_.InsertSpsPpsNalus(sps, pps);
331
332 // Insert first packet of the IDR.
333 H264VideoHeader idr_header;
334 idr_header.is_first_packet_in_frame = true;
335 AddIdr(&idr_header, 0);
336
337 EXPECT_EQ(tracker_.CopyAndFixBitstream(kData, &idr_header).action,
338 H264SpsPpsTracker::kRequestKeyframe);
339 }
340
TEST_F(TestH264SpsPpsTracker,SaveRestoreWidthHeight)341 TEST_F(TestH264SpsPpsTracker, SaveRestoreWidthHeight) {
342 std::vector<uint8_t> data;
343
344 // Insert an SPS/PPS packet with width/height and make sure
345 // that information is set on the first IDR packet.
346 H264VideoHeader sps_pps_header;
347 AddSps(&sps_pps_header, 0, &data);
348 AddPps(&sps_pps_header, 0, 1, &data);
349 sps_pps_header.width = 320;
350 sps_pps_header.height = 240;
351
352 EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &sps_pps_header).action,
353 H264SpsPpsTracker::kInsert);
354
355 H264VideoHeader idr_header;
356 idr_header.is_first_packet_in_frame = true;
357 AddIdr(&idr_header, 1);
358 data.insert(data.end(), {1, 2, 3});
359
360 EXPECT_EQ(tracker_.CopyAndFixBitstream(data, &idr_header).action,
361 H264SpsPpsTracker::kInsert);
362
363 EXPECT_EQ(idr_header.width, 320);
364 EXPECT_EQ(idr_header.height, 240);
365 }
366
367 } // namespace video_coding
368 } // namespace webrtc
369