OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. | 2 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 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 | 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 | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
11 #include "webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.h" | 11 #include "webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.h" |
12 | 12 |
13 #include <algorithm> | 13 #include <algorithm> |
14 | 14 |
15 // NOTE(ajm): Path provided by gyp. | 15 // NOTE(ajm): Path provided by gyp. |
16 #include "libyuv/scale.h" // NOLINT | 16 #include "libyuv/scale.h" // NOLINT |
17 | 17 |
18 #include "webrtc/base/checks.h" | 18 #include "webrtc/base/checks.h" |
19 #include "webrtc/modules/video_coding/codecs/vp8/screenshare_layers.h" | 19 #include "webrtc/modules/video_coding/codecs/vp8/screenshare_layers.h" |
20 #include "webrtc/modules/video_coding/utility/simulcast_rate_allocator.h" | |
20 #include "webrtc/system_wrappers/include/clock.h" | 21 #include "webrtc/system_wrappers/include/clock.h" |
21 | 22 |
22 namespace { | 23 namespace { |
23 | 24 |
24 const unsigned int kDefaultMinQp = 2; | 25 const unsigned int kDefaultMinQp = 2; |
25 const unsigned int kDefaultMaxQp = 56; | 26 const unsigned int kDefaultMaxQp = 56; |
26 // Max qp for lowest spatial resolution when doing simulcast. | 27 // Max qp for lowest spatial resolution when doing simulcast. |
27 const unsigned int kLowestResMaxQp = 45; | 28 const unsigned int kLowestResMaxQp = 45; |
28 | 29 |
29 uint32_t SumStreamTargetBitrate(int streams, const webrtc::VideoCodec& codec) { | |
30 uint32_t bitrate_sum = 0; | |
31 for (int i = 0; i < streams; ++i) { | |
32 bitrate_sum += codec.simulcastStream[i].targetBitrate; | |
33 } | |
34 return bitrate_sum; | |
35 } | |
36 | |
37 uint32_t SumStreamMaxBitrate(int streams, const webrtc::VideoCodec& codec) { | 30 uint32_t SumStreamMaxBitrate(int streams, const webrtc::VideoCodec& codec) { |
38 uint32_t bitrate_sum = 0; | 31 uint32_t bitrate_sum = 0; |
39 for (int i = 0; i < streams; ++i) { | 32 for (int i = 0; i < streams; ++i) { |
40 bitrate_sum += codec.simulcastStream[i].maxBitrate; | 33 bitrate_sum += codec.simulcastStream[i].maxBitrate; |
41 } | 34 } |
42 return bitrate_sum; | 35 return bitrate_sum; |
43 } | 36 } |
44 | 37 |
45 int NumberOfStreams(const webrtc::VideoCodec& codec) { | 38 int NumberOfStreams(const webrtc::VideoCodec& codec) { |
46 int streams = | 39 int streams = |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
85 inst->numberOfSimulcastStreams > 1) { | 78 inst->numberOfSimulcastStreams > 1) { |
86 return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; | 79 return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; |
87 } | 80 } |
88 if (inst->codecSpecific.VP8.automaticResizeOn && | 81 if (inst->codecSpecific.VP8.automaticResizeOn && |
89 inst->numberOfSimulcastStreams > 1) { | 82 inst->numberOfSimulcastStreams > 1) { |
90 return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; | 83 return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; |
91 } | 84 } |
92 return WEBRTC_VIDEO_CODEC_OK; | 85 return WEBRTC_VIDEO_CODEC_OK; |
93 } | 86 } |
94 | 87 |
95 // TL1 FrameDropper's max time to drop frames. | |
96 const float kTl1MaxTimeToDropFrames = 20.0f; | |
97 | |
98 struct ScreenshareTemporalLayersFactory : webrtc::TemporalLayersFactory { | 88 struct ScreenshareTemporalLayersFactory : webrtc::TemporalLayersFactory { |
99 ScreenshareTemporalLayersFactory() | 89 ScreenshareTemporalLayersFactory() {} |
100 : tl1_frame_dropper_(kTl1MaxTimeToDropFrames) {} | |
101 | |
102 virtual ~ScreenshareTemporalLayersFactory() {} | 90 virtual ~ScreenshareTemporalLayersFactory() {} |
103 | 91 |
104 virtual webrtc::TemporalLayers* Create(int num_temporal_layers, | 92 virtual webrtc::TemporalLayers* Create(int num_temporal_layers, |
105 uint8_t initial_tl0_pic_idx) const { | 93 uint8_t initial_tl0_pic_idx) const { |
106 return new webrtc::ScreenshareLayers(num_temporal_layers, rand(), | 94 return new webrtc::ScreenshareLayers(num_temporal_layers, rand(), |
107 webrtc::Clock::GetRealTimeClock()); | 95 webrtc::Clock::GetRealTimeClock()); |
108 } | 96 } |
109 | |
110 mutable webrtc::FrameDropper tl0_frame_dropper_; | |
111 mutable webrtc::FrameDropper tl1_frame_dropper_; | |
112 }; | 97 }; |
113 | 98 |
114 // An EncodedImageCallback implementation that forwards on calls to a | 99 // An EncodedImageCallback implementation that forwards on calls to a |
115 // SimulcastEncoderAdapter, but with the stream index it's registered with as | 100 // SimulcastEncoderAdapter, but with the stream index it's registered with as |
116 // the first parameter to Encoded. | 101 // the first parameter to Encoded. |
117 class AdapterEncodedImageCallback : public webrtc::EncodedImageCallback { | 102 class AdapterEncodedImageCallback : public webrtc::EncodedImageCallback { |
118 public: | 103 public: |
119 AdapterEncodedImageCallback(webrtc::SimulcastEncoderAdapter* adapter, | 104 AdapterEncodedImageCallback(webrtc::SimulcastEncoderAdapter* adapter, |
120 size_t stream_idx) | 105 size_t stream_idx) |
121 : adapter_(adapter), stream_idx_(stream_idx) {} | 106 : adapter_(adapter), stream_idx_(stream_idx) {} |
(...skipping 10 matching lines...) Expand all Loading... | |
132 webrtc::SimulcastEncoderAdapter* const adapter_; | 117 webrtc::SimulcastEncoderAdapter* const adapter_; |
133 const size_t stream_idx_; | 118 const size_t stream_idx_; |
134 }; | 119 }; |
135 | 120 |
136 } // namespace | 121 } // namespace |
137 | 122 |
138 namespace webrtc { | 123 namespace webrtc { |
139 | 124 |
140 SimulcastEncoderAdapter::SimulcastEncoderAdapter(VideoEncoderFactory* factory) | 125 SimulcastEncoderAdapter::SimulcastEncoderAdapter(VideoEncoderFactory* factory) |
141 : factory_(factory), | 126 : factory_(factory), |
142 encoded_complete_callback_(NULL), | 127 encoded_complete_callback_(nullptr), |
143 implementation_name_("SimulcastEncoderAdapter") { | 128 implementation_name_("SimulcastEncoderAdapter") { |
144 memset(&codec_, 0, sizeof(webrtc::VideoCodec)); | 129 memset(&codec_, 0, sizeof(webrtc::VideoCodec)); |
130 rate_allocator_.reset(new SimulcastRateAllocator(codec_)); | |
145 } | 131 } |
146 | 132 |
147 SimulcastEncoderAdapter::~SimulcastEncoderAdapter() { | 133 SimulcastEncoderAdapter::~SimulcastEncoderAdapter() { |
148 Release(); | 134 Release(); |
149 } | 135 } |
150 | 136 |
151 int SimulcastEncoderAdapter::Release() { | 137 int SimulcastEncoderAdapter::Release() { |
152 // TODO(pbos): Keep the last encoder instance but call ::Release() on it, then | 138 // TODO(pbos): Keep the last encoder instance but call ::Release() on it, then |
153 // re-use this instance in ::InitEncode(). This means that changing | 139 // re-use this instance in ::InitEncode(). This means that changing |
154 // resolutions doesn't require reallocation of the first encoder, but only | 140 // resolutions doesn't require reallocation of the first encoder, but only |
(...skipping 27 matching lines...) Expand all Loading... | |
182 } | 168 } |
183 | 169 |
184 int number_of_streams = NumberOfStreams(*inst); | 170 int number_of_streams = NumberOfStreams(*inst); |
185 const bool doing_simulcast = (number_of_streams > 1); | 171 const bool doing_simulcast = (number_of_streams > 1); |
186 | 172 |
187 if (doing_simulcast && !ValidSimulcastResolutions(*inst, number_of_streams)) { | 173 if (doing_simulcast && !ValidSimulcastResolutions(*inst, number_of_streams)) { |
188 return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; | 174 return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; |
189 } | 175 } |
190 | 176 |
191 codec_ = *inst; | 177 codec_ = *inst; |
178 rate_allocator_.reset(new SimulcastRateAllocator(codec_)); | |
179 std::vector<uint32_t> start_bitrates = | |
180 rate_allocator_->GetAllocation(codec_.startBitrate); | |
192 | 181 |
193 // Special mode when screensharing on a single stream. | 182 // Special mode when screensharing on a single stream. |
194 if (number_of_streams == 1 && inst->mode == kScreensharing) { | 183 if (number_of_streams == 1 && inst->mode == kScreensharing) { |
195 screensharing_tl_factory_.reset(new ScreenshareTemporalLayersFactory()); | 184 screensharing_tl_factory_.reset(new ScreenshareTemporalLayersFactory()); |
196 codec_.codecSpecific.VP8.tl_factory = screensharing_tl_factory_.get(); | 185 codec_.codecSpecific.VP8.tl_factory = screensharing_tl_factory_.get(); |
197 } | 186 } |
198 | 187 |
199 std::string implementation_name; | 188 std::string implementation_name; |
200 // Create |number_of_streams| of encoder instances and init them. | 189 // Create |number_of_streams| of encoder instances and init them. |
201 for (int i = 0; i < number_of_streams; ++i) { | 190 for (int i = 0; i < number_of_streams; ++i) { |
202 VideoCodec stream_codec; | 191 VideoCodec stream_codec; |
203 bool send_stream = true; | 192 bool send_stream = true; |
204 if (!doing_simulcast) { | 193 if (!doing_simulcast) { |
205 stream_codec = codec_; | 194 stream_codec = codec_; |
206 stream_codec.numberOfSimulcastStreams = 1; | 195 stream_codec.numberOfSimulcastStreams = 1; |
207 } else { | 196 } else { |
208 bool highest_resolution_stream = (i == (number_of_streams - 1)); | 197 bool highest_resolution_stream = (i == (number_of_streams - 1)); |
209 PopulateStreamCodec(&codec_, i, number_of_streams, | 198 uint32_t start_bitrate_kbps = start_bitrates[i]; |
199 if (!start_bitrate_kbps) { | |
noahric
2016/08/29 21:32:09
Prefer == 0 for integral values. Most of libjingle
sprang_webrtc
2016/08/30 13:27:24
Done.
| |
200 // Set max bitrate of previous encoder, but don't send it. | |
201 // We need this in order for the multi-encoder to work. | |
202 start_bitrate_kbps = codec_.simulcastStream[i - 1].maxBitrate; | |
203 } | |
204 PopulateStreamCodec(&codec_, i, start_bitrate_kbps, | |
210 highest_resolution_stream, &stream_codec, | 205 highest_resolution_stream, &stream_codec, |
211 &send_stream); | 206 &send_stream); |
212 } | 207 } |
213 | 208 |
214 // TODO(ronghuawu): Remove once this is handled in VP8EncoderImpl. | 209 // TODO(ronghuawu): Remove once this is handled in VP8EncoderImpl. |
215 if (stream_codec.qpMax < kDefaultMinQp) { | 210 if (stream_codec.qpMax < kDefaultMinQp) { |
216 stream_codec.qpMax = kDefaultMaxQp; | 211 stream_codec.qpMax = kDefaultMaxQp; |
217 } | 212 } |
218 | 213 |
219 VideoEncoder* encoder = factory_->Create(); | 214 VideoEncoder* encoder = factory_->Create(); |
(...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
365 } | 360 } |
366 if (new_bitrate_kbit < codec_.minBitrate) { | 361 if (new_bitrate_kbit < codec_.minBitrate) { |
367 new_bitrate_kbit = codec_.minBitrate; | 362 new_bitrate_kbit = codec_.minBitrate; |
368 } | 363 } |
369 if (codec_.numberOfSimulcastStreams > 0 && | 364 if (codec_.numberOfSimulcastStreams > 0 && |
370 new_bitrate_kbit < codec_.simulcastStream[0].minBitrate) { | 365 new_bitrate_kbit < codec_.simulcastStream[0].minBitrate) { |
371 new_bitrate_kbit = codec_.simulcastStream[0].minBitrate; | 366 new_bitrate_kbit = codec_.simulcastStream[0].minBitrate; |
372 } | 367 } |
373 codec_.maxFramerate = new_framerate; | 368 codec_.maxFramerate = new_framerate; |
374 | 369 |
375 bool send_stream = true; | 370 std::vector<uint32_t> stream_bitrates = |
376 uint32_t stream_bitrate = 0; | 371 rate_allocator_->GetAllocation(new_bitrate_kbit); |
377 for (size_t stream_idx = 0; stream_idx < streaminfos_.size(); ++stream_idx) { | 372 for (size_t stream_idx = 0; stream_idx < streaminfos_.size(); ++stream_idx) { |
378 stream_bitrate = GetStreamBitrate(stream_idx, streaminfos_.size(), | 373 uint32_t stream_bitrate = stream_bitrates[stream_idx]; |
379 new_bitrate_kbit, &send_stream); | 374 bool send_stream = stream_bitrate > 0; |
375 if (!send_stream) { | |
376 // Not enough bitrate for this stream. | |
377 // Return our max bitrate of |stream_idx| - 1, but we don't send it. We | |
378 // need to keep this resolution coding in order for the multi-encoder to | |
379 // work. | |
noahric
2016/08/29 21:32:09
I see this comment goes waaaay back in webrtc (in
sprang_webrtc
2016/08/30 13:27:24
Gotcha, thanks.
vp8_impl.cc already has it's own
noahric
2016/08/30 18:14:33
Yup! By inspection, at least, I don't expect probl
| |
380 stream_bitrate = stream_bitrates[stream_idx - 1]; | |
381 } | |
380 // Need a key frame if we have not sent this stream before. | 382 // Need a key frame if we have not sent this stream before. |
381 if (send_stream && !streaminfos_[stream_idx].send_stream) { | 383 if (send_stream && !streaminfos_[stream_idx].send_stream) { |
382 streaminfos_[stream_idx].key_frame_request = true; | 384 streaminfos_[stream_idx].key_frame_request = true; |
383 } | 385 } |
384 streaminfos_[stream_idx].send_stream = send_stream; | 386 streaminfos_[stream_idx].send_stream = send_stream; |
385 | 387 |
386 // TODO(holmer): This is a temporary hack for screensharing, where we | 388 // TODO(holmer): This is a temporary hack for screensharing, where we |
387 // interpret the startBitrate as the encoder target bitrate. This is | 389 // interpret the startBitrate as the encoder target bitrate. This is |
388 // to allow for a different max bitrate, so if the codec can't meet | 390 // to allow for a different max bitrate, so if the codec can't meet |
389 // the target we still allow it to overshoot up to the max before dropping | 391 // the target we still allow it to overshoot up to the max before dropping |
(...skipping 20 matching lines...) Expand all Loading... | |
410 const CodecSpecificInfo* codecSpecificInfo, | 412 const CodecSpecificInfo* codecSpecificInfo, |
411 const RTPFragmentationHeader* fragmentation) { | 413 const RTPFragmentationHeader* fragmentation) { |
412 CodecSpecificInfo stream_codec_specific = *codecSpecificInfo; | 414 CodecSpecificInfo stream_codec_specific = *codecSpecificInfo; |
413 CodecSpecificInfoVP8* vp8Info = &(stream_codec_specific.codecSpecific.VP8); | 415 CodecSpecificInfoVP8* vp8Info = &(stream_codec_specific.codecSpecific.VP8); |
414 vp8Info->simulcastIdx = stream_idx; | 416 vp8Info->simulcastIdx = stream_idx; |
415 | 417 |
416 return encoded_complete_callback_->OnEncodedImage( | 418 return encoded_complete_callback_->OnEncodedImage( |
417 encodedImage, &stream_codec_specific, fragmentation); | 419 encodedImage, &stream_codec_specific, fragmentation); |
418 } | 420 } |
419 | 421 |
420 uint32_t SimulcastEncoderAdapter::GetStreamBitrate( | |
421 int stream_idx, | |
422 size_t total_number_of_streams, | |
423 uint32_t new_bitrate_kbit, | |
424 bool* send_stream) const { | |
425 if (total_number_of_streams == 1) { | |
426 *send_stream = true; | |
427 return new_bitrate_kbit; | |
428 } | |
429 | |
430 // The bitrate needed to start sending this stream is given by the | |
431 // minimum bitrate allowed for encoding this stream, plus the sum target | |
432 // rates of all lower streams. | |
433 uint32_t sum_target_lower_streams = | |
434 SumStreamTargetBitrate(stream_idx, codec_); | |
435 uint32_t bitrate_to_send_this_layer = | |
436 codec_.simulcastStream[stream_idx].minBitrate + sum_target_lower_streams; | |
437 if (new_bitrate_kbit >= bitrate_to_send_this_layer) { | |
438 // We have enough bandwidth to send this stream. | |
439 *send_stream = true; | |
440 // Bitrate for this stream is the new bitrate (|new_bitrate_kbit|) minus the | |
441 // sum target rates of the lower streams, and capped to a maximum bitrate. | |
442 // The maximum cap depends on whether we send the next higher stream. | |
443 // If we will be sending the next higher stream, |max_rate| is given by | |
444 // current stream's |targetBitrate|, otherwise it's capped by |maxBitrate|. | |
445 if (stream_idx < codec_.numberOfSimulcastStreams - 1) { | |
446 unsigned int max_rate = codec_.simulcastStream[stream_idx].maxBitrate; | |
447 if (new_bitrate_kbit >= | |
448 SumStreamTargetBitrate(stream_idx + 1, codec_) + | |
449 codec_.simulcastStream[stream_idx + 1].minBitrate) { | |
450 max_rate = codec_.simulcastStream[stream_idx].targetBitrate; | |
451 } | |
452 return std::min(new_bitrate_kbit - sum_target_lower_streams, max_rate); | |
453 } else { | |
454 // For the highest stream (highest resolution), the |targetBitRate| and | |
455 // |maxBitrate| are not used. Any excess bitrate (above the targets of | |
456 // all lower streams) is given to this (highest resolution) stream. | |
457 return new_bitrate_kbit - sum_target_lower_streams; | |
458 } | |
459 } else { | |
460 // Not enough bitrate for this stream. | |
461 // Return our max bitrate of |stream_idx| - 1, but we don't send it. We need | |
462 // to keep this resolution coding in order for the multi-encoder to work. | |
463 *send_stream = false; | |
464 return codec_.simulcastStream[stream_idx - 1].maxBitrate; | |
465 } | |
466 } | |
467 | |
468 void SimulcastEncoderAdapter::PopulateStreamCodec( | 422 void SimulcastEncoderAdapter::PopulateStreamCodec( |
469 const webrtc::VideoCodec* inst, | 423 const webrtc::VideoCodec* inst, |
470 int stream_index, | 424 int stream_index, |
471 size_t total_number_of_streams, | 425 uint32_t start_bitrate_kbps, |
472 bool highest_resolution_stream, | 426 bool highest_resolution_stream, |
473 webrtc::VideoCodec* stream_codec, | 427 webrtc::VideoCodec* stream_codec, |
474 bool* send_stream) { | 428 bool* send_stream) { |
475 *stream_codec = *inst; | 429 *stream_codec = *inst; |
476 | 430 |
477 // Stream specific settings. | 431 // Stream specific settings. |
478 stream_codec->codecSpecific.VP8.numberOfTemporalLayers = | 432 stream_codec->codecSpecific.VP8.numberOfTemporalLayers = |
479 inst->simulcastStream[stream_index].numberOfTemporalLayers; | 433 inst->simulcastStream[stream_index].numberOfTemporalLayers; |
480 stream_codec->numberOfSimulcastStreams = 0; | 434 stream_codec->numberOfSimulcastStreams = 0; |
481 stream_codec->width = inst->simulcastStream[stream_index].width; | 435 stream_codec->width = inst->simulcastStream[stream_index].width; |
(...skipping 11 matching lines...) Expand all Loading... | |
493 // kComplexityHigher, which maps to cpu_used = -4. | 447 // kComplexityHigher, which maps to cpu_used = -4. |
494 int pixels_per_frame = stream_codec->width * stream_codec->height; | 448 int pixels_per_frame = stream_codec->width * stream_codec->height; |
495 if (pixels_per_frame < 352 * 288) { | 449 if (pixels_per_frame < 352 * 288) { |
496 stream_codec->codecSpecific.VP8.complexity = webrtc::kComplexityHigher; | 450 stream_codec->codecSpecific.VP8.complexity = webrtc::kComplexityHigher; |
497 } | 451 } |
498 // Turn off denoising for all streams but the highest resolution. | 452 // Turn off denoising for all streams but the highest resolution. |
499 stream_codec->codecSpecific.VP8.denoisingOn = false; | 453 stream_codec->codecSpecific.VP8.denoisingOn = false; |
500 } | 454 } |
501 // TODO(ronghuawu): what to do with targetBitrate. | 455 // TODO(ronghuawu): what to do with targetBitrate. |
502 | 456 |
503 int stream_bitrate = GetStreamBitrate(stream_index, total_number_of_streams, | 457 stream_codec->startBitrate = start_bitrate_kbps; |
504 inst->startBitrate, send_stream); | |
505 stream_codec->startBitrate = stream_bitrate; | |
506 } | 458 } |
507 | 459 |
508 bool SimulcastEncoderAdapter::Initialized() const { | 460 bool SimulcastEncoderAdapter::Initialized() const { |
509 return !streaminfos_.empty(); | 461 return !streaminfos_.empty(); |
510 } | 462 } |
511 | 463 |
512 void SimulcastEncoderAdapter::OnDroppedFrame() { | 464 void SimulcastEncoderAdapter::OnDroppedFrame() { |
513 streaminfos_[0].encoder->OnDroppedFrame(); | 465 streaminfos_[0].encoder->OnDroppedFrame(); |
514 } | 466 } |
515 | 467 |
516 bool SimulcastEncoderAdapter::SupportsNativeHandle() const { | 468 bool SimulcastEncoderAdapter::SupportsNativeHandle() const { |
517 // We should not be calling this method before streaminfos_ are configured. | 469 // We should not be calling this method before streaminfos_ are configured. |
518 RTC_DCHECK(!streaminfos_.empty()); | 470 RTC_DCHECK(!streaminfos_.empty()); |
519 for (const auto& streaminfo : streaminfos_) { | 471 for (const auto& streaminfo : streaminfos_) { |
520 if (!streaminfo.encoder->SupportsNativeHandle()) | 472 if (!streaminfo.encoder->SupportsNativeHandle()) |
521 return false; | 473 return false; |
522 } | 474 } |
523 return true; | 475 return true; |
524 } | 476 } |
525 | 477 |
526 const char* SimulcastEncoderAdapter::ImplementationName() const { | 478 const char* SimulcastEncoderAdapter::ImplementationName() const { |
527 return implementation_name_.c_str(); | 479 return implementation_name_.c_str(); |
528 } | 480 } |
529 | 481 |
530 } // namespace webrtc | 482 } // namespace webrtc |
OLD | NEW |