blob: 86ae8475ce796827785d01ac9d1bdd7355f4e74c [file] [log] [blame]
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +00001/*
2 * Copyright (c) 2012 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// This is the implementation of the PacketBuffer class. It is mostly based on
12// an STL list. The list is kept sorted at all times so that the next packet to
13// decode is at the beginning of the list.
14
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020015#include "modules/audio_coding/neteq/packet_buffer.h"
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +000016
Yves Gerey988cc082018-10-23 12:03:01 +020017#include <algorithm>
18#include <list>
19#include <memory>
20#include <type_traits>
21#include <utility>
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +000022
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020023#include "api/audio_codecs/audio_decoder.h"
Ivo Creusen3ce44a32019-10-31 14:38:11 +010024#include "api/neteq/tick_timer.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020025#include "modules/audio_coding/neteq/decoder_database.h"
26#include "modules/audio_coding/neteq/statistics_calculator.h"
Yves Gerey988cc082018-10-23 12:03:01 +020027#include "rtc_base/checks.h"
Ivo Creusen7b463c52020-11-25 11:32:40 +010028#include "rtc_base/experiments/struct_parameters_parser.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020029#include "rtc_base/logging.h"
Jakob Ivarsson46dda832019-07-03 16:00:30 +020030#include "rtc_base/numerics/safe_conversions.h"
Ivo Creusen7b463c52020-11-25 11:32:40 +010031#include "system_wrappers/include/field_trial.h"
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +000032
33namespace webrtc {
henrik.lundin067d8552016-09-01 23:19:05 -070034namespace {
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +000035// Predicate used when inserting packets in the buffer list.
36// Operator() returns true when |packet| goes before |new_packet|.
37class NewTimestampIsLarger {
38 public:
ossua73f6c92016-10-24 08:25:28 -070039 explicit NewTimestampIsLarger(const Packet& new_packet)
Yves Gerey665174f2018-06-19 15:03:05 +020040 : new_packet_(new_packet) {}
41 bool operator()(const Packet& packet) { return (new_packet_ >= packet); }
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +000042
43 private:
ossua73f6c92016-10-24 08:25:28 -070044 const Packet& new_packet_;
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +000045};
46
henrik.lundin067d8552016-09-01 23:19:05 -070047// Returns true if both payload types are known to the decoder database, and
48// have the same sample rate.
49bool EqualSampleRates(uint8_t pt1,
50 uint8_t pt2,
51 const DecoderDatabase& decoder_database) {
kjellander7c856582017-02-26 19:53:40 -080052 auto* di1 = decoder_database.GetDecoderInfo(pt1);
53 auto* di2 = decoder_database.GetDecoderInfo(pt2);
henrik.lundin067d8552016-09-01 23:19:05 -070054 return di1 && di2 && di1->SampleRateHz() == di2->SampleRateHz();
55}
minyue-webrtc0c3ca752017-08-23 15:59:38 +020056
57void LogPacketDiscarded(int codec_level, StatisticsCalculator* stats) {
58 RTC_CHECK(stats);
59 if (codec_level > 0) {
60 stats->SecondaryPacketsDiscarded(1);
61 } else {
62 stats->PacketsDiscarded(1);
63 }
64}
65
Ivo Creusen7b463c52020-11-25 11:32:40 +010066absl::optional<SmartFlushingConfig> GetSmartflushingConfig() {
67 absl::optional<SmartFlushingConfig> result;
68 std::string field_trial_string =
69 field_trial::FindFullName("WebRTC-Audio-NetEqSmartFlushing");
70 result = SmartFlushingConfig();
71 bool enabled = false;
72 auto parser = StructParametersParser::Create(
73 "enabled", &enabled, "target_level_threshold_ms",
74 &result->target_level_threshold_ms, "target_level_multiplier",
75 &result->target_level_multiplier);
76 parser->Parse(field_trial_string);
77 if (!enabled) {
78 return absl::nullopt;
79 }
80 RTC_LOG(LS_INFO) << "Using smart flushing, target_level_threshold_ms: "
81 << result->target_level_threshold_ms
82 << ", target_level_multiplier: "
83 << result->target_level_multiplier;
84 return result;
85}
86
henrik.lundin067d8552016-09-01 23:19:05 -070087} // namespace
88
henrik.lundin84f8cd62016-04-26 07:45:16 -070089PacketBuffer::PacketBuffer(size_t max_number_of_packets,
90 const TickTimer* tick_timer)
Ivo Creusen7b463c52020-11-25 11:32:40 +010091 : smart_flushing_config_(GetSmartflushingConfig()),
92 max_number_of_packets_(max_number_of_packets),
93 tick_timer_(tick_timer) {}
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +000094
95// Destructor. All packets in the buffer will be destroyed.
96PacketBuffer::~PacketBuffer() {
Ivo Creusen7b463c52020-11-25 11:32:40 +010097 buffer_.clear();
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +000098}
99
100// Flush the buffer. All packets in the buffer will be destroyed.
Ivo Creusen7b463c52020-11-25 11:32:40 +0100101void PacketBuffer::Flush(StatisticsCalculator* stats) {
102 for (auto& p : buffer_) {
103 LogPacketDiscarded(p.priority.codec_level, stats);
104 }
ossua73f6c92016-10-24 08:25:28 -0700105 buffer_.clear();
Ivo Creusen7b463c52020-11-25 11:32:40 +0100106 stats->FlushedPacketBuffer();
107}
108
109void PacketBuffer::PartialFlush(int target_level_ms,
110 size_t sample_rate,
111 size_t last_decoded_length,
112 StatisticsCalculator* stats) {
113 // Make sure that at least half the packet buffer capacity will be available
114 // after the flush. This is done to avoid getting stuck if the target level is
115 // very high.
116 int target_level_samples =
117 std::min(target_level_ms * sample_rate / 1000,
118 max_number_of_packets_ * last_decoded_length / 2);
119 // We should avoid flushing to very low levels.
120 target_level_samples = std::max(
121 target_level_samples, smart_flushing_config_->target_level_threshold_ms);
122 while (GetSpanSamples(last_decoded_length, sample_rate, true) >
123 static_cast<size_t>(target_level_samples) ||
124 buffer_.size() > max_number_of_packets_ / 2) {
125 LogPacketDiscarded(PeekNextPacket()->priority.codec_level, stats);
126 buffer_.pop_front();
127 }
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000128}
129
Karl Wiberg7f6c4d42015-04-09 15:44:22 +0200130bool PacketBuffer::Empty() const {
131 return buffer_.empty();
132}
133
Ivo Creusen7b463c52020-11-25 11:32:40 +0100134int PacketBuffer::InsertPacket(Packet&& packet,
135 StatisticsCalculator* stats,
136 size_t last_decoded_length,
137 size_t sample_rate,
138 int target_level_ms,
139 const DecoderDatabase& decoder_database) {
ossua73f6c92016-10-24 08:25:28 -0700140 if (packet.empty()) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100141 RTC_LOG(LS_WARNING) << "InsertPacket invalid packet";
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000142 return kInvalidPacket;
143 }
144
ossua73f6c92016-10-24 08:25:28 -0700145 RTC_DCHECK_GE(packet.priority.codec_level, 0);
146 RTC_DCHECK_GE(packet.priority.red_level, 0);
ossua70695a2016-09-22 02:06:28 -0700147
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000148 int return_val = kOK;
149
ossua73f6c92016-10-24 08:25:28 -0700150 packet.waiting_time = tick_timer_->GetNewStopwatch();
henrik.lundin84f8cd62016-04-26 07:45:16 -0700151
Ivo Creusen7b463c52020-11-25 11:32:40 +0100152 // Perform a smart flush if the buffer size exceeds a multiple of the target
153 // level.
154 const size_t span_threshold =
155 smart_flushing_config_
156 ? smart_flushing_config_->target_level_multiplier *
157 std::max(smart_flushing_config_->target_level_threshold_ms,
158 target_level_ms) *
159 sample_rate / 1000
160 : 0;
161 const bool smart_flush =
162 smart_flushing_config_.has_value() &&
163 GetSpanSamples(last_decoded_length, sample_rate, true) >= span_threshold;
164 if (buffer_.size() >= max_number_of_packets_ || smart_flush) {
165 size_t buffer_size_before_flush = buffer_.size();
166 if (smart_flushing_config_.has_value()) {
167 // Flush down to the target level.
168 PartialFlush(target_level_ms, sample_rate, last_decoded_length, stats);
169 return_val = kPartialFlush;
170 } else {
171 // Buffer is full.
172 Flush(stats);
173 return_val = kFlushed;
174 }
175 RTC_LOG(LS_WARNING) << "Packet buffer flushed, "
176 << (buffer_size_before_flush - buffer_.size())
177 << " packets discarded.";
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000178 }
179
180 // Get an iterator pointing to the place in the buffer where the new packet
181 // should be inserted. The list is searched from the back, since the most
182 // likely case is that the new packet should be near the end of the list.
183 PacketList::reverse_iterator rit = std::find_if(
Yves Gerey665174f2018-06-19 15:03:05 +0200184 buffer_.rbegin(), buffer_.rend(), NewTimestampIsLarger(packet));
minyue@webrtc.orgc8039072014-10-09 10:49:54 +0000185
186 // The new packet is to be inserted to the right of |rit|. If it has the same
187 // timestamp as |rit|, which has a higher priority, do not insert the new
188 // packet to list.
ossua73f6c92016-10-24 08:25:28 -0700189 if (rit != buffer_.rend() && packet.timestamp == rit->timestamp) {
minyue-webrtc0c3ca752017-08-23 15:59:38 +0200190 LogPacketDiscarded(packet.priority.codec_level, stats);
minyue@webrtc.orgc8039072014-10-09 10:49:54 +0000191 return return_val;
192 }
193
194 // The new packet is to be inserted to the left of |it|. If it has the same
195 // timestamp as |it|, which has a lower priority, replace |it| with the new
196 // packet.
197 PacketList::iterator it = rit.base();
ossua73f6c92016-10-24 08:25:28 -0700198 if (it != buffer_.end() && packet.timestamp == it->timestamp) {
Peng Yub90e63c62018-06-07 19:20:55 +0800199 LogPacketDiscarded(it->priority.codec_level, stats);
minyue@webrtc.orgc8039072014-10-09 10:49:54 +0000200 it = buffer_.erase(it);
201 }
ossua73f6c92016-10-24 08:25:28 -0700202 buffer_.insert(it, std::move(packet)); // Insert the packet at that position.
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000203
204 return return_val;
205}
206
henrik.lundinda8bbf62016-08-31 03:14:11 -0700207int PacketBuffer::InsertPacketList(
208 PacketList* packet_list,
209 const DecoderDatabase& decoder_database,
Danil Chapovalovb6021232018-06-19 13:26:36 +0200210 absl::optional<uint8_t>* current_rtp_payload_type,
211 absl::optional<uint8_t>* current_cng_rtp_payload_type,
Ivo Creusen7b463c52020-11-25 11:32:40 +0100212 StatisticsCalculator* stats,
213 size_t last_decoded_length,
214 size_t sample_rate,
215 int target_level_ms) {
minyue-webrtc12d30842017-07-19 11:44:06 +0200216 RTC_DCHECK(stats);
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000217 bool flushed = false;
ossua73f6c92016-10-24 08:25:28 -0700218 for (auto& packet : *packet_list) {
219 if (decoder_database.IsComfortNoise(packet.payload_type)) {
henrik.lundinda8bbf62016-08-31 03:14:11 -0700220 if (*current_cng_rtp_payload_type &&
ossua73f6c92016-10-24 08:25:28 -0700221 **current_cng_rtp_payload_type != packet.payload_type) {
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000222 // New CNG payload type implies new codec type.
Danil Chapovalovb6021232018-06-19 13:26:36 +0200223 *current_rtp_payload_type = absl::nullopt;
Ivo Creusen7b463c52020-11-25 11:32:40 +0100224 Flush(stats);
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000225 flushed = true;
226 }
Oskar Sundbom12ab00b2017-11-16 15:31:38 +0100227 *current_cng_rtp_payload_type = packet.payload_type;
ossua73f6c92016-10-24 08:25:28 -0700228 } else if (!decoder_database.IsDtmf(packet.payload_type)) {
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000229 // This must be speech.
henrik.lundin067d8552016-09-01 23:19:05 -0700230 if ((*current_rtp_payload_type &&
ossua73f6c92016-10-24 08:25:28 -0700231 **current_rtp_payload_type != packet.payload_type) ||
henrik.lundin067d8552016-09-01 23:19:05 -0700232 (*current_cng_rtp_payload_type &&
ossua73f6c92016-10-24 08:25:28 -0700233 !EqualSampleRates(packet.payload_type,
henrik.lundin067d8552016-09-01 23:19:05 -0700234 **current_cng_rtp_payload_type,
235 decoder_database))) {
Danil Chapovalovb6021232018-06-19 13:26:36 +0200236 *current_cng_rtp_payload_type = absl::nullopt;
Ivo Creusen7b463c52020-11-25 11:32:40 +0100237 Flush(stats);
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000238 flushed = true;
239 }
Oskar Sundbom12ab00b2017-11-16 15:31:38 +0100240 *current_rtp_payload_type = packet.payload_type;
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000241 }
Ivo Creusen7b463c52020-11-25 11:32:40 +0100242 int return_val =
243 InsertPacket(std::move(packet), stats, last_decoded_length, sample_rate,
244 target_level_ms, decoder_database);
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000245 if (return_val == kFlushed) {
246 // The buffer flushed, but this is not an error. We can still continue.
247 flushed = true;
248 } else if (return_val != kOK) {
249 // An error occurred. Delete remaining packets in list and return.
ossua73f6c92016-10-24 08:25:28 -0700250 packet_list->clear();
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000251 return return_val;
252 }
253 }
ossua73f6c92016-10-24 08:25:28 -0700254 packet_list->clear();
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000255 return flushed ? kFlushed : kOK;
256}
257
258int PacketBuffer::NextTimestamp(uint32_t* next_timestamp) const {
259 if (Empty()) {
260 return kBufferEmpty;
261 }
262 if (!next_timestamp) {
263 return kInvalidPointer;
264 }
ossua73f6c92016-10-24 08:25:28 -0700265 *next_timestamp = buffer_.front().timestamp;
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000266 return kOK;
267}
268
269int PacketBuffer::NextHigherTimestamp(uint32_t timestamp,
270 uint32_t* next_timestamp) const {
271 if (Empty()) {
272 return kBufferEmpty;
273 }
274 if (!next_timestamp) {
275 return kInvalidPointer;
276 }
277 PacketList::const_iterator it;
278 for (it = buffer_.begin(); it != buffer_.end(); ++it) {
ossua73f6c92016-10-24 08:25:28 -0700279 if (it->timestamp >= timestamp) {
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000280 // Found a packet matching the search.
ossua73f6c92016-10-24 08:25:28 -0700281 *next_timestamp = it->timestamp;
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000282 return kOK;
283 }
284 }
285 return kNotFound;
286}
287
ossu7a377612016-10-18 04:06:13 -0700288const Packet* PacketBuffer::PeekNextPacket() const {
ossua73f6c92016-10-24 08:25:28 -0700289 return buffer_.empty() ? nullptr : &buffer_.front();
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000290}
291
Danil Chapovalovb6021232018-06-19 13:26:36 +0200292absl::optional<Packet> PacketBuffer::GetNextPacket() {
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000293 if (Empty()) {
294 // Buffer is empty.
Danil Chapovalovb6021232018-06-19 13:26:36 +0200295 return absl::nullopt;
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000296 }
297
Danil Chapovalovb6021232018-06-19 13:26:36 +0200298 absl::optional<Packet> packet(std::move(buffer_.front()));
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000299 // Assert that the packet sanity checks in InsertPacket method works.
ossua73f6c92016-10-24 08:25:28 -0700300 RTC_DCHECK(!packet->empty());
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000301 buffer_.pop_front();
minyue@webrtc.orgc8039072014-10-09 10:49:54 +0000302
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000303 return packet;
304}
305
minyue-webrtcfae474c2017-07-05 11:17:40 +0200306int PacketBuffer::DiscardNextPacket(StatisticsCalculator* stats) {
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000307 if (Empty()) {
308 return kBufferEmpty;
309 }
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000310 // Assert that the packet sanity checks in InsertPacket method works.
minyue-webrtc0c3ca752017-08-23 15:59:38 +0200311 const Packet& packet = buffer_.front();
312 RTC_DCHECK(!packet.empty());
313 LogPacketDiscarded(packet.priority.codec_level, stats);
ossua73f6c92016-10-24 08:25:28 -0700314 buffer_.pop_front();
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000315 return kOK;
316}
317
minyue-webrtcfae474c2017-07-05 11:17:40 +0200318void PacketBuffer::DiscardOldPackets(uint32_t timestamp_limit,
319 uint32_t horizon_samples,
320 StatisticsCalculator* stats) {
minyue-webrtc0c3ca752017-08-23 15:59:38 +0200321 buffer_.remove_if([timestamp_limit, horizon_samples, stats](const Packet& p) {
322 if (timestamp_limit == p.timestamp ||
323 !IsObsoleteTimestamp(p.timestamp, timestamp_limit, horizon_samples)) {
324 return false;
325 }
326 LogPacketDiscarded(p.priority.codec_level, stats);
327 return true;
henrik.lundin63d146b2017-07-05 07:03:34 -0700328 });
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000329}
330
minyue-webrtcfae474c2017-07-05 11:17:40 +0200331void PacketBuffer::DiscardAllOldPackets(uint32_t timestamp_limit,
332 StatisticsCalculator* stats) {
333 DiscardOldPackets(timestamp_limit, 0, stats);
Karl Wiberg7f6c4d42015-04-09 15:44:22 +0200334}
335
minyue-webrtcfae474c2017-07-05 11:17:40 +0200336void PacketBuffer::DiscardPacketsWithPayloadType(uint8_t payload_type,
337 StatisticsCalculator* stats) {
minyue-webrtc0c3ca752017-08-23 15:59:38 +0200338 buffer_.remove_if([payload_type, stats](const Packet& p) {
339 if (p.payload_type != payload_type) {
340 return false;
ossu61a208b2016-09-20 01:38:00 -0700341 }
minyue-webrtc0c3ca752017-08-23 15:59:38 +0200342 LogPacketDiscarded(p.priority.codec_level, stats);
343 return true;
344 });
ossu61a208b2016-09-20 01:38:00 -0700345}
346
Peter Kastingdce40cf2015-08-24 14:52:23 -0700347size_t PacketBuffer::NumPacketsInBuffer() const {
348 return buffer_.size();
Karl Wiberg7f6c4d42015-04-09 15:44:22 +0200349}
350
ossu61a208b2016-09-20 01:38:00 -0700351size_t PacketBuffer::NumSamplesInBuffer(size_t last_decoded_length) const {
Peter Kastingdce40cf2015-08-24 14:52:23 -0700352 size_t num_samples = 0;
353 size_t last_duration = last_decoded_length;
ossua73f6c92016-10-24 08:25:28 -0700354 for (const Packet& packet : buffer_) {
355 if (packet.frame) {
ossua70695a2016-09-22 02:06:28 -0700356 // TODO(hlundin): Verify that it's fine to count all packets and remove
357 // this check.
ossua73f6c92016-10-24 08:25:28 -0700358 if (packet.priority != Packet::Priority(0, 0)) {
minyue@webrtc.org0aa3ee62014-05-28 07:48:01 +0000359 continue;
minyue@webrtc.orgb28bfa72014-03-21 12:07:40 +0000360 }
ossua73f6c92016-10-24 08:25:28 -0700361 size_t duration = packet.frame->Duration();
ossu61a208b2016-09-20 01:38:00 -0700362 if (duration > 0) {
turaj@webrtc.org7b75ac62013-09-26 00:27:56 +0000363 last_duration = duration; // Save the most up-to-date (valid) duration.
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000364 }
365 }
turaj@webrtc.org7b75ac62013-09-26 00:27:56 +0000366 num_samples += last_duration;
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000367 }
368 return num_samples;
369}
370
Jakob Ivarsson46dda832019-07-03 16:00:30 +0200371size_t PacketBuffer::GetSpanSamples(size_t last_decoded_length,
372 size_t sample_rate,
373 bool count_dtx_waiting_time) const {
Jakob Ivarsson1b4254a2019-03-12 15:12:08 +0100374 if (buffer_.size() == 0) {
375 return 0;
376 }
377
378 size_t span = buffer_.back().timestamp - buffer_.front().timestamp;
379 if (buffer_.back().frame && buffer_.back().frame->Duration() > 0) {
Jakob Ivarsson46dda832019-07-03 16:00:30 +0200380 size_t duration = buffer_.back().frame->Duration();
381 if (count_dtx_waiting_time && buffer_.back().frame->IsDtxPacket()) {
382 size_t waiting_time_samples = rtc::dchecked_cast<size_t>(
383 buffer_.back().waiting_time->ElapsedMs() * (sample_rate / 1000));
384 duration = std::max(duration, waiting_time_samples);
385 }
386 span += duration;
Jakob Ivarsson1b4254a2019-03-12 15:12:08 +0100387 } else {
388 span += last_decoded_length;
389 }
390 return span;
391}
392
Ivo Creusenc7f09ad2018-05-22 13:21:01 +0200393bool PacketBuffer::ContainsDtxOrCngPacket(
394 const DecoderDatabase* decoder_database) const {
395 RTC_DCHECK(decoder_database);
396 for (const Packet& packet : buffer_) {
397 if ((packet.frame && packet.frame->IsDtxPacket()) ||
398 decoder_database->IsComfortNoise(packet.payload_type)) {
399 return true;
400 }
401 }
402 return false;
403}
404
henrik.lundin@webrtc.orgd94659d2013-01-29 12:09:21 +0000405} // namespace webrtc