blob: a3cb379d18a655548682229137e3e8fdbf551b50 [file] [log] [blame]
Stefan Holmerf7044682018-07-17 10:16:41 +02001/*
2 * Copyright (c) 2018 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 <memory>
philipelbf2b6202018-08-27 14:33:18 +020012#include <set>
Stefan Holmerf7044682018-07-17 10:16:41 +020013
Stefan Holmer9416ef82018-07-19 10:34:38 +020014#include "call/rtp_payload_params.h"
Stefan Holmerf7044682018-07-17 10:16:41 +020015#include "modules/video_coding/include/video_codec_interface.h"
philipelbf2b6202018-08-27 14:33:18 +020016#include "test/field_trial.h"
Stefan Holmerf7044682018-07-17 10:16:41 +020017#include "test/gtest.h"
18
19namespace webrtc {
20namespace {
21const uint32_t kSsrc1 = 12345;
22const uint32_t kSsrc2 = 23456;
23const int16_t kPictureId = 123;
24const int16_t kTl0PicIdx = 20;
25const uint8_t kTemporalIdx = 1;
26const int16_t kInitialPictureId1 = 222;
27const int16_t kInitialTl0PicIdx1 = 99;
philipelbf2b6202018-08-27 14:33:18 +020028const int64_t kDontCare = 0;
Stefan Holmerf7044682018-07-17 10:16:41 +020029} // namespace
30
31TEST(RtpPayloadParamsTest, InfoMappedToRtpVideoHeader_Vp8) {
32 RtpPayloadState state2;
33 state2.picture_id = kPictureId;
34 state2.tl0_pic_idx = kTl0PicIdx;
35 std::map<uint32_t, RtpPayloadState> states = {{kSsrc2, state2}};
36
37 RtpPayloadParams params(kSsrc2, &state2);
38 EncodedImage encoded_image;
39 encoded_image.rotation_ = kVideoRotation_90;
40 encoded_image.content_type_ = VideoContentType::SCREENSHARE;
Niels Möllerd3b8c632018-08-27 15:33:42 +020041 encoded_image.SetSpatialIndex(1);
Stefan Holmerf7044682018-07-17 10:16:41 +020042
43 CodecSpecificInfo codec_info;
44 memset(&codec_info, 0, sizeof(CodecSpecificInfo));
45 codec_info.codecType = kVideoCodecVP8;
philipelbf2b6202018-08-27 14:33:18 +020046 codec_info.codecSpecific.VP8.temporalIdx = 0;
Stefan Holmerf7044682018-07-17 10:16:41 +020047 codec_info.codecSpecific.VP8.keyIdx = kNoKeyIdx;
philipelbf2b6202018-08-27 14:33:18 +020048 codec_info.codecSpecific.VP8.layerSync = false;
Stefan Holmerf7044682018-07-17 10:16:41 +020049 codec_info.codecSpecific.VP8.nonReference = true;
50
philipelbf2b6202018-08-27 14:33:18 +020051 RTPVideoHeader header =
52 params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
53
54 codec_info.codecType = kVideoCodecVP8;
philipelbf2b6202018-08-27 14:33:18 +020055 codec_info.codecSpecific.VP8.temporalIdx = 1;
56 codec_info.codecSpecific.VP8.layerSync = true;
57
58 header = params.GetRtpVideoHeader(encoded_image, &codec_info, 1);
Stefan Holmerf7044682018-07-17 10:16:41 +020059
60 EXPECT_EQ(kVideoRotation_90, header.rotation);
61 EXPECT_EQ(VideoContentType::SCREENSHARE, header.content_type);
62 EXPECT_EQ(1, header.simulcastIdx);
63 EXPECT_EQ(kVideoCodecVP8, header.codec);
Philip Eliassond52a1a62018-09-07 13:03:55 +000064 const auto& vp8_header =
65 absl::get<RTPVideoHeaderVP8>(header.video_type_header);
66 EXPECT_EQ(kPictureId + 2, vp8_header.pictureId);
67 EXPECT_EQ(kTemporalIdx, vp8_header.temporalIdx);
68 EXPECT_EQ(kTl0PicIdx + 1, vp8_header.tl0PicIdx);
69 EXPECT_EQ(kNoKeyIdx, vp8_header.keyIdx);
70 EXPECT_TRUE(vp8_header.layerSync);
71 EXPECT_TRUE(vp8_header.nonReference);
Stefan Holmerf7044682018-07-17 10:16:41 +020072}
73
74TEST(RtpPayloadParamsTest, InfoMappedToRtpVideoHeader_Vp9) {
75 RtpPayloadState state;
76 state.picture_id = kPictureId;
77 state.tl0_pic_idx = kTl0PicIdx;
78 RtpPayloadParams params(kSsrc1, &state);
79
80 EncodedImage encoded_image;
81 encoded_image.rotation_ = kVideoRotation_90;
82 encoded_image.content_type_ = VideoContentType::SCREENSHARE;
Niels Möllerd3b8c632018-08-27 15:33:42 +020083 encoded_image.SetSpatialIndex(0);
Stefan Holmerf7044682018-07-17 10:16:41 +020084 CodecSpecificInfo codec_info;
85 memset(&codec_info, 0, sizeof(CodecSpecificInfo));
86 codec_info.codecType = kVideoCodecVP9;
87 codec_info.codecSpecific.VP9.num_spatial_layers = 3;
88 codec_info.codecSpecific.VP9.first_frame_in_picture = true;
Stefan Holmerf7044682018-07-17 10:16:41 +020089 codec_info.codecSpecific.VP9.temporal_idx = 2;
90 codec_info.codecSpecific.VP9.end_of_picture = false;
91
philipelbf2b6202018-08-27 14:33:18 +020092 RTPVideoHeader header =
93 params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
Stefan Holmerf7044682018-07-17 10:16:41 +020094
95 EXPECT_EQ(kVideoRotation_90, header.rotation);
96 EXPECT_EQ(VideoContentType::SCREENSHARE, header.content_type);
97 EXPECT_EQ(kVideoCodecVP9, header.codec);
philipel29d88462018-08-08 14:26:00 +020098 const auto& vp9_header =
99 absl::get<RTPVideoHeaderVP9>(header.video_type_header);
100 EXPECT_EQ(kPictureId + 1, vp9_header.picture_id);
101 EXPECT_EQ(kTl0PicIdx, vp9_header.tl0_pic_idx);
102 EXPECT_EQ(vp9_header.temporal_idx, codec_info.codecSpecific.VP9.temporal_idx);
Niels Möllerd3b8c632018-08-27 15:33:42 +0200103 EXPECT_EQ(vp9_header.spatial_idx, encoded_image.SpatialIndex());
philipel29d88462018-08-08 14:26:00 +0200104 EXPECT_EQ(vp9_header.num_spatial_layers,
Stefan Holmerf7044682018-07-17 10:16:41 +0200105 codec_info.codecSpecific.VP9.num_spatial_layers);
philipel29d88462018-08-08 14:26:00 +0200106 EXPECT_EQ(vp9_header.end_of_picture,
Stefan Holmerf7044682018-07-17 10:16:41 +0200107 codec_info.codecSpecific.VP9.end_of_picture);
108
109 // Next spatial layer.
110 codec_info.codecSpecific.VP9.first_frame_in_picture = false;
Stefan Holmerf7044682018-07-17 10:16:41 +0200111 codec_info.codecSpecific.VP9.end_of_picture = true;
112
Niels Möllerd3b8c632018-08-27 15:33:42 +0200113 encoded_image.SetSpatialIndex(1);
philipelbf2b6202018-08-27 14:33:18 +0200114 header = params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
Stefan Holmerf7044682018-07-17 10:16:41 +0200115
116 EXPECT_EQ(kVideoRotation_90, header.rotation);
117 EXPECT_EQ(VideoContentType::SCREENSHARE, header.content_type);
118 EXPECT_EQ(kVideoCodecVP9, header.codec);
philipel29d88462018-08-08 14:26:00 +0200119 EXPECT_EQ(kPictureId + 1, vp9_header.picture_id);
120 EXPECT_EQ(kTl0PicIdx, vp9_header.tl0_pic_idx);
121 EXPECT_EQ(vp9_header.temporal_idx, codec_info.codecSpecific.VP9.temporal_idx);
Niels Möllerd3b8c632018-08-27 15:33:42 +0200122 EXPECT_EQ(vp9_header.spatial_idx, encoded_image.SpatialIndex());
philipel29d88462018-08-08 14:26:00 +0200123 EXPECT_EQ(vp9_header.num_spatial_layers,
Stefan Holmerf7044682018-07-17 10:16:41 +0200124 codec_info.codecSpecific.VP9.num_spatial_layers);
philipel29d88462018-08-08 14:26:00 +0200125 EXPECT_EQ(vp9_header.end_of_picture,
Stefan Holmerf7044682018-07-17 10:16:41 +0200126 codec_info.codecSpecific.VP9.end_of_picture);
127}
128
129TEST(RtpPayloadParamsTest, InfoMappedToRtpVideoHeader_H264) {
130 RtpPayloadParams params(kSsrc1, {});
131
132 EncodedImage encoded_image;
133 CodecSpecificInfo codec_info;
134 memset(&codec_info, 0, sizeof(CodecSpecificInfo));
135 codec_info.codecType = kVideoCodecH264;
136 codec_info.codecSpecific.H264.packetization_mode =
137 H264PacketizationMode::SingleNalUnit;
138
philipelbf2b6202018-08-27 14:33:18 +0200139 RTPVideoHeader header =
140 params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
Stefan Holmerf7044682018-07-17 10:16:41 +0200141
142 EXPECT_EQ(0, header.simulcastIdx);
143 EXPECT_EQ(kVideoCodecH264, header.codec);
144 const auto& h264 = absl::get<RTPVideoHeaderH264>(header.video_type_header);
145 EXPECT_EQ(H264PacketizationMode::SingleNalUnit, h264.packetization_mode);
146}
147
148TEST(RtpPayloadParamsTest, PictureIdIsSetForVp8) {
149 RtpPayloadState state;
150 state.picture_id = kInitialPictureId1;
151 state.tl0_pic_idx = kInitialTl0PicIdx1;
152
153 EncodedImage encoded_image;
154 CodecSpecificInfo codec_info;
155 memset(&codec_info, 0, sizeof(CodecSpecificInfo));
156 codec_info.codecType = kVideoCodecVP8;
Stefan Holmerf7044682018-07-17 10:16:41 +0200157
158 RtpPayloadParams params(kSsrc1, &state);
philipelbf2b6202018-08-27 14:33:18 +0200159 RTPVideoHeader header =
160 params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
Stefan Holmerf7044682018-07-17 10:16:41 +0200161 EXPECT_EQ(kVideoCodecVP8, header.codec);
Philip Eliassond52a1a62018-09-07 13:03:55 +0000162 EXPECT_EQ(kInitialPictureId1 + 1,
163 absl::get<RTPVideoHeaderVP8>(header.video_type_header).pictureId);
Stefan Holmerf7044682018-07-17 10:16:41 +0200164
165 // State should hold latest used picture id and tl0_pic_idx.
166 state = params.state();
167 EXPECT_EQ(kInitialPictureId1 + 1, state.picture_id);
168 EXPECT_EQ(kInitialTl0PicIdx1 + 1, state.tl0_pic_idx);
169}
170
171TEST(RtpPayloadParamsTest, PictureIdWraps) {
172 RtpPayloadState state;
173 state.picture_id = kMaxTwoBytePictureId;
174 state.tl0_pic_idx = kInitialTl0PicIdx1;
175
176 EncodedImage encoded_image;
177 CodecSpecificInfo codec_info;
178 memset(&codec_info, 0, sizeof(CodecSpecificInfo));
179 codec_info.codecType = kVideoCodecVP8;
180 codec_info.codecSpecific.VP8.temporalIdx = kNoTemporalIdx;
181
182 RtpPayloadParams params(kSsrc1, &state);
philipelbf2b6202018-08-27 14:33:18 +0200183 RTPVideoHeader header =
184 params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
Stefan Holmerf7044682018-07-17 10:16:41 +0200185 EXPECT_EQ(kVideoCodecVP8, header.codec);
Philip Eliassond52a1a62018-09-07 13:03:55 +0000186 EXPECT_EQ(0,
187 absl::get<RTPVideoHeaderVP8>(header.video_type_header).pictureId);
Stefan Holmerf7044682018-07-17 10:16:41 +0200188
189 // State should hold latest used picture id and tl0_pic_idx.
190 EXPECT_EQ(0, params.state().picture_id); // Wrapped.
191 EXPECT_EQ(kInitialTl0PicIdx1, params.state().tl0_pic_idx);
192}
193
194TEST(RtpPayloadParamsTest, Tl0PicIdxUpdatedForVp8) {
195 RtpPayloadState state;
196 state.picture_id = kInitialPictureId1;
197 state.tl0_pic_idx = kInitialTl0PicIdx1;
198
199 EncodedImage encoded_image;
200 // Modules are sending for this test.
201 // OnEncodedImage, temporalIdx: 1.
202 CodecSpecificInfo codec_info;
203 memset(&codec_info, 0, sizeof(CodecSpecificInfo));
204 codec_info.codecType = kVideoCodecVP8;
205 codec_info.codecSpecific.VP8.temporalIdx = 1;
206
207 RtpPayloadParams params(kSsrc1, &state);
philipelbf2b6202018-08-27 14:33:18 +0200208 RTPVideoHeader header =
209 params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
Stefan Holmerf7044682018-07-17 10:16:41 +0200210
211 EXPECT_EQ(kVideoCodecVP8, header.codec);
Philip Eliassond52a1a62018-09-07 13:03:55 +0000212 const auto& vp8_header =
213 absl::get<RTPVideoHeaderVP8>(header.video_type_header);
214 EXPECT_EQ(kInitialPictureId1 + 1, vp8_header.pictureId);
215 EXPECT_EQ(kInitialTl0PicIdx1, vp8_header.tl0PicIdx);
Stefan Holmerf7044682018-07-17 10:16:41 +0200216
217 // OnEncodedImage, temporalIdx: 0.
218 codec_info.codecSpecific.VP8.temporalIdx = 0;
219
philipelbf2b6202018-08-27 14:33:18 +0200220 header = params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
Stefan Holmerf7044682018-07-17 10:16:41 +0200221 EXPECT_EQ(kVideoCodecVP8, header.codec);
Philip Eliassond52a1a62018-09-07 13:03:55 +0000222 EXPECT_EQ(kInitialPictureId1 + 2, vp8_header.pictureId);
223 EXPECT_EQ(kInitialTl0PicIdx1 + 1, vp8_header.tl0PicIdx);
Stefan Holmerf7044682018-07-17 10:16:41 +0200224
225 // State should hold latest used picture id and tl0_pic_idx.
226 EXPECT_EQ(kInitialPictureId1 + 2, params.state().picture_id);
227 EXPECT_EQ(kInitialTl0PicIdx1 + 1, params.state().tl0_pic_idx);
228}
229
230TEST(RtpPayloadParamsTest, Tl0PicIdxUpdatedForVp9) {
231 RtpPayloadState state;
232 state.picture_id = kInitialPictureId1;
233 state.tl0_pic_idx = kInitialTl0PicIdx1;
234
235 EncodedImage encoded_image;
236 // Modules are sending for this test.
237 // OnEncodedImage, temporalIdx: 1.
238 CodecSpecificInfo codec_info;
239 memset(&codec_info, 0, sizeof(CodecSpecificInfo));
240 codec_info.codecType = kVideoCodecVP9;
241 codec_info.codecSpecific.VP9.temporal_idx = 1;
242 codec_info.codecSpecific.VP9.first_frame_in_picture = true;
243
244 RtpPayloadParams params(kSsrc1, &state);
philipelbf2b6202018-08-27 14:33:18 +0200245 RTPVideoHeader header =
246 params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
Stefan Holmerf7044682018-07-17 10:16:41 +0200247
248 EXPECT_EQ(kVideoCodecVP9, header.codec);
philipel29d88462018-08-08 14:26:00 +0200249 const auto& vp9_header =
250 absl::get<RTPVideoHeaderVP9>(header.video_type_header);
251 EXPECT_EQ(kInitialPictureId1 + 1, vp9_header.picture_id);
252 EXPECT_EQ(kInitialTl0PicIdx1, vp9_header.tl0_pic_idx);
Stefan Holmerf7044682018-07-17 10:16:41 +0200253
254 // OnEncodedImage, temporalIdx: 0.
255 codec_info.codecSpecific.VP9.temporal_idx = 0;
256
philipelbf2b6202018-08-27 14:33:18 +0200257 header = params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
Stefan Holmerf7044682018-07-17 10:16:41 +0200258
259 EXPECT_EQ(kVideoCodecVP9, header.codec);
philipel29d88462018-08-08 14:26:00 +0200260 EXPECT_EQ(kInitialPictureId1 + 2, vp9_header.picture_id);
261 EXPECT_EQ(kInitialTl0PicIdx1 + 1, vp9_header.tl0_pic_idx);
Stefan Holmerf7044682018-07-17 10:16:41 +0200262
263 // OnEncodedImage, first_frame_in_picture = false
264 codec_info.codecSpecific.VP9.first_frame_in_picture = false;
265
philipelbf2b6202018-08-27 14:33:18 +0200266 header = params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
Stefan Holmerf7044682018-07-17 10:16:41 +0200267
268 EXPECT_EQ(kVideoCodecVP9, header.codec);
philipel29d88462018-08-08 14:26:00 +0200269 EXPECT_EQ(kInitialPictureId1 + 2, vp9_header.picture_id);
270 EXPECT_EQ(kInitialTl0PicIdx1 + 1, vp9_header.tl0_pic_idx);
Stefan Holmerf7044682018-07-17 10:16:41 +0200271
272 // State should hold latest used picture id and tl0_pic_idx.
273 EXPECT_EQ(kInitialPictureId1 + 2, params.state().picture_id);
274 EXPECT_EQ(kInitialTl0PicIdx1 + 1, params.state().tl0_pic_idx);
275}
philipelbf2b6202018-08-27 14:33:18 +0200276
277TEST(RtpPayloadParamsTest, PictureIdForOldGenericFormat) {
278 test::ScopedFieldTrials generic_picture_id(
279 "WebRTC-GenericPictureId/Enabled/");
280 RtpPayloadState state{};
281
282 EncodedImage encoded_image;
283 CodecSpecificInfo codec_info{};
284 codec_info.codecType = kVideoCodecGeneric;
285
286 RtpPayloadParams params(kSsrc1, &state);
287 RTPVideoHeader header =
288 params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
289
290 EXPECT_EQ(kVideoCodecGeneric, header.codec);
291 ASSERT_TRUE(header.generic);
292 EXPECT_EQ(0, header.generic->frame_id);
293
294 header = params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
295 ASSERT_TRUE(header.generic);
296 EXPECT_EQ(1, header.generic->frame_id);
297}
298
299class RtpPayloadParamsVp8ToGenericTest : public ::testing::Test {
300 public:
301 enum LayerSync { kNoSync, kSync };
302
philipel569397f2018-09-26 12:25:31 +0200303 RtpPayloadParamsVp8ToGenericTest()
304 : generic_descriptor_field_trial_("WebRTC-GenericDescriptor/Enabled/"),
305 state_(),
306 params_(123, &state_) {}
philipelbf2b6202018-08-27 14:33:18 +0200307
308 void ConvertAndCheck(int temporal_index,
309 int64_t shared_frame_id,
310 FrameType frame_type,
311 LayerSync layer_sync,
312 const std::set<int64_t>& expected_deps) {
313 EncodedImage encoded_image;
314 encoded_image._frameType = frame_type;
315
316 CodecSpecificInfo codec_info{};
317 codec_info.codecType = kVideoCodecVP8;
318 codec_info.codecSpecific.VP8.temporalIdx = temporal_index;
319 codec_info.codecSpecific.VP8.layerSync = layer_sync == kSync;
320
321 RTPVideoHeader header =
322 params_.GetRtpVideoHeader(encoded_image, &codec_info, shared_frame_id);
323
324 ASSERT_TRUE(header.generic);
325 EXPECT_TRUE(header.generic->higher_spatial_layers.empty());
326 EXPECT_EQ(header.generic->spatial_index, 0);
327
328 EXPECT_EQ(header.generic->frame_id, shared_frame_id);
329 EXPECT_EQ(header.generic->temporal_index, temporal_index);
330 std::set<int64_t> actual_deps(header.generic->dependencies.begin(),
331 header.generic->dependencies.end());
332 EXPECT_EQ(expected_deps, actual_deps);
333 }
334
335 protected:
philipel569397f2018-09-26 12:25:31 +0200336 test::ScopedFieldTrials generic_descriptor_field_trial_;
philipelbf2b6202018-08-27 14:33:18 +0200337 RtpPayloadState state_;
338 RtpPayloadParams params_;
339};
340
341TEST_F(RtpPayloadParamsVp8ToGenericTest, Keyframe) {
342 ConvertAndCheck(0, 0, kVideoFrameKey, kNoSync, {});
343 ConvertAndCheck(0, 1, kVideoFrameDelta, kNoSync, {0});
344 ConvertAndCheck(0, 2, kVideoFrameKey, kNoSync, {});
345}
346
347TEST_F(RtpPayloadParamsVp8ToGenericTest, TooHighTemporalIndex) {
348 ConvertAndCheck(0, 0, kVideoFrameKey, kNoSync, {});
349
350 EncodedImage encoded_image;
351 encoded_image._frameType = kVideoFrameDelta;
352 CodecSpecificInfo codec_info{};
353 codec_info.codecType = kVideoCodecVP8;
354 codec_info.codecSpecific.VP8.temporalIdx =
355 RtpGenericFrameDescriptor::kMaxTemporalLayers;
356 codec_info.codecSpecific.VP8.layerSync = false;
357
358 RTPVideoHeader header =
359 params_.GetRtpVideoHeader(encoded_image, &codec_info, 1);
360 EXPECT_FALSE(header.generic);
361}
362
363TEST_F(RtpPayloadParamsVp8ToGenericTest, LayerSync) {
364 // 02120212 pattern
365 ConvertAndCheck(0, 0, kVideoFrameKey, kNoSync, {});
366 ConvertAndCheck(2, 1, kVideoFrameDelta, kNoSync, {0});
367 ConvertAndCheck(1, 2, kVideoFrameDelta, kNoSync, {0});
368 ConvertAndCheck(2, 3, kVideoFrameDelta, kNoSync, {0, 1, 2});
369
370 ConvertAndCheck(0, 4, kVideoFrameDelta, kNoSync, {0});
371 ConvertAndCheck(2, 5, kVideoFrameDelta, kNoSync, {2, 3, 4});
372 ConvertAndCheck(1, 6, kVideoFrameDelta, kSync, {4}); // layer sync
373 ConvertAndCheck(2, 7, kVideoFrameDelta, kNoSync, {4, 5, 6});
374}
375
376TEST_F(RtpPayloadParamsVp8ToGenericTest, FrameIdGaps) {
377 // 0101 pattern
378 ConvertAndCheck(0, 0, kVideoFrameKey, kNoSync, {});
379 ConvertAndCheck(1, 1, kVideoFrameDelta, kNoSync, {0});
380
381 ConvertAndCheck(0, 5, kVideoFrameDelta, kNoSync, {0});
382 ConvertAndCheck(1, 10, kVideoFrameDelta, kNoSync, {1, 5});
383
384 ConvertAndCheck(0, 15, kVideoFrameDelta, kNoSync, {5});
385 ConvertAndCheck(1, 20, kVideoFrameDelta, kNoSync, {10, 15});
386}
387
Stefan Holmerf7044682018-07-17 10:16:41 +0200388} // namespace webrtc